深入分析軟件復(fù)雜度
軟件復(fù)雜度的成因
Eric Evans的經(jīng)典著作《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》的副標(biāo)題為“軟件核心復(fù)雜性應(yīng)對(duì)之道”,這說明了Eric對(duì)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的定位就是應(yīng)對(duì)軟件開發(fā)的復(fù)雜度。Eric甚至認(rèn)為:“領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)只有應(yīng)用在大型項(xiàng)目上才能產(chǎn)生***的收益”。他通過Smart UI反模式逆向地說明了在軟件設(shè)計(jì)與開發(fā)過程中如果出現(xiàn)了如下問題,就應(yīng)該考慮運(yùn)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):
- 沒有對(duì)行為的重用,也沒有對(duì)業(yè)務(wù)問題的抽象。每當(dāng)操作用到業(yè)務(wù)規(guī)則時(shí),都必須重復(fù)這些規(guī)則。
- 快速的原型建立和迭代很快會(huì)達(dá)到其極限,因?yàn)槌橄蟮娜狈ο拗屏酥貥?gòu)的選擇。
- 復(fù)雜的功能很快會(huì)讓你無所適從,所以程序的擴(kuò)展只能是增加簡(jiǎn)單的應(yīng)用模塊,沒有很好的辦法來實(shí)現(xiàn)更豐富的功能。
因此,選擇領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),就是要與軟件系統(tǒng)的復(fù)雜作一番殊死拼搏,以降低軟件復(fù)雜度為己任。那么,什么才是復(fù)雜呢?
什么是復(fù)雜?
即使是研究復(fù)雜系統(tǒng)的專家,如《復(fù)雜》一書的作者M(jìn)elanie Mitchell,都認(rèn)為復(fù)雜沒有一個(gè)明確得到公認(rèn)的定義。不過,Melanie Mitchell在接受Ubiquity雜志專訪時(shí),還是“勉為其難”地給出了一個(gè)通俗的復(fù)雜系統(tǒng)定義:由大量相互作用的部分組成的系統(tǒng),與整個(gè)系統(tǒng)比起來,這些組成部分相對(duì)簡(jiǎn)單,沒有中央控制,組成部分之間也沒有全局性的通訊,并且組成部分的相互作用導(dǎo)致了復(fù)雜行為。
這個(gè)定義庶幾可以表達(dá)軟件復(fù)雜度的特征。定義中的組成部分對(duì)于軟件系統(tǒng),就是我所謂的“設(shè)計(jì)單元”,基于粒度的不同可以是函數(shù)、對(duì)象、模塊、組件和服務(wù)。這些設(shè)計(jì)單元相對(duì)簡(jiǎn)單,然而彼此之間的相互作用卻導(dǎo)致了軟件系統(tǒng)的復(fù)雜行為。
Jurgen Appelo從理解力與預(yù)測(cè)能力兩個(gè)維度分析了復(fù)雜系統(tǒng)理論,這兩個(gè)維度又各自分為不同的復(fù)雜層次,其中,理解力維度分為simple與comlicated兩個(gè)層次,預(yù)測(cè)能力維度則分為ordered,complex與chaotic三個(gè)層次,如下圖所示:
參考復(fù)雜的含義,complicated與simple(簡(jiǎn)單)相對(duì),意指非常難以理解,而complex則介于ordered(有序的)與chaotic(混沌的)之間,認(rèn)為在某種程度上可以預(yù)測(cè),但會(huì)有很多出乎意料的事情發(fā)生。顯然,對(duì)于大多數(shù)軟件系統(tǒng)而言,系統(tǒng)的功能都是難以理解的;在對(duì)未來需求變化的把控上,雖然我們可以遵循一些設(shè)計(jì)原則來應(yīng)對(duì)可能的變化,但未來的不可預(yù)測(cè)性使得軟件系統(tǒng)的演進(jìn)仍然存在不可預(yù)測(cè)的風(fēng)險(xiǎn)。因此,軟件系統(tǒng)的所謂“復(fù)雜”其實(shí)覆蓋了complicated與complex兩個(gè)方面。要理解軟件復(fù)雜度的成因,就應(yīng)該結(jié)合理解力與預(yù)測(cè)能力這兩個(gè)因素來幫助我們思考。
理解力
在軟件系統(tǒng)中,是什么阻礙了開發(fā)人員對(duì)它的理解?想象團(tuán)隊(duì)招入一位新人,就像一位游客來到了一座陌生的城市,他是否會(huì)迷失在阡陌交錯(cuò)的城市交通體系中,不辨方向?倘若這座城市實(shí)則是鄉(xiāng)野郊外的一座村落,不過只有房屋數(shù)間,一條街道連通城市的兩頭,還會(huì)生出迷失之感嗎?
因而,影響理解力的***要素是規(guī)模。
1. 規(guī)模
軟件的需求決定了系統(tǒng)的規(guī)模。當(dāng)需求呈現(xiàn)線性增長(zhǎng)的趨勢(shì)時(shí),為了實(shí)現(xiàn)這些功能,軟件規(guī)模也會(huì)以近似的速度增長(zhǎng)。由于需求不可能做到完全獨(dú)立,導(dǎo)致出現(xiàn)相互影響相互依賴的關(guān)系,修改一處就會(huì)牽一發(fā)而動(dòng)全身。就好似城市的一條道路因?yàn)槭┕ば枰R時(shí)關(guān)閉,此路不通,通行的車輛只得改道繞行,這又導(dǎo)致了其他原本已經(jīng)飽和的道路因?yàn)橛咳敫嘬囕v而超出道路的負(fù)載變得更加擁堵,這種擁堵現(xiàn)象又會(huì)順勢(shì)向這些道路的其他分叉道路蔓延,形成一種輻射效應(yīng)的擁堵現(xiàn)象。
軟件開發(fā)的擁堵現(xiàn)象或許更嚴(yán)重:
- 函數(shù)存在副作用,調(diào)用時(shí)可能對(duì)函數(shù)的結(jié)果作了隱含的假設(shè);
- 類的職責(zé)繁多,不敢輕易修改,因?yàn)椴恢@種變化會(huì)影響到哪些模塊;
- 熱點(diǎn)代碼被頻繁變更,職責(zé)被包裹了一層又一層,沒有清晰的邊界;
- 在系統(tǒng)某個(gè)角落,隱藏著伺機(jī)而動(dòng)的Bug,當(dāng)誘發(fā)條件具備時(shí),就會(huì)讓整條調(diào)用鏈癱瘓;
- 不同的業(yè)務(wù)場(chǎng)景包含了不同的例外場(chǎng)景,每種例外場(chǎng)景的處理方式都各不相同;
- 同步處理與異步處理代碼糾纏在一起,不可預(yù)知程序執(zhí)行的順序。
當(dāng)需求增多時(shí),軟件系統(tǒng)的規(guī)模也會(huì)增大,且這種增長(zhǎng)趨勢(shì)并非線性增長(zhǎng),會(huì)更加陡峭。倘若需求還產(chǎn)生了事先未曾預(yù)料到的變化,我們又沒有足夠的風(fēng)險(xiǎn)應(yīng)對(duì)措施,在時(shí)間緊迫的情況下,難免會(huì)對(duì)設(shè)計(jì)做出妥協(xié),頭疼醫(yī)頭,腳疼醫(yī)腳,在系統(tǒng)的各個(gè)地方打上補(bǔ)丁,從而欠下技術(shù)債(Technical Debt)。當(dāng)技術(shù)債務(wù)越欠越多,累計(jì)到某個(gè)臨界點(diǎn)時(shí),就會(huì)量變引起質(zhì)變,整個(gè)軟件系統(tǒng)的復(fù)雜度達(dá)到***,步入衰亡的老年期,成為“可怕”的遺留系統(tǒng)。正如飼養(yǎng)場(chǎng)的“奶牛規(guī)則”:奶牛逐漸衰老,最終無奶可擠;然而與此同時(shí),飼養(yǎng)成本卻在上升。
2. 結(jié)構(gòu)
你去過迷宮嗎?相似而回旋繁復(fù)的結(jié)構(gòu)使得本來封閉狹小的空間被魔法般地?cái)U(kuò)展為一個(gè)***的空間,變得無窮大,仿佛這空間被安置了一個(gè)循環(huán),倘若沒有找到正確的退出條件,循環(huán)就會(huì)無休無止,永遠(yuǎn)無法退出。許多規(guī)模較小卻格外復(fù)雜的軟件系統(tǒng),就好似這樣的一座迷宮。
此時(shí),結(jié)構(gòu)成了決定系統(tǒng)復(fù)雜度的關(guān)鍵因素。
結(jié)構(gòu)之所以變得復(fù)雜,多數(shù)情況下還是因?yàn)橄到y(tǒng)的質(zhì)量屬性決定的。例如,我們需要滿足高性能、高并發(fā)的需求,就需要考慮在系統(tǒng)中引入緩存、并行處理、CDN、異步消息以及支持分區(qū)的可伸縮結(jié)構(gòu)。倘若我們需要支持對(duì)海量數(shù)據(jù)的高效分析,就得考慮這些海量數(shù)據(jù)該如何分布存儲(chǔ),并如何有效地利用各個(gè)節(jié)點(diǎn)的內(nèi)存與CPU資源執(zhí)行運(yùn)算。
從系統(tǒng)結(jié)構(gòu)的視角看,單體架構(gòu)一定比微服務(wù)架構(gòu)更簡(jiǎn)單,更便于掌控,正如單細(xì)胞生物比人體的生理結(jié)構(gòu)要簡(jiǎn)單數(shù)百倍;那么,為何還有這么多軟件組織開始清算自己的軟件資產(chǎn),花費(fèi)大量人力物力對(duì)現(xiàn)有的單體架構(gòu)進(jìn)行重構(gòu),走向微服務(wù)化?究其主因,不還是系統(tǒng)的質(zhì)量屬性在作祟嗎?
縱觀軟件設(shè)計(jì)的歷史,不是分久必合,合久必分,而是不斷拆分繼續(xù)拆分持續(xù)拆分的微型化過程。分解的軟件元素不可能單兵作戰(zhàn)。怎么協(xié)同,怎么通信,就成為了系統(tǒng)分解后面臨的主要問題。如果沒有控制好,這些問題固有的復(fù)雜度甚至?xí)谀承﹫?chǎng)景下超過因?yàn)榉纸饨o我們帶來的收益。
無論是優(yōu)雅的設(shè)計(jì),還是拙劣的設(shè)計(jì),都可能因?yàn)槟撤N設(shè)計(jì)權(quán)衡而導(dǎo)致系統(tǒng)結(jié)構(gòu)變得復(fù)雜。唯一的區(qū)別在于前者是主動(dòng)地控制結(jié)構(gòu)的復(fù)雜度,而后者帶來的復(fù)雜度是偶發(fā)的,是錯(cuò)誤的滋生,是一種技術(shù)債,它可能會(huì)隨著系統(tǒng)規(guī)模的增大而導(dǎo)致一種無序設(shè)計(jì)。
在Pete Goodliffe講述的《兩個(gè)系統(tǒng)的故事:現(xiàn)代軟件神話》中詳細(xì)地羅列了無序設(shè)計(jì)系統(tǒng)的幾種警告信號(hào):
- 代碼沒有顯而易見的進(jìn)入系統(tǒng)中的路徑;
- 不存在一致性、不存在風(fēng)格、也沒有統(tǒng)一的概念能夠?qū)⒉煌牟糠纸M織在一起
- 系統(tǒng)中的控制流讓人覺得不舒服,無法預(yù)測(cè)
- 系統(tǒng)中有太多的“壞味道”,整個(gè)代碼庫散發(fā)著腐爛的氣味,是在大熱天里散發(fā)著刺激氣體的一個(gè)垃圾堆
- 數(shù)據(jù)很少放在使用它的地方。經(jīng)常引入額外的巴羅克式緩存層,目的是試圖讓數(shù)據(jù)停留在更方便的地方。
我們看一個(gè)無序設(shè)計(jì)的軟件系統(tǒng),就好像隔著一層半透明的玻璃觀察事物一般,系統(tǒng)中的軟件元素都變得模糊不清,充斥著各種技術(shù)債。細(xì)節(jié)層面,代碼污濁不堪,違背了“高內(nèi)聚松耦合”的設(shè)計(jì)原則,導(dǎo)致許多代碼要么放錯(cuò)了位置,要么出現(xiàn)重復(fù)的代碼塊;架構(gòu)層面,缺乏清晰的邊界,各種通信與調(diào)用依賴糾纏在一起,同一問題域的解決方案各式各樣,讓人眼花繚亂,仿佛進(jìn)入了沒有規(guī)則的無序社會(huì)。
預(yù)測(cè)能力
當(dāng)我們掌握了事物發(fā)展的客觀規(guī)律時(shí),我們就具有了一定的對(duì)未來的預(yù)測(cè)能力。例如我們洞察了萬有引力的本質(zhì),就可以對(duì)我們能夠觀察到的宇宙天體建立模型,相對(duì)準(zhǔn)確地推測(cè)出各個(gè)天體在未來一段時(shí)間的運(yùn)行軌跡。然而,宇宙空間變化莫測(cè),或許因?yàn)橐粋€(gè)星球的死亡產(chǎn)生黑洞的吸噬能力,就可能導(dǎo)致那一片星域產(chǎn)生劇烈的動(dòng)蕩,這種動(dòng)蕩會(huì)傳遞到更遠(yuǎn)的星空,從而干擾了我們的預(yù)測(cè)。坦白說,我們現(xiàn)在連自己居住的地球天氣都不能做一個(gè)準(zhǔn)確的預(yù)測(cè),何敢妄談對(duì)星空的預(yù)測(cè)?之所以如此,正是因?yàn)槲粗淖兓漠a(chǎn)生。
1. 變化
未來總會(huì)出現(xiàn)不可預(yù)測(cè)的變化。這種不可預(yù)測(cè)性帶來的復(fù)雜度,使得我們產(chǎn)生畏懼,因?yàn)槲覀儾恢篮螘r(shí)會(huì)發(fā)生變化,變化的方向又會(huì)走向哪里,這就導(dǎo)致心理滋生一種仿若失重一般的感覺。變化讓事物失去控制,受到事物牽扯的我們會(huì)感到惶恐不安。
在設(shè)計(jì)軟件系統(tǒng)時(shí),變化讓我們患得患失,不知道如何把握系統(tǒng)設(shè)計(jì)的度。若拒絕對(duì)變化做出理智的預(yù)測(cè),系統(tǒng)的設(shè)計(jì)會(huì)變得僵化,一旦變化發(fā)生,修改的成本會(huì)非常的大;若過于看重變化產(chǎn)生的影響,渴望涵蓋一切變化的可能,一旦預(yù)期的變化不曾發(fā)生,我們之前為變化付出的成本就再也補(bǔ)償不回來了。這就是所謂的“過度設(shè)計(jì)”。
從需求的角度講,變化可能來自業(yè)務(wù)需求,也可能來自質(zhì)量屬性。以對(duì)系統(tǒng)架構(gòu)的影響而言,尤以后者為甚,因?yàn)樗赡軤可娴秸麄€(gè)基礎(chǔ)架構(gòu)的變更。George Fairbanks在《恰如其分的軟件架構(gòu)》一書中介紹了郵件托管服務(wù)公司RackSpace的日志架構(gòu)變遷,業(yè)務(wù)功能沒有任何變化,卻因?yàn)猷]件數(shù)量的持續(xù)增長(zhǎng),為滿足性能需求,架構(gòu)經(jīng)歷了三個(gè)完全不同解決方案的變遷:從最初的本地日志文件,到中央數(shù)據(jù)庫,再到基于HDFS的分布式存儲(chǔ),整個(gè)系統(tǒng)幾乎發(fā)生了顛覆性的變化。這并非RackSpace的設(shè)計(jì)師欠缺設(shè)計(jì)能力,而是在公司草創(chuàng)之初,他們沒有能夠高瞻遠(yuǎn)矚地預(yù)見到客戶數(shù)量的增長(zhǎng),導(dǎo)致日志數(shù)據(jù)增多,以至于超出了已有系統(tǒng)支持的能力范圍。俗話說:“事后諸葛亮”,當(dāng)我們?cè)趯?duì)一個(gè)軟件系統(tǒng)的架構(gòu)設(shè)計(jì)進(jìn)行復(fù)盤時(shí),總會(huì)發(fā)現(xiàn)許多設(shè)計(jì)決策是如此的愚昧。殊不知這并非愚昧,而是在設(shè)計(jì)當(dāng)初,我們手中掌握的籌碼不足以讓自己贏下這場(chǎng)面對(duì)未來的戰(zhàn)爭(zhēng)罷了。
2. 這就是變化之殤!
如果將軟件系統(tǒng)中我們自己開發(fā)的部分都劃歸為需求的范疇,那么還有一種變化,則是因?yàn)槲覀円蕾嚨牡谌綆臁⒖蚣芑蚱脚_(tái)、甚至語言版本的變化帶來的連鎖反應(yīng)。例如,作為Java開發(fā)人員,一定更垂涎于Lambda表達(dá)式的簡(jiǎn)潔與抽象,又或者Jigsaw提供的模塊定義能力,然而現(xiàn)實(shí)是我們看到多數(shù)的企業(yè)軟件系統(tǒng)依舊在Java 6或者Java 7中裹足不前。
這還算是幸運(yùn)的例子,因?yàn)槲覀儽M可以滿足這種故步自封,因?yàn)榍闆r并沒有到必須變化的境地。但當(dāng)我們依賴的第三方有讓我們不得不改變的理由時(shí),難道我們還能拒絕變化嗎?
許多軟件在版本變遷過程中都盡量考慮到API變化對(duì)調(diào)用者帶來的影響,因而盡可能保持版本向后兼容。我親自參與過系統(tǒng)從Spring 2.0到4.0的升級(jí),Spark從1.3.1到1.5再到1.6的升級(jí),感謝這些框架或平臺(tái)設(shè)計(jì)人員對(duì)兼容性的體貼照顧,使得我們的升級(jí)成本能夠被降到***;但是在升級(jí)之后,倘若沒有對(duì)系統(tǒng)做全方位的回歸測(cè)試,我們的內(nèi)心始終是惴惴不安的。
對(duì)第三方的依賴看似簡(jiǎn)單,殊不知我們所依賴的庫、平臺(tái)或者框架又可能依賴了若干對(duì)于它們而言又份屬第三方的更多庫、平臺(tái)和框架。每回初次構(gòu)建軟件系統(tǒng)時(shí),我都為漫長(zhǎng)等待的依賴下載過程而感覺煩躁不安。多種版本共存時(shí)可能帶來的所謂依賴地獄,只要親身經(jīng)歷過,就沒有不感到不寒而栗的。倘若你運(yùn)氣欠佳,可能還會(huì)有各種古怪問題接踵而來,讓你應(yīng)接不暇,疲于奔命。
如果變化是不可預(yù)測(cè)的,那么軟件系統(tǒng)也會(huì)變得不可預(yù)測(cè)。一方面我們要盡可能地控制變化,至少要將變化產(chǎn)生的影響限制在較小的空間范圍內(nèi);另一方面又要保證系統(tǒng)不會(huì)因?yàn)闈M足可擴(kuò)展性而變得更加復(fù)雜,***背上過度設(shè)計(jì)的壞名聲。軟件設(shè)計(jì)者們就像走在高空鋼纜的技巧挑戰(zhàn)者,驚險(xiǎn)地調(diào)整重心以維持行動(dòng)的平衡。故而,變化之難,在于如何平衡。
【本文為51CTO專欄作者“張逸”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】