Clean Code: Function 函式
Chapter 3: Function
-
簡短!
關於函式的首要準則就是簡短,第二項準則就是要比第一項的簡短函式還要更簡短。
-
區塊(Blocks)和縮排(Indenting)
If、else、while及其他描述都應該只有一行,而那行通常是函式呼叫描述。不僅能維持封閉函式的簡短,區塊內呼叫的函式名稱也能來描述意圖。
public static String renderPageWithSetupsAndTeardowns(PageData pageData, bool isSuite) { if (isTestPage(pageData)) includesSetupAndTeardownPages(pageData, isSuite); return pageData.getHtml(); }
-
只做一件事情
函式應該只做一件事情
-
函式的段落
如果一個函式被拆成幾個段落,包含宣告區 declarations、初始區 initializations、 過濾區 sieve,這是做超過一件事情的徵兆
-
每個函式只有一層抽象概念
-
由上而下閱讀程式碼:降層準則
我們希望在閱讀程式碼時,能夠像閱讀一連串TO段落,每個段落描述著目前所處的抽象層次,並且提及了接續的下個層次的TO段落
-
Switch描述
Switch函式問題一可能會太冗長,問題二涵式可能做超過一件事情,問題三可能違反單一職責原則(SRP),第四點當新型態加入後,函式必須改變所以它違反了開放封閉原則(OCP)
作者認為可以使用switch描述來產生Emplyee介面(interface)的實體(instance),另外,其他不同的函式都藉由介面,透過多型的方式來指派,例如calculatePay(計算薪水)、isPayDate(是否為發薪日)、deliverPay(發薪水)
-
使用具描述能力的名稱
當每個你看到的程式,執行結果都跟你想得差不多,你會察覺到你正工作在Clean Code上
維持命名的一致性,在你的模組裡,使用一致的片語、名詞和動詞來替函式取名
-
函式的參數
最理想是零參數函式,其次是一個,盡量避免使用三個參數。
-
單一參數的常見形式
1.與這個參數有關的問題,例如 boolean fileExists("MyFile")
2.對參數進行某種操作,例如 InputSteam fileOpen("MyFile")
你應該選擇能明顯區分這兩種的名稱,如果一個函式會轉換輸入的參數,則轉換後的產物應該出現在回傳值裡
-
兩個參數的函式
writeField(name) 比 writeField(outputStream, name) 更容易理解 outputStream.writeField(name) 是更好的方式
-
三個參數的函式
使用者需反覆閱讀查看才能理解
-
物件型態的參數
Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius);
可以使用類別來統整概念相似的參數
-
動詞和關鍵字
函式和參數要形成一個動詞/名詞的良好配對,例如寫入名稱write(name),name會被寫入,或writeField(name)更能告訴我們name是一個欄位(field)。
關鍵字式的命名,例如 assertEquals -> assertExpectedEqualsActual(expected, actual),減輕了需記住參數的負擔
-
要無副作用(side effects)
你的函式保證只做一件事,但卻暗地裡偷偷做了其他事情
public class UserValidator { public boolean checkPassword(string userName, string password) { User user = UserRep.findByName(userName); if (user != null) { if (user.password == password) { Session.initialize(); //side effects 使用者並不知道此函式內會改變Session,這是有風險的 //,除非將函式名稱修改為checkPasswordAndInitializeSession return true; } else { return false; } } } }
-
輸出型的參數
要避免使用輸出型參數,如果函式必須要改變物件的某種狀態,就讓該物件改變其本身的狀態吧
public void appendFooter(StringBuffer report); -> report.appendFooter();
-
指令和查詢的分離
將指令Command和查詢Query分開,避免模稜兩可的情形。
public boolean set(String attribute, String value); if (set("username","unclebob")) ... //無法判斷是詢問username被設為unclebob或是將username設為unclebob並回傳... //應該修改為 if (attributeExists("username")) { setAttribute("username","unclebob"); }
-
使用例外處理取代傳回錯誤碼
//將指令函式當作判斷表達式用 if (deletePage(page) == E_OK) //可能回導致更深層的巢狀結構 if (deletePage(page) == E_OK) { if (registry.deleteKey(page.name.makeKey()) == E_OK) { logger.log("page deleted"); } else { logger.log("configkey not deleted"); } else { logger.log("deleteReference from registry failed"); } } else { logger.log("delete failed"); return E_ERROR; }
try { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey()); } catch (Exception e) { logger.log(e.getMessage()); }
-
結構化程式設計
每個函式裡的區塊,都應該只有一個進入點跟一個離開點,迴圈內不能有任何的break或continue描述,且永遠不可以有goto描述
將函式分開、重新命名、減少重複、縮短方法並重新安排方法的順序,有時候打散整個類別,並持續保持單元測是可以通過
-
不要重複自己
重複程式碼也許是軟體裡所有的邪惡根源,物件導向就是一種減少重複程式碼的策略