Go 中的 init 如何用?它的常见应用场景有哪些呢?

嗨,大家好!我是波罗学。本文是系列文章 Go 技巧第十六篇,系列文章查看:Go 语言技巧。

Go 中有一个特别的 init() 函数,它主要用于包的初始化。init() 函数在包被引入后会被自动执行。如果在 main 包中,它也会在 main() 函数之前执行。

本文将以此为主题,介绍 Go 中 init() 函数的使用和常见使用场景。还有,我在工作中更多看到的是 init() 函数的滥用。

init() 函数的执行时机

首先,init() 的执行时机处于包级别变量声明和 main() 函数执行之间。

这意味着在包中声明的全局变量,如果附带初始化表达式,这些表达式将在任何 init() 函数执行之前进行初始化。

我们通过一个示例演示,代码如下:

var Age = GetAge()func GetAge() int {return 18
}func init() {fmt.Printf("You're %d years old.\n", Age)Age = 3
}func main() {fmt.Printf("You're %d years old.\n", Age)
}

输出:

You're 18 years old
You're 3 years old

从输出可知,GetAge() 函数作为 Age 的初始化函数,于 init() 函数前执行,赋值 Age3。而 init() 函数于其后执行,赋值 Age3main() 函数则在最后执行,输出最终的 Age 值。

这个顺序是符合我们预期的。

与被引入包的 init() 函数

如果一个包导入了其他包,被导入包的初始化 init() 则会先于导入它的包的变量初始化和 init 函数前执行。

举例来说明吧!

假设,我们有一个 main 包,它导入了 sub 包,并且同样有一个 init() 函数:

// main.gopackage mainimport ("fmt"_ "demo/sub"
)var age = GetAge()func GetAge() int {fmt.Println("main initialize variables.")return 18
}func init() {fmt.Println("main package init")
}func main() {fmt.Println("main function")
}

sub 包中包含定义的 init() 函数

// sub/sub.gopackage subimport "fmt"var age = GetAge()func GetAge() int {fmt.Println("sub initialize variables.")return 18
}func init() {fmt.Println("sub package init")
}// 其他可能的函数和声明

当你运行 main.go 时,输出将会按照以下顺序出现:

sub initialize variables.
sub package init
main initialize variables.
main package init
main function

这个示例清晰地展示了包的初始化顺序:首先是被导入包(sub)的 init() 函数,然后是导入它的包(main)的 init() 函数,最后是 main 函数。

这也确保了依赖包在使用前已经被正确初始化。

特别说明:

init() 区别于其他函数,不需要我们显式调用,它会自动被 Go runtime 调用。而且,每个包中的 init() 只会被执行一次。

一个包其实可有多个 init(),无论是在分部在包中的同一个文件中还是多个文件中。如果分布在多个文件中,执行顺序通常是按照文件名的字典顺序。

为说明这个问题,我们首先修改 sub.go 文件,内容如下:

// sub/sub.gopackage subimport "fmt"var age = GetAge()func GetAge() int {fmt.Println("sub initialize variables.")return 18
}func init() {fmt.Println("sub init 1")
}func init() {fmt.Println("sub init 2")
}

新增一个 sub1.go 文件,如下所示:

// sub/sub1.gopackage subimport "fmt"var age = GetAge1()func GetAge1() int {fmt.Println("sub1 initialize variables.")return 18
}func init() {fmt.Println("sub1 init")
}

输出:

sub initialize variables.
sub init 1
sub init 2
sub1 initialize variables.
sub1 init
main initialize variables.
main package init
main function

结果符合预期。

init() 的使用场景

init() 函数通常用于进行一些必要的设置或初始化操作,例如初始化包级别的变量与命令行参数、配置加载、环境检查、甚至注册插件等。

请添加图片描述

项目开发中,组件依赖管理通常比较令人头疼。但一些简单的依赖关系,即使没有如 wire 这样依赖注入工具的加持,通过 init 也可管理。

命令行参数

对于开发一个简单的命令行应用,init() 和标准库 flag 包结合,可快速完成命令命令行参数的初始化。

package mainimport ("flag""fmt"
)var name string
var help boolfunc init() {flag.StringVar(&name, "name", "World", "a name to say hello to")flag.StringVar(&help, "name", "World", "display help information")flag.Parse()
}func main() {if help {fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])flag.PrintDefaults()os.Exit(1)} fmt.Printf("Hello, %s!\n", name)
}

以上示例中,init() 函数解析了命令行参数并初始化变量 namehelp 变量。

配置加载

init 函数的领哇一个常见场景是配置加载。配置通常是程序启动时要尽早执行的操作。

例如,你有一个 web 服务,要在启动服务器前加载数据库配置、API 密钥或其他服务配置。

var config AppConfigfunc init() {configFile, err := os.Open("config.json")if err != nil {log.Fatal(err)}defer configFile.Close()jsonParser := json.NewDecoder(configFile)jsonParser.Decode(&config)
}

如果配置加载都出现问题,很大程度说明服务配置不正常,要立刻退出服务。我们可使用 log.Fatal(err) (更优雅)或 panic(err) 退出服务。

环境检查

init() 还可以用于检查和验证程序运行所需的环境。如,我们要确保必要的环境变量已设置,或者必要的外部服务可用。

如我们的必须依赖一个需要认证的外部服务,示例代码:

func init() {if os.Getenv("XXX_API_KEY") == "" {log.Fatal("XXX_API_KEY environment variable not set")}apiKey := os.Getenv("XXX_API_KEY")// instantiating Component// ...
}

通过,如果要实例化的组件不需要赖加载,创建和配置验证同时 init() 中完成即可。

注册插件或服务

如果你的程序用的是插件架构,我们可以在程序启动时注册这些插件。init() 正可以用来自动注册这些插件。

示例代码:

func init() {plugin.Register("myPlugin", NewMyPlugin)
}

Go 的数据库驱动管理可作为这种场景的典型案例。

Go 的 database 操作通常依赖 database/sql 包,它提供了一种通用接口与 SQL 或类 SQL 数据库交互。而具体的驱动实现(如 MySQL、PostgreSQL、SQLite 等)通常是通过实现 database/sql 包定义接口来提供支持。

这种架构下,init() 被用于驱动的自动注册。

例如,如下这个 MySQL 驱动的实现:

package mysqlimport ("database/sql"
)func init() {sql.Register("mysql", &MySQLDriver{})
}type MySQLDriver struct {// 驱动的实现
}

我们只要导入这个 database driver 包,它的 init() 就会被调用,将驱动注册到 database/sql 包中。

我们使用的时候,通过 database/sql 接口即可使用该 MySQL 驱动,而不需关心它的实现细节。

import ("database/sql"_ "github.com/go-sql-driver/mysql" // 导入 MySQL 驱动
)func main() {db, err := sql.Open("mysql", "user:password@/dbname")// ...
}

通过这种方式,Go 的数据库驱动代码更加模块化和灵活性。使用方只需关心与 database/sql 交互即可,而不必关心驱动的实现细节。

实际的场景案例,我觉得肯定不止这么多。对于任何需要提前初始化和验证的场景,可适当考虑是否可通过使用 init() 来简化代码。

注意点

讲了那么多 init() 的使用,但我在平时发现,更多的时候 init() 函数是在被滥用。

我这里不得不提一些注意点。

请添加图片描述

启动耗时

首先,由于 init() 函数在程序启动时自动执行,这就导致它会增加程序启动时间,特别是一些组件初始化耗时较长。

非必要场景,懒加载依然是不错的选择。

什么是必要场景呢?简单来说,如果这个操作失败了,这个程序就没有继续启动的必要了。

依赖关系

还有,过多或过于复杂的 init() 函数可能会导致程序难以理解维护,依赖关系混乱。

这点在单体项目中体现的特别明显,所有人维护一个项目,所以依赖都加载到 init() 中。

如何解决呢?

如前面所有,一方面要仅在必要场景时使用 init() 函数初始化一些操作。

另外,有条件的话,建议尽量保持服务简单,如果依赖过多,如出现要一个服务连接多个相同组件(数据库、Redis),就是时候考虑优化系统设计了,可考虑将部分业务抽离为独立服务。

总结

本文介绍了到 init() 函数在 Go 中的特殊之处和使用方式。它提供了一种不同于其他语言的机制来初始化包,但也需谨慎使用以避免不必要的复杂性。

最后,希望这篇文章能帮助你更好地理解和使用 Go 的 init() 函数。

感谢阅读。

博客地址:Go 中的 init 如何用?它的常见应用场景有哪些呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/696853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT基本组件

四、基本组件 Designer 设计师(重点) Qt包含了一个Designer程序,用于通过可视化界面设计开发界面,保存文件格式为.ui(界面文件)。界面文件内部使用xml语法的标签式语言。 在Qt Creator中创建文件时&#xf…

滚雪球学Java(67):深入理解 TreeMap:Java 中的有序键值映射表

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好…

机器人内部传感器阅读笔记及心得-位置传感器-旋转变压器、激光干涉式编码器

旋转变压器 旋转变压器是一种输出电压随转角变化的检测装置,是用来检测角位移的,其基本结构与交流绕线式异步电动机相似,由定子和转子组成。 旋转变压器的原理如图1所示,定子相当于变压器的一次侧,有两组在空间位置上…

MyBatis-Plus 优雅实现数据加密存储

文章目录 前言一、数据库字段加解密实现1. 定义加密类型枚举2. 定义AES密钥和偏移量3. 配置定义使用的加密类型4. 加密解密接口5. 解密解密异常类6. 加密解密实现类6.1 AES加密解密实现类6.2 Base64加密解密实现类 7. 实现数据库的字段保存加密与查询解密处理类8. MybatisPlus配…

Selenium安装与配置

文章目录 一、selenium安装1. Python环境准备:2. 安装Selenium:3. 浏览器驱动安装:4. 验证安装: 二、常见问题1. Selenium版本与浏览器驱动程序不兼容:2. 浏览器驱动程序路径未正确设置: Selenium是一个用于…

2024年1月手机市场行业分析:苹果手机份额骤降,国产高端手机成功逆袭!

小米Ultra发布。 一方面,我们有望看到国产手机再一次超越自己的决心,继续创新追逐高端;另一方面,我们也不得不正视目前手机市场所面临的危机状态。 2024年1月的线上手机市场远不如去年。根据鲸参谋数据显示,今年1月京…

Java面试题之分布式/微服务篇

经济依旧不景气啊,如此大环境下Java还是这么卷,又是一年一次的金三银四。 兄弟们,你准备好了吗?冲冲冲!欧里给! 分布式/微服务相关面试题解 题一:CAP理论,BASE理论题二:…

【2024软件测试面试必会技能】Postman(1): postman的介绍和安装

Postman的介绍 Postman 是一款谷歌开发的接口测试工具,使API的调试与测试更加便捷。 它提供功能强大的 Web API & HTTP 请求调试。它能够发送任何类型的HTTP 请求 (GET, HEAD, POST, PUT..),附带任何数量的参数 headers。 postman是一款支持http协议的接口调试…

springboot整合mybatisPlus超级详细

springboot整合mybatis-plus超级详细 一、环境二、springboot整合myBatisPlus2.1新建2.2 添加Mybatis-plus和mysql依赖2.3 修改配置文件2.4 新建包和文件2.5 新建表2.6 创建实体类2.7 创建Mapper接口2.8 创建Service接口2.9 创建Service实现类2.10 增删改查 MyBatis-Plus&#…

C# Onnx 使用onnxruntime部署实时视频帧插值

目录 介绍 效果 模型信息 项目 代码 下载 C# Onnx 使用onnxruntime部署实时视频帧插值 介绍 github地址:https://github.com/google-research/frame-interpolation FILM: Frame Interpolation for Large Motion, In ECCV 2022. The official Tensorflow 2…

四.QT5工具安装和环境变量的配置

1.以管理员身份运行安装包 2.登录qt账号,点击【next】 3.选中同意 4.选择安装目录,注意不能有中文和空格 5.勾选 64位 mingw。点击【next】,等待安装完成 6.配置环境变量

为什么很多人选用QT开发,有哪些应用实例?

在软件开发领域,Qt框架作为一种跨平台的C应用程序开发框架,近年来受到越来越多开发者的青睐。这主要得益于其卓越的跨平台性能、丰富的功能库、开发效率以及社区支持。以下将通过详实的分析,从不同角度探讨为什么很多人改用QT开发&#xff0c…

力扣645. 错误的集合(排序,哈希表)

Problem: 645. 错误的集合 文章目录 题目描述思路复杂度Code 题目描述 思路 1.排序 1.对nums数组按从小到大的顺序排序; 2.遍历数组时若判断两个相邻的元素则找到重复元素; 3.记录一个整形变量prev一次置换当前位置元素并与其作差,若差等于2着说明缺失的…

Mysql索引操作

1、索引语法 2、慢查询日志 慢查询日志记录了所有执行时间超过指定参数( long_query_time ,单位:秒,默认 10 秒)的所有 SQL 语句的日志。 MySQL 的慢查询日志默认没有开启,我们可以查看一下系统变量 slo…

【2024软件测试面试必会技能】Appium自动化(5):元素定位工具

常用元素定位工具使用 uiautomatorviewer定位工具: 元素定位主要用来获取元素信息,获取元素信息后才能用appium提供的相关API去识别和操作元素。 谷歌在AndroidSDK中,提供了元素定位工具uiautomatorviewer,该工具可在android-s…

三opencv源码解压及环境变量配置

1.双击opencv-3.4.6-vc14-vc15.exe 2.选择解压的路径,点击【extract】 3.设计环境变量

从零学习Linux操作系统第二十七部分 shell脚本中的变量

一、什么是变量 变量的定义 定义本身 变量就是内存一片区域的地址 变量存在的意义 命令无法操作一直变化的目标 用一串固定的字符来表示不固定的目标可以解决此问题 二、变量的类型及命名规范 环境级别 export A1 在环境关闭后变量失效 退出后 关闭 用户级别&#xff…

《初阶数据结构》尾声

目录 前言: 《快速排序(非递归)》: 《归并排序》: 《归并排序(非递归)》: 《计数排序》: 对于快速排序的优化: 分析: 总结: 前言&#xff1a…

新疆营盘古城及古墓群安防舱体实施方案

3 总体布局 3.1设计原则 3.1.1执行有效的国家标准、国家军用标准和行业标准; 3.1.2满足指标要求; 3.1.3采用通用化、模块化设计,提高设备可维修性; 3.1.4采用人机工程学知识进行设计,充分考虑安全性。 3.2 总体…

51单片机学习(3)-----独立按键控制LED的亮灭状态

前言:感谢您的关注哦,我会持续更新编程相关知识,愿您在这里有所收获。如果有任何问题,欢迎沟通交流!期待与您在学习编程的道路上共同进步了。 目录 一. 器件介绍及实验原理 1.独立按键 (1)独…