--- tags: Pluralsight Note, Clean Coding Principles in C# --- # Clean Coding Principles in C# 讀書筆記 [Clean Coding Principles in C# 課程連結](https://app.pluralsight.com/library/courses/csharp-clean-coding-principles/table-of-contents) ## Why Writing Clean Code Matters ### 前言 > Programmer is the are of telling another human what one wants computer to do --- By Donald Knuth > Any fool can write code that a computer can understand. Good programmer writes code that human can understand 我們都可以輕易寫出執行**邏輯符合期望表現**的程式, 但這只是基本, 如果我們要自稱為程式設計師 , 那麼我們應該試圖寫出**他人可以輕易看得懂的程式碼.** ### Reasons to Write Clean Code - Reading is harder than writing - 產生糞 code 永遠比理解 糞 code 簡單 - Technical debt is depressing - 技術債會讓人感受到挫折 ---> 當技術債大到難以解決的時候, 可能會導致優秀人才離開 - You are lazy - 請為了讓未來的自己偷懶, 而寫出**好的 code** - Sloppy = Slow - Don't be a Verb - 假設 Sloane 寫 code 很髒, 那當我們看到 code 變髒了, 就會說 this code has been Sloane. 請努力讓你的名字不要成為這樣的動詞...XD ## Clean Coding Principles * Right tool for the job * Watch for boundaries * Stay Native * Only Language per file * Maximize signal to noise ratio * DRY * Watch the repeated pattern * Strive for Self-documenting code ### Right tool for the job ##### 工欲善其事, 必先利其器. 選擇錯誤的工具會事半功倍. - 課程舉的例子, 使用 Regrex 去驗證 email ( 完全沒辦法想像這段 Regex 要怎麼維護...) ![RegexExample](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/RegexExample.png?raw=true) - 使用錯誤的資料結構, e.g. 明明只需要 List 裝載成員, 但卻使用 Dictionary. 導致會產生不必要的程式碼 <--- 每一個成員都必須想一個 Key 給它, 且存取的時候, 也必須使用這個 Key - 課程舉的例子, 需要了解技術和技術的邊界. 然後選擇應該在哪實作. - ![WatchBoundary](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/WatchBoundary.png?raw=true) -![AvoidUsing](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/AvoidUsing.png?raw=true) - ![EveryTech](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/EveryTech.png?raw=true) ### High signal to noise ratio - Signal to Noise Ratio is important - ![Watchiing](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/Watchiing.png?raw=true) - 人的大腦一次能解析的事情是有限的, 所以盡可能讓讀者能輕易地知道你的程式碼的意圖, 避免產生任何可能會讓他們誤解的雜訊程式碼 - e.g. 沒有使用到的變數 ```var a = 5``` <--- 沒有人使用到, 但也不移除. - e.g. 控制類別內使用到的方法數量, 方法使用到的參數數量, 當前 scope 使用到的變數數量... - 事後重構絕對比事前寫好來的困難, 別再欺騙自己, 以後你會重構的(誤). - DRY Principle : Don't Repeat Yourself - Copy and Paste is often a **design problem : duplication issues** - Decrease Signal to Noise Ratio - 想像同一個段落, 在一本書中的不同頁出現. <--- 超奇怪的 ! - Increases the number of lines of code - 讀者需要理解的東西變多了XD - > Measuring programming progress by lines of code is like measuring aircraft building progress by weight - 透過程式碼行數來評估程式開發進度, 就像是透過飛機重量來評估飛機建造進度一樣, 並不是一個很好的評估方式. 這句話的比喻是要表達, 軟體開發進度應該透過更有意義的方式來評估, 例如已經完成的功能數量、軟體品質、使用者反饋等等. 因為程式碼行數多寡並不能代表程式開發的進度和品質, 一個有效率的程式設計師可能用更少的行數完成更多的功能, 而不是花費更多的時間寫更多的程式碼. - 老師說 : 有研究顯示, 越少的程式碼, 會有越少的 bug 產生. - Creates a maintenance problem - Copy and Paste 代表 bug 也會被複製, 意即當你修掉一個 bug, 不代表你連複製的 bug 也修掉了. - 小心 Repeat Pattern ! (類似的 pattern, 只是略有不同) - 善用 Method 等技術消除 Repeat Pattern ### Self-documenting > Understanding the original programmer's intent is the most difficult problem - Clear Intent - 盡可能讓讀者輕易看懂你實作的意圖 - Layers of abstractions - 善用繼承以及多型解決問題 - Format for readability - Coding Style 最好統一 - Favor code over comments - 好讀易懂的 code, 比你寫再多的註解都要來的好. ## Naming - The name should say it all - Use specific class names - Watch for function side-effects - Avoid Abbreviation - Boolean should sound true / false - Strive for symmetry - Verbalize when struggling ### Why Naming Matters ##### 比較命名好壞的差異 - ![NameingMatters](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/NameingMatters.png?raw=true) ### Naming Methods - 好的方法名稱可以節省讀者理解的時間 - 命名方式. V(動詞)+N(名詞) ![MethodNameSays](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/MethodNameSays.png?raw=true) - 如何決定方法名稱 - 若你發現很難命名, 先思考是否有違反 SRP(單一職責原則)了. 理論上, 若有遵守 SRP, 那方法名稱應該不會太難命名 - Naming Warning Signs - 當你發現你想出來的方法名稱有 "And" "Or" "If" 等字眼時, 代表這個方法大概率是違反 SRP 了. - Verbalizing aids creativity - 當你在思考方法名稱時, 先試圖對自己或是他人解釋你在實做的東西. - 因為你會開始思考牽涉的角色, 以及實作等等細節. ### Avoid Side Effects > Make sure the method names are comprehensive and tell the whole true - 請確保方法只有實作方法名稱內敘述的事情, 不要欺騙讀者 ! - e.g. ```C# // 此方法除了回傳最大值以外, 還偷偷將最小值存起來. public class SampleClass { private int _minValue; public int FindMaxValue(List<int> collection){ _minValues = collection.Min(); retun collection.Max(); } } - 命名時需要依據方法的實作. 盡可能不要加入不必要的具體資訊 - e.g. ```C# public class SuperMarketProduct : Product {} public class AnotherClass { private List<Product> _products = new List<Product>; // 目前的 User Cass 可能只會加入 SuperMarketProduct 實體. // 但難保未來不會有其他的產品會透過這個方法被加入 _products. publci void AddSuperMarketProduct(Product product) => _products.Add(product); } ``` ### Avoid Abbreviations ![Abbreviations](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/Abbreviations.png?raw=true) - 現在不是 80 年代那個需要為了記憶體分秒必爭的年代了 XD - Abbreviations 容易造成誤解, 不同背景的人看到 Abbreviations, 都可能有不同的解讀 ### Naming Variables : Boolean ![NamingVariablesBooleans](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/NamingVariablesBooleans.png?raw=true) - 顯然, 右邊的例子更能看出變數的性質 ### Naming Variables : Be Symmetrical ![BeSymmetrical](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/BeSymmetrical.png?raw=true) ## Writing Conditionals that conveny intent 條件式就像是路牌, 簡單易懂的路牌幫助我們了解程式 ### Boolean Comparisons - ![Comparisons](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/Comparisons.png?raw=true) - 口語上, 我們不會說如果` loggIn 等於 true 所以 ...` 而是會說`如果 loggIn 所以...` 故表達意圖上, 使用下面的方式比較好. ### Boolean Assignments - ![BooleanAssignments](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/BooleanAssignments.png?raw=true) - 程式碼越少, 越容易理解閱讀. 同時這也代表讀者能用最少的字看到最多的資訊. - 上面不好的例子, 同一個 bool 變數能在兩個不同地方做初始化. 增加閱讀的複雜度. - 上面不好的例子, 重複了變數名稱三次, 這增加了犯錯的風險. (觸犯 DRY 原則) - 分別朗讀好的例子和不好的例子的程式碼 - 好例子 ---> `我將會去吃辣椒當作午餐, 如果我錢包內的錢超過 6 元.` - 不好的例子 ---> `如果我錢包內的錢超過 6 元, 我將會去吃辣椒當作午餐, 否則我不會吃辣椒當作午餐.` - 由以上兩個句子可以發現, 好例子的句子能用比較少的字表示較多的意圖. ### Prefer Positive Conditionals - ![PositiveConditionals](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/PositiveConditionals.png?raw=true) - 人比較容易理解正面條件, 相比於負面條件. 所以變數命名請盡可能使用 positive conditionals 命名. (不用讓讀者用負負得正的思考方式) - e.g var isNotLoggedIn = false <--- 沒有 沒有登入 (有登入) - e.g var isNotLoggedIn = true <--- 沒有登入 - e.g. var isLoggedIn = false <--- 沒有登入 - e.g. var isLoggedIn = true <--- 有登入 ### Ternary Operation is Beautiful - ![TernaryOperation](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/TernaryOperation.png?raw=true) * 程式碼越少, 越容易理解閱讀. 同時這也代表讀者能用最少的字看到最多的資訊. * 上面不好的例子, 同一個 int 變數能在兩個不同地方做初始化. 增加閱讀的複雜度. * 上面不好的例子, 重複了變數名稱三次, 這增加了犯錯的風險. (觸犯 DRY 原則) * **三元運算雖然可能讓 code 變簡單, 但不要寫成巢狀三元 ...** ### Be Strongly Typed(via constants, enums..) ![StronglyTyped](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/StronglyTyped.png?raw=true) - 使用字串有打錯字的風險, 但使用 Enum 不會. 你不會想在程式中尋找你打錯的字的... - 現在 IDE 的 Intellisense 可以幫你推斷你可能要打的東西 e.g. Manager 可能就不用打 <--- 少打字 - 可透過 Enum 了解程式中淺在邏輯 e.g. Enum EmployeeType <-- 依據其他值看出還有哪些員工 - 搜尋使用處參考時, Enum 比字串好搜尋. 因為搜尋字串的範圍非常的大 (甚至連註解都會搜尋) ### Magic Numbers ![MagicNumbers](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/MagicNumbers.png?raw=true) - 魔術數字期望讀者自行去了解這數字(21)的含意 , 讀者可能必須透過閱讀前後程式碼去猜測此數字的用途. 例如為啥是這個數字等... (應加讀者的困擾) - 試圖簡單地透過變數名稱或是 Enum 去解釋魔術數字的含意 ### Handling Complex Conditionals 有兩種方式去避免複雜的條件式判斷隨著時間而成長. 1. Intermediate Variables 1. Encapsulate via function - Intermediate Variables ![IntermediateVariables](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/IntermediateVariables.png?raw=true) - 使用 bool 變數儲存判斷結果, 讀者可以透過變數名稱去了解這串判斷的邏輯. - Encapsulate via function ![EncapsulateVia](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/EncapsulateVia.png?raw=true) - 透過方法將判斷封裝起來. 讀者可以透過方法名稱去了解意圖, 但卻不需要了解實作細節. - 我們應該抽方法, 讓每一個方法盡可能遵守 SRP 原則 - 若此方法只會在一個地方被使用, 請抽成 Local Method. ### Favor Polymorphism Over Switch ![FavorPolymorphism1](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/FavorPolymorphism1.png?raw=true) ![FavorPolymorphism2](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/FavorPolymorphism2.png?raw=true) ![FavorPolymorphism3](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/FavorPolymorphism3.png?raw=true) ![FavorPolymorphism4](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/FavorPolymorphism4.png?raw=true) - 複雜的 If 條件式或是 Switch 有時候對於讀者來說是痛苦的, 可適時地善用多型去消除累贅的分之判斷. - 這挺考驗軟體工程師對於抽象化的功力的說XD. ### Be Declarative ![BeDeclarative](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/BeDeclarative.png?raw=true) * 你應該讓你**寫出的程式碼宣告你想做甚麼**, 而不是去寫出**能實現你想要的功能或達成你的目標**的程式碼 * 如果可能, 盡可能寫出宣告式的程式碼, 而非陳述式 (宣告式的風格比較能清楚地彰顯意圖) ### Table Driven Methods ![TableDriven](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/TableDriven.png?raw=true) - 此例子為把保費費率的判斷轉換成表格資料 **並存在資料庫** - 相對於數據邏輯, 人類更善於處理數據. 透過將複雜的邏輯判斷轉換成人類容易處理的資料, 從而能降低系統複雜度. - 並不是一定要使用程式碼作邏輯處理才能解決問題, 撰寫程式碼前先思考這是否是你的程式碼該處理的. - 此處將邏輯判斷轉成表格資料後, 你的程式碼就不需負責處理保費相關邏輯, 只需要從 DB 叫出對應資料即可. #### Benefit of Table Driven Methods - Great for dynamic logic - Avoids hard coding - 特別是改變頻率很頻繁的邏輯或是資料. - Write less code - Avoids complex data structures - Make change without a code deployment ## Writing Clean Methods ### When to Create a Function ![WhenTo](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/WhenTo.png?raw=true) - Duplication - 我們應該試圖遵守 DRY(don't repeat youself) 原則. - Indentation ```C# for(var a =1 ;a<10;a++){ for(var a =1 ;a<10;a++){ for(var a =1 ;a<10;a++){ for(var a =1 ;a<10;a++){ for(var a =1 ;a<10;a++){ for(var a =1 ;a<10;a++){ // 先別提看不看的懂 code 的邏輯... // 太長的縮排會讓讀者難以閱讀...連讀都很困難... } } } } } } ``` - Unclear intent - 方法名稱, 方法參數, 回傳值型態等資訊... 能傳遞意圖給讀者. - '> 1 task - 方法應該試圖遵守 SRP 原則 ### Why Create a Method - Reason 1: **Avoid Duplication** #### Code is a liability Don't repeat yourself is one of the most important principles in sofeware development since it helps assure that we minimize the lines of code that we have to maintain 程式碼是一種負擔或者說是一種責任. 這句話是在提醒開發者, 在開發過程中應該謹慎地考慮每一行程式碼的必要性, 以避免增加軟體開發和維護成本. #### Less is more Duplicated code means two places to maintain, and two redunant place to read 簡單就是美. 此是提醒我們應該追求簡潔和精煉. 以讓產品更加易於使用、易於維護和易於擴展 #### Look for patterns Duplication in our code is not always obvious. Sometimes you should detect it just by squinting. 我們需要觀察程式中相同的 pattern , 並且找出不同之處. 以下是一個例子, 我們可以發現除了紅框之外的地方, 都一模一樣. 這代表我們可以抽一個方法出來, 並且使用方法參數去設定紅框處. ![LookFor](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/LookFor.png?raw=true) ### Why Create a Method - Reason 2: **Excessive Indentation** ![ExcessiveIndentation](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ExcessiveIndentation.png?raw=true) ##### Comprehension decreases beyond three levels of nested 'if' blocks. study by Noam Chomsky and Gerald Weinberg --> 巢狀 block 盡量不要超過兩層 ! #### Excessive Indentation : Solution 1. Extract Method 2. Fail Fast 3. Return Early ### Excessive Indentation Solution 1: Extract Method ![ExtractMethod](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ExtractMethod.png?raw=true) - 常見消除 Excessive Indentation 的作法(降低 cyclomatic complexity)是先從內圈的邏輯開始抽方法, 並且賦予具有可讀性的方法名稱. 令讀者可從方法名稱快速了解你實作的意圖. > Many smell methods allow the reader to read the code at their desired level of abstraction - 即使抽出的方法只被一個地方使用一次, 但只要讀者可透過方法名稱去快速了解意圖, 那我們就應該抽方法. - 此情境可考慮先使用 Local Method. 未來其他若有相同的需求, 在調整. ### Excessive Indentation - Solution 2: Fail Fast ![FailFast](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/FailFast.png?raw=true) - 使用 Guard Clauses 檢查, 提早發現是否可正確執行. 不只可以消除 Excessive Indentation , 而且語意上, 也可讓使用者知道什麼情況下, 此方法無法運作. - 此例子使用 Throw Exception. 拋例外只是告知使用者, "發生錯誤了"的其中一種手段(態度最強硬的). - 使用者只需要是否錯誤的狀態(不強迫使用者做錯誤處理) - 回傳 bool e.g. 若方法回傳 true, 代表執行成功, 反之則是執行失敗. - 回傳 value? e.g. int? - 使用者還需要錯誤訊息(不強迫使用者做錯誤處理) - 回傳 Tuple e.g. (bool isSuccess, string errorMessage, T result) - 回傳自定義物件 e.g. ```C# class 自定義物件{ bool IsSuccess{get;} string RrrorMessage{get;} T Result{get;} } ``` - 使用者還需要錯誤訊息且需要被強迫做錯誤處理 - Throw Exception (強迫使用者做 Error Handle) - 需考慮使用者是否可以容易地提前知道, 方法會噴例外 e.g. File.Exist() - 需考慮是否能將錯誤處理的自由交給使用者自行判斷. 假設不行, 再使用拋例外的方式. ![Switch](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/Switch.png?raw=true) - Switch 的 default 也有 Fail Fast 的效果 (假設前面的 case 數不多的話) ### Excessive Indentation - Solution 3: Return Early > Use a return when it enhances readability...In certain routines, once you know the answer...not return immediately means that you have to write more code. #### Bad Sample ![BadSample](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/BadSample.png?raw=true) #### Good Sample ![GoodSample](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/GoodSample.png?raw=true) ### Why Create a Method - Reason 3: Convey Intent ![ConveyIntent](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ConveyIntent.png?raw=true) - 讀者可透過方法名稱, 快速了解意圖(抽象) e.g. GetFile() <---你並不想知道實作邏輯, 但你卻知道了這個方法再做甚麼. 此可以幫助讀者能快速地閱讀你的程式碼. ### Why Create a Method - Reason 4: Do One Thing #### 方法應該盡可能遵守 SRP 原則 ! ![DoOneThing](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DoOneThing.png?raw=true) - Aids the reader - 一個命名良好的方法名稱能幫助讀者快速大致了解方法的功能(方法名稱應該像是個摘要). - Promotes reuse - 一個功能越複雜的方法, 越難被重複使用. - Eases Naming and Testing - 若方法名稱難以命名或是命名範圍過於廣泛(方法名稱應該要有摘要的感覺, 所以若牽涉範圍過大, 並沒有幫讀者過濾資訊的效果) , 這可能代表這個方法的職責過於龐大, 需要重構拆分. - Avoids side-effects - 職責單一的方法, 應該比較不可能會有 side-effects 的產生. ### Mayfly Variables ![MayflyVariables](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/MayflyVariables.png?raw=true) - 人類的短期記憶依次只能記住最多 7 個 item, 此範例會耗損我們短期記憶的心力. 因為我們必須再方法的開頭就盡可能記住這四個變數直到方法結束... - 這四個開頭的變數會影響重構時所需要付出的心力, 因為重構時需要特別考慮這些變數. - we should strive to minimize the number of variables in scope at the given time (盡可能減少變數的生命週期時間) - 兩個實現 Mayfly Variables 的作法 - Initialize variables just in time - 需要時才建立變數 - Bad Sample ```C# var index = 0; // do more thing for(index;index<5;index++) { // do some thing } ``` - Good Sample ```C# // good for(var index=0;index<5;index++) { // do some thing } ``` - Let Method do one thing. - 方法參數本身就是 Mayfly Variables 了. (變數生命週期等於方法生命週期) - e.g. int Add(int num1, int num2); ### How Many Method Parameters Is Too Many ? > A high number of parameters makes a funcation harder to understand. It is a sign the function is doing too much 當你試圖減少方法參數時, 這可能會使你的方法 - 容易閱讀 - 容易了解 - 容易測試 - 不容易讓方法負責太多事情. ##### PS :老師是建議方法參數數量應該是 0 ~ 2 個參數. (但實務上, 應該蠻困難地. 我就爛 orz) #### Bad Sample ![IsTooMany](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/IsTooMany.png?raw=true) #### Avoid Flag Arguments ![AvoidFlagArguments](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/AvoidFlagArguments.png?raw=true) - Flag Arguments 是一個很強烈的訊號. (你的方法做了兩件事) - 當 Flag = True, 要做甚麼, 當 Flag = Flase, 不要做甚麼. ### Signs a Method Is too Long ![SignsA](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/SignsA.png?raw=true) - Whitespace & Comments - 當你發現你的方法內有非常多段的註解或是斷行時, 這可能是一個表示你的方法做太多事情的訊號. (即使行數短) ```C# private List<Data> _data; // OnButtonClick 做了三件事情. private void OnButtonClick(object sender, EventArgs e){ // 得到使用者選擇的檔案路徑 var dialog = new SaveFileDialog(); dialog.ShowDialog(); var path = dialog.FileName; if (!string.IsNullOrWhiteSpace (path)) { // Json 序列化 var json = JsonConvert.SerializeObject(_data); // 寫入檔案 File.WriteAllText(dialog.FileName, json); } } --- // 可以考慮先簡單重構成這樣.(日後再考慮是否要抽到 class) private void OnButtonClick(object sender, EventArgs e) { var path = GetPath(); if (!string.IsNullOrWhiteSpace(path)) { var json = Convert(_data); Write(path, json); } } private string GetPath() { var dialog = new SaveFileDialog(); dialog.ShowDialog(); return dialog.FileName; } private string Convert(object data) => JsonConvert.SerializeObject(data); private void Write(string path, string content) => File.WriteAllText(path, content); ``` - Scrolling Required - 當你發現你的方法內的程式碼, 一個螢幕看不完, 需要滾動才看得完的時候 == - Naming Issues - 做太多事情的方法, 會很難很好精準地命名 - Multiple Conditionals - 條件判斷可以考慮額外抽出成獨立的方法 - Hard to digest - 當發現太多生命週期太長的變數時(你需要一直記住, 但卻沒有馬上用到), 考慮抽方法. #### Bob 大叔的建議 - Rarely be over 20 lines - Rarely over 3 parameters #### Linux Style Guide > The Maximum length... is inversely proportional to the complexity and indentation level of that function. So, if you have a conceptually simply function that is just one line(but simple) case statement...It is Okay to have a long function...If you have complex function...adhere to limits all the more closely - 方法的最大長度與方法複雜度和縮排程度成反比. 這代表著當方法越複雜, 縮排程度越多時, 方法的最大長度應該要更短. 如果你有一個概念上簡單的方法, 它只有一行, 並且是一個簡單的 case statement, 那麼這樣的一個長方法就沒有問題。但是, 如果你有一個複雜的方法, 則應該更加嚴格地遵守方法長度的限制. **(概念簡單的方法可以寫得長, 但概念複雜的方法必須寫的短)** ### Handling Exceptions #### 有三種例外 ![HandlingExceptions](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/HandlingExceptions.png?raw=true) - Unrecoverable - 最常見的例外類型 - Recoverable - 當遇到 Recoverable 例外時, 通常會選擇再**重新執行**作為處理策略. - Recoverable Exception 需要考慮何時放棄**重新執行** 以避免程式陷入無限循環 - Ignorable - 這個例外的重要程度並沒有那麼重要, 即使出現問題, 也不會影響程式的正常運行, 故可以被忽略. e.g. Log 失敗. #### 請不要捕捉你無法處理的例外 !!! ![TryCatchLog](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/TryCatchLog.png?raw=true) ##### Example 2 ```C# public void MyInsideMethod(){ try{ The_Method_Will_Thorw_Exception(); }catch(Exception e){ throw e; } } public void MyInsideMethod2(){ try{ MyInsideMethod(); }catch(Exception e){ throw e; } } public void MyMethod(){ try{ MyInsideMethod2(); }catch(Exception e){ throw e; } } ``` 如果你這一層無法處理, 就不要去捕捉例外. 因為你會造成日後讀 code 的人困擾, 甚至可能會有邏輯的例外. 另外 try catch 語法帶有可能會有例外發生的意圖, 所以當你每一個地方都用 try catch 包起來, 會讓人無法判斷真正有可能會有例外的地方在哪裡. 試圖將最原始資訊的例外呈現給知道如何處理或是能夠(必須)處理的那一層. ps : throw vs throw ex 的差異在於會不會重置 stack information. > throw; 會保留例外狀況的原始堆疊追蹤,其儲存在 屬性中 Exception.StackTrace 。 相反地, throw e; 更新 StackTrace 的 e 屬性。 [例外狀況處理語句 - throw 、 try-catch 、 try-finally 和 try-catch-finally](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/statements/exception-handling-statements) #### Try Catch Body Standalone ![BodyStandalone](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/BodyStandalone.png?raw=true) ## Writing Clean Classes ### When to Create Class ![CreateClass](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/CreateClass.png?raw=true) ### Class Cohesion Overview ##### class Responsibilities Should Be Strongly Related > Cohesion : How strongly the class responsibilities of a class are related ![ClassCohesion1](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ClassCohesion1.png?raw=true) ![ClassCohesion2](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ClassCohesion2.png?raw=true) #### 高 Cohesion 的好處 - Enhances readability - High Cohesion 代表這個類內的成員都是為了同一個目的而存在的. 讀者可從 class 名稱中大致猜出這個類的成員在做甚麼. - Increases likelihood of reuse - 因為這個類的成員都是為了同一個目的而存在, 故當 developer 在開發時, 可能會比較容易發現這個類別的方法是不是他所需要的, 減少重複複製貼上的可能性. - Avoids attracting the lazy - High Cohesion 意味著方法名稱應該是比較能容易地遵守 SRP 原則. (有些懶惰的使用者會試圖取一些很 General 的名子, 因為他們不想去思考這個方法應該屬於哪一個類別) - Watch for: - Standalone methods - 當你發現有方法能不跟類別中其他成員互動時, 但仍然能運作時, 可能就要考慮是否有低 Cohesion 的問題. - Fields used by only one method (Field 應該被很多方法使用) - 如果 field 只在一個方法中使用, 可以考慮將其轉換為該方法的局部變數, 以提高程式碼的可讀性和可重用性. 如果 field 需要在多個方法中使用, 則需要將其保留為類的 field, 但應將其存取權限為私有, 以實現封裝的概念. - 老師說可以考慮把 filed 抽到其他不同的類別中. 你的類只要負責用就好. - Class that change often - 當你觀察你的 git hub commit history, 發現你的某個 class 很常改變的時候, 可能要考慮這個類是不是沒有高內聚(違反 SRP) ### Names and Cohesion ![NamesAndCohesion](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/NamesAndCohesion.png?raw=true) #### Broad Names Lead To Poor Cohesion - Classes with broad,generic names often grow quickly - 想想叫做 XXX-Utility 的 class , 是不是通常很複雜(?) - Specific name lead to smaller more cohesive classes - The name alone should make its specific use-case clear ! ### Signs a Class Is too Small ![ClassIsTooSmall](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ClassIsTooSmall.png?raw=true) #### Inappropriate intimacy 若有兩個類都重度需要使用另外一個類的方法或成員, 代表這兩個類可能可以合併. ```C# public class WaferMap { public void Draw(...){}; } public class Reticle{ private WaferMap _WaferMap = new WaferMap(); public void DrawReticle() { // Setting some thing about Reticle... _WaferMap.Draw(...); } } public class Die{ private WaferMap _WaferMap = new WaferMap(); public void DrawDie() { // Setting some thing about Die... _WaferMap.Draw(...); }; } // 合併 public class WaferMapService { private WaferMap _WaferMap = new WaferMap(); public void DrawReticle() { // Setting some thing about Reticle... _WaferMap.Draw(...); } public void DrawDie() { // Setting some thing about Die... _WaferMap.Draw(...); }; } ``` #### Feature envy Feature envy 是一種壞味道. Feature envy 的發生通常是因為將屬性和方法放置在了不恰當的類中, 使得某個類中的方法過度依賴另一個類中的屬性和方法, 導致程式碼難以維護和擴展. 解決 Feature envy 的方法通常是 - 將相關的屬性和方法放置在同一個類中(合併) - 僅在需要使用的時候將它們作為參數傳遞給相應的方法中 - 將職責進行分離. 如果一個類需要使用另一個類的大量屬性和方法, 這可能表明被使用的那個類的責任過於繁重, 應該進行分離. ```C# // Feature envy sample public class Order { public Customer Customer { get; set; } public decimal TotalPrice { get; set; } public void CalculatePrice() { // 使用 Customer 中的方法和屬性來計算訂單的價格 decimal discount = Customer.CalculateDiscount(); decimal tax = Customer.StateTaxRate * TotalPrice; TotalPrice = TotalPrice - discount + tax; } } public class Customer { public string Name { get; set; } public decimal StateTaxRate { get; set; } public decimal CalculateDiscount() { // 根據客戶的名稱和購買紀錄計算折扣 // ... return discount; } } ``` Order 的 CalculatePrice 方法依賴於 Customer 的 CalculateDiscount 方法和 StateTaxRate 屬性. 這可能會導致 Feature envy 的問題, 因為 Order 需要使用 Customer 類的大量細節來計算訂單的價格, 而這些細節本應該屬於 Customer 的範疇. 這個問題可以透過將相關的屬性和方法放置在同一個類中來解決, 或者在需要使用的時候將它們作為參數傳遞給相應的方法中 (把計算 TotalPrice 的職責藏到 Customer 或其他類去, 這樣 Order 就不需要知道 Customer 的 StateTaxRate 了) #### Too many pieces - 當系統有過多的小類別. 這可能會導致系統過於分散, 無法知道在系統中, 他們如何一起工作. 從而增加程式碼的複雜性和理解難度 - 系統的功能可能需要多個類別共同協作才能實現. 這可能會導致程式碼之間的依賴性增加, 使程式碼更加脆弱並且難以維護 <--- 需要更多的協調者類別. ##### 解決方法 - 當多個類別之間存在相互依賴關係時(甚至有時序耦合), 可以考慮將它們合併成一個類別 ### Primitive Obsession ![PrimitiveObsession](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/PrimitiveObsession.png?raw=true) - 將方法所需要的參數抽出到一個 User 類別中 - 1.User 這個名字本身就能提供讀者資訊. - 2.原本在讀者眼中這些只是一堆沒有關聯的參數, 但現在我們知道 **"這些參數"** 隸屬於 User. - 3.屬性可以封裝存取邏輯 - 例如 User.Phone 的格式必須是 02-XXXXXXXX, 這些檢查邏輯可以統一放在 User 中, 不會是由各個使用者自己處理. - 4.Visual Studio supports "Find By Reference" <--- 對於維護超重要的功能 ! - 4.未來即使增加 User 的屬性, 也不會影響 SaveUser 這個方法. ### The Proximity Principle ![ProximityPrinciple](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ProximityPrinciple.png?raw=true) - 盡可能把有關聯的方法放在一起. 且盡量維持由上而下的方式 - e.g. ValidateRegistration() call ValidateDate() 和 SpealerMethodOurRequirements() 的順序是 ValidateDate() 先, 所以實作時, 也是 ValidateDate()放在上面, 然後才放 SpealerMethodOurRequirements() ## Writing Clean Comments ### Prefer expressive code over comments Code is more likely to be kept updated. And it's the definitive reference of what's actually happening #### Use comments when code is not sufficient 一般來說註解應該盡量減少使用, 因為開發者應該盡可能讓讀者從程式碼就能看懂意圖. 但有些情況, 讀者不太可能從程式碼中看出如此實作的理由. 以下是開發者可能需要使用 Comment 的情境 ##### 說明程式碼意圖 有時候程式碼無法完整地表達開發者的意圖, 這時候使用註解來補充說明. 例如:某段程式碼用來處理特殊情況, 開發者可以使用註解來說明這段程式碼的用途. ##### 說明程式碼的邏輯 有時候程式碼中使用了一些複雜的邏輯, 開發者可以使用註解來說明程式碼的運作方式, 以方便其他開發者理解程式碼的邏輯 例如: Math.Sqrt(double) 的實作, 就需要點數學底子... ##### 說明程式碼的限制 有時候程式碼的功能受到一些限制, 例如兼容性、效能等, 開發者可以使用註解來說明這些限制。 ##### 說明程式碼的缺陷 有時候程式碼存在一些缺陷, 開發者可以使用註解來標註這些缺陷, 以提醒其他開發者在修改程式碼時需要注意這些缺陷. 例如你需要使用某個元件來完成某個功能, 但這個元件有 bug, 但你又沒有時間修那個元件, 此時你只好被迫使用 template 解, 並加上註解說明. #### Comments to avoid - Redundant - Intent - Apology - Warning - Zombie Code - Divider - Brace Tracker - Bloated Header - Defect Log ### Dirty Comment 1: Redundant ##### 別加一些別人看你的 code, 就能馬上看懂的無用註解 == ![DirtyComment1](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment1.png?raw=true) - Redundant Comments break the DRY principle. they are needless repetition - Comments 內容跟 code 內容是在提供的資訊上是重複的. - Redundant Comments lower the signal to noise ratio - Comments 沒有提供更多的資訊(因為跟 code 提供的一樣). 這會降低讀者閱讀的速度, 以及增加困擾. 因為要花時間讀你的無用註解 == - Avoid requiring comments for every method. A good name will often suffice - 如果你有一個好的方法名稱足以說明意圖, 那你並不需要為每一個方法都添加註解. ### Dirty Comment 2: Intent ![DirtyComment2](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment2.png?raw=true) #### Clarify intent without comments - Improve function name (看方法名稱就能知道要做甚麼) - Declare intermediate variable - Declare a constant or enum - Extract conditional to function (看方法名稱就能知道要做甚麼) ### Dirty Comment 3: Apology ![DirtyComment3](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment3.png?raw=true) ### Dirty Comment 4: Warning ![DirtyComment4](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment4.png?raw=true) - 當你發現 Warning Comments 的存在時, 通常這可能是你應該要重構的訊號了. ### Dirty Comment 5: Zombie Code ![DirtyComment5](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment5.png?raw=true) - Zombie Code is not really dead because it is still in the code. it effects searches and refactoring. - 如果你要註解一段程式不執行, 那你不如把它刪除掉. 因為這段註解程式碼會影響讀者的閱讀, 也會影響後續開發者的重構和維護所需要花費的心力. 但它又不會被執行(0作用) - 會覺得註解 code 很棒還勇於 push 上板控的人, 到底腦袋裡是在想甚麼 = = ### Dirty Comment 6: Divider ![DirtyComment6](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment6.png?raw=true) - 想像一下一個方法三千行, 你在中間某一段前面加上註解去描述後面一千行的行為. - 與其加註解去解釋, 不如好好的讓方法遵守 SRP ! ### Dirty Comment 7: Brace Tracker ![DirtyComment7](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment7.png?raw=true) - 與其用註解去解釋甚麼行為做完了, 不如抽方法, 並給一個可以快速理解行為的方法名稱. ### Dirty Comment 8: Bloated Header ![DirtyComment8](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment8.png?raw=true) - 強者我前同事, 超級喜歡寫這種華麗地註解...而且還會強迫你跟他一樣, 不寫就說你不專業 == - 假設團隊規定真的要這樣寫註解...可能要想辦法找到工具使用... - [File Header Comment](https://marketplace.visualstudio.com/items?itemName=doi.fileheadercomment) - [Add file header](https://learn.microsoft.com/en-us/visualstudio/ide/reference/add-file-header?view=vs-2022) ### Dirty Comment 9: Defect Log ![DirtyComment9png.png](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/DirtyComment9png.png?raw=true) - 善用 Git Commit ... - 善用 Jira or Bitbucket 等工具 (如果公司有提供的話) ### Clean Comments - To Do - Summary - Documentaion #### To Do ![ToDoRefactor](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/ToDoRefactor.png?raw=true) 在團隊開發中, 個人不太建議這樣寫註解. 理由是我們現在有 Jira 等等工具可以更好地掌控待完成任務. 除非你確保這個註解會被消除.(任務會被完成) 例如 : - 有一個任務被拆分成三個步驟, 並且這三個步驟是交給三個不同的人, 此時前一個步驟的開發者想要給下一個步驟的開發者一些訊息.(下一個步驟的開發者已經差不多準備好要開發了) - 雖然不知道是何時, 但你確認已經被放在開發計畫內. e.g. TODO : SBISW-12345 will refactor this. ##### 可以在 VS2019 的 View -> Other Windows -> Task List 找到這個功能 ![OtherWindows](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/OtherWindows.png?raw=true) #### Summary ![Summary](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/Summary.png?raw=true) ##### 註解有時候也能作為摘要使用, 讓讀者快速理解意圖 ! - 有時候意圖不一定能從方法或變數名稱 (code) 去讓人簡單地了解. 此時可以添加一些註解輔助. - Example ```C# // 傳入一個陣列, 然後回傳裡面的所有元素都加一的新陣列 public int[] IncrementArray(int[] array) { var result = new int[array.Length]; for (var i = 0; i < array.Length; i++) { result[i] = IncrementOne(array[i]); } return result; } // 傳入一個數字, 然後將它加一後再回傳 public int IncrementOne(int number)=> number + 1; ``` - 有時候單從方法或變數名稱 (code) 沒辦法讓人理解類別內方法之間或是類別與類別間的**互動**. 此時可以添加一些註解輔助. - Example ```C# // A customer in the system. public class Customer { public Customer(string name) => Name = name; /// <summary> /// Places an order for the customer. /// </summary> /// <param name="order">The order to place.</param> public void PlaceOrder(Order order) { // Code to place the order } } ``` - **註解應該只是輔助, 不要讓註解搶了風采 !** #### Documentaion ![Documentaion](https://github.com/s0920832252/C_Sharp/blob/master/Files/Clean_Coding_Principles_in_CSharp/Documentaion.png?raw=true) #### --- ###### Thank you! You can find me on - [GitHub](https://github.com/s0920832252) - [Facebook](https://www.facebook.com/fourtune.chen) 若有謬誤 , 煩請告知 , 新手發帖請多包涵 # :100: :muscle: :tada: :sheep: <iframe src="https://skilltree.my/c67b0d8a-9b69-47ce-a50d-c3fc60090493/promotion?w=250" width="250" style="border:none"></iframe>