工程中 AI 协同研发:方式、规约与提交门禁 一、AI 协同研发的主要方式1.1 双路径并行模式AI 辅助开发不是一条路走到黑而是实验与生产分离各走各的节奏实验路径Prompt-to-DeployCursor / Comate → 快速生成 → 本地/Preview 环境验证 → 快速迭代适用原型探索、想法验证、非生产功能生产路径Git-Push-to-DeployAI 生成代码 → 人工 Review → PR 提交 → CI/CD 门禁 → Merge → 部署适用所有进入主干的代码实验路径的典型场景你让 Comate 帮你写一个读 CSV 算平均值的 C 程序本地gcc跑一下看效果跑不通继续改不进main分支没有任何心理负担。生产路径是所有要上线代码的唯一通道每一步都有门禁责任在人而不在 AI。两条路径都要有审计追踪能回答这段代码谁生成的、什么时候改的、谁审过。1.2 多 Agent 协作分工不是让一个 AI 包办一切而是多个专职 Agent 开发者拍板Agent职责C 语言例子代码生成写功能代码生成read_config()代码审查找 bug、风格指出i n越界测试生成写单元测试生成test_divide()安全审计查漏洞发现strcpy可能溢出核心原则AI 提建议人做决策。合并、上线必须由具体的人决定。1.3 Plan-Review-Execute 流程复杂任务不要一上来就让 AI 写代码分三步走Step 1 — PlanAI 先出方案人确认你给项目加配置文件读取功能 AI 输出方案 1. 在根目录新增 config.c / config.h 2. 用 fopen fgets 读 INI 格式 3. 错误返回 int 错误码禁止 exit() 4. 不改现有 main 逻辑只新增调用 5. 新增测试文件 test_config.cStep 2 — Review你审方案架构放对了吗业务层不该直接读文件边界条件想到了吗文件不存在、空文件要不要新依赖能不能只用标准库Step 3 — Execute方案确认后 AI 才生成代码逐块出 diff逐块审查后合入。小改动可以跳过 Plan但涉及安全、架构、数据库的改动必须走完三步。二、AI 协同研发规约2.1 Rules 文件体系这是给 AI 的项目级说明书以.cursor/rules/为例各层加载时机目录何时加载C 项目例子basic/每次必加载检查malloc返回值、禁止硬编码密码modules/改对应模块时改utils/时加载utils.mdcworkflow/做特定任务时新增文件读取功能时加载规则文件标准格式# C 语言错误处理规范 ## 基础规范 - 所有系统调用必须检查返回值 - 使用 strerror(errno) 记录错误原因 ## 强制行为 - fopen 失败必须打印错误信息并返回错误码 - 函数返回前必须释放已分配的资源 ## 禁止行为 - 禁止在库函数/工具函数中调用 exit() - 禁止硬编码文件路径或凭证 ## 示例代码 // ✅ 正确做法 int read_config(const char *path, char *buf, size_t size) { FILE *fp fopen(path, r); if (fp NULL) { fprintf(stderr, cannot open %s: %s\n, path, strerror(errno)); return -1; // 返回错误让调用方决定怎么办 } // ... fclose(fp); return 0; }为什么示例代码最重要AI 对「写好错误处理」理解模糊但对一段具体的fopen失败返回-1的代码理解很准模仿代码比遵循文字描述可靠得多。2.2 规约设计原则原则说明好/坏例子分层架构basic/modules/workflow 分开✅ basic 管内存modules 管业务层职责分离一个文件只管一件事❌ 把 SQL 规范和 UI 规范写在一起可执行性只写有具体操作的规则❌「确保高性能」✅「循环内禁止 malloc」示例驱动用代码代替抽象描述✅ 给一段正确的 read_file 实现优先级明确冲突时 basic 层赢basic 说必须检查 NULLworkflow 不能例外2.3 AI 协作执行协议ai.mdc这是 AI 每次改代码前的标准操作流程写入ai.mdc# AI 协作执行协议 ## 执行流程 1. 识别当前场景新增功能 / 重构 / Bug 修复 2. 加载对应规则basic/ 必须全加载modules/ workflow/ 按需 3. 读取项目现有示例代码作为风格参考 4. 执行强制行为规避禁止行为 5. 输出 diff 前自检是否引入安全漏洞是否符合架构约束 ## 禁止项 - 禁止生成含硬编码凭证的代码 - 禁止在库函数中调用 exit() 跳过错误处理 - 禁止引入项目未使用的新依赖需先确认2.4 C 语言错误处理AI 最容易踩的坑这是 AI 生成 C 代码时最高频的问题值得单独讲清楚。核心原则层级该怎么做库函数 / 工具函数返回错误码打日志释放资源main函数可以return 1表示程序失败退出不可恢复时才考虑abort()极少用exit(1)的问题在工具函数里调用整个进程立刻结束上层完全没机会重试、降级或清理资源。写法一返回错误码最常用#include stdio.h #include errno.h #include string.h int read_file(const char *path, char *buf, size_t buf_size) { if (path NULL || buf NULL || buf_size 0) { return -1; } FILE *fp fopen(path, r); if (fp NULL) { fprintf(stderr, cannot open %s: %s\n, path, strerror(errno)); return -1; // 记录原因返回错误让调用方决定 } if (fgets(buf, (int)buf_size, fp) NULL) { fprintf(stderr, read failed: %s\n, strerror(errno)); fclose(fp); return -1; } fclose(fp); return 0; }调用方自己决定怎么办char buf[256]; if (read_file(config.txt, buf, sizeof(buf)) ! 0) { fprintf(stderr, load config failed, using defaults\n); // 用默认配置继续运行而不是直接崩掉 }写法二枚举错误类型区分错误原因typedef enum { ERR_OK 0, ERR_INVALID_ARG -1, ERR_OPEN_FILE -2, ERR_READ_FILE -3, } err_t; err_t read_file(const char *path, char *buf, size_t buf_size) { if (path NULL || buf NULL || buf_size 0) { return ERR_INVALID_ARG; } FILE *fp fopen(path, r); if (fp NULL) { perror(path); return ERR_OPEN_FILE; } if (fgets(buf, (int)buf_size, fp) NULL) { perror(fgets); fclose(fp); return ERR_READ_FILE; } fclose(fp); return ERR_OK; }写法三main 里统一退出只在最外层int main(void) { FILE *fp fopen(data.txt, r); if (fp NULL) { fprintf(stderr, cannot open data.txt: %s\n, strerror(errno)); return 1; // ✅ main 里 return 非 0程序失败 } /* 使用 fp ... */ fclose(fp); return 0; }对比为什么工具函数里不要 exit/* ❌ 不好调用方完全没机会处理 */ int load_config(const char *path) { FILE *fp fopen(path, r); if (!fp) exit(1); return 0; } /* ✅ 好把错误往上交 */ int load_config(const char *path) { FILE *fp fopen(path, r); if (!fp) { fprintf(stderr, open %s failed: %s\n, path, strerror(errno)); return -1; } fclose(fp); return 0; }一句话记忆fopen失败 → 记录原因 →return错误码 → 让上层决定怎么办。2.5 提交信息规约AI 生成的代码在 commit message 中要标明来源fix(utils): handle fopen failure in read_config - Return ERR_OPEN_FILE instead of exit(1) - Add strerror(errno) logging on failure - Release fp before return on read error Co-authored-by: AI (reviewed by jerry)这样git log能清楚看出哪些是 AI 辅助的谁审过出问题时追责有据可查。三、提交门禁设定门禁 代码合并/上线前必须通过的检查关卡。AI 写代码快更容易藏 bug所以门禁比纯人工研发更重要。git commit ↓ 【第一道】Pre-commit Hooks本地 ↓ git push → 开 PR ↓ 【第二道】PR 阶段AI 预审 人工 Review ↓ 【第三道】CI 阶段自动化质检 ↓ 【第四道】合并门禁全绿 Approve ↓ 【第五道】生产发布人工签字3.1 Pre-commit Hooks本地第一道门在git commit的瞬间本地自动跑检查不通过就 commit 失败。# .pre-commit-config.yaml repos: - repo: local hooks: - id: lint-check # 代码格式/风格检查 - id: secret-scan # 扫描硬编码密钥gitleaks - id: unit-test-quick # 快速单元测试 30sC 项目的等效实现.githooks/pre-commit#!/bin/bash set -e # 1. 编译检查 echo Compiling... gcc -Wall -Wextra -stdc11 -lm calculator.c -o /tmp/calc_check echo OK # 2. 单元测试 echo Running tests... make test # 3. 硬编码密钥扫描 echo Scanning secrets... if grep -rn password\s*\|api_key\s*\|secret\s* *.c; then echo ERROR: hardcoded credential detected exit 1 fi echo All checks passed安装方式cp .githooks/pre-commit .git/hooks/pre-commit chmod x .git/hooks/pre-commit注意.git/hooks/不进版本控制新克隆的开发者不会自动生效。推荐在Makefile加一个setuptargetsetup: cp .githooks/pre-commit .git/hooks/pre-commit chmod x .git/hooks/pre-commit3.2 PR 阶段AI 预审 人工 Review提交 PR 后触发两件事并行提交 PR ├── AI 预审30 秒内→ 审查报告贴到 PR 评论区 └── 人工 Reviewer 介入 → 以 AI 报告为基础做最终判断AI 预审覆盖维度维度检查项C 语言例子安全性注入、越权、泄露strcpy溢出、密码硬编码正确性边界、空指针、并发i n越界、malloc未检查可维护性复杂度、重复、命名单函数超 200 行架构合规分层、循环依赖业务层直接#include mysql.h必须人工 Review 的场景AI 不可替代涉及认证 / 鉴权 / 加密逻辑涉及数据库 schema 变更AI 生成代码量 500 行人工 Review 具体看什么以下面这段 PR diff 为例// AI 生成的改动 for (int i 0; i 5; i) { sum numbers[i]; }CI 跑测试sum 15→ ✅ 通过同事 Review「5应改成sizeof(numbers)/sizeof(numbers[0])不然数组长度变了又越界」→ Request changes这就是人工 Review 不能省的原因机器测的是当前输入人看的是代码本身的健壮性。典型的 Review 对话同事fopen 失败为什么没打日志 你 已加 fprintf(stderr, ...)请再看 同事OKApprove ✅3.3 CI 阶段自动化质检代码推到 GitHub 后服务器自动跑完整流水线# .github/workflows/ci.yml name: CI on: push: branches: [feature/*, fix/*] pull_request: branches: [main, develop] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Compile run: gcc -Wall -Wextra -stdc11 -lm calculator.c -o calculator unit-test: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Run tests run: make test # 目标覆盖率 80%需配合 gcov sast-scan: needs: unit-test runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: cppcheck run: | cppcheck --enableall --error-exitcode1 \ --suppressmissingIncludeSystem \ calculator.c deploy: needs: [build, unit-test, sast-scan] if: github.ref refs/heads/main runs-on: ubuntu-latest steps: - name: Deploy run: echo Deploy to productionPR 页面上你会看到Checks ✅ build — 编译成功 ✅ unit-test — 单元测试通过 ✅ sast-scan — cppcheck 无报错 ❌ unit-test — 覆盖率 67% 80% ← Merge 按钮变灰CI 的核心价值可重复、可审计不依赖「我本地测过了」这种口头保证。没有 CI有 CI靠人说「本地没问题」机器在标准环境再测一遍AI 漏边界条件可能混进去测试失败会拦住各人环境不同结果不一致环境统一结果可信3.4 合并门禁Merge Gate所有条件满足才能点 Merge必须全部通过 ✅ CI 所有 Stage 绿灯 ✅ 至少 1 位 Senior Engineer Approve ✅ AI 预审无 P0/P1 级未处理问题 ✅ SAST 扫描无高危漏洞 ✅ 无未解决的 Review Comment 特殊场景额外要求 ✅ 安全相关变更 → Security Team Approve ✅ 数据库变更 → DBA Review ✅ 架构变更 → 架构委员会确认P0/P1 严重等级P0必须立刻修如缓冲区溢出、SQL 注入P1合并前必须修如缺少错误处理、未检查malloc返回值3.5 生产发布最后一道门核心原则2026 业界共识必须有具体的人签字承担责任不允许 AI 代码直接自动上线。发布申请 → 技术负责人确认清单 □ 变更影响范围是否已评估 □ 回滚方案是否已准备出问题怎么撤 □ 监控告警是否已配置上线后怎么知道挂了 □ 我是否理解这段代码的逻辑并愿意为其负责不允许的模式AI 生成 → CI 过了 → 自动上线中间没有人担责。四、版本控制协作规范4.1 超细粒度提交策略AI 生成快更要小步提交方便定位和回滚# 每完成一个小功能点立即 commit git commit -m feat: add input validation for operator git commit -m fix: handle modulo by zero git commit -m test: add test cases for modulo operator实验性分支隔离失败了直接丢弃不污染主干git worktree add ../feature-ai-experiment experiment/ai-exp # 实验失败 git worktree remove ../feature-ai-experiment git branch -D experiment/ai-exp4.2 分支策略main保护分支只能 PR 合入 └── develop ├── feature/ai-xxx # AI 辅助功能开发 ├── fix/ai-xxx # AI 辅助 Bug 修复 └── experiment/xxx # AI 纯实验分支门禁可松五、完整流程串联一个真实例子以给calculator.c新增幂运算^为例走完生产路径1. Plan你帮我加一个幂运算支持 ./calculator 2 ^ 10 AI 方案 - 新增 double power(double base, double exp) 函数 - 使用标准库 pow()需要 -lm已有 - 在 switch 里加 case ^ - 新增 test_power() 测试函数 你OK但幂运算 exp 为负数时要处理2. ExecuteAI 生成 diff你逐行审查后合入到本地/* calculator.c 新增 */ double power(double base, double exp) { return pow(base, exp); // pow 处理负指数返回 NaN 时调用方检查 } /* test_calculator.c 新增 */ void test_power() { ASSERT_DOUBLE_EQ(power(2, 10), 1024); ASSERT_DOUBLE_EQ(power(2, 0), 1); ASSERT_DOUBLE_EQ(power(2, -1), 0.5); printf([PASS] test_power\n); }3. Pre-commit Hook 自动跑 Compiling... OK Running tests... Running Tests [PASS] test_add [PASS] test_subtract [PASS] test_multiply [PASS] test_divide [PASS] test_power All Tests Passed Scanning secrets... OK All checks passed4. Commitgit commit -m feat(calc): add power operator support - Add power() function using stdlib pow() - Add case ^ in main switch - Add test_power() with positive/zero/negative exponents Co-authored-by: AI (reviewed by jerry)5. PR CIChecks ✅ build ✅ unit-test ✅ sast-scan (cppcheck)6. 同事 Review Approve同事power() 没有检查 pow() 返回 NaN 的情况 你 已在 main 里加 isnan(result) 检查请再看 同事OKApprove ✅7. Merge → 发布Merge Gate 通过 → 合入 develop → PR 到 main 技术负责人确认清单全勾 → 发布