[讀書心得]Clean Code - Meaningful Names

  • 35765
  • 0
  • 2013-06-03

[讀書心得]Clean Code - Meaningful Names

前言

一本不錯的書,書名是Clean Code: A Handbook of Agile Software Craftsmanship
Amazon網址:http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882
其中第二章有對Naming列出不少的好概念以及範例程式碼,以及為什麼要避免這樣的命名。
以下針對書中的重點以及自己的感想,做點小整理,也推薦大家可以看看這一本書,很貼近寫code,尤其是維護code的developer心聲。

文中斜體的部分為引用書本中的內容,因為覺得寫的太好了,翻譯可能會失去點味道,所以只加上心得。
 

讀書心得

Clean Code - Meaningful Names

  • Use Intention-Revealing Names
    • 透過命名就能淺而易見其意圖
      • 如果一個命名,還需要註解來解釋它,就代表他還不夠清楚表示意思
        • 例子1
          • 不好的例子
            int d; // elapsed time in days 
          • 好的例子
            int elapsedTimeInDays; 
            int daysSinceCreation; 
            int daysSinceModification; 
            int fileAgeInDays; 
        • 例子2
           public List<int[]> getThem() { 
             List<int[]> list1 = new ArrayList<int[]>(); 
             for (int[] x : theList) 
               if (x[0] == 4) 
                 list1.add(x); 
             return list1; 
           } 
            • 1.   What kinds of things are in theList?
            • 2.   What is the significance of the zeroth subscript of an item in theList?
            • 3.   What is the significance of the value 4?
            • 4.   How would I use the list being returned?
            • 改善後的例子
              public List<int[]> getFlaggedCells() { 
                 List<int[]> flaggedCells = new ArrayList<int[]>(); 
                 for (int[] cell : gameBoard) 
                   if (cell[STATUS_VALUE] == FLAGGED) 
                     flaggedCells.add(cell); 
                 return flaggedCells; 
              } 
            • 改善再改善的例子
              public List<Cell> getFlaggedCells() { 
                 List<Cell> flaggedCells = new ArrayList<Cell>(); 
                 for (Cell cell : gameBoard) 
                   if (cell.isFlagged()) 
                     flaggedCells.add(cell); 
                 return flaggedCells; 
              } 
  • Avoid Disinformation
    • Programmers must avoid leaving false clues that obscure the meaning of code
      • 不要使用特殊的縮寫而導致閱讀的人錯誤的認知
        • 例如hp, aix, sco
          • Even if you are coding a hypotenuse and hp looks like a good abbreviation, it could be disinformative
    • Do not refer to a grouping of accounts as an "accountList"
      • 因為List對programmer來說是有意義的,即使accountList真的是List型別,也要避免把型別掛上去,因為會增加維護上的困難
      • So "accountGroup" or "bunchOfAccounts" or just plain "accounts" would be better.
    • Beware of using names which vary in small ways
      • 避免兩個以上的名字太過相像,而導致讀者直覺以為是同一個東西,尤其當Name很長一串時,就更難分辨細微的差異
        • 命名應避免出現不必要或沒意義的贅字
          • 例如
            • XYZControllerForEfficientHandlingOfStrings
            • XYZControllerForEfficientStorageOfStrings
    • 代表同樣意思、指同一件事、指同一個東西時,命名應該一致(glossary)
      • 代表類似的term或概念,可以透過一樣的單字,透過「自動完成」的IDE快速找到需要的字眼
    • A truly awful example of disinformative names would be the use of lower-case L or uppercase O as variable names, especially in combination
      • 0與1,小寫O與L的差異
        int a = l; 
        if ( O == l ) 
        a = O1; 
        else 
        l = 01; 
  • Make Meaningful Distinctions
    • 獨自開發時容易因為一時的方便,只為了建置或開發方便,而隨手使用了不具意義或暫時的命名,往往之後帶來相當多潛在的問題
    • Number-series naming is noninformative
      public static void copyChars(char a1[], char a2[]) { 
         for (int i = 0; i < a1.length; i++) { 
           a2[i] = a1[i]; 
         } 
      } 
        • 以這例子而言,a1應命成source[],a2則命成destination[]
    • 避免用不同字眼代表同一件事,例如info與data,或是a, an, the的差異
      • Class : Product
        • ProductInfo
        • ProductData
    • Noise  words  are  redundant
      • 多餘的字,只會造成誤解。例如NameStringCustomerObject
        • 會讓閱讀的人想,是否有其他型別的同義物件
        • How is "NameString" better than "Name"? Would a Name ever be a floating point number?
        • 假設有一個class叫Customer,另一個叫做CustomerObject,讀者如何區分兩者的不同?
        • getActiveAccount();
          getActiveAccounts();
          getActiveAccountInfo();
          • How are the programmers in this project supposed to know which of these functions to call?
          • 誰有法子看得出來這三個function有啥差異
      • The  word  "variable" should  never  appear  in  a  variable name. The word "table" should never appear in a table name.
        • 例子
          • "moneyAmount" is indistinguishable from "money"
          • "customerInfo" is indistinguishable from "customer"
          • "accountData" is indistinguishable from "account"
          • "theMessage" is indistinguishable from "message"
  • Use Pronounceable Names
    • 使用可以發音的Name
      • 幫助溝通,而且不會顯得這麼愚蠢
      • 例子
        • 無法發音的Naming
          class DtaRcrd102 { 
          private Date genymdhms; 
          private Date modymdhms; 
          private final String pszqint = "102"; 
          /* ... */ 
          }; 
            • genymdhms(generation date, year, month, day, hour, minute, and second)
              • saying “gen why emm dee aich emm ess”
        • 轉換可以發音後的Naming
          class Customer { 
          private Date generationTimestamp; 
          private Date modificationTimestamp;; 
          private final String recordId = "102"; 
          /* ... */ 
          }; 
            • 溝通上的轉變
              • “Hey, Mikey, take a look at this record! The generation timestamp is set to tomorrow's date! How can that be?”
  • Use Searchable Names
    • Single-letter names and numeric constants have a particular problem in that they are not easy to locate across a body of text
      • 例子
        • 使用"7"這個magic number,hard-code使用
        • 使用constant:  MAX_CLASSES_PER_STUDENT
      • 例子
        • the name "e" is a poor choice for any variable for which a programmer might need to search
          • Name太容易重複出現,就會導致不容易搜尋與定位
      • longer names比shorter names好,至少搜尋容易定位
      • 單一字母的命名,最多只允許在很短的迴圈或method中使用
      • 例子
        • 鬼才看的懂的magic code
          for (int j=0; j<34; j++) { 
          s += (t[j]*4)/5; 
          } 
        • 轉換成有意義的constant
          int realDaysPerIdealDay = 4; 
          const int WORK_DAYS_PER_WEEK = 5; 
          int sum = 0; 
          for (int j=0; j < NUMBER_OF_TASKS; j++) { 
          int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; 
          int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK); 
          sum += realTaskWeeks; 
          } 
            • 轉換後,少了很多magic number,並且容易尋找與閱讀,也比較能看得出設計的邏輯
  • Avoid Encodings
    • 外星球命名法
      • 例子
        • RGB001_256A
      • 避免自行定義特殊的編碼命名規則,會增加維護上的困難跟training成本
  • Hungarian Notation
    • 匈牙利命名法
    • 由來
      • Fortran開始使用first letter for the type,也就是匈牙利命名法
      • 在C的時代,compiler不會幫忙檢查 type,所以HN是有意義的
    • class跟function只負責單一功能,要能一眼看出來每一個變數的意義,加上型別prefix只是贅字
    • 不再需要type encoding ,加上type反而導致未來修改型別時,需要改更多的命名,否則將導致bug
      • 例子
        • PhoneNumber phoneString;
          // name not changed when type changed!
  • Member Prefixes
    • 不需要再透過前置詞來表示該變數是區域、全域或屬性
      • 不好的例子
        public class Part { 
        private String m_dsc; // The textual description 
        void setName(String name) { 
          m_dsc = name; 
        } 
        } 
      • 好的例子
        public class Part { 
        String description; 
        void setDescription(String description) { 
        this.description = description; 
        } 
        } 
          • 應只需用特定字,例如this,會有高亮度或顏色提示,這個屬性是在此class中
  • Interfaces and Implementations
    • Interface與Implementation,寧可加識別在Implementation上,例如ShapeFactoryImp,
      也不要在Interface上用IShapeFactory,因為不需要讓使用Interface的時候,知道那是Interface
      • IShapeFactory與ShapeFactoryImp,介面與實作命名的取捨
  • Avoid Mental Mapping
    • 避免需要用腦袋做Mapping的命名,尤其是單一字母,例如i,j,k 更糟糕的是小寫L。
      • 得用腦袋自己做一次所謂的common sense轉換
    • Clarity is king
  • Class Names
    • Class name 應該是名詞,而不該出現動詞。也要避免模糊的字眼,例如Data, Info等等
      • 例子
        • 錯的
          • Manager
          • Processor
          • Data
          • Info
        • 對的
          • Customer
          • WikiPage
          • Account
          • AddressParser
  • Method Names
    • Method name 應該具有動詞
      • 例子
        • postPayment
        • deletePage
        • save
    • 當建構式方法多載時,建議使用static factory method ,比較能瞭解丟一個參數進來for what
      • Complex fulcrumPoint = Complex.FromRealNumber(23.0);
      • Complex fulcrumPoint = new Complex(23.0);
  • Don't Be Cute
    • 不要在Naming上搞笑,可能導致只有自己懂笑點,而別人搞不懂這個東西是什麼
      • 例子
        • HolyHandGrenade
        • DeleteItems
    • 用白話、常見的term命名,不要用文言文,或俚語
      • 例子
        • whack()
        • kill()
      • 例子
        • eatMyShorts()
        • abort()
  • Pick One Word per Concept
    • Pick one word for one abstract concept and stick with it
      • 同樣意思就用同樣的字,例如取得資料,就用get,不要有get又有fetch又有retrieve
  • Don't Pun
    • 不要一語雙關,讓讀者有猜測的空間,或需要玩猜猜看
      • 不要使用同一個字代表兩種目的
        • 例如我們用了add這個字來代表兩個值相加。
          但在另一個情況,例如要將某一個element 加入array中,雖然也是加入。
          但應避免使用add這個字,因為代表兩種不同意思。可選擇換用insert 或是 append
  • Use Solution Domain Names
    • 命名應使用programmer常用、理解的term
      • 符合computer science
        • 例如
          • AccountVisitor
            • the VISITOR pattern
          • JobQueue
  • Use Problem Domain Names
    • 如果沒有適合使用的programmer term ,那就使用domain的term ,讓接手維護的人至少還可以問domain專家,這代表什麼
  • Add Meaningful Context
    • 將具有抽象意義與關係的變數,轉變成同一個domain object的屬性
      • 不好的例子
        private void printGuessStatistics(char candidate, int count) { 
           String number; 
           String verb; 
           String pluralModifier; 
           if (count == 0) { 
             number = "no"; 
             verb = "are"; 
             pluralModifier = "s"; 
           } else if (count == 1) { 
             number = "1"; 
             verb = "is"; 
             pluralModifier = ""; 
           } else { 
             number = Integer.toString(count); 
             verb = "are"; 
             pluralModifier = "s"; 
           } 
           String guessMessage = String.format( 
             "There %s %s %s%s", verb, number, candidate, pluralModifier 
           ); 
           print(guessMessage); 
        } 
          • The function is a bit too long and the variables are used throughout
          • 三個變數number,verb,pluralModifier看不出來彼此之間的關係
        • 好的例子
          public class GuessStatisticsMessage { 
          private String number; 
          private String verb; 
          private String pluralModifier; 
          public String make(char candidate, int count) { 
             createPluralDependentMessageParts(count); 
             return String.format( 
               "There %s %s %s%s", 
                verb, number, candidate, pluralModifier ); 
          } 
          private void createPluralDependentMessageParts(int count) { 
             if (count == 0) { 
               thereAreNoLetters(); 
             } else if (count == 1) { 
               thereIsOneLetter(); 
             } else { 
               thereAreManyLetters(count); 
             } 
          } 
          private void thereAreManyLetters(int count) { 
             number = Integer.toString(count); 
             verb = "are"; 
             pluralModifier = "s"; 
          } 
          private void thereIsOneLetter() { 
             number = "1"; 
             verb = "is"; 
             pluralModifier = ""; 
          } 
          private void thereAreNoLetters() { 
             number = "no"; 
             verb = "are"; 
             pluralModifier = "s"; 
          } 
          } 
        • create a GuessStatisticsMessage class and make the three variables fields of this class
        • 拆成class,並賦予domain意義
  • Don't Add Gratuitous Context
    • 每個東西命名若都加上application或class的prefix,反而造成每個term都有,那就多餘了
      • 例如
        • accountAddresscustomerAddress都是不錯的instance of Address class,但不適合直接拿來當class name
  • Final Words
    • 不要害怕rename,現在的IDE重構工具都相當方便使用
      • 持續使用refactoring tool 改善命名,只需要花一點小時間就可以省下以後一長段時間

 

結論

Naming Rule的精神就是「名符其實」,有一些以往受限於工具、語言的習慣,其實對現在的工具與語言特性,甚至開發模式,都可能會造成不容易維護、缺乏彈性,都還有不少的改善空間。

這本書的第二章針對Naming提出了不錯的Guide供大家審視與訂出命名規則,或許每個人、每個環境有不同的習慣,不過歡迎大家一起來討論與補充。


blog 與課程更新內容,請前往新站位置:http://tdd.best/