1. 什么是整潔代碼
我喜歡優(yōu)雅和高效的代碼昭娩。代碼邏輯應(yīng)當(dāng)直截了當(dāng)凛篙,叫缺陷難以隱藏;盡量減少依賴關(guān)系,使之便于維護题禀;依據(jù)某些分層戰(zhàn)略完善錯誤處理代碼鞋诗;性能調(diào)至最優(yōu)膀捷,省得引誘別人做沒規(guī)矩的優(yōu)化迈嘹,搞出一堆混亂來。整潔的代碼只做好一件事情全庸。 --Bjarne Stroustrup秀仲,C++語言發(fā)明者,C++ Programming Language(中譯版《C++程序設(shè)計語言》)一書作者
整潔的代碼簡單直接。整潔的代碼如同優(yōu)美的散文。整潔的代碼從不隱藏設(shè)計者的意圖卷胯,充滿了干凈利落的抽象和直接了當(dāng)?shù)目刂普Z句范删。 --Grady Booch,Object Oriented Analysis and Design with Applications(中譯版《面向?qū)ο蠓治雠c設(shè)計》)一書作者
整潔的代碼應(yīng)可由作者之外的開發(fā)者閱讀和增補狸演。 它應(yīng)有單元測試和驗收測試。它使用又意義的命名。它只提供一種而非多種做一件事的途徑炮障。它只有盡量少的依賴關(guān)系, 而且要明確地定義和提供清晰坤候、盡量少的API胁赢。代碼應(yīng)通過其字面表達含義,因為不同的語言導(dǎo)致并非所有必需信息均可通過代碼自身清晰表達白筹。--“老大”Dave Thomas智末,OTI公司創(chuàng)始人,Eclipse戰(zhàn)略教父
我可以列出我留意到的整潔代碼的所有特點徒河,但其中有一條是根本性的系馆。整潔的代碼總是看起來像是某位特別在意它的人寫的。幾乎沒有改進的余地顽照。代碼作者什么都想到了它呀,如果你企圖改進它,總會回到原點,贊嘆某人留給你的代碼——全心投入的某人留下的代碼纵穿。 --Michael Feathers下隧,Working Effectively with Legacy Code(中譯版《修改代碼的藝術(shù)》)一書作者。--
近年來谓媒,我開始研究貝克的簡單代碼規(guī)則淆院,差不多也都琢磨透了。簡單代碼句惯,依其重要順序:
- 能通過所有測試
- 沒有重復(fù)代碼
- 體現(xiàn)系統(tǒng)中的全部設(shè)計理念
- 包括盡量少的實體土辩, 比如類、方法抢野、函數(shù)等拷淘。
在以上諸項中,我最在意代碼重復(fù)指孤。如果同一段代碼反復(fù)出現(xiàn)启涯,就表示某種想法未在代碼中得到良好的體現(xiàn)。我盡力去找出到底那是什么恃轩,然后再盡力更清晰地表達出來结洼。
在我看來,有意義的命名是體現(xiàn)表達力的一種方式叉跛,我往往會修改好幾次才會定下名字來松忍。借助Eclipse這樣的現(xiàn)代編碼工具,重命名代價極低筷厘,所以我無所顧忌鸣峭。然而,表達力還不只體現(xiàn)在命名上酥艳。我也會檢查對象或方法是否想做的事太多摊溶。如果對象功能太多,最好是切分為兩個或多個對象玖雁。如果方法功能太多更扁,我總是使用抽取手段(Extract Method)重構(gòu)之,從而得到一個能較為清晰地說明自身功能的方法赫冬,以及另外數(shù)個說明如何實現(xiàn)這些功能的方法浓镜。
消除重復(fù)和提高表達力讓我在整潔代碼方面獲益良多,只要銘記這兩點劲厌,改進臟代碼時就會大有不同膛薛。不過,我時常關(guān)注的另一規(guī)則就不太好解釋了补鼻。
這么多年下來哄啄,我發(fā)現(xiàn)所有程序都由極為相似的元素構(gòu)成雅任。例如“在集合中查找某物”。不管是雇員記錄數(shù)據(jù)庫還是名-值對哈希表咨跌,或者某類條目的數(shù)組沪么,我們都會發(fā)現(xiàn)自己想要從集合中找到某一特定條目。一旦出現(xiàn)這種情況锌半,我通常會把實現(xiàn)手段封裝到更抽象的方法或類中禽车。這樣做好處多多。
可以先用某種簡單的手段刊殉,比如哈希表來實現(xiàn)這一功能殉摔,由于對搜索功能的引用指向了我那個小小的抽象,就能隨需應(yīng)變记焊,修改實現(xiàn)手段逸月。這樣就既能快速前進,又能為未來的修改預(yù)留余地遍膜。
另外碗硬,該集合抽象常常提醒我留意“真正”在發(fā)生的事,避免隨意實現(xiàn)集合行為捌归,因為我真正需要的不過是某種簡單的查找手段肛响。
減少重復(fù)代碼岭粤,提高表達力惜索,提早構(gòu)建簡單抽象。這就是我寫整潔代碼的方法剃浇。
--Ron Jeffries巾兆,Extreme Programming Installed(中譯版《極限編程實施》)以及Extreme Programming Adventures in C#(中譯版《C#極限編程探險》)作者。
Ron以寥寥數(shù)段文字概括了本書的全部內(nèi)容虎囚。不要重復(fù)代碼角塑,只做一件事,表達力淘讥,小規(guī)模抽象圃伶。該有的都有了。
如果每個例程都讓你感到深合己意蒲列,那就是整潔代碼窒朋。如果代碼讓編程語言看起來像是專為解決那個問題而存在,就可以稱之為漂亮的代碼蝗岖。 --Ward Cunningham侥猩,Wiki發(fā)明者,eXtreme Programming(極限編程)的創(chuàng)始人之一抵赢,Smalltalk語言和面向?qū)ο蟮乃枷腩I(lǐng)袖欺劳。所有在意代碼者的教父--
編程中的讀寫比例
在寫新代碼的時候唧取, 我們會一直讀舊代碼。 讀與寫花費時間的比例超過10:1划提!既然比例如此之高枫弟, 我們想讓讀的過程變得輕松, 即便那會使得編寫過程更難鹏往。沒有可能光寫不讀媒区,所以使之易讀實際也使之易寫。
童子軍軍規(guī)
讓營地比你來時更干凈掸犬。
如果每次嵌入時袜漩,代碼都比簽出時干凈,那么代碼就不會腐壞湾碎。 清理并不一定要花多少功夫宙攻, 也許只是改好一個變量名, 拆分一個有點過長的函數(shù)介褥, 消除一點點重復(fù)代碼座掘, 清理一個嵌套的if語句。
持續(xù)改進是專業(yè)性的內(nèi)在組成部分柔滔。
2. 有意義的命名
名副其實
bad:
int d; // 消逝的時間溢陪,以日記
good:
int elapsedTimeInDays;
用一個名副其實的函數(shù),掩蓋住魔術(shù)數(shù),例如:
good:
if ( cell.IsFlagged() )
bad:
if (cell[STATUS_VALUE] == FLAGGED)
避免誤導(dǎo)
程序員必須避免留下掩藏代碼本意的錯誤線索睛廊。
別用accountList來指定一組賬號形真,除非它真的是List類型。 用accountGroup或bunchOfAccounts, 甚至直接用accounts都會好一些超全。
做有意義的區(qū)分
good:
public static void copyChars(char source[], char destination[]) {
for (int i = 0; i < source.length; i++ ) {
destination[i] = source[i];
}
}
bad:
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++ ) {
a2[i] = a1[i];
}
}
廢話是另一種沒有意義的區(qū)分咆霜。 假設(shè)你有一個Product類型。 如果還有一個ProductInfo 或 ProductData類嘶朱, 那它們的名稱雖然不同蛾坯, 意思卻無區(qū)別。Info 和 Data 就像 a疏遏、an和 the 一樣脉课。
bad:
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
// 程序員怎么能知道該調(diào)用哪個函數(shù)呢。
要區(qū)分名稱财异,就要以讀者能鑒別不同之處的方式來區(qū)分倘零。
使用讀得出來的名稱
不要使用非恰當(dāng)?shù)挠⒄Z詞(不用讀出傻乎乎的自造詞)
bad:
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqpint = "102";
/* ... */
};
good:
class Customer {
private Date generationTimeStamp;
private Date modificationTimestamp;
private final String recordId = "102";
/* ... */
}
使用可搜索的名稱
bad:
for (int j=0; j<34; j++) {
s += (t[j] * 4)/5;
}
good:
int realDaysRerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for ( int j=0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimatej] * realDaysRerIdealDay ;
int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK );
sum += realTaskWeeks;
}
成員前綴
也不必使用 m_ 前綴來標(biāo)明成員變量。應(yīng)當(dāng)把類和函數(shù)做得足夠小宝当,消除對成員前綴的需要视事。
public class Part {
private String m_desc; // The testual description
void setName(String name) {
m_desc = name;
}
}
----------------------------------
public class Part{
String description;
void setDescription(String descripthion) {
this.description = description;
}
}
人們會很快學(xué)會無視前綴(或后綴), 只看到名稱中有意義的部分庆揩。 代碼讀得越多俐东,眼中就越?jīng)]有前綴跌穗。
接口和實現(xiàn)
如果你在做一個創(chuàng)建形狀用的抽象工廠(Abstract Factory)。 該工廠是個接口(Interface), 要用具體類來實現(xiàn)虏辫。 你怎么命名工廠和具體類呢蚌吸?
如何寫出好的函數(shù)
寫代碼和寫別的東西很像,在寫論文或者文章時砌庄,你先想什么就寫什么羹唠,然后再打磨它。初稿也許粗陋無序娄昆,你就斟酌推敲佩微,直至達到你心目中的樣子。
我寫函數(shù)時萌焰,一開始都冗長而復(fù)雜哺眯。有太多縮進和嵌套循環(huán)。有過長的參數(shù)列表扒俯。名稱時隨意取的奶卓,也會有重復(fù)的代碼。不過我會配上一套單元測試撼玄,覆蓋每行丑陋的代碼夺姑。 然后我打磨這些代碼,分解函數(shù)掌猛、修改名稱盏浙、消除重復(fù)。我縮短和重新安置方法留潦。有時我還拆散類只盹。同時保持測試通過辣往。
最后兔院,遵循本章列出的規(guī)則,我組裝好這些函數(shù)站削。我并不從一開始就按照規(guī)則寫函數(shù)坊萝。我想沒人做得到。
函數(shù)
1许起、短小 < 15行
函數(shù)的第一規(guī)則是短小十偶,第二規(guī)則是更短小。每個函數(shù)都只說一件事园细,每個函數(shù)都一目了然惦积。每個函數(shù)都依序把你帶到下一個函數(shù)。
2猛频、代碼塊和縮進(不超過三層)
if else while語句等狮崩,其中的代碼塊應(yīng)該只有一行蛛勉,即函數(shù)調(diào)用語句,以保持函數(shù)短小睦柴。
3诽凌、只做一件事
函數(shù)應(yīng)該只做一件事,做好這件事坦敌。只做這一件事侣诵。判斷函數(shù)是否不止做了一件事的方法,看是否能再抽出一個函數(shù)狱窘。函數(shù)中如果存在多個區(qū)段杜顺,表明函數(shù)做的事情不止一件。
4蘸炸、每個函數(shù)一個抽像層級哑舒。
每個函數(shù)一個抽像層級。確保函數(shù)只做一件事幻馁,函數(shù)中所有的語句都在一個抽象層級上洗鸵。函數(shù)中混雜不同抽象層級,往往讓人迷惑仗嗦,更惡劣的是膘滨,就像破損的窗戶,一旦細節(jié)與基礎(chǔ)概念混雜稀拐,更多的細節(jié)就會在函數(shù)中糾結(jié)起來火邓。
5、自頂向下閱讀代碼:向下規(guī)則
讓每個函數(shù)后面都跟著位于下一抽象層級的函數(shù)德撬,在查看函數(shù)列表時铲咨,就能依抽象層級向下閱讀。
6蜓洪、確保每個switch都埋藏在較低的抽象層級纤勒,而且永遠不重復(fù)
7、使用描述性的名稱
使用描述性的名稱:函數(shù)越短小隆檀,功能越集中摇天,就越便于取個好名字
長而具有描述性的名稱,比短而令人費解的名稱好恐仑。長而具有描述性的名稱泉坐,要比描述性的長注釋好。讓函數(shù)名稱中的多個單詞容易閱讀裳仆,然后使用這些單詞給函數(shù)取個能說清其功用的名稱腕让。別害怕花時間取名字,嘗試不同的名稱歧斟,實測閱讀效果纯丸。選擇描述性的名稱能理清你關(guān)于模塊的設(shè)計思路司训,并幫你改進。追索好名稱液南,往往導(dǎo)致對代碼的改善重構(gòu)壳猜。命名方式要保持一致。使用與模塊名一脈相承的短語滑凉、名詞和動詞給函數(shù)命名统扳。使用類似措辭,依序講出一個故事畅姊。
8咒钟、函數(shù)參數(shù),最好沒有若未,盡量避免三個朱嘴。
足夠特殊理由才能用三個以上參數(shù)。從測試角度看粗合,參數(shù)更叫人為難萍嬉。
9、標(biāo)識參數(shù) 隙疚,這是標(biāo)識函數(shù)不止做一件事情的明確信號壤追,應(yīng)該拆分。
10供屉、二元函數(shù)行冰,盡量改寫為一元函數(shù)
利用一些機制將其轉(zhuǎn)換為一元函數(shù)。(多個參數(shù)封裝成臨時類伶丐,不是一個好的解決方式)
11悼做、函數(shù)和參數(shù)應(yīng)當(dāng)形成一種非常良好的動/、名詞對形式
如assertEqual 改成assertExpectedEqualsActual(expected哗魂,actual)大大減輕記憶參數(shù)順序的負擔(dān)肛走。
12避免使用輸出參數(shù)
如果函數(shù)必須要修改某種狀態(tài),就修改所屬對象的狀態(tài)啡彬。
13羹与、分隔指令與詢問
函數(shù)要么做什么事 要么回答什么事,但二者不可得兼庶灿。
函數(shù)應(yīng)該修改某對象的狀態(tài),或是返回該對象的有關(guān)信息吃衅。兩樣都干常會導(dǎo)致混亂往踢。
14、使用異常替代返回錯誤碼徘层。
從指令式函數(shù)返回錯誤碼輕微違反了指令與詢問分隔的規(guī)則峻呕。鼓勵了在if語句判斷中把指令當(dāng)做表達式使用利职。另一方面,如果使用異常替代返回錯誤碼瘦癌,錯誤處理代碼就能從主路徑徑代碼中分離出來猪贪,得到簡化。
15讯私、抽離Try/Catch代碼塊热押。
Try/Catch代碼丑陋不堪。他們搞亂了代碼結(jié)構(gòu)斤寇,把錯誤處理與正常流程混為一談桶癣。把try和catch代碼塊的主體部分抽離出來,另外形成函數(shù)娘锁。
函數(shù)應(yīng)該只做一件事牙寞。錯誤處理就是一件事。因此莫秆,處理錯誤的函數(shù)不應(yīng)該做其他的事间雀,如果關(guān)鍵字try在某個函數(shù)中存在,它就應(yīng)該是這個函數(shù)的第一個單詞镊屎,而且在catch/fianlly代碼塊后面也不應(yīng)該有其他內(nèi)容雷蹂。
16、別重復(fù)自己
重復(fù)可能是軟件中一切邪惡的根源杯道。許多原則與實踐規(guī)則都是為控制與消除重復(fù)而創(chuàng)建匪煌。如面向?qū)ο髮⒋a集中到積累,避免冗余党巾。