极简 Code Review 指北

301 阅读6分钟

Code Review的两个目标:

  • 提高代码质量。寻找bug,预测可能的bug,检查代码清晰度,检查编码风格的一致性
  • 提升程序员水平。通过Code Review来学习新的语言特性,项目设计,代码规范。

坏味道代码例子1:

public static int dayOfYear(int month, int dayOfMonth, int year) {
    if (month == 2) {
        dayOfMonth += 31;
    } else if (month == 3) {
        dayOfMonth += 59;
    } else if (month == 4) {
        dayOfMonth += 90;
    } else if (month == 5) {
        dayOfMonth += 31 + 28 + 31 + 30;
    } else if (month == 6) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31;
    } else if (month == 7) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30;
    } else if (month == 8) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31;
    } else if (month == 9) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31;
    } else if (month == 10) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30;
    } else if (month == 11) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31;
    } else if (month == 12) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 31;
    }
    return dayOfMonth;
}

Don't repeat yourself(DRY)

重复代码是危险的,例如在两个地方有相似或者相同的代码,则可能两处都有bug,并且修复了一个地方的问题后可能忘记修复另一个地方。

例如上述代码每个月的天数重复出现,dayOfMonth += 重复出现多次。

改进方式:可以用数组存储天数,for while循环精简dayOfMonth+=。

良好的注释

优秀的软件开发者为他们的代码编写恰到好处的注释。好的注释能够让代码更容易理解,更少的bug,更易更改。

  • 出现在方法,类之前的重要注释是一种规范。Java中,它通常以 Javadoc 注释的形式编写,以 / ** 开头,并包含 @语法,例如方法的 @param 和 @return。
  • 另一种关键的注释指定从其他地方复制或改编的一段代码的来源。
  • 并不是每个语句都需要注释,阅读者至少是知道编程语言的用法的。对于晦涩难懂的代码应该添加注释。

例如上述例子,应该添加月数计算是从1-12 还是 0-11。

Fail fast

Failing fast意味着代码应该尽可能早的暴露bug。越早发现问题,查找和修复问题就越容易。

例如上述方法就不是Fail Fast的,如果参数传递错误可能会得到错误的结果。改进方式可以改变参数类型,月份用枚举表示,或者检查月份范围抛出异常。

避免魔术数字

有个计算机科学的笑话,计算机科学家只能理解的数字是0、1,有时可以有2,所有其他的数字都叫魔术数字。一种方法是为数字提供注释,更好的方法则是将数字声明为一个具备好的清晰名字的常量。

  • 数字没有名字具备可读性。
  • 数字可能在未来需要改变。
  • 数字可能依赖于其他数字。因此不应该硬编码手动计算得到的数字。

例如上述例子中月份、天数都为魔术数字

一个变量一个目的

变量不是稀缺资源,自由的引入变量并赋予好的名字。方法名称应该尽量不可更改。

例如上述例子中dayOfMonth被用来计算天数作为返回结果。

好的命名

好的方法以及变量名是长且自描述的。好的方法名以及变量名往往可以避免大量的注释。

例如变量名tmp、temp、data都是差的命名,没有意义的。

Java命名中:

  • methodsAreNamedWithCamelCaseLikeThis
  • variablesAreAlsoCamelCase
  • CONSTANTS_ARE_IN_ALL_CAPS_WITH_UNDERSCORES
  • ClassesAreCapitalized
  • packages.are.lowercase.and.separated.by.dots

方法名通常使用动词短语,变量名通常为名词短语。选择简单精确的单词避免缩写。例如message比msg更好。

使用空白符帮助读者

使用一致的缩进。空白符和tab键仁者见仁智者见智,个人提倡使用空白符(因为不同的工具对待空白符的规则不一样),为编辑器设置tab键转空白符号。

坏味道例子2:

public static int LONG_WORD_LENGTH = 5;
public static String longestWord;

public static void countLongWords(String text) {
    String[] words = text.split(' ');
    if (words.length == 0) {
        System.out.println("0");
        return;
    }
    int n = 0;
    longestWord = "";
    for (String word: words) {
        if (word.length() > LONG_WORD_LENGTH) ++n;
        if (word.length() > longestWord.length()) longestWord = word;
    }
    System.out.println(n);
}

不要使用全局变量

全局变量:是一个变量其值可以被改变;程序的任意地方都可以访问并更改。

Java中public static用来声明全局变量。通过添加 final就可以声明一个全局常量,全局常量是普通以及有用的。

上述例子中:LONG_WORD_LENGTH、longestWord都为全局变量

方法应该返回结果而不是打印他们

例如上述方法就不便于更改,其将结果打印到终端。只有最上层的程序应该和用户交互,底层的程序应该将输出作为结果返回。唯一的例外是调试输出,当然可以将其输出到控制台。但是这种输出不应该是设计的一部分,而应该是调试设计的一部分。

避免特殊情况的代码

程序员通常写一些特殊代码处理一些特殊情况,例如参数为0,空列表,空字符串。例如上述例子判断words列表为空,if语句是多余的。实际上,单独处理这种特殊情况已导致可能的错误——空列表的处理方式与碰巧没有长字的非空列表相比有所不同。

积极抵制分开处理特殊情况的诱惑。编写更广泛的通用代码会有所收获,它导致方法更短,更易于理解,并且隐藏错误的位置更少。它可能会更安全,免受错误影响,因为它对所使用的值做出的假设较少。而且,它为更改做好了更多的准备,因为在更改方法的行为时,有较少的更新位置。首先编写干净,简单,通用的算法,然后在有实际帮助的情况下对其进行优化。

总结:好的软件需要具备三个关键属性。

  • Safe from bugs. DRY帮助开发者只需要在一处修改bug,使用清晰的注释记录假设条件能够防止其他程序员引入错误,Fail fast能够帮助尽早发现错误,避免全局变量能够帮助定位与本地变量有关的bug。
  • Easy to understand. Code review是找到晦涩难懂的代码的唯一方法,因为其他程序员阅读并尝试理解它。使用恰到好处的注释,避免魔术数字,一个变量一个目的,更好的命名,更好地使用空格都可以提供代码的可理解性。
  • Ready for change. DRY 代码更方便更改,返回结果而不是打印输出让代码更容易适配新的目的。