夜雨聆风学习资料网

ARTICLE · 1234812

研发执行Agent:基于设计文档的代码生成、审查与质量门禁

研发执行Agent:基于设计文档的代码生成、审查与质量门禁

研发执行Agent:基于设计文档的代码生成、审查与质量门禁

本文聚焦研发全生命周期中的第三阶段——研发执行。技术设计文档定稿后,RD进入编码阶段。研发执行Agent不是简单的代码补全工具,而是深度理解技术设计文档的编码伙伴——它能读设计文档生成代码骨架,能作为第一个Reviewer审查PR,能嵌入CI/CD流水线做质量门禁。

1. 开篇:现有编码工具不理解设计文档

Copilot、Cursor、Claude Code——这些编码工具已经很好了,但它们有一个共同的盲区:它们只理解代码上下文,不理解设计文档。

一个真实场景:RD写完技术设计文档,里面有完整的接口定义、数据模型、业务流程。打开IDE开始编码,Copilot的补全基于当前文件的上下文——它不知道设计文档里定义了什么接口、什么数据模型、什么流程。

结果是:RD一边看着设计文档,一边手动把文档里的接口定义、数据模型翻译成代码。这个翻译过程是机械的、重复的、容易出错的。如果设计文档改了,代码不一定跟着改。如果代码改了,设计文档不一定同步。

研发执行Agent要解决的,就是从设计文档到代码的最后一公里:

  1. 基于设计文档生成代码骨架
    ——不是基于当前文件上下文补全,而是读取设计文档中的接口定义、数据模型、业务流程,生成代码骨架和核心实现
  2. PR审查
    ——Agent作为第一个Reviewer,检查代码与设计文档的一致性、代码规范、潜在Bug、安全风险
  3. 质量门禁
    ——在CI/CD流水线中嵌入Agent质量检查节点,检查单测覆盖率、集成测试完整性、日志埋点完备性

1.1 研发执行Agent的三大能力

1.2 和现有编码工具的定位差异

维度
Copilot/Cursor
研发执行Agent
上下文来源
当前文件+工作区
技术设计文档+代码仓库
生成粒度
行级/函数级补全
模块级骨架+核心实现
审查能力
无
PR级结构化审查
质量门禁
无
CI/CD嵌入
设计文档感知
无
深度读取理解
代码仓库检索
局部(当前工作区)
全局(向量化语义检索)

定位关系:两者互补,不是替代。Copilot负责行级补全,研发执行Agent负责设计文档到代码骨架的翻译、PR审查和CI门禁。RD在日常编码中两者都用。

2. 设计文档驱动代码生成

2.1 代码生成架构

from dataclasses import dataclassfrom typing importOptionalfrom enum import EnumclassGenerateTarget(Enum):"""生成目标类型"""    INTERFACE = "interface"# 接口定义    MODEL = "model"# 数据模型/Entity    SERVICE = "service"# Service层    REPOSITORY = "repository"# 数据访问层    CONTROLLER = "controller"# Controller层    CONFIG = "config"# 配置类    TEST = "test"# 测试类    SKELETON = "skeleton"# 完整骨架@dataclassclassGeneratedCode:"""生成的代码"""    target_type: GenerateTarget    file_path: str    class_name: str    code: str    language: str    imports: list[str]    dependencies: list[str]       # 依赖的其他类    test_code: Optional[str]      # 对应的测试代码    design_doc_ref: str# 对应设计文档的章节classCodeGenerator:"""设计文档驱动代码生成器"""def__init__(self, llm_client, code_search, design_doc: str):self.llm = llm_clientself.code_search = code_searchself.design_doc = design_docself._parsed_doc = Nonedefgenerate(self, target: GenerateTarget) -> list[GeneratedCode]:"""根据目标类型生成代码"""        doc = self._get_parsed_doc()        generators = {            GenerateTarget.MODEL: self._gen_models,            GenerateTarget.INTERFACE: self._gen_interfaces,            GenerateTarget.REPOSITORY: self._gen_repositories,            GenerateTarget.SERVICE: self._gen_services,            GenerateTarget.CONTROLLER: self._gen_controllers,            GenerateTarget.SKELETON: self._gen_skeleton,        }        gen_func = generators.get(target)ifnot gen_func:return []return gen_func(doc)def_get_parsed_doc(self) -> dict:"""解析设计文档为结构化数据"""ifself._parsed_doc:returnself._parsed_doc        prompt = f"""        解析以下技术设计文档,提取结构化信息:{self.design_doc[:5000]}        输出JSON格式:        {{            "project_info": {{                "name": "项目名",                "language": "java/python/go",                "framework": "spring boot/fastapi/gin",                "base_package": "com.xxx.xxx"            }},            "data_models": [                {{                    "name": "类名",                    "table_name": "表名",                    "fields": [                        {{"name":"字段名","type":"类型","nullable":true,"comment":"注释"}}                    ],                    "indexes": ["索引字段"],                    "relations": ["关联的表"]                }}            ],            "api_endpoints": [                {{                    "method": "GET/POST/PUT/DELETE",                    "path": "/api/xxx",                    "description": "描述",                    "request_params": [{{"name":"","type":"","required":true}}],                    "response": "返回类型",                    "auth_required": true                }}            ],            "business_flows": [                {{                    "name": "流程名",                    "steps": ["步骤1","步骤2"],                    "exception_handling": "异常处理方式"                }}            ]        }}        """        result = self.llm.chat(prompt)import jsonself._parsed_doc = json.loads(result)returnself._parsed_docdef_gen_models(self, doc: dict) -> list[GeneratedCode]:"""生成数据模型代码"""        models = doc.get("data_models", [])        project = doc.get("project_info", {})        results = []for model in models:            prompt = self._build_model_prompt(model, project)            code = self.llm.chat(prompt)            test_code = self._gen_model_test(model, project, code)            results.append(GeneratedCode(                target_type=GenerateTarget.MODEL,                file_path=self._get_model_path(model["name"], project),                class_name=model["name"],                code=code,                language=project.get("language", "java"),                imports=[],                dependencies=[],                test_code=test_code,                design_doc_ref=f"数据模型: {model['name']}",            ))return resultsdef_build_model_prompt(self, model: dict, project: dict) -> str:"""构建数据模型生成Prompt"""        lang = project.get("language", "java")        framework = project.get("framework", "spring boot")        pkg = project.get("base_package", "com.example")        fields_str = "\n".join(f"  - {f['name']}: {f['type']} "f"({'可空'if f.get('nullable') else'非空'}) "f"{f.get('comment', '')}"for f in model.get("fields", [])        )        indexes_str = ", ".join(model.get("indexes", []))        relations_str = ", ".join(model.get("relations", []))returnf"""        生成{lang}数据模型类。        项目信息:        - 语言:{lang}        - 框架:{framework}        - 包名:{pkg}        模型信息:        - 类名:{model['name']}        - 表名:{model.get('table_name', model['name'].lower())}        - 字段:{fields_str}        - 索引:{indexes_str}        - 关联:{relations_str}        要求:        1. 使用{framework}的标准注解        2. 包含所有字段的getter/setter(或Lombok注解)        3. 添加字段注释        4. 包含表名映射注解        5. 生成完整的import语句        """def_gen_model_test(        self, model: dict, project: dict, model_code: str) -> str:"""生成数据模型的测试代码"""        lang = project.get("language", "java")        prompt = f"""        为以下{lang}数据模型生成单元测试:        模型名:{model['name']}        模型代码:{model_code[:2000]}        测试要求:        1. 测试所有字段的getter/setter        2. 测试equals和hashCode(如适用)        3. 测试边界值(null/空字符串/最大长度)        4. 使用JUnit 5 + AssertJ        """returnself.llm.chat(prompt)def_gen_interfaces(self, doc: dict) -> list[GeneratedCode]:"""生成接口定义代码"""        endpoints = doc.get("api_endpoints", [])        project = doc.get("project_info", {})# 按模块分组接口        modules = {}for ep in endpoints:            module = self._extract_module(ep["path"])if module notin modules:                modules[module] = []            modules[module].append(ep)        results = []for module, eps in modules.items():            class_name = f"{module.capitalize()}Controller"            prompt = self._build_controller_prompt(eps, project, class_name)            code = self.llm.chat(prompt)            test_code = self._gen_controller_test(eps, project, code)            results.append(GeneratedCode(                target_type=GenerateTarget.CONTROLLER,                file_path=self._get_controller_path(class_name, project),                class_name=class_name,                code=code,                language=project.get("language", "java"),                imports=[],                dependencies=[],                test_code=test_code,                design_doc_ref=f"接口设计: {module}",            ))return resultsdef_build_controller_prompt(        self, endpoints: list, project: dict, class_name: str) -> str:"""构建Controller生成Prompt"""        lang = project.get("language", "java")        framework = project.get("framework", "spring boot")        pkg = project.get("base_package", "com.example")        eps_str = "\n".join(f"  - {ep['method']}{ep['path']}: {ep.get('description','')}\n"f"    参数: {ep.get('request_params', [])}\n"f"    返回: {ep.get('response', '')}\n"f"    鉴权: {'是'if ep.get('auth_required') else'否'}"for ep in endpoints        )returnf"""        生成{lang}{framework} Controller类。        项目信息:        - 语言:{lang}        - 框架:{framework}        - 包名:{pkg}        类名:{class_name}        接口列表:{eps_str}        要求:        1. 使用{framework}的标准注解(@RestController @RequestMapping等)        2. 每个接口包含参数校验注解        3. 统一返回格式(Result<T>)        4. 异常处理注解        5. 完整的import语句        6. 每个方法添加JavaDoc注释        7. 日志埋点(入参+出参+耗时)        """def_gen_controller_test(        self, endpoints: list, project: dict, controller_code: str) -> str:"""生成Controller测试"""        prompt = f"""        为以下Controller生成集成测试:{controller_code[:3000]}        接口列表:{endpoints}        测试要求:        1. 使用 MockMvc        2. 每个接口测试正常流程和异常流程        3. 测试参数校验        4. 测试鉴权        """returnself.llm.chat(prompt)def_gen_services(self, doc: dict) -> list[GeneratedCode]:"""生成Service层代码"""        flows = doc.get("business_flows", [])        endpoints = doc.get("api_endpoints", [])        project = doc.get("project_info", {})# 按模块分组        modules = set()for ep in endpoints:            modules.add(self._extract_module(ep["path"]))        results = []for module in modules:            class_name = f"{module.capitalize()}Service"# 收集该模块的流程            module_flows = [                f for f in flowsif module.lower() in f.get("name", "").lower()            ]            prompt = f"""            生成{project.get('language','java')} Service类。            项目:{project.get('framework','spring boot')}            包名:{project.get('base_package','com.example')}            类名:{class_name}            相关接口:{[ep for ep in endpoints if module in ep['path']]}            业务流程:{module_flows}            要求:            1. Service接口+实现类            2. 事务注解            3. 日志埋点            4. 异常处理            5. 依赖注入Repository            """            code = self.llm.chat(prompt)            results.append(GeneratedCode(                target_type=GenerateTarget.SERVICE,                file_path=self._get_service_path(class_name, project),                class_name=class_name,                code=code,                language=project.get("language", "java"),                imports=[],                dependencies=[],                test_code=None,                design_doc_ref=f"业务流程: {module}",            ))return resultsdef_gen_repositories(self, doc: dict) -> list[GeneratedCode]:"""生成数据访问层代码"""        models = doc.get("data_models", [])        project = doc.get("project_info", {})        results = []for model in models:            class_name = f"{model['name']}Repository"            prompt = f"""            生成{project.get('language','java')} Repository接口。            框架:{project.get('framework','spring boot')}            包名:{project.get('base_package','com.example')}            实体:{model['name']}            表名:{model.get('table_name', model['name'].lower())}            要求:            1. 继承JpaRepository(或MyBatis Mapper)            2. 包含基本CRUD            3. 根据索引字段添加查询方法            4. 自定义查询使用@Query注解            """            code = self.llm.chat(prompt)            results.append(GeneratedCode(                target_type=GenerateTarget.REPOSITORY,                file_path=self._get_repo_path(class_name, project),                class_name=class_name,                code=code,                language=project.get("language", "java"),                imports=[],                dependencies=[model["name"]],                test_code=None,                design_doc_ref=f"数据访问: {model['name']}",            ))return resultsdef_gen_skeleton(self, doc: dict) -> list[GeneratedCode]:"""生成完整项目骨架"""        all_code = []        all_code.extend(self._gen_models(doc))        all_code.extend(self._gen_repositories(doc))        all_code.extend(self._gen_services(doc))        all_code.extend(self._gen_interfaces(doc))return all_codedef_extract_module(self, path: str) -> str:"""从API路径提取模块名"""        parts = path.strip("/").split("/")return parts[0] if parts else"default"def_get_model_path(self, name: str, project: dict) -> str:        pkg = project.get("base_package", "com.example").replace(".", "/")returnf"src/main/java/{pkg}/model/{name}.java"def_get_controller_path(self, name: str, project: dict) -> str:        pkg = project.get("base_package", "com.example").replace(".", "/")returnf"src/main/java/{pkg}/controller/{name}.java"def_get_service_path(self, name: str, project: dict) -> str:        pkg = project.get("base_package", "com.example").replace(".", "/")returnf"src/main/java/{pkg}/service/{name}.java"def_get_repo_path(self, name: str, project: dict) -> str:        pkg = project.get("base_package", "com.example").replace(".", "/")returnf"src/main/java/{pkg}/repository/{name}.java"

2.2 代码生成示例

以"支付对账功能"为例,Agent基于设计文档生成的Model和Controller代码:

// === 生成的数据模型 ===package com.example.payment.model;import javax.persistence.*;import lombok.Data;import java.time.LocalDateTime;/** * 对账结果表 * 对应表: reconciliation_result */@Data@Entity@Table(name = "reconciliation_result",        indexes = {           @Index(name = "idx_batch_id", columnList = "batch_id"),           @Index(name = "idx_channel", columnList = "channel"),           @Index(name = "idx_status", columnList = "status"),           @Index(name = "idx_created_at", columnList = "created_at")       })publicclassReconciliationResult {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;/** 对账批次ID */@Column(name = "batch_id", nullable = false, length = 64)private String batchId;/** 支付通道: ALIPAY/WXPAY/UNIONPAY */@Column(name = "channel", nullable = false, length = 20)private String channel;/** 对账模式: T0/T1 */@Column(name = "reconcile_mode", nullable = false, length = 4)private String reconcileMode;/** 本方订单号 */@Column(name = "local_order_id", length = 64)private String localOrderId;/** 通道订单号 */@Column(name = "channel_order_id", length = 64)private String channelOrderId;/** 对账状态: MATCHED/MISMATCH/LOCAL_ONLY/CHANNEL_ONLY */@Column(name = "status", nullable = false, length = 20)private String status;/** 差异金额(分) */@Column(name = "diff_amount")private Long diffAmount;/** 差异原因 */@Column(name = "diff_reason", length = 500)private String diffReason;/** 处理状态: PENDING/AUTO_RESOLVED/MANUAL_RESOLVED */@Column(name = "resolution_status", length = 20)private String resolutionStatus;@Column(name = "created_at", nullable = false)private LocalDateTime createdAt;@Column(name = "updated_at")private LocalDateTime updatedAt;}
// === 生成的Controller ===package com.example.payment.controller;import com.example.payment.model.ReconciliationResult;import com.example.payment.service.ReconciliationService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;import javax.validation.Valid;import java.util.List;/** * 对账接口 */@Slf4j@RestController@RequestMapping("/api/reconciliation")@RequiredArgsConstructor@Api(tags = "对账管理")publicclassReconciliationController {privatefinal ReconciliationService reconciliationService;/**     * 触发对账     */@PostMapping("/trigger")@ApiOperation("手动触发对账")public Result<String> triggerReconciliation(@Valid@RequestBody TriggerRequest request) {        log.info("触发对账: channel={}, mode={}",                 request.getChannel(), request.getMode());StringbatchId= reconciliationService.trigger(            request.getChannel(), request.getMode());        log.info("对账触发成功: batchId={}", batchId);return Result.success(batchId);    }/**     * 查询对账结果     */@GetMapping("/results")@ApiOperation("查询对账结果")public Result<List<ReconciliationResult>> getResults(@RequestParam String batchId,@RequestParam(required = false) String status) {        log.info("查询对账结果: batchId={}, status={}", batchId, status);        List<ReconciliationResult> results =             reconciliationService.getResults(batchId, status);return Result.success(results);    }/**     * 查询差异列表     */@GetMapping("/diffs")@ApiOperation("查询差异列表")public Result<List<ReconciliationResult>> getDiffs(@RequestParam String batchId,@RequestParam(defaultValue = "1")int page,@RequestParam(defaultValue = "20")int size) {        log.info("查询差异列表: batchId={}, page={}, size={}",                 batchId, page, size);        List<ReconciliationResult> diffs =             reconciliationService.getDiffs(batchId, page, size);return Result.success(diffs);    }/**     * 处理差异     */@PostMapping("/diffs/{id}/resolve")@ApiOperation("处理差异")public Result<Void> resolveDiff(@PathVariable Long id,@Valid@RequestBody ResolveRequest request) {        log.info("处理差异: id={}, action={}", id, request.getAction());        reconciliationService.resolveDiff(id, request);return Result.success(null);    }}

2.3 代码生成的质量控制

classCodeQualityValidator:"""代码质量验证器"""def__init__(self, llm_client):self.llm = llm_clientdefvalidate(        self, generated: GeneratedCode, design_doc_section: str) -> dict:"""验证生成的代码质量"""        issues = []# 1. 设计文档一致性检查        consistency = self._check_design_consistency(            generated, design_doc_section        )        issues.extend(consistency)# 2. 代码规范检查        convention = self._check_convention(generated)        issues.extend(convention)# 3. 编译可行性检查        compilable = self._check_compilable(generated)        issues.extend(compilable)return {"total_issues": len(issues),"issues": issues,"quality_score": self._calc_score(issues),        }def_check_design_consistency(        self, code: GeneratedCode, doc_section: str) -> list[dict]:"""检查代码与设计文档的一致性"""        prompt = f"""        检查以下代码是否与设计文档一致:        设计文档章节:{doc_section[:1000]}        生成的代码:{code.code[:2000]}        检查:        1. 字段名是否与设计文档一致        2. 字段类型是否匹配        3. 接口路径是否一致        4. 是否遗漏了设计文档中的字段/接口        输出JSON数组:[{{"type":"","description":""}}],无问题返回[]。        """        result = self.llm.chat(prompt)import jsontry:return json.loads(result)except json.JSONDecodeError:return []def_check_convention(self, code: GeneratedCode) -> list[dict]:"""检查代码规范"""        issues = []# 检查是否有日志if code.target_type in [            GenerateTarget.CONTROLLER, GenerateTarget.SERVICE        ]:if"log."notin code.code and"logger."notin code.code:                issues.append({"type": "convention","description": "缺少日志埋点",                })# 检查是否有注释if"//"notin code.code and"/*"notin code.code:            issues.append({"type": "convention","description": "缺少代码注释",            })# 检查异常处理if code.target_type == GenerateTarget.SERVICE:if"try"notin code.code and"throw"notin code.code:                issues.append({"type": "convention","description": "Service层缺少异常处理",                })return issuesdef_check_compilable(self, code: GeneratedCode) -> list[dict]:"""检查编译可行性(简化版)"""        issues = []# 检查括号匹配        open_braces = code.code.count("{")        close_braces = code.code.count("}")if open_braces != close_braces:            issues.append({"type": "syntax","description": f"大括号不匹配: {{={open_braces}, }}={close_braces}",            })# 检查分号(Java)if code.language == "java":            lines = code.code.split("\n")for i, line inenumerate(lines, 1):                stripped = line.strip()if (stripped andnot stripped.endswith(";") andnot stripped.endswith("{") andnot stripped.endswith("}")andnot stripped.endswith(",")andnot stripped.startswith("//")andnot stripped.startswith("*")andnot stripped.startswith("/*")andnot stripped.startswith("package")andnot stripped.startswith("import")):                    issues.append({"type": "syntax","description": f"第{i}行可能缺少分号: {stripped[:50]}",                    })return issuesdef_calc_score(self, issues: list[dict]) -> float:        score = 10.0for issue in issues:if issue.get("type") == "syntax":                score -= 2.0elif issue.get("type") == "convention":                score -= 0.5elif issue.get("type") == "design_consistency":                score -= 1.0returnmax(score, 0.0)

3. PR智能审查

3.1 审查引擎架构

3.2 PR审查引擎

@dataclassclassReviewComment:"""审查意见"""    file_path: str    line_number: int    severity: str# critical / major / minor / nitpick    category: str# 设计一致性/规范/Bug/安全/性能    comment: str    suggestion: str# 修改建议(含代码)    inline: bool# 是否行内评论@dataclassclassPRReviewResult:"""PR审查结果"""    overall_score: float    comments: list[ReviewComment]    summary: str    recommendation: str# approve / request_changes / comment    checked_files: int    checked_lines: intclassPRReviewEngine:"""PR审查引擎"""def__init__(self, llm_client, code_search, design_doc: str):self.llm = llm_clientself.code_search = code_searchself.design_doc = design_docself.reviewers = [            DesignConsistencyReviewer(llm_client, design_doc),            CodeConventionReviewer(llm_client),            BugDetectionReviewer(llm_client),            SecurityReviewer(llm_client),            PerformanceReviewer(llm_client),        ]defreview(        self,        pr_diff: str,        pr_metadata: dict,        existing_comments: list = None,) -> PRReviewResult:"""执行PR审查"""# 解析diff为文件变更        file_changes = self._parse_diff(pr_diff)        all_comments = []for reviewer inself.reviewers:            comments = reviewer.review(file_changes, pr_metadata)            all_comments.extend(comments)# 去重        all_comments = self._deduplicate(all_comments)# 生成总结        summary = self._generate_summary(all_comments, pr_metadata)# 计算评分和建议        score = self._calc_score(all_comments)        recommendation = self._get_recommendation(all_comments, score)        checked_files = len(file_changes)        checked_lines = sum(len(f.get("added_lines", [])) for f in file_changes        )return PRReviewResult(            overall_score=score,            comments=all_comments,            summary=summary,            recommendation=recommendation,            checked_files=checked_files,            checked_lines=checked_lines,        )def_parse_diff(self, diff: str) -> list[dict]:"""解析diff为文件变更列表"""import re        files = []        current_file = Nonefor line in diff.split("\n"):if line.startswith("diff --git"):if current_file:                    files.append(current_file)                current_file = {"path": "", "added_lines": [], "removed_lines": []}match = re.search(r'b/(.+)$', line)ifmatch:                    current_file["path"] = match.group(1)elif line.startswith("+++") or line.startswith("---"):continueelif line.startswith("+") andnot line.startswith("+++"):if current_file:                    current_file["added_lines"].append(line[1:])elif line.startswith("-") andnot line.startswith("---"):if current_file:                    current_file["removed_lines"].append(line[1:])if current_file:            files.append(current_file)return filesdef_deduplicate(        self, comments: list[ReviewComment]) -> list[ReviewComment]:"""去重"""        seen = set()        unique = []for c in comments:            key = f"{c.file_path}:{c.line_number}:{c.category}"if key notin seen:                seen.add(key)                unique.append(c)return uniquedef_generate_summary(        self, comments: list[ReviewComment], metadata: dict) -> str:"""生成审查总结"""        critical = sum(1for c in comments if c.severity == "critical")        major = sum(1for c in comments if c.severity == "major")        minor = sum(1for c in comments if c.severity == "minor")        prompt = f"""        生成PR审查总结。        PR标题: {metadata.get('title', '')}        PR描述: {metadata.get('description', '')}        审查结果:        - Critical: {critical}        - Major: {major}        - Minor: {minor}        关键问题:{self._format_top_issues(comments[:5])}        生成200字以内的审查总结。        """returnself.llm.chat(prompt)def_format_top_issues(        self, comments: list[ReviewComment]) -> str:return"\n".join(f"- [{c.severity}] {c.file_path}:{c.line_number}{c.comment}"for c in comments        )def_calc_score(self, comments: list[ReviewComment]) -> float:        score = 10.0for c in comments:if c.severity == "critical": score -= 2.0elif c.severity == "major": score -= 1.0elif c.severity == "minor": score -= 0.3elif c.severity == "nitpick": score -= 0.1returnmax(score, 0.0)def_get_recommendation(        self, comments: list[ReviewComment], score: float) -> str:        critical = sum(1for c in comments if c.severity == "critical")        major = sum(1for c in comments if c.severity == "major")if critical > 0:return"request_changes"elif major > 2or score < 6.0:return"request_changes"elif major > 0or score < 8.0:return"comment"else:return"approve"

3.3 设计一致性审查器

classDesignConsistencyReviewer:"""设计一致性审查器——检查代码是否与设计文档一致"""def__init__(self, llm_client, design_doc: str):self.llm = llm_clientself.design_doc = design_docdefreview(        self, file_changes: list[dict], pr_metadata: dict) -> list[ReviewComment]:        comments = []for change in file_changes:            file_path = change["path"]            added = "\n".join(change["added_lines"])ifnot added.strip():continue# 检查与设计文档的一致性            prompt = f"""            检查以下代码变更是否与技术设计文档一致。            设计文档:{self.design_doc[:3000]}            文件: {file_path}            新增代码:{added[:2000]}            检查维度:            1. 接口路径是否与设计文档一致            2. 字段名和类型是否匹配            3. 是否遗漏了设计文档中定义的字段/接口            4. 业务逻辑是否与流程设计一致            输出JSON数组:            [{{                "line": 0,                "severity": "major/minor",                "comment": "问题描述",                "suggestion": "修改建议"            }}]            无问题返回[]。            """            result = self.llm.chat(prompt)import jsontry:                issues = json.loads(result)for issue in issues:                    comments.append(ReviewComment(                        file_path=file_path,                        line_number=issue.get("line", 0),                        severity=issue.get("severity", "minor"),                        category="设计一致性",                        comment=issue.get("comment", ""),                        suggestion=issue.get("suggestion", ""),                        inline=True,                    ))except (json.JSONDecodeError, KeyError):passreturn commentsclassCodeConventionReviewer:"""代码规范审查器"""def__init__(self, llm_client):self.llm = llm_clientdefreview(self, file_changes: list, metadata: dict) -> list[ReviewComment]:        comments = []for change in file_changes:            file_path = change["path"]for i, line inenumerate(change["added_lines"], 1):# 检查命名规范if"class "in line:                    class_name = line.split("class ")[1].split(" ")[0].split("{")[0].strip()if class_name andnot class_name[0].isupper():                        comments.append(ReviewComment(                            file_path=file_path, line_number=i,                            severity="minor", category="规范",                            comment=f"类名{class_name}应大写开头",                            suggestion=f"改为{class_name.capitalize()}",                            inline=True,                        ))# 检查魔法数字import re                numbers = re.findall(r'\b(\d{3,})\b', line)for num in numbers:if num notin ["200", "404", "500", "301", "302"]:                        comments.append(ReviewComment(                            file_path=file_path, line_number=i,                            severity="nitpick", category="规范",                            comment=f"魔法数字{num},建议提取为常量",                            suggestion="提取为命名常量",                            inline=True,                        ))# 检查TODO/FIXMEif"TODO"in line or"FIXME"in line:                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="minor", category="规范",                        comment="包含TODO/FIXME,上线前需处理",                        suggestion="处理或创建Issue跟踪",                        inline=True,                    ))return commentsclassBugDetectionReviewer:"""Bug检测审查器"""def__init__(self, llm_client):self.llm = llm_clientdefreview(self, file_changes: list, metadata: dict) -> list[ReviewComment]:        comments = []for change in file_changes:            file_path = change["path"]            added = "\n".join(change["added_lines"])ifnot added.strip():continue            prompt = f"""            检查以下代码变更中的潜在Bug:            文件: {file_path}            代码:{added[:2000]}            检查:            1. 空指针风险            2. 并发安全问题            3. 资源泄漏(未关闭的流/连接)            4. 数组越界            5. 类型转换异常            6. 逻辑错误(条件判断/循环)            7. 事务问题            输出JSON数组:[{{"line":0,"severity":"critical/major/minor","comment":"","suggestion":""}}]            无问题返回[]。            """            result = self.llm.chat(prompt)import jsontry:                issues = json.loads(result)for issue in issues:                    comments.append(ReviewComment(                        file_path=file_path,                        line_number=issue.get("line", 0),                        severity=issue.get("severity", "minor"),                        category="Bug",                        comment=issue.get("comment", ""),                        suggestion=issue.get("suggestion", ""),                        inline=True,                    ))except (json.JSONDecodeError, KeyError):passreturn commentsclassSecurityReviewer:"""安全风险审查器"""def__init__(self, llm_client):self.llm = llm_clientdefreview(self, file_changes: list, metadata: dict) -> list[ReviewComment]:        comments = []for change in file_changes:            file_path = change["path"]for i, line inenumerate(change["added_lines"], 1):# SQL注入风险if"String sql"in line and"+"in line:                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="critical", category="安全",                        comment="可能的SQL注入:字符串拼接SQL",                        suggestion="使用参数化查询或PreparedStatement",                        inline=True,                    ))# 敏感信息硬编码import reif re.search(r'(password|secret|key|token)\s*=\s*["\']', line, re.I):                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="critical", category="安全",                        comment="敏感信息硬编码",                        suggestion="使用配置中心或环境变量",                        inline=True,                    ))# XSS风险if"<script"in line.lower():                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="major", category="安全",                        comment="可能的XSS风险",                        suggestion="对用户输入进行转义",                        inline=True,                    ))return commentsclassPerformanceReviewer:"""性能审查器"""def__init__(self, llm_client):self.llm = llm_clientdefreview(self, file_changes: list, metadata: dict) -> list[ReviewComment]:        comments = []for change in file_changes:            file_path = change["path"]for i, line inenumerate(change["added_lines"], 1):# N+1查询风险if".forEach("in line and".findById("in line:                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="major", category="性能",                        comment="可能的N+1查询:循环内查数据库",                        suggestion="使用批量查询",                        inline=True,                    ))# 循环内创建对象if ("for "in line or".stream()"in line) and"new "in line:                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="minor", category="性能",                        comment="循环内创建对象,考虑复用",                        suggestion="提取到循环外",                        inline=True,                    ))# 大列表未分页if".findAll()"in line:                    comments.append(ReviewComment(                        file_path=file_path, line_number=i,                        severity="minor", category="性能",                        comment="全量查询风险,数据量大时性能差",                        suggestion="使用分页查询",                        inline=True,                    ))return comments

3.4 PR审查报告示例

# PR审查报告## 审查摘要| 指标 | 值 ||------|-----|| 评分 | 7.5/10 || 审查文件 | 8 || 审查行数 | 245 || Critical | 0 || Major | 2 || Minor | 5 || Nitpick | 3 |**建议**: request_changes——存在2个主要问题需修复## 审查总结PR实现了对账功能的Controller、Service和Repository层,整体架构与设计文档一致。主要问题:1)ReconciliationService的compare方法缺少事务注解,可能导致差异写入时的数据不一致;2)通道适配器未做超时处理,通道响应慢时会阻塞对账主流程。建议修复后合并。## 审查意见| # | 严重度 | 文件 | 行 | 类别 | 描述 | 建议 ||---|--------|------|----|------|------|------|| 1 | major | ReconciliationService.java | 45 | Bug | compare方法缺少@Transactional | 添加@Transactional注解 || 2 | major | AlipayAdapter.java | 32 | 性能 | 未设置HTTP超时 | 添加5s超时配置 || 3 | minor | ReconciliationController.java | 28 | 设计一致性 | 接口路径为/trigger但设计文档为/reconcile | 统一为/reconcile || 4 | minor | ReconciliationResult.java | 15 | 规范 | 魔法数字200 | 提取为常量MAX_BATCH_SIZE || 5 | minor | DiffProcessor.java | 67 | Bug | 循环内IO操作 | 批量处理 || 6 | minor | ReconciliationService.java | 89 | 规范 | TODO注释 | 创建Issue跟踪 || 7 | nitpick | ReconciliationResult.java | 30 | 规范 | 字段命名diffAmount不一致 | 改为differenceAmount |

4. CI/CD质量门禁

4.1 质量门禁架构

4.2 质量门禁引擎

@dataclassclassGateResult:"""门禁检查结果"""    gate_name: str    passed: bool    score: float    details: str    blocking: bool# 是否阻断构建classQualityGateEngine:"""CI/CD质量门禁引擎"""def__init__(self, llm_client, design_doc: str):self.llm = llm_clientself.design_doc = design_docself.gates = [            DesignConsistencyGate(design_doc),            TestCoverageGate(),            LoggingGate(),            ExceptionHandlingGate(),            SecurityScanGate(),            SQLPerformanceGate(),        ]defrun_gates(        self,        repo_path: str,        changed_files: list[str],        test_results: dict,) -> list[GateResult]:"""运行所有质量门禁"""        results = []for gate inself.gates:            result = gate.check(repo_path, changed_files, test_results)            results.append(result)# 阻断性门禁失败,立即返回if result.blocking andnot result.passed:breakreturn resultsdefgenerate_report(        self, results: list[GateResult]) -> str:"""生成质量门禁报告"""        all_passed = all(r.passed for r in results)        report = "# 质量门禁检查报告\n\n"        report += f"**总体结果**: {'✅ 通过'if all_passed else'❌ 阻断'}\n\n"        report += "| 门禁 | 状态 | 评分 | 详情 |\n"        report += "|------|------|------|------|\n"for r in results:            status = "✅"if r.passed else"❌"            report += f"| {r.gate_name} | {status} | {r.score:.1f} | {r.details[:50]} |\n"# 阻断项详情        blocking = [r for r in results ifnot r.passed and r.blocking]if blocking:            report += "\n## 阻断项详情\n\n"for r in blocking:                report += f"### {r.gate_name}\n{r.details}\n\n"return reportclassDesignConsistencyGate:"""设计文档一致性门禁"""def__init__(self, design_doc: str):self.design_doc = design_docdefcheck(self, repo_path, changed_files, test_results) -> GateResult:# 简化检查:检查代码中是否包含设计文档定义的接口路径import os        missing = []for file_path in changed_files:            full_path = os.path.join(repo_path, file_path)ifnot os.path.exists(full_path):continuewithopen(full_path, 'r') as f:                content = f.read()# 检查设计文档中的接口是否在代码中实现import re            endpoints = re.findall(r'(GET|POST|PUT|DELETE)\s+(/\S+)', self.design_doc            )for method, path in endpoints:if path notin content:                    missing.append(f"{method}{path}")if missing:return GateResult(                gate_name="设计文档一致性",                passed=False, score=5.0,                details=f"缺少接口: {', '.join(missing[:3])}",                blocking=True,            )return GateResult(            gate_name="设计文档一致性",            passed=True, score=10.0,            details="所有设计文档接口已实现",            blocking=True,        )classTestCoverageGate:"""测试覆盖率门禁"""    MIN_COVERAGE = 0.70# 最低70%覆盖率defcheck(self, repo_path, changed_files, test_results) -> GateResult:        coverage = test_results.get("coverage", 0)if coverage < self.MIN_COVERAGE:return GateResult(                gate_name="测试覆盖率",                passed=False,                score=coverage * 10,                details=f"覆盖率{coverage*100:.1f}%低于阈值{self.MIN_COVERAGE*100:.0f}%",                blocking=True,            )return GateResult(            gate_name="测试覆盖率",            passed=True,            score=coverage * 10,            details=f"覆盖率{coverage*100:.1f}%",            blocking=True,        )classLoggingGate:"""日志埋点门禁"""defcheck(self, repo_path, changed_files, test_results) -> GateResult:import os        missing_log = []for file_path in changed_files:if"Controller"notin file_path and"Service"notin file_path:continue            full_path = os.path.join(repo_path, file_path)ifnot os.path.exists(full_path):continuewithopen(full_path, 'r') as f:                content = f.read()# 检查每个public方法是否有日志import re            methods = re.findall(r'public\s+\w+\s+(\w+)\s*\(', content            )for method in methods:if method in ['getter', 'setter', 'toString']:continue# 简化检查:方法前后是否有log调用if content.count("log.") < len(methods):                    missing_log.append(f"{file_path}::{method}")if missing_log:return GateResult(                gate_name="日志埋点",                passed=True,  # 非阻断                score=6.0,                details=f"部分方法缺少日志: {', '.join(missing_log[:3])}",                blocking=False,            )return GateResult(            gate_name="日志埋点",            passed=True, score=10.0,            details="日志埋点完备",            blocking=False,        )classExceptionHandlingGate:"""异常处理门禁"""defcheck(self, repo_path, changed_files, test_results) -> GateResult:import os        issues = []for file_path in changed_files:if"Service"notin file_path:continue            full_path = os.path.join(repo_path, file_path)ifnot os.path.exists(full_path):continuewithopen(full_path, 'r') as f:                content = f.read()# 检查是否有裸catchif"catch (Exception e)"in content and"throw"notin content:                issues.append(f"{file_path}: 裸catch未抛出异常")# 检查是否有e.printStackTrace()if"printStackTrace"in content:                issues.append(f"{file_path}: 使用printStackTrace而非日志")if issues:return GateResult(                gate_name="异常处理",                passed=False, score=5.0,                details="; ".join(issues[:3]),                blocking=False,            )return GateResult(            gate_name="异常处理",            passed=True, score=10.0,            details="异常处理规范",            blocking=False,        )classSecurityScanGate:"""安全扫描门禁"""defcheck(self, repo_path, changed_files, test_results) -> GateResult:import os, re        issues = []for file_path in changed_files:            full_path = os.path.join(repo_path, file_path)ifnot os.path.exists(full_path):continuewithopen(full_path, 'r') as f:                content = f.read()# SQL注入if re.search(r'String\s+sql\s*=.*\+', content):                issues.append(f"{file_path}: SQL拼接风险")# 硬编码密码if re.search(r'password\s*=\s*["\']', content, re.I):                issues.append(f"{file_path}: 硬编码密码")# 不安全的随机数if"Math.random()"in content and"token"in content.lower():                issues.append(f"{file_path}: 使用Math.random生成token")if issues:return GateResult(                gate_name="安全扫描",                passed=False, score=0.0,                details="; ".join(issues),                blocking=True,            )return GateResult(            gate_name="安全扫描",            passed=True, score=10.0,            details="无安全风险",            blocking=True,        )classSQLPerformanceGate:"""SQL性能门禁"""defcheck(self, repo_path, changed_files, test_results) -> GateResult:import os, re        issues = []for file_path in changed_files:ifnot file_path.endswith((".java", ".xml")):continue            full_path = os.path.join(repo_path, file_path)ifnot os.path.exists(full_path):continuewithopen(full_path, 'r') as f:                content = f.read()# 检查SELECT *if re.search(r'SELECT\s+\*', content, re.I):                issues.append(f"{file_path}: SELECT * 查询")# 检查无LIMIT的查询if re.search(r'findAll\(\)', content):                issues.append(f"{file_path}: findAll()无分页")# 检查无索引的WHEREif re.search(r'WHERE\s+\w+\s*LIKE\s+[\'\"]%', content, re.I):                issues.append(f"{file_path}: LIKE前缀模糊查询无法走索引")if issues:return GateResult(                gate_name="SQL性能",                passed=True, score=5.0,                details="; ".join(issues[:3]),                blocking=False,            )return GateResult(            gate_name="SQL性能",            passed=True, score=10.0,            details="SQL性能检查通过",            blocking=False,        )

4.3 门禁报告示例

# 质量门禁检查报告**总体结果**: ❌ 阻断| 门禁 | 状态 | 评分 | 详情 ||------|------|------|------|| 设计文档一致性 | ✅ | 10.0 | 所有设计文档接口已实现 || 测试覆盖率 | ❌ | 5.2 | 覆盖率52.0%低于阈值70% || 日志埋点 | ✅ | 6.0 | 部分方法缺少日志 || 异常处理 | ✅ | 10.0 | 异常处理规范 || 安全扫描 | ✅ | 10.0 | 无安全风险 || SQL性能 | ✅ | 5.0 | SELECT * 查询 |## 阻断项详情### 测试覆盖率覆盖率52.0%低于阈值70%。缺失覆盖的类:- ReconciliationService (35%)- AlipayAdapter (20%)- DiffProcessor (40%)建议补充以下测试:1. ReconciliationService.compare() 的正常和异常流程测试2. AlipayAdapter.fetchBill() 的超时和重试测试3. DiffProcessor.process() 的边界值测试

5. 研发执行Agent与编码工具的协作模型

5.1 工作流分工

5.2 三层协作模型

层次
负责方
职责
工具
骨架生成
研发执行Agent
基于设计文档生成Model/Service/Controller骨架+测试
Agent LLM
逻辑编码
RD+Copilot
填充业务逻辑、实现复杂算法
IDE+Copilot
PR审查
研发执行Agent
设计一致性/Bug/安全/性能检查
Agent LLM
质量门禁
研发执行Agent
CI/CD嵌入的自动化检查
Agent+CI

关键原则:Agent生成骨架和审查,RD填充逻辑和做业务决策。两者不是替代关系,而是分工协作。

6. 效率提升与质量度量

6.1 效率对比

环节
无Agent
有Agent
提升
骨架代码编写
1天
0.2天
5x
测试代码编写
0.5天
0.1天
5x
PR自审
0.3天
0.05天
6x
CI门禁检查
人工抽查
全自动
质变
总计1.8天0.35天5x

6.2 质量指标

指标
定义
目标
骨架代码可用率
生成的骨架代码无需修改直接可编译的比例
≥ 80%
设计一致性
代码与设计文档一致的比例
≥ 90%
PR审查召回率
Agent发现的问题占所有问题的比例
≥ 70%
门禁误报率
门禁阻断中误报的比例
≤ 5%
安全漏报率
安全问题漏检的比例
≤ 2%

7. 本篇小结

核心观点回顾

  1. 研发执行Agent的核心差异是"理解设计文档"——Copilot/Cursor理解代码上下文,Agent理解设计文档,两者互补

  2. 代码生成从设计文档驱动——读取设计文档中的接口定义、数据模型、业务流程,生成Model/Repository/Service/Controller骨架+测试代码

  3. PR审查是Agent作为第一个Reviewer——五个维度审查:设计一致性、代码规范、Bug检测、安全风险、性能问题,产出结构化审查意见

  4. 质量门禁嵌入CI/CD流水线——六个门禁:设计一致性(阻断)、测试覆盖率(阻断)、安全扫描(阻断)、日志埋点(非阻断)、异常处理(非阻断)、SQL性能(非阻断)

  5. 三层协作模型——Agent生成骨架+审查+门禁,RD+Copilot填充逻辑,各自发挥优势

  6. 代码生成不是替代RD编码——Agent生成的骨架需要RD修改完善,填充业务逻辑。Agent的价值是减少机械性翻译工作,让RD专注在业务逻辑实现上

记住:研发执行Agent不是要替代RD写代码,而是要成为RD的"第一个助手"——帮RD把设计文档翻译成代码骨架、帮RD做第一轮PR审查、帮RD在CI流水线里守质量底线。RD的价值不在写Model/Controller的getter/setter,而在设计业务逻辑、处理边界条件、做技术权衡。Agent把这些机械性工作接管了,RD才能真正专注于高价值工作。

相关学习资料