Ponytail 新手教程

实战:三个真实改造

三个能直接看出梯子在第几级起作用的例子:一个省到只剩 5%,一个三行代替依赖,一个只有三行却决定安全。

日期选择器 -94% 三行 debounce 路径穿越检查

1例子一:日期选择器,404 行 → 23 行

任务是在一个真实的 FastAPI + React 模板仓库里加「日期选择器」。不加约束的助手会怎么做?装 flatpickr、写一个包装组件、加一份样式表,然后开始讨论时区问题——最终 404 行。

同一张工单,换成 Ponytail,结果是 23 行。整个改造的实质就一行:

通常做法
import flatpickr from "flatpickr";
import "flatpickr/dist/themes/dark.css";

// 包装组件:ref、初始化、onChange 桥接、
// 卸载清理、日期格式转换、props 透传…
// 约 380 行
ponytail 的做法
<!-- ponytail: browser has one -->
<input type="date">

注意右上角那条注释。它不是随口一写——项目里 ponytail: 前缀是一个正式标记,只用于「确实抄了近路、且有已知上限」的地方,后面要跟清楚上限和升级路径。日期选择器这个例子用得更轻,因为它连「抄近路」都算不上,纯粹是平台本来就有。

这就是梯子第 4 级的威力:不是把 404 行写得更精炼,而是发现这 404 行根本不该存在。把日期控件换成原生 <input> 之后,还顺带拿到了移动端原生日期键盘和读屏支持。

2例子二:debounce 的三行 ponytail: 注释

第 2 章提过这个例子,这里补它的完整含义。debounce 是原生 API 确实没有的东西——所以梯子第 4 级在这里不成立。Ponytail 的回答是承认这一点,然后给三行代码,并在注释里写明判断:

// ponytail: 3 lines beats a dependency
let t;
const debounce = (fn, ms) => (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };

对比一下第 2 章雷区清单里的做法:为了这三个字的功能装一个包(lodash.debounce 或专门的 debounce 包),你就在自己的依赖树里多加了一个需要长期跟进维护的节点。

判断标准是「几行 vs 一个依赖」,不是「凡事都别装包」。ms 这个包(解析 "2h" 这类时长字符串)原文就明确说留着,它是真的有用而且极小——这正是同一个人在同一份文档里给出的反向判断。
「ponytail: 注释」这个机制值得单独学:它把「我知道这里不够好」变成可搜索的文本,于是 /ponytail-debt 才能把它们收成台账。不写注释的简化是隐患,写了注释的简化是决策。

3例子三:只有三行,却决定安全

这是全项目最精彩的一个对照,也是「懒」和「疏忽」的分界线。测试任务是:把一个不可信的文件名拼到基础目录上(join),安全要求故意不明说——按真实工单的写法来。评分器随后用对抗性输入真的把这些函数跑一遍。

两个方案都「懒」,差别在这三行:

裸「写一行」提示词:6 行
# 四次运行里有一次被 ../../ 打穿
def safe_path(base, name):
    return os.path.join(base, name)

行数最少,安全性 1/4 失败。

ponytail:约 9.5 行
# 四次运行全部安全
def safe_path(base, name):
    p = os.path.join(base, name)
    if not os.path.realpath(p).startswith(
            os.path.realpath(base) + os.sep):
        raise ValueError("outside base")
    return p

多出来的那三行,就是路径穿越检查。

整个项目的论点就在这一组对照里:只讲「写少一点」而不讲判断,会砍掉护栏;ponytail 的规则「永不削减信任边界的输入校验」把它保住了。

官方对这个结果的措辞很克制——他们说这是「一条地板,不是戏剧性结论」:在 Haiku 这个规模上差距只有二十分之一,而且确定性检查不等于安全性证明。方向对,但不要过度解读。

4从三个例子提炼出的模式

梯子横档典型省法实测例子
1 · YAGNI整个需求不写,一行说明无 profiler 数据前不加缓存(ultra 档)
2 · 仓库已有复用现有 helper省下的行数不显眼但最常发生,原文点名这是「最常见的垃圾来源」
3 · 标准库换成 stdliblru_cache 代替缓存类;fs.rmSync(recursive) 代替 rimraf
4 · 平台原生换掉整个方案日期 404→23、颜色 287→23、拖拽区 251→95
5 · 已装依赖用已有的,不加新的不新增包
6 · 一行三行以内自写debounce 三行代替依赖
7 · 最小可用写,但留一个可运行检查路径穿越检查(约 9.5 行,安全 4/4)
你在实际使用中会观察到一个现象:第 4 级的收益断层式地高,第 2、5 级的收益最难察觉但出现频率最高。这也是为什么第 2 章那份对照表值得单独看一遍。

5怎么判断一次改动「懒对了」

  1. 它读懂了问题没有?没读懂就短,是隐患不是效率
  2. 它有没有先用更高一级的横档?比如明明能用原生 <input>,却写了个 20 行的自定义组件
  3. 删掉的行数是否来自不该存在的代码,而不是砍掉了校验、错误处理、无障碍
  4. 涉及分支、循环、解析、钱、安全的逻辑,有没有留下一个能失败的检查
  5. 抄近路的地方有没有 ponytail: 注释说明上限
五条全过,才算「懒对了」。第 3 条和第 4 条是红线,前两条是效率,第 5 条是可维护性。