【軟件構造】第六章第一節 可維護性的度量與構造原則

第六章第一節 可維護性的度量與構造原則

本章面向另外一個質量指標:可維護性——軟件發生變化時,是否能夠以很小的代價適應變化? 
本節是宏觀介紹:(1)什麼是軟件維護;(2)可維護性如何度量;(3)實現高可維護性的設計原則——很抽象。html

Outline

  • 軟件的維護和演化
  • 可維護性的常見度量指標
  • 聚合度與耦合度
  • 面向對象五大原則SOLID
    • 單一職責原則SRP(Single Responsibility Principle)
    • 開放封閉原則OCP(Open-Close Principle)
    • 裏式替換原則LSP(the Liskov Substitution Principle LSP)
    • 依賴倒置原則DIP(the Dependency Inversion Principle DIP)
    • 接口分離原則ISP(the Interface Segregation Principle ISP)

Notes

## 軟件的維護和演化

  • 定義:軟件可維護性是指軟件產品被修改的能力,修改包括糾正、改進或軟件對環境、需求和功能規格說明變化的適應。簡而言之,軟件維護:修復錯誤、改善性能。
  • 類型:糾錯性(25%)、適應性(25%)、完善性(50%)、預防性(4%)
  • 演化:軟件演化是一個程序不斷調節以知足新的軟件需求過程。
  • 演化的規律:軟件質量降低,延續軟件生命
  • 軟件維護和演化的目標:提升軟件的適應性,延續軟件生命 。
  • 意義:軟件維護不只僅是運維工程師的工做,而是從設計和開發階段就開始了 。在設計與開發階段就要考慮未來的可維護性 ,設計方案須要「easy to change」
  • 基於可維護性建設的例子:
    • 模塊化
    • OO設計原則
    • OO設計模式
    • 基於狀態的構造技術
    • 表驅動的構造技術
    • 基於語法的構造技術

 

## 可維護性的常見度量指標

  • 可維護性:可輕鬆修改軟件系統或組件,以糾正故障,提升性能或其餘屬性,或適應變化的環境。
  • 除此以外,可維護性還有其餘許多別名:可擴展性(Extensibility)、靈活性(Flexibility)、可適應性(Adaptability)、可管理性(Manageability)、支持性(Supportability)。總之,有好的可維護性就意味着容易改變,容易擴展。
  • 軟件可維護性的五個子特性:
    • 易分析性。軟件產品診斷軟件中的缺陷或失效緣由或識別待修改部分的能力。
    • 易改變性。軟件產品使指定的修改能夠被實現的能力,實現包括編碼、設計和文檔的更改。若是軟件由最終用戶修改,那麼易改變性可能會影響易操做性。
    • 穩定性。軟件產品避免因爲軟件修改而形成意外結果的能力。
    • 易測試性。軟件產品使已修改軟件能被確認的能力。
    • 維護性的依從性。軟件產品遵循與維護性相關的標準或約定的能力。
  • 一些經常使用的可維護性度量標準:
    • 圈複雜度(CyclomaticComplexity):度量代碼的結構複雜度。
    • 代碼行數(Lines of Code):指示代碼中的大體行數。
    • Halstead Volume:基於源代碼中(不一樣)運算符和操做數的數量的合成度量。
    • 可維護性指數(MI):計算介於0和100之間的索引值,表示維護代碼的相對容易性。 高價值意味着更好的可維護性。
    • 繼承的層次數:表示擴展到類層次結構的根的類定義的數量。 等級越深,就越難理解特定方法和字段在何處被定義或從新定義。
    • 類之間的耦合度:經過參數,局部變量,返回類型,方法調用,泛型或模板實例化,基類,接口實現,在外部類型上定義的字段和屬性修飾來測量耦合到惟一類。
    • 單元測試覆蓋率:指示代碼庫的哪些部分被自動化單元測試覆蓋。

 

## 模塊化設計規範:聚合度與耦合度

  • 模塊化編程的含義:模塊化編程是一種設計技術,它強調將程序的功能分解爲獨立的可互換模塊,以便每一個模塊都包含執行所需功能的一個方面。
  • 設計規範:高內聚低耦合
  • 評估模塊化的五個標準:
    • 可分解性:將問題分解爲各個可獨立解決的子問題
    • 可組合性:可容易的將模塊組合起來造成新的系統
    • 可理解性:每一個子模塊均可被系統設計者容易的理解
    • 可持續性:小的變化將隻影響一小部分模塊,而不會影響整個體系結構
    • 出現異常以後的保護:運行時的不正常將侷限於小範圍模塊內
  • 模塊化設計的五條原則:
    • 直接映射:模塊的結構與現實世界中問題領域的結構保持一致
    • 儘量少的接口:模塊應儘量少的與其餘模塊通信
    • 儘量小的接口:若是兩個模塊通信,那麼它們應交換儘量少的信息
    • 顯式接口:當A與B通信時,應明顯的發生在A與B的接口之間
    • 信息隱藏:常常可能發生變化的設計決策應儘量隱藏在抽象接口後面

【內聚性】編程

  • 又稱塊內聯繫。指模塊的功能強度的度量,即一個模塊內部各個元素彼此結合的緊密程度的度量。若一個模塊內各元素(語名之間、程序段之間)聯繫的越緊密,則它的內聚性就越高。
  • 所謂高內聚是指一個軟件模塊是由相關性很強的代碼組成,只負責一項任務,也就是常說的單一責任原則。

【耦合性】設計模式

  • 也稱塊間聯繫。指軟件系統結構中各模塊間相互聯繫緊密程度的一種度量。模塊之間聯繫越緊密,其耦合性就越強,模塊的獨立性則越差。模塊間耦合高低取決於模塊間接口的複雜性、調用的方式及傳遞的信息。
  • 對於低耦合,粗淺的理解是:一個完整的系統,模塊與模塊之間,儘量的使其獨立存在。也就是說,讓每一個模塊,儘量的獨立完成某個特定的子功能。模塊與模塊之間的接口,儘可能的少而簡單。若是某兩個模塊間的關係比較複雜的話,最好首先考慮進一步的模塊劃分。這樣有利於修改和組合。

更多請參考 永迪的專欄 淺談高內聚低耦合框架

## SOLID原則 

  更多請參考 我理解的SOLID 淺談SOLID原則的具體使用

  S.O.L.I.D是面向對象設計和編程(OOD&OOP)中幾個重要編碼原則(Programming Priciple)的首字母縮寫。

綜述:設計模式前五個原則,偏偏是告訴咱們用抽象構建框架,用實現擴展細節的注意事項而已:運維

  單一職責原則告訴咱們實現類要職責單一;里氏替換原則告訴咱們不要破壞繼承體系;依賴倒置原則告訴咱們要面向接口編程;接口隔離原則告訴咱們在設計接口的時候要精簡單一;迪米特法則告訴咱們要下降耦合。而開閉原則是總綱(實現效果),它告訴咱們要對擴展開放,對修改關閉。模塊化

SRP The Single Responsibility Principle 單一責任原則
OCP The Open Closed Principle 開放封閉原則
LSP The Liskov Substitution Principle 里氏替換原則
ISP The Interface Segregation Principle 接口分離原則
DIP The Dependency Inversion Principle 依賴倒置原則

【SRP 單一責任原則】post

  • 含義:須要修改某個類的時候緣由有且只有一個。換句話說就是讓一個類只作一種類型責任,當這個類須要承當其餘類型的責任的時候,就須要分解這個類。
  • 若是一個類包含了多個責任,那麼將引發不良後果:引入額外的包,佔據資源;致使頻繁的從新配置、部署等。
  • SRP是最簡單的原則,倒是最難作好的原則。
  • SRP的一個反例:

 

 【OCP 開放封閉原則】性能

  • 軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。這個原則是諸多面向對象編程原則中最抽象、最難理解的一個。
    • 模塊的行爲應是可擴展的,從而該模塊可表現出新的行爲以知足需求的變化。
    • 模塊自身的代碼是不該被修改的
    • 擴展模塊行爲的通常途徑是修改模塊的內部實現
    • 若是一個模塊不能被修改,那麼它一般被認爲是具備固定的行爲。
  • 關鍵解決方案:抽象技術。 使用繼承和組合來改變類的行爲。
  • OCP的一個反例:

  •  OCP的一個例子:
 1 // Open-Close Principle - Bad example   
 2 class GraphicEditor {  
 3 public void drawShape(Shape s) {   
 4     if (s.m_type==1)   
 5         drawRectangle(s);   
 6     else if (s.m_type==2)   
 7         drawCircle(s);   
 8     }   
 9     public void drawCircle(Circle r)   
10         {....}   
11     public void drawRectangle(Rectangle r)   
12         {....}   
13 }  
14   
15 class Shape { int m_type; }  
16 class Rectangle extends Shape { Rectangle() { super.m_type=1; } }  
17 class Circle extends Shape { Circle() { super.m_type=2; } } 

    上面代碼存在的問題:單元測試

  • 不可能在不修改GraphEditor的狀況下添加新的Shape
  • GraphEditor和Shape之間的緊密耦合
  • 不調用GraphEditor就很難測試特定的Shape

    改進以後的代碼:測試

// Open-Close Principle - Good example  
class GraphicEditor {   
public void drawShape(Shape s) {   
    s.draw();   
    }   
}  
class Shape { abstract void draw(); }  
class Rectangle extends Shape { public void draw() { // draw the rectangle } }   

 

 【LSP 里氏替換原則】

  • Liskov's 替換原則意思是:"子類型必須可以替換它們的基類型。"或者換個說法:"使用基類引用的地方必須能使用繼承類的對象而沒必要知道它。" 這個原則正是保證繼承可以被正確使用的前提。一般咱們都說,「優先使用組合(委託)而不是繼承」或者說「只有在肯定是 is-a 的關係時才能使用繼承」,由於繼承常常致使」緊耦合「的設計。
  •  【軟件構造】第五章第二節 設計可複用的軟件 中有所描述

 【ISP 接口分離原則】

  • 含義:客戶端不該依賴於它們不須要的方法。換句話說,使用多個專門的接口比使用單一的總接口總要好。
  • 客戶模塊不該該依賴大的接口,應該裁減爲小的接口給客戶模塊使用,以減小依賴性。如Java中一個類實現多個接口,不一樣的接口給不用的客戶模塊使用,而不是提供給客戶模塊一個大的接口。
  • 「胖」接口具備不少缺點。
    • 胖接口可分解爲多個小的接口;
    • 不一樣的接口向不一樣的客戶端提供服務;
    • 客戶端只訪問本身所須要的端口。
  • 下圖展現出了這種思想:
  • ISP的一個反例

【DIP 依賴轉置原則】

  • 定義:
    • 高層模塊不該該依賴於低層模塊,兩者都應該依賴於抽象 
    • 抽象不該該依賴於細節,細節應該依賴於抽象
  • 這個設計原則的亮點在於任何被DI框架注入的類很容易用mock對象進行測試和維護,由於對象建立代碼集中在框架中,客戶端代碼也不混亂。有不少方式能夠實現依賴倒置,好比像AspectJ等的AOP(Aspect Oriented programming)框架使用的字節碼技術,或Spring框架使用的代理等。
    • 高層模塊不要依賴低層模塊;
    • 高層和低層模塊都要依賴於抽象;
    • 抽象不要依賴於具體實現; 
    • 具體實現要依賴於抽象;
    • 抽象和接口使模塊之間的依賴分離。
  • 一個具體的例子:

進行抽象改進後:

【SOLID 總結】

 

  1. 一個對象只承擔一種責任,全部服務接口只經過它來執行這種任務。
  2. 程序實體,好比類和對象,向擴展行爲開放,向修改行爲關閉。
  3. 子類應該能夠用來替代它所繼承的類。
  4. 一個類對另外一個類的依賴應該限制在最小化的接口上。
  5. 依賴抽象層(接口),而不是具體類。
相關文章
相關標籤/搜索