2020年4月29日 星期三

The Clean Code Talks - Don't Look For Things! 整理

重點整理:
  • 用細緻的方式回到基本去討論依賴注入(Dependency Injection)
  • 測試系統時需要實例化物件,實例化物件時常放了很多測試替身(Test-double)進去,讓測試更困難
  • 類別的靜態變數有時牽涉到單例(Singleton)設計模式,要一起實例化,讓測試更麻煩
  • 實例化很困難意味著難以測試
  • 超文本文件類別為例,文件只在乎客戶端產生的文件,不需要瞭解客戶端如何產生文件
  • 車類別為例,車只在乎產生的引擎,不需要了解引擎怎麼產生
服務定位器模式(Service Locator pattern)的問題:
  • 違反得墨忒耳定律(Law of Demeter a.k.a. LoD,簡單說即只拿直接需要的物件)
  • 將服務定位器傳給要實例化的物件,看建構子參數不夠,還得追建構子程式碼才知道相依的物件,這會隱藏物件間真正的相依性
  • 以房子類別為例,實例化房子卻傳入整個家的狀態,追建構子才知只要門、窗與屋頂
  • 給房子還要給整個家的物件,導致物件重用性變差
  • 解決方式是不透過定位器,明確將門、窗與屋頂作為參數傳給房子
  • 此舉能讓獨立測試變得容易
  • 服務定位器把查詢與建立物件的功能混淆
  • 還需要額外的介面、覆寫或仿造服務定位器的設定方法(setter)
  • 以致一旦依賴服務定位器,也需要依賴其他跟服務定位器有關的東西
  • 服務定位器有很多別名,本質上都是只需要部分物件但給了全世界
得墨忒耳定律:
  • 以結帳為例,一般來說只會拿錢(直接需要的物件)給店員(請求物件的物件),不會整個錢包(全域狀態)給店員
  • 只請求直接需要的物件
  • a.getX().getY()亦即間接取得物件即違反得墨忒耳定律
  • 好萊塢法則(Hollywood principle, that is "Don't call us, we'll call you")
破解依賴注入的迷思
  • 子物件新增一個參數,則父物件要一路傳遞參數直到給子物件
  • 事實上父物件並不負責建構子物件,父物件並不知道子物件怎麼建構的,所以也沒有傳遞參數的問題
  • 房子類別為例,房子並不知道門有門把,房子只需要建構好的門物件
  • 工廠模式(Factory pattern)負責將物件組合起來,與業務邏輯分開
  • 同一個生命週期的類別們只要同一個工廠組合就夠,並不用一個類別一間工廠,所以不會導致類別倍數增生
  • 注入的物件生命週期應該大於等於被注入的物件,如果有生命週期較短的物件,應該調整架構,將短生命週期的物件分群
偏執狂程式設計(Paranoid programming/coding)
  • 到處是空指標檢查讓測試變得困難
  • 測試房子的油漆功能不需要門,傳入門的空指標,但門的空指標檢查讓測試無法繼續
  • 與其到處用先決條件檢查,不如用許多測試確保程式能跑
  • 如果物件是給外部使用的應用程式介面(API),加入先決條件檢查是合理的
  • 但若給內部使用,一堆先決條件檢查表示對程式的穩定性沒有信心
  • 產品程式碼不應該傳空指標給物件,應該用空集合代替,若希望傳入的集合做事就實作方法,不然就給無操作(NOP a.k.a. NOOP)
  • 測試程式碼可以傳空指標給物件,目的在表明此物件與測試無關
  • 產品程式碼不應該在工廠類別外的類別呼叫new運算元,即業務與建構邏輯混淆
  • 測試程式碼為了實例化小部分的程式做測試可以呼叫new運算元
總結
  • 絕大部分的情況應該避免呼叫new運算元實例化物件,除了最基底的類別或物件圖的葉類別,其餘有與其他類別互動的類別,應該請求物件
  • 將業務類別與建構類別分開,測試會變得容易,因為可以將業務邏輯與建構邏輯分別獨立測試
  • 若將業務與建構邏輯混淆,測試建構邏輯時還不得不跑業務邏輯做的事
Q&A
  • 問:是否建議不需要假(dummy)物件?
  • 答:否,當測試的時候,無關的物件可以傳空物件進行無操作(NOP),空物件是假物件更強的表現形式

2020年4月27日 星期一

"The Clean Code Talks -- Inheritance, Polymorphism, & Testing" 整理


重點整理:
  • 讓程式碼更好懂、更好測試與更好擴充
  • 盡可能用多型代替if
  • 一個類別負責一種事情
  • 若看到switch則用多型代替
  • 若出現重複判斷某個flag的if則用多型替代
  • 把建構子類別(工廠模式)與業務邏輯類別分開,在建構時做if判斷,處理業務邏輯時就不用一直做if判斷
優點:
  • 多型能讓不同的功能分散在不同檔案
  • 程式碼更好被理解與測試
  • 能讓重複的if判斷改成集中在一個地方
  • 不再有重複的程式碼,減少錯誤發生
  • 用類別將負責工作分開,不再需要全域變數做狀況判斷
Q&A重點整理:
  • 確保類別只單一繼承,多重繼承表示類別負責多種事情
  • 多型在運算子實例化時就決定,類別的方法只會給那個類別用,不會一個類別只在某些狀況用到某些方法,另一些方法在別種狀況才用到

練習:
寫一個小專案盡可能用多型代替if

10 Tips For Clean Code 整理

引言重點整理:

  • 為了趕交期寫出骯髒的程式碼,產生更多臭蟲,日後改版會讓維護成本越疊越高
  • 看程式碼與寫程式碼的時間比例最好落在10:1
  • 花時間寫出易讀的程式碼,日後更易寫
十點建議:
  1. 為程式碼的品質負責,別為了交期放棄專業態度與做法
  2. 與其用簡短的變數加註解,不如把變數意義直接寫在變數上,好的程式碼如一篇散文,變數就是散文中的名詞
  3. 函數/方法宣告名稱要表達明確的意圖,不需要前後文、不用先懂他的類別就知道意思
  4. 註解常說謊,盡量做到程式碼即註解(CTO說這個比較有爭議性。其他文章看到頗認同的是,好的註解是寫「為什麼」,後續開發者看註解才能理解並檢討前人的動機)
  5. 童子軍守則(Boy Scout Rule),改善訪視過的程式碼讓他變得更好(實務上最好問一下原作:P)
  6. 單一功能原則(Single-responsibility Principle a.k.a. SRP):一個函數/方法/類別幾乎只做一件事,一個需要太多參數的函數可能做太多事;一個類別某些物件只用到部分性質與方法,另一些物件則用到其他的,這個類別應該拆分成多個類別
  7. 寫測試,分為整合性測試與單元測試,使用測試驅動開發
  8. 疊代式開發,用畫畫比喻,漸進/疊像是把畫中某一塊畫到完美再進行下一塊,反覆/代是先畫整張畫的草稿,再上底色,再做後續直到完美
  9. 軟體架構是獨立的,用來支持使用者案例,框架只是用來達成架構的工具,不是要遵循的架構,講者用WordPress舉例
  10. 練習,練習,再練習,台上一分鐘,台下十年功

2020年4月26日 星期日

The Clean Code Talks - "Global State and Singletons" 整理

重點整理:
  • 全域狀態(Global State)會有幾個問題:測試時每次結果不一樣(Flakiness);測試時必須有順序;某些測試無法平行進行
  • 單例(Singleton)封裝了全域狀態
  • 有全域狀態的程式會變得很難測試
  • 單例直接或間接碰到的變數或狀態,理論上會變成無限多個
  • 測試無法觸及實例化的單例中的私有變數,只能寫專門測試的方法來測
  • 單例(Singleton)容易產生欺騙性的API
  • 欺騙性的API獨立測試會產生許多問題,必須回溯並解決相依類別
  • 相依類別間沒有明顯的順序性,讓最佳化效能或流程增加許多阻礙或負擔
  • 有依賴注入(Dependency Injection),測試目標類別初始化與其相依類別的順序會很清楚,後續開發者看到程式碼很容易了解類別初始化的流程。
  • 依賴注入強制編譯時期初始化的順序
  • 每一個相依類別可以獨立測試
Q&A重點整理:
  • 不要混淆業務邏輯與物件初始化
  • 只有需要用到相依物件的物件才知道相依類別,其他層不會知道
  • 依賴注入讓類別間的相依明確,若一個類別初始化時吃太多參數,也許是這個類別負責太多事情,最好將它拆開成不同類別
  • 某些情況,例如框架強制無參數的建構子,導致無可避免地使用單例,則委派某個單例類別專門處理這些事情,用最少的力氣去處理它
  • 每個物件應該知道它直接合作的物件,間接需要的物件不應該知道
  • 當一個應用程式需要多個實例來執行不同的任務,全域變數會阻礙這樣的可能性
  • 即使一個環境有許多全域變數,能避免使用它可以讓程式更容易測試