0. 前言
程序員給人的印象大多都是蓬頭垢面,衣著隨意家夺。然而脱柱,我認(rèn)為一個優(yōu)秀的程序員對于代碼一定有他的審美。如果你做過很多項目拉馋,寫過多年的代碼榨为,對程序還僅僅是停留在能夠跑通,能實現(xiàn)某個功能就算完成了煌茴,對于代碼的是否足夠elegant沒有任何的追求随闺,我認(rèn)為你不過就是一個code monkey。其實市面上類似的書有很多蔓腐,例如《clean code》矩乐,這本書在深度上可能與《clean code》相比還有所不及,但在廣度上,遠(yuǎn)遠(yuǎn)超過了《clean code》散罕。因為它關(guān)注的是編碼本身分歇,所以并不局限于一種編程語言。作者對于代碼的精雕細(xì)琢已經(jīng)達(dá)到了一種令人發(fā)狂的地步欧漱,仿佛代碼本身就是一件藝術(shù)品职抡。如果你對于代碼的腐爛有著一種敏銳的嗅覺,并且具有一種強(qiáng)迫癥误甚,想要努力去重構(gòu)它缚甩,那么恭喜你,這本書適合你窑邦。最后我想引用軟件設(shè)計大師Martin Fowler在他的《Refactoring : Improving the Design of Existing Code》中的一句話:
任何一個傻瓜都能寫出計算機(jī)可以理解的程序蹄胰,只有寫出人類容易理解的程序才是優(yōu)秀的程序員。
1.把信息裝在名字里
無論是名字變量奕翔、函數(shù)還是類,都可以使用很多相同的原則浩蓉。我們喜歡把名字當(dāng)作一個小小的注釋派继。盡管空間不算很大,但選擇一個好名字可以讓它承載很多的信息捻艳。
1.1選擇專業(yè)的名詞
“把信息裝在名字中”包括要選擇非常專業(yè)的詞驾窟,并且避免使用“空洞”的詞,例如认轨,“get”這個詞就非常不專業(yè)绅络,例如下面的例子:
def getPage(url):...
“get”這個詞沒有表達(dá)出很多信息。這個方法是從本次緩存中得到一個頁面嘁字,還是從數(shù)據(jù)庫中恩急,或者從互聯(lián)網(wǎng)中,更專業(yè)的名詞可以使FetchPage()或者DownloadPage()纪蜒。
1.2 找到更優(yōu)表現(xiàn)力的詞
下面是一些例子衷恭,這些單詞更有表現(xiàn)力,可能適合你的語境:
單詞 | 更多選擇 |
---|---|
send | deliver纯续、dispatch随珠、announce、distribute猬错、route |
find | search窗看、extract、locate倦炒、recover |
start | launch显沈、create、begin析校、open |
make | create构罗、set up铜涉、build、generate遂唧、compose芙代、add、new |
1.3 避免像tmp和retval這樣泛泛的名字
使用像tmp盖彭、retval和foo這樣的名字往往是“我想不出名字”的托辭纹烹。
建議
retval這個名字沒有包含很多信息。用一個描述該變量的值的名字來代替它召边。
然而铺呵,在循環(huán)迭代器中,像i隧熙、j片挂、iter等名字常用作索引和循環(huán)迭代器。盡管這些名字很空泛贞盯,但是大家都知道它們的意思是“我是一個迭代器”音念。
1.4 用具體的名字代替抽象的名字
例如,假設(shè)你有一個內(nèi)部方法叫做serverCanStart()躏敢,它檢測服務(wù)是否可以監(jiān)聽某個TCP/IP端口闷愤。然而serverCanStart()有點抽象。canListenOnPort()就更具體一些件余。
1.5 為名字附帶更多信息
我們前面提到讥脐,一個變量名就像是一個小小的注釋。下表給出更多需要給名字附加上額外信息的例子:
情形 | 變量名 | 更好的名字 |
---|---|---|
一個“純文本”格式的密碼啼器,需要加密后才能進(jìn)一步使用 | password | plaintext_password |
一條用戶提供的注釋旬渠,需要轉(zhuǎn)義之后才能用于顯示 | comment | unescaped_comment |
已轉(zhuǎn)化為UTF-8格式的html字節(jié) | html | html_utf8 |
1.6 在小的作用域里可以使用短的名字
作用域小的標(biāo)識符不用帶上太多信息,因為所有信息(變量的類型镀首、它的初值坟漱、如何析構(gòu)等)都很容易看到,所以可以用很多的名字更哄。
1.7 利用名字的格式來傳遞含義
例如在Java中芋齿,通常以字母全部大寫加下劃線的形式表示常量(CONSTANT_NAME)。再比如成翩,給jQuery返回的結(jié)果通常會加上$作為前綴觅捆。
2. 不會誤解的名字
關(guān)鍵思想
要多問自己幾遍:“這個名字會被別人解讀成其他的含義嗎?”要仔細(xì)審視這個名字麻敌。
2.1 推薦用min和max來標(biāo)識(包含)的極限
加入你的購物車應(yīng)用程序最多不能超過10件物品:
MAX_ITEM_IN_CART = 10
if shopping_cart.num_items() > MAX_ITEM_IN_CART:
Error("Too many items in cart.")
2.2 推薦用begin和end來表示包含/排除范圍
因為對begin/end的使用是如此常見栅炒,至少在c++標(biāo)準(zhǔn)庫中是這樣的,還有大多數(shù)需要分片的數(shù)組也是這樣用的,它已經(jīng)是最好的選擇了赢赊。
2.3 給布爾值命名
當(dāng)為布爾值變量或者返回布爾值的函數(shù)選擇名字時乙漓,要確保返回true和false的意義很明確。通常來講释移,加上像is叭披、has、can玩讳、should這樣的詞涩蜘,可以把布爾值變得很明確。
2.4 與使用者的期望相匹配
很多程序員都習(xí)慣了把以get開始的方法當(dāng)作“輕量級訪問器”這樣的用法熏纯,它只是簡單地返回一個內(nèi)部成員變量同诫。如果違背這個習(xí)慣很可能會誤導(dǎo)用戶。
以下是一個用Java寫的例子樟澜,請不要這樣做:
public class StatisticsConllector {
public double getMean() {
// Iterate through all samples and retuan total / num_samples
}
}
在這個例子中误窖,getMean()的實現(xiàn)是要遍歷所有經(jīng)過的數(shù)據(jù)并同時計算中值。如果有大量的數(shù)據(jù)的話秩贰,這樣的一步可能會有很大的代價贩猎!但一個容易輕信的程序員可能會隨意地調(diào)用getMean(),還以為是個沒什么代價的調(diào)用萍膛。
3. 審美
好的源代碼應(yīng)當(dāng)“看上去養(yǎng)眼”。使用好的留白嚷堡、對齊及順序可以讓你的代碼更容易閱讀蝗罗。
確切地說,有三條原則:
- 使用一致的布局蝌戒,讓讀者很快就習(xí)慣這種風(fēng)格串塑。
- 讓相似的代碼看上去相似。
- 把相關(guān)的代碼行分組北苟,形成代碼塊桩匪。
4. 該寫什么樣的注釋
當(dāng)你寫代碼時,你的腦海里有很多有價值的信息友鼻。當(dāng)其他人讀你的代碼時傻昙,這些信息已經(jīng)丟失了,他們所見到的只是眼前的代碼彩扔。
關(guān)鍵思想
注釋的目的是盡量幫助讀者了解得和作者一樣多妆档。
4.1 什么不需要注釋
下面代碼中的注釋沒有任何價值:
# remove everything after the second '*'
name = '*'.join(line.split('*')[:2])
從技術(shù)上講,這里的注釋沒有表達(dá)出任何新信息虫碉。不要為那些從代碼本身就能快速推斷的事實寫注釋贾惦。
4.2 不要給不好的名字加注釋——應(yīng)該把名字改好
注釋不應(yīng)該應(yīng)用于粉飾不好的名字,我們完全可以用一個更加自我說明的名字。寫代碼的人常常把這條規(guī)則表述為:好代碼>壞代碼+好注釋
4.3 記錄你的思想
現(xiàn)在知道了什么不需要注釋须板,下面討論什么需要注釋碰镜。
很多好的注釋僅通過“記錄你的想法”就能得到,也就是那些你在寫代碼時有過的重要的想法习瑰。下面是一個例子:
//出乎意外的是绪颖,對于這些數(shù)據(jù)用二叉樹比用哈希表快40%
//哈希運算的代價比左/右比較大得多
這段注釋教會讀者一些事情,并且防止他們?yōu)闊o謂的優(yōu)化而浪費時間杰刽。
4.4 為代碼中的瑕疵寫注釋
代碼始終在演進(jìn)菠发,并且在這過程中肯定會有瑕疵。不要不好意思把這些瑕疵記錄下來贺嫂。
例如滓鸠,當(dāng)代碼需要改進(jìn)時:
// TODO:采用更快的算法
或者當(dāng)代碼沒有完成時:
// TODO(dustin):處理除JPEG以外的圖像格式
4.5 “全局觀”注釋
對于團(tuán)隊的新成員來講,最難的事情之一就是理解“全局觀”第喳,類之間如何交互糜俗,數(shù)據(jù)如何在整個系統(tǒng)中流動,以及入口點在哪里曲饱。設(shè)計系統(tǒng)的人經(jīng)常忘記給這些東西加注釋悠抹,“只緣身在此山中”。下面是一個文件級別注釋的簡單例子:
// 這個文件包含一些輔助函數(shù)扩淀,為我們的文件系統(tǒng)提供了更便利的接口
// 它處理了文件權(quán)限及其他基本的細(xì)節(jié)楔敌。
4.6 總結(jié)性注釋
全局觀注釋代表文件級別的注釋,就算在一個函數(shù)的內(nèi)部驻谆,寫一個總結(jié)性的注釋也是個不錯的注意卵凑,使讀者不至迷失在細(xì)節(jié)中。這段注釋巧妙的總結(jié)了其后的底層代碼:
#Find all the item that customers purchase for themselves
for customers_id in all_customers:
for sale in all_sales[customer_id].sales:
if sales.recipient == customer_id:
....