Skip to content

《The Pragmatic Programmer》做一位務實的程式設計師

發佈

《The Pragmatic Programmer: From Journeyman to Master》原書最初在 1999 年 10 月出版,這時我還未滿周歲。

我一直都相信一個能在市場上長時間存在的東西一定有其過人之處(就算是在奇怪的方面上),在變化快速的軟體/程式界中更是如此。就像 Design Pattern 一樣,這些都是全世界無數工程師和專案下反覆驗證有效的東西。

就像書中提到的新的語言:Clojure、Elixir、Elm、F#、Go、Haskell、Python、R、ReasonML、Ruby、Rust、Scala、Swift、TypeScript。據說這些「新語言」和第一版列出的幾乎完全不同,但是這本書的知識能夠延續 20 年以上仍然適用。

本文只會大致節錄一些重點作為我自己的小筆記,如果想確認詳細內容的話,推薦買本原書來看。

務實的哲學

貓吃了我的原始碼

人都會犯錯,但是要坦誠面對錯誤似乎不是容易的事。如果什麼事搞砸了,不要找藉口。

如果你的電腦壞了,而且所有程式、文件和資料都在裡面,你大概不能和主管說你要多幾個月的時間來重做,而且錯的是那個水杯及沒有 IP56 認證的筆電,你應該要做好備份。

——告訴老闆「貓吃了我的原始碼」是行不通的。

軟體亂度

(entropy)有很多種表現方式,但根本都是「無序」或「混亂」的程度。例如夏農熵(Shannon entropy,或訊息熵)是指訊息系統中的不確定性。而熱力學的熵增定律則說明封閉系統中的熵(亂度)只會增加或保持不變,簡單說就是,系統總是趨於混亂。

在軟體中,我們通常使用技術債(technical debt)或軟體凋零(software rot)描述軟體亂度的增加。書中認為要抑制這種情況,且最好的做法是「不要讓破窗存在」。破窗效應大家都聽過,但可能不是每個人都深知它的力量,我們可能無形之中總是收到破窗的影響。

——不要因為其它程式碼是垃圾,所以我也跟著寫垃圾。

夠好的軟體

足夠好的程式軟體就是最好的軟體。

有時後我們會對某段程式不夠滿意,雖然它現在運作的很好、沒有明顯的安全性漏洞、沒沒神奇的語法破壞可讀性、沒有對性能有影響,對未來的擴展也不是非常影響(而且這個未來的擴展可能永遠不會到來),但就是看它不爽想要改它。

過早最佳化(Knuth’s optimization principle)是一個常常被大家討論的話題。雖然人們對它還是有一些爭論、正反兩方各有理由,但大部分的情況我們應該都有能力做出取捨,而確切的那把尺只能靠經驗的磨練了。

知識是會過期的資產

身為程式設計師,知識和經驗無疑是我們最大的價值所在,但是,知識和經驗都會過期,隨著時間流逝價值也跟著下降。所以未來保持你的資產,你必須要定期投資新知識、重新審視各項技術、保持多樣性。

或許你可以嘗試做一些你從來沒做過的事情,例如如果你只用用過 Windows 的話,試著用用看 Linux;如果你只在包裝完善的 IDE 內寫程式的話,試著從底層工具鏈架設並在 Terminal 上開發。反之亦然。

還有批判性思考也是很重要的,現在社會的資訊太多太雜,其中還充斥著不實的訊息。在接收到一項資訊時,多問問自己:為什麼?這對誰有好處?時空背景呢?在什麼情況下?會什麼會有這個問題?

溝通

說話是門藝術。大家都知道,但能做好的人真的不多,所以才會是藝術。

我自己碰過非常多人,他們總是用一大段文字描述,用一些不精確的名詞、還不斷重複。終於講完了,但你還是不知道在講什麼。當然,我想如何說話是人們一輩子的學習,我也在想辦法精進,但是在說話前,再多想想你想表達什麼。

我自己很常和腦中的自己對話,我們會辯論某個議題,我們會模擬下週會議的報告,有時其中一位負責當你的上司,對內容的細節展開攻擊;有時扮演不同領域的同仁,對你報告中艱澀難懂的說明提出疑問。我總是自己推翻自己,重複這樣無數次。我經常發現「對誒,這裡只要這樣講就好了」。

另外文件也是很重要的。雖然可能有些流派會主張不需要文件,但我認為許多實際情況下文件還是有用處的。不過你可以盡可能將文件和程式靠進一點,例如 Doxygen。但是也要注意,只有當這部分有文件會比單純看程式碼還更快或更好理解時才有必要,能夠以程式本身描述的情況還是回歸程式本身(但是可能要考慮一下交接的人的程度?),不然它們大概和註解一樣在說謊或重複。

務實的方法

優秀程式的精髓

一個好的程式應用要容易修改,也就是 ETC(Easier to Change)。但實際的問題不是我們刻意去選擇一條不易修改的路,而是我們不知道哪條路在未來才是 ETC。書中給出兩種建議:

  1. 試著讓這段程式可以替換,這樣無論未來的發展,它都不會是阻礙。實際上這就是要求保持低耦合和高內聚。
  2. 在工作筆記上記錄這次的情況與最後的選擇,如果你最後猜對了,這會變成正面激勵,即使猜錯了,這也會形成負面激勵。它們都會經驗的培養有幫助。

DRY——不要重複

DRY 原則應該大家都聽過,不過書中指出不是重複的程式碼就代表違反 DRY,DRY 應該要專注在知識範疇上。如果兩段程式碼一模一樣,但是它們的高層次概念是不同的,那它們也不該被視為重複,它們只是剛好擁有相同規則的獨立邏輯。

而我們時常會因為性能考量等因素違反 DRY,但書中認為我們應該盡可能把這樣的程式封裝在內部,不要公開。

正交性

正交性(orthogonality)代表彼此之間是獨立的(就像幾何學的那樣),在軟體中,它代表修改一個東西時,不會影響到另一個,也就是去耦合的象徵。我們都希望程式和程式間、模組和模組間保持正交。

可逆性

需求總是在變化,如果一開始就假設某件事或決定不會改變,那在未來常常會付出嚴重的代價。

原型和便利貼

你可以在帶有風險、以往沒做過的事情上使用原型設計進行研究。選擇一個快速、靈巧、方便的高階語言(例如 Python),並忽略掉一些正確、強健、完整性和文件,快速第進行驗證和學習。

基本工具

純文字的威力

純文字的(人類可讀)資料擁有自描述的能力,它們不會過時,就算在未來當初支援它的工具已經失效了,最壞的情況下你還是可以用一些腳本語言快速地自己實作出一個簡單的解析器。

shell

習慣 GUI IDE 工具的開發者可能會不過熟悉 shell,但是學會 shell 可以讓你更方便地建立許多自動化流程。我一直都堅信能夠叫電腦做的事就不自己動手,比較程式的誕生就是為了自動化。

然後,把你的 shell 客製化成你喜歡的樣子吧。不管是顏色主題還是指令別名。

功能強大的編輯器

熟悉你使用的編輯器可以大幅地提高效率。身為 Vim 的使用者我深感同意。

操縱文字

操縱文字是自動化的根本,如果你擅長處理文字資料,那你就可以將很多事情自動化。所以,找一個適合操作文字的語言。書中提到 Python、Ruby 和 Perl。

務實的偏執

合約式設計

合約式設計(Design by Contract,DBC)是記錄並約定軟體模組權利和責任的技術。確認好每個程式(函式)的前置條件、後置條件(結果)和類別不變量。在寫程式前,先想想輸入容許範圍、邊界條件、它要回傳什麼。

利用 assert 達成 Fail fast 也是一個好的方式,可以儘早發現並診斷問題。

死程式不說謊

——防禦性程式設計是浪費時間,直接讓它崩潰!(Erlang 的發明者和 Programming Erlang 的 作者 Joe Armstrong)

如果一個程式發生了本來就不可能發生的、意外的事情,那程式本身本來就不可行了,所以要儘快終止它。不過也不是所有環境都適合直接退出(例如 Embedded)。

assertion 式程式設計

不要做「xx事情不可能發生」的自我欺騙,用 assert 進行實際驗證。

想想看,有可能出現內角和不是 180° 的三角形嗎?可能,在空間曲率(curvature)不為零的非歐氏幾何中就是。

書中還提出其它「不可能」發生的情況,也都很有趣。

如何平衡資源

如果你說資源的取得和釋放沒有想法,那最簡單的守則就是,由取得資源的人負責釋放。

彎曲或弄壞

去耦合

耦合(coupling)就是程式碼之間的依賴關係。耦合會讓程式難以變化,因為它會讓你的程式牽一髮而動全身。

TDA(tell-don’t-ask)告訴我們不用依賴物件的內部狀態來更新,這樣會破壞封裝。外界不應該自動內部的情況。

LoD(Law of Demeter,最少知識原則)告訴我們應該只能和對象直接互動,不該和陌生人交流。用比較簡單的方法說就是,不用串聯呼叫方法。但是如果這個對象非常穩定的話(例如語言本身的函式庫),那你可以打破它。

全域可存取資料更是造成耦合的大問題,就算是 singleton 也一樣。而且外部資料也相同。如果你想更好的管理全域資料的話,可以用個 API 包裝起來。

行走江湖

善用這些策略來幫助你撰寫有關事件的程式:

轉換式程式設計

將資料看成一個。使用程式碼 -> 資料 -> 程式碼 -> 資料 -> 程式碼… 的方式來處理。

繼承稅

聚合優於繼承已經有相當多的討論了。你應該使用 interface、委派或擴充方法來取代繼承。

設定

將設定資料包裝在一個簡單的 API 後,這樣可以降低它們和程式之間的耦合性,也可以讓你的資料能在動態設定。

並行

打破時間耦合

並行性(concurrency)是一種軟體機制,而平行性(parallelism)是一種硬體機制。我們可以利用像是 UML 的活動圖(activity diagram)來找出哪些事情是可以同時進行的。

不要共用狀態

共用狀態(例如一個全域變數)在並行中問題的根源。你必須使用像是 Mutex 或 Semaphore 這樣的技術保證狀態同時只有一個人擁有。

當您寫程式時

靠巧合寫程式

如果你一開始就不知道為什麼這段程式能夠工作,那你在未來它出錯時也無法修復。這聽起來很廢話,但是你發現你的程式的數值總是剛好只差 1 的時候,你總是會明確的找出為什麼嗎?還是默默地 +1 讓它可以暫時運作。如果選擇後者,你可能掩蓋了某些更根本的問題,導致程式在未來變得難以修復。

所以,不用依賴巧合寫程式。

演算法速度

如果有個 O(n²) 的演算法,試著讓資料分成獨立的兩半處理,最後再合併它們,這樣會讓時間複雜度降低到 O(n lg n)

重構

重構(refactor)的重點是外部行為不變,僅有內部結構改變,所以嚴格來說,你需要有測試來驗證,每次重構完都要可以通過測試。而且重構應該儘量在規模還小時儘早進行。

並且以我自身的經驗,不要因為只是改改命名、調整一下等效寫法,覺得程式不會壞掉就忽略了版本控制,你還是應該為每一次的小重構都做 Atomic commit。等到這部分重構都完成了,測試也通過了,如果你覺得有太多的 commit,那再來考慮用 rebase 合併它們。

專案啓動前

需求坑

沒有人知道自己想要的是什麼。不要完全相信客戶所說的,試著考慮一些極端的邊緣條件,把自己當成奧客,並問問客戶,這樣的情況下會出現某某問題,這是你們想要的嗎?可以的話,提出一些建議。

解開不可能的謎題

我們常聽到「跳出框框思考」,但要做到這件事可能不是這麼容易。框框是限制和條件,真正的問題在於如何找到框框。它可能非常大,關鍵在於那些才是真正的限制,並瞭解你擁有的自由度。

書中提到的「用 3 條直線連結 4 個點」的小謎題很有趣也很具象。

成對程式設計

成對程式設計(pair programming)是極限程設(XP)的一種實作。它指一個開發者負責操作鍵盤,另一個不操作。不負責打字的那個可以更自由地思考高層次的問題,而負責打字的會專注在程式語法本身的低層次細節。每過一段時間就交換。

我曾經在研究所時期無意間實作過成對程設,我們要參加某個比賽,但關鍵功能的算法一直沒有進展,所以我們一個人負責寫程式並執行,另一人提供想法,寫的人也可以回應說「這樣的語法上有問題」。這樣的方式可以打破僵局。

暴民程式設計

暴民程式設計(mob programming)是成對程式設定的拓展。你可以找更多人來參與,而且可以找不同領域的人,不要只有程式工程師。

務實的專案

這一章算是以前面的所有內容做一個總結,以較高的角度再一次審視它們如果運用在整個專案中。

小結

這本書寫了各種建議,有技術本身的,也就管理或方法上的,但都很實用。我想它很適合每個一段時間就翻閱,或許每次都有不同的發現。最後,讓我再引用一句話:

你應該問的是「我是否已經儘了最大努力來保護這段程式碼的使用者免受傷害?」

書籍資訊

延伸讀物


在 NeoVim 中使用 Flash 取代 Easy Motion 來快速跳轉
《流暢的C》——適合C語言的各種 Pattern

留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)