一、概叙
CodeReview对于一个技术团队来讲是非常重要核心的,如果没有一个良好的CodeReview制度,会导致技术债堆积严重,长时间下来影响系统的可维护性、质量以及性能等各方面问题。
良好的的CodeReview习惯,可以有效提高整体代码质量,及时发现代码中可能存在的问题。包括像Google、微软这些公司,Code Review都是基本要求,代码合并之前必须要有人审查通过才行。
尤其对于一个创业团队来讲做好CodeReview真心不容易,创业团队要追求业务快速增长,一方面从上到下很少人会重视,认真做CodeReview的很少,有的流于形式,有的可能根本就没有Code Review的环节,代码质量只依赖于事后的测试。也有些团队想做好代码审查,但不知道怎么做比较好。
希望我们都能够有效而迅速进行CoderReview,一方面提升代码质量本身,另一方面也可以创造一个良好的学习氛围互相支持提升团队的整体代码水平。
二、CodeReview作用
其实大部分人都知道CodeReview的作用。这里简述下几点主要作用:
①尽量避免bug的出现。只能尽量降低bug的"产出率",提前发现bug,降低生产事故发生率。
②方便维护,降低维护成本。编写规范的代码和优秀的代码可以给未来接手的同学更好的理解其意图进行维护或者在其基础上继续开发,不会让新同学想“打死前任”的冲动。
③正确处理业务需求逻辑。开发同学实现的业务需求是否与需求对应,是否会存在理解偏差,是否存在处理不当等等。
④尽量最优实现。在编写代码的过程中,每个人的逻辑思维都可能存在不同,在评审过程中,开发同学写的代码还存在现有更好、更优的实现方式,或者把实现的最优逻辑分享给其它同学讨论学习。
三、CodeReview相关工具
在正式代码Review之前,推荐阿里Java代码开发规范P3C进行扫描一遍:https://github.com/alibaba/p3c/,都有相应的Eclipse/IDEA插件。
推荐使用ReviewBoard:https://github.com/reviewboard/reviewboard
ReviewBoard支持pre commit和post commit,条件允许的情况下推荐使用pre commit,时间紧迫的情况可以使用post commit。
四、如何推进CodeReview
1.把CodeReview作为开发流程的必选项而不是可选项
在很早以前,我就尝试过将代码审查作为代码流程的一部分,但只是一个可选项,没有Code Review也可以把代码合并到master。这样的结果就是想起来才会去做Code Review,去检查的时候已经有了太多的代码变更,审查起来非常困难,另外就算审查出问题,也很难得以修改。
我们现在对代码的审查则是作为开发流程的一个必选项,每次开发新功能或者修复Bug,开一个新的分支,分支要合并到master有两个必要条件:
1)所有的自动化测试通过。
2)有至少一个人CodeReview通过,如果是新手的PR,还必须有资深程序员CodeReview通过。
这样把CodeReview作为开发流程的一个必选项后,就很好的保证了代码在合并之前有过Code Review。而且这样合并前要求代码审查的流程,好处也很明显:
1)由于每一次合并前都要做代码审查,这样一般一次审查的代码量也不会太大,对于审查者来说压力也不会太大
2)如果在CodeReview时发现问题,被审查者希望代码能尽快合并,也会积极的对审查出来的问题进行修改,不至于对审查结果太过抵触
2.把CodeReview变成一种开发文化而不仅仅是一种制度
把Code Review 作为开发流程的必选项后,不代表CodeReview这件事就可以执行的很好,因为Code Review的执行,很大部分程度上依赖于审查者的认真审查,以及被审查者的积极配合,两者缺一不可!
如果仅仅只是当作一个流程制度,那么就可能会流于形式。最终结果就是看起来有CodeReview,但没有人认真审查,随便看下就通过了,或者发现问题也不愿意修改。
真要把CodeReview这件事做好,必须让CodeReview变成团队的一种文化,开发人员从心底接受这件事,并认真执行这件事。
要形成这样的文化,不那么容易,也没有想象的那么难,比如这些方面可以参考:
首先,得让开发人员认识到CodeReview这件事为自己、为团队带来的好处
然后,得要有几个人做好表率作用,榜样的力量很重要
还有,对于管理者来说,你激励什么,往往就会得到什么
最后,像写自动化测试一样,把CodeReview要作为开发任务的一部分,给审查者和被审查者都留出专门的时间去做这件事,不能光想着马儿跑得快又舍不得给马儿吃草。
如何形成这样的文化,有心的话,还有很多方法可以尝试。只有真正让大家都认同和践行,才可能去做好Code Review这件事。
3.CodeReview需要注意事项
CodeReview沟通常见问题:
1)把在CodeReview中别人给你提的修改意见当成了对自己能力的否定,以为对方是在说自己是个不合格的程序员。这样就会在工作中带有强烈的个人情绪,导致整个CodeReview环节气氛变的尴尬无比
2)由于CodeReview是书写方式,被review的人在看到你的批注时候,经常会多想,明明是一句简单的批注:此处应该关闭流。当事人看到也会理解为:我靠,这么简单的都忘记了,连关闭流都不知道,真挫
3)你要求的修改点在对方看来是鸡蛋里挑骨头,或是对方觉得我这样实现就是很好,为什么要改。冲突就这样产生,如果这个时候没处理好,直接就会导致冲突的上升,就不仅仅是技术讨论的问题。
个人情感的代入是批注部分最难的问题根源之一。
不要凸显自己的牛X,自己技术比别人好的优越感。抛开这种优越感。记住CodeReview的主要目的不仅仅为了找代码的bug, 而是提供认真专业的反馈来帮助队友提高,你的每条建议都是给双方一次学习的机会。
建议:
1)在CodeReview的批注中加入code sample
2)在批注中尽量不要用指代人称,如 你****,如果要用可以用我们来替代你。用我们能强化团队的整体意思,而你则更多带有指责的意思
3)尽量不要用命令的口吻去表达自己的意见。如果你的公司阶级观念比较重,那就另当别论。
4)CodeReview评论要友好,避免负面词汇;有说不清楚的问题当面沟通。
5)批注中对要改的内容作出说明原因时候,尽量能有理论依据支撑,而不是我觉得就应该这样。这样做到有理有据,不会出现相互不服问题
五、常见CodeReview问题总结
1.基础规范
比如团队制定一些内部规范,比如代码模板、代码分层结构、内部组件库使用、开源软件使用规范等,这些基本的前提是要遵守的,这一点是CodeReview前提。
1)统一的代码format格式
这里不讨论哪种代码风格好,比如右花括号是单独一行显示好,还是在不用哪个换行的好。个人觉得团队需要做的不是是花时间讨论代码format格式没有哪种好哪种差,而是需要制定一种格式风格,然后团队每个开发成员去遵守即可,推荐使用阿里巴巴的P3C插件的code format,如果代码format格式执行不到位,后面代码merge成本会非常高。
2)统一的代码提交comment风格
如果是做的是一个功能,需要注明本次提交做的是哪个功能,实现思路是什么
如果是修改bug的,需要注明本次提交修改了什么bug, bug的原因以及如何修改的
这样就方便Code Review的人能很清楚的知道你的代码思路
推荐使用angular的代码提交规范:https://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html
3)代码风格
方法名:在计算机科学中,命名是一个难题。一个函数被命名为==get_message_queue_name==,但做的却是完全不同的事情,比如从输入内容中清除html,那么这是一个不准确的命名,并且可能会误导。
值名:对于数据结构,==foo== or ==bar== 可能是无用的名字。相比==exception==, ==e==同样是无用的。如果需要(根据语言)尽可能详细,在重新查看代码时,那些见名知意的命名是更容易理解的。
函数长度:对于一个函数的长度,我的经验值是小于20行,如果一个函数在50行以上,最好把它分成更小的函数块。
类的长度:我认为类的长度应该小于300行,最好在100内。把较长的类分离成独立的类,这样更容易理解类的功能。
文件的长度:对于Python,一个文件最多1000行代码。任何高于此的文件应该把它分离成更小更内聚,看一下是否违背的“单一职责” 原则。
文档:对于复杂的函数来说,参数个数可能较多,在文档中需要指出每个参数的用处,除了那些显而易见的。
注释代码:移除任何注释代码行。
函数参数个数:不要太多, 一般不要超过3个。。
可读性:代码是否容易理解?在查看代码时要不断的停下来分析它?
以上相关的规范推荐使用阿里巴巴的P3C插件定义的规范。
4)使用异常还是错误码返回
在分布式微服务应用中,系统调用依赖关系复杂,系统内部之间抛异常传递,系统与系统之间使用错误码返回,这样能够清晰看到异常错误原因、定位问题。
5)统一错误码
错误码本身不算是代码问题,不过基于整个组织和工程的可维护性来说,可以将错误码不符合规范作为一种错误加以避免。方法:对错误码进行可控的管理和遵循规范使用。可以使用公共文档维护, 也可以开发错误码管理系统来避免相同的错误码。
6)参数检测
参数检测是对业务处理的第一层重要过滤。如果参数检测不足够,就会导致脏数据进入服务处理,轻则导致异常,重则插入脏数据到数据库,对后续维护都会造成很多维护成本。
方法:采用“契约式编程”,规定前置条件,并使用单测进行覆盖。
对于复杂的业务应用, 优雅的参数检测处理尤为重要。根据 “集中管理和处理一致性原则”, 可以建立一个 paramchecker包或者基于hibernate validator进行参数检测, 设计一个可复用的微框架来对应用中所有的参数进行统一集中化检测。
参数检测主要包括:
(1) 参数的值类型, 可以根据不同值类型做基础的检测;
(2)参数的业务类型,有基础非业务参数, 基础业务参数和具体业务参数。不同的参数业务类型有不同的处理。将参数值类型与参数业务类型结合起来, 结合一致性的异常捕获处理, 就可以实现一个可复用的参数检测框架。参数检测既可以采用普通的分支语句,也可以采用注解方式。采用注解方式更可读,不过单测编写更具技巧。
7)小而完整的代码提交习惯
很多开发很怕麻烦,觉得修改一个bug需要重新创建一个分支并提交一次pullrequest, 太浪费时间。但是不知道是,一次提交太多的代码或是多个功能或是多个bug一起提交,会导致code review的效果大打折扣。建议一次提交的改动尽量不要超过100行,否则review起来花的时间会特别长,同时也会导致code review的人失去耐心,从而没有真正起到review的作用。
2.代码业务逻辑
我们在Code Review的过程中,也根据实际业务进行总结了跟业务紧密关联的问题,也就是业务开发规范。在业务处理的时候,根据业务特点或者业务处理方式,我们需要按照实际业务去调用我们规定的方式,因为曾多次出现,所以做下记录,防止后人或新人重复掉坑里,甚至重复出现同一类问题事故。同时,我们针对总结的问题,也按照建议和强制的规范级别来要求或者指导我们的同学进行更好的开发。
业务逻辑正确这是最基本的前提,如果逻辑存在缺陷肯定会出现bug,所以这是CodeReview的基本前提。
3.体系结构和代码设计
常见的Code Review的设计问题如下分类:
单一职责原则:一个类有且只能一个职责。我通常使用这个原则去衡量,如果我们必须使用“和”来描述一个方法做的事情,这可能在抽象层上出了问题。
开闭原则:如果是面向对象的语言,对象对可扩展开放、对修改关闭。如果我们需要添加另外的内容会怎样?
代码复用:根据"三振法",如果代码被复制一次,虽然如喜欢这种方式,但通常没什么问题。但如果再一次被复制,就应该通过提取公共的部分来重构它。
潜在的bugs:是否会引起的其他错误?循环是否以我们期望的方式终止?
错误处理:错误确定被优雅的修改?会导致其他错误?如果这样,修改是否有用?
效率:如果代码中包含算法,这种算法是否是高效?例如,在字典中使用迭代,遍历一个期望的值,这是一种低效的方式。
有些新人发现自己的代码提交PR(Pull Request)后,会收到一堆的Code Review意见,必须要做大量的改动。这多半是因为在开始做之前,没有做好设计,做出来后才发现问题很多。
建议在做一个新功能之前,写一个简单的设计文档,表达清楚自己的设计思路,找资深的先帮你做一下设计的审查,发现设计上的问题。设计上没问题了,再着手开发,那么到Review的时候,相对问题就会少很多,这也同时体现了技术方案评审的重要性。
4.可维护性
可维护性问题是“在当前业务变更的范围内通常不会导致BUG、故障,却会在日后埋下地雷,引发BUG、故障、维护成本大幅增加”的类别。
1)硬编码
硬编码主要有三种情况:a. “魔数”;b. 写死的配置;c. 临时加的逻辑和文案。
“魔数”与重复代码类似,当前或许不会引发问题,时间一长,为了弄清楚其代表的含义,增加很多沟通维护成本,且分散在各处很容易导致修改的时候遗漏不一致。务必清清除。方法也比较简单:定义含义明显的枚举或常量,代表这个魔数在代码中发言。
“写死的配置”不会影响业务功能, 不过在环境变更或系统调优的时候,就显得很不方便了。方法:尽量将配置抽离出来做成配置项放到配置文件里。
“临时加的逻辑和文案”也是一种破坏系统可维护性的做法。方法:抽离出来放在单独的函数或方法里,并特别加以注释。
2)重复代码
重复代码在当前可能不会造成 BUG,但上线后,需要维护多处的事实一致性;时间一长,后续修改的时候就特别容易遗漏或处理不一致导致 BUG;重复代码是公认的“代码坏味”,必当尽力清除。方法:抽离通用的部分,定制差异。重复代码还有一种情况出现,即创造新函数时,先看看是否既有方法已经实现过。
3)通用逻辑与定制业务逻辑耦合
这大概是每个媛猿们在开发生涯中遇到的最恶心的事情之一了。通用逻辑与具体的各种业务逻辑混杂交错,想插根针都难。遇到这种情况,只能先祈福,然后抽离一个新的函数,严格判断相应条件满足后去调用它。
如果是新创建逻辑,可以使用函数式编程或基于接口的编程,将通用处理流程抽离出来,而将具体业务逻辑以回调函数的形式传入处理。
不要让不同的业务共用相同的函数,然后在函数里一堆 if-else plus switch , 而是每个业务都有各自的函数, 并可复用相同的通用逻辑和流程处理;或者各个业务可以覆写同样命名的函数。 复用,而非混杂。
4)直接在原方法里加逻辑
有业务改动时,猿媛们图方便倾向于直接在原方法里加判断和逻辑。这样做是很不好的习惯。一方面,增加了原方法的长度,破坏了其可维护性;另一方面,有可能对原方法的既有逻辑造成破坏。可靠的方式是:新增一个函数,然后在原方法中调用并说明原因。
5)多业务耦合
在业务边界未仔细划分清晰的情况下出现,一个业务过多深入和掺杂另一个非相关业务的实现细节。在项目和系统设计之初,特别要注意先划分业务边界,定义好接口设计和服务依赖关系,再着手开发;否则,延迟到后期做这些工作,很可能会导致重复的工作量,含糊复杂的交互、增加后期系统维护和问题排查的许多成本。磨刀不误砍柴工。划分清晰的业务、服务、接口边界就属于磨刀的功夫。
6)代码层次不合理
代码改动逻辑是正确的,然而代码的放置位置不符合当前架构设计约定,导致后续维护成本增加。
代码层次不合理可能导致重复代码。比如获取操作人和操作记录,如果写在类 XController 里, 那么类 YController 就面临尴尬局面:如果写在 YController , 就会导致重复代码;如果跨层去调用 XController 方法,又是非常不推荐的做法。因此, 获取操作人和操作记录,最好写在 Service 层, Controller 层只负责参数传入、检测和结果转译、返回。
7)不用多余的代码
工程中常常会有一些不用的代码。或者是一些暂时未用到的Util工具或库函数,或者是由于业务变更导致已经废弃不用的代码,或者是由于一时写出后来又重写的代码。尽量清除掉不用多余的代码,对系统可维护性是一种很好的改善,同时也有利于CodeReview。
8)使用全局变量
使用全局变量并没有“错”,错的是,一旦出现问题,排查和调试问题起来,真的会让人“一夜之间白了头”,耗费数个小时是轻微惩罚。此外,全局变量还能“顺手牵羊”地破坏函数的通用性,导致可维护性变差。务必消除全局变量的使用。当然,全局常量是可以的。
9)缺乏必要的注释
对重要和关键点的代码缺乏必要的注释,使用到的重要算法缺乏必要的引用出处,对特别的处理缺乏必要的说明。
原则上, 每个方法至少要用一个简短的单行注释, 适宜地描述了方法的用途、业务逻辑、作者及日期。对于特殊甚至奇葩的需求的特别实现,要加一些注释。这样后续维护时有个基础。
5.空值
空值恐怕是最容易出现的地方之一。常见错误有:
a. 值为NULL导致空指针异常;
b. 参数字符串含有前导或后缀空格没有Trim导致查询为空。导致以上结果的原因主要有:无此记录、有此记录但由于SQL访问异常而没查到、网络调用失败、记录中有脏数据、参数没传。
原则上,对于任何异常, 希望能够打印出具体的错误信息,根据错误信息很快明白是什么原因, 而不是一个 null ,还要在代码里去推敲为什么为空。这样我们必须识别出程序中可能的null, 并及时检测、捕获和抛出异常。
对于空值,最好的防护是“防御式编程”。当获取到对象之后, 使用之前总是判断是否为空,并适当抛出异常、打错误日志或做其它处理。
6.异常处理
常见的异常处理规范包括如下:
1)尽量用try…catch…finally的语句来处理异常,在finally应当尽可能回收内存资源。
2)尽量减少用try监控的代码块。
3)先用专业的异常来处理,最后再用Exception异常来兜底。
4)在catch从句里,别简单地抛出异常了事,应当尽可能地处理异常,避免吞掉异常不做任何处理。
5)出现异常后,应尽量保证项目不会终止,应尽量把异常造成的影响缩小到最小程度。
6)遇到异常如果无法处理,逐层往上抛
7)未捕获潜在的异常,调用API接口、库函数或系统服务等,只顾着享受便利却不做防护,常导致因为局部失败而影响整体的功能。最好的防护依然是“防御式编程”。要么在当前方法捕获异常并返回合适的空值或空对象,要么抛给高层处理。
7.日志打印
对于重要而关键的实例状态、代码路径及API调用,应当添加适当的Info日志;对于异常,应当捕获并添加Error日志。缺乏日志并不会影响业务功能,但出现问题排查时,就会非常不方便,甚至错失极宝贵的机会(不易重现的情况尤其如此)。此外,缺乏日志也会导致可控性差,难以做数据统计和分析。
同时打印过多的日志并不好,一方面遮掩真正需要的信息,导致排查耗费时间, 另一方面造成服务器空间浪费、影响性能。生产环境日志一般只开放 INFO及以上级别的日志;Debug 日志只在调试或排错的时候使用,生产环境可以禁止debug日志。
8.并发问题
并发的问题更难检测、复现和调试。常见的问题有:
1)在可能由多线程并发访问的对象中含有共享变量却没有同步保护;
2)在代码中手动创建缺乏控制的线程或线程池
3)并发访问数据库时没有做任何同步措施
4)多个线程对同一对象的互斥操作没有同步保护
使用线程池、并发库、并发类、同步工具而不是线程对象、并发原语。在复杂并发场景下,还需注意多个同步对象上的锁是否按合适的顺序获得和释放以避免死锁,相应的错误处理代码是否合理。
并发问题还特别容易导致线程安全问题,在作为全局变量使用需要特别注意,比如常见的HasMap、SimpleDateFormat非线程安全对象避免作为全局变量使用。
9.幂等问题
幂等问题也是分布式系统中经常遇到的问题,客户端或者网络会触发一些重试场景,如果程序没有做到幂等,会导致一些脏数据或者bug问题,需要抽象出一个公共幂等组件针对该场景进行处理。
10.资源泄露
资源泄露通常有以下几种情况:
1)打开文件却没有关闭
2)连接池的连接未回收
3)重复创建的脚本引用没有置空,无法被回收
4)已使用完的集合元素始终被引用,无法被回收
11.事务问题
事务方面常出现的问题是:
1)多个紧密关联的业务操作和 SQL 语句没有事务保证。
2)在资金业务操作或数据强一致性要求的业务操作中,要注意使用事务,保证数据更新的一致性和完整性。
3)涉及到数据库事务和其他远程调用问题,比如文件上传是先保存数据库还是先进行文件上传,文件上传不属于数据库事务,如何保证一致性是个需要解决问题。
12.性能问题
低性能会导致产品功能不好用、不可用,甚至导致产品失败。
常见情况有:
a. 循环地逐个调用单个接口获取数据或访问数据库;
b. 重复创建几乎完全相同的(开销大的)对象;
c. 数据库访问、网络调用等服务未处理超时的情况;
d. 多重循环对于大数据量处理的算法性能低;
e. 大量字符串拼接时使用了String而非StringBuilder.
对于 a,最好提供批量接口或批量并发获取数据;
对于 b, 将可复用对象抽离出循环,一次创建多次使用;
对于 c,设置合理的超时时间并捕获超时异常处理;
对于 d,使用预排序或预处理, 构造合适的数据结构, 使得算法平均性能在 O(n) 或 O(nlogn) ;
对于 e, 记住:少量字符串拼接使用String, 大量字符串拼接使用 StringBuilder, 通常不会使用到 StringBuffer.
当然这里面还有更多的涉及到性能问题,比如一些工具类或JDK使用:Apache的BeanUils比Spring 的BeanUils性能低,使用Java中的正则要进行预编译等。。。
13.SQL问题
SQL的正确性通常可以通过 DAO 测试来保证。SQL问题主要是指潜在的性能问题和安全问题。
要避免SQL性能问题, 在表设计的时候就要做好索引工作。在表数据量非常大的情况下,SQL语句编写要非常小心。查询SQL需要添加必要索引,添加合适的查询条件和查询顺序,加快查询效率,避免慢查;尽量避免使用 Join, 子查询;避免SQL注入。
尤其避免在 update 语句中使用 where-if !很容易导致全表更新和严重的数据丢失,造成严重的线上故障 !!!
另外一点避免使用存储过程,以及SQL中写业务逻辑。
14.安全
安全问题一向是互联网产品研发中极容易被忽视、而在爆发后又极引发热议的议题。安全和隐私是用户的心理红线之一。应用、数据、资金的安全性应当仅次于产品功能的准确性和使用体验。
常见的安全问题包括XSS、CSRF以及其他越权等,关于XSS可以定义全局的XSSFilter或者在网关层实现xss过滤,其他CSRF和越权问题在编码时需要格外注意,否则会引发安全漏洞,造成不必要的损失。
另外还有一些常见问题:缓冲区溢出;恶意代码注入;权限赋予不当;应用目录泄露等。
安全问题的CodeReview可参见检查点清单:信息安全 。主要是如下措施:
a. 严格检查和屏蔽非法输入;
b. 对含敏感信息的请求加密通信;
c. 业务处理后消除任何敏感私密信息的任何痕迹;
d. 结果返回前在反序列化中清除敏感私密信息;
e. 敏感私密信息在数据存储设备中应当加密存储;
f. 应用有严格的角色、权限、操作、数据访问分级和控制;
g. 切忌暴露服务器的重要的安全性信息,防止服务器被攻击影响正常服务运行。
15.设计问题
常见的设计问题通常体现在:
1)是否有潜在的性能问题
2)是否有安全问题
3)业务变化时是否容易扩展
4)是否有遗漏的点
5)持续高负荷压力下是否会崩溃
六、CodeReview实践
具体CodeReview时要先Review设计实现思路,然后Review设计模式,接着Review成形的骨干代码,最后Review完成的代码,如果程序复杂的话,需要拆成几个单元或模块分别Review。
另外在CodeReview之前需要强调几点:
1)代码在提交CodeReview之前,要自己先REVIEW和测试一遍
2)提交PR要小
在做Code Review的时候,如果有大量的文件修改,那么Review起来是很困难的,但如果PR比较小,相对就比较容易Review,也容易发现代码中可能存在的问题。所以在提交PR时,PR要小,如果是比较大的改动,那么最好分批提交,以减轻审查者的压力。
3)对评论进行分级
在做CodeReview时,需要针对审查出有问题的代码行添加评论,如果只是评论,有时候对于被审查者比较难甄别评论所代表的含义,是不是必须要修改。
4)评论要友好,避免负面词汇;有说不清楚的问题当面沟通
最佳实践建议:
1)制定规范:编码规范+技术规范+业务规范+数据库规范,形成标准文档化。
2)根据需求开发,定期安排相关开发人员、评审人员参与Code Review,有需要的话可以邀请测试人员参与。
3)开始评审前,要求相关代码通过阿里编码规范/FIndBugs/SonarLint等插件进行前期扫描,避免出现比较多的基本问题。
4)新同学首次参入进来,需要着重关注新人,给予更好的建议,帮助提升代码质量。
5)针对评审质量很高的代码给予肯定,并且表扬相关人员。
6)定期总结Code Review存在的问题,记录相关问题于wiki(文档)上,按月度或季度定期输出总结。
7)制定与实际业务相关的开发规范,防止重复掉坑,重复出现同一类问题事故等。
CodeReview的目标和原则
CodeReview的目的是提升代码质量,尽早发现潜在缺陷与BUG,降低修复成本,同时促进团队内部知识共享,帮助更多人更好地理解系统。
建议CodeReview的原则如下:
发现代码的正确性
代码审查用意是在代码提交前找到其中的问题——你要发现的是它的正确性。在代码审查中最常犯的错误—几乎每个新手都会犯的错误是,审查者根据自己的编程习惯来评判别人的代码。
不仅是在Review Code,更是在分享和学习
Code Review最重要的是讲解者分享业务流程和设计思路,参与者通过这些讲解获得这些信息,使得更多人理解这个系统,提升团队整体水平,使得团队维护代码的能力提升。
高效迅速完成CodeReview
我们不能为了应付匆匆忙忙的进行一次代码审查,效率也是很重要的,如果不能保证Code Review目的实现,那么评审便是徒劳的。
如何高效完成CodeReview?
参与者要检查设计的合理性以及业务逻辑是否错误,检查代码可读性;讲解者要想办法分享设计、技术、经验等知识。
1.检查设计的合理性和业务逻辑的正确性:
代码的设计是否符合设计要求:
如果存在代码和设计有出入的地方需要问询为什么要变动,因为这些变动有可能是出于开发者在真正设计代码时候的深入考虑,或者是由于一时大意出现偏差。
业务逻辑是否正确:
业务流程是否按照详细设计的流程走,不要出现原本是先A流程后B流程而设计的时候出现先B后A,或者丢失流程。
某些传入参数是否合理:判断某些接口的参数输入是否是冗余的,比如输入A字段可以满足A接口里面的所有操作,那么多输入一个B就冗余的。
数据库字段的设计,数据库的设计是对实际业务的映射,我们要保证每一个字段的出现都反应实际业务并且经过合理性的验证,比如设计table1的时候A字段在table2中已经出现,并且A和B表有相应的关联,那么要注意A字段对于table1的冗余是否有合理性,如果没有合理的说服性可以去掉A,而节省对A字段的维护成本(存储空间,更新操作等)。
某些判断是否合理,比如某些参数的输入金额是否可以为0的判断等等。
系统交互是否合理,比如在代码设计的时候没有关注考虑系统交互的顺序而造成有些信息不能获取到;比如获取支付方式的费率信息必须要等待支付的时候才能拿到,那么获取这些信息就应该放在pay_trans的时候而不是create_trans,大多数这种问题其实都是详细设计时出现的,代码评审阶段比较少见。
是否有异常处理机制,一个好的代码设计应该考虑各种异常并对相应的异常做出合理的处理,比如接口的可重入,当代码检测到有重入的这种情况,怎样去做这种异常处理使得调用方能捕捉的这些异常而进行后面的处理。
关注业务可拓展性:
我们的业务在不断的发展,每一个项目设计都会影响后续业务的拓展,一个好的设计应该考虑到后续业务的发展,而尽量避免定制化的设计。
关注使用到的数据结构、设计模式和代码性能:
一个好的数据结构和设计模式可以增加代码的可维护性安全性和效率等,比如我们在设计的时候要考虑到不同的场景选择什么样的数据结构,有时候我们会纠结于用map还是用hash_map,这时候我们要根据具体的情况具体分析;
当我们设计代码的时候如果能用上系统提供的函数那么最好不要自己去写,比如自己实现一个链表的时候是否可以想到用系统库提供的list_head以确保链表结构的正确性;
某些设计如果能套用设计模式会让设计更加美观也让阅读者更加明了;出于对系统性能的考量,我们要关注编写代码对系统的开销,包括使用的算法是否合理,以及对某些比较耗时的操作比如数据库的操作要加以关注。
2.检查代码可读性和可维护性:
如果代码的可读性强,那么维护起来也就方便很多;一个好的代码规范和编码风格会节省大家对代码的理解时间,减少维护成本;虽然我们有编程规范检查工具,但有些内容检查不出来,是需要靠大家去规范的。
关注代码注释:我们在编写函数和进行逻辑判断的时候最好要标注一下这个函数或者这段判断是用来做什么的;做了这种注释的好处,一来当别人阅读这段代码的时候看到你的注释以后就会根据你的思路快速理解代码,甚至不阅读直接跳过;二来防止自己由于长时间不阅读代码而忘记这段代码的用途。
关注命名规范:虽然我们有自己的编码规范,但是这种规范只是限制了使用驼峰命名法还是其他命名法;而好的命名风格应该是看到变量或者函数名字就能“望文生义”,毕竟我们不能把自己写的所有代码都做注释。
关注重复代码:如果出现大量的重复性代码,要考虑将这些代码抽象出公用函数,以减少代码量并增强代码可读性。
关注繁琐的逻辑:如果一个简单的功能却对应大篇幅的代码,要考虑一下是不是有比较简单的实现方式,因为过于复杂的代码会给后来者的维护带来麻烦;如果没有简略的办法,一定要把注释写好。
3.分享设计、技术、知识和经验
在代码审查的过程中,大家往往把关注点放在发现代码的不足上,忽略了代码评审过程中的设计思想、技术方法、业务知识的传播,我觉得这些内容也是非常重要的,也需要同时关注。
评审者在自己的代码时会深入业务流程,参与这可以看到评审代码的一些算法、数据结构、设计模式甚至是系统架构等知识以及评审者在编码过程中踩过的坑;通过这些信息参与者可以提升自己的业务水平和技术能力使得整个团队的水平得到提高。
参与者除了要有这种学习意识外,评审者也要想办法让参与者更加快速高效的去理解代码中传播的知识,这样能帮助提升Review速度,所以建议评审者能简单介绍一下项目的背景以及详细设计,这些信息的介绍有以下好处:
首先,代码的设计是按照详细设计来执行的,但是设计者在真正code的时候会出现一些变动,这些变动要给大家一个同步;
其次,参与过详细设计的人可能由于没有直接参与的code,时间长会忘记之前详细设计的流程,简单介绍之后就会让参与者想起,方便参与者的理解;
第三,对于没参与详细设计的同学,在简单介绍过这些信息后,可以有个大致了解,不然整个评审过程会很煎熬;
所以,如果参与者对代码的信息不理解,会造成参与者理解代码的难度,也就不能提出有建设性的意见,同时也难以学到评审中传播的知识;这一点在之前的评审中是比较容易被大家忽略的,尤其是在跨团队代码评审时,准备不足和经验不足的同学是很难理解对方在讲什么的。
讲解code的时候最好是以接口功能为单位去讲解
如果评审者一下子把所有的详细设计都讲解完,可能会因为信息量比较大,或者设计到一些细节问题,参与者不能有效的记住或理解也会影响评审的速度和效率;
评审者可以在讲解的过程中分享一下自己踩过的坑,参与者可以随时根据自己发现的问题进行讨论。
如何迅速完成CodeReview?
所谓的迅速就是节省时间,只要我们尽量避免一些意义不大的事情就能节省时间,加快评审速度,要做到这点建议大家尽量不要做以下这些事情:
1.不要刻意地去寻找代码bug
有些代码的逻辑是比较复杂的,如果是很容易就发现的缺陷,大多数情况下评审者自己在编码过程就会发现,那么剩下不容易发现的缺陷要发现也会花费较多的时间,这些问题可以交给测试人员去发现;
如果参与者刻意去找bug会造成顾此失彼,忽略更重要的东西;当然,有些bug你可能一眼就看出来了,那提出来就再好不过了。
2.不要按照自己的编程风格去评论别人的代码
有些人参与者比较自信,对自己写得代码感觉很满意,所以有时候就会根据自己熟悉的编码语言或者编码风格去评论别人的代码;
作为参与者,只要觉得评审者的代码符合命名要求和设计要求就可以了,但假如评审者的代码缺陷很明显,可以提出带大家进行讨论。
3.不要带着抨击和质疑别人能力的心态去进行代码评审
有时候参与者可能心情不好,或者感觉对方是新人就忍不住会抨击对方的代码,这样会比较容易在模棱两可的问题上浪费时间;
参与者可能认为A方法好,评审者可能认为B方法也不坏,这样就会造成没有必要的争论而浪费时间。
4.不要在不确定的问题上争来争去
大家在讨论的时候如果某些问题讨论一段时间以后仍然没有结论,或者需要第三方确认或者评审者不能马上理解参与者提出的意见时,不要花太多时间讨论这些问题;
把这些问题先记录下来,等会议结束后评审者可以与参与者进行线下讨论,同时将这些问题根据自己的理解进行解决,之后给大家一个反馈即可,这样可以节省很多时间。
5.不要听不进别人的意见
有些评审者比较固执,不愿意接受大家的意见,会造成一些不必要的争端和讨论,浪费时间;
当然,有时候参与者的意见不见得是最好的,作为评审者将其作为一个参照的方向和视角,如果存在争论,这些建议也可以做成会议记录,评审者私下和建议提出者讨论以后给大家一个结论。
6.参与者最好不要自己都没想明白就提意见
如果参与者自己没有想明白的事情就去提意见,那么评审者反问的时候会浪费大家的时间;
参与者可以先将自己的想法大致记下来,自己想清楚了之后再提给评审者也是节省时间的办法。
7.评审前最好先通过代码静态检查工具检测
一般规范性的问题都可以通过静态检测工具发现,借助工具是最省事,也是效率最高的,还可以避免大家都评审时提出很多规范性问题,而遗漏了业务逻辑、设计合理性等问题。