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