Code Review 4

总结
代码评审是一种广泛应用的软件质量提升方法。它可以检测出代码中的各种问题,但是作为一个初学课程,这篇阅读材料只提及了下面几个好代码通用的原则:

不要重复你的代码(DRY)
仅在需要的地方做注释
快速失败/报错
避免使用幻数
一个变量有且仅有一个目的
使用好的命名
避免使用全局变量
返回结果而非打印它
使用空白符提升可读性
下面把今天学的内容和我们的三个目标联系起来:

远离bug. 通常来说,代码评审使用人的审查来发现bug。DRY使得你只用在一处地方修复bug,避免bug的遗漏。注释使得原作者的假设很清晰,避免了别的程序员在更改代码的时候引入新的bug。快速报错/失败使得bug能够尽早发现,避免程序一直错更多。避免使用全局变量使得修改bug更容易,因为特定的变量只能在特定的区域修改。
易读性. 对于隐晦或者让人困惑的bug,代码评审可能是唯一的发现方法,因为阅读者需要尝试理解代码。使用明智的注释、避免幻数、变量目的单一化、选择好的命名、使用空白字符都可以提升代码的易读性。
可更改性. DRY的代码更具有可更改性,因为代码只需要在一处进行更改。返回结果而不是打印它使得代码更可能被用作新的用途。

一、减少重复代码片段

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;
}
  • 练习
    假设你有一个数组:
    int[] monthLengths = new int[] { 31, 28, 31, 30, …, 31}
    下列哪个代码框架可以用来干出足够的代码,以便dayOfMonth+=只出现一次?
    A for (int m = 1; m < month; ++m) { … }
    B switch (month) { case 1: …;
    break; case 2: …; break; … }
    C while (m < month) { …; m += 1; }
    D if (month == 1) { … } else { … dayOfYear(month-1, dayOfMonth,year) … }
    注:循环语句像for和while都是压缩重复代码的好方法。switch只会消除“month==”,他是if-else if -else的改写。

    二、合理注释

  • 练习

以下哪些注释是合理并且需要的?

/** @param month month of the year, where January=1 and December=12  [C1] */
public static int dayOfYear(int month, int dayOfMonth, int year) {
    if (month == 2) {      // we're in February  [C2]
        dayOfMonth += 31;  // add in the days of January that already passed  [C3]
    } else if (month == 3) {
        dayOfMonth += 59;  // month is 3 here  [C4]
    } else if (month == 4) {
        dayOfMonth += 90;
    }
    ...
    } else if (month == 12) {
        dayOfMonth += 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 31;
    }
    return dayOfMonth; // the answer  [C5]
}

注:[c1]方法前要有规格说明,它告诉我们了month这个参数代表了什么。[c2][c3]解释了2和31代表什么,不过可以这么写:
month == FEBRUARY
dayOfMonth += MONTH_LENGTH[JANUARY]

三、Fail-fast
尽量快速暴露bug

四、避免幻数
1、定义:不知道从哪冒出来的,不是自己期待的输入
2、如何避免:解决幻数的一个办法就是写注释,但是另一个更好的办法是声明一个具有合理名字的变量。例如:

  • 月份2,…..,12如果用 FEBRUARY, …, DECEMBER.会更加容易理解
  • days-of-months 30, 31, 28等等 如果用存储在数据结构中的数会更加容易理解,例如列表或者数组e.g.
    MONTH_LENGTH[month]

3、一些原则:
safe from bugs (SFB)
easy to understand (ETU)
ready for change (RFC)

  • 总问题
for (int i = 0; i < 5; ++i) {
    turtle.forward(36);
    turtle.turn(72);
}

注:不好修改,修改每个数字都得去代码里边找;也不好阅读,5是什么,36,72是什么;

  • 例子1
final int five = 5;
final int thirtySix = 36;
final int seventyTwo = 72;
for (int i = 0; i < five; ++i) {
    turtle.forward(thirtySix);
    turtle.turn(seventyTwo);
}

注:更糟糕。多此一举的做法。

  • 例子2
int[] numbers = new int[] { 5, 36, 72 }; 
for (int i = 0; i < numbers[0]; ++i) {
    turtle.forward(numbers[1]);
    turtle.turn(numbers[2]);
}

注:更糟糕。把幻数收集进了一个叫numbers的数组里,这样5现在变得更加神秘numbers[0],并且5,36和72之间隐藏的关系仍然存在。

  • 例子3
int x = 5;
for (int i = 0; i < x; ++i) {
    turtle.forward(36);
    turtle.turn(360.0 / x);
}

注:更安全,更容易改变。更安全:72改成了360.0 / x,说明了turn后边的参数72跟5的关系,避免bug;更容易改变:原来要改5和72,现在只需要修改x

  • 例子4
final double fullCircleDegrees = 360.0;
final int numSides = 5;
final int sideLength = 36;
for (int i = 0; i < numSides; ++i) {
    turtle.forward(sideLength);
    turtle.turn(fullCircleDegrees / numSides);
}

注:更安全,更容易改变,更让人清楚。camelCase编写,(如:fullCircleDegrees )说明在方法中是声明的局部常量。同时集中在上method边进行修改。

五、声明的变量只有一个含义
在 dayOfYear 这个例子中, dayOfMonth 被用来做不同意义的值:一开始它是这个月的第几天,最后它是返回的结果(是今年的第几天)
特别地,方法的参数不应该被修改(这和“易改动性”相关——在未来如果这个方法的某一部分想知道参数传进来的初始值,那么你就不应该在半路修改它)。所以应该使用final关键词修饰参数(这样Java编译器就会对它进行静态检查,防止重引用),然后在方法内部声明其他的变量使用。

正确做法:

public static int dayOfYear(final int month, final int dayOfMonth, final int year) {
    ...
}

五、第二个例子

public static boolean leap(int y) {
    String tmp = String.valueOf(y);
    if (tmp.charAt(2) == '1' || tmp.charAt(2) == '3' || tmp.charAt(2) == 5 || tmp.charAt(2) == '7' || tmp.charAt(2) == '9') {
        if (tmp.charAt(3)=='2'||tmp.charAt(3)=='6') return true; /*R1*/
        else
            return false; /*R2*/
    }else{
        if (tmp.charAt(2) == '0' && tmp.charAt(3) == '0') {
            return false; /*R3*/
        }
        if (tmp.charAt(3)=='0'||tmp.charAt(3)=='4'||tmp.charAt(3)=='8')return true; /*R4*/
    }
    return false; /*R5*/
}
  • 出现几个幻数?
    注:12*2个,因为tmp.charAt(n)==’k’结构的式子都会出现幻数
  • 假设你写了一个帮助方法:

public static boolean isDivisibleBy(int number, int factor) { return number % factor == 0; }
接着 leap() 使用这个 isDivisibleBy(year, …)方法重写,并且正确的使用 leap year algorithm中描述的算法,这时该方法中会出现几个幻数?

注:重写的代码:
闰年判定:能被400整除。或者能被4整除但不能被100整除

public static boolean isLeapYear(int year) {
    if (isDivisibleBy(year, 400)) return true;
    else if (isDivisibleBy(year, 100)) return false;
    //已经除去了被100整除的情况
    else return isDivisibleBy(year, 4);
}

所以有3个幻数

六、使用好的命名
糟糕: tmp, temp, 和 data 这样变量名
好的:(使用更长、更有描述性的命名) lead -> isLeapYear

七、不要使用全局变量
全局常量:public static final(常用)
全局变量:public static (不好)

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

public static void countLongWords(List<String> words) {
   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);
}

对以下变量使用final会发生什么?

  • LONG_WORD_LENGTH 固定值

  • words 添加final到words变量意味着该变量不能被重新分配 - 但List它指向的对象仍然可能发生变化。这种微妙的差异可能是错误的根源

  • word 固定

  • longestWord 、n运行前会出现错误

猜你喜欢

转载自blog.csdn.net/leafdown_/article/details/79841475