Clean Code 無瑕的程式碼 第3章 函式
?? ? ? 第三章的主題是函式,第三章可以給讀者有很大的幫助。第三章後半部 Try/Catch 的部分可與第七章錯誤處理對照一起閱讀。
3-1重構(gòu)之後變成3-2,3-1與3-2 比較起來3-2寫得比較簡短易懂。
簡短!
函式要越短越好,3-2重構(gòu)之後變成3-3。
Listing 3-3
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
? ? if(isTestPage(pageData))
? ? ? ? includeSetupAndTeardownPages(pageData, isSuite);
? ? return pageData.getHtml();
}
區(qū)塊(Blocks)和縮排(Indenting)
If、else、while及其他敘述都應該只有一行。
函式不應該大到包含巢狀結(jié)構(gòu),函式裡的縮排程度不應該大過一或兩層。
只做一件事情
等同單一職責原則,函式應該只做一件事。
如何判斷只做一件事?
從「抽象概念」判斷是否只做一件事,3-1有不同層次的抽象概念,3-2有兩層抽象概念,3-3已經(jīng)無法再提取新的函式。
函式的段落
4-7 generatePrimes函式有幾個段落,宣告區(qū)、初始區(qū)、過濾區(qū)。這是做超過一件事的徵兆。
每個函式只有一層抽象概念
3-1程式?jīng)]有在同一層抽象概念
getHtml()? (高層次)
String pagePathName = PathParser.render(pagePath) (中層次)
.append(“\n”) (低層次)
抽象概念與細節(jié)混在一起,會讓程式碼變得更複雜。
由上而下閱讀程式碼:降層準則
程式閱讀就像由上而下的敘事,每個函式後面緊接下一個層次的抽象概念。
將函式維持在單一層次的抽象概念。
讀者可以想一想「階層式樹狀圖」就能夠理解這段內(nèi)容。
Switch 敘述
寫程式無法避免Switch,3-4是常見的情況,新增一個狀況的時候就新增一個case。
?
作者認為這種有下列缺點
?
太過冗長
函式做了超過一件事
超過一個理由可以改變函式,違反單一職掌原則(SRP)
新型態(tài)加入後,函式必須改變,違反開放閉合原則(OCP)
其他地方有相同的Switch case 結(jié)構(gòu)
?
有兩個方法可以解決Switch case引發(fā)的問題多型(Polymorphism)與讓每個switch敘述埋藏在較低的抽象層次的類別,而且不會被重複使用。
?
3-5 將Switch埋在抽象工廠(ABSTRACT FACTORY)底下,產(chǎn)生Employee介面的衍生實體,不同的函式透過多型來指派。
?
使用具描述能力的名稱
函式名稱要能夠正確表達函式的功能,函數(shù)名稱可與第二章對照看。
函式的參數(shù)
最理想的情況是零個參數(shù),儘量避免使用三個以上的參數(shù),參數(shù)過多會讓函數(shù)難以測試。
單一參數(shù)的常見形式
與這個參數(shù)有關(guān)的問題
對這個參數(shù)進行操作,然後回傳
輸入事件(event)
旗標(flag)參數(shù)
作者反對旗標參數(shù)(輸入true 或 false),違反單一職責原則,表示函式做兩件事情,應該將函式分成兩個參數(shù)。
兩個參數(shù)的函式
兩個參數(shù)的函式可讀性不如一個參數(shù)的函式。
平面上的點位point會出現(xiàn)兩個參數(shù)的情況,可以設(shè)法將兩個參數(shù)化為一個參數(shù)。
三個參數(shù)的函式
儘量不要出現(xiàn)三個參數(shù)以上的函數(shù)。
物件型態(tài)的參數(shù)
參數(shù)過多的時候,可將其中一些參數(shù)包裝在一個類別。
參數(shù)串列
部分情況參數(shù)沒有固定的數(shù)量,如同一個List。List會被視做一個參數(shù)。
動詞和關(guān)鍵字
函式名稱儘量使用動詞與關(guān)鍵字,可增加程式可讀性。
assertExpectedEqualsActual(expected, actual) 這種函數(shù)名稱寫法參數(shù)順序與意義一目瞭然。
要無副作用
函式只做一件事,副作用指函數(shù)還會做其他事情。
3-6 函式還會進行初始化的動作,函數(shù)名稱並沒有提到會做初始化的動作。
輸出型的參數(shù)
作者反對輸出型參數(shù),會影響程式的可讀性。
例如? function( out? p) 函數(shù)將計算的結(jié)果輸出到p 。
如果不看函式宣告,會不知道是輸出型參數(shù)。
物件導向程式設(shè)計出現(xiàn)以後,this扮演輸出型參數(shù)的角色。物件可改變本身的狀態(tài),取代輸出型參數(shù)。
指令和查詢的分離
考慮以下函式
public boolean set(String attribute, String value);
使用的時候
if(set(“username”, “uncleb”))...........
指令與查詢混在一起,很難從呼叫中知道真正的意義
指令(command)與查詢(query)分開,修改物件狀態(tài)與回傳物件資訊要分開完成。修改函數(shù)名稱之後,能夠清楚表達真正的意義。
if(attributeExists(“username”)){
? ?setAttribute(“username”,”unclebob”);
}
使用例外處理取代回傳錯誤碼
用Try/Catch 例外處理取代回傳錯誤碼
提取 Try/Catch 區(qū)塊
從範例可看出從原本的Try 區(qū)塊提取出新的函式讓程式整潔。
錯誤處理就是一件事
處理錯誤的函式不應該再做其他事。try在函式裡幾乎是函式的開頭,在catch/finally區(qū)塊之後也不應該出現(xiàn)其他程式碼。
Error.java 的依附性磁鐵
作者說明為何例外處理的寫法比回傳錯誤碼的方式好?
假設(shè)新增一種錯誤狀況
回傳錯誤碼的方法會修改列舉,會變成相關(guān)的類別必須重新編譯。
例外處理的寫法新的例外可由例外類別衍生出來,不必全部重新編譯。
不要重複自己
軟體發(fā)展領(lǐng)域的所有創(chuàng)新,都是為了消除原始碼的重複。
結(jié)構(gòu)化程式設(shè)計
每個函式區(qū)塊都應該只有一個進入點與一個離開點。
一個函式只能有一個return敘述,迴圈內(nèi)不能有任何break或continue敘述,不可以有g(shù)oto敘述。
結(jié)構(gòu)化程式設(shè)計對小函式幫助有限,對大函式才能展現(xiàn)出巨大的好處。
goto 敘述只有在大型函式才有用。
你要如何寫出這樣的函式?
寫程式如同寫文章,不可能「馬上好」只能「馬上漸漸好」,初稿總是雜亂無章,因為有單元測試程式,可以持續(xù)修改程式碼。
總結(jié)
函式是動詞,類別是名詞。
寫程式像是說故事。