
如果您想真正學(xué)習(xí),最好的方法是閱讀Robert C Martin的Clean Code。基本上,它普遍推薦給亞馬遜的新員工。
中文版由人民郵電出版社異步社區(qū)出版,中文名:《代碼整潔之道》,作者:Robert,C,Martin。

學(xué)寫整潔代碼很難。它可不止于要求你掌握原則和模式。你得在這上面花工夫。你須自行實(shí)踐,且體驗(yàn)自己的失敗。你須觀察他人的實(shí)踐與失敗。你須看看別人是怎樣蹣跚學(xué)步,再轉(zhuǎn)頭研究他們的路數(shù)。你須看看別人是如何絞盡腦汁做出決策,又是如何為錯(cuò)誤決策付出代價(jià)。
閱讀本書要多用心思。這可不是那種降落前就能讀完的“感覺不錯(cuò)”的飛機(jī)書。本書要讓你用功,而且是非常用功。如何用功?閱讀代碼——大量代碼。而且你要去琢磨某段代碼好在什么地方、壞在什么地方。在我們分解,而后組合模塊時(shí),你得亦步亦趨地跟上。這得花些工夫,不過值得一試。
本書大致可分為3個(gè)部分。前幾章介紹編寫整潔代碼的原則、模式和實(shí)踐。這部分有相當(dāng)多的示例代碼,讀起來頗具挑戰(zhàn)性。讀完這幾章,就為閱讀第2部分做好了準(zhǔn)備。如果你就此止步,只能祝你好運(yùn)啦!
第2部分最需要花工夫。這部分包括幾個(gè)復(fù)雜性不斷增加的案例研究。每個(gè)案例都清理一些代碼——把有問題的代碼轉(zhuǎn)化為問題少一些的代碼。這部分極為詳細(xì)。你的思維要在講解和代碼段之間跳來跳去。你得分析和理解那些代碼,琢磨每次修改的來龍去脈。
你付出的勞動(dòng)將在第3部分得到回報(bào)。這部分只有一章,列出從上述案例研究中得到的啟示和靈感。在遍覽和清理案例中的代碼時(shí),我們把每個(gè)操作理由記錄為一種啟示或靈感。我們嘗試去理解自己對(duì)閱讀和修改代碼的反應(yīng),盡力了解為什么會(huì)有這樣的感受、為什么會(huì)如此行事。結(jié)果得到了一套描述在編寫、閱讀、清理代碼時(shí)思維方式的知識(shí)庫。
如果你在閱讀第2部分的案例研究時(shí)沒有好好用功,那么這套知識(shí)庫對(duì)你來說可能所值無幾。在這些案例研究中,每次修改都仔細(xì)注明了相關(guān)啟示的標(biāo)號(hào)。這些標(biāo)號(hào)用方括號(hào)標(biāo)出,如:[H22]。由此你可以看到這些啟示在何種環(huán)境下被應(yīng)用和編寫。啟示本身不值錢,啟示與案例研究中清理代碼的具體決策之間的關(guān)系才有價(jià)值。
如果你跳過案例研究部分,只閱讀了第1部分和第3部分,那就不過是又看了一本關(guān)于寫出好軟件的“感覺不錯(cuò)”的書。但如果你肯花時(shí)間琢磨那些案例,亦步亦趨——站在作者的角度,迫使自己以作者的思維路徑考慮問題,就能更深刻地理解這些原則、模式、實(shí)踐和啟示。這樣的話,就像一個(gè)熟練地掌握了騎車的技術(shù)后,自行車就如同其身體的延伸部分那樣;對(duì)你來說,本書所介紹的整潔代碼的原則、模式、實(shí)踐和啟示就成為了本身具有的技藝,而不再是“感覺不錯(cuò)”的知識(shí)。
干貨分享:第12章 迭進(jìn)
12.1 通過迭進(jìn)設(shè)計(jì)達(dá)到整潔目的
假使有4條簡(jiǎn)單的規(guī)矩,跟著做就能幫助你創(chuàng)建優(yōu)良的設(shè)計(jì),會(huì)如何?假使遵循這些規(guī)矩你就能洞見代碼的結(jié)構(gòu)和設(shè)計(jì),更輕易地應(yīng)用SRP和DIP之類原則,又會(huì)如何?假使這4條規(guī)則有利于良好的設(shè)計(jì)“浮現(xiàn)”出來,又會(huì)如何?
我們中的許多人認(rèn)為,Kent Beck關(guān)于簡(jiǎn)單設(shè)計(jì)[1]的四條規(guī)則,對(duì)于創(chuàng)建具有良好設(shè)計(jì)的軟件有著莫大的幫助。
- 據(jù)Kent所述,只要遵循以下規(guī)則,設(shè)計(jì)就能變得“簡(jiǎn)單”:
- 運(yùn)行所有測(cè)試;
- 不可重復(fù);
- 表達(dá)了程序員的意圖;
- 盡可能減少類和方法的數(shù)量;
- 以上規(guī)則按其重要程度排列。
12.2 簡(jiǎn)單設(shè)計(jì)規(guī)則1:運(yùn)行所有測(cè)試
設(shè)計(jì)必須制造出如預(yù)期一般工作的系統(tǒng),這是首要因素。系統(tǒng)也許有一套絕佳設(shè)計(jì),但如果缺乏驗(yàn)證系統(tǒng)是否真按預(yù)期那樣工作的簡(jiǎn)單方法,那就無異于紙上談兵。
全面測(cè)試并持續(xù)通過所有測(cè)試的系統(tǒng),就是可測(cè)試的系統(tǒng)。看似淺顯,但卻重要。不可測(cè)試的系統(tǒng)同樣不可驗(yàn)證。不可驗(yàn)證的系統(tǒng),絕不應(yīng)部署。
幸運(yùn)的是,只要系統(tǒng)可測(cè)試,就會(huì)導(dǎo)向保持類短小且目的單一的設(shè)計(jì)方案。遵循SRP的類,測(cè)試起來較為簡(jiǎn)單。測(cè)試編寫得越多,就越能持續(xù)走向編寫較易測(cè)試的代碼。所以,確保系統(tǒng)完全可測(cè)試能幫助我們創(chuàng)建更好的設(shè)計(jì)。
緊耦合的代碼難以編寫測(cè)試。同樣,編寫測(cè)試越多,就越會(huì)遵循DIP之類規(guī)則,使用依賴注入、接口和抽象等工具盡可能減少耦合。如此一來,設(shè)計(jì)就有長(zhǎng)足進(jìn)步。
遵循有關(guān)編寫測(cè)試并持續(xù)運(yùn)行測(cè)試的簡(jiǎn)單、明確的規(guī)則,系統(tǒng)就會(huì)更貼近OO低耦合度、高內(nèi)聚度的目標(biāo)。編寫測(cè)試引致更好的設(shè)計(jì)。
12.3 簡(jiǎn)單設(shè)計(jì)規(guī)則2~4:重構(gòu)
有了測(cè)試,就能保持代碼和類的整潔,方法就是遞增式地重構(gòu)代碼。添加了幾行代碼后,就要暫停,琢磨一下變化了的設(shè)計(jì)。設(shè)計(jì)退步了嗎?如果是,就要清理它,并且運(yùn)行測(cè)試,保證沒有破壞任何東西。測(cè)試消除了對(duì)清理代碼就會(huì)破壞代碼的恐懼。
在重構(gòu)過程中,可以應(yīng)用有關(guān)優(yōu)秀軟件設(shè)計(jì)的一切知識(shí)。提升內(nèi)聚性,降低耦合度,切分關(guān)注面,模塊化系統(tǒng)性關(guān)注面,縮小函數(shù)和類的尺寸,選用更好的名稱,如此等等。這也是應(yīng)用簡(jiǎn)單設(shè)計(jì)后三條規(guī)則的地方:消除重復(fù),保證表達(dá)力,盡可能減少類和方法的數(shù)量。
12.4 不可重復(fù)
重復(fù)是擁有良好設(shè)計(jì)系統(tǒng)的大敵。它代表著額外的工作、額外的風(fēng)險(xiǎn)和額外且不必要的復(fù)雜度。重復(fù)有多種表現(xiàn)。極其雷同的代碼行當(dāng)然是重復(fù)。類似的代碼往往可以調(diào)整得更相似,這樣就能更容易地進(jìn)行重構(gòu)。重復(fù)也有實(shí)現(xiàn)上的重復(fù)等其他一些形態(tài)。例如,在某個(gè)群集類中可能會(huì)有兩個(gè)方法:
int size() {}
boolean isEmpty() {}
這兩個(gè)方法可以分別實(shí)現(xiàn)。isEmpty方法跟蹤一個(gè)布爾值,而size方法則跟蹤一個(gè)計(jì)數(shù)器。或者,也可以通過在isEmpty中使用size方法來消除重復(fù):
boolean isEmpty() {
return 0 == size();
}
要想創(chuàng)建整潔的系統(tǒng),需要有消除重復(fù)的意愿,即便對(duì)于短短幾行也是如此。例如以下代碼:
public void scaleToOneDimension(
float desiredDimension, float imageDimension) {
if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
RenderedOp newImage = ImageUtilities.getScaledImage(
image, scalingFactor, scalingFactor);
image.dispose();
System.gc();
image = newImage;
}
public synchronized void rotate(int degrees) {
RenderedOp newImage = ImageUtilities.getRotatedImage(
image, degrees);
image.dispose();
System.gc();
image = newImage;
}
要保持系統(tǒng)整潔,應(yīng)該消除scaleToOneDimension和rotate方法里面的少量重復(fù):
public void scaleToOneDimension(
float desiredDimension, float imageDimension) {
if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
replaceImage(ImageUtilities.getScaledImage(
image, scalingFactor, scalingFactor));
}
public synchronized void rotate(int degrees) {
replaceImage(ImageUtilities.getRotatedImage(image, degrees));
}
private void replaceImage(RenderedOp newImage) {
image.dispose();
System.gc();
image = newImage;
}
做了一點(diǎn)點(diǎn)共性抽取后,我們意識(shí)到已經(jīng)違反了SRP原則。所以,可以把一個(gè)新方法分解到另外的類中,從而提升其可見性。團(tuán)隊(duì)中的其他成員也許會(huì)發(fā)現(xiàn)進(jìn)一步抽象新方法的機(jī)會(huì),并且在其他場(chǎng)景中復(fù)用之。“小規(guī)模復(fù)用”可大量降低系統(tǒng)復(fù)雜性。要想實(shí)現(xiàn)大規(guī)模復(fù)用,必須理解如何實(shí)現(xiàn)小規(guī)模復(fù)用。
模板方法模式[2]是一種移除高層級(jí)重復(fù)的通用技巧。例如:
public class VacationPolicy {
public void accrueUSDivisionVacation() {
// code to calculate vacation based on hours worked to date
// ...
// code to ensure vacation meets US minimums
// ...
// code to Apply vaction to payroll record
// ...
}
public void accrueEUDivisionVacation() {
// code to calculate vacation based on hours worked to date
// ...
// code to ensure vacation meets EU minimums
// ...
// code to apply vaction to payroll record
// ...
}
}
除了計(jì)算法定最少數(shù)量假期的部分,accrueUSDivisionVacation和accrueEuropeanDivision Vacation中有大量代碼雷同。那部分的算法,依據(jù)員工類型而變。
可以通過應(yīng)用模板方法模式來消除明顯的重復(fù)。
abstract public class VacationPolicy {
public void accrueVacation() {
calculateBaseVacationHours();
alterForLegalMinimums();
applyToPayroll();
}
private void calculateBaseVacationHours() { /* ... */ };
abstract protected void alterForLegalMinimums();
private void applyToPayroll() { /* ... */ };
}
public class USVacationPolicy extends VacationPolicy {
@Override protected void alterForLegalMinimums() {
// US specific logic
}
}
public class EUVacationPolicy extends VacationPolicy {
@Override protected void alterForLegalMinimums() {
// EU specific logic
}
}
子類填充了accrueVacation算法中的“空洞”,提供不重復(fù)的信息。
12.5 表達(dá)力
我們中的大多數(shù)人都經(jīng)歷過費(fèi)解代碼的糾纏。我們中的許多人自己就編寫過費(fèi)解的代碼。寫出自己能理解的代碼很容易,因?yàn)樵趯戇@些代碼時(shí),我們正深入于要解決的問題中。代碼的其他維護(hù)者不會(huì)那么深入,也就不易理解代碼。
軟件項(xiàng)目的主要成本在于長(zhǎng)期維護(hù)。為了在修改時(shí)盡量降低出現(xiàn)缺陷的可能性,很有必要理解系統(tǒng)是做什么的。當(dāng)系統(tǒng)變得越來越復(fù)雜,開發(fā)者就需要越來越多的時(shí)間來理解它,而且也極有可能誤解。所以,代碼應(yīng)當(dāng)清晰地表達(dá)其作者的意圖。作者把代碼寫得越清晰,其他人花在理解代碼上的時(shí)間也就越少,從而減少缺陷,縮減維護(hù)成本。
可以通過選用好名稱來表達(dá)。我們想要聽到好類名和好函數(shù)名,而且在查看其權(quán)責(zé)時(shí)不會(huì)大吃一驚。
也可以通過保持函數(shù)和類尺寸短小來表達(dá)。短小的類和函數(shù)通常易于命名,易于編寫,易于理解。
還可以通過采用標(biāo)準(zhǔn)命名法來表達(dá)。例如,設(shè)計(jì)模式很大程度上就關(guān)乎溝通和表達(dá)。通過在實(shí)現(xiàn)這些模式的類的名稱中采用標(biāo)準(zhǔn)模式名,例如COMMAND或VISITOR,就能充分地向其他開發(fā)者描述你的設(shè)計(jì)。
編寫良好的單元測(cè)試也具有表達(dá)性。測(cè)試的主要目的之一就是通過實(shí)例起到文檔的作用。讀到測(cè)試的人應(yīng)該能很快理解某個(gè)類是做什么的。
不過,做到有表達(dá)力的最重要方式卻是嘗試。有太多時(shí)候,我們寫出能工作的代碼,就轉(zhuǎn)移到下一個(gè)問題上,沒有下足功夫調(diào)整代碼,讓后來者易于閱讀。記住,下一位讀代碼的人最有可能是你自己。
所以,多少尊重一下你的手藝吧。花一點(diǎn)點(diǎn)時(shí)間在每個(gè)函數(shù)和類上。選用較好的名稱,將大函數(shù)切分為小函數(shù),時(shí)時(shí)照拂自己創(chuàng)建的東西。用心是最珍貴的資源。
12.6 盡可能少的類和方法
即便是消除重復(fù)、代碼表達(dá)力和SRP等最基礎(chǔ)的概念也會(huì)被過度使用。為了保持類和函數(shù)短小,我們可能會(huì)造出太多的細(xì)小類和方法。所以這條規(guī)則也主張函數(shù)和類的數(shù)量要少。
類和方法的數(shù)量太多,有時(shí)是由毫無意義的教條主義導(dǎo)致的。例如,某個(gè)編碼標(biāo)準(zhǔn)就堅(jiān)稱應(yīng)當(dāng)為每個(gè)類創(chuàng)建接口。也有開發(fā)者認(rèn)為,字段和行為必須切分到數(shù)據(jù)類和行為類中。應(yīng)該抵制這類教條,采用更實(shí)用的手段。
我們的目標(biāo)是在保持函數(shù)和類短小的同時(shí),保持整個(gè)系統(tǒng)短小精悍。不過要記住,這在關(guān)于簡(jiǎn)單設(shè)計(jì)的四條規(guī)則里面是優(yōu)先級(jí)最低的一條。所以,盡管使類和函數(shù)的數(shù)量盡量少是很重要的,但更重要的卻是測(cè)試、消除重復(fù)和表達(dá)力。
12.7 小結(jié)
有沒有能替代經(jīng)驗(yàn)的一套簡(jiǎn)單實(shí)踐手段呢?當(dāng)然不會(huì)有。另一方面,本章中寫到的實(shí)踐來自于本書作者數(shù)十年經(jīng)驗(yàn)的精練總結(jié)。遵循簡(jiǎn)單設(shè)計(jì)的實(shí)踐手段,開發(fā)者不必經(jīng)年學(xué)習(xí)就能掌握好的原則和模式。