公號:碼農充電站pro
主頁:https://codeshellme.github.io
如果有人問你,“什么樣的代碼是好代碼”,你會怎樣回答呢?
0,什么是高質量代碼
我覺得回答這個問題,應該從兩個方面考慮。
- 從業務角度考慮。首先,在公司開發一款軟件,應該是業務在驅動。所以,從這個角度來說,代碼第一個應該滿足的是業務需求,如果連最基本的業務需求都滿足不了,別的也就無從談起。
- 從純代碼層面考慮。本篇我們重點要介紹的也就是這個問題。
那從純代碼層面來說,什么樣的代碼才是好代碼呢?
通常會有以下幾個判斷標准:
- 可維護性:在當前代碼的基礎上,做修正或改進,是否容易做到?
- 可擴展性:當有了新的需求,在不對當前代碼做大的改動的前期下,是否容易滿足?
- 可復用性:代碼是否能較容易的遷移到別的地方使用?不重復造輪子。
- 可讀性:當其他人閱讀代碼,或者過一段時間自己再閱讀,是否容易理解?
- 靈活性:是否足夠靈活,易調整?
- 簡潔性:是否簡單,不復雜?
- 可測試性:是否容易測試正確性?
好的代碼,不一定要滿足以上所有的條件。但一條也不滿足的代碼,基本上就不是好代碼了。
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 關系都有哪些。
希望本篇文章對你有所幫助,如果哪里有任何問題也歡迎指正,謝謝!
(完。)
歡迎關注作者公眾號,獲取更多技術干貨。