軟體構造-10 面向可維護性的構造技術
本章面向可維護性:軟體發生變化時,是否可以以很小的代價適應變化?
(本章偏理論,主要為記憶性內容,本部落格僅為簡單的記錄與整理)
1.軟件可維護性及其演化
- 軟體可維護性型別: 糾錯性(25%),適應性(21%),完善性(50%),預防性(4%)
- 軟體維護不僅僅是運維工程師的工作,而是從設計和開發階段就開始了/在設計與開發階段就要考慮將來的可維護性/設計方案要“easy to change”
2.可維護性指標
1.可維護性別名:可維護性,可擴充套件性,靈活性,可適應性,可管理性,支援性
2.code review時關於可維護性經常會問的問題:
設計結構是否足夠簡單
模組之間是否鬆散耦合
模組內部是否高度聚合
是否使用非常深的繼承樹
是否使用delegation替代繼承
程式碼的圈/環複雜度是否太高
是否存在重複程式碼
3.常用可維護性度量:
1)圈/環複雜度:獨立路徑的數量 CC=number of areas
2)程式碼行數
3)Halstead Volume:多少獨立算符
4)可維護性指數(MI):
5)繼承的層次數
6)類之間的耦合度(儘量減少非常複雜的相互呼叫)
7)單元測試的覆蓋度
3. 模組化設計和模組化原則
模組化程式設計:高內聚,低耦合(多模組之間關聯較小,防止一個模組的大幅度改變影響另一個模組),分離關注點,資訊隱藏
(1) 評估模組化的五個標準:
- 可分解性:能否容易分解成各個可獨立解決的子問題,是模組之間的依賴關係顯式化和最小化
- 可組合性:能夠容易將多個模組組合起來形成新的系統,使模組在不同的環境下複用
- 可理解性:每個部分都可以被獨立的理解
- 可持續性:發生變化時受影響範圍最小
- 出現異常之後的保護:出現異常後受影響範圍最小
(2) 模組化設計的五個原則:
- 直接對映:模組的結構與現實世界中問題領域的結構保持一致-》持續性,可分解性
- 儘可能少的介面:模組應儘可能少的與其他模組通訊-》可持續性、保護性、可理解性、可組合性
- 儘可能小的介面:如果兩個模組通訊,那麼它們應交換儘可能 少的資訊-》可持續性,保護性
- 顯式介面:當A與B通訊時,應明顯的發 生在A與B的介面之間-》可分解性、可組合性、可持續性、 可理解性
5.資訊隱藏:經常可能發生變化的設計決策應 儘可能隱藏在抽象介面後面
(3)耦合和內聚:
1.耦合:模組間的介面數目,每個介面的複雜度
2.內聚:模組內部的方法聯絡是否緊密
4. OO設計原則:SOLID
(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:單一責任原則
1)ADT中不應該有多於1個原因讓其發生變化,否則就拆分開
2)責任:變化的原因
3)不應有多於1個的原因使得一個類發生變化
4)包含多個責任會導致:– 引入額外的包,佔據資源
– 導致頻繁的重新配置、部署等
5)一個類,一個責任
(2)OCP:(面向變化的)開放/封閉原則
1)對擴充套件性的開放:模組的行為應是可擴充套件的,從而該模組可表現出新的行為以滿足需求的變化
2)對修改的封閉:但模組自身的程式碼是不應被修改的/擴充套件模組行為的一般途徑是修改模組的內部實現/如果一個模組不能被修改,那麼它通常被認為是具有固定的行為
3)關鍵解決方案:抽象技術(儘量少用具體的)
(3)LSP:Liskov替換原則
子型別必須能夠替換其基型別/派生類必須能夠通過其基類的介面使用,客戶端無需瞭解二者之間的差異
(4)ISP:介面隔離原則
1)不能強迫客戶端依賴於它們不需要的介面:只提供必需的介面
2)避免介面汙染/避免胖介面
(5)DIP:依賴轉置原則
1)高層模組不應該依賴於低層 模組,二者都應該依賴於抽象
2)抽象不應該依賴於實現細節,實現細節應該依賴於抽象
OO設計的兩大武器:抽象(LSP,DIP,OCP)/分離(SPR,ISP)
5. 語法驅動的構造
有一類應用,從外部讀取文字資料,在應用中做進一步處理
正則表示式:根據語法,開發一個它的解析器,用於後續的解析(不變的直接用‘’裡的字元表示,其餘可變的用符號組合表示,中間有比較複雜的模組就進行命名,單獨脫離出來也寫成一個表示式,比如下面eg中的hostname)
(1)基本操作:
– Concatenation 連線: x ::= y z (x匹配y與z連線)
– Repetition 重複: x ::= y* (x匹配0或更多個y)(自動機裡的克林閉包?)
– Union 選擇 : x ::= y | z (x匹配y或者z)
eg:
(2)更多操作:
1)選擇 (出現0或1次),用 ? 表示 :
x ::= y? (x匹配y或者空)
2)出現1次或更多次 ,用 + 表示:
x ::= y+ (x匹配一個或更多y,與 x ::= y y*相等 )
3)字元類 [...],表示長度為 1 的字串,其中包含任何一個方框中列出的字元:
x ::= [a-c] 等同於 x ::= 'a' | 'b' | 'c'
x ::= [aeiou] 等同於 x ::= 'a' | 'e' | 'i' | 'o' | 'u'
4) 倒排字元類 [^...],表示長度為 1 的字串,其中包含括號中未列出的任何字元:
x ::= [^a-c] 等同於 x ::= 'd' | 'e' | 'f' | ...
(3)優先順序:* ? +優先順序最高,連線次之,| 最低
(4)在Java中使用正則表示式:
1)使用java.util.regex包中的三個類:
1>Pattern:Pattern物件是對regex正則表示式進行編譯之後得到的結果
2>Matcher: 利用Pattern對輸入字串進行解析
3>PatternSyntaxException:object 是一個未經檢查的異常,表示正則表示式中的語法錯誤
2)eg:
(5)字元類:
(6)預定義字元類:
(7)量詞: