一、簡(jiǎn)短函數(shù)
1.1 總述
函數(shù)的第一規(guī)則是要簡(jiǎn)短,凝練瓤鼻。
1.2 說(shuō)明
長(zhǎng)函數(shù)有時(shí)是合理的,因此并不硬性限制函數(shù)的長(zhǎng)度贤重。但如果函數(shù)超過(guò)
40
行茬祷,可以思索一下能不能在不影響程序結(jié)構(gòu)的前提下對(duì)其進(jìn)行分割。函數(shù)20行封頂最佳并蝗,最大不超過(guò)80
行祭犯,每行字符數(shù)不應(yīng)超過(guò)120個(gè)。即使一個(gè)長(zhǎng)函數(shù)現(xiàn)在工作的非常好滚停,一旦有人對(duì)其修改沃粗,有可能出現(xiàn)新的問(wèn)題,甚至導(dǎo)致難以發(fā)現(xiàn)的Bug键畴。使函數(shù)盡量簡(jiǎn)短最盅,以便于他人閱讀和修改代碼。
在處理代碼時(shí)起惕,你可能會(huì)發(fā)現(xiàn)復(fù)雜的長(zhǎng)函數(shù)涡贱。不要害怕修改現(xiàn)有代碼:如果證實(shí)這些代碼使用、調(diào)試起來(lái)很困難惹想,或者你只需要使用其中的一小段代碼问词,考慮將其分割為更簡(jiǎn)短并易于管理的若干函數(shù)。
if語(yǔ)句嘀粱、else語(yǔ)句激挪、while語(yǔ)句等,其中的代碼塊應(yīng)該只有一行草穆。該行大抵應(yīng)該是一個(gè)函數(shù)調(diào)用語(yǔ)句灌灾。這樣不但能保持函數(shù)短小,而且悲柱,因?yàn)閴K內(nèi)調(diào)用的函數(shù)擁有較具說(shuō)明性的名稱锋喜,從而增加了文檔上的價(jià)值。這意味著函數(shù)不應(yīng)該大到足以容納嵌套結(jié)構(gòu)。所以嘿般,函數(shù)的縮進(jìn)層級(jí)不該多于一層或兩層段标。
二、抽象層級(jí)
2.1 總述
每個(gè)函數(shù)只做一件事炉奴。
每個(gè)函數(shù)一個(gè)抽象層級(jí)逼庞。
2.2 說(shuō)明
- 函數(shù)應(yīng)該做一件事。做好這件事瞻赶。只做這一件事赛糟。
代碼清單2-1 HtmlUtil.java(FitNesse 20070619)
public static String testableHtml(
PageData pageData,
boolean includeSuiteSetup
) throws Exception {
WikiPage wikiPage = pageData.getWikiPage();
StringBuffer buffer = new StringBuffer();
if (pageData.hasAttribute("Test")) {
if (includeSuiteSetup) {
WikiPage suiteSetup =
PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_SETUP_NAME, wikiPage
);
if (suiteSetup != null) {
WikiPagePath pagePath =
suiteSetup.getPageCrawler().getFullPath(suiteSetup);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -setup .")
.append(pagePathName)
.append("\n");
}
}
WikiPage setup =
PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
if (setup != null) {
WikiPagePath setupPath =
wikiPage.getPageCrawler().getFullPath(setup);
String setupPathName = PathParser.render(setupPath);
buffer.append("!include -setup .")
.append(setupPathName)
.append("\n");
}
}
buffer.append(pageData.getContent());
if (pageData.hasAttribute("Test")) {
WikiPage teardown =
PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (teardown != null) {
WikiPagePath tearDownPath =
wikiPage.getPageCrawler().getFullPath(teardown);
String tearDownPathName = PathParser.render(tearDownPath);
buffer.append("\n")
.append("!include -teardown .")
.append(tearDownPathName)
.append("\n");
}
if (includeSuiteSetup) {
WikiPagePath pagePath =
suitTeardown.getPageCrawler().getFullPath(suiteTeardown);
buffer.append("!include -teardown .")
.append(pagePathName)
.append("\n");
}
}
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}
代碼清單2-2 HtmlUtil.java(重構(gòu)之后)
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
代碼清單2-1手忙腳亂,而代碼清單2-2則只做一件簡(jiǎn)單的事砸逊。它將設(shè)置和拆解包納到測(cè)試頁(yè)面中璧南。
代碼清單2-2只做了一件事,對(duì)吧师逸?其實(shí)也很容易看作是三件事:
(1)判斷是否為測(cè)試頁(yè)面司倚;
(2)如果是,則容納進(jìn)設(shè)置和分拆步驟篓像;
(3)渲染成HTML动知。
如果函數(shù)只是做了該函數(shù)名下同一抽象層上的步驟,則函數(shù)還只是做了一件事员辩。編寫函數(shù)畢竟是為了把大一些的概念(換言之盒粮,函數(shù)的名稱)拆分為另一抽象層上的一系列步驟。代碼清單2-1明顯包括了處于多個(gè)不同抽象層級(jí)的步驟屈暗。
所以拆讯,要判斷函數(shù)是否不止做了一件事,還有一個(gè)方法养叛,就是看是否能再拆出一個(gè)函數(shù)种呐,該函數(shù)不僅只是單純地重新詮釋其實(shí)現(xiàn)。
要確保函數(shù)只做一件事弃甥,函數(shù)中的語(yǔ)句都要在同一抽象層級(jí)上爽室。代碼清單2-1違反了這條規(guī)矩。那里面有g(shù)etHtml()等位于較高抽象層的概念淆攻,也有String pagePathName = PathParser.render(pagePath)等位于中間抽象層的概念阔墩,還有.append("\n")等位于相當(dāng)?shù)偷某橄髮拥母拍睢?/p>
自頂向下讀代碼:向下規(guī)則(我們想要讓代碼擁有自頂向下的閱讀順序。我們想要讓每個(gè)函數(shù)后面都跟著位于下一抽象層級(jí)的函數(shù)瓶珊,這樣一來(lái)啸箫,在查看函數(shù)列表時(shí),就能循抽象層級(jí)向下閱讀了伞芹。)
三忘苛、函數(shù)參數(shù)
3.1 總述
最理想的參數(shù)數(shù)量是零(零參數(shù)函數(shù))蝉娜,其次是一(單參數(shù)函數(shù)),再次是二(雙參數(shù)函數(shù))扎唾,應(yīng)盡量避免三(三參數(shù)函數(shù))召川。有足夠特殊的理由才能用三個(gè)以上參數(shù)(多參數(shù)函數(shù))。
3.2 一元函數(shù)
一元函數(shù)的普遍形式是有輸入?yún)?shù)而無(wú)輸出參數(shù)胸遇。對(duì)于轉(zhuǎn)換荧呐,使用輸出參數(shù)而非返回值令人迷惑。如果函數(shù)要對(duì)輸入?yún)?shù)進(jìn)行轉(zhuǎn)換操作纸镊,轉(zhuǎn)換結(jié)果就該體現(xiàn)為返回值倍阐。
3.3 二元函數(shù)
二元函數(shù)不算惡劣,而且你當(dāng)然也會(huì)編寫二元函數(shù)逗威。不過(guò)收捣,你得小心,使用二元函數(shù)要付出代價(jià)庵楷。你應(yīng)該盡量利用一些機(jī)制將其轉(zhuǎn)換為一元函數(shù)。例如楣颠,可以把writeField方法寫成outputStream的成員之一尽纽,從而能這樣用:outputStream.writeField(name)⊥觯或者弄贿,也可以把outputStream寫成當(dāng)前類的成員變量,從而無(wú)需再傳遞它矫膨。還可以分離出類似FieldWriter的新類差凹,在其構(gòu)造器中采用outputStream,并且包含一個(gè)write方法侧馅。
3.4 參數(shù)順序
- 函數(shù)的參數(shù)順序?yàn)椋狠斎雲(yún)?shù)在先危尿,后跟輸出參數(shù)。
- 輸入?yún)?shù)通常是值參或者
const
引用馁痴,輸出參數(shù)或輸入谊娇、輸出參數(shù)則一般為非const
指針。在排列參數(shù)順序時(shí)罗晕,將所有的輸入?yún)?shù)置于輸出參數(shù)之前济欢。特別要注意,在加入新參數(shù)時(shí)不要因?yàn)樗鼈兪切聟?shù)就置于參數(shù)列表最后小渊,而是仍然要按照前述的規(guī)則法褥,即將新的輸入?yún)?shù)也置于輸出參數(shù)之前。
3.5 引用參數(shù)
3.5.1 總述
所有按引用傳遞的參數(shù)必須加上const
酬屉。
3.5.2 定義
在C語(yǔ)言中半等,如果函數(shù)需要修改變量的值,參數(shù)必須為指針,如int foo(int *pval)
酱鸭。在C++中吗垮,函數(shù)還可以聲明為引用參數(shù):int foo(int &val)
。
3.5.3 優(yōu)點(diǎn)
定義引用參數(shù)可以防止出現(xiàn)(*pval)++
這樣丑陋的代碼凹髓。引用參數(shù)對(duì)于拷貝構(gòu)造函數(shù)這樣的應(yīng)用也是必需的烁登。同時(shí)也更明確地不接受空指針。
3.5.4 缺點(diǎn)
容易引起誤解蔚舀,因?yàn)橐迷谡Z(yǔ)法上是值變量卻擁有指針的語(yǔ)義饵沧。
3.5.5 結(jié)論
函數(shù)參數(shù)列表中,所有引用參數(shù)都必須是const
:
void Foo(const string &in, string *out);
有時(shí)候赌躺,在輸入形參中用const T*
指針比const T&
更明智狼牺。比如:
- 可能會(huì)傳遞空指針。
- 函數(shù)要把指針或?qū)Φ刂返囊觅x值給輸入形參礼患。
總而言之是钥,大多時(shí)候輸入形參往往是const T&
。若用cosnt T*
則說(shuō)明輸入另有處理缅叠。所以若要使用cosnt T*
悄泥,則應(yīng)給出相應(yīng)的理由,否則會(huì)使得讀者感到迷惑肤粱。
3.6 缺省參數(shù)
3.6.1 總述
只允許在非虛函數(shù)中使用缺省參數(shù)弹囚,且必須保證缺省參數(shù)的值始終一致。缺省參數(shù)與 函數(shù)重載 遵循同樣的規(guī)則领曼。一般情況下建議使用函數(shù)重載鸥鹉,尤其是在缺省函數(shù)帶來(lái)的可讀性提升不能彌補(bǔ)下文中所提到的缺點(diǎn)的情況下。
3.6.2 優(yōu)點(diǎn)
有些函數(shù)一般情況下使用默認(rèn)參數(shù)庶骄,但有時(shí)需要又使用非默認(rèn)的參數(shù)毁渗。缺省參數(shù)為這樣的情形提供了便利,使程序員不需要為了極少的例外情況編寫大量的函數(shù)单刁。和函數(shù)重載相比祝蝠,缺省參數(shù)的語(yǔ)法更簡(jiǎn)潔明了,減少了大量的樣板代碼幻碱,也更好地區(qū)別了“必要參數(shù)”和“可選參數(shù)”绎狭。
3.6.3 缺點(diǎn)
缺省參數(shù)實(shí)際上是函數(shù)重載語(yǔ)義的另一種實(shí)現(xiàn)方式,因此所有 不應(yīng)當(dāng)使用函數(shù)重載的理由 也也都適用于缺省參數(shù)褥傍。
虛函數(shù)調(diào)用的缺省參數(shù)取決于目標(biāo)對(duì)象的靜態(tài)類型儡嘶,此時(shí)無(wú)法保證給定函數(shù)的所有重載聲明的都是同樣的缺省參數(shù)。
缺省參數(shù)是在每個(gè)調(diào)用點(diǎn)都要進(jìn)行重新求值的恍风,這會(huì)造成生成的代碼迅速膨脹蹦狂。作為讀者誓篱,一般來(lái)說(shuō)也更希望缺省的參數(shù)在聲明時(shí)就已經(jīng)被固定了,而不是在每次調(diào)用時(shí)都可能會(huì)有不同的取值凯楔。
缺省參數(shù)會(huì)干擾函數(shù)指針, 導(dǎo)致函數(shù)簽名與調(diào)用點(diǎn)的簽名不一致. 而函數(shù)重載不會(huì)導(dǎo)致這樣的問(wèn)題.
3.6.4 結(jié)論
對(duì)于虛函數(shù)窜骄,不允許使用缺省參數(shù),因?yàn)樵谔摵瘮?shù)中缺省參數(shù)不一定能正常工作摆屯。如果在每個(gè)調(diào)用點(diǎn)缺省參數(shù)的值都有可能不同邻遏,在這種情況下缺省函數(shù)也不允許使用。(例如虐骑,不要寫像 void f(int n = counter++);
這樣的代碼准验。)
在其他情況下,如果缺省參數(shù)對(duì)可讀性的提升遠(yuǎn)遠(yuǎn)超過(guò)了以上提及的缺點(diǎn)的話廷没,可以使用缺省參數(shù)糊饱。如果仍有疑惑,就使用函數(shù)重載颠黎。
四另锋、函數(shù)重載
4.1 總述
若要使用函數(shù)重載,則必須能讓讀者一看調(diào)用點(diǎn)就胸有成竹狭归,而不用花心思猜測(cè)調(diào)用的重載函數(shù)到底是哪一種砰蠢。這一規(guī)則也適用于構(gòu)造函數(shù)。
4.2 定義
你可以編寫一個(gè)參數(shù)類型為 const string&
的函數(shù)唉铜,然后用另一個(gè)參數(shù)類型為 const char*
的函數(shù)對(duì)其進(jìn)行重載:
class MyClass {
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};
4.3 優(yōu)點(diǎn)
通過(guò)重載參數(shù)不同的同名函數(shù),可以令代碼更加直觀律杠。模板化代碼需要重載潭流,這同時(shí)也能為使用者帶來(lái)便利。
4.4 缺點(diǎn)
如果函數(shù)單靠不同的參數(shù)類型而重載(acgtyrant 注:這意味著參數(shù)數(shù)量不變)柜去,讀者就得十分熟悉 C++ 五花八門的匹配規(guī)則灰嫉,以了解匹配過(guò)程具體到底如何。另外嗓奢,如果派生類只重載了某個(gè)函數(shù)的部分變體讼撒,繼承語(yǔ)義就容易令人困惑。
4.5 結(jié)論
如果打算重載一個(gè)函數(shù)股耽,可以試試改在函數(shù)名里加上參數(shù)信息根盒。例如,用 AppendString()
和 AppendInt()
等物蝙,而不是一口氣重載多個(gè) Append()
炎滞。如果重載函數(shù)的目的是為了支持不同數(shù)量的同一類型參數(shù),則優(yōu)先考慮使用 std::vector
以便使用者可以用 列表初始化 指定參數(shù)诬乞。
册赛。
五钠导、分隔指令與詢問(wèn)
5.1 總述
指令與詢問(wèn)分隔:函數(shù)要么修改其對(duì)象的狀態(tài),要么返回該對(duì)象的有關(guān)信息森瘪,一個(gè)函數(shù)只做一件事牡属。
5.2 說(shuō)明
public boolean set(String attribute, String value);
該函數(shù)設(shè)置某個(gè)指定屬性,如果成功就返回true扼睬,如果不存在那個(gè)屬性則返回false逮栅。這樣就導(dǎo)致了以下語(yǔ)句:
if (set("username", "unclebob"))...
從讀者的角度考慮一下,這是什么意思呢痰驱?它是在問(wèn)username屬性值是否之前已設(shè)置為unclebob嗎证芭?或者它是在問(wèn)username屬性值是否成功設(shè)置為unclebob呢?從這行調(diào)用很難判斷其含義担映,因?yàn)閟et是動(dòng)詞還是形容詞并不清楚废士。
作者本意,set是個(gè)動(dòng)詞蝇完,但在if語(yǔ)句的上下文中官硝,感覺它像是個(gè)形容詞。該語(yǔ)句讀起來(lái)像是說(shuō)“如果username屬性值之前已被設(shè)置為unclebob”短蜕,而不是“設(shè)置username屬性為unclebob氢架,看看是否可行,然后······”朋魔。要解決這個(gè)問(wèn)題岖研,可以將set函數(shù)重命名為setAndCheckIfExists,但這對(duì)提高if語(yǔ)句的可讀性幫助不大警检。真正的解決方案是把指令與詢問(wèn)分隔開來(lái)孙援,防止混淆的發(fā)送:
if (attributeExists("username")) {
setAttribute("username", "unclebob");
···
}
? 由 Leung 寫于 2019 年 4 月 30 日
? 參考:Google 開源項(xiàng)目風(fēng)格指南——4.函數(shù)
[代碼整潔之道]