内疚驱动开发
摘要
Markus Eliasson 讲述了他在度假期间经历的一次误报错误,并反思了软件开发者在问题发生时产生的内疚感,探讨了软件质量不佳所带来的更广泛影响。
<p><a href="https://lobste.rs/s/klffhr/guilt_driven_development">评论</a></p>
查看缓存全文
缓存时间: 2026/08/14 19:34
# 内疚驱动开发 - Markus Eliasson
来源:https://markuseliasson.se/article/guilt-driven-development
几周前的仲夏节前夕,我开始了暑假。合并了最后一个拉取请求后,我关闭了电脑,期待着几周的轻松时光。
仲夏节早晨,我检查工作邮件时发现,早些时候有人报告了我维护的一项服务出现了故障。
真糟糕!故障与一个 HTTP 端点有关,而我关机前最后几项修改中就包括将部分端点迁移到新技术栈。
我极其厌恶这种感觉。某个无辜的人——不幸未能在仲夏节休假——无法完成工作。至少我当时是这么想的。我煮了杯咖啡,重新打开电脑——我需要在庆祝活动开始前仔细研究这个错误,否则无法安心放松。
调查后发现是虚惊一场。故障真实存在,但由机器人流量触发而非“真实”流量,且与我的更改无关。*呼——*
不过,这个请求本不应触发错误报告(无论是否机器人流量)。这需要调整,但并非紧急事项。我为错误添加了备注说明,再次关闭电脑去庆祝仲夏节。
这让我思考:那种糟糕感觉的本质是什么?我该如何避免它?
## 内疚感
每当我在生产环境引入错误时都会感到内疚——我辜负了某些人。有人正试图使用我构建(或参与构建)的软件完成任务却失败了。昨天还能正常运行的功能,现在却不行了。
或许用户尝试的任务对他们至关重要?这个故障是否会阻碍他们的工作?对他们的收入或客户会产生什么影响?我的失误波及范围有多大?
你看,故障会在领域和组织间产生连锁反应。我常将其比作水面的涟漪。投入石子(错误)后,涟漪会扩散开来。多少圈波纹?取决于石子(错误)的形状。就像水面涟漪,影响终会消散,但可能已激起无数波纹。
故障也会以恶劣的方式扩散。你的多少用户正在受影响——一个、一百个、成千上万个?那可能是巨大的波纹。
许多年前,我参与过一个项目进行大爆炸式发布。一切运行顺利,直到问题出现。项目经理致电告知,有一千家商店的销售终端系统瘫痪了。我问有多少家。“一千家,”他回答。幸而这发现于晚间而非高峰时段。我和同事通宵回滚了所有人认为导致停机的更改(我们并不确定,几乎没有任何自动化测试)。后来查明另有基础设施变更,很可能才是问题根源,但无人能确凿证实。
那是我职业生涯中最糟糕的一天。
## 怠惰
这个世界充斥着糟糕的软件——我们都经历过。设计拙劣的“解决方案”,或我们不得不使用的不稳定软件。
劣质软件是巨大的资源浪费/机遇错失。但真正激怒我的是那些漏洞——功能失灵,或被迫绕开系统才能完成任务!
如今,“将变通方案作为修复手段”几乎已成为常态。这很荒谬,变通方案绝非真正修复!
**时间。**时间本身是无限的,但对每个人和组织而言都是有限且极其宝贵的。这是所有人的共识。那么为何我们(作为软件制造者)如此漠视他人的时间?
**价值。**软件本应创造价值;当软件未能实现预期功能时,其价值不是零,而是负值!用户需要耗费(你猜对了)时间来弥补故障软件造成的影响。
## 内疚驱动开发?
我不记得首次听到这个术语的来源(可能许多人不约而同创造了它),但我喜欢这个说法。
你不应该(也不应该被强迫)交付令你感到内疚的东西——因未完成、不够健壮、难以维护等而产生的内疚感。
我在仲夏节感受到的那种内疚,我希望避免。我通过遵循四个“T”来实现:**思考(Thinking)、类型(Types)、测试(Tests)、遥测(Telemetry)**。
### 思考
这可能会让你惊讶。每个人都在思考,对吧?对吧?其实不然。我自己和见过他人都曾陷入立即掏出键盘敲打解决方案的陷阱。
对我而言,提升解决方案质量的绝对最佳方式是呈现给他人并获取反馈。根据所处理事项的规模,我会选择:
- 与团队成员进行一对一会议,阐述任务内容及解决思路。这涵盖从用户视角看如何运作(黑盒)和技术实现方式(白盒)。
- 撰写包含上述内容的解决方案说明文档,然后让多人提供反馈。
我刻意模糊了评审对象和内容。理想情况下,当然希望领域专家和技术同事都提供反馈。但有时任务足够清晰,其中一方反馈即可。
他们应评审什么?这取决于你。我不相信形式主义、严格流程或关卡审批。我通常包含:
- 为何需要此更改?
- 我将如何实现?
- 如何处理兼容性?
- 用户交互如何运作?
在编写任何代码~~之前~~进行思考有诸多好处:
- 正如那句著名(但出处难考)的名言:*“写作是自然让你知晓自己思考多草率的方式。”*
- 获得关于你是否准确理解“为何要做”的反馈。
- 获得关于此技术方案是否合理的反馈。
事实证明,每当跳过此步骤,我误解或遗漏技术细节的风险就很高。而修改后期软件几乎总是比首次做对更昂贵。
### 类型
我记得第一次听说Ruby的`method_missing`时,觉得这简直太疯狂了!
这些年来,我用过许多不同语言编程。曾有一段时期我青睐动态类型语言如Python和Clojure。但过去十年左右,我相当确信静态类型语言更适合大多数团队和大多数问题。
但类型不仅关乎类型是否存在,更在于如何使用它们。
Elm语言(可能其他语言也有)有句格言:
> 让不可能的状态成为不可能
使用类型设计程序时,应尝试在设计层面消除问题。这与动态类型语言的格言相反:
> 如果它像鸭子般行走、游泳和鸣叫,那它很可能就是鸭子。
“很可能”不够好,且是在运行时评估的。显然,不是所有函数或调用点都会额外确保类型正确性。这很容易被遗忘。而使用静态类型语言和编译器,这始终存在,无需手动完成。
编译器的帮助仅是益处的一小部分。真正的好处在于设计层面。通过使用聚合、值对象、代数数据类型等在领域中表示概念,你可以在编译时(当时间充裕、用户尚未接触时)排除大量错误。
不同编程语言的类型系统提供的功能差异很大。但有几点几乎是通用的:
**值对象**——小型不可变类型,代表领域中的事物而非数据类型。
**类型不变式**——保证类型规则或条件的类型。例如`Blog`的标题不能为空。
**数据传输对象**——为其他系统(如HTTP、队列消息等)设计不同结构的类型。
**映射**——在需要时使用多种类型并在其间映射。例如让验证层将代表HTTP API载荷的DTO映射到具有更强约束的类型(如服务层使用的输入类型)。
我感觉许多人错误地将这些概念视为“企业软件”,认为过于冗长繁琐,不适合“现代”软件。这很遗憾。虽然对“企业软件”的某些批评是正确的,但问题不在类型概念本身,而在于其设计和使用方式(例如注解、反射、自动映射)。这就像因你运行过的所有糟糕脚本而责怪Perl一样!
### 测试
我之前写过关于测试的文章(https://markuseliasson.se/article/tdd-looking-back),并将职业生涯中未使用TDD的时期视为“黑暗时代”。
好吧,你不必非要使用测试驱动开发,但你必须拥有测试!我认为不测试的软件开发是对用户傲慢的极致表现。
我总会为编写的任何代码添加单元测试作为最基本的合理性检查。我的所有代码还有某种服务层测试(在我的工作方式中接近集成测试),虽然并非覆盖所有变体。并在适当时添加集成测试。
拥有测试能确保实现按预期工作。同时它们帮助队友和未来的*你*避免做出错误假设、引发未曾想到的回归问题。将针对我能想到的所有情况和输入编写测试作为开发过程的一部分,这确实是游戏规则的改变。它迫使你思考输入验证、错误处理等问题。
学习编写好的测试很难,但我会说这是我职业生涯中回报率最高的投资之一。
### 遥测
当我尽可能预先考虑后,会为任何希望关注的边界情况或故障添加日志或类似机制。
有时状态可能是理论上的,或极少发生。你需要捕获这类情况,以防发生。也许你的“这永远不会发生”的假设在看似无关的更改后被证伪了?
同时,务必捕获来自客户端(如网页浏览器)的故障。每当出现问题,你都希望能尽早知道以便修复!
就像仲夏节的错误,区分何为错误至关重要。你不想(像我一样)被噪音困扰!
## 关于智能体的说明
鉴于当前时代,我在此提前说明一些反馈。使用AI和智能体编码并无不同。四个“T”与以往同样重要,甚至更为关键!
**思考**——智能体会营造能力错觉。不要将你的思考外包给智能体。当然可以依靠智能体辅助思考,但*你*仍应依靠他人评审你的方法。
**类型**——使用定义良好的类型并保持不可能状态的不可实现性,对充分利用智能体极有帮助。在我看来,当智能体能依托清晰的边界时,会产出更好的代码。
**测试**——让智能体以现有测试为准则,并为未来会话提供新测试,在我看来,这是使用智能体编码的*必备条件*。有太多假设和错觉并不成立,测试能大幅降低此类问题。
**遥测**——无需补充。无论代码如何产生,你都需要捕获所有错误。
## 完美无缺?
那么,如果我开发软件时使用了这些绝妙理念,为何仲夏节仍需重启电脑?
类型和测试仍只是你思考的正式化表达。有时我的思考是错的,有时我的测试不正确或不足,有时我的类型约束不够强。人皆会犯错。
但当错误发生时,我总会新增一个重现问题的测试,然后实施修复。绝不容许同一错误重现两次!
修复过程的一部分是退后一步,审视设计是否有缺陷(思考问题),或调整类型能否防止问题再次发生。
这就是我尝试描述如何避免让用户失望的内疚感。**非常期待**听到你的做法、对我的流程改进建议,或任何你想交流的内容!
请通过电子邮件分享你的想法,感谢阅读。
相似文章
为我的AI职业感到内疚
一位非营利组织的AI开发者表达了因社会对AI的反弹而产生的内疚和恐惧,尽管他负责任地使用AI来协助项目员工,并严格遵守不取代任何工作的准则。
论问责制
一篇关于软件工程和大型语言模型开发中缺乏问责制的反思文章,源自ICST 2024的一个主旨演讲,该演讲呼吁应像其他工程领域一样承担责任。
软件令人疯狂
本文探讨了软件开发的独特特性,如其速度、灵活性和隐性成本,如何引发开发者和利益相关者的精神压力与非理性行为。
如何避免死于千刀万剐,或者说如何思考软件质量(2023)
一篇反思软件质量本质的博客文章,主张质量在于优雅地进行开发,并让代码库变得比发现时更好,同时探讨如何在软件产品中培养或破坏质量。
引用 Florian Herrengt
Florian Herrengt 博客文章中的一段引文讨论了 AI 辅助开发如何导致无法调试、错综复杂的代码库,连 Claude 等 AI 工具也无法修复问题,凸显了软件工程中日益严重的问题。