【ITニュース解説】Your tests share your blind spots. Readers don't.
2026年09月24日に「Dev.to」が公開したITニュース「Your tests share your blind spots. Readers don't.」について初心者にもわかりやすく解説しています。
ITニュース概要
開発者が書くテストは、自身の盲点を反映し、見落としを生むことがある。筆者のシステムも、テスト通過後に外部の読者の指摘で重大なバグが発覚し修正された。テストは既知の問題防止に役立つが、新たな問題を発見するには、開発者と異なる視点を持つ第三者の客観的なレビューが不可欠だ。
ITニュース解説
ITシステム開発において、テストは品質を保証するために非常に重要な工程だが、テストには開発者自身の思考の限界、つまり「盲点」が存在する。開発者自身が書いたテストは、開発者が想定するシステム挙動に基づいて作成されるため、開発者が見落としている問題点までテストが共有してしまうことがある。この記事では、筆者が開発した機能で、テストを通過したにもかかわらず後になって見つかったバグを、コードを読んだ第三者が見つけ出し、その解決に至るまでの経緯を通して、開発における多角的な視点の重要性を解説する。
筆者が開発している「Infrawise」というシステムは、AI(コードエージェント)が実際のITインフラの状況を理解し、適切なコードを書くための情報を提供する。このシステムの中心的な機能の一つに「リンカー」がある。ITシステムでは、ソースコード上で定義された関数名と、実際にデプロイされて動いているクラウド上の関数名が異なることが多々ある。リンカーは、これら二つの異なる名前を正しく紐づける役割を担う。例えば、ソースコードでは checkout.ts というファイル内に export const handler という関数があっても、実際にデプロイされると checkout-handler-prod のような名前になる。リンカーは、これらを「同じもの」として認識させる必要がある。
リンカーには二種類存在する。一つは、Infrastructure as Code (IaC) ツールであるTerraformやCDKで明示的に定義された情報に基づいて紐づける「確実な」方法だ。もう一つは、今回問題となった「ヒューリスティックリンカー」と呼ばれる、名前を分析して推測で紐づける方法である。ヒューリスティックリンカーは、関数名を「正規化」することで比較を行う。正規化とは、名前から不要な部分を取り除き、比較しやすい形に統一する処理のことだ。具体的には、normalizeName という関数がこの処理を担当する。この関数は、文字列を小文字に変換し、前後の空白を除去し、ファイル拡張子(.ts, .jsなど)を削除する。さらに、「prod」「dev」「staging」といったデプロイ環境を示す「ステージトークン」や、「handler」「fn」「func」といった一般的な関数名の一部である「ノイズトークン」も取り除く。この正規化処理によって、checkout-handler-prod というデプロイ済み関数名も、checkout.ts というソースファイル名も、最終的には checkout という統一された名前に変換される。リンカーはこの正規化された名前同士を比較し、一致すれば紐づけを行う仕組みだった。
筆者はこのリンカー機能のリリースにあたり、テストコードを作成した。その中には、「もし複数のソース関数が、一つのデプロイ済み関数にマッチしてしまうような曖昧な状況があれば、リンクを拒否する」というテストも含まれていた。これは一見すると、曖昧性による誤ったリンクを防ぐための適切なテストであり、このテストは成功し、システムの品質が保証されたように見えた。
しかし、このテストには重大な盲点があった。筆者の作成したコードもテストも、常に「デプロイ済み関数」を起点として、「それに紐づくソース関数は一つだけか?」という一方向の問いかけしかしていなかったのだ。見落とされていたのは、その逆のケース、すなわち「複数のデプロイ済み関数が、たった一つのソース関数に紐づいてしまう」という状況だった。具体的には、checkout-handler-prodとcheckout-devという二つの異なるデプロイ済み関数が、正規化されるとどちらもcheckoutとなるため、結果的に一つしかないcheckout.tsというソース関数にマッチしてしまうケースである。リンカーは個々のデプロイ済み関数に対して処理を行うため、checkout-handler-prodに対しては「checkout.tsという唯一のソース関数が見つかった」と判断し、checkout-devに対しても同様に「checkout.tsという唯一のソース関数が見つかった」と判断してしまう。その結果、両方のデプロイ済み関数が同じcheckout.tsに紐づけられてしまうのだ。prodやdevといったデプロイ環境を区別する情報は正規化の過程で失われてしまい、リンカーは本来異なる二つのデプロイ済み関数を区別できなくなっていた。筆者のテストは、この「逆方向の曖昧性」を全くカバーしておらず、筆者のコードと同じ「盲点」を共有していたのである。
この盲点は、筆者が開発ブログで記事を公開した後、コメント欄の読者たちによって発見された。彼らは筆者のコードを客観的に読み解き、筆者自身が気づかなかった問題の本質を指摘した。ある読者は、マッチング結果がどのように作成されたか(リゾルバーの推測か、確実な情報か)を記録する必要があると指摘した。正規化によってprodやdevといった重要な情報が削除されると、その情報は後から取り戻せないため、早い段階で問題を検知する必要があるという。別の読者は、曖昧な結果は「計測結果」であって「ツールによる決定」であってはならず、一つに絞り込めるまで結果を使うべきではないと主張した。また別の読者は、確実なリンクと推測によるリンクは異なる質問に対する答えであり、ツールは「未確定」であることを明確に伝えるべきで、不確実な状況で無理に確実な答えを「製造」すべきではないと述べた。これらの指摘は、いずれも筆者のテストファイルには書かれていなかった、全く新しい視点だった。
読者からの指摘を受け、筆者は迅速に問題を記録し、修正に取りかかった。特に重要だったのは、analyze_functionという関数が、リンクできなかった場合に"triggers": [](トリガーなし)と返していた点である。これは、関数がイベント駆動ではない、という「主張」をしていたことになる。しかし実際は、リンカーがトリガーを特定できなかっただけであり、ツールが判断不能だったと伝えるべきだった。もしAIエージェントが、この空のtriggers配列を「トリガーがない」という事実だと信じてしまえば、誤ったイベントハンドラを作成し、システムの不具合を引き起こす可能性があった。ツールが自信満々に間違った情報を返すことは、情報がないよりも危険な場合があるのだ。
修正として、リンカーの戻り値の構造が大幅に変更された。成功したリンクはlinksに、解決できなかったリンクはunresolved(未解決)に分類されるようになった。特にunresolvedの場合には、no_match(マッチなし)、multiple_functions(複数のソース関数にマッチ)、multiple_lambdas(複数のデプロイ済み関数が同じソース関数にマッチ)といった具体的な理由が添えられるようになった。multiple_lambdasの場合、衝突した他のデプロイ済み関数の名前もリストされる。最も重要な変更は、正規化によってprodやdevといった情報が失われる前に、つまり複数のデプロイ済み関数が同じ正規化名に変換されようとしている段階で、その衝突を検知するようにロジックを修正した点である。これにより、情報が失われる前に問題を認識し、適切な理由を付けて「未解決」と報告できるようになった。triggersに関しても、関数がリンクできなかった場合は、空配列を返すのではなく、結果にunresolvedLambdasという情報が追加され、それによってトリガーの情報は明示的に示されなくなった。これにより、AIエージェントは「トリガーがない」と誤解せず、「リンクが解決できなかったため、トリガーも不明」と判断できるようになる。
筆者はこの経験から、今後の開発においていくつかの重要な教訓を得た。一つは、ユニーク性(唯一性)のチェックは両方向から行うべきだということだ。「一つのデプロイ済み関数に、一つだけソース関数がマッチするか?」だけでなく、「一つのソース関数に、他の複数のデプロイ済み関数がマッチしないか?」という逆方向の質問も常に念頭に置く必要がある。二つ目は、正規化などの情報が失われる変換処理を行う際に、その直前で重要なチェック(例えば衝突検知)を行うことだ。一度情報が失われると、後からその情報に基づいて判断することは不可能になる。三つ目は、ツールが何かを決定できなかった場合、その状態を「何もなかった」という「不在」としてではなく、「なぜ決定できなかったのか」という「理由」を添えて明示的に伝えることだ。空の配列などは、間違った情報を提供する危険があるため、避けるべきである。最後に、自身の開発している内部実装を積極的に公開し、外部からのレビューを募ることの重要性である。これは、特に一人で開発を進めている場合に、開発者の盲点を補完し、テストでは発見できない構造的なバグを見つけ出すための、非常に安価で効果的な手段となる。
この一連の出来事は、開発におけるテストの役割と限界、そして第三者の客観的な視点がいかに重要であるかを明確に示している。テストは、一度学んだ教訓をシステムに組み込み、同じバグの再発を防ぐには不可欠なものだ。しかし、新しい種類のバグや、開発者自身の思考モデルが持つ構造的な盲点を見つけることには限界がある。コードを客観的に読める、外部の目や意見は、開発者の盲点を突き、より堅牢で信頼性の高いシステムを構築するための強力なツールとなる。特に、AIエージェントのように、正しい答えと間違った答えが見分けにくいシステムにおいては、このような多角的な視点によるレビューが、システムの品質と安全性を高める上で極めて重要な役割を果たす。