プログラミング
AIとの協業:具体的な例
Working With AI: A concrete example (htmx.org)
要約
この記事では、HTMXの作成者が、ハイパースクリプトのメンテナンス中にAIと協力してバグを修正した具体的な例を紹介しています。AIは問題の原因特定とテスト作成には非常に役立ったものの、クリーンな解決策の提案には弱く、技術的負債を回避するためには人間の介入が不可欠であることを示しています。AIの利点と限界、そしてAIに過度に依存することの危険性(魔法使いの弟子問題)について考察しています。
全文翻訳
AIとの協業:具体的な例
カーソン・グロス 2026年6月29日
私は、一般的にAIに対してどちらとも言えない感情を抱いています。この1年で開発における非常に強力なツールになったことは間違いありませんが、私たち個人にとっても(例:知力の緩やかな鈍化)、集合体としても(例:環境問題、個人用コンピューティングの費用増加など)多くの危険を伴います。
「Code is Cheap(er)」では、開発者がAIに依存し、構築しているシステムで発生する問題を理解し適切に対処できなくなる「魔法使いの弟子」問題を警告しています。この記事では、ハイパースクリプトをメンテナンスする際にAIと行った具体的なやり取りを通じて、AIの一般的な強みと弱みを明らかにし、特に(私が辛うじて回避した)「魔法使いの弟子」問題を示すことを目的としています。
ハイパースクリプト・パーサー
背景として、ハイパースクリプトはWeb用の代替解釈型スクリプト言語です。皮肉なことに、完全にJavaScriptで書かれています。これは奇妙なソフトウェアです。私は、物事がどのように機能するかを実験するために、意図的に解析の多くのルールを破って書きました。いくつかの例を挙げます。
解析ロジックは解析要素に併置されている
パーサーはプラグイン可能で、文法は動的に定義される
プロパティアクセスに複数の構文をサポートしている
これはほとんどのプログラミング言語には推奨しないアプローチですが、このプロジェクトではかなりうまくいきました。ソフトウェアにおいて確かに複数の方法があることを示すもう一つのデモンストレーションです。
バグ報告
私たちの話は、ユーザーが0.9.91リリースにアップグレードした際に回帰を報告したことから始まります。以下の式が適切に解析されなくなりました。
fetch `{% url 'trade:get_symbol_data' %}?symbol=${symbol}` as JSON
特に、「as JSON」が結合しすぎて、ユーザーが期待していた(そして以前行っていた)動作、つまり指定されたURLをフェッチし、結果をJSONとして扱うのではなく、文字列リテラルをfetchに渡す前にJSONに変換しようとしていました。この種の結合衝突は、解析における古典的な問題です。ハイパースクリプトはxTalkスタイルの言語であり、英語の多くの曖昧さを継承しているため、この問題はさらに悪化します。
原因の調査
最初に行うべきことは、この回帰がなぜ発生したのかを調査することでした。これは、私が通常AIに頼る分野です。私はClaudeを使用しており、その根本原因を見つけるのに見事な仕事をしてくれました。0.9.91では、goコマンドをリファクタリングする際に、fetchコマンドとロジックを再利用/共有するために過度に積極的になっていました。これらのコマンドの両方が使用する共通メソッド、parseURLOrExpression()を抽出しましたが、そうすることで、誤ってfetchコマンド後の文法を一般的な式、つまり式に拡張してしまいました。
asキーワードは式において意味を持ちます。これは型間の変換を可能にする変換式です。
set x to "42" as Int
しかし、asキーワードはfetchコマンドの修飾子でもあり、応答をどのように変換するかを指示します。
fetch https://hyperscript.org as Text
(もしかしたら、この事実で少し吐き気がするかもしれません。それでいいのです。)
問題の核心は、リファクタリングで不注意にも、fetchキーワードの後に式を解析するようにパーサーを変更してしまい、asキーワードをfetchの修飾子としてではなく、式として消費するようになってしまったことでした。Claudeの助けを借りて、これを数分で突き止めることができ、自分一人で解決するよりもはるかに速かったです。
問題の修正
AIは問題の原因特定には非常に役立ちました。しかし、問題の修正に関しては、はるかに弱かったです。ここで怠惰だったことを認めますが、AIに解決策を求めました。ですから、それらの解決策について不平を言うのは、少し、まあ、怠惰に感じるかもしれませんが、一連の出来事は有益だとまだ考えているので、何が起こったのかを正確に見ていきましょう。
提案された修正1:ハック
最初に提示された提案は、最初に「string-like」リーフを解析し、次に完全な式にフォールバックするというものでした。
return this.parseElement("stringLike") || this.requireElement("expression");
この修正は、ユーザーが提示した差し迫った問題を解決したでしょう。しかし、これは報告されたバグに非常に特化しており、変数をfetchのターゲットとして使用する場合など、一般的なケースは修正されなかったでしょう。
fetch $url as JSON
このため、私はこの提案を却下しました。あまりにもハッキーで、汎用性が低すぎたからです。(ハイパースクリプト・パーサーには有機的に供給されたハックが豊富にあるため、これは五十歩百歩だったかもしれません。)
提案された修正2:より良いが不要な複雑さ
2番目の提案はより興味深いものでした。パーサーにnoConversionsフラグを追加し、URL解析の周囲でそれを設定し、AsExpression.parseが設定されている場合に中止するようにするものです。
// AsExpression.parse()
if (parser.noConversions) return;
これは多くのパーサーエンジニアをぞっとさせるでしょう。なぜなら、ハイパースクリプト・パーサーをコンテキスト依存にするからです。それでいいのです。ハイパースクリプト・パーサーはすでにコンテキスト依存でした。この修正を見て少し考えてみると、新しいフラグをパーサーに導入することなく、必要なハッキーなコンテキスト依存のインフラストラクチャがすでに存在することに気づきましたが、Claudeはそれを見逃していました。
ハイパースクリプト・パーサーにおける「follows」
ハイパースクリプト・パーサーには「follows」という概念があります。これは、「上位の」解析要素によってフォロー・トークンとして主張されるトークンです。ハイパースクリプト・パーサーは(やや奇妙な)再帰下降パーサーであり、これにより解析要素(通常はコマンド)がキーワードを「主張」することができ、解析中に式がそれらと一致しなくなります。例として、when機能は、その宣言で論理結合子としてではなく、セパレータとしてorを使用します。
<div _="when $x or $y changes put it into me"></div>
(多くのパーサーエンジニアが怒ってこのウィンドウを閉じているのが聞こえます。それでいいのです。)
この機能が私たちが望むものを達成するために使用できることが判明しました。パーサーに新しいフラグを追加する代わりに、asをフォローとしてプッシュし、次に式を解析し、次にasをフォローとしてポップするのです。これにより、AsExpressionが解析されるのを防ぎながら、変数などのほとんどの一般的な式が機能するようになります。
提案された修正3:惜しいが不十分
私はClaudeにこれを指摘すると、興奮して「その通りです!」と言い、この手法を使ってバグを修正し始めました。ClaudeはparseURLOrExpression()に正しいコードを追加し、追加のパーサーインフラストラクチャを導入することなく、問題を一般的に修正しました。これで準備万端です。
最終的な、半有機的な修正
しかし、変更をレビューしていると、新しい修正が広範囲すぎることに気づきました。fetchとgoの両方がこのメソッドを共有していましたが、fetchだけがasを修飾子として使用していました。既存の修正は、goコマンドにおけるas変換式の完全に有効な使用も妨げていました。
そこで、私は最終的な修正をFetchCommand#parse()に自分自身で実装しました。
parser.pushFollow("as");
try {
var url = parser.parseURLOrExpression();
}
finally {
parser.popFollow();
}
if (parser.matchToken("as")) {
...
ここで、私は特殊なケースをfetchコマンドのみに絞り、goの解析には影響を与えませんでした。これが最終的なバグ修正の答えとなりました。
テスト
その過程で、私はClaudeに様々なケースのテストを生成させました。ハイパースクリプトには良い既存のテストスイートがあり、Claudeは問題を特定し、修正が適切に機能していることを示す小さく集中的なテストを作成するのに良い仕事をしました。AIがうまく機能するもう一つの分野です。
物語の教訓
さて、このかなりありふれたバグ修正の話で何が興味深いのでしょうか?AIがうまく機能した点、つまり調査とテスト作成において、そしてクリーンな解決策を考案する際にはそれほど良くなかった点と対比して見ることが興味深いと思います。もし私がハイパースクリプト・パーサーとそのインフラストラクチャに精通していなかったら、この修正はプロジェクトに技術的負債を蓄積させる結果になったかもしれません。別のハッキーな解析の隅のケース、パーサー上の別の状態ビットなどです。技術的負債は、証拠なしに主張しますが1、指数関数的に増加します。