147 lines
8.5 KiB
Markdown
147 lines
8.5 KiB
Markdown
# 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) 查看导航与一页纸总则。
|