彻底解决问题:如何让代码缺陷永不重现

本文深入探讨了软件问题解决的根本哲学:不仅要消除表面症状,更要追踪并解决根本原因,通过自动化测试和持续集成确保问题永不复发,从而构建可持续的健康代码库。

让问题永不重现 » 代码简洁性

管理同意
为了提供最佳体验,我们使用如cookies等技术来存储和/或访问设备信息。同意这些技术将允许我们处理数据,例如在此网站上的浏览行为或唯一ID。不同意或撤回同意,可能会对某些特性和功能产生不利影响。

功能
功能 始终活跃
技术存储或访问对于使订阅者或用户明确请求的特定服务能够使用的合法目的是严格必要的,或者仅为了通过电子通信网络进行通信传输的唯一目的。

偏好
偏好
技术存储或访问对于存储订阅者或用户未请求的偏好的合法目的是必要的。

统计
统计
技术存储或访问仅用于统计目的。技术存储或访问仅用于匿名统计目的。没有传票、互联网服务提供商的自愿合规或来自第三方的额外记录,仅为此目的存储或检索的信息通常不能用于识别您的身份。

营销
营销
技术存储或访问是创建用户配置文件以发送广告,或在网站或跨多个网站上跟踪用户以用于类似营销目的所必需的。

管理选项 管理服务 管理{vendor_count}供应商 阅读更多关于这些目的 接受 拒绝 查看偏好 保存偏好 查看偏好 Cookie政策 {title} 法律声明

代码简洁性

让问题永不重现

2013年11月19日 by Max Kanat-Alexander

在代码库中解决问题时,当症状停止时你并没有完成。当问题消失且永远不会再出现时,你才算完成。

当问题不再有任何可见症状时,很容易停止解决它。你已经修复了bug,没有人抱怨,而且似乎还有其他紧迫的问题。那么为什么要继续处理它呢?现在没问题了,对吧?

不对。记住,在软件中我们最关心的是未来。软件公司陷入代码库无法管理的情况,是因为他们没有真正处理问题直到问题被彻底解决。

这也解释了为什么一些组织无法将混乱的代码库恢复到良好状态。他们看到代码中的一个问题,处理它直到没有人再抱怨,然后转向处理他们看到的下一个症状。他们没有建立一个框架来确保问题永远不会再出现。他们没有追踪问题的根源并使其消失。因此,他们的代码库从未真正变得"健康"。

这种未能完全处理问题的模式非常常见。结果,许多开发者认为大型软件项目不可能保持良好设计——他们说:“所有软件最终都必须被扔掉并重写。”

这不是真的。我职业生涯的大部分时间要么是从头设计可持续的代码库,要么是将糟糕的代码库重构为好的。无论代码库有多糟糕,你都可以解决其问题。然而,你必须理解软件设计,你需要足够的人力,并且你必须处理问题直到它们永远不会再出现。

一般来说,一个问题需要解决到什么程度的一个好准则是:一个问题被解决到没有人需要再关注它的程度。

在绝对意义上实现这是不可能的——你无法预测整个未来,等等——但这更多是哲学上的反对而不是实际上的。在大多数实际情况下,你可以有效地解决问题到没有人需要现在关注它,并且没有立即明显的原因他们将来需要关注它。

示例

假设你有一个网页,你为网站写了一个"点击计数器"来跟踪有多少人访问了它。你发现点击计数器有一个bug——它计算的访问次数是应该计算的1.5倍。你有几个选项来解决这个问题:

你可以忽略问题。
这里的理由是,你的网站不是很受欢迎,所以点击计数器说谎没关系。而且,它让你的网站看起来比实际更成功,这可能对你有帮助。
这是一个糟糕的解决方案的原因是,在许多未来场景中,这可能再次成为问题——特别是如果你的网站变得非常成功。例如,一家主要新闻出版物发布了你的点击数字——但它们是假的。这引起丑闻,你的用户失去对你的信任(毕竟,你知道问题但没有解决它),你的网站再次变得不受欢迎。人们可以轻易想象这个问题以其他方式回来困扰你。

你可以快速hack一个解决方案。
当你显示点击次数时,只需将它们除以1.5,数字就准确了。然而,你没有调查根本原因,结果发现它在早上8:00到11:00之间计算了3倍的点击次数。后来你的流量模式改变,你的计数器又完全错误了。你可能甚至一段时间不会注意到,因为hack会使调试更加困难。

调查并解决根本原因。
你发现它在8:00到11:00之间计算了3倍的点击次数。你发现这是因为你的网络服务器在那段时间从磁盘删除许多旧文件,这由于某种原因干扰了点击计数器。
此时你有另一个机会hack一个解决方案——你可以简单地禁用删除过程或使其运行频率降低。但这并不是真正追踪根本原因。你想知道的是,“为什么仅仅因为机器上发生其他事情就会错误计数?”
进一步调查,你发现如果你中断程序然后重新启动它,它会再次计算最后一次访问。删除过程在机器上使用了如此多的资源,以至于在8:00到11:00之间的每次访问中它中断了计数器两次。所以在那段时间它计算了每次访问三次。但实际上,根据机器上的负载,这个bug可能添加了无限(或至少不可预测)的计数。
你重新设计计数器,使其即使被中断也能可靠计数,问题消失了。

显然,从列表中正确的选择是调查根本原因并解决它。这使问题消失,大多数开发者会认为他们完成了。然而,如果你真的想确保问题永远不会再需要人类关注,还有更多要做。

首先,有人可能会来改变点击计数器的代码,将来将其恢复到损坏状态。显然,正确的解决方案是添加一个自动化测试,确保点击计数器即使被中断也能正确运行。然后你确保该测试持续运行,并在失败时提醒开发者。现在你完成了,对吧?

不。即使在这时,还有一些未来风险必须处理。

下一个问题是,你写的测试必须易于维护。如果测试难以维护——当开发者更改代码时它变化很大,测试代码本身是神秘的,如果代码更改它很容易返回假阳性,等等——那么测试很可能会中断或有人将来会禁用它。然后问题可能再次需要人类关注。所以你必须确保你写了一个可维护的测试,如果不可维护则重构测试。这可能引导你走上另一条调查测试框架或被测系统的路径,以找出使测试代码更简单的重构。

之后,你有关注如持续集成系统(测试运行器)——它可靠吗?它可能以某种方式失败,使你的测试需要人类关注吗?这可能是另一条调查路径。

所有这些调查路径可能发现其他问题,然后必须追踪到它们的来源,这可能发现更多问题要追踪,等等。你可能会发现,你可以发现(并可能解决)所有代码库的主要问题,只需从一些症状开始,并非常坚定地追踪根本原因。

有人真的这样做吗?是的。起初可能看起来很困难,但当你解决越来越多这些根本问题时,事情真的开始变得更容易,你可以越来越快地移动,问题越来越少。

深入兔子洞

除此之外,如果你真的想冒险,还有一个问题你可以问:为什么开发者首先写了有bug的代码?为什么bug可能存在?是开发者教育的问题吗?是他们的过程有问题吗?他们应该边写代码边写测试吗?系统中是否有某些设计问题使其难以修改?编程语言太复杂吗?他们使用的库写得不好吗?操作系统行为不好吗?文档不清楚吗?

一旦你得到答案,你可以问那个问题的根本原因是什么,并继续问那个问题直到你满意。但要小心:这可能带你深入兔子洞,进入一个改变你对软件开发整体看法的地方。事实上,理论上这个系统是无限的,最终会解决整个软件行业的根本问题。你想走多远取决于你。

分享 点击在Facebook上分享(在新窗口中打开) Facebook
点击在LinkedIn上分享(在新窗口中打开) LinkedIn
点击在Hacker News上分享(在新窗口中打开) Hacker News
点击在Reddit上分享(在新窗口中打开) Reddit
点击在Threads上分享(在新窗口中打开) Threads
点击在X上分享(在新窗口中打开) X

9条评论 留下回复

Jeff Dickey 说:2013年11月20日上午9:26
做了几年TDD后,我发现它迫使我一直这样思考(回归是遥远的记忆)
好文章!喜欢点击计数器的例子!
回复

Max Kanat-Alexander 说:2013年12月8日晚上9:57
谢谢,Jeff!TDD为你做到了这一点真是太棒了。我认为TDD并不总是固有解决的一个方面是 creeping complexity,尽管——理论上仍然可能创建一个相当复杂的野兽,恰好经过良好测试。(尽管测试[如果本身写得好]倾向于帮助重构,这使得问题不那么难以修复。)
-Max
回复

Bug修复真的重要,不是吗? | 一千种编程语言 说:2015年4月8日上午11:56
[…] 不仅如此… 它还说,当你解决问题时,你应该让它永不回来! […]
回复

调试的基本哲学 - 最佳应用开发平台、软件、应用开发者 说:2017年8月16日凌晨4:18
[…] 但人们做不那么愚蠢的版本,不修复bug。他们没有深入到问题的基本原因,而是用一些变通方法掩盖bug,这些方法永远存在于代码库中[…]
回复

Mike W. 说:2017年9月1日晚上8:37
很好的例子;非常好地说明了观点。应该注意的是,例子并没有完全追踪到底。为什么点击计数器在中断和重新启动时再次计算最后一次访问?对于每个shell脚本bug,“深入兔子洞"部分描述了问题。非常非常少写shell脚本的人真正理解shell。他们从其他作者那里复制shell脚本片段,这些片段基于其他片段,这些片段是由也不理解shell的人写的。https://unix.stackexchange.com/q/131766/135943 是一个很好的起点,至少澄清最常见的误解,但重点是"bug"实际上是广泛缺乏正确的教育和正确的shell脚本示例。
完全追踪到底看到bug消失真是太棒了。
回复

Max Kanat-Alexander 说:2017年9月2日晚上7:47
它完全追踪到底。重新设计它甚至消除了问题。也就是说,你不必问问题,因为被质疑的对象不再存在。例子可能缺少一些细节来完全回答你的问题(并显示它实际上已经回答),这在 http://www.codesimplicity.com/post/the-fundamental-philosophy-of-debugging/ 中更覆盖。
从我的角度来看,shell脚本的实际兔子洞是,语言的设计方式对普通用户来说是难以理解的、不直观的和令人惊讶的。它与大多数其他编程语言不一致。
但从实际角度来看,既然我们不是来修复bash的,确实你可以追踪到程序员理解上的失败。事实上,我认为所有编程失败都追溯到理解、责任或意识上的问题。这可能是未来博客的主题。
-Max
回复

Mike W. 说:2018年11月28日下午4:31
我最近遇到 https://www.fastcompany.com/28121/they-write-right-stuff,这是你"深入兔子洞"部分的一个很好的例子。它描述了洛克希德-马丁团队如何处理火箭飞行软件的代码准确性。当他们发现一个bug时,他们不只是修复它;他们检查过程如何允许bug首先到达那里,寻找所有可能由过程中相同缺陷允许的潜在bug,修复过程,修复所有bug,等等。
回复

让问题永不重现 – coding | factory 说:2018年12月22日晚上6:49
[…] 更多 […]
回复

调试的基本哲学 - Komunitas 101 News 说:2019年2月22日凌晨1:49
[…] 但人们做不那么愚蠢的版本,不修复bug。他们没有深入到问题的基本原因,而是用一些变通方法掩盖bug,这些方法永远存在于代码库中[…]
回复
留下回复取消回复

联系 关于 书:理解软件 书:代码简洁性
输入你的邮箱…
订阅

© 2025 版权所有。由The Fox提供支持。
管理同意联系 关于 书:理解软件 书:代码简洁性
转到顶部

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计