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.pswp.cn/news/696853.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696853.shtml
英文地址,請注明出處:http://en.pswp.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配…

使用python進行量化交易

yfinance yfinance國內不能使用,可以使用tushare、akshare代替 import yfinance as yf# 輸入股票代碼 stock_symbol AAPL # 替換為你想要查詢的股票代碼# 獲取股票數據 data yf.download(stock_symbol)# 打印實時數據 print(data)pip install akshare import …

Selenium安裝與配置

文章目錄 一、selenium安裝1. Python環境準備:2. 安裝Selenium:3. 瀏覽器驅動安裝:4. 驗證安裝: 二、常見問題1. Selenium版本與瀏覽器驅動程序不兼容:2. 瀏覽器驅動程序路徑未正確設置: Selenium是一個用于…

2024年1月手機市場行業分析:蘋果手機份額驟降,國產高端手機成功逆襲!

小米Ultra發布。 一方面,我們有望看到國產手機再一次超越自己的決心,繼續創新追逐高端;另一方面,我們也不得不正視目前手機市場所面臨的危機狀態。 2024年1月的線上手機市場遠不如去年。根據鯨參謀數據顯示,今年1月京…

Qt(C++)面試題 | 精選25項常問

面試是每個求職者都必須經歷的一關,而QT面試更是需要面試者有深厚的編程基礎和豐富的實戰經驗。下面我們為大家整理了25道QT面試題,希望能夠幫助大家在求職路上獲得成功。 ?Qt 中常用的五大模塊是哪些? Qt 中常用的五大模塊包括: QtCore:提供了 Qt 的核心功能,例如基本的…

Java面試題之分布式/微服務篇

經濟依舊不景氣啊,如此大環境下Java還是這么卷,又是一年一次的金三銀四。 兄弟們,你準備好了嗎?沖沖沖!歐里給! 分布式/微服務相關面試題解 題一:CAP理論,BASE理論題二:…

深度神經網絡

包括:深度前饋神經網絡、深度卷積神經網絡、深度循環神經網絡 深度神經網絡全面概述:從基本概念到實際模型和硬件基礎-騰訊云開發者社區-騰訊云

MQL語言實現JSON協議庫

文章目錄 一、MQL語言實現JSON協議的意義二、定義JSON數據枚舉類型簡單數據類型復雜數據類型枚舉數據類型定義類變量清理與賦值方法構造與析構方法重載運算符添加與設置方法序列化與反序列方法 一、MQL語言實現JSON協議的意義 數據交互:JSON是一種輕量級的數據交換格…

【2024軟件測試面試必會技能】Postman(1): postman的介紹和安裝

Postman的介紹 Postman 是一款谷歌開發的接口測試工具,使API的調試與測試更加便捷。 它提供功能強大的 Web API & HTTP 請求調試。它能夠發送任何類型的HTTP 請求 (GET, HEAD, POST, PUT..),附帶任何數量的參數 headers。 postman是一款支持http協議的接口調試…

【PTA|函數題|期末復習】指針

目錄 6-1 計算兩數的和與差(5分) 函數接口定義: 裁判測試程序樣例: 輸入樣例: 輸出樣例: 代碼 6-2 拆分實數的整數與小數部分 (5分) 函數接口定義: 裁判測試程序樣例: 輸入…

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.配置環境變量

[07] 組件注冊

目錄 Vue.js 組件局部注冊全局注冊 Vue.js 組件 組件(Component)是 Vue.js 最強大的功能之一。 組件可以擴展 HTML 元素,封裝可重用的代碼。 組件系統讓我們可以用獨立可復用的小組件來構建大型應用,幾乎任意類型的應用的界面都…

為什么很多人選用QT開發,有哪些應用實例?

在軟件開發領域,Qt框架作為一種跨平臺的C應用程序開發框架,近年來受到越來越多開發者的青睞。這主要得益于其卓越的跨平臺性能、豐富的功能庫、開發效率以及社區支持。以下將通過詳實的分析,從不同角度探討為什么很多人改用QT開發&#xff0c…

低代碼開發:學校低成本數字化轉型的新引擎

隨著科技的飛速發展,數字化轉型已經成為教育行業的一大趨勢。然而,對于許多學校來說,高昂的數字化改造成本成為了一道難以逾越的門檻。本文將探討如何通過低代碼開發,以低成本實現學校數字化轉型,為教育行業注入新活力…

力扣熱題100_子串_560_和為 K 的子數組

文章目錄 題目鏈接解題思路解題代碼 題目鏈接 560.和為 K 的子數組 解題思路 1.定義變量count(計算個數,返回值)為0、n(當前nums長度)、preSums(利用利用defaultdict的特性,當presum-k不存在…