代码整洁之道 读书笔记 - 第3章 函数

短小

函数的第一规则是要短小。第二条规则是还要更短小。

函数20行封顶最佳

if语句、else语句、while语句等,其中的代码块应该只有一行,而且,块内调用的函数拥有较具说明性的名称,还能起到文档的作用。

只做一件事

函数应该做一件事。做好这件事。只做这一件事。

每个函数一个抽象层级

自顶向下读代码:向下规则.

switch语句

只出现一次,用于创建多态对象,且隐藏在某个继承关系中,系统其他部分看不到。

使用描述性的名称

沃德原则:如果每个例程都让你感到深合己意,那就是整洁代码。

长而具有描述性的名称,要比短而令人费解的名称好,要比描述性的长注释好。

函数参数

理想的参数数量是越少越好,尽量避免三个以上的参数。

参数带有太多概念性,includeSetupPage()要比includeSetupPageInto(newPage-Content)易于理解。

从测试的角度看,要编写能确保参数的各种组合运行正常的测试用例。

1、一元函数的普遍形式

    1)有输入参数也有输出参数

boolean fileExists("MyFile")
InputStream fileOpen("MyFile")

    2)有输入参数没有输出参数

void passwordAttemptFailedNtimes(int attempts)

    3)尽量避免编写不遵循这些形式的一元函数

void includeSetupPageInto(StringBuffere pageText)

    对于转换,使用输出参数而非返回值令人迷惑。

    如果函数要对输入参数进行转换操作,转换结果就该体现为返回值。实际上,StringBuffer transform(StringBuffer in) 比 void transform(StringBuffer out) 好。

2、标识参数

    向函数传入布尔值就是说明本函数不止做一件事。如果标识为true将会这样做,标识为false则会那样做。

    将render(Boolean isSuite)函数一分为二:renderForSuite() 和 renderForSingleTest()

3、二元函数

    1)两个参数的函数要比一元函数难懂

writeField(name)  //一个参数的可读性更好
writeField(outoutStream, name)

    2)有些时候两个参数正好

Point p = new Point(0, 0)

    笛卡儿点天生拥有两个参数。如果看到new Point(0)会反而感到奇怪。

    3)应该尽量利用一些机制将其转换成一元函数

outputStream.writeField(name)  //把writeField方法写成outputStream的成员之一

4、三元函数

    有三个参数的函数要比二元函数难懂得多,建议在写三元函数前一定要想清楚。

5、参数对象

    如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

6、参数列表

7、动词与关键字

    给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。

    对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式

write(name)  //不管这个“name”是什么,都要被“write”
writeField(name)  //更好的名称,它告诉我们,“name”是一个“field”

无副作用

副作用是一种谎言。函数承诺只做一件事,但还是会做其他被藏起来的事

public class UserValidator {
  private Cryptographer cryptographer;

  public boolean checkPassword(String userName, String password) {
    User user = UserGateway.findByName(userName);
    if (user != User.NULL) {
      String codedPhrase = user.getPhraseEncodedByPassword();
      String phrase = cryptographer.decrypt(codedPhrase, password);
      if ("Valid Password".equals(phrase)) {
        Session.initialize();
        return true;
      }
    }
    return false;
  }
}

代码的副作用在于对Session.initialize()的调用。checkPassword函数,就是用来检查密码的。并未暗示它会初始化该次会话。当某个误信了函数名的调用者想要检查用户有效性时,就得冒抹除现有会话数据的风险。

这一副作用造出了一次时序性耦合。checkPassword如果在不合适的时候调用,会话数据就有可能沉默地丢失。

如果一定要时序性耦合,就应该在函数名称中说明。重命名函数为checkPasswordAndInitializeSession,但这还是违反了“只做一件事”的规则。

分隔指令与询问

函数要么做什么事,要么回答什么事,但二者不可兼得

public boolean set(String attribute, String value);

 该函数设置某个指定属性,如果成功就返回true,如果不存在那个属性则返回false。这样就导致了以下语句

if (set("username", "unclebob")) ...

它是在问username属性值是否之前已设置为unclebob,或是在问username属性值是否成功设置为unclebob。

要解决这个问题可以将set函数重命名为setAndCheckIfExists,但真正的解决方案是把指令与询问分隔开来,防止混淆的发生

if (attributeExists("username")) {
  setAttribute("username", "unclebob");
  ...
}

使用异常替代返回错误码

1、抽离Try/Catch代码块

    把Try/catch代码块的主体部分抽离出来,另外形成函数。

2、错误处理就是一件事

    函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其他事。

    关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容。

3、依赖磁铁

    返回错误码通常暗示某处有个类或是枚举定义了所有错误码

public enum Error {
  OK,
  INVALID,
  NO_SUCH,
  LOCKED,
  OUT_OF_RESOURCES,
  WAITING_FOR_EVENT;
}

    这样的类就是一块依赖磁铁(dependency magnet);当Error枚举修改时,所有使用它的类都需要重新编译和部署。所以程序员宁愿复用旧的错误码,而不添加新的。

别重复自己

重复可能是软件中一切邪恶的根源。许多原则与实践规则都是为控制与消除重复而创建。

考德(Codd)的数据库范式都是为消灭数据重复而服务。

面向对象编程将代码集中到基类,从而避免了冗余。

面向方面编程(Aspect Oriented Programming)、面向组件编程(Component Oriented Programming)多少也都是消除重复的一种策略。

结构化编程

有些程序员遵循Edsger Dijkstra的结构化编程规则。Dijkstra认为,每个函数、函数中的每个代码块都应该有一个入口、一个出口

遵循这些规则,意味着每个函数中只该有一个return语句,循环中不能有break或continue语句,而且永永远远不能有任何goto语句

对于小函数,这些规则助益不大,而且偶尔出现return、break或continue语句没有坏处,甚至还比单入单出原则更具有表达力。

posted @ 2018-11-13 17:13  TanSea  阅读(296)  评论(0编辑  收藏  举报