Everything should be made as simple as possible, but not simpler.
-- Albert Einstein
我們一直在談簡(jiǎn)單設(shè)計(jì)扳肛,但究竟什么是簡(jiǎn)單設(shè)計(jì)此改?更具體的說(shuō),對(duì)于同一個(gè)問(wèn)題狼忱,設(shè)計(jì)決策A和B,究竟哪一個(gè)更符合簡(jiǎn)單設(shè)計(jì)的要求他巨?
對(duì)于這類(lèi)問(wèn)題澡匪,如果沒(méi)有一個(gè)明確的標(biāo)尺,那么"簡(jiǎn)單設(shè)計(jì)"就不免會(huì)成為一句無(wú)法評(píng)判的空洞口號(hào)拆融,讓程序設(shè)計(jì)者無(wú)從判斷和遵守蠢琳。
對(duì)此啊终,Kent Beck給出了清晰的答案:
- 通過(guò)所有測(cè)試(Passes its tests)
- 盡可能消除重復(fù) (Minimizes duplication)
- 盡可能清晰表達(dá) (Maximizes clarity)
- 更少代碼元素 (Has fewer elements)
- 以上四個(gè)原則的重要程度依次降低。
這組定義被稱(chēng)做簡(jiǎn)單設(shè)計(jì)原則傲须。
初看上去蓝牲,這組原則平淡無(wú)奇,似乎是一組耳熟能詳?shù)脑瓌t的羅列泰讽。但只要細(xì)細(xì)品味例衍,就會(huì)發(fā)現(xiàn)其精妙絕倫之處。
通過(guò)所有測(cè)試
直觀的看已卸,這句話貌似在講測(cè)試:一個(gè)項(xiàng)目只有具備完善的自動(dòng)化測(cè)試佛玄,才算在做簡(jiǎn)單設(shè)計(jì)。
但事實(shí)上并非如此累澡。這里提到的測(cè)試梦抢,真正的意思是客戶驗(yàn)收。如果你的項(xiàng)目通過(guò)了客戶的所有驗(yàn)收條件(Acceptance Criteria)愧哟,那就說(shuō)明你們已經(jīng)完成與客戶約定的全部需求奥吩。至于驗(yàn)收方式是靠人工還是靠自動(dòng)化測(cè)試則無(wú)關(guān)緊要。
所以蕊梧,這句話強(qiáng)調(diào)的是對(duì)外部需求——包括功能性需求和非功能性需求——正確的完成霞赫。
盡可能消除重復(fù)
重復(fù),意味著低內(nèi)聚肥矢,高耦合端衰。而消除重復(fù)的過(guò)程,也就意味著是讓軟件走向高內(nèi)聚橄抹,低耦合靴迫,達(dá)到良好正交性的過(guò)程。識(shí)別和消除重復(fù)楼誓,對(duì)于增強(qiáng)軟件應(yīng)對(duì)變化能力的重要程度玉锌,怎么強(qiáng)調(diào)都不為過(guò)。關(guān)于這一點(diǎn)疟羹,我會(huì)另文說(shuō)明主守,這里就不再贅述。
不過(guò)榄融,并不是所有的重復(fù)都可以消除:比如参淫,C++一個(gè)源文件里對(duì)外部公開(kāi)的類(lèi),其每個(gè)public方法原型愧杯,除了在源文件里定義時(shí)涎才,需要聲明一次,還需要在頭文件里再次聲明。這樣的重復(fù)耍铜,是語(yǔ)言機(jī)制的要求邑闺,無(wú)法消除。
因而棕兼,這條原則被描述為最小化重復(fù)陡舅,而不是消除重復(fù)。
盡可能清晰表達(dá)
清晰性伴挚,指的是一個(gè)設(shè)計(jì)容易理解的程度靶衍。注意:這不僅僅是對(duì)整潔代碼(Clean Code)及聲明式設(shè)計(jì)(Declarative Design)的強(qiáng)調(diào)。關(guān)于這一點(diǎn)茎芋,有著非常有趣的部分颅眶,我們?cè)陔S后的部分談到。
更少代碼元素
這一條是點(diǎn)睛之筆败徊,正是因?yàn)樗拇嬖谥愫簦@組原則才被稱(chēng)做簡(jiǎn)單設(shè)計(jì)原則,從而區(qū)別于其它設(shè)計(jì)原則皱蹦。
在這里,常量眷蜈,變量沪哺,函數(shù),類(lèi)酌儒,包 …… 都屬于代碼元素辜妓。代碼元素的數(shù)量,通常反映了設(shè)計(jì)的復(fù)雜度忌怎。因而籍滴,這句話強(qiáng)調(diào)的是:盡可能降低復(fù)雜度,保持簡(jiǎn)單榴啸。
重要程度排序
這一句最容易讓人忽視孽惰,卻恰恰最為重要。如果第四條是點(diǎn)睛之筆鸥印,那么第五條就是將之前四條貫穿起來(lái)的那條龍勋功。正是這一句,讓你知道當(dāng)以上四條發(fā)生沖突時(shí)库说,應(yīng)該如何取舍狂鞋。
我們已經(jīng)知道,第四條潜的,是簡(jiǎn)單設(shè)計(jì)的精髓骚揍,但是,它在前四條原則卻最不重要啰挪。
對(duì)于第一條信不,它強(qiáng)調(diào):簡(jiǎn)單固然好纤掸,但你不能為了簡(jiǎn)單,而不去實(shí)現(xiàn)和客戶約定好的需求浑塞。(當(dāng)然借跪,如果需求不合理,你應(yīng)該在前期通過(guò)和客戶協(xié)商拒絕酌壕,或修改掏愁。但那是關(guān)于需求管理這個(gè)話題有關(guān)的故事,感興趣者可以去查閱相關(guān)文章和書(shū)籍)卵牍。
而對(duì)于第二條果港,比如,我們現(xiàn)在有兩個(gè)類(lèi):它們之間有一部分重復(fù)代碼糊昙。為了消除掉這個(gè)重復(fù)辛掠,我們將重復(fù)代碼提取到一個(gè)新的類(lèi)里。于是兩個(gè)類(lèi)變?yōu)槿齻€(gè)類(lèi)释牺,增加了一個(gè)新的代碼元素萝衩。這當(dāng)然讓設(shè)計(jì)變得更復(fù)雜了。但這種復(fù)雜度產(chǎn)生了更重要的價(jià)值(讓軟件更具備正交性没咙,從而讓軟件更易于修改)猩谊,所以,不能為了保持簡(jiǎn)單祭刚,而不去消除這個(gè)重復(fù)牌捷。
對(duì)于第三條也是如此,比如下面這句代碼中有一個(gè)magic number:
a = 1000;
為了讓這段代碼更容易理解涡驮,我們將代碼修改為:
const int MAX_NUM_OF_CONNECTIONS = 1000;
a = MAX_NUM_OF_CONNECTIONS;
從而增加了一個(gè)新的代碼元素暗甥。因而也稍微增加了設(shè)計(jì)的復(fù)雜度。但由于這個(gè)新的代碼元素也產(chǎn)生了相對(duì)于簡(jiǎn)單更重要的價(jià)值捉捅,在簡(jiǎn)單和表達(dá)力之間撤防,我們應(yīng)該選擇后者。
反向價(jià)值
而簡(jiǎn)單設(shè)計(jì)的價(jià)值锯梁,也可以從相反的角度來(lái)看:如果新增的一條代碼元素即碗,不能產(chǎn)生上述三個(gè)價(jià)值,它就不應(yīng)該存在陌凳。
對(duì)于第一條剥懒,你不應(yīng)該去實(shí)現(xiàn)一個(gè)客戶還不需要的需求,因?yàn)槟菚?huì)增加系統(tǒng)的復(fù)雜度合敦。
對(duì)于第二條初橘,你不應(yīng)該為還沒(méi)有出現(xiàn)的重復(fù),或者為尚未出現(xiàn)的變化方向,去增加任何額外的復(fù)雜度保檐。比如建立一個(gè)抽象接口耕蝉,卻只有一個(gè)對(duì)應(yīng)的實(shí)現(xiàn)。
而第四條對(duì)于前兩條的約束夜只,就是我們耳熟能祥的YAGNI(You Aren't Gonna Need It)垒在。它強(qiáng)調(diào),我們要著眼當(dāng)下扔亥,不去為自己猜想出的未來(lái)可能性去增加系統(tǒng)的復(fù)雜度场躯。
總之,當(dāng)你看到一個(gè)代碼元素旅挤,沒(méi)有產(chǎn)生之前三條中的任何一條價(jià)值踢关,那么它就應(yīng)該被刪除掉。
而這正是簡(jiǎn)單設(shè)計(jì)能夠簡(jiǎn)單的原因粘茄。
需求最大
拋開(kāi)第四條签舞,單看前三條,它們之前的重要程度也是依次降低的柒瓣。比如儒搭,你不應(yīng)該因?yàn)榕庐a(chǎn)生很難消除、或干脆消除不掉的重復(fù)而放棄一個(gè)對(duì)客戶有價(jià)值的需求嘹朗。換句話說(shuō)师妙,哪怕一個(gè)需求會(huì)導(dǎo)致重復(fù)代碼,你也要去實(shí)現(xiàn)它屹培。
同樣的,如果一個(gè)需求怔檩,會(huì)導(dǎo)致你的設(shè)計(jì)更加晦澀褪秀,你卻不應(yīng)該因?yàn)樗鼡p害了清晰性、可理解性而拒絕它薛训。
對(duì)于這兩點(diǎn)媒吗,絕大多數(shù)人都沒(méi)有太多爭(zhēng)議。(也有一派認(rèn)為乙埃,不應(yīng)該讓需求破壞設(shè)計(jì)的優(yōu)雅闸英,當(dāng)兩者發(fā)生沖突時(shí),選擇優(yōu)雅)介袜。
最具爭(zhēng)議話題的解決
我們經(jīng)常能夠聽(tīng)到一種爭(zhēng)議:一些人認(rèn)為甫何,放在一起的長(zhǎng)篇累牘的大塊流程代碼,反而更容易理解遇伞。因?yàn)橄貜?fù)而導(dǎo)致的單一職責(zé)辙喂,會(huì)將一大段代碼分布到不同的類(lèi)或者模塊,為了理解它,就不得不在類(lèi)或者模塊間跳來(lái)跳去巍耗,反而不容理解了秋麸。
另外一部分人并不認(rèn)同這種看法。他們認(rèn)為炬太,由于模塊或類(lèi)的單一職責(zé)性灸蟆,其每塊邏輯都更加簡(jiǎn)單清晰,然后在另外一個(gè)層面再去看它們之間的交互亲族,就可以很快理解整個(gè)邏輯炒考。 這比大坨的面條式代碼更容易理解。而對(duì)于SLAP(Single Level of Abstraction Priciple)的遵守孽水,會(huì)更進(jìn)一步的增加可理解性票腰。
由于可理解性屬于更加個(gè)人、更加主觀的事情女气,因而究竟哪種方式更容易理解杏慰,可能永遠(yuǎn)也不會(huì)有統(tǒng)一的答案。
而簡(jiǎn)單設(shè)計(jì)原則通過(guò)第二條和第三條之間的排序炼鞠,給出了清晰的決策依據(jù):由于消除重復(fù)缘滥,把一大塊代碼分隔到了不同的地方,即便團(tuán)隊(duì)認(rèn)為這確實(shí)損害了可理解性谒主,但由于重復(fù)所導(dǎo)致的惡果更加嚴(yán)重朝扼,因而優(yōu)先選擇消除重復(fù)。
另外霎肯,關(guān)于簡(jiǎn)單設(shè)計(jì)原則擎颖,社區(qū)內(nèi)有多個(gè)版本,用詞不同观游,但意思大致相同泽篮。關(guān)鍵的差別是第二條和第三條的順序:即消除重復(fù)和提升表達(dá)力哪個(gè)更重要输玷。有一些人認(rèn)為表達(dá)力比消除重復(fù)重要,因而把提升表達(dá)力放在第二條。但更多人認(rèn)同的是本文之前的版本莽使。
對(duì)于這一點(diǎn)紊馏,我個(gè)人的觀點(diǎn)是薄腻,越是主觀的東西编振,就越不具備可驗(yàn)證性或科學(xué)性,因而對(duì)于工程技術(shù)而言工碾,重要程度就越低弱睦。
另外,回歸到具體項(xiàng)目里倚喂,為了避免爭(zhēng)議每篷,在不違背前兩點(diǎn)原則的情況下瓣戚,團(tuán)隊(duì)可以根據(jù)大多數(shù)人的審美和認(rèn)知,決定怎樣的設(shè)計(jì)才更具備可理解性焦读。事實(shí)上子库,在重復(fù)已經(jīng)被消除殆盡的情況下,對(duì)于可理解性問(wèn)題矗晃,無(wú)論怎樣選擇仑嗅,影響都是局部的。
因而张症,對(duì)于這個(gè)問(wèn)題仓技,團(tuán)隊(duì)覺(jué)得舒服最重要。
結(jié)論
簡(jiǎn)單設(shè)計(jì)原則俗他,通過(guò)對(duì)需求脖捻、易修改性、可理解性兆衅、復(fù)雜度地沮,這四個(gè)在設(shè)計(jì)決策中最關(guān)鍵的因素給出了排序,讓簡(jiǎn)單設(shè)計(jì)不再一個(gè)語(yǔ)義模糊的口號(hào)羡亩,而是對(duì)設(shè)計(jì)決策給出了清晰的guideline摩疑。
根據(jù)筆者經(jīng)驗(yàn),深入理解畏铆、并在項(xiàng)目中反復(fù)品味和應(yīng)用它雷袋,可以避免掉很多不必要的爭(zhēng)議,也會(huì)對(duì)設(shè)計(jì)質(zhì)量產(chǎn)生非常顯著的幫助辞居。