# Clean code 讀書會筆記 --- 讀書會進行方式: 1.當周讀書會決定下週導讀的人選 2.導讀的人分享自己的筆記,儘量能與工作經驗結合,或是想到某篇文章、某本書、上過的課的內容 3.共筆會放在hackMD 之後由當週導讀的人編輯 若是討論時有想到東西也可補充共筆裡 --- # 第一章 - 無瑕的程式碼 (2021/11/30導讀by 廷釗) Q.何謂無瑕的程式碼? A:無瑕的程式碼並沒有標準答案。雖說良好的的程式碼大多吻合低偶和、高凝聚、易擴充、單一工作原則等等大量的特徵,但為了達成目的往往得做出不符合原則的邏輯。完美是不存在的,良好的程式碼是為了達成目的,盡可能寫出好維護易擴充的妥協下的成果。 無瑕的程式碼其實是一種態度,既使已經完成功能,但程式往往無法一次到位,持續的重構修改,讓程式持續進步的態度 --- # 第二章 - 有意義的命名 (2021/11/30導讀by 廷釗) Q.為了成為一個優秀的程式設計師,第一個該學的語言是什麼? A:英文 良好的程式碼,要能完整的表達自己的目的、行為甚至演算法;註解是最後手段。所以能表示參數和方法的正確命名非常重要。 程式設計師能合作,不是因為有相同的程式水平,是因為有相同的程式共通語言。所以使用彼此有共識的用語,能讓溝通更順暢 --- # 第三章 - 函式 (2021/12/7導讀by Alice) ### 簡短 * <= 20行,一行 <= 150個字母(作者認為) ### 只做一件事(SRP) * 如何得知函式只做一件事?是否能提煉出另一函式 * 同一層抽象概念的幾個步驟算只做一件事 * 大架構與細節分離 * 無副作用 * 錯誤處理就是一件事 ### Switch / 一連串的if-else * 只出現一次,且是產生多型,並藏在某個繼承關係下 ### 別害怕取較長的名稱 ### 別使用超過三個參數的函式 * 因參數和函式在不同抽象層會使測試組合變多 * 解法:利用物件包裝成一個概念,進而減少參數 ### 可試著將參數名加入函式名 * ex: assertEquals(expected, actual) -> assertExpectedEqualsActual(expected, actual) DRY(Don’t Repeat Yourself) ### 減少重複 ### 會議上提到 * Anonymous Function(匿名函式) * dot syntax 延伸資料(by Allen): Pure Function functional programming引入immutable class -> state linked list 範例: https://www.youtube.com/watch?v=Fb-bDigImpw --- # 第四章 - 註解 Comments (2021/12/14導讀by Allen) [詳細資料](https://paper.dropbox.com/doc/Chapter-4-Comments-oEz76S5TrFEE1fP7fiOzz) ## When should you NOT use comments? - Comments should not explain code, the code should. - Change logs, Attributions. - Position marker comments, ending braces comments. - Not adding any new value - noise comments. - Mumblings or frustrations in comments. - Misleading or wrong comments. - Confusing comments - Need a comment to explain comments! - Private API - Comments for all functions - NO! - Too much details in comments. ## When should you use comments? - Public API’s. - Reveal **Intent**. - Explain **rationale** behind decisions made. - To provide **justification** on approach taken. - For providing caution or **amplify**/emphasis. - Explaining the “**why**” program works, “how” is for code to tell. - Difficult understand code could use comments. --- # 第五章 - 編排 Formatting (2021/12/14導讀by Allen) [詳細資料](https://paper.dropbox.com/doc/Chapter-5-Formatting-oxW1eIwvBZKQf3f1kRkya) ## Formatting == Communication == Readability - Formatting is all about **communication** with your fellow developers. - Readability == Maintainability == Extensibility - Without indentation the code is not readable. - Virtical Alignment of Code - Similar functions/concepts grouped together, avoid unnessary comments - Most important functions code first - Horizontal Alignment of Code - Developer should not have to scroll to the right - Spaces to be used to accentuate ease of reading. - **TEAM formatting rules** = braces, indent size, space/tabs, IDE rules. --- # 第六章 - 物件及資料結構 (2021/12/21導讀by 小猴) ## 資料抽象化 讓使用者在不需要知道實現過程的狀態下,還能操縱資料的本質 ## 資料/物件的反對稱性 - 結構化的程式碼容易添加新的函式。物件導向的程式碼容易添加新的類別 - 結構化的程式碼難以添加新的資料結構。物件導向的程式碼難以添加新的函式 ## 德摩特爾法則 得墨忒耳定律(Law of Demeter,縮寫LoD) - 每個單元對於其他的單元只能擁有有限的知識:只是與當前單元緊密聯繫的單元。 - 每個單元只能和它的朋友交談:不能和陌生單元交談。 - 只和自己直接的朋友交談。 ## 資料傳輸物件 (Data Transfer Objects,DTO) 最佳的資料結構型式,是一個類別裡只有公用變數,沒有任何函式。 --- # 第七章 - 錯誤處理 (2021/12/28導讀by John) 錯誤處理很重要,但***不能***模糊原本程式碼的邏輯 演進 - 從return non-zero code到throw exception讓處理例外的程式碼與程式邏輯分離了 TDD - Test Driven Development 先寫例外狀況滿足test case Use Unchecked Exceptions - 避免巢狀修改破壞 [Open/Closed principle] (https://zh.wikipedia.org/wiki/开闭原则) Unchecked/Checed: compile-time checked or not 提供例外的相關資訊,Call Stack 可能有也可能沒有詳細錯誤資訊 > 個人覺得通常好的方法名稱可以表達得出操作流程 用呼叫者的角度定義例外類別,呼叫者不需關心你細部的錯誤類別有多少 定義正常的程式流程,其實就是程式碼的分工階層,錯誤處理可以在越底層被處理越好,影響的範圍越小,除非外部真的需要去知道錯誤的細節 不要回傳null,多使用exception與try-catch。 > 不過真的需要用到的話,對於null判斷來說optional chaining是個解套方式 總結: 可讀性至上 --- # 第八章 - 邊界Boundaries (2022/01/04導讀by 水箭龜) ## 為什麼邊界要管理? - 適當的封裝可以增加可讀性與維護性。 - 程式語言與環境是會改變的,透過學習性測試時的測試案例可以知道環境是否有改變。 - 在第三方(環境)還沒準備好時,設計預期的規格(Interface)來透過此規格轉寫模擬程式,可以幫助開發。 ## 註釋 ### 邊界 邊界指系統與程式的交界處,使用API、第三方套件都算程式的邊界。 ![Boundaries](https://lh3.googleusercontent.com/pw/AM-JKLVUVtdJVRajVzpfr_ivncomrmSM8bNuCCcxLmJGvpDMbBYBlIruEbx-xRLayc_ERfrukwqwqMlGDUYsCvjlaPdQOTb5uVqs0iH7t5rx4brzh_eD2z7bhaRMNFLmJMTmmKglIBR4VML1Xi57NY19K6ES=w900-h600-no) ### 學習式測試 透過撰寫測試案例來了解怎麼運用邊界。 ## 參考資料: https://github.com/JuanCrg90/Clean-Code-Notes#chapter8 https://medium.com/%E6%89%8B%E5%AF%AB%E7%AD%86%E8%A8%98/boundaries-774cae00dfbd --- # 第九章 - 單元測試 (2022/1/4導讀by Allen) ## The Three Laws of TDD > First Law - You may not write production code until you have written a failing unit test. 在動手寫或修改任何一行 production code 之前,都應該要有目的,除非是為了重構。而這個目的、需求,都應該要能用測試案例來描述出來。當透過測試案例描述完這個「待完成」的需求,自然就會得到一個紅燈,而實際動手異動任何一行程式碼,都是把完成這個需求當作目標。 > Second Law - You may not write more of a unit tests than is sufficient to fail, and not comipling is failing. 當你已經有一個失敗的測試案例時,要做的應該是修改 production code 以通過這個測試案例,而不是增加更多失敗的測試案例。當然,編譯錯誤也算錯誤。 > Third Law - You may not write more production code than is sufficient to pass the currently failing tests. 你寫的每一行 production code 其目的應該都只為了讓眼前這個紅燈變成綠燈,也就是通過眼前這個失敗的測試案例。跟這個測試案例無關的程式碼,一行也不准多寫。 ## Clean Tests > What makes a clean test? > Three things. Readability, readability, readability. ## One Assert per test It's recomendable maintain only one asserts per tests, because this helps to maintain each tests easy to understand. ## Single concept per Test This rule will help you to keep short functions. - Write one test per each concept that you need to verify ## F.I.R.S.T - Fast - Test should be fast. - Independient - Test should not depend on each other. - Repeatable - Test Should be repeatable in any environment. - Self-Validating - Test should have a boolean output. either they pass or fail. - Timely - Unit tests should be written just before the production code that makes them pass. If you write tests after the production code, then you may find the production code to be hard to test. ## 參考資料: https://github.com/JuanCrg90/Clean-Code-Notes#chapter9 https://www.dotblogs.com.tw/hatelove/2015/12/14/explanation-the-three-laws-of-tdd https://medium.com/%E6%89%8B%E5%AF%AB%E7%AD%86%E8%A8%98/unit-tests-ecc8ed18d583 --- # 第十章 (2022/1/11導讀) --- # 第十一章 (2022/1/11導讀) --- # 第十二章 - 羽化 (2022/1/18導讀by John) 普遍認為「簡單設計」四守則 by Kent Beck有助於產生良好的設計(單一職則原則,相依性反向原則) ## 1. 執行完所有的測試 * 遵守單一職責原則的程式,以及降低耦合度的程式容易被測試,才能被驗證,進而部屬到系統中 * 有了測試能夠消除「整理程式碼會破壞程式碼」 的恐懼 ## 2. 沒有重複 * 提取重複的部份,簡化資料維度 * 根據Template Method Pattern 建立base class(可以是abstract class) 參考資料: [Template Method Pattern](https://en.wikipedia.org/wiki/Template_method_pattern) ## 3. 表達力 * 良好與簡短的命名 * 可以使用Pattern中的標準命名 (例如 SomeCommand 或 SomeVisitor) * 用心照顧函式與類別 ## 4. 最小化類別與方法數量 * 消除重複 * 不要製造大型類別和方法 --- # 第十三章 - 平行化 (2022/1/18導讀by Alice) 撰寫整潔的且正確的平行化程式是困難的 ## 為什麼要平行化? 經由正確的平行化能將『做什麼』與『何時作』解耦合,改善產能與結構。 1. 有時候可以改善效能。 2. 需要修改原有的設計。 3. 會帶來額外負擔,包含程式效能處理及撰寫額外的程式碼 4. 正確的平行化是複雜的,即便是簡單的問題 5. 平行化的錯誤通常不容易出現 6. 對設計策略上進行整體的修改 ## 平行化的防禦原則 單一職責原則 1. 平行化設計本身已經複雜到足以成為一個修改的理由,因此值得和其餘的程式碼有所劃分。 2. 建議清楚劃分『平行化相關程式碼』與『其他程式碼』。 推論 1. 限制資料的視野 - 封裝,避免忘記保護 - 使用synchronized 來保護目標共享物件的臨界區域程式碼 - 嚴格限制共享資料的存取次數 2. 使用副本,自多執行緒收集複本,在單一執行緒將結果合併 3. 執行緒盡可能獨立處理 ## 理解你的函式庫 1. 使用函式庫提供的安全執行緒集合(java.util.concurrent) 2. 使用executor框架來執行不相關的工作 3. 減少使用non blocking 的解法 建議應該瞭解的類別: ConcurrentHashMap ReentrantLock Semaphore CountDownLatch java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks ## 各種鎖與多重執行緒問題與經典OS題目 學基本的演算法--哪些?? ## 當心同步方法之間的相依性 避免在一個共享物件上使用超過一個方法。 三個解法: 1.基於客戶端的鎖定 2.基於伺服器的鎖定 3.適應性伺服器 (Adapted Server) ## 保持同步區塊的簡短 ## 撰寫正確的關閉(Shut-Down)程式碼是困難的 可能產生死結,檢視現有的演算法。 ## 測試執行緒程式碼 撰寫有能力暴露問題的測試,曾經失敗就追蹤,不可忽略。 1.將偽造的失敗看作是潛在的執行緒問題 不要把系統錯誤當做偶發事件,一定是程式有問題。 2.先讓你的非執行緒程式碼能順利運行 先做到單一執行緒正確,再考慮多執行緒的情況。 3.讓你的執行緒是可隨插即用的 測試程式碼可以隨意添加或刪減,可模擬不同的情況。 4.讓你的執行緒程式碼是可調校的 調整程式的環境參數是可以,例如執行緒的數量。 5.執行比處理器數量還要多的執行緒 要測試工作交換(task swapping)的情況,執行緒數量要比處理器或核心數量還要多。 6.在不同的平台上運行 在所有目標平台上進行測試。 7.調整你的程式碼,使之試圖產生失敗或強制產生失敗 對程式碼加工之四種函式:Object.wait()、sleep()、yield()、priority() (1)手動撰寫,例如增加上述四種函式呼叫。 問題: a.找到合適地方插入呼叫 b.如何得知該分在哪及放哪種函式 c.這些函式為不必要且會使執行速度變慢 d.不一定找得到(靠運氣) (2)自動化 透過 Aspect-Oriented Framework (剖面導向框架)、CGLIB 或 ASM 。 可以想像類別有兩種實作 a.第一種實作測試程式什麼都不做 b.第二種測試程式會產生隨機數,製造各種測試條件(隨機產生上述四種函式呼叫)。 ## 總結 SRP 注意多執行緒共享的資料 封裝 瞭解基本演算法 ---