🌐
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が使えるところで使われているか
- 例外発生時もリソースが解放されるか
-
StreamAPIの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コードでリソース解放が忘れられがちな理由は:
- 学習データに古いコードが多い
- 簡潔さを優先してエラーハンドリングを省略する
- 局所的なパターンマッチングに偏りがち
この問題に対処するには:
- ✅ AI生成コードは必ずレビューする
- ✅ 静的解析ツールを導入する
- ✅ AIへの指示を具体的にする
- ✅ try-with-resourcesを標準とする
- ✅ テストで実際にリソースが解放されることを確認する
AIは強力なツールですが、完璧ではありません。 特にリソース管理のような「見えにくいが重要」な部分は、人間が注意深くチェックする必要があります。
AIを上手に活用しながら、品質の高いコードを書いていきましょう!
参考資料
- The try-with-resources Statement (Oracle)
- Effective Java, Third Edition - Item 9: Prefer try-with-resources to try-finally
この記事がお役に立ちましたら、ぜひLIKEやコメントをお願いします!
Discussion