# 第三章 函式 ###### tags: `Tag(Clean Code)` ## 定義: 不應該讓函式有==冗長==和==重複==的和==無意義==的字串出現在程式碼中,應該要讓維護者在不清楚全部細節狀況下可以能夠了解函式做的事,也就是需要易讀好懂的函式程式碼。 ### 簡短! #### 區塊(Block)和縮排(Indenting) if、else、while及其他函是都應該只有一行,而這一行通常是==函式呼叫敘述==,函式不應該大到巢狀式結構和超過兩層。 要讓函式只做一件事,做好一件事。 > 「將函式維持在單一層次的抽象概念」。 > 學習這個技巧很重要,讓函式保持簡短的關鍵。 > #### 如何知道只做一件事 觀察函式是否超過「一件事」,看是否能提煉出另一種新的函式。 1. **只做一件事** 如果函式只做了名稱下「同一層抽象概念」的幾個步驟,那麼此函式就算是只做一件事。 2. **做很多事** 函式內的段落,被拆解成不同段落,ex:宣告區(declarations)、初始區(initializations)、過濾區(sieve),這就是超過一件事,做一件事的函式是無法被分出做不同事情的段落。 3. 每個函式只有一層抽象概念 * 高層次 -> getHtml() * 中層次 -> includeSetupAndTeardownPages(pageData, isSuite) ==String pagePathName = PathParser.reader(pagePath)== * 低層次 -> includeSetupAndTeardownPages(pageData, isSuite) ==.append("\n")== **一層抽象概念範例**: 做了三件事 1. 判斷此頁面是否為測試頁面 2. 如果是測試頁面,納入設定與拆解步驟 3. 頁面轉換Html網頁 但為什麼符合一件事的概念,因為此函式三步驟都符合名稱下的同一層抽象概念中。 > ==閱讀此函式==: > RenderPageWithSetupsAndTeardowns,檢查此頁面是否為測試頁面,如果是就納入設定與拆解步驟,不論什麼情況下都轉換成Html網頁 ``` public static String renderPageWithSetupsAndTeardowns(PageData pageData , boolean isSuite) throws Exception{ if(isTestPage(pageData)){ includeSetupAndTeardownPages(pageData, isSuite) } return pageData.getHtml(); } ``` 4. **由上而下閱讀程式碼:降層準則** 閱讀程式可以依照看到的一連串函式,對應著抽象層次降層閱讀。 ### Switch 敘述 switch 敘述總是在做N件事,如何做到永遠不重複,可以用多型(Polymorphism來達到目的 ==問題範例==: ``` public Money calculatePay(Employee e) throw InvalidEmployee switch(e.type){ case COMMISSIONED: return calculatePayCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new InvalidEmployeeType(e.Type); } } ``` 問題: 1. 太冗長,加入新職員型態時,會變得更長。 2. 做超過一件事 3. 可能因為一個理由改變函式,違反[單一職責原則](https://zh.wikipedia.org/wiki/%E5%8D%95%E4%B8%80%E5%8A%9F%E8%83%BD%E5%8E%9F%E5%88%99) 4. 當有新的型態加入後,違反[開閉原則(OCP)](https://ithelp.ithome.com.tw/articles/10211845) 5. 程式中可能還有相同的 --- 竟量讓一個敘述中出現一個,而且用來產生多型物件,並藏匿在某個繼承關係之下。 ==利用多型改善範例== ``` public abstract class Employee(){ public abstract boolean isPayday(); public abstract Money CalculatePay(); public abstract void deliverPay(Money pay); } public interface EmployeeFactory(){ public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; } public class EmployeeFactoryImpl implements EmployeeFactory{ public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType{ switch(r.Type){ case COMMISSIONED: return calculatePayCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new InvalidEmployeeType(e.Type); } } } ``` ### 使用具有描述能力的名稱 別害怕取較長名字,一個較長但具有描述的名稱,比較短但難以理解得名稱還要好。 ### 函式的參數 函數的參數理想為零個(零參數函式;niladic),其次一個(單參數函式;dyadic),再不然三個(三參數函式;triadic),如果要使用超過三個參數(多參數函式; polyadic),必須要有特別理由,否則不應該如此,原因是要讓函式容易理解。 輸出型參數(回傳值;return value)和輸入型參數