生成AIのコード生成能力を検証【Java編】/Cursorエディタ利用

生成AI・機械学習

はじめに

生成AIを利用したコード生成について、HelloWorldのような簡単な例の紹介記事が多いため、より実務的なコード生成について検証します。プロンプトからどのようなコードが生成されるのかをステップバイステップで見ていきます。プログラミング言語にはJavaを使用し、生成AIはCursorエディタ経由でChatGPTを利用します。

Cursorエディタについて

ChatGPTと統合されたVSCodeライクな次世代AIエディタです。特徴は以下の通り。

  • ChatGPTと統合されており、コードを書きながらシームレスにAIの利用が出来る
  • コードアシスト、コード生成、リファクタリングなどに対応
  • エディタで管理しているコードをChatGPTに参照させることが出来る
  • VSCodeをベースとしており、多くのエクステンションもそのまま利用可能
  • 無償版あり(AIの利用回数や速度に制限あり)
  • コードを共有しないプライベートモードもあり

生成AIを利用したJavaプログラミング例

JPEGファイルをPNGファイルに変換

引数で指定されたJPEGファイルをPNGファイルに変換するコードを生成してみます。

以下のクラスを生成
・クラス名:JpegToPng
・機能;
  1.JpegファイルをPngファイルに変換する関数を実装
  2.変換するファイルは引数で絶対パスで受け取る
  3.指定されたファイルの存在チェックを行う
  4.変換後のPngファイルの出力先とファイル名は引数で指定する

以下のJavaコードが生成されました。意図どおりで優秀です。

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class JpegToPng {
    public static void convertJpegToPng(String inputPath, String outputPath) throws IOException {
        // 入力ファイルの存在チェック
        File inputFile = new File(inputPath);
        if (!inputFile.exists()) {
            throw new IOException("入力ファイルが存在しません: " + inputPath);
        }
        // 画像の読み込み
        BufferedImage image = ImageIO.read(inputFile);
        // 出力ファイルの作成
        File outputFile = new File(outputPath);
        // PNGとして保存
        ImageIO.write(image, "png", outputFile);
        System.out.println("変換が完了しました: " + outputPath);
    }
    public static void main(String[] args) {
        try {
            convertJpegToPng("/path/to/input.jpg", "/path/to/output.png");
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e.getMessage());
        }
    }
}

log4jのログ出力を追加

log4jを利用し、info, errorレベルのログ出力を依頼してみます。

以下のコードを追加
・エラー発生時はエラーレベルのログを出力
・関数の最初と最後には情報レベルのログを出力
・ログ出力にはlog4jを利用する

一箇所エラーログ出力が漏れていますが、問題ないコードが生成されました。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class JpegToPng {
    private static final Logger logger = LogManager.getLogger(JpegToPng.class);
    public static void convertJpegToPng(String inputPath, String outputPath) throws IOException {
        logger.info("JPEG to PNG変換を開始します。入力: {}, 出力: {}", inputPath, outputPath);
        try {
            // 入力ファイルの存在チェック
            File inputFile = new File(inputPath);
            if (!inputFile.exists()) {
                throw new IOException("入力ファイルが存在しません: " + inputPath);
            }
            // 画像の読み込み
            BufferedImage image = ImageIO.read(inputFile);
            // 出力ファイルの作成
            File outputFile = new File(outputPath);
            // PNGとして保存
            ImageIO.write(image, "png", outputFile);
            logger.info("変換が完了しました: {}", outputPath);
        } catch (IOException e) {
            logger.error("変換中にエラーが発生しました", e);
            throw e;
        }
        logger.info("JPEG to PNG変換を終了します");
    }
    public static void main(String[] args) {
        try {
            convertJpegToPng("/path/to/input.jpg", "/path/to/output.png");
        } catch (IOException e) {
            logger.error("メイン処理でエラーが発生しました", e);
        }
    }
}

Cursorエディタではコード生成時に、次のような説明文も出力されます。とても丁寧で分かりやすいです。

このコードでは以下の変更を行いました:
log4jのLoggerを追加し、クラスレベルで初期化しています。
convertJpegToPngメソッドの開始時と終了時に情報レベルのログを出力しています。
エラー発生時にはエラーレベルのログを出力しています。
mainメソッド内のエラーハンドリングもログ出力を使用するように変更しました。
注意点:
このコードを実行するには、log4jライブラリをプロジェクトに追加する必要があります。
log4jの設定ファイル(通常はlog4j2.xmlまたはlog4j2.properties)を適切に設定する必要があります。
ログの出力先や形式はlog4jの設定ファイルで制御できます。
log4jの設定ファイルの例や詳細な使用方法については、Apache Log4jの公式ドキュメントを参照してください。

ファイル存在チェックエラー時のログを追加

入力ファイルの存在チェックでエラーが発生した場合のエラーログ出力が無かったので追加依頼をします。

入力ファイルの存在チェックエラー発生時にもエラーログを追加

指示通りエラーログを追加してくれました。

            // 入力ファイルの存在チェック
            File inputFile = new File(inputPath);
            if (!inputFile.exists()) {
                logger.error("入力ファイルが存在しません: {}", inputPath);
                throw new IOException("入力ファイルが存在しません: " + inputPath);
            }

JUnitテストコードを生成

作成した「JpegToPng」クラスのJUnitテストコード生成を依頼します。

JpegToPngクラスのテストクラスを別ファイルで作成。
テストコードはJUnitを利用して作成。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

class JpegToPngTest {

    @Test
    void testConvertJpegToPng(@TempDir Path tempDir) throws IOException {
        // テスト用のJPEGファイルを作成
        File inputFile = tempDir.resolve("test.jpg").toFile();
        BufferedImage testImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
        ImageIO.write(testImage, "jpg", inputFile);

        // 出力用のPNGファイルパスを設定
        File outputFile = tempDir.resolve("test.png").toFile();

        // 変換メソッドを呼び出し
        JpegToPng.convertJpegToPng(inputFile.getAbsolutePath(), outputFile.getAbsolutePath());

        // 変換結果を検証
        assertTrue(outputFile.exists(), "出力ファイルが存在しません");
        BufferedImage convertedImage = ImageIO.read(outputFile);
        assertEquals("png", ImageIO.getImageReaders(ImageIO.createImageInputStream(outputFile)).next().getFormatName());
        assertEquals(100, convertedImage.getWidth());
        assertEquals(100, convertedImage.getHeight());
    }

    @Test
    void testConvertJpegToPngWithNonExistentFile(@TempDir Path tempDir) {
        String nonExistentFile = tempDir.resolve("non_existent.jpg").toString();
        String outputFile = tempDir.resolve("output.png").toString();

        assertThrows(IOException.class, () -> {
            JpegToPng.convertJpegToPng(nonExistentFile, outputFile);
        });
    }
}

JPEGファイルをPNGファイル変換する正常系のテストケースと、指定したファイルが存在しない場合の異常系ケースを1つずつ生成してくれました。なかなか優秀です。

ログ出力された文字列を検証するテストコードを追加

標準出力されるログ出力文字列が想定通りか?を検証するテストコードの追加を依頼してみます。

ログ出力の文字列が想定どおりであることを確認するテストを追加
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

class JpegToPngTest {

    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
    private final PrintStream originalOut = System.out;

    @BeforeEach
    void setUpStreams() {
        System.setOut(new PrintStream(outContent));
    }

    @AfterEach
    void restoreStreams() {
        System.setOut(originalOut);
    }

    @Test
    void testConvertJpegToPng(@TempDir Path tempDir) throws IOException {
        // テスト用のJPEGファイルを作成
        File inputFile = tempDir.resolve("test.jpg").toFile();
        BufferedImage testImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
        ImageIO.write(testImage, "jpg", inputFile);

        // 出力用のPNGファイルパスを設定
        File outputFile = tempDir.resolve("test.png").toFile();

        // 変換メソッドを呼び出し
        JpegToPng.convertJpegToPng(inputFile.getAbsolutePath(), outputFile.getAbsolutePath());

        // 変換結果を検証
        assertTrue(outputFile.exists(), "出力ファイルが存在しません");
        BufferedImage convertedImage = ImageIO.read(outputFile);
        assertEquals("png", ImageIO.getImageReaders(ImageIO.createImageInputStream(outputFile)).next().getFormatName());
        assertEquals(100, convertedImage.getWidth());
        assertEquals(100, convertedImage.getHeight());

        // ログ出力を検証
        String expectedLog = "JPEG画像をPNGに変換しました。\n入力: " + inputFile.getAbsolutePath() + "\n出力: " + outputFile.getAbsolutePath() + "\n";
        assertEquals(expectedLog, outContent.toString());
    }

    @Test
    void testConvertJpegToPngWithNonExistentFile(@TempDir Path tempDir) {
        String nonExistentFile = tempDir.resolve("non_existent.jpg").toString();
        String outputFile = tempDir.resolve("output.png").toString();

        assertThrows(IOException.class, () -> {
            JpegToPng.convertJpegToPng(nonExistentFile, outputFile);
        });

        // エラーログ出力を検証
        String expectedErrorLog = "エラー: 入力ファイルが見つかりません。\n";
        assertEquals(expectedErrorLog, outContent.toString());
    }
}

意図通りに追加してくれました。Cursorエディタでは以下のように修正内容の概要も出力してくれました。

この更新されたテストクラスでは、以下の変更を行いました:
1. ByteArrayOutputStreamとPrintStreamを使用して、標準出力をキャプチャするためのセットアップを追加しました。
@BeforeEachと@AfterEachアノテーションを使用して、各テストの前後で標準出力のリダイレクトを設定および復元するメソッドを追加しました。
testConvertJpegToPngメソッドに、ログ出力を検証するアサーションを追加しました。
testConvertJpegToPngWithNonExistentFileメソッドに、エラーログ出力を検証するアサーションを追加しました。
これらの変更により、JpegToPngクラスのログ出力が期待通りであることを確認するテストが追加されました。テストを実行すると、変換処理の結果だけでなく、ログ出力の正確性も検証されます。

まとめ

生成AIによるコード生成は、この1年で大きく進歩し、優れたコードを生成できるようになったと感じました。意図しないコードが生成されることもありますが、その多くはインプット情報の不足が原因です。インプットを正確にすれば、意図通りのコードを生成してくれます。

得意不得意はありますが、仕様が明確なコード生成においては、多くのプログラマーよりも正確なコードを瞬時に生成してくれるため、業務で上手く活用すれば生産性や品質の向上に大きく貢献すると思います。イメージとしては優秀な海外オフショアのエンジニアが常に隣にいてくれるような感覚でした。(日本語は得意ではないが明確な指示を出せば高い成果を出してくれる)

以上

タイトルとURLをコピーしました