本文目录7 节

修好之后,最容易丢掉的是“为什么”

一个 Bug 能够复现、修复并通过构建,不代表我已经理解了相关代码。

如果只记住最后改了哪一行,下次遇到相似问题时,我仍然可能从界面表现开始试错。更值得留下的是:这个值原本从哪里来,哪一层第一次偏离,为什么另一个看起来合理的方向并不是根因。

最近几次状态问题给了我很直接的提醒:

  • 生成结果预览显示了输入框现在的内容,而不是创建任务时的提示词;
  • 新会话已经写进状态仓库,地址栏却仍保留旧会话标识;
  • 页面已经开始离场,旧视图却根据新路由重新计算了自己的顶部状态;
  • 自动化脚本报告“没有进入预览”,实际抛错的却是脚本里一行无关的调试代码。

它们的表面现象不同,继续追下去都在问同一件事:页面现在使用的值,究竟是不是这项业务事实的正确来源?

第一步:给每个值标出来源和时间

前端状态经常不止一份。输入框、组件属性、页面状态、全局仓库、URL、服务端记录和本地持久化里,都可能保存看起来相同的字符串或标识。

我先把它们分成三类:

  1. 当前值:用户此刻在输入框或页面上看到的内容;
  2. 事实快照:创建任务、发送消息或完成操作时实际使用的值;
  3. 恢复标识:页面重新进入后,用来找到同一项业务数据的稳定身份。

例如生成图片时,用户可能在任务提交后继续选择其他模板。输入框会变化,但已经生成的结果不能跟着变化。结果卡需要携带任务创建时保存的提示词快照,预览也应该读取这份快照,而不是在字段为空时回退到当前输入框。

修复本身并不复杂,重要的是把“生成结果属于哪次请求”重新写清楚。否则今天污染的是预览文案,明天还可能污染“复用提示词”等后续操作。

第二步:把两个状态源的优先级画出来

有些问题不是值缺失,而是两份状态都存在,却没有同步。

聊天页面的会话身份同时保存在 URL 和状态仓库中。旧会话进入页面时,URL 是可靠的恢复入口;新会话创建成功后,仓库会得到新标识。如果此时只更新仓库,地址栏仍然指向旧会话,离开页面再返回时,页面就可能按 URL 的高优先级重新加载旧内容。

这类问题可以先写成一条很短的优先级关系:

页面恢复时:URL 会话标识 > 仓库当前会话
新建成功后:新会话事实 -> 仓库 + URL 同步

当两份状态确实都承担职责时,不应该靠“以后大概会刷新”维持一致。需要在业务事实变化的那个时刻,显式同步两边;同时清理只属于旧会话的临时标记。

画出优先级后,我也更容易判断应该用新增历史记录还是替换当前地址。若只是把当前页面从“待创建”变成“已创建”,替换地址通常比再压入一层相同页面更符合返回语义。

第三步:用正常路径寻找第一次分叉

异常入口很复杂时,一个表现正常的入口往往是最好的对照组。我不需要把两条链路重新讲一遍,只比较相同数据从哪里开始使用了不同字段、哪一步之后才出现连锁偏差。

这个对照能把修改位置留在第一处分叉,而不是一路追到最终的预览组件。它也能保护已经正常的入口,避免修复范围继续扩大。

第四步:把框架和验证脚本也当成边界

如果现有证据指向路由缓存、组件生命周期或测试环境,我会再读相关源码,并检查脚本是否真的执行到了目标动作。源码可以证伪一个看似合理的框架假设;脚本日志也可能只说明验证过程提前失败,不能直接证明业务代码有问题。

这里不需要为每个误判再写一篇案例。我要确认的只有两件事:状态依赖是否属于当前组件,以及现有证据能不能覆盖我准备写下的结论。

修复后还会补做什么

一个问题修好后,我会用下面几步确认自己是否理解了它:

写出用户现象
  -> 列出这个值的全部来源
    -> 标出每份状态的更新时间和优先级
      -> 找一个正常入口做对照
        -> 定位第一次分叉
          -> 排除框架与测试环境的假象
            -> 记录验证边界

最后再用一句不依赖文件名的话解释根因。如果只能说“改了某个组件里的判断”,通常还没有吃透;如果能说明“预览读取了当前输入,而业务需要请求快照”,这条经验才有机会迁移到下一次问题。

验证仍然要保留边界

这几类问题中,有的完成了浏览器场景回归,有的通过了生产构建和定向检查,也有的仍需要真实设备或后端环境确认。

我不会因为找到统一的方法,就把所有案例写成同一种完成状态。理解代码也包括理解证据能证明到哪里:源码能够排除错误假设,构建能够证明打包链路,浏览器能够证明页面时序,真机和真实账号才能覆盖系统能力与完整业务结果。

修复 Bug 是交付的终点之一,但它也可以是重新理解系统的起点。

Recommended

继续阅读

  1. 付费页面里,入口参数为什么不能代替真实权益状态工程笔记 · 7 分钟
  2. 一个一直显示“生成中”的任务,为什么不能直接重新提交工程笔记 · 8 分钟