何か月もの間、コンバーターには私には見えないバグがありました。生成したボックスシャドウが、すべて捨てられていたのです。描画が間違っていたのではなく、破棄されていました。変換は成功し、インポートも成功し、要素も届いているのに、シャドウだけがどこにもありませんでした。
例外も、コンソールの警告も、失敗したリクエストもありません。ログにも何も残りません。自分でそのページを作ったのでなければ、何かが欠けていることには気づかず、ただ変換の出来がいまひとつだと思うだけでしょう。
これを書き残しておく価値があるのは、問題の構図が一般的なものだからです。Elementorはその具体例の1つにすぎません。一般化すると、認識しないキーを無視するシステムに書き込むとき、タイプミスや古い思い込みが、恒久的で気づかれないデータ損失に変わる、ということです。
なぜ見えないのか
Elementorは各要素をJSONとして保存しています。要素タイプとsettingsオブジェクトの組み合わせです。レンダリング時には登録済みのコントロールを順にたどり、それぞれについてsettingsオブジェクトに値を問い合わせます。登録済みのコントロールに対応しないキーが参照されることはありません。拒否されるのではなく、単に一度も読まれないのです。
これは理にかなった設計です。古いデータはプラグインを更新しても生き残り、新しいバージョンの未知のキーが古いバージョンを壊すこともありません。そしてこれこそが、間違ったキー名に対してまったく何の反応も返ってこない理由でもあります。検証を行うAPIであれば、間違ったキーは書き込み時のエラーになり、次のテスト実行で捕捉されます。無視するAPIでは何も起こらず、数か月後にユーザーが発見することになります。発見されれば、の話ですが。
なぜ名前を調べるだけでは済まないのか
当然思いつくのは、コントロール名をドキュメントと照らし合わせることです。問題は、その多くがどこにも書かれていないことです。生成される名前だからです。
Elementorにはグループコントロールがあります。タイポグラフィ、境界線、背景、ボックスシャドウといった、再利用可能なコントロールのまとまりです。グループは名前を付けてウィジェットに追加し、個々のコントロールIDは実行時に組み立てられます。グループ名、アンダースコア、そして各フィールド名という順です。そのため、titleとして登録したタイポグラフィグループからは、title_font_sizeというコントロールが生まれます。この文字列が登場するファイルはどこにもありません。grepで探すこともできません。ルールを知り、それを当てはめるしかないのです。
こういう状況こそ、記憶やフォーラムの回答、古いブログ記事が役に立たないどころか害になる場面です。どれも自信たっぷりに具体的で、しかもしばしば間違っており、APIがそれを正してくれることもありません。
バグ:シャドウは5つではなく1つのコントロール
コンバーターは、シャドウ1つにつき7つのキーを書き込んでいました。オンにするためのキーが1つ、残りは色、水平、垂直、ぼかし、広がり、位置をそれぞれ別の値として書くものです。丁寧に見えますが、ほぼすべてが間違っていました。
ボックスシャドウグループが登録するフィールドは、ちょうど2つだけです。シャドウそのものは1つの複合コントロールで、その値は水平、垂直、ぼかし、広がり、色をまとめて持つ1つのオブジェクトです。2つ目のフィールドは、アウトライン/インセットの位置です。これにポップオーバーのトグルを加えると、実際のキーは3つになります。グループ自体の名前がbox_shadowなので、いずれも2重にプレフィックスが付きます。
書き込まれていた7つのキーのうち、5つは存在しないものでした。6つ目は名前こそ正しかったものの、期待されるオブジェクトではなく文字列"yes"が渡されていました。7つ目は惜しいところでしたが、プレフィックスが欠けていました。
いくら推測しても決してたどり着けなかったであろう細部が1つあります。「アウトライン」の値は空文字列ではなく、半角スペース1つなのです。これはオプションの配列の中にしか書かれていません。
2つ目のバグ:正しい名前なのに何も起きない
同じ見直しで、flex_growとflex_shrinkも見つかりました。明示的な幅を持つ要素が、フレックスの親によって引き伸ばされたり押し縮められたりしないように書き込んでいたものです。これも間違いでした。これらのコントロールは別の名前で登録されたグループに属しているため、プレフィックスが必要だったのです。
しかし、プレフィックスを直してもそれでも機能しなかったはずです。ここが覚えておくべきポイントです。どちらのフィールドにも条件が付いていて、グループのサイズコントロールが「custom」に設定されているときにしか適用されません。条件はパネル上の表示だけでなく、CSSの生成も制御します。正しいキー名は必要条件であって、十分条件ではないのです。
実際の修正は、どちらの試みよりも単純で、しかも最初からずっとソースの中にありました。サイズコントロールには「none」というオプションがあり、これはまさに「伸ばさない、縮めない」を意味します。条件付きの3つのコントロールではなく、条件なしの1つのコントロールで済むのです。ソースを読んだことでバグが直っただけでなく、目指していたものより優れた実装にたどり着きました。
残りを監査する
読むことで見つかったバグは2つでした。ほかにも、誰も読んでいないキーが90個あまりありました。そこで次は機械的に進めました。公式プラグインのソースを解析し、ウィジェットごとに正式なコントロール名の集合を作り、コンバーターが出力しうるすべてのキーとの差分を取ったのです。
監査スクリプトは、正しい答えを出すまでに4回間違えました。どの失敗も一般化できます。
- 名前は次の行にあることが多い。コントロールの登録では、呼び出しの次の行に名前が書かれていることがよくあります。1行だけを対象にしたパターンではそれを見落とし、すべてのウィジェットが無効なキーを5つ出力しているように見えました。「高度な設定」タブ全体です。標準的なコントロールを未知として指摘するチェッカーは、バグを報告しているのではなく、自分自身の死角を報告しているのです。
- 同じことに2通りの書き方がある。あるグループは1つの変数名でフィールドを組み立て、別のグループは異なる変数名を使っています。1つ目だけにマッチさせると、グループが丸ごと黙って抜け落ちました。皮肉なことに、まさに調査中のグループもその1つでした。
- 引数の配列は入れ子になる。最初の閉じ括弧でマッチを止めると、最初の入れ子配列のところで切れてしまい、登録全体が読み飛ばされました。完全に有効なレイアウト用のキーがいくつも、最初は「無効」と表示されたのはこのためです。
- 広いキャプチャ範囲は周囲を飲み込む。前の問題を、広い範囲を取り込むことで直そうとすると、各マッチがその範囲内にある別の登録を飲み込んでしまいました。あるウィジェットの既知のコントロール数は281から60に減りましたが、差分は相変わらず成功を報告していました。
そしてもう1つ、このコードベース特有の問題があります。ボタンウィジェットのファイルには、コントロールの登録が1つも含まれていません。すべてトレイトの中にあるのです。最初の実行では、ボタンにはコントロールがまったくないと報告されました。
これらのエラーがどちらの方向に働いたかに注目してください。3つは誤検知を生みました。煩わしいものの、自然に正されます。調べればツールのほうが間違っていると分かるからです。残る1つは誤った合格を生みました。こちらが危険な種類です。既知の正しい名前が少なくなるほど、報告される問題も少なくなります。この失敗は静かで、しかも安心感を与えてしまうのです。
ほかのチェックを信頼できるものにするチェック
ここから、この作業全体を通じて残しておく価値のある、ただ1つの考えが導かれます。
「すべて問題なし」と報告するチェッカーは、「問題あり」と報告できることを証明するまでは何の価値もありません。
そこで監査の最後に、ネガティブコントロールを置いています。既知の不正なキー名5つをあえて入力し、どれか1つでも受け入れられたら実行全体を失敗させるのです。後で抽出処理が壊れても(パターンを広げた、ファイルを読み飛ばした、キャッシュが壊れたなど)、「すべて合格」の行が嘘をつく前に、このアサーションが赤くなります。
さらに2つの歯止めを組み合わせています。確実に存在するコントロール名のリストがすべて見つかること、そして合計数が黙って減ることは決してないことです。281から60への激減を捉えられたはずなのは、この2つ目です。
修正した状態を保つ
差分がきれいになった時点での最終結果は、9種類のウィジェットで205個のキーをチェックし、未知のキーは1つもない、というものでした。そのうえで、この状態を保つために継続的インテグレーションのジョブを用意しました。シャドウを含むページを変換し、その出力に複合キーが存在すること、そして古い個別の名前が1つも復活していないことをアサートします。
純粋関数のユニットテストではありません。実際にリリースされる成果物に対するアサーションであり、すでに一度起きたまさにその間違いをチェックするものです。
一般化した教訓
未知のキーを無視するもの——ページビルダー、CMSのメタテーブル、アナリティクスのペイロード、フィーチャーフラグのデータ、世にある設定フォーマットの半分など——に書き込むなら、次のことを守ってください。
- 有効なキーの集合は、信頼できる情報源から導き出す。記憶やドキュメントに頼ってはいけません。ドキュメントが記述するのは意図であり、生成される名前は実行時にしか存在しません。
- 出力するキーを、それと機械的に突き合わせる。90個のキーは目視ではレビューできませんし、間違ったキーは正しいキーとまったく同じように見えます。
- まずチェッカーを疑う。自信のある箇所を実行結果が指摘したときは、コードよりツールのほうが間違っていることがはるかに多いのです。
- ネガティブコントロールを加える。チェッカーが失敗できることを証明しなければ、その合格には何の意味もありません。
- CIで、リリースされる成果物に対してアサートする。この種のバグは、誰かが出力側のコードをリファクタリングした瞬間に再発するからです。
シャドウは今では正しく機能しています。それ以上に有益だったのは、構造上エラーを出すことができないAPIを相手に、「エラーが出ない」ことが何か月も正しさの証拠として扱われていたと気づけたことです。