哈工大 軟件構造課程 復習考點總結(第六、七章)
可維護性的常見度量指標
- Cyclomatic complexity 圈復雜度
- Lines of Code LoC 代碼行數
- Maintainability Index (MI) 可維護性指數
- Depth of Inheritance 繼承的層次數
- Class Coupling 類之間的耦合度
- Unit test coverage 測試代碼覆蓋率
Coupling 耦合度 and Cohesion 聚合度
- Coupling 耦合度:
模塊之間的依賴性。
- Conhesion 聚合度
功能專一性。高聚合度:模塊的所有元素作用於同一目標。
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 接口聚合原則
- SRP 單一責任原則
一個類僅有一個責任;
至多有1個原因使得一個類發生變化。
- OCP 開放-封閉原則
- 對擴展性開放
模塊的行為可擴展,從而該模塊可表現出新的行為以滿足需求的變化。
- 對修改封閉
模塊自身原本的代碼不應被修改。
擴展模塊行為的一般途徑是修改模塊的內部實現。
如果一個模塊不能被修改,那麽通常認為其具有固定的行為。
- 對擴展性開放
實現方案:抽象:繼承、組合
註意:
避免使用 if-else / switch-case 語句。
- LSP liskov替換原則
- DIP 依賴轉置原則
低層模塊依賴於高層模塊,具體依賴於抽象(抽象類、接口)。
- ISP 接口聚合原則
客戶端不應被迫依賴於它們不需要的方法。
客戶端不需要實現(接口中)不需要的方法。
實現方案:將"胖"接口分解為若幹個小接口,不同的接口向不同客戶端提供服務。
語法、正則表達式
Productions 產生式節點,也稱nonterminal非終止節點。(中間變量)
Terminal 終止節點。(終止字符)
Root 根節點。(起始字符)
正則語法:簡化之後可以表達為一個產生式而不包含任何非終止節點。
Java中:
匹配:
Pattern . matcher ( regex , string)
或 Pattern pattern = Pattern.compile( regex ) ;
Matcher m = pattern.matcher (string) ;
m.matcher().
捕獲:
Pattern parse = Pattern . compile (regex) ;
Matcher match = parse.matcher(string) ;
match.find() ;
match.group(index)
設計模式
Creational patterns
Factory method pattern 工廠方法模式
當client不知道要創建哪個具體類的實例,或不想在client代碼中指明要具體創建的實例時,采用工廠方法。
定義一個用於創建對象的接口,讓其子類來決定實例化哪一個類,從而使一個類的實例化延遲到其子類。
有新的子類時,只需添加新的工廠方法。(OCP)
Abstract factory pattern 抽象工廠模式
提供接口來創建一組相互關聯/相互依賴的對象,但不需要指明其具體類。
抽象工廠接口的每個具體實現類實現一種具體的產品生產搭配。
抽象工廠創建的不是一個完整產品,而是"產品族"(遵循固定搭配規則的多類產品的實例),
得到的結果是:多個不同產品的對象,各產品創建過程對client可見(client或輔助類中調用工廠實現類的相關方法組裝具體部件,但每塊部件到底選擇哪種類型實現,取決於工廠類的搭配規則。),但"搭配"不能改變。
本質上,Abstract Factory 是把多類產品的factory method組合到一起,抽象出配置每塊部件的方法。
Builder pattern 構造器模式
創建復雜對象,包含多個組成部分。
創建一個完整的產品,有多個部分組成,client不了解各部分如何創建、各部分如何組合,最終得到一個完整產品對象。
Structural patterns
Bridge 橋接模式
通過delegation + inheritance 建立兩個具體類之間的關系。(DIP 依賴轉置,抽象依賴於抽象)
實例化abstraction時,new RefinedAbstraction(左側繼承樹的子類)並將接口實現類傳入構造函數(右側繼承樹的子類)。
Proxy Pattern 代理模式
某個對象比較具有高隱私性,不希望client直接訪問,設置proxy,在二者間建立防火墻。
隔離對復雜對象的訪問,降低難度/代價,定位在"訪問/使用"行為。
Composite 組合模式
遞歸組合,組合對象生成樹結構,來體現層級關系(如職位等級圖)。
在同類型的對象之間建立起樹型層次結構,一個上層對象可包含多個下層對象。
Behavioral patterns
Observer
"粉絲"(observer)對"偶像"(subject)感興趣,希望隨時得知偶像的一舉一動;
- 粉絲到偶像那裏註冊(obsever關聯subject , 並調用subject的相關方法將自身加入其"粉絲"容器),
- 偶像一但有新聞發生,就推送給已註冊(容器中的對象)的"粉絲"(回調observer的特定功能)。
在java中:
Observer 接口,實現該接口,構造"粉絲"。
Observable抽象類,派生子類,構造"偶像。"
Visitor pattern
對特定類型的object的特定操作(visit),在運行時將二者動態綁定到一起,該操作可以靈活更改,無需更改被visit的類。
本質上:將數據和作用於數據上的某種/某些特定操作分離開來。
類似strategy,不過visitor對ADT本身進行操作。
State pattern 狀態模式
具體狀態實現state接口,根據操作state不同實現之間轉換。
Memento pattern 備忘錄模式
記錄originator對象的歷史狀態,並可恢復記錄或刪除。
Robustness 健壯性 和 Correctness 正確性
健壯性 | 正確性 |
系統在不正常輸入或不正常外部環境下仍能夠表現正常的程度。 | 程序按照spec加以執行的能力,是最重要的質量指標。 |
出錯後:·1、退出(並提示信息) 2、容錯(轉為正常) | Fail fast |
讓用戶變得更容易:出錯也可以容忍,程序內部已有容錯機制。 | 讓開發者變得更容易:用戶輸入錯誤(不滿足precondition的調用),直接結束。 |
對外部接口,開放(傾向於健壯) | 對內部邏輯:保守(傾向於正確) |
可靠性reliability = robustness + correctness
Error --> defeat/fault / bug -->failure
Mistake 程序員犯的錯誤 缺陷 失效,運行時程序的外部表現
對程序運行結果(Throwable):
- Error 程序員無能為力(設備、I/O、網絡、JVM等)
- Exception 程序員自身引入:捕獲、處理
- Return (正常)
- Throws (非正常)
Thorwable
- Error 內部錯誤:java運行時系統內部錯誤或資源不足
程序員無能為力,一旦發生,想辦法讓程序優雅的結束。
分類:用戶輸入錯誤、設備錯誤、物理限制等
- Exception 異常:程序捕捉到的error(不是Error)
程序導致的問題,可以捕獲、可以處理。
Exception
異常:程序執行中的非正常事件,程序無法再按預想的流程執行。
程序若找不到異常處理程序,則退出,在控制臺打印出statck trace。
Runtime Exception 運行時異常,有程序代碼、或I/O等外部原因造成,不需要捕獲/處理。
Checked and Unchecked exceptions
從異常處理機制的角度進行分類:異常被誰check? 編譯器/程序員
Unchecked異常:Error + RuntimeException
程序代碼產生,編譯正常,可以不處理,拋出則程序失敗。(類似 dynamic type checking)
Checked 異常:
必須捕獲並制定錯誤處理機制,否則編譯無法通過。(類似 static type checking)
如何選擇? 對於一個異常:
如果客戶端可以通過其他的方法恢復異常,則采用checked exception;
如果客戶端對出現的異常無能為力,則采用unchecked exception。
異常處理
根據LSP:
父類拋出異常與子類協變,或子類不拋出異常;
若父類不拋出異常,則子類不拋出並自行處理異常。
Try-catch-finally :
無論程序是否碰到異常(無論Exception還是Error(包括assertion)),finally均會被執行。
(try 中 return 後,finally中若有return則,則依然執行,若無則在try執行return前執行)
(若 try或catch中強制退出(exit),則finally無法執行)
Try-with-resource:
try-with-resources這種聲明方式指定了一個或多個資源,而且這些資源需要在程序結束的時候進行關閉。這種方式可以確保每個指定的資源都可以在聲明結束的時候進行關閉(就是在try(){}結束的時候)。但是前提是這些資源必須實現接口java.lang.AutoCloseable(其中包括實現了java.io.Closeable接口的所有對象),原因是java.io.Closeable接口繼承了java.lang.AutoCloseable接口。
如上圖,BufferedReader 在try結束後自動關閉資源(br.close()).
Assertion 斷言機制
Assert (布爾值) : (輸出字符串) ;
Assert false 則打印輸出字符串,並throw AssertionError
斷言主要用於開發階段,為了調試程序、盡快避免錯誤。
什麽時候用斷言:
- Inernal invariants 內部不變量
- Rep invariants 表示不變量
如,驗證private Boolean checkRep();
- Control-Flow Invariante 控制流不變量
判定不能到達的位置,如switch-case中的default語句
- 方法的前置條件
對public方法:使用異常處理(因為是不受控制的外部條件)
對private方法:使用斷言(對內嚴格)
- 方法的後置條件
斷言 維護正確性,使用斷言處理"絕不應該發生"的情況;
錯誤/異常處理 維護健壯性,處理"預料到可以發生"的不正常情況。
Debug 調試
識別錯誤根源,消除錯誤
預防:防禦式編程、assertion、exception
Debug: test -> debug ->
重現 -> 診斷(假設檢驗,分治法) -> 修復 -> 反思(是否存在相似的bug等)
Git bisect (診斷方法) 在輸入的正確commit和錯誤commit之間二分查找,定位出錯的commit。
常用手段:
Print(到處print)、stack tracing(輸出調用棧),memory dump(內存導出分析),logging(記錄日誌),
斷點調試。
Testing 測試
探測是否存在錯誤。
測試的等級:單元測試、集成測試、系統測試、驗收測試,回歸測試。
白盒測試:對程序內部代碼結構的測試。
黑盒測試:對程序外部表現出來的行為的測試。
測試優先編程:先寫spec,再寫符合spec的測試用例,寫代碼、執行測試、發現問題並修改、再測試直到通過。
Code coverage 代碼覆蓋度: 測試效果/測試難度:路徑覆蓋(所有可能的分支、路徑)>分支覆蓋>語句覆蓋
黑盒測試
檢查程序是否符合規約。
用盡可能少的測試用例,盡快運行,並盡可能大的發現程序的錯誤。
- 等價類劃分
將被測函數的輸入域劃分為等價類,從等價類中導出測試用例。
針對每個輸入數據需要滿足的約束條件,劃分等價類。
每個等價類代表著對輸入約束加以滿足/違反的有效/無效數據的集合。
- 邊界值分析 BVA(Boundary Value Analysis)
在等價類劃分時,將邊界作為等價類之一加入考慮。不僅考慮邊界,還要考慮邊界兩側(稍微偏離邊界)。
哈工大 軟件構造課程 復習考點總結(第六、七章)