編寫好程式碼:如何減少程式碼的認知負荷
Bug 少,效能好,容易修改。好的程式碼影響深遠,而且它可能是產生 10 倍工作效率的開發者的主要原因。儘管好程式碼十分重要,但開發新手卻不得要領。關於這一主題的技巧多而冗雜,讓新手們如何記得住?“《程式碼大全》)” 是這個主題的經典,但內容多達 960 頁!
我認為應該建立起良好的心態,這樣,不管你用什麼語言或者庫,都會自然而然的寫出高質量的程式碼。這裡我主要談到 5 個相關的概念。記住它們,輕鬆寫出寫出好程式碼。
避免特立獨行
當你讀到一些文章中的新技巧時,如醍醐灌頂,一定會想要寫點看起來很聰明的程式碼,讓同行們眼前一亮。
問題是人們只是想修完 BUG,然後繼續處理其它事情。那些聰明的技巧常常只會成為一種消遣。我曾經在“
[譯者注:圖片中的註釋內容:這在 C 語言中用於避免誤寫成 variable = null。最近它造成不少人困惑,但似乎並沒帶來多大好處]
不要在工作中使用太多可能需要額外解釋的個性化方式。
不要用“你的方式”來編寫程式碼,只需要按照標準(的程式碼規範)來編寫就好。再次強調,要寫讓人讀得明白,看得下去的程式碼,讓人家能夠理解它。
分而治之
模組化可以使複雜的程式碼結構變得清晰,除此之外還有很多方法可以達到同樣的目的,而無需建立更多函式。將長長的條件表示式儲存為一到兩個變數就是個不錯的方法,可以避免呼叫函式的開銷。這些變數可以用在其它地方,甚至可用於組合更復雜的條件。
拆解問題的方法在於儘可能的讓每個部分保持集中,隻影響區域性狀態,不要混入不相關的問題,要避免副作用。程式語言和庫通常會帶來各自相應的問題,避免這些問題可以讓你的程式碼更專注於其表達的業務。單一責任原則就是通過集中程式碼和區域性化程式碼帶來良好設計的例子。
[譯者注:圖中註釋內容:這是不需要額外函式開銷的一種模組化方法]
我喜歡利用變更來進行邏輯劃分。
TDD(Test Driven Development,測試驅動開發)的成功實施表現出了它所帶來的好處,它迫使人們運用一些以前不受歡迎的準則。無狀態的程式碼曾經被嫌棄又慢又沒必要(大部分老的 C/C++ 程式碼中可以看到),然而現在每個人都在談論純函式。就算你不採用 TDD,你也應該學習它背後的原則。在新的模式下工作會讓你成為適應性極強的開發者。
分離程式碼並使其可分別處理
你寫程式碼的時候面臨著什麼樣的困難,你的計算機和工具也面臨著同樣的困難。程式碼的複雜性,與需要進行的預處理和需要處理的突發情況存在著或多或少的聯絡。
現在暫時拋開那些額外的構建工具所帶來的好處。它們需要你使用特定領域的語言,比如自定義模板,或者複雜的動態資料結構,比如哈稀表。IDE 通常不善於處理這些東西,要找到相關的程式碼段則更加困難。
儘量避免使用不能很好支援 IDE 的語言擴充套件和庫。它們給你的生產力帶來的好處,遠大於簡易配置和用簡潔語法儲存擊鍵帶來的小便利。
[譯者注:圖中註釋內容:使用神奇的字型串可能造成 IDE 不能正確識別你的程式碼]
ServiceLocator 是與 IDE 整合不佳的一個設計樣例。
另一個保持 IDE“整合度”的相關方法是避免編寫特殊的程式碼。多數語言都提供了編寫動態程式碼的能力。如果濫用這些特性,比如特殊的字串、特殊的陣列索引和自定義模板語言特性等,會產生難以連線的程式碼庫[譯者注:這裡的連線應該是指相互關聯的關係,連線最直接的影響是在使用 IDE 等工具進行重構的時候可以自動根據連線關係修改相關引用]。一般說來,那些只有你一個人才能看懂的特性會讓你摔跟頭的,因為如果 IDE 不能理解這些程式碼,在你想進行結構調整的時候,IDE 就沒法幫你進行重構。
讓程式可讀
致力於可預測的架構。這種架構下你的隊友要進行某項查詢就會很容易,可以節約不少時間。一旦你為專案確定了一個整體的架構,就一定要把主要元素放在顯眼的位置。使用 MVC?把模型、檢視和控制器放在他們自己的目錄下,不要放在三個深層次的目錄中,也不要放在幾個不同的地方。
我在前面談到了模組化。也存在過度的模組化,讓定位程式碼這種事情變得艱難無比。IDE 可能會帶來一些幫助,但通常你往往會讓 IDE 忽略庫目錄,因為其中有很多不相關的程式碼,或者它的索引需要人工處理一些問題,就會造成兩敗俱傷的局面。儘量使用較少的庫,選用那些儘可能多覆蓋你需求的庫。
庫和工具也可能成為新人的障礙。我最近使用 EcmaScript 7 (babel) 構建了一個專案,後來我才意識到我們的初級開發人員一直因為想搞明白它而卡在那裡,這對團隊的生產力造成了巨大損失。我低估了這對一個新手所帶來的壓力。不要使用對當前來說太難掌握的工具,等時機成熟再使用。
這是我寫的一個 makefile 中的真實程式碼。新手不需掌握過多的新技術。
讓程式碼易於理解
如果你已經做到了這一點,那我們來解決更重要的問題——選擇好名字,這是軟體開發中的重要部分。構建工具在這方面不能提供幫助,原因很簡單,計算機不會真正知道解決方案背後的邏輯。你得通過文件來解釋程式碼,而與主題相關,且符合上下文,體現變數和功能的名稱就能很好做到這一點。語義化的名稱甚至可以減少對文件的需求。
在名稱中使用字首對理解它們很有幫助。這在過去是一種流行的做法,我認為對這種作法的誤用導致了它的消亡。像匈牙利命名法這樣的字首系統最初只是為了增加意義,但最後用於其中的上下文越來越少,終於少得只剩型別資訊。
[譯者注:圖中的註釋內容:使用名稱來表達意圖,不要利用語言來耍小聰明]
近來,Fluent 介面經常被濫用。
最後要說說老生常談的回溯複雜度。簡單地說就是要儘可能減少條件分支的數目。每多一個分支都會增加縮排,同時降低可讀性。不過更重要的是,增加的東西越多,你需要跟蹤的東西就越多。
結論 & 相關閱讀
本文介紹了五個簡單的總體概念,我希望你能從中輕鬆的學習到組織程式碼的方法。
實踐是最好的老師,用程式設計來鞏固理論。如果你還沒有這樣做,我誠摯向你推薦《程式碼大全》。它帶來了大量的示例,幾乎剖析了你可能遇到的每一種問題。