目录
3440 字
17 分钟
需求变更的经典笑话:客户说「就改一个小地方」

先放一段几乎人人都经历过的对话:

客户:这个页面挺好的,就改一个小地方——用户列表加一列,显示最后登录时间。 我:行,大概半天。 三天后:还在改。

这段对话好笑,是因为听的人都知道结局,而说「行」的那个人第一次也真的相信了半天。

「小地方」这个词有一种魔法:它描述的是需求描述的字数,不是改动的工作量。在软件里,这两件事的相关性低到可以忽略。

一、需求变更笑话博物馆#

「就改一个小地方」#

所有需求笑话的母题,其他段子都是它的变体。

它的杀伤力来自一个不对称:提需求的人只能看到界面,看不到界面后面。他看到的是一张表格多了一列;他没看到的是这张表被几个接口读、被几个任务 join、有没有缓存、有没有导出、有没有人在做报表。

所以「小地方」这个判断,在他掌握的信息范围内是完全正确的。错的不是判断力,是可见性。

「你照着淘宝做一个就行」#

这句话的完整版通常是:「功能不用那么多,照着淘宝做一个就行,先做个简单的。」

它的问题不在于「照着淘宝做」工作量有多大——而在于「不用那么多」是一句无法验收的话。验收标准在需求方脑子里,只有在成品面前才会显形。于是项目会精确地走到那个经典时刻:东西做完了,需求方说「我要的不是这个」。

把这句话翻译成工程语言,它其实是:「我没有需求文档,但我知道你做错了。」

「你先做,做完我就知道要什么了」#

这句话听起来像耍赖,但它描述了需求的真实形态。

需求不是一份躺在某处等着被收集的清单,它更像一个只能通过观察来测量的物理量——用户看到东西之后,才知道自己不要什么。原型法的整个理论基础就这一句。

真正的问题不是这句话,而是说完之后没有下一步:没有走查、没有反馈节点、没有「这一版不作为最终验收」的书面约定。那就变成了一次没有护栏的赌博。

「五彩斑斓的黑」#

设计圈的名场面,通常还会配上「logo 放大的同时缩小一点」「这个字要显得高端一点」。

它跟程序员的关系是:这就是另一种形态的「加个字段」。需求方描述的是一种感受,而实现方需要的是一组参数。感受无法直接编译,中间的翻译工作没人认领,就变成了来回拉锯。

「这个很简单吧,不就加个按钮」#

加按钮本身确实简单。不简单的是这个按钮按下去之后要发生的一切——尤其是当它按下去之后什么都不该发生的时候(权限不足、条件不满足),这部分通常没写进需求,但必须有人写出来。

「需求没变,只是补充说明了一下」#

这句是所有段子里最危险的一句。

前五句只是让工期变长,这一句让工期无法承诺。因为「补充说明」意味着需求的边界是开放的:任何一次口头补充都可以事后追认为「本来就是这个意思」。

它还有一种更隐蔽的形态:变更没有消失,只是从会上转移到了私聊里。三个月后回头看 git log,没有任何一条记录说这个功能为什么要长成现在这样。

二、把「小地方」拆开看#

回到开头那个「加一列最后登录时间」。这一列到底要动多少东西:

  1. 数据从哪来——现在没有这个字段,users 表要加列。
  2. 谁写它——登录成功时更新。看着无害,但登录是高频写路径。
  3. 写在哪——每次登录都 UPDATE users,主库上就多了一份和登录 QPS 等量的写流量。想避免,就得改成异步写或者另开一张表。
  4. 空值语义——历史导入的用户从没登录过。显示「从未登录」还是空白?这两种是不同的业务含义,不能用一个 NULL 糊过去。
  5. 时区——库里存 UTC,界面上显示本地时间。那么「最近 7 天登录」的边界按哪个时区切?用户在日本、服务器在美东、数据库在 UTC,三个答案都对,只能选一个。
  6. 排序——「顺便把最近登录的排前面」是这句话的必然续集。于是要索引。
  7. 权限——登录行为是敏感数据。运营能看,用户能看到同事的吗?对外的 API 要不要返回这个字段?
  8. 缓存——用户列表接口大概率有缓存。缓存 key 的维度够不够?这份数据允许多旧?
  9. 导出——后台那个 CSV 导出要不要带上?带了,是不是要一起改那个跑在凌晨的导出任务?
  10. 下游——BI 团队正在同步 users 表。加列会不会把他们的宽表打歪,会不会让某个 SELECT * 多出意外的字段?
  11. 回归面——users 是全库被 join 最多的表。

写代码大概占这里面的 10%。剩下 90% 是确认前面十条。

所以变更的真实成本,从来不等于代码的行数,而约等于它的影响半径(blast radius)。同一个「加一列」:

场景影响半径真实成本
一张只被一个接口读的从表1 个调用点半小时
核心业务表,有缓存、有导出、有下游宽表十几个消费方数天
已经对外发布的 API 契约未知数量的外部调用方需要版本化 + 迁移期,以季度计

这也解释了那个反复出现的困惑:为什么同一个需求,在这个项目里要一天,在上个项目里要两周。不是有人偷懒,是两张表的影响半径差了一个数量级。

三、变更成本曲线,和它的反例#

教科书上那张著名的图叫「变更成本曲线」:越晚发现要改,代价越高。

Barry Boehm 在《Software Engineering Economics》里给出的经验数字,经常被复述为「需求阶段 1、设计阶段 3~6、编码阶段 10、上线后 100」这样的量级。具体倍数在不同研究里出入很大,但方向是一致的:修正成本随阶段呈量级增长,原因也很朴素——越往后,越多东西建立在那个错误的假设之上。

不过这个故事有个重要的反例,值得一起放进来。

极限编程(XP)的实践者提出过一个相反的假设:变更成本曲线可以被压平。他们的论据不是「变更不重要」,而是如果测试、重构、持续集成做得好,代码就不会随着时间变成一团不敢碰的泥。泥是曲线陡峭的原因,不是时间本身。

两边其实不矛盾,合起来的结论更有用:

变更成本 = 修改本身的成本 × 影响半径。 好的工程实践压的是后半部分,压不动前半部分。

一个字段改了要重新过一遍十几个消费方,这件事和你代码写得多漂亮无关。所以「让变更变便宜」和「让变更变少」是两件必须同时做的事。

四、需求为什么会变#

先说一句被引用了无数次的福特名言:「如果我问人们想要什么,他们会说想要更快的马。」

这句话在沟通学教材里出场率极高,但没有任何证据表明福特真的说过它——福特的著作和访谈里都找不到出处,它更像一句事后附会的鸡汤。有意思的是,它恰好和本文主题呼应:一个关于「需求表达不可靠」的段子,本身也是一次不可靠的转述。

把这句话剥掉修辞,剩下的是一个真问题:用户描述的是方案,不是问题。 他要的是更快的马,真实需求是更快的位移。照做,你就去优化马蹄铁;追问,你才有可能去造车。

所以需求变更不是意外,它是常态。真正要分清的只有两类:

  • 有价值的变更——它来自对真实问题的更深理解,越早发生越省钱;
  • 无记录的变更——它来自口头、来自私聊、来自「我以为你懂」,它唯一的产物是三个月后没人记得为什么。

后者才是所有笑话的主角。

五、把段子变成流程#

段子不会消失,但可以让它不致命。下面这几条都是能落地的。

1. 影响面三问#

不要直接回答「要多久」。先问三个问题,这时候的「不知道」是专业的:

  • 改哪一个数据? 找到那张表、那个字段,然后看它被谁读。
  • 谁的行为会变? 只有内部后台,还是外部用户能看到?
  • 怎么验证它生效了? 没有验收方式的变更,等于没有终点。

顺手做个习惯:每次口头变更之后,回到工单里补一行。一句话就够,比如「2026-09-26 追加:列表需按最后登录时间倒序」。这不是流程仪式,这是三个月后唯一的考古证据。

2. 让变更变便宜:expand-contract#

「重命名一个字段」在传统做法里是道送命题,因为它必须一次性完成——改代码、改数据库、同时上线。而 expand-contract(也叫 parallel change)把它拆成三步,每一步都可以单独上线、单独回滚:

-- 第 1 步 expand:只加,不改,不删。旧代码依然能跑
ALTER TABLE users ADD COLUMN full_name VARCHAR(100); -- MySQL 8.0.12+ 可为 INSTANT
-- 应用层进入双写期:写 name 的同时也写 full_name,读仍然读 name
-- 第 2 步 migrate:分批回填历史数据
-- 关键是 LIMIT + 循环,别让一条 UPDATE 锁住整张表
UPDATE users SET full_name = name
WHERE full_name IS NULL
ORDER BY id
LIMIT 1000;
-- 第 3 步 contract:读切到 full_name,观察一到两个发布周期,确认没人在写旧列
-- 只有到这时才删
ALTER TABLE users DROP COLUMN name;

三步的价值不在于技术含量,而在于它把一次「大爆炸」变成三次可回滚的小操作。任何一步出事,退回去就好,不需要「全组通宵 + 祈祷」。

同样的思路适用于 API:先加新字段(expand),让所有调用方迁移(migrate),确认旧字段无人使用后再移除(contract)。这也是为什么「加字段容易、删字段难」——加字段在 expand 阶段,删字段在 contract 阶段,中间隔着一整个迁移期。

3. 用开关解耦「上线」和「发布」#

一个功能写到一半,需求方说「先上一半看看」。用分支硬扛,最后会得到一个活了三个月的 feature/new-list-v2 分支,和一个每次合并都像拆弹的下午。

用开关就简单得多:

if (flags.enabled('show-last-login', { userId })) {
renderLastLogin(user);
}

代码可以先合进主干(部署了但不生效),再按白名单放量(灰度),最后全量——或者随时关掉。「上线」和「发布」变成两个独立动作,这是让变更便宜的最廉价手段之一。

代价是开关本身会积累。所以每条开关都该有一个删除日期,否则三年后你会拥有一套没人敢动的、由两百个 if 组成的考古现场。

4. 冻结窗口,和「大改小」#

发布前一天不接受新需求,这不是官僚主义,是风险管理——大改动的代价不是平均值,是尾部。一个改动如果非要赶在发布前上,那就把它拆成几次小改动,分开发布。

反过来说也成立:如果一个变更没法拆分,那它多半不是一个「小地方」。 这句话本身就是一句很好的需求评审用语。

5. 用能跑的东西代替文档#

需求歧义的解法不是写更长的文档(没人读),而是尽早给一个能点的东西。一个跑起来的丑原型,能在十分钟里暴露文档里三个月的歧义。

这其实也是在压那条变更成本曲线:把「发现要改」的时刻,从编码阶段提前到原型阶段。

写在最后#

需求变更的笑话之所以流传得这么广,是因为它们全都指向同一件事:

软件的成本从来不在「写出来」,而在「改对」。

所以下次再听到「就改一个小地方」,别急着报价。先问一句:「这个字段,还有谁在读它?」

对方大概率会愣一下——然后你们就开始聊正事了。那一愣,就是这篇笑话集锦唯一真正的用处。

参考来源#

延伸阅读:本博客里也有两篇讲「说不清楚的话如何变成技术问题」的文章——《技术黑话词典》和《程序员估算时间的经典笑话》。

需求变更的经典笑话:客户说「就改一个小地方」
https://www.hehonglei.cn/posts/requirement-change-jokes/
作者
Honglei He
发布于
2026-09-26
许可协议
CC BY-NC-SA 4.0