AI编程协作闭环:Harness与SDD构建可交付代码的工程实践 1. 项目概述从“能跑通”到“能交付”的协作鸿沟如果你和我一样在过去一年里深度参与了AI辅助编程的实践那么你肯定经历过这样的场景你和你的AI伙伴无论是GitHub Copilot、Cursor还是基于大模型的本地Agent配合默契它帮你生成了大段代码修复了恼人的bug甚至重构了整个模块。代码在本地跑起来了功能也实现了你长舒一口气感觉生产力爆棚。但当你准备把这份“成果”提交到团队仓库进入正式的Code Review和CI/CD流程时头疼的事情才刚刚开始。生成的代码风格和团队规范不一致需要手动调整AI可能引入了一些未声明的依赖或使用了过时的API更棘手的是当多个AI Agent或者多个开发者使用AI并行工作时他们各自生成的代码如何整合如何确保每一次AI的“贡献”都是清晰、可追溯、且符合交付标准的这就是“AI编程可闭环协作”要解决的核心问题。前两卷我们探讨了基础的单Agent工作流和多Agent的通信与任务分解而这一卷我们要啃最硬的骨头如何让AI驱动的代码改动变得像资深工程师提交的PR一样可签收、可合并。这不仅仅是给AI生成的代码打个标签那么简单。它关乎一整套工程实践和思维模式的转变。我们引入两个关键概念Harness测试与验证套件和SDD示例驱动开发。Harness确保AI的产出是“正确”的而SDD则确保AI的产出是“符合预期”的。两者的结合旨在为每一次AI协作产生的代码改动建立一个从生成、验证到集成的可信流水线。简单说我们要让AI的“黑盒”输出变得透明、可评估、可放心地合入主干。2. 核心理念拆解Harness与SDD为何是闭环的关键在传统的软件工程中我们对人类工程师的产出有一系列质量门禁单元测试、集成测试、代码规范检查、同行评审。这些门禁建立在一些默认的共识之上工程师理解业务需求、遵循团队约定、具备调试和修正能力。但AI Agent目前还不完全具备这些“共识”。它可能完美地通过了你给出的单个测试用例却完全误解了模块的职责边界它可能写出了语法正确的代码但采用了项目禁止的设计模式。因此我们不能直接把给人用的质量门禁套用在AI身上而是需要为AI量身定制一套“沟通语言”和“验证机制”。这就是Harness和SDD登场的原因。2.1 Harness不止于测试的“验证与执行环境”Harness这个词在软件测试中常指“测试套件”或“测试工具”但在这里我们赋予它更广的含义一个针对特定任务或代码单元的、可执行的验证与约束环境。你可以把它想象成给AI Agent准备的一个标准化“工作台”或“质检车间”。一个完整的Harness通常包含以下要素输入/输出规格定义明确说明函数、模块或任务需要接收什么以及期望输出什么。这不仅仅是类型签名还包括更复杂的约束如数据范围、边界条件、性能指标。可执行的验证逻辑这是一组自动化的检查可以是单元测试断言、属性测试、集成测试场景甚至是调用一个外部验证服务。上下文与依赖模拟为AI提供完成任务所需的完整上下文例如模拟的数据库连接、伪造的API响应、特定的配置文件状态。这能避免AI因为缺失环境信息而生成无法运行的代码。资源与安全限制明确CPU/内存/时间的限制以及网络、文件系统访问的白名单。防止AI生成消耗过大资源或不安全的代码。Harness的核心价值在于“可执行性”和“客观性”。它把模糊的、基于自然语言的需求描述转化为一系列机器可以无情、快速执行的检查。当AI Agent接收到一个附带了Harness的任务时它不是在猜测“人类想要什么”而是在求解“如何通过所有这些检查”。这极大地对齐了AI与人类的期望。注意构建Harness本身需要成本。一个经验法则是为那些会被频繁修改、核心业务逻辑复杂、或由不同AI Agent协作的模块创建Harness。对于一次性的、简单的脚本任务过度设计Harness可能得不偿失。2.2 SDD示例驱动开发——用“榜样”代替“说教”SDD即Specification by Example or Example-Driven Development并非新概念。但在AI编程的上下文中它的价值被无限放大。我们都有体会给AI下指令时“写一个函数处理用户登录”远不如“参考/src/auth/legacy_login.py里handle_oauth_callback函数的风格和错误处理写一个处理短信登录的函数”来得有效。SDD的核心是用具体的、可运行的正面和反面例子来定义需求、接口和行为规范。对于AI来说一个好的例子胜过千言万语的需求文档。在协作上下文中SDD可以体现为接口示例提供一个调用新函数的示例代码展示参数传递和返回值的使用方式。成功用例给出输入A期望得到输出B的具体案例。失败用例给出输入C明确期望抛出何种异常或返回何种错误码。代码风格示例直接提供一段项目内的典范代码作为代码结构、命名、注释的模板。重构示例展示“坏味道”代码和重构后代码的对比让AI理解重构的方向。SDD与Harness的关系是互补的Harness提供了“判断题”的评分标准对/错而SDD提供了“范文”和“常见错误解析”指导AI如何写出能通过Harness的“正确作文”。在任务分发给AI Agent时同时附上Harness和SDD能最大程度保证产出质量。3. 实操框架构建“可签收”的AI协作工作流理论讲完了我们来看如何落地。目标是建立一个工作流使得任何一个由AI无论是主Agent还是子Agent产生的代码改动在合并前都满足“可签收”状态。下图展示了一个基于Harness和SDD的协作与合并流程flowchart TD A[产品/技术负责人] --|创建| B[“史诗级任务brEpic”] B -- C[“任务分析与拆解br生成SDD与Harness”] C -- D{“任务类型判断”} D -- “清晰、独立子任务” -- E[“附带完整HarnessSDDbr派发给AI Agent执行”] D -- “模糊、探索性任务” -- F[“派发给AI Agent进行br探索与SDD草案生成”] E -- G[“AI Agent生成代码br并在本地Harness中验证”] F -- H[“AI Agent提交探索结果br与SDD草案”] H -- I[“人工审核与精化SDDbr补充Harness”] I -- E G -- J{“验证是否通过”} J -- “是” -- K[“生成‘可签收’改动集br代码通过记录”] J -- “否” -- L[“AI Agent根据错误br进行迭代修复”] L -- G K -- M[“改动集进入评审队列”] M -- N[“人工或自动化评审br重点看设计、可读性”] N -- O{“评审是否通过”} O -- “是” -- P[“安全合入主干分支”] O -- “否” -- Q[“提供反馈br返回修正”] Q -- L下面我们来拆解这个流程中的几个关键环节。3.1 环节一任务准备——定义Harness与SDD这是整个流程中最需要人工智慧和经验的环节通常由资深开发者或技术负责人完成。1. 为任务创建专属的验证环境Harness假设我们需要AI协助“为现有的用户服务UserService添加一个根据手机号前缀批量查询用户的功能”。我们不是直接让AI去改代码而是先为这个任务创建一个Harness。创建独立的测试文件test_user_service_batch_query.py。这个文件就是Harness的载体。编写验证逻辑在文件中我们基于项目的测试框架如pytest编写清晰的测试用例。这些用例不仅包括功能验证还应包含边界条件、错误处理。# test_user_service_batch_query.py import pytest from your_project.user_service import UserService from your_project.models import User # 这是一个Harness示例它定义了“正确”的标准 class TestUserServiceBatchQuery: pytest.fixture def service(self): # 提供依赖模拟 return UserService(mock_db_session) def test_batch_query_by_phone_prefix_success(self, service): 成功用例查询存在的号码前缀返回正确用户列表 # 准备测试数据SDD中的例子 mock_users [User(phone13800138001), User(phone13800138002)] insert_mock_data(mock_users) # 定义输入和期望输出Harness的核心 prefixes [1380013] result service.batch_query_by_phone_prefix(prefixes) assert len(result) 2 assert all(user.phone.startswith(1380013) for user in result) def test_batch_query_empty_result(self, service): 成功用例查询不存在的号码前缀返回空列表 prefixes [999] result service.batch_query_by_phone_prefix(prefixes) assert result [] def test_batch_query_invalid_input(self, service): 失败用例输入为空列表应抛出ValueError with pytest.raises(ValueError): service.batch_query_by_phone_prefix([]) def test_batch_query_performance(self, service): 非功能需求查询1000个前缀应在1秒内完成 import time prefixes [f13{i:08d} for i in range(1000)] start time.time() _ service.batch_query_by_phone_prefix(prefixes) elapsed time.time() - start assert elapsed 1.0, f查询耗时{elapsed}秒超过性能要求2. 编写任务说明书SDD创建一个TASK_SPEC.md文件与Harness测试文件放在一起。# 任务说明书为UserService添加批量手机号前缀查询功能 ## 目标 在 UserService 类中添加一个方法 batch_query_by_phone_prefix(prefixes: List[str]) - List[User] 用于根据手机号前缀列表批量查询用户。 ## 接口与示例SDD核心 **方法签名** python def batch_query_by_phone_prefix(self, prefixes: List[str]) - List[User]:调用示例# 成功调用 service UserService(db_session) users service.batch_query_by_phone_prefix([138, 139]) # users 应包含所有手机号以138或139开头的用户列表可能为空。 # 错误处理示例 try: service.batch_query_by_phone_prefix([]) except ValueError as e: print(e) # 应提示“前缀列表不能为空”参考实现SDD核心请参考同一模块中现有的query_by_phone(phone: str)方法的数据库查询方式使用SQLAlchemy的filter(User.phone.startswith(...))。错误处理模式对输入参数进行非空校验。日志记录在方法开始和结束时记录INFO级别日志。返回类型直接返回ORM对象列表。注意事项该方法应忽略重复的前缀。查询条件应使用OR逻辑即查找匹配任一前缀的用户。性能要求详见Harness中的性能测试。不要修改现有query_by_phone方法的逻辑。验收标准链接到Harness通过所有在test_user_service_batch_query.py中定义的测试。现在我们将这个包含TASK_SPEC.md和test_user_service_batch_query.py的目录作为一个完整的、可签收的任务包派发给AI Agent。 ### 3.2 环节二AI执行与本地验证 AI Agent例如配置了特定指令的Cursor或ChatGPT高级数据分析接收到任务包后它的工作流是 1. **阅读理解**首先读取TASK_SPEC.md理解目标、示例和约束。 2. **代码生成/修改**根据SDD的指引打开user_service.py文件添加新的方法。 3. **运行Harness自验证**这是关键一步。AI Agent需要在本地或一个隔离环境中运行我们提供的Harnesspytest test_user_service_batch_query.py。 4. **迭代修复**如果测试失败AI Agent会读取错误信息分析是逻辑错误、边界情况未处理还是性能不达标然后修改代码再次运行测试直到**所有测试通过**。 5. **生成改动集**测试通过后AI Agent将user_service.py的改动以及**Harness的运行成功日志或报告**打包成一个“可签收”的改动集。这个报告是“可签收”状态的客观证明。 **实操心得**在这一步务必为AI Agent配置好与项目一致的Python环境、依赖和测试数据库或使用更完善的依赖模拟。否则Harness无法运行整个流程就断了。一个技巧是使用Docker容器或GitHub Codespaces为每个AI任务提供标准化的执行环境。 ### 3.3 环节三人工评审与安全合并 AI提交的“可签收”改动集进入了团队的代码评审队列。此时评审者的负担大大减轻 - **无需再验证基础功能**因为Harness已经证明了功能正确性。评审者可以跳过“这个函数到底对不对”的层面。 - **聚焦高级问题**评审者可以专注于 - **代码风格与一致性**命名是否符合规范注释是否清晰 - **设计与架构**新增的方法放在这个类里是否合适有没有更好的设计模式 - **可读性与维护性**逻辑是否清晰有没有隐藏的复杂度 - **边界情况覆盖**Harness里的用例是否足够是否需要补充更多边缘场景这实际上是在完善SDD和Harness - **查看执行证明**附带的测试通过报告给了评审者合并的信心。 如果评审通过这个改动就可以安全地合入主分支。由于它自带完整的、通过的测试集Harness它立即成为了项目回归测试的一部分守护着未来的代码不被破坏。 ## 4. 高级模式多Agent协作中的Harness与SDD流转 在更复杂的多Agent协作场景中例如一个“架构师Agent”拆解任务一个“后端Agent”实现API一个“前端Agent”实现UIHarness和SDD成为了Agent之间传递需求和验证结果的“合同”。 1. **架构师Agent**接收产品需求将其拆解为后端API任务和前端组件任务。它为每个任务生成**初版的SDD**接口定义、数据格式和**集成测试Harness**如API的Contract Test。 2. **后端Agent**接收API任务的SDD和Harness实现API并运行Contract Test确保接口符合约定。完成后它除了提交代码还可能更新SDD补充更详细的错误码说明。 3. **前端Agent**接收前端任务和API的SDD作为数据源约定实现UI组件。它可能运行基于Mock API的组件测试Harness。 4. **集成阶段**当一个Agent的产出如后端API被另一个Agent前端依赖时它们共享的HarnessContract Test就是集成是否成功的客观标准。任何一方违反约定测试就会失败。 这种模式下SDD和Harness像接力棒一样在Agent间传递和演化确保了整个协作链条的可靠性和一致性。 ## 5. 常见问题与避坑指南 在实际推行这套流程时我踩过不少坑这里分享一些核心经验 **Q1编写Harness和SDD的时间成本是不是太高了** **A1**初期确实有学习成本但这是将模糊需求工程化的必要投资。一旦形成习惯编写SDD示例和基础Harness测试的速度会很快。更重要的是它**极大地减少了后期因误解导致的返工和调试时间**并且这些Harness未来可以复用成为项目的资产。对于常见模式如CRUD操作可以建立模板库。 **Q2AI会不会过度拟合Harness中的测试写出“考试机器”式的糟糕代码** **A2**有可能。这就是SDD中“参考实现”和“注意事项”部分的重要性。Harness确保“功能正确”SDD引导“实现优美”。此外**人工评审的环节不可省略**评审者要检查代码的“灵魂”而非仅仅“肉体”。你也可以在Harness中加入一些“代码风格检查”如使用black, flake8或“圈复杂度”检查作为非功能约束。 **Q3如何处理探索性任务一开始无法写出完整的Harness** **A3**这是SDD发挥作用的另一个场景。对于探索性任务第一步不是写代码而是派发一个“生成SDD草案和原型”的任务。让AI Agent进行快速探索产出几个可能的实现方案、接口草案和关键的测试场景。人类基于这些产出再精化出正式的SDD和Harness。这是一个“AI探索人类决策再AI实现”的循环。 **Q4多个AI生成的Harness和测试风格和质量参差不齐怎么办** **A4**需要建立团队的“Harness规范”。包括 - **测试框架和断言风格**统一使用pytest还是unittest断言用assert还是特定的断言库 - **测试结构**如何组织fixture测试类如何命名 - **覆盖范围**要求至少包含成功、失败、边界用例。 - 可以将这些规范写成模板或代码片段在创建任务时提供给AI Agent作为SDD的一部分。 **Q5如何管理这些大量的、与特性绑定的Harness测试文件** **A5**建议将Harness测试文件与它验证的产品代码放在相邻目录遵循项目的测试目录结构。例如/src/services/user_service.py对应的Harness可以是/tests/services/test_user_service_batch_query.py。这样便于查找和维护。在CI/CD流水线中所有测试都会自动运行。 **最大的坑认为有了AI和这套流程就可以完全放手。** 目前AI是强大的执行者和协作者但核心的产品决策、架构设计、以及最终的质量把关仍然需要人类的智慧和责任。Harness和SDD是人类将自身智慧和规范“注入”AI流程的桥梁而不是替代人类思考的魔术棒。用好它们你将收获一个超级靠谱的编程伙伴用不好或完全依赖则可能被引入更隐蔽的歧途。