代码精炼审查
2021年1月28日
当人们想到代码审查时,他们通常会想到开发团队工作流程中的一个明确步骤。如今,在集成前审查中,对拉取请求进行的审查是代码审查最常见的机制,以至于许多人无意识地认为,不使用拉取请求就消除了进行代码审查的所有机会。这种狭隘的代码审查观点不仅忽略了大量明确的审查机制,更重要的是忽略了可能最强大的代码审查技术——由整个团队进行的持续精炼。
软件中最普遍的观点之一是,软件是我们构建和完成的东西——因此出现了建筑和架构的无休止的隐喻。然而,软件的关键属性是它很“软”,在发布后可以像最初在程序员的编辑器中编写时一样轻松地修改。这就是为什么 Erik Dörnenburg 明智地认为架构是一个糟糕的隐喻,最好用城市规划来代替。有价值的软件通常处于不断变化的状态,因为我们从对它所能带来的价值的更深入理解中添加功能。但机会不仅在于添加新功能,还在于改进软件——将团队不断了解到的关于如何最好地利用该软件来实现这些变化的经验教训融入其中。
在合适的环境下,我可以查看六个月前编写的一段代码,发现一些关于其编写方式的问题,并快速修复它们。这可能是因为这段代码在编写时存在缺陷,或者代码库中的更改导致这段代码不再完全正确。无论原因是什么,重要的是尽快解决问题,因为它们开始阻碍我们。一旦我对代码有了理解,而这些理解不是从阅读代码中立即显而易见的,我就有责任(正如 Ward Cunningham 妙语连珠所说)将这种理解从我的脑海中提取出来,并将其放入代码中。这样,下一个读者就不必那么费力了。
这种精炼过程与代码审查中发生的事情完全相同,但它是在每次查看代码时触发,而不是在代码添加到代码库时触发。对我来说,这是一个至关重要的见解。毕竟,代码审查试图解决的许多问题,都是只有在将来阅读代码时才会成为问题的问题。有充分的理由在那一刻之前不要担心它们。毕竟,就像添加大型公寓楼会改变交通模式一样,我们可能在六个月后改变了代码的上下文,从而改变了代码所需的修复类型。它还涉及更多的人,在这个方案中,每个阅读代码的开发人员都是一个审查者,并且能够根据他们实际使用代码而不是一些通用但往往是模糊的理由来进行审查。
思考一种实践有效性的方法是思考如果它成为垄断会发生什么。如果我们唯一的代码审查机制是来自后来的程序员的迭代呢?一个后果是,审查注意力集中在更常被阅读的代码区域——这主要是应该得到关注的区域。一个担忧是,从未被阅读的代码将永远不会被审查——但大多数情况下这没关系。一个拥有良好测试实践的团队可以确信代码有效,性能测试可以识别性能问题。鉴于此,如果代码不再需要被查看,我们就不需要花费精力使其易于理解。我预计这种情况会非常罕见,但这是一个有启发性的思想实验。
但大多数≠全部。这里一个明显的例外是安全问题。代码可以正常运行多年,直到攻击者找到漏洞,那时我们会为它缺乏审查而感到惋惜。这是一个高影响但罕见的安全性问题,值得特别审查。然而,这并不意味着我们不应该有意识地使用精炼作为代码审查机制。相反,这意味着我们应该意识到罕见的高影响问题,并调整我们的工作流程,以在我们的环境中根据需要关注这种特定问题。威胁分析应该提醒我们哪些模块需要额外关注以及它们面临的风险类型。可以针对安全问题安排有针对性的代码审查,这些审查可以更有效地运行,因为它们专注于特定类型的问题。
为了进行这种持续的代码精炼,我们需要其他实践。如果我要更改代码,我需要确信它不会破坏现有功能,因此我需要类似于自测试代码的东西。我需要知道它不会给其他人带来很大的合并冲突,因此我需要持续集成。我们都需要擅长重构,这样我们才能有效地更改代码。由于这依赖于许多开发人员被期望修改代码库的任何部分,因此我们最好采用集体(或至少是弱)代码所有权。但是,对于拥有这些技能的团队来说,他们可以依靠使用他们常规的精炼作为其代码审查策略的重要组成部分。
如果别无他法,我认为重要的是我们更多地思考精炼作为代码审查的作用。仅仅关注集成前审查的危险之一是,它会导致团队忽视代码库中的更改方式。如果我有一个原始的主线,并确保合并到该主线的每个提交都是原始的——我能确定代码库在六个月后仍然是原始的吗?我认为我不能,因为更改意味着六个月前关于某些代码的良好决定现在不再是良好的决定了。精炼代码使我们能够根据这种不断变化的使用情况来评估旧代码,从而保持其健康。