- 行業(yè)發(fā)展迅速
- 技術(shù)發(fā)展迅速
- 代碼編寫本身的難度
二、為什么要寫好代碼
從公司角度講,現(xiàn)在互聯(lián)網(wǎng)已經(jīng)進入到一個相對成熟理性的階段,很多一二線互聯(lián)網(wǎng)公司成立的時間都超過了十年。現(xiàn)在各家公司的發(fā)展方式都逐漸從單純注重速度轉(zhuǎn)變?yōu)樗俣扰c質(zhì)量兼顧。軟件的質(zhì)量,尤其是關(guān)鍵系統(tǒng)、核心系統(tǒng)的軟件質(zhì)量,對于各家公司的重要性不言而喻。最近的一個忽視軟件質(zhì)量的例子 —— 波音公司,其后果我想大家都是知道的。
互聯(lián)網(wǎng)公司的軟件質(zhì)量如果出問題,通常不至性命關(guān)天,但大筆真金白銀和用戶口碑的損失也是擔受不起。
從程序員角度講,代碼編寫能力就有如武林中人的內(nèi)功般重要。內(nèi)功會決定你的發(fā)展高度。即便日后做了技術(shù)管理崗的工作,如有扎實的代碼編寫能力,也會助你做好管理工作。因為技術(shù)人員總有這么一個特點,外行是不容易管好內(nèi)行的。如果你打算長期在技術(shù)線上發(fā)展,那代碼編寫能力就更加重要。
從管理角度講,如果你是一個技術(shù)團隊的直接負責人,那你需要重視你團隊成員的編碼質(zhì)量,因為這直接關(guān)乎你所負責項目的質(zhì)量、團隊的研發(fā)效率、業(yè)務(wù)的發(fā)展。如果你是更高等級的 Leader,你的直接下屬是其它 Leader。雖然你通常無需關(guān)心的技術(shù)細節(jié),但編碼質(zhì)量還是會對前面幾個問題產(chǎn)生影響。更重要的是,對于一個技術(shù)部門,或者是一個以技術(shù)為驅(qū)動力的公司而言,管理層需要從整個公司技術(shù)人員和技術(shù)團隊長期良性發(fā)展的角度考慮問題,代碼質(zhì)量等技術(shù)細節(jié)如果長期不被重視,會對技術(shù)團隊的穩(wěn)定性等方面產(chǎn)生負面影響。
三、何為好代碼
如同想成為好作者就要先學會識好書一樣,想成為優(yōu)秀的程序員就要知道什么樣的代碼是好代碼。個人認為,判斷一段代碼的好壞,要從可讀性和可擴展性兩點入手。
3.1 可讀性
可讀性是大家談到好代碼時往往會第一個想到的。什么是可讀性?顧名思義,就是指代碼易于閱讀和理解的程度。一些大牛所說過的關(guān)于何為好代碼的名言,往往都是從可讀性的角度表述的:
比如 ThoughtWorks 的首席科學家,《重構(gòu)》一書的作者 Martin Fowler 說過:
任何一個傻瓜都能寫出計算機可以理解的代碼。唯有寫出人類容易理解的代碼,才是優(yōu)秀的程序員。
IBM 的首席軟件工程科學家 Grady Booch 說過:
整潔的代碼如同優(yōu)美的散文。
換言之,代碼是給人看的,而不是給計算機看的。可讀性好的代碼就如同一篇優(yōu)秀的說明書,第一步做什么、第二步再做什么,都應有清晰的描述;可讀性好的代碼要有恰如其分、名副其實的命名;可讀性好的代碼要有合適的格式和組織,哪些代碼在前,哪些代碼在后,哪里需要縮進,哪里需要空行。這些都是有嚴格規(guī)范的。
總之,對于代碼可讀性,其實
3.2 可擴展性
但可讀性好的代碼也不一定會是優(yōu)秀的代碼。優(yōu)秀的代碼還應具有良好的可擴展性。
可擴展性指的是代碼易于擴展功能的程度。軟件行業(yè)是個變化迅速的行業(yè),互聯(lián)網(wǎng)更是如此。面對迅速的變化,擴展性的重要便體現(xiàn)了出來。可讀性好的代碼,程序員易于修改,從而易于擴展功能。但這往往還不夠。可擴展性往往追求的是在不修改原有代碼的情況下去擴展功能。即軟件設(shè)計原則中的開閉原則。
不過很多時候,代碼的可讀性和可擴展性是有一定程度的相互矛盾。如果大家閱讀過一些開源軟件的源碼,對這一點就會有體會。這些開源軟件的代碼質(zhì)量通常都不錯,但讀懂卻不是那么容易。背后的原因除了你需要具備對應領(lǐng)域的知識以外,更多的就是因為可擴展性所引入的復雜設(shè)計一定程度上降低了可讀性。
但在這種情況下,可讀性的稍微降低并不代表這個軟件的代碼不優(yōu)秀。優(yōu)秀但卻復雜的代碼,往往會有詳盡的文檔和注釋,代碼設(shè)計和編寫上往往能讓閱讀者有章可循。并且從表及里呈現(xiàn)出層層遞進形式,使閱讀者即可了解大意和結(jié)構(gòu),也可逐漸深入,了解細節(jié)。這一點同優(yōu)秀的書籍類似。
3.3 何為爛代碼
判斷何為好代碼,也可以從另一個角度進行,那就是判斷何為爛代碼。爛代碼的特性通常被稱為代碼的壞味道。壞味道在《重構(gòu)》一書中有詳細討論,這里我只簡單說幾個:
- 代碼重復
- 方法過長、類過長、參數(shù)過長
- 過多的、嵌套過深的 if...else 或 switch
- 分散式變化、霰彈式修改、依賴情結(jié)
代碼重復
編程界的另一位大神 Martin 叔叔說過:
重復可能是軟件中一切邪惡的根源。—— Robert C.Martin
所以說代碼重復可以說是頭號壞味道,原因是重復代碼會大幅增加代碼維護成本,也是各種 Bug 潛在的溫床。現(xiàn)在各種集成開發(fā)環(huán)境和代碼檢查工具都有重復代碼檢查功能,可以大大降低重復代碼發(fā)現(xiàn)成本,可以幫助開發(fā)者及時消除重復代碼。
除了工具可發(fā)現(xiàn)的重復代碼,在項目中可能還會有很多需要程序員仔細觀察才能發(fā)現(xiàn)的重復代碼。這些重復代碼往往是由原來簡單的重復代碼演變而來,并且具有更大的隱蔽性和危害性。這也說明了重復代碼需要及時修復。
不過現(xiàn)在流行的微服務(wù)架構(gòu),會在一定程度上增加代碼重復程度(有些同學可能對此沒有體會,詳細,微服務(wù)做的多了就能理解了),而且因為這些重復的代碼是跨系統(tǒng)、跨項目的,傳統(tǒng)的工具無法發(fā)現(xiàn)。
方法過長、類過長、參數(shù)過長
通常而言,過長的方法、類和參數(shù)都意味著這段代碼是一段糟糕的代碼。那么多長算長呢?以 Java 為例,一個方法長度不應超過50行,一個類不應超過1000行,最好不超過500行,方法參數(shù)不超過4個。
這些只是建議,不應該一刀切地判斷。因為對于一個復雜算法或技術(shù)的實現(xiàn),過于控制方法、類和參數(shù)的長度是不適宜的,因為對于這些算法技術(shù)本身的理解其實遠超過理解代碼實現(xiàn)的難度。但是,這不能作為普通程序員對自己代碼的長度不加控制的理由,畢竟多數(shù)人寫的代碼所要表達的邏輯都是很容易理解的。
方法和類過長通常都說明這段代碼違反了單一職責原則。參數(shù)過長同樣如此,通常都是一個方法關(guān)心了太多不該它關(guān)心的事情所致,也有些是由于所有參數(shù)平鋪所致。
過多的、嵌套過深的 if...else 或 switch
過于復雜的條件語句是另一種很明顯的代碼壞味道。對于這一點,我想我不必做過多解釋,寫過代碼的應該都懂。
對于如何解決復雜條件語句這個問題,我寫過專門的文章 —— 《如何“干掉”if...else》http://www.lxweimin.com/p/1db0bba283f0 。因此這里我就不再贅述。
分散式變化、霰彈式修改、依賴情結(jié)
這三點壞味道不如之前的容易理解。這里先一句話介紹這三個壞味道的含義(在《重構(gòu)》一書中有詳細解釋):
- 分散式變化:一個類常因為不同原因而進行修改
- 霰彈式修改:多個類常因為相同原因而進行修改
- 依賴情結(jié):一個方法對其它類的興趣高過自己所屬類的興趣
看過一句話介紹之后相信還會有很多同學不理解,再詳細介紹一下。分散式變化常反映出一個類(或方法)不滿足單一職責原則。它做的事太多,才會導致各種原因的變化都會帶來對它的修改。霰彈式修改則與分散式變化相對,它反映的是軟件設(shè)計的另一個問題:低內(nèi)聚。一個功能,分散的到處都是,這樣通常就會導致一個需求變化需要到處修改。
從上面的介紹也能看出,軟件設(shè)計的復雜性。很多原則其實相互矛盾,就像單一職責和內(nèi)聚性。軟件工程師需要在設(shè)計時平衡這些相互矛盾的原則,才能設(shè)計出優(yōu)秀的軟件。
依賴情結(jié),雖然字面上不容易理解,但是在日常工作中體現(xiàn)的其實更多。經(jīng)常能看到這樣的方法:它從一個或幾個類中取出數(shù)據(jù),然后經(jīng)過處理,然后設(shè)置到另一個類中。這個方法從始至終都沒有使用過自己類的屬性。如果是靜態(tài)方法,通常也無可厚非(畢竟靜態(tài)方法不能訪問自己類的屬性)。可是我們更常見到的都實例方法。這其實反映出一個事實:定義這個方法的位置錯了。
小結(jié)一下:
- 分散式變化反映軟件設(shè)計違反了單一職責
- 霰彈式修改反映出軟件設(shè)計的不夠內(nèi)聚
- 依賴情結(jié)反映出方法放錯了位置
四、寫好代碼的方法
寫好代碼應該是各級別程序員共同的目標。換言之,寫好代碼就是程序員的自我修養(yǎng)。
但不同級別的程序員,寫好代碼這件事其實有不同的要求。
對于普通的程序員,更多的精力應該放在如何提高代碼可讀性為主要的目標。即努力把代碼寫的清楚、寫的明白。這里涉及到的技術(shù)通常是代碼編寫的一些基本規(guī)范、技巧、簡單的代碼重構(gòu)手段,可能還包括面向?qū)ο蠓矫娴闹R。
重點說明的是,我并沒有提及各種軟件設(shè)計方面的原則,比如單一職責、開閉原則。原因在于,所謂原則,就是一些你看似明白,實則不懂的東西。掌握原則,需要多加練習和思考。
而對于高級和資深的工程師,應具備編寫兼具可讀性和可擴展性的代碼。這里還需再次強調(diào),可讀性和可擴展性有時是矛盾的。因此,這一階段的程序員需要能平衡好可讀性和可擴展性。同時也需要能從工程和業(yè)務(wù)的角度考慮,代碼要避免過度設(shè)計,但也不能不考慮擴展。
所以,編寫可擴展性高的代碼,除了需要具備熟練掌握各種設(shè)計模式、設(shè)計原則和思想、重構(gòu)手段等等。還需要開發(fā)者對所在業(yè)務(wù)領(lǐng)域有深入理解,從而在何時的地方做出具有合適擴展能力的設(shè)計。
接下來說幾個簡單的提高代碼質(zhì)量的方法。
4.1 命名
第一個想強調(diào)的代碼的命名。命名是一個不被人重視的編碼細節(jié),但能夠為代碼、軟件起一個簡單明了、恰如其分的名字其實是非常有價值的,而且也不是一個簡單的事情。
試想一下,如果你有了孩子,是不是需要仔細考慮孩子的姓名?如果隨便起個張三李四,那是一定不是一個稱職的父母。同樣,對于代碼,你隨便起個名字,那同樣也是不負責任的表現(xiàn)。
命名并不是簡單想幾個單詞并拼接在一起而已。命名其實反映了開發(fā)者對業(yè)務(wù)理解的程度和軟件設(shè)計的能力。一個好名字實際是對一個業(yè)務(wù)功能簡短而又精準的表述,其背后體現(xiàn)了開發(fā)者對代碼規(guī)范、面向?qū)ο笤O(shè)計、設(shè)計原則、設(shè)計模式,甚至架構(gòu)設(shè)計等能力的掌握和運用的好壞。
方法命名
方法命名的一個原則是解釋目的,而不是手段。即方法命名只需說明這個方法是干什么的即可,不用通過方法命名體現(xiàn)這個方法是如何做的。
方法命名的一般格式是:動詞+名詞短語+(額外修飾)。
例如,在 Spring 的 BeanFactory 接口中有如下方法定義:
Object getBean(String name)
這個方法命名就是動詞+名詞的形式,因為方法功能比較簡單,所以沒有加額外修飾。
有時我們能看到方法名稱體現(xiàn)了內(nèi)部實現(xiàn)方式。假如,我們需要實現(xiàn)一個分布式的 Spring,Bean 的定義存在 Redis 里(實際顯然沒有這個必要,這里只是舉個多數(shù)人容易理解的例子)。那估計 getBean 這個方法就會有人定義成如下形式:
Object getBeanInRedis(String name)
這時,InRedis
體現(xiàn)就是方法內(nèi)部實現(xiàn)方法。這么做是多余的,即違反了簡單的原則,也違反了方法命名體現(xiàn)目的,而非方法的原則(另外也非常的不面向?qū)ο蟆H绻阏娴南雽崿F(xiàn)一個基于 Redis 的 Spring,可以創(chuàng)建一個 BeanFactory 的實現(xiàn)類 —— RedisBeanFactory)
其實上述方法命名有時還不夠簡單。例如在 Spring Data 的 Repository 定義中,我們能看到如下方法:
save
saveAll
findById
findAll
這些方法的命名簡單到連名詞部分也省略了。原因在于 Repository 接口的實現(xiàn)(如 OrderRepository)中已經(jīng)包含了這些方法所操作的對象,所以也就不用重復了。在面向?qū)ο笳Z言中,方法調(diào)用通常都是 object(class).method(args)
的形式。這時,object 或 class 的命名應該反映出一些業(yè)務(wù)含義,這些含義不必在方法命名中重復表現(xiàn)。
在非面向?qū)ο笳Z言中,道理同樣存在。如在 Golang 中有這樣的方法 time.Parse(layout, value string)
。這里的 time 是包名,但在命名上起到作用同面向?qū)ο笳Z言的對象和類是一樣的。
剛看到了一些簡單的方法命名的例子,接下來看一些復雜的命名:
startEventDispatchThreadIfNecessary
上面這個例子是 JDK 中的一個方法。這個方法的命名很長,翻譯過來就是“啟動時間分發(fā)線程,如果必要”。前半句好理解,那為什么后面要加上一句“如果必要”呢?原因在于如果不加,其他開發(fā)者會誤以為調(diào)用這個方法一定會啟動一個事件分發(fā)線程,但實際情況是有些情況下不會這么做。那什么情況下不會這么做呢?這算是一個細節(jié),一般情況下不用在方法命名上體現(xiàn),否則方法名就太長了。如果這個細節(jié)確屬必要,那可以通過注釋來描述。
方法命名中帶有 IfXxx
例子還有很多,在各種開源軟件的源碼中都能找到。這里想要說的是,為了達到讓使用者正確理解一個方法所要達到的目的,有時需要在動詞+名詞的命名形式之上再增加額外的描述。
變量命名
對于變量的命名,它的作用主要有兩點:一是描述對象(或數(shù)據(jù)結(jié)構(gòu))所具有的屬性;二是對方法執(zhí)行過程進行輔助性描述。
接下來我將介紹一些代碼命名的基本規(guī)則,以及幾個例子。
對于變量命名的第一點作用,很容易理解。因為對于面向?qū)ο笳Z言來說,一個類就是數(shù)據(jù)和行為的封裝,而數(shù)據(jù)其實就是對象的屬性。對于非面向?qū)ο笳Z言,如 C、Go,它們的結(jié)構(gòu)體也包含有數(shù)據(jù)(雖然不能定義方法,沒有行為)。
對于變量命名的第二點作用,多解釋一下。這一點作用通常是對局部變量而言的。在一個方法體中,另一個方法的返回值需要被使用多次使用,這時最好使用臨時變量保存這個方法的返回值。這很容易理解。如果只是用一次呢?其實有時也需一個臨時變量。這個臨時變量的作用通常為了更好的解釋這個值的目的和含義。
舉例來說:
List<Order> paidOrders = findAllByStatus(OrderStatus.PAID);
這時,paidOrders
顯然比 findAllByStatus(OrderStatus.PAID)
更容易理解,也更簡短。
理解了變量的作用之后,如何命名也就清楚了。畢竟命名的目的在于用更簡單的方式描述作用。
所有,對于下面的例子,哪種命名更好呢?
class Order {
private String name;
private String orderName;
}
顯然,name
更好。雖然 orderName
也能體現(xiàn)“訂單名稱”這個作用,但是前者更簡單。
類
在 Java 語言中,類是第一類公民,也是編程者遇到的第一個需要命名的東西。類的基本命名規(guī)則通常為形容詞+名詞的形式,最后一部分的名詞詞組表示的是這個類所表示的是哪一類事物。如果一個接口只有一種實現(xiàn)類,通常可將這個類命名為接口名+Impl,這也是被廣泛接受的命名形式。
類的背后其實體現(xiàn)的是面向?qū)ο蟮脑O(shè)計(看到這里我相信會有很多人對面向?qū)ο筻椭员恰4_實,以 Java 為代表的面向?qū)ο缶幊陶Z言不如函數(shù)式的語言簡單靈活。但請相信,在更好的方法出現(xiàn)之前,面向?qū)ο蟮脑O(shè)計方法是應對復雜業(yè)務(wù)邏輯最好的方法。同時,Java 使用過程中出現(xiàn)的很多問題,實則是開發(fā)者沒有理解好面向?qū)ο笤O(shè)計所導致的)
面向?qū)ο笤O(shè)計,看似簡單,但其實需要對業(yè)務(wù)領(lǐng)域的深刻理解。在這方面,領(lǐng)域驅(qū)動設(shè)計是一個非常好的指南,它能夠指導如何設(shè)計一個業(yè)務(wù)系統(tǒng),自然也能夠指導如何命名。
但僅僅將類命名成某實體類、某 Factory、某 Repository、某 Service 是遠遠不夠的。除此之外,能夠指導我們命名類(也包括接口)的是各種設(shè)計模式。比如某某 Builder、某某 Strategy、某某 Command。當然,也沒必要死抱著設(shè)計模式,因為設(shè)計模式體現(xiàn)的是實現(xiàn)方法,而這一點通常不是命名首要考慮的問題(命名首要考慮的是目的)。
在類的命名上,常見的一個具體問題是 Service 和 Manager 這兩種命名隨意使用。表面上這兩者都是用來實現(xiàn)務(wù)邏輯的組件,但還是有些區(qū)別。一般來說,Service 通常是無狀態(tài)的業(yè)務(wù)組件,而 Manager 通常為有狀態(tài)的。
小結(jié)一下:
- 類命名是面向?qū)ο笤O(shè)計的體現(xiàn)
- 業(yè)務(wù)系統(tǒng)的類命名可參考領(lǐng)域驅(qū)動設(shè)計
- 其它領(lǐng)域的類命名可參考設(shè)計模式
- 不用死抱上述建議,只要命名體現(xiàn)類設(shè)計的目的即可
接口
不同于類所表示的具體的概念,接口表示的是泛化的概念。接口通常表示一類事物,或一類事物所共有的特性。同類和變量的基本命名規(guī)則一樣,接口的命名通常也是名詞形式。例如,Spring Framework 中的 ApplicaitonContext
、BeanFactory
、InitializingBean
等接口。但我們有時能看到一些代碼在接口前加 I 這個前綴,以表示這個是一個接口,而非一個類。這種風格是非常不推薦的。開發(fā)者應當把接口看作是更通用的一個概念,而非特殊概念。因此,不應在命名前加增 I 這個前綴,因為增加前綴是一種特殊化的做法。比如,ApplicationContext
是一個好的接口命名的實例,但 IApplicationContext
就不是。少數(shù)情況下,增加前綴還會導致歧義,比方說,沒人會把 IPhone
理解為是電話這個概念的接口。
除了名詞形式的命名,接口還有另一類命名方式 —— 形容詞命名。比如,在 JDK 中,我們常見的有 Serializable
、Cloneable
、Comparable
、Runnable
,其它開源項目中這樣的命名方式也有很多,就不一一列舉了。這種風格的命名所表示的都是一種特性 —— 能做什么。
其它接口命名實例還有 XxxAware,這在 Spring Framework 中比較常見。
小結(jié)一下,接口命名的方法主要體現(xiàn)了接口的兩類作用:
- 表示一類事物:名詞形式接口命名
- 表示某種特性:形容詞形式接口命名
4.2 一些提高編碼能力的“旁門左道”
重復造輪子
重復造輪子通常都是編程界的貶義詞,但在今天這個話題里,我認為“重復造輪子”是褒義詞。這里我們重復造輪子的目的是通過模仿現(xiàn)有開源技術(shù)提高自己的編程能力。提高編程能力沒有捷徑,最終看的就是代碼編寫量,還要是高質(zhì)量的代碼編寫量。在日常工作中,允許你對代碼精打細磨的機會并不多,這時你就需要尋找額外“訓練”機會。研究開源技術(shù)源碼,嘗試重寫,或者更進一步,為開源技術(shù)貢獻代碼,能讓你的編碼能力提高很多。
結(jié)對編程
結(jié)對編程是敏捷開發(fā)中所提到的一個工程實踐。不過似乎在國內(nèi)公司中實踐的較少(我在外企和互聯(lián)網(wǎng)行業(yè)工作時實踐過一些結(jié)對編程)。
結(jié)對編程有很多好處,在提高編碼質(zhì)量方面,因為結(jié)對編程通常一人寫一人看,或一人寫實現(xiàn)一人寫單測。因此,你的代碼不僅需要自己理解,至少還需要你的同伴理解。而且因為這一閱讀理解的過程是實時進行的,這就使得對代碼的 Review 非常細粒度,這也促使你的代碼質(zhì)量的提高。
單元測試
要寫好代碼就少不了修改代碼,那如何對已正確實現(xiàn)業(yè)務(wù)功能的代碼進行修改還保證不出錯呢?這就需要單元測試。測試可以是軟件工程中最重要的環(huán)節(jié)之一,重要性不亞于開發(fā)。而單元測試,應當是測試粒度最細,也是與開發(fā)人員距離最近的測試形式。如果一個項目沒有任何單元測試,基本可以斷定它不會是一個好項目。
單元測試可說是寫好代碼的前提,因此要想把代碼寫好的同學已經(jīng)要掌握編寫單元測試的技術(shù)。可能有同學覺得寫單元測試就是 JUnit(對于其它語言也有類似框架或有內(nèi)置單元測試支持)。如果你這么想,那你就是太 naive 了。我面試時問過十幾個程序員關(guān)于單元測試的問題,沒聽說過值校驗和行為校驗的一個沒有。可能這個問題有些偏門,答不出來可以理解。那單測中的 Mock 技術(shù)都有作用?Mock 和 Stub 的區(qū)別?能答出來也沒有。這些問題表面上是概念性的問題,但其實能反映一個技術(shù)人員對單元測試技術(shù)的實際經(jīng)驗的多少。
單元測試的不易的另一個體現(xiàn)在于單元測試的兩個矛盾。第一個矛盾是單元測試本身也是代碼,開發(fā)人員編碼質(zhì)量的好壞也會影響單元測試代碼。單元測試寫不好,最終會導致別人無法理解測試用例的含義,也會對整個項目的維護性造成很大的負面影響。另外就是單元測試如果覆蓋完整的話,實際的代碼量會比被測代碼本身還多。如何把單元測試寫的精簡、易于理解、覆蓋完整也是一個頗有技術(shù)含量的工作。
單元測試的第二個矛盾是對于某些代碼質(zhì)量不高的項目來說,補充單元測試是一個很有挑戰(zhàn)的工作。但是不補充單元測試項目的代碼重構(gòu)又很難保證質(zhì)量。不重構(gòu)又難以提高代碼質(zhì)量。。。看到?jīng)],這就是個死循環(huán):代碼質(zhì)量差 -> 難以單測 -> 代碼質(zhì)量差。筆者之前所在那個外企項目就死在這一點上了。
所以,早寫單測。
少動鼠標
用好各種開發(fā)工具,如 VIM、IDEA 的快捷鍵,以及各種命令行工具,盡量少用鼠標。這么做不一定成為編程高手,但編程高手都能這么玩。如同競技游戲中,哪個魔獸、星際、DOTA 高手用鼠標放技能呢?編程同理。
五、參考
- 《重構(gòu) - 改善既有代碼的設(shè)計》by Martin Fowler
- 《代碼整潔之道》by Robert C. Martin
- 《重構(gòu)與模式》by Joshua Kerievsky
- 《實現(xiàn)模式》by Kent Beck