【C语言】如何写出优雅的函数(Clean Code读书笔记之二)

函数是代码组合的主题单位,高级编程语言的前行从结构化到面向对象,再到近来大有要复兴之势的函数式编程,函数都是结合那座大厦不可或缺的着力组成部分,它的重中之重不言而喻。本文将基于「clean
code」第三章的始末,大约捋五次如何写出优雅的函数。

其三章讲了在写函数时应当小心的工作,小编首先拿一个开源的测试工具(Fitnesse)来举了一个例证,来表明好的函数该是什么样子。原则上实在和上一篇中讲到的命名的有的规则很一般,就是一个名字只要可以自解释的,当然这一章还会讲到很多新的事物,那里拿那些函数作为一个引子。

//代码2-1
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite)throws Exception{
    boolean isTestPage = pageData.hasAttribute("Test");
    if(isTestPage){
        WikiPage testPage = pageData.getWikiPage();
        StringBuffer newPageContent = new StringBuffer();
        includeSetupPages(test Page, newPageContent, isSuite);
        newPageContent.append(pageData.getContent());
    }
}

从上述代码可以见见上一章中涉及的片段事物,不要惧怕你的函数或者变量名定义的很长,在编译器已经长足发展的明天,对于很长的函数命名的处理已经不会变成语言照旧性质的瓶颈了;然后所有函数就像在描述做一件工作的步子,每一步我们都能看懂那是在干些什么业务,以上那些事例很好的突显了一个「好函数」应该的旗帜。

我早已听过一个Oracle的工程师讲到他们的编码须要,包罗一个函数内部的if不可以超越七个,所有的函数应该限制在10行以内等,与那些例子的想念都是如出一辙。

上边开首列举小编对一个写好一个函数应该依照的条件的讲述:

1. 小!!!

The first rule of functions is that they should be small. The second
rule of functions is that they should be smaller than that.

小的函数不必然好,不过足以一定的是太长的函数一定是在某种程度上很烂的。

小编谈到他径直以来的理念是函数不应有超越一个显示器能展现的程度,当然现在的屏幕越来越大,那么一旦非要使用一个数字来说,他觉得一行不应有超越150个字符(我认为实际最好还保持在80或120个字符以内),函数的行数最好不要超越100行,要是能简单20行最好然则了。

小编在这里讲述了他和资深的Kent Beck的一段讲话来比喻,Kent
Beck说一个函数最多应该不要超过4行。那几个确实有点恐怖,我觉得能操纵在20行以内就已经很厉害了。函数要规划的小并不是目标,而是通过写出来小函数来已毕让程序的阅读者可以长足的看懂那段程序,而且更短的次序往往意味着更少的bug。接下来会有部分标准化,如若你可以了解它们并尽可能遵守,会使得的提携你写出小而高质量的代码。

  • 代码块和缩进
    作者在这一节讲了三个工作,代码块(就是if或者else块)的情节最多不当先一行,那样这么些在块中的函数往往能够有一个自解释的名字;一个函数的缩进等级最多无法跨越2级。这多个须求在我看来都是最为严刻了,一般人都做不到,然而朝着那方面努力总是好的。

2. 只做一件事

一个好的函数应该只做一件事,那是豪门平常来看的传教,那么怎么才能说一个函数是「只做了一家事」呢,即便一个函数做了3件事,那而3件事又能够说是其余一件事的3个步骤,那么这几个算不到底「只做一件事」呢?

作者认为那种情形是属于「只做了一件事」的,不过这些函数应该只含有那3个步骤,而不包罗3个步骤的切实可行落到实处,也就是说,即使这几个函数里带有了某一个步骤的实际完成,那么那些函数就不是「只做一件事」。

换言之,假若一个函数function1里的多少个语句可以被extract出来改成一个新的函数function2,那么function1就不曾达标「只做一件事吗」的正儿八经。

  • 函数里的小节
    稍稍人写代码喜欢在一个函数里使用差距的代码小节(比如第四节定义变量,第一节实例化这几个变量,首节再对那些变量进行部分操作),作者认为那严重违反了「只做一件事」原则,是可怜不佳的习惯。

3. 每个函数只含有同一个层级的悬空

本条条件是相比较好领悟的,比如代码2-1中的getWikiPage()函数的中间贯彻,和renderPageWithSetupsAndTeardowns()里的多少个函数调用就不在一个空洞层级上;或者对newPageContent.append()的函数调用鲜明就与其余函数调用不是在同样抽象层级。

  • 自上而下的翻阅代码:层层向下
    小编认为好的代码读起来应当像是记叙文,自上而下的讲授完结一件业务的手续。换种说法就是阅读一个程序就像在翻阅一堆的TO(英文单词to,为了……)段落:为了做某件业务1,大家去做了作业2,为了工作情2,大家去做了业务3。与此同时小编又说认为那一个原则根据起来格外难,但是那是一个尽力的势头,努力去上学那个标准会支持您写出来更小的,只含有同一流抽象的函数。

4. Switch语句

在coding进度中很难防止要用到switch语句的处境,在这种情形下就很难去维持以上讲到的有些平整,作者的提出是对于Switch语句,应该将它包裹起来,使用多态(具体讲或许就是概念抽象工厂方法,然后switch可以被放在抽象工厂方法的兑现类里,同时让switch对于它的调用者完全透明)为这一个函数的着实使用者提供服务。

5. 选取自解释(descriptive)的名字

函数的名字要可以描述它本身的行事内容,不要惧怕函数名会变得很长,一个长的自解释的名字比一个短的不明所以的名字要好得多。

与此同时在名字的选料上要前后一致,那些标准同前一篇讲命名中的一些规则如出一辙。

6. 函数参数

最出彩的函数应该没有参数的,其次比较好的是唯有一个参数的、唯有八个的,包蕴多少个参数的函数应该尽可能被防止选用,多少个以上的参数的函数不应当留存。

含有参数的函数分明已经包括了一个和函数内容不在同一个层级上的虚幻(参数本身),还有从测试的视角看,参数的存在也升高了写测试用例的难度。

偶然有些参数还被作为出口用途,那种状态应当尽量防止。

「Clean
Code」整本书都是基于Java和其余类似的尖端语言为底蕴的,不过在有些见识不一的语言中,含有多少个参数的函数在领略上是全然小难点的,可是它们或者在其他方面(比如编写测试用例)也会存在五光十色的难点。

  • 单个参数
    有二种比较广泛的处境适合拔取单个参数的函数,一种是「函数是要对此参数问一个难点,并获得一个答案」(比如
    boolean
    fileExists(“MyFile”),另一种是「此函数是要对此函数对此参数举办一个操作,把它变成此外一中东西,并将它回到」(InputStream
    fileOpen(“MyFile”))。

除此以外还有一种情景比较不那么周边,但是也至极得力的单个参数函数格局,event。那种情形下函数接受一个参数event,可是尚未再次来到值,函数会依据这一个event对象来举办局地其余操作。

使用这一个样式时也同时要运用一个适宜的名字来清晰的讲述函数的用处,从而让代码阅读者可以清楚飞快地问询函数的目标。

  • Flag参数
    Flag参数是丑陋的,不应该被运用;往往一个饱含Flag参数的函数可以被解说或简捷的利用if
    else代码块就能够达到相同的效应。

  • 几个参数
    作者认为多少个参数的函数是难以领悟的,唯一可能比较方便的风貌是,那四个参数本身就是「一个独自对象的七个不变的零件」,比如Point(x,
    y),x轴坐标和y轴坐标共同构成了平面坐标系中的一个点。
    不过,小编认为四个参数的函数并不是暴虐了,所有人都不可避免的在其实编程中行使它们,不过毫无疑问要询问它们是会推动一些不良后果的,最好是能够把这一个函数都转化成单个参数的函数。

  • 多少个参数
    七个参数的函数相当不便精通如故不时会被误会,所以在动用前最好三思再三思。

  • 参数对象
    当一个函数须求2个或者3个参数是,往往可以将她们尽数要么局地封装成一个对象,从而达到收缩参数数量,使函数越发便于通晓和掩护的目标。

  • 参数列表
    偶然一个函数会接受一个变长的参数列表,那种场合下实际它们可以用作一个List结果的参数对象,所以可以被视作一个单个参数的函数,小编是比较推荐那种措施的。

      public String format(String format, Object...args)
    
  • 动词和要紧词
    行使一个好的函数名可以清晰的分解函数和参数的目标。对于单个参数的函数,函数和参数应该是一个「动词 +
    名词」的重组。

再有一种「关键词」的方式来作为函数的名字,比如利用assertEquals(expected,
actural),而不是assertExpectedEqualsActual(expected,
actural),那样就不必要读者必须通晓参数的依次,从而下跌了阅读此代码的难度。

7. 决不有副成效

函数的副成效就好像谎言一样,一个函数声称它要做一件事,然则同时它又做了其余一件「隐藏的」事,有时候它会修改自己的类中的属性,有时候它会修改传进来的参数或者其余全局变量,不管哪个种类景况,那都是不佳的。

  • 出口参数
    在「面向对象编程」出现此前(比如C语言),有时候必须使用一个参数(平时是一个指针)来作为函数的出口,然则在「OOP」中,「this」指针往往隐喻了它是用来作为出口指针的效用,那么输出参数应该尽量的幸免使用,要是有这么的需要,修改「this」中的属性往往是更好的挑选。

8. 实践和搜索分离

一个函数要不履行了某个行为(比如改变了一个对象的动静),要不解惑了某个难点(比如再次回到某个对象的少数音讯),不过不该同时做这么两件事。

9. 使用Exception,不要选用重回错误码

回去错误码轻微地反其道而行之了上一个平整,小编提议不要选取再次回到错误码而使用抛出Exception的艺术。那样的艺术往往会时代码更短而明显易读。

  • 提取try/catch代码块
    小编提议在动用try代码块时要将try中的代码提取,使得整个try/catch代码块是一个通通的错误处理代码块,而不包蕴具体的任何操作逻辑,那就符合了「只做一件事」的标准。

  • 错误处理是「一件事」
    作者提出做错误处理的函数,应该只做错误处理那「一件事」,也就是说一个包括try关键词的错误处理函数应该是以try这一个单词为开首的。如代码2-2所示:

      代码2-2
      public void delete(Page page){
          try{
              deletePageAndAllReferences(page).
          }catch(Exception e){
              logError(e);
          }
      }
    
  • 依傍磁铁
    归来错误码的方法还有一个标题是,往往在那种光景下,所有的内需错误处理的地点都会须要依靠那一个类仍然文件,那样的被广大看重的类叫做「重视磁铁」

借助于磁铁在普通的家常用度中很难幸免,而且我觉着这也不是一个亟需强力制止的尺度。

10. 并非再一次自己(Don’t repeat yourself)

咱俩在写代码时频仍会将同样一个算法、或者一段处理逻辑、甚至一段同样的代码重复的现身在三个地点,甚至是同一个源文件的不比地点。那往往是过多代码质量难点的源头,也有为数不少编程原则和特级实践都是为着控制或者消灭重复而发出的。

  • 结构化编程
    小编认为一旦能担保保持函数「很小」,那么很多题材就足以化解了,那么结构化编程的一对条条框框(比如一个函数唯有一个输入和一个输出)则不必要被坚守了。

11. 您哪些才能写出如此的函数

程序员写代码跟另外品类的文章一样,是一个不住革新的长河。小编认为写出好的函数大致是那般多少个步骤

  • 先直接把想法写成代码,它们或者是很长同时复杂的,可能违反了上述很多条条框框,同时也会有一套单元测试来cover所有的代码用来做回归测试,做为将来对代码重构的底蕴
  • 然后先导对代码举办改动,分离函数,修改名称,消灭重复,同时要维持测试用例完全通过。
  • 末段动用上述所有规则来对函数举办地毯式的最后修改。

12. 总结

若果您依据以上所有的平整,你的函数会变体面量短小、优良命名、并且存有得天独厚的社团结构。但是永远不要遗忘这不是目标而是手段,你的终极目标是让总体连串进一步完善。

自己的博客