# Devflow Design

> 在规格确认后、写代码前做软件设计时使用；也在设计评审被打回、或实现中发现模块边界/接口契约/错误处理需要重新设计时使用。涵盖模块划分、接口契约、错误模型、数据所有权、方案取舍与测试设计。

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

---


# DevFlow 设计

## 总览

设计回答的问题：**用什么结构来满足规格，让代码做对的同时也值得长期持有。** 一份好设计的检验标准：

1. 拿着它，不看代码就能写出测试（接口契约完整）；
2. 实现者不需要再做任何"发明"（结构决策已闭合）；
3. 每个结构决策都能回答「为什么不是更简单的方案」（复杂度有理由）。

**设计分两级**（团队开发流程要求）：

| 级别 | 工件 | 何时需要 | 模板 |
|---|---|---|---|
| 组件级设计 | 组件根下 `features/<id>/component-design-draft.md` → ship 时 promote 到组件根下 `docs/component-design.md`（或团队覆盖路径） | 工作项影响组件边界：对外接口 / 组件依赖 / 状态机 / 组件职责变化，或组件设计基线缺失、过期 | `references/devflow-component-design-template.md` |
| 工作项级设计 | 组件根下 `features/<id>/design.md`（或团队覆盖路径） | 每个工作项（微小修改可按 `using-devflow` 裁剪） | `references/devflow-ar-design-template.md` |

**硬性顺序**：影响组件边界时，必须**先**修订组件设计草稿并经评审与模块架构师确认，**再**写工作项设计；工作项设计只能引用组件基线（功能编号、接口契约、软件单元），不得重新定义组件级架构。组件根下 `docs/component-design.md`（或团队覆盖的组件设计基线）不存在而工作项触及组件边界 → 先补建组件设计，不要在工作项设计里"顺便"定义组件架构。

**设计的第一律：简单性。** 满足当前规格的最少结构就是好结构。每多一层间接、一个抽象、一个配置项，都要付出理解、测试和演进的复利成本。本文所有原则最终都服务于这一条。

## 工作流

1. **读 spec 与组件基线**：先读 plan.md 头部记录的组件根与工件根，或按 `using-devflow` 重新解析；读该组件根下已确认的 `spec.md` 和 `docs/component-design.md`（存在时，或团队覆盖路径），列出本变更触碰的既有模块与新增职责。
2. **判定设计级别**：按 spec 的接口候选契约与影响面判断是否触及组件边界；触及 → 先按组件模板修订 `component-design-draft.md`，确认后再继续。
3. **划分模块职责**（见下文 §职责与边界）。
4. **设计接口契约与错误模型**（见 §接口契约、§错误模型）。
5. **记录方案取舍**：有真实可选方案时写 2-3 个选项的对比；只有一个合理方案时写明其他方案为什么不成立（见 §方案取舍）。
6. **写测试设计**：把 spec 的每条验收标准映射成测试用例表（见 §测试设计）。
7. **更新追溯**：在组件根下 `features/<id>/traceability.md`（或团队覆盖路径）填入每条需求对应的组件设计章节 / 工作项设计章节 / 测试设计用例列。
8. **自检**（文末清单）通过后只表示作者侧设计产物就绪，下一步必须进入 R2 门禁：派发 `devflow-review` 按 design rubric 做**独立评审**并落盘记录（必经节点）；评审 verdict 通过后，attended 模式再把评审记录与 verdict 呈人确认，并更新 plan.md 门禁表。**R2 门禁未通过（含 attended 下未确认）前不进入实现。**

实现中发现设计有误：停下、回来改 design.md（必要时回到组件设计）、重新评审确认，不在代码里悄悄偏离。

## 职责与边界

### 一句话职责测试

每个模块（文件/类/组件）的职责必须能用一句不含「和」「以及」的话说清。说不清，或者句子里有两个动词短语，就是两个职责。

```text
❌ ConfigManager：负责加载配置、校验配置、监听配置变化，以及把变化通知给订阅者
✅ ConfigStore：持有当前生效配置，提供原子读取
✅ ConfigLoader：从存储读取并校验配置块
✅ ConfigNotifier：把配置变化分发给订阅者
```

是否真的要拆成三个文件取决于规模——小就先放一个文件里，但**内部结构按职责组织**，这样将来拆分是搬运而不是手术。

### 按变化理由划分，而不是按技术层次

判断两段代码该不该放一起：**它们是否因同一个理由而变化**。协议格式变化时要改的代码放一起；业务规则变化时要改的代码放一起。反例是「所有回调放 callbacks.c、所有结构体放 types.h」这类按形态分类——一次行为变更要横跨所有文件（霰弹式修改）。

### 耦合的可操作判断

「低耦合」不是感觉，按下面检查：

| 检查 | 坏信号 | 动作 |
|---|---|---|
| 依赖方向 | 底层模块 include 上层头文件；两模块互相 include | 依赖必须单向：上层依赖下层、具体依赖抽象。互相依赖 → 提取第三方共同依赖或用回调/事件反转 |
| 知识泄漏 | 调用方需要知道被调方的内部状态/调用顺序才能正确使用（"先调 init 再调 open，但 reset 之后要重新 init"） | 把时序约束收进模块内部，或用状态机显式拒绝非法顺序 |
| 数据泥团 | 三四个参数总是结伴出现在多个签名里 | 提取成结构体，给这组数据一个名字 |
| 特性依恋 | 一个函数大量读写另一个模块的数据，却几乎不碰自己模块的 | 函数搬到数据所在的模块 |
| 扇出过大 | 一个模块 include / 调用 7-8 个以上其他模块 | 它在做协调器还是上帝模块？拆出子职责 |
| 实现泄漏 | 公共头文件暴露内部结构体字段、私有函数、实现用的宏 | 头文件只放契约；内部细节进 .c / detail 命名空间 |

### 内聚的可操作判断

模块内聚的检验：随机删掉模块里的一个函数，其余函数是否大概率也要跟着改？是 → 内聚好。模块里有一半函数和另一半函数互不引用、不共享数据 → 那是两个模块住在一个文件里。

## 抽象纪律

**抽象必须由真实的重复或真实的变化轴支撑，不由想象支撑。**

- **Rule of three**：第三个真实用例出现前，重复通常比错误的抽象便宜。错误抽象一旦被依赖，纠正成本远高于消除重复。
- **单实现接口是负债**：只有一个实现的 interface/抽象基类，在没有第二个真实实现（不含测试 mock 的伪需求）或明确的稳定契约要求前，就是纯开销。直接用具体类型。
- **可配置性不是免费的**：每个"以后可能要改"的配置项/策略钩子/插件点，现在就要文档、测试和维护。spec 里没有的变化轴不要预留。

```c
/* ❌ 当前只需要写日志到文件，却设计了插件框架 */
typedef struct {
    int (*open)(void *ctx);
    int (*write)(void *ctx, const log_entry_t *e);
    int (*flush)(void *ctx);
    int (*close)(void *ctx);
} log_backend_ops_t;
int log_register_backend(const log_backend_ops_t *ops, void *ctx);

/* ✅ 满足当前规格的最少结构；将来真有第二个后端再提取接口，
   届时已知道两个实现的真实差异，抽象才会切在正确的位置 */
int log_file_open(const char *path);
int log_file_write(const log_entry_t *e);
```

什么时候间接层**值得**引入：跨越所有权边界（隔离第三方库、硬件、协议栈，让它们可替换可仿真）；隔离真实的不稳定源（spec 明确说协议版本会变）；切断循环依赖。

## SOLID 翻译表

SOLID 不是新增流程，也不是为了制造抽象。它是设计与重构时识别变化理由、依赖方向和契约稳定性的速查语言；每条都必须落回 DevFlow 的可检查问题。

| 原则 | DevFlow 判据 | 常见坏信号 | 默认动作 |
|---|---|---|---|
| SRP | 一个模块只有一个变化理由 | 职责句里出现“和/以及”；一次需求变更横跨无关职责 | 拆职责；规模还小时至少按职责组织内部结构 |
| OCP | 真实变化轴有稳定扩展点 | 每加一种类型要改多处 switch / if 链和调用方 | 先确认变化轴真实，再提取表驱动、策略或多态 |
| LSP | 替换实现不削弱接口契约 | 子实现改变错误语义、前置条件或失败后状态保证 | 收紧契约，拆接口；不成立时取消继承/抽象 |
| ISP | 调用方只依赖自己使用的契约 | 公共头文件暴露大而全接口、内部字段、私有宏 | 拆小接口；隐藏内部字段与实现细节 |
| DIP | 高层策略不依赖底层细节 | 上层知道硬件、协议、存储或第三方库调用细节 | 在真实边界引入端口/适配层；拒绝无第二用例的单实现接口 |

## 接口契约

接口契约描述**可观察行为**，不是函数名列表。每个对外接口（公共头文件函数、服务操作、协议消息）写全六项：

1. **输入与前置条件**：参数含义、单位、合法范围、NULL 语义、调用上下文限制（可否在中断里调）
2. **输出与后置条件**：返回值、出参、成功后系统状态的变化
3. **错误语义**：每个错误码什么条件下返回、出错后系统状态如何（见 §错误模型）
4. **副作用**：写了什么状态、发了什么事件、持有了什么资源
5. **并发与时序**（如适用）：线程安全性、可重入性、阻塞行为、超时
6. **兼容性**（modify/remove 时）：旧调用方迁移策略、错误码集变化、废弃计划

```c
/* ❌ 这不是契约，只是签名 */
int mode_set(int mode);

/* ✅ 可冷读的契约（最终落在头文件注释 + design.md）*/
/**
 * 请求切换运行模式。线程安全；不可在中断上下文调用。
 *
 * @param mode  目标模式，必须是 MODE_NORMAL 或 MODE_SAFE。
 * @return OK              已接受请求；下一控制周期内完成切换并发出
 *                         ModeChanged 事件（见 design.md §事件语义）。
 *         ERR_INVALID_ARG mode 非法；内部状态不变，不发事件。
 *         ERR_BUSY        上一次切换尚未完成；调用方应退避重试。
 * 副作用：成功路径更新 mode 状态并向事件队列投递一条 ModeChanged。
 */
int mode_set(mode_t mode);
```

接口设计的取向：**让误用难以编译通过、让正确用法成为唯一明显写法**。用枚举不用魔法 int；语义不同的量用不同类型（`duration_ms_t` 而不是裸 `uint32_t`）；需要配对调用的资源返回句柄并提供成对 API。

## 错误模型

错误处理是设计决策，不是实现时的临场发挥。设计阶段定三件事：

**1. 错误分类**——不同类别的处理策略不同：

| 类别 | 例子 | 策略 |
|---|---|---|
| 调用方编程错误 | 传 NULL、非法枚举、违反调用顺序 | 校验并返回明确错误码（或按项目约定 assert）；不进入降级逻辑 |
| 可预期的运行时失败 | 资源暂不可用、队列满、超时、外部输入非法 | 返回错误码，调用方有明确的恢复/退避路径 |
| 环境/硬件故障 | 存储损坏、外设无响应 | 进入设计好的降级模式，上报诊断事件 |
| 不可恢复的内部矛盾 | 状态机进入"不可能"状态 | 按项目故障策略（安全状态/复位/记录后受控终止） |

**2. 传播策略**：错误在哪一层被翻译、哪一层被处理。底层错误码原样穿透到顶层是泄漏（调用方被迫了解三层之下的细节）；每层都包一遍是噪音。默认：**在模块边界翻译一次**（"flash 写失败" → "配置保存失败"），中间层只透传。

**3. 失败路径的状态保证**：每个可失败操作明确——失败后已发生的副作用是回滚、保留还是半完成？接口契约里写清。「出错后状态未定义」在评审中按 critical 处理。

## 数据所有权与生命周期

每块跨边界的数据（缓冲区、句柄、回调上下文）在设计里明确三个问题：**谁分配、谁释放、指针在调用返回后是否仍可用**。

- 默认取向：**谁分配谁释放**；跨边界传递用复制或显式转移所有权（并在契约里写明）。
- 回调注册类接口必须写明：注销后是否还可能被回调一次（in-flight callback）、ctx 指针的生命周期由谁保证。
- 长生命周期模块持有外部传入指针 = 红色信号，改为复制或在契约中写明调用方必须保证的存活期。

## 方案取舍

只在**真实存在多个合理方案**时写选项对比，每个方案至少回答：改动范围、复杂度、对既有调用方的兼容性、失败时回滚成本、长期维护影响。然后**给出推荐和理由**——列完选项不推荐等于把设计工作推给评审者。

只有一个合理方案时，写一段「为什么不是 X」：X 是评审者最可能问的替代方案（通常是"更简单的做法"或"更通用的做法"）。这不是形式——它强迫你检验自己是否真的考虑过更简单的路径。

伪选项是常见造假：三个方案其实是同一方案的不同措辞，或者两个陪跑方案明显荒谬。评审会按风险信号处理。

## 测试设计

设计文档必须含测试设计章节——这是第一层规格通向第二层 TDD 的桥。把 spec 的每条验收标准映射成用例。**canonical 测试设计表只有一张**：工作项设计模板第 6.1 的 Case ID 汇总表（或等价表），它是 `devflow-tdd` 细化 plan 的唯一入口；第 6.2+ 子表只能展开步骤、mock、风险覆盖，不得引入无法回指到第 6.1 的新用例。

| Case ID | 覆盖需求 | 场景（Given/When/Then 摘要） | 层级 | 预期结果 |
|---|---|---|---|---|
| TC-001 | FR-001 | SAFE 下 SetMode(NORMAL) → 切换+事件 | unit | 返回 OK；周期内 ModeChanged=NORMAL |
| TC-002 | FR-001 | SetMode(非法值) → 拒绝 | unit | ERR_INVALID_ARG；状态与事件无变化 |
| TC-003 | NFR-001 | 1000 次切换延迟测量 | integration | p95 ≤ 5ms（QAS 阈值） |

规则：

- 每条 FR/IFR 至少一个正向 + 一个异常/边界用例；每条 NFR 的 QAS Response Measure 对应一个可量化用例
- `modify` 需求必须有回归用例（旧行为中要保留的部分）；`remove` 必须有删除后语义用例
- 写明每个用例的层级（unit / integration / simulation）与 mock 边界：只 mock 硬件、外部组件、慢速依赖；内部纯逻辑不 mock
- Case ID 必须稳定（`TC-xxx`），并能双向追溯：spec Acceptance → Case ID → plan 任务；组件级测试项如需引用，先映射到工作项级 `TC-xxx`
- 写不出用例的需求 = 规格不可测试 → 回 `devflow-specify`

这张表就是 `devflow-tdd` 的任务来源：实现时逐用例 RED→GREEN→REFACTOR。

## 风险信号

- 工作项触及对外接口/依赖/状态机，却没有组件设计修订（在工作项设计里"顺便"改了组件架构）
- 设计文档里只有结构图和文件清单，没有接口契约和错误语义（实现者仍然要猜）
- 「错误处理：返回错误码」一笔带过（哪些错误码？出错后状态？谁恢复？）
- 出现"以后可能需要"作为某个抽象层/配置项的唯一理由
- 单实现接口、单子类继承、只被调用一次的"通用工具"
- 方案对比是同一方案的三种措辞
- 测试设计只有正向路径，或某条验收标准没有对应用例
- 改了对外接口语义却没有兼容性章节

## 自检清单

- [ ] 设计级别判定正确：触及组件边界时组件设计草稿已先行修订并确认
- [ ] 工作项设计只引用组件基线，未重新定义组件级架构
- [ ] 每个新增/修改模块的职责能用一句话说清；按变化理由组织
- [ ] 依赖方向单向；公共头文件无实现泄漏；无新增循环依赖
- [ ] 每个抽象/间接层指得出真实用例或真实变化轴
- [ ] 每个对外接口契约六项齐全（输入/输出/错误/副作用/并发/兼容）
- [ ] 错误模型三件事已定：分类、传播策略、失败路径状态保证
- [ ] 跨边界数据的分配/释放/存活期已明确
- [ ] 方案取舍有推荐有理由；单方案写了「为什么不是 X」
- [ ] 测试设计覆盖全部验收标准，含异常/边界/回归用例与 mock 边界
- [ ] traceability.md 已填组件设计章节 / 工作项设计章节 / 测试设计用例列
- [ ] 适用的语言规范（`<language>-coding-standards`）与领域开发技能已读取并体现在契约里；领域技能按各自 description 触发，不依赖固定枚举

## 支撑参考

| 文件 | 用途 |
|---|---|
| `references/devflow-ar-design-template.md` | 工作项级设计（design.md）模板，含「高质量设计增补」章节 |
| `references/devflow-component-design-template.md` | 组件级设计模板，含「高质量设计增补」章节 |

