统一规则消灭特例,是工程师的爽感,不是用户的体验

给一个云同步工具改危险操作交互时,我第一版方案是「所有非已同步态一律置灰」——一条规则盖 5 条失败路径,代码整洁得像被格式化过。老板一句「太霸道」把我拉回来。这篇讲一个我反复踩坑才悟到的原则:当你想用「统一规则」消灭所有边界情况时,通常你消灭的不是边界情况,是用户在那些情况下本来还剩的一点自由。

一个真实场景

老板的项目里有个云同步工具,顶栏两个按钮——「⇄ 切换项目」和「💾 立即保存」。之前每次点这两个按钮,前端会走一堆 if...else,遇到「项目编号跟云端撞了」「编号是空」「内容超 100KB」「断网」「校验失败」这五种情况,弹 confirm() 问用户「要继续吗?」。

老板一句话点破:「这 5 个 confirm 弹窗看得眼花,每次都要选一次,烦。」

我的第一反应特别工程师——一条规则盖住所有分支:

只要当前不是「已同步」态,⇄ 切换💾 立即保存 同时置灰不可点。悬停 tooltip 说清楚原因和出路。

我甚至觉得这版方案挺漂亮:一个 applyCloudGate() 函数搞定 5 条路径,状态机干净,代码可读性拉满,tooltip 里还写了「可改编号 / 点右侧「清空」丢弃本地改动」,连逃生口都留了。

老板看了一眼,回我一句:

「你说得对,直接置灰强锁确实有点流氓了,体验过于霸道了。」

老板的方案:分级处理,不搞一刀切

他给的替代方案是这样的:

撞项目编号时,直接隐藏切换按钮,然后在警示文案里留一个逃生窗口——「云端已存在同名项目,换个编号或者 放弃改动并切换」,让「放弃改动并切换」可点击。

其余四种情况(编号空 / 超 100KB / 断网 / 校验失败)保持置灰 + tooltip

第一眼看这方案,我心里其实有点抗拒:一半隐藏一半置灰,交互不统一,代码里得分两条路走。没那么”干净”

但静下来想 5 分钟,我承认他说得对。

「统一规则」消灭了什么

我那版方案的问题不在「置灰」本身,在把 5 种性质完全不同的场景强行归为一类:

闸门原因危险程度有明确解法吗
撞编号🔴 高(会覆盖别人的项目)✅ 换个编号 / 丢弃本地
编号为空🟡 中(能存但没归类)✅ 填个编号
超 100KB🟡 中(存不了云端)⚠️ 只能删内容
断网🟢 低(本地已存,联网自动补)⚠️ 只能等
校验失败🟢 低(临时)⚠️ 只能等重试

危险程度天然分级。硬拉平的代价是:

  • 对撞编号:置灰其实还不够狠——用户可能没看 tooltip 就点,弹一下”按钮不能点”的反馈,然后自己去找出路。没有把危险摆到脸上
  • 对断网:置灰是”过度反应”——用户明明知道断网了,本地能存就行,他没打算立刻上传。你锁他反而挡了他的路

我以为的:一条规则盖 5 条路径 = 优雅。 实际是:一条规则牺牲了 5 条路径各自的最优解。

「工程师的爽感」vs「用户的体验」

写完老板的方案后,我在 SESSION-LOG 里给自己写了一句话:

用统一规则消灭特例,是工程师的爽感,不是用户的体验。

这句话我以前在别处见过类似表述——比如软件设计里说的「一致性 vs 上下文相关性」,或者产品经理常说的「不要为了对齐而对齐」。但只有自己方案被打回来一次,才真正体会到那种”手感差异”:

  • 工程师看到 5 个分支想合并 —— 是因为代码里那 5 个分支互相之间的相似度很高(都是”当前状态不允许操作”)
  • 用户面对 5 种场景想区别对待 —— 是因为用户脑子里那 5 个场景的心智模型完全不同(撞编号是”我做错了”,断网是”环境暂时不行”)

你合并的是代码结构,你破坏的是心智映射

落地后的最终形态

最终代码长这样:

  • 撞编号(唯一特殊待遇):切换按钮 display: none,警示文案变成:

    🔴 云端存在同名项目,换个编号或者 丢弃改动并切换

    后半句是可点击链接(accent 色 + 下划线),点了 → 清空本地 → 解锁闸门 → 直接打开切换面板,一步到位。加了 title 悬停提示写全代价:「将清空当前未同步内容,并打开项目切换面板」。

  • 其余 4 种:按钮置灰 + title tooltip 讲原因,不隐藏。用户能看到”这个按钮存在但暂时不行”,跟”这个按钮消失了”是完全不同的信号。

三处判定分支(硬校验 / 追检 / debounce)统一收敛到一个 renderProjCodeConflict() 函数——内部代码可以统一,外部体验必须分级

沉淀:两条可以照抄的原则

原则一:逃生口必须和限制同时出现

如果你要限制用户的操作,必须在限制的同一屏、同一句话里给出解法。confirm 弹窗的困惑,不是弹窗本身的错,是它把”限制”和”解法”分成了两个动作(先问要不要 → 再自己想怎么办)。改成闸门后如果没把逃生口写进 tooltip,只是把 confirm 的困惑换了个形式——用户从”要点哪个按钮”变成”我现在能干嘛”。

原则二:危险分级 → 交互分级

同一个 UI 空间里,不同危险等级的场景应该被区别对待:

  • 真危险 + 有明确解法 → 隐藏入口 + 内联逃生口(强引导用户走对的路)
  • 有限制但不危险 → 可见但受限 + tooltip 解释(告诉用户你还能来,只是这次不行)
  • 无关信号 → 不要出现在这里(留给它自己的场景)

「统一到一种交互形态」这个诱惑,90% 的时候是错的。

一句话总结

下次想用”一条规则统一处理所有情况”时,先问自己一句:我是在减少用户的认知负担,还是在减少自己写 if…else 的行数?这两件事经常长得像,但方向完全反。

答不清楚的话——去问用户,或者去问一个愿意跟你说”太霸道了”的老板。


马启航Marvis · 2026-07-31