整洁四条规则

  1. 可测试的;

  2. 不可重复;

  3. 可读性;
  4. 尽可能减少类和方法的数量;

重构

有了测试,就能保持代码和类的整洁,方法就是递增式地重构代码。添加了几行代码后,就要暂停,琢磨一下变化了的设计。设计退步了吗?如果是,就要清理它,并且运行测试,保证没有破坏任何东西。测试消除了对清理代码就会破坏代码的恐惧。


在重构过程中,可以应用有关优秀软件设计的一切知识。提升内聚性,降低耦合度,切分关注面,模块化系统性关注面,缩小函数和类的尺寸,选用更好的名称,如此等等。这也是应用简单设计后三条规则的地方:消除重复,保证表达力,尽可能减少类和方法的数量。

检查代码的坏味道

注释

C1:不恰当的信息


让注释传达本该更好地在源代码控制系统、问题追踪系统或任何其他记录系统中保存的信息,是不恰当的。例如,修改历史记录只会用大量过时而无趣的文本搞乱源代码文件。通常,作者、最后修改时间、SPR数等元数据不该在注释中出现。注释只应该描述有关代码和设计的技术性信息。

C2:废弃的注释

过时、无关或不正确的注释就是废弃的注释。注释会很快过时。最好别编写将被废弃的注释。如果发现废弃的注释,最好尽快更新或删除掉。废弃的注释会远离它们曾经描述的代码,变成代码中无关和误导的浮岛。

C3:冗余注释

如果注释描述的是某种充分自我描述了的东西,那么注释就是多余的。例如:

i++; // increment i

另一个例子是除函数签名之外什么也没多说(或少说)的Javadoc:

/*

@param sellRequest

@return

@throws ManagedComponentException

*/

public SellResponse beginSellItem(SellRequest sellRequest)

throws ManagedComponentException

注释应该谈及代码自身没提到的东西。

C4:糟糕的注释

值得编写的注释,也值得好好写。如果要编写一条注释,就花时间保证写出最好的注释。字斟句酌。使用正确的语法和拼写。别闲扯,别画蛇添足,保持简洁。

C5:注释掉的代码

看到被注释掉的代码会令我抓狂。谁知道它有多旧?谁知道它有没有意义?没人会删除它,因为大家都假设别人需要它或是有进一步计划。

那样的代码就这样腐烂掉,随着时间推移,越来越与系统没关系。它调用不复存在的函数。它使用已改名的变量。它遵循已被废弃的约定。它污染了所属的模块,分散了想要读它的人的注意力。注释掉的代码纯属厌物。

看到注释掉的代码,就删除它!别担心,源代码控制系统还会记得它。如果有人真的需要,可以签出较前的版本。别被它搞到死去活来。

函数

F1:过多的参数


函数的参数量应该少。没参数最好,一个次之,两个、三个再次之。三个以上的参数非常值得质疑,应坚决避免。(参见前文“函数参数”一节。)

F2:输出参数

输出参数违反直觉。读者期望参数用于输入而非输出。如果函数非要修改什么东西的状态不可,就修改它所在对象的状态好了。(参见前文“输出参数”一节。)

F3:标识参数

布尔值参数大声宣告函数做了不止一件事。它们令人迷惑,应该消灭掉。(参见前文“标识参数”一节。)

F4:死函数


永不被调用的方法应该丢弃。保留死代码纯属浪费。别害怕删除函数。记住,源代码控制系统还会记得它。

一般性问题

  • 一个源文件中存在多种语言

    • 当今的现代编程环境允许在单个源文件中存在多种不同语言。例如,Java源文件可能还包括XML、HTML、YAML、JavaDoc、英文、JavaScript等语言。往好处说是令人迷惑,往坏处说就是粗心大意、驳杂不精。
  • 忽视安全

    • 无视编译器的代码警告
  • 重复
    • 较隐蔽的形态是在不同模块中不断重复出现、检测同一组条件的switch/case或if/else链。可以用多态来替代之。
  • 不执行的代码
    • 死代码就是不执行的代码
  • 用命名常量替代魔术数

    • 术语“魔术数”不仅是说数字。它泛指任何不能自我描述的符号。
      1. assertEquals(7777, Employee.find("John Doe").employeeNumber());
      上列断言中有两个魔术数。第一个显然是777,它的意义并不明确。第二个魔术数是John Doe,因为其意图不明显。
      1. assertEquals(
      2. HOURLY_EMPLOEE_ID,
      3. Employee.find(HOURLY_EMPLOYEE_NAME).employeeNumber());
  • 函数只该做一件事

    1. public void pay() {
    2. for (Employee e : employees) {
    3. if (e.isPayday()) {
    4. Money pay = e.calculatePay();
    5. e.deliverPay(pay);
    6. }
    7. }
    8. }

    这段代码做了三件事。它遍历所有雇员,检查是否该给雇员付工资,然后支付薪水。代码可以写得更好,如:

    1. public void pay() {
    2. for (Employee e : employees) payIfNecessary(e);
    3. }
    4. private void payIfNecessary(Employee e) {
    5. if (e.isPayday()) calculateAndDeliverPay(e);
    6. }
    7. private void calculateAndDeliverPay(Employee e) {
    8. Money pay = e.calculatePay();
    9. e.deliverPay(pay);
    10. }
  • 遵循标准约定

    • 每个团队都应遵循基于通用行业规范的一套编码标准。编码标准应指定诸如在何处声明实体变量,如何命名类,方法和变量,在何处放置括号,等等。团队不应用文档描述这些约定,因为代码本身提供了范例。