Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

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

42 175 vues

Publié le

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

Publié dans : Technologie

無瑕的程式碼 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.

×