我和几个小伙伴一起翻译了Google前一段时间放出来的Google’s Engineering Practices documentation,翻译后的github仓库https://github.com/xindoo/eng-practices-cn,目前只是翻译完了,因为译者水平有限,还需要审校。另外后续google肯定还会有新内容放出来,欢迎想参与贡献的小伙伴加入。
这篇文章是Google’s Engineering Practices documentation的第一章Code Review实践指南
- 谷歌Code Review指南, 包含两个子章节:
术语
部分文档中会用到一些谷歌内部的术语,特在此说明:
-
CL: “changelist"的缩写,代表已经进入版本控制软件或者正在进行代码评审的变更。
其他组织经常称为"change"或者"patch”。 - LGTM: "Looks Good to Me."的缩写,看起来不错,当评审者通过代码评审时会这么说。
评审者指南
Code Review标准
Code Review的主要目的是始终保证随着时间的推移,谷歌代码越来越健康,所有Code Review的工具和流程也是针对于此设计的。
为了完成这点,我们不得不权衡利弊。
首先,开发者应当在他们代码中做一些 改进 ,如果你永远都不做出改进,代码库整体质量也不会提升。但是如果审查者为难所有变更,开发者未来也会失去改进的动力。
另一方面,保证代码库随时间推移越来越健康是审查者的责任,而不是让代码库质量变得越来越差。这很棘手,因为代码质量一般都会随着时间推移越来越差,尤其是在团队有明确时间限制、而且他们觉得不得不采取一些投机取巧的方式才能完成任务的情况下。
但是,代码评审者得对他们Review的代码负责,所以他们想始终确保代码库一致、可维护(其他指标见"Code Review应该关注什么?")
依据这些,我们将以下准则作为我们期望的Code Review标准:
通常而言,只要代码对系统有明显的提升且正常工作,即便不完美,评审者也应该倾向于通过这次变更。
这是所有Code Review指南中的高级原则。
当然这也有些局限。例如,如果变更里加入了有些评审者在系统里不想要的功能,即便代码设计的很好,评审者也可以拒绝掉。
关键点是没有任何完美的代码,只有更好的代码。代码评审者绝对不应该要求开发者在批准前对变更中的每一个小块进行打磨,相反,评审者应该权衡向前推进和他们采纳他们建议二者的重要性。评审者应该追求 持续提高 ,而不是追求完美。那些可以提升整个系统可维护性、可读性和可以理解性的变更不应该因为代码不够完美而被推迟几天甚至几周。
评审者要 始终 不拘谨于在代码评论里提示可以更好的想法。 但如果不是很重要信息,可以在评论前面加上标识告诉他们可以忽略。
注意:这篇文档中没有任何地方辩解在变更中的检查会让整个系统代码变得 更糟糕 。你唯一能做的在紧急度中说明。
指导性
Code Review有个重要的作用,那就是可以教会开发者关于语言、框架或者通用软件设计原理。在Code Review中留下评论来帮助开发者学习新东西是很值得提倡的,毕竟共享知识也是长期提升系统代码健康度的一部分。但请注意,如果你的评论纯粹是教育性的,并且不是这篇文档中提到的关键标准,请在前面加上“Nit:”标识,或者明确指出不需要在这次变更中解决。
原则
- 技术和数据高于意见和个人偏好。
- 关于风格问题, 风格指南是绝对的权威。任何不在样式指南中指出的样式(比如空格等)都是个人偏好的问题。风格应该与现有的一致。如果没有以前的风格,就按作者的风格来。
- 软件设计从来不是纯粹的代码风格或是个人偏好问题,它们是基于一些应当被权衡的规则而不仅仅是个人倾向。有时候也会有多种有效的选项,如果开发者能证明(通过数据或者原理)这些方法都同样有效,那么评审者应该接受作者的偏好,否则应该遵从软件设计标准。
- 如果没有其他的规则使用,只要保证不会影响系统的健康度,评审者可以要求开发者保持和现有的代码库一致。
解决代码冲突
如果Code Review中有任何冲突,开发人员和评审人员都应该首先根据开发者指南和评审者指南中其他文档的内容,尝试达成一致意见。
当很难达成一致时,开发者和评审者不应该在Code Review评论里解决冲突,而是应该召开面对面会议或者找个权威的人来协商。(如果你在评论里协商,确保在评论里记录了讨论结果,以便日后其他人翻阅。)
如果这样都解决不了问题,那解决问题的方式就应该升级了。通常的方式是拉着团队一起讨论、让团队主管来权衡、参考代码维护者的意见,或者让管理层来决定。不要因为开发者和评审者不能达成一致而把变更一直放在那里。
Code Review应该关注什么?
注意:当我们考虑以下点时,应当始终遵循Code Review标准。
设计
Code Review中最重要的一个点就是把握住变更中的整体设计。变更中各个部分的代码交互是否正常?整个改动是否属于你负责的代码库?是否和你系统中其他部分交互正常?现在是否是添加整个功能的恰当时间?
功能性
开发者在这个变更中想做什么? 开发人员打算为该代码的用户带来什么好处?(这里”用户“是指受变更影响到的实际用户,和将来会使用到这些代码的开发者)
重要的是,我们希望开发者能充分测试代码,以确保代码在Code Review期间正常运行。但无论如何,你作为审查者也要考虑一些特殊情况,像是有些并发问题。站在用户的角度,确保你正在看的代码没有bug。
你可以验证变更,尤其是在有面向用户的影响时,评审者应该仔细检查整个变更。有时候单纯看代码很难理解这个变更如何影响到用户,这种情况下如果你不方便自己在CL中打补丁并亲自尝试,你可以让开发者为你提供一个功能性的demo。
另一个在Code Review时需要特别关注的点是,CL中是否有 并发编程,并发理论上可能会导致死锁或资源争抢,这种问题在代码运行时很难被检测出来,所以需要有人(开发者和评审者)仔细考虑整个逻辑,以确保不会引入线程安全的问题。(所以除了死锁和资源争抢之外,增加Code Review复杂度也可以成为拒绝使用多线程模型的一个理由。)
复杂性
变更是否比预期的更复杂?检测变更的每个级别,是否个别行太复杂?功能是否太复杂?类是否太复杂?”复杂“意味着代码阅读者很难快速理解代码,也可意味着开发者尝试调用或修改此代码时可能会引入bug。。
一个典型的复杂性问题就是 过度设计,当开发者让代码变得更通用,或者增加了许多当前不需要的功能时,评审者应该额外注意是否过度设计。鼓励开发者解决现在遇到的问题,而不是揣测未来可能遇到的问题。未来的问题应当在遇到时解决,到那个时候你才能看到问题本质和实际需求。
测试
开发人员应当进行单元测试、集成测试或端到端测试。一般来说,变更中应该包含测试,除非这个变更只是为了处理紧急情况。
确保变更中的测试是正确、合理和有用的。因为测试本身无法测试自己,而且我们很少会为测试编写测试,所以必须确保测试是有效的。
如果代码出了问题,测试会失败吗?如果代码发生改动,它们会误报吗?每一个测试都有断言吗?是否按照不同的测试方法对测试进行分类?
记住,不要以为测试不是二进制中的一部分就不关注其复杂度。
命名
开发人员是否使用了良好的命名方式?好的命名要能够充分表达一个项(变量、类名等)是什么或者用来做什么,但又不至于让人难以阅读。
注释
开发者有没有写出清晰易懂的注释?所有的注释都是必要的吗? 通常注释应该解释清楚为什么这么做,而不是做了什么。如果代码不清晰,不能清楚地解释自己,那么代码可以写的更简单。当然也有些例外(比如正则表达式和复杂的算法,如果能够解释清楚他们在做什么,肯定会让阅读代码的人受益良多),但大多数注释应该指代码中没有包含的信息,比如代码背后的决策。
变更中附带的其他注释也很重要,比如列出一个可以移除的待办事项,或者一个不要做出代码变更的建议,等等。
注意,注释不同于类、模块或函数文档,文档是为了说明代码片段如何使用和使用时代码的行为。
风格
在谷歌内部,主流语言甚至部分非主流语言都有编码风格指南 ,确保变更遵循了这些风格规范。
如果你想对风格指南中没有提及的风格做出改进,可以在注释前面加上“Nit:”,让开发人员知道这是一个你认为可以改进的地方,但不是强制性的。但请不要只是基于个人偏好来阻止变更的提交。
开发人员不应该将风格变更与其他变更放在一起,这样很难看出变更里有哪些变化,让合并和回滚变得更加复杂,也会导致更多的其他问题。如果开发者想要重新格式化整个文件,让他们将重新格式化后的文件作为单独的变更,并将功能变更作为另一个变更。
文档
如果变更改变了用户构建、测试、交互或者发布代码相关的逻辑,检测是否也更新了相关文档,包括READMEs, g3doc页面,以及任何相关文档。如果开发者删除或者弃用某些代码,考虑是否也应该删除相关文档。如果文档有确实,让开发者补充。
代码细节
查看你整个Code Review中的每一行代码,比如你可以扫到的数据文件,生成的代码或大型数据结构,但不要只扫一遍手写的类,函数或者代码块,然后假设它们都能正常运行。显然,有些代码需要仔细检查,这完全取决于你,但你至少应该理解所有的代码都在做什么。
如果你很难看懂代码,导致Code Review的速度慢了下来,你要让开发者知道,并且在Review前澄清原因。在谷歌,我们有最优秀的工程师,你也是其中之一。如果你都看不懂,很可能其他人也看不懂。所以要求开发者理清代码逻辑也是在帮助未来的开发者。
如果你理解代码,但又觉得没有资格做代码评审,确保有资格的变更评审人员在代码评审时考虑到了安全性、并发性、可访问性、国际化等问题。
上下文
看改动上下文代码对Code Review很有帮助,因为通常Code Review工具只会显示改动部分上下几行代码,但有时你必须查看整个文件确保这次变更可以正常运行。例如,你可能看到加了4行代码,但是看到整个文件时才会发现这加的4行代码是在一个50多行的方法里,这个方法现在应该被拆分为多个小的方法了。
Code Review时考虑到整个系统的上下文也很重要,这次改动提升了系统健康度?或者增加了系统复杂性?少了测试用例?…… 不要通过哪些会损害系统健康的代码。 很多系统变复杂都是因为一点一点的小改动日积月累造成的,所以千万不要放进来任何增加复杂性的改动。
亮点
如果你看到变更中做的好的地方,告诉开发者他们做的很棒,尤其是当他们用某种很精巧的方式解决你评论中提到的问题时更应不吝赞美。 Code Review过多关注于错误,但你也应该为开发者展现出好的一面点赞。在指导他人的时候,有时候告诉开发者正确的做法比告诉他们错误的做法更有价值。
总结
在做Code Review时,你需要注意以下:
- 代码设计良好。
- 代码功能对用户有用。
- 任何UI改动应当是深思熟虑过而且看起来不错的。
- 保证线程安全。
- 代码没有增加不必要的复杂性。
- 开发者没有写有些将来需要但现在不知道是否需要的东西。
- 代码有适当的单元测试。
- 测试逻辑设计良好。
- 开发者使用了清晰明了的命名。
- 注释清晰明了实用,通常解释清楚了为什么这么做,而不是做了啥。
- 代码又相应完善的文档。
- 代码风格符合规范。
确保你review了要求你看的每一行代码,确保你正在提升代码质量,并且为开发者做的提升点赞。
Code Review步骤
概要
现在你知道了要从CL中得到什么,那能在众多文件中完成评审的方法是什么?
- 这条改动是否生效?这次变更是不是描述清晰?
- 首先阅读CL最重要的一部分,整体上是否设计良好?
- 按照合适的顺序阅读剩下的变更。
第一步: 全面了解变化
阅读CL描述并且明白CL大体内容。这些修改是否有意义?首先如果这个修改不应该有,请立刻说明为什么这些修改不应该有。当你因为这个拒绝了这次改动提交时,告诉开发人员该怎么去做是非常好的。
例如,您可能会说:“看起来您为此做了一些出色的工作,谢谢!但是,我们实际上正着手删除FooWidget系统,您正在此处进行修改,因此我们不想进行任何新的修改现在。所以,您可以重构我们的新BarWidget类吗?"
请注意,审阅者不仅拒绝了当前的CL并提供了替代建议,但他们礼貌地做到了。这种礼貌是重要,因为我们想表明我们甚至在开发人员之间也相互尊重,尤其是当我们意见不同时。
如果您得到的多个CL并且您不想进行的更改,您应该考虑重新设计团队的开发流程或发布的外部贡献者的流程,以便在CL完成之前进行更多的交流。最好在做大量工作之前告诉人们“不必做”,因为现在要将其丢弃或彻底重写。
第二步: 检查CL的主要部分
查找属于此CL的“主要”部分的文件。通常来说一个CL最重要的部分是它逻辑修改数最多的那个文件。这样有助于了解相关的CL,通常来说会加快code review。如果CL太大,您无法确定哪些部分是主要部分,请开发人员告诉你应该首先看什么,或要求他们将CL拆分为多个CL。
如果您发现CL的这一部分存在一些主要的设计问题,则即使您现在没有时间审查CL的其余部分,也应立即写下这些评注。 实际上,检查其余的CL可能会浪费时间,因为如果设计问题足够严重,那么许多其他正在检查的代码将消失并且无论如何都不会发生。
立即写下这些主要设计评注非常重要有两个主要原因:
-
开发人员通常会邮寄一个CL,然后在等待审核时立即基于该CL进行新工作。 如果您正在审查的CL中存在重大设计问题,那么他们也将不得不重新设计其以后的CL。你能在他们做太多无用功之前制止他们。
-
重要的设计变更比小的变更需要更长的时间。 开发人员几乎都有截止日期;为了在截止日期之前完成工作, 并在代码库中保留高质量的代码,开发人员需要尽快开始CL的所有主要的重做。
第三步:以适当的顺序阅读其余的CL
确认CL整体上没有大的设计问题后,请尝试找出逻辑顺序来浏览文件,同时还要确保不要错过对任何文件的审查。通常,在浏览了主要文件之后,按照代码审查工具向您展示它们的顺序浏览每个文件是最简单的。有时在阅读主要代码之前先阅读测试代码也是有帮助的,因为这样您就可以知道更改应该做什么。
Code Review的速度
为什么Code Review应该快?
在谷歌内部,我们在持续优化团队开发新产品的速度,而不是优化单个开发人员编写代码的速度。 个人开发的速度固然重要,但它没有团队整体的速度那么重要。
如果Code Review速度慢了,可能会发生以下这些事:
- 整个团队的速度会降低,是的,你不快速响应别人的Code Review也可以完成其他的工作。但是,整个团队的新功能、bug修复可能会因为没有人做cr被延迟几天、几周甚至几个月。
- 开发者应维护Code Review的流程,如果审查者很少回复Code Review,但是每次都对CL提出大量的改动,这可能会打击到开发者。通常,开发者可能会抱怨审查者太严格。如果审查者能在开发者更新后快速响应,并提出有实质性提升的建议(能显著提升代码运行状况的CL),抱怨就会消失。 Code Review中绝大多数抱怨会随着CR速度的提升而消失。
- 可能影响到代码质量。 如果CR慢了,可能会给开发者一种需要提交不太好代码的压力。CR慢了,也会影响到代码清理、重构、和现有CL的进一步提升。
应该以什么样的速度做Code Review?
如果你不是在做需要高度专注的任务,Code Review应该越快越好
应当在一个工作日之内回应Code Review请求。
遵循这些指导意味着典型的CL应该在一天内进行多轮审查(如果需要)。
速度 vs 中断
只有一种情况下,个人的速度要胜过团队速度。如果您正在处理诸如编写代码之类的重要工作,请不要打断自己的工作去做Code Review,研究人在专注中被打断后需要很长的时间才能重新恢复到专注状态。所以,为了不让其他开发者等会而且中断自己的编码工作,明显得不偿失。
所以,建议你在工作断点的时候回应Code Review,比如写完代码、午饭后、会议结束后、从茶水间回来……
快速响应
当我们谈论Code Review的速度时,一方面是指响应时间,另一方面是指整个Review从提交到通过的时间。整个过程应该足够快,但是对于每个人来说,迅速做出反应比迅速完成整个过程更为重要。
即使有时需要很长时间才能完成整个Code Review流程,评审者在整个过程中的快速响应也可以大大缓解开发人员因为Code Review“慢”而产生的挫败感。
如果你太忙不能全身心投入到Code Review中,你也应该让开发者知道你什么时候会去Review,或者建议其他评审者快速响应,再或者提供一些初步的建议。(注意:这不是建议你中断自己的工作,而是在工作间隙合理响应)
评审者有必要花时间去做Code Review来保证代码符合标准,不管怎么样,每个回应应当保证足够快速。
跨时区Code Review
当遇到跨时区的Code Review时,尽量在作者回家前回复。如果他们已经回家了,尽量在他们每天来公司前完成Code Review。
LGTM和注释
为了让CR更快,有些情况下也应该让CR提前通过,即便有些评论没有被解决,比如:
- 审查者信任开发者能适当解决所有评审者的建议。
- 其余的改动很小,开发者不必做。
除非另有明确说明,评审者应指明他们打算使用这些选项中的哪一个。
当开发者和评审者在不同时区时,应当着重考虑下通过CR,否则开发者还得等一天。
大的CL
如果有人给你提交了一个非常大的Code Review,你也不确定你有时间看,你最好建议开发者把CL拆分成几个小的部分,分多次Code Review,而不是一次性全部提交上来。这即便开发者多做一些额外的工作,也是可以把它拆分开的,而且拆分也有利于评审者。
如果CL不能拆分成多个小CL,你也没有时间快速完整的Review整个代码,只是对整体设计提一些建议,然后发回给开发者改进。作为评审者,你的目标之一是在不牺牲代码质量的前提下,不阻碍开发者的进程或者尽可能让他们向前推进。
持续提升Code Review
如果你遵从这些建议并在Code Review中严格执行这些准则,你就会发现整个Code Review的流程会越来越快。 开发者将了解健康代码的要求,并从一开始就交出完美的代码,然后Code Review的时间会越来越少。评审者将学会如何快速做出响应,并且不是在这个Code Review过程中增加不必要的延迟。
但是,不要为了提升速度牺牲Code Review的标准和代码质量。 从长远来看,这并不会提升速度。
紧急情况
当然也有一些紧急的CL需要快速走完这个Code Review流程,这时候在质量上的把控可以稍微放松一些。可以参考紧急事件一文来了解哪些是紧急事件哪些不是。
怎样写代码评论
概要
- 礼貌
- 解释你的观点.
- 明确指出方向和问题,帮助开发人员去权衡作出决定.
- 鼓励开发人员通过注释和精简代码来解决你的困惑而不是通过解释
礼貌
通常来说当你Code Review代码时保持礼貌和尊重能使开发人员更加清晰,得到更多帮助。这样是为了保证你的代码评论仅仅针对的是code而不是针对开发人员。你不必一直这么去做,但是当你的评论会让开发人员生气或者产生争执时有必要这么去做。比如:
不好的例子: “你为什么会在这里使用线程,这样做难道会有任何好处?”
好的例子: "我并没有发现这个并发模块给程序带来了多少帮助,并且还增加了程序的复杂性,因此我认为这段代码最好是用单线程而不是多线程。
解释清楚原因
从上面“好”的例子当中你能发现,这样有助于开发人员理解为什么你写了这些评注。你不一定非得包含这些信息在你的评注里面,但是适当的多解释你的意图或者多给出一些提升代码质量的建议都是非常好的实践。
给予指导
通常来说修复CL是开发人员的职责而不是评审人员的。你不需要向开发人员提供详细的解决方案或者代码。
但是这并不意味着评审员就不应该提供帮助。你需要在指出问题和提供直接指导之间找到平衡。指出问题并且帮助开发人员决策能够帮助开发人员学同事让code review变得更加简单。这样也能产生更好的方案,因为开发人员比评审者更加了解代码。
尽管这样,有时候直接给出指导,建议甚至是代码更有帮助。code review的主要目的是尽可能得到最好的CL。其次是提高开发人员的技能这样就能减少以后评审的次数。
接受解释
当你要求开发人员解释一段你看不懂的代码的时候,通常的结果是他们重写代码,让代码更清晰。当一段代码不是太过于复杂的时候通过加一些注释偶尔也是一种不错的做法。
**解释仅仅是写在Code Review工具里的,不利于将来的代码阅读者。**只有极少数情况是可行的,比如你对你评审的需求不太熟悉,但是开发人员解释的是大多数人都知道的。
处理Code Review中的反驳
有时开发者会在Code Review中反驳你,他们可能不同意你的意见,或者抱怨你太严格了。
谁是谁非?
当开发者不同意你的建议时,首先花点思考下他们是否是对的,但通常而言你比他们更熟悉代码,所以可能在某个方面理解更深。他们的争论有意义吗?从代码健康的角度来看他们的反驳有意义吗?如果是,让他们知道他们是对的,然后这个问题就解决了。
然而,开发者不总是对的,这种情况下评审者应当进一步介绍为什么他们的建议是对的。好的解释不仅得体现出对开发人员回复的理解,而且还要说明为什么要这么改。
如果评审者认为他们的建议可以改善代码质量,并且他们认为带来的代码质量改进值得开发者做这些额外的工作,评审者就应该坚持自己的立场。
不积跬步无以至千里,不积小流无以成江河
有时候要让开发者接受,你得花很多时间反复解释,但始终确保该有的礼貌,并让开发者知道在知道他们了什么,即便是你不同意。
惹恼开发者
有时评审者会认为坚持让开发者做出改动,可能会惹恼开发者。有时开发者确实会恼怒,但这种情况通常都很短暂,而且之后他们会感激你帮助他们提高了代码质量。 如果你在Code Review中很有礼貌,开发者根本不会被惹恼,这种担心是多余的。通常,令惹恼开发者的是你写注解的方式,而不是你对代码质量的坚持。
稍后解决
一种常见的反驳原因是开发者希望能尽快完成任务。他们不想一轮又一轮地做Code Review,然后就会说他们会在后续CL中处理这些问题,你只需要通过就行。有些开发者做的很好,他们会立马提交后续CL处理这些问题。然而,经验告诉我们原始CL通过之后拖的时间越久,就越不可能修复。除非开发者在当前CL通过后立马修复,否则他们就不可能修复。这并不是开发者不负责任,而是因为他们有好多工作要做,而修复工作也会因为工作压力而被遗忘。所以最好坚持让开发者现在就在CL中处理掉这些问题,“留着以后清理”是一种不可取的方式。
如果CL中引入了新的复杂性,提交之前必须清理掉,除非是紧急情况。 如果CL中暴露出一些目前还无法定位的问题,开发者应该记录下这些bug,并将其分配给他们自己,确保这些问题不会被遗忘。他们还可以在代码中加入 TODO 注释,指向已经记录好的 bug。
抱怨太严格
如果你之前Code Review很宽容,然后突然变得严格起来,可能会引起一些开发者的抱怨。不过没关系,加快Code Review的速度通常会让这些抱怨消失。
有时,这些抱怨可能需要几个月的时间才能消除,但最终开发者在看到产出的优质代码时会理解严格Code Review带来的价值。有时候,一旦发生某些事让他们真正看到严格Code Review的价值,*最大声的人甚至会成为你最坚定的支持者。
解决冲突
如果你遵循了上述方法,但仍然会在Code Review中遇到无法解决的冲突,请再次参阅Code Review标准,了解那些有助于解决冲突的指导和原则。
开发者指南
写一个好的CL描述
一个CL描述是做了什么更改以及为什么的公开记录。
它将成为我们版本控制历史的永久组成部分,多年来除你的评审者以外,还可能有数百人会阅读它。
将来的开发者将会基于它的描述来搜索你的CL。
将来可能会有人由于没有现成的细节,而根据他模糊的记忆来寻找你的改变。
如果所有重要的细节都在代码中而不是描述中,那么他们将很难定位你的CL。
第一行
- 言简意赅
- 语义完整
- 空行隔开
CL描述的第一行应该是对CL正在做的具体工作的简短总结,紧跟一个空行。
在未来,这是大多数的代码搜索者在浏览一段代码的版本控制历史时会看到的,因此第一行应该足够有信息量,他们不必阅读你的CL或它的整个描述就可以大致了解你的CL实际做了什么。
按照传统,CL描述的第一行是一个完整的句子,就像它是一个命令(一个祈使句)。
例如,说"Delete the FizzBuzz RPC and replace it with the new system.",而不是"Deleting the FizzBuzz RPC and replacing it with the new system."。
不过,你不必把其余的描述写成祈使句。
正文内容丰富
其余描述应该是具有丰富的内容。它可能包括对正在解决的问题的简要描述,以及为什么这是最好的方法。 如果这种方法有任何缺点,就应该提到。将相关的背景信息(例如bug数目,基准测试结果),以及设计文档的链接。即使是很小的CL也值得关注细节。请将来龙去脉放入CL中。
反例
"Fix bug"是一个不充分的CL描述。是什么bug?你是怎么修复它的?
其他一些类似的不好的CL描述:
- “Fix build.”
- “Add patch.”
- “Moving code from A to B.”
- “Phase 1.”
- “Add convenience functions.”
- “kill weird URLs.”
这里有一些是真实的CL描述。他们的作者可能认为他们提供的信息是有用的,但他们并没有给出一个CL描述的意图。
好的CL描述
这里有一些好的CL描述的例子.
功能改变
rpc: remove size limit on RPC server message freelist.
Servers like FizzBuzz have very large messages and would benefit from reuse.
Make the freelist larger, and add a goroutine that frees the freelist entries
slowly over time, so that idle servers eventually release all freelist
entries.
前几个词描述了CL描述实际做了什么。其余描述在说明解决的问题,为什么这是一个好的方案,以及具体实现的细节。
重构
Construct a Task with a TimeKeeper to use its TimeStr and Now methods.
Add a Now method to Task, so the borglet() getter method can be removed (which
was only used by OOMCandidate to call borglet’s Now method). This replaces the
methods on Borglet that delegate to a TimeKeeper.Allowing Tasks to supply Now is a step toward eliminating the dependency on
Borglet. Eventually, collaborators that depend on getting Now from the Task
should be changed to use a TimeKeeper directly, but this has been an
accommodation to refactoring in small steps.Continuing the long-range goal of refactoring the Borglet Hierarchy.
第一行描述了CL做了什么,以及它是如何与过去发生变化的。
其余的描述将讨论具体的实现、CL的来龙去脉、解决方案的不理想以及未来可能的方向。
它同样是解释为什么要进行这个修改。
需要上下文的小CL
Create a Python3 build rule for status.py.
This allows consumers who are already using this as in Python3 to depend on a
rule that is next to the original status build rule instead of somewhere in
their own tree. It encourages new consumers to use Python3 if they can,
instead of Python2, and significantly simplifies some automated build file
refactoring tools being worked on currently.
第一句描述了实际做了什么。其余的描述解释了为什么要进行更改,并给了reviewer很多背景。
在提交CL之前,请检查描述
在Review期间CL可能会经历重大改变。在提交CL之前,有必要review CL描述,以确保描述仍然反映CL所做的事情。
简短化的CL
为什么要写成一系列简短的CL?
简短的CL有这些好处:
- Code Review更快 与比起花30分钟审查一个大型的CL相比,对审查者来说花5分钟审查一系列小的CL更加容易.
- 审查更加彻底。 进行较大的更改后,审阅者和作者往往会因大量详细评论的来回移动而感到沮丧,有时这些评论会漏掉或遗漏重要的观点。
- 减少导致bug的可能性。 由于您所做的更改较少,因此您和您的审阅者更容易有效地推断出CL的影响,并查看是否导致bug。
- 减少不必要的工作 当你写了一个巨大的CL,然后审查者觉得你总体方向错了,这会导致你浪费大量的工作。
- 更方便合并代码 因为大型的CL会导致大量的冲突,因此合并大型的CL会浪费很多时间,并且这将会是你经常做的工作。
- 有助于你作出更好的设计 优雅的设计并且完善一个小的改动比大的改动更加容易
- 降低审查者的难度 提审部分改动,不会影响你继续编码。
- 回滚更容易 大型CL很有可能会在初始CL提交和回滚CL之间更新修改文件,从而使回滚变得复杂(中型CL也可能也需要回滚可能也会这样)。
注意! 审查者可以因为你的改动过于巨大直接拒绝掉你通常,他们会感谢您的贡献,但要求您以某种方式使它成为一系列较小的更改。不管是你把这些改动拆分成小的改动,还是和审查者争论让他接受都会耗费你大量的时间。但是编写小型改动就不会有这样的问题。
怎么样算简短?
通常,CL的正确大小是一个独立的更改。 这意味着:
- CL所做的最小更改仅解决了一件事情。 通常,这只是功能的一部分,而不是一次完整的功能。 通常,宁可编写过小的CL也不要写太大的CL。你可以和你的审查者商量找到合适的尺度。
- 审阅者需要了解的有关CL的所有内容(将来的开发除外)都在CL,CL的描述,现有代码库或他们已经查看过的CL中。
- 检入CL后,系统将继续对其用户和开发人员正常运行。
- CL不够小的话会导致其难以理解。如果你新增加了api,那么在同一个CL应该包括这个api用到的方法,以便审查者更好的理解。也能防止检入没有用的api。
多大算大,没有一个明确的要求。 对于CL而言,100行通常是一个合理的大小,而1000行通常太大,但这取决于您的审阅者的判断。 更改分布的文件数量也会影响其“大小”。 可以在一个文件中进行200行的更改,但是将其分布在50个文件中通常会太大。
请记住,尽管从您开始编写代码起就一直与您紧密联系,但审阅者通常没有上下文。 对您来说,看起来像可接受大小的CL可能会让您的审阅者不知所措。毫无疑问,尽可能把CL些小是正确的。审查者很少抱怨CL太小
什么时候大型的CL也是可以的?
在某些情况下,较大的更改没有那么糟糕:
- 通常,您可以将整个文件的删除视为更改的一行,因为它不会花费很长时间来审阅审阅者。
- 有时,您完全信任的自动重构工具已经生成了一个较大的CL,而审阅者的工作只是检查健全性,然后指出他们认为需要修改的地方。 这些CL可能更大,尽管会有一些风险(例如合并和测试)仍然适用。
按文件分类
拆分CL的另一种方法是通过将文件分组,如果这些文件将是独立的更改,可以分配各不同的审阅者。
比如: 你提交一个修改的CL,创建一个修改的缓冲区,另一个CL的代码修改也可以提交到这里面。你必须按照顺序提交CL,但是审阅者可以同时进行审阅. 如果这么做,你需要尽可能告诉所有审阅者另一个CL的信息,以便他们能得到上线文信息。
另一个示例:您发送一个CL进行代码更改,而另一个CL则发送使用该代码的配置或实验; 如果需要,这也更容易回滚,因为有时将配置/实验文件推送到生产环境中比更改代码更快。
把代码重构分离出来
通常重构最好不要和功能修改或者bug fix一起提CL。比如移动或者重命名一个Class,最好和这个Class的bug fix分开提CL。这样对于审阅者来说,理解每个CL单独引入的更改要容易得多。
不过一些小的代码清理工作比如变量重命名可以包含在功能修改或者bug fix的CL种。 这取决于开发者和审阅者的判断,当然如果巨大的重构包含在同一个CL中会大大增加审阅的难度。
将相关的测试代码保存在同一CL中
避免将测试代码拆分为单独的CL。 验证代码修改的测试应该进入相同的CL,即使这样做会增加代码行数。
但是根据重构准则,独立的测试修改可以单独写入CL。比如
- 使用新测试验证先前存在的提交代码。
- 重构测试代码(例如,引入辅助函数)。
- 引入更大的测试框架代码(例如集成测试)。
不要破坏结构
如果您有多个相互依赖的CL,则需要找到一种方法来确保在提交每个CL之后整个系统都能正常工作。 否则,可能破坏代码结构导致后面的开发者浪费大量的时间等你的提交(如果这些CL提交出了问题,则更长的时间)。
小到不能再小
有时候,您会遇到CL很大的情况。 这很少是真的。 练习编写小型CL的作者几乎总是可以找到一种将功能分解为一系列小的更改的方法。
在写大型CL之前,思考下是不是能够将重构分离出来,这是一个更加清晰的思路。多和你的队友交流学习下他们对缩小CL的实践和方法。
如果所有这些选项都失败了(这种情况很少见),那么请事先获得您的审阅者的同意,告诉他们一个巨大的CL将要来临。 在这种情况下,审查过程会耗费极其长的实践,这样请花费更多的时间去写测试代码,避免引入bug。
如何处理评审者的评论
当你提交了一个变更做Code Review时,很可能你都会收到评审者在变更中的评论。这里有一些处理这些评论的建议。
不要掺杂个人情感
Code Review的目标是维护代码库和产品的质量。如果评审者批评了你的代码,可以理解为他们在帮你、帮整个代码库、甚至是帮整个公司,而不是攻击你或者是质疑你的能力。
有时候评审者会情绪低落,然后在评论中说出一些令人沮丧的话,虽然评审者这样做不对,但作为开发者你应当有心理准备。问问你自己,“评审者试图与我交流的建设性意见是什么?” 然后照他们说的那些去做。
永远不要回应充满怒气的评论,Code Review工具中违反职业礼仪的情况永远存在。如果你真的忍无可忍,建议先离开电脑也会儿,或者干一些其他的事,等心情平复下来再回复。
通常,如果评审者没有以礼貌的方式提供反馈,请亲自向他们解释。如果你无法亲自或者通过视频和他们联系,就向他们发一份私人邮件。 友好地告诉他们你不喜欢的事情以及你希望他们做些什么。 如果他们也以非建设性的方式对此私密讨论做出回应,或者没有起到预期的作用,请酌情上报给您的经理。如果他们已经以不礼貌的方式回应,或者没有取得预期的效果,视情况汇报给你的经理。
修复代码
如果评审者说他们理解不了你代码中的某些内容,你首先应该把代码写清晰。如果让代码更清晰,添加注释来解释清楚代码的逻辑。 如果评论似乎毫无意义,那么您的答复应该只是代码查看工具中的解释。
如果评审者无法理解你的某部分代码,那边可能未来的阅读者也可能理解不了。在Code Review工具中回应帮不了未来的读者,但是代码中的注释可以。
自我反思
写一个变更会花费你很大的精力,提价Code Review时会感觉如释负重,而且自己也相当确定所有工作已经做完了。所以当评审者提出改进建议时,你很容易认为那些都是错的,或者认为是评审者给你不必要的阻挠,再或者觉得评审者应该让你提价变更。 无论如何,不管你怎么想,花点时间回想下评审者给你的反馈有助于提升公司的代码质量。你始终问下自己“如果评审者是对的呢?”
如果你回答不了评审者的问题,那可能说明评审者的评论不够清楚。
如果你认真考虑过后依旧认为你是对的,放心大胆地解释清楚为什么你的方法对公司更有利。通常,评审者只是提供建议,并且希望你能思考出更好的方法。也许你已经知道一些评审者不知道的关于用户、代码库、或者变更,把这些都写下来,给评审者更多的上下文信息,通常你都可以根据某些事实和评审者达成某些共识。
解决冲突
解决冲突的第一步,和你的评审者达成共识,如果无法达成共识,参阅Code Review的标准获取更多内容。