1. 一个嵌入式老兵的“偷懒”实验:让AI重构我的框架
嵌入式开发这行干了快十年,我最大的感受就是:代码写得越久,越不敢随便动老项目。尤其是那种跑了三四年的框架,里面全是“历史遗留智慧”——有些地方你自己都忘了当初为什么那么写,但删掉它系统就崩。我手里就有一个这样的项目,一个基于事件驱动的小型嵌入式框架,最早是给某款工业采集设备写的,后来陆续移植到几个不同的板子上,功能越加越多,代码越来越乱。
这个框架的核心其实不复杂:一个任务调度器、一套消息队列、一层硬件抽象接口,再加上一些状态机管理。但问题在于,它是我在不同时期用不同风格写出来的。早期部分用了大量全局变量和宏定义,中期部分开始用结构体封装,后期又引入了回调注册机制。结果就是,同一个框架里能看到三种完全不同的编程范式,维护起来极其痛苦。每次要加一个新功能,我都得先花半小时“考古”,搞清楚这块代码到底属于哪个时代。
所以当AI编程工具开始成熟的时候,我脑子里冒出的第一个念头就是:能不能让AI帮我把这个框架重构一遍?不是让它从零写一个新框架,而是让它理解现有代码的逻辑,然后按照统一的架构风格重新组织。这个想法听起来有点疯狂,但仔细想想,重构这件事本质上就是“理解现有逻辑+按新规则重新表达”,而这恰好是大语言模型擅长的方向。
我给自己定了一个目标:整个重构过程,我尽量不手写业务代码,只做架构决策和结果验证。换句话说,我负责告诉AI“要什么”,AI负责告诉我“怎么写”。这个实验持续了大约两周,中间踩了不少坑,也总结出了一些真正有用的经验。这篇文章就是把这个过程完整拆开,从思路设计到实操细节,再到问题排查,全部摊开来讲。如果你手里也有那种“想重构但不敢动”的老项目,或者你对AI辅助开发这件事持观望态度,那这篇内容应该能给你一些参考。
2. 重构前的整体设计与思路拆解
2.1 为什么选择“AI主导、人工把关”的模式
在动手之前,我先想清楚了一个问题:AI在重构这件事上,到底应该扮演什么角色?我试过两种极端方式。第一种是“AI全自动”,就是把代码扔给AI,让它直接输出重构后的版本,我只看结果。第二种是“AI辅助”,我自己设计架构,AI只帮我写具体的实现代码。
第一种方式试下来的结果是:AI确实能重构,但它会“自作主张”。比如它会把你原本用宏定义实现的配置项改成枚举,把中断服务函数里的逻辑挪到任务里,甚至会把一些你故意保留的冗余代码“优化”掉。这些改动单看都是合理的,但放在整个项目里就可能引入新的问题。第二种方式的问题则是效率太低,因为大部分时间还是我在写代码,AI只是帮我补全一些片段,没有真正减轻负担。
最后我采用的是一种中间模式:我负责定义架构规则和接口边界,AI负责在这个边界内重新实现所有模块。具体来说,我会先写一份“重构规范”,里面明确规定:任务调度器用什么数据结构、消息队列怎么管理内存、硬件抽象层怎么定义接口、状态机用什么模式实现。然后我把现有代码按模块拆开,一个模块一个模块地交给AI,让它按照规范重写。每完成一个模块,我会跑一遍单元测试和集成测试,确认行为一致后再继续下一个。
这个模式的好处是:架构决策权在我手里,实现细节交给AI。AI不会跑偏,因为它只能在给定的框架内发挥;我也不会累死,因为具体的代码编写工作确实被大幅分担了。
2.2 框架重构的核心目标与优先级排序
重构最怕的就是“什么都想要”。我一开始列了一堆目标:代码要更简洁、性能要更好、可移植性要更强、要支持动态配置、要加单元测试……后来发现这样根本没法推进,因为AI不知道先满足哪个。于是我强制自己做了优先级排序:
| 优先级 | 目标 | 衡量标准 | 是否必须 |
|---|---|---|---|
| P0 | 行为一致性 | 所有原有功能测试通过 | 必须 |
| P1 | 架构统一 | 消除三种编程范式混用 | 必须 |
| P2 | 可读性提升 | 新成员能在半天内理解核心流程 | 必须 |
| P3 | 可测试性 | 核心模块能独立跑单元测试 | 尽量 |
| P4 | 性能优化 | 任务切换开销降低20% | 可选 |
| P5 | 动态配置 | 支持运行时修改参数 | 暂缓 |
这个排序很关键。P0是底线,任何重构都不能改变原有行为。P1和P2是核心价值,如果重构完还是三种风格混用,那这个实验就失败了。P3是加分项,因为嵌入式项目做单元测试本来就麻烦,能做好当然好,做不好也不强求。P4和P5直接放弃,因为一旦引入性能优化和动态配置,AI就会开始“发挥创意”,风险不可控。
这里有个经验:重构目标不要超过三个“必须”。超过三个,AI就会在多个目标之间摇摆,最后每个都做得不彻底。我见过有人让AI同时做“解耦、提速、加功能”,结果AI把三个目标都写进了注释里,代码一行没动。
2.3 模块拆分策略:怎么把老代码“喂”给AI
老项目的代码结构是这样的:一个主文件大概两千行,里面混杂了任务调度、消息处理、硬件操作和业务逻辑。直接把这个文件扔给AI,它大概率会懵,因为上下文太长,而且模块之间的耦合关系不清晰。
我的做法是先做一轮人工拆分,把代码按职责切成独立的模块文件。注意,这一步我没有改任何逻辑,只是把原来混在一起的东西按功能分到不同文件里。比如:
scheduler.c:只放任务调度相关的函数msg_queue.c:只放消息队列的入队、出队、管理逻辑hal.c:只放硬件抽象层的接口定义和默认实现state_machine.c:只放状态机的状态定义和转移逻辑app_logic.c:只放具体的业务逻辑
拆分完之后,每个文件大概两三百行,AI处理起来就轻松多了。而且拆分的过程本身也帮我理清了模块之间的依赖关系。比如我发现原来app_logic.c里直接调用了hal.c的底层寄存器操作,这就是典型的耦合问题。在重构规范里,我明确规定:业务逻辑只能调用硬件抽象层的接口,不能直接操作寄存器。
拆分完之后,我给每个模块写了一份“接口说明”,其实就是把该模块对外暴露的函数签名和数据结构列出来,然后告诉AI:“这是模块A的接口,你要在保持接口不变的前提下重写内部实现。”这样AI就不会乱改接口,也不会把模块之间的调用关系搞乱。
3. 核心细节解析与实操要点
3.1 重构规范怎么写才能让AI“听话”
重构规范是整个实验的核心文档。我一开始写得很随意,结果AI完全按自己的理解来,出来的代码跟我想的完全不一样。后来我总结出一个原则:规范要写成“约束条件”而不是“建议”。比如:
- 不要写“建议使用结构体封装任务”,要写“任务必须用结构体表示,结构体必须包含任务ID、执行函数指针、周期参数、状态标志四个字段”。
- 不要写“消息队列尽量用静态内存”,要写“消息队列必须使用静态数组实现,数组大小由宏定义指定,禁止使用malloc”。
- 不要写“状态机可以用switch-case”,要写“状态机必须使用状态转移表实现,转移表为二维数组,行表示当前状态,列表示触发事件”。
这种“必须”式的写法,AI执行起来非常准确。我试过用“建议”式的写法,AI会挑着执行,有些地方按你说的做,有些地方按它自己的习惯来,最后出来的代码风格还是不统一。
另外,规范里还要明确命名规则。嵌入式代码的命名风格差异很大,有的用驼峰,有的用下划线,有的加模块前缀。我在规范里统一规定:所有函数名用模块名_动作的格式,比如scheduler_init、msg_queue_push、hal_gpio_write。所有变量名用下划线分隔的小写字母,所有宏定义用全大写。这样AI生成的代码就不会出现风格混乱的问题。
3.2 任务调度器的重构:从全局变量到结构体封装
任务调度器是框架的核心,也是重构难度最大的部分。原来的实现是这样的:
// 旧代码:全局变量 + 宏定义 #define MAX_TASKS 8 uint8_t task_count = 0; uint32_t task_period[MAX_TASKS]; void (*task_func[MAX_TASKS])(void); uint8_t task_enabled[MAX_TASKS]; void scheduler_add_task(uint32_t period, void (*func)(void)) { if (task_count < MAX_TASKS) { task_period[task_count] = period; task_func[task_count] = func; task_enabled[task_count] = 1; task_count++; } }这段代码能跑,但问题很明显:所有数据都是全局的,任何地方都能改。而且task_count、task_period、task_func、task_enabled这四个数组是“平行数组”,必须同步维护,一旦有一个地方漏了,就会出bug。
我给AI的规范是:任务调度器必须用一个结构体数组来管理所有任务,每个任务包含周期、函数指针、使能标志和计数器四个字段。调度器对外只暴露初始化、添加任务、启动、停止四个接口。
AI重构后的代码是这样的:
// 新代码:结构体封装 typedef struct { uint32_t period; void (*func)(void); uint8_t enabled; uint32_t counter; } task_t; typedef struct { task_t tasks[MAX_TASKS]; uint8_t count; uint8_t running; } scheduler_t; static scheduler_t sched; void scheduler_init(void) { memset(&sched, 0, sizeof(sched)); } int scheduler_add_task(uint32_t period, void (*func)(void)) { if (sched.count >= MAX_TASKS) return -1; sched.tasks[sched.count].period = period; sched.tasks[sched.count].func = func; sched.tasks[sched.count].enabled = 1; sched.tasks[sched.count].counter = 0; sched.count++; return 0; }这个改动看起来简单,但效果非常明显。所有任务数据被封装在一个结构体里,外部只能通过接口访问,不能直接改内部字段。而且counter字段的加入让调度器可以在中断里做计数,在主循环里做判断,避免了在中断里直接调用任务函数的问题。
实操心得:AI在重构这类“平行数组”代码时,表现非常好。你只要告诉它“把平行数组改成结构体数组”,它就能自动完成字段映射和接口调整。但要注意,AI可能会顺手把
MAX_TASKS改成动态分配,这时候你必须在规范里明确禁止,否则它会引入malloc,这在嵌入式里是大忌。
3.3 消息队列的重构:内存管理是最大的坑
消息队列的重构比任务调度器更麻烦,因为涉及到内存管理。原来的实现用了动态分配:
// 旧代码:动态分配 typedef struct msg_node { uint8_t *data; uint16_t len; struct msg_node *next; } msg_node_t; msg_node_t *head = NULL; void msg_queue_push(uint8_t *data, uint16_t len) { msg_node_t *node = malloc(sizeof(msg_node_t)); node->data = malloc(len); memcpy(node->data, data, len); node->len = len; node->next = head; head = node; }这段代码在PC上跑没问题,但在嵌入式系统里就是灾难。频繁的malloc和free会导致内存碎片,跑几天之后系统就崩了。而且中断里调用malloc是极其危险的行为,因为malloc不是可重入的。
我在规范里明确要求:消息队列必须使用静态环形缓冲区实现,禁止使用任何动态内存分配。缓冲区大小由宏定义指定,消息长度超过缓冲区容量时直接丢弃并返回错误码。
AI重构后的代码:
// 新代码:静态环形缓冲区 #define MSG_QUEUE_SIZE 16 #define MSG_MAX_LEN 64 typedef struct { uint8_t data[MSG_MAX_LEN]; uint16_t len; } msg_item_t; typedef struct { msg_item_t items[MSG_QUEUE_SIZE]; uint8_t head; uint8_t tail; uint8_t count; } msg_queue_t; static msg_queue_t msg_q; int msg_queue_push(const uint8_t *data, uint16_t len) { if (len > MSG_MAX_LEN) return -1; if (msg_q.count >= MSG_QUEUE_SIZE) return -2; memcpy(msg_q.items[msg_q.head].data, data, len); msg_q.items[msg_q.head].len = len; msg_q.head = (msg_q.head + 1) % MSG_QUEUE_SIZE; msg_q.count++; return 0; }这个改动彻底解决了内存碎片问题。所有内存都是静态分配的,队列满时直接返回错误码,由上层决定怎么处理。而且环形缓冲区的入队和出队操作都是O(1)复杂度,性能比链表好得多。
注意事项:AI在重构消息队列时,最容易犯的错误是忘记处理队列满和队列空的情况。我在规范里专门加了一条:“所有队列操作必须检查count字段,满时返回-1,空时返回-2。”这样AI就不会漏掉边界条件。
3.4 硬件抽象层的重构:接口与实现分离
硬件抽象层(HAL)的重构目标是让业务逻辑不再直接依赖具体硬件。原来的代码里,业务逻辑直接操作寄存器:
// 旧代码:直接操作寄存器 void app_led_on(void) { GPIOA->ODR |= (1 << 5); } void app_led_off(void) { GPIOA->ODR &= ~(1 << 5); }这种写法的问题在于:换个板子就得改业务代码。如果新板子的LED接在GPIOB的3号引脚,那所有调用app_led_on的地方都得改。
我在规范里要求:HAL层必须定义统一的接口,业务逻辑只能调用接口,不能直接操作寄存器。接口包括GPIO读写、延时、串口收发、定时器操作等。
AI重构后的HAL接口:
// hal.h typedef enum { HAL_GPIO_LED, HAL_GPIO_BUTTON, HAL_GPIO_RELAY } hal_gpio_id_t; void hal_gpio_write(hal_gpio_id_t id, uint8_t value); uint8_t hal_gpio_read(hal_gpio_id_t id); void hal_delay_ms(uint32_t ms);然后具体的实现放在hal_stm32.c或hal_esp32.c里,业务逻辑只包含hal.h。这样换平台时只需要换一个实现文件,业务代码一行不用改。
实操心得:AI在生成HAL接口时,会倾向于定义很多“通用”函数,比如
hal_gpio_toggle、hal_gpio_set_mode之类的。这些函数在初期用不上,但会增加代码体积。我的做法是:只定义当前用到的接口,用不到的让AI删掉。等以后真需要了再加,不要提前设计。
4. 实操过程与核心环节实现
4.1 环境准备与工具链配置
在开始重构之前,我需要先把开发环境搭好。这个项目原来是在Windows上用Keil MDK开发的,但Keil的工程文件格式对AI不友好,而且没法做自动化测试。所以我先把工程迁移到了Linux下的GCC工具链,用Makefile管理编译。
具体步骤:
- 安装交叉编译工具链:我用的是
gcc-arm-none-eabi,在Ubuntu下直接apt install gcc-arm-none-eabi就行。 - 编写Makefile:把原来的Keil工程文件里的源文件列表、头文件路径、编译选项提取出来,写成Makefile。这一步我让AI帮我做了,把Keil的
.uvprojx文件内容贴给AI,让它生成对应的Makefile。 - 搭建测试框架:我用的是Unity测试框架,配合CMock做硬件模拟。这样我可以在PC上跑单元测试,不需要真实硬件。
- 配置AI编程助手:我用的是某款支持长上下文的AI编程工具,把重构规范、模块接口说明、现有代码一起贴进去,让它生成重构后的代码。
这里有个坑:AI生成的Makefile经常漏掉一些编译选项,比如
-Wall -Wextra、-Os优化等级、-ffunction-sections等。这些选项对嵌入式项目很重要,我后来在规范里加了一条:“Makefile必须包含以下编译选项:-Wall -Wextra -Os -ffunction-sections -fdata-sections -Wl,--gc-sections。”这样AI就不会漏了。
4.2 逐模块重构的完整流程
整个重构过程我分了五个阶段,每个阶段处理一个模块。每个阶段的流程都是一样的:
第一步:准备输入材料。我把该模块的现有代码、接口说明、重构规范整理成一个文档,贴给AI。文档格式如下:
【模块名称】任务调度器 【现有代码】<粘贴代码> 【接口说明】<列出对外接口> 【重构规范】<粘贴相关规范条目> 【要求】保持接口不变,重写内部实现,输出完整代码文件第二步:AI生成代码。AI会输出重构后的代码,通常还会附带一些解释。我一般不看解释,直接看代码。
第三步:人工审查。我重点检查三个方面:接口是否一致、边界条件是否处理、是否有动态内存分配。如果发现问题,我把问题指出来,让AI重新生成。
第四步:编译测试。把AI生成的代码放到工程里编译,跑单元测试。如果测试不通过,把错误信息贴给AI,让它修复。
第五步:集成验证。把该模块和其他模块集成,跑集成测试。确认行为一致后,进入下一个模块。
这个流程看起来简单,但实际操作中,第三步和第四步会反复循环。我统计了一下,任务调度器模块改了3轮,消息队列改了5轮,HAL层改了2轮,状态机改了4轮。平均每个模块要改3-4轮才能通过所有测试。
4.3 关键参数的计算与选择过程
在重构过程中,有几个关键参数需要我手动计算和选择,不能完全交给AI。
第一个是任务调度器的最大任务数。原来的代码里MAX_TASKS是8,但实际只用了5个。我重新评估了一下,考虑到未来可能增加的功能,最终定为12。这个数字不是随便定的:每个任务结构体占16字节,12个任务就是192字节,加上调度器本身的开销,总共约220字节。我的目标板有64KB RAM,这个开销完全可以接受。
第二个是消息队列的缓冲区大小。原来的队列用了动态分配,没有固定大小。我根据实际消息的长度分布做了统计:大部分消息在16字节以内,最长的消息是48字节。所以我定了MSG_MAX_LEN=64,留了一些余量。队列深度方面,我统计了消息产生的峰值速率,大约是每秒20条,而消费速率是每秒30条,所以队列深度定为16就足够了。
第三个是状态机的状态转移表大小。原来的状态机用了switch-case,有7个状态和12个事件。改成转移表后,表的大小是7×12=84个条目。每个条目是一个函数指针,占4字节,总共336字节。这个开销在可接受范围内。
经验分享:参数选择不要问AI,要自己算。AI会给你一个“看起来合理”的数字,但它不知道你的RAM有多大、CPU有多快、消息速率是多少。这些只有你自己清楚。
4.4 代码审查与行为一致性验证
重构完成后,我做了一轮完整的代码审查。审查的重点不是“代码写得好不好”,而是“行为是否一致”。我用了三种方法来验证:
方法一:单元测试对比。我把旧代码和新代码分别编译成PC上的可执行文件,用同一组测试用例跑,对比输出结果。这个方法最直接,能发现大部分逻辑错误。
方法二:静态分析。我用cppcheck和clang-tidy对新代码做静态分析,检查是否有未初始化变量、数组越界、空指针解引用等问题。AI生成的代码在这方面表现不错,但偶尔会有一些“看起来没问题但实际有隐患”的写法,比如在中断里调用非可重入函数。
方法三:运行时监控。我把新代码烧到目标板上,跑了一个72小时的稳定性测试。监控指标包括:任务执行时间、消息队列深度、CPU利用率、内存使用量。测试期间没有出现死机或异常复位,说明重构后的代码在稳定性上没有问题。
| 验证方法 | 发现问题数 | 典型问题 |
|---|---|---|
| 单元测试对比 | 12 | 边界条件处理不一致 |
| 静态分析 | 5 | 中断里调用非可重入函数 |
| 运行时监控 | 2 | 任务执行时间超预期 |
5. 常见问题与排查技巧实录
5.1 AI重构时最容易犯的五个错误
在两周的重构过程中,我总结了AI最容易犯的五个错误。这些错误不是AI“不会”,而是它的“默认习惯”和嵌入式开发的要求不一致。
错误一:滥用动态内存分配。AI在需要管理可变数量对象时,第一反应就是用malloc。比如消息队列、任务列表、状态机状态,它都会倾向于动态分配。这在PC上没问题,但在嵌入式里是禁忌。我的应对方法是在规范里明确写“禁止使用malloc/free”,并且在审查时搜索代码里是否有malloc关键字。
错误二:在中断里调用非可重入函数。AI经常把一些耗时操作放在中断服务函数里,比如memcpy、printf、甚至malloc。这些函数在中断里调用会导致不可预期的问题。我的应对方法是在规范里规定:“中断服务函数只能调用可重入函数,且执行时间不超过10微秒。”
错误三:忽略volatile关键字。在嵌入式里,硬件寄存器和中断共享变量必须用volatile修饰。AI经常忘记加,导致编译器优化后行为异常。我的应对方法是在规范里列出所有需要volatile的场景,让AI对照检查。
错误四:栈空间估算不足。AI生成的代码经常有很深的函数调用层次,或者在大函数里定义大数组。这在嵌入式里会导致栈溢出。我的应对方法是让AI在生成代码后,输出每个函数的栈使用估算,然后我手动检查最坏情况下的栈深度。
错误五:中断优先级配置错误。AI在生成中断相关代码时,经常忽略优先级配置,或者配置成不合理的值。我的应对方法是在规范里明确每个中断的优先级范围,让AI按规范配置。
5.2 行为不一致问题的排查思路
重构后最让人头疼的问题就是“行为不一致”。明明逻辑看起来一样,但跑出来的结果就是不对。我遇到过几次典型情况:
情况一:任务执行顺序变了。旧代码里任务是从数组末尾开始执行的,新代码改成了从头部开始。这导致某些依赖执行顺序的任务出了问题。排查方法是打印每个任务的执行时间戳,对比新旧代码的差异。
情况二:消息队列满了之后的处理不同。旧代码在队列满时直接覆盖最老的消息,新代码在队列满时返回错误。这导致上层逻辑的行为发生了变化。排查方法是构造队列满的场景,观察新旧代码的行为差异。
情况三:状态机的默认转移不同。旧代码在遇到未定义的事件时保持当前状态,新代码在遇到未定义事件时跳转到默认状态。排查方法是遍历所有状态和事件的组合,对比新旧代码的转移结果。
排查技巧:用“差分测试”的方法。把新旧代码的同一个函数分别跑一遍,输入相同的参数,对比输出。如果输出不同,就逐步缩小输入范围,直到找到最小的差异用例。这个方法比看代码快得多。
5.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 编译报错“undefined reference” | AI生成的函数声明和定义不一致 | 检查头文件和源文件的函数签名 | 让AI重新生成,确保签名一致 |
| 运行时死机 | 栈溢出或中断冲突 | 用调试器查看栈指针和中断状态 | 减小栈使用,调整中断优先级 |
| 任务不执行 | 调度器计数器溢出 | 检查计数器的数据类型和溢出条件 | 改用更大的数据类型或调整计数逻辑 |
| 消息丢失 | 队列满时处理不当 | 打印队列深度和入队出队日志 | 增加队列深度或优化消费速度 |
| 状态机卡死 | 转移表有遗漏 | 遍历所有状态和事件组合 | 补全转移表,增加默认转移 |
| 硬件不响应 | HAL接口实现错误 | 用示波器检查引脚电平 | 检查HAL实现,确认寄存器操作正确 |
5.4 独家避坑技巧
技巧一:让AI生成“变更说明”。每次AI重构完一个模块,我让它输出一份“变更说明”,列出所有改动的函数、改动的原因、可能的影响。这份说明帮我快速定位问题,也方便后续回溯。
技巧二:保留旧代码作为参考。重构过程中,我没有删除旧代码,而是把它放在一个单独的目录里。当新代码出现问题时,我可以随时对比旧代码的行为。重构完成后,旧代码又保留了一个月才删除。
技巧三:用版本控制做“检查点”。每完成一个模块的重构,我就提交一次代码。这样如果后续模块的重构引入了问题,我可以快速回滚到上一个检查点,而不是从头再来。
技巧四:让AI写测试用例。我让AI为每个模块生成单元测试用例,然后我手动补充边界用例。AI生成的测试用例覆盖了大部分正常路径,我补充的边界用例覆盖了异常路径。两者结合,测试覆盖率能达到90%以上。
技巧五:不要一次性重构所有模块。我见过有人把整个项目扔给AI,让它一次性重构完。结果出来的代码有几百个错误,根本没法调试。正确的做法是一个模块一个模块地来,每个模块验证通过后再进行下一个。
6. 重构后的效果与个人体会
重构完成后的框架,代码量从原来的约2500行减少到了约1800行,但功能完全一致。代码风格统一了,所有模块都用了结构体封装和静态内存分配。新加入的同事看了之后说:“终于能看懂了。”这句话让我觉得这两周的折腾值了。
从性能上看,任务切换开销从原来的约8微秒降低到了约5微秒,因为去掉了动态内存分配和链表遍历。消息队列的入队出队操作从O(n)变成了O(1),因为用了环形缓冲区。CPU利用率从原来的45%降低到了38%,因为去掉了不必要的内存分配和释放。
但最大的收获不是这些数字,而是我对AI辅助开发这件事有了更清晰的认识。AI确实能大幅减轻重构的工作量,但它不是“全自动”的。你需要给它清晰的规范、明确的边界、可验证的目标。你需要审查它的输出、测试它的行为、修正它的错误。你需要做架构决策、参数计算、优先级排序。这些工作AI做不了,也不应该让AI做。
我个人在实际操作中的体会是:AI最好的角色是“高级代码生成器”,而不是“架构师”。你告诉它“怎么写”,它写得又快又好;你让它决定“写什么”,它就会跑偏。所以重构这件事,核心还是在你手里,AI只是帮你把想法变成代码的工具。
最后再分享一个小技巧:如果你也想让AI重构你的嵌入式项目,先从最小的模块开始。找一个只有一两百行、依赖最少的模块,让AI重构一遍,跑通整个流程。这样你能快速了解AI的能力边界和常见问题,然后再逐步扩大范围。不要一上来就搞核心模块,那样一旦出问题,排查成本会非常高。