当第三方代码中没有警告时,为什么不将所有警告视为错误?

Why exactly not treat all warnings as errors when there're no warnings in third party code?

本文关键字:警告 错误 为什么不 代码 第三方      更新时间:2023-10-16

这个问题不是c++特有的,只是用c++的东西作为例子。

一个普遍的观点是"将所有警告视为错误"(如/WXVisual c++选项)是好的,因为警告是一个等待发生的错误(顺便说一句,链接的线程充满了"我的目标为零警告"语句)。

到目前为止,我看到的唯一反对意见是,一些第三方代码在没有警告的情况下无法编译。

好了,在这个问题的持续时间里,让我们假设编译器有办法暂时禁用某些代码中的警告(比如Visual c++中的这个):

#pragma warning(push)
#pragma warning(disable:X)
#include <ThirdParty.h>
#pragma warning(pop)

然后第三方代码就不再是问题了

假设我们完全控制所有的代码(没有第三方或者我们只能禁用第三方代码中的警告),有什么理由不将警告视为错误?

因为有时候你比编译器更清楚。

在现代编译器中不一定经常这样做,但有时您需要做一些稍微超出规范的事情或对类型进行一些棘手的操作,在这种特殊情况下它是安全的,但不正确。这将导致一个警告,因为从技术上讲,它通常在某些时候是错误的,编译器会告诉你什么时候可能是错误的。

这似乎归结为编译器通常知道得最好,但并不总是看到全局或完全知道你的是什么意思。有些时候,警告是而不是错误,不应该被视为错误。

当您进一步偏离标准用法时,例如钩子函数和在内存中重写代码,不准确的警告变得更加常见。编辑导入表或模块结构可能涉及一些指针运算,看起来有点滑稽,因此您会收到警告。

另一种可能的情况是当您使用编译器给出警告的非标准特性时。例如,在MSVC10中:

enum TypedEnum : int32_t
{
... 
};

将给出一个非标准扩展警告。当您向编译器编码时,代码是完全有效的,但仍然会触发警告(我相信在第4级以下)。现在c++ 11中的许多特性,以前是作为编译器特定的特性实现的,将遵循这个(完全安全,完全有效,仍然是一个警告)。

另一种给出警告的安全情况是强制值为bool值,如:

bool FlagSet(FlagType flags) { return (flags & desired); }

给出性能警告。如果您知道您想要这样做,并且它不会导致性能下降,则警告是无用的,但仍然存在。

现在,这是一个粗略的,因为你可以很容易地围绕它进行编码,但这带来了另一个问题:有时可能有两种不同的方法做某事具有相同的结果,速度和可靠性,但其中一种可读性较差,另一种不太正确。您可能会选择更干净的代码而不是正确的代码,并导致警告。

还有其他可能发生潜在问题的情况,警告可以解决这些问题。例如,MSVC C4683的描述字面意思是"当……时要谨慎"。这是一个经典意义上的警告,可能会发生不好的事情。如果你知道你在做什么,它就不适用。

它们中的大多数都有某种替代的代码风格或编译器提示来禁用警告,但那些不需要的可能需要将其关闭。

就我个人而言,我发现打开警告,然后修复它们有助于摆脱大多数小错误(打字错误,off-by-one之类的事情)。然而,有些地方编译器不喜欢必须以一种特定的方式完成的事情,这就是警告错误的地方。

我看到了三个原因:

  • 遗留代码:接受充满警告的代码并缓慢更新以使其符合标准是一项艰巨的任务。任何修改都有引入新bug的风险。有时候,潜在的好处不值得冒险。
  • 无知:通常情况下,这不是一个真正明智的决定,许多人不去摆弄编译器设置
  • 懒惰:又名,在地毯下扫,希望只在爱好项目中采用(我很乐观,我很乐观,我很乐观…)

遗留代码当然是一个问题,但它可以有效地处理:

  • 将遗留代码视为第三方代码(即中国长城)
  • 修改代码,要么一次修改一个文件(对未修改的文件使用"UglyWarningDeactivator.h"),要么一次修改一个警告(通过选择性地启用它们)

当测试和代码一样糟糕或者时间很紧张时,最好使用长城策略。否则,在资源和测试信心允许的情况下,我强烈建议您采用增量重写方法。


在一个新的代码库?没有任何理由。在最坏的情况下,如果您确实需要一些棘手的东西(类型双关语,…),您总是可以选择性地取消有关代码区域的警告。真正了不起的是,它记录了一些可疑的事情正在发生!

我不同意"没有理由"的说法。

我个人认为正确的方法是零警告策略,但不将警告视为错误。我的断言是,这增加了程序员的生产力和责任。

我在团队中使用了两种方法:1)警告作为错误,2)零警告作为策略,但不由编译器强制执行。在这两种情况下,释放都没有警告,警告级别保持在零左右。然而,在后一种情况下,偶尔会有几个州的警告级别在很短的一段时间内上升到少数。

这导致了更高质量的代码和更好的团队。为了了解如何根据记忆想出一个合理的例子。如果你足够聪明和上进心,你就能找出漏洞,但试着扩展你的记忆和想象力,我想你会明白我的观点,不管我的例子中有什么缺陷。

假设你有一些遗留的,c风格的代码,一直使用有符号整型进行索引,并使用负大小写进行某种特殊处理。您希望使代码现代化,以利用std算法,或者boost提供的某些功能。你修复了代码的一个角落,这是一个很好的概念证明,所以你想把它添加到代码审查堆栈中,因为你很确定你想用这种方式做整个事情。

最终带符号的东西会消失,但目前你得到的警告是,你比较有符号和无符号的int。如果您的公司正在执行无警告构建,您可以:

static_casting
  1. 引入一些不必要的临时代码。
  2. 去大爆炸——一次迁移整个东西。

同样的事情可能由于很多原因而发生。所有这些都不如提出一些警告,在团队会议上讨论这些警告,并在接下来的几周内清理它们。强制类型转换和临时代码会在未来几年里持续存在并污染代码,而在一个有动力的团队中,跟踪警告会很快被清除。

在这一点上,有些人会声称"是的,但是人们不会有足够的动力"或"不是我的团队,他们都是垃圾"等等(至少我经常听到这些论点)。我发现这通常是基本归因错误的一个例子。如果你把你的同事当作不负责任、漠不关心的混蛋,他们也会这样做。

虽然有大量的社会科学支持这一观点,但我只能提供我的个人经验,这当然是轶事。我曾在两个团队中工作过,我们一开始有大量的遗留代码库和大量的警告(数以千计)。这的确是一个可怕的情况。除了数以千计的警告之外,还有更多的警告被忽略了,因为这会过多地污染编译器的输出。

小组1:警告作为警告,但不能容忍

在团队1中,我们跟踪了jenkins中的警告次数,在弱团队会议中,我们讨论了正在进行的警告次数。这是一个5人的团队,我们中有两个人非常关心。当我们中的一个人降低警告级别时,另一个人会在会议上表扬他们。当清除警告删除了未发现的错误时,我们宣传了这一事实。几个月后,其他5个程序员中有2个加入进来,我们很快(在一年内)将警告降为零。警告不时出现,有时是十几二十来个。当这种情况发生时,负责的开发人员会过来道歉,并解释他们为什么在那里,以及他们希望什么时候清理这些垃圾。有一个人从来没有真正受到激励,至少他受到了同伴压力的足够激励,没有添加任何警告。

这大大改善了团队氛围。在某些情况下,我们进行了富有成效的讨论,讨论处理特定警告或警告类的最佳方法,这使我们所有人都成为更好的程序员,并改进了代码。我们都养成了关心简洁代码的习惯,这使得其他讨论——比如一个带有10个参数的1000行递归函数是不是好的代码——变得容易得多。

Team 2: Warnings as errors

团队2在开始时与团队1几乎相同。同样的遗留代码充满了警告。同样数量的开发者,有动力的和没有动力的。在团队2中,我们中的一个人(我)想要保留警告作为警告,但专注于减少警告。我的另一位同事声称,所有其他开发人员都是漏洞,如果我们不发出错误警告,他们就永远不会消除这些错误,你不这样做能得到什么好处呢?我向他解释了我在第一队的经历,但他一点也不感兴趣。

我仍然在一队工作。一年过去了,我们的代码没有警告,但它充满了快速清除警告和不必要代码的技巧。当我们解决了许多实际问题时,许多潜在的问题却被掩盖了。

此外,团队凝聚力并没有提高。如果有的话,它被降级了,管理层正考虑因此解散团队。那些从不关心警告的同事们仍然不关心,人们对质量的关注也没有增加。事实上,情况正好相反。每当有人谈到代码质量时,那群人就会翻白眼,认为这是另一种压迫性的、愚蠢的管理痴迷,只会降低生产力,而且没有什么好处。

更糟糕的是,每当我们想引入另一个可以提供有用信息的警告时,这都是一个大项目,因为我们要么必须更改我们的"警告即错误"策略,要么在引入警告之前修复所有现有的警告。因为我们没有通过内在动机进行观察和修复警告的练习,前者很可能会导致真正的问题,所以我们将任务添加到我们的积压中,而它却一直徘徊不去。

另一个让我发现这个问题并写下这个答案的例子是c++ 11的[[deprecated]]特性,我想用它来标记不支持的代码,这样我们就可以逐步淘汰它,作为我们警告清理的一部分。然而,这与all-warnings-as-errors不兼容。

我的建议是:在你的团队中把警告当作错误,但不要告诉你的编译器这样做。可以将一些特别有害的警告视为错误,但要加以区分。

我非常喜欢使用带有一些警告的代码库,而不是由于某些"不可协商"的警告被关闭而允许生成警告的代码激增的代码库(我不会假装这种情况不会发生)。至少在前一种情况下,可能需要一些代码审查的区域可以通过重建清楚地看到。

此外,拥有致命/警告/良性三个类别允许在不破坏构建的情况下向警告类别添加少量有用信息(参见C4061)的很大范围。只有一个致命或不致命的区别,警告列表必须更加明智。

在许多情况下,在一个程序集中的更改,或者在一种语言中的更改,可能会导致过去编译干净的代码开始发出警告。在某些情况下,暂时容忍这样的警告可能比要求在测试任何内容之前执行消除这些警告所需的所有编辑要好。