這篇文章,是關於整潔代碼函數書寫的一些準則。
一、引言
以下引言的內容,有必要伴隨這個系列的每一次更新,這次也不例外。
《代碼整潔之道》這本書提出了一個觀點:代碼質量與其整潔度成正比,乾淨的代碼,既在質量上可靠,也爲後期維護、升級奠定了良好基礎。書中介紹的規則均來自作者多年的實踐經驗,涵蓋從命名到重構的多個編程方面,雖爲一“家”之言,然誠有可資借鑑的價值。
但我們知道,很多時候,理想很豐滿,現實很骨感,也知道人在江湖,身不由己。因爲項目的緊迫性,需求的多樣性,我們無法時時刻刻都寫出整潔的代碼,保持自己輸出的都是高質量、優雅的代碼。
但若我們理解了代碼整潔之道的精髓,我們會知道怎樣讓自己的代碼更加優雅、整潔、易讀、易擴展,知道真正整潔的代碼應該是怎麼樣的,也許就會漸漸養成持續輸出整潔代碼的習慣。
而且或許你會發現,若你一直保持輸出整潔代碼的習慣,長期來看,會讓你的整體效率和代碼質量大大提升。
二、本文涉及知識點思維導圖
國際慣例,先放出這篇文章所涉及內容知識點的一張思維導圖,就開始正文。大家若是疲於閱讀文章正文,直接看這張圖,也是可以Get到本文的主要知識點的大概。
三、整潔代碼的函數書寫準則
函數的第一規則是要短小。第二規則還是要短小。
《代碼整潔之道》一書作者Bob大叔寫道,“近40年來,我寫過各種長度不同的函數。我寫過令人憎惡的長達3000行的厭物,也寫過許多100行到300行的函數,還寫過20行到30行的。經過漫長的試錯,經驗告訴我,函數就該短小”。
那麼函數應該有多短小合適呢?通常來說,應該短於如下這個函數:
-
public static StringrenderPageWithSetupsAndTeardowns
-
(PageData pageData, boolean isSuite
-
)throws Exception
-
{
-
booleanisTestPage = pageData.hasAttribute("Test");
-
if(isTestPage) {
-
WikiPagetestPage = pageData.getWikiPage( );
-
StringBuffernewPageContent = new StringBuffer( );
-
includeSetupPages(testPage,newPageContent, isSuite);
-
newPageContent.append(pageData.getContent());
-
includeTeardownPages(testPage,newPageContent, isSuite);
-
pageData.setContent(newPageContent.toString());
-
}
-
returnpageData.getHtml( );
-
}
而其實,最好應該縮短成如下的樣子:
-
public static StringrenderPageWithSetupsAndTeardowns(
-
PageDatapageData, boolean isSuite) throws Exception
-
{
-
if(isTestPage(pageData))
-
includeSetupAndTeardownPages(pageData,isSuite);
-
returnpageData.getHtml( );
-
}
總之,十行以內是整潔的函數比較合適的長度,若沒有特殊情況,我們最好將單個函數控制在十行以內。
評論區有一些討論,也放到正文來吧。
“函數是否應該足夠短小,算是《代碼整潔之道》中最具爭議的議題之一。
書寫短小函數的時候,其實我們不要忽略一點,那就是,函數名名稱本身就具描述性。短小的函數構成,如果要追根溯源瞭解內部實現,自然需要一層層找到最終的實現。但若是想大致知道這個函數到底做了什麼,結合這個短小函數體內具描述性的一些函數名,應該也就一目瞭然了。試想,當你眼前的這個函數是幾十上百上千行的龐然大物的時候,你能做到一眼就一目瞭然,將其大概做了什麼瞭然於心嗎?函數短小的一方面優點,在這裏就體現出來了。
函數應該短小這個議題,仁者見仁智者見智,在實際編碼過程中任何人都很難做到嚴格遵守,但大的方向,若想寫出整潔的代碼,應該去向短小的函數靠攏,對吧?”
函數應該只做一件事情。只做一件事,做好這件事。
設計模式中有單一職責原則,我們可以把這條原則理解爲代碼整潔之道中的函數單一職責原則。
要判斷函數是不是隻做了一件事情,還有一個方法,就是看能否再拆出一個函數,該函數不僅只是單純地重新詮釋其實現。
“如果每個例程都讓你感到深合己意,那就是整潔的代碼。”要遵循這一原則,大半工作都在於爲只做一件事的小函數取個好名字。函數越短小,功能越集中,就越便於取個好名字。
別害怕長名稱。長而具有描述性的名稱,比短而令人費解的名稱好。而且長而具有描述性的名稱,比描述性的長註釋要好。且選擇描述性的名稱能理清你關於模塊的設計思路,並幫你改進之。當然,如果短的名稱已經足夠說明問題,還是越短越好。
命名方式要保持一致。使用與模塊名一脈相承的短語、名詞和動詞給函數命名。比如,includeSetupAndTeardownPages,includeSetupPages, includeSuiteSetupPage, and includeSetupPage等。這些名詞使用了類似的措辭,依序講述一個故事,就是是比較推崇的命名方式了。
最理想的函數參數形態是零參數,其次是單參數,再次是雙參數,應儘量避免三參數及以上參數的函數,有足夠的理由才能用三個以上參數(多參數函數)。
函數參數中出現標識符參數是非常不推崇的做法。有標識符參數的函數,很有可能不止在做一件事,標示如果標識符爲true將這樣做,標識符爲false將那樣做。正確的做法應該將有標識符參數的函數一分爲二,對標識符爲true和false分別開一個函數來處理。
重複的代碼會導致模塊的臃腫,整個模塊的可讀性可能會應該重複的消除而得到提升。
其實可以這樣說,重複可能是軟件中一切邪惡的根源,許多原則與實踐規則都是爲控制與消除重複而創建的。仔細想一想,面向對象編程是如何將代碼集中到基類,從而避免了冗餘的。而面向方面編程(Aspect Oriented Programming)、面向組件編程(ComponentOriented Programming)多少也是消除重複的一種策略。這樣看來,自子程序發明以來,軟件開發領域的所有創新都是在不斷嘗試從源代碼中消滅重複。
重複而囉嗦的代碼,乃萬惡之源,我們要盡力避免。
四、範例
有必要貼出一段書中推崇的整潔代碼作爲本次函數書寫準則的範例。
-
using System;
-
-
public class SetupTeardownIncluder
-
{
-
private PageData pageData;
-
private boolean isSuite;
-
private WikiPage testPage;
-
private StringBuffer newPageContent;
-
private PageCrawler pageCrawler;
-
-
public static String render(PageData pageData) throws Exception
-
{
-
return render(pageData, false);
-
}
-
public static String render(PageData pageData, boolean isSuite)throws Exception
-
{
-
return new SetupTeardownIncluder(pageData).render(isSuite);
-
}
-
private SetupTeardownIncluder(PageData pageData)
-
{
-
this.pageData = pageData;
-
testPage = pageData.getWikiPage();
-
pageCrawler = testPage.getPageCrawler();
-
newPageContent = new StringBuffer();
-
}
-
-
private String render(boolean isSuite) throws Exception
-
{
-
this.isSuite = isSuite;
-
if (isTestPage())
-
includeSetupAndTeardownPages();
-
return pageData.getHtml();
-
}
-
-
private boolean isTestPage() throws Exception
-
{
-
return pageData.hasAttribute("Test");
-
}
-
-
private void includeSetupAndTeardownPages() throws Exception
-
{
-
includeSetupPages();
-
includePageContent();
-
includeTeardownPages();
-
updatePageContent();
-
}
-
-
private void includeSetupPages() throws Exception
-
{
-
if (isSuite)
-
includeSuiteSetupPage();
-
includeSetupPage();
-
}
-
-
private void includeSuiteSetupPage() throws Exception
-
{
-
include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
-
}
-
-
private void includeSetupPage() throws Exception
-
{
-
include("SetUp", "-setup");
-
}
-
-
private void includePageContent() throws Exception
-
{
-
newPageContent.append(pageData.getContent());
-
}
-
-
private void includeTeardownPages() throws Exception
-
{
-
includeTeardownPage();
-
if (isSuite)
-
includeSuiteTeardownPage();
-
}
-
-
private void includeTeardownPage() throws Exception
-
{
-
include("TearDown", "-teardown");
-
}
-
-
private void includeSuiteTeardownPage() throws Exception
-
{
-
include(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
-
}
-
-
private void updatePageContent() throws Exception
-
{
-
pageData.setContent(newPageContent.toString());
-
}
-
-
private void include(String pageName, String arg) throws Exception
-
{
-
WikiPage inheritedPage = findInheritedPage(pageName);
-
if (inheritedPage != null)
-
{
-
String pagePathName = getPathNameForPage(inheritedPage);
-
buildIncludeDirective(pagePathName, arg);
-
}
-
}
-
-
private WikiPage findInheritedPage(String pageName) throws Exception
-
{
-
return PageCrawlerImpl.getInheritedPage(pageName, testPage);
-
}
-
-
private String getPathNameForPage(WikiPage page) throws Exception
-
{
-
WikiPagePath pagePath = pageCrawler.getFullPath(page);
-
return PathParser.render(pagePath);
-
}
-
-
private void buildIncludeDirective(String pagePathName, String arg)
-
{
-
newPageContent
-
.append("\n!include ")
-
.append(arg)
-
.append(" .")
-
.append(pagePathName)
-
.append("\n");
-
}
-
}
上面這段代碼,滿足了函數書寫短小、單一職責、命名合適、參數儘可能少、不重複囉嗦這幾條準則。整潔的函數代碼大致如此。
五、小結
大師級程序員把系統當作故事來講,而不是當做程序來寫。這是之前已經提到過的一個觀點。
本文講述瞭如何編寫良好函數的一些準則,如果你遵循這些準則,函數就會短小,有個好名字,而且被很好的歸置。不過永遠不要忘記,我們真正的目標在於講述系統的故事,而你編寫的函數必須乾淨利落的拼裝到一起,形成一種精確而清晰的語言,幫助你講故事。
程序員,其實是故事家。
六、本文涉及知識點提煉整理
整潔代碼的函數書寫,可以遵從如下幾個原則:
- 第一原則:短小。若沒有特殊情況,最好將單個函數控制在十行以內。
- 第二原則:單一職責。函數應該只做一件事情。只做一件事,做好這件事。
- 第三原則:命名合適且具描述性。長而具有描述性的名稱,比短而令人費解的名稱好。當然,如果短的名稱已經足夠說明問題,還是越短越好。
- 第四原則:參數儘可能少。最理想的函數參數形態是零參數,其次是單參數,再次是雙參數,應儘量避免三參數及以上參數的函數,有足夠的理由才能用三個以上參數。
- 第五原則:盡力避免重複。重複的代碼會導致模塊的臃腫,整個模塊的可讀性可能會應該重複的消除而得到提升。
這篇文章,是關於整潔代碼函數書寫的一些準則。
一、引言
以下引言的內容,有必要伴隨這個系列的每一次更新,這次也不例外。
《代碼整潔之道》這本書提出了一個觀點:代碼質量與其整潔度成正比,乾淨的代碼,既在質量上可靠,也爲後期維護、升級奠定了良好基礎。書中介紹的規則均來自作者多年的實踐經驗,涵蓋從命名到重構的多個編程方面,雖爲一“家”之言,然誠有可資借鑑的價值。
但我們知道,很多時候,理想很豐滿,現實很骨感,也知道人在江湖,身不由己。因爲項目的緊迫性,需求的多樣性,我們無法時時刻刻都寫出整潔的代碼,保持自己輸出的都是高質量、優雅的代碼。
但若我們理解了代碼整潔之道的精髓,我們會知道怎樣讓自己的代碼更加優雅、整潔、易讀、易擴展,知道真正整潔的代碼應該是怎麼樣的,也許就會漸漸養成持續輸出整潔代碼的習慣。
而且或許你會發現,若你一直保持輸出整潔代碼的習慣,長期來看,會讓你的整體效率和代碼質量大大提升。
二、本文涉及知識點思維導圖
國際慣例,先放出這篇文章所涉及內容知識點的一張思維導圖,就開始正文。大家若是疲於閱讀文章正文,直接看這張圖,也是可以Get到本文的主要知識點的大概。
三、整潔代碼的函數書寫準則
函數的第一規則是要短小。第二規則還是要短小。
《代碼整潔之道》一書作者Bob大叔寫道,“近40年來,我寫過各種長度不同的函數。我寫過令人憎惡的長達3000行的厭物,也寫過許多100行到300行的函數,還寫過20行到30行的。經過漫長的試錯,經驗告訴我,函數就該短小”。
那麼函數應該有多短小合適呢?通常來說,應該短於如下這個函數:
-
public static StringrenderPageWithSetupsAndTeardowns
-
(PageData pageData, boolean isSuite
-
)throws Exception
-
{
-
booleanisTestPage = pageData.hasAttribute("Test");
-
if(isTestPage) {
-
WikiPagetestPage = pageData.getWikiPage( );
-
StringBuffernewPageContent = new StringBuffer( );
-
includeSetupPages(testPage,newPageContent, isSuite);
-
newPageContent.append(pageData.getContent());
-
includeTeardownPages(testPage,newPageContent, isSuite);
-
pageData.setContent(newPageContent.toString());
-
}
-
returnpageData.getHtml( );
-
}
而其實,最好應該縮短成如下的樣子:
-
public static StringrenderPageWithSetupsAndTeardowns(
-
PageDatapageData, boolean isSuite) throws Exception
-
{
-
if(isTestPage(pageData))
-
includeSetupAndTeardownPages(pageData,isSuite);
-
returnpageData.getHtml( );
-
}
總之,十行以內是整潔的函數比較合適的長度,若沒有特殊情況,我們最好將單個函數控制在十行以內。
評論區有一些討論,也放到正文來吧。
“函數是否應該足夠短小,算是《代碼整潔之道》中最具爭議的議題之一。
書寫短小函數的時候,其實我們不要忽略一點,那就是,函數名名稱本身就具描述性。短小的函數構成,如果要追根溯源瞭解內部實現,自然需要一層層找到最終的實現。但若是想大致知道這個函數到底做了什麼,結合這個短小函數體內具描述性的一些函數名,應該也就一目瞭然了。試想,當你眼前的這個函數是幾十上百上千行的龐然大物的時候,你能做到一眼就一目瞭然,將其大概做了什麼瞭然於心嗎?函數短小的一方面優點,在這裏就體現出來了。
函數應該短小這個議題,仁者見仁智者見智,在實際編碼過程中任何人都很難做到嚴格遵守,但大的方向,若想寫出整潔的代碼,應該去向短小的函數靠攏,對吧?”
函數應該只做一件事情。只做一件事,做好這件事。
設計模式中有單一職責原則,我們可以把這條原則理解爲代碼整潔之道中的函數單一職責原則。
要判斷函數是不是隻做了一件事情,還有一個方法,就是看能否再拆出一個函數,該函數不僅只是單純地重新詮釋其實現。
“如果每個例程都讓你感到深合己意,那就是整潔的代碼。”要遵循這一原則,大半工作都在於爲只做一件事的小函數取個好名字。函數越短小,功能越集中,就越便於取個好名字。
別害怕長名稱。長而具有描述性的名稱,比短而令人費解的名稱好。而且長而具有描述性的名稱,比描述性的長註釋要好。且選擇描述性的名稱能理清你關於模塊的設計思路,並幫你改進之。當然,如果短的名稱已經足夠說明問題,還是越短越好。
命名方式要保持一致。使用與模塊名一脈相承的短語、名詞和動詞給函數命名。比如,includeSetupAndTeardownPages,includeSetupPages, includeSuiteSetupPage, and includeSetupPage等。這些名詞使用了類似的措辭,依序講述一個故事,就是是比較推崇的命名方式了。
最理想的函數參數形態是零參數,其次是單參數,再次是雙參數,應儘量避免三參數及以上參數的函數,有足夠的理由才能用三個以上參數(多參數函數)。
函數參數中出現標識符參數是非常不推崇的做法。有標識符參數的函數,很有可能不止在做一件事,標示如果標識符爲true將這樣做,標識符爲false將那樣做。正確的做法應該將有標識符參數的函數一分爲二,對標識符爲true和false分別開一個函數來處理。
重複的代碼會導致模塊的臃腫,整個模塊的可讀性可能會應該重複的消除而得到提升。
其實可以這樣說,重複可能是軟件中一切邪惡的根源,許多原則與實踐規則都是爲控制與消除重複而創建的。仔細想一想,面向對象編程是如何將代碼集中到基類,從而避免了冗餘的。而面向方面編程(Aspect Oriented Programming)、面向組件編程(ComponentOriented Programming)多少也是消除重複的一種策略。這樣看來,自子程序發明以來,軟件開發領域的所有創新都是在不斷嘗試從源代碼中消滅重複。
重複而囉嗦的代碼,乃萬惡之源,我們要盡力避免。
四、範例
有必要貼出一段書中推崇的整潔代碼作爲本次函數書寫準則的範例。
-
using System;
-
-
public class SetupTeardownIncluder
-
{
-
private PageData pageData;
-
private boolean isSuite;
-
private WikiPage testPage;
-
private StringBuffer newPageContent;
-
private PageCrawler pageCrawler;
-
-
public static String render(PageData pageData) throws Exception
-
{
-
return render(pageData, false);
-
}
-
public static String render(PageData pageData, boolean isSuite)throws Exception
-
{
-
return new SetupTeardownIncluder(pageData).render(isSuite);
-
}
-
private SetupTeardownIncluder(PageData pageData)
-
{
-
this.pageData = pageData;
-
testPage = pageData.getWikiPage();
-
pageCrawler = testPage.getPageCrawler();
-
newPageContent = new StringBuffer();
-
}
-
-
private String render(boolean isSuite) throws Exception
-
{
-
this.isSuite = isSuite;
-
if (isTestPage())
-
includeSetupAndTeardownPages();
-
return pageData.getHtml();
-
}
-
-
private boolean isTestPage() throws Exception
-
{
-
return pageData.hasAttribute("Test");
-
}
-
-
private void includeSetupAndTeardownPages() throws Exception
-
{
-
includeSetupPages();
-
includePageContent();
-
includeTeardownPages();
-
updatePageContent();
-
}
-
-
private void includeSetupPages() throws Exception
-
{
-
if (isSuite)
-
includeSuiteSetupPage();
-
includeSetupPage();
-
}
-
-
private void includeSuiteSetupPage() throws Exception
-
{
-
include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
-
}
-
-
private void includeSetupPage() throws Exception
-
{
-
include("SetUp", "-setup");
-
}
-
-
private void includePageContent() throws Exception
-
{
-
newPageContent.append(pageData.getContent());
-
}
-
-
private void includeTeardownPages() throws Exception
-
{
-
includeTeardownPage();
-
if (isSuite)
-
includeSuiteTeardownPage();
-
}
-
-
private void includeTeardownPage() throws Exception
-
{
-
include("TearDown", "-teardown");
-
}
-
-
private void includeSuiteTeardownPage() throws Exception
-
{
-
include(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
-
}
-
-
private void updatePageContent() throws Exception
-
{
-
pageData.setContent(newPageContent.toString());
-
}
-
-
private void include(String pageName, String arg) throws Exception
-
{
-
WikiPage inheritedPage = findInheritedPage(pageName);
-
if (inheritedPage != null)
-
{
-
String pagePathName = getPathNameForPage(inheritedPage);
-
buildIncludeDirective(pagePathName, arg);
-
}
-
}
-
-
private WikiPage findInheritedPage(String pageName) throws Exception
-
{
-
return PageCrawlerImpl.getInheritedPage(pageName, testPage);
-
}
-
-
private String getPathNameForPage(WikiPage page) throws Exception
-
{
-
WikiPagePath pagePath = pageCrawler.getFullPath(page);
-
return PathParser.render(pagePath);
-
}
-
-
private void buildIncludeDirective(String pagePathName, String arg)
-
{
-
newPageContent
-
.append("\n!include ")
-
.append(arg)
-
.append(" .")
-
.append(pagePathName)
-
.append("\n");
-
}
-
}
上面這段代碼,滿足了函數書寫短小、單一職責、命名合適、參數儘可能少、不重複囉嗦這幾條準則。整潔的函數代碼大致如此。
五、小結
大師級程序員把系統當作故事來講,而不是當做程序來寫。這是之前已經提到過的一個觀點。
本文講述瞭如何編寫良好函數的一些準則,如果你遵循這些準則,函數就會短小,有個好名字,而且被很好的歸置。不過永遠不要忘記,我們真正的目標在於講述系統的故事,而你編寫的函數必須乾淨利落的拼裝到一起,形成一種精確而清晰的語言,幫助你講故事。
程序員,其實是故事家。
六、本文涉及知識點提煉整理
整潔代碼的函數書寫,可以遵從如下幾個原則:
- 第一原則:短小。若沒有特殊情況,最好將單個函數控制在十行以內。
- 第二原則:單一職責。函數應該只做一件事情。只做一件事,做好這件事。
- 第三原則:命名合適且具描述性。長而具有描述性的名稱,比短而令人費解的名稱好。當然,如果短的名稱已經足夠說明問題,還是越短越好。
- 第四原則:參數儘可能少。最理想的函數參數形態是零參數,其次是單參數,再次是雙參數,應儘量避免三參數及以上參數的函數,有足夠的理由才能用三個以上參數。
- 第五原則:盡力避免重複。重複的代碼會導致模塊的臃腫,整個模塊的可讀性可能會應該重複的消除而得到提升。
本文就此結束。
下篇文章,我們將繼續《代碼整潔之道》的精讀與演繹,探討更多的內容。
Best Wish~