系統(tǒng)困境與軟件復(fù)雜度,為什么我們的系統(tǒng)會如此復(fù)雜
一、前言
有一天,一個醫(yī)生和一個土木工程師在一起爭論“誰是世界上最古老的職業(yè)”。醫(yī)生說:“上帝用亞當(dāng)?shù)睦吖窃斐隽讼耐蓿@是歷史上第一次外科手術(shù),所以最古老的職業(yè)應(yīng)該是醫(yī)生”,土木工程師說:“在創(chuàng)世紀(jì)之前,上帝從混沌中創(chuàng)造了天堂與人間,這是更早之前的一次土木作業(yè),所以最古老的職業(yè)應(yīng)該是土木工程”。這時軟件工程師拖著鍵盤走出來說,“那你認(rèn)為,是誰創(chuàng)造了那片混沌?”
建筑師不會輕易給100層的高樓增加一個地下室,但我們卻經(jīng)常在干這樣的事,并且總有人會對你說,“這個需求很簡單”。到土里埋個地雷,這確實(shí)不復(fù)雜,但我們往往面臨的真實(shí)場景其實(shí)是:“在這片雷區(qū)里加一個雷”,而雷區(qū)里哪里有雷,任何人都不知道 。
二、什么是復(fù)雜性
我們一直在說系統(tǒng)很復(fù)雜,那到底什么是復(fù)雜性?關(guān)于復(fù)雜的定義有很多種,其中比較有代表的是Thomas J. McCabe 在1976提出的理性派的復(fù)雜性度量,與John Ousterhout 教授提出的感性派的復(fù)雜性認(rèn)知。
1.理性度量
復(fù)雜性并不是什么新概念,早在上世紀(jì)70年代,軟件就已經(jīng)極其復(fù)雜,開發(fā)與維護(hù)的成本都非常高。1976年McCabe&Associates公司開始對軟件進(jìn)行結(jié)構(gòu)測試,并提出了McCabe Cyclomatic Complexity Metric,我們也稱之為McCabe圈復(fù)雜度。它通過多個維度來度量軟件的復(fù)雜度,從而判斷軟件當(dāng)前的開發(fā)/維護(hù)成本。
圈復(fù)雜度 | 代碼狀況 | 測性成本 | 維護(hù)成本 |
圈復(fù)雜度1 - 10 | 清晰/結(jié)構(gòu)化 | 可測性高 | 維護(hù)成本低 |
圈復(fù)雜度10 - 20 | 復(fù)雜 | 可測性中 | 維護(hù)成本中 |
圈復(fù)雜度20 - 30 | 非常復(fù)雜 | 可測性低 | 維護(hù)成本高 |
圈復(fù)雜度30 | 不可讀 | 不可測 | 維護(hù)成本非常高 |
2.感性認(rèn)知
復(fù)雜度高的代碼一定不是好代碼,但復(fù)雜度低的也不一定就是好代碼。John Ousterhout教授認(rèn)為軟件的復(fù)雜性相對理性的分析,可能更偏感性的認(rèn)知。
Complexity is anything that makes software hard to understand or to modify
-- John Ousterhout 《A Philosophy of Software Design》
譯:所謂復(fù)雜性,就是任何使得軟件難于理解和修改的因素。
50年后的今天,John Ousterhout教授在 A Philosophy of Software Design 書中提到了一個非常主觀的見解,復(fù)雜性就是任何使得軟件難于理解和修改的因素。
模糊性與依賴性是引起復(fù)雜性的2個主要因素,模糊性產(chǎn)生了最直接的復(fù)雜度,讓我們很難讀懂代碼真正想表達(dá)的含義,無法讀懂這些代碼,也就意味著我們更難去改變它。而依賴性又導(dǎo)致了復(fù)雜性不斷傳遞,不斷外溢的復(fù)雜性最終導(dǎo)致系統(tǒng)的無限腐化,一旦代碼變成意大利面條,幾乎不可能修復(fù),成本將成指數(shù)倍增長。
三、復(fù)雜性的表現(xiàn)形式
復(fù)雜的系統(tǒng)往往也有一些非常明顯的特征,John教授將它抽象為變更放大(Change amplification)、認(rèn)知負(fù)荷(Cognitive load)與未知的未知(Unknown unknowns)這3類。當(dāng)我們的系統(tǒng)出現(xiàn)這3個特征,說明我們的系統(tǒng)已經(jīng)開始逐漸變得復(fù)雜了。
癥狀1-變更放大
Change amplification: a seemingly simple change requires code modifications in many different places.
-- John Ousterhout 《A Philosophy of Software Design》
譯:看似簡單的變更需要在許多不同地方進(jìn)行代碼修改。
變更放大(Change amplification)指得是看似簡單的變更需要在許多不同地方進(jìn)行代碼修改。比較典型的代表是Ctrl-CV式代碼開發(fā),領(lǐng)域模型缺少內(nèi)聚與收攏,當(dāng)需要對某段業(yè)務(wù)進(jìn)行調(diào)整時,需要改動多個模塊以適應(yīng)業(yè)務(wù)的發(fā)展。
/**
* 銷售撿入客戶
*/
public void pick(String salesId, String customerId) {
// 查詢客戶總數(shù)
long customerCnt = customerDao.findCustomerCount(salesId);
// 查詢銷售庫容
long capacity = capacityDao.findSalesCapacity(salesId);
// 判斷是否超額
if(customerCnt >= capacity) {
throws new BizException("capacity over limit");
}
// 代碼省略 do customer pick
}
在CRM領(lǐng)域,銷售撿入客戶時需要進(jìn)行庫容判斷,這段代碼也確實(shí)可以滿足需求。但隨著業(yè)務(wù)的發(fā)展,簽約的客戶要調(diào)整為不占庫容。而客戶除了銷售撿入,還包括主管分發(fā)、leads分發(fā)、手工錄入、數(shù)據(jù)采買等多個場景,如果沒對庫容域做模型的收攏,一個簡單的邏輯調(diào)整,就需要我們在多個場景做適配才能滿足訴求。
癥狀2-認(rèn)知負(fù)荷
Cognitive load: how much a developer needs to know in order to complete a task.
-- John Ousterhout 《A Philosophy of Software Design》
譯:開發(fā)人員需要多少知識才能完成一項任務(wù)。
認(rèn)知負(fù)荷(Cognitive load)是指開發(fā)人員需要多少知識才能完成一項任務(wù)。使用功能性框架時,我們希望它操作簡單,部署復(fù)雜系統(tǒng)時,我們希望它架構(gòu)清晰,其實(shí)都是降低一項任務(wù)所需的成本。盲目的追求高端技術(shù),設(shè)計復(fù)雜系統(tǒng),增加學(xué)習(xí)與理解成本都屬于本末倒置的一種。
TMF是整個星環(huán)的支柱,也是業(yè)務(wù)中臺面向可復(fù)用可擴(kuò)展架構(gòu)的核心。但TMF太過復(fù)雜,認(rèn)知與學(xué)習(xí)成本非常高,我們?nèi)粘V兴媾R的一些擴(kuò)展訴求99%(或者應(yīng)該說100%)都不適合TMF,可能通過一些設(shè)計模式或者就是一些if else,可能更適合解決我們的問題。
除此之外,還包括一些簡單搜索場景卻用到了blink等流式引擎,簡單后臺系統(tǒng)通過DDD進(jìn)行構(gòu)建,幾個商品發(fā)布的狀態(tài)機(jī)轉(zhuǎn)換用上了規(guī)則引擎等等,都屬于認(rèn)知負(fù)荷復(fù)雜度的一種。
癥狀3-未知的未知
Unknown unknowns: it is not obvious which pieces of code must be modified to complete a task
-- John Ousterhout 《A Philosophy of Software Design》
譯:必須修改哪些代碼才能完成任務(wù)。
未知的未知(Unknown unknowns)是指必須修改哪些代碼才能完成任務(wù),或者說開發(fā)人員必須獲得哪些信息才能成功地執(zhí)行任務(wù)。這一項也是John Ousterhout教授認(rèn)為復(fù)雜性中最糟糕的一個表現(xiàn)形式。
當(dāng)你維護(hù)一個有20年歷史的項目時,這種問題的出來相對而言就沒那么意外。由于代碼的混亂與文檔的缺失,導(dǎo)致你無法掌控一個500萬行代碼的應(yīng)用,并且代碼本身也沒有明顯表現(xiàn)出它們應(yīng)該要闡述的內(nèi)容。這時“未知的未知”出現(xiàn)了,你不知道改動的這行代碼是否能讓程序正常運(yùn)轉(zhuǎn),也不知道這行代碼的改動是否又會引發(fā)新的問題。這時候我們發(fā)現(xiàn),那些“上帝類”真的就只有上帝能拯救了。
四、為什么會產(chǎn)生復(fù)雜性
那軟件為什么越來越復(fù)雜,是不是減少一些犯錯就能避免一場浩劫呢?回顧那些復(fù)雜的系統(tǒng),我們可以找到很多因素導(dǎo)致系統(tǒng)腐化。
- 想簡單圖省事,沒有及時治理不合理的內(nèi)容
- 缺少匠心追求,對骯臟代碼視而不見
- 技術(shù)能力不夠,無法應(yīng)對復(fù)雜系統(tǒng)
- 交接過渡缺失,三無產(chǎn)品幾乎無法維護(hù)
除了上述內(nèi)容外,還可以想到很多理由。但我們發(fā)現(xiàn)他們好像有一個共同的指向點(diǎn) - 軟件工程師,似乎所有復(fù)雜的源頭就是軟件工程師的不合格導(dǎo)致,所以其實(shí)一些罪惡的根因是我們自己?
1.統(tǒng)一的中國與分裂的歐洲
歐洲大陸面積大體與中國相當(dāng),但為什么歐洲是分裂的,而中國是統(tǒng)一的。有人說他們文化不一樣,也有人說他們語言不通是主要原因,也有人說他們?nèi)币粋€秦始皇。其實(shí)我們回顧歐洲的歷史,歐洲還真不缺一個大一統(tǒng)的帝國。羅馬帝國曾經(jīng)讓地中海成為自己的內(nèi)海,拿破侖鼎盛時期掌管著1300萬平方公里的領(lǐng)地。歐洲也曾出現(xiàn)過偉大的帝國,但都未走向統(tǒng)一。
我們再觀察地圖,其實(shí)除了中國、俄羅斯以外,全世界99%的國家都是小國。分裂才是常態(tài),統(tǒng)一才不正常。馬老師也曾說過,成功都有偶然性只有失敗才存在必然。只有極少國家才實(shí)現(xiàn)了大一統(tǒng),所以我們不應(yīng)該問為什么歐洲是分裂的,而應(yīng)該問為什么中國是統(tǒng)一的。類比到我們的軟件也同樣如此,復(fù)雜才是常態(tài),不復(fù)雜才不正常。
2.軟件固有的復(fù)雜性
The Complexity of software is an essential property, not an accidental one.
-- Grady Booch 《Object-Oriented Analysis and Design with Applications》
譯:軟件的復(fù)雜性是一個基本特征,而不是偶然如此。
Grady Booch在 Object-Oriented Analysis and Design with Applications 中提出這樣一個觀念,他認(rèn)為軟件的復(fù)雜性是固有的,包括問題域的復(fù)雜性、管理開發(fā)過程的困難性、通過軟件可能實(shí)現(xiàn)的靈活性與刻畫離散系統(tǒng)行為的問題,這4個方面來分析了軟件的發(fā)展一定伴隨著復(fù)雜,這是軟件工程這本科學(xué)所必然伴隨的一個特性。
Everything, without exception, requires additional energy and order to maintain itself. I knew this in the abstract as the famous second law of thermodynamics, which states that everything is falling apart slowly.
-- Kevin Kelly 《The Inevitable》
譯:世間萬物都需要額外的能量和秩序來維持自身,無一例外。這就是著名的熱力學(xué)第二定律,即所有的事務(wù)都在緩慢地分崩離析。
Kevin Kelly在 The Inevitable 也有提過類似的觀點(diǎn),他認(rèn)為世間萬物都需要額外的能量和秩序來維持自身,所有的事物都在緩慢地分崩離析。沒有外部力量的注入事物就會逐漸崩潰,這是世間萬物的規(guī)律,而非我們哪里做得不對。
五、軟件架構(gòu)治理復(fù)雜度
為軟件系統(tǒng)注入的外力就是我們的軟件架構(gòu),以及我們未來的每一行代碼。軟件架構(gòu)有很多種,從最早的單體架構(gòu),到后面的分布式架構(gòu)、SOA、微服務(wù)、FaaS、ServiceMesh等等。所有的軟件架構(gòu)萬變不離其宗,都在致力解決軟件的復(fù)雜性。
1.架構(gòu)的本質(zhì)
編程范式指的是程序的編寫模式,軟件架構(gòu)發(fā)展到今天只出現(xiàn)過3種編程范式( paradigm ),分別是結(jié)構(gòu)化編程,面向?qū)ο缶幊膛c函數(shù)式編程。
- 結(jié)構(gòu)化編程取消 goto 移除跳轉(zhuǎn)語句,對程序控制權(quán)的直接轉(zhuǎn)移進(jìn)行了限制和規(guī)范
- 面向?qū)ο缶幊滔拗?指針 的使用,對程序控制權(quán)的間接轉(zhuǎn)移進(jìn)行了限制和規(guī)范
- 函數(shù)式編程以 λ演算法 為核心思想,對程序中的賦值進(jìn)行了限制和規(guī)范
面向?qū)ο蟮奈宕笤O(shè)計原則 S.O.L.I.D。依賴倒置限制了模塊的依賴順序、單一職責(zé)限制模塊的職責(zé)范圍、接口隔離限制接口的提供形式。
軟件的本質(zhì)是約束。商品的代碼不能寫在訂單域,數(shù)據(jù)層的方法不能寫在業(yè)務(wù)層。70年的軟件發(fā)展,并沒有告訴我們應(yīng)該怎么做,而是教會了我們不該做什么。
2.遞增的復(fù)雜性
軟件的復(fù)雜性不會憑空消失,并且會逐級遞增。針對遞增的復(fù)雜性有3個觀點(diǎn):
- 模糊性創(chuàng)造了復(fù)雜,依賴性傳播了復(fù)雜
- 復(fù)雜性往往不是由單個災(zāi)難引起的
- 我們可以容易地說服自己,當(dāng)前變更帶來的一點(diǎn)點(diǎn)復(fù)雜性沒什么大不了
曾經(jīng)小李跟我抱怨,說這段代碼實(shí)在是太惡心了,花了很長時間才看懂,并且代碼非常僵硬,而正好這個需求需要改動到這里,代碼真的就像一坨亂麻。我問他最后是怎么處理的,他說,我給它又加了一坨。
3.編程思維論
戰(zhàn)術(shù)編程
其實(shí)小李的這種做法并非是一個個體行為,或許我們在遇到復(fù)雜代碼時都曾這樣茍且過,John教授這種編程方法稱之為“戰(zhàn)術(shù)編程”。戰(zhàn)術(shù)編程最主要的特點(diǎn)是快,同時具備如下幾個特點(diǎn)。
- 當(dāng)前一定是最快的
- 不會花費(fèi)太多時間來尋找最佳設(shè)計
- 每個編程任務(wù)都會引入一些復(fù)雜度
- 重構(gòu)會減慢當(dāng)前任務(wù)速度,所以保持最快速度
@HSFProvider(serviceInterface = AgnDistributeRuleConfigQueryService.class)
public class AgnDistributeRuleConfigQueryServiceImpl implements AgnDistributeRuleConfigQueryService {
@Override
public ResultModel<AgnDistributeRuleConfigDto> queryAgnDistributeRuleConfigById(String id) {
logger.info("queryAgnDistributeRuleConfigById id=" + id);
ResultModel<AgnDistributeRuleConfigDto> result = new ResultModel<AgnDistributeRuleConfigDto>();
if(StringUtils.isBlank(id)){
result.setSuccess(false);
result.setErrorMsg("id cannot be blank");
return result
}
try {
AgnDistributeRuleConfigDto agnDistributeRuleConfigDto = new AgnDistributeRuleConfigDto();
AgnDistributeRuleConfig agnDistributeRuleConfig = agnDistributeRuleConfigMapper.selectById(id);
if(agnDistributeRuleConfig == null){
logger.error("agnDistributeRuleConfig is null");
result.setSuccess(false);
result.setErrorMsg("agnDistributeRuleConfig is null");
return result
}
this.filterDynamicRule(agnDistributeRuleConfig);
BeanUtils.copyProperties(agnDistributeRuleConfig, agnDistributeRuleConfigDto);
result.setSuccess(true);
result.setTotal(1);
result.setValues(agnDistributeRuleConfigDto);
} catch (Exception e) {
logger.error("queryAgnDistributeRuleConfigById error,", e);
result.setSuccess(false);
result.setErrorMsg(e.getMessage());
}
return result;
}
}
我們看上面這段代碼,是一段查詢分發(fā)規(guī)則的業(yè)務(wù)邏輯。雖然功能能夠work,但不規(guī)范的地方其實(shí)非常多
- Facade層定義全部邏輯 - 未做結(jié)構(gòu)分層
- 業(yè)務(wù)與技術(shù)未做分離 - 耦合接口信息與業(yè)務(wù)數(shù)據(jù)
- Try catch 滿天飛 - 缺少統(tǒng)一異常處理機(jī)制
- 沒有規(guī)范化的日志格式 - 日志格式混亂
但不可否認(rèn),他一定是當(dāng)前最快的。這就是戰(zhàn)術(shù)設(shè)計的特點(diǎn)之一,永遠(yuǎn)按當(dāng)前最快速交付的方案進(jìn)行推進(jìn),甚至很多組織鼓勵這種工作方式,為了使功能更快運(yùn)作,只注重短期收益而忽略長期價值。
戰(zhàn)術(shù)龍卷風(fēng)
Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado.
-- John Ousterhout 《A Philosophy of Software Design》
譯:幾乎每個軟件開發(fā)組織都有至少一個將戰(zhàn)術(shù)編程發(fā)揮到極致的開發(fā)人員:戰(zhàn)術(shù)龍卷風(fēng)。
將戰(zhàn)術(shù)編程發(fā)揮到極致的人,叫戰(zhàn)術(shù)龍卷風(fēng)。戰(zhàn)術(shù)龍卷風(fēng)以腐化系統(tǒng)為代價換取當(dāng)前最高效的解決方案(或許他自己并未覺得)。戰(zhàn)術(shù)龍卷風(fēng)也有如下幾個特點(diǎn):
- 是一位多產(chǎn)的程序員,沒人比龍卷風(fēng)更快完成任務(wù)
- 總能留下龍卷風(fēng)后毀滅的痕跡??留給后人去清理
- 是真的很卷
一些組織甚至?xí)?zhàn)術(shù)龍卷風(fēng)視為英雄,為什么能干得又多又快?因?yàn)樗麑⒊杀痉诺搅宋磥?。軟件工程最大的成本在于維護(hù),我們每一次代碼的改動,都應(yīng)該是對歷史代碼的一次整理,而非單一的功能堆積。龍卷風(fēng)能贏得現(xiàn)在,但終將失去未來,而這個失敗的未來或許需要全團(tuán)隊與他一起買單。
戰(zhàn)略編程
John教授提出與戰(zhàn)術(shù)編程相對的是戰(zhàn)略編程,戰(zhàn)略編程更注重長期價值,不滿足于功能work,致力于制作出色的設(shè)計,以滿足對未來擴(kuò)展的訴求(注意,不要過度)。戰(zhàn)略設(shè)計有如下4個特點(diǎn)
- 工作代碼遠(yuǎn)遠(yuǎn)不夠
- 引入不必要的復(fù)雜度不可接受
- 不斷對系統(tǒng)設(shè)計進(jìn)行小幅改進(jìn)
- 投資心態(tài)(每位工程師都需要對良好的設(shè)計進(jìn)行連續(xù)的少量投資 10~20%)
John Ousterhout教授在 A Philosophy of Software Design 書中提到了戰(zhàn)略設(shè)計與戰(zhàn)術(shù)設(shè)計的總成本投入。隨著時間的流逝,戰(zhàn)略設(shè)計可以有效控制軟件成本,但戰(zhàn)術(shù)設(shè)計會隨著時間的推移線性遞增。這與Martin Fowler在 Patterns of Enterprise Application Architecture 這本書中所提的關(guān)于數(shù)據(jù)驅(qū)動與領(lǐng)域驅(qū)動關(guān)于復(fù)雜度的治理是同樣的含義,要致力于長期的價值投資。
4.系統(tǒng)的困境與演進(jìn)
沒有系統(tǒng)是天然復(fù)雜的,為了快速完成任務(wù)不斷引入新的復(fù)雜度至系統(tǒng)逐漸腐化,無限增長與無限傳遞的復(fù)雜度讓軟件需求越來越難“快速完成”。當(dāng)有一天我們意識到系統(tǒng)的復(fù)雜性時再試圖通過戰(zhàn)略設(shè)計進(jìn)行軟件的迭代,你會發(fā)現(xiàn)舉步維艱,一處很小的修改需要投入大量的基建修復(fù),最終我們不得不向成本低頭,不斷再通過戰(zhàn)術(shù)設(shè)計無限的茍且。
A condition that is often incorrectly labeled software maintenance. To be more precise, it is maintenance when we correct errors; it is evolution when we respond to changing requirements; it is preservation when we continue to use extraordinary means to keep an ancient and decaying piece of software in operation. Unfortunately, reality suggests that an inordinate percent- age of software development resources are spent on software preservation.
-- Grady Booch 《Object-Oriented Analysis and Design with Applications》
譯:我們總是說我們需要“維護(hù)”這些老系統(tǒng)。而準(zhǔn)確的說,在軟件發(fā)展過程里,只有我們修正錯誤時,才是維護(hù);在我們應(yīng)對改變的需求時,這是演進(jìn);當(dāng)我們使用一些極端的手段來保持古老而陳腐的軟件繼續(xù)工作時,這是保護(hù)(茍且)。事實(shí)證明我們更多的時間是在應(yīng)對最后一種狀況。
如同Grady Booch在 Object-Oriented Analysis and Design with Applications 中所提到的觀點(diǎn),當(dāng)我們使用一些極端的手段來保持古老而陳腐的軟件繼續(xù)工作時,這確實(shí)是一種茍且。我們小心翼翼、集成測試、灰度發(fā)布、及時回滾等等,我們沒有在“維護(hù)”他們,而是以一種丑陋的方式讓這些丑陋的代碼繼續(xù)能夠成功茍且下去。當(dāng)代碼變成意大利面條時,將幾乎是不可能修復(fù),成本將成指數(shù)倍增長,并且似乎我們的系統(tǒng)已經(jīng)存在這樣的代碼,并且可能還在持續(xù)增加中。
六、架構(gòu)偽論
在架構(gòu)設(shè)計中,總有一些軟件工程師所堅信的詩和遠(yuǎn)方,但到不了的烏托邦不一定就是遙不可及的美好圣地,實(shí)則也可能是對系統(tǒng)無益甚至有害的架構(gòu)設(shè)計。這里列舉其中2條可能存在的架構(gòu)偽論。
1.好的代碼自解釋
Comments do not make up for bad code
-- Martin Fowler 《Clean Code》
譯:注釋不是對劣質(zhì)代碼的補(bǔ)救
Martin Fowler在 Clean Code 書中提到注釋不是對劣質(zhì)代碼的補(bǔ)救,以前我也一直堅信如果代碼足夠好是不需要注釋的。但實(shí)則這是一個偽命題,John教授這么評價它 ‘good code is self-documenting’ is a delicious myth。
/**
* 批量查詢客戶信息
*/
public List<CustomerVO> queryCustomerList(){
// 查詢參數(shù)準(zhǔn)備
UserInfo userInfo = context.getLoginContext().getUserInfo();
if(userInfo == null || StringUtils.isBlank(userInfo.getUserId())){
return Collections.emptyList();
}
LoginDTO loginDTO = userInfoConvertor.convert(userInfo);
// 查詢客戶信息
List<CustomerSearchVO> customerSearchVOList = customerRemoteQueryService.queryCustomerList(loginDTO);
Iterator<CustomerSearchVO> it = customerSearchVOList.iterator();
// 排除不合規(guī)客戶
while(it.hasNext()){
CustomerSearchVO customerSearchVO = it.next();
if(isInBlackList(customerSearchVO) || isLowQuality(customerSearchVO)){
it.remove();
}
}
// 補(bǔ)充客戶其他屬性信息
batchFillCustomerPositionInfo(customerSearchVOList);
batchFillCustomerAddressInfo(customerSearchVOList);
return customerSearchVOList;
}
這段代碼我們可以很輕松的在5秒內(nèi)看明白這個函數(shù)是做什么的,并且知道它內(nèi)部的一些業(yè)務(wù)規(guī)則。無限的私有方法封裝會讓代碼鏈路過深,無限類的拆解會造成更多網(wǎng)狀依賴,至少有3點(diǎn)內(nèi)容,讓我們絕不能拋棄注釋。
無法精準(zhǔn)命名
命名的含義是抽象實(shí)體隱藏細(xì)節(jié),我們不能在一個名字上賦予它全部的信息,而必要的注釋可以完美的進(jìn)行輔佐。
設(shè)計思想的闡述
代碼只能實(shí)現(xiàn)設(shè)計不能闡述設(shè)計,這也是為什么一些復(fù)雜的架構(gòu)設(shè)計我們需要文檔的支撐而非代碼的‘自解釋’,在文檔與代碼之間的空隙,由注釋來填補(bǔ)。
母語的力量
這點(diǎn)尤其適合我們中國人,有時并不是因?yàn)樽⑨屔俅a多,所以我們下意識會首先看代碼。而是我們幾十年感受的文化,讓我們對中文與ABC具有完全不一樣的感觀。
2.永遠(yuǎn)追求最優(yōu)雅
雷布斯曾自夸自己寫的代碼像詩一樣優(yōu)雅,追求優(yōu)雅的代碼應(yīng)該是每個軟件工程師的心中的圣地。但有時存在一些不優(yōu)雅,存在一些‘看似不合理’并不代表就不對,反而有時在追求更優(yōu)雅的路上我們持續(xù)跑偏。
The goal of software architecture is to minimize the human resources required
to build and maintain the required system.
-- Robert C.Martin 《Clean Architecture》
譯:軟件架構(gòu)的終極目標(biāo)是,用最小的人力成本來滿足構(gòu)建和維護(hù)該系統(tǒng)的需求
Robert C.Martin在 Clean Architecture 一書中提到了架構(gòu)終極目標(biāo),用最小的人力成本來滿足構(gòu)建和維護(hù)該系統(tǒng)的需求。架構(gòu)始終是我們解決復(fù)雜度的一個工具,如果當(dāng)前系統(tǒng)并不復(fù)雜,我們不需要為了所謂的優(yōu)雅去過分改造與優(yōu)化它,持續(xù)將成本置在一個較低水位,就是軟件最好的解決辦法。
業(yè)務(wù)簡單的系統(tǒng)不應(yīng)用DDD架構(gòu),弱交互場景也無需進(jìn)行前后端分離,哪怕是鄧總設(shè)計師在規(guī)劃新中國的發(fā)展上,也是制定了一套‘中國特色社會主義’制度。不要盲從一些教條的觀念,選擇適合自己的,控制在可控制范圍內(nèi),既不過度也不缺失。畢竟沒有絕對的優(yōu)雅,甚至沒有絕對的正確。
七、寫在最后
很多人認(rèn)為做業(yè)務(wù)開發(fā)顯得沒那么有挑戰(zhàn)性,但其實(shí)正好相反。最難解決的bug是無法重現(xiàn)的bug,最難處理的問題域是不確定性的問題域。業(yè)務(wù)往往是最復(fù)雜的,面向不確定性設(shè)計才是最復(fù)雜的設(shè)計。軟件工程學(xué)科最難的事情是抽象,因?yàn)樗鼪]有標(biāo)準(zhǔn)、沒有方法、甚至沒有對錯。如何在軟件固有的復(fù)雜性上找到一條既不過度也不缺失的路,是軟件工程師的終身課題,或許永遠(yuǎn)也無法達(dá)到,或許我們已經(jīng)在路上了。
參閱書籍
《A Philosophy of Software Design》《Object Oriented Analysis and Design with Applications》《Clean Code》《Clean Architecture》《Patterns of Enterprise Application Architecture》