# Clean Code - Ch10 類別 ## 1. 類別的結構 在Java裡面從上到下的順序為: 1. 公用靜態常數 (public const paramater) 2. 私有靜態變數 (private static parameter) 3. 私有實體變數 (private parameter) 4. 公用函數 (public function) 5. 私有函數 (private function) **一般來說應該盡量保持封裝,但有時候為了寫 Unit Test 不得不將其從 private 改成 protected** ## 2. 類別要夠簡短 不同於函式用程式碼行數來衡量大小,而類別的職責數量越少越好 以下類別雖然程式碼少,但擁有太多職責數量 ``` csharp public class SuperDashboard : JFrame , MetaDataUser { public Componet getLastFocusedComponent() {} public void setLastFocused(Component lastFocused) {} public int getMajorVersionNumber() {} public int getMinorVersionNumber() {} public int getBuildNumber() {} // and other function... } ``` 類別的命名應該要可以描述他的職責,因此如果名稱取得越籠統越模稜兩可,就會導致類別的內容變得過多。因此類別的命名應該盡量少用含糊不清的字(ex. Processor、Business、Super...) ## 3. 單一職責原則 (Single Responsibility Principle,SRP) **類別應該只有一個職責** = **一個類別應該只有一個修改的理由** 每個足夠大的系統,都會有大量且複雜的邏輯及流程。要管理這些邏輯的首要目標就是要可以組織且將他們歸類好,使開發者可以快速地找到需要的東西。 而如果是數量少但含有大量邏輯的類別的話,這樣會導致我們在尋找我們需要的功能前還要先花大量的時間去了解其他不相關的邏輯。 因此比起數量少但龐大的類別,數量多但小型的類別可以提供足夠的彈性讓我們可以輕鬆地了解邏輯並調用。 ## 4. 凝聚性 類別應該要只有少量的全域變數,該類別的function應該都要操縱一個或以上該類別內的變數,在function裡面用到的該類別內的變數越多,代表這個function跟這個類別的凝聚性越高。 凝聚性高代表類別理的方法和變數是相互依賴的,並互相結合成一個邏輯上的整體。 若一個類別內的每個變數都被用在每個function內,代表這個類別的凝聚性達到最高。但通常不建議也不容易做到這個程度。 以下就是一個達到高凝聚性的class,除了size()外,其他的function都有用到所有的變數。 ``` csharp public class Stack { private int topOfStack = 0; List<Integer> elements = new LinkedList<Integer>(); public int size() { return topOfStack; } public void push(int element) { topOfStack++; elements.add(element); } public int pop() throws PopedWhenEmpty { if (topOfStack == 0) { throw new PopedWhenEmpty(); } int element = elements.get(--topOfStack); elements.remove(topOfStack); return element; } } ``` 隨著專案的進行,類別可能會變得越來越大,為了保持簡短的function並 **"減少類別內的參數個數"** 可能會讓 **"使用特定變數"** 的一些function數量增加 **(ex. 功能接近的function)** ,這樣代表著可以試著將這些變數與function抽出去到新的類別裡面。 抽出去的類別裡面的代碼可能會變得更多,因為他們的內容變得更細,並使用了更具說明性的名稱 ## 5. 為了變動而構思組織 對系統而言,系統的改變是持續性的,而每次的修改都是有風險的。因此在最初建立系統時,就可以根據預期後續的改動而先給你的系統一個彈性的架構,方便後續的修改。 假設今天要增加 Update() 的function,就必須要改動到sql這個類別,這有可能會因為改錯導致sql這個類別裡面的其他部分被影響到導致之後出錯 ``` csharp public class Sql { public Sql(String table, Column[] colums) {} public String create() {} public String insert(Object[] fields) {} public String selectAll() {} public String select(Column column, String pattern) {} //... } ``` 而書上是建議可以改成這個樣子,增加一個Update的class,並繼承sql的class 這樣是確保就算出錯也只有update會錯而不會影響到其他舊有的部分 ``` csharp abstract public class Sql { public Sql(String table, Column[] colums) {} abstract public String generate(); } public class CreateSql extends Sql { public CreateSql(String table, Column[] colums) {} @Override public String generate() {} } public class InsertSql extends Sql { public InsertSql(String table, Column[] colums) {} @Override public String generate() {} } ``` 類別可以遵循開放封閉原則 (Open-Closed Principle,OCP) 可以擴充但是不可以修改的,達到類別對擴充具有**開放性**,對修改具有**封閉性** ## 6. 隔離修改 雖著時間過去,越續的需求會使得系統出現變更。但如果都是直接將邏輯寫在類別裡,而不是透過interface來界接這些實作的部分,將會使得類別無法被Unit Test測試到 而就算可以測試的部分也會因為越來越多的引用導致你需要花更多的時間把所有的部分都了解過才能寫出符合的測試場景 >我們真正所需要的、依賴的,其實不是實際的類別與物件,而是他所擁有的功能。 其實這就是 依賴倒置原則 DIP (Dependency Inversion Principle): > >1. 高階模組不應該依賴於低階模組,兩者都該依賴抽象。 >2. 抽象不應該依賴於具體實作方式。 >3. 具體實作方式則應該依賴抽象。 > >[Dependency Inversion Principle,DIP](https://notfalse.net/1/dip) ###### tags: `Clean Code` `Book`