2014年6月1日 星期日

書摘:『易讀程式之美學』

程式碼必需易於理解,更精 確地說,
    撰寫程式碼時應將他人理解程式碼所需的時間縮到最短。

易讀程式之美學:提升程式碼可讀性的簡單法則
  • 作者: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

===========
=  善用排版風格
    應透過「空格」、「對齊」以及「段落順序」讓程式易讀,原則:
  1. 排版一致。
  2. 相似功用程式碼有相似的外觀。
  3. 組織相關程式碼成為段落。

    看起來美觀的程式,常常結構也是優的。所以可將過長或重複出現的程式碼,改成 method;或分成段落,每段落前加註解。最後要有排版風格一致性,如「左括號位置」、「斷行習慣」。 一致的風格比別人所謂的正確風格更為重要。


===========
=  為程式加上註解
    註解最重要的是讓讀者能了解程式碼作者的思想

    所以不要註解那些能很快從程式碼中知道的事實。但技術上來說,對大多數工程師來說,讀註解還是比讀程式碼快,所以若這行指令包含多個運算,還是加上說明會比較好

寫下創作時的想法
    例如有考慮到哪些事但因為實際上不容易發生,所以沒有寫,可以避免後續者開發無用的修補程式;或是紀錄未來可以添補的部分。
  • //TODO:未實作的部分。
  • //FIXME:已知的問題。
  • //HACK:程式不夠優雅。
  • //XXX:重大缺陷。

常數也標明註解
  • 用途說明。
  • 目前設定的數字的由來。
  • 建議如何調整。

在一大段程式前,先寫全局註解摘要註解
    這樣才容易先知道後續程式的來龍去脈;可避免讀者陷入細節中。

讓註解精確簡潔
    因為註解也占畫面,所以應該簡潔。當用文字描述不容易說明時,加上個具代表性範例就會很清楚。
  • 用詞精確,不要用代名詞,並且直述重點且精確說明行為
    沒人知道你說的「這個」是哪個!?
    差://根據是否用到這個URL給不同優先權
    優://給予不曾爬過的URL較高優先權

    countLines(txt) …因為每個人對行的定義不同,所以註解可以直接寫出,大家望文就知
    差://計算檔案行數
    優://計算文中換行字元(‘\n’) 個數


  • 寫出範例:說明傳入值與傳回結果。要選具代表性或用邊界值。

  • 直接點明高階意圖
    差://以相反順序查訪串列
    優://從高到低顯示所有價格



※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
簡化迴圈與邏輯

簡化程式才能減低閱讀時的心理負擔,常見可改善區塊包括:

  • (1)Flow-Control 程式區塊 太複雜
  • (2)巨大表示式 (如過長、太多項)
  • (3)變數 的個數

針對Flow-Control的可讀性
    應和思考順序一致,才能讓程式的流程更易閱讀。
  • IF-ELSE 區塊順序
    1. 先肯定後否定。
    2. 先簡單。
    3. 先處理明顯常用的。
  • 除非程式很簡單才用 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處理一些日常工作,而不要老是要寫出新的程式。