第2章:有意義的命名

為變數、方法、類別取一個名符其實的名稱,通常不容易,但也絕對可以在未來省下更多的時間。不要擔心 rename 會花去太多的時間,因為現在的 IDE 工具很棒。但是產品發佈後的rename,就要仰頼完整的自動測試(重構的第一法則)。

命名應該可以解答多數我們想在註解中看到的問題:為什麼這個"變數/方法/類別"出現在此?它的用途為何?如何使用?以下是書中修改前後的範例,我們可以體會一下兩者的可讀性差異,以及後者的名稱中傳遞了哪些資訊?

 public List getThem() { // 修改前
   List list1 = new ArrayList(); 
   for (int[] x : theList) 
     if (x[0] == 4) 
       list1.add(x); 
   return list1; 
 }

修改後:

 public List getFlaggedCells() { // 修改後
   List flaggedCells = new ArrayList(); 
   for (Cell cell : gameBoard) 
     if (cell.isFlagged()) 
       flaggedCells.add(cell); 
   return flaggedCells; 
}

要「避免誤導」

命名的最高原則是「避免誤導閱讀者」。以下摘錄書中的各條原則說明(但順序與原文有所不同)。

每種概念使用一種字詞

這是最重要的一點,不要把多個字詞對到同一個概念:如 getXXX / fetchXXX / queryXXX / retrieveXXX,為例,他們有什麼區別?在同一個專案/團隊中,我們能否看到這些字首,就知道取得的資料來源是在物件內部,或是再自第三方的媒介取得?

由於JavaBean規範中的Accessor是 set/get/is,所以我通常會限制對這三個KEYWORD的使用。

也盡量避免把一個字詞對應到多種概念:add 表示的概念是相加,還是相連!? 有時使用 insert / append 可能會是更好的選擇(可以參考JQuery中的元素操作METHOD命名)。

命名的優先選擇,是資訊解決方案領域(Solution Domain)術語,如資料結構的名稱、Pattern的名稱。對於程式設計師而言,看到 xxxList 就認為這個物件是個 List 是理所當然的,我們會預期它的add(item)具有 List 的特性,而非 Set。如果你寫的是個 MVC MODEL2的JaveEE 專案,理當也沒有人會誤解 xxxxController 在其中扮演的角色。但若 ~VO / ~TO / ~DTO 這些字尾同時存在,那麼團隊就應該要好好定義一下它們的角色差異了。

一些無意義或贅餘的字尾應當被移除,因為一般人無法分別 Product、 ProductDTO、 ProductInfo、 ProductData、 ProductDataDTO 有什麼差異,除非開發團隊已有事先定義 -Info 、-Data 的特定意涵。

字首、字尾、縮寫與術語

  編碼/縮寫是難以避免的,但不要再把型別或SCOPE資訊也加入到編碼的一環,這樣通常會再加重我們閱讀時的負擔。對於所謂的「匈牙利標誌法」,在JAVA界是不被需要的,它泰半用在編譯器不會檢查型別正確性或部分型別沒有明確定義的語言環境,所以使用強型別語言時,就忘了這件事吧。與此項雷同需要避免的,是成員變數字首 (m or ) 、函式參數字首 ( p_ or p ) 諸如此類的特殊編碼(沒有標準、每個團隊的習慣不一)。

我剛剛來到目前專案團隊時,你知道,我們人很多,來來去去的 PG 或許有超過100人次?當時也已經是專案的後期了。很不幸的,在我們的原始碼中就會看到各式各樣的字首命名風格,除了閱讀時會混淆以外,需要用一些grep工具去全面搜尋程式碼內容時,也容易有漏網之魚。

在 Interface 名稱前在上一個I,這曾是某段時期流行過的命名方式,但現在較為常見報推薦的是在實作類別後面加上 Imp/Impl 字尾,無論如何,整個團隊中應當統一。

之前曾碰到一個情境,在一大堆現成的類別如 xxx1Job、xxx2Job、xxx3Job、yyy1Job、yyy2Job中,有少數幾個JOB要抽出介面的需求,那個時候將就把它們取名為 IxxxJob、IyyyJob,但系統的其它部分並非使用此慣例。現在想起來,那是個錯誤的重構命名決策,因為後來被其它人沿伸出更多的Izzz類別,造成整體命名的混亂。

一個由多個WORD拼成的名稱,有時會長到令人髮指,如書上的 XYZControllerForEfficientHandlingOfStrings / XYZControllerForEfficientStorageOfStrings 範例。這是相似的過長名稱,讓人不易區分,也較難在 IDE 的自動完成介面找到正確的選項。有時我們會如後文為它們添加適當的上下文資訊,有時我們會選擇部分字詞做編碼/縮寫。    通常我們遇到編碼/縮寫時,一定要用問題領域的專用術語,不要亂來。例如寫一個RPG遊戲時,hp 是個可能是個常見的縮寫字。 但是在其它領域的專案中,把以下單字hypotenuse(直角三角斜邊)、hippocampus(海馬)、hypotension(低血壓->LBp)簡寫成hp,更多人想到的可能是(HP/惠普)。而長字的的縮寫,每個人的習慣也不盡相同,如 TransactionContext --> txContext/txnContext/transactionCtx 都有可能。所以在專案中,需要維護一份編碼/縮寫對照表,未列於其中的,就不應該亂縮。

 在本地化的專案中,有時會遇到難以翻譯的術語,要取一些有區別性的英文名稱很難,這時考慮選擇使用編碼命名,我想應不為過。

範圍Scope與上下文資訊

變數命名的長度,一般與其作用範圍Scope的大小成正比。例如在同一函式中,出現以下區域變數 firstName, lastName, street, city, state, zipcode,我們會認知它們是屬於地址資訊的一部分。但在一個 Something.class 中,看到 state 這個名稱呢?在 AddressDTO.class,看到 state 這個名稱呢?

類別的名字、函式的名字是最直接的上下文資訊(context),若在一個較長的函式中,有一些區域變數被從頭到尾使用。那麼它的名字可能加上些字首做為辨識為佳,例如 addrFirstName, addrLastName, addrState。而如果在很小並且不會和其他名稱衝突的scope,比如說迴圈,使用i, j, k 就還可以接受,因為這是約定俗成的用法。

但用到 l/1、O/0 時,要小心 Java Puzzlers 中提到的陷阱,這些都是容易誤讀的字元。   

其它

  • 避用俗語及髒字,如果在程式碼中看到Fxxking字樣,那只是個人情緒的宣洩,對於專案沒有半點好處。
  • 使用可發音的字詞。
  • 類別應取名詞命名,方法應以動詞開頭。

results matching ""

    No results matching ""