你没有哪些需求?
摘要
一篇关于软件设计哲学的工程博客,论述优雅的系统设计往往来自于主动放弃某些需求(如一致性、并发写、低延迟等),并探讨了在业务与技术之间权衡取舍为何困难。
<p><a href="https://lobste.rs/s/nuepys/what_requirements_don_t_you_have">评论</a></p>
查看缓存全文
缓存时间: 2026/10/19 03:56
# 你没有哪些需求?
原文:https://www.seangoedecke.com/what-requirements-dont-you-have/
在设计软件时,你**没有**的需求和你**有**的需求同样重要。识别出哪些需求是可以妥协的——哪些可以慢、可以过时、可以不灵活——通常是实现优雅设计的关键。
### 优雅的系统会舍弃部分需求
以 [Unicorn](https://github.com/defunkt/unicorn/) 这类预派生(preforking)Web 服务器为例。由于它舍弃了在服务器实例之间共享可变状态的特性,它可以将每个服务器运行在独立进程中,天然获得了隔离性。由于 Unicorns 特别忽略了慢客户端处理的问题[^1],它可以直接在 Unix socket 上排队请求,大幅简化了实现。
[S3](https://aws.amazon.com/s3) 不支持多 key 的原子更新——你无法在单个事务中更新多个 blob——因此它可以按自己的意愿在 blob 层面存储数据。这让它快得多,因为读写不需要跨越存储分区,也让它更容易扩展,因为更少的协调意味着更少的一致性来源。
[StatsD](https://github.com/statsd/statsd) 默认使用 UDP,这意味着它不能保证你发送的每一条数据都进入存储。舍弃这一需求的代价是网络开销大幅降低,这对于一个不断通过网络传输数据的指标聚合服务来说是一个巨大的优势。
将数据分发到边缘缓存的通用设计模式,通过舍弃数据必须始终保持最新这一需求,换取了大量的速度和扩展能力。[SQLite](https://www.sqlite.org/) 通过舍弃并发写入的需求,换来了运维上的极简——它可以在单个文件中运行!通过接受[高尾部延迟](https://cursor.com/blog/git-at-any-scale)[^2],Cursor 能更轻松地扩展其分布式 git 后端。设计良好的系统能将弱点转化为优势。
### 为什么舍弃需求如此困难
任何设计过真实世界软件的人(或学习过[分布式系统](https://en.wikipedia.org/wiki/CAP_theorem)的人)都知道,系统设计就是一个取舍的过程。如果你试图满足每一个潜在的需求——速度、并发性、数据的实时性、简洁的接口、支持复杂的用户行为——你根本无法设计出一个可用的系统。
弄清楚哪些需求可以放宽之所以困难,是因为**它同时需要业务和技术两方面的专业知识**。从技术角度精确识别某个设计变更允许的数据陈旧程度本身就很有挑战性;同样,从商业角度判断用户是否能接受这种程度的陈旧性也很困难。
在大多数情况下,理解技术侧的工程师需要尝试向产品经理解释,由产品经理做出业务侧的判断。但这种合作关系很难把握好。工程师和产品经理有不同的激励机制,且几乎无法互相审核对方的工作,这使得[建立信任](https://www.seangoedecke.com/working-with-product-managers/#why-its-so-hard-to-build-trust)变得困难。没有信任,产品经理很可能不愿做出任何让步[^3],最终让设计陷入不可能的境地。
这是一个经常被低估的原因——为什么独立开发者或初创公司有时能远超大公司。由于技术和业务决策统一在一个人身上,他们可以做出大公司不可能实现的设计取舍,从而构建出远比大公司更优雅、更强大的系统。
### 与 LLM 协作
在与编码代理协作时,知道自己没有哪些需求变得尤为重要。前沿模型非常强大,但它们都经历过 [RLVR](https://rlvrbook.com/) 的烈火洗礼:就像成绩优异的小学生一样,它们痴迷于让评分器(Grader)满意。除此之外,与有经验的人类相比,它们几乎没有业务上下文。这通常表现为它们揪出所谓的正确性或一致性"bug",但实际上那只是假想中未被满足的需求。
比如,LLM 经常会不经意地引入 mutex 或其他并发机制来确保两段半相关的数据保持同步,即使你并没有要求这样做。我见过 LLM 在请求处理的中间添加奇怪的检查,确保某个已经被检查过的数据在传输过程中没有变化,即使这样并不能真正防止它所担心的 [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) bug[^4]。LLM 对这类边界情况非常着迷,比如"如果当前用户在我们处理其管理员请求的过程中失去了管理员权限怎么办?"[^5],或者"如果存储分区 A 中的缓存比存储分区 B 中的提前一纳秒更新,导致某些用户看到不同的数据怎么办?"尤其是在循环运行或被告知"不许犯错"的情况下,它们会不断堆砌这类情况,直到把你的四行代码改动变成一个一千行的怪物。
这就是为什么我对一些人宣称[代码审查已死](https://newsletter.pragmaticengineer.com/p/what-is-happening-with-code-reviews)感到困惑。大约一半的情况下,当我审查一个大型的 AI 生成的变更时,我会注意到某个不必要的需求使 diff 变得更加复杂。当我深入查看时,这个需求无一例外是 LLM 自己引入的。
在使用缓存时,我经常会在 prompt 中明确说明我不介意数据偶尔过时或缓存在实例之间短暂不一致。在审查 Golang 代码时,我做的第一件事就是扫描是否添加了我看来不必要的 mutex 或间接层。
如果任由这类需求在代码库中不受控制地传播,它们会随时间逐渐将系统石化。整个系统会充斥着检查、再检查、加锁和强制一致性,以至于根本无法扩展。保持系统优雅需要无情的专注:接受过时的数据或偶尔的尾部延迟,避免非关键场景下的协调,以及总体上弄清楚哪些需求是你*不必*满足的。
以下是本文标签相同的关联文章预览。
相似文章
完美并非过度工程
探讨软件开发中完美与过度工程的区别,认为过度工程是解决错误的问题,而完美源于明确的需求。
优秀的架构不需要胡萝卜,也不需要大棒
这篇博客文章主张,良好的软件架构应当是不言自明且毫无阻力的,倡导采用 Netflix/Spotify 式的“铺平道路”模式,而非依赖强制性的治理委员会或嵌入式架构师。
Dependency Cultures - Richard Feldman (Software Should Work Conf 2026)
Richard Feldman 通过对比不同编程语言网站和游戏的依赖图,指出大部分依赖选择是文化规范而非技术必要性。
规范并不存在
本文讨论了复杂软件系统中形式化规范的缺失,通过假设情景来强调计算机科学中形式化方法的挑战与重要性。
软件关乎人,而非代码(2020)
一篇论述软件成功更多取决于理解人及其需求,而非编写完美代码的文章,并以被遗弃的、未解决实际问题的代码库为例。