Go 語言之道 [譯]

近期閱讀了 John Arundel[1] 的文章《The Tao of Go》[2],看完後我都有心去閱讀一遍《道德經》了:)。作者將 Go 語言設計哲學與慣例 [3] 與 “道” 學三寶有機的聯繫到一起,給了我不小的啓發。這裏譯成中文,供大家參考。

你可以讓水牛去任何地方,只要它們想去 - 傑拉爾德・溫伯格 《諮詢的奧祕》[4](譯註:原文似乎將 Gerald M. Weinberg 寫成了 Jerry Weinberg,應該是筆誤)

“道” 是指事物的內在本質或自然趨勢。例如,水向低處流:這就是水的 “道”。你可以築壩、疏導、抽水,或以其他方式干擾它,但儘管你做了種種努力,它最終可能還是會流向它要去的地方。

你遵循 “道” 並將其作爲你的處事原則,就是要對事物的自然趨勢保持敏感,不浪費精力與之抗爭,而是順勢而爲,而非逆勢而爲

擴展一下水的比喻,一個泳技糟糕的游泳者在水中亂撲騰,製造了很多噪音和騷動,但卻沒有真正前進。而道家,則是在水中衝浪 (譯註:藉助波浪,順勢而爲)。

那麼什麼是 Go 語言之道呢?如果我們以一種敏感的、聰明的方式來進行 Go 的軟件開發,遵循語言和問題的自然輪廓,而不是試圖用推土機把它們推開,那會是什麼樣子呢?讓我們試着建立一些一般原則

一. 仁慈 (Kindness)

我有三寶,持而保之。一曰慈 (kindness),二曰儉 (simplicity),三曰不敢爲天下先 (humility,謙遜)。- 《道德經》[5]

用仁慈 (譯註:最大程度的善意) 和同情心編寫程序是什麼意思呢?它意味着我們爲人而不是爲計算機寫代碼。人容易犯錯,沒有耐心,缺乏經驗,注意力不集中,而且在其他方面也不完美。我們可以通過在 Go 代碼的設計和細節上花些心思 [6],使大家的生活(和工作)變得更加輕鬆。

我們可以善待我們的用戶:給我們的庫起一個描述性的名字 (譯註:比如像 net、os 這樣的名字),讓它們容易導入,提供良好的文檔,併爲它們提供寬鬆的開源許可。我們可以設計深度抽象,讓用戶利用小而簡單的 API 來訪問強大而有用的行爲。

我們可以善待那些運行我們程序的人,使他們易於安裝和更新,要求最少的配置和依賴,捕捉最常見的使用錯誤和運行時的錯誤,給用戶提供有用的、準確的和友好的信息,告訴他們出現了什麼錯誤以及如何解決。

聰明是一種天賦,仁慈是一種選擇。 - 傑夫・貝佐斯

我們可以通過儘可能的清晰 (clear)、簡單(simple) 和明確 (explicit) 來善待那些不得不閱讀我們代碼的人。我們可以給類型和函數起一個在上下文中有意義的名字,讓用戶用我們的抽象概念在直接的、邏輯的組合中創建自己的程序。我們可以通過遵守慣例、實現標準接口和以明顯的方式做明顯的事情來消除認知上的障礙和速度上的障礙。

在代碼審查中,我們保持溫和並多用鼓勵性的語言。我們在別人的工作中找到值得稱讚的地方,我們不會因爲別人犯錯或忽略細節而把他們當成傻瓜。如果我們對自己坦誠,我們會承認我們也會犯同樣的錯誤。我們也知道,接受批評對我們來說是痛苦和困難的,但我們可以用仁慈來調節以應對對自己的批評。

最後,我們也要對自己仁慈,通過編寫優秀的測試,使我們的程序在未來容易理解、修正和改進,當我們發現錯誤或設計缺陷時,不要對自己生氣。

一旦代碼倉庫變成了意大利麪條,就幾乎不可能修復。 - John Ousterhout, 《軟件設計的哲學》[7]

另一個對我們未來的自己和我們的繼任者仁慈的方法是,對程序的結構和整體設計進行小的、持續的改進。好的程序能活很久(當然,有一些糟糕的程序也能活很久),許多小改動的累積效應通常會使代碼庫變得混亂、複雜和笨拙。我們可以花一點額外的時間來幫助避免這種情況的發生,每當我們爲某些事情訪問代碼庫時,我們就可以重構和清理它。因爲我們很少有機會從頭開始重寫系統,所以長期投入少量時間進行微改進是保持系統健康的唯一實用方法。

二. 簡單 (Simplicity)

“道”教給我們的第二種美德是節儉 (frugality)、謙虛(modesty)、簡單(simplicity):以小博大,消除雜亂。Go 本身就是一種“節儉” 的語言,擁有較少的語法和表面積(譯註:可能是較少的 API 的意思)。它並不試圖做所有的事情,或者取悅所有人

我們的生活被瑣碎的細節浪費掉了。簡化,再簡化”— 亨利・大衛・梭羅《瓦爾登湖》[8]

我們也應該這樣做,使我們的程序小而聚焦,不混亂,做好一件事。深度抽象爲強大的機器提供了一個簡單的接口。我們不會讓用戶爲了獲得調用我們庫的特權而做大量的文書工作。只要我們能夠爲最常見的情況提供一個簡單的 API 和合理的默認值,我們就會這麼做。

靈活性是一件好事,但我們不應該試圖處理每一種情況,或提供每一種功能。可擴展性是很好的,但我們不應該爲了我們目前還不需要的東西做出妥協而損害一個簡單的設計。事實上,一個簡單的程序比一個複雜的程序更容易擴展。

我有最簡單的口味。我總是對最好的東西感到滿意。 - 奧斯卡 - 王爾德,引自 Edgar Saltus,"奧斯卡 - 王爾德,一個閒人的印象"

我們不會用函數、類型、接口、回調、參數和功能選項 (options) 來壓垮用戶。最小的 API 是最好的,因爲它需要最少的知識來使用。我們不會用幾十個包和子文件夾的子文件夾來將我們的 module 複雜化。我們不會採用無休止的命令行標誌或要求用戶編寫冗長的配置文件。

我們滿足於重複大塊的代碼,而不是純粹爲了滿足我們保持代碼乾燥的願望而發明不必要的抽象概念。如果我們可以通過爲幾種不同的類型實現相同的函數來解決問題,我們就不寫複雜的代碼生成器或泛型函數 (譯註:Go 1.18 泛型落地後,有些時候使用泛型函數感官上代碼更爲簡潔)。如果一個方法自然有點長,我們會讓它長,而不是積極地把它重構爲不必要的子函數,只是爲了讓每個子函數都能有幾行長。

如果一個就夠了,我們就不寫十個測試。如果只需要一個函數,我們就不創建一個接口。我們不要讓用戶實現我們的接口,而是要實現他們的接口。

The way out is through the door. Why is it that no one will use this method? — 孔子 (譯註:沒找到對應的論語原文,直譯又明顯沒那個味道兒,於是不譯了)

我們是明確的 / 顯式的 (explicit);我們避免魔法。我們不在沒有幫助的地方使用併發性。我們讓包自成一體,與其他包解耦,我們避免讓一個包或 API 的類型泄漏到我們代碼庫的其他部分。我們設定明顯的內部和外部界限,並加以執行。

我們節約資源;我們避免泄漏,並在必要時使用盡可能少的內存或 CPU。我們高效地處理數據流,而不是將其放入大塊的內存中。我們產生的垃圾越少,需要收集的就越少。我們不在不需要的地方傳遞 Context。

我們不糾結於性能問題。Go 是高性能的。但我們的代碼可能不需要這麼快;至少,不需要以犧牲簡單性爲代價來換取高性能。

正如 Go 諺語 [9] 所說:我們接受接口值。這樣我們就需要對它們是什麼做出最少的假設,但我們會返回具體的值(結構),這樣用戶就不用爲它們編寫大量的類型斷言。

三. 謙遜 (Humility)

第三件寶物是謙遜。像水一樣,道家尋求低調,不爭,不比,不試圖打動別人。Go 本身是謙遜和務實的:它沒有其他語言的所擁有高科技功能特性和理論優勢。事實上,它故意忽略了許多其他語言的重要賣點。它的設計者對創造一種令人印象深刻的編程語言或在人氣調查中名列前茅並不感興趣,而是想爲人們提供一種小型而簡單的工具,以最實用和最直接的方式完成有用的工作。

Go 認識到我們很容易犯錯,因此它提供了很多方法來保護我們不犯錯。它負責分配內存,清理我們已經用完的東西,並警告我們有未使用的包導入或變量。它是爲那些知道自己不是什麼都懂,並且瞭解自己的錯誤傾向的人 (換句話說,是謙虛的人) 設計的語言。

最危險的錯誤是沒有認識到我們自己的錯誤傾向。 - 巴塞爾 - 利德爾 - 哈特

作爲 Go 程序員,我們寫代碼不要顯得過於聰明,我們通過這種方式來保持謙遜。我們寫代碼並不是爲了給大家留下我們是多麼了不起的程序員的印象:相反,我們滿足於做明顯的事情。我們清楚而直截了當地表達自己,而不覺得有必要把自己的個性強加在代碼中。

當標準庫能解決問題時,我們就使用它,而只有在它不能解決問題時才使用第三方庫。如果有一個堪稱事實標準的包,我們就使用它:如果它對別人足夠好,對我們也足夠好。

我們避免通過 panic 或調用 os.Exit 或 log.Fatal 來意外地終止用戶的程序,因爲我們認識到,我們沒有足夠的智慧來事先確定問題是否真的是致命的。相反,我們會在問題發生時處理一切可以處理的事情,而當我們不能處理時,我們會謙遜地返回一個錯誤,並提供有用的上下文信息,讓我們的用戶來決定如何處理。

我們可以認識到,我們並不瞭解所有的事情,我們也無法做出非常準確的預測(尤其是對未來的預測),所以我們不應該浪費時間和精力來預先設計我們可能永遠不需要的東西。我們不認爲我們最瞭解其他的軟件是否會想和我們的軟件一起使用,所以我們不會硬性規定對它的依賴。

我們假設我們寫的任何東西都會包含 bug,所以我們寫了詳盡的測試代碼,試圖引出意外的行爲或不正確的結果。我們明白不可避免地會有一些我們不知道或無法正確預測的重要事情,所以我們不會爲了現狀而過多地優化代碼,因爲很多工作最終都會被浪費掉。

以學生的姿態(不斷學習),永遠不要覺得已經長大而不問問題,永遠不要覺得知道得太多而拒絕學習新事物。- 奧格 · 曼狄諾, 《世界上最偉大的銷售員》的作者

當我們審查別人的代碼時,我們不會自動假設我們是最瞭解的:我們很樂意向任何有東西可以教我們的人學習。如果有些東西看起來很奇怪或不對,我們就會問:"從什麼角度看這是有意義的?我沒有什麼信息可以解釋爲什麼這是必要的?"

我們把我們的評論當作問題,以真誠而不是諷刺的方式提出:這有必要嗎?如果...... 會怎樣?你有沒有考慮過......?如果...... 會不會更好?我們尊重別人的時間,就像尊重自己的時間一樣,所以我們不要求他們提供不必要的信息,或者只爲了符合我們喜歡的風格而做出微小的改變,或者坐在走形式的會議中浪費時間,或者寫多餘的狀態報告。

我們知道我們並不總是正確的。明智的人可以以一種文明和建設性的方式對事情提出異議。如果我們把人們當做白癡,那麼當他們反應不好的時候就不應該感到驚訝。相反,我們一開始就假設對方是理性的、體面的,並根據他們對情況的最佳理解,真誠地行事。有時情況並非如此,但這仍然是正確的默認假設,直到他們最終證明不是這樣。

在要求別人審查自己的代碼之前,我們會謙虛地審查自己的代碼,因爲如果我們這都懶得做,他們爲什麼要做?我們花時間逐行閱讀,像一個新的用戶或開發者那樣閱讀,遵循邏輯逐一論證:從哪裏開始閱讀是否很清楚?程序是否在一開始就引入了關鍵的類型或常量並說明它們是如何使用的?命名是否清楚並準確地指明瞭它們的作用,或者它們在十幾次重構中是否變得混亂和過時了?程序是否整齊自然地融入了它的結構,或者它在某些部分過度堆砌,而在其他部分奇怪地留下了空白?

因爲我們沒有被自己的聰明和優雅所束縛,我們不需要把三四個不同的想法塞進一行代碼中。相反,我們把邏輯清晰、簡單、顯式地列出,一步一步地,一個一個地陳述,一點一點地,以讀者所期望的方式準確地做必要的事情。在無法做到這一點的地方,我們會不厭其煩地向讀者解釋他們需要知道什麼才能理解發生了什麼。

因爲我們知道我們不是天才,我們不可能把程序寫得那麼出色,不言自明,所以我們在解釋上花了一些功夫。我們爲代碼提供文檔,不僅說明程序的作用,而且說明如何用它完成用戶可能想要做的事情。文檔還包含了詳細的使用例子,準確地顯示出需要做什麼,從頭開始,以執行現實的任務,當它完成時,用戶應該期望看到什麼,以及他們接下來應該做什麼,並且我們嚴格地定期檢查這些例子,以確保它們仍然有效。

四. 無爲 (Not Striving)

我們已經談到了一些將仁慈、簡單和謙遜這三件寶物應用於用 Go 編寫軟件的方法。這些品質已經存在於每個人身上,即使它們在一些人身上隱藏得很好。同樣地,每個人都已經知道如何在編程和生活中遵循 “道”。的確,他們不能不這樣做。但是,一旦你理解了這個事實,並停止在所有事情上做這樣的掙扎,生活就會變得更加有趣。

道給我們的最後一個教導是無爲(not striving)。這有時會被誤解爲懶惰、退縮或被動;恰恰相反。努力工作並不總是意味着工作出色。我們都知道那些長期忙碌的人,總是匆匆忙忙,手忙腳亂,活動頻繁,但他們似乎從未真正取得過什麼成就。而且,他們過得很痛苦,因爲他們也知道這一點。

相反,我們經常在別人看來我們什麼都沒做的時候以最佳狀態完成我們的工作:在一個美麗的日子裏在河邊散步,或者坐在門廊上看蜘蛛結網。如果我們能聰明地從忙碌中停下來,只需一分鐘,正確的想法往往就會直接出現在我們的頭腦中。

與其把每個問題都當作要攻擊的敵人、要攀登的山峯或要拆毀的牆壁,我們可以使用 “無爲” 原則(有時 “不強迫(not forcing)” 會是更好的翻譯)。我們可能都有過這樣的尷尬經歷:無果地推一扇頑固的門,最後才意識到這扇門對拉動的反應更好。在我們的日常工作中,我們忽略了哪些小跡象表明我們應該拉而不是推?

解決問題的心態是好的,但消除問題則更好。我們怎樣才能重新規劃這個問題,使其消失?對需求的重述會使解決方案變得微不足道,甚至顯而易見?是否有一個簡單而優雅的設計是我們沒有看到的,因爲我們專注於一些被證明是不相關的細節?我們是否可以不用嘗試解決這個問題?最好的優化是根本不做這件事。

把編程和打字混爲一談是一個常見的錯誤。如果有人只是坐在那裏盯着空間,那就不像是在做什麼有用的事情。但是,如果他們在鍵盤上瘋狂地敲擊,我們就會認爲他們在做什麼。事實上,真正的編程發生在打字之前,有時甚至代替了打字。當我們構思了一段非常好的程序,我們唯一需要按的鍵往往是刪除鍵。

真正有趣的是,你不需要相信我的話來證明道家原則在編程中的有效性,或者在生活的其他領域。世界本身會教你什麼是有效的,什麼是無效的,以及如何分辨它們。在行使你的仁慈、簡單和謙遜方面做一些小實驗,看看會發生什麼,感覺如何。

你不必稱它爲 “道 ",如果這讓你感到惱火。這只是一個人爲編造的詞。如果你一直知道做事情有正確的方法和錯誤的方法,而且你認爲我只是用了一個花哨的中文名字而沒有說什麼新的或有價值的東西,你是對的。

下次你遇到問題時,試一次不努力或不強迫,看看是否可以溫和地鼓勵問題自己解決。如果你發現自己在努力把水牛送到你想去的地方,就不要再掙扎了。問問自己,你是否能找到水牛想去的地方,也許那可能不是它的最佳位置。


Gopher Daily(Gopher 每日新聞) 歸檔倉庫 - https://github.com/bigwhite/gopherdaily

我的聯繫方式:

參考資料

[1]  John Arundel: https://bitfieldconsulting.com/

[2]  《The Tao of Go》: https://bitfieldconsulting.com/golang/tao-of-go

[3]  Go 語言設計哲學與慣例: https://book.douban.com/subject/35720728/

[4]  《諮詢的奧祕》: https://book.douban.com/subject/25785829/

[5]  《道德經》: https://book.douban.com/subject/35461183/

[6]  在 Go 代碼的設計和細節上花些心思: https://tonybai.com/2021/04/09/ten-commandments-of-go

[7]  《軟件設計的哲學》: https://book.douban.com/subject/30218046/

[8]  《瓦爾登湖》: https://book.douban.com/subject/35015980/

[9]  Go 諺語: http://go-proverbs.github.io

[10]  “Gopher 部落” 知識星球: https://wx.zsxq.com/dweb2/index/group/51284458844544

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xgiE4B5wu-ieWp0OwGkwsQ