[TOC]

在这里插入图片描述

嵌入式新项目与重构中使用 AI 辅助实现的真正风险:不是 AI 越界,而是工程师逐渐把方向盘交出去

面向:嵌入式 C/C++、MCU、RTOS、驱动框架、BSP 适配、硬件抽象层、底层库重构。
核心观点:在 AI 辅助新项目或大规模重构中,最危险的不是一开始没有写边界,而是多轮执行后工程师疲劳、焦虑或不耐烦,主动放开边界,让 AI 以“更通用、更完整、更兼容”为名接管需求解释和架构扩张。


结论

在嵌入式代码的新项目和重构中,AI 带来的主要风险并不是“它会不会生成一段错误代码”。错误代码反而容易被编译器、静态检查、单元测试、硬件验证或工程师经验发现。更隐蔽、更容易造成长期损失的风险是:工程师一开始知道自己要做什么,也写了目标、边界和约束,但在多轮 AI 实现、AI 审核、AI 修改之后,逐渐把这些边界放开了。

这种放开往往不是一次性发生的。它通常以非常合理的形式出现:

  • “这是新项目,可以不兼容旧接口。”
  • “既然以后可能支持更多芯片,那就先兼容一下。”
  • “AI 说这个特殊功能不支持会有问题,那就加上。”
  • “这轮先让 AI 改,改完再审。”
  • “既然另一个模型也提出了类似方向,那应该确实要处理。”
  • “先把它做完整,以后再精简。”

工程上真正危险的点就在这里。AI 并没有强行越权,越权权限是工程师自己给的。 一开始写给 AI 的边界并不是没有价值,但边界本身不会自动生效。边界真正约束的对象不是 AI,而是工程师自己:当 AI 提出越来越多“看起来合理”的建议时,工程师是否还能坚持“当前阶段只解决当前问题”。

在嵌入式领域,这个问题会被放大。因为嵌入式框架天然面对大量差异:芯片系列差异、外设能力差异、BSP 初始化差异、RTOS 上下文差异、DMA/Cache/中断差异、低功耗差异、Flash/RAM 约束差异。AI 很容易把“通用”理解为“尽可能覆盖差异”,而工程上更常见的通用应当是“公共层只承担稳定共性,差异留在底层或私有扩展中”。

本文的核心判断可以压缩为一句话:

AI 可以参与生成、解释、归纳和交叉检查,但工程师不能把“我要做什么”交给 AI。新项目和重构中,最先失控的不是代码,而是工程师对目标的坚持。


1. AI 辅助开发的风险,首先是人的风险

很多关于 AI 编程的讨论,会把重点放在模型能力上:模型是否会幻觉、是否会生成漏洞、是否能理解上下文、是否能通过测试、是否能处理复杂工程。这个方向没有错,但在新项目和重构场景下,它不够完整。

因为工程师并不是被动接收 AI 输出的人。工程师会写提示词,会选择是否接受建议,会决定是否扩大范围,会决定是否让 AI 继续改,会决定是否因为“不放心”再找另一个模型复审。也就是说,AI 辅助开发的结果并不只取决于 AI 的能力,还取决于工程师在多轮交互中的判断质量。

这和 NIST AI Risk Management Framework 的思路是吻合的。NIST 将 AI 风险管理放在 AI 产品、服务和系统的设计、开发、使用和评估过程中,而不是只看模型本身。也就是说,风险来自系统化使用方式,而不仅是某一次输出。[^nist-ai-rmf]

对于嵌入式工程师来说,这个判断非常实际。一个 ADC 驱动框架、一个参数管理模块、一个通信协议栈适配层、一个 RTOS 组件,不会因为某一行 AI 代码写得漂亮就变成好设计。它是否好,取决于它是否符合当前项目目标、硬件约束、维护成本、验证条件和未来扩展方式。

因此,AI 辅助开发最容易被低估的风险不是“AI 没有边界感”,而是:

  1. 工程师一开始有边界,但没有持续维护边界;
  2. 工程师知道目标,但在多轮建议中逐渐改变目标;
  3. 工程师知道哪些需求当前不做,但在 AI 的“完整性提醒”下逐个接受;
  4. 工程师原本只想完成一个简洁框架,却被带入平台化、全芯片覆盖、全特性抽象;
  5. 工程师因为 diff 太大、上下文太长、审查太累,开始用 AI 的“没有发现严重问题”替代自己的理解。

这不是 AI 单方面造成的失败,而是人和 AI 组合后的失败。AI 给出了建议,工程师放弃了筛选。


2. “已经写了约束”并不等于约束仍然有效

在比较规范的 AI 辅助开发中,工程师通常会在任务开始时写清楚要求。例如:

  • 只做基础功能;
  • 不追求全系列芯片覆盖;
  • 不引入复杂配置模型;
  • 不修改对外 API;
  • 不改变数据结构;
  • 不做未来可能需要但当前未验证的功能;
  • 不为了兼容边缘功能而污染公共框架。

这些要求本身是正确的。但实际问题是,项目推进到第三轮、第五轮、第十轮时,工程师可能已经不再严格执行这些要求。

第一次让 AI 审核时,工程师可能会说:

请检查是否存在明显缺陷,不要扩大需求。

第二次修改后,工程师可能会说:

可以从更通用、更合理的角度再审一下。

第三次 AI 报出某个芯片差异后,工程师可能会说:

这是通用框架,那就考虑兼容一下。

第四次发现兼容后 API 不够用,工程师可能会说:

可以允许不兼容重构,把结构重新设计得更完整。

第五次另一个 AI 模型提出 DMA、触发源、多 ADC 同步、内部通道等问题时,工程师可能会说:

那这些边缘特性也要预留。

到这里,最初的约束已经实质失效。不是 AI 忽略了约束,而是工程师在后续提示词中重新授权了 AI。最终 AI 做出的复杂设计,从交互记录看,甚至是“遵守工程师最新指令”的结果。

这也是为什么在 AI 辅助重构中,单纯强调“提示词写清楚”不够。提示词不是合同,它会被后续提示词覆盖。上下文中的目标不是永恒不变的,它会被工程师的焦虑、疲劳、临时想法和模型建议逐步稀释。

真正要控制的不是某一句提示词,而是工程师自己在多轮执行中是否还记得:

这次到底要解决什么问题,哪些问题即使真实存在也不是这次要解决的。


3. “允许不兼容重构”是一个高风险授权

在新项目或重构中,工程师经常会写“可以不兼容旧接口”“可以重新设计”“可以重构得更合理”。这类表述对人类工程师来说,通常只是为了避免 AI 被旧代码束缚。但对 AI 来说,它往往会被理解为:

  • 可以改 API;
  • 可以改数据结构;
  • 可以改模块边界;
  • 可以改配置模型;
  • 可以改调用责任;
  • 可以把隐式逻辑显式抽象出来;
  • 可以把未来可能出现的场景提前纳入设计。

如果当前任务只是修一个接口、整理一个驱动、抽出一层公共代码,这种授权可能过大。

“允许不兼容”本身不是错误。在某些情况下,它甚至是必要的。例如旧接口已经无法表达新需求,旧数据结构导致状态混乱,旧模块边界让 bug 无法局部修复。这时不兼容重构是合理工程决策。

但问题在于,不兼容重构不是一个给 AI 的自由许可,而应当是工程师基于明确原因作出的局部决策。

不兼容应当回答几个问题:

  1. 为什么必须不兼容?
  2. 不兼容发生在哪一层?
  3. 哪些接口允许变,哪些接口不允许变?
  4. 变更后减少了什么复杂度?
  5. 有没有新增配置、状态或平台差异?
  6. 当前硬件和 BSP 是否能验证?
  7. 如果这个设计错了,回退成本有多大?

如果这些问题没有先由工程师回答,而是直接对 AI 说“允许不兼容重构”,那么 AI 很可能会把“不兼容”用在最方便它完成抽象的位置,而不是最符合工程目标的位置。

Google 的代码审查实践中有一个很值得借鉴的观点:如果一个变更添加的是审查者不希望系统拥有的功能,即使代码设计得不错,也可以拒绝;代码审查追求的是整体代码健康,而不是把每个看起来更完善的建议都合入。[^google-review-standard] 这个观点放到 AI 辅助开发里非常关键:AI 提出的建议即使“技术上成立”,也不代表它属于当前系统。


4. 多轮 AI 审核会制造一种“还有问题”的幻觉

工程师让 AI 审核 AI 写的代码,或者让不同模型交叉审核,是很自然的做法。问题不在于 AI 审核本身,而在于工程师如何理解 AI 审核结果。

不同 AI 模型的审查角度本来就会不同。同一个模型,在提示词、上下文、温度、输入范围不同的情况下,也会输出不同的意见。一次说“没有严重问题”,下一次说“建议增加某某保护”,再下一次说“架构可以进一步抽象”,这并不一定表示代码确实存在越来越多问题。它可能只是模型在不同角度下寻找可评论点。

如果工程师把每一轮 AI 审核结果都视为“必须处理的问题”,就会进入一种循环:

  1. 代码量大,工程师只能粗看;
  2. AI 审核提出若干点;
  3. 有些点看起来确实合理;
  4. 工程师让 AI 修改;
  5. diff 变大;
  6. 工程师更难完整理解;
  7. 工程师更依赖 AI 再审;
  8. AI 再提出新的角度;
  9. 工程师继续修改;
  10. 直到 AI 暂时不再报严重问题。

这时,工程师获得的不是确定性,而是一种虚假的安全感:

“AI 已经审了多轮,最后没有问题,所以应该可以。”

但事实可能是:

  • AI 已经把代码改到它自己更容易解释的形态;
  • 原始目标已经变化;
  • 公共层多了很多不必要的概念;
  • 特殊芯片能力侵入了通用 API;
  • 配置项数量超过普通 BSP 适配者能理解的范围;
  • 工程师不再能用简单语言说明框架职责;
  • 测试只覆盖了 AI 当前设计下的 happy path。

这类风险和 OWASP 在 LLM 应用安全中提到的 “Overreliance” 与 “Excessive Agency” 有相似性:对 LLM 输出缺少关键评估会损害决策,给 LLM 过多自主能力也可能带来非预期后果。[^owasp-llm] 在嵌入式代码场景中,所谓 agency 不一定是让 AI 自动提交代码,也包括在设计判断上把“是否该做”交给 AI。


5. 工程师疲劳后,最容易放弃的是“拒绝权”

AI 审查意见最难处理的地方在于,它们往往不是明显错误。它们通常具有以下特点:

  • 有真实芯片或真实场景作为理由;
  • 有一定工程经验味道;
  • 能指出当前实现确实没有覆盖的情况;
  • 文字表达得很确定;
  • 给出的修改方案看起来完整;
  • 修改后代码仍然能编译;
  • 如果不做,工程师会担心以后踩坑。

例如 AI 说:

该 ADC 框架没有考虑某系列 STM32 的 ADC 差分输入能力,建议在公共配置结构中增加差分模式字段。

这句话本身未必错。某些芯片确实有差分输入。但工程师此时要判断的不是“这个特性是否存在”,而是:

当前框架是否应该承担这个特性?

这两个问题完全不同。

AI 擅长回答“某个特性是否可能存在”“某个实现是否缺少保护”“某个边界是否没有覆盖”。但 AI 不天然知道当前项目的阶段性目标、BSP 维护人员能力、测试资源、产品期限、芯片选型范围、团队历史包袱和长期维护策略。

当工程师疲劳时,最容易丢掉的不是编码能力,而是拒绝能力。也就是:明明知道一个建议“以后可能有用”,但仍然能够说:

当前不做。

这是 AI 辅助开发中最重要的人类能力。

如果工程师没有这种拒绝权,AI 每发现一个边缘情况,框架就会长出一个字段;每发现一个芯片差异,公共层就会长出一个分支;每发现一个未来可能需求,接口就会长出一个抽象。最后项目不是被 bug 拖垮,而是被“合理建议”拖垮。


6. ADC 框架案例:从简洁通用到复杂平台层

以 STM32 多 BSP ADC 框架为例,最初目标可能很清楚:

  • 不同 BSP 能注册 ADC 设备;
  • 上层可以通过统一接口读取通道值;
  • 支持基本采样参数;
  • 底层负责芯片寄存器、HAL/LL 或裸寄存器差异;
  • 公共框架保持小而稳定;
  • 特殊能力不强行进入公共 API。

这个目标适合很多 MCU RTOS 场景。因为 RTOS 设备框架往往需要提供稳定、轻量、易适配的公共接口,而不是覆盖所有芯片能力。很多特殊能力可以留给 BSP、底层私有配置、控制命令或扩展指针。

但 AI 审核时可能会沿着“通用性”继续展开:

6.1 第一轮:发现芯片差异

AI 可能指出:

  • 不同 STM32 系列 ADC 分辨率不同;
  • 某些系列通道编号映射不同;
  • 某些系列采样时间枚举不同;
  • 某些系列校准方式不同;
  • 某些系列内部通道启用方式不同。

这些都是真实问题。工程师如果没有踩刹车,就会让 AI 把这些差异逐个加入公共配置。

结果是公共结构体开始变大:

  • resolution
  • sample_time
  • calibration_mode
  • internal_channel_enable
  • channel_rank
  • adc_clock_source
  • data_align
  • oversampling
  • trigger_source

字段越多,框架看起来越完整。但对普通 BSP 来说,适配成本也越高。很多字段并不是所有芯片都有意义;没有意义就要定义默认值、无效值、兼容行为、错误码和文档说明。

6.2 第二轮:公共层开始理解特殊特性

当字段增加后,AI 可能继续建议:

  • 如果芯片不支持 oversampling,应在公共层返回错误;
  • 如果芯片支持 injected channel,应增加独立 API;
  • 如果支持 DMA,应在框架层管理缓冲区;
  • 如果支持模拟看门狗,应增加阈值配置;
  • 如果支持外部触发,应抽象触发源;
  • 如果多 ADC 同步,应抽象 master/slave 关系。

到这里,公共框架已经不再只是“统一基本 ADC 读数”。它开始理解越来越多硬件特性,并试图替底层做决策。

这一步很危险。因为公共层一旦理解某个特殊特性,就必须承担:

  1. 语义定义;
  2. 参数合法性;
  3. 错误返回;
  4. 不支持时的行为;
  5. 与其他特性的组合关系;
  6. 文档和测试;
  7. 不同芯片族的差异解释。

很多时候,框架复杂度并不是由代码行数决定,而是由“公共层承诺的语义数量”决定。

6.3 第三轮:为了验证 AI 建议,又继续增加抽象

当公共层承诺变多后,AI 审查会继续提出:

  • 某配置没有 feature flag;
  • 某字段没有默认值;
  • 某芯片不支持该组合;
  • 某错误码不够细;
  • 某 API 缺少异步版本;
  • 某 DMA buffer 生命周期不清楚;
  • 某配置应改成表驱动;
  • 某些宏应按芯片系列拆分。

这些建议也可能合理。但它们是在前面复杂化之后产生的新问题。也就是说,AI 先帮助你增加了复杂度,然后又帮你发现复杂度带来的问题。

如果工程师继续让 AI 修,框架会越来越像一个完整 ADC 中间层。它可能支持很多芯片,但每个 BSP 的适配者都要理解复杂配置模型;它可能有很多安全检查,但每个新特性都要维护组合规则;它可能看起来专业,但已经偏离“简洁、通用、易适配”的最初目标。

6.4 更合理的方向:公共层只承诺共性,特殊能力留给底层和用户

对这类 ADC 框架,更稳妥的设计通常不是公共层覆盖所有能力,而是:

  • 公共层提供最小稳定能力,例如 open/close/read/control;
  • 公共配置只包含跨芯片稳定存在的概念;
  • BSP 私有配置由底层结构体承载;
  • 特殊能力通过 privuser_data、芯片私有 ops、控制命令或底层句柄暴露;
  • 公共层不解释它无法统一验证的硬件语义;
  • 文档明确说明哪些能力属于框架共性,哪些属于 BSP 私有扩展。

这样做不是“偷懒”,而是嵌入式通用框架常见的边界控制。公共层越小,越容易稳定;底层越自由,越容易适配特殊芯片;用户如果确实需要差分输入、同步触发、DMA 环形采集,可以直接进入芯片私有配置或底层接口,而不是要求公共框架统一抽象所有细节。

这也是 Google 代码审查中“警惕过度工程”的思想在嵌入式框架里的具体体现:不要为未来猜测的问题提前增加泛化能力,应优先解决当前已经确定的问题。[^google-review-lookfor]


7. “AI 说得对”不等于“这次应该做”

嵌入式工程里有很多问题,AI 说出来是对的,但不应该在当前层处理。

例如:

AI 提出的点 可能正确的事实 工程上真正要问的问题
某芯片没有某 ADC 特性 可能正确 当前公共框架是否承诺这个特性?
某芯片支持差分输入 可能正确 差分输入是否应进入公共 API?
DMA 采集效率更高 可能正确 当前任务是否需要异步采集和缓冲生命周期管理?
多 ADC 同步触发应抽象 可能正确 当前 BSP 是否有验证条件?
配置项应更完整 可能正确 完整配置是否会增加普通用户成本?
不兼容重构后更统一 可能正确 统一后是否减少了整体复杂度?
建议增加错误码 可能正确 调用者是否能基于这些错误码采取不同动作?

这张表背后的核心是:事实判断和设计判断必须分开。

AI 可以帮助发现事实:某个芯片有某个限制,某个调用路径缺少检查,某个异常场景没有处理,某个状态组合可能出错。

但是否要改公共架构,是另一个问题。它需要结合当前项目阶段、维护成本、验证能力、接口稳定性、后续 BSP 接入难度。这个判断不能由 AI 自动完成。

近期关于 AI 生成代码质量的研究也强调,AI 生成代码质量受人类因素、AI 系统特性和人机交互动态共同影响,其中任务定义、提示和开发者专业能力都会影响结果。[^ai-quality] 换句话说,AI 输出不是一个孤立技术结果,它与工程师如何定义任务、如何接受建议直接相关。


8. 大 diff 会削弱理解,而不是增加确定性

AI 很擅长在短时间内生成大量代码。对于新项目和重构,这种能力很诱人。几十个文件、几千行 diff、多个抽象层、文档和测试一起生成,看起来效率很高。

但嵌入式工程的真实风险往往藏在细节中:

  • 中断上下文能不能调用;
  • 是否可能阻塞;
  • DMA buffer 生命周期是否清楚;
  • Cache invalidate/clean 是否匹配;
  • 寄存器写入顺序是否符合手册;
  • RTOS 对象是否在正确上下文创建和销毁;
  • 错误码是否会被上层误判;
  • 条件编译是否覆盖正确芯片;
  • 默认配置是否会增加 RAM/Flash;
  • 是否把 BSP 私有行为泄漏到了公共层。

大 diff 会让这些细节更难审查。工程师很容易只能看整体结构,然后把细节交给 AI 再审。问题是,AI 审查细节时也受上下文长度、输入质量和目标约束影响。更糟的是,当 diff 已经很大时,AI 提出的每个修复又会继续扩大 diff。

这里会产生一种“验证债”。代码生成越快,工程师需要理解和验证的债务越大。某些研究在科学编程场景中也观察到,用户可能用“接受了多少生成代码”衡量生产力,而不是用验证质量衡量生产力;接受的代码量越大,越容易产生验证不足的担忧。[^more-code-less-validation]

在嵌入式项目里,这种验证债更难偿还。因为很多问题不是跑一个脚本就能发现,需要真实硬件、示波器、逻辑分析仪、RTOS 压力场景、低功耗切换、异常中断时序和芯片手册交叉验证。

因此,大 diff 不应被视为“AI 做得多”,而应被视为“工程师理解负债增加”。


9. 多模型审查不是越多越安全

有些工程师会在不放心时让多个 AI 模型审查同一份代码。这个方法有价值,但不能误用。

多模型审查适合做:

  • 找明显遗漏;
  • 从不同角度发现风险;
  • 对复杂代码做交叉解释;
  • 提供候选问题列表;
  • 帮助工程师准备人工审查重点。

但多模型审查不适合做:

  • 最终架构裁决;
  • 需求范围扩张;
  • API 稳定性决策;
  • 是否支持边缘特性;
  • 是否不兼容重构;
  • 是否把特殊能力放入公共层。

多个模型输出不同意见是正常现象。它并不必然说明代码还有很多缺陷,也不必然说明哪个模型更懂项目。很多时候,只是因为模型在寻找“可改进点”。而软件永远有可改进点。

Google 的代码审查材料里强调,审查应关注整体代码健康,而不是追求完美;不重要的意见应标识为 nit 或可选项。[^google-review-standard] 这对 AI 审查尤其重要。AI 反馈也必须有“必改、可选、拒绝”的区分。如果所有 AI 意见都被当作必改,代码会被审查意见拖着走。

更具体地说,当不同模型给出不同建议时,工程师不应问:

哪个模型说得更对?

而应问:

哪些建议和我当前目标相关?哪些建议即使正确,也不属于这次?

这两个问题的差别,决定了工程师是在使用 AI,还是被 AI 带着重构。


10. 嵌入式项目中最容易被 AI 扩大的责任

在嵌入式框架里,有几类责任特别容易被 AI 从底层或用户侧拉到公共框架中。

10.1 硬件特殊能力

例如 ADC 差分输入、oversampling、injected channel、模拟看门狗、多 ADC 同步、定时器触发、DMA 环形采集。

这些能力真实存在,但并不一定属于公共框架。公共框架如果承诺它们,就必须在所有芯片、所有 BSP、所有配置组合里定义行为。很多时候,更合理的是让 BSP 私有层暴露这些能力。

10.2 芯片兼容逻辑

AI 可能建议公共层根据芯片系列做条件编译或 feature flag。例如:

1
2
3
4
5
#if defined(SOC_SERIES_STM32F4)
...
#elif defined(SOC_SERIES_STM32H7)
...
#endif

这类写法短期能解决问题,但长期会让公共层变成芯片差异表。公共层越理解芯片系列,越难保持独立。

10.3 配置合法性组合

AI 很喜欢补充参数检查。例如某字段不能为 NULL、某模式不支持某触发、某采样时间不能和某分辨率组合。

参数检查本身必要,但如果公共层检查的是芯片私有语义,就说明责任边界已经上移。公共层开始替底层判断硬件能力,这通常会带来大量维护成本。

10.4 生命周期管理

例如 DMA buffer、回调函数、RTOS 信号量、线程、事件、互斥锁。AI 为了“完整”,可能把这些对象直接纳入框架层。

但生命周期一旦进入公共层,就会涉及创建/释放时机、中断上下文、并发保护、低功耗恢复、错误恢复。对 MCU 来说,这些不是普通代码组织问题,而是运行时风险。

10.5 错误码语义

AI 可能建议区分大量错误码:不支持、参数错误、忙、超时、未校准、DMA 错误、触发源错误、通道无效等。

错误码细化不是坏事,但前提是调用者能基于错误码采取不同动作。如果调用者最终都只能返回失败,那么大量错误码只是复杂度。


11. 工程师要守住的不是提示词,而是“问题定义”

在 AI 辅助开发里,很多人会把注意力放在“怎么写一个更好的提示词”。提示词有用,但它不是根本。

根本问题是工程师是否能持续回答:

  1. 当前最小目标是什么?
  2. 这次不解决什么?
  3. 哪些复杂性是当前必须承受的?
  4. 哪些复杂性只是 AI 因为“通用”而建议引入的?
  5. 当前接口是否还能用一句话说明?
  6. 当前公共层是否只承担它能稳定承诺的语义?
  7. 如果这轮修改全部回退,项目会损失什么?
  8. 如果这轮修改全部合入,未来谁来维护?

这些问题不是写给 AI 的,而是写给工程师自己的。

AI 可以帮工程师解释代码,但不能替工程师建立工程意图。AI 可以指出边界场景,但不能决定当前阶段是否处理。AI 可以生成复杂抽象,但不能保证这些抽象符合团队长期维护能力。

在这方面,代码审查中的“reviewer 对代码健康负责”很有启发。Google 的代码审查指南明确强调,审查者对正在审查的代码拥有责任,要保证代码库保持一致、可维护和可理解。[^google-review-standard] 对 AI 生成代码而言,这句话应当进一步强化:

只要工程师接受了 AI 的代码,代码责任就已经转移给工程师。不能因为某段代码是 AI 写的,就降低理解要求。


12. “边界”应当成为工程师拒绝 AI 的依据,而不是 AI 的装饰文本

很多时候,工程师会在文档里写边界,但真正执行时又把边界当成可协商项。更准确的做法是:边界不是为了让 AI 少犯错,而是为了让工程师在 AI 提出合理建议时有拒绝依据。

例如 ADC 框架可以写下如下判断:

  • 公共框架只提供基础同步采样;
  • DMA 连续采集不进入第一阶段公共 API;
  • 差分输入不进入公共配置结构;
  • injected channel 不进入公共 API;
  • 芯片内部通道由 BSP 私有配置处理;
  • 公共层不包含芯片系列条件编译;
  • 所有无法在至少两个 BSP 上验证的抽象不进入公共层;
  • 私有扩展通过底层 ops 或 user data 承载。

这些内容不是为了让 AI 永远不提 DMA、差分输入或 injected channel。AI 仍然可能提,而且有时提得对。它们的作用是让工程师在看到 AI 建议时可以判断:

这个建议可能正确,但违反当前边界,所以拒绝或延后。

这就是边界的真实用途。边界不是模型控制技术,而是工程师自己的决策锚点。


13. 新项目中,最危险的是“还没成形,所以什么都能改”

很多工程师在新项目中更愿意让 AI 自由发挥,因为没有历史包袱,没有兼容压力,没有旧用户。这看似合理,但实际上新项目比旧项目更容易被带偏。

旧项目至少有约束:已有 API、已有用户、已有测试、已有行为、已有 bug 历史。AI 想大改,工程师很容易发现它破坏了旧逻辑。

新项目没有这些约束。于是 AI 可以轻松引入:

  • 看似完整的分层;
  • 看似优雅的配置模型;
  • 看似通用的 feature 表;
  • 看似面向未来的 ops 集合;
  • 看似严谨的状态机;
  • 看似可扩展的回调体系;
  • 看似标准化的错误处理。

这些东西单独看都像好设计。但新项目的首要目标通常不是“第一次就做成最终平台”,而是先做出一个能被真实需求校正的最小稳定形态。

如果第一版就由 AI 扩成大平台,后面真实需求出现时,团队反而会被第一版抽象束缚。因为一旦公共 API、配置结构和文档写出去,删除复杂功能的成本会比添加功能更高。

因此,新项目并不意味着 AI 可以随便重构。新项目更需要工程师明确:第一版到底要证明什么。


14. 重构中,最危险的是“既然都要改,不如顺便改好”

重构场景也类似。工程师一开始可能只是想:

  • 拆分过大的文件;
  • 去掉重复代码;
  • 抽出公共 ops;
  • 统一命名;
  • 简化 BSP 适配;
  • 修正条件编译;
  • 补充注释和测试。

但 AI 审查后可能会提出更多“顺便”:

  • 顺便改 API;
  • 顺便增加异步模式;
  • 顺便支持更多芯片;
  • 顺便重做错误码;
  • 顺便引入对象生命周期;
  • 顺便把配置改成动态注册;
  • 顺便把同步接口改成可扩展控制接口。

“顺便”是重构中的高频风险词。每一个顺便都可能有理由,但多个顺便叠加后,原本的重构会变成重写。

重构的价值在于降低未来修改成本,而不是制造一套新的大系统。如果重构后工程师更难解释代码、更难验证行为、更难新增 BSP、更难定位问题,那么即使代码看起来更现代,也不是成功重构。

NIST SSDF 强调安全软件开发需要把人员、过程和技术准备好,并在开发中产生更安全的软件、识别和响应残余漏洞。[^nist-ssdf] 这对重构也适用:重构不是只看生成结果,而要看团队是否具备验证、维护和响应问题的能力。如果 AI 重构引入了团队无法验证和维护的复杂性,那就是风险。


15. AI 最常见的“好心办坏事”模式

15.1 把当前问题扩大成通用平台问题

工程师要解决的是“几个 STM32 BSP 的 ADC 读数接口不一致”。AI 可能把它升级成“跨 STM32 全系列的 ADC 能力抽象”。

15.2 把底层私有差异提升到公共 API

工程师只需要底层能根据芯片初始化不同寄存器。AI 可能把差异字段放进公共配置,让每个用户都看到这些硬件细节。

15.3 把未来可能需求当成当前必要需求

AI 很容易说“为了未来扩展,建议预留”。但预留不是免费的。每个预留字段都需要语义、默认值、测试和维护。

15.4 把“更完整”当成“更正确”

一个配置结构更完整,不代表设计更好。一个 API 覆盖更多模式,不代表更适合当前项目。

15.5 把“可以编译”当成“可以维护”

AI 修改后的代码可能能编译,也可能能通过简单测试。但嵌入式代码更重要的是长期维护、硬件验证、异常路径和资源约束。

15.6 把“审查没有严重问题”当成“目标没有偏移”

AI 审查通常更容易发现局部缺陷,不一定能发现项目目标已经变化。目标偏移需要工程师自己判断。


16. 代码健康不是“功能越多越好”

Google 代码审查材料中关于复杂度和过度工程的说明很直接:如果代码比必要的更复杂,读者不能快速理解,或者未来调用和修改更容易引入 bug,就应当警惕;尤其要避免为尚未出现的未来问题提前泛化。[^google-review-lookfor]

这个观点对嵌入式尤其重要。因为嵌入式框架的复杂度有额外成本:

  • Flash 增加;
  • RAM 增加;
  • 初始化路径变长;
  • 条件编译变多;
  • BSP 适配成本增加;
  • 默认配置难以选择;
  • 调试时需要理解更多状态;
  • 硬件验证矩阵膨胀;
  • 用户误用概率上升。

所以嵌入式代码健康不是“覆盖更多芯片能力”,而是:

  • 常用路径是否足够简单;
  • 不支持能力是否能清楚留在底层;
  • 公共 API 是否稳定;
  • BSP 适配是否低成本;
  • 出错路径是否可预测;
  • 资源开销是否可控;
  • 新人是否能在短时间内理解主路径。

AI 往往会把“可扩展性”表达得很诱人。但没有需求校验的可扩展性,通常就是提前支付复杂度。


17. 工程师应当把 AI 输出看成“候选材料”,不是“工程事实”

AI 生成的代码、审查意见、架构建议,都应被视为候选材料。

候选材料可以很有价值:

  • 帮助补全遗漏;
  • 帮助发现边界;
  • 帮助写测试;
  • 帮助解释芯片差异;
  • 帮助整理文档;
  • 帮助列出风险;
  • 帮助生成重复性代码。

但候选材料必须经过工程师转化,才能成为工程事实。工程事实需要满足:

  1. 与当前目标一致;
  2. 能被工程师解释;
  3. 能在当前硬件或测试条件下验证;
  4. 不把未来不确定需求提前固化;
  5. 不增加无法承担的维护成本;
  6. 不模糊公共层和底层的责任。

研究中也有类似迹象:在人机结对编程比较中,研究者观察到开发者倾向于以比面对人类结对伙伴更低的审查强度接受 Copilot 建议。[^copilot-knowledge] 这并不表示 AI 一定不好,而是提醒工程师:AI 输出的流畅性会降低人的警惕。

在嵌入式开发中,流畅输出尤其危险。因为驱动和框架代码常常“看起来都差不多”:结构体、ops、init、read、control、callback、mutex、宏配置。AI 很容易写出风格完整的代码,但风格完整不代表硬件语义正确。


18. 真正的判断标准:工程师能否独立说清楚这套设计

判断 AI 辅助实现是否已经失控,可以不用先看所有代码,而是问工程师几个问题:

  1. 这套框架当前只解决哪三个问题?
  2. 哪些需求明确不属于当前版本?
  3. 公共层承诺了哪些语义?
  4. 底层 BSP 保留了哪些自由度?
  5. 如果某芯片有特殊能力,用户如何使用?
  6. 如果某芯片缺少某能力,公共层是否需要知道?
  7. 当前配置结构中哪些字段是必需的?哪些只是为了未来预留?
  8. 当前 API 是否因为某个边缘功能变复杂?
  9. 哪些修改是 AI 建议后才加入的?加入理由是否仍然成立?
  10. 如果删除一半配置项,常用路径是否仍然可用?

如果工程师无法独立回答这些问题,而只能说“AI 审过了”“AI 说这样更通用”“AI 说这个边缘场景要支持”,那么项目已经有失控迹象。

AI 辅助开发的最低要求不是工程师逐行手写,而是工程师必须拥有完整解释权。解释权包括:

  • 为什么这样分层;
  • 为什么这个字段在公共层;
  • 为什么这个功能当前不做;
  • 为什么这个特殊能力留给底层;
  • 为什么这个错误码有意义;
  • 为什么这个抽象值得引入;
  • 为什么这个复杂度必须承担。

没有解释权,就没有真正的所有权。


19. 针对 ADC 框架的重新判断

回到 ADC 框架案例,假设工程师已经在多轮 AI 建议后加入了大量功能:

  • 不同系列 feature flag;
  • oversampling 配置;
  • injected channel API;
  • DMA 环形采集;
  • 外部触发源;
  • 多 ADC 同步;
  • 内部通道统一建模;
  • 差分输入;
  • 校准模式;
  • 复杂错误码;
  • 芯片系列条件编译。

此时不应继续问 AI:

这个框架还有什么问题?

因为这个问题会继续诱导 AI 找更多问题。更应该问的是工程师自己:

这还是我最初要做的框架吗?

如果最初目标是“不同 BSP 之间有一个简洁 ADC 读数框架”,那么当前设计很可能已经过度。更合理的回退方向可能是:

  • 公共层只保留基础 ADC 设备模型;
  • 基础同步读取作为第一能力;
  • 公共配置只保留必要字段;
  • 底层通过 ops 完成芯片初始化和读取;
  • 特殊能力不进入公共结构体;
  • 私有配置通过指针传入底层;
  • 高级能力由 BSP 或芯片私有扩展提供;
  • 公共层只提供能力查询或通用 control 通道,而不解释每个芯片特性。

这样并不是放弃通用性,而是重新定义通用性:

通用不是公共层知道所有差异,而是公共层足够稳定,让差异可以被底层安全承载。


20. AI 辅助开发中,工程师要保留的三种权力

20.1 需求解释权

AI 可以帮助理解需求,但不能决定需求。工程师必须明确当前要做什么,不做什么。尤其是新项目和重构,不能因为“可以不兼容”就让 AI 重新解释需求。

20.2 架构拒绝权

AI 提出更通用的设计时,工程师必须能拒绝。拒绝不是保守,而是维护边界。很多好的架构不是因为能做很多事,而是因为它清楚地知道自己不做什么。

20.3 最终验收权

AI 审查可以作为输入,但不能作为最终验收。最终验收必须回到工程师对目标、资源、实时性、硬件验证和维护成本的判断。

如果这三种权力被交给 AI,哪怕代码每一轮都看似更好,项目也可能偏离。


21. 工程师疲劳时的危险信号

以下信号出现时,应当暂停继续让 AI 修改:

  • 已经说不清当前 diff 为什么变大;
  • 开始用“AI 说有问题”作为修改理由;
  • 多次允许 AI 不兼容重构;
  • 公共结构体字段快速增加;
  • 新增功能没有真实 BSP 验证;
  • AI 提出的边缘特性逐个被接受;
  • 工程师只看总体框架,不再理解细节;
  • 审查重点从“是否符合目标”变成“AI 还有没有报问题”;
  • 代码越来越完整,但常用路径越来越难解释;
  • 每次修改都需要 AI 再解释上一轮改了什么。

这些信号说明问题已经不只是代码质量,而是人机协作方式出现偏移。

此时最有效的动作不是再换一个模型审查,而是回到最初问题:

当前版本到底要交付什么?

如果回答不了,应暂停修改。继续让 AI 改,只会扩大损失。


22. 可以接受 AI 建议的几种情况

本文并不是反对 AI 审查,也不是要求工程师拒绝所有扩展建议。AI 建议在很多情况下非常有价值。

可以优先接受的建议通常有这些特征:

  1. 修复明确 bug;
  2. 不改变公共 API;
  3. 不扩大模块责任;
  4. 不引入新配置模型;
  5. 能用现有测试或硬件验证;
  6. 减少代码而不是增加抽象;
  7. 让常用路径更清晰;
  8. 修正注释和实现不一致;
  9. 修正条件编译错误;
  10. 修复资源释放、空指针、越界、并发等明确风险。

应谨慎接受的建议通常有这些特征:

  1. 增加公共字段;
  2. 增加公共 API;
  3. 支持当前硬件没有验证条件的特性;
  4. 让公共层理解芯片私有语义;
  5. 增加大量 feature flag;
  6. 引入新的线程、队列、锁或 DMA 生命周期;
  7. 因为未来可能需要而提前抽象;
  8. 需要大量文档解释才能使用;
  9. 使普通 BSP 适配者需要理解更多硬件差异;
  10. 修改后难以回退。

这不是流程模板,而是判断重心:AI 建议要先看它是否改变问题边界,再看它技术上是否正确。


23. 最小可维护性比最大通用性更重要

嵌入式框架经常追求“通用”。但通用性有两种:

第一种是强公共抽象:公共层尽可能覆盖所有芯片能力,所有差异都映射为统一配置和统一 API。

第二种是弱公共抽象:公共层只抽象稳定共性,特殊差异通过底层私有机制承载。

AI 更容易推动第一种,因为它看起来更完整、更体系化、更像框架设计。但嵌入式工程中,第二种往往更稳。原因是:

  • MCU 资源有限;
  • 芯片差异细碎;
  • 很多特殊能力很少使用;
  • BSP 维护者更关心接入成本;
  • 公共 API 一旦暴露就难以收回;
  • 没有硬件验证的抽象很容易成为负担;
  • 底层私有扩展比公共层全覆盖更灵活。

因此,AI 辅助设计时应始终警惕一个问题:

这个“通用”是降低了接入成本,还是把所有芯片差异都暴露给了用户?

如果是后者,就不是好的通用。


24. 工程师应当允许 AI 犯小错,但不能允许 AI 改目标

在 AI 辅助开发中,小错误并不可怕。例如:

  • 某个宏名写错;
  • 某个 include 缺失;
  • 某个参数检查不完整;
  • 某个注释不准确;
  • 某个返回值处理不严谨。

这些问题可以修。真正危险的是目标被改掉:

  • 原本做基础 ADC 读数,变成全功能 ADC 平台;
  • 原本做 BSP 适配简化,变成芯片能力统一建模;
  • 原本减少重复代码,变成新架构重写;
  • 原本同步接口,变成异步 DMA 运行时;
  • 原本私有扩展,变成公共 API 承诺。

小错是局部质量问题,目标偏移是系统性失败。

工程师在使用 AI 时,应该把注意力从“AI 有没有发现更多问题”转移到“AI 有没有把我的问题变成另一个问题”。


25. 文章最终观点

对于嵌入式新项目和重构,AI 的价值很大。它可以加速样板代码生成,可以帮助整理驱动接口,可以辅助阅读芯片差异,可以做交叉审查,可以补充测试思路,也可以把复杂代码解释成更清晰的结构。

但 AI 的危险也恰恰来自它的高效率和高表达能力。它会把建议写得很合理,把边缘场景列得很完整,把复杂抽象包装得很专业。工程师如果在多轮交互中疲劳、不耐烦、焦虑,就很容易放开最初的边界,让 AI 从“辅助实现者”变成“需求解释者”和“架构扩张者”。

所以,嵌入式 AI 辅助开发最重要的纪律不是写一个完美提示词,也不是让 AI 审查更多轮,而是工程师始终明确自己要干什么。

尤其在 ADC、SPI、I2C、PWM、CAN、USB、以太网、参数管理、设备框架、BSP 适配这类底层代码中,工程师应当反复确认:

  • 当前公共层是否仍然简洁;
  • 当前 API 是否仍然稳定;
  • 当前特性是否都有真实需求;
  • 当前复杂度是否能被团队维护;
  • 当前设计是否还能用简单语言解释;
  • 当前修改是否仍然服务最初目标。

如果答案变得模糊,就不要继续让 AI 改。先停下来,重新确认目标。

最终可以用一句话收束:

AI 不能替工程师决定项目要成为什么。工程师一旦把这个决定权交出去,代码即使越来越完整,工程也可能越来越失败。


参考资料

[^nist-ai-rmf]: NIST, AI Risk Management Framework, https://www.nist.gov/itl/ai-risk-management-framework

[^nist-ssdf]: NIST SP 800-218, Secure Software Development Framework (SSDF) Version 1.1, https://doi.org/10.6028/NIST.SP.800-218

[^google-review-standard]: Google Engineering Practices, The Standard of Code Review, https://google.github.io/eng-practices/review/reviewer/standard.html

[^google-review-lookfor]: Google Engineering Practices, What to look for in a code review, https://google.github.io/eng-practices/review/reviewer/looking-for.html

[^owasp-llm]: OWASP, Top 10 for Large Language Model Applications, https://owasp.org/www-project-top-10-for-large-language-model-applications/

[^ai-quality]: Vehid Geruslu, Zulfiyya Aliyeva, Eray Tüzün, Factors Influencing the Quality of AI-Generated Code: A Synthesis of Empirical Evidence, arXiv:2603.25146, https://arxiv.org/abs/2603.25146

[^more-code-less-validation]: Gabrielle O’Brien, Alexis Parker, Nasir Eisty, Jeffrey Carver, More code, less validation: Risk factors for over-reliance on AI coding tools among scientists, arXiv:2512.19644, https://arxiv.org/abs/2512.19644

[^copilot-knowledge]: Alisa Welter et al., From Developer Pairs to AI Copilots: A Comparative Study on Knowledge Transfer, arXiv:2506.04785, https://arxiv.org/abs/2506.04785