1、模塊復(fù)雜度:修改擴(kuò)散、認(rèn)知負(fù)擔(dān)、不可知;軟件復(fù)雜度 = 模塊復(fù)雜度 * 使用頻率
2、分層:抽象、復(fù)雜性下沉;分模塊:深淺模塊【防腐層】、接口設(shè)計(jì)通用化和功能實(shí)現(xiàn)專(zhuān)用化、信息隱藏;注釋?zhuān)焊邔幼⑨層糜诿枋鼋涌?、重視What和Why、編碼前寫(xiě)注釋
一、前言
斯坦福教授、Tcl語(yǔ)言發(fā)明者John Ousterhout 的著作《A Philosophy of Software Design》。用一句話概括《A Philosophy of Software Design》,軟件設(shè)計(jì)的核心在于降低復(fù)雜性。
二、如何定義復(fù)雜性
關(guān)于復(fù)雜性,尚無(wú)統(tǒng)一的定義,從不同的角度可以給出不同的答案??梢杂脭?shù)量來(lái)度量,比如芯片集成的電子器件越多越復(fù)雜(不一定對(duì));按層次性[2]度量,復(fù)雜度在于層次的遞歸性和不可分解性。在信息論中,使用熵來(lái)度量信息的不確定性。
John Ousterhout選擇從認(rèn)知的負(fù)擔(dān)和開(kāi)發(fā)工作量的角度來(lái)定義軟件的復(fù)雜性,并且給出了一個(gè)復(fù)雜度量公式:

子模塊的復(fù)雜度cp乘以該模塊對(duì)應(yīng)的開(kāi)發(fā)時(shí)間權(quán)重值tp,累加后得到系統(tǒng)的整體復(fù)雜度C。系統(tǒng)整體的復(fù)雜度并不簡(jiǎn)單等于所有子模塊復(fù)雜度的累加,還要考慮該模塊的開(kāi)發(fā)維護(hù)所花費(fèi)的時(shí)間在整體中的占比(對(duì)應(yīng)權(quán)重值tp)。也就是說(shuō),即使某個(gè)模塊非常復(fù)雜,如果很少使用或修改,也不會(huì)對(duì)系統(tǒng)的整體復(fù)雜度造成大的影響。
子模塊的復(fù)雜度cp是一個(gè)經(jīng)驗(yàn)值,它關(guān)注幾個(gè)現(xiàn)象:
修改擴(kuò)散,修改時(shí)有連鎖反應(yīng)。
認(rèn)知負(fù)擔(dān),開(kāi)發(fā)人員需要多長(zhǎng)時(shí)間來(lái)理解功能模塊。
不可知(Unknown Unknowns),開(kāi)發(fā)人員在接到任務(wù)時(shí),不知道從哪里入手。
造成復(fù)雜的原因一般是代碼依賴和晦澀(Obscurity)。其中,依賴是指某部分代碼不能被獨(dú)立地修改和理解,必定會(huì)牽涉到其他代碼。代碼晦澀,是指從代碼中難以找到重要信息。
三、解決復(fù)雜性的一般原則
首先,互聯(lián)網(wǎng)行業(yè)的軟件系統(tǒng),很難一開(kāi)始就做出完美的設(shè)計(jì),通過(guò)一個(gè)個(gè)功能模塊衍生迭代,系統(tǒng)才會(huì)逐步成型;對(duì)于現(xiàn)存的系統(tǒng),也很難通過(guò)一個(gè)大動(dòng)作,一勞永逸地解決所有問(wèn)題。系統(tǒng)設(shè)計(jì)是需要持續(xù)投入的工作,通過(guò)細(xì)節(jié)的積累,最終得到一個(gè)完善的系統(tǒng)。因此,好的設(shè)計(jì)是日拱一卒的結(jié)果,在日常工作中要重視設(shè)計(jì)和細(xì)節(jié)的改進(jìn)。
其次,專(zhuān)業(yè)化分工和代碼復(fù)用促成了軟件生產(chǎn)率的提升。比如硬件工程師、軟件工程師(底層、應(yīng)用、不同編程語(yǔ)言)可以在無(wú)需了解對(duì)方技術(shù)背景的情況下進(jìn)行合作開(kāi)發(fā);同一領(lǐng)域服務(wù)可以支撐不同的上層應(yīng)用邏輯等等。其背后的思想,無(wú)非是通過(guò)將系統(tǒng)分成若干個(gè)水平層、明確每一層的角色和分工,來(lái)降低單個(gè)層次的復(fù)雜性。同時(shí),每個(gè)層次只要給相鄰層提供一致的接口,可以用不同的方法實(shí)現(xiàn),這就為軟件重用提供了支持。分層是解決復(fù)雜性問(wèn)題的重要原則。
第三,與分層類(lèi)似,分模塊是從垂直方向來(lái)分解系統(tǒng)。分模塊最常見(jiàn)的應(yīng)用場(chǎng)景,是如今廣泛流行的微服務(wù)。分模塊降低了單模塊的復(fù)雜性,但是也會(huì)引入新的復(fù)雜性,例如模塊與模塊的交互,后面的章節(jié)會(huì)討論這個(gè)問(wèn)題。這里,我們將第三個(gè)原則確定為分模塊。
最后,代碼能夠描述程序的工作流程和結(jié)果,卻很難描述開(kāi)發(fā)人員的思路,而注釋和文檔可以。此外,通過(guò)注釋和文檔,開(kāi)發(fā)人員在不閱讀實(shí)現(xiàn)代碼的情況下,就可以理解程序的功能,注釋間接促成了代碼抽象。好的注釋能夠幫助解決軟件復(fù)雜性問(wèn)題,尤其是認(rèn)知負(fù)擔(dān)和不可知問(wèn)題(Unknown Unknowns)。
四、解決復(fù)雜性之日拱一卒
4.1 拒絕戰(zhàn)術(shù)編程
戰(zhàn)術(shù)編程致力于完成任務(wù),新增加特性或者修改Bug時(shí),能解決問(wèn)題就好。這種工作方式,會(huì)逐漸增加系統(tǒng)的復(fù)雜性。如果系統(tǒng)復(fù)雜到難以維護(hù)時(shí),再去重構(gòu)會(huì)花費(fèi)大量的時(shí)間,很可能會(huì)影響新功能的迭代。
戰(zhàn)略編程,是指重視設(shè)計(jì)并愿意投入時(shí)間,短時(shí)間內(nèi)可能會(huì)降低工作效率,但是長(zhǎng)期看,會(huì)增加系統(tǒng)的可維護(hù)性和迭代效率。

設(shè)計(jì)系統(tǒng)時(shí),很難在開(kāi)始階段就面面俱到。好的設(shè)計(jì)應(yīng)該體現(xiàn)在一個(gè)個(gè)小的模塊上,修改bug時(shí),也應(yīng)該抱著設(shè)計(jì)新系統(tǒng)的心態(tài),完工后讓人感覺(jué)不到“修補(bǔ)”的痕跡。經(jīng)過(guò)累積,最終形成一個(gè)完善的系統(tǒng)。從長(zhǎng)期看,對(duì)于中大型的系統(tǒng),將日常開(kāi)發(fā)時(shí)間的10-15%用于設(shè)計(jì)是值得的。有一種觀點(diǎn)認(rèn)為,創(chuàng)業(yè)公司需要追求業(yè)務(wù)迭代速度和節(jié)省成本,可以容忍糟糕的設(shè)計(jì),這是用錯(cuò)誤的方法去追求正確的目標(biāo)。降低開(kāi)發(fā)成本最有效的方式是雇傭優(yōu)秀的工程師,而不是在設(shè)計(jì)上做妥協(xié)。
4.2 設(shè)計(jì)兩次
為一個(gè)類(lèi)、模塊或者系統(tǒng)的設(shè)計(jì)提供兩套或更多方案,有利于我們找到最佳設(shè)計(jì)。以我們?nèi)粘5募夹g(shù)方案設(shè)計(jì)為例,技術(shù)方案本質(zhì)上需要回答兩個(gè)問(wèn)題,其一,為什么該方案可行? 其二,在已有資源限制下,為什么該方案是最優(yōu)的?為了回答第一個(gè)問(wèn)題,我們需要在技術(shù)方案里補(bǔ)充架構(gòu)圖、接口設(shè)計(jì)和時(shí)間人力估算。而要回答第二個(gè)問(wèn)題,需要我們?cè)陉P(guān)鍵點(diǎn)或爭(zhēng)議處提供二到三種方案,并給出建議方案,這樣才有說(shuō)服力。通常情況下,我們會(huì)花費(fèi)很多的時(shí)間準(zhǔn)備第一個(gè)問(wèn)題,而忽略第二個(gè)問(wèn)題。其實(shí),回答好第二個(gè)問(wèn)題很重要,大型軟件的設(shè)計(jì)已經(jīng)復(fù)雜到?jīng)]人能夠一次就想到最佳方案,一個(gè)僅僅“可行”的方案,可能會(huì)給系統(tǒng)增加額外的復(fù)雜性。對(duì)聰明人來(lái)說(shuō),接受這點(diǎn)更困難,因?yàn)樗麄兞?xí)慣于“一次搞定問(wèn)題”。但是聰明人遲早也會(huì)碰到自己的瓶頸,在低水平問(wèn)題上徘徊,不如花費(fèi)更多時(shí)間思考,去解決真正有挑戰(zhàn)性的問(wèn)題。
五、解決復(fù)雜性之分層
5.1 層次和抽象
軟件系統(tǒng)由不同的層次組成,層次之間通過(guò)接口來(lái)交互。在嚴(yán)格分層的系統(tǒng)里,內(nèi)部的層只對(duì)相鄰的層次可見(jiàn),這樣就可以將一個(gè)復(fù)雜問(wèn)題分解成增量步驟序列。由于每一層最多影響兩層,也給維護(hù)帶來(lái)了很大的便利。分層系統(tǒng)最有名的實(shí)例是TCP/IP網(wǎng)絡(luò)模型。

在分層系統(tǒng)里,每一層應(yīng)該具有不同的抽象。TCP/IP模型中,應(yīng)用層的抽象是用戶接口和交互;傳輸層的抽象是端口和應(yīng)用之間的數(shù)據(jù)傳輸;網(wǎng)絡(luò)層的抽象是基于IP的尋址和數(shù)據(jù)傳輸;鏈路層的抽象是適配和虛擬硬件設(shè)備。如果不同的層具有相同的抽象,可能存在層次邊界不清晰的問(wèn)題。
5.2 復(fù)雜性下沉
不應(yīng)該讓用戶直面系統(tǒng)的復(fù)雜性,即便有額外的工作量,開(kāi)發(fā)人員也應(yīng)當(dāng)盡量讓用戶使用更簡(jiǎn)單。如果一定要在某個(gè)層次處理復(fù)雜性,這個(gè)層次越低越好。舉個(gè)例子,Thrift接口調(diào)用時(shí),數(shù)據(jù)傳輸失敗需要引入自動(dòng)重試機(jī)制,重試的策略顯然在Thrift內(nèi)部封裝更合適,開(kāi)放給用戶(下游開(kāi)發(fā)人員)會(huì)增加額外的使用負(fù)擔(dān)。與之類(lèi)似的是系統(tǒng)里隨處可見(jiàn)的配置參數(shù)(通常寫(xiě)在XML文件里),在編程中應(yīng)當(dāng)盡量避免這種情況,用戶(下游開(kāi)發(fā)人員)一般很難決定哪個(gè)參數(shù)是最優(yōu)的,如果一定要開(kāi)放參數(shù)配置,最好給定一個(gè)默認(rèn)值。
復(fù)雜性下沉,并不是說(shuō)把所有功能下移到一個(gè)層次,過(guò)猶不及。如果復(fù)雜性跟下層的功能相關(guān),或者下移后,能大大下降其他層次或整體的復(fù)雜性,則下移。
5.3 異常處理
異常和錯(cuò)誤處理是造成軟件復(fù)雜的罪魁禍?zhǔn)字?。有些開(kāi)發(fā)人員錯(cuò)誤的認(rèn)為處理和上報(bào)的錯(cuò)誤越多越好,這會(huì)導(dǎo)致過(guò)度防御性的編程。如果開(kāi)發(fā)人員捕獲了異常并不知道如何處理,直接往上層扔,這就違背了封裝原則。
降低復(fù)雜度的一個(gè)原則就是盡可能減少需要處理異常的可能性。而最佳實(shí)踐就是確保錯(cuò)誤終結(jié),例如刪除一個(gè)并不存在的文件,與其上報(bào)文件不存在的異常,不如什么都不做。確保文件不存在就好了,上層邏輯不但不會(huì)被影響,還會(huì)因?yàn)椴恍枰幚眍~外的異常而變得簡(jiǎn)單。
六、解決復(fù)雜性之分模塊
分模塊是解決復(fù)雜性的重要方法。理想情況下,模塊之間應(yīng)該是相互隔離的,開(kāi)發(fā)人員面對(duì)具體的任務(wù),只需要接觸和了解整個(gè)系統(tǒng)的一小部分,而無(wú)需了解或改動(dòng)其他模塊。
6.1 深模塊和淺模塊
深模塊(Deep Module)指的是擁有強(qiáng)大功能和簡(jiǎn)單接口的模塊。深模塊是抽象的最佳實(shí)踐,通過(guò)排除模塊內(nèi)部不重要的信息,讓用戶更容易理解和使用。

Unix操作系統(tǒng)文件I/O是典型的深模塊,以O(shè)pen函數(shù)為例,接口接受文件名為參數(shù),返回文件描述符。但是這個(gè)接口的背后,是幾百行的實(shí)現(xiàn)代碼,用來(lái)處理文件存儲(chǔ)、權(quán)限控制、并發(fā)控制、存儲(chǔ)介質(zhì)等等,這些對(duì)用戶是不可見(jiàn)的。
與深模塊相對(duì)的是淺模塊(Shallow Module),功能簡(jiǎn)單,接口復(fù)雜。通常情況下,淺模塊無(wú)助于解決復(fù)雜性。因?yàn)樗麄兲峁┑氖找妫üδ埽┍粚W(xué)習(xí)和使用成本抵消了。以Java I/O為例,從I/O中讀取對(duì)象時(shí),需要同時(shí)創(chuàng)建三個(gè)對(duì)象FileInputStream、BufferedInputStream、ObjectInputStream,其中前兩個(gè)創(chuàng)建后不會(huì)被直接使用,這就給開(kāi)發(fā)人員造成了額外的負(fù)擔(dān)。默認(rèn)情況下,開(kāi)發(fā)人員無(wú)需感知到BufferedInputStream,緩沖功能有助于改善文件I/O性能,是個(gè)很有用的特性,可以合并到文件I/O對(duì)象里。假如我們想放棄緩沖功能,文件I/O也可以設(shè)計(jì)成提供對(duì)應(yīng)的定制選項(xiàng)。
關(guān)于淺模塊有一些爭(zhēng)議,大多數(shù)情況是因?yàn)闇\模塊是不得不接受的既定事實(shí),而不見(jiàn)得是因?yàn)楹侠硇?。?dāng)然也有例外,比如領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)里的防腐層,系統(tǒng)在與外部系統(tǒng)對(duì)接時(shí),會(huì)單獨(dú)建立一個(gè)服務(wù)或模塊去適配,用來(lái)保證原有系統(tǒng)技術(shù)棧的統(tǒng)一和穩(wěn)定性。
6.2 通用和專(zhuān)用
設(shè)計(jì)新模塊時(shí),應(yīng)該設(shè)計(jì)成通用模塊還是專(zhuān)用模塊?一種觀點(diǎn)認(rèn)為通用模塊滿足多種場(chǎng)景,在未來(lái)遇到預(yù)期外的需求時(shí),可以節(jié)省時(shí)間。另外一種觀點(diǎn)則認(rèn)為,未來(lái)的需求很難預(yù)測(cè),沒(méi)必要引入用不到的特性,專(zhuān)用模塊可以快速滿足當(dāng)前的需求,等有后續(xù)需求時(shí)再重構(gòu)成通用的模塊也不遲。
以上兩種思路都有道理,實(shí)際操作的時(shí)候可以采用兩種方式各自的優(yōu)點(diǎn),即在功能實(shí)現(xiàn)上滿足當(dāng)前的需求,便于快速實(shí)現(xiàn);接口設(shè)計(jì)通用化。
設(shè)計(jì)通用性接口需要權(quán)衡,既要滿足當(dāng)前的需求,同時(shí)在通用性方面不要過(guò)度設(shè)計(jì)。一些可供參考的標(biāo)準(zhǔn):
滿足當(dāng)前需求最簡(jiǎn)單的接口是什么?在不減少功能的前提下,減少方法的數(shù)量,意味著接口的通用性提升了。
接口使用的場(chǎng)景有多少?如果接口只有一個(gè)特定的場(chǎng)景,可以將多個(gè)這樣的接口合并成通用接口。
滿足當(dāng)前需求情況下,接口的易用性如何?如果接口很難使用,意味著我們可能過(guò)度設(shè)計(jì)了,需要拆分。
6.3 信息隱藏
信息隱藏是指,程序的設(shè)計(jì)思路以及內(nèi)部邏輯應(yīng)當(dāng)包含在模塊內(nèi)部,對(duì)其他模塊不可見(jiàn)。如果一個(gè)模塊隱藏了很多信息,說(shuō)明這個(gè)模塊在提供很多功能的同時(shí)又簡(jiǎn)化了接口,符合前面提到的深模塊理念。軟件設(shè)計(jì)領(lǐng)域有個(gè)技巧,定義一個(gè)"大"類(lèi)有助于實(shí)現(xiàn)信息隱藏。這里的“大”類(lèi)指的是,如果要實(shí)現(xiàn)某功能,將該功能相關(guān)的信息都封裝進(jìn)一個(gè)類(lèi)里面。
信息隱藏在降低復(fù)雜性方面主要有兩個(gè)作用:一是簡(jiǎn)化模塊接口,將模塊功能以更簡(jiǎn)單、更抽象的方式表現(xiàn)出來(lái),降低開(kāi)發(fā)人員的認(rèn)知負(fù)擔(dān);二是減少模塊間的依賴,使得系統(tǒng)迭代更輕量。舉個(gè)例子,如何從B+樹(shù)中存取信息是一些數(shù)據(jù)庫(kù)索引的核心功能,但是數(shù)據(jù)庫(kù)開(kāi)發(fā)人員將這些信息隱藏了起來(lái),同時(shí)提供簡(jiǎn)單的對(duì)外交互接口,也就是SQL腳本,使得產(chǎn)品和運(yùn)營(yíng)同學(xué)也能很快地上手。并且,因?yàn)橛凶銐虻某橄?,?shù)據(jù)庫(kù)可以在保持外部兼容的情況下,將索引切換到散列或其他數(shù)據(jù)結(jié)構(gòu)。
與信息隱藏相對(duì)的是信息暴露,表現(xiàn)為:設(shè)計(jì)決策體現(xiàn)在多個(gè)模塊,造成不同模塊間的依賴。舉個(gè)例子,兩個(gè)類(lèi)能處理同類(lèi)型的文件。這種情況下,可以合并這兩個(gè)類(lèi),或者提煉出一個(gè)新類(lèi)(參考《重構(gòu)》[3]一書(shū))。工程師應(yīng)當(dāng)盡量減少外部模塊需要的信息量。
6.4 拆分和合并
兩個(gè)功能,應(yīng)該放在一起還是分開(kāi)?“不管黑貓白貓”,能降低復(fù)雜性就好。這里有一些可以借鑒的設(shè)計(jì)思路:
共享信息的模塊應(yīng)當(dāng)合并,比如兩個(gè)模塊都依賴某個(gè)配置項(xiàng)。
可以簡(jiǎn)化接口時(shí)合并,這樣可以避免客戶同時(shí)調(diào)用多個(gè)模塊來(lái)完成某個(gè)功能。
可以消除重復(fù)時(shí)合并,比如抽離重復(fù)的代碼到一個(gè)單獨(dú)的方法中。
通用代碼和專(zhuān)用代碼分離,如果模塊的部分功能可以通用,建議和專(zhuān)用部分分離。舉個(gè)例子,在實(shí)際的系統(tǒng)設(shè)計(jì)中,我們會(huì)將專(zhuān)用模塊放在上層,通用模塊放在下層以供復(fù)用。
七、解決復(fù)雜性之注釋
注釋可以記錄開(kāi)發(fā)人員的設(shè)計(jì)思路和程序功能,降低開(kāi)發(fā)人員的認(rèn)知負(fù)擔(dān)和解決不可知(Unkown Unkowns)問(wèn)題,讓代碼更容易維護(hù)。通常情況下,在程序的整個(gè)生命周期里,編碼只占了少部分,大量時(shí)間花在了后續(xù)的維護(hù)上。有經(jīng)驗(yàn)的工程師懂得這個(gè)道理,通常也會(huì)產(chǎn)出更高質(zhì)量的注釋和文檔。
注釋也可以作為系統(tǒng)設(shè)計(jì)的工具,如果只需要簡(jiǎn)單的注釋就可以描述模塊的設(shè)計(jì)思路和功能,說(shuō)明這個(gè)模塊的設(shè)計(jì)是良好的。另一方面,如果模塊很難注釋?zhuān)f(shuō)明模塊沒(méi)有好的抽象。
7.1 注釋的誤區(qū)
關(guān)于注釋?zhuān)芏嚅_(kāi)發(fā)者存在一些認(rèn)識(shí)上的誤區(qū),也是造成大家不愿意寫(xiě)注釋的原因。比如“好代碼是自注釋的"、"沒(méi)有時(shí)間“、“現(xiàn)有的注釋都沒(méi)有用,為什么還要浪費(fèi)時(shí)間”等等。這些觀點(diǎn)是站不住腳的?!昂么a是自注釋的”只在某些場(chǎng)景下是合理的,比如為變量和方法選擇合適的名稱,可以不用單獨(dú)注釋。但是更多的情況,代碼很難體現(xiàn)開(kāi)發(fā)人員的設(shè)計(jì)思路。此外,如果用戶只能通過(guò)讀代碼來(lái)理解模塊的使用,說(shuō)明代碼里沒(méi)有抽象。好的注釋可以極大地提升系統(tǒng)的可維護(hù)性,獲取長(zhǎng)期的效率,不存在“沒(méi)有時(shí)間”一說(shuō)。注釋也是一種可以習(xí)得的技能,一旦習(xí)得,就可以在后續(xù)的工作中應(yīng)用,這就解決了“注釋沒(méi)有用”的問(wèn)題。
7.2 使用注釋提升系統(tǒng)可維護(hù)性
注釋?xiě)?yīng)當(dāng)能提供代碼之外額外的信息,重視What和Why,而不是代碼是如何實(shí)現(xiàn)的(How),最好不要簡(jiǎn)單地使用代碼中出現(xiàn)過(guò)的單詞。
根據(jù)抽象程度,注釋可以分為低層注釋和高層注釋?zhuān)蛯哟蔚淖⑨層脕?lái)增加精確度,補(bǔ)充完善程序的信息,比如變量的單位、控制條件的邊界、值是否允許為空、是否需要釋放資源等。高層次注釋拋棄細(xì)節(jié),只從整體上幫助讀者理解代碼的功能和結(jié)構(gòu)。這種類(lèi)型的注釋更好維護(hù),如果代碼修改不影響整體的功能,注釋就無(wú)需更新。在實(shí)際工作中,需要兼顧細(xì)節(jié)和抽象。低層注釋拆散與對(duì)應(yīng)的實(shí)現(xiàn)代碼放在一起,高層注釋一般用于描述接口。
注釋先行,注釋?xiě)?yīng)該作為設(shè)計(jì)過(guò)程的一部分,寫(xiě)注釋最好的時(shí)機(jī)是在開(kāi)發(fā)的開(kāi)始環(huán)節(jié),這不僅會(huì)產(chǎn)生更好的文檔,也會(huì)幫助產(chǎn)生好的設(shè)計(jì),同時(shí)減少寫(xiě)文檔帶來(lái)的痛苦。開(kāi)發(fā)人員推遲寫(xiě)注釋的理由通常是:代碼還在修改中,提前寫(xiě)注釋到時(shí)候還得再改一遍。這樣的話就會(huì)衍生兩個(gè)問(wèn)題:
首先,推遲注釋通常意味著根本就沒(méi)有注釋。一旦決定推遲,很容易引發(fā)連鎖反應(yīng),等到代碼穩(wěn)定后,也不會(huì)有注釋這回事。這時(shí)候再想添加注釋?zhuān)偷脤?zhuān)門(mén)抽出時(shí)間,客觀條件可能不會(huì)允許這么做。
其次,就算我們足夠自律抽出專(zhuān)門(mén)時(shí)間去寫(xiě)注釋?zhuān)⑨尩馁|(zhì)量也不會(huì)很好。我們潛意識(shí)中覺(jué)得代碼已經(jīng)寫(xiě)完了,急于開(kāi)展下一個(gè)項(xiàng)目,只是象征性地添加一些注釋?zhuān)瑹o(wú)法準(zhǔn)確復(fù)現(xiàn)當(dāng)時(shí)的設(shè)計(jì)思路。
避免重復(fù)的注釋。如果有重復(fù)注釋?zhuān)_(kāi)發(fā)人員很難找到所有的注釋去更新。解決方法是,可以找到醒目的地方存放注釋文檔,然后在代碼處注明去查閱對(duì)應(yīng)文檔的地址。如果程序已經(jīng)在外部文檔中注釋過(guò)了,不要在程序內(nèi)部再注釋了,添加注釋的引用就可以了。
注釋屬于代碼,而不是提交記錄。一種錯(cuò)誤的做法是將功能注釋放在提交記錄里,而不是放在對(duì)應(yīng)代碼文件里。因?yàn)殚_(kāi)發(fā)人員通常不會(huì)去代碼提交記錄里去查看程序的功能描述,很不方便。
7.3 使用注釋改善系統(tǒng)設(shè)計(jì)
良好的設(shè)計(jì)基礎(chǔ)是提供好的抽象,在開(kāi)始編碼前編寫(xiě)注釋,可以幫助我們提煉模塊的核心要素:模塊或?qū)ο笾凶钪匾墓δ芎蛯傩?。這個(gè)過(guò)程促進(jìn)我們?nèi)ニ伎?,而不是?jiǎn)單地堆砌代碼。另一方面,注釋也能夠幫助我們檢查自己的模塊設(shè)計(jì)是否合理,正如前文中提到,深模塊提供簡(jiǎn)單的接口和強(qiáng)大的功能,如果接口注釋冗長(zhǎng)復(fù)雜,通常意味著接口也很復(fù)雜;注釋簡(jiǎn)單,意味著接口也很簡(jiǎn)單。在設(shè)計(jì)的早期注意和解決這些問(wèn)題,會(huì)為我們帶來(lái)長(zhǎng)期的收益。
八、后記
John Ousterhout累計(jì)寫(xiě)過(guò)25萬(wàn)行代碼,是3個(gè)操作系統(tǒng)的重要貢獻(xiàn)者,這些原則可以視為作者編程經(jīng)驗(yàn)的總結(jié)。有經(jīng)驗(yàn)的工程師看到這些觀點(diǎn)會(huì)有共鳴,一些著作如《代碼大全》、《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》也會(huì)有類(lèi)似的觀點(diǎn)。本文中提到的原則和方法具有一定實(shí)操和指導(dǎo)價(jià)值,對(duì)于很難有定論的問(wèn)題,也可以在實(shí)踐中去探索。
關(guān)于原則和方法論,既不必刻意拔高,也不要嗤之以鼻。指導(dǎo)實(shí)踐的不是更多的實(shí)踐,而是實(shí)踐后的總結(jié)和思考。應(yīng)用原則和方法論實(shí)質(zhì)是借鑒已有的經(jīng)驗(yàn),可以減少我們自行摸索的時(shí)間。探索新的方法可以幫助我們適應(yīng)新的場(chǎng)景,但是新方法本身需要經(jīng)過(guò)時(shí)間檢驗(yàn)。
軟件開(kāi)發(fā)的核心就是管理復(fù)雜度