1. 程式人生 > >軟體設計,那些你不知道的事

軟體設計,那些你不知道的事

程式碼質量和產出是衡量一個程式設計師是否優秀最直接的標準。如何提高程式碼質量和產出?這就要從軟體重構和review入手。市面上有很多關於重構和review的書籍,但是看完之後,程式碼能力並不能立竿見影顯著提升,只能幫助我們解決表面的bug和規範點,無法幫助我們發現更深層次的設計問題。

從設計角度來考慮review,識別程式碼壞味道可以有效減少技術債務。技術債務是指有意或無意的做出錯誤的或非最優的設計決策所引發的債務。債務越積越多,最後只能重新徹底重構專案才能解決問題,這也叫做技術破產。如何解決技術債務問題,就要從根源上明確引起技術債務的重要的原因——設計壞味和重構認識不足。

首先要明確軟體設計原則

  • 抽象原則:通過精簡和概括來簡化實體:精簡指的是刪除不必要的細節,概括是找出並定義重要的通用特徵。

  • 非迴圈依賴原則:包之間的關係不可形成迴圈。

  • 不自我重複原則:在詳細設計中,設計實體和程式碼和重複可能表現為型別名重複和實現重複。

  • 封裝原則:通過隱藏抽象的實現細節和隱藏變化等方法實現關注點分離和資訊隱藏。

  • 資訊隱藏原則:找出棘手或可能變化的設計決策,並建立合適的模組或型別來對其他模組或型別隱藏這些決策。

  • 保持簡單原則:簡潔是軟體系統設計的重要目標,應避免引入不必要的複雜性。

  • 里氏替換原則:所有的子型別都必須至少提供超型別承諾的行為且對每個超型別的引用都可替換成子型別例項。

  • 層次介面原則:使用分類、概括、替換、排序等方法以層次方式組織對抽象。

  • 模組化原則:通過集中和分解等手法建立高內聚、低耦合的抽象。

  • 開閉原則:型別應對擴充套件開放,對修改關閉。具體是模組應該能夠在不修改程式碼情況下支援新需求。

  • 單一職責原則:絕不應有多個導致類需要修改的原因,如修改一個成員可能影響類的其他不相關職責,導致類難以維護。

  • 變化封裝原則:倡導一種資訊隱藏方式,建議將可能發生變化的概念封裝起來。很多設計模式都體現了這種設計原則,如策略模式、橋樑模式、觀察者模式。

我們從設計的角度來看程式碼時,要遵循六要素:

瞭解完設計原則和六要素後,我們再來看設計壞味。

本文中每種壞味我們只選其中一例做具體說明。

抽象型壞味

抽象原則倡導通過精簡和概括來簡化實體:精簡指的是刪除不必要的細節,而概括指的是找出並定義通過的重要特徵。交通標誌是用於交流的抽象示例,而數字符號和程式語言是用於解決問題的抽象示例。

缺失抽象

使用一系列資料或者編碼字串,而不建立類或者介面時會產生這種壞味

  1. 概念
  • 應用抽象原則的一種實現手法是建立概念邊界清晰,身份唯一的實體。由於沒有建立抽象來表示實體,而是使用基本資料型別或編碼字串等原始資料來表示它,這違反了抽象原則,將這種壞味稱為缺失抽象(Missing Abstraction)。不必要的抽象也違反了模組化原則。
  1. 潛在原因
  • 未做重複的設計分析
  • 未重構
  • 錯誤的將重點放在細微的效能改善上
  1. 示例
  • 在JDK1.0中方法printStackTrace()以字串的方式將棧跟蹤列印到標準錯誤流。
  • 在需要以程式設計方式訪問棧跟蹤元素的客戶程式中,必須要程式設計程式碼來獲取資料,如行號等,由於客戶程度依賴這種字串格式,JDK設計人員只能在後續版本中相容這種格式了。

重構建議:從Jdk1.4起對JAVA的API進行了改進,StackTraceElement類就是原來設計中缺失的物件。

  1. 別名
  • 基本型別偏執:使用基本型別對日期、金額進行編碼,而不建立類時,將引發這種壞味。
  • 資料泥團:在很多地方同事使用一系列資料項,而不建立類時,將引發這種壞味。
  1. 現實考慮
  • 避免過度設計:有時候,實體只是資料元素,沒有任何相關聯的行為。這種情況下使用類或者介面來表示它們可能導致過度設計。

封裝型壞味

封裝原則倡導通過隱藏抽象的實現細節和隱藏變化等手法實現關注的分離和資訊隱藏。比如開車必須知道發動機原理嗎?

不充分的封裝

對於抽象的一個或多個成員,宣告的訪問許可權超過了實際需求時,將導致這種壞味。例如,將欄位宣告為公有的類就存在「不充分封裝」壞味。

  1. 概念
  • 封裝的原則是將介面和實現分離,以便能獨立修改。這種關注點分離,讓客戶程式只依賴抽象的介面,而對它們隱藏具體實現。修改實現不影響客戶程式。對抽象的內部隱藏的不充分稱為不充分的封裝(Deficient Encapsulation)。
  1. 潛在原因
  • 為方便測試
  • 在面向物件中採用過程思維
  • 快速交付
  1. 示例
  • 來看看java.lang.System,in、out、err都被宣告成final,但可以通過java.lang.System的setIn、setOut、setErr分別賦值。任何程式碼都能很方便的使用它們,比如System.out.println();
  • PrintStream是java 1.0就有的,只支援8位的ASCII值,Java1.1出的PrintWriter支援Unicode,然而就是因為應用程式都能直接使用PrintStream來訪問PrintStream的方法,根本不能摒棄PrintStream類。

  1. 重構建議:Java 1.6引入了java.io.Console類,他提供了用於訪問基於字元的控制檯的方法。reader()、writer()來獲取Console相關的Writer和Reader物件。

  2. 別名

  • 可隱藏的公有屬性、方法
  • 未封裝的類
  • 包含未引數化方法的類
  1. 現實考慮
  • 巢狀或匿名類中過於寬鬆的訪問性
  • 效能考慮:比如前面說的java.lang.System

模組化壞味

模組化原則倡導利用集中和分解等手法建立高內聚、低耦合的抽象。

拆散的模組化

應集中放在一個抽象中的資料和方法分散在多個抽象中時,將導致這種壞味。表現為類被用作資料容器沒有任何方法、類的方法更多的被其他類的成員呼叫。

  1. 概念一種重要的模組化實現手法是「將相關的資料和方法集中在一起」。如果抽象中只包含資料成員,而操作這些資料成員的方法位於其他抽象中,它就違反了這種實現手法,存在「拆散的模組化」壞味。稱為拆散的模組化(Broken Modularization)。

  2. 潛在原因

  • 以過程思維使用面嚮物件語言
  • 不熟悉既有設計
  1. 重構建議對於包含大量資料類的過程型設計,可採用重構手法“將過程型設計轉換為物件”。

  2. 別名

  • 被動地儲存資料的類
  • 資料類
  • 資料記錄
  • 記錄類
  • 資料容器
  • 錯位的操作
  • 依戀情結
  • 錯位的控制
  1. 現實考慮
  • 自動生成的程式碼
  • 資料傳輸物件
  • 層次型壞味
  • 層次結構原則倡導採用分類、歸併、替換和排序等手法以層次方式組織抽象。比如地球上的870萬種生物。

缺失的層次結構

程式碼片段使用條件邏輯來顯式管理行為變化,而原本可以建立一個層次目錄,並使用它來封裝這些變化,會產生這種壞味

  1. 概念
  • 基於型別碼的switch語句(或串接的if-else語句)是最著名的設計壞味之一。
  • 使用型別碼來處理行為變化表明沒有進行有意義的分類,導致設計中缺少相應的層次結構。稱為缺失層次結構(Missing Hierarchy)。
  1. 潛在原因
  • 錯誤的採用過於簡單的設計
  • 過程型設計思維
  • 忽視了繼承也是一種設計手法
  1. 示例
  • 串接的if else語句顯示的檢查型別AbstractButton,JToolBar和JTextCompont並在各種條件下呼叫方法getMargin(),這種造成的情況是將來可能在程式碼中的其他地方也會出現。

  1. 重構建議
  • 如果條件檢查中的多個實現呼叫方法相同,可引入相關的介面來抽象共同的協議。
  • 如果程式碼中包含可轉換為類的條件語句,可採用重構手法“提取層次結構”來建立一個類層次結構,其中每個類都表示條件檢查中的一種情形。
  1. 別名
  • 標記類
  • 繼承缺位
  • 緊縮的型別層次結構
  • 內嵌功能
  1. 現實考慮
  • 與外部互動

——————————————————分割線——————————————————

我是小微,專注微服務技術分享,致力挖掘更多“高、精、全”的微服務知識分享給大家。

我的微信:weiweiweiblack (備註:部落格園 )

微信公號:黑少微服務,“分享技術,熱愛生活”,歡迎關注