[讀書心得]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; }
-
-
例子1
-
如果一個命名,還需要註解來解釋它,就代表他還不夠清楚表示意思
-
透過命名就能淺而易見其意圖
-
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
-
例如hp, aix, sco
-
不要使用特殊的縮寫而導致閱讀的人錯誤的認知
-
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
-
例如
-
命名應避免出現不必要或沒意義的贅字
-
避免兩個以上的名字太過相像,而導致讀者直覺以為是同一個東西,尤其當Name很長一串時,就更難分辨細微的差異
-
代表同樣意思、指同一件事、指同一個東西時,命名應該一致(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;
-
0與1,小寫O與L的差異
-
Programmers must avoid leaving false clues that obscure the meaning of code
-
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
-
Class : Product
-
Noise words are redundant
-
多餘的字,只會造成誤解。例如NameString,CustomerObject
- 會讓閱讀的人想,是否有其他型別的同義物件
- 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"
-
例子
-
多餘的字,只會造成誤解。例如NameString,CustomerObject
-
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”
-
genymdhms(generation date, year, month, day, hour, minute, and second)
-
-
轉換可以發音後的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?”
-
溝通上的轉變
-
-
無法發音的Naming
-
使用可以發音的Name
-
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太容易重複出現,就會導致不容易搜尋與定位
-
the name "e" is a poor choice for any variable for which a programmer might need to search
- 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,並且容易尋找與閱讀,也比較能看得出設計的邏輯
-
-
鬼才看的懂的magic code
-
例子
-
Single-letter names and numeric constants have a particular problem in that they are not easy to locate across a body of text
-
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!
-
PhoneNumber phoneString;
-
例子
-
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,介面與實作命名的取捨
-
Interface與Implementation,寧可加識別在Implementation上,例如ShapeFactoryImp,
-
Avoid Mental Mapping
-
避免需要用腦袋做Mapping的命名,尤其是單一字母,例如i,j,k 更糟糕的是小寫L。
- 得用腦袋自己做一次所謂的common sense轉換
- Clarity is king
-
避免需要用腦袋做Mapping的命名,尤其是單一字母,例如i,j,k 更糟糕的是小寫L。
-
Class Names
-
Class name 應該是名詞,而不該出現動詞。也要避免模糊的字眼,例如Data, Info等等
-
例子
-
錯的
- Manager
- Processor
- Data
- Info
-
對的
- Customer
- WikiPage
- Account
- AddressParser
-
錯的
-
例子
-
Class name 應該是名詞,而不該出現動詞。也要避免模糊的字眼,例如Data, Info等等
-
Method Names
-
Method name 應該具有動詞
-
例子
- postPayment
- deletePage
- save
-
例子
-
當建構式方法多載時,建議使用static factory method ,比較能瞭解丟一個參數進來for what
- Complex fulcrumPoint = Complex.FromRealNumber(23.0);
- Complex fulcrumPoint = new Complex(23.0);
-
Method name 應該具有動詞
-
Don't Be Cute
-
不要在Naming上搞笑,可能導致只有自己懂笑點,而別人搞不懂這個東西是什麼
-
例子
- HolyHandGrenade
- DeleteItems
-
例子
-
用白話、常見的term命名,不要用文言文,或俚語
-
例子
- whack()
- kill()
-
例子
- eatMyShorts()
- abort()
-
例子
-
不要在Naming上搞笑,可能導致只有自己懂笑點,而別人搞不懂這個東西是什麼
-
Pick One Word per Concept
-
Pick one word for one abstract concept and stick with it
- 同樣意思就用同樣的字,例如取得資料,就用get,不要有get又有fetch又有retrieve
-
Pick one word for one abstract concept and stick with it
-
Don't Pun
-
不要一語雙關,讓讀者有猜測的空間,或需要玩猜猜看
-
不要使用同一個字代表兩種目的
-
例如我們用了add這個字來代表兩個值相加。
但在另一個情況,例如要將某一個element 加入array中,雖然也是加入。
但應避免使用add這個字,因為代表兩種不同意思。可選擇換用insert 或是 append
-
例如我們用了add這個字來代表兩個值相加。
-
不要使用同一個字代表兩種目的
-
不要一語雙關,讓讀者有猜測的空間,或需要玩猜猜看
-
Use Solution Domain Names
-
命名應使用programmer常用、理解的term
-
符合computer science
-
例如
-
AccountVisitor
- the VISITOR pattern
- JobQueue
-
AccountVisitor
-
例如
-
符合computer science
-
命名應使用programmer常用、理解的term
-
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意義
-
-
不好的例子
-
將具有抽象意義與關係的變數,轉變成同一個domain object的屬性
-
Don't Add Gratuitous Context
-
每個東西命名若都加上application或class的prefix,反而造成每個term都有,那就多餘了
-
例如
- accountAddress與customerAddress都是不錯的instance of Address class,但不適合直接拿來當class name
-
例如
-
每個東西命名若都加上application或class的prefix,反而造成每個term都有,那就多餘了
-
Final Words
-
不要害怕rename,現在的IDE重構工具都相當方便使用
- 持續使用refactoring tool 改善命名,只需要花一點小時間就可以省下以後一長段時間
-
不要害怕rename,現在的IDE重構工具都相當方便使用
結論
Naming Rule的精神就是「名符其實」,有一些以往受限於工具、語言的習慣,其實對現在的工具與語言特性,甚至開發模式,都可能會造成不容易維護、缺乏彈性,都還有不少的改善空間。
這本書的第二章針對Naming提出了不錯的Guide供大家審視與訂出命名規則,或許每個人、每個環境有不同的習慣,不過歡迎大家一起來討論與補充。
blog 與課程更新內容,請前往新站位置:http://tdd.best/