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

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