ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

与 AI 协作修复解析器 Bug:一次 hyperscript 上下文敏感解析问题的完整复盘

与 AI 协作修复解析器 Bug:一次 hyperscript 上下文敏感解析问题的完整复盘 前端【免费下载链接】htmxhtmx - high power tools for HTML项目地址https://gitcode.com/GitHub_Trending/ht/htmx点击查看免费下载本文是 htmx 项目维护者 Carson Gross 撰写的实战复盘以 hyperscript 解析器在一次版本升级后出现的as关键字绑定回归为例完整还原了AI 定位根因 → AI 给出三版修复方案 → 人类工程师最终收敛出正确方案 → AI 生成测试的全过程。读者可以从中掌握 hyperscript 解析器follows 机制的底层原理理解上下文敏感解析的取舍并形成一套人类在环human-in-the-loop AI 代理的可控协作方法论。引言为什么值得复盘一次平淡无奇的 Bug 修复作者在文中坦言自己对 AI 的态度是普遍矛盾的一方面过去一年 AI 无疑已成为非常强大的开发工具另一方面它也带来诸多风险——对个人而言是智力的缓慢钝化对集体而言是环境成本、越来越昂贵个人计算设备等。在此前发表的 Code is Cheap(er) 一文中作者已警告过魔法师的学徒The Sorcerers Apprentice问题开发者过度依赖 AI以至于无法理解和处理自己所构建系统中出现的问题。本文的价值在于它把一次真实的、与 AI 的交互过程完整摊开展示 AI 的强项调查定位、测试生成与弱项给出干净、贴合现有架构的解决方案并具体演示了作者如何险些落入魔法师的学徒陷阱、最终又凭借对底层基础设施的熟悉而避开它。hyperscript 解析器一个故意打破解析规则的实验品要理解这个 Bug必须先了解 hyperscript 解析器的设计取向。hyperscript 是一种面向 Web 的、替代性的解释型脚本语言。具有讽刺意味的是它完全用 JavaScript 编写。在本仓库中即可找到它的完整源码test/lib/_hyperscript.js该文件约 6000 行被 htmx 的测试环境直接引入作为测试依赖运行。它是一个相当奇怪的软件作者在编写它时故意打破了许多解析规则把它当作一场实验想看看结果会怎样。典型例子包括解析逻辑与语法元素parse elements就近放置colocated解析器是可插拔的语法由运行时动态定义同一个语义如属性访问 property access支持多种语法写法。作者并不推荐大多数编程语言采用这种思路但对 hyperscript 这个项目而言它运转得相当不错——这再次证明软件世界里杀猫不止一种方法there are indeed multiple ways to skin the cat。Bug 报告一次版本升级引入的回归故事的起点是一位用户报告在升级到0.9.91版本后出现的回归下面的表达式不再能正确解析fetch {% url trade:get_symbol_data %}?symbol${symbol} as JSON具体症状是as JSON绑定得过紧在字符串字面量交给fetch之前就被当作类型转换表达式试图把字符串转成 JSON而用户期望的行为也是旧版本的行为是先 fetch 给定的 URL再把响应结果当作 JSON 处理。这种绑定冲突binding conflict是解析领域的经典问题。而由于 hyperscript 是一种 xTalk 风格的语言继承了英语的许多歧义性这个问题在它身上被放得更大——as这个词同时具有两种含义是整件事的核心矛盾。调查根因AI 的强项定位为什么会出现这个回归是作者通常会求助 AI的领域。作者使用的工具是 Claude而 Claude 在查找根因上表现令人满意根因在 0.9.91 中作者重构go命令时过于激进试图让go与fetch命令复用/共享逻辑。为此提取了一个公共方法parseURLOrExpression()但在这个过程中意外地把fetch命令之后的语法从URL 字面量扩展成了通用的expression表达式。于是冲突产生了在表达式中as是转换表达式conversion expression关键字允许做类型转换例如set x to 42 as Int在fetch命令中as又是一个修饰符告诉命令如何转换响应例如fetch https://hyperscript.org as Text作者调侃道也许这个事实会让你恶心一下。很好。问题症结在于重构之后解析器在fetch关键字后开始解析一个通用表达式而这个表达式先吞掉了as关键字使其被当作表达式的一部分而不再有机会成为fetch的修饰符。借助 Claude作者几分钟内就弄清了这一点——比独自排查快得多。修复阶段AI 的三版提案与逐版否决与定位问题时的出色表现相反AI 在修复问题上要弱得多。作者坦言当时自己有点偷懒直接向 AI 索要解决方案——但接下来的连环提案仍然非常有信息量。提案 1一个 HackAI 的第一个建议是先解析所谓的 string-like类字符串叶子节点失败后再回退到完整表达式return this.parseElement(stringLike) || this.requireElement(expression);这个修复能立刻解决用户上报的眼前问题但它只针对这个具体 Bug无法覆盖一般情况——例如用变量作为 fetch 目标时fetch $url as JSON作者否决了它理由是太 hacky、不够通用。作者自嘲hyperscript 解析器里其实本来就长满了自然生长的 hack这可能是五十步笑百步。提案 2更聪明但引入了不必要的复杂度第二个提案更有意思在解析器上新增一个noConversions标志在解析 URL 时设置它并让AsExpression.parse在标志生效时直接退出// AsExpression.parse() if (parser.noConversions) return;这会让很多解析工程师感到震惊——因为它把 hyperscript 解析器变成了上下文敏感context-sensitive的。但作者的反应是好。因为hyperscript 解析器本来就已经是上下文敏感的了。不过在审视这个方案时作者意识到项目里已经有所需的hacky 上下文敏感基础设施根本不需要给解析器新增标志——而 Claude 没有发现这一点。提案 3转折hyperscript 解析器中的 follows 机制hyperscript 解析器中有一种follows概念即被更上层的语法元素声明认领claim为 follow 令牌的 token。解析器是一个有些奇怪的递归下降解析器这个机制允许某个语法元素通常是命令认领一个关键字使表达式在解析期间不会去匹配它。该机制在源码中的实现非常直白。在 test/lib/_hyperscript.js 中follows 就是一个简单的栈var follows []; function pushFollow(str) { follows.push(str); } function popFollow() { follows.pop(); }而它之所以能拦截匹配关键在于matchToken的第一步检查见 test/lib/_hyperscript.jsfunction matchToken(value, type) { if (follows.indexOf(value) ! -1) { return; // disallowed token here } ... }也就是说只要某个关键字位于 follows 栈中任何语法元素用matchToken去匹配它都会直接失败。一个现成的例子是when特性它把or当作分隔符使用而不是逻辑连接词div _when $x or $y changes put it into me/div作者调侃已经能听到很多解析工程师愤怒地关掉窗口了。好。把这个机制用于本次修复与其给解析器新增一个标志不如pushas为 follow → 解析表达式 → pop follow。这既能阻止AsExpression解析又仍然允许变量等大多数通用表达式正常工作。作者把这个思路指给 Claude 后Claude 在一阵兴奋中表示作者绝对正确并开始用这一技术修复 Bug——Claude 在parseURLOrExpression()中加入了正确代码在未新增任何解析器基础设施的前提下一般性地解决了问题。看起来可以收工了。最终修复人类的最后一公里然而在审阅变更时作者发现这个新修复仍然过宽fetch和go共享同一个方法parseURLOrExpression()但只有fetch用as表示修饰符现有修复把go命令中完全合法的as转换表达式也一并禁用了。于是作者亲自实现了最终修复把特殊处理收窄到FetchCommand#parse()内部parser.pushFollow(as); try { var url parser.parseURLOrExpression(); } finally { parser.popFollow(); } if (parser.matchToken(as)) { ... }这里的关键是try/finally结构无论解析是否抛错follow 都会被弹栈避免污染后续解析状态。通过把pushFollow(as)限定在 fetch 命令的 URL 解析区间内go的解析完全不受影响。这正是作者对 Bug 的最终答案。值得一提的对照在本仓库 vendored 的 hyperscript 源码 test/lib/_hyperscript.js 中fetch命令的语法元素注册依然保留了as修饰符的解析分支——json、response、html、text以及自定义转换路径dotOrColonPath——可见as作为 fetch 修饰符是该语言的长期既定语义也印证了本次修复必须保住这条路径的正确性。测试AI 的另一个出色领域在修复过程中作者让 Claude 为各种情况生成了一些测试。hyperscript 已有相当好的测试套件而 Claude 出色地创建了小而聚焦的测试既展示出问题的存在又证明修复生效。这构成了AI 表现良好的第二个领域调查与测试创建。故事的教训人类在环的价值那么这段平淡无奇的 Bug 修复故事有什么值得玩味的作者认为值得注意的对照是AI 在调查和测试创建上表现出色但在给出干净的解决方案上表现不佳。如果作者不熟悉 hyperscript 解析器及其基础设施这次修复很容易给项目积累技术债多一个 hacky 的解析特例或在解析器上多一块状态。而作者断言虽未提供证据注释里调侃这是在梦里被启示的技术债呈指数级增长因此控制技术债极其重要。这个故事展示一个熟悉底层基础设施的人类与 AI 代理协作在控制复杂度方面远胜于放任 AI 自行其是。具体到本案例作者扮演的不是盲目接受 AI 方案的魔法师的学徒而是魔法师本人——他理解问题、看清了正确解法、能指挥 AI 达成目标并借助 AI 生成的测试验证方案。这与当下某些vibe coding氛围编程形成鲜明对比——后者中开发者或任何人似乎以不理解实际发生了什么为荣。番外AI 与年长开发者作者在回顾这次经历时还有另一层感触。他是一位年长开发者当年 50 岁。随着年龄增长开发者往往会在一定程度上失去快球lose our fastball具体到作者身上表现为两点记忆力不如从前无法像以前那样长时间工作。而 AI 恰好直接缓解了这两个问题关于记忆虽然无法记住以前能记住的一切但借助恰当的提示作者能很快重新理解事物。AI 很擅长帮他做到这一点也让他能在开源项目与工作项目之间切换得更高效。关于长时间工作AI 能持续苦干这是即使年轻时的作者也难以跟上的。这意味着作者能为项目维护远比从前更庞大的测试套件——例如本次案例中 Claude 生成的测试其覆盖面就超过了作者自己愿意投入精力去写的程度。因此AI 直击了作者作为年长开发者形成的两个相对短板。但硬币的另一面作者非常担心 AI 同时也在加速自己整体智力的衰退——这本来就会随年龄自然发生而 AI 依赖可能加速这一过程。回顾这段经历作者甚至对自己在亲手做正确的事之前依赖 Claude 那么久感到有些惭愧。这是他仍在摸索的领域。结论作者写下这一系列交互是因为它同时捕捉了 AI 辅助编程的好与坏它证明了一个还算称职的开发者 AI 代理的价值也展示了盲目接受 AI 给出的第一个或第二个方案的危险性。这个故事的可迁移结论可以总结为三条实操原则让 AI 干它擅长的活问题定位/根因调查、测试用例生成是当前 AI 的高性价比区域——本次案例中Claude 把找到parseURLOrExpression重构引入回归这一根因分析做得很出色。修复方案必须由熟悉系统架构的人把关AI 的三版提案要么过窄仅解决单个用例的 hack、要么过重给解析器新增全局状态、要么过宽误伤go命令。最终修复之所以收敛靠的是作者对 follows 机制、pushFollow/popFollow栈以及fetch/go共享方法的透彻理解。用测试锚定修复AI 生成的小而聚焦的测试既复现了回归也验证了修复是人类在环协作中成本最低的验证手段。希望这段真实记录能帮助读者形成自己的 AI 代理使用策略——尤其是在让代理动手之前先确保自己对系统有足够理解并始终把复杂度控制握在自己手里。延伸阅读原始文章working-with-ai.md作者对代码便宜的论述Code is Cheap(er)本次讨论的解析器实现test/lib/_hyperscript.jsfollows 栈见 第 463-471 行matchToken拦截见 第 340-348 行asExpression注册见 第 2704-2719 行fetch命令注册见 第 4838-4869 行赞分享前端【免费下载链接】htmxhtmx - high power tools for HTML项目地址https://gitcode.com/GitHub_Trending/ht/htmx点击查看免费下载相关推荐get-shit-done 阶段删除深度解析修复 phase remove --force 旗标位置敏感问题Bug 3409get shit done 阶段删除深度解析修复 phase remove force 旗标位置敏感问题Bug 3409 本文围绕 changeset 记人工智能AI 应用提示工程开发工具工作流自动化AI Agent上一篇告别对账噩梦ERPNext如何自动搞定关联方交易凭证处理下一篇OpenCore Legacy Patcher 手把手教程老款 Mac 升级最新 macOS 的完整流程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表