命名

  • 别害怕长名称
  • 命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名

函数

  • 函数要么做什么事,要么回答什么事(分离指令与询问,询问指的是如if-else)
  • 函数只做一件事,函数中的语句都要在同一抽象层级上
  • 让代码拥有自顶向下的阅读顺序,让每个函数后面都跟着下一抽象层级的函数
  • 「单一出口」规则,其实不是那么有用。保持代码清晰才是最关键的:如果「单一出口」能使这个函数更清楚易读,那么就使用单一出口;否则就不必这么做。(《重构2》)
//单一出口
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else {
if (_isRetired) result = retiredAmount();
else result = normalPayAmount();
};
}
return result;
};
//清晰易读
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};

参数

  • 有足够的理由才能用三个以上参数
  • 如果函数要对输入参数进行转换操作,转换结果就该体现为返回值
  • 如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了
  • 有可变参数的函数可能是一元、二元甚至三元,超过这个数量就可能要犯错了
  • 对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式

错误处理

  • try/catch代码块把错误处理与正常流程混为一谈,最好把try和catch代码块的主体部分抽离出来,另外形成函数
  • 遇到错误时,使用异常而非返回码
  • 在编写可能抛出异常的代码时,最好先写try-catch-finally语句
  • 每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所
  • 创建一个类或配置一个对象,用来处理特例,异常行为被封装到特例对象中
  • 创建信息充分的错误消息,并和异常一起传递出去
  • 自定义的异常类最重要的考虑是它们如何被捕获
  • 将第三方API打包,方便将一些复杂的异常捕获过程封装
  • 尽量别返回null值,也别传递null值

对象

  • 得墨忒耳律(The Law of Demeter):模块不应了解它所操作对象的内部情形,意味着对象不应通过存取器曝露其内部结构,因为这样更像是曝露而非隐藏其内部结构
  • 最为精练的数据结构,是一个只有公共变量、没有函数的类,这种被称为数据传送对象,或DTO(Data Transfer Objects)

  • 放-闭合原则(OCP):类应当对扩展开放(通过扩展系统而非修改现有代码来添加新特性),对修改封闭
  • 依赖倒置原则(Dependency Inversion Principle,DIP),类应该依赖于抽象而不是依赖于具体细节
  • 内聚性强,如果一个类的每个变量都被每个方法所使用,则该类具有最大的内聚性
  • 职责单一,类的名称应当描述其权责
  • 类应该从一级变量列表开始,如果有公共静态变量,应该先出现,然后是私有静态变量,以及实体变量,很少会有公共变量
  • 公共函数应该跟在变量列表之后
  • 保持变量和工具函数的私有性,但并不执着于此
  • 保持函数和参数列表短小的策略,有时会导致为一组子集方法所用的实体变量数量增加,此时应当尝试将这些变量和方法分拆到两个或多个类中,让新的类更为内聚
  • 将大函数拆为许多小函数,往往也是将类拆分为多个小类的时机

注释

  • 注释总是一种失败