代碼風(fēng)格(2)——函數(shù)

一、簡(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ù)
    [代碼整潔之道]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扇雕,隨后出現(xiàn)的幾起案子拓售,更是在濱河造成了極大的恐慌,老刑警劉巖镶奉,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件础淤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哨苛,警方通過(guò)查閱死者的電腦和手機(jī)鸽凶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)建峭,“玉大人吱瘩,你說(shuō)我怎么就攤上這事〖W海” “怎么了使碾?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蜜徽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我票摇,道長(zhǎng)拘鞋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任矢门,我火速辦了婚禮盆色,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祟剔。我一直安慰自己隔躲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布物延。 她就那樣靜靜地躺著宣旱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叛薯。 梳的紋絲不亂的頭發(fā)上浑吟,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音耗溜,去河邊找鬼组力。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抖拴,可吹牛的內(nèi)容都是我干的燎字。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼阿宅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼候衍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起家夺,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伐弹,沒想到半個(gè)月后拉馋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惨好,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年煌茴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片日川。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔓腐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出龄句,到底是詐尸還是另有隱情回论,我是刑警寧澤散罕,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站傀蓉,受9級(jí)特大地震影響欧漱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葬燎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一误甚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谱净,春花似錦窑邦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浩蓉,卻和暖如春派继,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捻艳。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工驾窟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人认轨。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓绅络,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嘁字。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恩急,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351