私たちのコードベースにおいて、通常達成・監視したい次元が複数あります:機能的正しさ(意図した通りに動作する)、アーキテクチャの適合性(十分に高速/安全/使いやすいか)、そして保守性です。ここでは保守性を「時間の経過とともにコードベースを変更しやすく、リスクを低くする」ことと定義します — 「内部品質」とも呼ばれるもの。つまり、今日だけでなく将来も素早く変更できるようにしたいのです。また、変更のたびにバグの発生や適合性の低下を心配したくありません — あるいはAIに変更させるときも同様です。AI生成のコードベースで保守性の最初の兆候が現れるのは、小さな調整のために変更されるファイルの数が増加するときです。または、以前は動作していたものが変更によって壊れ始めるときです。

内部品質の問題は、人間の開発者に影響するのと同様に、AIエージェントにも影響します。絡み合ったコードベースで作業するエージェントは、既存の実装を誤った場所で探したり、重複に気づかずに一貫性のないコードを作成したり、タスクが必要とする以上のコンテキストを読み込まざるを得なくなったりする可能性があります。

本記事では、コードベースの保守性を私たちやAIが振り返るのに役立つさまざまなセンサーの実験と、そこから学んだことを説明します。

アプリケーション

私はコミュニティマネージャー向けの社内分析ダッシュボードに取り組んでおり、複数のAPIからチャットスペースのアクティビティ、エンゲージメント、人口統計データを読み取り、Webフロントエンドでデータを表示します。

アプリケーションのフロントエンド、バックエンド、4つの外部API(Google Chat、Google People、Employee API、Gemini API)を示す概要

図1: 例のアプリ:Web UI、サービスレイヤー、外部API。

技術スタックはTypeScript、NextJS、Reactです。バックエンドはAPIからデータを読み取り結合します。このアプリケーションは以前から存在していましたが、これらの実験のためにAIを使ってゼロから再構築しました。

コード品質や保守性に関するAI向けのガイド(例:Markdownファイル)はほとんど存在しないため、センサーフィードバックのみに頼った場合にどの程度うまく動作するかを確認したいと考えました。

使用した全センサーの概要

センサーの概要:コーディングセッション中、パイプラインへの統合後、繰り返し実行、および本番環境でのランタイムフィードバック

図2: センサーが実行できる場所:初期コーディングセッション中、パイプライン内、スケジュール上、および本番環境内。

本記事では、本番環境への道筋全体で設定したセンサーの概要を説明します。

コーディングセッション中

エージェントに高速フィードバックを提供するため、継続的に実行されるセンサーです。

  • 型チェッカー(計算型)
  • ESLint(計算型)
  • Semgrep、社内AppSecチームが規定するSASTツール(計算型)
  • dependency-cruiser、内部モジュール依存関係をチェックする構造的ルールを実行(計算型)
  • テストカバレッジを含むテストスイート結果(計算型 — ただし、テストスイートはAIによって生成されるため、推論的な方法で作成)
  • インクリメンタルミューテーション・テスト(計算型)
  • GitLeaksはpre-commitフックの一部として実行され、エージェントがコミットを試みたときにフィードバックを与えるため、センサーと見なす(計算型)

統合後 — パイプライン

同じ計算型センサーがCIでも再度実行されます。セッション内センサーは開発中にエージェントに早期フィードバックを与え、CIパイプラインはクリーンなインフラ上での統合後の結果を確認します。

繰り返し

その場限りのエラーではなく、時間の経過とともに蓄積されるドリフトを検出するため、より遅い周期で実行されるセンサーです。

  • セキュリティレビュー、社内アプリケーション向けAppSecチェックリストから導き出されたプロンプト(推論型)
  • データ処理レビュー、プロンプトは「ユーザー名をWebフロントエンドに送信してはならない」などの内容を記述(推論型)
  • 依存関係の鮮度レポート。まずスクリプトを実行してライブラリ依存関係の経過年数とアクティビティを取得し、その後AIに潜在的なアップグレード、非推奨などに関する推奨を含むレポートを作成させる(計算型および推論型)
  • モジュール性および結合度レビュー(計算型および推論型)

この文脈を踏まえ、最初のセンサーカテゴリについて詳しく見ていきましょう。

基本ハーネスとモデル

アプリケーションの構築を通じて、Cursor、Claude Code、OpenCode(使用頻度の順)を混在して使用しました。デフォルトモデルは通常Claude Sonnetを使用し、一部の計画・分析タスクではClaude Opusを使用し、実装タスクでは頻繁にCursorのcomposer-2モデルを使用しました。

静的コード解析:基本的なリンティング

ESLintをこのアプリケーションで使用した際の学びから始めます。ESLintのような基本的なリンティングツールは、主に個々のファイルや関数レベルの保守性リスクを対象とします。

典型的なAIの弱点に対するルール

私の経験上、静的コード解析で最も低コストで対処できるAIの失敗モードは以下の通りです。

  • 関数の最大引数数
  • ファイル長
  • 関数長
  • 循環的複雑度

ただし、これらはESLintのデフォルトプリセットでは有効化されていなかったため、まず最大値を設定する必要がありました。将来的に静的解析ツールがAI向けにより優れたプリセットを提供することを期待します。少し調査したところ、FactoryがこのESLintプラグインのように、エージェントの既知の失敗モード(テストファイルの要求や構造化ロギングなど)を具体的にターゲットにしたルールセットを公開し始めていることがわかりました。

自己修正のためのガイダンス

センサーはエージェントにフィードバックを与え、自己修正を可能にするためのものです。理想的には、自己修正のための追加コンテキストを提供したいと考えます — 良い形のプロンプトインジェクションです。そのために、AIの助けを借りて(当然ながら)、カスタムESLintフォーマッタを構築し、デフォルトメッセージの一部をオーバーライドしました。

以下はno-explicit-any警告に対するガイダンスの例です。

型付けすることでエラーを回避しやすくしたい。特に重要な概念については。ただし、不要な型でコードベースを乱雑にすることは避けたい。判断を下してください。型を導入しないことを選択した場合、以下で抑制してください:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- (抑制する理由を記載)`,

警告の管理 — 今なら実現可能?

静的コード解析は長い間存在していましたが、チームは設定していたとしても一貫して使用しないことがよくありました。その理由の1つは、管理オーバーヘッドです。この解析を効果的に使用するには、チームが「整理整頓された状態」を維持する必要があります。そうでなければ、メトリクスは単なるノイズになります。特に上記のno-explicit-anyのような警告は、常に修正したいとは限らないため扱いが難しく、1つずつ抑制するのは退屈で、コード内のノイズのように感じられました。

コーディングエージェントを使えば、このクリーンベースラインを達成できる可能性があります。上記のガイダンステキストでは、エージェントに判断を下すよう指示し、コード内で警告を抑制することを許可しています。これにより、抑制が管理しやすく、可視化され、レビュー可能になります。

最大行数や許容循環的複雑度のしきい値については、リントメッセージ内で、特定のケースでリファクタリングが不要または不可能だと判断した場合、しきい値をわずかに引き上げることを許可するようエージェントに伝えました。これはしきい値を永続的に抑制するものではなく、単に引き上げるだけなので、将来的にさらに悪化した場合にはルールが再度発火します。制約は維持されつつ、二者択一(抑制するか遵守するか)の選択を強制しません。

観察結果

  • AIが作成した例外(抑制された警告、引き上げられたしきい値)を確認することは、コードレビューの良い出発点となりました。
  • AIは循環的複雑度のしきい値を頻繁に引き上げる判断をしましたが、さらに促すと良いリファクタリングを提案しました。これは唯一そのようなカテゴリであり、後になってこのルールに対する自己修正ガイダンスが設定されていなかったため、しきい値の引き上げが絶対的な例外であるという明示的な指示がなかったことがわかりました。これは、カスタムリントメッセージが実際に大きな違いを生むことを示す指標です。
  • コードの異なる部分でルールを異なる扱いにしたい場合があります。no-consoleを例に取ると、console.logの使用をAIに警告するものです。バックエンドではロガーコンポーネントの使用を望み、フロントエンドでは直接的なロギングを一切使用したくない、または少なくとも異なるロギングコンポーネントを使用する必要があります。これは自己修正ガイダンスの力のもう1つの例であり、AIが解析警告の意味判断と管理を支援できる場面です。
  • ルール間のトレードオフの例を探していました。唯一見つけたのはmax-linesmax-lines-per-functionのルールによるものでした。このセンサーフィードバックの結果として、AIは有用なリファクタリングや、より小さな関数・コンポーネントへの分解をかなり行いました。しかし、Reactフロントエンドでは、ますます小さくなるコンポーネントのチェーンを通じて値を渡す結果として、非常に多くのプロパティを持つコンポーネントが増加する懸念すべき傾向が見られます。AIがそのようなトレードオフについて一貫した判断を下せるかどうかについては、まだ有用な観察結果が得られていません。

主なポイント

全体として、静的解析でどれだけ多くのことをカバーできるかに好印象を持ちました。過去にこのようなリンターがあまり使われてこなかった理由と、何が変わったのかを何度も思い出す必要がありました:コストベネフィットのバランスです。コストは、AIによってカスタムスクリプトやルールを作成することがはるかに安価になったため削減されました。ベネフィットも増加しました:解析結果は、私自身がコードを書く場合にはあまり発生しない多くの衛生要因を最初に把握するのに役立ち、一般的なAIのミスを排除できます。

しかし、これが誤った安心感や品質の幻想につながるのではないかと懸念せざるを得ません。結局のところ、このようなリンターが過去にあまり使われてこなかったもう1つの理由は、限界があるためであり、それを品質の簡易指標として使用することに慎重だったからです。静的解析では捉えられない、意味的な品質の側面が数多く存在します。AIがツールとのパートナーシップでそのギャップを適切に埋められるかどうかは、まだわかりません。また、新しいルールセットを有効化するたびに、コード内に新たな問題を発見しました。無関係なものと実際に重要なものが混在していました。そのため、エージェントへのフィードバック過多を懸念し、過剰にエンジニアリングされたリファクタリングのスパイラルに陥る可能性があります。

静的コード解析:依存関係ルール

基本的なリンティングは、主にファイルや関数内の品質と複雑さに焦点を当てています。次に、ファイルやモジュールの境界を越える保守性の懸念について、私たちやAIにフィードバックを与えられるセンサーを探し始めました。この分野の解析ツールは、歴史的に基本的なリンティングよりもさらに利用されていません。

コードベース内のモジュール性を良好に保つのに役立つセンサーの可能性を学ぶため、以下の3つを調査しました:

  • 依存関係ルール(決定論的)
  • 結合度解析(決定論的および推論的)
  • モジュール性レビュー(推論的)

依存関係ルールから始めましょう。アプリケーションのレイヤードモジュール構造をエージェントと協力して考案し、実装の途中で導入しました。dependency-cruiserルールを書くのを手伝ってもらい、これらのレイヤーを強制しました。

sensors-structure-chativity.png

図3: レイヤードモジュール構造と依存関係ルール

例として、以下のルールはclientsフォルダ内のコードがservicesフォルダから何もインポートしないことを強制します:

{
  name: "clients-no-services",
  comment:
    "API clients must not depend on the orchestration layer above them. " + LAYERS,
  severity: "error",
  from: { path: "^server/clients/", pathNot: "/__tests__/" },
  to: { path: "^server/services/" },
},

ESLintメッセージと同様に、エラーメッセージも自己修正のためのガイダンスとして拡張し、レイヤリングの概念全体を要約しました:

ERROR  clients-no-services
  API clients must not depend on the orchestration layer above them. 
  [Layers: routes -> services -> clients + domain; Services orchestrate: fetch data via clients, compute via domain -- no I/O, no SDKs, no knowledge of data fetching.]

観察結果

  • AIがなければ、これらのルールを迅速に導入することはできなかったでしょう。このツールの設定構文には急な学習コストがあり、AIがほぼ完全にそのコストを吸収しました。
  • エージェントはルール導入後に数回違反しましたが、dependency-cruiserのフィードバックに基づいて自己修正したため、フォルダの概念を維持するのに役立ちました。
  • フロントエンドでのReactフックの構造に関する規約を導入するためにも、同じアプローチを使用しました。
  • AIがこの構造外に新しいフォルダを作成し始めた場合を検出する方法を考案する必要があり、すべての新しいファイルが事前定義されたフォルダ構造内のどこかに配置されることを要求するルールを作成しました。

主なポイント

これらのルールを導入した時点で、コードのフォルダへの構造化はすでにやや場当たり的になっていました。ルールがエージェントの整理に役立ち、その後これらのレイヤーを継続的に強制できることがわかりました。Markdownガイドでコード構造を記述するのに代わる有用な手段であることがわかりました。ただし、このようなツールはインポート、ファイル名、フォルダ構造で表現可能なものに限定されます。

静的コード解析:結合度データ

次に、コードベースから典型的な結合度メトリクス(ファイルごとの着信・発信インポート数と呼び出し数)を抽出する実験を行いました。

既存のツールは使用せず、TypeScriptコンパイラの助けを借りてこれらのメトリクスを作成するアプリケーションをコーディングエージェントに書かせ、実験の一環として最大限の柔軟性を確保しました。2つのインターフェースを追加しました:人間の消費のためのさまざまな視覚化を備えたWebインターフェースと、コーディングエージェントにメトリクスを提供できるCLIです。

sensors-coupling-dashboards.png

図4: 結合度メトリクス:Web視覚化とエージェント向けCLI。

人間の消費用

これらの視覚化のほとんどは、依存関係構造マトリックス(DSM)など、確立された概念です。解釈が煩雑で、vibe codingで作成されたため改善の余地はあるものの、それはデータの性質によるものだと考えます。非常に詳細なデータであり、解釈し、より高次の良いプラクティスにマッピングするには多くのコンテキストと経験が必要です。そのため、このタイプのツールは、AIが変更したコードベースをレビューする際の人間の認知的負荷を大幅に軽減することには、まだあまり役立たないと感じています。

AIの消費用

エージェントにこのカスタムCLI(coupling-analyser)へのアクセスを与え、データに基づいてレポートを作成し、重大な問題の改善方法を提案するよう依頼しました。

以下は、そのプロンプトの抜粋です — 主に、モジュール性の良し悪しについて実際にはほとんどガイダンスを与えなかったことを示すために再掲しています。主にモデルに委ねて、良し悪しを解釈させました:

対象のTypeScriptコードベースのモジュール性と結合度の品質に関するMarkdownレポートを、静的な閲覧のみに基づく推測ではなく、npx coupling-analyserからの実際のCLI出力に基づいて作成してください。

証拠の収集(CLIの実行)

CLIを実行してstdoutをキャプチャします。reportサブコマンドを使用し、質問に応じて組み合わせてください: …

Markdownレポートの作成

明確な見出しを使用します。CLI出力から引用または要約した具体的なモジュールID/パスと数値を優先します。

推奨セクション:

  1. コンテキスト — 分析対象

  2. エグゼクティブサマリー — 2–5箇条書き:全体的なモジュール性の状況、上位1–3のシステム的問題。

  3. ツールからの所見 — ホットスポット、上位リスク、注目すべき循環や相互依存、CLIが報告した行動ハイライトを要約。

  4. 解釈(モジュール性の観点) — メトリクスをソフトウェア設計に結びつける:凝集性 vs. 変更の広がり、安定性 vs. 依存関係の方向性、ファンイン/ファンアウトの直感、循環の影響。

  5. 各高・重大問題の詳細分析

  • 問題の内容 — モジュール、システム内の役割、依存関係の近隣(CLI + 必要に応じて最小限のコード確認)。
  • 現在の責任 …
  • なぜ問題なのか …
  • 設計オプション(妥当な場合は2つ以上) …
  • 新しい設計が優れている理由 — 循環の減少、より明確な依存関係の方向性、より小さなサーフェス、テストの継ぎ目、予想される変更ベクトルとの整合。
  • 将来の変更リスク — 各オプションが回帰リスクをどのように低減し、安全な進化をより安価にするか(具体的なシナリオ:「Xの追加」、「Yの交換」、「Zの独立したリリース」)。

このLLM主導の分析は、実際の視覚図を見た場合と同じ結合度のホットスポットを指摘しましたが、より消化しやすい形式でした。また、LLMに決定論的ツールの結果に基づいて分析を根拠づけるよう求めたことで、より高い信頼性が得られ、エージェントがコードベース自体をスキャンして結合度の問題を見つける場合よりも、時間とトークンの消費が少なかった可能性があります。

観察結果

このデータに基づいてLLMが見つけた内容はかなり不十分でした(Claude Opus 4.7を使用):

  • 最大の問題の1つは、必要なすべてのコンポーネントを初期化するファクトリだと述べましたが、私はそのファクトリを軽量な依存性注入フレームワークのように機能するコンポーネントとして意図的に導入したものです。
  • もう1つの問題は、フロントエンドとバックエンド間で共有される(zod)スキーマで、LLMによって「god module」と宣言されました。これはバックエンドとフロントエンドの間の明示的な契約を作成するための一般的なパターンであり、バックエンドとフロントエンドがともに進化する場合、または私のケースのように同じリポジトリに存在する場合、それほど問題ではありません。
  • 正当なパターンが高結合ハブとして現れる場合、将来の分析で抑制する方法が必要になります。そうでなければ、さらにノイズが増えます。
  • 唯一興味深い発見:domainフォルダ内のindex.tsファイルが./domain内のすべてのファイルを無差別に公開しており、多くの場所からインポートされています。これはレイヤーの明示的な契約を作成するための一般的なパターンでもありますが、 pros and consがあり、少なくともこのコードベースに適切かどうかを調査する価値があります。

主なポイント

上記の例は、基本的なリンティング以上に、良い悪いに明確な定義がなく、すべてが適切であるかどうかにかかっていることを示しています。また、適切な結合度は、コードベースの生の呼び出し・インポートグラフだけでなく、多くのコンテキストに依存します。この小さな実験に基づくと、このタイプの結合度データはAIにとって単独では有用ではないという印象を持ちました。

このデータのより実用的な用途として、コードレビューのリスクトリアージが考えられます。AIが行ったコード変更をレビューする際、変更されたファイルの影響範囲を知ることは有用であり、例えば10人以上の呼び出し元を持つファイルが変更された場合に、より注意を払うことができます。また、AIレビューのエージェントがトークンを費やす場所を優先順位付けするためにデータを使用することも可能です。

静的コード解析:AIモジュール性レビュー

結合度データの分析結果が不十分だった理由は複数考えられます:

  • 分析内容に関するプロンプトが十分に具体的ではなかった
  • 結合度データがAIにとって有用ではなかった
  • 結合度データが浅すぎて、完全なコードのコンテキストが欠けていた

そこで最後に、完全に推論的なアプローチに進み、Vlad Khononovの「Modularity Skills」を使用してコードベースの設計を分析し、モジュール性の問題を見つけることにしました。これは非常に実り多い結果となりました! 将来の変更リスクを明らかに低減するリファクタリングのための興味深いヒントが多数得られました。2回目の実行では、結合度分析CLIへのアクセスを与えました。AIは主にデータで確認しましたが、追加の所見は得られませんでした。むしろ、CLIが欠落している点を多数指摘しました。また、2回目の分析(1回目のコンテキストなし)では、1回目には見つからなかった別の問題が浮上しました。重要な場合には、LLMベースの分析を複数回実行して、より完全な picture を得る価値があることを示す有用なリマインダーでした。

観察結果

以下は結果のハイライトです(使用したモデルは結合度分析と同じClaude Opus 4.7):

  • 重複したルートコード — 3つのバックエンドエンドポイントすべてに独自のルートファイルがあり、それぞれの実装はほぼ同一でした。したがって、バックエンドAPIの一般原則(リクエストIDの導入やエラーハンドリング、ロギングアプローチの変更など)を変更したい場合、複数のファイルで変更する必要があります。3つ目のエンドポイントを導入したばかりなので、まだ抽象化されていなかったのは当然です。しかし、私の経験では、AIエージェントは通常、3回目または4回目にコードを繰り返す場合、明示的な促しなしにリファクタリングを開始せず、コピー&ペーストを好みます。
  • バックエンド呼び出しの一貫性の欠如 — あるいは、別の形の意味的重複です。アプリケーションの3ページがバックエンドを呼び出すために同じパラメータセット(選択されたチャットスペースと分析対象の日付範囲)が必要です。2つのページは同じフックと一般的なアプローチを使用していましたが、AIが3ページ目を導入したとき、それから逸脱し、独自の方法で類似の動作を再実装しました。これにより、例えばエラーハンドリングの不整合や、バックエンドAPIの原則変更時に複数のファイルを変更する必要が生じる可能性があります。
  • コア引数の非効率な処理 — 前述の通り、アプリケーションのすべてのページはチャットスペースIDと日付範囲をバックエンドに渡します。ユーザーが日付範囲を指定する方法を変更した際、AIがその変更のために非常に多くのファイル(40以上!)を変更する必要があったことにすでに気づいていました。そのため、何か問題があることはすでに認識しており、分析で確認されました:「問題:各レベルでリクエストパラメータが繰り返される」。推奨は、これらのパラメータをすべてラップするオブジェクトを導入することでした。AIはすでに何らかの形でそれを行っていましたが、そのオブジェクトの使用を完全に徹底しなかったため、一貫性のない混乱した状態でした。
  • 責任の誤った配置 — レビューでは、モジュールの配線のみを担当するはずのファクトリ内に認証コードが配置されていることがわかりました。ユーザーが認証されていない場合にモックデータにフォールバックする機能を実装していました。このような予期しない場所は、新しいルートが追加されたときに見逃されるリスクを生みます。
  • 許容可能な高インポート数「ハブ」のより良い解釈 — 以前の結合度分析で発見された「god classes」を思い出してください。モジュール性スキルもこれらに気づきましたが、どちらの場合も、これらのアプリケーションのコンテキストにおいて目的があることを適切に指摘しました。これは、これらのスキルの優れたプロンプティングによるものか、またはこの分析が実際にコードの内容を読み取ったのに対し、もう一方は結合度データのみに依存するよう依頼したためだと考えられます。

主なポイント

  • dependency-cruiserのような依存関係パーサーは、一部の基本的なフォルダ構造と依存関係の方向を強制するライブセンサーとして効果的ですが、限界があります。
  • AIモジュール性レビューは「ガベージコレクション」の優れた例であり、強力なプロンプトを与えると非常にうまく機能しました。実際の結合度データに基づくことは大きな違いをもたらしませんでした。コミット内の変更ファイルにこれを適用してパイプラインの早い段階で実行する方法を見つけることは素晴らしいですが、まだ探求していません。
  • このタイプのレビューを自分自身に適用せずにコードベースの大部分を構築した後にモジュール性レビューを実行しましたが、将来のリスクを高める非常に懸念すべき、かつ非常に妥当な所見がいくつかありました。これは、人間のレビューと結合度の専門知識、そしてこれらの追加のAIレビューがなければ、エージェントが確実に意図しない技術的負債を蓄積していたことを示しています。

全体として、コードベースの設計とモジュール性は、計算型センサーだけではあまり役に立たない懸念事項であり、意味解釈を追加し、トレードオフを考慮するためにAIが必要です。

回帰センサーとしてのテストスイート

テストには多くの目的があります — 設計について考え、設計を推進するのに役立ち、アプリケーションの望ましい動作を文書化し(究極の仕様です!)、回帰を検出するのに役立ちます。つまり、変更によって既存の機能が壊れたときに教えてくれます。効果的な回帰テストはコードベースの保守性に大きな役割を果たし、変更をより安全にします。したがって、保守性センサーの文脈では、回帰センサーとしてのテストスイートの役割について説明します。

既存のテストが失敗した場合、私たちは自問する必要があります:「偶然に何かを壊したので、実装を変更する必要があるのか? それとも意図的に動作を変更しているので、テストを新しい仕様に適応させるために変更する必要があるのか?」。失敗したテストは、AIにまさにその質問をする機会を与えます。常に正しい判断を下すとは限りません! しかし、良いテストスイートは、AIが望ましい既存の動作を壊す確率を低下させます。

私のチャット分析アプリケーションでは、AIに手動テストとテストカバレッジの監視以外の監督をほとんど行わずに、時間をかけてすべてのテストを書かせました。AI生成の完全なテストスイートを持ち、事後的にその回帰効果を分析したいと考えました。

レビューなしでAIにテストを生成させるアプローチには、主に2つのリスクがあります:

  • カバレッジはテストの有効性の十分な指標ではない
  • テストが誤った動作をテストしている可能性がある — これはテストの有効性をチェックするよりもはるかに難しい問題であり、別の機会に議論します。本記事ではテストの有効性のみに焦点を当てます、つまり、コードが望ましい動作を実装していると仮定した場合、壊れたコードを検出するテストがあるかどうかを確認します。

ツールボックスにあるもの

  • カバレッジ ($) — テストによって実行されるコードの部分を追跡し、テストから可視・不可視の部分を示す指標を与えます。
  • プロパティベーステスト ($) — 定義されたプロパティから多数の入力組み合わせを生成することで、手作業で作成した例ではなく、欠落した論理的テストケースを見つけることができます。
  • ファズテスト ($$) — 予期しないまたは不正な入力をシステムに投げつけることで、入力耐性の欠落したテストケースを見つけることができます。
  • ミューテーション・テスト ($$) — 小さなコードの突然変異を導入し、テストスイートがそれを検出するかどうかを確認することで、欠落したアサーションを見つけることができます。

私のアプリケーションでは、カバレッジとミューテーション・テストを使用しました。プロパティベーステストとファズテストは私のユースケースにあまり適していなかったためです。

ミューテーション・テスト

ミューテーション・テストがアサーションのギャップを見つけるのにどのように役立つかを説明するための、コードベースからの小さな例を以下に示します。ミューテーション・テスト結果の分析中に、エージェントがこの図を作成してくれました。

mappersおよび関連コードのミューテーション・テスト分析図

図5: コードベースからのミューテーション・テストの例。

mappers.tsファイルは100%のステートメントカバレッジと75%のブランチカバレッジを報告していましたが、実際にはユニットテストがなく、Stryker(使用したミューテーション・テストツール)は13のサバイバー(つまり、Strykerのコード突然変異13個の後、テストスイートはまだグリーン)を報告しました。この場合のカバレッジが高かったのは、コードベースにこれらの関数を最終的に呼び出す大きな受け入れテストがあったためです — カバレッジは行が実行されたことを示しますが、その影響が検証されたことを示すものではありません。この小さなマッパーヘルパー関数dvpToSchemaが将来変更された場合、UIのデータグラフの表示が破損する可能性があります。

観察結果

  • AIはミューテーションのホットスポットを分析し、テスト品質を向上させる優先順位付けされた計画を立てるのに非常に役立ちました。
  • Strykerは結果を巨大なJSONファイルに書き込みます。分析を支援し、コンテキストウィンドウを誤って詰まらせることを避けるため、エージェントがStrykerの結果を効率的にクエリできるカスタムスクリプトを生成しました。これは、AIがAIを助けるのを助けた多くの例の1つに過ぎません。
"""コマンドラインからStrykerのミューテーションテストJSONレポートをクエリします。

使用方法:
python query_stryker.py <report.json>; <command> [options]  

コマンド:
   summary 全体のステータス合計、ミューテーションスコア、しきい値。
   files ファイルごとの内訳、デフォルトでミューテーションスコアの昇順でソート。
   hotspots サバイバー/カバレッジなしミューテーションが最も多い行。
   tests テストの有効性:弱い、使用されていない、またはトップキラーテスト。

例

# 1. 全体の健全性 — ミューテーションスコア、ステータスの内訳、しきい値の合格/不合格
python ./query_stryker.py reports/mutation/mutation.json summary

# 2. 最悪のファイルを最初に、アクションヒント付き(アサーションの強化 vs テストの追加)
python ./query_stryker.py reports/mutation/mutation.json files --top 10 -v

# 3. 同様に、gitで変更したファイルのみ(リポジトリを自動検出)
python ./query_stryker.py reports/mutation/mutation.json files --changed -v

# 4. 1つのファイルにズームイン:すべての(行、実行可能なカウント、サンプルミューテータ)
python ./query_stryker.py reports/mutation/mutation.json hotspots --file server/services/ai-summaries.ts --top 30

"""

主なポイント

現在、エンドツーエンドスタイルの受け入れテストへのトレンドがあるようです。冒頭で述べたように、AIはテストの生成が非常に上手くなったため、開発者がAIに多くのテストを生成させ、ほとんどレビューせずに済ますことが一般的になりました。特にユニットテストのレビューは非常に退屈です。まったく見ないことが良いことだと言っているわけではありません — しかし、人間によるすべてのテストのレビューが持続可能だと考えるのは非現実的であり、人々が実際にそれを行うと考えるのも非現実的であるという現実を認めています。したがって、AIコーディングの未来に適したテストピラミッド/アイスクリームコーン/マフィンの形状を探し求めている間、承認済みシナリオのようなテクニックが人気を集めています。上記で示したように、受け入れテストはカバレッジを向上させますが、アサーションが少ないことが多く、テストの有効性について誤った安心感を与えます — ミューテーション・テストは、そのギャップを監視するのに役立ちます。

ミューテーション・テストには当然ながら実用的な限界があります:リソース集約的です。私のセットアップでは、他のセンサーのように継続的に実行するのではなく、手動でインクリメンタルな実行をトリガーしました。

結論と未解決の問題

計算型センサーは、ファイルおよび関数レベルで最も印象的でした。モジュール性や結合度などのファイル横断的な懸念は別の話で、生データ自体は非常にノイズが多く、LLMの意味解釈、つまり推論型センサーなしではあまり役に立ちませんでした。しかし、良いプロンプトで得られる出力とアドバイスには非常に感心し、また、異なる経験レベル向けにこの情報を異なる方法で提示する可能性にも感心しました。

実験では見られませんでしたが、問題になる可能性があると疑っているのはセンサー間の競合です。max-linesmax-lines-per-functionのルールは緊張関係の兆候を示し、より小さな関数へのリファクタリングが代わりにコンポーネントのプロパティチェーンに複雑さを押しやる結果となりました。そのようなトレードオフは他にも潜んでいる可能性が高く、将来的に問題になるかどうか、どのように問題になるかは興味深いところです。

このアプリケーションでは、センサーの効果をより純粋に見るために、ガイドは一切使用しませんでした。ガイドとセンサーのバランスがどのように進化するかは興味深いところです。センサーのセットに自信が持てたら、どのガイドを削除できるでしょうか? センサーは、より弱いモデルの使用をより現実的にするでしょうか? ガイドとセンサーを互いに一貫性を保ち、何らかの方法でバンドルして、メンテナンスを容易にする方法を見つけられるでしょうか?

回帰テストの分野では、テストの大部分をAIに任せるという決定をした場合、ミューテーション・テストがいかに重要になるかに本当に目が開かれました... そして、テストの正しさに関する別の会話が全体としてあることを改めて強調したいと思います!

これらのセンサーの一部は、結果の品質に対する信頼を本当に高めてくれますが、人間を完全にループから外すための魔法の解決策ではありません。しかし、計算型と推論型の両方のセンサーをパートナーとして使用することで、レビューの経験と信頼レベルが確実に向上したことを実感しました。