Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

無瑕的程式碼 Clean Code 心得分享

38,714 views

Published on

Clean Code 讀書心得分享
https://kylinyu.win/php_best_practice

Published in: Technology

無瑕的程式碼 Clean Code 心得分享

  1. 1. 無瑕程式碼的彩虹七式 Clean Code 無瑕的程式碼 痞客邦 PIXNET 讀書⼼心得分享 PIXNET @ Win 17/03/01
  2. 2. • 選取七個核⼼心章節 • 建議隨時筆記您覺得不錯的觀念念 • 依然推薦閱讀原著 👍 • 讓我們輕鬆開始 服⽤用說明
  3. 3. 有意義的命名 讓名稱代表意圖,使之名符其實
  4. 4. 避免誤導 (命名) • ⼩小寫的 L 或是⼤大寫的 O • ⼀一群帳⼾戶:accountList ❌ • 三⾓角形斜邊 (hypotenuse) : hp ❌ int a = 1; if (O == l) a = O1; else l = 01;
  5. 5. 有意義的區別 • 別⽤用序列列命名 (a1, a2, … , aN) • 無意義的字詞是多餘的 ProductInfo vs ProductData • 參參數名改⽤用 source, destination 會更更好理理解 public static void copyCharts(char a1[], char a2[]) { for (int i = 0; i < a1.length; i++) { a2[i] = a1[i]; } }
  6. 6. 能念念出來來可以幫助記憶 genymdhms() 字⾯面上看起來來是⼀一個產⽣生 timestamp 的⽅方法,但無法藉 由發⾳音幫助記憶,不是個良好的命名。
  7. 7. 可被搜尋的名字 • 長命名勝過短命名,常數作為命名,搜尋時更更準確,不易易 造成 bug 比 5 還來來的容易易被找到WORK_DAYS_PER_WEEK
  8. 8. 不要⽤用匈牙利利命名法 https://zh.wikipedia.org/wiki/匈牙利利命名法 • 變數前加上型別 lAccountNum:變數是⼀一個長整數("l") arru8NumberList:變數是⼀一個無符號8位元整型陣列列("arru8") szName:變數是⼀一個零結束字串串(”sz") 成員的字⾸首 • 不要額外加上字⾸首 m_dsc = name;
  9. 9. 類別的命名 • ⽤用名詞 Customer • 避免使⽤用含糊不清的字 Data, Info, Manager, Processor
  10. 10. ⽅方法的命名 • ⽤用動詞 postPayment • Javabean ❖ 取出器(accessors): get ❖ 修改器(mutators): set ❖ 判定器(predicates): is
  11. 11. 不要裝可愛 • 清楚闡述比娛樂還來來的重要多了了。 DeleteItems 命名成 HolyHandGrenade (神聖⼿手榴彈) EatMyShort(來來吃我褲襠) 來來表達 abort
  12. 12. 別加無理理由的上下⽂文資訊 • 在同⼀一個應⽤用中,不需要在所有類別加上共同的字⾸首,如 此反⽽而造成各 IDE 中⾃自動補⿑齊功能無法於第⼀一時間補⿑齊。 • 較少的名稱若若可以清楚表達,勝過於長名稱。
  13. 13. 遵循上⾯面所說的規則,然後觀察你的程式碼 可讀性是否獲得了了改善
  14. 14. 函式 函式是所有程式組成的⾸首要基礎
  15. 15. 簡短 • 函式的⾸首要準則就是要簡短。 • 第⼆二項準則,就是要比第⼀一項還要簡短。
  16. 16. 只做⼀一件事情 • 函式應該做⼀一件事,它們應該把這件事做好,⽽而且只做這 件事。
  17. 17. Switch 敘述 • 太過冗長 • 做超過⼀一件事 • 違反單⼀一職責原則 (SRP) • 當加入新形態時,違反開放封閉原則 (OCP) 1 public Money caluculatePay(Employee e) throws InvalidEmployeeType { 2 switch (e.type) { 3 case COMMISSIONED: 4 return calculateCommissionedPay(e); 5 case HOURLY: 6 return calculateHourlyPay(e); 7 default: 8 throw new InvalidEmployeeType(e.type); 9 } 10 }
  18. 18. Switch 敘述 • 把 Switch 埋在抽象⼯工廠 (Abstract Factory) 底下 1 public Money caluculatePay(Employee e) throws InvalidEmployeeType { 2 switch (e.type) { 3 case COMMISSIONED: 4 return calculateCommissionedPay(e); 5 case HOURLY: 6 return calculateHourlyPay(e); 7 default: 8 throw new InvalidEmployeeType(e.type); 9 } 10 }
  19. 19. 使⽤用具描述能⼒力力的名稱 • 『當每個你看到的程式,執⾏行行結果都與你想的差不多,你 會察覺到你正⼯工作在 Clean Code 之上』
  20. 20. 函式的參參數 • 最理理想的是 (零參參數函式:niladic) • 其次是 (單函式參參數:monadic) • 再者才是 兩兩個(雙參參數函式:dyadic)。 • 非必要使⽤用 (三參參數函式:triadic),(多參參數函式:polyadic)
  21. 21. 旗標參參數 • 將布林林傳給函式,是⼀一種很恐怖的習慣 • 這會造成該函式不只做⼀一件事情 True: doSomething, False: SaySomething
  22. 22. 兩兩個參參數的函式的命名 • writeField(name) vs writeField(outputStream, name) 雖然兩兩個都很清楚,但第⼆二個要做短暫的停留留思考,容 易易令⼈人忽略略進⽽而有機會產⽣生 bug。 • assertEquals(expected, actual) 你是否曾經搞錯 expected, actual 這兩兩者的位置?
  23. 23. 三個參參數的函式 • assertEquals(message, expected, actual) 看到 message 卻以為他是 expected? • 使⽤用者容易易被三個參參數絆住或頓住,需要反覆查看參參數。
  24. 24. 要無副作⽤用 • 保證只做⼀一件事! • 不要在暗地裡偷偷做了了其他事情 ‣ 轉換成其他傳參參數傳給其他函式 ‣ 變成全域變數
  25. 25. 使⽤用”例例外處理理” 取代 “回傳錯誤碼” • try/catch 會混淆結構,將函式從 try 和 catch 中提取出來來 1 public void delete(Page page) { 2 try { 3 deletePageAndAllReferences(pages); 4 } catch (Exception e) { 5 logError(e); 6 } 7 }
  26. 26. 不要重複⾃自⼰己 • DRY (Don’t Repeat Yourself.) • 重複的程式碼是軟體裡所有邪惡惡的根源
  27. 27. 結構化程式設計 • 每個函式,以及每個函式理理的區塊,都應該只有⼀一個進入 點及⼀一個離開點。 • 我們現在都⽤用 Guard Clause 原則了了
  28. 28. 如何寫出這種函式 • 將函式分開,重新命名,減少重複 • 我不會⼀一開始就這樣寫,我也不認為有⼈人可以辦得到
  29. 29. 函式是這個語⾔言的動詞 類別是這個語⾔言的名詞 如果你遵循這章的準則: 你的函式會簡短,有良好的命名,以及漂亮的結構
  30. 30. 註解 真相永遠只存在⼀一個地⽅方:程式碼
  31. 31. 註解無法彌補糟糕的程式碼 • 整潔具有表達⼒力力⼜又極少使⽤用註解的程式碼,遠優於雜亂⼜又 滿是註解的程式碼。
  32. 32. ⽤用程式碼表達你的本意 • 多想幾秒鐘,在⼤大部分情況下的註解,都可以簡單地融入 到建立的函式名稱中。
  33. 33. 有益的註解 • 真正有益的註解,是你想辦法不寫它的註解
  34. 34. 法律律型的註解 • 有些需要撰寫標準規範,或者著作權聲明、作者資訊等, 就是必須且合理理的註解。 比較好的做法是讓註解去參參考⼀一個外部註解 // Copyright (c) 2003, 2004, 2005 by Object Mentor, Inc. All rights reserved. // Released under the terms of GNU General Public License version 2 or late.
  35. 35. 對於後果的告誡 • ⽤用於警告其他⼯工程師會出現某種特殊後果的註解,也是有 ⽤用的。
  36. 36. TODO (代辦事項) 註解 • TODO 是⼯工程師認為應該要完成的事情,但基於某些原因無法在此 時做到。 ‣ 提醒移除過時的功能 ‣ 請某⼈人注意這個問題 ‣ 請某⼈人尋找更更好的命名 ‣ 依賴於某個未來來計畫⽽而提醒所需要的修改 • 最後不管如何,都不應該成為讓糟糕程式碼留留在系統裡的藉⼝口。
  37. 37. 糟糕的註解 • 喃喃⾃自語 • 多餘的註解 • 規定型註解 • ⼲干擾型註解
  38. 38. 糟糕的註解 - 喃喃⾃自語 • 如果你決定要寫註解,你就應該要花上必要的時 間,確保你寫出的是最好的註解
  39. 39. 糟糕的註解 - 多餘的註解 • 含有開頭註解的簡單函式,讀這段註解可能比讀 這段程式更更花時間 1 // Utility method that returns when this.closed is true. Throws an exception 2 // if the timeout is reached. 3 public synchronized void waitForClose(final long timeoutMillis) 4 throws Exception 5 { 6 if (!closed) { 7 wait(timeoutMillis); 8 if(!closed) 9 throw new Exception ("MockResponseSender could not be closed"); 10 } 11 }
  40. 40. 糟糕的註解 - 規定型註解 • 含有開頭註解的簡單函式,讀這段註解可能比讀 這段程式更更花時間 1 /** 2 * 3 * @param title The title of the CD 4 * @param author The author of the CD 5 * @param tracks The number of tracks on the CD 6 * @param durationInMinutes The duration of the CD in munutes 7 */ 8 public void addCD(String title, String author, int tracks, int durationInMinutes) 9 { 10 CD cd = new CD(); 11 cd.title = title; 12 cd.author = author; 13 cd.tracks = tracks; 14 cd.duration = durationInMinutes; 15 cdList.add(cd); 16 }
  41. 41. 糟糕的註解 - ⼲干擾型註解 • 沒有被⼲干擾到,真的嗎? 1 /* 2 * Default constructor. 3 */ 4 protected AnnualDateRule() {} 5 6 /* The day of the month. */ 7 private int dayOfMonth; 8 9 /* 10 * Return the day of the month. 11 * 12 * @return the day of the month. 13 */ 14 public int getDatofMonth() 15 { 16 return dayOfMonth; 17 }
  42. 42. 當你可以使⽤用函式或者變數時就別 ⽤用註解 1 // does the module from the global list<mod> depend on the 2 // subsystem we are part of? 3 if (semodule.getDependSubSystems().contains(subSysMod.getSubSystem()) 4 5 ArrayList moduleDpendees = smodule.getDependSubSystems(); 6 String outSubSystem = subSysMod.getSubSystem(); 7 if (moduleDpendees.contains(ourSubSystem))
  43. 43. 出處及署名 • 想要儲存該類的訊息,原始碼版本控制系統(e.g. GIT, SVN)是⼀一個比較好的選擇。 /* Added by Rick */
  44. 44. 被註解起來來的程式碼 • 把程式碼註解掉的⾏行行為,這是很討⼈人厭的,不要這樣做!! • 我可以刪掉嗎?還是要繼續留留著?
  45. 45. 不要替糟糕的程式碼寫註解,你應該重寫程式碼
  46. 46. 編排 良好的編排有助於閱讀,也是專業
  47. 47. 編排的⽬目的 • 程式的編排實在是太重要了了。 • 程式碼的風格與可讀性,會影響程式的可維護性及可擴充 性。
  48. 48. 垂直的編排 • 報紙的啟發 • 最重要的概念念會最先出現,希望⽤用最少的細節來來表達他們。
  49. 49. 垂直的編排 • 概念念間的垂直空⽩白區隔 • 空⽩白⾏行行代表⼀一個視覺上的提⽰示,提⽰示著空⽩白⾏行行的後⽅方將接 續⼀一個新⽽而不同的概念念。 1 private static final Pattern pattern = Pattern.compile("'''(.+?)'''", Pattern.MULTILINE); 2 3 public BoldWidget(ParentWidge parent, String text) throws Exception { 4 super(parent); 5 6 Matcher match = pattern.matcher(text); 7 match.find(); 8 9 addChildWidgets(match.group(1)); 10 }
  50. 50. 垂直的密度 • 如果垂直空⽩白區分開個個概念念,那垂直密度則意味著密切 相關的程度 • ⼀一看就知道有兩兩個變數,⼀一個⽅方法的類別。 1 public class ReporterConfig { 2 private String m_className; 3 private List<Property> m_properties = new ArrayList<Property>(); 4 5 public void addProperty($Property property) { 6 m_properties.add(property); 7 } 8 }
  51. 51. 垂直的距離 • 相近的概念念,在垂直編排上,要盡可能的靠近 • 相近的概念念不該被分散在不同的檔案裡 • 垂直的距離⽤用來來衡量量他們對彼此的了了解有多重要
  52. 52. 垂直的距離 - 變數宣告 • 盡可能靠近變數被使⽤用的地⽅方,因為我們的函⽰示非常簡短。 1 public int countTestCase() { 2 int count = 0; 3 for (Test each: tests) 4 count += each.countTestCased(); 5 return count; 6 }
  53. 53. 垂直的距離 - 實體變數 • 實體變數都應該被宣告在⼀一個⼤大家都熟悉的地⽅方,通常放 在類別的最上⽅方。
  54. 54. 垂直的距離 - 相依的函式 • 如果函式呼叫了了另⼀一個函式,那兩兩個函式在垂直編排上要 盡可能靠近。由上⽽而下,增加模組的可讀性。
  55. 55. 垂直的距離 - 概念念相似性 • 函式的概念念上有⾼高度相似性時,垂直距離應該愈短愈好。 • 概念念上的⾼高度相似性 • 類似的命名規則 • 就算沒有互相呼叫,也應該盡可能放在⼀一起
  56. 56. ⽔水平的編排 - 空⽩白間格和密度 • 運算⼦子(assignment operators) 的左右都加入空⽩白,使其 更更為突出 • 函式名稱和⼩小左括號之間不空⽩白 • 空⽩白的另⼀一種⽤用途是強調運算⼦子的優先權
  57. 57. ⽔水平的對⿑齊 • 沒有幫助,我傾向不再為宣告與設定敘述作特別的對⿑齊 1 public int FitNesseExpediter(Scoket s, 2 FitNesseContext context) throws Exception 3 { 4 this.context = context; 5 scocket = s; 6 input = s.getInputStream(); 7 output = s.getOutputStream(); 8 reguestParsingTimeLimit = 1000, 9 10 }
  58. 58. 縮排 • ⼀一個原始檔是個階層結構,⽽而非⼤大綱結構。 • 讓視野的層次結構更更顯⽽而易易⾒見見 ‣ 檔案 ‣ 類別 ‣ ⽅方法 ‣ 程式區塊 ‣ ⺟母程式 ‣ ⼦子程式
  59. 59. 你們團隊的共同準則?
  60. 60. 錯誤處理理 錯誤處理理很重要,但不該模糊原本程式碼邏輯
  61. 61. 使⽤用例例外事件⽽而非回傳錯誤碼 • 比較好的做法是,在你遇到⼀一個錯誤的時候,拋出⼀一個例例 外事件。如此,呼叫程式碼就會變得乾淨許多。
  62. 62. 在開頭寫下你的 Try-Catch-Finally 敘述 • 如果你寫的程式可能會拋出例例外事件,請養成 try-catch- finally 成為開頭敘述的好習慣。
  63. 63. 提供發⽣生例例外的相關資訊 • 請傳遞⾜足夠的資訊給 catch 區,使例例外能夠被記錄下來來。
  64. 64. 不要回傳 null (空值) • 不要回傳 null ,因為只要有⼀一處忘記檢查 null,就會導致 程式進入混亂無法控制的狀狀態。 • 如果你想讓⽅方法回傳 null ,那不如試著拋出⼀一個例例外事 件,或回傳⼀一個 SPECIAL CASE 物件。
  65. 65. 不要傳遞 null • ⽅方法回傳 null 是糟糕的⾏行行為。然⽽而傳遞 null 到⽅方法裡是更更 糟糕的⾏行行為。 • 當你養成這樣的習慣之後你在寫程式碼時,就會避免傳遞 null 給函式,因為你知道如果 null 出現在參參數裡,代表了了 潛在問題的預兆。 最終能⼤大幅降低出錯的可能。
  66. 66. Clean Code 是易易讀的,但也是耐⽤用的,這兩兩者不是互相 衝突的⽬目標。 當我們將錯誤處理理獨立於主要邏輯的可讀程式,我們就能 獨立地處理理它,並且在維護性上也向前邁進⼀一⼤大步。
  67. 67. 類別 更更⾼高層次:《程式碼敘述和程式碼所構成的函式》
  68. 68. 類別的結構 • 公⽤用函式(Public function) 應該接在⼀一連串串變數宣告的後⽅方 • 我們喜歡將私有⼯工具函式(Private function) 緊接在呼叫的 公⽤用函⽰示後⽅方 • 以上兩兩點遵循 『降層法則(Stepdown rule)』,也有助於 『讓程式閱讀』
  69. 69. 封裝 • 先想辦法保持變數以及⼯工具函式的私有性 (private) ,放鬆 封裝限制是最後不得已的⼿手段 • 優先順序:1. protected(⽅方便便進⾏行行測試) 2. public
  70. 70. 類別要夠簡短 • 在函式裡,計算真正的程式⾏行行數,來來衡量量函式的⼤大⼩小在類 別裡,計算職責的數量量,來來衡量量類別的⼤大⼩小
  71. 71. 單⼀一職責原則 (SRP) • 主張⼀一個類別或⼀一個模組只能有⼀一個修改的理理由。 • 我們強調的主張:系統是由許多⼩小型類別左組成,⽽而不是 由少數幾個⼤大型類別組成 • 每個⼩小類別『封裝單⼀一的職責』、『只有⼀一個修改的裡由』 以及『與其他少數幾個類別合作來來完成系統要求的⾏行行為』
  72. 72. 凝聚性 • 保持凝聚性會得到許多⼩小型的類別 • 在⽅方法裡操縱愈多的變數,代表這個⽅方法更更凝聚於該類別。 • 若若有某個類別的每個變數都被使⽤用在每個⽅方法中,那麼這 個類別就是具有最⼤大凝聚性的類別
  73. 73. 凝聚性 • 當凝聚性⾼高,代表類別裡的⽅方法和變數是相互依賴的, 並且相互結和為邏輯上的整體。 • 當類別失去凝聚性的時候,就把他們拆開來來吧!
  74. 74. ⽻羽化 透過⽻羽化設計來來達到整潔
  75. 75. ⼀一個簡單的設計 ① 執⾏行行完所有的測試 ② 沒有重複的部分 ③ 表達程式設計師的本意 ④ 最⼩小化類別和⽅方法的數量量 這些守則,根據重要性來來排 序。
  76. 76. 簡單設計守則 1: 執⾏行行完所有的測試 • 類別若若遵守單⼀一職責原則 (SRP),那麼測試就說⼀一件很簡 單的事。 • 系統以物件導向的主要⽬目標,讓程式有低耦合度和⾼高凝聚 度。 所以撰寫測試程式,最終帶來來了了更更佳的設計。
  77. 77. 簡單設計守則 2~4: 程式重構 • ⼀一旦有了了測試,就能保持程式和類別的整潔,利利⽤用逐步增 加的⽅方式來來進⾏行行重構。 • 重構的過程中,我們可以應⽤用與良好軟體設計有關的所有 知識。 ➡ 增加凝聚性 ➡ 降低耦合度 ➡ 分離關注點 ‣ 模組化系統關注點 ‣ 替函式和類別瘦⾝身 ‣ 良好的命名
  78. 78. 禁⽌止重複 • 重複程式碼是『良好設計系統』的主要敵⼈人。他代表額外 的⼯工作、額外的風險及額外不必要的複雜性。
  79. 79. 具表達⼒力力 • 『選擇⼀一個良好的名稱』 • 『讓函式和類別簡短』 • 『標準命名法』 • 『良好的單元測試』也具良好的表達⼒力力,主要⽬目的『⽤用範例例學』 • 多花點時間在你的函式和類別上,選擇較好的名稱,將⼤大型函式 拆解成⼩小型函式 • ⽤用⼼心是⼀一項珍貴的資源
  80. 80. 最⼩小化類別及⽅方法的數量量 • ⼩小⼼心『消除重複程式碼』,『單⼀一職責原則』這類的基本 觀念念會做過頭,⽽而產⽣生太多微型的類別和⽅方法。 所以該守 則建議我們,必須讓類別及⽅方法的數量量保持少少的。 • 但這條守則卻是簡單設計四守則裡優先最低的。所以測 試、消除重複及表達⼒力力等其他三條守則,才是最重要的。
  81. 81. 簡單的設計 ① 執⾏行行完所有的測試 ② 沒有重複的部分 ③ 表達程式設計師的本意 ④ 最⼩小化類別和⽅方法的數量量
  82. 82. • 無瑕的程式碼是可以被練習的 • 無瑕的程式碼風格,能被讀者(隊友) 信任 • 無瑕的程式碼是專業 讀後語
  83. 83. Thank you.

×