上回我们从微观角度,以一个实际的例子,从正确、高效、易读等特性着手,介绍了如何编写高质量的代码。这次,我们从宏观出发,从软件开发流程入手,着重介绍其中的几个方面,包括代码规范、Code Review、测试等。
如果说上回的内容注重个人编码,那么本文将偏向团队开发。
代码规范
团队的代码规范,一般由领导和大佬们制定后,大家统一实行。这里面有几个问题:
真的需要代码规范吗?
言下之意,制定和执行代码规范是否浪费时间?
答案是:It depends。如果项目很庞大、代码质量要求很高,那么,制定和执行代码规范所花费的时间,将大大少于后期因为不规范开发带来的种种调试和维护成本。如果是小打小闹的代码,就无所谓了。
代码规范的制定为什么这么难?
原因众多,其中一个很重要的部分是团队每个人的口味和观点不尽相同。就代码风格而言,有人喜欢对内置类型变量i使用i++,有人坚持认为应该使用++i不管i是不是复杂类型。因此,制定代码规范需要在讨论之后最后拍板决定,这里面甚至需要独裁!是的,独裁!
代码规范制定需要注意什么事项?
如果代码规范限制太松,那么等于没有规范;如果太严,大大影响开发效率。这里面的尺度,需要根据项目需要、团队成员特点全面考量,进行取舍。
需要注意的是,没有任何一种代码规范是完美的。例如,在C++中,如果启用异常,那么代码的流程将会被各种异常处理中断,各种try catch throw让代码很不美观;如果禁用异常,也就是在开发的过程中不能使用异常特性,那么团队成员可能因为长期没有接触这项语言feature而造成知识和技能短板。
代码风格举例
举两个我认为比较重要、比较新鲜、比较有趣的代码风格。
1,使用引用需要判空吗?
1 2 |
|
我们都知道,在g中,使用*p前需要对p是否为NULL进行判断,那么f呢?如果质量非常关键、代码安全非常重要的场景,那么实际上,也是需要的。想想看,这是为什么。
2,级联if else语句。
首先看一个个人认为不好的代码风格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
这个函数的核心在于do sth部分。其实我们可以改写为级联if-else形式,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
是不是优美多了?前面只做一些错误处理、前期准备、参数检查等,最后的else分支做实实在在的功能性事情。
Code Review
什么是Code Review?
很多人把它翻译为代码审查,我觉得太政治味了。程序员尤其是新手写完代码后,可能会有风格问题(比如不符合团队的代码规范)、安全性问题(比如忘记指针判空)、优雅性问题(比如大量冗余代码)、正确性问题(比如算法设计错误),那么在发布代码到公共库之前,提交给师兄或者mentor,让他帮你review一下代码,并提出可能的问题和建议,让你好好修改。这样的过程,就叫做Code Review。
我的天呐,那这不是很占用时间?
是的。一个写代码,一个看代码,看代码的时间可能并不比全新写一份代码少。那么,这又是何必呢?
主要的原因有:
1,review确实占用了开发时间,然而开发,或者说写代码,其实只占很少的时间比例。很多时间花在debug、调试、写文档、需求分析、设计算法、维护等等上。
2,代码质量非常重要,这点时间投入是值得的。与其后期苦逼追bug,不如前期多投入点时间和人力。
3,培养新人,让新手更快成长。
如何更好的执行Code Review
这里给几点建议:
1,不走过场。走过场,还不如不要这个流程。
2,作为Reviewer,看不懂代码就把作者拉过来,当面询问,不要不懂装懂,也不要爱面子不好意思问。
3,作为Coder,心里要有感激之情。真的。不要得了便宜还卖乖,感恩reviewer,感激reviewer对自己的进步和成长所做出的贡献,所花费的心血。中国人里狼心狗肺、忘恩负义、不懂感恩的人还算少吗?
4,作为Coder,给Reviewer Review之前,请先做单元测试并确保通过,并自己尝试先整体看一遍自己本次提交的代码。
注意,不要给别人提还没调试通过的代码,这是非常不尊重别人的表现。
质量保证
1,测试不是专属QA的活儿,对自己写的代码提供质量保证,是程序员的职责。QA要负责的,是系统的质量,不是模块的质量。
2,测试,需要意识,需要坚持。我发现C++程序员、Java程序员的测试意识或者说质量意识最强。
3,测试用例的编写和设计需要保证一定的代码覆盖率(过分追求覆盖率也很没意思,也没必要),力求让绝大多数分支和流程的代码都走到,然后分析运行结果是否是符合期望的,不要只考虑正确路径上的那些分支。
4,测试用例的编写和设计力求全面,考虑到方方面面。以非常经典的二分搜索为例:
int binary_search(int *p, int n, int target, int &idx);
binary_search函数返回值为0表示成功执行,输出参数idx返回target在有序数组p中(第一次出现)的位置,-1表示不存在。
那么测试用例至少应该涵盖:
-
p为NULL的情况
-
数组大小n分别为负数、0、1、2时情况
-
数组p不是有序数组的情况
-
target在数组中出现0次、1次、n次的情况
你是否都考虑到了呢?
5,有时候,自己书写测试用例显得刀耕火种,现在已经有很多辅助的工具,读者可以自行google一下。