生活随笔
收集整理的這篇文章主要介紹了
2.函数(代码的整洁之道)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
2.函數(shù)(代碼的整潔之道)
目錄
短小只做一件事每個函數(shù)一個抽象層次switch語句使用描述性的名稱函數(shù)參數(shù)無副作用分隔指令與詢問使用異常代替返回的錯誤碼別重復(fù)自己結(jié)構(gòu)化編程如何寫出這樣的函數(shù)小結(jié)
注:代碼的整潔之道PDF: https://pan.baidu.com/s/16PLDWPiusGjcUfW_jgOm5w 密碼: s708
1. 短小
函數(shù)的第一規(guī)則是要短小,第二條規(guī)則還是要更短小。每個函數(shù)都只說一件事,而且每個函數(shù)會依序把你帶到下一個函數(shù)。函數(shù)應(yīng)該有多短小?應(yīng)該縮短成下面代碼的樣子。
if語句、else語句、while語句等,其中的代碼應(yīng)該只有一行,該行大抵應(yīng)該是一個函數(shù)調(diào)用語句。這樣不但能保持函數(shù)短小,而且因為塊內(nèi)調(diào)用的函數(shù)擁有較具體說明性的名稱,從而增加了文檔上的價值。這也意味著函數(shù)不應(yīng)該大到足夠容納嵌套結(jié)構(gòu)。函數(shù)的縮進層不該多余一層或兩層。
2. 只做一件事
函數(shù)應(yīng)該只做一件事,做好這件事。只做這一件事。編寫函數(shù)是為了把大一些的概念拆分成另一抽象層上的一系列步驟。自頂向下規(guī)則,讓每個函數(shù)后面都跟著位于下咦抽象層級的函數(shù),在查看函數(shù)列表時,就能遵循抽象層級向下閱讀了。
3. 每個函數(shù)一個抽象層次
要確保函數(shù)只做一件事,函數(shù)中的語句都要在同一抽象層級上
4. switch語句
寫出短小的switch語句很難,及時只有兩種條件也要比單個代碼塊或函數(shù)大得多。switch存在幾個問題 它太長,每當(dāng)出現(xiàn)新的類型,還會變得更長其實,明顯不止做了一件事。違反了單一權(quán)責(zé)原則違反了開放閉合原則,每當(dāng)添加新類型,就必須修改。最麻煩的是可能導(dǎo)出皆有類似結(jié)構(gòu)的函數(shù)。 該解決方案是將switch語句埋到抽象工廠下,不讓任何人看到。改工廠使用switch語句為Employee的派生物創(chuàng)建適當(dāng)?shù)膶嶓w,而不同的函數(shù),如calculatePay、isPayday和deliverPay等,則由Employee接口多態(tài)地接受派遣。對于switch語句,如果只出現(xiàn)一次,用于創(chuàng)建多態(tài)對象,而且隱藏在某個繼承關(guān)系中,在系統(tǒng)其它部分看不到,就還能容忍。
5. 使用描述性的名稱
如果每個例程都讓你感到深合已意,就是整潔代碼。函數(shù)越短小,功能越集中,就越便于取個好名字。別害怕長名稱,長而具有描述性的名稱,要比短而令人費解的名稱好。命名方式要保持一致。使用與模塊名一脈相承的短語、名詞和動詞給函數(shù)命名。例如,includeSetupAndTeardownPages、includeSetupPages、includeSuiteSetupPage和includeSetupPage等。這些詞使用了類似的措辭,依序講出一個故事。
6. 函數(shù)參數(shù)
最理想的參數(shù)數(shù)量是零,其次是一(單參數(shù)函數(shù)),再次是二(雙參數(shù)函數(shù)),應(yīng)盡量避免三(三參數(shù)函數(shù))。參數(shù)帶有太多的概念性,參數(shù)與函數(shù)名處在不同的抽象層級,要求你了解目前并不特別重要的細(xì)節(jié)。
1. 一元函數(shù)的普遍形式
像在boolean fileExists(“MyFile”)中那樣,可能是操作該參數(shù),將其轉(zhuǎn)換為其他說明東西,再輸出。例如,InputStream fileOpen(“MyFile”)把String類型的文件名轉(zhuǎn)換為InputStream類型的返回值,就是讀者看到函數(shù)時所期待的東西。你應(yīng)該選用較能區(qū)別這兩種理由的名稱,而且總能在一致的上下文中使用這兩種形式。還有一種就是事件。在這種形式中,有輸入?yún)?shù)而無輸出參數(shù)。程序?qū)⒑瘮?shù)看作是一個事件,使用該參數(shù)修改系統(tǒng)狀態(tài),例如 void passowrdAttemptFailedNtimes(int attempts)。小心使用這種形式,應(yīng)該讓讀者很清楚的了解它是個事件。謹(jǐn)慎的選用名稱與上下文語境。對于轉(zhuǎn)換,使用輸出參數(shù)而非返回值令人迷惑。**如果函數(shù)要對輸入?yún)?shù)進行轉(zhuǎn)換操作,轉(zhuǎn)換結(jié)果就該體現(xiàn)為返回值。**實際上,StringBuffer transform(StringBuffer in)要比void transform(StringBuffer out)強,即便第一種形式只簡單地返回輸出參數(shù)。
2. 標(biāo)識參數(shù)
標(biāo)識參數(shù)丑陋不堪,向函數(shù)傳入布爾值簡直就是駭人聽聞,表示函數(shù)不止做一件事,如果標(biāo)識為true將這么做,標(biāo)識為false將那樣做。看到render(Boolean isSuite),應(yīng)該把函數(shù)一分為二:renderForSuite()和renderForSingleTest()
3. 二元函數(shù)
有兩個參數(shù)的函數(shù)要比一元函數(shù)難懂。例如,writeField(name)要比writeField(outputStream,name)好懂。你應(yīng)該盡量利用一些機制將其轉(zhuǎn)換成一元函數(shù),例如,可以把writeField方法寫成outputStream的成員之一,從而能這樣用:outputStream.writeField(name),也可以把outputStream寫成當(dāng)前類的成員變量,從而無需再傳遞它。還可以分離出類似FieldWriter的新類,在其構(gòu)造器中采用outputStream,并包含一個write方法。
4. 三元函數(shù)
有三個參數(shù)的函數(shù)要比二元函數(shù)難懂很多。
5. 參數(shù)對象
如果函數(shù)看來需要兩個、三個或以上的參數(shù),就說明其中一些參數(shù)應(yīng)該封裝成類了。例如,下面兩個聲明的差別 Circle makeCircle(double x, double y, double radius)Circle makeCircle(Point center, double radius) 從參數(shù)創(chuàng)建對象,從而減少參數(shù)數(shù)量,當(dāng)一組參數(shù)被共同傳遞,就像上面的x和y,往往就是該有自己名稱的某個概念的一部分。
6. 動詞與關(guān)鍵詞
給函數(shù)取個好名字,能較好解釋函數(shù)的意圖,以及參數(shù)的順序和意圖。對于一元函數(shù),函數(shù)和參數(shù)應(yīng)該形成良好的動詞/名詞對形式,例如,write(name)就相當(dāng)令人認(rèn)同。這個例子展示了函數(shù)名稱的關(guān)鍵字形式。使用這種形式,我們把參數(shù)的名稱編碼成了函數(shù)名,例如,assertEqual改成assertedEqualsActual(expected, actual)可能會好些。
7. 無副作用
函數(shù)承諾只做一件事,,但有時會對自己類中的變量做出未能預(yù)期的改動。有時,它會把變量搞成向函數(shù)傳遞的參數(shù)或者是系統(tǒng)的全局變量。無論哪種情況,會導(dǎo)致古怪的時序性耦合以及順序依賴。如下面代碼
副作用在于Session.initialize()的調(diào)用。checkPassword函數(shù),顧名思義,是用來檢查密碼的,該名稱并未暗示它會初始化該次會話。這一副作用造出了一次時序性耦合,也就是說,checkPassword只能在特定的時刻調(diào)用。在本例中,可以重命名函數(shù)為 checkPasswordAndInitializeSession,雖然還是違反了只做一件事原則。
1. 輸出參數(shù)
面向?qū)ο笳Z言中對輸出參數(shù)的大部分需求已經(jīng)消失了,換言之public void appendFooter(StringBuffer report)最好改成 report.appendFooter()
8. 分隔指令與詢問
函數(shù)要么做什么事,要么回答什么事,但二者不可得兼。函數(shù)應(yīng)該修改某對象的狀態(tài),或是返回該對象的有關(guān)信息。兩樣都干會導(dǎo)致混亂。比如:public boolean set(String attribute, String value);該函數(shù)設(shè)置了某個指定屬性,如果成功就返回true,如果不存在那個屬性則返回false。這就導(dǎo)致了以下語句
if (set("username", "unclebob"))
從if調(diào)用很難判斷其含義,因為set是動詞還是形容詞并不清楚??蓪et函數(shù)重命名為setAndCheckIfExists,但這對提高if語句的可讀性幫助不大。真正解決方案是把指令與詢問分隔開來,防止混淆的發(fā)生:
if (attributeExists("username")){setAttribute("username", "unclebob")
}
....
9. 使用異常代替返回的錯誤碼
從指令式函數(shù)返回錯誤碼輕微違反了指令與詢問分隔的規(guī)則,它鼓勵在if語句判斷中把指令當(dāng)做表達(dá)式使用。
if (deletePage(page
)== E_OK
)
這不會引起動詞/形容詞混淆,但卻導(dǎo)致更深層次的嵌套結(jié)構(gòu)。如果使用異常代替返回錯誤碼,錯誤處理就能出主路徑代碼中分離出來,得到簡化。
1. 抽離 Try/Catch 代碼塊
try/catch代碼塊丑陋不堪,搞亂了代碼結(jié)構(gòu),最好把try/catch代碼塊的主體部分抽離出來,另外形成函數(shù)。
上例中,delete函數(shù)只與錯誤處理有關(guān)。
10. 別重復(fù)自己
重復(fù)的代碼會導(dǎo)致臃腫,并且需要修改的地方在增加,增加了錯誤的可能性。
11. 結(jié)構(gòu)化編程
12. 如何寫出這樣的函數(shù)
剛開始寫函數(shù)時,會冗長和復(fù)雜,但要打磨這些代碼,分解函數(shù)、修改名稱、消除重復(fù)??s短和重新安置方法,有時還拆散類。
13. 小結(jié)
代碼賞析
public class HtmlUtilCleanCode {private WikiPage testPage
;private StringBuffer newPageContent
;private boolean isSuiteSetup
;private PageCrawler pageCrawler
;private PageData pageData
;public static String
reader(PageData pageData
) {return reader(pageData
, false);}public static String
reader(PageData pageData
, boolean isSuite
) {return (new HtmlUtilCleanCode(pageData
)).reader(isSuite
);}private HtmlUtilCleanCode(PageData pageData
) {this.testPage
= pageData
.getwikiPage();this.pageCrawler
= this.testPage
.getPageCrawler();this.newPageContent
= new StringBuffer();}private String
reader(boolean isSuite
) {this.isSuiteSetup
= isSuite
;if (this.isTestPage()) {this.includSetupAndTearPages();}return this.pageData
.getHtml();}private boolean isTestPage() {return this.pageData
.hasAttribute("test");}private void includSetupAndTearPages() {this.includeSetupPages();this.includePageContent();this.includeTearPages();}private void includeSetupPages() {if (this.isSuiteSetup
) {this.include("SuiteResponder.SUITE_SETUP_NAME", "-setup");}this.includeSetupPage();}private void includeSetupPage() {this.include("SetUp", "-setup");}private void includePageContent() {this.newPageContent
.append(this.pageData
.getContent());}private void includeTearPages() {if (this.isSuiteSetup
) {this.include("SuiteResponder.SUITE_SETUP_NAME", "-teardown");}this.includeTearPage();}private void includeTearPage() {this.include("TearDown", "-teardown");}private void include(String pageName
, String args
) {WikiPage innerPage
= PageCrawlerImpl
.getInheritedPage(pageName
, this.testPage
);if (innerPage
!= null
) {WikiPagePath pagePath
= innerPage
.getPageCrawler().getFullpath(innerPage
);String pagePathName
= PathParser
.render(pagePath
);this.newPageContent
.append("!include -").append(args
).append(".").append(pagePathName
).append("\n");}}
}
總結(jié)
以上是生活随笔為你收集整理的2.函数(代码的整洁之道)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。