Golang初學(xué)者易犯的三種錯(cuò)誤

序言

筆者學(xué)習(xí)并使用Golang已經(jīng)有一個(gè)多月了看成,盡管Golang的特性少君编、語法簡(jiǎn)單且功能強(qiáng)大,但作為初學(xué)者绍昂,難免會(huì)犯一些大家都犯過的錯(cuò)誤啦粹。筆者在實(shí)踐的基礎(chǔ)上,將初學(xué)者易犯的錯(cuò)誤進(jìn)行了簡(jiǎn)單梳理窘游,暫時(shí)總結(jié)了三種錯(cuò)誤唠椭,先分享給大家,希望對(duì)大家有一定的幫助忍饰。

資源關(guān)閉

這里的資源包括文件贪嫂、數(shù)據(jù)庫(kù)連接和Socket連接等,我們以文件操作為例艾蓝,說明一下常見的資源關(guān)閉錯(cuò)誤力崇。

文件操作的一個(gè)代碼示例:

file, err := os.Open("test.go") 
if err != nil {
    fmt.Println("open file failed:", err)
    return
}
...

一些同學(xué)寫到這就開始專注業(yè)務(wù)代碼了,最后“忘記”了寫關(guān)閉文件操作的代碼赢织。殊不知亮靴,這里埋下了一個(gè)禍根。在Linux中于置,一切皆文件茧吊,當(dāng)打開的文件數(shù)過多時(shí),就會(huì)觸發(fā)"too many open files“的系統(tǒng)錯(cuò)誤,從而讓整個(gè)系統(tǒng)陷入崩潰搓侄。

我們?cè)黾由详P(guān)閉文件操作的代碼瞄桨,如下所示:

file, err := os.Open("test.go")
defer file.Close()
if err != nil {
    fmt.Println("open file failed:", err)
    return
}
...

Golang提供了一個(gè)很好用的關(guān)鍵字defer,defer語句的含義是不管程序是否出現(xiàn)異常讶踪,均在函數(shù)退出時(shí)自動(dòng)執(zhí)行相關(guān)代碼芯侥。遺憾的是,上面的修改又引入了新問題乳讥,即如果文件打開錯(cuò)誤柱查,調(diào)用file.Close會(huì)導(dǎo)致程序拋出異常(panic),所以正確的修改應(yīng)該將file.Close放到錯(cuò)誤檢查之后雏婶,如下:

file, err := os.Open("test.go")
if err != nil {
    fmt.Println("open file failed:", err)
    return
}
defer file.Close()
...

變量的大小寫

Golang對(duì)關(guān)鍵字的增加非常吝嗇物赶,其中沒有private白指、protected和public這樣的關(guān)鍵字留晚。要使某個(gè)符號(hào)對(duì)其他包(package)可見(即可以訪問),需要將該符號(hào)定義為以大寫字母開頭告嘲,這些符號(hào)包括接口错维,類型,函數(shù)和變量等橄唬。

對(duì)于那些比較在意美感的程序員赋焕,尤其是工作在Linux平臺(tái)上的C/C++程序員,函數(shù)名或變量名以大寫字母開頭可能會(huì)讓他們感覺不太適應(yīng)仰楚,同時(shí)他們嚴(yán)格遵循最小可見性的原則隆判,接口名和類名以小寫字母開頭也會(huì)讓他們很糾結(jié)。在他們自己寫代碼的時(shí)候可能會(huì)順手將函數(shù)名或變量名改成以小寫字母開頭僧界,當(dāng)與小寫字母開頭的接口名或類型名沖突時(shí)(包內(nèi)可見性)侨嘀,還得費(fèi)心的另外想一個(gè)名字。如果不小心捂襟,將包外可見性的符號(hào)rename成了以小寫字母開頭咬腕,則會(huì)遇到編譯錯(cuò)誤,即明明有符號(hào)卻偏偏找不到葬荷,不過這對(duì)于有一些編程經(jīng)驗(yàn)的程序員來說還是比較好解決的涨共。

下面的例子對(duì)于Golang的初學(xué)者,即使有一些編程經(jīng)驗(yàn)宠漩,也較難排查举反,往往要花費(fèi)稍微多一些的時(shí)間。

type Position struct {
    X int 
    Y int
    Z int
}

type Student struct {
    Name string
    Sex string
    Age int
    position Position
}

func main(){
    position1 := Position{10, 20, 30}
    student1 := Student{"zhangsan", "male", 20, position1}
    position2 := Position{15, 10, 20}
    student2 := Student{"lisi", "female", 18, position2}    

    var srcSlice = make([]Student, 2)
    srcSlice[0] = student1
    srcSlice[1] = student2
    fmt.Printf("Init:srcSlice is : %v\n", srcSlice)
    data, err := json.Marshal(srcSlice)
    if err != nil{
        fmt.Printf("Serialize:json.Marshal error! %v\n", err)
        return
    }

    var dstSliece = make([]Student, 2)
    err = json.Unmarshal(data, &dstSliece)
    if err != nil {
        fmt.Printf("Deserialize: json.Unmarshal error! %v\n", err)
        return
    }
    fmt.Printf("Deserialize:dstSlice is : %v\n", dstSliece)
}

我們看一下打印結(jié)果:

Init:srcSlice is : [{zhangsan male 20 {10 20 30}} {lisi female 18 {15 10 20}}]
Deserialize:dstSliece is : [{zhangsan male 20 {0 0 0}} {lisi female 18 {0 0 0}}]

很意外的是扒吁,我們反序列化后獲取的對(duì)象數(shù)據(jù)是錯(cuò)誤的火鼻,而json.Unmarshal沒有返回任何異常。
為了進(jìn)一步定位,我們將序列化后的json串打印出來:

Serialize:data is : [{"Name":"zhangsan","Sex":"male","Age":20},{"Name":"lisi","Sex":"female","Age":18}]

從打印結(jié)果可以看出凝危,Position的數(shù)據(jù)丟了波俄,這使得我們想到了可見性,即大寫的符號(hào)在包外可見蛾默。通過走查代碼懦铺,我們發(fā)現(xiàn)Student的定義中,Position的變量名是小寫開始的:

type Student struct {
    Name string
    Sex string
    Age int
    position Position
}

對(duì)于習(xí)慣寫C/C++/Java代碼的同學(xué)支鸡,修改這個(gè)變量的名字變得很糾結(jié)冬念,以往“類名大寫開頭,對(duì)象名小寫開頭”的經(jīng)驗(yàn)不再適用牧挣,不得不起一個(gè)不太順溜的名字急前,比如縮寫:

type Student struct {
    Name string
    Sex string
    Age int
    Posi Position
}

再次運(yùn)行程序,結(jié)果正常瀑构,打印如下:

Init:srcSlice is : [{zhangsan male 20 {10 20 30}} {lisi female 18 {15 10 20}}]
Serialize:data is : [{"Name":"zhangsan","Sex":"male","Age":20,"Posi":{"X":10,"Y":20,"Z":30}},{"Name":"lisi","Sex":"female","Age":18,"Posi":{"X":15,"Y":10,"Z":20}}]
Deserialize:dstSliece is : [{zhangsan male 20 {10 20 30}} {lisi female 18 {15 10 20}}]

對(duì)于json串裆针,很多人喜歡全小寫,對(duì)于大寫開頭的key感覺很刺眼寺晌,我們繼續(xù)改進(jìn):

type Position struct {
    X int `json:"x"`
    Y int `json:"y"`
    Z int `json:"z"`
}

type Student struct {
    Name string `json:"name"`
    Sex string `json:"sex"`
    Age int `json:"age"`
    Posi Position `json:"position"`
}

兩個(gè)斜點(diǎn)之間的代碼世吨,比如json:"name",作用是Name字段在從結(jié)構(gòu)體實(shí)例編碼到JSON數(shù)據(jù)格式的時(shí)候呻征,使用name作為名字耘婚,這可以看作是一種重命名的方式。

再次運(yùn)行程序陆赋,結(jié)果是我們期望的沐祷,打印如下:

Init:srcSlice is : [{zhangsan male 20 {10 20 30}} {lisi female 18 {15 10 20}}]
Serialize:data is : [{"name":"zhangsan","sex":"male","age":20,"position":{"x":10,"y":20,"z":30}},{"name":"lisi","sex":"female","age":18,"position":{"x":15,"y":10,"z":20}}]
Deserialize:dstSliece is : [{zhangsan male 20 {10 20 30}} {lisi female 18 {15 10 20}}]

局部變量初始化(:=)

Golang中有一種局部變量初始化方法,即使用冒號(hào)和等號(hào)的組合“:=”來進(jìn)行變量聲明和初始化,這使得我們?cè)谑褂镁植孔兞繒r(shí)很方便攒岛。

初始化一個(gè)局部變量的代碼可以這樣寫:

v := 10

指定類型已不再是必需的赖临,Go編譯器可以從初始化表達(dá)式的右值推導(dǎo)出該變量應(yīng)該聲明為哪種類型,這讓Go語言看起來有點(diǎn)像動(dòng)態(tài)類型語言阵子,盡管Go語言實(shí)際上是不折不扣的強(qiáng)類型語言(靜態(tài)類型語言)思杯。

說明:感覺與C++11中auto關(guān)鍵字的作用有點(diǎn)類似

Golang中引入了一個(gè)關(guān)于錯(cuò)誤處理的標(biāo)準(zhǔn)模式,即error接口挠进,大家都太愛用了色乾,以至于明顯只有bool屬性的返回值或變量都用error來修飾,我們看一個(gè)例子:

port, err := createPort()
if err != nil {
    return
}

veth, err := createVeth()
if err != nil {
    return
}

err = insert()
if err != nil {
    return
}
...

這里的兩個(gè)局部變量err是同一個(gè)變量嗎领突?答案是肯定的

通過冒號(hào)和等號(hào)的組合“:=”來進(jìn)行變量初始化有一個(gè)限制暖璧,即出現(xiàn)在“:=”左側(cè)的變量至少有一個(gè)是沒有聲明過的,否則編譯失敗君旦。

很多人不知道這個(gè)規(guī)則澎办,則寫出下面的代碼:

port, errPort := createPort()
if errPort != nil {
    return
}

veth, errVeth := createVeth()
if errVeth != nil {
    return
}

errInsert := insert()
if errInsert != nil {
    return
}
...

對(duì)于喜歡寫簡(jiǎn)單優(yōu)美代碼的同學(xué)可能接受不了這樣的命名嘲碱,比如errPort, errVeth和errInsert等,所以對(duì)于error接口的變量命名局蚀,在筆者心中的baby names只有一個(gè)麦锯,那就是err。

除過命名琅绅,另一個(gè)常見錯(cuò)誤是局部變量有可能遮蓋或隱藏全局變量扶欣,因?yàn)橥ㄟ^“:=”方式初始化的局部變量看不到全局變量。

我們先看一段代碼:

var n int

func foo() (int, error) {
    return 5, nil
}

func bar() {
    fmt.Println("bar n:", n) 
}

func main() {
    n, err := foo()
    if err != nil {
        fmt.Println(err)
        return
    }
    bar()
    fmt.Println("main n:", n)
}

這段代碼的原意是定義一個(gè)包內(nèi)的全局變量n千扶,用foo函數(shù)的返回值對(duì)n進(jìn)行賦值料祠,在bar函數(shù)中使用n。
預(yù)期結(jié)果是bar()和main()中均輸出5澎羞,但程序運(yùn)行后的結(jié)果卻不是我們期望的:

bar n: 0
main n: 5

通過增加打印進(jìn)一步定位髓绽,發(fā)現(xiàn)main函數(shù)中調(diào)用foo函數(shù)后的n的地址(0x201d2210)與全局變量的n的地址(0x56b4a4)并不一樣,也就是說前者是一個(gè)局部變量妆绞,同時(shí)從bar函數(shù)中的打印來看顺呕,全局變量n在foo函數(shù)返回時(shí)并未被賦值為它的返回值5,仍然是初始的默認(rèn)值0摆碉。

最初對(duì)語句“n, err := foo()”的理解是塘匣,Golang會(huì)定義新變量err,n為初始定義的那個(gè)全局變量巷帝。但實(shí)際情況是,對(duì)于使用“:=”定義的變量扫夜,如果新變量n與那個(gè)已同名定義的變量(這里就是那個(gè)全局變量n)不在一個(gè)作用域中時(shí)楞泼,那么Golang會(huì)新定義這個(gè)變量n,并遮蓋或隱藏住大作用域的同名變量笤闯,這就是導(dǎo)致該問題的真兇堕阔。

知道真兇后就很好解決了,即我們用“=”代替“:=":

func main() {
    var err error
    n, err = foo()
    if err != nil {
        fmt.Println(err)
        return
    }
    bar()
    fmt.Println("main n:", n)
}

再次運(yùn)行該程序颗味,執(zhí)行結(jié)果完全符合預(yù)期:

bar n: 5
main n: 5

小結(jié)

本文總結(jié)了Golang初學(xué)者易犯的三種錯(cuò)誤超陆,包括資源關(guān)閉、符號(hào)的大小寫和局部變量初始化浦马,希望對(duì)像我一樣的新手有一點(diǎn)幫助时呀,從而在業(yè)務(wù)實(shí)現(xiàn)過程中少走一些彎路,更快更安全的面向業(yè)務(wù)編程晶默,持續(xù)的向用戶交付價(jià)值谨娜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市磺陡,隨后出現(xiàn)的幾起案子趴梢,更是在濱河造成了極大的恐慌漠畜,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坞靶,死亡現(xiàn)場(chǎng)離奇詭異憔狞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)彰阴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門躯喇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硝枉,你說我怎么就攤上這事廉丽。” “怎么了妻味?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵正压,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我责球,道長(zhǎng)焦履,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任雏逾,我火速辦了婚禮嘉裤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栖博。我一直安慰自己屑宠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布仇让。 她就那樣靜靜地躺著典奉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丧叽。 梳的紋絲不亂的頭發(fā)上卫玖,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音踊淳,去河邊找鬼假瞬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛迂尝,可吹牛的內(nèi)容都是我干的脱茉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼雹舀,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼芦劣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起说榆,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤虚吟,失蹤者是張志新(化名)和其女友劉穎寸认,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體串慰,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偏塞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邦鲫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灸叼。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖庆捺,靈堂內(nèi)的尸體忽然破棺而出古今,到底是詐尸還是另有隱情,我是刑警寧澤滔以,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布捉腥,位于F島的核電站,受9級(jí)特大地震影響你画,放射性物質(zhì)發(fā)生泄漏抵碟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一坏匪、第九天 我趴在偏房一處隱蔽的房頂上張望拟逮。 院中可真熱鬧,春花似錦适滓、人聲如沸敦迄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颅崩。三九已至,卻和暖如春蕊苗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沿彭。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工朽砰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喉刘。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓瞧柔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親睦裳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子造锅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言 本規(guī)范是針對(duì) Go 語言的編碼規(guī)范,目的是為了統(tǒng)一項(xiàng)目的編碼風(fēng)格廉邑,提高源程序的可讀性哥蔚、可靠性和可重用性倒谷,從而...
    _張曉龍_閱讀 1,959評(píng)論 5 21
  • 班級(jí)情況 校區(qū):科學(xué)創(chuàng)想機(jī)器人茂業(yè)店 時(shí)間:周六下午3點(diǎn)30-5點(diǎn)30 學(xué)員:曲冠名抖格,范凱博,郝嘉成 老師:張玲 ...
    樂搭閱讀 485評(píng)論 0 1
  • 【一】 他熱愛攝影咕晋,用心去捕捉一切美好雹拄。在他鏡頭里,山水掌呜、人物都自成一種韻味滓玖,有美在無聲地流動(dòng)。他為自己能定格那么...
    凌星虹閱讀 508評(píng)論 0 30
  • 啤酒作為人類最古老的酒精飲料之一,于二十世紀(jì)初傳入中國(guó)饰剥。 啤酒是以大麥芽﹑酒花﹑水為主要原料﹐經(jīng)酵母發(fā)酵作用釀制而...
    咪咪盟閱讀 764評(píng)論 0 1