這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Golang使用心得
轉自:http://blog.jobbole.com/84554/
13年上半年接觸了Golang,對Golang十分喜愛。現在是2015年,離春節還有幾天,從開始學習到現在的一年半時間裡,前前後後也用Golang寫了些代碼,其中包括業餘時間的,也有產品項目中的。一直有想法寫點Golang相關的總結或者感想,決定還是在年前總結下吧。註明下:我只是Golang的喜好者,不是腦殘粉,也無意去挑起什麼語言之爭。
特性少,文法簡單。GO是崇尚極簡主義的,提倡少即是多。這點在它的Spec上尤其凸顯,一下午的時間絕對可以看完。GO的特性很少,很多GO的使用者都反饋,GO的關鍵字至少完全可以記在大腦裡。同時它的文法極為簡單,而且語義清晰。
部署方便。GO是一個強型別靜態語言,可以把代碼編譯為本地機器指令。它的RUNTIME是會在編譯時間一起連結到執行檔案中,這也就意味著我們不需要像JAVA那樣裝一個JVM。而且編譯出的執行檔案本身不依賴於其他動態庫,完全可以做到輕鬆的發布。當然,如果你用GO編寫了調用一些動態庫介面的代碼,那麼還是需要根據實際情況來部署這個動態庫的。這點在很多從python/java轉到go的朋友來說,非常喜歡。
有較完善的標準庫並且較為健壯。GO自身帶的標準庫是比較全面的,從檔案歸檔、壓縮、加密、資料庫到資料序列化,字元格式設定化、校正和以及網路程式庫、同步庫等應有盡有。基本上能夠滿足很多基本的需求了。更好的是,這些標準庫的品質都非常高,都很健壯。介面也較為簡單,有清晰的文檔說明。同時隨著這兩年的發展,GO的第三方庫也多了起來,雖然可能沒有像python那麼多,但是較其發展時間來說,還是非常不錯的。
整合測架構。在之前用C++寫代碼時,寫單元測試不是個容易的工作。需要一些技巧和努力才可以做起來。但是在GO中整合了單元測試架構,只要源碼檔案以_test.go結尾,就可以直接通過go test執行單元測試。同時還提供了代碼測試覆蓋率工具,可以很容易實施自動化測試。除此之外,還整合了基準測試架構功能,可以很容易的測量自己寫的函數的運行效率。另外,還有效能剖析器,可以在運行時,測試時剖析程式的瓶頸點,進而可以進行最佳化。
健全的代碼風格與檢查工具。當初學GO的時候,很多文章和書都會提到go fmt這個命令,統一了代碼風格。我覺著這點實在是解決了風格之爭了。帶來的影響就是別人寫的代碼感覺也是自己寫的一樣。還有golint,可以按照go team的風格和要求來寫代碼。還有go vet可以用來檢查一些在GO中很隱形坑。
簡單卻強大的包管理。GO的包管理可能在很多其他語言的包管理看來太弱了,但是在我看來,它解決了我需要的兩個問題,一個是循環相依性問題,GO是拒絕有循環相依性關係的包;二是包的初始化,每個包的檔案都可以實現一個init函數,用來在匯入時執行。這點在分工合作時非常有用。
容易編寫跨平台代碼。如果你用純GO,不牽扯到CGO的話,你可以非常容易的做到跨平台。只需要在檔案尾碼.go前,引入_linux,_windows,_x86,_x64等字元為檔案名稱首碼的結尾就可以做到只在對應的平台中編譯。GO還有build constraints控制碼在什麼條件下編譯。如果你用到了CGO,就牽扯到了C的跨平台問題,所以稍微麻煩那麼一些,但是問題也不是太大。
記憶體回收。GC的存在極大的降低了並發代碼的編寫,而且還提供了程式的健壯性。做為一個從C/C++做起,有過驅動開發經驗的程式員來說,GC這個東西是我一直沒有涉獵過的。對我來說GC就等於噩夢。但是當我開始試著接受GC時發現,GC真的是解決了程式員的生產力,極大的提高了效率。雖然目前來說GO已經1.4版本了,但是GC還算不得上優秀,按照GO的路線圖,後面會有更優秀的GC實現添加進來。
介面與struct。在第一次學習GO的interface的時候,我第一反應是這就是我想要的。雖然很多人也在說GO的這個interface的不好,而且說的很有道理,比如老趙的《為什麼我不喜歡GO語言式的介面》。interface可以通過組合擴充為新的interface,struct也可以通過組合擴充為新的struct。沒有繼承,只有組合。可以通過匿名組合達到類似繼承的效果。可以對interface進行查詢,有點類似COM的味道,但是文法層面上更為簡單。struct到interface的映射是隱式的,不需要聲明某個struct實現了某個interface。雖然可能會出現名字上的衝突,但是可以通過wrapper進行解決。這種interface的另外一個好處是單元測試時很容易實現MOCK,這點非常喜歡。也可以看這篇文章《Go interfaces make test stubbing easy》
統一的工作布局。GO定義了項目的目錄結構,比如bin目錄,pkg目錄以及src目錄。這個和我日常的項目布局是一致的。之前用C++開發時我們也是如此安排布局,所以就這點來說,我覺著容易過渡。
內建的並發原語。提到GO就不得不提到goroutine和channel。廉價的goroutine可以讓我們歡快的處理非同步任務,channel可以用來交換資料。藉助goroutine,可以很容易的實現高效能的服務端。goroutine及其調度器可以很容易和EPOLL,IOCP等系統機制結合起來,再通過Half Sync/Half Async模式,很容易在文法層面上達到同步形式,卻不失效能。
2013年初的時候還在做一個用戶端,當時使用了C++0x,其中印象最深的事情是lambad,std::function/bind,結合著線程+隊列的方式,可以很容易的實作類別似於Chromuim的執行緒模式。在處理UI與慢任務比如讀檔案,請求網路資料等互動時,為了保證UI的體驗,使這些任務非同步化是非常有必要的。如果有對Golang瞭解或熟悉的朋友就會明白,這是類似與Golang的goroutine+channel。但是在使用這個模型時,覺著還是繁瑣了些,我要注意對象的生命週期,這個在C++雖然有各種智能指標的協助,但是難免還會掛一漏萬。而且遇到稍微的複雜的問題,比如一個慢任務接著一個慢任務時,就會涉及到一個任務鏈,在沒有future/promise(Facebook開源的folly庫中有個不錯的實現futures,後來還發現WINDOWS有個基於actor model的並發庫Asynchronous Agents Library非常不錯,只可惜目前只在WINDOWS上使用。)機制的協助下很容易進入Callback Hell。這樣就會導致代碼相對來說比較難維護,而且容易滋生BUG。好在當時這個部分的代碼不是太多,而且也不是太過於複雜,很容易通過自測穩定下來。
上面雖然提到future/promise, AAL可以解決部分Callback Hell的問題,但是像future還是要用到callback。所以我在想,如果可以做到代碼層面上是同步式的,背後卻是非同步就爽了。GO就滿足了我這個需求。
GO標準庫中還提供了sync包,其中有基本的mutex說,還有RMutex這樣的讀寫鎖,還有Once,WaiterGroup等東西。基本滿足日常中對鎖的需求了。
GO為了協助程式員解決在並發時經常遇到的race condition問題,還提供了相應的race condition工具。還有相應的死結偵查工具。
雖然GO社區有個slogan:”do not communicate by sharing memory; instead, share memory by communicating.“,但是每個goroutine之間並不是完全獨立的,一樣可以做到通過記憶體共用資料。這個時候只能依靠程式員自己去遵守了。而且因為goroutine不是完全獨立,panic這種東西就可能會導致整個程式掛掉。這點和Erlang比起來確實不是很好。
蛋疼的defer。用習慣了C++的RAII後,十分反感GO的defer機制,但是有的時候又不得不用。原因就是這個defer不是block層級的,而是函數層級的,需要在函數返回前才得到執行。所以這就會導致在處理一些類似於檔案開啟,操作再關閉的邏輯時非常蛋疼,回到了C的年代,必須手動去Close。
蛋疼的panic。雖然我在C++下不怎麼用異常,但是對於panic這個設計我表示非常的不滿意啊。因為它會影響全域。而要捕捉panic就需要用defer。如果panic只是讓當前goroutine掛掉我覺著就嗨皮壞了。
沒有泛型。GO沒有泛型帶來的蛋疼地方是,要麼就用interface{}來做運行時泛型,要麼就自己手動寫代碼產生器。比如我自己為了產生網路通訊協定序列化代碼就擼了一個產生器。而且因為沒有泛型,想實作類別似C++ STL的容器與演算法基本沒太可能,當然方法還是有的,繼續使用代碼產生器。而且GO1.4乾脆引入了一個叫go generate的命令。
總結
GO裡面其他一些內建的資料結構,比如slice,map等,但這些也是詬病,因為它又沒給予程式員可以享用range關鍵字的福利。
在GO的所有特性裡,最喜歡就是GC,goroutine,channel以及interface。而其餘的特性(比如上面我列舉的很多特性)我覺著都不是太重要,其中很多都可以在工程中實踐,和語言本身沒有太大關係。
總結下來,這東西就是一個工程工具,各種好用,但是從設計角度講各種粗糙,沒必要過度高估。它算的上工程實踐中的好朋友。在寫服務端時,它是把利器,至少在寫服務端程式時,我自己感覺如此。
有朋友說一個語言好不好就看它有沒有開拓你的眼界,給予你新的思想,我想至少GO在這點上滿足了。