這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
繼續昨天的:[翻譯]Go程式設計語言,或者:為什麼除了它,其他類C語言都是垃圾(1)。
總算切入正題,開始說 Go 了。
———————–翻譯分割線———————–
Go程式設計語言,或者:為什麼除了它,其他類C語言都是垃圾
(譯註:續[翻譯]Go程式設計語言,或者:為什麼除了它,其他類C語言都是垃圾(1))
進入 Go 的世界
概述
在第一次聽說 Google 的新程式設計語言時,我有一些懷疑。於是忽略了那條新聞。在那之後,下一代新的、偉大的語言就充滿了各個地方。其中一些享受於璀璨奪目,然後就暗淡消沉;有一些走上了邪路;還有一些截止現在已經準備了十年的發布。
過了一段時間,我再次與它相遇。這回我湊近看了看。有一個我之前沒有留意的事情:其中一個發明者是因 Unix 和 Plan9 而聞名遐邇的 Ken Thompson, 而他同樣也間接參與了 C。那麼如果新的程式設計語言是由曾經參與過大型主機時代的領軍任務人物設計的,那麼可能還是有料的。
因此 Go 到底可以給我什麼是 Perl、Python 和 JavaScript 無法做到的?它能做什麼是它們之前可以做到的?是什麼使得它與其他失敗或者有限的成功的語言不同的?它會在以後的 30 年裡稱雄嗎?以及最重要的:它能滿足我的需求嗎?
第一次接觸
Go 的一個重要事實是它面向的使用者群。它是按照系統程式設計語言設計的,所以瞄準的是底層軟體,甚至是 OS 的核心。因此,由於複雜度和與硬體結構的吻合度,高層次的構架可能會缺失。有趣的是,大部分便利卻仍然存在。
接下來瞭解到的是,這是一個 C 血統的語言,因此使用 US 鍵盤配置讓花括弧容易按。但是當閱讀例子代碼時,它看起來並沒有想象中的那麼像 C 系的。更少的括弧,看不到分號,幾乎沒有變數聲明,至少第一看看上去沒有。文法相當輕量,有著明顯不同的保留字和控制結構,但是還是很好懂的。
與 C 不同的要點
對於那些熟悉 C/C++ 的人,來一起對那些不同做一個快速的對比:
沒有分號!
不是玩笑!當然,實際上是有分號的,但是被省略了。這就像 JavaScript,有簡單的規則讓文法分析器在正確的行結束處添加分號。而這個規則相當簡單,所以它很容易在源碼處理工具中實現。
OTBS(譯註:K&R樣式)
接下來是“真正的邪端”:Go 定義了聲明規範和 One True Bracing 樣式(譯註:就是 K&R 的代碼樣式)。而 RMS(譯註:Richard Stallman)可能不會對此感到高興。 以至於提供了 gofmt,一個用標準格式化代碼的工具。當然,Java 開發人員已經這麼做了,當他們停留在一些導致腦癱的情況下(縮排為2?SRSLY(譯註:我真不知道這是什麼,另一種代碼格式?)?)。Go 代碼的格式化可以歸納為:
用 tab 縮排(這讓使用者可以按照他舒服的空格數量設定編輯器)
花括弧與其隸屬的控制語句在同一行
折行不能以反花括弧或標識符結束,例如操作符留在上一行,而不是新行的開始。
簡單的分號插入原則的結果其實導致了第三點。我希望能有辦法繞過它,因為我喜歡串連的操作符在折行的開始,來強調發生了什麼,而不是將其隱藏在一堆東西的後面(譯註:開始我也覺得這點有點噁心,不過習慣了之後,就會先從後面看起了)。
不過除了這個,其他多數都還是非常明智的。許多人已經深入的解釋了這個。要點是更少視覺幹擾的可讀性。就像 Python 那樣,只是我覺得 Python 看起來過於缺少可視的提示。縮排並不總是足夠清晰的,所以仍然保留心愛的花括弧吧。
強制花括弧
關於花括弧,在 if 和 loop 上是沒有無花括弧的模式的,我認為這令人遺憾。代碼樣式純粹論者可能會喜歡它。但是最終,我並不在意這個。對於確實短的語句,我可以這麼做
if cur < min { min = cur }
雙選項
前一個要點的重要的技術原因之一是控制語句可以接收雙選項。只有 Perl 6 在沒有花括弧的情況下會嘗試解析,而我們都知道 Perl 的文法解析器之前有多麼複雜(顯而易見,現在也是)。所以,這實際上是一個關於什麼是強制的,什麼不是的抉擇。由於在多數情況下都需要用到花括弧,所以這是非常明智的。這閱讀起來不同尋常,必須適應有這樣的劃分,但是一旦習慣了它,Go 代碼感覺比 C 代碼更輕量一些。
明確命名類型、函數和變數
為了說明這個,需要用到保留字 type、func 和 var。這很清晰,會有一個很好的閱讀“流”。技術上的原因,接著讀吧。
隱式定義,自動設定類型
變數總是靜態指定類型,就像 C 那樣。但看起來並不是這樣。如果你不指定類型,類型代由指派陳述式指定。利用新的定義和初始化操作符,甚至可以連同聲明一起省略:
foo := Bar(1,2,3)
這定義了叫做 foo 的變數,並向其賦 Bar 類型的值。這個作用與
var foo Bar = Bar(1,2,3)
完全相同。
這並不是在介紹動態類型,這不允許改變已經定義了的變數的類型,也沒有將變數定義的需要移除,這不允許將變數定義兩次。這和之前完全一樣,語義上,在文法上輕量很多。感覺像特定的指令碼語言,但是仍然有靜態類型的好處。
倒序的變數定義
在 Go 中,變數的類型和函數傳回值的類型在名字之後,要用
var amount int
代替
int amount;
這補充了之前可以省略顯示的類型定義的特色,提供了更加完整的概念。
沒有運算的指標
仍然有指標,只是它們現在僅作為值的引用。所有的對象是實值型別,因此賦值會複製整個對象。指標提供了在 Java 中作為預設的引用語義。但是沒有指標運算。你被強制像數組這樣的方式來訪問,這為安全信任問題的解決帶來了迴旋的餘地。是的,先生,我們都有邊界檢查!
因此,指標不再是演算法的核心。它們僅服務於一個目的,引用 vs. 值的語義。這使得無法通過指標訪問未引用的成員的實現變得簡單。foo.Bar() 同時工作於指標和值的情況下。
記憶體回收
了之前的要點大量描述了是否能傳遞值以及所有的指標是否都是安全的概念,以及對於每個和所有變數來說是否能夠擷取地址,就像你在 C 和 C++ 中不能做的。
然後:記憶體管理是由記憶體回收處理的。最終!通過整合 boehm-gc 這平衡的類記憶體回收使得可以安全的傳遞一個指標到局部變數,或者擷取一個臨時變數的地址,這都將正常工作!嘢!
對那些沒有與時俱進的研究記憶體回收的人來說,可能會不僅僅對 GC 解決了若干使用 malloc/free 的錯誤導致的安全性感興趣,可能還關注 GC 是否足夠快。一個適當的記憶體回收實際上可以比手工記憶體管理更快,通過恰當時間的延遲記錄,或通過重用未使用的對象完全避免記錄。一些更加進階的 GC 將這些聯合使用,使得記憶體更加有效率,緩衝使用記憶體減少片段的產生。這不再是 C64 了。
現階段 Go 的 GC 相當簡單,但是一個更加進階的實現進行中中。在大多數情況下,GC 是勝利者,但是它當然也有局限。在一些臨界情況下,可以使用預指派至的數組。
變長數組
這與那些在運行時就確定了大小,並不再變化的數組無關。這是那些你可能不得不使用 realloc() 的東西。數組總是有固定的大小,這是來自 GNU-擴充 C 的一個小的向下相容。但是,作為代替,就有了 slice。
slice 看起來和感覺起來都像數組,但實際上它們只是映射到一個固定大小的原始數組的一個子範圍。由於有記憶體回收,slice 可以引用到匿名數組。這是獲得動態大小的數組的真相。有內建函數用於重新指定 slice 的大小,並且當底層數組過小時進行替換,因此在其上編寫一個向量類也是很容易的。
反射
Go 支援反射,也就是說,可以查看任何類型,並擷取其類型資訊、結構、方法等等。Java 和 C++ RTTI 支援這個,但是 C 裡面沒有。
不定大小常量
常量可以不指定類型,甚至不指定大小。數字常量可以用在其數字是合法的任何上下文中,並且會使用相關的資料類型指定的精度。常量運算式用完整精度進行計算,然後當需要時截斷到目標類型的精度。沒有類似 C 中的常量大小的尾碼。
錯誤處理
Go 沒有異常。等等——是嚴肅的嗎?這藐視了常識中接受的關於安全和穩定的程式的常規!這不是巨大的倒行逆施嗎?
實際上不是。誠實的說,什麼時候你很好的檢查並處理了異常?大多數時間,它們是這樣的:
try { openSomeFile(); doSomeWorkOnTheData(); yadda... yadda... yadda... closeItAgain();} catch (IOException foo) { alert("Something failed, but there's not enough time to do proper error handling");}
羅嗦,增加一個縮排層級而沒有任何好處,也沒有解決問題的根源,開發人員的懶惰。如果想要單獨處理每一個調用的錯誤,如此羅嗦的代碼使得更加不清晰。
因此總是可以在複雜的代碼過程上回到學校中按部就班的錯誤處理。傳回值使用了許多年了,但是 Go 有多值返回這個很好的現代功能。所以忘記會導致腦癱的 atoi() 吧,我們有附帶的標記。為了那些在乎的人們。為了那些不在乎的人們,那些即使 Java 嘗試強制要求錯誤處理,也都不在乎的人們。
這裡有 panic。它用於“可能無法繼續”類型的錯誤。嚴重的初始化錯誤, 威脅正常的資料和運算的情況,諸如這類問題。無法恢複的錯誤,簡單來說。語言的運行時環境也可能產生 panic,例如數組越界。
當然,這將我們帶回到清理使用過的資源這個問題了。為了這個,defer 語句出現了,並且它相當優美,讓錯誤處理屬於它應在的地方,正確的對待問題:
handle, err := openSomeFile()
if err != nil { return nil, err }
defer closeSomeFile(handle)
return happilyDoWork()
defer 非常像 Java 中的 finally 分支,但它看起來更像修飾函數調用的。它確保了 closeSomeFile 被調用,無論函數是如何退出的。另一方面,當成功時也可以越過關閉它。很少的代碼冗餘,簡單並且明了的錯誤處理。多個 defer 也是允許的,可以按照 LIFO 順序正確執行。
對於那些在 panic 後想要繼續的情況下,有 recover。panic 和 recover 一起就可以實現通常意義上的異常。由於它們會引起程式流程的混亂,官方建議非致命 panic 不應該越過包的邊界。對於錯誤處理沒有核心思想,所以兩種情況最好都處理好,並且應當手工選擇對於任務來說不複雜的那個方法。
控制結構
感謝 range 保留字使得僅有的 for 迴圈可以像 foreach 那樣工作。在特殊情況下使用 for,這種文法使得更輕量(看上面),這讓我感覺很好。或者更好的:可以將標籤放在嵌套的迴圈中,通過它們可以一次跳出多層。完美了!
同樣可以用 goto 拿自己來打靶。好吧,開個玩笑,那些邪惡的東西只是簡單禁止。但是如果誰願意,為了一些簡單的理由也可以這麼做。
這些僅僅是一些概覽。還有一些細節,等會會涉及到,但是所有一切對於 C 來說都是重大的改進。在許多個快樂的夜晚編寫沒有對象的過程代碼就已經足夠了,不是嗎?
———————–翻譯分割線———————–
一個德國人,用了這麼多英文生僻的詞語,這麼多生僻的用法,這麼多俚語和縮減語,他是怎麼做到的?怎麼做到的呢?求秘籍啊!求秘籍啊!
今天就到這裡吧……還有好多,好多內容沒完成。
這位德國人,有著他們血統的認真與執著。好吧,他大段大段的增加了內容。我也跟隨補充了“錯誤處理”和“控制結構”。