大綱
軟體維護和演變
可維護性度量
模組化設計和模組化原則
OO設計原則:SOLID
OO設計原則:GRASP
總結
軟體維護和演變
什麼是軟體維護?
軟體工程中的軟體維護是交付後修改軟體產品以糾正故障,提高效能或其他屬性。軟體維護:修復錯誤,改善效能
在“ISO / IEC 14764:2006軟體工程 – 軟體生命週期過程 – 維護”
運維工程師
維護是軟體生產中最困難的方面之一,因為維護包含了所有其他階段的各個方面
使用者報告故障並由維護工程師處理。
維護工程師必須具備出色的除錯技能
- 故障可能存在於產品中的任何地方,故障的原因可能在於現在不存在的規格或設計文件(錯誤/問題本地化)。
- 需要高超的診斷技能,測試技能和文件技能(測試,修復和記錄更改)。
軟體維護的型別
糾正性維修25%糾錯性
- 對交付後執行的軟體產品進行無功修改,以糾正發現的問題;
適應性維護21%適應性
- 修改交付後執行的軟體產品,以保持軟體產品在變化或變化的環境中可用;
完善性維護50%完善性
- 交付後增強軟體產品以提高效能或可維護性;
預防性維護4%預防性
- 交付後對軟體產品進行修改,以便在軟體產品發生有效故障前檢測並糾正軟體產品中的潛在故障。
雷曼關於軟體演化的規律(Lehman’s Laws on Software Evolution)
反饋系統
持續變化
持續增長
質量下降
增加複雜性
自我調節
- 保持組織穩定
- 保持熟悉
軟體維護和進化的目標
軟體維護和演化的目標:為了提高軟體的適應性和適應性,並保持其活力,即“長期軟體(低熵軟體)”
提高軟體的適應性,延續軟體生命
Linux核心發展的一個例子:可維護性指數
維護不僅僅是op工程師的任務……
維護不僅僅是維護和操作工程師的任務,也是軟體設計人員和開發人員的潛在任務。 軟體維護不僅僅是運維工程師的工作,而是從設計和開發階段就開始了
對他們來說,在設計和施工階段必須考慮軟體的未來潛在變化/擴充套件;在設計與開發階段就要考慮將來的可維護性
因此,靈活和可擴充套件的設計/結構被全面考慮,換句話說,“易於改變/擴充套件”。 設計方案的“容易改變”
這就是所謂的軟體構建的“可維護性”,“可擴充套件性”和“靈活性”。
可維護性建設的例子
模組化設計與實現模組化
- 低耦合和高內聚力
OO設計原則OO設計原則
- SOLID,GRASP
OO設計模式OO設計模式
- 工廠方法模式,Builder模式
- 橋模式,代理模式
- 紀念模式,狀態模式
基於狀態的構造技術(自動機程式設計)
表驅動的構造技術
基於語法的構造技術(Grammar-based construction)
可維護性度量
許多可維護性的名字
可維護性
- “軟體系統或元件可輕鬆修改以糾正故障,改善效能或其他屬性,或適應變化的環境”。
可擴充套件性
- 軟體設計/實現將未來的增長考慮在內,並被視為擴充套件系統能力的系統性測量以及實現擴充套件所需的工作量。
靈活性
- 軟體根據使用者需求,外部技術和社會環境等容易改變的能力。
可適應性
- 一種互動式系統(自適應系統)的能力,可以根據所獲得的有關其使用者及其環境的資訊,使其行為適應個人使用者。
可管理性
- 如何有效和輕鬆地監控和維護軟體系統,以保持系統的正常執行,安全並平穩執行。
可支援性
- 基於包含質量文件,診斷資訊以及知識豐富且可用的技術人員的資源,可以有效地使軟體在部署後保持執行。
有關可維護性的問題
Code review的時候經常問的關於可維護性的問題:
- 結構和設計簡單:改變事物有多容易?
- 事情緊密或鬆散(即關注點分離)?
- 包/模組中的所有元素是否具有凝聚力,其職責是否清晰且密切相關?
- 它是否具有過深的繼承層次結構,還是贊成繼承的組合?
- 方法定義中存在多少個獨立的執行路徑(即,cycolmatic複雜度)?
- 存在多少程式碼重複?
一些常用的可維護性度量標準
圈複雜度 – 測量程式碼的結構複雜度。
- 它是通過計算程式流程中不同程式碼路徑的數量而建立的。
- 具有複雜控制流程的程式將需要更多的測試來實現良好的程式碼覆蓋率,並且將不易維護。
- CC = E-N + 2,CC = P + 1,CC =區域的數量
Lines of Code(程式碼行數) – 表示程式碼中的近似行數。
- 非常高的數值可能表明某種型別或方法試圖做太多工作,應該分手。
- 這也可能表明型別或方法可能難以維護。
對於給定的問題,令:
η_1=不同運算子的數目
η_2=不同的運算元的數量
Ν_1=運算子總數
Ν_2=運算元總數
從這些數字中可以計算出幾項度量:
程式詞彙表:η=η_1+η_2
程式長度:N=Ν_1+Ν_2
計算的程式長度:N ̂=η_1 log_2〖η_1 〗+η_2 log_2〖η_2 〗
體積:V=N×log_2η
難度:D=η_1/2×Ν_2/η_2
代價:E=D×V
難度測量與程式編寫或理解的難度有關
代價度量使用以下關係轉化為實際編碼時間,
程式設計所需的時間:T=E/18 秒
Halstead提供的錯誤(B)是對執行錯誤數量的估計。
提供的錯誤數量:B = E^(2/3)/3000或更接近的B = V/3000也被接受。
Halstead Volume:基於原始碼中(不同)運算子和運算元的數量的合成度量。
一些常用的可維護性度量標準
可維護性指數(MI)- 計算介於0和100之間的索引值,表示維護程式碼的相對容易性。 高價值意味著更好的可維護性。 它的計算基於:
- Halstead體積(HV)
- 圈複雜性(CC)
- 每個模組的平均程式碼行數(LOC)
- 每個模組註釋行的百分比(COM)。
一些常用的可維護性度量標準
繼承的層次數
- 表示擴充套件到類層次結構的根的類定義的數量。 等級越深,就越難理解特定方法和欄位在何處被定義或重新定義。
類之間的耦合度
- 通過引數,區域性變數,返回型別,方法呼叫,泛型或模板例項化,基類,介面實現,在外部型別上定義的欄位和屬性修飾來測量耦合到唯一類。
- 良好的軟體設計決定了型別和方法應該具有高內聚性和低耦合性。
- 高耦合表示一種設計難以重用和維護,因為它與其他型別之間存在許多相互依賴關係。
單元測試覆蓋率
- 指示程式碼庫的哪些部分被自動化單元測試覆蓋。 (將在第7章中研究)
模組化設計和模組化原則
模組化程式設計
模組化程式設計是一種強調將程式功能分離為獨立,可互換模組的設計技術,每種模組都包含執行所需功能一個方面所需的一切。
將整個程式的程式碼高度分解為結構化程式設計和OOP。
模組化程式設計設計的目標是將系統劃分為模組,並通過以下方式在元件之間分配責任:
- 模組內高凝聚力(高內聚)
- 模組之間的耦合鬆耦合(低耦合)
模組化降低了程式設計師在任何時候都必須處理的總體複雜性,假設:
- 將功能分配給將相似功能組合在一起的模組(分離關注點)
- 模組之間有小而簡單的定義明確的介面(資訊隱藏)
內聚和耦合的原則可能是評估設計可維護性的最重要的設計原則。
(1)評估模組性的五個標準
Decomposability(可分解性)
- 較大的元件是否分解為較小的元件?
Composability(可組合性)
- 較大的元件是否由較小的元件組成?
Understandability(可理解性)
- 元件是否可單獨理解
Continuity(可持續性)
- 規範的小改動是否會影響本地化和有限數量的元件?
Protection(出現異常之後的保護)
- 執行時異常的影響是否侷限於少數相關元件?
(2)模組化設計的五條原則
Direct Mapping (直接對映)
Few Interfaces (儘可能少的介面)
Small Interfaces (儘可能小的介面)
Explicit Interfaces (顯式介面)
Information Hiding (資訊隱藏)
(3)耦合和內聚
耦合
耦合是模組之間依賴關係的度量。 如果兩個模組之間的變化可能需要另一個模組的變更,則兩個模組之間存在依賴關係。
模組之間的耦合度取決於:
- 模組之間的介面數量(數量)和
- 每個介面的複雜性(由通訊型別決定)(質量)
HTML,CSS和JavaScript之間的耦合
一個精心設計的網路應用程式模組化:
- 指定資料和語義的HTML檔案
- 規定HTML資料的外觀和格式的CSS規則
- 定義頁面行為/互動性的JavaScript
內聚
內聚是衡量一個模組的功能或責任有多強烈程度的一個指標。
如果一個模組的所有元素都朝著相同的目標努力,那麼它就具有很高的內聚。
最好的設計在模組內具有高內聚力(也稱為強內聚力)和模組之間的低耦合(也稱為弱耦合)。
OO設計原則:SOLID
SOLID:5類設計原則
(SRP) The Single Responsibility Principle 單一責任原則
(OCP) The Open-Closed Principle 開放-封閉原則
(LSP) The Liskov Substitution Principle Liskov替換原則
(DIP) The Dependency Inversion Principle 依賴轉置原則
(ISP) The Interface Segregation Principle 介面聚合原則
(1)單一責任原則(SRP)
“類改變不應該有一個以上的原因”,即一個類應該集中精力做一件事,只能一件事。
責任:“變更的理由”(責任:變化的原因)
SRP:
- 類改變不應該有一個以上的原因。 (不應有多於1個的原因使得一個類發生變化)
- 一個類,一個責任。(一個類,一個責任)
如果一個類包含了多個責任,那麼將引起不良後果:
- 引入額外的包,佔據資源
- 導致頻繁的重新配置,部署等
SRP是原則中最簡單的一種,也是最難做到的一種。(最簡單的原則,卻是最難做好的原則)
(2)開放/封閉原則(OCP)
類應擴充套件(對擴充套件性的開放)
- 這意味著模組的行為可以擴充套件。 我們可以根據應用程式的需求變化,或者為了滿足新應用程式的需求,使模組以新的和不同的方式執行。 (模組的行為應是可擴充套件的,從而該模組可表現出新的行為以滿足需求的變化)
但關閉修改。(對修改的封閉)
- 這種模組的原始碼是不可侵犯的。 沒有人可以對其進行原始碼更改。(但模組自身的程式碼是不應被修改的)
- 擴充套件模組行為的正常方法是對該模組進行更改。(擴充套件模組行為的一般途徑是修改模組的內部實現)
- 無法更改的模組通常被認為具有固定的行為。(如果一個模組不能被修改,那麼它通常被認為是具有固定的行為)
Key:abstraction(關鍵的解決方案:抽象技術)
“軟體實體(類,模組,函式等)應該被開啟以進行擴充套件,但是為了修改而關閉”,即,使用繼承和組合來改變類的行為
開放封閉原則 – 幾個問題…。
不可能在不修改GraphEditor的情況下新增新的Shape
重要的是要了解GraphEditor新增一個新的形狀
GraphEditor和Shape之間的緊密耦合
不參與GraphEditor就很難測試特定的Shape
If-Else-/Case應該被避免
OCP表示單一選擇
只要軟體系統必須支援一組替代方案,系統中的一個且只有一個模組應該知道他們的詳盡列表。
編輯器:一組命令(插入,刪除等)
圖形系統:圖形型別集(矩形,圓形等)
編譯器:語言結構集(指令,迴圈,表示式等)
(3)Liskov替換原則(LSP)
“使用指標或對基類的引用的函式必須能夠使用派生類的物件而不知道它”,即,子類在使用它們的基類時應該表現得很好
LSP:子型別必須可替代其基本型別。(子型別必須能夠替換其基型別)
派生類必須可以通過基類介面使用,而不需要客戶端了解其差異。 (派生類必須能夠通過其基類的介面使用,客戶端無需瞭解二者之間的差異)
在第5-2節中已經討論過可複用性。
(4)介面隔離原理(ISP)
“客戶不應該被迫依賴他們不使用的介面”,即保持介面小。
不要強制類來實現它們不能實現的方法(Swing / Java)
不要用很多方法汙染介面
避免“胖”的介面
客戶不應該被迫依賴他們不使用的方法。(客戶端不應依賴於它們不需要的方法)
介面屬於客戶端,而不屬於層次結構。
這個原則處理“胖”介面的缺點。(“胖”介面具有很多缺點)
具有“胖”介面的類是介面不具有內聚性的類。(不夠聚合)
- 該類的介面可以分解為多組成員函式。(胖介面可分解為多個小的介面)
- 每個小組服務一組不同的客戶端(不同的介面向不同的客戶端提供服務)。
- 因此有些客戶使用一組成員函式,而其他客戶使用其他組。(客戶端只訪問自己所需要的埠)
(5)依賴倒置原理(DIP)
高階模組不應該依賴於低階模組。 兩者都應該取決於抽象。
- 抽象不應該依賴於細節(抽象的模組不應依賴於具體的模組)
- 細節應該取決於抽象(具體應依賴於抽象)
應該使用大量的介面和抽象!
為什麼DIP?
優點:
- 將類契約正式化。
- 您根據前置和後置條件定義例程的服務。 這非常清楚會發生什麼。
嘗試設計測試
- 建立一個測試友好的設計
- 測試友好的模組可能會展現其他重要的設計特徵。
例如:你會避免迴圈依賴。 如果您必須從UI單獨測試,業務邏輯將更好地與UI程式碼隔離
OO設計原則:GRASP
什麼是GRASP模式
一般責任分配軟體模式(原則),縮寫為GRASP,包含為OOP中的類和物件分配責任的準則。
GRASP模式是幫助理解基本物件設計的學習輔助,並以有條理,合理,可解釋的方式應用設計推理。
這種理解和使用設計原則的方法是基於對班級分配責任的模式。
GRASP是關於如何為“類”和“物件”指派“職責”的一系列原則
什麼是責任
物件的責任:與物件的義務有關
瞭解:
- 瞭解私有封裝資料
- 瞭解相關物件
- 瞭解它可以派生或計算的事物
這樣做:
- 自己做一些事情,比如建立一個物件或者做一個計算
- 在其他物件中發起行動
- 控制和協調其他物件的活動。
總結
軟體維護和演變
可維護性度量
模組化設計和模組化原則
OO設計原則:SOLID
OO設計原則:GRASP