2019年9月28日 星期六

Working Effectively with Legacy

  • 時間急迫時的處理方式:
    • 新生方法(sprout method):為原有function 添加新邏輯時,另外寫新的方法,並在原有的方法中呼叫新方法,可方便單獨對新方法做單元測試,並抽換掉新方法驗證添加後是否改變行為。
    • 新生類別(sprout class):如果在原有class 內加新方法無法new instance測試,可另外寫在新的class,以方便針對新方法做單元測試。
    • 外覆方法(wrap method):原method pay 重新命名原有method為dispatchXXX,建立一個與舊method 同名的method為pay,裡面呼叫dispatchXXX以及新加method logXXX,但但需注意命名要清楚。
    • 外覆類別(wrap class):參考裝飾者模式(decorator pattern),但一層一層的外覆類別較為複雜需小心使用。(參考:修饰模式
    • 缺點:舊的爛程式沒有被改善且有可能造成重複邏輯的程式碼存在
  • 善用依賴反轉原則,讓程式碼依賴interface 或抽象類別,因為介面不易被更動,當實作內容邏輯調整時不易受影響
  • 使用TDD(測試驅動開發)
    • 為想要測試的class 寫一個Test
    • 讓測試通過
    • 消除重複於新舊程式碼中的邏輯
  • 使用繼承去override method
    • 過度使用繼承可能會造成錯誤的理解,會比較建議需要被override 的方法改成abstract method,這樣任何類別都不會同一方法有多個實作(這樣不會違反Liskvo 替換原則,里氏替換原則就是子類可以擴充套件父類的功能,但不能改變父類原有的功能。
    • Liskvo 替換原則: LSP(Liskov Substitution Principle)Liskvo替換原則
    • 且兩個各自繼承自某父類別的class 若同時需要存在使用就會無法達成,建議抽成config 並把需判別config 的method 搬移到設定中
  • 參數化建構子可解開在建構子中隱藏的依賴物件,在建構子中new 的instance 可改用constructor 傳入,若不想異動其他method,則新增一個不需要傳入此instance的constructor供呼叫即可, 若constructor 太多可組成一個instance 傳入
  • 一個Method  不能同時具備Command & Query ,Command 是指改變物件狀態 但不回傳值.Query 是指有回傳值 但不改變物件狀態
  • 修改方法時可以畫一個影響結構圖,判斷影響了哪些方法需注意:
    • 查看該方法會修改那些值? 使用這些值的方法是否受影響?
    • 該方法若有return value 誰使用了他?
    • 誰使用了此修改的method?
    • 查看是否有父類別或子類別 override 了方法或使用了被修改的東西?
    • 是否用了別的方法而該方法是共用的也被改到?
    • 是否有全域變數或靜態方法被修改到?
  • 找出Pinch Point(匯點:是自然封裝邊界, 指使用修改方法的共通處) 在匯點處寫測試的好處是只要寫少數幾個測試就能探測大量其他方法變動的目的
    • 影響結構圖關注於他們如何聚集再一起
    • 當匯點相關的各個類別重構過未來都有各自的單元測試後,或許匯點就不再重要可被移除
  • 特徵測試:
    • 描述系統目前的實際行為而不是應該行為
    • 如果刻意引入Bug 測試須能感測出錯誤,如果沒有就需要放入更多測試
  • 重構需編寫測試來驗證行為沒有被改變,且確認測試覆蓋了被移轉的程式碼,且正確的連接在系統中
  • 違反單一原則(SRP)有兩種形式:
    • 介面層面的違反:一個類別的介面呈現負責多樣事務的型態
    • 實作層面的違反:是否實作了很多事情,還是僅委託其他類別完成
  • 介面隔離原則(ISP):類別大,但客戶並不會全部使用所有方法,可把特別客戶用的那組方法提取出來,建立一個介面,讓這個大類別implements ,客戶存取時便可改用該介面。有利於資訊隱藏,大類別改變時客戶端也不需重新編譯。
  • 同一時間只作一件事
  • 消除重複:將重複的程式碼提取到父類別,差別的地方用abstract method 由子類別各自實作
  • 消除重複後程式碼會自然地往開放/封閉原則靠攏,開放擴展、封閉修改。
  • 面對巨型方法若有開發工具有自動重構可善加運用,否則可最好有測試再做重構,不然就是保守的小塊小塊程式碼進行提取
  • 解依賴
    • Extract and Override Factory Method
    • Extract Interface 介面提取
    • 當無法對一個參數進行介面提取或難以偽裝時EX: HttpServletRequest 可使用參數適配 。(把 HttpServletRequest  包裝於一個類別裡面,原先操作 HttpServletRequest 的方法透過該類別提供方法來取用即可)
    • 常常一起被使用的全域變數應抽出單獨放同一類別
    • 引入靜態設置方式,直接new 一service 改為為static setService(),程式中用到該service 的方法皆可用該service,如此一來便可做一個FakeService 透過 setService去設定並讓測試程式測試
    • 建構子寫死初始化工作會給測試帶來很大麻煩 , 如果用建構子建立一個物件,最好的方法就是將建立的過程外部化,在類別外建立該物件再透過建構子傳進來,也可以再寫一個方便的建構子提供原來的方法使用,如此一來可方便做測試
    • Inventory.getInventory().addItem() --> 將 Inventory.getInventory() 抽成一個getXXX Method 將原先語法改為 getXXX().addItem(),在撰寫FakeInventory 並於測試程式中使用
    • Subclass and Override Method

沒有留言:

張貼留言