您的位置:首頁(yè) > 熱點(diǎn) >

《代碼整潔之道》精讀與演繹】之四 優(yōu)秀代碼的格式準(zhǔn)則

這篇文章將與大家一起聊一聊,書(shū)寫(xiě)代碼過(guò)程中一些良好的格式規(guī)范。

一、引言

以下引言的內(nèi)容,有必要伴隨這個(gè)系列的每一次更新,這次也不例外。

代碼整潔之道》這本書(shū)提出了一個(gè)觀(guān)點(diǎn):代碼質(zhì)量與其整潔度成正比,干凈的代碼,既在質(zhì)量上可靠,也為后期維護(hù)、升級(jí)奠定了良好基礎(chǔ)。書(shū)中介紹的規(guī)則均來(lái)自作者多年的實(shí)踐經(jīng)驗(yàn),涵蓋從命名到重構(gòu)的多個(gè)編程方面,雖為一“家”之言,然誠(chéng)有可資借鑒的價(jià)值。

但我們知道,很多時(shí)候,理想很豐滿(mǎn),現(xiàn)實(shí)很骨感,也知道人在江湖,身不由己。因?yàn)轫?xiàng)目的緊迫性,需求的多樣性,我們無(wú)法時(shí)時(shí)刻刻都寫(xiě)出整潔的代碼,保持自己輸出的都是高質(zhì)量、優(yōu)雅的代碼。

但若我們理解了代碼整潔之道的精髓,我們會(huì)知道怎樣讓自己的代碼更加優(yōu)雅、整潔、易讀、易擴(kuò)展,知道真正整潔的代碼應(yīng)該是怎么樣的,也許就會(huì)漸漸養(yǎng)成持續(xù)輸出整潔代碼的習(xí)慣。

而且或許你會(huì)發(fā)現(xiàn),若你一直保持輸出整潔代碼的習(xí)慣,長(zhǎng)期來(lái)看,會(huì)讓你的整體效率和代碼質(zhì)量大大提升。

二、本文涉及知識(shí)點(diǎn)思維導(dǎo)圖

國(guó)際慣例,先放出這篇文章所涉及內(nèi)容知識(shí)點(diǎn)的一張思維導(dǎo)圖,就開(kāi)始正文。大家若是疲于閱讀文章正文,直接看這張圖,也是可以Get到本文的主要知識(shí)點(diǎn)的大概。

三、優(yōu)秀代碼的書(shū)寫(xiě)格式準(zhǔn)則

1 像報(bào)紙一樣一目了然

想想那些閱讀量巨大的報(bào)紙文章。你從上到下閱讀。在頂部,你希望有個(gè)頭條,告訴你故事主題,好讓你決定是否要讀下去。第一段是整個(gè)故事的大綱,給出粗線(xiàn)條概述,但隱藏了故事細(xì)節(jié)。接著讀下去,細(xì)節(jié)漸次增加,直至你了解所有的日期、名字、引語(yǔ)、說(shuō)話(huà)及其他細(xì)節(jié)。

優(yōu)秀的源文件也要像報(bào)紙文章一樣。名稱(chēng)應(yīng)當(dāng)簡(jiǎn)單并且一目了然,名稱(chēng)本身應(yīng)該足夠告訴我們是否在正確的模塊中。源文件最頂部應(yīng)該給出高層次概念和算法。細(xì)節(jié)應(yīng)該往下漸次展開(kāi),直至找到源文件中最底層的函數(shù)和細(xì)節(jié)。

2 恰如其分的注釋

帶有少量注釋的整潔而有力的代碼,比帶有大量注釋的零碎而復(fù)雜的代碼更加優(yōu)秀。

我們知道,注釋是為代碼服務(wù)的,注釋的存在大多數(shù)原因是為了代碼更加易讀,但注釋并不能美化糟糕的代碼。

另外,注意一點(diǎn)。注釋存在的時(shí)間越久,就會(huì)離其所描述的代碼的意義越遠(yuǎn),越來(lái)越變得全然錯(cuò)誤,因?yàn)榇蠖鄶?shù)程序員們不能堅(jiān)持(或者因?yàn)橥?去維護(hù)注釋。

當(dāng)然,教學(xué)性質(zhì)的代碼,多半是注釋越詳細(xì)越好。

3 合適的單文件行數(shù)

盡可能用幾百行以?xún)?nèi)的單文件來(lái)構(gòu)造出出色的系統(tǒng),因?yàn)槎涛募ǔ1乳L(zhǎng)文件更易于理解。當(dāng)然,和之前的一些準(zhǔn)則一樣,只是提供大方向,并非不可違背。

例如,《代碼整潔之道》第五章中提到的FitNess系統(tǒng),就是由大多數(shù)為200行、最長(zhǎng)500行的單個(gè)文件來(lái)構(gòu)造出總長(zhǎng)約5萬(wàn)行的出色系統(tǒng)。

4 合理地利用空白行

古詩(shī)中有留白,代碼的書(shū)寫(xiě)中也要有適當(dāng)?shù)牧舭?,也就是空白行?/p>

在每個(gè)命名空間、類(lèi)、函數(shù)之間,都需用空白行隔開(kāi)(應(yīng)該大家在學(xué)編程之初,就早有遵守)。這條極其簡(jiǎn)單的規(guī)則極大地影響到了代碼的視覺(jué)外觀(guān)。每個(gè)空白行都是一條線(xiàn)索,標(biāo)識(shí)出新的獨(dú)立概念。

其實(shí),在往下讀代碼時(shí),你會(huì)發(fā)現(xiàn)你的目光總停留于空白行之后的那一行。用空白行隔開(kāi)每個(gè)命名空間、類(lèi)、函數(shù),代碼的可讀性會(huì)大大提升。

5 讓緊密相關(guān)的代碼相互靠近

如果說(shuō)空白行隔開(kāi)了概念,那么靠近的代碼行則暗示了他們之間的緊密聯(lián)系。所以,緊密相關(guān)的代碼應(yīng)該相互靠近。

舉個(gè)反例(代碼段1):

[csharp] view plain copy print?

public class ReporterConfig

{

/**

* The class name of the reporter listener

*/

private String m_className;

/**

* The properties of the reporter listener

*/

private Listm_properties = new ArrayList();

public void addProperty(Property property)

{

m_properties.add(property);

}

}

再看個(gè)正面示例(代碼段2):

[cpp] view plain copy print?

public class ReporterConfig

{

private String m_className;

private Listm_properties = new ArrayList();

public void addProperty(Property property)

{

m_properties.add(property);

}

}

以上這段正面示例(代碼段2)比反例(代碼段1)中的代碼好太多,它正好一覽無(wú)遺,一眼就能看這個(gè)是有兩個(gè)變量和一個(gè)方法的類(lèi)。而再看看反例,注釋簡(jiǎn)直畫(huà)蛇添足,隔斷了兩個(gè)實(shí)體變量間的聯(lián)系,我們不得不移動(dòng)頭部和眼球,才能獲得相同的理解度。

6 基于關(guān)聯(lián)的代碼分布

關(guān)系密切的概念應(yīng)該相互靠近。對(duì)于那些關(guān)系密切、放置于同一源文件中的概念,他們之間的區(qū)隔應(yīng)該成為對(duì)相互的易懂度有多重要的衡量標(biāo)準(zhǔn)。應(yīng)該避免迫使讀者在源文件和類(lèi)中跳來(lái)跳去。變量的聲明應(yīng)盡可能靠近其使用位置。

對(duì)于大多數(shù)短函數(shù),函數(shù)中的本地變量應(yīng)當(dāng)在函數(shù)的頂部出現(xiàn)。例如如下代碼中的is變量的聲明:

[cpp] view plain copy print?

private static void readPreferences()

{

InputStream is= null;

try

{

is= new FileInputStream(getPreferencesFile());

setPreferences(new Properties(getPreferences()));

getPreferences().load(is);

}

catch (IOException e)

{

DoSomeThing();

}

}

而循環(huán)中的控制變量應(yīng)該總在循環(huán)語(yǔ)句中聲明,例如如下代碼中each變量的聲明:

[cpp] view plain copy print?

public int countTestCases()

{

int count = 0;

for (Test each : tests)

count += each.countTestCases();

return count;

}

在某些較長(zhǎng)的函數(shù)中,變量也可能在某代碼塊的頂部,或在循環(huán)之前聲明。例如如下代碼中tr變量的聲明:

[cpp] view plain copy print?

...

for (XmlTest test : m_suite.getTests())

{

TestRunner tr = m_runnerFactory.newTestRunner(this, test);

tr.addListener(m_textReporter);

m_testRunners.add(tr);

invoker = tr.getInvoker();

for (ITestNGMethod m : tr.getBeforeSuiteMethods())

{

beforeSuiteMethods.put(m.getMethod(), m);

}

for (ITestNGMethod m : tr.getAfterSuiteMethods())

{

afterSuiteMethods.put(m.getMethod(), m);

}

}

...

另外,實(shí)體變量應(yīng)當(dāng)在類(lèi)的頂部聲明(也有一些流派喜歡將實(shí)體變量放到類(lèi)的底部)。

若某個(gè)函數(shù)調(diào)用了另一個(gè),就應(yīng)該把它們放到一起,而且調(diào)用者應(yīng)該盡量放到被調(diào)用者上面。這樣,程序就有自然的順序。若堅(jiān)定地遵守這條約定,讀者將能夠確信函數(shù)聲明總會(huì)在其調(diào)用后很快出現(xiàn)。

概念相關(guān)的代碼應(yīng)該放到一起。相關(guān)性越強(qiáng),則彼此之間的距離就該越短。

這一節(jié)的要點(diǎn)整理一下,大致就是:

變量的聲明應(yīng)盡可能靠近其使用位置。

循環(huán)中的控制變量應(yīng)該在循環(huán)語(yǔ)句中聲明。

短函數(shù)中的本地變量應(yīng)當(dāng)在函數(shù)的頂部聲明。

而對(duì)于某些長(zhǎng)函數(shù),變量也可以在某代碼塊的頂部,或在循環(huán)之前聲明。

實(shí)體變量應(yīng)當(dāng)在類(lèi)的頂部聲明。

若某個(gè)函數(shù)調(diào)用了另一個(gè),就應(yīng)該把它們放到一起,而且調(diào)用者應(yīng)該盡量放到被調(diào)用者上面。

概念相關(guān)的代碼應(yīng)該放到一起。相關(guān)性越強(qiáng),則彼此之間的距離就該越短。

7 團(tuán)隊(duì)遵從同一套代碼規(guī)范

一個(gè)好的團(tuán)隊(duì)?wèi)?yīng)當(dāng)約定與遵從一套代碼規(guī)范,并且每個(gè)成員都應(yīng)當(dāng)采用此風(fēng)格。我們希望一個(gè)項(xiàng)目中的代碼擁有相似甚至相同的風(fēng)格,像默契無(wú)間的團(tuán)隊(duì)所完成的藝術(shù)品,而不是像一大票意見(jiàn)相左的個(gè)人所堆砌起來(lái)的殘次品。

定制一套編碼與格式風(fēng)格不需要太多時(shí)間,但對(duì)整個(gè)團(tuán)隊(duì)代碼風(fēng)格統(tǒng)一性的提升,卻是立竿見(jiàn)影的。

記住,好的軟件系統(tǒng)是由一系列風(fēng)格一致的代碼文件組成的。盡量不要用各種不同的風(fēng)格來(lái)構(gòu)成一個(gè)項(xiàng)目的各個(gè)部分,這樣會(huì)增加項(xiàng)目本身的復(fù)雜度與混亂程度。

四、范例代碼

和上篇文章一樣,有必要貼出一段書(shū)中推崇的整潔代碼作為本次代碼書(shū)寫(xiě)格式的范例。書(shū)中的這段代碼采用java語(yǔ)言,但絲毫不影響使用C++和C#的朋友們閱讀。

[cpp] view plain copy print?

public class CodeAnalyzer implements JavaFileAnalysis

{

private int lineCount;

private int maxLineWidth;

private int widestLineNumber;

private LineWidthHistogram lineWidthHistogram;

private int totalChars;

public CodeAnalyzer()

{

lineWidthHistogram = new LineWidthHistogram();

}

public static ListfindJavaFiles(File parentDirectory)

{

Listfiles = new ArrayList();

findJavaFiles(parentDirectory, files);

return files;

}

private static void findJavaFiles(File parentDirectory, Listfiles)

{

for (File file : parentDirectory.listFiles())

{

if (file.getName().endsWith(".java"))

files.add(file);

else if (file.isDirectory())

findJavaFiles(file, files);

}

}

public void analyzeFile(File javaFile) throws Exception

{

BufferedReader br = new BufferedReader(new FileReader(javaFile));

String line;

while ((line = br.readLine()) != null)

measureLine(line);

}

private void measureLine(String line)

{

lineCount++;

int lineSize = line.length();

totalChars += lineSize;

lineWidthHistogram.addLine(lineSize, lineCount);

recordWidestLine(lineSize);

}

private void recordWidestLine(int lineSize)

{

if (lineSize > maxLineWidth)

{

maxLineWidth = lineSize;

widestLineNumber = lineCount;

}

}

public int getLineCount()

{

return lineCount;

}

public int getMaxLineWidth()

{

return maxLineWidth;

}

public int getWidestLineNumber()

{

return widestLineNumber;

}

public LineWidthHistogram getLineWidthHistogram()

{

return lineWidthHistogram;

}

public double getMeanLineWidth()

{

return (double)totalChars / lineCount;

}

public int getMedianLineWidth()

{

Integer[] sortedWidths = getSortedWidths();

int cumulativeLineCount = 0;

for (int width : sortedWidths)

{

cumulativeLineCount += lineCountForWidth(width);

if (cumulativeLineCount > lineCount / 2)

return width;

}

throw new Error("Cannot get here");

}

private int lineCountForWidth(int width)

{

return lineWidthHistogram.getLinesforWidth(width).size();

}

private Integer[] getSortedWidths()

{

Setwidths = lineWidthHistogram.getWidths();

Integer[] sortedWidths = (widths.toArray(new Integer[0]));

Arrays.sort(sortedWidths);

return sortedWidths;

}

}

五、小結(jié):讓代碼不僅僅是能工作

代碼的格式關(guān)乎溝通,而溝通是專(zhuān)業(yè)開(kāi)發(fā)者的頭等大事,所以良好代碼的格式至關(guān)重要。

或許之前我們認(rèn)為“讓代碼能工作”才是專(zhuān)業(yè)開(kāi)發(fā)者的頭等大事。然而,《代碼整潔之道》這本書(shū),希望我們能拋棄這個(gè)觀(guān)點(diǎn)。

讓代碼能工作確實(shí)是代碼存在的首要意義,但作為一名有追求的程序員,請(qǐng)你想一想,今天你編寫(xiě)的功能,極可能在下一版中被修改,但代碼的可讀性卻會(huì)對(duì)以后可能發(fā)生的修改行為產(chǎn)生深遠(yuǎn)影響。原始代碼修改之后很久,其代碼風(fēng)格和可讀性仍會(huì)影響到可維護(hù)性和擴(kuò)展性。即便代碼已不復(fù)存在,你的風(fēng)格和律條仍影響深遠(yuǎn)。

“當(dāng)有人在閱讀我們的代碼時(shí),我們希望他們能為其整潔性、一致性和優(yōu)秀的細(xì)節(jié)處理而震驚。我們希望他們高高揚(yáng)起眉毛,一路看下去,希望他們感受能到那些為之勞作的專(zhuān)業(yè)人士們的優(yōu)秀職業(yè)素養(yǎng)。但若他們看到的只是一堆由酒醉的水手寫(xiě)出的鬼畫(huà)符,那他們多半會(huì)得出結(jié)論——這個(gè)項(xiàng)目的其他部分應(yīng)該也是混亂不堪的。”

所以,各位,在開(kāi)發(fā)過(guò)程中請(qǐng)不要僅僅是停留在“讓代碼可以工作”的層面,而更要注重自身輸出代碼的可維護(hù)性與擴(kuò)展性。請(qǐng)做一個(gè)更有追求的程序員。

六、本文涉及知識(shí)點(diǎn)提煉整理

整潔代碼的書(shū)寫(xiě)格式,可以遵從如下幾個(gè)原則:

第一原則:像報(bào)紙一樣一目了然。優(yōu)秀的源文件也要像報(bào)紙文章一樣,名稱(chēng)應(yīng)當(dāng)簡(jiǎn)單并且一目了然,名稱(chēng)本身應(yīng)該足夠告訴我們是否在正確的模塊中。源文件最頂部應(yīng)該給出高層次概念和算法。細(xì)節(jié)應(yīng)該往下漸次展開(kāi),直至找到源文件中最底層的函數(shù)和細(xì)節(jié)。

第二原則:恰如其分的注釋。帶有少量注釋的整潔而有力的代碼,比帶有大量注釋的零碎而復(fù)雜的代碼更加優(yōu)秀。

第三原則:合適的單文件行數(shù)。盡可能用幾百行以?xún)?nèi)的單文件來(lái)構(gòu)造出出色的系統(tǒng),因?yàn)槎涛募ǔ1乳L(zhǎng)文件更易于理解。

第四原則:合理地利用空白行。在每個(gè)命名空間、類(lèi)、函數(shù)之間,都需用空白行隔開(kāi)。

第五原則:讓緊密相關(guān)的代碼相互靠近??拷拇a行暗示著他們之間的緊密聯(lián)系。所以,緊密相關(guān)的代碼應(yīng)該相互靠近。

第六原則:基于關(guān)聯(lián)的代碼分布。

變量的聲明應(yīng)盡可能靠近其使用位置。

循環(huán)中的控制變量應(yīng)該在循環(huán)語(yǔ)句中聲明。

短函數(shù)中的本地變量應(yīng)當(dāng)在函數(shù)的頂部聲明。

對(duì)于某些長(zhǎng)函數(shù),變量也可以在某代碼塊的頂部,或在循環(huán)之前聲明。

實(shí)體變量應(yīng)當(dāng)在類(lèi)的頂部聲明。

若某個(gè)函數(shù)調(diào)用了另一個(gè),就應(yīng)該把它們放到一起,而且調(diào)用者應(yīng)該盡量放到被調(diào)用者上面。

概念相關(guān)的代碼應(yīng)該放到一起。相關(guān)性越強(qiáng),則彼此之間的距離就該越短。

第七原則:團(tuán)隊(duì)遵從同一套代碼規(guī)范。一個(gè)好的團(tuán)隊(duì)?wèi)?yīng)當(dāng)約定與遵從一套代碼規(guī)范,并且每個(gè)成員都應(yīng)當(dāng)采用此風(fēng)格。

本文就此結(jié)束。

下篇文章,我們將繼續(xù)《代碼整潔之道》的精讀與演繹,探討更多的內(nèi)容。

Best Wish~

標(biāo)簽: 代碼

相關(guān)閱讀