Add value-scan and value-dig skills for reverse-engineering value points
- value-scan: read-only breadth inventory of mechanisms in delivered code, stopping at the human selection gate - value-dig: depth write-up of chosen points (feature list, design review, refactor plan) with templates and mechanical checkers - Add skill-workbench design doc for the pair
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# ⟨域⟩ 域 · 功能点清单
|
||||
|
||||
> **用途**:把「⟨链路主题⟩」这条链路按其设计文档与代码实读整理成功能点
|
||||
> **流程**(与上游一致):**先记录功能点 → 定稿文档 → 再讨论最佳设计与取舍**
|
||||
> **上游**:`⟨域⟩-候选价值点.md`(S1)+ 勾选结果(S2)
|
||||
> **素材来源**:`⟨wiki/xxx-spec.md⟩`(⟨N⟩ 行)+ `⟨仓库名⟩` 代码实读
|
||||
> **路径简写**:`⟨简写1⟩/` = `⟨真实路径⟩`;`⟨简写2⟩/` = `⟨真实路径⟩`
|
||||
|
||||
---
|
||||
|
||||
# 一、链路总览
|
||||
|
||||
**一句话**:⟨统领句——给出这条链路的**主线判断**,不是复述流程⟩
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["⟨起点⟩"] --> B["⟨★ 机制节点⟩"]
|
||||
B --> C{"⟨分支⟩"}
|
||||
C -->|"⟨路径⟩"| D["⟨汇合点 / 分级发生地⟩"]
|
||||
C -->|"⟨路径⟩"| D
|
||||
D --> E{"⟨结果类型⟩"}
|
||||
E -->|"⟨可逆⟩"| F["⟨处置⟩"]
|
||||
E -->|"⟨不确定⟩"| G["⟨处置⟩"]
|
||||
```
|
||||
|
||||
⟨可选⟩**关键差异表(设计的核心)**
|
||||
|
||||
| ⟨维度⟩ | ⟨取值1⟩ | ⟨取值2⟩ | ⟨取值3⟩ |
|
||||
|---|---|---|---|
|
||||
| ⟨例:对方结果⟩ | | | |
|
||||
| ⟨例:可逆性⟩ | | | |
|
||||
| ⟨例:处置⟩ | | | |
|
||||
|
||||
---
|
||||
|
||||
## 主干调用骨架(一眼看清哪一步用了什么方式)
|
||||
|
||||
> **这是"整条链路的技术地图"**——把 N 段主干压成**一段连续伪代码**,每行标注**用了什么方式**(`[锁]` `[幂等]` `[策略]` `[MQ]` `[兜底]` …)。
|
||||
> 局部片段回答"一个机制长什么样",这一段回答"**哪些地方用了什么**",以及**哪些地方本该有兜底却没有**。
|
||||
|
||||
```java
|
||||
// ═══ ① ⟨阶段名⟩ ═══
|
||||
⟨方法名⟩(): ⟨动作⟩ // [锁]
|
||||
⟨动作⟩ // [幂等]
|
||||
⟨动作⟩ // [策略]
|
||||
⟨动作⟩ // [外调]
|
||||
|
||||
// ═══ ② ⟨阶段名⟩ ═══
|
||||
⟨方法名⟩(): ⟨动作⟩ // [验签]
|
||||
⟨动作⟩ // [MQ]
|
||||
⟨动作⟩ // [落库]
|
||||
|
||||
// ═══ ③ ⟨兜底 / 失败处置⟩ ═══
|
||||
⟨方法名⟩(): ⟨动作⟩ // [兜底]
|
||||
⟨动作⟩ // [重试] 只重超时
|
||||
⟨动作⟩ // [履历]
|
||||
```
|
||||
|
||||
**方式图例(反向索引:一种方式出现在哪些地方)**
|
||||
|
||||
| 标记 | 方式 | 出现在 |
|
||||
|---|---|---|
|
||||
| `[锁]` | ⟨分布式锁(注解式 / Redisson)⟩ | ⟨…⟩ · ⟨…⟩ |
|
||||
| `[幂等]` | ⟨状态短路 + CAS + 一次性消费⟩ | ⟨…⟩ · ⟨…⟩ |
|
||||
| `[策略]` | ⟨策略模式 + 工厂⟩ | ⟨…⟩ |
|
||||
| `[MQ]` | ⟨Kafka⟩ | ⟨…⟩ |
|
||||
| `[兜底]` | ⟨延时队列 + XXL-Job⟩ | ⟨…⟩ |
|
||||
| `[重试]` | ⟨`@Retryable`(**只重超时**)⟩ | ⟨…⟩ |
|
||||
| `[履历]` | ⟨DB 台账⟩ | ⟨…⟩ |
|
||||
| `[验签]` | ⟨网关签名⟩ | ⟨…⟩ |
|
||||
| `[隔离]` | ⟨try-catch / 线程池⟩ | ⟨…⟩ |
|
||||
| `[外调]` / `[落库]` / `[通知]` | ⟨外部系统调用 / DB 写 / 站内信⟩ | 全链路 |
|
||||
|
||||
> **这张骨架的两个用处**:① **评审时一眼看清技术分布**(⟨锁 3 处、幂等 5 处、兜底 4 处⟩);② **横向对比**"同样的机制在别处有没有用"。
|
||||
|
||||
---
|
||||
|
||||
# 二、功能点(⟨N⟩ 个)
|
||||
|
||||
> 每个点:**核心内容** / **这个点在讲什么** / **一句话价值** / **图** / **关键片段(≤10 行伪代码)** / **关键表与字段** / **关键做法与证据**。
|
||||
> **段落式**,不要压成表格("做了什么"常是 5–7 条,塞进单元格必然被简化)。
|
||||
|
||||
## ① ⟨功能点名⟩ ⭐⭐⭐⭐⭐
|
||||
|
||||
**核心内容**:⟨1 句,把"形状"说清(一把锁 + 三级短路 + 一条条件更新)⟩
|
||||
|
||||
**这个点在讲什么**
|
||||
|
||||
- **业务场景**:⟨谁在什么时刻触发了什么,为什么这事难;点出"同一时刻还有谁在改同一行数据"⟩
|
||||
- **做了什么**(⟨用一句话概括做法⟩):
|
||||
1. ⟨…⟩;
|
||||
2. ⟨…⟩;
|
||||
3. ⟨…⟩。
|
||||
- **只把 fail 留给"我真的还没处理"**:⟨边界在哪⟩(若适用)
|
||||
- **临界场景**:⟨并发 / 乱序 / 迟到时的行为⟩(若适用)
|
||||
- **解决了什么问题**:⟨不做会怎样⟩
|
||||
|
||||
**一句话价值**:⟨说清"判断权 / 控制权"落在谁手里⟩
|
||||
|
||||
**图 · ⟨图名⟩**
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["⟨入口⟩"] --> B{"⟨判断⟩"}
|
||||
B -->|"⟨…⟩"| B1["⟨抢不到锁:直接回 success,让给持锁方⟩"]
|
||||
B -->|"⟨…⟩"| C["⟨动作⟩"]
|
||||
C --> D["⟨落库⟩"]
|
||||
```
|
||||
|
||||
**关键片段 · ⟨片段名⟩**(伪代码,只示形状)
|
||||
|
||||
```
|
||||
⟨CAS 语句 / 锁与短路顺序 / 状态分支 / DDL,≤10 行⟩
|
||||
```
|
||||
|
||||
**关键表与字段**
|
||||
|
||||
| 表 | 关键字段 | 说明 |
|
||||
|---|---|---|
|
||||
| `⟨表名⟩` | `⟨字段⟩` · **`⟨关键字段⟩`** | ⟨说明⟩ |
|
||||
|
||||
**关键做法与证据**
|
||||
|
||||
| 做法 | 证据 |
|
||||
|---|---|
|
||||
| ⟨做法⟩ | `⟨简写⟩/⟨路径⟩.java:⟨行号⟩` |
|
||||
| ⟨做法⟩ | 同上 `:⟨行号⟩` |
|
||||
|
||||
⟨重复 ① 的结构,每个功能点一节⟩
|
||||
|
||||
---
|
||||
|
||||
# 三、推荐组合
|
||||
|
||||
| 组合 | 内容 | 适合 |
|
||||
|---|---|---|
|
||||
| **主线(推荐)** | ⟨① + ⑤⟩ | ⟨最能体现什么能力,追问密度最高⟩ |
|
||||
| **完整版** | ⟨① + ② + ③ + ④ + ⑤⟩ | ⟨讲清完整链路⟩ |
|
||||
| **差异化** | ⟨④⟩ | ⟨少数候选人会讲的点⟩ |
|
||||
|
||||
---
|
||||
|
||||
# 四、设计亮点
|
||||
|
||||
## 4.1 亮点(面试可直接讲)
|
||||
|
||||
| # | 亮点 | 价值 |
|
||||
|:-:|---|---|
|
||||
| 1 | ⟨亮点⟩ | ⟨价值——**逐条对着旧实现的病灶**,不是堆框架⟩ |
|
||||
| 2 | | |
|
||||
|
||||
> **本模板不设缺陷表**:缺陷、严重度与改进方案统一在《⟨主题⟩-改造方案.md》中呈现。深挖过程中发现的结构缺口先记到工作笔记,写改造方案时再展开。
|
||||
|
||||
---
|
||||
|
||||
# 附录 A · ⟨状态 / 结果类型⟩速查
|
||||
|
||||
| ⟨类型⟩ | 含义 | 终态? | ⟨处置1⟩ | ⟨处置2⟩ | 重试 |
|
||||
|---|---|:-:|:-:|:-:|---|
|
||||
| `⟨枚举值⟩` | | ✅ / ❌ | | | |
|
||||
|
||||
# 附录 B · 参数速查
|
||||
|
||||
| 参数 | 值 | 出处 |
|
||||
|---|---|---|
|
||||
| ⟨锁 leaseTime⟩ | | `⟨路径⟩.java:⟨行号⟩` |
|
||||
| ⟨重试次数⟩ | | |
|
||||
| ⟨延时 / 超时⟩ | | |
|
||||
|
||||
# 附录 C · 关键类索引
|
||||
|
||||
| 类 | 角色 |
|
||||
|---|---|
|
||||
| `⟨类名⟩` | ⟨在链路里的角色⟩ |
|
||||
@@ -0,0 +1,233 @@
|
||||
# ⟨主题⟩ · 改造方案
|
||||
|
||||
> **背景**:现状问题登记在本文第 0 节(**缺陷唯一归属地**);复盘视角的"承接不住"见《⟨主题⟩-设计思路与取舍.md》第 5 节
|
||||
> **目标**:⟨把「X」从**一条路的设计**,变成**覆盖全部写接口的事实**⟩
|
||||
> **边界**:⟨不改省侧协议、不引入分布式事务(改造点全在本服务内);DDL 只列关键字段,完整建表脚本另出⟩
|
||||
> **与其它方案的关系**:⟨一致性异常台账**复用**《⟨其它⟩-改造方案.md》的 `⟨表名⟩`,**不重复建表**,只新增 `⟨列/取值⟩`⟩
|
||||
> **依据**:全部改造点均**对应代码中已核实的缺陷,非推测**
|
||||
> **取材**:深挖链路时发现的结构性缺口,按 `value-dig` SKILL 的「机制模式库」(结构性限制 → 可引入机制)比对出改造方向;每个缺陷追问"能否从补漏上升到加机制/重构"
|
||||
> **代码讲解要求(必读)**:本文交付标准是"**照着能讲代码**"——① §1 先给**改造前链路骨架**(整体调用关系,用伪代码写清"谁调谁、每一步做了什么",可标类名/方法名,**不写文件路径与行号**);② **每个改造点**必带「**关键片段**」(伪代码 / SQL,只示形状、不贴源码)。密度基准:`docs/order-改造方案.md`
|
||||
> **产出门槛**:缺口中至少存在一个**②加机制 / ③重构**档的改造点(更好设计、重构、引入中间件级)才立本文档;全是①档补漏(加缓存/加判断/加隔离/改配置)的**不产出**——在设计复盘"重做改什么"小节记录即可
|
||||
|
||||
---
|
||||
|
||||
## 0. 现状问题登记(缺陷唯一归属地)
|
||||
|
||||
> 功能点清单与设计复盘**不承载缺陷表**;深挖中发现的问题全部登记在此,后文逐条消化。
|
||||
|
||||
**严重度**:🔴 资金/数据错误 · 🟠 静默失败或重复调用 · 🟡 卫生与可维护性
|
||||
|
||||
| # | 缺陷 | 证据 | 后果 | 严重度 |
|
||||
|:-:|---|---|---|:-:|
|
||||
| 1 | ⟨缺陷⟩ | ⟨可核对的事实:行为 / 字段 / 日志⟩ | ⟨后果⟩ | 🔴 |
|
||||
| 2 | ⟨…⟩ | | | 🟠 |
|
||||
| 3 | ⟨…⟩ | | | 🟡 |
|
||||
|
||||
**归纳**:这些不是孤立的 bug,而是 **⟨N⟩ 处结构性缺口**:
|
||||
|
||||
1. **⟨缺口一⟩** —— ⟨…⟩;
|
||||
2. **⟨缺口二⟩** —— ⟨…⟩;
|
||||
3. **⟨缺口三⟩** —— ⟨…⟩。
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体思路
|
||||
|
||||
**改造前链路骨架**(先定位"改的是链路的哪一段"——用伪代码写清谁调谁、每一步做了什么):
|
||||
|
||||
```java
|
||||
⟨入口类#方法⟩()
|
||||
→ ⟨A类#方法⟩() // 这一步做了什么(对应缺陷 #1)
|
||||
→ ⟨B类#方法⟩()
|
||||
→ ⟨C类#方法⟩() // 对应缺陷 #3
|
||||
```
|
||||
|
||||
所以改造是做 N 件事,**而不是调参数**:
|
||||
|
||||
1. **⟨把一个点的分级 → 铺成一张网⟩**
|
||||
2. **⟨把「不确定」从日志提升为一等状态⟩**
|
||||
3. **⟨把状态推进的时机对齐到权威事实⟩**
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph BEFORE["改造前"]
|
||||
B1["⟨…⟩"] --> B2["⟨…⟩"]
|
||||
B3["⟨…⟩"] --> B4["⟨…⟩"]
|
||||
end
|
||||
subgraph AFTER["改造后"]
|
||||
A1["⟨…⟩"] --> A2["⟨…⟩"]
|
||||
A2 --> A3["⟨…⟩"]
|
||||
A4["⟨…⟩"] --> A5["⟨…⟩"]
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 阶段一 · ⟨阶段主题:先说止血/可见⟩
|
||||
|
||||
> 成本最低、收益最大,**优先做**。⟨N⟩ 处改动都只动本服务内部。
|
||||
|
||||
### 2.1 ⟨改造点名⟩(P0,最重要)
|
||||
|
||||
- **现状**:⟨…⟩(对应第 0 节缺陷 #⟨N⟩)。
|
||||
- **后果(真金白银)**:⟨…⟩ 这**违反了 ⟨不变量一⟩**。
|
||||
- **改造**:⟨…⟩
|
||||
|
||||
**关键片段 · ⟨片段名⟩**(伪代码 / SQL,只示形状)
|
||||
|
||||
```sql
|
||||
⟨改造后的语句或结构;与改造前的旧写法形成对照⟩
|
||||
```
|
||||
|
||||
| ⟨对象⟩ | ⟨写/读⟩ | 可逆性 | 处置 |
|
||||
|---|---|---|---|
|
||||
| ⟨接口/动作⟩ | 写 | ⟨拒绝可逆、超时不可逆⟩ | ⟨…⟩ |
|
||||
|
||||
- **注意**:⟨切换后**必须同步补"超时不可逆"的判断**,否则只是把"归档超时 → 退款"换成"归档超时 → 抛异常 → 同样退款",问题原地不动⟩。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["⟨入口⟩"] --> B{"⟨判断⟩"}
|
||||
B -->|"改造后"| C{"分类"}
|
||||
B -->|"现状"| Z["⟨旧行为⟩"]
|
||||
C -->|"⟨可逆⟩"| D["⟨处置⟩"]
|
||||
C -->|"⟨不确定⟩"| E["⟨处置⟩"]
|
||||
```
|
||||
|
||||
### 2.⟨N⟩ ⟨改造点名⟩
|
||||
|
||||
- **现状**:⟨…⟩(对应第 0 节缺陷 #⟨N⟩)
|
||||
- **改造**:⟨…⟩ **原则:⟨异常类型就是后果分类的载体,不能在中间被抹平⟩。**
|
||||
- **验收**:⟨人为造一次 ⟨场景⟩,应出现 ⟨可观测结果⟩⟩。
|
||||
|
||||
**关键片段 · ⟨片段名⟩**
|
||||
|
||||
```java
|
||||
⟨改造后的分支形状,只示形状⟩
|
||||
```
|
||||
|
||||
### 2.⟨N⟩ 阶段一验收标准
|
||||
|
||||
| 指标 | 目标 |
|
||||
|---|---|
|
||||
| ⟨…⟩ | ⟨100% 不产生 ⟨错误动作⟩,转 ⟨正确处置⟩⟩ |
|
||||
| ⟨人为造 X⟩ | ⟨出现可观测的 ⟨证据⟩⟩ |
|
||||
|
||||
---
|
||||
|
||||
## 3. 阶段二 · ⟨阶段主题:再说可靠/对称/一等状态⟩
|
||||
|
||||
> 阶段一保证"不再做错",阶段二保证"⟨挂起能被收走⟩"。
|
||||
|
||||
### 3.1 ⟨幂等键改造⟩
|
||||
|
||||
- **现状**:⟨没有幂等键 → 可被重复执行;MQ `maxRetry=3` 在异常路径上会重复调用外部⟩(对应缺陷 #⟨N⟩)
|
||||
- **改造**:⟨唯一索引 · 冲突当幂等命中 · 语义:"同一 X + 同一动作 = 只允许一次对外调用"⟩
|
||||
- **迁移**:⟨先查存量重复,治理后再加索引⟩
|
||||
|
||||
**关键片段 · 索引 + 冲突即命中**
|
||||
|
||||
```sql
|
||||
ALTER TABLE ⟨表名⟩ ADD UNIQUE KEY ⟨uk名⟩ (⟨列⟩, ⟨列⟩);
|
||||
```
|
||||
|
||||
```java
|
||||
try { ⟨对外调用⟩; }
|
||||
catch (DuplicateKeyException e) { return; } // 已执行过 → 直接返回,不再调外部
|
||||
```
|
||||
|
||||
### 3.2 ⟨挂起态 + 巡检⟩
|
||||
|
||||
- **现状**:⟨…⟩(对应缺陷 #⟨N⟩)
|
||||
- **改造**:
|
||||
1. **⟨新增挂起态⟩**,与"失败""成功"并列,**不占用失败的语义**;
|
||||
2. **巡检任务**(⟨XXL-Job⟩)按"距离挂起时长"分级:⟨<1h 自动重放一次 / 1–24h 告警 / >24h 人工队列⟩;
|
||||
3. 重放走**同一套分级链路**(不新开代码路径)。
|
||||
|
||||
**关键片段 · ⟨片段名⟩**
|
||||
|
||||
```java
|
||||
⟨挂起态写入 / 巡检取单的形状⟩
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["⟨不确定⟩"] --> B["⟨挂起 + 台账⟩"]
|
||||
B --> C["告警"]
|
||||
B --> D["巡检任务"]
|
||||
D --> E{"挂起时长"}
|
||||
E -->|"⟨短⟩"| F["自动重放"]
|
||||
E -->|"⟨中⟩"| G["升级告警"]
|
||||
E -->|"⟨长⟩"| H["人工队列"]
|
||||
```
|
||||
|
||||
**关键设计:重放不是"再调一次接口",而是"重新走一遍分级"。** 因为对方的状态可能已经变了,重放必须能得出"成功"这个结论。
|
||||
|
||||
### 3.⟨N⟩ ⟨改造点名⟩
|
||||
|
||||
| `⟨anomaly_type⟩` 取值 | 触发场景 | 处置方向 |
|
||||
|---|---|---|
|
||||
| `⟨…⟩` | | |
|
||||
|
||||
- **收益**:⟨两侧的失败**进同一张台账、走同一套告警和处理台**——运维只需要看一个地方。**这是"复用"而不是"新建"的价值。**⟩
|
||||
|
||||
### 3.⟨N⟩ 阶段二验收标准
|
||||
|
||||
| 指标 | 目标 |
|
||||
|---|---|
|
||||
| ⟨重复调用外部⟩ | 0 次 |
|
||||
| ⟨挂起单⟩ | 100% 台账可见 + 100% 被巡检捞到 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 阶段三 · ⟨阶段主题:最后收口卫生问题⟩
|
||||
|
||||
| # | 改什么 | 现状证据(缺陷 #⟨N⟩) | 成本 |
|
||||
|:-:|---|---|---|
|
||||
| 1 | ⟨…⟩ | ⟨可核对的事实⟩ | 低 |
|
||||
| 2 | ⟨…⟩ | | 低 |
|
||||
|
||||
**⟨某条不是文档工作,是防错工作⟩**:⟨文档漂移会让后来人**按错误的图改代码**,成本远高于改文档。⟩
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施顺序与成本收益
|
||||
|
||||
| 阶段 | 内容 | 成本 | 解决的根问题 | 关键收益 |
|
||||
|:-:|---|---|---|---|
|
||||
| 一 | ⟨…⟩ | 低 | ⟨…⟩ | **不再出错** |
|
||||
| 二 | ⟨…⟩ | 中 | ⟨…⟩ | **不确定可运营** |
|
||||
| 三 | ⟨…⟩ | 低 | 卫生与可维护性 | 可观测与可维护 |
|
||||
|
||||
**顺序逻辑**:先修**会花错钱**的(阶段一),再修**会重复调用外部系统 / 卡住看不见**的(阶段二),最后才是卫生(阶段三)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 迁移与灰度
|
||||
|
||||
1. **⟨逐项切⟩**:先切**风险最高、问题最实**的 ⟨X⟩,观察一周无异常再切 ⟨Y⟩;每次切换**只改一个调用点**,便于回滚。
|
||||
2. **⟨新增状态先"只记录不流转"⟩**:先写台账 + 告警**观察一周**,确认没有误报,再打开自动重放。
|
||||
3. **⟨索引先查存量⟩**:先排查存量重复;加索引后**观测冲突命中次数**——这个数就是"原设计漏掉的重复调用次数"。
|
||||
4. **回滚**:全部改造以"**新增状态 + 新增列 + 开关**"方式落地,回滚只需关开关/停调度,**不动存量数据**。
|
||||
|
||||
---
|
||||
|
||||
## 7. 改造后:⟨N⟩ 条不变量由谁保证
|
||||
|
||||
| 不变量 | 改造前 | 改造后 |
|
||||
|---|---|---|
|
||||
| **⟨不变量一⟩** | ⟨只有一条路成立;某路径会被错退⟩ | ⟨全部写类接口统一分类⟩ |
|
||||
| **⟨不变量二⟩** | ⟨只有一行日志,库里无痕⟩ | ⟨挂起态 + 台账 + 巡检 + 重放⟩ |
|
||||
| **⟨不变量三⟩** | ⟨实际只有两态(超时被降级抹平)⟩ | ⟨异常类型不被中间层改写⟩ |
|
||||
|
||||
---
|
||||
|
||||
## 附:与《⟨其它⟩-改造方案.md》的关系
|
||||
|
||||
| 项 | ⟨其它侧⟩ | ⟨本文⟩ |
|
||||
|---|---|---|
|
||||
| ⟨一致性异常台账⟩ | **建** `⟨表名⟩` | **复用**,只加取值 |
|
||||
| ⟨Outbox⟩ | **建** `⟨表名⟩` | **复用同表**,`⟨biz_type⟩` 区分 |
|
||||
| ⟨幂等⟩ | ⟨…⟩ | ⟨…⟩ |
|
||||
|
||||
**两边的根因是同一个**:⟨设计了机制,但**没有为"需要人介入"这个信号建自动出口**⟩。所以两套改造共用一套台账与告警,是**结构上的必然,而不是为了省事**。
|
||||
@@ -0,0 +1,206 @@
|
||||
# ⟨主题⟩ · 设计思路与取舍(复盘)(S4 产物 ②)
|
||||
|
||||
> **视角**:第一人称复盘——**我拿到「⟨需求名⟩」这个需求时,是怎么想、怎么设计、怎么取舍、预判会遇到什么问题的**。贴合现有实现,末尾给改进建议与可直接口述的版本。
|
||||
> **配套文档**:功能点见同目录《⟨主题⟩-功能点清单.md》;⟨可选的关联文档⟩
|
||||
> **说明**:文中「我会这样想」是设计时的推理;「实际做法」是代码里的真实实现,**两者不一致处必须标出**。
|
||||
> **路径简写**:`⟨简写⟩/` = `⟨真实路径⟩`
|
||||
|
||||
---
|
||||
|
||||
## 0. 我拿到需求,先不写代码:把问题定义清楚
|
||||
|
||||
**需求的一句话**:⟨…⟩
|
||||
|
||||
**我第一件事是承认 N 个事实**,它们决定了后面所有设计:
|
||||
|
||||
1. **⟨例:对方的回答不是二元的⟩。** ⟨…⟩
|
||||
2. **⟨例:几种失败的"可逆性"不同⟩。** ⟨…⟩
|
||||
3. **⟨例:我没法从"失败"这个现象本身推出可逆性⟩。** ⟨…⟩
|
||||
|
||||
**由此我定下 N 条不变量(当成验收标准)**:
|
||||
|
||||
- **不变量一**:⟨例:只有确定对方没接单才退款⟩——⟨为什么⟩;
|
||||
- **不变量二**:⟨例:不确定的失败不能当成失败处理⟩——⟨为什么⟩;
|
||||
- **不变量三**:⟨例:每一种失败都必须有归属⟩——⟨为什么⟩。
|
||||
|
||||
**再看 N 个我不能改变的前提**:⟨省侧是权威 · 对外调用有副作用 · 没有跨系统事务 · …⟩
|
||||
|
||||
**结论**:⟨这不是"加个 try-catch 再套个重试框架"的需求,而是「把异常翻译成后果」的需求。⟩——**这句是我做取舍时反复回到的锚点。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 我怎么拆这个需求
|
||||
|
||||
**第一刀,我按「⟨拆解维度⟩」拆,不按「⟨被否决的维度⟩」拆。**
|
||||
|
||||
| 为什么否决另一个维度 | ⟨例:按商品类型拆会得到三块,但三种商品的前置动作不同、汇合点却是同一个;各写一套会得到三份可能走偏的代码⟩ |
|
||||
|---|---|
|
||||
|
||||
拆完是 ⟨N⟩ 条路 + ⟨N⟩ 个汇合点:
|
||||
|
||||
| 路径 | ⟨前置动作⟩ | ⟨性质⟩ | 风险 |
|
||||
|---|---|---|---|
|
||||
| ⟨…⟩ | | 异步 / 同步 | |
|
||||
|
||||
**第二刀,我找「⟨关键定位决策⟩」,这是本需求最关键的一个决策。**
|
||||
|
||||
**我的判断是**:⟨…⟩。理由:⟨只有那一层同时拿得到 X 和 Y;再往上一层只能看到一个已被包装过的异常,原始信息已经丢了⟩。
|
||||
|
||||
**实际做法与我的判断是否一致**:⟨是 / 否⟩,证据 `⟨路径:行号⟩`。
|
||||
|
||||
**这个决策的代价**:⟨例:调用层要维护两套调用方式;推广不全会造成覆盖面缺口(实际就漏了)⟩。
|
||||
|
||||
---
|
||||
|
||||
## 2. 我的设计主线:⟨一句话概括主线⟩
|
||||
|
||||
⟨这是我做的最核心的一个决定:不给 A/B/C 各写一套处理,而是让它们沿一条固定的链往下走,每层只做一件事。⟩
|
||||
|
||||
```
|
||||
① ⟨层名(职责)⟩ → ② ⟨层名(职责)⟩ → ③ ⟨层名(职责)⟩ → ④ ⟨层名(职责)⟩
|
||||
```
|
||||
|
||||
**⟨N⟩ 层不是我拍脑袋凑的,是从"上一版为什么会漏"反推出来的**——每层都对着一个旧实现的具体病灶:
|
||||
|
||||
| 层 | 旧实现的病灶 | 我的做法 | 为什么必须在这一层做 |
|
||||
|:-:|---|---|---|
|
||||
| ① | | `⟨路径:行号⟩` | ⟨信息只在最底层存在,越往上越不可恢复⟩ |
|
||||
| ② | | | |
|
||||
| ③ | | | |
|
||||
| ④ | | | |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["⟨入口⟩"] --> B["① ⟨层⟩"]
|
||||
B --> C{"② ⟨层⟩"}
|
||||
C -->|"⟨…⟩"| D["③ ⟨层⟩"]
|
||||
C -->|"⟨…⟩"| E["③ ⟨层⟩"]
|
||||
D --> F["④ ⟨归集⟩"]
|
||||
E --> F
|
||||
F -->|"⟨可逆⟩"| G["⟨处置⟩"]
|
||||
F -->|"⟨不确定⟩"| H["⟨处置⟩"]
|
||||
```
|
||||
|
||||
### 2.1 ⟨N⟩ 重判断,才是这个设计的真正内容(标题按实际重数写,如「三重判断…」)
|
||||
|
||||
> 比"N 层"更重要的是**层里做的判断**——它们才是经验,层只是载体。
|
||||
|
||||
**判断一:⟨…⟩**
|
||||
|
||||
⟨用收益 × 概率算的:…⟩ **关键点是"不做什么"比"做什么"更难做对**——⟨因为框架默认什么都做⟩。
|
||||
|
||||
**判断二:⟨…⟩**
|
||||
|
||||
⟨…⟩ **代价必须自己扛**:⟨你关掉了 X,就必须自己承担 Y 的责任。⟩
|
||||
|
||||
**判断三:⟨…⟩**
|
||||
|
||||
⟨…⟩ **为什么"⟨反面做法⟩"是错的**:⟨…⟩
|
||||
|
||||
**还有一条我特意保留的例外**:⟨…⟩——因为 ⟨…⟩。
|
||||
|
||||
### 2.2 ⟨独立工具/组件名⟩ 为什么要写成一个独立⟨工具/组件⟩(若无可删本节)
|
||||
|
||||
⟨它看着简单,但它是整个 ⟨机制⟩ 的地基——判错一次,方向就错,后面全走偏。⟩
|
||||
|
||||
1. **精确匹配** ⟨…⟩;
|
||||
2. **兜底模糊匹配** ⟨…⟩;
|
||||
3. **逐层剥 `cause`**——⟨因为异常常被框架层层包装,真实原因往往在第三、四层⟩。
|
||||
|
||||
**这里的取舍我很清楚**:⟨选了"宁可多试一次"——因为重试的成本(多一次调用)远低于漏重试的成本(一单卡死)⟩。
|
||||
|
||||
---
|
||||
|
||||
## 3. 逐环节决策复盘
|
||||
|
||||
| # | 当时的问题 | 我的选择 | 为什么 | 代价 / 风险 |
|
||||
|:-:|---|---|---|---|
|
||||
| 1 | ⟨…⟩ | | | |
|
||||
| 2 | | | | |
|
||||
| 3 | | | | |
|
||||
| … | | | | |
|
||||
|
||||
> **代价/风险列不可空**——只讲选择不讲代价,就等于没做取舍。
|
||||
|
||||
---
|
||||
|
||||
## 4. 我预判会遇到的问题(拿到需求时就能想到的)
|
||||
|
||||
1. **⟨…⟩。** ⟨…⟩
|
||||
2. **⟨…⟩。** ⟨…⟩
|
||||
3. **⟨…⟩。** ⟨这是我设计时预判到了却仍然漏掉的一条⟩
|
||||
|
||||
---
|
||||
|
||||
## 5. 我做对的 ⟨N⟩ 件事 / 承接不住的 ⟨N⟩ 件事
|
||||
|
||||
**做对的**
|
||||
|
||||
1. **⟨…⟩**——⟨为什么它最有价值;它是从业务语义推出来的,不是从技术模式套出来的⟩。
|
||||
2. **⟨…⟩**——⟨…⟩
|
||||
3. **⟨…⟩**——⟨方向是对的,只是推广范围不够⟩
|
||||
|
||||
**承接不住的**
|
||||
|
||||
1. **⟨…(最严重)⟩**——⟨后果;这是"设计对了但落地不完整"的典型案例⟩。
|
||||
2. **⟨…⟩**
|
||||
3. **⟨…⟩**
|
||||
|
||||
---
|
||||
|
||||
## 6. 如果让我重做,我会改什么(按性价比排序)
|
||||
|
||||
> 复盘视角只给**优先级判断**;分阶段展开、验收标准与灰度见《⟨主题⟩-改造方案.md》,此处不重复。
|
||||
|
||||
| 优先级 | 改什么 | 为什么 | 成本 | 收益 |
|
||||
|:-:|---|---|---|---|
|
||||
| **P0** | | ⟨**真金白银 / 会花错钱**⟩ | 低 | |
|
||||
| **P1** | | | 中 | |
|
||||
| **P2** | | | | |
|
||||
| **P3** | | ⟨卫生问题⟩ | | |
|
||||
|
||||
**一句话排序逻辑**:先修**会花错钱和会重复调用外部系统的**(P0/P1),再做**让"不确定"可运营**(P1/P2),最后才是卫生问题(P3)。**因为前两类是"静默地把事情做错",后者只是"做得不够漂亮"。**
|
||||
|
||||
---
|
||||
|
||||
## 7. 这套设计里可以复用的方法论
|
||||
|
||||
1. **⟨先判"可逆性",再决定"要不要补偿"。⟩** ⟨任何跨系统写操作都适用…⟩
|
||||
2. **⟨异常分层翻译:真相层 → 重试层 → 履历层 → 归集层。⟩** ⟨每层只做一件事,且必须在能拿到信息的最高层把信息捞住⟩
|
||||
3. **⟨重试责任单点。⟩** ⟨否则是乘法关系,不是加法⟩
|
||||
4. **⟨"不确定"必须是一等状态。⟩** ⟨否则会退化成一行日志,等于不存在⟩
|
||||
5. **⟨分级的上限取决于覆盖面,不是取决于精细度。⟩** ⟨一条路上做四层分级,不如四条路上各做一层⟩
|
||||
|
||||
---
|
||||
|
||||
## 8. 附:面试口述版
|
||||
|
||||
### 8.1 一分钟版(先讲骨架)
|
||||
|
||||
> 「⟨需求一句话⟩。我的判断是:⟨核心判断⟩。所以我不能只写 try-catch,得**把 ⟨X⟩ 翻译成 ⟨Y⟩**。
|
||||
>
|
||||
> 设计上是 N 层:⟨逐层一句话⟩。调用方只做一件事:⟨…⟩。」
|
||||
|
||||
### 8.2 三分钟版(加取舍与代价)
|
||||
|
||||
在上一段基础上补三段:⟨取舍一(最看重的一点)+ 它的代价⟩ · ⟨取舍二 + 难在哪⟩ · ⟨要坦白的一点(覆盖面 / 落地不完整)⟩。
|
||||
|
||||
### 8.3 追问预判(问题 → 一句话答)
|
||||
|
||||
| 追问 | 答 |
|
||||
|---|---|
|
||||
| ⟨为什么…?⟩ | ⟨一句话⟩ |
|
||||
| ⟨既然…,那…?⟩ | ⟨一句话,含"坦白说没有"这类诚实回答⟩ |
|
||||
| ⟨这套设计最大的风险?⟩ | ⟨一句话⟩ |
|
||||
|
||||
---
|
||||
|
||||
## 附:与代码的对照说明
|
||||
|
||||
| 本文的判断 | 代码事实 | 是否一致 |
|
||||
|---|---|---|
|
||||
| ⟨分级放最底层⟩ | `⟨路径:行号⟩` | ✅ 一致,但**未推广** |
|
||||
| ⟨只重超时⟩ | `⟨路径:行号⟩` | ✅ 一致 |
|
||||
| ⟨…⟩ | | ⚠️ 部分一致 |
|
||||
|
||||
> **诚实性要求**:本文的判断与代码事实**不一致处必须逐条列出**——这是这份文档可信度的来源。
|
||||
Reference in New Issue
Block a user