如何在快速开发中保持高品质代码
高质量的代码库能使产品迭代、协作和维护变得更为容易,加快长期项目开发的速度。在Quora,我们十分重视代码库的质量。
然而,纵然有上面提到的好处,维护高质量的代码仍会间接耗费大笔费用,占用开发周期。权衡得失对很多人而言是件难事,他们面临相悖的两个选择:降低代码质量以提高开发速度;还是放缓步伐,提高代码质量?对初创公司而言,开发速度快更重要,所以人们假定你会牺牲代码质量。
通过设计一系列工具和相应流程,我们得以打破僵局——在快速开发中仍能保持高品质代码。本文将介绍我们在Quora使用的这套方法和具体案例。
维护高质量代码的原因
高质量代码主要有助于加快长期项目的开发速度,这是我们的着眼点以及宗旨。只需在开发前期编写更整洁的代码,这些短期成本就能换来长久的收益。
在该理念指导下,我们发现以下四个基本原则对保证代码质量有益:
Quora代码质量原则
- 代码应可读易读——阅读代码比写代码更耗时间,因此,为尽量减少阅读时间,适当放慢写代码的速度也无妨。
- 每部分代码应符合其对应的质量标准要求——不同的代码行有不同的使用期、适用范围,它们的崩溃风险程度、修复崩溃和破损的费用各异。综上,不同代码行对长期迭代速度的影响迥异,强求所有代码行都遵从统一的质量标准并非最佳抉择。
- 维护代码质量的成本可被缩减——通过自动化技术、使用更好的工具和程序、提高开发者素质,往往能降低因维护高质量代码而产生的间接成本。
- 注重代码库编码一致性——虽就局部而言,有的代码还可以更好,但保持整个代码库的一致性仍然有意义。代码风格不一会加大阅读和理解难度(见第一点),编写及使用自动工具优化也会遇到困难。
接下来,我谈谈在开发过程中如何实践这些原则。
提交代码后审查
在我们的代码库中,每处修改都必须在以下六个方面接受同行评议:正确性、隐私、性能、构架、复用性和风格。由于阅读代码在代码审查中必不可少,代码审查自然能(大幅)提高代码可读性。
但不巧的是,代码审查也会降低开发速度。例如,预提交代码前预审查制是行业惯例,虽然只有2到3次迭代,每轮审查也仅需两天,却很容易让程序员在这里浪费大把时间,工作停滞不前。
在Quora,我们一般使用提交后审查,即先在生产环境中使用代码,再派人审查。昨天,我们中的48个人一共提交了187次代码,这个数据可以帮助你了解我们的提交规模。提交后审查制的好处在于,它将程序员从等待(预提交)代码审查中解放了出来,让他们能继续其他任务。代码审查员也能更好地规划工作时间,他们不必因程序员的等待而加紧审查的节奏,相反,他们可以自由选择最合适的时间工作。我们预期所有代码通过审查应在一周内,但通过合理安排,多数审查都能在1到2天完成。“一周”这个时间是经过慎重考虑的,它够长,以便审查员灵活安排,也够短,将可能出现的坏结果降到最低,例如减少在代码库中传播差劲的代码。在实践中,它也契合了大部分开发者的日常工作节奏。
之所以能采用提交后审查,是因为我们充分信任每位开发者 —— 毕竟我们只聘用优秀的人,并且在代码质量培训和工具上投入了大量资金。这也督促我们更严格地测试代码。就算还未经过任何审查,良好的测试覆盖率也有助于确保代码正确。在此基础上,我们还使用了Phabricator,一款可靠且可灵活配置的审查工具。我们对它做了几项改进,这样一来,在提交后审查工作流程中,效果就更为出色。例如我们构建了一款命令行工具,用来生成代码和请求审查。有了它,Phabricator就能在不修改已提交代码的情况下继续执行diff命令。
话虽如此,对于不同类型的代码修改,我们的审查标准有别。如果潜在的漏洞/错误会造成严重的问题,代价高昂,我们就会改变平时的提交后审查流程,转为提交前审查。典型情况包括:
- 触及用户隐私和匿名的代码
- 触及影响大量其他代码的核心抽象
- 触及基础结构的某些部分,它们有问题会导致宕机
这也取决于开发者的倾向 —— 如果他们想通过提交前审查来收集更多建议,也可如其所愿,不过这种情况少之又少。
代码审查任务分派
为了让审查达到良好效果,每处更改都须由背景知识足够丰富的人来把关。如果审核代码的人同时也负责后续维护就更好了,这样便能长期受益。
我们实现了一个简单的系统,在模块/目录等级的代码“ownership”上添加标签,例如:
__reviewer__ = 'vanessa, kornel'如果提交新变更,系统就自动会分析文件(或其上层目录),将新审查者的名字添加到提交数据中。我们还有额外的规则将代码审核派发给合适的人,例如在A/B测试中,系统会自动指派一位科学家作为审核人。 其实,为了能轻松地添加更多定制“分配”规则,我们还制作了一些工具。举个例子,只要我们想,就能轻而易举地把所有新员工提交的代码派发给他们的导师来审查。
借助智能系统,把审查任务派发给恰当的人,不仅减轻了程序员的负担,还能确保每个审核者都是最佳人选。
代码测试
在我们的开发流程中,代码测试是重要一环。我们撰写了覆盖各个环节(包括单元、函数和UI等)的测试案例。覆盖全面的测试可以帮助开发者快速前进,不需担心已有功能受影响。为了减少撰写测试案例的成本,我们投入大量时间开发测试框架(基于nosetests),令完成这项工作更便利、快捷。
我们还开发了多种自动化测试工具。正如之前我们发布的一篇关于持续部署系统的文章所讲述的那样,一台中央服务器会在所有新代码部署前运行一遍所有的测试。测试服务器高度并行化运作,因此即便是运行全部的测试也只需5分钟。如此快捷的运转,激励开发者经常撰写和运行测试。还有一个叫做“test-local”的工具,仅用于识别和执行相关测试文件,从而测试正在运行的代码中发生的改变。为了让这个工具正常运转,测试必须是模块化的(也便于在测试失败时进行快速故障排除)。为了实现这个目标和其他一些优秀测试代码所必须的属性,我们维护了一个共享文档,用于描述撰写测试案例的最佳实践。这些指南都在代码审查中严格执行。
通代码审查一样,对于不同类型的更改我们也有不同的测试标准,不论何时不论推倒已有工作的成本有多高,我们都需要更高的测试标准。
所有这些系统加在一起帮助我们充分利用那些为了在长远开发流程中减少损耗而撰写的测试样例。
代码质量指南
我们非常乐意共享代码质量标准指南,因为它有几个作用:
- 这些指南是培训新开发者的极佳工具
- 这些指南帮助我们给整个团队分享基于我们开发实践的智慧和最佳实践。
- 设定通用的标准,从而帮助我们维持代码库的一致性。
- 减少开发过程和代码审查过程中的损耗。比如,在每一次代码审查时都讨论一段代码应该是80个字符还是100个字符是没有意义的,如果只需讨论一次,做出定论,未来都不需讨论,那么就可以减少很多损耗。
除了给不同的语言设定语法风格指南,我们还给更抽象的事物设定了指南,如:如何撰写好的测试样例、如何组织代码模块来减少阅读时间。这些指南都不是一成不变的,而是会随着开发更容易理解的代码而不断调整。我们还积攒了一大堆代码重构工具(其中包含像codemod这样的开源工具,页包括一些我们自己开发的工具),这些工具在一些我们需要回溯的案例中非常好用,能够在我们修改风格指南后帮助我们“修正”过去所有的代码。
旧代码清理
快速前进的团队会尝试不同事物,虽然期望新事物可用,但也有很多没法用,这很自然。因此,任何快速发展的公司的代码库中都会有些质量参差不齐的代码,有些代码不再使用了,但留在那里不动又会造成更多的麻烦。清理这些质量参差不齐的代码能保证代码库的健康,提高开发的速度。
我们会定期组织“清理周”,来清理这些质量差的代码。在这段时间,有些团队甚至有时是整个公司会拿出时间专门清理旧代码。这种定期的清理减少了“一般开发工作”和“代码清理工作”之间工作状态切换带来的成本损耗,并且让代码清理变得更社交化,也更有意思。
代码库中有些代码要比其他代码更容易清理。同样的,清理代码库中的一些代码也对开发速度有不同程度的影响。为了能充分利用花在代码清理上的时间,我们根据清理的成本和清理后会对未来开发速度的影响来对需要清理的模块进行优先级排序。
代码校验(Linting)
在一次性实例中如果不遵循句法规则的代码质量指南(如docstring或代码长度的格式)我们很容易低估这造成的成本损耗,但是时间一长,这些成本是会累加到一起的。记住和应用许多不同的规则经常让人很恼火,尤其是这些规则还在不断增加的时候。所以,当工业界中的许多人决定不遵循哪些规矩的时候,也没什么奇怪的。
我们开发了一款叫做“qlint”的内部使用的代码校验器,用来减少那些惹恼人们的麻烦。Qlint是一个“聪明的”代码校验器,既懂得文本,也理解结构,还能处理AST,它是基于flake8和pylint开发的。设计qlint的初衷是让以后添加自定义的lint规则变得简单。比如,我们遵循的一个规则是:Python所有的“私有”变量之前都应该有一个下划线,所以我们在qlint中增加了一个规则,这个规则可以检测所有没有加下划线的私有变量,并判定为“非法”。
我们在许多系统中都嵌入了qilint来提供无缝的开发环境,这样一来开发人员不必时刻自己关注lint错误。首先,qlint完全兼容我们最常用的几款编辑器,如Vim、Emacs和Sublime,并且一旦某个规则被违犯,编辑器内就会出现可见的反馈(红色标记)。qlint还整合到了我们的push流程中,任何时候一有人push代码,qlint就会启动。事实上,qlint还会根据commit时违反的规则来阻止代码部署。我们的代码开发风格指南也和qlint集成到了一起,对于每一处lint错误,qlint都会建立一个超链接,链到风格指南中相应的规则。我们的代码审核工具Phabricator也被设置成可以使用qlint。通过这样的方法,所有被qlint发现的错误都会用可见的标记标出来,让代码审核变得非常简单。
所有这些改变都在损耗很小的前提下提高了我们代码的一致性和质量。
结论
正如本文所述,Quora非常看重代码质量。我们务实地架构了先进而合理的系统、工具和流程来确保可以促进和维持长久高效开发,同时,我们也努力保持平衡。团队仍在不断成长和进步,所以我们相信,在未来会开发出更多工具和系统。