2.函数(代码的整洁之道)

2.函数(代码的整洁之道)


目录

  1. 短小
  2. 只做一件事
  3. 每个函数一个抽象层次
  4. switch语句
  5. 使用描述性的名称
  6. 函数参数
  7. 无副作用
  8. 分隔指令与询问
  9. 使用异常代替返回的错误码
  10. 别重复自己
  11. 结构化编程
  12. 如何写出这样的函数
  13. 小结

注:代码的整洁之道PDF: https://pan.baidu.com/s/16PLDWPiusGjcUfW_jgOm5w 密码: s708


1. 短小

  1. 函数的第一规则是要短小,第二条规则还是要更短小。
  2. 每个函数都只说一件事,而且每个函数会依序把你带到下一个函数。
  3. 函数应该有多短小?应该缩短成下面代码的样子。
    在这里插入图片描述
  4. if语句、else语句、while语句等,其中的代码应该只有一行,该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且因为块内调用的函数拥有较具体说明性的名称,从而增加了文档上的价值。
  5. 这也意味着函数不应该大到足够容纳嵌套结构。函数的缩进层不该多余一层或两层。

2. 只做一件事

  1. 函数应该只做一件事,做好这件事。只做这一件事。
  2. 编写函数是为了把大一些的概念拆分成另一抽象层上的一系列步骤。
  3. 自顶向下规则,让每个函数后面都跟着位于下咦抽象层级的函数,在查看函数列表时,就能遵循抽象层级向下阅读了。

3. 每个函数一个抽象层次

  1. 要确保函数只做一件事,函数中的语句都要在同一抽象层级上

4. switch语句

  1. 写出短小的switch语句很难,及时只有两种条件也要比单个代码块或函数大得多。
  2. switch存在几个问题
    1. 它太长,每当出现新的类型,还会变得更长
    2. 其实,明显不止做了一件事。
    3. 违反了单一权责原则
    4. 违反了开放闭合原则,每当添加新类型,就必须修改。
    5. 最麻烦的是可能导出皆有类似结构的函数。
  3. 该解决方案是将switch语句埋到抽象工厂下,不让任何人看到。改工厂使用switch语句为Employee的派生物创建适当的实体,而不同的函数,如calculatePay、isPayday和deliverPay等,则由Employee接口多态地接受派遣。
  4. 对于switch语句,如果只出现一次,用于创建多态对象,而且隐藏在某个继承关系中,在系统其它部分看不到,就还能容忍。
    在这里插入图片描述

5. 使用描述性的名称

  1. 如果每个例程都让你感到深合已意,就是整洁代码。
  2. 函数越短小,功能越集中,就越便于取个好名字。
  3. 别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。
  4. 命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。例如,includeSetupAndTeardownPages、includeSetupPages、includeSuiteSetupPage和includeSetupPage等。这些词使用了类似的措辞,依序讲出一个故事。

6. 函数参数

  1. 最理想的参数数量是零,其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)
  2. 参数带有太多的概念性,参数与函数名处在不同的抽象层级,要求你了解目前并不特别重要的细节。

1. 一元函数的普遍形式

  1. 像在boolean fileExists(“MyFile”)中那样,可能是操作该参数,将其转换为其他说明东西,再输出。例如,InputStream fileOpen(“MyFile”)把String类型的文件名转换为InputStream类型的返回值,就是读者看到函数时所期待的东西。你应该选用较能区别这两种理由的名称,而且总能在一致的上下文中使用这两种形式。
  2. 还有一种就是事件。在这种形式中,有输入参数而无输出参数。程序将函数看作是一个事件,使用该参数修改系统状态,例如 void passowrdAttemptFailedNtimes(int attempts)。小心使用这种形式,应该让读者很清楚的了解它是个事件。谨慎的选用名称与上下文语境。
  3. 对于转换,使用输出参数而非返回值令人迷惑。**如果函数要对输入参数进行转换操作,转换结果就该体现为返回值。**实际上,StringBuffer transform(StringBuffer in)要比void transform(StringBuffer out)强,即便第一种形式只简单地返回输出参数。

2. 标识参数

  1. 标识参数丑陋不堪,向函数传入布尔值简直就是骇人听闻,表示函数不止做一件事,如果标识为true将这么做,标识为false将那样做。
  2. 看到render(Boolean isSuite),应该把函数一分为二:renderForSuite()和renderForSingleTest()

3. 二元函数

  1. 有两个参数的函数要比一元函数难懂。例如,writeField(name)要比writeField(outputStream,name)好懂。
  2. 你应该尽量利用一些机制将其转换成一元函数,例如,可以把writeField方法写成outputStream的成员之一,从而能这样用:outputStream.writeField(name),也可以把outputStream写成当前类的成员变量,从而无需再传递它。还可以分离出类似FieldWriter的新类,在其构造器中采用outputStream,并包含一个write方法。

4. 三元函数

  1. 有三个参数的函数要比二元函数难懂很多。

5. 参数对象

  1. 如果函数看来需要两个、三个或以上的参数,就说明其中一些参数应该封装成类了。
  2. 例如,下面两个声明的差别
    1. Circle makeCircle(double x, double y, double radius)
    2. Circle makeCircle(Point center, double radius)
  3. 从参数创建对象,从而减少参数数量,当一组参数被共同传递,就像上面的x和y,往往就是该有自己名称的某个概念的一部分。

6. 动词与关键词

  1. 给函数取个好名字,能较好解释函数的意图,以及参数的顺序和意图。
  2. 对于一元函数,函数和参数应该形成良好的动词/名词对形式,例如,write(name)就相当令人认同。
  3. 这个例子展示了函数名称的关键字形式。使用这种形式,我们把参数的名称编码成了函数名,例如,assertEqual改成assertedEqualsActual(expected, actual)可能会好些。

7. 无副作用

  1. 函数承诺只做一件事,,但有时会对自己类中的变量做出未能预期的改动。有时,它会把变量搞成向函数传递的参数或者是系统的全局变量。无论哪种情况,会导致古怪的时序性耦合以及顺序依赖。
  2. 如下面代码
    在这里插入图片描述
  3. 副作用在于Session.initialize()的调用。checkPassword函数,顾名思义,是用来检查密码的,该名称并未暗示它会初始化该次会话。
  4. 这一副作用造出了一次时序性耦合,也就是说,checkPassword只能在特定的时刻调用。
  5. 在本例中,可以重命名函数为 checkPasswordAndInitializeSession,虽然还是违反了只做一件事原则。

1. 输出参数

  1. 面向对象语言中对输出参数的大部分需求已经消失了,换言之
  2. public void appendFooter(StringBuffer report)最好改成 report.appendFooter()

8. 分隔指令与询问

  1. 函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干会导致混乱。比如:
  2. public boolean set(String attribute, String value);
  3. 该函数设置了某个指定属性,如果成功就返回true,如果不存在那个属性则返回false。这就导致了以下语句
if (set("username", "unclebob"))
  1. 从if调用很难判断其含义,因为set是动词还是形容词并不清楚。可将set函数重命名为setAndCheckIfExists,但这对提高if语句的可读性帮助不大。
  2. 真正解决方案是把指令与询问分隔开来,防止混淆的发生:
if (attributeExists("username")){
    
    
	setAttribute("username", "unclebob")
}
....

9. 使用异常代替返回的错误码

  1. 从指令式函数返回错误码轻微违反了指令与询问分隔的规则,它鼓励在if语句判断中把指令当做表达式使用。
if (deletePage(page)== E_OK)
  1. 这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。
  2. 如果使用异常代替返回错误码,错误处理就能出主路径代码中分离出来,得到简化。

1. 抽离 Try/Catch 代码块

  1. try/catch代码块丑陋不堪,搞乱了代码结构,最好把try/catch代码块的主体部分抽离出来,另外形成函数。
    在这里插入图片描述
  2. 上例中,delete函数只与错误处理有关。

10. 别重复自己

  1. 重复的代码会导致臃肿,并且需要修改的地方在增加,增加了错误的可能性。

11. 结构化编程

12. 如何写出这样的函数

  1. 刚开始写函数时,会冗长和复杂,但要打磨这些代码,分解函数、修改名称、消除重复。缩短和重新安置方法,有时还拆散类。

13. 小结

  1. 代码赏析
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");
        }

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_41910694/article/details/110920676