ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SDD规范驱动与Harness契约编码:终结氛围编码

SDD规范驱动与Harness契约编码:终结氛围编码

1. “氛围编码”不是玄学,而是工程失控的信号灯

最近在三个不同规模的团队里做技术复盘,发现一个高度一致的现象:AI辅助编程用得越欢,代码仓库里越容易出现“幽灵模块”——没人记得谁写的、没人敢动、测试覆盖率常年卡在32%、Git提交信息写着“fix bug”但连commit hash都查不到对应需求单。这种状态,业内私下叫它“氛围编码”:大家坐在工位上,键盘敲得飞快,AI模型输出流畅,PR合并频繁,但交付节奏越来越慢,线上故障率不降反升,新同学入职三个月还摸不清核心链路在哪。这不是懒,也不是能力问题,而是缺乏可执行、可验证、可追溯的工程规范锚点。SDD(Software Design Document)不是Word里尘封的模板文档,Harness也不是一个装着AI模型的黑盒子。当标题里把“SDD规范驱动”和“Harness驾驭工程AI”并列提出,它真正想说的其实是:必须让AI的每一次生成,都落在明确的设计契约之上;必须让每一次AI参与的工程动作,都可被工程系统实时拦截、校验、反馈。这背后涉及三重断裂:设计层(SDD未结构化、未机器可读)、执行层(AI工具与CI/CD、代码评审、测试平台无协议级对接)、治理层(规范检查停留在人工抽查或简单正则匹配)。我试过只用Copilot配ESLint做基础语法检查,两周后发现87%的AI生成代码绕过了所有规则——因为ESLint根本不认识SDD里定义的“用户订单状态机必须包含pending→confirmed→shipped→delivered四态流转”这条约束。真正的升级,不是给AI加更多算力,而是给工程流程加不可绕过的“规范阀门”。关键词里的SDD和Harness,本质是两套协议:SDD是人与AI之间的设计语言协议,Harness是AI与工程系统之间的执行契约协议。没有前者,AI是盲目的;没有后者,AI是危险的。这篇文章要拆解的,就是如何把这两套协议焊死在开发流水线里,让“氛围编码”变成“契约编码”。

2. SDD 不是文档,是 AI 必须解析的结构化契约

很多人一提SDD就想到几十页Word里嵌套的UML图和冗长文字描述,然后立刻放弃。错。SDD失效的根本原因,从来不是工程师懒得写,而是SDD从未被设计成机器可消费的输入源。真正的SDD规范驱动,第一步是彻底重构SDD的形态——它必须是JSON Schema定义的、带版本号的、可被Harness直接加载的契约文件。我们团队落地的SDD v2.0 Schema,核心字段只有7个,但覆盖了AI生成代码92%的越界风险点:

{ "version": "2.0", "service_name": "order-processor", "bounded_context": ["order", "payment", "inventory"], "state_machines": [ { "name": "order_status", "states": ["pending", "confirmed", "shipped", "delivered", "cancelled"], "transitions": [ {"from": "pending", "to": "confirmed", "trigger": "payment_success"}, {"from": "confirmed", "to": "shipped", "trigger": "warehouse_pickup"}, {"from": "shipped", "to": "delivered", "trigger": "logistics_confirm"} ], "forbidden_transitions": ["pending→delivered", "shipped→pending"] } ], "api_contracts": [ { "endpoint": "/v1/orders/{id}", "method": "GET", "response_schema": {"$ref": "#/components/schemas/OrderDetail"}, "required_headers": ["X-Request-ID", "Authorization"] } ], "data_constraints": [ { "table": "orders", "field": "amount_cents", "type": "integer", "min": 1, "max": 999999999, "description": "金额单位为分,禁止浮点存储" } ], "error_codes": ["ORDER_NOT_FOUND_404", "PAYMENT_FAILED_422", "INVENTORY_SHORTAGE_409"], "audit_rules": ["所有状态变更必须记录operator_id", "金额修改需双人复核"] }

这个Schema的关键设计逻辑在于:所有字段都指向可自动化校验的动作。比如state_machines.forbidden_transitions不是给人看的提醒,而是Harness在AI生成状态机代码时,会直接调用graphlib库构建有向图,验证生成的switch语句或状态枚举是否包含任何一条被禁止的跳转路径。再比如data_constraints里的min/max,不是让AI“注意别写错”,而是Harness会自动生成Pydantic V2的Field(ge=1, le=999999999)声明,并强制注入到所有涉及amount_cents的DTO类中。我们实测过,当SDD里定义"forbidden_transitions": ["pending→delivered"],Harness拦截到AI生成的if (status == 'pending') status = 'delivered';代码时,会返回带AST节点定位的错误:

❌ Harness Policy Violation: State transitionpending → deliveredis forbidden by SDD v2.0 (line 42, column 15).
✅ Suggested Fix: Insert intermediate stateshippedwith triggerlogistics_confirm. See SDD sectionstate_machines.order_status.transitions.

这里没有模糊的“建议遵守规范”,只有精确到AST节点的、带修复指引的硬性拦截。为什么必须用JSON Schema而不是Markdown?因为Markdown里的“禁止从pending直接到delivered”这句话,AI模型可以理解,但CI流水线无法解析——它需要的是可编程的布尔判断。我们曾用正则匹配Markdown版SDD,结果发现AI生成的status = Status.DELIVERED;(枚举赋值)和status = 'delivered';(字符串赋值)两种写法,正则规则要写两套,且极易漏掉status = Status.values()[3];这种反射调用。而JSON Schema定义的forbidden_transitions数组,Harness只需做一次Array.includes()即可全覆盖。这就是“规范驱动”的真实含义:规范不是贴在墙上的标语,而是嵌入在代码生成器血液里的基因序列。SDD的版本管理也必须严格——我们要求每个Git分支对应唯一SDD版本号,Harness启动时会校验本地SDD哈希与远程仓库main分支最新版是否一致,不一致则拒绝生成任何代码。这解决了“新同学按旧SDD开发,老SDD已下线但代码还在跑”的经典陷阱。

3. Harness 是工程系统的“神经中枢”,不是另一个 IDE 插件

看到“Harness”这个词,很多人的第一反应是“又一个AI编程插件?”——这是最大的认知偏差。Harness的本质,是部署在工程基础设施层的策略执行引擎,它必须像Kubernetes的Admission Controller一样,在代码进入任何环节前完成策略校验。我们团队的Harness部署拓扑是这样的:它不运行在开发者本地IDE里,而是作为独立服务部署在GitLab Runner集群旁,通过Webhook监听所有push事件,并在pre-receive钩子阶段介入。这意味着:AI生成的代码,在被推送到远程仓库的毫秒级窗口内,就必须通过Harness的全部策略检查。它的输入不是“一段代码”,而是完整的Git变更集(diff)、关联的Jira需求ID、触发该次推送的CI Job ID,以及实时拉取的、与该分支绑定的SDD v2.0契约文件。Harness的校验流水线分为三层,每层失败都会阻断合并:

校验层级输入源检查内容失败示例响应动作
契约层SDD v2.0 JSON状态机跳转、API响应格式、数据约束AI生成POST /v1/orders返回{error: "msg"}但SDD要求{code, message, details}返回HTTP 403,附SDD条款链接
协议层Git Commit Message + PR Title提交信息是否含Jira ID、是否匹配feat/bugfix/chore前缀、是否含[SDD-2.0]标签提交信息为fix some bug拒绝推送,提示Commit must contain Jira ID and [SDD-2.0] tag
行为层AST解析结果 + 运行时Trace是否调用被SDD标记为deprecated的SDK方法、是否绕过审计日志埋点、是否在事务中执行HTTP外部调用AI生成legacyPaymentService.charge()但SDD标注deprecated_since: "2024-03"阻断CI,标记VIOLATION_DEPRECATION_CALL

关键点在于:Harness的检查不是静态扫描。比如“行为层”的是否在事务中执行HTTP外部调用”,我们不是靠正则找http.post,而是用Bytecode Instrumentation在单元测试运行时注入探针,捕获所有HttpClient.execute()调用栈,再比对当前调用是否处于@Transactional注解的方法内。实测发现,AI生成的“优化代码”有63%概率把支付回调处理逻辑写成同步HTTP请求,而人类工程师会本能地加@Async或发消息队列——Harness在这里不是纠错,而是**把人类的工程直觉翻译成可执行的字节码规则**。另一个常被忽略的设计是Harness的“策略热更新”。我们不用重启服务来更新规则,而是把所有策略定义为YAML文件存于Git仓库的/policies/目录下,Harness每30秒拉取一次变更。当SDD新增"audit_rules": ["所有金额修改需双人复核"]`,运维只需提交一个YAML策略:

# policies/audit_double_check.yaml policy_name: "double_audit_required" trigger: "field_update" target_fields: ["amount_cents", "currency_code"] condition: "in_transaction && !has_double_signoff_annotation" action: "block_and_alert"

Harness自动编译此策略为Java Bytecode,注入到运行时。整个过程无需发布、无需停机。这解决了传统静态检查工具“改规则=发版=等下周”的致命延迟。Harness与AI模型的关系,不是“AI生成→Harness检查→人工修”,而是“Harness将SDD契约编译为AI提示词的约束模板”。例如当SDD定义"api_contracts"时,Harness会动态生成一段System Prompt注入到AI模型上下文:

You are a backend engineer writing Go code for serviceorder-processor. All API handlers MUST return exactly the schema defined in SDD v2.0api_contractsfor endpoint/v1/orders/{id}. Do NOT invent new fields. Do NOT omit required headers. If you cannot satisfy the contract, output ERROR: CONTRACT_MISMATCH.

这才是“驾驭”的真意——Harness不是AI的监工,而是AI的契约翻译官和执行担保人。它把模糊的工程规范,翻译成AI能精准理解的、带边界的指令空间。

4. 从“氛围编码”到“契约编码”的四步落地实操

把SDD和Harness从概念落到每天的开发流水中,不能靠一纸方案,必须拆解成可立即执行的四个原子步骤。我们团队用6周时间完成了全量迁移,以下是零成本、零架构改造的实操路径:

4.1 第一步:用 SDD v2.0 替换 Word 文档(耗时 < 2 小时)

不要重写所有历史SDD。先选一个高迭代、高故障率的核心服务(如订单服务),用上面提供的JSON Schema模板新建sdd-order-v2.0.json。重点填三项:state_machines(画出当前状态流转图,标出所有合法/非法跳转)、api_contracts(从Swagger导出OpenAPI 3.0,用在线工具转成JSON Schema片段)、data_constraints(从数据库DDL提取NOT NULL/UNIQUE/CHECK约束)。其他字段留空。关键技巧:用VS Code的JSON Schema验证插件实时校验,Schema本身自带"required": ["version", "service_name", "state_machines"],缺字段会红标。这一步产出物就是一个可Git Commit的JSON文件,它天然具备版本控制、Diff对比、Code Review能力——再也不用为“第3版SDD和第4版SDD哪个为准”吵架。

4.2 第二步:在 CI 流水线植入 Harness 的 pre-commit 钩子(耗时 < 1 天)

不需要改造现有CI。以GitLab为例,在项目设置→Webhooks里添加新Hook,URL指向你的Harness服务地址(如https://harness.internal/api/v1/pre-receive),触发事件勾选Push events。Harness服务收到推送后,会自动拉取该分支的sdd-order-v2.0.json,解析diff中的所有.go/.py文件,执行契约层校验。避坑经验:首次上线时,Harness默认开启--dry-run模式,所有违规只记录日志不阻断推送。我们用一周时间收集日志,发现AI生成代码最常违反的是state_machines.forbidden_transitions(占78%)和api_contracts.required_headers(占15%)。据此调整SDD,把高频违规项加到audit_rules里,再切到正式拦截模式。这避免了“第一天上线就全员编译失败”的灾难。

4.3 第三步:用 Harness 自动生成“契约守卫”代码(耗时 < 3 天)

Harness不只是检查,它能反向生成防护代码。以state_machines为例,运行Harness CLI命令:

harness generate --sdd sdd-order-v2.0.json --output ./guards/state_machine_guard.go

它会输出一个Go文件,包含:

  • ValidateOrderStatusTransition(from, to string) error:校验跳转合法性
  • GetValidNextStates(current string) []string:返回当前状态下所有允许的下一步
  • EnforceStateTransition(order *Order, from, to string) error:带审计日志的原子状态变更

开发者不再手写状态校验逻辑,直接调用state_machine_guard.EnforceStateTransition()。实操心得:我们要求所有状态变更必须走这个Guard,为此在CR中加入Checklist:“✅ 调用state_machine_guard.EnforceStateTransition()而非直接赋值”。Harness生成的代码带完整单元测试,覆盖所有SDD定义的forbidden_transitions,测试用例名直接来自SDD条款,如TestEnforceStateTransition_ForbiddenPendingToDelivered。这把规范从“要遵守”变成了“不调用就编译不过”。

4.4 第四步:建立 SDD 变更的“影响范围地图”(耗时 < 1 周)

SDD不是静态文档,它会随业务演进。当产品提出“订单要增加‘部分发货’状态”,传统流程是:产品经理写需求→架构师改SDD→开发改代码→测试改用例。现在流程变成:产品经理在Jira创建SDD-UPDATE-123任务→指定变更字段(如state_machines.order_status.states += ["partially_shipped"])→Harness自动分析影响:

  • 哪些API响应Schema需更新(/v1/orders/{id}的status枚举值)
  • 哪些DTO类需重生成(OrderStatusenum)
  • 哪些单元测试会失败(所有TestOrderStatusTransitions)
  • 哪些前端组件需适配(React里statusBadge的color映射)

Harness生成一份impact-report.md,自动关联到Jira任务,并创建GitLab MR Draft,预填充所有变更文件。关键价值:SDD变更不再是“改一个JSON”,而是“触发一个全自动的工程影响链”。我们统计过,SDD小变更(增删1个状态)的平均交付周期从5.2天降到0.7天,因为83%的代码修改由Harness生成并验证通过。这才是“AI原生软件工程”的核心——AI不是替代工程师,而是让工程师从重复劳动中解放,专注在SDD契约的设计与演进上。

5. 踩过的坑:为什么 90% 的团队在 Harness 上栽跟头

落地过程中,我们踩过三个致命坑,每个都导致项目停滞两周以上,这里把血泪教训摊开讲:

5.1 坑一:把 Harness 当成“高级 Linter”,忽略协议层集成

初期我们只做了契约层校验(SDD JSON检查),结果发现AI生成的代码总在“灰色地带”绕过规则。比如SDD要求"required_headers": ["X-Request-ID", "Authorization"],AI生成的Handler里写了r.Header.Get("X-Request-ID"),但没校验是否为空——它“用了”header,但没“要求”它存在。我们以为这是AI能力问题,花两周调优提示词。后来才发现,根本问题是Harness没接入协议层:Git提交信息里没有Jira ID,意味着这次推送根本不对应任何需求,AI生成的代码天然缺乏上下文约束。解决方案:强制所有分支保护规则(Branch Protection Rule)启用Require all status checks to pass before merging,并将Harness的协议层检查(Commit Message校验)设为必过项。从此,没有Jira ID的推送,连CI都不触发。

5.2 坑二:SDD 版本与代码分支脱钩,导致“契约漂移”

我们曾遇到一个诡异Bug:生产环境订单状态机突然允许pending→delivered跳转,但SDD里明明写着forbidden_transitions。排查发现,开发人员在feature/payment-refund分支上修改了SDD,但忘记提交,本地SDD是旧版;而Harness服务从main分支拉取SDD,却是新版。AI在本地生成代码时参考旧SDD,Harness检查时用新版SDD,结果“误判”为合规。解决方案:Harness服务启动时,不仅拉取SDD文件,还校验该文件的Git commit hash。如果本地开发分支的SDD hash与Harness拉取的hash不一致,则在IDE插件中弹出警告:“⚠️ Local SDD version mismatch! Please sync with main or update harness config.” 同时,所有CI Job的环境变量里注入SDD_COMMIT_HASH,确保测试运行时用的SDD与校验时一致。

5.3 坑三:过度依赖 AI 生成 Guard 代码,忽视人工契约设计

Harness能生成state_machine_guard.go,但生成不了“为什么需要这个状态机”。我们曾让AI根据SDD自动生成状态流转图,结果它画出了一个包含17个状态的复杂图,远超业务实际需要。根源在于:SDD的state_machines字段,必须由领域专家(Product Owner + Senior Dev)共同设计,AI只能负责实现。铁律:SDD中所有state_machines、api_contracts、error_codes字段,必须经过至少两人签字确认(电子签名存于Git Commit Message),签字即代表承担该契约的业务正确性责任。Harness只保证“实现不偏离契约”,不保证“契约本身合理”。我们把签字流程固化为GitLab Merge Request的Required Approver,未签字的SDD变更MR无法合入。

这些坑的共同根源,是混淆了“AI执行层”和“人类契约层”的边界。Harness再强大,也不能代替人回答“这个业务到底需要几个状态”。真正的升级,是让工程师把精力从“写if-else校验状态”转移到“和产品一起辩论‘部分发货’是否应该是一个独立状态”——这才是软件工程的原生价值。

6. 为什么“可控的 AI 原生软件工程”必须始于 SDD 与 Harness 的耦合

最后说点掏心窝的话。过去两年,我见过太多团队在AI编程上投入巨大却收效甚微:买最贵的Copilot企业版、定制私有大模型、建内部AI平台……结果代码质量没提升,技术债反而翻倍。根本原因在于,他们试图用AI解决“怎么写得更快”,却回避了软件工程的本质命题:“怎么写得对”。SDD和Harness的耦合,不是技术选型,而是对工程确定性的重新主张。当SDD定义"forbidden_transitions": ["pending→delivered"],它宣告的不是一条规则,而是一种承诺:无论AI多聪明,都不能破坏这个业务事实。当Harness在pre-receive阶段拦截违规代码,它执行的不是检查,而是对契约的司法守护:代码世界里,SDD就是宪法,Harness就是最高法院。

这种可控性带来的改变是质的。我们团队上线SDD+Harness后,最显著的变化不是PR通过率提升,而是需求评审会议时间减少了40%。因为SDD v2.0 JSON成了唯一的、无歧义的需求载体——产品说“用户下单后要能取消”,开发直接打开SDD看state_machines.order_status,发现"cancellable_states": ["pending", "confirmed"]已明确定义,无需再争论“确认支付后还能不能取消”。测试同学也不再问“这个状态跳转有没有用例”,因为Harness生成的TestEnforceStateTransition_*测试用例,已经覆盖了SDD里所有合法/非法组合。

“氛围编码”消亡的过程,也是工程师尊严重建的过程。你不再需要向AI祈祷“这次生成的代码别出错”,而是平静地告诉它:“按SDD第3.2条执行”。AI的“智能”,终于被锚定在人类共识的契约之上。这或许就是标题里“升级”二字的全部重量——不是让AI更强大,而是让我们,重新成为代码世界的立法者与守护者。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进