代码简洁之道:让问题永不重现的深度调试技术

本文深入探讨软件调试的核心哲学,通过点击计数器案例展示如何彻底解决问题根源,涉及自动化测试、持续集成和技术债务管理等关键技术实践,帮助开发者构建可持续维护的高质量代码库。

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

管理同意

为了提供最佳体验,我们使用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,这是你"深入兔子洞"部分的一个很好的例子。它描述了Lockheed-Martin团队如何处理火箭飞行软件的代码准确性。当他们发现一个bug时,他们不只是修复它;他们检查过程如何允许bug首先到达那里,寻找所有可能由过程中相同缺陷允许进入的潜在bug,修复过程,修复所有bug,等等。 回复

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

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

联系 关于 书:理解软件 书:代码简洁性

输入你的邮箱… 订阅

Max Kanat-Alexander 6月27日 我目前认为,AI代理可以成功生成的输出数量和质量由以下因素决定:

  1. 模型的质量。
  2. 代理的质量。
  3. 输入的质量(如提示或其他上下文)。
  4. 代理可以独立运行的确定性、客观验证的质量。 我目前认为,除非你是模型开发者,最重要的部分是"确定性、客观验证”。 简单来说,如果你告诉AI做某事,它需要某种方式能够验证它做了正确的事,靠自己。在软件中,这通常意味着运行测试、linter等,如果系统做了错误的事就会失败。 这意味着你的测试和验证工具越好,你从模型得到的输出就越好。不仅仅是测试数量。它们必须是好的测试,具有智能断言和良好的错误消息。 这也意味着代理成功涉及思考如何将任务分解成可以分别由自动化测试、linter等客观验证的单个部分。 作为说明,输入的质量也很重要并在我们控制之下。如果我们写更好的文档和更清晰的代码,代理做得更好。令人惊讶的是,几乎一切帮助人类写代码的东西也帮助代理。 阅读更多 2010分享

Max Kanat-Alexander 6月4日 技术债务有价值的想法大多是一个神话。当你做出糟糕的软件工程决策时,它会在几小时、几天或几周内减慢你的速度。那是上限。许多人认为需要几个月或几年才能看到在软件设计上偷工减料的影响,但这根本不是真的。它几乎立即开始。做正确事的成本通常是几小时,也许一天,你几乎总是立即收回那个时间。也就是说,做对通常花费与做错相同的时间。“等等,什么?怎么可能?我们偷工减料是为了节省时间!“结果几乎总是它根本不会节省你时间!它使处理变更更加混乱。它使代码审查更长。它在测试期间以更混乱的方式失败,需要更长时间调试。它几乎从不节省你实际时间。 偶尔,你路径上有一些"岩石"如此巨大,你根本无法移动它。没有人应该花三个月设计一个新工具,只是为了你能交付一个一天的功能,一次。但那是技术债务决策的极小极小部分。 这变得更疯狂,因为如果你随着进行做对一切(你总是重构系统,使其看起来像是设计来做 exactly 它现在做的),那么一切始终保持相对容易。但如果你偷工减料,一切都变得复合性更困难,到点你生成"岩石"没有人能移动。不像复杂性是某种线性事物,你可以后来投资线性量时间修复。你绝对可以使自己陷入如此糟糕的情况,你公司中没有人有时间或专业知识修复它。 这一切感觉如此无辜:“让我们偷工减料一点,它将帮助我们满足这个截止日期,"(一个截止日期通常是虚构的,无论如何,你可以滑几周没有后果)。“很难弄清楚如何做对,我们可以推迟它吗?“然后砰,很快你发现自己在一个沼泽中,一切都困难,“我们不知道为什么!” 我留给你最后一件事:当我直接编码我生活的每一天时,我对此完全 uncompromising。我基本上无法看坏代码——我必须重构它或我不能做工作。而且, somehow,我从未错过那部分整个职业生涯中的单个截止日期。 阅读更多 9342分享

Max Kanat-Alexander 5月30日 我估计,我每看到或被告知一万条坏建议,就收到一条好建议。互联网充满坏建议,因为文明充满坏建议。文明充满坏建议,因为全球教育大多不教人们思考、研究或确定什么是真实的。相反,它教人们记忆和重复事实,其中大多数不重要、不真实或无用。 所以一般来说,我们不应该惊讶互联网(特别是社交媒体)充满不重要、不真实或无用的信息。并非全部那样——我在网上学到了大量非常有用的东西。有大量有价值的内容。我会说,人们对事物的意见,无论是在报纸、博客文章、论坛还是什么,很少具有那种价值。 那么要点是什么?嗯,最好有某种方式在开始依赖或向他人重复之前确定某事是否重要或有用的。最简单的工具是说:“它有效吗?“这条数据可靠地帮助你完成某事吗?它100%时间有效,还是更少? 对于许多帖子,你会发现:这条数据根本不帮助你做任何事。 阅读更多 219分享

Max Kanat-Alexander 5月29日 你对系统做的任何更改在推出时会收到来自某人的负面反馈。这发生不是因为每个更改有什么问题,而是因为用户习惯系统的工作方式,他们不喜欢它改变了。他们可能批评新用户界面,或有某种情感论证关于新东西为什么"糟透了”,但通常他们真的只是 upset 任何改变发生了。这不是不寻常的;它是几乎所有人类中存在的一个因素。人们倾向于有某种 appetite 对于他们愿意在一段时间内经历多少改变,如果你超出那个,他们变得 upset。 重要的是认识到人们的反馈何时仅仅是这种"改变厌恶” vs 关于有价值事物的真实反馈。有几种方式判断:

  1. 改变厌恶通常持续3到10天。如果你在推出更改后前3到10天内从用户获得反馈,并且相同反馈不是由大量用户提供,值得考虑用户的响应是否只是改变厌恶。
  2. 改变厌恶反馈通常是情感的。它可能是侮辱性的。它可能表达为仅仅一个意见(“新菜单的颜色丑陋”) vs 一个事实(“我测量了,新工具比旧工具慢10倍。")。 你在管理改变时试图避免的是创造如此多改变厌恶以至于你引起反抗。反抗基本上看起来像如此多人变得愤怒以至于他们去找你的管理层,他们停止你的工作。当有疑问时,向更少人以更慢的扩展速率推出更小的更改。随着时间的推移,你将学习你可以推出多少改变,多快。永远,永远不要做"大爆炸"发布,你所有用户一次经历大规模改变。 现在,所有这些 said,永远不要归因所有反馈给"改变厌恶”。通常人们确实有合法的反馈。如果许多人提供你相同的事实反馈,你看着它,他们的反馈有道理,你认为修复它会改进产品,那很可能不是改变厌恶。另外,说"这只是改变厌恶"并不意味着你应该完全忽略反馈。你至少应该承认它,让人们知道他们被听到了。它确实意味着你不应该与那个人争论或试图与他们推理,因为他们有情感反应,你试图"逻辑"他们 out of it 不会帮助任何人。如果你认为是改变厌恶,你承认他们,他们只是继续与你斗争,有时你应该只是忽略他们并继续做你的工作。如果他们3天后带着更合理的论证回来,那么你知道不仅仅是改变厌恶,而且他们可能到那时更有帮助。 阅读更多 234分享 加载更多

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

转到顶部

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