我犯过的最严重的错误

2025-06-07

我犯过的最严重的错误

对于新开发者来说,制造生产环境的 bug 是一种必经之路。虽然我发誓我做的好事比坏事多,但我制造的 bug 数量也多得我都不愿承认。让我来告诉你我曾经制造过的最严重的 bug——以及你可以做些什么来避免重蹈我的覆辙。

正如您所料,它处理线程。

异步代码的危险

让我们回顾一下,当时我在一家 ASP .NET Web 开发公司担任Windows Presentation Foundation (WPF)专家。我负责一个项目,将一个 WPF 应用程序转换为Silverlight,以便更多用户可以在浏览器和非 Windows 操作系统上使用它。

WPF 允许同步 Web 服务调用,而 Silverlight 则要求异步 Web 调用。平心而论,我一开始就应该使用异步调用,但技术债确实很难摆脱

事情是这样的——我之前没写过太多异步代码。我做过一些零碎的代码,但把整个 Web 服务层转换成异步通信对我来说还是新鲜事。

没什么大不了的,对吧?作为开发者,我们经常会做一些以前从未做过的事情。而且,我们当时还没有如今所有优秀的异步代码语言特性。

问题是,当您从同步切换到异步通信时,状态管理的行为会有所不同。

现在,您不再只有一个线程与数据模型通信,而是可能同时有多个线程与同一个对象通信。这意味着,要么所有对象都需要一次由一个线程访问,要么这些对象需要在设计时考虑并发性

就我而言,应用程序会向 Web 服务发出一个或两个请求,现在它们可以按随机顺序或同时返回,每个响应都由一个新线程处理。

因为我对异步 Web 服务代码还很陌生,所以我不知道.NET 中多线程的最佳实践,并且在桌面开发方面也没有比我更好的人,所以我以我所知道的最好的方式做了所有事情。

在本地测试一切正常。同样,我们在测试和预发布阶段也没有发现任何问题。于是,我们高调地将代码投入生产。

可能出现什么问题?

一切都着火了!

这

当产品投入生产时,错误开始大量涌现。客户很生气,同事很恼火,而我非常困惑为什么代码突然不起作用了。

什么改变了?

我给你一个提示:我们的测试环境位于现场。

与远程服务器通信的额外延迟会导致足够的延迟,从而使异步代码在生产中的行为有所不同。

由于这种行为的改变,由于一个调用完成,项目被从字典中删除,然后其他线程试图找到它们,导致NullReferenceExceptions灾难性的后果。

一对序列图,其中同步代码进行更新调用,获取响应,然后进行删除调用并获取响应。在异步序列图中,更新和删除方法是异步调用的,但删除方法在更新之前完成,导致行为不可预测。
比较异步方法调用的不可预测性的序列图

如上图所示,通过一个略显牵强的例子,如果您不打算以潜在的随机顺序返回调用,代码可能会以您意想不到的方式运行,包括遇到错误。

其他工程师也参与进来,我们仔细研究了问题。然后,我们确定了问题集中出现的层级。一旦我们了解了导致问题出现的原因,就进行了必要的更改。服务恢复了,而那些事件最终被我们的客户,以及我的同事们遗忘了。

我们没有在那次调查中使用软件质量的 7 个基本工具。但是,我们检查所有相关缺陷的分析方法与该流程类似。

经验教训

所以,可以肯定地说,我们从这一事件中吸取了许多教训:

如何正确使用线程

显然,我的失败让我学到了很多关于线程的知识。

使用线程时,需要更加谨慎地设计代码。尤其需要考虑以下几点:

  • 哪些线程将与哪些对象一起工作
  • 对象应如何处理并发行为
  • 如何测试多线程代码
  • 创建线程所需的开销

线程安全是一个很大的话题,无法在短短的章节中全部涵盖。不过,通常情况下,你应该采用以下策略之一:

  • 使用lock对象同步线程并限制对象一次只能由一个线程使用
  • 明确设计关键对象为线程安全或内置于并发集合中
  • 将工作交给专用线程,该线程是唯一处理相关对象的线程

Beta/预览测试人员很重要

我们无法在每个客户所在地测试我们的软件,甚至无法使用他们的数据。话虽如此,还是有一些方法可以降低发布新代码的风险。

我强烈建议在主要版本发布之前考虑基于生产数据构建一个预览环境。这​​有助于在正式部署功能之前识别不同地区或数据集特有的问题。当然,您仍然需要说服用户尝试这个环境。

测试环境应该看起来像生产环境

如果我们在测试时使用基于云的服务器,我们就会在测试中发现这些问题。

简而言之,让你的开发和测试环境看起来像生产环境。在正式发布到生产环境之前,你会发现更多问题,你的用户也会感激你的付出。

让其他开发人员了解情况

你需要找一个可以一起讨论代码的人。这会带来更好的长期成果。即使对方从未给你任何反馈,这也很宝贵。引导其他开发人员解决问题可以开辟新的代码思维方式。

这样做的副作用是让其他开发人员熟悉你的工作。这使得其他人更容易在截止日期前提供帮助或调查问题。

让其他人参与进来也能让你更轻松地安心度假。如果你生病或需要离开,你的组织需要正常运转,所以现在就让他们参与进来吧。

恐惧确认偏差

就我而言,我感觉自己找到了解决应用程序问题的方法,大家遇到的 bug 很少见,只是数据异常,而不是更严重的系统性问题。因为我不愿相信我的代码有问题,所以我花了比预期更长的时间才去调查早期的问题报告。

测试自己的代码时尤其要警惕确认偏差。就我而言,如果没有异地服务器,我不确定如何才能重现这些问题,但我内心深处希望并期望我的代码能够正常工作,所以我可能会对测试中偶尔遇到的任何异常情况不以为然。

谨慎是有用的

就我而言,我并没有完全了解自己所不知道的东西。我知道我在开发过程中遇到了足够多的问题,我应该提高警惕,并寻求一些关于线程的技术培训。

沟通很重要

在这次发布之前,我一直以极快的速度工作,努力满足组织的需求,不断添加新功能,最终将其发布到市场。虽然我们明白我的节奏难以为继,但我认为组织也理解这种节奏带来的质量风险。

他们没有。

您可以考虑将此归咎于组织,但沟通质量和技术债务风险的首要责任在于开发团队,在这种情况下,我认为其他人知道或正在思考我的想法,但这是错误的。

你的失败会一直存在

尽管我的代码不一定很糟糕,但我遇到的知识差距加上缺乏适当的测试策略导致了大量错误的涌入。

除此之外,这些错误主要表现为NullReferenceException与初级开发人员相关的耻辱。

我花了很多年的时间才摆脱因这一系列错误而被视为不合格程序员的印象,并且我的职业生涯也因此受到影响。

你的成功也会持续下去

总而言之,我们发布了一款满足用户需求并填补组织关键战略空白的产品,帮助我们继续扩张和发展。

此外,尽管应用程序的成功不再完全属于我自己,但至今我仍为此感到自豪,而我的不完美让我学到了很多关于软件开发、架构、测试和沟通的知识。


你的第一个产品 bug 是什么?最糟糕的一次是什么?请不要透露公司的具体信息,但我很想听听你是如何从错误中吸取教训

我曾经造成的最严重的 Bug一文首先出现在Kill All Defects上。

文章来源:https://dev.to/integerman/the-worst-bug-i-ever-caused-382d
PREV
使用 C# 中的扩展方法构建流畅的代码
NEXT
T型开发人员的神话