婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > 為什么不建議在go項目中使用init()

為什么不建議在go項目中使用init()

熱門標簽:不封卡外呼系統 湛江crm外呼系統排名 地圖標注免費定制店 重慶慶云企業400電話到哪申請 上海極信防封電銷卡價格 鄭州智能語音電銷機器人價格 寧波語音外呼系統公司 仙桃400電話辦理 宿遷便宜外呼系統代理商

前言

goinit函數給人的感覺怪怪的,我想不明白聰明的 google團隊為何要設計出這么一個“雞肋“的機制。實際編碼中,我主張盡量不要使用init函數。

首先來看看 init函數的作用吧。

init() 介紹

init()與包的初始化順序息息相關,所以先介紹一個go中包的初始化順序吧。(下面的內容部分摘自《The go programinng language》)

大體而言,順序如下:

  • 首先初始化包內聲明的變量
  • 之后調用 init 函數
  • 最后調用 main 函數

變量的初始化順序

變量的初始化順序由他們的依賴關系決定

應該任何強類型語言都是這樣子吧。

例如:

var a = b + c;
var b = f();	// 需要調用 f() 函數
var c = 1
func f() int{return c + 1;}

a 依賴 bcb 依賴 f()f() 依賴 c。因此,他們的初始化順序理所當然是 c -> b -> a

graph TB; b-->a c-->a f-->b c-->b

Ps:其實在這里可能引申出一個沒用的小技巧。當你有一個函數需要在包被初始化的過程中被調用時,你可以把這個函數賦值給一個包級變量。這樣,當包被初始化時就會自動調用這個函數了,這個函數甚至能夠在 init() 之前被調用!不過話說回來,它既然比 init() 更早被調用,那它才是真正的 init() 才對;此外你也可以在 init() 中調用該函數,這樣才更合理一些。

// 笨版
// 函數必須得有一個返回值才行
var _ = func() interface{} {
	fmt.Println("hello")
	return nil
}()

func init() {
	fmt.Println("world")
}

func main() {

}
// Output:
// hello
// world
// 更合理的版本
func init() {
	fmt.Println("hello")
	fmt.Println("world")
}

func main() {

}
// Output:
// hello
// world

包內變量的初始化順序

一個包內往往有多個 go文件,這么go文件的初始化順序由它們被提交給編譯器的順序決定,順序和這些文件的名字有關。

init()

主角出場了。先來看看它的設計動機吧:

Each variable declared at package level starts life with the value of its initializer expression, if any, but for some variables, like tables of data,an initializer expression may not be the simplest way to set its initial value.In that case,the init function mechanism may be simpler. 《The go pragramming language P44》

這句話的意思是有的包級變量沒辦法用一條簡單的表達式來初始化,這個 時候,init機制就派上用場了。

init() 不能被調用,也不能被 reference,它們會在程序啟動時自動執行。

同一個 go 文件中 init 函數的調用順序

一個包內,甚至 go 文件內可以包含多個 init(),同一個 go 文件中的 init() 調用順序由他們的聲明順序決定 。

func init() {
	fmt.Print("a")
}
func init() {
	fmt.Print("b")
}
func init() {
	fmt.Print("c")
}
// Output
// abc

同一個包下面不同 go 文件中 init() 的調用順序

依舊是由它們的聲明順序決定,同一個包下面的所有go 文件在編譯時會被編譯器合并成一個“大的go文件“(并不是真正合并,僅僅是效果類似而已)。合并的順序由編譯器決定。

不要把程序是否能夠正常工作寄托在init()能夠按照你期待的順序被調用上。

不過話說回來,正經人誰在一個包里寫很多 init() 呀,而且還把這些 init() 放在不同文件里,更可惡的是每個文件里還有多個 init()。要是看到這樣的代碼,我立馬:@#$%^*...balabala...

一個包里最多寫一個init()(我甚至覺得最好連一個 init() 都不要有)

不同包內 init 函數的調用順序

唯獨這個順序,我們程序員是絕對可控的。它們的調用順序由包之間的依賴關系決定。假設 a包需要 import b包,b包需要import c包,那么很顯然他們的調用順序是,c包的init()最先被調用,其次是b包,最后是a包。

graph LRc-->bb-->a

一個包的init函數最多會被調用一次

道理類似于一個變量最多會被初始化一次。

有的同學會問,一個變量明明可以多次賦值呀,可第二次對這個變量賦值那還能夠叫初始化么?

例如有如下的包結構,B包和C包都分別import A包,D包需要import B包和C包。

graph TD; A-->B A-->C B-->D C-->D

A包中有 init()

func init() {
	fmt.Println("hello world")
}

D包是 main 包,最終程序只輸出了一句 hello world

我不喜歡 init 函數的原因

我不喜歡 init 函數的一個重要原因是,它會隱藏掉程序的一些細節,它會在沒有經過你同意的情況下,偷偷干一些事情。go 的函數王國里,所有的函數都需要程序員顯示的調用(Call)才會被執行,只有它——init(),是個例如,你明明沒 Call 它,它卻偷偷執行了。

有的同學會說,c++ 里類的構造函數也是在對象被創建時就會默默執行呀。確實是這樣,但在 c++ 里,當你點進這個類的定義時,你就能立馬看到它的構造函數和析構函數。在 go 里,當你點進某個包時,你能立馬看到包內的init()么?這個包有沒有init()以及有幾個init()完全是個未知數,你需要在包內的所有文件中搜索 init() 這個關鍵字才能摸清包的 init()情況,而大多數人包括我懶得費這個功夫。在c++中創建對象時,程序員能夠很清楚的意識到這個操作會觸發這個類的構造函數,這個構造函數的內容也能很快找到;但在 go 中,import 包時,一切卻沒那么清晰了。

希望將來 goland 或者 vscode 能夠分析包內的 init() 情況,這樣我對 init() 的惡意會減半。

init() 給項目維護帶來的困難

當你看到這樣的 import 代碼時

import(
	_ "pkg"
)

你立馬能夠知道,這個 import 的目的就是調用 pkg 包的 int()

當看到

import(
	"pkg"
)

你卻很難知道,pkg 包里藏著一個 init(),它被偷偷調用了。

但這還好,你起碼知道如果 pkg 包有 init() 的話,它會在此處被調用。

但當pkg 包,被多個包 import 時,pkg 包內的 init() 何時被調用的,就是一個謎了。你得搞清楚這些包之間的 import 先后順序關系,這是一場噩夢。

使用 init()的時機

先說一下我的結論:我認為 init()應該僅被用來初始化包內變量。

《The go programming language》提供了一個使用 init函數的例子。

// pc[i] 是 i 中 bit = 1 的數量
var pc [256]byte

func init() {
	for i := range pc {
		pc[i] = pc[i/2] + byte(i1)
	}
}

// 返回 x 中等于 1 的 bit 的數量
func PopCount(x uint64) int {
	return int(pc[byte(x>>(0*8))] +
		pc[byte(x>>(1*8))] +
		pc[byte(x>>(2*8))] +
		pc[byte(x>>(3*8))] +
		pc[byte(x>>(4*8))] +
		pc[byte(x>>(5*8))] +
		pc[byte(x>>(6*8))] +
		pc[byte(x>>(7*8))])
}

PopCount 函數的作用數計算數字中等于 1bit 的數量。例如 :

var i uint64 = 2

變量 i 的二進制表示形式為

0000000000000000000000000000000000000000000000000000000000000010

把它傳入 PopCount 最終得到的結果將為 1,因為它只有一個 bit 的值為 1

pc 是一個表,它的 index 為 x,其中 0 = x = 255,value 為 x 中等于 1 的 bit 的數量。

它的初始化思想是:

  • 如果一個數x最后的 bit 為 1,那么這個數值為 1 的bit數 = x/2 的值為1的bit數 + 1;
  • 如果一個數x最后的 bit 為 0,那么這個數值為 1 的bit數 = x/2 的值為1的bit數;

PopCount 中把一個 8byte 數拆成了 8 個單 byte 數,分別計算這8個單 byte 數中 bit1 的數量,最后累加即可。

這里 pc 的初始化確實比較復雜,無法直接用

var pc = []byte{0, 1, 1,...}

這種形式給出。

一個可以替代 init()的方法是:

var pc = generatePc()

func generatePc() [256]byte {
	var localPc [256]byte
	for i := range localPc {
		localPc[i] = localPc[i/2] + byte(i1)
	}
	return localPc
}

我覺得這樣子初始化比利用 init() 初始化要更好,因為你可以立馬知道 pc 是怎樣得來的,而利用 init() 時,你需要利用 ide 來查找 pc 的 write reference,之后才能知道,哦,原來它(pc)來這里(init()) 被初始化了呀。

當包內有多個變量的初始化流程比較復雜時,可能會寫出如下代碼。

var pc = generatePc()
var pc2 = generatePc2()
var pc3 = generatePc3()
// ...

有的同學可能不太喜歡這種寫法,那么用上 init() 后,會寫成這樣

func init() {
	initPc()
	initPc2()
	initPc3()
}

我覺得兩種寫法都說的過去吧,雖然我個人更傾向第一種寫法。

使用 init()的時機,僅僅有一個例外,后面說。

不使用 init 函數的時機

init()除了初始化變量,不應該干其他任何事!

有兩個原則:

  • 一個包的 init() 不應該依賴包外的環境
  • 一個包的 init() 不應該對包外的環境造成影響

設置這兩個原則的理由是:任何對外部有依賴或者對外部有影響的代碼都有義務顯式的讓程序員知曉,不應該自己悄咪咪地去做,最好是顯式地讓程序員自己去調用。

init() 的活動范圍就應該僅僅被局限在包內,自己和自己玩,不要影響了其他小朋友的游戲體驗。

如下幾條行為就踩了紅線:

  • 讀取配置(依賴于外部的配置文件,且一般讀取配置得到的 obj 會被其他包訪問,違反了第一條和第二條)
  • 注冊路由(因為修改了 http 包中的 routeMap,會對 http 包造成影響,違反了第二條)
  • 連接數據庫(連接數據庫后一般會得到一個 db 對象給業務層去curd吧?違反了第二條)
  • etc... 我暫時只能想到這么多了

一個反面教材 https://github.com/go-sql-driver/mysql

反面教材就是:https://github.com/go-sql-driver/mysql 這個大名鼎鼎的包

當使用這個包時,一個必不可少的語句是:

import (
	_ "github.com/go-sql-driver/mysql"
)

原因是它里面有個 init函數,會把自己注冊到 sql 包里。

func init() {
	sql.Register("mysql", MySQLDriver{})
}

按照之前的標準,此處明顯不符合規范,因為它影響了標準庫的 sql 包。

我認為一個更好的方法是,創建一個 export 的專門用來做初始化工作的方法:

// Package mysql
func Init() {
	sql.Register("mysql", MySQLDriver{})
}

然后在 main 包中顯式的調用它:

// Package main
func main(){
    mysql.Init();
    // other logic
}

來比較一下兩種方式吧。

  1. 使用 Init()
  • 是否需要告訴開發者額外的信息?

需要。

需要告訴開發者:使用這個庫時,記得一定要調用 Init() 哦,我在里面做了一些工作。

開發者,點進 Init(),瞬間了然。

  • 是否能夠阻止開發者不正確的調用?

不能。

因為是 export 的,所以開發者可以想到哪兒調用就到哪兒調用,想調用多少次就調用多少次。

因此需要額外告訴開發者:請您務必只調用一次,之后就不要調用了。且必須在用到 sql 包之前調用,一般而言都是在 main() 的第一句調用。

  1. 使用 init()
  • 是否需要告訴開發者額外的信息?

需要

依舊需要告訴開發者,一定要用 _ "github.com/go-sql-driver/mysql"這個語句顯式的導入包哦,因為我利用init()在里面做一些工作。

開發者:那你做了什么工作

庫:親,請您點進 mysql 包,在目錄下搜索 init() 關鍵字,慢慢找哦。

開發者:......

  • 是否能夠阻止開發者不正確的調用?

勉強可以吧。

因為 init() 只會被調用一次,不可能被調用多次,這從根本上杜絕了開發者調用多次的可能性。

可你管不了開發者的 import 時機,假設開發者在其他地方 import 了,導致你在 sql.Open()時,mysqldriver 沒有被正常注冊,你還是拿開發者沒有辦法。只能哀嘆一聲:我累了,毀滅吧。

我覺得作為庫的提供者,最主要的是提供完善的機制,在用戶使用你的庫時,能利用你提供的機制,寫出無bug 的代碼。而不是像保姆一樣,想方設法避免用戶出錯。

所以可能使用 init() 為了的優勢就是減少了代碼量吧。

使用 Init() 時,需要兩句代碼

import (
	"github.com/go-sql-driver/mysql"	// 這句
)

func main(){
    mysql.Init()				  // 這句
}

但是使用 init 時,卻只需要一句代碼

import (
	_ "github.com/go-sql-driver/mysql"	// 這句
)

oh yeah,足足少寫了一句代碼!

一個例外 單元測試

可能使用 init 的唯一例外就是寫單元測試的時候了吧。

假設我現在需要需要對 dao 層的增刪改查邏輯的寫一個單元測試。

func TestCURDPlayer(t *testing.T) {
	// 測試 curd 玩家信息
}

func TestCURDStore(t *testing.T) {
	// 測試 curd 商店信息
}

func TestCURDMail(t *testing.T) {
	// 測試 curd 郵件信息
}

很顯然,這些測試都是依賴數據庫的,因此為了正常的測試,必須初始化數據庫

func TestCURDPlayer(t *testing.T) {
	// 測試 curd 玩家信息
    initdb()
    // balabala
}

func TestCURDStore(t *testing.T) {
	// 測試 curd 商店信息
    initdb()
    // balabala
}

func TestCURDMail(t *testing.T) {
	// 測試 curd 郵件信息
    initdb()
    // balabala
}

func initdb(){
    // sql.Open()...
}

難道我每次新增一個單元測試,都要在單元測試的代碼中加一個 initdb() 么,這也太麻煩了吧。

這個時候 init() 就派上用場了。可以這樣

func TestCURDPlayer(t *testing.T) {
	// 測試 curd 玩家信息
    // balabala
}

func TestCURDStore(t *testing.T) {
	// 測試 curd 商店信息
    // balabala
}

func TestCURDMail(t *testing.T) {
	// 測試 curd 郵件信息
    // balabala
}

func init(){
    initdb()
}

func initdb(){
    // sql.Open()...
}

這樣,當對這個文件進行單元測試時,可以確保在執行每個 TestXXX 函數時,db 肯定是被正確初始化了的。

那為什么這個地方可以利用 init() 來初始化數據庫呢?

理由之一是它的影響范圍很小,僅僅在 xxx_test.go 文件中生效,在 go run 時不會起作用,在 go test 時才會起作用。

理由之二是我懶。。。

總結

init 更像是一個語法糖,它會讓開發者對代碼的追蹤能力變弱,所以能不用就最好不用。

到此這篇關于為什么不建議在go項目中使用用init()的文章就介紹到這了,更多相關go init內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • 淺談golang package中init方法的多處定義及運行順序問題
  • go語言的初始化順序,包,變量,init詳解
  • Go語言init函數詳解
  • GO語言ini配置文件的讀取的操作

標簽:儋州 遼寧 青海 電子產品 物業服務 安康 海南 西雙版納

巨人網絡通訊聲明:本文標題《為什么不建議在go項目中使用init()》,本文關鍵詞  為什么,不,建議,在,項,目中,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《為什么不建議在go項目中使用init()》相關的同類信息!
  • 本頁收集關于為什么不建議在go項目中使用init()的相關信息資訊供網民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    亚洲图片自拍偷拍| 日韩欧美激情一区| 91蝌蚪porny| 26uuu久久综合| 九九**精品视频免费播放| 91精品国产一区二区人妖| 亚洲综合清纯丝袜自拍| 欧美日韩国产首页| 紧缚捆绑精品一区二区| 日韩一区二区视频| 欧美美女网站色| 免费xxxx性欧美18vr| 欧美日韩和欧美的一区二区| 五月天精品一区二区三区| 欧美日本一道本| 2023国产精华国产精品| 国产精品久久久久久久久动漫| 亚洲少妇最新在线视频| 欧洲在线/亚洲| 毛片基地黄久久久久久天堂| 成人一区二区视频| 一区二区免费视频| 欧美日韩黄视频| 欧美精品一区二区三区高清aⅴ| 久久精品夜色噜噜亚洲a∨| 国产最新精品免费| 国产精品丝袜久久久久久app| 亚洲制服丝袜在线| 欧美成人激情免费网| 国产精品高潮呻吟久久| 国产91高潮流白浆在线麻豆 | 欧美一区二区三区免费观看视频| 久久精品视频一区| 色猫猫国产区一区二在线视频| 国产网红主播福利一区二区| 色成人在线视频| 久久99九九99精品| 欧美亚洲一区三区| 精品国产一区a| 成人av网址在线观看| 天天操天天综合网| 国产成人午夜高潮毛片| 亚洲激情欧美激情| 欧美激情中文不卡| 国模冰冰炮一区二区| 亚洲人成7777| 色诱视频网站一区| 精品亚洲aⅴ乱码一区二区三区| 91精品国产综合久久福利| 成人午夜激情片| 国产在线观看免费一区| 亚洲在线观看免费视频| 精品一二三四区| 3751色影院一区二区三区| 六月婷婷色综合| 亚洲婷婷国产精品电影人久久| 国产九色精品成人porny| 性做久久久久久| 国产精品久久久久影视| 国产精品一区二区男女羞羞无遮挡| 欧美巨大另类极品videosbest | 亚洲成人午夜电影| 久久久久国产精品麻豆| 久久国产精品露脸对白| 亚洲精品视频在线| 自拍偷拍国产精品| 国产欧美精品一区二区三区四区| 韩国欧美国产1区| 亚洲欧洲99久久| 国产欧美一区二区精品性色超碰| 风间由美性色一区二区三区| 国产精品欧美经典| 国产视频视频一区| 久久免费精品国产久精品久久久久| 国产麻豆视频一区二区| 狠狠色狠狠色综合系列| 极品尤物av久久免费看| 国产在线精品一区二区| 日韩三级精品电影久久久| 欧美三级日韩在线| 在线播放日韩导航| 欧美一区二区三区四区视频| 欧美一区三区四区| 精品一区二区日韩| 免费在线观看视频一区| 国产成人精品免费| 色婷婷精品久久二区二区蜜臀av| 亚洲一区二区视频| 日韩欧美久久一区| 欧美高清一级片在线| 久久久影视传媒| 色久综合一二码| 91视频免费播放| 日韩一级欧美一级| 国产丝袜在线精品| 亚洲一区视频在线| 亚洲同性gay激情无套| 夜夜爽夜夜爽精品视频| 久久av中文字幕片| 成人国产一区二区三区精品| 欧美性xxxxx极品少妇| 久久一区二区视频| 亚洲综合在线免费观看| 亚洲国产精品传媒在线观看| 亚洲人成网站精品片在线观看| 久久久久久久久99精品| 亚洲精品成人悠悠色影视| 久久新电视剧免费观看| 国产精品国产三级国产普通话蜜臀| 久久久久久久综合色一本| 亚洲嫩草精品久久| 国产精品一区在线观看乱码| 欧美自拍丝袜亚洲| 91免费国产在线观看| 7777精品伊人久久久大香线蕉超级流畅 | 大尺度一区二区| 欧美三级在线看| 中文字幕亚洲欧美在线不卡| 精品亚洲国内自在自线福利| 欧美少妇bbb| 综合激情网...| 1区2区3区国产精品| 蜜桃传媒麻豆第一区在线观看| 婷婷中文字幕一区三区| 风间由美性色一区二区三区| 欧美一区二区三区白人| 亚洲欧美日韩国产综合| 国产很黄免费观看久久| 欧美精品一区二区蜜臀亚洲| 视频一区视频二区中文| 欧美亚日韩国产aⅴ精品中极品| 成人h动漫精品一区二区| 日韩一区二区在线观看视频| 亚洲成av人片在www色猫咪| 亚洲国产欧美在线| 成人精品国产一区二区4080| 国产欧美一区视频| 国产成人精品影视| 国产亚洲人成网站| 国产福利一区二区三区视频| 久久久久久久综合| 国产69精品久久777的优势| 久久美女艺术照精彩视频福利播放| 国产精品网曝门| 亚洲永久精品国产| 日本精品视频一区二区| 艳妇臀荡乳欲伦亚洲一区| 在线观看亚洲一区| 亚洲欧美一区二区三区国产精品| 手机精品视频在线观看| 欧美日韩午夜在线视频| 日日夜夜免费精品| 国产在线一区观看| 91丨porny丨国产入口| 中文字幕在线观看一区| 91国产免费观看| 视频一区二区欧美| 国产日韩精品一区二区三区 | 欧美日韩日日骚| 亚洲18色成人| 久久综合成人精品亚洲另类欧美 | 欧美一级免费观看| 日韩成人一级片| 久久综合中文字幕| 国产欧美日本一区二区三区| 成人h动漫精品一区二区| 亚洲综合一区在线| 欧美不卡一区二区三区| 国产高清久久久| 亚洲黄色性网站| 欧美一级视频精品观看| 粉嫩aⅴ一区二区三区四区 | 91一区二区三区在线观看| 欧美精品亚洲二区| 国产老妇另类xxxxx| 一区二区三区不卡在线观看 | 日韩伦理免费电影| 欧美巨大另类极品videosbest| 亚洲少妇30p| 欧美高清视频一二三区| 成人性生交大片免费看在线播放| 精品日本一线二线三线不卡| 色素色在线综合| 久久国产精品72免费观看| 不卡一二三区首页| 亚洲va国产天堂va久久en| 国产午夜精品理论片a级大结局 | 欧美a级理论片| 欧美成人一区二区三区在线观看| 国产日韩欧美a| 91精品国产综合久久久蜜臀粉嫩| 香蕉久久夜色精品国产使用方法 | 亚洲第一在线综合网站| 日韩理论电影院| 欧美日韩亚州综合| 懂色av一区二区三区免费观看| 日韩欧美在线观看一区二区三区| 国产精品国产馆在线真实露脸| 狠狠色丁香婷婷综合久久片| 久久久91精品国产一区二区精品|