程式碼的雙重溝通對象#

寫程式時同時與兩個對象對話:

  • 電腦:告訴它要執行什麼運算。
  • 讀者:呈現你完成這件事所走的步驟與思路。

最該被傳達的不是 how,而是 what(在做什麼)與 why(為什麼)。對讀者來說,程式應該像一本書(即使語法生硬),能依需求陳述追蹤邏輯。

Communicate with Your Code——程式碼應傳達它的目的與意圖。

範例:

if (a_customer.has_late_rental())
    a_customer.suspend_rental_privilege();

讀者看一眼就知道:客戶因為逾期租借而被暫停。實作細節(記錄在哪、怎麼存)被推到下一層。

可讀性的衡量是「別人能不能讀懂」。沒有 pair programming 時,可由 2–3 位開發者分別讀過、向作者解釋程式在做什麼,作者再依回饋調整。

命名:每個概念一個清晰名字#

A Rose by Any Other Name Is Not a Rose——為系統中的每個概念建立清晰、明確的名字,並一路用到資料庫欄位、XML 標籤、文件中。

實踐要點:

  • 避免單字元差異rundll.dllrundlI.dll 在常見字型下難以區分;變數命名也應確保至少差兩個字元。
  • 單字元變數只用於迴圈索引:選擇形狀差異大的字母(ik)而非容易混淆的(ij)。
  • 長度依範圍而定:方法內部的區域變數可短,跨方法的參數與屬性必須完整拼寫。
  • 拼字與術語檢查:把駝峰名或下底線名拆字後跑 spellchecker,並把客戶/領域接受的縮寫加入字典。

名字也可能太短。作者寫 FORTRAN 時受限六字元,自己寫的 CMPORBORBCMP 反而分不清楚——這是過度節省字元換來的災難。

顯式優於隱式#

Explicitness Beats Implicitness——明說可以降低誤判。

明顯的取捨:

  • 完全限定名稱java.util.VectorVector 多了類別屬於哪個套件的資訊。
  • 避免布林開關當作行為旗標UpdateData(TRUE) 沒人記得真到底是讀畫面還是寫畫面,應改成 RetrieveValuesFromWindowControls()SetValuesOfWindowControls()
  • JDOM 的例子:與其用 getText(true),不如分成 getText()getTextTrim()getTextNormalize(),方法名直接告訴你發生什麼事。

但完全明說會讓程式變得冗長,仍須假設一些通用知識(例如語言本身的語法);領域相關的程式則應更明說,因為其他人不一定記得你從客戶那裡學到的細節。

命名要露骨地長:Extreme Naming#

class FileWithLines
    String read_line_up_to_new_line_and_toss_new_line()
  • 短名(read_line)需靠記憶或文件回憶細節。
  • 顯式長名(含參數,例如 read_line(TOSS_NEW_LINE))讓讀者一眼明白方法的副作用。
  • 高頻使用的短名可以接受,但少用的方法名最好寫長一點。

讀程式的時候若不斷查文件回憶方法用途,等同於小時候邊查字典邊讀《David Copperfield》——文采可能還在,但敘事流被破壞。

不要重載方法名#

search(int an_int)
search(String a_string)

問題:

  • 文字搜尋會找到所有 search 呼叫,難以鎖定特定版本(雖然 IDE 的 find-usages 能緩解)。
  • 同一型別不同語意的搜尋無法共存。

改寫:

search_for_first_name(CommonString a_string)
search_for_last_name(CommonString a_string)

Overloading Functions Can Become Overloading——使用獨立名稱讓函式自我描述。

運算子重載也是同理:Time t; Time s = t + 5; 中的 5 是時、分、秒?改成 t.add_hours(5) 一目了然。

宣告勝過執行#

Declaration over Execution——宣告式程式可以提供無需改程式的彈性。

實踐:

  • 不把規則寫死在 switch,改用查表(base_rental_period_per_category[category])。
  • 表可進一步外移到設定檔(CSV、XML、資料庫),新類別只新增一筆設定,不必改程式。

用客戶語言寫程式#

if (a_customer.is_good_customer())
    a_customer.provide_discount();

對開發者「太冗長」,但對 Sam 來說「就是這樣的事」。即便 Sam 不維護程式,也能比對程式邏輯是否符合他的政策。

Use the Client’s Language——把客戶語言納入程式,讓程式邏輯與客戶邏輯易於對照(領域驅動設計的核心精神)。

用相同版型得到相同版型#

Use the Same Layout to Get the Same Layout——對於結構雷同的類別與方法,使用模板或腳本維持一致版型。

例:SongSearchResultsPerformerSearchResultsCDCatalogItemSearchResults 邏輯相同、僅型別不同。

template <Item>
class SearchResults
    {
    private Item[] results;
    Boolean has_more_items();
    Item[] get_next_batch(int number_to_get);
    }

語言不支援泛型時,可寫一份原始碼模板,由建置流程的指令稿做替換產生實際類別。重點是避免複製貼上,同時保持版型一致。

一致即簡單#

Consistency Is Simplicity——一致的風格與解法讓程式更易維護。

但要避免愚蠢的一致:

  • 例外處理、契約檢查、命名規則該怎麼做,都要事先約定指南。
  • 但「死板的一致是小心眼的妖怪」(Emerson)——沒有理由不可一概套用。

一致也要看「給誰看」。速食店點餐機改了菜單後,店員出錯數天,是因為他們依肌肉記憶按鍵。對使用者的一致與對開發者的一致同樣重要。

拼字與大環境配合#

  • 拼字檢查:可把駝峰拆字後跑 spellchecker,編譯器只能驗語法不能驗語意。
  • 依環境調風格:自用的小腳本可以省掉許多溝通成本;多人專案則要把語言、命名、格式約定清楚。
  • 偽程式碼可以演進為實際程式碼:先寫接近自然語言的偽程式碼決定流程,再把它替換成實作;偽程式碼描述 what,方法描述 how。

可讀性原則總結#

指南訊息
Communicate with Your Code讓程式自我表達意圖
Explicitness Beats Implicitness明說勝過隱含
A Rose by Any Other Name Is Not a Rose給每個概念清晰唯一的名字
Extreme Naming用長名換取一眼可懂
Overloading Functions Can Become Overloading用獨立名稱避免重載混淆
Declaration over Execution用宣告與資料表達彈性
Use the Client’s Language把客戶語言寫進程式
Use the Same Layout to Get the Same Layout用模板維持版型一致
Consistency Is Simplicity一致即簡單