十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI生成代码几秒,验证却要半个月?嵌入式验证体系深度拆解

AI生成代码几秒,验证却要半个月?嵌入式验证体系深度拆解 AI 生成代码几秒钟为什么验证却要半个月这个问题我琢磨了很久直到自己亲手用 Copilot 和 Claude Code 写完一版蓝牙协议栈的移植代码编译通过、静态检查通过然后烧到板子上第一轮就翻车我才真正意识到在嵌入式这个行当里“代码能跑”和“代码能用”之间的距离比大多数人想象的要远得多。先说结论AI 生成代码本身不是问题问题在于嵌入式软件对“正确性”的定义比互联网软件严苛得多。你写一个 Web 后端逻辑错了顶多报个 500重启一下就好但在嵌入式里一个指针越界可能直接踩掉中断向量表一个时序竞争可能让电机在没有任何报错的情况下突然反转。更不用提那些跑在量产设备上的固件一旦出了问题轻则返厂重则出安全事故。所以AI 几秒钟生成的代码我们花半个月去验证这笔账不仅划算而且是必须的。这篇文章我不会聊太多概念层面的“AI 赋能嵌入式”那个太虚。我想花点时间从一个实际做嵌入式软件验证的人的角度拆一拆这套验证体系到底由哪些环节组成每个环节为什么绕不开以及你真正落地时会踩到哪些坑。1. AI 生成代码的“快”恰恰是验证压力的来源1.1 同样的代码放在不同语境里风险完全不同如果你让 AI 写一段 Python 脚本处理 Excel 表格生成之后你肉眼扫一眼再跑两个样例数据基本就敢用了。但同样一段逻辑如果用 C 语言跑在 Cortex-M 内核的 MCU 上你需要确认的东西多一个量级这段代码用了多少栈最坏情况下会不会溢出有没有隐式的动态内存分配在长期运行的设备里堆碎片会不会导致内存耗尽中断上下文里能不能安全调用这些函数有没有不可重入的风险编译器会不会因为优化选项把某个 volatile 变量的访问给优化掉目标芯片的内存映射、外设寄存器地址、总线等待周期代码是否都考虑到了任何一个问题没有验证清楚这段 AI 生成的代码即使语法完美、逻辑自洽也等于一颗定时炸弹。1.2 生成速度越快代码审查的负担越重我见过不少团队引入 AI 编程工具之后代码合并请求MR的数量翻了好几倍。以前一个人一天写 200 行 C 代码已经不少了现在 AI 十分钟就能生成 200 行而且看起来很像模像样。但问题是代码量上去了审查的人没变审查的时间也没变。这就会造成一个很实际的问题审查者开始走马观花。我看过太多“AI 生成的代码逻辑上似乎没问题但风格完全不是团队风格”的 MR 被轻易放过去因为代码实在太长了人眼根本盯不过来。等到集成测试或者路试阶段出了问题再回头排查成本已经是生成阶段的几十倍。所以我在团队里定了一个规矩AI 生成的代码必须由另一名工程师逐行走查并且要写走查记录。这条规矩看着很笨但它反而让团队形成了正向循环——AI 生成得快审查也跟得上整体质量才没有崩掉。1.3 “快”不代表“正确”一句注释也可能决定生死还有一个容易被忽略的地方AI 生成的代码里注释和代码之间可能存在严重的不一致。我们团队有一次排查一个非常隐蔽的 bug函数名和变量名都叫req_send_timeout逻辑看起来也合理但实际行为却完全不是超时控制的逻辑。后来发现AI 是根据一个给普通工程师看的注释生成这个函数的而注释描述的场景跟这个模块的实际需求差了十万八千里。这种事不是个例。AI 对自然语言的理解是统计性的它并不知道你的硬件手册里那句话说“此寄存器写 1 清零”意味着什么。所以代码审查的时候我特别提醒团队不要只看代码本身要把注释和代码对照起来看更要把 AI 参考的那段文档或者需求描述拿出来看。验证的不只是代码还有代码背后那个逻辑链。2. 嵌入式代码验证的特殊性编译通过只是万里长征第一步2.1 静态分析和编译警告是底线不是亮点很多刚从互联网转过来的工程师有个习惯编译通过、没有 warning就觉得代码已经写完了。但在嵌入式场景里这充其量只是“代码上了手术台还没开刀”而已。我们常规的静态检查至少包括几个层次编译器开-Wall -Wextra -Werror把警告直接变成错误强制消除隐患用cppcheck或者clang-tidy做语法层之外的分析检查未初始化变量、空指针解引用、资源泄漏等用MISRA C:2012规则集做合规检查特别是对指针操作、类型转换、控制流这几大类做约束。AI 生成的代码在 MISRA 规则下往往能一次性通过率不高。比如AI 很喜欢用memcpy做结构体拷贝但如果目标是嵌入式系统尤其是安全性要求比较高的场景这会直接违反 MISRA 规则因为这种拷贝方式对内存对齐和结构体中间可能存在的 padding 太敏感了。静态分析这块我的建议是不要用默认参数直接跑一定要针对你的目标编译器、目标架构、目标工程定制规则集。否则静态分析的结果会像 Google 翻译一样——语法上挑不出刺但读起来总觉得哪里不对劲。2.2 单元测试在嵌入式里为什么这么重做嵌入式单元测试比做纯软件单元测试要麻烦原因是硬件依赖。你不能指望每个函数都能在 PC 上跑起来很多代码直接访问寄存器、操作外设底层就是硬件根本没有一个标准的运行时环境提供这些 API。我们这边的做法是引入硬件抽象层HAL把代码里所有跟硬件相关的访问都封装成可替换的接口。这样面向 PC 的单元测试就可以注入一个 mock 的 HAL模拟出各种各样寄存器值、中断标志位和时序状态把逻辑层测个底朝天。举一个我们实际测过的例子AI 帮我们生成了一段基于状态机的按键扫描逻辑带 20ms 软件消抖和长按、短按判断。代码逻辑本身没有语法问题但跑单元测试时发现状态机在“按下 15ms 后释放再立刻按下”这种极端时序下会进入一个未定义状态所有后续按键事件全部失效。这种问题靠人读代码也能发现但靠人读代码的时间足够 AI 再生成十几版代码了。单元测试在这里的价值是用机器取代了人眼去做这种最细粒度、最机械化、最不带感情色彩的检查。2.3 覆盖率到底要看哪一行有几个真实指标我们内部定的覆盖率指标不是硬性的“90% 行覆盖”而是拆成三个维度看指标关注点实际意义行覆盖率 (Line Coverage)每行代码是否被执行防止伪代码、死代码混入分支覆盖率 (Branch Coverage)每个条件分支的 true/false 是否都跑到防止边界值判断出错MC/DC 覆盖率每个条件独立影响结果关键安全逻辑必须做到MC/DC 这一项AI 生成的代码通常很难达标。原因也不难理解AI 生成代码的思路是“把看起来对的逻辑写出来”并不会主动构造测试用例去论证每个条件独立作用的场景。所以AI 生成代码之后我们往往要人工补一大批 MC/DC 用例这也是验证周期被拉长的重要原因之一。提示如果你们的产品要做功能安全认证比如 IEC 61508、ISO 26262MC/DC 覆盖率不是可选项而是硬性指标。这一块没有捷径可走只能老老实实把用例堆出来。3. 从编译到上路分层验证体系到底怎么设计3.1 分层验证的整体思路嵌入式 AI 生成代码的验证体系我倾向于用一个四层金字塔来描述静态分析层不运行代码只做语法、语义、风格、规则检查筛掉明显的问题。动态单元测试层在 PC 或模拟器上运行逻辑代码用 mock 屏蔽硬件依赖验证纯逻辑正确性。硬件在环测试层HIL把代码烧到真实芯片或者开发板上通过调试器、逻辑分析仪、信号发生器等设备验证代码在真实硬件上的表现。整机路试层把设备放到实际使用环境里跑完整的场景验证整个系统的协同行为、长期稳定性、异常恢复能力。每一层都有明确的入口条件和出口条件达不到标准不允许进入下一层。这个体系不复杂但很多团队跳层导致问题被不断往后推。3.2 静态分析层入口条件与实操清单入口条件其实很简单代码能过编译能过定制化的静态分析规则集。但出口条件我们要严格一些所有高优先级告警清零中优先级告警必须有明确解释和责任人签字。不允许出现“先放着以后再说”这种情况。实操时你会发现AI 生成的代码在静态分析层最容易出的问题不是逻辑错误而是风格不一致和“过度工程”。风格不一致AI 可能在同一份代码里混用uint8_t和unsigned char混用typedef struct和直接struct看起来不优雅但不至于出 bug。可一旦团队维护者习惯了某种风格这种不一致就会像牛皮癣一样反复出现。过度工程AI 可能给一个 simple 变量生成了三个不同的 getter/setter还加了个带互斥锁的单例模式。在嵌入式里这种过度设计不仅浪费 Flash还增加静态分析的复杂度。所以静态分析阶段的关注点应该是筛掉影响安全和可维护性的硬伤再花少量时间处理一致的代码风格。不要指望 AI 自动遵守团队的 MISRA 配置这个只能靠规则集约束。3.3 单元测试层怎么让 AI 生成的代码可测性好AI 生成的代码我们拿回来直接写单测往往会发现两个问题一是函数内部直接调用了硬件 API二是函数之间耦合度太高没法单独摘出来测。这时候不能硬着头皮去测要先做一点简单的重构把硬件依赖隔离出去。比如// 改造前 uint8_t read_button_state(void) { return (uint8_t)(GPIOA-IDR GPIO_PIN_0); } // 改造后 static uint8_t (*read_gpio_pin)(void) platform_read_gpio_pin; uint8_t read_button_state(void) { return read_gpio_pin(); }这样单测时只需要把read_gpio_pin替换成 mock 函数就能在 PC 上模拟任意按键状态序列。这个改造点很小但对可测性的提升是质的改变。我们在做单测时还特别注意构造“破坏性数据”。比如某个函数接受一个结构体指针AI 生成的实现里默认指针是有效的。我们的单测就会专门传一个 NULL 进去传一个只初始化了一半的结构体进去甚至传一个故意把 size 字段改大的结构体进去看代码会不会崩溃。这种测试思路对 AI 生成代码尤其重要因为 AI 对“前提条件”的理解往往过于理想化。3.4 硬件在环层AI 代码第一次接触真实硬件如果说前两层还在“软件”范畴里打转那么从 HIL 开始你就要面对真实的物理世界了。这一层最常见的问题包括时序问题AI 生成的代码在 PC 上跑得飞快但烧到真实的 MCU 上由于总线频率、Flash 等待周期、外设响应时间不一样原先逻辑上合理的时序假设可能完全不成立。比如两条连续执行的 I2C 操作之间如果没有足够的延时实际硬件上就可能出现 ACK 丢失。寄存器操作互斥AI 在一个功能里使能了某个外设中断在另一个功能里关闭了全局中断。单测时它们各跑各的完全没问题但在 HIL 阶段两个功能交替执行就可能在中断关闭期间错过了重要事件。低功耗模式很多 AI 生成的代码根本没考虑低功耗。你让它写一个定时采集传感器数据的任务它可能生成一个while(1) { read_sensor(); delay_ms(1000); }。这在开发板上没问题但如果是电池供电设备这种写法功耗直接上天设备半天就没电了。HIL 阶段我建议配备逻辑分析仪和示波器不要只看程序跑没跑通要对着时序图看每一个信号边沿。AI 代码在这方面是个迷它生成的外设初始化顺序可能与芯片手册推荐顺序不同但这种差异往往不会立刻报错而是在长期运行或某个特定触发条件下才暴露出来。3.5 整机路试层验证体系里最像“路试”的环节整机路试放在最后但它其实是最接近用户真实使用的一个阶段。我们通常会把设备放在典型应用场景里连续跑 72 到 168 个小时。这期间除了采集日志还会定期注入一些异常事件例如多次上下电模拟用户频繁开关机通信链路拔插模拟信号不稳定场景外设热插拔模拟使用过程中接入不同附件电源电压拉偏模拟电池电量波动。AI 生成的代码在这种长期运行测试中暴露的问题往往比较集中内存泄漏和状态机漂移。内存泄漏好理解就是不断重复某个操作后可用内存逐渐减少状态机漂移比较隐蔽是状态变量被修改到了未定义值导致整个系统进入一个既不是正常运行也不是异常处理的“鬼打墙”状态。路试阶段发现的 bug修复起来往往不难但定位过程非常折磨人。因为你要从几千条日志上下文里找到那条与故障强相关的蛛丝马迹。我们现在的做法是在代码里预埋足够的日志埋点特别是状态机跳转的地方务必把“从哪来到哪去”打印得清清楚楚。这招对 AI 生成代码尤其重要因为你对它的逻辑不熟悉没有日志几乎没法推断它当时在想什么。4. 容易被掩盖的缺陷类型AI 代码专属“暗坑”盘点4.1 静态时序分析缺失导致“看起来对、跑起来乱”先说一个比较典型的AI 生成的代码在处理外设寄存器时通常直接写赋值语句。但嵌入式硬件有一个重要特性——某些寄存器在写入后需要等待几个时钟周期才能读到正确值而有些寄存器需要先写某个特定的解锁序列才能访问。AI 不会知道这些硬件手册里的细节所以它生成的代码逻辑上完全正确但在真实硬件上就会时不时地抽风。我们在验证体系里加入了静态时序检查虽然不会完全自动化但会让工程师在走查代码时对照芯片手册把“寄存器操作后是否需要延时”这一项作为必查点。尤其是 AI 生成的初始化序列每个外设的初始化步骤都要核对一遍。这项工作很枯燥但它能省下后面 HIL 阶段的大量排障时间。4.2 大小端问题和结构体打包问题嵌入式平台千差万别ARM 基本都是小端但有些 MCU 是大端或者支持字节序切换。AI 生成代码时默认情况下它对端序的假设可能是错误的特别是在处理通信协议、文件系统、Flash 存储这类跟外部数据打交道的模块。举个实际例子AI 帮我们生成了一段用于解析 Modbus 报文的代码它在解析寄存器地址时直接用了*(uint16_t*)buf[offset]这种强转指针的方式。在一台 x86 的 PC 上跑单测完全没问题因为 x86 是小端。但放到一个支持字节序切换的 MCU 上只要工程师在配置里把字节序设成了大端这段 AI 代码解析出来的所有数据都是错乱的。验证体系里处理这类问题最直接的手段是在单测阶段就通过 CMake 或 Makefile 生成两套配置一套小端、一套大端跑同一批测试用例。这样可以在代码进入硬件之前先把端序相关的隐患揪出来。4.3 中断上下文里的非法操作这应该是嵌入式场景下 AI 生成代码最危险的“坑”之一。AI 生成的代码里如果某个函数被中断处理程序调用而这个函数内部又有阻塞式延时、printf、动态内存申请等操作整个系统的实时性就会崩塌。我们团队还撞到过一次特别隐蔽的情况AI 生成的中断处理函数里调用了另一个模块的查询函数导致几个中断之间的优先级反转系统间歇性卡死。这种问题在单测的时候是完全测不出来的因为单测跑在普通线程上下文里没有中断优先级的概念。验证这一类问题的办法是在代码走查时画一张“中断调用树”人工确认每个中断服务函数直接或间接调用了哪些函数逐一排查是否包含不安全的操作。这个工作靠人来做确实繁琐但目前也没有完全自动化的工具能替代至少我还没遇到。4.4 错误处理分支过于理想化AI 生成代码时对错误处理分支的构造往往比较草率。它们倾向于生成“如果条件不满足就返回错误码”这类标准结构但错误码本身怎么定义、上层怎么处理、是否需要重试、是否需要状态转换AI 经常是不管的。这就导致一种很尴尬的局面异常路径上报了错误码但系统不知道该拿这个错误码怎么办最终只能处于一个半死不活的状态。所以我们在验证 AI 生成的代码时会专门做一轮错误注入测试把每一个错误码可能对应的场景都模拟一遍看系统能不能按要求降级、重试或者安全停机。只有把错误路径也验证过才敢说这段代码“能上路”。5. 把验证流程固化到工具链让 AI 生成的每一行代码都被“自动盯着”5.1 从 AI 聊天窗到代码仓库的必经之路我经常看到一些工程师在 AI 工具里生成完代码复制粘贴到工程目录里就完事了。这种做法在嵌入式开发里真的要改一改。AI 工具只是一个生产代码的“输入设备”它输出的代码必须像有人写的一样走完版本管理、持续集成、自动化测试这一整套流程。我们在内部是这样操作的AI 生成代码先放到一个单独的分支然后自动触发 CI。CI 里至少包含这些步骤拉取最新代码编译多个目标平台ARM Cortex-M、RISC-V、x86 模拟器运行静态分析套件编译并运行 PC 端的单元测试生成覆盖率报告用west build或者cmake生成烧录镜像对 HIL 环境的测试固件进行自动烧录并运行冒烟测试用例。只要以上任何一步失败这个 AI 生成的分支就不允许合并到主干。这样做的好处是即使 AI 生成的代码质量不稳定也不会直接污染共享的开发主干。5.2 用测试用例集反向约束 AI 生成这是我在实际使用中摸索出来非常有用的经验在让 AI 生成代码之前先把测试用例写好用测试用例去约束 AI 的生成结果。换句话说把“验证”前置到“生成”阶段。比如我需要让 AI 生成一个温度传感器的驱动。我不会直接说“帮我写一个读温度的驱动”而是先把测试用例列出来TEST_CASE(read_temperature_mock_i2c_returns_valid_value) { mock_i2c_set_read_data(0x1A, 0x07D0); // 25.0 度 assert_true(fabs(read_temperature() - 25.0) 0.1); } TEST_CASE(read_temperature_i2c_nack_returns_error) { mock_i2c_set_nack(0x1A); assert_equal(read_temperature(), TEMP_I2C_ERROR); }然后把这段测试代码作为需求描述的一部分发给 AI让它在满足测试用例的前提下实现逻辑。这样生成的代码至少在一开始就具备可测试性不会出现“拿到手不知道怎么测”的尴尬场景。5.3 需要纳入版本管理的不止是代码还有测试向量和真机日志很多团队把版本管理范围限定在源代码和文档但嵌入式验证体系的产物远不止这些。测试向量、测试固件、压测记录、真机日志、硬件配置版本这些都是应该进入版本管理体系的。AI 生成的代码在某个版本能通过验证但在下一个版本可能因为硬件微调、编译器升级等因素挂掉这时候你要能回溯当时的验证环境否则排查起来会非常痛苦。我们现在的做法是在每次验证有结论时生成一份验证报告内容包括代码版本、编译器版本、目标板硬件版本、测试工具版本、覆盖率和结论签核人把这个报告提交到一个单独的仓库里。这样任何一次 AI 生成代码引入的验证记录都是可追溯的将来出问题也不至于扯皮。6. 验证体系的投入产出比算清楚这笔账6.1 不要只看测试花费的时间要用全生命周期视角去看AI 生成代码时你省下的是“写代码”的时间但如果你跳过或者压缩验证环节将来在量产现场、售后维护阶段花掉的时间会成倍地找回来。我们团队有次在一个简单的外设驱动上依赖了 AI 生成的代码省了不到半天结果在整机路试阶段发现通信偶发失败定位加修复加回归前后花了两周。这件事之后团队就形成了一条潜规则AI 生成的代码可以帮你减少写代码的时间但验证时间不得压缩。6.2 在哪些环节投入性价比最高根据我们的经验如果时间有限优先把资源花在这三块优先级验证环节理由高静态分析成本最低能筛掉 70% 的明显问题高单元测试能在不依赖硬件的情况下提前暴露逻辑缺陷中硬件在环测试发现单测覆盖不了的时序和硬件交互问题中低整机路试真实场景最会暴露问题但耗时最长最好通过自动巡检减少人力这个优先级不是固定的。如果你的产品对安全要求极高整机路试的权重必须提高如果只是内部原型验证静态分析和单元测试足够路试可以简化。6.3 验证体系不是一次性建设要跟着 AI 使用频率迭代最后提醒一点验证体系不能建一次就完事。AI 生成代码的使用频率越高验证体系就要做得越重。反过来验证体系跑出来的数据也要反过来喂给 AI 生成的流程——哪些错误模式反复出现就把它固化到静态分析规则和单测用例里。这样验证体系和 AI 生成之间会形成一个类似于“从实践中学习”的正循环。我们内部现在有一个“AI 生成代码缺陷库”每发现一个新类型的 bug就整理成一条规则定期补充到静态分析配置和代码走查清单里。几个月下来AI 生成的代码质量肉眼可见地提升这不是因为 AI 变聪明了而是因为验证体系把 AI 生成的“劣质行为”约束住了。最后说点实际的我一直觉得AI 生成代码这件事放在嵌入式领域的正确使用方式不是“替代工程师”而是“给工程师配了个写代码速度极快的实习生”。这个实习生可能基础不错但不懂你们的产品、不懂芯片手册的细节、不懂老设备里踩过的那些坑。你能不能让这个“实习生”发挥价值完全取决于你愿不愿意花时间验证它的工作成果。验证体系听起来是个“麻烦事”但把 AI 生成代码和验证体系当作一个整体来看你就会发现几秒钟的生成带来的速度红利只有配上扎实的验证环节才能真正转化为项目交付效率。否则那些被“省”下来的时间早晚会加倍地花在定位 bug 和售后救火上。
返回列表