《架構整潔知道》讀書筆記

語言: CN / TW / HK

《架構整潔知道》讀書筆記

舊書重讀,又有了新的體驗和感受。多年前讀過一次,囿於經歷有限,工作中沒有使用組件化開發,組件這部分內容對於我來説屬於困難區,對該部分的內容只是淺嘗輒止,沒有深入研究落地過。隨着工作經歷和經驗的增加,自己接觸這方面越來越多,組件這部分內容變成了我的拉伸區,再次讀這部分內容的時候又有了新的收貨,在工作中我也能以此為指導原則對組件做更好的落地。

概述

設計和架構究竟是什麼

架構和設計:以房屋設計圖為例,架構圖裏包含了所有的底層設計細節,這些細節共同支撐頂層架構設計,底層設計信息和頂層架構設計共同組成了整個房屋的架構文檔。

目標:用最小的人力成本滿足構建和維護系統的需求。

問題案例:團隊擴張的同時,效率下降、變更成本的提升的問題變得愈發的明顯。問題的原因:低估了良好代碼、良好設計的、整潔代碼的重要性。

兩個價值維度

行為價值:軟件產品的商業價值(利益相關者:業務部門、產品策劃部門)

架構價值:軟件產品的可維護、可擴展性的價值(利益相關者:技術部門)

如何權衡:從重要緊急角度來看,架構價值決定了軟件未來的實現、維護、擴展成本,屬於重要象限。技術團隊必須能夠從長期價值角度,對此進行謹慎和合理的評估,防止軟件陷入難以維護、難以擴展的困境。

從基礎構件開始:編程範式

面向對象

面向對象是什麼:面向對象就是以多態的手段對源碼中的依賴關係進行控制的能力,讓底層的實現組件和高層的策略組件實現分離,能夠實現獨立於高層組件的開發和部署,也就是一般意義上的插件架構。

設計模式之美中的定義

面向對象是一種編程範式,以類和對象作為代碼組織的最小單元,並將抽象、封裝、繼承、多態四大特性,作為設計和實現的基石。

OOA:面向對象分析

OOD:面向對象設計

OOP:面向對象實現

設計原則

單一職責原則 (SRP)

概念:任何一個軟件模塊都應該只對某一類行為者負責。

目標:為了代碼的可讀性、可維護性。

案例

員工的例子,員工類(Employee)提供了三個方法

  • 計算工資 calculatePay()(行為者:財務部門)
  • 計算工時 reportHours()(行為者:人力部門)
  • 保存到數據庫 save()(行為者:技術部門、數據庫、CTO)

員工類同時為三個行為者服務,違反了單一職責原則,

如何解決:

大的思路是根據職責進行拆分,拆分如下:

  • 員工數據類 EmployeeData
  • 工資處理類 PayCalculator
    • calculatePay()
  • 工時處理類 HourReporter
    • reportHour()
  • 數據庫保存類 EmployeeSaver
    • save()

並且為了方便調用,由一個類提供統一的接口給外部調用(這裏使用到了外觀模式)

  • EmployeeFacade
    • calculatePay()
    • reportHour()
    • save()

開閉原則(OCP)

概念:設計良好的軟件應該易於擴展,同事抗拒修改。

使用設計原則:依賴的雙方不依賴對方的實現,依賴對方的接口。(如果要讓B的修改不影響到A,那麼就讓B依賴A。)。

目標:提高代碼的可擴展性。

案例:報表的案例,類圖如下

Interector是處理核心業務模塊,不會因為其他模塊的修改而受到影響。‘

Controller模塊不會因為展示層Presenter(ScreenPresenter/PrintPresenter)的修改而受到影響。

image-20210711134626582

里氏替換原則(LSP)

概念:子類的對象能夠替換程序中父類對象出現的位置,能夠保證原來程序的邏輯行為不變以及正確性不被破壞。

解讀:LSP 看上去和 多態類似,但是是有本質上的區別的,多態是面向對象的原則,而LSP是關於繼承的指導原則。子類的設計應該按照父類的約定,而不能違反,導致程序的二義性。從 邏輯行為 和 正確性 兩個角度來看一些違反LSP的案例。

  • 邏輯行為:子類重寫父類的方法,但是拋出了父類未定義的異常。

  • 正確性:父類定義排序方法是自然排序正序,子類重寫方法修改wield了倒序或者ASCII排序,導致正確性問題。

接口隔離原則(ISP)

概念:軟件模塊不應該依賴沒有使用到的接口。

解讀:OOP層面解讀,如果接口中部分方法只有被部分的模塊使用,那麼應該把這部分接口獨立出來,提供給這部分模塊使用。

目的

  • 提高易用性,接口中更少的方法意味着更容易使用,不易出錯(迪米特法則)
  • 提高魯棒性,杜絕誤調用了該模塊中不用到的方法意外的錯誤出現。

控制反轉原則(DIP)

概念:High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.(高層模塊不能依賴低層模塊,兩者通過抽象來互相依賴。另外抽象不能依賴具體實現,具體實現可以依賴抽象)

案例:工廠模式

  • 高層模塊 Application
  • 低層模塊 ServiceFactoryImpl/ConcreteImpl
  • 底層模塊的抽象 SserviceFactory/Service

image-20210711134659317

組件構建原則

組件聚合

講解以下三個組件聚合的原則

• REP: The Reuse/Release Equivalence Principle • CCP: The Common Closure Principle • CRP: The Common Reuse Principle

複用/發佈等同原則(REP)

**概念:**軟件複用的最小粒度應等同於其發佈的最小粒度。

解讀: 組件是為了解決軟件複用的問題,但是在組件複用的時候我們會遇到組件版本的兼容以及使用者如何決定是否需要升級的問題。該原則指出了組件需要定義版本號以及添加對應更新日誌的必要性。

  • 定義版本號是為了解決複用組件的兼容問題;
  • 更新日誌是為了讓組件使用根據這些信息作出對應的升級策略。

共同閉包原則(CCP)

概念:我們應該將那些會同時修改,並且為相同目的而修改的類放到同一個組件中,而將不會同時修改,並且不會為了相同目的而修改的那些類放到不同的組件中。

解讀: 該原則是SRP原則的組件版本。兩者可以簡單的概括為:將相同原因和同時修改的東西放在一起,反之拆開來。

共同複用原則(CRP)

概念:不要強迫一個組件的用户依賴他們不需要的東西。

解讀: 該原則是ISP原則的組件版本。兩者可以簡單的概括為:不要依賴沒有使用到的東西。

組件聚合張力

組件的聚合張力如下所示,軟件生命週期中不同的時候,會有一定的偏向性。

  • 軟件早期,組件依賴不多,更多考慮研發性(開發週期),所以犧牲部分複用性,為維護性而組合,偏向 CCP
  • 隨着項目成熟度變高,組件的依賴變多變複雜,更多的考慮軟件的複用性,偏向了REP以及CRP

img

組件耦合

介紹解除組件耦合的一些原則

  • 無環依賴原則(ADP)
  • 自上而下的設計
  • 穩定依賴原則(SDP)
  • 穩定都想原則(SAP)

無環依賴原則(ADP)

概念:組件依賴關係中不應該出現環。

解讀:循環依賴給組件的測試、複用、發佈帶來了很大的麻煩。可以使用DIP、創建新組建的方式解除這種循環依賴。

自上而下的設計

概念: 設計一張自上而下的組件結構圖,用於指導組件的設計。

解讀: 1、項目早期,對組件的閉包和可複用的組件一無所知,並且沒有組件依賴的存在,組件結構圖的前提條件不成熟以及存在的必要性是非必須的。2、隨着項目成熟度的提高,出現了組件之間的各種依賴,有必要有一個組件結構圖用於指導組件設計,(a.)防止出現組件組件之間的循環依賴、(b.)隔離穩定組件和不穩定組件。

穩定依賴原則(SDP)

概念:依賴關係要指向更穩定的方向。

解讀:1、穩定組件依賴了不穩定組件,不穩定組件會難以變更,因為不穩定組件的變更都有可能影響穩定組件,可以使用DIP來修復這個問題。 2、如何量化組件的穩定程度,可以使用公式:I=fan-out/(fan-out+fan-in) (I表示不穩定性,I=0表示該組件是一個完全穩定組件、I=1表示該組件是一個完全不穩定組件;其中fan-in表示組件被依賴的次數;fan-out表示組件依賴其他的次數) 。3、可以使用DIP解除這種依賴。

img

使用DIP解除依賴之後的組件依賴關係

img

穩定抽象原則(SAP)

概念:一個組件的抽象化程度應該與其穩定性保持一致。

解讀:1、SAP是讓穩定組件同時具有擴展性的指導原則,可以使用OCP原則,添加穩定組件的接口和抽象類,讓具體實現的組件依賴於抽象組件(符合SDP),同時也提供了擴展功能。2、組件抽象的量化方法,可以使用公式:a=Na/Nc(a表示抽象程度[0-1],a=0表示組件沒有抽象,a=0表示組件完全抽象;Na表示抽象類或者接口個數;Nc表示實現類個數)。3、組件抽象程度和組件穩定程度之間的關聯關係可以使用如下圖形分析。(a.)處於痛苦區的穩定組件因為抽象比較少,難以擴展和修改;(b.)處於無用區的不穩定組件,抽象程度比較高,但是很少被依賴,可能是老舊代碼。

_2019_06_11_3_10_05

參考