5 things about programming I learned with Go By MICHAŁ KONARSKIgit
Go在最近一段時間內開始變得十分流行。語言相關的論文和博客天天都在更新,新的golang相關的項目在github中也層出不窮。Go語言的會議也吸引了愈來愈多的開發者的關注。Go語言的時代已經來臨,而且當選了TIOBE的2016年度語言,並一度進入流行度前十。程序員
我一年前開始接觸golang,而後決定試一試。通過一段時間的接觸,我發現這絕對是一個值得學習的語言。即便你不打算長期使用,學習一段時間也會是你的編程技巧有很大的提高。接下來我會告訴你們我學習golang的過5點感悟,並且這五點感悟對其餘編程語言也有用。github
我平常使用的編程語言是Ruby,我很是喜歡動態類型語言。這一特性使得語言很是容易學習、使用,而且開發效率很是高。隨着項目愈來愈多、愈來愈複雜,代碼變得不像其餘靜態類型安全語言那樣安全可靠。及時我十分謹慎的測試代碼,仍不能覆蓋到全部的邊緣情況,所以常常出現不但願出現的情況。那麼,有沒有哪一種語言既有着動態語言的特性又有靜態類型安全語言的可靠性。答案是確定的,咱們來說一講Go!golang
如今有一些爭論是關於golang究竟是不是面相對象的編程語言[1] [2] 。可是golang有個面相對象語言的特性--接口。格式上來看,和麪相對象的語言Java比較類似,一個包含不少方法的結構體:編程
type Animal interface { Speak() string }
固然,golang也有類的等價實現--結構體。結構體也能夠是數據和方法的封裝:安全
type Dog struct { name string }
而後咱們可使用該結構體做爲方法接收器--receiver,相似於類的成員方法:網絡
func (d Dog) Speak() string { return "Woof!" }
這不就是面相對象的三大特性之一--封裝麼。併發
和其餘面相對象語言不一樣的是,方法聲明在結構體外。golang的做者但願給結構體的使用者更多的靈活性。即便 你不是結構體的做者,你也能夠自由的爲它加上新的「成員方法」。異步
那咱們怎麼作到相似多態呢?很簡單:編程語言
func SaySomething(a Animal) { fmt.Println(a.Speak()) } dog := Dog{name: "Charlie"} SaySomething(dog)
Dog
實現了接口Animal
的全部方法,就能夠做爲Anmial
來使用,不須要主動的聲明。這種行爲被稱爲a statically typed duck typing。
「If it quacks like a duck, then it probably is a duck」.
正是由於接口的這種特性,可讓咱們像使用動態類型語言同樣使用golang,卻同時獲得類型安全的保障。
在以前的blog中我描述過一個問題,若是過分的使用面向對象的特性,咱們會讓本身陷進去。舉個例子,一個需求最初能夠用一個類來建模,而後逐漸擴展,在某種程度上,繼承彷佛是不斷增加的需求的完美答案。不幸的是,這樣作致使咱們有了一棵緊密相關的大樹,在那裏添加新的邏輯的同時想要保持簡單性和避免代碼重複是很是困難的。
我對這個故事的結論是,若是咱們想要減小在代碼複雜性中迷失的風險,咱們須要避免繼承而選擇組合。改變觀念很是困難,而使用一種不支持繼承的語言可以幫得上忙,你猜的對,就是Go。
Go的結構體設計的時候沒有繼承的概念。Go語言設計者是想保持語言的簡單和清爽。他們發現繼承不是必須的,可是他們保留組合的特性。舉個例子,汽車包含引擎和車身,使用兩個interface來表示:
type Engine interface { Refill() } type Body interface { Load() }
如今,咱們須要建立一個結構體Vechicle
組合上述接口:
type Vehicle struct { Engine Body }
發現什麼奇怪的地方了麼?我故意省略了接口類型的字段名。所以,我使用了叫作嵌入(embedding)的特性。這樣,咱們使用Vehicle
的實體能夠直接調用接口中的方法。咱們能夠方便的使用組合。代碼以下:
vehicle := Vehicle{Engine: PetrolEngine{}, Body: TruckBody{}} vehicle.refill() vehicle.load()
channels 和 goroutines 是很是酷的工具幫助咱們解決併發問題。
Goroutines 是Go的 green threads 由go自行管理和調度,並且佔用很是少的系統資源。
Channel 是一個管道,能夠用做協程間的通訊。它可讓協程間方便的進行異步通訊。
這裏給出一個 Goroutine 和 Channel 共同工做的例子。假設咱們有個方法執行一個耗時的計算任務,咱們不但願它阻塞進程,咱們能夠這樣作:
func HeavyComputation(ch chan int32) { // long, serious math stuff ch <- result }
正如你看到的,這個方法接受一個channel類型的參數,一旦計算出結果,就將結果放到channel中便可。那咱們怎麼調用這個方法呢:
ch := make(chan int32) go HeavyComputation(ch)
這裏的go
關鍵字能夠很是方便的進行異步處理。Go會新建一個協程執行HeavyComputation(ch)
,而後程序能夠不阻塞的執行其餘任務。獲取結果也很是簡單:
result := <-ch
當ch
裏有計算結果的時候,能夠直接讀出,不然將阻塞直到計算協程放入結果。
channels 和 goroutines 是很是簡單可是很是有效的併發處理機制。
(這裏標題沒有翻譯,由於英文你們更熟悉。)
傳統的編程語言通常在標準庫裏提供多個線程訪問同一塊共享內存的方法。爲了同步和避免同時訪問通常採用加鎖的方法。可是因爲Go有 goroutines 和 channels 可使用其餘的方法。與加鎖的方式不一樣,Go能夠方便的使用channel
來實現,保證了同時只有一個協程可以改變其內容。Go 的官方文檔給出瞭解釋:
One way to think about this model is to consider a typical single-threaded program running on one CPU. It has no need for synchronization primitives. Now run another such instance; it too needs no synchronization. Now let those two communicate; if the communication is the synchronizer, there’s still no need for other synchronization.
這絕對不是一個新概念,可是對於不少人來講,對於任何併發問題,加鎖仍然是首選的解決方案。固然,這並不意味着鎖是無效的。它能夠用來實現簡單的東西,好比原子計數器。可是對於更高層次的抽象,最好考慮不一樣的東西。
注:我認爲做者的意思是,更復雜的場景下,若是能更關心業務而不是加鎖解鎖的邏輯,系統會更加可靠。實際上channel的實現就是幫咱們集成了加鎖和解鎖的過程,當一個協程操做channel的時候,都會伴隨的加鎖和解鎖的過程。想詳細的瞭解,能夠參考Go語言中channel的實現。
通常語言都有異常捕獲和異常處理的概念。Go不一樣,Go在設計的時候沒有異常的概念。這彷彿把缺乏一個特性當成了Go的特性。可是仔細想,它是有用的。當出現錯誤的時候,咱們沒有辦法肯定究竟是那種錯誤--磁盤空間不足?IO網絡問題?若是是捕獲異常可能須要包含全部類型的異常。Go給出了不一樣的解決方案,將錯誤當作返回值。
f, err := os.Open("filename.ext"). if err != nil { fmt.Println(err) return err } // do something with the file
坦白說,這不必定是最優雅的解決方案,但這是最有效的方法鼓勵開發者處理錯誤。
Go是一種有趣的語言,它提供了一種不一樣編寫代碼方法。它丟棄了一些咱們從其餘語言中瞭解的一些特性,好比繼承或異常。相反,它鼓勵用戶使用本身的工具集來解決問題。所以,若是您想要編寫可維護的、乾淨的、健壯的代碼,您不妨以一種不一樣的、相似於Go的方式開始思考。這是一件好事,由於您在這裏學到的技能能夠在其餘語言中成功使用。你的年齡可能會有所不一樣,但我認爲一旦你開始接觸Go,你很快就會發現它可以幫助你成爲一個更好的程序員。