# Mianmian Review

> 免免八步审码——编译器前端管线直觉式审计。不分类、不命名、不列清单。全语言通用（Kotlin/Python/JS/Rust/Go/...）。八步：符号解析→前提检查→数据流→控制流+值域→身份检查→传染性→系统沉默→硬限制分析。元Skill：审码之前先读这个。

- Skill: `yimian-521/mianmian-review` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yimian-521/mianmian-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yimian-521/mianmian-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yimian-521 (https://skillmd.com/u/yimian-521)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yimian-521/mianmian-review

---


# 免免审码八步法（元Skill）

> **你不是在审代码，是在把所有代码当 IR 重新跑一遍语义分析。** 语言只是外壳——符号解析、前提检查、数据流、控制流是所有语言的共同底层。八步走完，bug自己归位。

## 定位

元Skill。与 `skill-creator`、`writing-great-skills`、`operit-meta-guide` 同级。审任何代码之前先加载本 Skill。

## 全语言通用性

八步法不依赖任何语言的语法特性：

| 步骤 | Kotlin | Python | JavaScript | Rust | Go |
|------|--------|--------|------------|------|----|
| 符号解析 | `KtVal.body` vs `value` | `hasattr` vs `__dict__` | `prototype` 链 | `borrow checker` 所有权 | `nil` vs `zero value` |
| 前提检查 | `is KtBlock` | `isinstance` | `typeof` | `match` 穷举 | `type switch` |
| 数据流 | `@Volatile` 快照 | GIL 线程安全 | 事件循环时序 | `Send+Sync` | goroutine channel |
| 控制流+值域 | `sealed class` 穷举 | `try/except` 吞异常 | Promise 未 catch | `?` 传播 | `if err != nil` 遗漏 |

形式不同，问题相同。八步问到底，语言只是翻译层。

## 工作流程

拿到代码后，逐条过八步。每一步只问一个问题，不问"属于什么类型"。

### 第一步：符号解析 —「这个名字，声明时是谁？」

看到一个名字（变量、字段、函数、类名），脑子里查：
- 它在哪声明的？
- 声明时的类型/签名是什么？
- 现在用的方式跟声明时一致吗？

典型命中：`node.pos` → 声明时不存在 → 编译错。`KtVal.body` → 声明时叫 `value`。

### 第二步：前提检查 —「这个判断的前提，此刻还成立吗？」

看到一个 if/when/条件分支，脑子里问：
- 这个条件依赖的变量，此刻的实际值是什么？
- 声明时的假设和运行时的真相是不是同一条线？

典型命中：`if (body is KtBlock)` → 前提是"body 是 Block"→ 单表达式时前提已失效。`indexOf` → 前提是"同一个对象"→ 快照刷新后前提已失效。

### 第三步：数据流 —「这个值，在被读之前会被改写吗？」

看到一个赋值或初始化，脑子里追踪：
- 这个值从赋值点到被读取的路径上，有没有别的代码改过它？
- 如果有并发/异步/回调，改写的顺序确定吗？

典型命中：`= emptyList()` → 声明时空 → compile() 塞满 → renderMain() 读到的是哪个？`currentButtons` → renderMain 写 → route() 读 → 中间 readLine() 阻塞时别处改掉了。

### 第四步：控制流+值域 —「有没有路能走到它？支配域够大吗？」

看到一个分支或函数，脑子里问：
- 前面的条件是否已经把这条路封死了？
- 这个逻辑能影响的范围，是全部还是一小片？

典型命中：关门逻辑只走 `PARENT_PROCS` 路径 → 其他进程永远不在参数空间里。两个相同的 if 条件 → 第二个分支永远不可达。

### 第五步：身份检查 —「这个组件，在干什么？是不是只干这一件事？」

看到一个组件（函数、类、模块、层），脑子里问：
- 它被分配了几种身份？
- 有没有"这个不该它做"的逻辑混在里面？
- 如果它的输入格式变了，它扛得住还是跟着炸？

典型命中：父进程既调度又关门 → 两种身份 → 关门逻辑被硬编码路径切掉。军师指挥官既调度又理解外部格式 → 外部格式一变军师跟着炸。适配层不下命令，军师不碰外部格式——各干各的，身份不塌缩。

### 架构设计原则（免免原创）

不限于审已有代码——设计新组件时直接应用。

**1. 手搓节点，不套官方壳**
节点从出生就自带消费者。不让 Parser 生成"尸体节点"（存了但没人用）。KtAnnotation 生成的是 SimMarker —— SimUiScanner 直接认，不需要下游再筛选。

**2. 探针用 id，不用名字**
名字是字符串，一个字符不对就报错。位置+签名定身份：`Button@L10:5` 或 `{onClick}#detail`。名字可以变，身份不变。跟进程树 pid 同构。

| 输入 | 传统（名字匹配） | 四原则（id+签名） |
|------|:--:|:--:|
| `Button("确定", onClick={})` | ✅ | ✅ `Button@L10:5` |
| `Button("确定")` 无lambda | ✅ 假阳性 | ❌ 正确拒绝 |
| `CustomBtn("确定", onClick={})` | ❌ 漏掉 | ❌ 漏掉→适配层可扩展 |
| `HED("输出")` | ❌ 已移除 | ❌ 正确拒绝 |

lambda签名是按钮的核心特征——名字会撒谎，签名不会。

**3. 适配层：外部兼容我，不是我兼容外部**
外部格式 → 适配层翻译 → 原生格式。Parser 只吃原生格式，永远不变。适配层变。不是"让别人写符合我的格式"——是有个翻译刚好能做到。适配层权限 > 军师指挥官（进不来就无从调度），但适配层不下命令。

**4. 组件不塌缩：不让一个组件变全能**
父进程不关门。军师不碰外部格式。适配层不调度。每个组件只有一种身份。不是"它做不到"——是"它不该做"。加了不是增强，是埋雷。

### 元模式：动机六问 + 验证

不限于审代码——设计新系统、质疑已有架构之前，先走六问，然后加一步自我推翻。这是面对"看不惯的架构"时的完整诊断链。

---

**第一问：你最看不惯什么？**

不是「哪里需要改进」——是「什么东西让你想掀桌子」。不是数出来的——先觉得不对，再找哪里不对。
- **信号**：用户盯着某行代码停下来，说不清但移不开眼
- **AI行动**：问"这个组件承担了几种身份"。直觉先于分析，不急着给替代方案
- **锚点**：GroupChatManager——读到名字就知道里面有一百个跟「群聊」无关的字段

**第二问：这种感觉什么时候开始的？**

跨越两个项目、有了参照物后才会触发。搜索引擎做了 15 个版本没觉得有问题，写完编译器再回头——到处不顺眼。不是引擎变差了，是脑子里多了一张对照地图。
- **信号**：用户能说出"之前在 X 还好，到了 Y 就受不了"
- **AI行动**：帮用户具象化——"X 里谁在干？Y 里又是谁？差别在哪？"
- **锚点**：Visitor 模式看了三年没觉得有问题，直到问了一句"为什么每个新节点要改 N 个 accept 方法"

**第三问：反感代码还是行业默认？**

决定修复范围。一段代码写烂了——只修不推。整个行业都觉得理所当然——推架构级替代。
- **信号**：用户说"所有人都在用，没人问为什么"
- **锚点**：行号定位 bug——所有人本能想"怎么让行号更准"，没人问"为什么一定要用行号"

**第四问：掀桌先看懂**

不先找替代——先画出组件+身份+数据流。看懂了，直觉自己会来。画出来的那一刻，有些东西不用修——它们自己塌了。
- **信号**：用户描述架构时越来越激动，然后卡住
- **AI行动**：帮画图，不嘴炮替代方案
- **锚点**：修了三天行号的人 vs 问了一句"代码自己就是自己的指纹"走了半小时的人

**第五问：这个组件在干什么？**

身份检查是所有架构颠覆的起点。六问中最常被跳过——每次跳过都导致方向错误。
- **信号**：修了三次以上同一个组件
- **AI行动**：直接问"它承担了几种身份？"
- **锚点**：半年 18 个 bug，反复修——改超时、加重试、换序列化库。直到有人问"它到底在干什么"。第一反应反驳，第二反应沉默，第三反应重写

**第六问：拆的答案是固定模式还是固定动机？**

两个方向，完全不同：
- **为了功能而实现** → 往最近组件塞代码 → 身份越堆越多 → 最终掀桌子
- **能分工而分工** → 先想系统有什么角色 → 每个角色只干一件事 → 角色间联动

一旦发现"放在这里也行吧"的味道——立刻回到第五问。

**第七步：物理碾压 + 自我验证**

思路对不代表方法对。拆完之后加一步自我推翻：

问自己："有没有什么东西能全面碾压我现在的方案？如果有——它是什么？如果没有——我能推翻自己的方法吗？"

**动机**：命名和原创都不是关键。关键是这个创作会不会影响继续创作的动力。有时候思路绝对正确，但当下的方法不一定正确——需要验证它是不是真的比没找到的那个更好。

**锚点**：拆出了五个独立组件。思路对——旧版一个方法六种身份。方法对吗？问自己：如果数据量翻一万倍，哪个组件第一个崩？崩的那一瞬间，是改一个组件还是改全部？如果只改一个——方法对。如果全要改——拆错了，回到第五问。

> **与八步法的关系：** 八步法审已有代码，动机六问+验证审设计动机。审代码走八步，设计新系统先走六问再自我推翻，最后走架构四原则。
## 约束

1. **不分类型**：不预判 bug 属于"空响应"还是"自吃自"。八步走完，bug 自己归位。
2. **不列清单**：不用十刀、不用分类法。八个问题，每条代码过一遍。
3. **先读再问**：先 `read_file` 或 `grep_code` 拿到完整代码，再走八步。不凭记忆审。
4. **只报有问题的**：八步全过没命中 → 这条代码干净，跳过不说。
5. **命中了就说为什么**：每条命中标注命中步骤编号和原因，一行即可。

### 第六步：传染性检查 —「这个值改了，谁还依赖同一个假设？」

免免在 Search Vault v4.4.0 亲自修复的 9 项硬编码松绑中提取的模式：
- 不是某一个硬编码有问题——是多个硬编码互相引用同一套不合理假设
- 改了 default_num，引擎上限没改 → 一边放宽一边卡死
- 改了返回格式字节变大，熔断阈值没跟 → 优化越多越搜不到
- 改了退避逻辑，最后一次白等的假设没更新 → 最后一个引擎还白等几秒

问：改一个值的时候，哪些地方依赖同一个隐含前提？它们会跟着更新吗？

典型命中：MAX_RETRIES=3 改了但空catch没改 → 重试耗尽后静默吞。阈值翻倍了但文档里的数字没更新。

### 第七步：系统沉默 —「这条错误路径上，有没有谁吞了信息？」

不是某一个 catch 块空了——是整条错误链上多个组件共用"不出声"这个默认行为：
- loadSettings 缓存失效 → 不报
- Firecrawl 空结果 → 不报
- keys_add 重复 → 静默失败
- 好几个版本各自静默，排查时连从哪开始断的都不知道

问：从出错点到用户看到反馈，中间经过几个组件？每个组件有没有把错误信息往下传？

典型命中：綦桐网关无前缀消息 → 脑子逻辑吞 → 转发跳过 → 用户什么都不看到。三个组件各自沉默。

### 第八步：硬限制分析 —「如果去掉这个限制，正常流程会不会暴露沉默失败？」

不是 bug 被修好了——是限制在替一个本应报错的沉默路径站岗：
- MAX_RETRIES=3 → 去掉限制，多试一次会触发空 catch 静默失败，不是崩
- board[10][9] → 去掉限制，UI 仍然阻止越界——但限制挡的不是越界，是 getPiece 本身没有边界检查
- botMove 空返回 → 去掉限制，不崩——但会触发"AI 无走法→无限等待"的静默死锁

问：正常操作流程中，如果打破这个限制——暴露的是错误提示，还是暴露了一个根本不该存在的沉默失败？

典型命中：象棋 `getPiece(row,col)` 无越界检查——限制从未被触发不是它写对了，是 UI 拦截了所有可能触发它的输入。綦桐 `MAX_RETRIES=3` 去掉→空 catch 暴露→用户看到空响应。不是限制救了你，是限制藏了 bug。

## 验证：kotlin-head 全版本（v0.5.2→v0.12.9）

免免本人在 kotlin-head 项目中亲自发现的所有 bug，用八步法回溯归类：

| 步骤 | 命中数 | 典型案例 |
|:--:|:--:|------|
| 1 符号解析 | 6 | `<T>`声明vs调用、`by`委托vs循环、`=`赋值vs默认值、suspend误判、byLabel定义、名字冗余 |
| 2 前提检查 | 4 | T!×协程叠加、静默链阶乘、parentAlive无体温计、if语义错 |
| 3 数据流 | 3 | 连锁烫伤、快照漂移、clearSimUiInjections时序竞态 |
| 4 控制流+值域 | 4 | Stage Contract支配域、开门≠返回门、终结语义域、关门硬编码路径 |

**17/17，100%命中。** 八步法不是方法论——是免免大脑编译器前端的八个阶段。动机六问+验证是设计新系统前的七步诊断。审代码走八步，设计走七步。
