代码先锋网 代码片段及技术文章聚合

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运行前会出现错误

版权声明:本文为leafdown_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/leafdown_/article/details/79841475

智能推荐

大厂Code Review 流程

提交cr的流程 检查代码风格(可以安装googlestyle或者Alibaba的一些stylecheck工具,也许各开发团队会有自己的风格规范) 从mainline中同步代码,注意使用(git pull --rebase)而不是(git pull) 测试你的代码,单元测试(UT)覆盖率至少达到90%以上,对于订单相关的组件修改,需要手动完成集成测试(最好通过脚本的方式) 一次提交尽可能的只包含一个...

代码评审 Code Review

介绍几个轻量级代码检查工具,包括代码静态检查,整理代码等。 无论是自己一个人写代码,还是与其他人合作写代码,都希望能有一份高质量的代码,以便别人或未来的自己可读、可维护和可扩展。 于是往往我们需要代码评审(Code Review)。正式的代码评审已经有不少书籍介绍且与开发环境有关,这里只介绍几个轻量级代码检查工具,方便日常开发过程中提高自己的代码质量。 Python Pylint https://...

Code Review流程

Code Review也就是代码评审。代码评审有两种不同的方法,一种是代码审查(比较正式),一种是代码走查(没那么正式),我们这里讨论的仅指代码走查。 之所以需要代码评审,是因为通常自己对自己写的代码都难以发现问题,因此需要以第二双眼睛再次检查代码,帮助我们及时地发现潜在的问题。 一、代码评审前的前提与准备工作 做代码评审之前,团队成员间需要达成一个共识,制定一份合理的代码规范标准。以此为前提,后...

code review集锦

1.查过3个表的join,改用java进行数据组装 我们经常会遇到前端查询列表,但是列表的数据存在于多个表中。一般少于三个表的话,我们就会使用jion语句来处理。但是超过3个表的话,用jion显然不是好办法。通常就会采用java来处理。 伪代码如下: 2.数据封装过多时,采用异步线程 对于第一个问题,如果一个基础列表查出来,需要封装的数据过多时,可以采用异步线程来处理封装过程。对于使用到线程池的地...

Code Review之Groogle安装

由于公司的管理、规范和项目组本身的各种因素,导致代码上一直就存在这很大的问题,几个同事的离职和新人的加入,前几天一看他们的代码,立刻吓了一跳,看来代码的问题已经迫在眉睫了,终于决定把代码审查提上日程。于是开始研究各个开源的代码审查框架,花费了两天时间,成功安装了Groogle和JCR。顺便说一声,国内的资料真少……   Groogle介绍:   Gr...

猜你喜欢

Golang code review comments 【译文】

翻译自:https://github.com/golang/go/wiki/CodeReviewComments 注释 注释应该是一段完整的语句 注释应该以所描述内容的名字开头,并且以句号结尾 声明空的切片 应该使用: var t []string, 而不是: t := []string{} 前者会避免内存分配,除非使用了append() 不要使用panic 对于普通的错误处理,不要使用panic...

APACHE ZOOKEEPER 3.5.3 CODE REVIEW

导语 zab协议包含两个阶段崩溃恢复与消息广播,基于zookeeper 3.5.3集群启动以及ZooKeeper.setData来分别说明下两阶段的流程。 一.Index 1.1 崩溃恢复 1.2 消息广播 二.reference https://zhuanlan.zhihu.com/p/25332396 从Paxos到Zookeeper-分布式一致性原理与实践 三.下篇 REDIS 3.2.8 ...

APACHE HBASE 1.2.0 CODE REVIEW

导语 以下基于hbase1.2,目前已经出了2.0版本,2.0具体细节没跟过,但是基本思路不变,优化了offheap、AssignmentManager(不再依赖zk做state数据交换,感觉这个思路有点像kafka的GroupCoordinator的思路,启动另一个管理节点,然后通过接口将结果分发到各工作节点,而尽量只用zk做小范围的一致性协调,减小数据写入,毕竟zk的写入吞吐量有限)等优化 一...

APACHE KAFKA 0.10.0 CODE REVIEW

导语 Kafka 0.10.0还未实现Exactly-Once[1],consume虽然可以基于事务实现Exactly-Once,但是produce流程如果ack阶段丢失响应依然会造成日志重复,所以本版本的实现Exactly-Once的方式还是建议采用消费端做幂等来实现。 一.Index 1.1 Produce流程 1.2 Consume流程 1.3 Replicas机制 二.reference ...

APACHE YARN 2.6.0 CODE REVIEW

导语 YARN 2.6.0 版本还未支持GPU,且未启用DRF的DominantResourceCalculator的情况下,默认的DefaultResourceCalculator只是基于Memory来切割分配Container。 一.各组件间通信协议 框架层协议 应用层协议 二.on-yarn-app生命周期 Client请求RM获取AM_ID,然后封装ApplicationSubmissio...