自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于爛代碼的那些事

開發(fā) 后端 前端
最近寫了不少代碼,review了不少代碼,也做了不少重構(gòu),總之是對著爛代碼工作了幾周。為了抒發(fā)一下這幾周里好幾次到達崩潰邊緣的情緒,我決定寫一篇文章談一談爛代碼的那些事。 這里是上篇,談一談爛代碼產(chǎn)生的原因和現(xiàn)象。

1.摘要

最近寫了不少代碼,review了不少代碼,也做了不少重構(gòu),總之是對著爛代碼工作了幾周。為了抒發(fā)一下這幾周里好幾次到達崩潰邊緣的情緒,我決定寫一篇文章談一談爛代碼的那些事。 這里是上篇,談一談爛代碼產(chǎn)生的原因和現(xiàn)象。

2.寫爛代碼很容易

剛?cè)?span id="k6zqhab033oa" class="wp_keywordlink">程序員這行的時候經(jīng)常聽到一個觀點:你要把精力放在ABCD(需求文檔/功能設(shè)計/架構(gòu)設(shè)計/理解原理)上,寫代碼只是把想法翻譯成編程語言而已,是一個沒什么技術(shù)含量的事情。

當(dāng)時的我在聽到這種觀點時會有一種近似于高冷的不屑:你們就是一群傻X,根本不懂代碼質(zhì)量的重要性,這么下去遲早有一天會踩坑,呸。

可是幾個月之后,他們似乎也沒怎么踩坑。而隨著編程技術(shù)一直在不斷發(fā)展,帶來了更多的我以前認(rèn)為是傻X的人加入到程序員這個行業(yè)中來。

語言越來越高級、封裝越來越完善,各種技術(shù)都在幫助程序員提高生產(chǎn)代碼的效率,依靠層層封裝,程序員真的不需要了解一丁點技術(shù)細節(jié),只要把需求里的內(nèi)容逐行翻譯出來就可以了。

很多程序員不知道要怎么組織代碼、怎么提升運行效率、底層是基于什么原理,他們寫出來的是在我心目中爛成一坨翔一樣的代碼。

但是那一坨翔一樣代碼竟然他媽的能正常工作。

即使我認(rèn)為他們寫的代碼是坨翔,但是從不接觸代碼的人的視角來看(比如說你的boss),代碼編譯過了,測試過了,上線運行了一個月都沒出問題,你還想要奢求什么?

所以,即使不情愿,也必須承認(rèn),時至今日,寫代碼這件事本身沒有那么難了。

3.爛代碼終究是爛代碼

但是偶爾有那么幾次,寫爛代碼的人離職了之后,事情似乎又變得不一樣了。

想要修改功能時卻發(fā)現(xiàn)程序里充斥著各種無法理解的邏輯、改完之后莫名其妙的bug一個接一個,接手這個項目的人開始漫無目的的加班,并且原本一個挺樂觀開朗的人漸漸的開始喜歡問候別人祖宗了。

我總結(jié)了幾類經(jīng)常被艸祖宗的爛代碼:

3.1.意義不明

能力差的程序員容易寫出意義不明的代碼,他們不知道自己究竟在做什么.

就像這樣:

  1. public void save() { 

  2. for(int i=0;i<100;i++) { 

  3. //防止保存失敗,重試100次 

  4. document.save(); 

對于這類程序員,我一般建議他們轉(zhuǎn)行。

3.2.不說人話

不說人話是新手最經(jīng)常出現(xiàn)的問題,直接的表現(xiàn)就是寫了一段很簡單的代碼,其他人卻看不懂。

比如下面這段:

  1. public boolean getUrl(Long id) { 

  2. UserProfile up = us.getUser(ms.get(id).getMessage().aid); 

  3. if (up == null) { 

  4. return false

  5. if (up.type == 4 || ((up.id >> 2) & 1) == 1) { 

  6. return false

  7. if(Util.getUrl(up.description)) { 

  8. return true

  9. else { 

  10. return false

很多程序員喜歡簡單的東西:簡單的函數(shù)名、簡單的變量名、代碼里翻來覆去只用那么幾個單詞命名;能縮寫就縮寫、能省略就省略、能合并就合并。這類人寫出來的代碼里充斥著各種g/s/gos/of/mss之類的全世界沒人懂的縮寫,或者一長串不知道在做什么的連續(xù)調(diào)用。

還有很多程序員喜歡復(fù)雜,各種宏定義、位運算之類寫的天花亂墜,生怕代碼讓別人一下子看懂了會顯得自己水平不夠。

簡單的說,他們的代碼是寫給機器的,不是給人看的。

3.3.不恰當(dāng)?shù)慕M織

不恰當(dāng)?shù)慕M織是高級一些的爛代碼,程序員在寫過一些代碼之后,有了基本的代碼風(fēng)格,但是對于規(guī)模大一些的工程的掌控能力不夠,不知道代碼應(yīng)該如何解耦、分層和組織。

這種反模式的現(xiàn)象是經(jīng)常會看到一段代碼在工程里拷來拷去;某個文件里放了一大坨堆砌起來的代碼;一個函數(shù)堆了幾百上千行;或者一個簡單的功能七拐八繞的調(diào)了幾十個函數(shù),在某個難以發(fā)現(xiàn)的猥瑣的小角落里默默的調(diào)用了某些關(guān)鍵邏輯。

這類代碼大多復(fù)雜度高,難以修改,經(jīng)常一改就崩;而另一方面,創(chuàng)造了這些代碼的人傾向于修改代碼,畏懼創(chuàng)造代碼,他們寧愿讓原本復(fù)雜的代碼一步步變得更復(fù)雜,也不愿意重新組織代碼。當(dāng)你面對一個幾千行的類,問為什么不把某某邏輯提取出來的時候,他們會說:

“但是,那樣就多了一個類了呀。”

3.4.假設(shè)和缺少抽象

相對于前面的例子,假設(shè)這種反模式出現(xiàn)的場景更頻繁,花樣更多,始作俑者也更難以自己意識到問題。比如:

  1. public String loadString() { 

  2. File file = new File("c:/config.txt"); 

  3. // read something 

文件路徑變更的時候,會把代碼改成這樣:

  1. public String loadString(String name) { 

  2. File file = new File(name); 

  3. // read something 

需要加載的內(nèi)容更豐富的時候,會再變成這樣:

  1. public String loadString(String name) { 

  2. File file = new File(name); 

  3. // read something 

  4. public Integer loadInt(String name) { 

  5. File file = new File(name); 

  6. // read something 

之后可能會再變成這樣:

  1. public String loadString(String name) { 

  2. File file = new File(name); 

  3. // read something 

  4. public String loadStringUtf8(String name) { 

  5. File file = new File(name); 

  6. // read something 

  7. public Integer loadInt(String name) { 

  8. File file = new File(name); 

  9. // read something 

  10. public String loadStringFromNet(String url) { 

  11. HttpClient ... 

  12. public Integer loadIntFromNet(String url) { 

  13. HttpClient ... 

這類程序員往往是項目組里開發(fā)效率比較高的人,但是大量的業(yè)務(wù)開發(fā)工作導(dǎo)致他們不會做多余的思考,他們的口頭禪是:“我每天要做XX個需求”或者“先做完需求再考慮其他的吧”。

這種反模式表現(xiàn)出來的后果往往是代碼很難復(fù)用,面對deadline的時候,程序員迫切的想要把需求落實成代碼,而這往往也會是個循環(huán):寫代碼的時候來不及考慮復(fù)用,代碼難復(fù)用導(dǎo)致之后的需求還要繼續(xù)寫大量的代碼。

一點點積累起來的大量的代碼又帶來了組織和風(fēng)格一致性等問題,最后形成了一個新功能基本靠拷的遺留系統(tǒng)。

3.5.還有嗎

爛代碼還有很多種類型,沿著功能-性能-可讀-可測試-可擴展這條路線走下去,還能看到很多匪夷所思的例子。

那么什么是爛代碼?個人認(rèn)為,爛代碼包含了幾個層次:

如果只是一個人維護的代碼,滿足功能和性能要求倒也足夠了。

如果在一個團隊里工作,那就必須易于理解和測試,讓其它人員有能力修改各自的代碼。

同時,越是處于系統(tǒng)底層的代碼,擴展性也越重要。

所以,當(dāng)一個團隊里的底層代碼難以閱讀、耦合了上層的邏輯導(dǎo)致難以測試、或者對使用場景做了過多的假設(shè)導(dǎo)致難以復(fù)用時,雖然完成了功能,它依然是坨翔一樣的代碼。

3.6.夠用的代碼

而相對的,如果一個工程的代碼難以閱讀,能不能說這個是爛代碼?很難下定義,可能算不上好,但是能說它爛嗎?如果這個工程自始至終只有一個人維護,那個人也維護的很好,那它似乎就成了“夠用的代碼”。

很多工程剛開始可能只是一個人負責(zé)的小項目,大家關(guān)心的重點只是代碼能不能順利的實現(xiàn)功能、按時完工。

過上一段時間,其他人參與時才發(fā)現(xiàn)代碼寫的有問題,看不懂,不敢動。需求方又開始催著上線了,怎么辦?只好小心翼翼的只改邏輯而不動結(jié)構(gòu),然后在注釋里寫上這么實現(xiàn)很ugly,以后明白內(nèi)部邏輯了再重構(gòu)。

再過上一段時間,有個相似的需求,想要復(fù)用里面的邏輯,這時才意識到代碼里做了各種特定場景的專用邏輯,復(fù)用非常麻煩。為了趕進度只好拷代碼然后改一改。問題解決了,問題也加倍了。

幾乎所有的爛代碼都是從“夠用的代碼”演化來的,代碼沒變,使用代碼的場景發(fā)生變了,原本夠用的代碼不符合新的場景,那么它就成了爛代碼。

4.重構(gòu)不是萬能藥

程序員最喜歡跟程序員說的謊話之一就是:現(xiàn)在進度比較緊,等X個月之后項目進度寬松一些再去做重構(gòu)。

不能否認(rèn)在某些(極其有限的)場景下重構(gòu)是解決問題的手段之一,但是寫了不少代碼之后發(fā)現(xiàn),重構(gòu)往往是程序開發(fā)過程中最復(fù)雜的工作?;ㄒ粋€月寫的爛代碼,要花更長的時間、更高的風(fēng)險去重構(gòu)。

曾經(jīng)經(jīng)歷過幾次忍無可忍的大規(guī)模重構(gòu),每一次重構(gòu)之前都是找齊了組里的高手,開了無數(shù)次分析會,把組內(nèi)需求全部暫停之后才敢開工,而重構(gòu)過程中往往哀嚎遍野,幾乎每天都會出上很多意料之外的問題,上線時也幾乎必然會出幾個問題。

從技術(shù)上來說,重構(gòu)復(fù)雜代碼時,要做三件事:理解舊代碼、分解舊代碼、構(gòu)建新代碼。而待重構(gòu)的舊代碼往往難以理解;模塊之間過度耦合導(dǎo)致牽一發(fā)而動全身,不易控制影響范圍;舊代碼不易測試導(dǎo)致無法保證新代碼的正確性。

這里還有一個核心問題,重構(gòu)的復(fù)雜度跟代碼的復(fù)雜度不是線性相關(guān)的。比如有1000行爛代碼,重構(gòu)要花1個小時,那么5000行爛代碼的重構(gòu)可能要花2、3天。要對一個失去控制的工程做重構(gòu),往往還不如重寫更有效率。

而拋開具體的重構(gòu)方式,從受益上來說,重構(gòu)也是一件很麻煩的事情:它很難帶來直接受益,也很難量化。這里有個很有意思的現(xiàn)象,基本關(guān)于重構(gòu)的書籍無一例外的都會有獨立的章節(jié)介紹“如何向boss說明重構(gòu)的必要性”。

重構(gòu)之后能提升多少效率?能降低多少風(fēng)險?很難答上來,爛代碼本身就不是一個可以簡單的標(biāo)準(zhǔn)化的東西。

舉個例子,一個工程的代碼可讀性很差,那么它會影響多少開發(fā)效率?

你可以說:之前改一個模塊要3天,重構(gòu)之后1天就可以了。但是怎么應(yīng)對“不就是做個數(shù)據(jù)庫操作嗎為什么要3天”這類問題?爛代碼“爛”的因素有不確 定性、開發(fā)效率也因人而異,想要證明這個東西“確實”會增加兩天開發(fā)時間,往往反而會變成“我看了3天才看懂這個函數(shù)是做什么的”或者“我做這么簡單的修 改要花3天”這種神經(jīng)病才會去證明的命題。

而另一面,許多技術(shù)負責(zé)人也意識到了代碼質(zhì)量和重構(gòu)的必要性,“那就重構(gòu)嘛”,或者“如果看到問題了,那就重構(gòu)”。上一個問題解決了,但實際上關(guān)于 重構(gòu)的代價和收益仍然是一筆糊涂賬,在沒有分配給你更多資源、沒有明確的目標(biāo)、沒有具體方法的情況下,很難想象除了有代碼潔癖的人還有誰會去執(zhí)行這種莫名 其妙的任務(wù)。

于是往往就會形成這種局面:

  • 不寫代碼的人認(rèn)為應(yīng)該重構(gòu),重構(gòu)很簡單,無論新人還是老人都有責(zé)任做重構(gòu)。

  • 寫代碼老手認(rèn)為應(yīng)該遲早應(yīng)該重構(gòu),重構(gòu)很難,現(xiàn)在湊合用,這事別落在我頭上。

  • 寫代碼的新手認(rèn)為不出bug就謝天謝地了,我也不知道怎么重構(gòu)。

5.寫好代碼很難

與寫出爛代碼不同的是,想寫出好代碼有很多前提:

  • 理解要開發(fā)的功能需求。

  • 了解程序的運行原理。

  • 做出合理的抽象。

  • 組織復(fù)雜的邏輯。

  • 對自己開發(fā)效率的正確估算。

  • 持續(xù)不斷的練習(xí)。

寫出好代碼的方法論很多,但我認(rèn)為寫出好代碼的核心反而是聽起來非常low的“持續(xù)不斷的練習(xí)”。這里就不展開了,留到下篇再說。

很多程序員在寫了幾年代碼之后并沒有什么長進,代碼仍然爛的讓人不忍直視,原因有兩個主要方面:

  • 環(huán)境是很重要的因素之一,在爛代碼的熏陶下很難理解什么是好代碼,知道的人大部分也會選擇隨波逐流。

  • 還有個人性格之類的說不清道不明的主觀因素,寫出爛代碼的程序員反而都是一些很好相處的人,他們往往熱愛公司團結(jié)同事平易近人工作任勞任怨–只是代碼很爛而已。

而工作幾年之后的人很難再說服他們?nèi)ヌ岣叽a質(zhì)量,你只會反復(fù)不斷的聽到:“那又有什么用呢?”或者“以前就是這么做的啊?”之類的說法。

那么從源頭入手,提高招人時對代碼的質(zhì)量的要求怎么樣?

前一陣面試的時候增加了白板編程、最近又增加了上機編程的題目。發(fā)現(xiàn)了一個現(xiàn)象:一個人工作了幾年、做過很多項目、帶過團隊、發(fā)了一些文章,不一定能代表他代碼寫的好;反之,一個人代碼寫的好,其它方面的能力一般不會太差。

舉個例子,最近喜歡用“寫一個代碼行數(shù)統(tǒng)計工具”作為面試的上機編程題目。很多人看到題目之后第一反映是,這道題太簡單了,這不就是寫寫代碼嘛。

從實際效果來看,這道題識別度卻還不錯。

首先,題目足夠簡單,即使沒有看過《面試寶典》之類書的人也不會吃虧。而題目的擴展性很好,即使提前知道題目,配合不同的條件,可以變成不同的題目。比如要求按文件類型統(tǒng)計行數(shù)、或者要求提高統(tǒng)計效率、或者統(tǒng)計的同時輸出某些單詞出現(xiàn)的次數(shù),等等。

從考察點來看,首先是基本的樹的遍歷算法;其次有一定代碼量,可以看出程序員對代碼的組織能力、對問題的抽象能力;上機編碼可以很簡單的看出應(yīng)聘者是不是很久沒寫程序了;還包括對于程序易用性和性能的理解。

最重要的是,最后的結(jié)果是一個完整的程序,我可以按照日常工作的標(biāo)準(zhǔn)去評價程序員的能力,而不是從十幾行的函數(shù)里意淫這個人在日常工作中大概會有什么表現(xiàn)。

但即使這樣,也很難拍著胸脯說,這個人寫的代碼質(zhì)量沒問題。畢竟面試只是代表他有寫出好代碼的能力,而不是他將來會寫出好代碼。

6.悲觀的結(jié)語

說了那么多,結(jié)論其實只有兩條,作為程序員:

  • 不要奢望其他人會寫出高質(zhì)量的代碼

  • 不要以為自己寫出來的是高質(zhì)量的代碼

如果你看到了這里還沒有喪失希望,那么可以期待一下這篇文章的第二部分,關(guān)于如何提高代碼質(zhì)量的一些建議和方法。

#p#

1.摘要

這是爛代碼系列的第二篇,在文章中我會跟大家討論一下如何盡可能高效和客觀的評價代碼的優(yōu)劣。

在發(fā)布了《關(guān)于爛代碼的那些事(上)》之后,發(fā)現(xiàn)這篇文章竟然意外的很受歡迎,很多人也描(tu)述(cao)了各自代碼中這樣或者那樣的問題。

最近部門在組織bootcamp,正好我負責(zé)培訓(xùn)代碼質(zhì)量部分,在培訓(xùn)課程中讓大家花了不少時間去討論、改進、完善自己的代碼。雖然剛畢業(yè)的同學(xué)對 于代碼質(zhì)量都很用心,但最終呈現(xiàn)出來的質(zhì)量仍然沒能達到“十分優(yōu)秀”的程度。 究其原因,主要是不了解好的代碼“應(yīng)該”是什么樣的。

2.什么是好代碼

寫代碼的第一步是理解什么是好代碼。在準(zhǔn)備bootcamp的課程的時候,我就為這個問題犯了難,我嘗試著用一些精確的定義區(qū)分出“優(yōu)等品”、“良品”、“不良品”;但是在總結(jié)的過程中,關(guān)于“什么是好代碼”的描述卻大多沒有可操作性

2.1.好代碼的定義

隨便從網(wǎng)上搜索了一下“優(yōu)雅的代碼”,找到了下面這樣的定義:

Bjarne Stroustrup,C++之父:

  • 邏輯應(yīng)該是清晰的,bug難以隱藏;

  • 依賴最少,易于維護;

  • 錯誤處理完全根據(jù)一個明確的策略;

  • 性能接近最佳化,避免代碼混亂和無原則的優(yōu)化;

  • 整潔的代碼只做一件事。

Grady Booch,《面向?qū)ο蠓治雠c設(shè)計》作者:

  • 整潔的代碼是簡單、直接的;

  • 整潔的代碼,讀起來像是一篇寫得很好的散文;

  • 整潔的代碼永遠不會掩蓋設(shè)計者的意圖,而是具有少量的抽象和清晰的控制行。

Michael Feathers,《修改代碼的藝術(shù)》作者:

  • 整潔的代碼看起來總是像很在乎代碼質(zhì)量的人寫的;

  • 沒有明顯的需要改善的地方;

  • 代碼的作者似乎考慮到了所有的事情。

看起來似乎說的都很有道理,可是實際評判的時候卻難以參考,尤其是對于新人來說,如何理解“簡單的、直接的代碼”或者“沒有明顯的需要改善的地方”?

而實踐過程中,很多同學(xué)也確實面對這種問題:對自己的代碼總是處在一種心里不踏實的狀態(tài),或者是自己覺得很好了,但是卻被其他人認(rèn)為很爛,甚至有幾次我和新同學(xué)因為代碼質(zhì)量的標(biāo)準(zhǔn)一連討論好幾天,卻誰也說服不了誰:我們都堅持自己對于好代碼的標(biāo)準(zhǔn)才是正確的。

在經(jīng)歷了無數(shù)次code review之后,我覺得這張圖似乎總結(jié)的更好一些:

代碼質(zhì)量的評價標(biāo)準(zhǔn)某種意義上有點類似于文學(xué)作品,比如對小說的質(zhì)量的評價主要來自于它的讀者,由個體主觀評價形成一個相對客觀的評價。并不是依靠字?jǐn)?shù),或者作者使用了哪些修辭手法之類的看似完全客觀但實際沒有什么意義的評價手段。

但代碼和小說還有些不一樣,它實際存在兩個讀者:計算機和程序員。就像上篇文章里說的,即使所有程序員都看不懂這段代碼,它也是可以被計算機理解并運行的。

所以對于代碼質(zhì)量的定義我需要于從兩個維度分析:主觀的,被人類理解的部分;還有客觀的,在計算機里運行的狀況。

既然存在主觀部分,那么就會存在個體差異,對于同一段代碼評價會因為看代碼的人的水平不同而得出不一樣的結(jié)論,這也是大多數(shù)新人面對的問題:他們沒有一個可以執(zhí)行的評價標(biāo)準(zhǔn),所以寫出來的代碼質(zhì)量也很難提高。

有些介紹代碼質(zhì)量的文章講述的都是傾向或者原則,雖然說的很對,但是實際指導(dǎo)作用不大。所以在這篇文章里我希望盡可能把評價代碼的標(biāo)準(zhǔn)用(我自認(rèn)為)與實際水平無關(guān)的評價方式表示出來。

2.2.可讀的代碼

在權(quán)衡很久之后,我決定把可讀性的優(yōu)先級排在前面:一個程序員更希望接手一個有bug但是看的懂的工程,還是一個沒bug但是看不懂的工程?如果是后者,可以直接關(guān)掉這個網(wǎng)頁,去做些對你來說更有意義的事情。

2.2.1.逐字翻譯

在很多跟代碼質(zhì)量有關(guān)的書里都強調(diào)了一個觀點:程序首先是給人看的,其次才是能被機器執(zhí)行,我也比較認(rèn)同這個觀點。在評價一段代碼能不能讓人看懂的 時候,我習(xí)慣讓作者把這段代碼逐字翻譯成中文,試著組成句子,之后把中文句子讀給另一個人沒有看過這段代碼的人聽,如果另一個人能聽懂,那么這段代碼的可 讀性基本就合格了。

用這種判斷方式的原因很簡單:其他人在理解一段代碼的時候就是這么做的。閱讀代碼的人會一個詞一個詞的閱讀,推斷這句話的意思,如果僅靠句子無法理 解,那么就需要聯(lián)系上下文理解這句代碼,如果簡單的聯(lián)系上下文也理解不了,可能還要掌握更多其它部分的細節(jié)來幫助推斷。大部分情況下,理解一句代碼在做什 么需要聯(lián)系的上下文越多,意味著代碼的質(zhì)量越差。

逐字翻譯的好處是能讓作者能輕易的發(fā)現(xiàn)那些只有自己知道的、沒有體現(xiàn)在代碼里的假設(shè)和可讀性陷阱。無法從字面意義上翻譯出原本意思的代碼大多都是爛代碼,比如“ms代表messageService“,或者“ms.proc()是發(fā)消息“,或者“tmp代表當(dāng)前的文件”。

2.2.2.遵循約定

約定包括代碼和文檔如何組織,注釋如何編寫,編碼風(fēng)格的約定等等,這對于代碼未來的維護很重要。對于遵循何種約定沒有一個強制的標(biāo)準(zhǔn),不過我更傾向于遵守更多人的約定。

與開源項目保持風(fēng)格一致一般來說比較靠譜,其次也可以遵守公司內(nèi)部的編碼風(fēng)格。但是如果公司內(nèi)部的編碼風(fēng)格和當(dāng)前開源項目的風(fēng)格沖突比較嚴(yán)重,往往代表著這個公司的技術(shù)傾向于封閉,或者已經(jīng)有些跟不上節(jié)奏了。

但是無論如何,遵守一個約定總比自己創(chuàng)造出一些規(guī)則要好很多,這降低了理解、溝通和維護的成本。如果一個項目自己創(chuàng)造出了一些奇怪的規(guī)則,可能意味著作者看過的代碼不夠多。

一個工程是否遵循了約定往往需要代碼閱讀者有一定經(jīng)驗,或者需要借助checkstyle這樣的靜態(tài)檢查工具。如果感覺無處下手,那么大部分情況下跟著google做應(yīng)該不會有什么大問題:可以參考google code style,其中一部分有對應(yīng)的中文版。

另外,沒有必要糾結(jié)于遵循了約定到底有什么收益,就好像走路是靠左好還是靠右好一樣,即使得出了結(jié)論也沒有什么意義,大部分約定只要遵守就可以了。

2.2.3.文檔和注釋

文檔和注釋是程序很重要的部分,他們是理解一個工程或項目的途徑之一。兩者在某些場景下定位會有些重合或者交叉(比如javadoc實際可以算是文檔)。

對于文檔的標(biāo)準(zhǔn)很簡單,能找到、能讀懂就可以了,一般來說我比較關(guān)心這幾類文檔:

  1. 對于項目的介紹,包括項目功能、作者、目錄結(jié)構(gòu)等,讀者應(yīng)該能3分鐘內(nèi)大致理解這個工程是做什么的。

  2. 針對新人的QuickStart,讀者按照文檔說明應(yīng)該能在1小時內(nèi)完成代碼構(gòu)建和簡單使用。

  3. 針對使用者的詳細說明文檔,比如接口定義、參數(shù)含義、設(shè)計等,讀者能通過文檔了解這些功能(或接口)的使用方法。

有一部分注釋實際是文檔,比如之前提到的javadoc。這樣能把源碼和注釋放在一起,對于讀者更清晰,也能簡化不少文檔的維護的工作。

還有一類注釋并不作為文檔的一部分,比如函數(shù)內(nèi)部的注釋,這類注釋的職責(zé)是說明一些代碼本身無法表達的作者在編碼時的思考,比如“為什么這里沒有做XXX”,或者“這里要注意XXX問題”。

一般來說我首先會關(guān)心注釋的數(shù)量:函數(shù)內(nèi)部注釋的數(shù)量應(yīng)該不會有很多,也不會完全沒有,個人的經(jīng)驗值是滾動幾屏幕看到一兩處左右比較正常。過多的話可能意味著代碼本身的可讀性有問題,而如果一點都沒有可能意味著有些隱藏的邏輯沒有說明,需要考慮適當(dāng)?shù)脑黾右稽c注釋了。

其次也需要考慮注釋的質(zhì)量:在代碼可讀性合格的基礎(chǔ)上,注釋應(yīng)該提供比代碼更多的信息。文檔和注釋并不是越多越好,它們可能會導(dǎo)致維護成本增加。關(guān)于這部分的討論可以參考簡潔部分的內(nèi)容。

2.2.4.推薦閱讀

《代碼整潔之道》

2.3.可發(fā)布的代碼

新人的代碼有一個比較典型的特征,由于缺少維護項目的經(jīng)驗,寫的代碼總會有很多考慮不到的地方。比如說測試的時候似乎沒什么異常,項目發(fā)布之后才發(fā)現(xiàn)有很多意料之外的狀況;而出了問題之后不知道從哪下手排查,或者僅能讓系統(tǒng)處于一個并不穩(wěn)定的狀態(tài),依靠一些巧合勉強運行。

2.3.1.處理異常

新手程序員普遍沒有處理異常的意識,但代碼的實際運行環(huán)境中充滿了異常:服務(wù)器會死機,網(wǎng)絡(luò)會超時,用戶會胡亂操作,不懷好意的人會惡意攻擊你的系統(tǒng)。

我對一段代碼異常處理能力的第一印象來自于單元測試的覆蓋率。大部分異常難以在開發(fā)或者測試環(huán)境里復(fù)現(xiàn),即使有專業(yè)的測試團隊也很難在集成測試環(huán)境中模擬所有的異常情況。

而單元測試可以比較簡單的模擬各種異常情況,如果一個模塊的單元測試覆蓋率連50%都不到,很難想象這些代碼考慮了異常情況下的處理,即使考慮了,這些異常處理的分支都沒有被驗證過,怎么指望實際運行環(huán)境中出現(xiàn)問題時表現(xiàn)良好呢?

2.3.2.處理并發(fā)

我收到的很多簡歷里都寫著:精通并發(fā)編程/熟悉多線程機制,諸如此類,跟他們聊的時候也說的頭頭是道,什么鎖啊互斥啊線程池啊同步啊信號量啊一堆一堆的名詞滔滔不絕。而給應(yīng)聘者一個實際場景,讓應(yīng)聘者寫一段很簡單的并發(fā)編程的小程序,能寫好的卻不多。

實際上并發(fā)編程也確實很難,如果說寫好同步代碼的難度為5,那么并發(fā)編程的難度可以達到100。這并不是危言聳聽,很多看似穩(wěn)定的程序,在面對并發(fā) 場景的時候仍然可能出現(xiàn)問題:比如最近我們就碰到了一個linux kernel在調(diào)用某個系統(tǒng)函數(shù)時由于同步問題而出現(xiàn)crash的情況。

而是否高質(zhì)量的實現(xiàn)并發(fā)編程的關(guān)鍵并不是是否應(yīng)用了某種同步策略,而是看代碼中是否保護了共享資源:

  • 局部變量之外的內(nèi)存訪問都有并發(fā)風(fēng)險(比如訪問對象的屬性,訪問靜態(tài)變量等)

  • 訪問共享資源也會有并發(fā)風(fēng)險(比如緩存、數(shù)據(jù)庫等)。

  • 被調(diào)用方如果不是聲明為線程安全的,那么很有可能存在并發(fā)問題(比如java的hashmap)。

  • 所有依賴時序的操作,即使每一步操作都是線程安全的,還是存在并發(fā)問題(比如先刪除一條記錄,然后把記錄數(shù)減一)。

前三種情況能夠比較簡單的通過代碼本身分辨出來,只要簡單培養(yǎng)一下自己對于共享資源調(diào)用的敏感度就可以了。

但是對于最后一種情況,往往很難簡單的通過看代碼的方式看出來,甚至出現(xiàn)并發(fā)問題的兩處調(diào)用并不是在同一個程序里(比如兩個系統(tǒng)同時讀寫一個數(shù)據(jù) 庫,或者并發(fā)的調(diào)用了一個程序的不同模塊等)。但是,只要是代碼里出現(xiàn)了不加鎖的,訪問共享資源的“先做A,再做B”之類的邏輯,可能就需要提高警惕了。

2.3.3.優(yōu)化性能

性能是評價程序員能力的一個重要指標(biāo),很多程序員也對程序的性能津津樂道。但程序的性能很難直接通過代碼看出來,往往要借助于一些性能測試工具,或者在實際環(huán)境中執(zhí)行才能有結(jié)果。

如果僅從代碼的角度考慮,有兩個評價執(zhí)行效率的辦法:

  • 算法的時間復(fù)雜度,時間復(fù)雜度高的程序運行效率必然會低。

  • 單步操作耗時,單步耗時高的操作盡量少做,比如訪問數(shù)據(jù)庫,訪問io等。

而實際工作中,也會見到一些程序員過于熱衷優(yōu)化效率,相對的會帶來程序易讀性的降低、復(fù)雜度提高、或者增加工期等等。對于這類情況,簡單的辦法是讓作者說出這段程序的瓶頸在哪里,為什么會有這個瓶頸,以及優(yōu)化帶來的收益。

當(dāng)然,無論是優(yōu)化不足還是優(yōu)化過度,判斷性能指標(biāo)最好的辦法是用數(shù)據(jù)說話,而不是單純看代碼,性能測試這部分內(nèi)容有些超出這篇文章的范圍,就不詳細展開了。

2.3.4.日志

日志代表了程序在出現(xiàn)問題時排查的難易程度,經(jīng)(jing)驗(chang)豐(cai)富(keng)的程序員大概都會遇到過這個場景:排查問題時就少一句日志,查不到某個變量的值不知道是什么,導(dǎo)致死活分析不出來問題到底出在哪。

對于日志的評價標(biāo)準(zhǔn)有三個:

  • 日志是否足夠,所有異常、外部調(diào)用都需要有日志,而一條調(diào)用鏈路上的入口、出口和路徑關(guān)鍵點上也需要有日志。

  • 日志的表達是否清晰,包括是否能讀懂,風(fēng)格是否統(tǒng)一等。這個的評價標(biāo)準(zhǔn)跟代碼的可讀性一樣,不重復(fù)了。

  • 日志是否包含了足夠的信息,這里包括了調(diào)用的上下文、外部的返回值,用于查詢的關(guān)鍵字等,便于分析信息。

對于線上系統(tǒng)來說,一般可以通過調(diào)整日志級別來控制日志的數(shù)量,所以打印日志的代碼只要不對閱讀造成障礙,基本上都是可以接受的。

2.3.5.擴展閱讀

《Release It!: Design and Deploy Production-Ready Software》(不要看中文版,翻譯的實在是太爛了)

Numbers Everyone Should Know

2.4.可維護的代碼

相對于前兩類代碼來說,可維護的代碼評價標(biāo)準(zhǔn)更模糊一些,因為它要對應(yīng)的是未來的情況,一般新人很難想象現(xiàn)在的一些做法會對未來造成什么影響。不過根據(jù)我的經(jīng)驗,一般來說,只要反復(fù)的提問兩個問題就可以了:

  • 他離職了怎么辦?

  • 他沒這么做怎么辦?

2.4.1.避免重復(fù)

幾乎所有程序員都知道要避免拷代碼,但是拷代碼這個現(xiàn)象還是不可避免的成為了程序可維護性的殺手。

代碼重復(fù)分為兩種:模塊內(nèi)重復(fù)和模塊間重復(fù)。無論何種重復(fù),都在一定程度上說明了程序員的水平有問題,模塊內(nèi)重復(fù)的問題更大一些,如果在同一個文件里都能出現(xiàn)大片重復(fù)的代碼,那表示他什么不可思議的代碼都有可能寫出來。

對于重復(fù)的判斷并不需要反復(fù)閱讀代碼,一般來說現(xiàn)代的IDE都提供了檢查重復(fù)代碼的工具,只需點幾下鼠標(biāo)就可以了。

除了代碼重復(fù)之外,很多熱衷于維護代碼質(zhì)量的程序員新人很容易出現(xiàn)另一類重復(fù):信息重復(fù)。

我見過一些新人喜歡在每行代碼前面寫一句注釋,比如:

  1. // 成員列表的長度>0并且<200 
  2. if(memberList.size() > 0 && memberList.size() < 200) { 
  3. // 返回當(dāng)前成員列表 
  4. return memberList; 

看起來似乎很好懂,但是幾年之后,這段代碼就變成了:

  1. // 成員列表的長度>0并且<200 
  2. if(memberList.size() > 0 && memberList.size() < 200 || (tmp.isOpen() && flag)) { 
  3. // 返回當(dāng)前成員列表 
  4. return memberList; 

再之后可能會改成這樣:

  1. // edit by axb 2015.07.30 
  2. // 成員列表的長度>0并且<200 
  3. //if(memberList.size() > 0 && memberList.size() < 200 || (tmp.isOpen() && flag)) { 
  4. // 返回當(dāng)前成員列表 
  5. // return memberList; 
  6. //} 
  7. if(tmp.isOpen() && flag) { 
  8. return memberList; 

隨著項目的演進,無用的信息會越積越多,最終甚至讓人無法分辨哪些信息是有效的,哪些是無效的。

如果在項目中發(fā)現(xiàn)好幾個東西都在做同一件事情,比如通過注釋描述代碼在做什么,或者依靠注釋替代版本管理的功能,那么這些代碼也不能稱為好代碼。

2.4.2.模塊劃分

模塊內(nèi)高內(nèi)聚與模塊間低耦合是大部分設(shè)計遵循的標(biāo)準(zhǔn),通過合理的模塊劃分能夠把復(fù)雜的功能拆分為更易于維護的更小的功能點。

一般來說可以從代碼長度上初步評價一個模塊劃分的是否合理,一個類的長度大于2000行,或者一個函數(shù)的長度大于兩屏幕都是比較危險的信號。

另一個能夠體現(xiàn)模塊劃分水平的地方是依賴。如果一個模塊依賴特別多,甚至出現(xiàn)了循環(huán)依賴,那么也可以反映出作者對模塊的規(guī)劃比較差,今后在維護這個工程的時候很有可能出現(xiàn)牽一發(fā)而動全身的情況。

一般來說有不少工具能提供依賴分析,比如IDEA中提供的Dependencies Analysis功能,學(xué)會這些工具的使用對于評價代碼質(zhì)量會有很大的幫助。

值得一提的是,絕大部分情況下,不恰當(dāng)?shù)哪K劃分也會伴隨著極低的單元測試覆蓋率:復(fù)雜模塊的單元測試非常難寫的,甚至是不可能完成的任務(wù)。所以直接查看單元測試覆蓋率也是一個比較靠譜的評價方式。

2.4.3.簡潔與抽象

只要提到代碼質(zhì)量,必然會提到簡潔、優(yōu)雅之類的形容詞。簡潔這個詞實際涵蓋了很多東西,代碼避免重復(fù)是簡潔、設(shè)計足夠抽象是簡潔,一切對于提高可維護性的嘗試實際都是在試圖做減法。

編程經(jīng)驗不足的程序員往往不能意識到簡潔的重要性,樂于搗鼓一些復(fù)雜的玩意并樂此不疲。但復(fù)雜是代碼可維護性的天敵,也是程序員能力的一道門檻。

跨過門檻的程序員應(yīng)該有能力控制逐漸增長的復(fù)雜度,總結(jié)和抽象出事物的本質(zhì),并體現(xiàn)到自己設(shè)計和編碼中。一個程序的生命周期也是在由簡入繁到化繁為簡中不斷迭代的過程。

對于這部分我難以總結(jié)出簡單易行的評價標(biāo)準(zhǔn),它更像是一種思維方式,除了要理解、還需要練習(xí)。多看、多想、多交流,很多時候可以簡化的東西會大大超出原先的預(yù)計。

2.2.4.推薦閱讀

《重構(gòu)-改善既有代碼的設(shè)計》

設(shè)計模式-可復(fù)用面向?qū)ο筌浖幕A(chǔ)》

《Software Architecture Patterns-Understanding Common Architecture Patterns and When to Use Them》

3.結(jié)語

這篇文章主要介紹了一些評價代碼質(zhì)量優(yōu)劣的手段,這些手段中,有些比較客觀,有些主觀性更強。之前也說過,對代碼質(zhì)量的評價是一件主觀的事情,這篇 文章里雖然列舉了很多評價手段。但是實際上,很多我認(rèn)為沒有問題的代碼也會被其他人吐槽,所以這篇文章只能算是初稿,更多內(nèi)容還需要今后繼續(xù)補充和完善。

雖然每個人對于代碼質(zhì)量評價的傾向都不一樣,但是總體來說評價代碼質(zhì)量的能力可以被比作程序員的“品味”,評價的準(zhǔn)確度會隨著自身經(jīng)驗的增加而增長。在這個過程中,需要隨時保持思考、學(xué)習(xí)和批判的精神。

下篇文章里,會談一談具體如何提高自己的代碼質(zhì)量。

 

責(zé)任編輯:王雪燕 來源: 2baxb
相關(guān)推薦

2015-09-14 09:28:47

2017-04-10 18:10:31

2015-07-23 09:30:43

爛代碼程序員

2012-05-01 08:06:49

手機

2021-03-18 16:05:20

SSD存儲故障

2009-02-19 10:21:00

路由多WAN口

2012-07-13 00:03:08

WEB前端開發(fā)WEB開發(fā)

2019-12-10 08:00:46

Kata容器Linux

2011-08-22 16:42:43

SqliteiPad

2012-01-02 19:30:22

iPad

2011-06-30 10:59:43

2011-07-19 15:33:57

iPhone

2022-09-09 08:08:28

開源項目服務(wù)

2011-07-04 13:51:02

QT 對象 模型

2011-08-01 17:31:25

Xcode開發(fā) Cocoa

2011-12-02 10:32:23

Java

2012-04-05 10:49:40

服務(wù)器SSL證書

2015-08-19 09:10:37

程序員面試

2021-05-17 08:18:35

Java內(nèi)存模型JMM

2012-05-01 21:27:55

圖標(biāo)
點贊
收藏

51CTO技術(shù)棧公眾號