代碼簡潔之道 - 筆記

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集中到積累,避免冗余党巾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萎庭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子齿拂,更是在濱河造成了極大的恐慌驳规,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件署海,死亡現(xiàn)場離奇詭異吗购,居然都是意外死亡,警方通過查閱死者的電腦和手機砸狞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門捻勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刀森,你說我怎么就攤上這事踱启。” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵埠偿,是天一觀的道長透罢。 經(jīng)常有香客問我,道長冠蒋,這世上最難降的妖魔是什么羽圃? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮抖剿,結(jié)果婚禮上朽寞,老公的妹妹穿的比我還像新娘。我一直安慰自己牙躺,他們只是感情好愁憔,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著孽拷,像睡著了一般吨掌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脓恕,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天膜宋,我揣著相機與錄音,去河邊找鬼炼幔。 笑死秋茫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的乃秀。 我是一名探鬼主播肛著,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跺讯!你這毒婦竟也來了枢贿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刀脏,失蹤者是張志新(化名)和其女友劉穎局荚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愈污,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡耀态,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了暂雹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片首装。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖擎析,靈堂內(nèi)的尸體忽然破棺而出簿盅,到底是詐尸還是另有隱情挥下,我是刑警寧澤揍魂,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布桨醋,位于F島的核電站,受9級特大地震影響现斋,放射性物質(zhì)發(fā)生泄漏喜最。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一庄蹋、第九天 我趴在偏房一處隱蔽的房頂上張望瞬内。 院中可真熱鬧,春花似錦限书、人聲如沸虫蝶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽能真。三九已至,卻和暖如春扰柠,著一層夾襖步出監(jiān)牢的瞬間粉铐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工卤档, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝙泼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓劝枣,卻偏偏與公主長得像汤踏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舔腾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容