Repository navigation
Engineering Guides
51hhh edited this page Sep 16, 2026
·
1 revision
大多数 bug 和技术债来自「没想到」,不是来自能力不足。
- 没想到层边界会发生什么 → 跨层 bug
- 没想到这个模式会重复 → 到处是重复代码
- 没想到边界情况 → 运行时错误
- 没想到后来维护的人 → 读不懂的代码
这一页的作用是在写代码之前问对问题。核心信条:30 分钟的思考省下 3 小时的调试。
- 功能涉及 3 层以上(IPC、领域模块、组件、数据库)
- 数据格式在层之间发生变化
- 多个消费者需要同一份数据
- 你不确定某段逻辑该放哪一层
- 你正在写和已有代码相似的东西
- 同一个模式重复出现 3 次以上
- 你在给多个地方添加同一个字段
- 你正在修改任何常量或配置
- 你正在创建新的工具/辅助函数 ← 先搜索!
改任何值之前,先搜索。
grep -r "value_to_change" .这一个习惯能防住绝大多数「忘了改 X」的 bug。
重复代码是不一致性 bug 的第一来源。 复制粘贴或重写已有逻辑会导致:bug 修复不传播、行为随时间分叉、代码库越来越难理解。
先搜索,再问四个问题:
| 问题 | 如果是 |
|---|---|
| 已经有类似函数了吗? | 用它或扩展它 |
| 这个模式别处用过吗? | 跟随已有模式 |
| 这能不能是个共享工具? | 放到正确的位置去创建 |
| 我在从另一个文件抄代码吗? | 停 —— 抽取成共享的 |
该抽象:同样代码出现 3 次以上;逻辑复杂到会有 bug;多人都需要。
不该抽象:只用一次;trivial 的一行;抽象本身比重复更复杂。
问题:当两种不同机制必须产出同一批文件时(例如初始化用递归目录复制,而更新用手工逐条列举),结构性变化——重命名、移动、新增子目录——只会通过自动机制传播,手工那条会静默漂移。
症状:初始化完全正常,但更新把文件建到了错误路径,或干脆漏掉。
预防:
- 迁移目录结构时,搜索所有引用旧结构的代码路径
- 如果一条路径是自动推导(glob/copy)而另一条是手工列举,手工那条需要同步更新
- 加一个回归测试,比较两种机制的输出
- 复查:所有实例都改到了吗?
- 搜索:跑一遍 grep 找漏掉的
- 考虑:这是不是该抽象了?
大多数 bug 发生在层边界,不在层内部。
典型跨层 bug:接口返回格式 A 而消费方期待格式 B;存储层存 X、服务层转成 Y 但丢了数据;多个层用不同方式实现同一逻辑。
来源 → 转换 → 存储 → 取出 → 转换 → 显示
对每一个箭头问:数据是什么格式?可能出什么错?谁负责校验?
| 边界 | 常见问题 |
|---|---|
| IPC ↔ 领域模块 | 类型不匹配、字段缺失 |
| 领域模块 ↔ 数据库 | 格式转换、null 处理 |
| 后端 ↔ 前端 | 序列化、时间格式、字节数组 vs 字符串 |
| 组件 ↔ 组件 | props 形状变化 |
每个边界都要明确:精确的输入格式、精确的输出格式、可能发生的错误。
隐式格式假设 —— 不检查就假定格式。应在边界做显式转换。
分散校验 —— 在多个层校验同一件事。应在入口处校验一次。
泄漏的抽象 —— 组件知道数据库 schema。每层只该知道它的邻居。
实现之前:
- 画完了完整数据流
- 识别了所有层边界
- 定义了每个边界的格式
- 决定了校验发生在哪里
实现之后:
- 用边界情况测过(null、空、非法)
- 验证了每个边界的错误处理
- 检查了数据能完整往返
- 对自写自读的格式,验证了写入方产出的每种产物都被同版本读取方接受
- 显示/预览表示与 canonical 可编辑源、坐标系是分开的
- 显式定义了部分成功的语义(例如文件已提交但剪贴板投递失败)
最后三条来自实际踩过的坑,值得单独说:
- 自写自读格式最容易出的问题是 writer 比 reader 宽松,产出一批自己读不回来的文件
- 把预览表示当作编辑源,会导致重复编辑逐次退化(例如效果类工具采样已绘制的画布而非原图)
- 「部分成功」不定义清楚,用户就会看到「保存失败」但文件其实已经写出去了
- 功能跨 3 层以上
- 数据格式复杂
- 这个功能以前出过 bug
新增的跨模块约定要写进 docs/architecture.md,否则下一个改这块代码的人会再踩一次。
发现了新的「没想到」时刻,就补充到这一页。这些清单的价值全部来自真实事故——保持它增长。