给编程 Agent 一套抑制过度设计的规则
作者将简化实现的决策顺序封装为 ponytail 技能:先判断需求是否必要,再检查现有代码、标准库、平台能力和已安装依赖,最后才新增代码。修复问题时,还要求优先定位共享函数中的根因。
AI 深度解读
让 AI 写一个缓存,它可能顺手交付类、TTL、淘汰策略、接口,再加一个工厂。代码能跑,维护成本也跟着来了。作者在独立开发 ParkEase 时,给 Claude Code 加了一个叫 ponytail 的 skill,让它先判断该不该写,再寻找已有能力,最后才生成最少的代码。
这套做法的价值,不是让 AI 少写几行,而是把「什么时候可以省、什么时候不能省」变成固定流程。原文给出了部署、免费托管和 Docker 的取舍,也承认:没理解完整调用链,所谓简化可能只是制造下一个 bug。
写代码之前,先过六道问题
ponytail 把 agent 设定成一个见过复杂系统、也处理过凌晨 3 点故障的资深开发者。真正起作用的,是一组按顺序执行的问题,找到能解决问题的那一级就停止:
- 这个需求真的需要存在吗?如果只是猜测以后会用到,就跳过,并用一句话说明。
- 代码库里已经有了吗?先复用附近的 helper。
- 标准库能解决吗?
- 平台原生能力能覆盖吗?例如数据库约束,或者
<input type="date">。 - 已安装的依赖能解决吗?
- 前面都不行,再写满足需求的最少代码。
顺序很关键。先问「用哪个库」,就默认了需要引入实现;先问「是否需要」,才有机会直接删除工作量。
修 bug 也有对应规则:沿调用链找到根因,在所有调用方都会经过的共享函数里修,而不是只给报错的那个调用方补一道判断。
少写代码的前提,是先把完整流程读明白。
哪些地方能省,哪些地方必须保留
作者明确保留了几类要求:信任边界上的输入校验、防止数据丢失的错误处理、安全、无障碍,以及用户明确提出的功能。所谓信任边界,就是外部数据进入系统、不能直接相信它的地方。
ponytail 缩短的是方案,不是理解代码的过程。遇到确实需要妥协的实现,还必须在代码里留下适用上限和升级路径。
例如,支付对账暂时逐笔调用 Razorpay,注释写明何时改成批量查询:
* ponytail: one Razorpay call per unreconciled capture. Switch to
* `transfers.all({ from, to })` when daily volume makes that slow.
要看的是 transfers.all({ from, to }):它把后续替代方案写到了具体 API,而不是留一句「以后优化」。
另一个注释记录了移动端心跳的限制:
* ponytail: FOREGROUND ONLY. JS timers stop when Android backgrounds the app,
* so a washer who switches apps stops beating and falls out of dispatch after
* `WASH_ONLINE_HEARTBEAT_WINDOW_SECONDS` (90s) while their switch still reads
* online. The upgrade path is an `expo-task-manager` background location task,
关键是 FOREGROUND ONLY 和 90s:应用切到后台后,JS 定时器停止,工作人员可能被移出派单范围,但界面开关还显示在线。注释给出了 expo-task-manager 后台位置任务这条升级路径,却没有消除当前的行为缺口。
类似地,每个 booking 单独查询 participants,也被注明只适合司机每周完成少量订单的情况;时间窗口或业务量扩大时再批量处理。配套命令可以汇总所有 ponytail: 注释,但原文没有给出命令名称。
三个部署取舍,说明它怎样减少工作量
可观测性需求原本包含 OpenTelemetry SDK、Sentry SDK 和独立 exporter 配置。作者发现 API 已经导出 OpenTelemetry traces,而 Sentry 接受 OTLP,于是方案变成:把现有 exporter 的环境变量指向 Sentry 的 OTLP endpoint,新增代码为零。
不过,这里要分清「方案成立」与「已经运行验证」。作者称这是文档支持的接法,但免费 demo 尚未需要开启;原文也没有给出环境变量名称。
免费托管没有后台 worker,作者没有搭建支持进程内外两种模式的任务运行抽象,而是写了一个 20 行启动器,在同一容器里启动 API 和 worker 两个进程。任一进程退出,就让容器退出,交给宿主重启。这个方案直接依赖宿主的重启机制。
Docker 的取舍也类似。精简生产依赖的镜像方案,需要先让 monorepo 中的 workspace 包产出编译结果,改动会跨越整个仓库。作者选择先在镜像里直接运行 TypeScript,并通过注释说明上限和升级路径。原文没有提供镜像体积、启动耗时等对照数据。
我的判断
我赞成把「复用、原生能力、现有依赖」排在新增代码前面。相比每次临时提醒 AI「别过度设计」,固定检查顺序更容易执行,也更方便审查它为什么选了某个方案。
但作者是在独立开发面向印度的点对点停车市场 ParkEase,免费 demo 的资源约束和维护方式,不一定适用于一般团队。20 行启动器适合当前部署条件,不能据此推断它能替代独立 worker;直接运行 TypeScript,也不能据此认定编译部署没有价值。
这些案例是作者的实践描述,没有对照实验,也没有维护成本的量化结果。把它当成工程经验更合适。
尤其是后台心跳案例,注释只能帮助后来者理解风险。如果功能要求切后台后仍保持在线,这项限制就需要修复,不能因为写了升级路径便算完成。
能拿来做什么
- 给日常 AI 编程规则加入这六道问题,要求 agent 在新增 helper、类或依赖前,先说明已有能力为什么不够用。
- 修复前端或 Node 问题时,要求 agent 先追踪完整调用链,再决定修改位置,重点检查是否应该修在共享函数里。
- 对暂时逐笔查询、共用容器等取舍,记录当前适用条件、失效信号和具体升级方案,让代码审查能讨论真实边界。
- 审查 AI 的简化方案时,单独核对输入校验、错误处理、安全、无障碍和明确需求,避免为了缩小 diff 丢掉必要行为。