Files
erqiwang_youle/docs/games/engineering/03-数据权威·错误处理·演进.md
T
2026-07-06 17:16:05 +08:00

147 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 03 · 数据权威 · 错误处理 · 演进
本篇是**正确性**的地基:数据从哪来、错了怎么暴露、代码怎么随规则长大而不腐化;数据权威部分在既有「数据权威原则」上扩展到**前后端全景**。
---
## 1. 数据权威(前后端全景)
### 1.1 四条铁律(承接数据权威原则)
1. **数据源唯一**:同一业务数据只有一个权威来源;上游算/写/校验,下游只读/消费,**不重复推断、不重复拼装**。
2. **禁止猜测兜底**:不用默认值掩盖缺失。关键权威字段缺失要**显式报错或返回 `null`**,由上层决定是否终止;
**禁止** `|| 0`、`|| []`、`|| ''`、双源回退等隐式掩盖。
3. **下游不修补**:下游不得为"让流程跑起来"而补写/重算上游本应提供的数据;发现下游在修补,**把责任前移到权威源**。
4. **边界内可默认**:只有**展示层、纯 UI 兼容、非关键日志**字段,才可在明确边界内用安全默认值。
### 1.2 前后端的权威划分
```
权威裁定 表现/预判
┌──────────────────────┐ ┌──────────────────────┐
│ 服务端 = 真相之源 │ 推送 │ 前端 = 从推送重建表现 │
│ 房卡/胜负/积分/发牌/ │ ─────▶ │ 只显示、可预校验, │
│ 手牌/状态/结算…全权威 │ │ 不"前端说了算" │
└──────────────────────┘ └──────────────────────┘
▲ 同一套 shared 纯逻辑(逐字相同)在两端各跑一份 ▲
└──────────────────────────────────────────────┘
```
- **服务端权威**:一切影响胜负/计分/状态的判定以服务端为准;前端结果只是**表现与预判**。
- **逻辑同源 ≠ 权威转移**:前后端共享同一套算法(shared)是为了**表现一致 + 预校验**,
最终仍以服务端算的为准(见各端 development-guide 的 shared 说明)。
- **一份数据一个方向**:产生 → 推送 → 消费,单向、可追踪;前端不把"预判结果"当权威回写。
### 1.3 审查信号
看到 `|| []`、`|| 0`、`|| ''`、三元默认、双源字段——先判断它是不是**权威字段**(影响发牌/庄家/
手牌/规则/结算/重连/状态流转):
- **是** → 改成权威读取(缺失显式失败)。
- **只是展示/日志/统计** → 才可保留默认值。
---
## 2. 错误处理与可观测性
### 2.1 fail-fast:把错误暴露在最近处
- 关键路径缺前置条件(缺权威数据、状态非法),**立即中止**并给出明确错误,别带病继续。
- **不吞异常**:`try/catch` 不是用来"让它别报错",而是用来**在正确的边界处理并记录**。空 `catch{}`
等于把故障藏起来,是重大反模式。
- **校验前置**:入口处集中校验参数/状态,不合法即返回;业务逻辑内部可假设前置条件已满足。
### 2.2 错误的分层归属
| 层 | 出错时怎么办 |
|----|--------------|
| 入口/收发层 | 参数/身份/状态校验失败 → 结束请求(对外不泄漏细节,见 development-guide 的"静默 return") |
| 编排层 | 前置条件不满足 → 显式失败,不替下层补数据 |
| 领域/算法层 | 输入非法 → 抛错/返回 `null`,**不猜**一个"看起来对"的结果 |
| 数据层 | 权威字段缺失 → 显式失败,暴露数据链断点 |
| 表现层 | 可容错降级(缺图/缺文案用占位),**不影响业务判断** |
### 2.3 可观测性
- **分级日志**:关键节点(收包、状态转换、结算、扩展点命中)留可复盘的日志;日志**说清上下文**
(房间、座位、阶段),不是一句 "error"。
- **可复盘**:保留收包/关键中间态记录(如调试保存收包),出问题能重放。
- **诊断探针即用即清**:为定位问题临时加的日志/探针,**定位完成后必须删除**,不留在正式代码里。
- **正式代码不为测试服务**:不为"让测试过"在正式代码加 fallback/守卫/冗余字段;测试构造符合
生产契约的输入与 stub(详见 development-guide 的测试纪律)。
---
## 3. 演进与重构纪律
代码随规则长大;让它**长而不腐**的关键,是持续把"并行/重复/临时"收敛掉。
### 3.1 收敛并行实现
- 同一职能出现**第二份实现**(哪怕是"简化版/兼容版"),就是分叉的开始——**尽快收敛为一份权威**。
- 收敛方向:把旁路实现删掉,改为调用权威模块;权威能力不足则**在权威模块内扩展**。
### 3.2 扩展点先行,而非到处开分支
- 需要支持新变体时,优先看**有没有现成扩展点**(策略注册/管线节点/工厂分派);有则加一项。
- 没有且已达"三次法则"→ **重构出扩展点**,再加变体;不要在核心里再堆一个 `if`。
### 3.3 死代码与"有意保留的扩展点"要区分
- **死代码**(不可达、被替代、幻觉引用)→ 删除,减少认知负担。
- **有意保留的扩展点**(当前不可达但为将来能力预留)→ **注释写明意图**,避免被当死代码清理,
也避免被误当"已实现"。二者的区别必须在代码里显性表达。
### 3.4 改动的提交纪律
- **一次提交聚焦一件事**(一个修复/一个功能/一处重构/一批测试),信息写清"做了什么/为什么"。
- 业务缺陷与测试缺陷**分开修、分开提交**;重构与功能改动不混在一个提交里。
---
## 4. 反模式清单(一眼识别)
| 反模式 | 为什么坏 | 正解 |
|--------|----------|------|
| 隐式兜底 `\|\| 0 / \|\| []` 掩盖缺失 | 把 bug 藏到远处才爆发 | 权威字段缺失显式失败/返回 null |
| 同一数据多处各算一遍 | 必然分叉、互相矛盾 | 单一权威源,下游只读 |
| 下游修补上游数据 | 责任错位,掩盖真问题 | 责任前移到权威源 |
| 入口/表现层里写算法 | 职责错层,难测难复用 | 下沉到领域/状态层 |
| 一个入口 `switch(action)` 二次路由 | 绕开分层、膨胀成上帝函数 | 一操作一入口/一 rpc 一处理器 |
| 空 `catch{}` 吞异常 | 故障隐形 | 在正确边界处理并记录 |
| 为"将来也许"预埋抽象 | 过度设计、徒增复杂 | YAGNI + 三次法则,就近演进 |
| 魔法数字/字符串散落 | 改规则要全局翻找 | 外提为语义化常量/配置 |
| 多处各自解析同一配置编码 | 解析不一致 | 解析一次 → 只读消费 |
| 同职能第二份"简化实现" | 并行逻辑分叉 | 收敛为一份权威,调用不重造 |
| 正式代码为测试加 fallback | 本末倒置 | 测试适配生产契约 |
| 诊断日志/探针遗留 | 噪声与信息泄漏 | 定位后即清 |
---
## 5. 架构审查清单
提交/评审前逐条自检(与 [01](./01-架构总则与分层.md)/[02](./02-可扩展性与配置化.md) 对应):
- [ ] **SSOT**:这段数据有没有第二处在算?下游是不是只读?
- [ ] **依赖方向**:有没有下层依赖上层 / 环依赖 / 入口层写算法?
- [ ] **职责边界**:这段逻辑属于本模块吗?是不是重造了别处的能力?
- [ ] **关注点分离**:决策与算法、数据与表现分开了吗?
- [ ] **扩展 vs 修改**:新增能力是"加+注册"还是"改核心"?该用扩展点吗?
- [ ] **不过度设计**:这个抽象有 ≥2 个真实变体吗?还是想象的?
- [ ] **配置化**:有会变/复用/无语义的裸值该外提吗?配置只解析一次吗?
- [ ] **显式失败**:关键路径缺数据是报错还是兜底掩盖?
- [ ] **错误处理**:有没有空 catch / 吞异常 / 带病继续?
- [ ] **可观测**:关键节点有可复盘日志吗?诊断探针清了吗?
- [ ] **演进**:有没有留下第二份并行实现 / 死代码没区分意图?
---
## 6. 本篇小结
- 数据权威四铁律 + 前后端权威划分:**服务端裁定、前端表现、shared 同源不转移权威**。
- 错误处理:**fail-fast、不吞异常、分层归属、可观测、诊断即清**。
- 演进:**收敛并行实现、扩展点先行、区分死代码与扩展点、一次一提交**。
- 用反模式清单和审查清单把三篇的原则落到每次改动上。
回到 [README](./README.md) 查看导航与一页纸总则。