終了ボタンが押せない。どこからも読み込まれていないファイルが一つあった

tscもテストも通るのに、動かない。

日本進化シミュレーション第5回 / 全16回目次

ファイル分割が一段落した頃、ゲームを一通り触って確認していた。
タイトル画面へ戻る「終了」ボタンを押した。
何も起きない。

コンソールにエラーは出ていない。型チェックも通っている。テストも全部緑だった。
「動くはずのものが、静かに動かない」というのは、エラーが出るより厄介だ。
僕には、何を手がかりに調べればいいのか分からない。

ボタンのイベント登録自体が、一度も実行されていなかった

終了ボタンの処理はexit.tsというファイルにまとまっていた。
中身を読むと、コードは正しい。ボタンを取得して、クリックイベントを登録している。
ロジックとしては何も間違っていない。

おかしいのはそこじゃなかった。
このファイルを、どこからもimportしていなかった。

exit.tsは、他のファイルから関数を呼ばれることはなく、DOMのイベント登録は自分の中で完結しているタイプのモジュールだった。
だから「使われているように見えて、実は一度も読み込まれていない」という状態に気づきにくい。
importされていなければ、中身がどれだけ正しくても実行されない。当たり前のことなのに、分割作業の中では見落とした。

tscもVitestも、この種のバグは検出できない

型チェックは、書かれているコードの型が正しいかを見る。
テストは、呼び出した関数が正しく動くかを見る。
どちらも「そのファイルが、そもそも一度でも実行されるか」までは見てくれない。

コンパイルは通る。型も合う。ただ、一度も評価されないだけ。
このゲームにはreport.tsphotomode.tsのような、同じ性質を持つファイルが他にもある。
「他ファイルから呼ばれる関数もあるが、DOMイベント登録は自分の中で完結している」という形のモジュールは、同じ穴に落ちる可能性がある。

孤児モジュールを機械的に見つける

一つ見つけて終わりにするのは怖かったので、全部のファイルを機械的にチェックすることにした。

for f in src/*.ts; do  name=$(basename "$f" .ts)  case "$name" in main|global|*.test) continue;; esac  hits=$(grep -lE "from \"\./$name\"|^import \"\./$name\";" src/*.ts 2>/dev/null | grep -v "^src/$name.ts$" | wc -l)  if [ "$hits" -eq 0 ]; then echo "ORPHAN: $name.ts"; fidone

どのファイルからも参照されていないファイル名を、ORPHANとして吐き出すだけの単純なワンライナーだ。
これを回して、exit.tsが本当に孤立していたことを確認し、main.tsimport "./exit";を一行足して直した。

コンパイルが通ることと、動くことは別の話

この一件で覚えたのは、「型チェックとテストが両方通っている」という安心感を、そのまま「機能が動く」に読み替えてはいけないということだった。

ファイルを分割するという作業そのものが、新しい種類のバグを持ち込む。
単一ファイルの時代には存在しなかった「importし忘れ」というミスは、分割した本人にしか気づけない。
以来、新しいファイルを追加したりimportを張り替えたりした後は、この孤児チェックを機能テストとは別に必ず回すようにしている。

スタンプラリーに挑戦する 行った場所・気になる場所は、現地チェックインでスタンプに残せます。 みんなのコースを見てみる 会員が作ったスポット巡りのコースを都道府県から探せます。自分だけのコースも作れます。 運営者の個人noteも書いています AI活用やサイト運営で気づいたことを、もう少し個人の視点で掘り下げています。

※運営者が個人で書いているnoteです。

技術ブログ一覧へ戻る