如果提交供代码检查的代码看起来太复杂了,该怎么办?


115

该代码很难遵循,但至少在表面测试下,它似乎(大部分)运行良好。可能到处都有小错误,但是很难通过阅读代码来判断它们是否是更深层问题或简单修复的症状。但是,即使有可能,通过代码审查手动验证总体正确性也是非常困难且耗时的。

在这种情况下最好的行动方案是什么?坚持要重做吗?部分重做?首先重构?仅修复错误并接受技术债务?对这些选项进行风险评估,然后决定吗?还有吗


4
出现还是?快速评估源文件将确认或否认您的怀疑。测量后,只需在代码审查中调出测量编号,并建议进行重构以降低复杂性编号。
乔恩·雷诺



4
请定义“太复杂”。代码是否过于复杂,是因为它使用了您的团队不熟悉的众所周知的设计模式,还是因为它没有使用您的团队所熟知的模式?判断代码“太复杂”的确切原因对于创建正确的评估方法至关重要。在诸如代码审查之类的深而复杂的知识领域中,简单到“太复杂”的声明对我来说是对开发人员的一次猎杀。
Pieter Geerkens '16

7
@PieterGeerkens还是因为解决一个复杂的问题而太复杂了?
凯西

Answers:


251

如果无法审核,则无法通过审核。

您必须了解,代码审查不是为了查找错误。这就是质量检查的目的。代码审查旨在确保将来可以维护代码。如果您现在甚至无法遵循该代码,那么如何在六个月内被指派进行功能增强和/或错误修复?现在发现错误只是一个附带好处。

如果太复杂,那就违反了许多SOLID原则。重构,重构,重构。将其分解为正确命名的函数,它们的功能要简单得多。您可以清理它,并且您的测试用例将确保它继续正确运行。您有测试用例,对吗?如果不是,则应开始添加它们。


49
这个非常。请记住,不仅您和将要阅读此代码的作者。这也将是10年内的一些随机实习生,因此您要确保他有机会了解正在发生的事情。
大卫·格林伯格

2
好答案。它取决于“代码审查”的目的。可读性是一回事,结构又是另一回事-但它们之间有着密切的联系。之前,我正在与MAJOR军团编写的一些开放源代码一起使用,由于var和fn的名称很容易理解,因此几乎是不可读的。

19
@DavidGrinberg实际上,“六个月内的你”是一个完全不同的人。
chrylis-罢工-16

2
将代码放置一段时间(足够长的时间让他不记得所有内容)。请原始编码器对其进行审查。看看他是否理解。
尼尔森

4
我不同意代码审查“不是”以发现错误。它经常会发现错误,这是代码审查的一个非常强大而有用的方面。更好的是,它有助于找到在将来的代码中完全避免错误的方法。重点可能是高估了,应该不是仅仅为了发现错误!
科迪·格雷

45

您提到的所有内容都非常有效,可以在代码审查中指出。

收到代码审查后,我会审查测试。如果测试没有提供足够的覆盖率,那是需要指出的。测试必须对确保代码按预期工作并将在更改中继续按预期工作有用。实际上,这是我在代码审查中寻找的第一件事。如果您还没有证明自己的代码符合要求,那么我不想花时间在上面。

一旦对代码进行了足够的测试,如果代码复杂或难以遵循,那也是人类应该考虑的事情。静态分析工具可以指出一些复杂性度量,并标记过于复杂的方法,以及发现代码中的潜在缺陷(并应在人工代码审查之前运行)。但是代码是由人类读取和维护的,因此需要首先编写代码以实现可维护性。仅当有理由使用较少可维护的代码时,才应以这种方式编写。如果您需要复杂或不直观的代码,则应记录(最好在代码中)为什么使用这种代码,并提供有用的注释,以供将来的开发人员了解代码的用途和作用。

理想情况下,拒绝没有适当测试或代码过于复杂而没有充分理由的代码审查。前进可能有商业原因,因此您需要评估风险。如果确实要承担代码中的技术债务,请立即将票证放入Bug跟踪系统,其中包含需要更改的内容的详细信息以及更改建议。


30

但是,即使有可能,通过代码审查手动验证总体正确性也是非常困难且耗时的。

那不是代码审查的重点。进行代码审查的方式是想象代码中存在错误,而您必须对其进行修复。以这种思维方式,浏览代码(尤其是注释)并问自己:“是否容易理解正在发生的事情,以便我缩小问题范围?” 如果是这样,那就是通行证。否则,那就失败了。至少需要更多的文档,或者可能需要重构才能使代码合理地理解。

除非您确定这是雇主所追求的,否则不要对此持完美态度,这一点很重要。大多数代码是如此糟糕,以至于可以轻松地将其连续十次进行重构,而每次可读性更高。但是您的雇主可能不想为拥有全球最易读的代码而付费。


4
很棒的评论!“大多数代码是如此糟糕,以至于它可以很容易地连续重构10次,每次都变得更具可读性。”男孩,我是否为此感到内::)
Dean Radcliffe

1
“大多数代码是如此糟糕,以至于可以很容易地将其连续十次进行重构,每次都变得更具可读性。” 确实,这就是现实世界中的情况。
彼得·莫滕森

@PeterMortensen确实,您在现实世界中发现了很多东西。但是以这种方式编写代码并不符合任何人的利益。我认为这样做的原因有两个。开发人员接受的教育很少投入精力来教如何编写可读代码。在某些企业中,这被认为是浪费时间:“如果开发人员已经编写了工作代码,我们为什么要关心它是否可读?只管把东西寄出。”
kasperd '16

15

但是,即使有可能,通过代码审查手动验证总体正确性也是非常困难且耗时的。

很多年前,实际上,我的工作是通过对学生的作业进行评分来做到这一点。虽然许多人提供了一些合理的质量,但到处都有错误,但仍有两个人脱颖而出。两者都始终提交没有错误的代码。我提交了一个可以从顶部和底部高速读取的代码,并用零的努力将其标记为100%正确。另一个提交的代码是另一个WTF,但是以某种方式设法避免了任何错误。值得纪念的绝对痛苦。

今天,第二个代码将在代码审查中被拒绝。如果验证正确性非常困难且耗时,那么这就是代码的问题。一位体面的程序员会弄清楚如何解决问题(花费时间X),并且在进行代码审查之前将其重构,这样它不仅可以完成任务,而且显然可以完成任务。它所花费的时间比X少得多,并在将来节省了大量时间。通常是在漏洞进入代码审查阶段之前就发现它们。接下来,使代码审查更快。并在以后的任何时候都使代码更容易适应。

另一个答案是,某些人的代码可以重构10次,每次可读性更高。真是可悲。那是一个应该寻找不同工作的开发人员。


我将代码重构10次所需的时间要少得多,然后我需要编写代码的第一个版本。如果其他人知道我已经完成了此重构,那么我将失败。
伊恩

6

这个旧代码是否做了些改动?(在10000行代码库中更改了100行代码仍然是一个很小的更改)有时会存在时间限制,开发人员被迫留在一个旧的不方便的框架中,仅仅是因为完整的重写将花费更长的时间并且超出预算。+通常会涉及风险,如果评估不当,可能会损失数百万美元。如果它是旧代码,则在大多数情况下,您将不得不忍受它。如果您自己不了解它,请与他们交谈,然后听听他们的意见,尝试理解。请记住,对您来说可能很难遵循,但对其他人而言却完全可以。站在他们的一边,从他们的尽头看。

这是新密码吗?根据时间限制,您应该提倡尽可能多地进行重构。如有必要,可以花更多时间进行代码审查吗?您不应该将自己的时间限制在15分钟以内,了解一下并继续前进。如果作者花了一周时间写东西,可以花4到8个小时来复习。您的目标是帮助他们重构。您不只是返回说“立即重构”的代码。看看可以分解哪些方法,尝试提出一些想法以引入新的类等。


2
您不只是返回说“立即重构”的代码 -为什么?我至少收到过一次这样的评论,而上次我记得它证明是有用且正确的。我不得不从头开始重写大量代码,这是正确的做法,因为回首过去,我自己看到旧代码是无法挽救的烂摊子。审阅者完全有资格注意到(我显然不是)
咬住了

4
@gnat:一方面,因为它很粗鲁。当您解释代码出了什么问题并努力帮助他人进行改进时,您看起来会更好。在大型公司中,否则可能会迅速将您带出门。特别是如果您查看高级人员的密码。
Neolisk '16

我在上面提到的那个案例,是在一家大型老牌公司中,刚好足够小心不要离开他们最有资格的开发人员,至少不是以被要求时直接分享他们的技术专长为理由
gna

1
@gnat:“现在重构”的方法可能会向下起作用,即,具有10年以上经验的高级开发人员说要重构为一个月前没有经验或类似情况的新开发人员。向上-您可能有问题。因为您可能不知道其他开发人员有多少经验,所以可以肯定地将尊重作为默认行为。它肯定不会伤害您。
Neolisk '16

1
@Neolisk:一个经验丰富的开发人员不得不在时间压力下编写代码,并且知道它不够好,可能只有在您拒绝代码时才会感到高兴,这给了他时间和借口来改进它。PHB认为足够好会使开发人员感到不满意;评论者认为不够好会使他感到高兴。
gnasher729

2

通常,“复杂的”补丁/变更列表是同时执行许多不同功能的补丁/变更列表。其中包括新代码,删除的代码,重构的代码,移动的代码,扩展的测试;这使得很难看清全局。

一个常见的线索是该补丁很大,但描述却很小:“实现$ FOO”。

处理此类补丁的合理方法是要求将其分解为一系列较小的独立部件。就像单一职责原则说的那样,一个功能应该只做一件事,一个补丁也应该只关注一件事。

例如,第一个补丁可能包含纯机械重构,不进行任何功能更改,然后最后一个补丁可以专注于$ FOO的实际实现和测试,而不会产生干扰和干扰。

对于需要大量新代码的功能,通常可以在可测试的块中引入新代码,这些可测试块不会改变产品的性能,直到该系列中的最后一个补丁实际调用新代码(标志翻转)。

至于巧妙地执行此操作,我通常将其说成是我的问题,然后寻求作者的帮助:“我在跟踪这里发生的所有事情时遇到了麻烦。您能否将此补丁分解为更小的步骤,以帮助我理解所有这些内容一起?” 有时有必要针对较小的步骤提出具体建议。

如此大的补丁(例如“ Implement $ FOO”)变成了一系列补丁,例如:

  1. 介绍一个需要一对迭代器的Frobnicate新版本,因为我将需要使用vector以外的序列来调用它来实现$ FOO。
  2. 将所有现有的Frobnicate呼叫者切换为使用新版本。
  3. 删除旧的Frobnicate。
  4. Frobnicate做得太多。将拒绝步骤分解为自己的方法,并为此添加测试。
  5. 通过测试介绍Zerzify。尚未使用,但我需要$ FOO。
  6. 根据Zerzify和新的Frobnicate实施$ FOO。

请注意,步骤1-5不会对产品进行任何功能更改。他们很容易审查,包括确保您拥有所有正确的测试。即使第6步仍然很“复杂”,至少它也将重点放在$ FOO上。日志自然可以使您更好地了解如何实现$ FOO(以及更改Frobnicate的原因)。


如果使用Git,一种方法是编写多个提交的请求请求。每个提交都尽可能地原子和独立,并具有自己的描述。然后,在PR正文中添加一个有用的注释,即可以手动检查每个更改。通常,这就是我处理大型PR的方式,例如全局重构或无懈可击的大型工具变更。
吉米·布雷克-麦基

1

正如其他人指出的那样,代码审查并不是真正旨在发现错误的设计。如果在代码检查期间发现错误,则可能意味着您没有足够的自动化测试范围(例如,单元/集成测试)。如果没有足够的覆盖范围使我相信代码可以完成预期的工作,那么我通常会要求进行更多的测试,并指出所需的测试用例,并且通常不允许代码进入没有足够覆盖范围的代码库。

如果高层体系结构太复杂或没有意义,我通常会召集几个团队成员开会讨论这个问题。有时很难在糟糕的架构上进行迭代。如果开发人员是新手,我通常会确保我们提前了解他们的想法,而不是对糟糕的请求提出反应。如果问题没有明显的解决方案(很可能会被选中),即使对于经验丰富的开发人员也通常如此。

如果将复杂性隔离到方法级别,则通常可以迭代并使用良好的自动化测试进行纠正。

最后一点。作为审阅者,您需要确定代码的复杂性是由于本质的还是偶然的 复杂性。本质上的复杂性与软件中确实难以解决的部分有关。偶然的复杂性指的是我们编写的代码的所有其他部分,它们无缘无故地变得过于复杂,可以轻松地进行简化。

我通常会确保具有本质复杂性的代码确实是这样,并且不能进一步简化。我还打算为这些零件提供更多的测试范围和良好的文档。在请求请求过程中几乎总是应该清除意外的复杂性,因为这些是我们处理的大部分代码,即使在短期内也很容易引起维护的噩梦。


0

测试是什么样的?它们应该清晰,简单并且易于理解,理想情况下只有一个断言。测试应该清楚地记录代码的预期行为和用例

如果未经过良好测试,则是开始进行审查的好地方。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.