在 我的簡單設計價值觀 一文中,我分享了我在實踐中形成對簡單設計的理解。而提到價值觀,平時跟同事討論某個技術實踐的時候,一旦觸碰到價值觀,我就會很謹慎,因爲在兩個人價值觀不同的前提下,去討論一項實踐的好壞,很可能在面紅耳赤之後不歡而散。
如果你壓根都不認同簡單設計價值觀,我不建議你閱讀此文。如果你跟我剛開始類似,並不是不認同簡單設計的價值觀,只是覺得它很抽象,沒法落地,本文我會基於Kent Beck提出的簡單設計原則,結合今天的軟件開發,對這幾個原則做一個全面的解讀。
設計的問題
在使用某項實踐之前我們一定要去思考一個問題:我們爲什麼要使用它? 我從代碼設計所面臨的問題爲起點來回答爲什麼要遵循簡單設計。
在代碼設計中,我們會面臨設計不足和過度設計的問題,比如不假思索過程式編寫代碼,以及不擇手段套用設計模式,在實際中通常位於這兩極端之間。設計不足主要表現爲冗餘、耦合、註釋覆蓋率高,過度設計主要表現爲複雜、臃腫、代碼閒置率高。這些現象通常會引發幾大問題:難以修改、難以測試、難以閱讀。
設計決策
針對上述常見的代碼設計問題,我們可以通過引入提前設計來規避。值得一提的是,極限編程中也提倡提前做少量的設計。那麼怎麼把控這個度,我們在設計決策的時候就需要警惕了。
在設計決策中我們應該關注需求、可理解性、易修改性 和複雜度 這四個主要的維度,針對這四個維度,嘗試問自己四個問題:
- 測試都通過了嗎?
- 代碼理解起來困難嗎?
- 代碼存在重複邏輯嗎?
- 能夠減少代碼元素嗎?
針對這四個問題,Kent Beck給出了四條參考原則 [1]:
- 通過測試
- 揭示意圖
- 消除重複
- 最少元素
原則解讀
通過測試
通過測試 通常會被一概地理解爲通過所編寫的測試,這個點成立的兩個前提條件是:
- 測試覆蓋率達到100%
- 所有測試都是有效的
如果你的項目中沒有滿足這兩點,就需要換一個角度去理解 通過測試。我們編寫測試始終應該聚焦在業務價值上,測試測的是業務價值,所以通過測試 廣義理解爲要滿足業務需求,通過客戶的驗收,不管是自動化測試還是手工測試,只要測試是業務價值驅動的,都可以理解爲通過測試。
消除重複
重複乃萬惡之源 – SJ
重複意味着低內聚、高耦合,導致的後果是難以修改,對變化的響應力降低。響應力降低勢必會造成維護工作量的提升,[我的簡單設計價值觀]({{ site.url }}{{ ‘/value-of-simple-design/’ }}) 一文中的懶惰 將驅使我盡我所能消除這些重複,從而減少修改時的工作量,提升軟件的響應力。
揭示意圖
在代碼設計層面,我們可從一增一減兩個方向同時去努力,從而達到揭示意圖:
- 增強代碼的自身可理解性,讓代碼自解釋
- 減弱代碼的其他干擾因素,讓代碼更純淨
增強方面,比如在變量、方法以及類的命名等,我們都竭盡全力去賦予它們一個更加表露意圖的名字,讓它們能夠自解釋,從而讓讀者能夠在深入細節之前就能夠在較高層次上快速理解代碼的意圖。減弱方面,比如在註釋、方法的組織、類的交互設計等,可以去除不必要的註釋,控制方法體的大小、降低類交互複雜度來讓代碼更純淨,從而讓讀者更好地聚焦在覈心代碼上。
最少元素
Kent Beck以類和方法來代表最少元素 中的元素。我們可以把元素的覆蓋面擴大,比如,變量、常量、註釋、註解、關鍵字、包等都屬於代碼元素。
最少元素 的核心思想是:在不必要的時候,儘可能減少代碼元素來降低代碼複雜度,保持簡單。它道出了簡單設計的精髓。
畫龍點睛
簡單設計四原則給設計決策提供了有效的指導,在實際運用過程中,當面臨衝突時,我們如何取捨,Kent Beck最後給出了一個優先級順序:
最少元素 這一條造就了簡單設計原則 的獨特性,它猶如點睛之筆,而優先級順序則像一條龍將四條原則串接起來,讓簡單設計原則具有強大生命力。優先級順序在簡單設計原則中的重要程度類似於敏捷宣言中的最後一句:也就是說,儘管右項有其價值,我們更重視左項的價值。
優先級解讀
通過測試,這條優先級最高的原則告訴我們任何時候我們編寫的軟件是要爲客戶創造真實價值的,如果爲了消除重複、揭示意圖或減少代碼元素,而編寫出不符合客戶期望的軟件,這就嚴重違背了該原則。
消除重複,告訴我們不應該爲未來編碼,即爲一個尚未出現的重複或變化方向去增加額外的複雜度,比如建立一個抽象接口,卻只有一個實現。
揭示意圖,在我看來,跟消除重複 不相上下,絕大多數時候這兩條是相輔相成的,不會因爲消除重複而有損揭示意圖,也不會通過引入重複增加揭示意圖。在實際開發中我們應該儘量同時遵循這兩條原則來提高軟件的質量。
最少元素,這條優先級最低的原則告訴我們除非在增加了代碼元素之後,能夠消除重複或揭示意圖,否則我們不應該增加不必要的元素。比如,我們會爲了消除重複邏輯而抽取了一個公共方法,會爲了揭示意圖使用常量替代魔鬼數字。
示例:抽取公共方法
public class DateFormater {
public LocalDate formatUserBirthday(String birthdayStr) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(birthdayStr, formatter);
}
public LocalDate formatRegisterDate(String registerDateStr) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(registerDateStr, formatter);
}
}
public class DateFormater {
public LocalDate formatUserBirthday(String birthdayStr) {
return format(birthdayStr);
}
public LocalDate formatRegisterDate(String registerDateStr) {
return format(registerDateStr);
}
private LocalDate format(String birthdayStr) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(birthdayStr, formatter);
}
}
示例:常量代替魔鬼數字
public class Scheduler {
public void executeJobs(int jobNumbers){
if (jobNumbers < 1000000){
for (int i = 0; i < jobNumbers; i++) {
execute();
}
}
}
}
public class Scheduler {
public static final int MAC_CONCURRENT_EXECUTOR_NUMBERS = 1000000;
public void executeJobs(int jobNumbers){
if (jobNumbers < MAC_CONCURRENT_EXECUTOR_NUMBERS){
for (int i = 0; i < jobNumbers; i++) {
execute();
}
}
}
}
針對揭示意圖、去除重複 這兩條業界存在一些爭論,覺大多數情況下,這兩者並不衝突,在我的經驗中,可能在一些測試用例中會通過引入重複來避免邏輯分隔等測試壞味道,從而對讀者更加友好。Kent Beck也提出唯一讓他有印象的衝突是發生在測試用例 [2]。
價值延伸
Kent Beck提出的簡單設計原則更多關注的是代碼設計,其實當你認同了 [簡單設計價值觀]({{ site.url }}{{ ‘/value-of-simple-design/’ }}) 之後,簡單設計可以運用在架構設計、溝通協作上。
架構設計
- 我們應該最先考慮的是滿足業務架構的系統架構(通過測試,性能、穩定性等)
- 藉助DDD來合理的劃分微服務(揭示意圖,明確限界上下文)
- 提取公共服務組件來分離關注點(消除重複,API Gateway、BFF等)
- 最後,我們在滿足了前三點的前提下儘可能簡化系統架構中的組件
溝通協作
- 在與客戶正式場合的溝通中,我們始終應該明確溝通主題,確定目標(通過測試 )
- 通過加強結構思考力來提升表達的結構性和清晰度,從而達到言簡意賅(消除重複,揭示意圖 )
- 最後,我們達到了前面三點之後儘量不說多餘的廢話
簡單設計價值觀甚至會影響你的生活方式,輔以斷、舍、離的心態修煉,相信你的生活會逐漸變得簡約而不簡單。
註釋
-
有關簡單設計四原則更權威的表述,請參考Kent Beck的*《Extreme Programming Explained: Embrace Change》*
-
參考Martin Fowler博客 BeckDesignRules
Posted by Yuan Shenjian
版權聲明:自由轉載•非商用•非衍生•保持署名 | Creative Commons BY-NC-ND 4.0