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 代码更方便更改,返回结果而不是打印输出让代码更容易适配新的目的。