Go 語言的 10 個實(shí)用技術(shù)_第1頁
Go 語言的 10 個實(shí)用技術(shù)_第2頁
Go 語言的 10 個實(shí)用技術(shù)_第3頁
Go 語言的 10 個實(shí)用技術(shù)_第4頁
Go 語言的 10 個實(shí)用技術(shù)_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

Go語言的10個實(shí)用技術(shù)這里是我過去幾年中編寫的大量Go代碼的經(jīng)驗(yàn)總結(jié)而來的自己的最佳實(shí)踐。我相信它們具有彈性的。這里的彈性是指:某個應(yīng)用需要適配一個靈活的環(huán)境。你不希望每過3到4個月就不得不將它們?nèi)恐貥?gòu)一遍。添加新的特性應(yīng)當(dāng)很容易。許多人參與開發(fā)該應(yīng)用,它應(yīng)當(dāng)可以被理解,且維護(hù)簡單。許多人使用該應(yīng)用,bug應(yīng)該容易被發(fā)現(xiàn)并且可以快速的修復(fù)。我用了很長的時間學(xué)到了這些事情。其中的一些很微小,但對于許多事情都會有影響。所有這些都僅僅是建議,具體情況具體對待,并且如果有幫助的話務(wù)必告訴我。隨時留言:)使用單一的GOPATH多個GOPATH的情況并不具有彈性。GOPATH本身就是高度自我完備的(通過導(dǎo)入路徑)。有多個GOPATH會導(dǎo)致某些副作用,例如可能使用了給定的庫的不同的版本。你可能在某個地方升級了它,但是其他地方卻沒有升級。而且,我還沒遇到過任何一個需要使用多個GOPATH的情況。所以只使用單一的GOPATH,這會提升你Go的開發(fā)進(jìn)度。許多人不同意這一觀點(diǎn),接下來我會做一些澄清。像etcd或camlistore這樣的大項(xiàng)目使用了像godep這樣的工具,將所有依賴保存到某個目錄中。也就是說,這些項(xiàng)目自身有一個單一的GOPATH。它們只能在這個目錄里找到對應(yīng)的版本。除非你的項(xiàng)目很大并且極為重要,否則不要為每個項(xiàng)目使用不同的GOPATHo如果你認(rèn)為項(xiàng)目需要一個自己的GOPATH目錄,那么就創(chuàng)建它,否則不要嘗試使用多個GOPATH。它只會拖慢你的進(jìn)度。將for-select封裝到函數(shù)中如果在某個條件下,你需要從for-select中退出,就需要使用標(biāo)簽。例如:ain(){r{select{case<-time.After(time.Second):fmt.Println("hello")default:breakL}fmt.Println("ending")}如你所見,需要聯(lián)合break使用標(biāo)簽。這有其用途,不過我不喜歡。這個例子中的for循環(huán)看起來很小,但是通常它們會更大,而判斷break的條件也更為冗長。如果需要退出循環(huán),我會將for-select封裝到函數(shù)中:ain(){o()t.Println("ending")oo(){r{select{case<-time.After(time.Second):fmt.Println("hello")default:return}你還可以返回一個錯誤(或任何其他值),也是同樣漂亮的,只需要:塞:=foo();err!=nil{處理err在初始化結(jié)構(gòu)體時使用帶有標(biāo)簽的語法這是一個無標(biāo)簽語法的例子:typeTstruet{FoostringBarint}funemain(){t:二T{"example",123}//無標(biāo)簽語法fmt.Printf("t%+v\n",t)}那么如果你添加一個新的字段到T結(jié)構(gòu)體,代碼會編譯失敗:struet{ostringrintxstringain(){:=T{"example",123}//無法編譯t.Printf("t%+v\n",t)如果使用了標(biāo)簽語法,Go的兼容性規(guī)則(/doe/go1eompat)會處理代碼。例如在向net包的類型添加叫做Zone的字段,參見:http:///doc/go1.1#library?;氐轿覀兊睦?,使用標(biāo)簽語法:struet{ostringrintxstringain(){:=T{Foo:"example",Qux:123}t.Printf("t%+v\n",t)這個編譯起來沒問題,而且彈性也好。不論你如何添加其他字段到T結(jié)構(gòu)體。你的代碼總是能編譯,并且在以后的Go的版本也可以保證這一點(diǎn)。只要在代碼集中執(zhí)行g(shù)ovet,就可以發(fā)現(xiàn)所有的無標(biāo)簽的語法。將結(jié)構(gòu)體的初始化拆分到多行如果有兩個以上的字段,那么就用多行。它會讓你的代碼更加容易閱讀,也就是說不要:""example"",Bar:someLongVariable,Qux:anotherLongVariable,B:forgetToAddTh而是:o:example,r:someLongVariable,x:anotherLongVariable,forgetToAddThisToo,為整數(shù)常量添加String()方法如果你利用iota來使用自定義的整數(shù)枚舉類型,務(wù)必要為其添加String()方法。例如,像這樣:tateint(nningState=iotaoppedbootingrminated如果你創(chuàng)建了這個類型的一個變量,然后輸出,會得到一個整數(shù)ain(){ate:=Runningprint:""state0〃t.Println(〃state",state)除非你回顧常量定義,否則這里的0看起來毫無意義。只需要為State類型添加String()方法就可以修復(fù)這個問題(/p/ewMK16K302):

func(sState)String()string{switchs{caseRunning:return""Running"caseStopped:return""Stopped"caseRebooting:return""Rebooting""caseTerminated:return""Terminated""default:return""Unknown"}}新的輸出是:state:Running。顯然現(xiàn)在看起來可讀性好了很多。在你調(diào)試程序的時候,這會帶來更多的便利。同時還可以在實(shí)現(xiàn)MarshalJSON()、UnmarshalJSON()這類方法的時候使用同樣的手段。讓iota從a+1開始增量在前面的例子中同時也產(chǎn)生了一個我已經(jīng)遇到過許多次的bug。假設(shè)你有一個新的結(jié)構(gòu)體,有一個State字段:struct{mestringrtintateState現(xiàn)在如果基于T創(chuàng)建一個新的變量,然后輸出,你會得到奇怪的結(jié)果ain(){:=T{Name:""example",Port:6666}prints:""t{Name:examplePort:6666State:Running}""t.Printf(""t%+v\n"",t)看到bug了嗎?State字段沒有初始化,Go默認(rèn)使用對應(yīng)類型的零值進(jìn)行填充。由于State是一個整數(shù),零值也就是0,但在我們的例子中它表示Runningo

那么如何知道State被初始化了?還是它真得是在Running模式?沒有辦法區(qū)分它們,那么這就會產(chǎn)生未知的、不可預(yù)測的bug。不過,修復(fù)這個很容易,只要讓iota從+1開始(nningState=iota+1oppedbootingrminated現(xiàn)在t變量將默認(rèn)輸出Unknown,不是嗎?:ain(){:二T{Name:""example",Port:6666}輸出:""t{Name:examplePort:6666State:Unknown}"t.Printf(""t%+v\n〃,t)不過讓iota從零值開始也是一種解決辦法。例如,你可以引入一個新的狀態(tài)叫做Unknown,將其修改為:(knownState=iotanningoppedbootingrminated返回函數(shù)調(diào)用我已經(jīng)看過很多代碼例如(/p/8RzlEJwFTZ):ar()(string,error){err:=foo()err!=nil{.〃〃return,errreturnv,nil然而,你只需要:ar()(string,error){turnfoo()更簡單也更容易閱讀(當(dāng)然,除非你要對某些內(nèi)部的值做一些記錄)。把slice、map等定義為自定義類型將slice或map定義成自定義類型可以讓代碼維護(hù)起來更加容易。假設(shè)有一個Server類型和一個返回服務(wù)器列表的函數(shù):erverstruct{mestringistServers()[]Server{turn[]Server{{Name:"Serverl"},{Name:"Server2"},{Name:"Foo1"},{Name:"Foo2"},現(xiàn)在假設(shè)需要獲取某些特定名字的服務(wù)器。需要對ListServers()做一些改動,增加篩選條件:tServers返回服務(wù)器列表。只會返回包含name的服務(wù)器??盏膎ame將會返回所有istServers(namestring)[]Server{rvers:二[]Server{{Name:"Server1"},{Name:"Server2"},{Name:"Foo1"},{Name:"Foo2"},

}//返回所有服務(wù)器ifname== {returnservers}//返回過濾后的結(jié)果filtered:二make([]Server,0)for_,server:=rangeservers{ifstrings.Contains(server.Name,name){filtered=append(filtered,server)}}returnfiltered}現(xiàn)在可以用這個來篩選有字符串Foo的服務(wù)器:ain(){rvers:=ListServers("Foo")輸出:“servers[{Name:Fool}{Name:Foo2}]”t.Printf("servers%+v\n",servers)顯然這個函數(shù)能夠正常工作。不過它的彈性并不好。如果你想對服務(wù)器集合引入其他邏輯的話會如何呢?例如檢查所有服務(wù)器的狀態(tài),為每個服務(wù)器創(chuàng)建一個數(shù)據(jù)庫記錄,用其他字段進(jìn)行篩選等等……現(xiàn)在引入一個叫做Servers的新類型,并且修改原始版本的ListServers()返回這個新類型:ervers[]ServertServers返回服務(wù)器列表istServers()Servers{turn[]Server{{Name:"Serverl"},{Name:"Server2"},

{Name:"Fool"},{Name:"Foo2"},}}現(xiàn)在需要做的是只要為Servers類型添加一個新的Filter()方法:ter返回包含name的服務(wù)器??盏膎ame將會返回所有服務(wù)器。sServers)Filter(namestring)Servers{ltered:二make(Servers,0)r_,server:=ranges{ifstrings.Contains(server.Name,name){filtered=append(filtered,server)}turnfiltered現(xiàn)在可以針對字符串Foo篩選服務(wù)器:ain(){rvers:=ListServers()rvers=servers.Filter("Foo")t.Printf("servers%+v\n",servers)哈!看到你的代碼是多么的簡單了嗎?還想對服務(wù)器的狀態(tài)進(jìn)行檢查?或者為每個服務(wù)器添加一條數(shù)據(jù)庫記錄?沒問題,添加以下新方法即可:sServers)Check()sServers)AddRecord()sServers)Len()w辻hContext封裝函數(shù)有時對于函數(shù)會有一些重復(fù)勞動,例如鎖/解鎖,初始化一個新的局部上下文,準(zhǔn)備初始化變量等等……這里有一個例子:

funcfoo(){mu.Lock()defermu.Unlock()//foo相關(guān)的工作}funcbar(){mu.Lock()defermu.Unlock()//bar相關(guān)的工作}funcqux(){mu.Lock()defermu.Unlock()//qux相關(guān)的工作}如果你想要修改某個內(nèi)容,你需要對所有的都進(jìn)行修改。如果它是一個常見的任務(wù),那么最好創(chuàng)建一個叫做withContext的函數(shù)。這個函數(shù)的輸入?yún)?shù)是另一個函數(shù),并用調(diào)用者提供的上下文來調(diào)用它:ithLockContext(fnfunc()){.Lockfermu.Unlock()()只需要將之前的函數(shù)用這個進(jìn)行封裝:oo(){thLockContext(func(){//foo相關(guān)工作ar(){thLockContext(func(){

//bar相關(guān)工作})}funcqux(){withLockContext(func(){//qux相關(guān)工作})}不要光想著加鎖的情形。對此來說最好的用例是數(shù)據(jù)庫鏈接?,F(xiàn)在對withContext函數(shù)作一些小小的改動:ithDBContext(fnfunc(dbDB)error)error{從連接池獲取一個數(shù)據(jù)庫連接Conn:二NewDB()turnfn(dbConn)如你所見,它獲取一個連接,然后傳遞給提供的參數(shù),并且在調(diào)用函數(shù)的時候返回錯誤。你需要做的只是:oo(){thDBContext(func(db*DB)error{//foo相關(guān)工作ar(){thDBContext(func(db*DB)error{//bar相關(guān)工作ux(){thDBContext(func(db*DB)error{//qux相關(guān)工作你在考慮一個不同的場景,例如作一些預(yù)初始化?沒問題,只需要將它們加到

withDBContext就可以了。這對于測試也同樣有效。這個方法有個缺陷,它增加了縮進(jìn)并且更難閱讀。再次提示,永遠(yuǎn)尋找最簡單的解決方案。為訪問map增加setter,getters如果你重度使用map讀寫數(shù)據(jù),那么就為其添加getter和setter吧。通過getter和setter你可以將邏輯封分別裝到函數(shù)里。這里最常見的錯誤就是并發(fā)訪問。如果你在某個goroutein里有這樣的代碼:"]=bar還有這個:(m,"foo")會發(fā)生什么?你們中的大多數(shù)應(yīng)當(dāng)已經(jīng)非常熟悉這樣的競態(tài)了。簡單來說這個競態(tài)是由于map默認(rèn)并非線程安全。不過你可以用互斥量來保護(hù)它們:k()〃門 〃1 〃」

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論