让 Agent 少写防御性代码:每条校验都要有理由

一个策略选择工具,本来只需要让 Agent 查看模板、选择版本、取得写作规则。写着写着,代码却变成了另一副样子:遍历字段名、判断字典形状、检查数组元素、猜测字段类型,再把已经检查过的数据交给下一层继续检查。 每一条判断单独看都有解释,放在一起却让人看不清工具到底做什么。 这发生在一次写作策略工具教程的审查中。删除一个通用转换助手后,代码甚至更长了:检查没有减少,只是搬进了另一个函数。问题并不是少用了一个框架,也不是变量命名不够规范,而是还没决定哪些错误需要处理,就先把能想到的检查写了出来。 本文适合已经了解 Python dataclass、正在让 Agent 编写小型工具或分层应用的读者。以下例子从那次审查中缩小而来,用来说明设计取舍,不是完整策略工具的实现。 前两篇分别讨论模型与仓储的职责和入口检查与业务约束的分工。这篇往前追问一步:在决定校验放在哪里之前,我们为什么需要这条校验? 一个数组转换,怎么长成了通用校验器 假设模板包含 structure 字段。JSON 中它是数组,领域对象中希望用 tuple 表示: structure = tuple(data["structure"]) 这行代码完成转换,但不保证输入正确: assert tuple("post") == ("p", "o", "s", "t") 如果 Agent 把数组写成字符串,转换不会报错,错误会以另一种形式进入模型。因此,在接收 Agent 请求的地方,检查它是不是字符串数组是有具体理由的。 但这不意味着每个处理 structure 的方法,都应该重新执行下面三项检查: 它是不是数组? 数组里的元素是不是字符串? 数组是不是非空? 前两项属于输入协议;第三项取决于业务:这个模板是否允许没有正文结构?如果模型已经保证结构不能为空,入口无需重复保证一次。 同样的字段,也可能来自程序自己写出的文件。是否检查,要先确定文件的使用方式。 数据来源 能依赖的前提 处理方式 Agent 提交的编辑 JSON 尚未确认类型和字段 入口解析,拒绝不符合约定的结构 已解析的内部请求对象 调用者遵守既定契约 直接使用,不重复判断类型 同一程序写出、没有独立编辑入口的文件 写入与读取遵循同一格式 可以直接映射,加载失败时暴露错误 允许人工编辑或导入的文件 内容可能不符合格式 把文件读取当作输入边界,定义需要的检查 “放在仓库里”不等于“可信”。如果维护者经常手改 JSON,它就是一个真实的编辑入口。反过来,受控文件也不是永远不会损坏;选择直接映射,意味着不承诺为每一种损坏提供定制诊断,更不保证发现所有能被转换的错误类型。 一个能直接运行的例子 缩小需求:模板名称不能为空,结构至少有一项,每一项不能是空白文字。外部请求只接受 name 和 structure,不自动转换数字或字符串数组。 保存下面代码为 template_boundary.py,使用 Python 3.10 或更新版本运行: from dataclasses import dataclass @dataclass(frozen=True) class Template: name: str structure: tuple[str, ...] def __post_init__(self): if not self.name.strip(): raise ValueError("模板名称不能为空") if not self.structure or any(not step.strip() for step in self.structure): raise ValueError("模板必须包含非空的写作步骤") def decode_template(data: object) -> Template: if not isinstance(data, dict) or set(data) != {"name", "structure"}: raise ValueError("请求必须包含且只能包含 name 和 structure") if not isinstance(data["name"], str): raise ValueError("name 必须是字符串") steps = data["structure"] if not isinstance(steps, list) or not all(isinstance(step, str) for step in steps): raise ValueError("structure 必须是字符串数组") return Template(name=data["name"], structure=tuple(steps)) def writing_outline(template: Template) -> str: return "\n".join(f"{i}. {step}" for i, step in enumerate(template.structure, 1)) if __name__ == "__main__": template = decode_template({ "name": "问题与解释", "structure": ["提出具体问题", "解释原因与适用条件"], }) print(writing_outline(template)) cases = [ ({"name": "问题与解释", "structure": "post"}, "structure 必须是字符串数组"), ({"name": "问题与解释", "structure": [42]}, "structure 必须是字符串数组"), ({"name": "问题与解释", "structure": []}, "模板必须包含非空的写作步骤"), ({"name": "问题与解释", "structure": [" "]}, "模板必须包含非空的写作步骤"), ({"name": " ", "structure": ["解释"]}, "模板名称不能为空"), ] for payload, expected in cases: try: decode_template(payload) except ValueError as error: assert str(error) == expected else: raise AssertionError(f"错误输入被接受:{payload!r}") print("正常输入和 5 个失败场景均符合约定") 运行 python3 template_boundary.py,输出: ...

2026年10月25日 · 3 分钟 · map[name:Jeanphilo]

AI 辅助编程不黑盒:责任主线工作流实战

副标题 / 摘要 你不需要“每个 commit 都手打重写”,但必须对核心 commit 具备独立实现能力。本文给出一套可落地的 AI 协作流程:让 AI 负责胶水和草稿,你负责领域规则、状态变化与边界裁决。 目标读者 正在大量使用 AI 写代码,但担心自己变成“黑盒工程师”的开发者 想同时提升交付效率和技术判断力的工程师 在做 DDD、业务系统或中后台项目的开发者 背景 / 动机 AI 代码生成越来越强,这不是坏事。真正的风险在于:当你只会“接收实现”,却不能解释“为何这样实现”,系统一复杂就失去控制权。 问题不是“要不要用 AI”,而是“哪些决策必须留在人手里”。 核心概念 责任主线(main):只保留你愿意为其设计与后果负责的 commit。 AI 草稿(ai/draft-*):一次性候选实现分支,用于对照、压力测试与发现盲区。 git worktree:在不二次 clone 的前提下,为不同分支创建多个物理目录(一个仓库,多处并行)。 核心 commit:包含领域规则、不变量、状态机、关键边界与失败路径的提交。 胶水 commit:CRUD、DTO、映射、样板接口、注释等可标准化提交。 硬核评价标准:去掉 AI 和网络,你仍能写出该 commit 的核心伪代码,并解释每一步为什么这么做。 实践指南 / 步骤 先分层,再决定谁写 把需求拆成 Domain / Application / Infrastructure。 Domain 核心规则优先自己实现;Infrastructure 优先交给 AI。 先写判断标准,再看 AI 方案 先写不变量、边界条件、错误路径、伪代码或测试,再生成 AI 草稿。 先有你的“尺子”,再拿 AI 代码来量。 为每轮任务创建短生命周期草稿分支 从当前 main 派生 ai/draft-<topic>,让 AI 快速给出候选实现。 草稿分支只做提议,不做长期维护。 按并行需求选择执行模式 轻量场景:一个目录里切分支即可。 高频并行:使用 git worktree 给 main 与 ai/draft-* 各开独立目录。 核心逻辑在 main 重写,不直接拷贝 对核心 commit,避免“在 AI 代码上修修补补”。 从你的模型出发重写,再对照吸收 AI 的有价值细节。 胶水层直接复用,提高吞吐 对非核心代码,可以直接 cherry-pick 或复制并快速审查。 把脑力留给不可替代的系统判断。 草稿分支通常不 merge,完成后删除 main 是责任集合,不是候选方案仓库。 只把你认可并能负责的结果放入主线。 ...

2026年2月7日 · 3 分钟 · map[name:Jeanphilo]