土曜日の午後。コーヒーを片手に、さっと新機能を追加するか、ようやくサイドプロジェクトを仕上げようと意気込んで座る。しかし、開始10分ですべてが止まってしまう。ロジックが複雑すぎるからでも、フレームワークを理解していないからでもない。たった一つのタグが閉じられていないというだけで、進捗が凍りつくのだ。

それこそが、今週末のチャレンジで起きたことだった。Liquidの構文エラーだ。タグが正しく閉じられていなかった。パーサーがファイルを読み進め、終了シーケンスを期待する箇所に到達したものの、何も見つからなかった。それだけで、ビルドは失敗した。これは、経験豊富な開発者をも謙虚にさせ、初心者には自己疑念の渦に陥らせるようなバグだ。一度気づいてしまえば、修正には数秒しかかからないというのに。

内部で何が起きていたのか

LiquidはShopifyによって作成されたテンプレート言語であり、eコマースのストアフロントからGitHub Pages上のJekyllベースのブログまで、あらゆるものを支えている。Liquidは2つの核となる構文パターンに依存している。{{ page.title }}のように、二重の中括弧は出力を処理する。{% if user %}{% for item in list %}のように、中括弧とパーセント記号はロジックやフロー制御を処理する。

すべての開始タグには、対となるタグが必要だ。{% if %}には{% endif %}が必要であり、{% for %}ループには{% endfor %}が必要だ。captureブロックには{% endcapture %}が必要になる。これらは「推奨」ではない。Liquidエンジンはテンプレートを順次読み込んでいく。開始構造に遭遇すると、内部スタックにフレームをプッシュして待機する。もしファイルが終了したり、期待されるタグが現れる前に別の主要なブロックが閉じられたりすると、エンジンはエラーを投げる。メッセージはしばしば無慈悲だ。「タグが正しく閉じられていません。システムは終了シーケンスを期待していました」といった具合だ。行番号が表示されることもあるが、パーサーはそれより下のすべてを読み終えて初めてパートナーの欠如に気づくため、その行番号が間違った場所を指していることもある。

具体的な例を考えてみよう。次のようなコードを書いたとする。

{% for product in collections.all.products %}
  <div class="card">
    <h2>{{ product.title }}</h2>
    {% if product.available %}
      <span>In stock</span>
    {% endif %}
  </div>
{% endfor %}

3つのタグはすべて閉じられている。ここで、ドキュメントからスニペットを素早くコピー&ペーストしている場面を想像してほしい。誤って最後の「r」を落としてしまったとする。

{% for product in collections.all.products %}
  <div class="card">
    <h2>{{ product.title }}</h2>
    {% if product.available %}
      <span>In stock</span>
  </div>
{% endfo %}

あるいは、大量のHTMLの下に隠れているために、単に{% endfor %}を完全に忘れてしまうこともある。エンジンは{% for %を見つけ、ループを登録するが、その相棒をいつまでも見つけられない。Shopifyの文脈では、これはテーマ全体のコンパイル失敗を意味する。Jekyllの場合、GitHub Pagesからビルド失敗のメールが届く。ローカル開発環境では、不可解なスタックトレースを吐き出すかもしれない。たった一つのタグの忘れ物が、パイプライン全体を止めてしまうのだ。

小さなミスの暴政

これらのエラーが苛立たしいのは、ミスの大きさに比例して問題が大きくなるわけではないからだ。データベースの設計を間違えたわけでも、誤ったアルゴリズムを選択したわけでもない。たった一文字を忘れただけなのだ。小さなミスが大きなバグを引き起こす。欠落した{% endif %}は、行を一つ壊して済むような親切なものではない。それは連鎖する。条件分岐がどこで終わるのか混乱したパーサーは、それより下のすべての行を不正な形式として誤認する可能性がある。わずか20行のテンプレートが、突然60行ものエラー出力(そのほとんどが誤解を招くもの)を生成することもある。

わずか一文字を忘れただけでこうしたエラーに直面することになるが、人間の脳はそうした現実に直面する準備ができていないことがほとんどだ。人間はパターン認識を通じてコードを読む。私たちは「意図」を見る。ifとその一致するロジックを見て、境界線を推論する。しかし、コンピュータは推論しない。曖昧さを一切許容せず、一文字ずつ、上から下へと読み進める。パートナーとなるタグを待ったままファイルの終端に達すると、コンピュータは諦めてしまう。あなたの仕事は、その隙間に気づけるようになるまで、パーサーのように考えることができる開発者になることだ。

これはLiquidに限ったことではない。Pythonでの閉じられていない括弧、Markdownでのバックティックの欠落、JavaScriptでの中括弧の忘れ、HTMLでの宙に浮いた不等号。今週末のチャレンジではLiquidを教材として使ったが、その根底にある教訓は、あなたが触れるあらゆる言語に通じるものだ。構文は文法であり、文法は容赦ない。

それらを追い詰める方法

この壁にぶつかったとき、最初に湧き上がる衝動は、ファイル全体をパニック状態で読み直すことだ。それをこらえてほしい。パニック状態で読むと、脳が勝手に補完してしまうため、まさに自分が落としたその文字を見落としてしまう。そうではなく、体系的に作業を進めるのだ。

タグを明示的に一致させる。 ファイル全体を確認し、すべての開始タグを声に出すか、紙に書き出してください。for には endfor が必要です。if には endif が必要です。unless には endunless が必要です。capture には endcapture が必要です。ブロックをネストしている場合は、頭の中でカウンターを増やしてください。for の中に if を開いたなら、ファイルが終わるまでに解決すべき義務が2つあることになります。

エディタを活用する。 定期的に Liquid を扱うなら、文法を認識するシンタックスハイライターをインストールしましょう。Visual Studio Code には、Liquid タグを暗くしたり色分けしたりする拡張機能があります。閉じタグの形式が間違っていると、色のパターンが変わります。コンパイルする前に、閉じられていないブロックを検出できるリンターもあります。Vim や Neovim を使っているなら、vim-liquid のようなプラグインを検討するか、Tree-sitter を設定して一致するタグをハイライトするようにしましょう。これらのツールは「考える必要性」をなくすものではありませんが、不一致を可視化してくれます。

テンプレートに対して二分探索を行う。 エラーメッセージが200行目を指しているのに、そこに何も問題が見当たらない場合、真犯人はおそらくその上の行にいます。テンプレートの下半分をコメントアウトしてください。ビルドできますか?もしできるなら、エラーはコメントアウトした半分の中にあります。その半分をさらにアンコメントしてください。壊れたブロックを特定できるまでこれを繰り返します。これは時間がかかるように感じられますが、フラストレーションを募らせながら同じ200行を6回も読み直すよりは遥かに速いです。

include を確認する。 Liquid は {% include %}{% render %} を通じてモジュール化された断片をサポートしています。閉じられていないタグは、メインファイルには全く存在しないかもしれません。親テンプレートが読み込んでいるスニペットの中にある可能性があります。ここでバージョン管理が正気を保つのに役立ちます。diff を実行してください。最後にビルドが成功してからの変更点を確認しましょう。多くの場合、答えは赤と緑(の差分)の中に浮かび上がっています。

インデントはドキュメントである。 {% if %} が 0 列目から始まっているのに、対応する {% endif %} がネストされた構造のどこかにインデントされている場合、視覚的な整列によって不一致に気づきやすくなります。HTML タグと Liquid タグで同じインデント形式を共有していれば、対応するタグが不適切な深さに配置されているのを、目が自然に捉えてくれるはずです。

真のカリキュラム

ウィークエンド・チャレンジが重要なのは、実際に作業するのと全く同じ状況を再現しているからです。上司は見守っておらず、締め切りに追われているわけでもありません。スキルアップのため、あるいは楽しむためにコードを書いていて、微小なエラーによって作業が完全に止まってしまう。その瞬間こそが教訓なのです。デバッグについて読んでデバッグを学ぶことはできません。外に出たい気分なのに、壊れたビルドをじっと見つめ、エラーメッセージを批判ではなく「データ」として扱うよう自分に強いることで学ぶのです。

これらのエラーを修正する方法を学んでください。なぜなら、それらが完全に消えることはないからです。キャリア10年目になっても、金曜の夜のデプロイ中に閉じタグを忘れることはあります。ジュニアとシニアの開発者の違いは、ミスをしないことではなく、復旧の速さです。シニアは構文エラーを見て、パターンを認識し、明らかな原因をチェックして、次に進みます。ジュニアはツールチェーン全体が壊れているのではないかと疑います。反復練習がその反射神経を養います。

コミュニティの側面がこれを加速させます。週末に複数の人が同じ壊れたテンプレートに取り組むと、一人の開発者だけでは気づけないパターンが現れます。エラーがネストされた for ループ内でのみ発生することに気づく人もいれば、一般的な Liquid タグの不一致を grep するシェルスクリプトを共有する人もいます。知識は、溜め込むのではなく、交換することで複利的に増えていくのです。特定のチャレンジの詳細や、他の人がどのように取り組んだかについては、こちらの Dev.to の投稿で読むことができます。同じ問題に取り組んでいる人たちと情報を交換したい場合は、Telegram のオプション学習コミュニティがあります。そこでは、こうした議論が週末を過ぎても続くことがよくあります。

まとめ

構文エラーを「本来の仕事の邪魔」と考えてはいけません。それ自体が根本的な仕事なのです。今週末のビルドを壊した Liquid タグの問題は、本質的にはテンプレートエンジンに関するものではありませんでした。それは、脳が「推測」したがる時に、正確に「読み取る」ための訓練だったのです。先週書いたファイルを開いてみてください。開いたタグをスキャンしましょう。すべてが正しく閉じられているか確認してください。ループを閉じ、条件分岐を解決する。そして、一文字ずつ正確に、再び構築に戻りましょう。