🌐

AIが生成するJavaコードは、なぜリソース解放を忘れがちなのか

に公開

AIが生成するJavaコードは、なぜリソース解放を忘れがちなのか

はじめに

ChatGPTやGitHub Copilotなどの生成AIを使ってコードを書く開発者が増えています。しかし、AIが生成するJavaコードには、ある共通の問題が潜んでいます。それはリソース解放の漏れです。

この記事では、なぜAIがリソース解放を忘れがちなのか、その背景にある技術的理由と、実際の問題例、そして対策について解説します。

よくある問題パターン

パターン1: try-with-resourcesを使わない古いスタイル

// AIが生成しがちなコード
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    BufferedReader br = new BufferedReader(reader);
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
    // closeが呼ばれていない!
}

このコードは一見動作しますが、ファイルハンドルが解放されません。正しくは以下のようにtry-with-resourcesを使うべきです。

// 正しいコード
public void readFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } // 自動的にcloseされる
}

パターン2: 例外発生時のリソースリーク

// 問題のあるコード
public void processData() throws Exception {
    Connection conn = DriverManager.getConnection(url, user, password);
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");
    
    // 処理中に例外が発生すると...
    processResults(rs);
    
    rs.close();
    stmt.close();
    conn.close(); // ここまで到達しない可能性
}

パターン3: ストリームAPIでのリソース管理の見落とし

// 問題のあるコード
public long countLines(String path) throws IOException {
    return Files.lines(Paths.get(path)).count(); // Streamがcloseされない
}

// 正しいコード
public long countLines(String path) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(path))) {
        return lines.count();
    }
}

なぜAIはリソース解放を忘れるのか

1. 学習データの偏り

AIモデルは大量のGitHubコードで学習していますが、そこには以下のような特徴があります:

  • 古いJavaコード(Java 7以前)が多い: try-with-resources(Java 7導入)以前のコードが学習データに含まれている
  • コードスニペットの断片性: 完全なエラーハンドリングを含まない短いサンプルコードが多い
  • チュートリアルコードの影響: 「わかりやすさ」を重視して、リソース管理を省略した教育用コードが含まれている

2. コンテキストの制約

AIは短いコードスニペットの生成を求められることが多く:

  • 簡潔さの優先: ユーザーが短いコードを期待していると判断し、エラーハンドリングを省略
  • 主要ロジックへの集中: 質問の焦点(例: ファイル読み込み)に注力し、付随する管理コードを省略
  • コメントや質問文の影響: 「ファイルを読むコード」と聞かれると、読む部分だけを生成しがち

3. パターンマッチングの限界

AIはパターン認識に基づいてコードを生成しますが:

  • 頻出パターンを優先: new FileReader()は頻繁に見るが、その後のclose()は別の場所にあることが多い
  • スコープの認識不足: メソッド全体の構造よりも、局所的なコード片を優先する傾向
  • エラーパスの軽視: 正常系のコードは生成するが、例外処理やクリーンアップは後回し

実際に起こる問題

メモリリーク

// 問題: 大量のファイルを処理する場合
public void processFiles(List<String> paths) {
    for (String path : paths) {
        try {
            FileInputStream fis = new FileInputStream(path);
            // 処理...
            // closeを忘れている
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

このコードを長時間稼働させると、オープンファイル数の上限に達してしまいます。

データベース接続プールの枯渇

// 問題: 接続が返却されない
public User getUser(int id) {
    try {
        Connection conn = dataSource.getConnection();
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();
        if (rs.next()) {
            return new User(rs.getString("name"), rs.getString("email"));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return null;
}

コネクションプールから借りた接続が返却されず、やがてプールが枯渇します。

対策とベストプラクティス

1. AI生成コードのレビューチェックリスト

AI生成コードをレビューする際は、以下を必ず確認しましょう:

  • InputStream/OutputStreamが適切にcloseされているか
  • データベース接続が確実に返却されているか
  • try-with-resourcesが使えるところで使われているか
  • 例外発生時もリソースが解放されるか
  • Stream APIのstreamがcloseされているか

2. 静的解析ツールの活用

<!-- pom.xmlにSpotBugsを追加 -->
<plugin>
    <groupId>com.github.spotbugs</groupId>
    <artifactId>spotbugs-maven-plugin</artifactId>
    <version>4.7.3.6</version>
</plugin>

SpotBugsやErrorProneは、リソースリークを検出してくれます。

3. AIへの指示を具体的にする

❌ 悪い例: "ファイルを読むコードを書いて"

✅ 良い例: "ファイルを読むコードを、try-with-resourcesを使って、
          エラーハンドリングも含めて書いて"

4. コードテンプレートの活用

// リソース管理のテンプレート
public <T> T withResource(ResourceSupplier<T> supplier, 
                          ResourceConsumer<T> consumer) throws Exception {
    T resource = supplier.get();
    try {
        return consumer.apply(resource);
    } finally {
        if (resource instanceof AutoCloseable) {
            ((AutoCloseable) resource).close();
        }
    }
}

5. 現代的なJavaの機能を明示的に要求する

AIに「Java 17以降」「最新のベストプラクティス」と指定することで、より現代的なコードが生成される可能性が高まります。

まとめ

AIが生成するJavaコードでリソース解放が忘れられがちな理由は:

  1. 学習データに古いコードが多い
  2. 簡潔さを優先してエラーハンドリングを省略する
  3. 局所的なパターンマッチングに偏りがち

この問題に対処するには:

  • ✅ AI生成コードは必ずレビューする
  • ✅ 静的解析ツールを導入する
  • ✅ AIへの指示を具体的にする
  • ✅ try-with-resourcesを標準とする
  • ✅ テストで実際にリソースが解放されることを確認する

AIは強力なツールですが、完璧ではありません。 特にリソース管理のような「見えにくいが重要」な部分は、人間が注意深くチェックする必要があります。

AIを上手に活用しながら、品質の高いコードを書いていきましょう!


参考資料


この記事がお役に立ちましたら、ぜひLIKEやコメントをお願いします!

Discussion