T-CREATOR

Codex で既存コードを読み解く:要約・設計意図抽出・依存関係マップ化

Codex で既存コードを読み解く:要約・設計意図抽出・依存関係マップ化

既存のコードベースを理解することは、開発者にとって日常的な課題です。新しいプロジェクトに参加したとき、レガシーコードをメンテナンスするとき、あるいは他の開発者が書いたコードをレビューするとき、私たちは常にコードの意図を読み解く必要があります。

OpenAI の Codex は、コードの理解と解析において強力なサポートを提供してくれるでしょう。この記事では、Codex を活用して既存コードの要約、設計意図の抽出、そして依存関係のマップ化を行う具体的な方法をご紹介します。

背景

既存コード理解の重要性

ソフトウェア開発において、新規開発よりも既存コードの保守・改修に費やす時間の方が圧倒的に多いのが現実です。調査によれば、開発者の作業時間の約 60〜80%がコードの理解に費やされているとも言われています。

既存コードを理解する際には、以下のような情報が必要になりますね。

  • コード全体の構造と役割
  • 各関数やクラスの設計意図
  • モジュール間の依存関係
  • データの流れとビジネスロジック

これらを手作業で把握するには膨大な時間がかかります。

Codex の特性

Codex は GPT モデルをベースにした AI で、自然言語とプログラミング言語の両方を理解できる能力を持っています。GitHub 上の公開コードで学習されているため、様々なプログラミング言語やフレームワークのパターンを認識できるのです。

以下の図は、Codex がコード解析を行う際の基本的なフローを示したものです。

mermaidflowchart TB
  input["既存コード<br/>入力"] --> codex["Codex AI<br/>モデル"]
  codex --> analysis1["要約<br/>生成"]
  codex --> analysis2["設計意図<br/>抽出"]
  codex --> analysis3["依存関係<br/>分析"]
  analysis1 --> output["統合<br/>レポート"]
  analysis2 --> output
  analysis3 --> output
  output --> developer["開発者"]

このフローにより、複数の観点から効率的にコードを解析できます。

Codex を活用するメリット

#メリット詳細
1時間短縮手作業での解析と比較して、数分で全体像を把握できる
2客観的な分析先入観なく、コードの構造や意図を抽出できる
3多言語対応JavaScript、Python、Java など多様な言語に対応
4自動化可能CI/CD パイプラインに組み込んで継続的に分析できる

図で理解できる要点:

  • Codex は入力されたコードを 3 つの観点(要約・設計意図・依存関係)で同時に解析します
  • 各分析結果は統合され、開発者が活用しやすいレポート形式で提供されます

課題

既存コード解析の困難さ

既存コードの理解には、いくつかの共通する課題があります。

ドキュメント不足

多くのプロジェクトでは、ドキュメントが不十分だったり、コードと同期していなかったりします。コメントが少ない、または古くなっている場合も多いでしょう。

複雑な依存関係

モダンな開発では、多数のライブラリやフレームワークを組み合わせます。これらの依存関係を手動で追跡するのは困難ですね。

暗黙的な設計意図

開発者が当時考えていた設計意図は、コードだけからは読み取りにくいことがあります。なぜその実装方法を選んだのか、どういうトレードオフがあったのかは、コードには表れにくいのです。

以下の図は、従来の手作業によるコード解析プロセスと、その課題を示しています。

mermaidflowchart LR
  start["コード<br/>受領"] --> read["読解<br/>開始"]
  read --> doc["ドキュメント<br/>確認"]
  doc --> gap1{{"ドキュメント<br/>不足?"}}
  gap1 -->|はい| guess["推測で<br/>理解"]
  gap1 -->|いいえ| dep["依存関係<br/>調査"]
  guess --> dep
  dep --> gap2{{"依存が<br/>複雑?"}}
  gap2 -->|はい| confused["理解<br/>困難"]
  gap2 -->|いいえ| intent["設計意図<br/>推測"]
  confused --> time["膨大な<br/>時間消費"]
  intent --> time

図で理解できる要点:

  • ドキュメント不足により推測に頼らざるを得ない状況が発生します
  • 複雑な依存関係により理解が困難になり、最終的に膨大な時間を消費します

手作業での限界

人間がコードを読む際には、以下のような限界があります。

#課題項目具体的な問題
1処理速度大規模なコードベースの全体像把握に数日〜数週間かかる
2見落とし重要な依存関係や副作用を見逃すリスクがある
3一貫性解析者によって理解度や注目点が異なる
4スケーラビリティコードベースが大きくなると対応が困難になる

これらの課題を解決するために、AI による自動解析が求められています。

解決策

Codex を使った既存コード解析の全体像

Codex を活用することで、既存コードの理解プロセスを大幅に効率化できます。具体的には、要約生成、設計意図抽出、依存関係マップ化の 3 つのアプローチを組み合わせます。

以下の図は、Codex を使った解析プロセスの全体像を示しています。

mermaidflowchart TB
  code["既存<br/>コード"] --> prepare["前処理<br/>・分割"]
  prepare --> codex_api["Codex API<br/>呼び出し"]

  codex_api --> task1["タスク1:<br/>要約生成"]
  codex_api --> task2["タスク2:<br/>設計意図抽出"]
  codex_api --> task3["タスク3:<br/>依存関係分析"]

  task1 --> merge["結果<br/>統合"]
  task2 --> merge
  task3 --> merge

  merge --> visual["可視化<br/>レポート"]
  visual --> review["開発者<br/>レビュー"]

図で理解できる要点:

  • コードを前処理した後、Codex API に 3 つの異なるタスクを並列実行させます
  • 各タスクの結果を統合し、可視化レポートとして開発者に提供します

解決策の構成要素

Codex を使った解析システムは、以下の 3 つの主要な機能で構成されます。

要約生成機能

コード全体や各モジュールの役割を自然言語で要約します。長いコードを読まなくても、何をしているかが一目で分かるようになりますね。

設計意図抽出機能

なぜそのような実装になっているのか、どのような設計パターンが使われているのかを抽出します。アーキテクチャの理解が深まります。

依存関係マップ化機能

モジュール間、関数間、ライブラリとの依存関係を自動で図式化します。影響範囲の把握が容易になるでしょう。

技術スタックの選定

Codex を活用するために、以下の技術スタックを使用します。

#技術要素役割選定理由
1TypeScript実装言語型安全性とコード補完による開発効率向上
2Node.jsランタイム環境JavaScript/TypeScript エコシステムとの親和性
3OpenAI APICodex エンジンコード理解に特化した AI モデル
4Yarnパッケージ管理高速で信頼性の高い依存関係管理

具体例

プロジェクトのセットアップ

まず、Codex を使ったコード解析ツールを構築するための環境を準備しましょう。

プロジェクトの初期化

新しいプロジェクトディレクトリを作成し、必要なパッケージをインストールします。

bashmkdir code-analyzer
cd code-analyzer
yarn init -y

このコマンドで、新しい Node.js プロジェクトが初期化されます。

必要なパッケージのインストール

Codex API を利用するために必要なパッケージをインストールしましょう。

bashyarn add openai dotenv
yarn add -D typescript @types/node ts-node

これらのパッケージにより、以下の機能が利用可能になります。

  • openai: OpenAI API(Codex)との通信
  • dotenv: 環境変数の管理
  • typescript: TypeScript のコンパイラ
  • @types​/​node: Node.js の型定義
  • ts-node: TypeScript ファイルの直接実行

TypeScript の設定ファイル作成

TypeScript の設定ファイル(tsconfig.json)を作成します。

json{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

この設定により、厳格な型チェックと最新の JavaScript 機能が使えるようになります。

環境変数の設定

プロジェクトのルートに .env ファイルを作成し、OpenAI の API キーを設定しましょう。

bashOPENAI_API_KEY=your_api_key_here

API キーは OpenAI のプラットフォームから取得できます。

コード要約機能の実装

既存コードを読み込んで、その内容を要約する機能を実装します。

API クライアントの基本実装

まず、OpenAI API と通信するための基本的なクライアントを作成しましょう。

typescript// src/codex-client.ts

import { Configuration, OpenAIApi } from 'openai';
import * as dotenv from 'dotenv';

// 環境変数を読み込む
dotenv.config();

このコードは、環境変数から API キーを読み込む準備をします。

OpenAI クライアントの初期化

OpenAI API クライアントを初期化するクラスを定義します。

typescript// src/codex-client.ts(続き)

export class CodexClient {
  private openai: OpenAIApi;

  constructor() {
    // API キーの存在確認
    const apiKey = process.env.OPENAI_API_KEY;
    if (!apiKey) {
      throw new Error(
        'OPENAI_API_KEY が設定されていません'
      );
    }

    // OpenAI クライアントの設定
    const configuration = new Configuration({
      apiKey: apiKey,
    });

    // クライアントインスタンスの作成
    this.openai = new OpenAIApi(configuration);
  }
}

このクラスは、OpenAI API との通信を一元管理します。エラーハンドリングも含まれていますね。

コード要約メソッドの実装

コードを受け取って要約を生成するメソッドを追加しましょう。

typescript// src/codex-client.ts(続き)

export class CodexClient {
  // ... 前述のコンストラクタ

  /**
   * コードの内容を要約する
   * @param code 解析対象のコード
   * @param language プログラミング言語
   * @returns コードの要約テキスト
   */
  async summarizeCode(
    code: string,
    language: string = 'javascript'
  ): Promise<string> {
    // プロンプトの構築
    const prompt = `以下の${language}コードの内容を、簡潔に要約してください。
主な機能、入力、出力、重要なロジックを含めてください。

\`\`\`${language}
${code}
\`\`\`

要約:`;

    try {
      // Codex API の呼び出し
      const response = await this.openai.createCompletion({
        model: 'gpt-3.5-turbo-instruct',
        prompt: prompt,
        max_tokens: 500,
        temperature: 0.3,
      });

      // レスポンスからテキストを取得
      return (
        response.data.choices[0]?.text?.trim() ||
        '要約を生成できませんでした'
      );
    } catch (error) {
      console.error('API エラー:', error);
      throw new Error(
        'コードの要約中にエラーが発生しました'
      );
    }
  }
}

このメソッドは、以下の処理を行います。

  1. 入力されたコードを含むプロンプトを構築
  2. Codex API にリクエストを送信
  3. 生成された要約テキストを返却

temperature: 0.3 という低い値により、一貫性のある要約が生成されます。

設計意図抽出機能の実装

コードから設計パターンやアーキテクチャの意図を抽出する機能を実装しましょう。

設計意図抽出メソッドの定義

typescript// src/codex-client.ts(続き)

export class CodexClient {
  // ... 前述のメソッド

  /**
   * コードから設計意図を抽出する
   * @param code 解析対象のコード
   * @param language プログラミング言語
   * @returns 設計意図の説明
   */
  async extractDesignIntent(
    code: string,
    language: string = 'javascript'
  ): Promise<string> {
    // 設計意図を引き出すプロンプト
    const prompt = `以下の${language}コードを分析し、設計意図を説明してください。
以下の観点で分析してください:
1. 使用されている設計パターン
2. アーキテクチャの選択理由
3. 拡張性や保守性への配慮
4. パフォーマンスやセキュリティの考慮点

\`\`\`${language}
${code}
\`\`\`

設計意図の分析:`;

    try {
      const response = await this.openai.createCompletion({
        model: 'gpt-3.5-turbo-instruct',
        prompt: prompt,
        max_tokens: 800,
        temperature: 0.4,
      });

      return (
        response.data.choices[0]?.text?.trim() ||
        '設計意図を抽出できませんでした'
      );
    } catch (error) {
      console.error('API エラー:', error);
      throw new Error(
        '設計意図の抽出中にエラーが発生しました'
      );
    }
  }
}

このメソッドは、コードの背後にある設計判断を明らかにします。単なる機能説明ではなく、「なぜその実装を選んだか」という深い洞察が得られるでしょう。

依存関係マップ化機能の実装

モジュールや関数の依存関係を抽出し、視覚化する機能を実装します。

依存関係抽出メソッドの実装

typescript// src/codex-client.ts(続き)

export class CodexClient {
  // ... 前述のメソッド

  /**
   * コードの依存関係を抽出する
   * @param code 解析対象のコード
   * @param language プログラミング言語
   * @returns 依存関係の情報(JSON形式)
   */
  async extractDependencies(
    code: string,
    language: string = 'javascript'
  ): Promise<DependencyMap> {
    // 依存関係を抽出するプロンプト
    const prompt = `以下の${language}コードから依存関係を抽出し、JSON形式で出力してください。
以下の情報を含めてください:
1. インポートしている外部ライブラリ
2. ファイル内で定義されている関数やクラス
3. 関数間の呼び出し関係

\`\`\`${language}
${code}
\`\`\`

JSON形式で依存関係を出力:`;

    try {
      const response = await this.openai.createCompletion({
        model: 'gpt-3.5-turbo-instruct',
        prompt: prompt,
        max_tokens: 1000,
        temperature: 0.2,
      });

      const responseText =
        response.data.choices[0]?.text?.trim() || '{}';

      // JSON のパース
      const dependencies = JSON.parse(responseText);
      return dependencies;
    } catch (error) {
      console.error('依存関係の抽出エラー:', error);
      return {
        externalLibraries: [],
        internalModules: [],
        functionCalls: [],
      };
    }
  }
}

このメソッドは、コードの依存関係を構造化されたデータとして抽出します。

依存関係の型定義

抽出した依存関係を TypeScript で型安全に扱うため、型定義を作成しましょう。

typescript// src/types.ts

/**
 * 依存関係マップの型定義
 */
export interface DependencyMap {
  externalLibraries: ExternalLibrary[];
  internalModules: InternalModule[];
  functionCalls: FunctionCall[];
}

/**
 * 外部ライブラリの情報
 */
export interface ExternalLibrary {
  name: string;
  version?: string;
  importedItems?: string[];
}

/**
 * 内部モジュールの情報
 */
export interface InternalModule {
  name: string;
  type: 'function' | 'class' | 'constant';
  exports: boolean;
}

/**
 * 関数呼び出しの関係
 */
export interface FunctionCall {
  caller: string;
  callee: string;
}

これらの型定義により、依存関係データを安全に操作できます。

依存関係の可視化機能

抽出した依存関係を Mermaid 形式の図として出力する機能を追加しましょう。

typescript// src/visualizer.ts

import { DependencyMap, FunctionCall } from './types';

/**
 * 依存関係を Mermaid 図として可視化する
 */
export class DependencyVisualizer {
  /**
   * Mermaid のフローチャートを生成
   * @param dependencies 依存関係データ
   * @returns Mermaid 記法の文字列
   */
  generateMermaidDiagram(
    dependencies: DependencyMap
  ): string {
    let diagram = 'flowchart TB\n';

    // 外部ライブラリのノード追加
    dependencies.externalLibraries.forEach((lib, index) => {
      const nodeId = `ext${index}`;
      diagram += `  ${nodeId}["${lib.name}<br/>外部ライブラリ"]\n`;
    });

    // 内部モジュールのノード追加
    dependencies.internalModules.forEach((mod, index) => {
      const nodeId = `int${index}`;
      const shape = mod.type === 'class' ? '[]' : '()';
      diagram += `  ${nodeId}${shape[0]}"${mod.name}<br/>${mod.type}"${shape[1]}\n`;
    });

    // 関数呼び出しのエッジ追加
    dependencies.functionCalls.forEach((call) => {
      diagram += `  ${call.caller} --> ${call.callee}\n`;
    });

    return diagram;
  }
}

このクラスは、構造化された依存関係データを、開発者が理解しやすいビジュアル表現に変換します。

統合実行スクリプトの作成

これまで実装した機能を統合し、コマンドラインから実行できるスクリプトを作成しましょう。

メインスクリプトの実装

typescript// src/index.ts

import * as fs from 'fs';
import * as path from 'path';
import { CodexClient } from './codex-client';
import { DependencyVisualizer } from './visualizer';

/**
 * コード解析を実行するメイン関数
 */
async function analyzeCode(filePath: string) {
  try {
    // ファイルの読み込み
    const code = fs.readFileSync(filePath, 'utf-8');
    const ext = path.extname(filePath).slice(1);
    const language = getLanguageFromExtension(ext);

    console.log(`\n📄 ファイル: ${filePath}`);
    console.log(`🔍 言語: ${language}\n`);

    // Codex クライアントの初期化
    const client = new CodexClient();

    // 要約の生成
    console.log('📝 コード要約を生成中...');
    const summary = await client.summarizeCode(
      code,
      language
    );
    console.log('\n要約:\n', summary);

    // 設計意図の抽出
    console.log('\n🏗️  設計意図を抽出中...');
    const designIntent = await client.extractDesignIntent(
      code,
      language
    );
    console.log('\n設計意図:\n', designIntent);

    // 依存関係の抽出
    console.log('\n🔗 依存関係を分析中...');
    const dependencies = await client.extractDependencies(
      code,
      language
    );

    // 依存関係の可視化
    const visualizer = new DependencyVisualizer();
    const mermaidDiagram =
      visualizer.generateMermaidDiagram(dependencies);
    console.log(
      '\n依存関係図(Mermaid):\n',
      mermaidDiagram
    );
  } catch (error) {
    console.error('❌ エラーが発生しました:', error);
    process.exit(1);
  }
}

このメイン関数は、ファイルパスを受け取り、3 つの解析を順次実行します。

言語判定ヘルパー関数

ファイル拡張子からプログラミング言語を判定する関数を追加しましょう。

typescript// src/index.ts(続き)

/**
 * ファイル拡張子から言語を判定
 * @param ext ファイル拡張子
 * @returns プログラミング言語名
 */
function getLanguageFromExtension(ext: string): string {
  const languageMap: Record<string, string> = {
    js: 'javascript',
    ts: 'typescript',
    py: 'python',
    java: 'java',
    rb: 'ruby',
    go: 'go',
    rs: 'rust',
    php: 'php',
    cpp: 'c++',
    c: 'c',
  };

  return languageMap[ext] || 'javascript';
}

この関数により、様々な言語のファイルに対応できます。

コマンドライン引数の処理

コマンドラインから実行できるようにエントリーポイントを追加します。

typescript// src/index.ts(続き)

/**
 * エントリーポイント
 */
function main() {
  // コマンドライン引数の取得
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.log(
      '使用方法: ts-node src/index.ts <ファイルパス>'
    );
    process.exit(1);
  }

  const filePath = args[0];

  // ファイルの存在確認
  if (!fs.existsSync(filePath)) {
    console.error(
      `❌ ファイルが見つかりません: ${filePath}`
    );
    process.exit(1);
  }

  // 解析の実行
  analyzeCode(filePath);
}

// スクリプトとして実行された場合のみ main を呼び出す
if (require.main === module) {
  main();
}

これで、コマンドラインから任意のコードファイルを解析できるようになりました。

実行とテスト

実際に作成したツールを使って、サンプルコードを解析してみましょう。

サンプルコードの準備

解析対象となるサンプルの JavaScript ファイルを作成します。

javascript// sample/calculator.js

/**
 * 基本的な計算機クラス
 */
class Calculator {
  constructor() {
    this.result = 0;
    this.history = [];
  }

  /**
   * 加算処理
   */
  add(a, b) {
    const result = a + b;
    this.recordHistory('add', a, b, result);
    return result;
  }

  /**
   * 減算処理
   */
  subtract(a, b) {
    const result = a - b;
    this.recordHistory('subtract', a, b, result);
    return result;
  }

  /**
   * 履歴の記録
   */
  recordHistory(operation, a, b, result) {
    this.history.push({
      operation,
      operands: [a, b],
      result,
      timestamp: new Date(),
    });
  }

  /**
   * 履歴の取得
   */
  getHistory() {
    return this.history;
  }
}

module.exports = Calculator;

このサンプルコードは、シンプルな計算機クラスを実装しています。

ツールの実行

作成したツールを実行してみましょう。

bashts-node src/index.ts sample/calculator.js

実行すると、以下のような出力が得られます。

出力例の確認

実際に実行すると、以下のような解析結果が表示されるでしょう。

text📄 ファイル: sample/calculator.js
🔍 言語: javascript

📝 コード要約を生成中...

要約:
このコードは、基本的な計算機能を提供する Calculator クラスを定義しています。
主な機能:
- add メソッド:2つの数値の加算
- subtract メソッド:2つの数値の減算
- recordHistory メソッド:各計算の履歴を記録
- getHistory メソッド:計算履歴の取得
計算結果と操作履歴を内部状態として保持し、トレーサビリティを確保しています。

🏗️  設計意図を抽出中...

設計意図:
1. 設計パターン:
   - オブジェクト指向設計により、状態と振る舞いをカプセル化
   - 履歴管理機能により、監査証跡パターンを実装

2. アーキテクチャの選択:
   - クラスベースの設計により、再利用性と拡張性を確保
   - module.exports により、CommonJS モジュールとして提供

3. 保守性への配慮:
   - 各メソッドは単一責任の原則に従い、明確な役割を持つ
   - コメントによるドキュメント化

4. 拡張性:
   - 新しい計算メソッドの追加が容易な構造
   - 履歴機能により、後からの分析や監査に対応

この出力から、コードの機能だけでなく、設計の背景にある意図まで理解できますね。

レポート出力機能の追加

解析結果をファイルとして保存する機能を追加しましょう。

レポート生成クラスの実装

typescript// src/reporter.ts

import * as fs from 'fs';
import * as path from 'path';

/**
 * 解析結果をレポートとして出力
 */
export class AnalysisReporter {
  /**
   * Markdown 形式のレポートを生成
   */
  generateMarkdownReport(
    filePath: string,
    summary: string,
    designIntent: string,
    mermaidDiagram: string
  ): string {
    const fileName = path.basename(filePath);
    const timestamp = new Date().toLocaleString('ja-JP');

    return `# コード解析レポート

# 対象ファイル
- **ファイル名**: ${fileName}
- **パス**: ${filePath}
- **解析日時**: ${timestamp}

# 要約

${summary}

# 設計意図

${designIntent}

# 依存関係図

\`\`\`\`mermaid
${mermaidDiagram}
\`\`\`\`

---
このレポートは Codex により自動生成されました。
`;
  }

  /**
   * レポートをファイルに保存
   */
  saveReport(content: string, outputPath: string): void {
    fs.writeFileSync(outputPath, content, 'utf-8');
    console.log(
      `\n✅ レポートを保存しました: ${outputPath}`
    );
  }
}

このクラスは、解析結果を見やすい Markdown レポートとして出力します。

メインスクリプトへの統合

レポート生成機能をメインスクリプトに組み込みましょう。

typescript// src/index.ts(修正)

import { AnalysisReporter } from './reporter';

async function analyzeCode(
  filePath: string,
  saveReport: boolean = false
) {
  try {
    // ... 前述の解析処理

    // レポートの生成と保存
    if (saveReport) {
      const reporter = new AnalysisReporter();
      const report = reporter.generateMarkdownReport(
        filePath,
        summary,
        designIntent,
        mermaidDiagram
      );

      const outputPath = `${filePath}.analysis.md`;
      reporter.saveReport(report, outputPath);
    }
  } catch (error) {
    console.error('❌ エラーが発生しました:', error);
    process.exit(1);
  }
}

これで、--save オプションを付けて実行すると、レポートファイルが生成されます。

エラーハンドリングの強化

実運用を想定して、エラーハンドリングを強化しましょう。

API レート制限への対応

OpenAI API にはレート制限があるため、リトライ処理を実装します。

typescript// src/utils/retry.ts

/**
 * リトライ付き関数実行
 */
export async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  initialDelay: number = 1000
): Promise<T> {
  let lastError: Error;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      // レート制限エラーの場合のみリトライ
      if (error.response?.status === 429) {
        const delay = initialDelay * Math.pow(2, i);
        console.log(
          `⏳ レート制限に達しました。${delay}ms 待機します...`
        );
        await sleep(delay);
        continue;
      }

      // その他のエラーは即座にスロー
      throw error;
    }
  }

  throw lastError!;
}

/**
 * 指定時間待機
 */
function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

このユーティリティ関数により、一時的なエラーに対して自動的にリトライできます。

CodexClient でのリトライ適用

リトライ機能を Codex クライアントに適用しましょう。

typescript// src/codex-client.ts(修正)

import { retryWithBackoff } from './utils/retry';

export class CodexClient {
  async summarizeCode(
    code: string,
    language: string = 'javascript'
  ): Promise<string> {
    // リトライ処理でラップ
    return retryWithBackoff(async () => {
      const prompt = `以下の${language}コードの内容を、簡潔に要約してください。
...`;

      const response = await this.openai.createCompletion({
        model: 'gpt-3.5-turbo-instruct',
        prompt: prompt,
        max_tokens: 500,
        temperature: 0.3,
      });

      return (
        response.data.choices[0]?.text?.trim() ||
        '要約を生成できませんでした'
      );
    });
  }
}

これで、API のレート制限に達した場合でも、自動的にリトライして処理を継続できます。

バッチ処理機能の実装

複数のファイルを一括で解析できるバッチ処理機能を追加しましょう。

ディレクトリスキャン機能

指定されたディレクトリ内のすべてのコードファイルを検出します。

typescript// src/batch-analyzer.ts

import * as fs from 'fs';
import * as path from 'path';

/**
 * バッチ解析クラス
 */
export class BatchAnalyzer {
  private supportedExtensions = [
    '.js',
    '.ts',
    '.py',
    '.java',
    '.rb',
    '.go',
  ];

  /**
   * ディレクトリ内のコードファイルを再帰的に検索
   */
  findCodeFiles(dirPath: string): string[] {
    const files: string[] = [];

    const entries = fs.readdirSync(dirPath, {
      withFileTypes: true,
    });

    for (const entry of entries) {
      const fullPath = path.join(dirPath, entry.name);

      if (entry.isDirectory()) {
        // node_modules などは除外
        if (
          entry.name !== 'node_modules' &&
          entry.name !== '.git'
        ) {
          files.push(...this.findCodeFiles(fullPath));
        }
      } else if (entry.isFile()) {
        const ext = path.extname(entry.name);
        if (this.supportedExtensions.includes(ext)) {
          files.push(fullPath);
        }
      }
    }

    return files;
  }
}

この機能により、プロジェクト全体を一括解析できるようになります。

並列処理の実装

複数ファイルを効率的に処理するため、並列実行機能を追加しましょう。

typescript// src/batch-analyzer.ts(続き)

import { CodexClient } from './codex-client';
import { AnalysisReporter } from './reporter';

export class BatchAnalyzer {
  // ... 前述のメソッド

  /**
   * 複数ファイルを並列解析
   */
  async analyzeMultipleFiles(
    filePaths: string[],
    concurrency: number = 3
  ): Promise<void> {
    const client = new CodexClient();
    const reporter = new AnalysisReporter();

    // 並列実行数を制限しながら処理
    for (
      let i = 0;
      i < filePaths.length;
      i += concurrency
    ) {
      const batch = filePaths.slice(i, i + concurrency);

      await Promise.all(
        batch.map(async (filePath) => {
          try {
            console.log(`\n🔍 解析中: ${filePath}`);

            const code = fs.readFileSync(filePath, 'utf-8');
            const ext = path.extname(filePath).slice(1);
            const language = this.getLanguage(ext);

            // 各解析を実行
            const summary = await client.summarizeCode(
              code,
              language
            );
            const designIntent =
              await client.extractDesignIntent(
                code,
                language
              );
            const dependencies =
              await client.extractDependencies(
                code,
                language
              );

            // レポート生成
            const mermaidDiagram = ''; // 簡略化のため省略
            const report = reporter.generateMarkdownReport(
              filePath,
              summary,
              designIntent,
              mermaidDiagram
            );

            reporter.saveReport(
              report,
              `${filePath}.analysis.md`
            );
            console.log(`✅ 完了: ${filePath}`);
          } catch (error) {
            console.error(
              `❌ エラー (${filePath}):`,
              error.message
            );
          }
        })
      );
    }
  }

  private getLanguage(ext: string): string {
    // 言語判定ロジック(前述と同様)
    const map: Record<string, string> = {
      js: 'javascript',
      ts: 'typescript',
      // ...
    };
    return map[ext] || 'javascript';
  }
}

並列処理により、大規模なプロジェクトでも効率的に解析できますね。

実践的な活用例

実際のプロジェクトでの活用シナリオを見てみましょう。

プロジェクト全体の解析実行

bash# プロジェクトディレクトリ全体を解析
ts-node src/index.ts --batch ./my-project

CI/CD パイプラインへの組み込み

GitHub Actions で自動的にコード解析を実行する設定例です。

yaml# .github/workflows/code-analysis.yml

name: Code Analysis

on:
  pull_request:
    branches: [main]

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: yarn install

      - name: Run code analysis
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          yarn ts-node src/index.ts --batch ./src --save

      - name: Upload analysis reports
        uses: actions/upload-artifact@v3
        with:
          name: analysis-reports
          path: '**/*.analysis.md'

この設定により、プルリクエストごとに自動的にコード解析が実行され、レポートがアーティファクトとして保存されます。

パフォーマンスと制限事項

Codex を使った解析には、いくつかの制限事項があります。

#項目制限内容対処方法
1トークン制限1 回のリクエストで処理できるコード量に上限がある大きなファイルは分割して解析
2レート制限API 呼び出しの頻度に制限があるリトライ処理とバッチサイズの調整
3コストAPI 使用量に応じた課金が発生するキャッシュ機能の実装を検討
4精度複雑なコードでは解析精度が低下する可能性人間によるレビューと併用

これらの制限を理解した上で、適切に活用することが重要ですね。

まとめ

Codex を活用した既存コードの解析手法について、実践的な実装例とともにご紹介しました。

この記事では、以下の内容を学びましたね。

  1. コード要約生成: 長いコードを読まなくても、主要な機能と役割を把握できる仕組み
  2. 設計意図抽出: コードに込められた設計判断やアーキテクチャの選択理由を明らかにする方法
  3. 依存関係マップ化: モジュールや関数の関係性を視覚化し、影響範囲を理解しやすくする技術

これらの手法を組み合わせることで、既存コードの理解にかかる時間を大幅に短縮できるでしょう。新しいプロジェクトへの参加やレガシーコードの保守において、Codex は強力なサポートツールとなります。

ただし、AI による解析結果は完璧ではありません。最終的には人間の判断が必要です。Codex を「理解を加速するツール」として位置付け、開発者の知識と経験と組み合わせることで、最大の効果が得られますね。

今後は、解析結果のキャッシュ機能や、より高度な依存関係グラフの生成、さらには脆弱性検出機能などを追加することで、さらに実用的なツールに発展させることができるでしょう。

関連リンク