AI编程(三) - 用 Agent 做嵌入式代码架构审查
背景
在嵌入式固件开发中,代码审查(Code Review)往往依赖资深工程师的经验积累——他们需要在脑中同时追踪状态机流转、I2C 分片逻辑、寄存器写入顺序,以及 uint8_t 移位这类细节 bug。这类工作耗时且高度依赖个人经验。
本文记录了使用 Claude Code 的 自定义 Agent 对电量计驱动进行架构审查的完整过程,包括:Agent 的调用方式、输出报告的关键发现,以及如何在一个主任务中调用多个 Agent 并行工作。
一、arch-review-reporter Agent 是什么
arch-review-reporter 是我自定义的 Agent,通过 Claude Code 的 Agent 框架创建,存放在 ~/.claude/agents/arch-review-reporter.md。它不是 IDE 内置功能,而是由工程师根据自己的审查需求定制的专用角色。
与直接问 Claude “帮我看看这段代码有没有问题”的区别:
| 方式 | 上下文 | 输出格式 | 记忆 |
|---|---|---|---|
| 直接问 Claude | 共用主对话 context | 随意,依赖提问质量 | 无 |
| 自定义 Agent | 独立 context window | 按 Agent 定义的结构输出 | 有持久记忆(见第二节) |
二、如何创建自定义 Agent
使用claude的 /agent命令创建自定义agent
如图引导页面会要求输入提示词说明自定义agent要做什么 ( 我的提示词是做代码架构分析,审查风险,考虑可优化方向 )
然后选择模型和访问权限等能力,最终输出agent的markdown配置

2.1 文件位置
自定义 Agent 是一个 Markdown 文件,放在:
1 | ~/.claude/agents/<agent-name>.md # 全局 Agent,所有项目可用 |
arch-review-reporter 创建为全局 Agent,路径是:
1 | C:\Users\cursorhu\.claude\agents\arch-review-reporter.md |
2.2 文件结构
Agent 定义文件由两部分组成:YAML frontmatter(元数据)和正文(系统提示)。
YAML frontmatter:
1 |
|
关键字段说明:
name:Agent 标识符,用于@agent-<name>触发description:触发条件描述,Claude 根据这段文字判断何时自动调用此 Agent。写好 description 是关键——它决定 Agent 是否被正确路由model:指定运行此 Agent 的模型(sonnet/opus/haiku),不填则继承主对话模型memory:设为user表示开启用户级持久记忆(跨项目、跨对话有效)
正文:即这个 Agent 的系统提示(system prompt),定义它的角色、工作流程和输出格式。
2.3 arch-review-reporter 的系统提示设计
正文定义了一个四阶段工作流程:
1 | 第一阶段:代码理解 |
输出规范要求每个问题必须定位到具体代码行号,不允许泛泛而谈,代码示例只能从实际源码中提取,不编造示例。
2.4 触发 Agent 的两种方式
方式一:显式调用(@前缀)
在对话框输入:
1 | @agent-arch-review-reporter 分析当前项目 |
Claude 直接路由到该 Agent,无需判断。
方式二:隐式触发(自然语言)
1 | 帮我做代码架构审查 |
Claude 根据 description 字段判断匹配度,自动调用 arch-review-reporter。description 中包含了三个具体示例(<example> 标签),帮助 Claude 精准识别触发场景。
三、Agent 的持久记忆机制(Memory)
3.1 为什么需要记忆
没有记忆的 Agent 每次都从零开始,无法积累对项目的认知。开启记忆后,Agent 可以记住:
- 项目采用的架构模式和设计约定
- 重复出现的代码风险类型(如这个项目里
const数组被写入的问题) - 已确认的技术债务和待改进项
- 用户的审查偏好和反馈
3.2 记忆的存储位置
memory: user 表示用户级记忆,存储路径:
1 | ~/.claude/agent-memory/arch-review-reporter/ |
每个记忆文件使用标准 frontmatter 格式:
1 | --- |
MEMORY.md 是索引文件,始终加载到 Agent 的 context 中(超过 200 行会截断),因此只存指针,不存内容:
1 | - [嵌入式 const 写入风险](embedded-const-write-risk.md) — MCU 项目中 static const 被 memcpy 写入的 HardFault 风险 |
3.3 记忆的生命周期
首次审查时记忆目录为空,Agent 完成审查后会将本次发现的模式和约定写入记忆文件。下次审查同一类项目时,Agent 会先读取 MEMORY.md,带着历史认知开始审查,输出的建议会更精准。
用户级记忆(memory: user)跨项目有效。如果切换到另一个嵌入式项目,Agent 仍然记得”嵌入式 C 项目要重点检查 const 写入”这条经验。
四、调用方式总结
| 场景 | 操作 |
|---|---|
| 快速触发(知道 Agent 名) | @agent-arch-review-reporter 分析当前项目 |
| 自然语言触发 | “帮我做代码架构审查” / “上线前审查一下” |
| 需要与审查结果继续对话 | Agent 完成后回到主对话追问 |
| 单函数级 bug | 直接问 Claude,无需 Agent |
| 多模块跨文件分析 | 必须用 Agent(保护主 context) |
五、审查对象:SD77428 电量计驱动
5.1 模块定位
fuel_gauge_sd77428.c 是 SD77428 电量计芯片的 MCU 侧驱动,核心职责:
- 通过 I2C 向芯片下发电池模型参数(level0~level8 分级参数体系)
- 读取 SOC、电压、电流、温度等实时信息
- 管理充电状态和省电模式(suspend/resume)
- 通过统一接口
sFuelGaugeInterface对上层 BMS 暴露功能
5.2 架构核心:分段式异步状态机
这是驱动最重要的设计特征。由于 MCU 主循环不能阻塞等待 I2C,所有 I2C 操作被拆分为多个 step:
1 | // 典型分段式函数骨架 |
sd77428_loop() 以 10ms 为节拍驱动 g_loop_list[],初始化流程由 g_init_list[] 中 8 个步骤按序完成。参数下发通过 g_download_list[] 描述符表批量执行,具有良好的可扩展性。
六、架构审查报告关键发现
Agent 将问题分为三级,共识别 4 个高风险(🔴)、6 个中风险(🟡)、4 个低风险(🟢)。
6.1 🔴 高风险:向 const 数组执行 memcpy 写入
位置:fuel_gauge_sd77428.c:1101,函数 sd77428_get_model_params
1 | // g_rc_tab 声明为 static const,但此处写入 |
风险:const 数据在 MCU 上通常链接到 Flash(ROM)段。运行时写入会触发 HardFault,或在某些平台上静默失败——模型参数实际未更新,但代码认为已更新,电量计算使用错误的 RC 表,SOC 显示严重偏差。
修复:去掉 const 修饰。
1 | static uint8_t g_rc_tab[][XAxis] = { ... }; |
6.2 🔴 高风险:状态机死区,lut_step 永远无法推进
位置:fuel_gauge_sd77428.c:1436-1448,函数 sd77428_check_lut_ocvsoc
1 | } else if (1 == g_sd77428.lut_step) { |
风险:初始化流程卡在 E_STEP_CHECK_LUT_SOC 阶段,电量计永远无法进入正常 loop。
修复:补充 else 分支,正常情况下直接推进到终态:
1 | } else if (1 == g_sd77428.lut_step) { |
6.3 🔴 高风险:uint8_t 移位到符号位,未定义行为
位置:fuel_gauge_sd77428.c:835-841,函数 sd77428_pack_rd_data
1 | // per_memb_size == sizeof(uint32_t) 时,slave_idx 最大为 3 |
C 标准规定:对有符号整数执行左移到符号位是未定义行为(UB)。在高优化级别下编译器可能产生不可预测的结果,导致 32 位寄存器读回值的最高字节解码错误,电压/电流等参数静默偏差。
修复:显式转型为 uint32_t:
1 | p_rd_u32[rd_idx] |= |
6.4 🔴 高风险:芯片版本检测精确匹配,固件升级后驱动失效
位置:fuel_gauge_sd77428.c:1152,函数 sd77428_detect_chip_id
1 | // FW_VER_A3_MIN 命名暗示"最低版本",但逻辑是精确匹配 |
修复:
1 | if (chip_id == chip_list[idx].chip_id) { |
6.5 🟡 中风险:design_fcc - 600 无符号下溢
位置:fuel_gauge_sd77428.c:1110-1112
1 | uint32_t reg_val2 = g_model_sample.design_fcc - 600; // 小容量电池时下溢 |
小容量电池(design_fcc < 600)时,reg_val2 下溢为 0xFDxx,写入芯片的 mindfcc 为异常大值,DFCC 算法行为失控。
修复:加下界保护:
1 | uint32_t reg_val2 = (design_fcc > 600) ? (uint32_t)(design_fcc - 600) : 0u; |
6.6 🟡 中风险:Flash 频繁写入(单位不一致导致每次都触发)
位置:fuel_gauge_sd77428.c:1598
batt_rsoc 经过 sd77428_batt_convert 映射后是显示值(0100 的百分比),但 Flash 中存储的历史值是原始寄存器值(01000 的千分比)。两者单位不一致,每次比较都不相等,每次都触发 Flash 写入,加速 Flash 磨损。
6.7 设计层面:全局状态结构体职责过重
sSd77428Run(g_sd77428)同时承载 20+ 个字段,其中 read_idx/write_idx/verify_idx 等实质是各子函数私有的局部状态。这导致:
- 任意函数都能意外修改其他函数的状态字段
- 无法在同一帧内连续调用两次
sd77428_read_bytes - 无法支持多实例(多颗电量计芯片)场景
建议将子状态提取为函数内 static 局部结构体,仅在 g_sd77428 中保留跨函数共享的字段(chip_type、addr、reinit_meter 等)。
七、如何在对话中触发多个 Agent 并行工作
单个 Agent 一次只能专注一件事。当任务需要同时分析多个维度时,可以用一句话让 Claude 并行启动多个 Agent。
7.1 用户侧:一句话触发并行
不需要编辑 CLAUDE.md,不需要写任何配置。直接在对话框描述多个独立任务,Claude 会自行决定并行执行:
1 | 请同时做两件事: |
或者明确要求并行:
1 | 请并行启动多个 agent 分别完成: |
Claude 收到后,会在同一次响应中并发调用多个 Agent,你能看到多个任务同时推进,总耗时约等于最慢的单个 Agent。
7.2 幕后机制:Claude 是编排者
多 Agent 并行的本质是:Claude 作为”主任务编排者”,在一次响应中同时发出多个子 Agent 请求。下面是 Claude 内部执行的逻辑(不是用户需要写的代码,仅作原理说明):
1 | # Claude 内部编排逻辑(原理示意,非用户操作) |
用户只需用自然语言描述任务,Claude 自动完成调度。
7.3 并行 Agent 的典型场景
1 | 用户一句话:对电量计固件做全面评估 |
7.4 串行 vs 并行的选择
Claude 会根据任务依赖关系自动判断,也可以在提示词中明确指定:
| 模式 | 适用场景 | 提示词示例 |
|---|---|---|
| 并行 | 任务之间无依赖 | “同时/并行做以下几件事” |
| 串行 | B 依赖 A 的结果 | “先…,完成后再…” |
| 混合 | 第一阶段并行收集,第二阶段综合 | “先并行读取各模块,然后汇总生成报告” |
7.5 后台运行
对于耗时长、不需要立即结果的任务,可以要求后台执行:
1 | 请在后台搜索整个项目中所有使用 STAY_PUT 返回值的函数,完成后通知我 |
Claude 会启动后台 Agent,主对话继续响应,任务完成后收到通知。
八、工程实践建议
何时启动架构审查 Agent
- 合并大型特性分支前(代替 PR review 的第一道过滤)
- 重要版本发布前(替代 checklist 式人工走查)
- 新人接手陌生模块时(快速建立风险地图)
- 移植到新平台前(识别平台相关的潜在问题,如 Flash 写 const)
Agent 输出的局限性
Agent 基于静态代码分析,无法替代以下工作:
- 运行时行为验证(实际挂载电池的 SOC 曲线测试)
- 边界条件的硬件级验证(真实 Flash 写保护触发)
- 需要跨 repo 上下文的分析(依赖外部 HAL 层的具体实现)
最佳实践是:Agent 负责提速和覆盖面,工程师负责判断优先级和验证修复效果。
总结
本文展示了用 arch-review-reporter Agent 审查约 1800 行嵌入式 C 代码的完整流程。Agent 在单次运行中识别出:
- 1 个运行时崩溃风险(向 Flash 段写入 const 数据)
- 1 个状态机死锁(初始化永远卡住)
- 2 个 C 语言 UB(符号位左移、无符号下溢)
- 多个设计层面的技术债(全局状态集中、Flash 频繁写入等)
这些问题中,const 写入和状态机死区两项在量产环境下会直接导致设备不可用,而单靠人工 review 在 1800 行代码中发现它们需要相当的经验积累。
多 Agent 并行模式进一步将这类分析从”串行耗时”变为”并行提速”,是复杂嵌入式项目代码质量管理的实用工具。用户侧无需任何配置,用自然语言描述多个独立任务,Claude 自动完成并行调度和结果汇总。