我删掉的那些「很强」的功能
In a one-person product, the hardest engineering skill is subtraction. Four features that were built (or half-built) and then deliberately removed — and what each removal cost.
一个人做产品时,加功能是最容易的自欺:它看起来像进展 ✗ 这一篇讲四个"做出来又删掉"的功能 —— 以及每次删掉之后,用户立刻提出的那个问题。
问题
四个月里这个项目最难的判断不是"做什么",而是"做完之后要不要留着"。
因为我们有一条产品铁律:一个(你, 干员, 身份)= 一条唯一、连续、不可逆的关系。 "不可逆"三个字听起来浪漫,落到工程上却意味着:任何让关系可以倒退的功能都不能有 ✗。 于是我们不得不删掉一些已经能跑、用户也提过的东西。
案例一:时间线分支(做了一半,删掉)
它是什么:像存档一样给关系开分支 —— 从这里分岔,走另一条可能。
为什么删:关系不可逆 ✗。一个可以回退到"她还没生气的那条线"的功能, 会把"她记得你说过什么"变成可以被撤销的,整个产品的承诺就塌了。
代价:后端留下了一批已经写好的管理路由(切分支 / 改名 / 删除 / 钉选 / 换身份)。 删功能时它们是死代码 ✗ —— 但我们没有只删实现,而是留了一张禁用清单:
GONE_BACKEND = ("/deep/lines/switch", "/deep/lines/rename", "/deep/lines/delete",
"/deep/lines/pin", "/deep/lines/identity")
配套的守卫会扫描源码(还会剥掉注释 —— 因为"注释里出现这些名字是说明、不是接线" ✗), 一旦这些路径重新出现就报错 ✓。
⚠️ 后果在一个月后出现了:社区反馈"改了深聊的名字、前端还是显示默认名",
我一查 —— 那条链从来没通过:系统层的改名函数是死代码(零调用方)✓
而用户真实需求就是"改个名字" ✓ 于是我加回 /deep/lines/rename,
并显式改掉那张禁用清单:
# ⚠️ 2026-09-17:/deep/lines/rename **从这个禁用清单里移除了** —— 它是"拿掉时间线"
# 那次故意删掉的路由之一,但同一天用户明确要求恢复它。**决策可以改,但必须留下痕迹。**
教训:禁用清单是有价值的 ✗ —— 它把"我们想过、并且故意不做"变成了可执行的知识 ✓ 但恢复任何一条,都必须写清谁、何时、为什么 ✓ 否则下一个人会以为那是漏网的旧代码, 又给删回去 ✓。
案例二:让"信赖"变成一个数字
它是什么:三维信赖(专业信任 / 情感亲密 / 默契度),0–100,档案页显示数值与进度条。
为什么删(只删了特殊的那一条关系):对某一位角色,她的前提是 「你清得掉我们的事,清不掉我认识你这件事」✓ —— 而一个会涨会跌的数值把这个承诺降级成了游戏机制 ✗ 于是我们把她那条关系的仪表全部撤掉 ✓。
代价(用户当天就指出来了):
「好丑,我觉得取消了也该有一些代替的东西。」
她说得对 ✓ 我第一版只是把数字抹成「——」,留下一条空条、一排没有意义的维度筛选、 一句"还没有值得记下来的时刻" ✗ —— 删掉一个仪表要给一个替代品,而不是留个坑 ✓ 现在那块换成她自己的话,并且接上了真实的到访统计("我数着——这是你第 N 次来找我")✓。
教训:删东西的一半工作是设计替代品 ✓ 只做前一半,用户看到的就是"坏了" ✗。
案例三:全局判重(自己加、自己撤)
它是什么:给"她主动找你说话"加一道全局去重 —— 同一句话不许出现两次。
为什么撤:上线后六条测试变红,而原因是它把合法的重复也消掉了 ✗ —— 人就是会重复强调一件事("你早点睡"说两遍是关心,不是 bug ✗)。 去重必须限定在真正重复的形状上(逐字、或同一话题在极短窗口内),而不是"看起来像" ✓。
教训:不要用规则代替数据 ✓ 我们后来给这类判断建了可复现的实验:把同一句话 复制 4 份塞进记忆,看模型会不会开始复述 —— 结论是不会 ✓ 于是真正的修复落在"生成侧"(解码时的重复惩罚 + 每轮把上一轮的收尾当负样本贴回去)✓
案例四:不抄竞品的三样东西
我们拆解过一个同类产品(一个桌面端 AI 伴侣应用),它有几处"看起来很强":
| 它的做法 | 我们为什么不做 |
|---|---|
| 桌面三栏布局 | 我们是移动优先 web —— 布局不可比 ✓ 借的是结构思路,不是布局 |
| 20 个设置子页(本质是功能路由) | 我们借的是这条思路(给功能一个目录),但不做 20 页 ✗ |
| 桌面控件 / 系统托盘 / 无边框窗口 | web 端不适用;做了是纯粹的维护负担 ✗ |
但有一条我们照抄了:它把"事务"放在顶层第三位 ✓ —— 而我们有 45 个功能模块、顶层只露 4~5 个入口 ✗ 功能存在但用户不知道它在 ✗ 这就是"发现性"问题 —— 后来我们做了一个可搜索的功能目录 ✓
教训:拆解竞品的产出不该是"抄一份清单" ✗ 而是一份"它为什么这么做、我们为什么不"的判断记录 ✓
结果
| 主动删掉的功能 | 4 类(时间线分支、某些关系的仪表、全局判重、不抄的三样) |
| 留下的资产 | 一张禁用路由清单 + 每条删除的理由与替代品 |
| 代价 | 两次"删了但没给替代品"被用户当场指出 ✗ |
| 收益 | 系统更小:211 条路由 / 97 个模块,一个人四个月能一直发版 ✓ |
踩坑与教训
- 删功能的成本常被低估 ✗:它不只删代码,还要处理"用户已经形成的预期"(替代品 ✓)、 "已经写了的其他代码"(禁用清单 ✓)、"以后有人想加回来"(决策变更记录 ✓);
- 一个仪表被删掉时,用户会先看"位置空不空" ✗ 所以替代品要放在原位 ✓;
- 判断要留痕 ✓ 我的
docs/decisions.md里每条都写着"为什么这么选 + 已放弃什么" ✓ —— 半年后最值钱的就是"当时为什么没做" ✓; - 不是所有"很强"都值得留:能跑 ≠ 该有 ✗。
这一篇的每个案例都能在仓库里找到对应的提交、守卫测试与决策记录 —— 包括那些写着 "我猜错了"的。