java基础与进阶CodeEase代码的艺术

如何写出优雅的函数(Clean Code读书笔记之二)

2016-05-02  本文已影响1034人  TheAlchemist

函数是代码组合的基本单位,高级编程语言的发展从结构化到面向对象,再到最近大有要复兴之势的函数式编程,函数都是组成这座大厦不可或缺的基本组成部分,它的重要性不言而喻。本文将依据「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。接下来会有一些原则,如果你可以理解它们并尽量遵守,会有效的帮助你写出小而高质量的代码。

2. 只做一件事

一个好的函数应该只做一件事,这是大家经常见到的说法,那么怎么才能说一个函数是「只做了一家事」呢,如果一个函数做了3件事,这而3件事又可以说是另外一件事的3个步骤,那么这个算不算是「只做一件事」呢?

作者认为这种情况是属于「只做了一件事」的,但是这个函数应该只包含这3个步骤,而不包括3个步骤的具体实现,也就是说,如果这个函数里包含了某一个步骤的具体实现,那么这个函数就不是「只做一件事」。

换言之,如果一个函数function1里的几个语句可以被extract出来成为一个新的函数function2,那么function1就没有达到「只做一件事呢」的标准。

3. 每个函数只包含同一个层级的抽象

这个原则是比较好理解的,比如代码2-1中的getWikiPage()函数的内部实现,和renderPageWithSetupsAndTeardowns()里的几个函数调用就不在一个抽象层级上;或者对newPageContent.append()的函数调用明显就与其他函数调用不是在同一抽象层级。

4. Switch语句

在coding过程中很难避免要用到switch语句的情况,在这种情况下就很难去保持以上讲到的一些规则,作者的建议是对于Switch语句,应该将它封装起来,使用多态(具体讲可能就是定义抽象工厂方法,然后switch可以被放在抽象工厂方法的实现类里,同时让switch对于它的调用者完全透明)为这个函数的真正使用者提供服务。

5. 使用自解释(descriptive)的名字

函数的名字要能够描述它本身的工作内容,不要害怕函数名会变得很长,一个长的自解释的名字比一个短的不明所以的名字要好得多。

同时在名字的选择上要前后一致,这个原则同前一篇讲命名中的一些规则如出一辙。

6. 函数参数

最理想的函数应该没有参数的,其次比较好的是只有一个参数的、只有两个的,包含三个参数的函数应该尽量被避免使用,三个以上的参数的函数不应该存在。

含有参数的函数明显已经包含了一个和函数内容不在同一个层级上的抽象(参数本身),还有从测试的观点看,参数的存在也提高了写测试用例的难度。

有时候有些参数还被作为输出用途,这种情况应该尽量避免。

「Clean Code」整本书都是基于Java和其他类似的高级语言为基础的,但是在一些理念不同的语言中,含有多个参数的函数在理解上是完全没有问题的,但是它们可能在其他方面(比如编写测试用例)也会存在各种各样的问题。

此外还有一种场景比较不那么常见,但是也十分有用的单个参数函数形式,event。这种情况下函数接受一个参数event,但是没有返回值,函数会根据这个event对象来进行一些其他操作。

使用这些形式时也同时要使用一个合适的名字来清晰的描述函数的用途,从而让代码阅读者可以清晰快速地了解函数的目的。

还有一种「关键词」的模式来作为函数的名字,比如使用assertEquals(expected, actural),而不是assertExpectedEqualsActual(expected, actural),这样就不需要读者必须知道参数的顺序,从而降低了阅读此代码的难度。

7. 不要有副作用

函数的副作用就像谎言一样,一个函数声称它要做一件事,但是同时它又做了另外一件「隐藏的」事,有时候它会修改自己的类中的属性,有时候它会修改传进来的参数或者其他全局变量,不管哪种情况,这都是不好的。

8. 执行和检索分离

一个函数要不执行了某个行为(比如改变了一个对象的状态),要不回答了某个问题(比如返回某个对象的某些信息),但是不应该同时做这样两件事。

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

返回错误码轻微地违反了上一个规则,作者建议不要使用返回错误码而使用抛出Exception的方法。这样的方法往往会时代码更短而清晰易读。

依赖磁铁在普通的日常开发中很难避免,而且我觉得这也不是一个需要强力避免的原则。

10. 不要重复自己(Don't repeat yourself)

我们在写代码时往往会将同样一个算法、或者一段处理逻辑、甚至一段相同的代码重复的出现在多个地方,甚至是同一个源文件的不同地方。这往往是很多代码质量问题的源头,也有很多编程原则和最佳实践都是为了控制或者消灭重复而产生的。

11. 你如何才能写出这样的函数

程序员写代码跟其他类型的写作一样,是一个不断改善的过程。作者认为写出好的函数大致是这样几个步骤

12. 总结

如果你遵循以上所有的规则,你的函数会变得体量短小、良好命名、并且具有良好的组织结构。但是永远不要忘记这不是目的而是手段,你的最终目的是让整个系统更加完美。

我的博客

上一篇 下一篇

猜你喜欢

热点阅读