設計模式之高質量程式碼

碼農充電站發表於2020-11-13

公號:碼農充電站pro
主頁:https://codeshellme.github.io

如果有人問你,“什麼樣的程式碼是好程式碼”,你會怎樣回答呢?

0,什麼是高質量程式碼

我覺得回答這個問題,應該從兩個方面考慮。

  1. 從業務角度考慮。首先,在公司開發一款軟體,應該是業務在驅動。所以,從這個角度來說,程式碼第一個應該滿足的是業務需求,如果連最基本的業務需求都滿足不了,別的也就無從談起。
  2. 從純程式碼層面考慮。本篇我們重點要介紹的也就是這個問題。

那從純程式碼層面來說,什麼樣的程式碼才是好程式碼呢?

通常會有以下幾個判斷標準:

  • 可維護性:在當前程式碼的基礎上,做修正或改進,是否容易做到?
  • 可擴充套件性:當有了新的需求,在不對當前程式碼做大的改動的前期下,是否容易滿足?
  • 可複用性:程式碼是否能較容易的遷移到別的地方使用?不重複造輪子。
  • 可讀性:當其他人閱讀程式碼,或者過一段時間自己再閱讀,是否容易理解?
  • 靈活性:是否足夠靈活,易調整?
  • 簡潔性:是否簡單,不復雜?
  • 可測試性:是否容易測試正確性?

好的程式碼,不一定要滿足以上所有的條件。但一條也不滿足的程式碼,基本上就不是好程式碼了。

1,如何編寫高質量程式碼

無規則不成方圓,寫程式碼也是如此。要寫出好的程式碼,需要遵守一些規則,主要有以下幾個方面:

  • 設計原則
  • 設計模式
  • 編碼規範
  • 持續重構

1.1,設計原則

每種設計模式都遵守了一個或多個設計原則。常用的設計原則有以下幾種:

  • 單一職責原則:一個類的職責要單一明確。
  • 開閉原則:程式碼應該對擴充套件開發,對修改關閉(儘量減少對原有程式碼的修改)。
  • 裡式替換原則:能夠使用父類物件的地方,也能使用子類。
  • 介面隔離原則:介面的使用者不應該被強迫依賴它不需要的介面。
  • 依賴倒置原則:高層模組不要依賴低層模組。高層模組和低層模組應該通過抽象來互相依賴。抽象不要依賴具體實現細節,具體實現細節依賴抽象。
  • KISS 原則:儘量保持程式碼簡單。
  • YAGNI 原則:不要編寫當前用不到的功能/程式碼,不要做過度設計。但並不是不需要考慮程式碼的擴充套件性。
  • DRY 原則:避免重複性程式碼。
  • LOD 原則:最小知識原則,每個模組只關心與自己關係密切的模組的有限知識。

另外在物件導向程式設計中,也有兩個比較重要的程式設計原則:

  • 基於介面而非實現程式設計:設計介面的時候要自頂向下,統攬全域性,不拘泥於細節。
  • 多用組合少用繼承:繼承的最大問題在於,當繼承層次過深層,過於複雜,就會影響到程式碼的可讀性和維護性。

在編碼的過程中,要時常想著這些原則,思考自己的程式碼是否符合其中的某項或多項原則。

1.2,設計模式

常見的設計模式有23 種,下面會詳細介紹。

1.3,編碼規範

編碼規範注重的是程式碼細節,主要目的是讓程式碼具有可讀性。整體上來說,好的程式碼,對外應該有一個統一的程式碼風格,程式碼風格不一定有好壞之分,但一定有是否統一之別。

另外,程式碼命名也很重要,大到專案命名,目錄命名,包名等。小到類名,介面名,方法名,物件名,變數名等。命名最基本的要求是用詞標準達意,讓人一看知道大概的用途是什麼。

還有,必要的地方要有必要的註釋,對於他人及自己回頭看程式碼都有幫助。

1.4,持續重構

隨著專案需求的增加變化,程式碼結構,程式碼量也都會跟著變化。程式碼重構需要我們不斷的從整體架構的角度審視整個專案程式碼的結構,是否已經變得混亂無序。只有不斷的對程式碼進行重構,才能使得程式碼持續的具有可維護性,可讀性等標準。

2,如何發現程式碼的問題

經過上文,我們已經知道了高質量程式碼的標準是什麼。那麼,當我們編寫完一部分程式碼後,應該怎樣判斷自己寫的程式碼是否是高質量呢?

文章開頭已經提到過,好的程式碼應該從業務和純程式碼兩個角度來衡量,下面我們就從這兩個角度來看,一般要對程式碼做哪些檢查?

業務角度:

  • 程式碼能否滿足業務需求(邏輯是否正確,是否有bug)?
  • 程式碼是否健壯,能否應對邊界條件(特殊情況)?
  • 軟體效能是否足夠,演算法是否最優?
  • 程式碼是否有執行緒安全問題,能夠支援併發?
  • 是否具備事物安全?

從純程式碼角度考慮:

  • 程式碼結構,目錄劃分是否清晰合理?
  • 是否滿足可維護性,可擴充套件性等標準?
  • 是否遵循設計原則?是否過渡設計?
  • 是否遵守程式碼規範,風格是否統一?
  • 有無必要使用設計模式,運用是否得當?
  • 有無單元測試,測試是否全面?

當我們完成某一階段的程式碼後,可以嘗試從以上幾點來檢查程式碼是否過關。

下面主要介紹設計模式。

3,設計模式

設計模式講的是如何編寫可擴充套件、可維護、可讀的高質量程式碼,它是針對軟體開發中經常遇到的一些設計問題,總結出來的一套通用的解決方案。

使用設計模式,可以使得我們編寫的程式碼具有一個良好的結構,從而寫出優雅的程式碼。設計模式關注的是,類與類之間以及物件與物件之間如何互動。

常見的設計模式有23 種,但並不是每一種模式都常用。這23 種設計模式可分為3 大類,分別是:

  • 建立型:用於解決物件的建立問題。
  • 結構型:用於處理類或物件之間的組合關係。
  • 行為型:用於處理類或物件之間怎樣互動及分配職責的問題。

下面介紹每類之中都包含哪些設計模式。

3.1,建立型

建立型包含5 種設計模式:

  • 單例模式:保證一個類只能有一個例項,並提供一個全域性訪問點。
  • 工廠方法:定義了一個建立物件的介面,由子類決定例項化哪一個類,使得類的例項化推遲到子類中。
    • 簡單工廠:嚴格來說不是一種模式,但經常用於封裝建立物件的過程。
  • 抽象工廠:提供了建立一系列相關物件的介面,而無需指定它們具體的類。
  • 建立者模式:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
  • 原型模式(不常用):用原型例項指定建立物件的種類,並且通過拷貝這個原型來建立新的物件。

3.2,結構型

結構型包含7 種設計模式:

  • 裝飾者模式:動態的給一個物件新增其它功能。從擴充套件性來說,這種方式比繼承更有彈性,更加靈活,可作為替代繼承的方案。
  • 介面卡模式:將一個類的介面轉換成客戶期望的另一個介面,使得原本介面不相容的類可以相互合作。
  • 代理模式:為物件提供一個代理,來控制對該物件的訪問。
  • 橋接模式:將抽象部分與它的實現部分分離,使它們可以獨立的變化。
  • 組合模式(不常用):可以將物件組合成樹形結構來表示“整體-部分”的層次結構,使得客戶可以用一致的方式處理個別物件和物件組合。
  • 外觀模式(不常用):提供了一個統一的介面,用來訪問子系統中的一群介面。它定義了一個高層介面,讓子系統更容易使用。
  • 享元模式(不常用):“享元”即共享單元,當一個系統中出現大量重複物件的時候,將物件設計成享元,以減少記憶體中物件的數量,節省記憶體。

3.3,行為型

行為型包含11 種設計模式:

  • 觀察者模式:定義了物件之間的一對多關係,以便當一個物件的狀態發生變化時,所有依賴它的物件都能得到通知,並自動更新。
  • 策略模式:定義一系列的演算法,將它們一個個封裝起來,讓它們之間可以互相替換。可使得演算法的變化獨立於使用它的客戶。
  • 迭代器模式:提供一個方法,可以順序訪問集合中的元素,而不暴露集合的內部表示。
  • 模板模式:在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中,使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。
  • 狀態模式:允許物件在內部狀態改變時,改變它的行為,物件看起來好像改變了它的類。
  • 職責鏈模式:多個物件可以處理同一個請求,這些物件連成一條鏈,並沿著這條鏈傳遞這個請求,直到有一個物件處理它。
  • 訪問者模式(不常用):表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下,定義作用於這些元素的新操作。
  • 命令模式(不常用):將“請求”封裝成物件,以便使用不同的請求,佇列或日誌來引數化其它物件。另外還支援可撤銷的操作。
  • 備忘錄模式(不常用):在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。以便以後恢復該物件。
  • 中介模式(不常用):用一箇中介物件來封裝一系列的物件互動,從而最小化物件之間的互動關係,降低程式碼複雜度。
  • 直譯器模式 (不常用):為某個語言定義它的語法表示,並定義一個直譯器,來解釋這種語法。

4,UML 建模

設計模式中經常會用到UML 圖來表示類與類之間的關係,下面介紹6 種UML 規定的類關係,分別是:

  • 泛化
  • 實現
  • 聚類
  • 組合
  • 關聯
  • 依賴

各種關係之間的強弱性為:

  • 泛化 = 實現 > 組合 > 聚合 > 關聯 > 依賴。
  • 泛化與實現表示的關係最強。
  • 依賴表示的關係最弱。

4.1,泛化關係

泛化可理解為繼承關係。如下程式碼中,類B繼承了類A。

public class A { ... }
public class B extends A { ... }

泛化關係用一個實線三角箭頭表示,關係圖如下,箭頭由B 指向A,表示B繼承了A:

在這裡插入圖片描述

4.2,實現關係

實現類和介面之間的關係,稱為實現關係。

如下程式碼中,類B 實現了介面A。

public interface A {...}
public class B implements A { ... }

實現關係用一個虛線三角箭頭表示,關係圖如下,箭頭由B 指向A,表示B 實現了A:

在這裡插入圖片描述

4.3,聚合關係

聚合是一種包含關係,A 類物件包含B 類物件,B類物件的生命週期可以不依賴A 類物件的生命週期。

如下程式碼中,A 類物件包含了B 類物件。

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}

聚合關係用一個實線空心菱形箭頭表示,關係圖如下,箭頭由B 指向A,表示A 中聚合了B:

在這裡插入圖片描述

4.4,組合關係

組合是一種包含關係。A 類物件包含B 類物件,B 類物件的生命週期依賴A 類物件的生命週期,B 類物件不可單獨存在。

如下程式碼中,A 類物件包含了B 類物件。

public class A {
  private B b;
  public A() {
    this.b = new B();
  }
}

組合關係用一個實線實心菱形箭頭表示,關係圖如下,箭頭由B 指向A,表示A 中組合了B:

在這裡插入圖片描述

4.5,關聯關係

關聯是一種非常弱的關係,包含聚合、組合兩種關係。如果B 類物件是A 類的成員變數,那麼B 類和A 類就是關聯關係。

如下兩種程式碼都是關聯關係。

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}

// 或者
public class A {
  private B b;
  public A() {
    this.b = new B();
  }
}

關聯關係用一個實線箭頭表示,關係圖如下,箭頭由A 指向B,表示A 關聯B:

在這裡插入圖片描述

4.6,依賴關係

依賴是一種比關聯關係更弱的關係,包含關聯關係。只要B 類物件和A 類物件有任何使用關係。

如下三種程式碼都是依賴關係。

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}
// 或者
public class A {
  private B b;
  public A() {
    this.b = new B();
  }
}
// 或者
public class A {
  public void func(B b) { ... }
}

依賴關係用一個虛線箭頭表示,關係圖如下,箭頭由A 指向B,表示A 依賴B:

在這裡插入圖片描述

本篇文章主要介紹了什麼是高質量程式碼,如何發現程式碼中的問題,以及如何編寫高質量程式碼。另外重點介紹了23 種設計模式都有哪些,及每種設計模式的含義。最後介紹了設計模式中經常用到的UML 關係都有哪些。

在這裡插入圖片描述

希望本篇文章對你有所幫助,如果哪裡有任何問題也歡迎指正,謝謝!

(完。)


歡迎關注作者公眾號,獲取更多技術乾貨。

在這裡插入圖片描述

相關文章