撰寫程式碼時應將他人理解程式碼所需的時間縮到最短。
- 作者:Dustin Boswell、Trevor Foucher
- 譯者:莊弘祥
- 出版社:歐萊禮
- 出版日期:2013年04月30日
- 我的推薦指數:★★★★☆
教授:
我無法了解這段古埃及文的意思,不過它似乎很像我們的語言。學生:
那是你寫的博士論文。
※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
提升程式碼可讀性的簡單法則
提升程式碼可讀性的簡單法則
寫在前面的感想與推薦
我們總是在深夜裡文思泉湧寫下程式碼;然後,在2週後忘了這段程式碼 要用來做啥!?
我想這是很多工程師共同的經驗,這除了代表我們年紀日漸增長而記性變差外。更有可能是因為我們沒有寫出『讓程式碼自己說故事』的程式。
寫出具高度閱讀性的程式碼,不僅僅是因為要讓別人理解;其實,也可以算是自己工作表現的勛章。更重要的是,能寫出具閱讀性的程式,通常也是較具結構化、且問題較少的程式。因為,在這樣的過程中,我們的大腦經過了更多的思考。
而這樣的練習與自我要求,我認為不光會在程式開發工作上獲得更高的樂趣;另方面也可以訓練自我的邏輯思考能力,進而在其他方面會有更高成就。
※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
表層改善 (名稱、註解與風格美學)
可讀性的旅程從讓人看到的『外表』改善開始:
- (1)選擇好的類別、變數與函數名稱
- (2)精確的使用空白、並以簡潔方式編排程式碼
- (3)撰寫好的註解
可讀性很重要,通常較短的程式較易理解,但最重要還是要寫註解。
===========
= 選擇好的名稱
# 應遵循每種程式語言的命名風格,如大小寫習慣 #
# 使用富含資訊的名稱:名稱可被視為簡短註解 #
- 選擇有意義的詞彙:明確且避免空洞的詞。
- 避免用通用名稱:如 tmp,就無法讓人清楚內容,若改更明確的 tmp_file 就較好。
- 優先使用具體名稱而非抽象名稱:如「鐵鎚」比「超硬鐵釘打擊器」容易理解。
- 在名稱中加入額外資訊:如 包含數值單位,start vs start_ms 、 如加入其它重要屬性 password vs plaintext_password (較佳 當資料是明文)。
- 決定適當的變數名稱長度:較小範圍用較短名稱; (eclipse:: Alt+/ 可自動代變數) / 縮寫不要亂用 (僅用約定俗成的 如 doc 代表 document) / 排除多餘字彙 如 toString 就夠清楚 而不要用 contertToString。
# 選擇不被誤解的名稱 #
- 語意不明確:例如 filter() 是 找出「符合的」還是「排除」,不同的人可能會有不同認定。所以若要找出符合的則用 select 才精確。
- 符合通用慣例:如 getMean 是取回 在 mean 變數內的值,若用來計算平均 應用 mean 或 computeMean。不要和約定俗成的規則不同。
- 遵守常見的習慣:
- 用於邊界值時命名方式:在名稱前加 min、max
- 用於封閉區間時命名方式:用 first、last
- 用於半開放區間時命名方式:用 begin、end
- 用於布林值時命名方式:在名稱前加 is、has、can、should
===========
= 善用排版風格
應透過「空格」、「對齊」以及「段落順序」讓程式易讀,原則:
- 排版一致。
- 相似功用程式碼有相似的外觀。
- 組織相關程式碼成為段落。
看起來美觀的程式,常常結構也是優的。所以可將過長或重複出現的程式碼,改成 method;或分成段落,每段落前加註解。最後要有排版風格一致性,如「左括號位置」、「斷行習慣」。 一致的風格比別人所謂的正確風格更為重要。
===========
= 為程式加上註解
註解最重要的是讓讀者能了解程式碼作者的思想。
所以不要註解那些能很快從程式碼中知道的事實。但技術上來說,對大多數工程師來說,讀註解還是比讀程式碼快,所以若這行指令包含多個運算,還是加上說明會比較好。
# 寫下創作時的想法 #
例如有考慮到哪些事但因為實際上不容易發生,所以沒有寫,可以避免後續者開發無用的修補程式;或是紀錄未來可以添補的部分。
- //TODO:未實作的部分。
- //FIXME:已知的問題。
- //HACK:程式不夠優雅。
- //XXX:重大缺陷。
# 為常數也標明註解 #
- 用途說明。
- 目前設定的數字的由來。
- 建議如何調整。
# 在一大段程式前,先寫全局註解、摘要註解 #
這樣才容易先知道後續程式的來龍去脈;可避免讀者陷入細節中。
# 讓註解精確與簡潔 #
因為註解也占畫面,所以應該簡潔。當用文字描述不容易說明時,加上個具代表性範例就會很清楚。
- 用詞精確,不要用代名詞,並且直述重點且精確說明行為:
沒人知道你說的「這個」是哪個!? 差: //根據是否用到這個URL給不同優先權 優: //給予不曾爬過的URL較高優先權
countLines(txt) …因為每個人對行的定義不同,所以註解可以直接寫出,大家望文就知 差: //計算檔案行數 優: //計算文中換行字元(‘\n’) 個數
- 寫出範例:說明傳入值與傳回結果。要選具代表性或用邊界值。
- 直接點明高階意圖:
差: //以相反順序查訪串列 優: //從高到低顯示所有價格
※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
簡化迴圈與邏輯
簡化程式才能減低閱讀時的心理負擔,常見可改善區塊包括:
- (1)Flow-Control 程式區塊 太複雜
- (2)巨大表示式 (如過長、太多項)
- (3)變數 的個數
# 針對Flow-Control的可讀性 #
應和思考順序一致,才能讓程式的流程更易閱讀。
- IF-ELSE 區塊順序:
- 先肯定後否定。
- 先簡單。
- 先處理明顯常用的。
- 除非程式很簡單才用 3元運算子 (A?B:C),不然儘量還是使用 IF-ELSE。
- 避免用DO-WHILE,應改用 WHILE。
- 避免用 goto。
- 減少巢狀結構。
# 分解巨大表示式增加可讀性 #
人腦一次只能思考3~4種東西,所以程式碼、判斷式越長時就越難理解。因此要透過分解程式碼,讓程式更易吸收。
- 解釋性變數 explaining variable、摘要性變數 summary variable:
差: If (request.user.id==document.owned_id) then .. 優: Final Boolean user_owns_document = (request.user.id==document.owned_id)
If (user_owns_document) then ..
- 儘量找出優雅的寫法,而非複雜顯示學問的很酷寫法;有時太複雜的邏輯,可用「反轉問題」或「考慮相反目標」來簡化。
- 善用笛摩根定律:if (!(a && !b)) = if (!a || b) 口訣:把 not 分配到各項,再反轉 and/or
# 改變使用變數的習慣 #
- 減少變數個數:
過多的變數也會讓程式不易理解,因為人無法同時記得這些變數的用途。和解釋變數等不同的點在,有些變數被使用後,無法消除重複的程式碼,甚至指被用了一次。過多的變數常常只是為了暫存 “計算過程中間的結果”,那讓程式儘快結束(早點return),就可消除很多變數。
- 避免被做為控制流程的變數:
如 flag done = false; … 應該用更結構化的程式來改善、或將部分程式移到另一個新函式。
- 避免用全域變數:
最好將變數生命範圍限縮在最少的程式碼內(可視範圍內),並且儘量使用只可寫入一次的變數 (用 final 等),程式更易理解。
※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
重新組織程式碼
- (1)重構
- (2)撰寫更少的程式碼
===========
= 對程式碼重構
# 抽離不相關子問題 #
將大問題分解成小問題,再將小問題的解答組合成原本大問題的解答。
當解決特定問題的程式碼行數夠多時,就抽離獨立成函式。這樣主程式和讀者都可以專心在最重要的高階問題。而這些被抽離出來的函式也比較容易測試。不過,過多也不好,應將這些函式維持在同一抽象層次。
# 一次一項工作 #
將每一個函式單純化,只解決一個問題。這樣才不易陷入複雜邏輯的陷阱。
# 先將想法寫下來:可先寫全區註解、區段註解 #
先用口語描述程式行為,然後再轉換為程式碼,這樣可以幫助設計師寫出更自然的程式碼;而且常常可以幫助設計師找出該處理或分解的子問題。(Rubber Ducking 技巧)
===========
= 撰寫較少的程式碼
甚麼時候不該寫程式是設計師最重要的技能。但工程師卻還是常常寫著用不到的功能,增加了複雜度。卻又低估了缺少文件與後續維護的額外負擔。
# 消除需求、解決簡化問題 #
# 儘量去引用現有的函式庫而不是自己又造一個 #
# 可以儘量將一些工作交給現成工具去執行 #
例如:UNIX、DOS、WINDOW 等OS裡 都提供了好用的 SHELL,特別是UNIX。可以透過這些SHELL處理一些日常工作,而不要老是要寫出新的程式。