【AI】Claude Codeでソースを改善する

IT IT勉強
DSC_0281

Visual Studio CodeにClaude Code用プラグインをインストールし、Claude Codeにソースコードの改善アドバイスを行ってもらいます。

Claude Codeとは

Claude Codeは、Anthropicが提供するコード解析に特化したAIを搭載したコーディングアシスタントです。
ソースコードの理解・説明・改善提案を自然言語で行うことができ、コードの構造把握や問題点の指摘を得意としています。
開発者が効率よく作業を進めるための強力なサポートツールです。

Claude Codeの利用には、Claudeの有償プランが必要です。
本記事では、有償プランを利用した環境で動作を確認しています。

注意事項

ソースコードをAIツールで解析する際には、Claude Codeに限らず、入力したコードがAIモデルの学習に利用されたり、外部に漏えいする可能性がある点に注意が必要です。
そのため、業務で扱うソースコードを、企業や顧客、プロジェクトの許可なくAIツールに読み込ませることは、後々トラブルや訴訟に発展するリスクがあります。

工数削減など明確な目的がある場合でも、まずは利用するAIツールの安全性やデータ取り扱いを確認したうえで、ソースコードの所有者である企業や顧客に事前相談し、了承を得てから使用するようにしてください。

なお、本手順の利用に伴ういかなる結果についても、当方では責任を負いかねます。
全て自己責任でご対応ください。

事前準備

アカウント作成

ブログ記事「【AI】Claude Codeでソースを解析する」に記載しています。

Claude Codeプラグインをインストール

Visual Studio CodeにClaude Codeのプラグインをインストールします。

Visual Studio Codeの拡張機能で、検索窓に「Claude Code for VS Code」を入力し、Claude Code for VS Codeをインストールします。

以下のダイアログが表示されるので、問題がなければ[発行元を信頼してインストール]をクリックします。

インストールが完了すると、Visual Studio Codeの右上にClaude Codeのボタンが表示されます。

Claude Codeでユーザ認証を行う

Claude Codeのボタンをクリックすると、右側にClaude Codeのビューが表示されます。
表示されたビューで[Claude.ai Subscription]ボタンをクリックします。

外部のWebサイトを開く確認ダイアログが表示されるので、[開く]ボタンをクリックします。

ブラウザに以下が表示されます。
今回は既にGoogleアカウントでログイン済みのため、画面下部にGoogleアカウントが表示されています。
[承認する]ボタンをクリックします。

ブラウザに以下のようなセットアップ完了のメッセージが表示されるので、ブラウザを閉じます。

Visual Studio Codeに戻ると、以下のようにClaude Codeが使用できる状態になります。

以降は、Visual Studio Codeで開いているmain.pyを使用して操作を行います。

main.pyの内容は以下の通りです。

import sys
from pathlib import Path
from typing import Dict
import base64

from azure.storage.blob import BlobServiceClient

import common.util as util

def main():
    
    AZURE_BLOB_CONNECTION_STRING = "【ストレージアカウント接続文字列】"
    AZURE_BLOB_CONTAINER_NAME = "bs-doc"
    AZURE_BLOB_CONTAINER_PATH = "document"
    DOC_FILE_PATH = "D:\\work\\ブログ\\Azure AI\\ドキュメント\\"
    DOC_CATEGORY = "モカ"
    
       
    
    # BlobServiceClientの作成
    blob_service_client = BlobServiceClient.from_connection_string(
        AZURE_BLOB_CONNECTION_STRING)
    # ContailerClientの取得
    container_client = blob_service_client.get_container_client(
        AZURE_BLOB_CONTAINER_NAME)

    # ファイル読み込み
    doc_files = list(Path(DOC_FILE_PATH).glob("**/*"))
    for doc_file in doc_files:

        if not doc_file.is_file():
            continue

        # タグ作成
        metadata: Dict[str, str] = {}
        metadata["filepath"] = base64.b64encode(
            str(doc_file.resolve()).encode("utf-8")).decode("utf-8")
        metadata["category"] = base64.b64encode(
            DOC_CATEGORY.encode("utf-8")).decode("utf-8")

        # ファイルをAzure Blob Storageに登録
        blob_client = container_client.get_blob_client(
            f"{AZURE_BLOB_CONTAINER_PATH}/{doc_file.name}")

        with open(doc_file, "rb") as data:
            blob_client.upload_blob(
                data, metadata=metadata, overwrite=True)


if __name__=="__main__":
    sys.exit(main())

ソースのリファクタリング

Claude Codeを使用して、ソースコードをリファクタリングしてもらいます。

Claude Codeのビュー下部を見ると「main.py」と表示されています。
デフォルトでは、現在前面に表示されているソースファイルについて質問や操作ができるようです。

今回はmain.pyをリファクタリングするため、プロンプトに「このファイルをリファクタリングしてください。」と入力し、[↑]ボタンをクリックします。

main.pyに書き込みを行ってよいか確認されるので、[Yes]を選択します。

するとmain.pyがリファクタリングされ、変更内容がClaude Codeのビューに表示されます。

リファクタリングされたソースは以下の通りです。

import sys
from pathlib import Path
from typing import Dict
import base64

from azure.storage.blob import BlobServiceClient

AZURE_BLOB_CONNECTION_STRING = "【ストレージアカウント接続文字列】"
AZURE_BLOB_CONTAINER_NAME = "bs-doc"
AZURE_BLOB_CONTAINER_PATH = "document"

DOC_FILE_PATH = "D:\\work\\ブログ\\Azure AI\\ドキュメント\\"
DOC_CATEGORY = "モカ"


def _encode(value: str) -> str:
    return base64.b64encode(value.encode("utf-8")).decode("utf-8")


def _build_metadata(doc_file: Path, category: str) -> Dict[str, str]:
    return {
        "filepath": _encode(str(doc_file.resolve())),
        "category": _encode(category),
    }


def upload_documents(container_client, doc_file_path: str, category: str) -> None:
    for doc_file in Path(doc_file_path).glob("**/*"):
        if not doc_file.is_file():
            continue

        metadata = _build_metadata(doc_file, category)
        blob_client = container_client.get_blob_client(
            f"{AZURE_BLOB_CONTAINER_PATH}/{doc_file.name}")

        with open(doc_file, "rb") as data:
            blob_client.upload_blob(data, metadata=metadata, overwrite=True)


def main():
    blob_service_client = BlobServiceClient.from_connection_string(
        AZURE_BLOB_CONNECTION_STRING)
    container_client = blob_service_client.get_container_client(
        AZURE_BLOB_CONTAINER_NAME)

    upload_documents(container_client, DOC_FILE_PATH, DOC_CATEGORY)


if __name__ == "__main__":
    sys.exit(main())

ソースへのコメント付与

Claude Codeにソースコードへコメントを付与してもらいます。

プロンプトに「ソース内にコメントを付与してください。」と入力し、[↑]ボタンをクリックします。

main.pyに書き込みを行ってよいか確認されるので、[Yes]を選択します。

するとmain.pyにコメントが付与され、その内容がClaude Codeのビューに表示されます。

コメントが付与されたソースコードは以下の通りです。

import sys
from pathlib import Path
from typing import Dict
import base64

from azure.storage.blob import BlobServiceClient

# Azure Blob Storage 接続設定
AZURE_BLOB_CONNECTION_STRING = "【ストレージアカウント接続文字列】"
AZURE_BLOB_CONTAINER_NAME = "bs-doc"
AZURE_BLOB_CONTAINER_PATH = "document"  # コンテナ内のアップロード先パス

# アップロード対象ドキュメントの設定
DOC_FILE_PATH = "D:\\work\\ブログ\\Azure AI\\ドキュメント\\"
DOC_CATEGORY = "モカ"


def _encode(value: str) -> str:
    """文字列をUTF-8でBase64エンコードして返す。"""
    return base64.b64encode(value.encode("utf-8")).decode("utf-8")


def _build_metadata(doc_file: Path, category: str) -> Dict[str, str]:
    """Blobに付与するメタデータ辞書を生成する。

    メタデータの値はAzureの制約に対応するためBase64エンコードする。
    """
    return {
        "filepath": _encode(str(doc_file.resolve())),
        "category": _encode(category),
    }


def upload_documents(container_client, doc_file_path: str, category: str) -> None:
    """指定ディレクトリ配下のファイルをAzure Blob Storageにアップロードする。

    サブディレクトリも再帰的に走査し、ファイルのみを対象とする。
    既存のBlobは上書きする。
    """
    for doc_file in Path(doc_file_path).glob("**/*"):
        if not doc_file.is_file():
            continue

        # Blobに付与するメタデータを生成
        metadata = _build_metadata(doc_file, category)

        # アップロード先のBlobクライアントを取得
        blob_client = container_client.get_blob_client(
            f"{AZURE_BLOB_CONTAINER_PATH}/{doc_file.name}")

        # ファイルをアップロード(既存のBlobは上書き)
        with open(doc_file, "rb") as data:
            blob_client.upload_blob(data, metadata=metadata, overwrite=True)


def main():
    # BlobServiceClientを接続文字列から生成
    blob_service_client = BlobServiceClient.from_connection_string(
        AZURE_BLOB_CONNECTION_STRING)

    # 対象コンテナのクライアントを取得
    container_client = blob_service_client.get_container_client(
        AZURE_BLOB_CONTAINER_NAME)

    upload_documents(container_client, DOC_FILE_PATH, DOC_CATEGORY)


if __name__ == "__main__":
    sys.exit(main())

テストコードの作成

Claude Codeにテスト用のコードを作成してもらいます。

プロンプトに「テスト用のソースを書いてください。」と入力し、[↑]ボタンをクリックします。

テスト用のコードが作成され、書き込みを行ってよいか確認されるので、[Yes]を選択します。

main.pyと同じフォルダ内に、テストコードtest_main.pyが作成されました。

Claude Codeによるコメントは以下の通りです。

作成されたテスト用コードは以下の通りです。

import base64
from pathlib import Path
from unittest.mock import MagicMock, patch, mock_open

import pytest

from main import _encode, _build_metadata, upload_documents


class TestEncode:
    def test_ascii_string(self):
        """ASCII文字列が正しくBase64エンコードされること。"""
        result = _encode("hello")
        assert result == base64.b64encode("hello".encode("utf-8")).decode("utf-8")

    def test_japanese_string(self):
        """日本語文字列が正しくBase64エンコードされること。"""
        result = _encode("モカ")
        assert result == base64.b64encode("モカ".encode("utf-8")).decode("utf-8")

    def test_empty_string(self):
        """空文字列が正しくBase64エンコードされること。"""
        result = _encode("")
        assert result == base64.b64encode(b"").decode("utf-8")

    def test_returns_str(self):
        """戻り値がstr型であること。"""
        assert isinstance(_encode("test"), str)


class TestBuildMetadata:
    def test_keys(self, tmp_path):
        """メタデータに filepath と category キーが存在すること。"""
        doc_file = tmp_path / "sample.pdf"
        doc_file.touch()
        metadata = _build_metadata(doc_file, "テスト")
        assert "filepath" in metadata
        assert "category" in metadata

    def test_filepath_is_encoded(self, tmp_path):
        """filepath の値がBase64エンコードされた絶対パスであること。"""
        doc_file = tmp_path / "sample.pdf"
        doc_file.touch()
        metadata = _build_metadata(doc_file, "テスト")
        expected = base64.b64encode(
            str(doc_file.resolve()).encode("utf-8")).decode("utf-8")
        assert metadata["filepath"] == expected

    def test_category_is_encoded(self, tmp_path):
        """category の値がBase64エンコードされた文字列であること。"""
        doc_file = tmp_path / "sample.pdf"
        doc_file.touch()
        metadata = _build_metadata(doc_file, "モカ")
        expected = base64.b64encode("モカ".encode("utf-8")).decode("utf-8")
        assert metadata["category"] == expected


class TestUploadDocuments:
    def test_uploads_files(self, tmp_path):
        """ディレクトリ内のファイルがアップロードされること。"""
        (tmp_path / "file1.txt").write_bytes(b"data1")
        (tmp_path / "file2.txt").write_bytes(b"data2")

        container_client = MagicMock()
        blob_client = MagicMock()
        container_client.get_blob_client.return_value = blob_client

        upload_documents(container_client, str(tmp_path), "カテゴリ")

        assert container_client.get_blob_client.call_count == 2
        assert blob_client.upload_blob.call_count == 2

    def test_skips_directories(self, tmp_path):
        """サブディレクトリはスキップされ、ファイルのみがアップロードされること。"""
        (tmp_path / "subdir").mkdir()
        (tmp_path / "file.txt").write_bytes(b"data")

        container_client = MagicMock()
        blob_client = MagicMock()
        container_client.get_blob_client.return_value = blob_client

        upload_documents(container_client, str(tmp_path), "カテゴリ")

        assert blob_client.upload_blob.call_count == 1

    def test_uploads_files_in_subdirectory(self, tmp_path):
        """サブディレクトリ内のファイルも再帰的にアップロードされること。"""
        subdir = tmp_path / "subdir"
        subdir.mkdir()
        (subdir / "nested.txt").write_bytes(b"nested data")

        container_client = MagicMock()
        blob_client = MagicMock()
        container_client.get_blob_client.return_value = blob_client

        upload_documents(container_client, str(tmp_path), "カテゴリ")

        assert blob_client.upload_blob.call_count == 1

    def test_upload_with_overwrite(self, tmp_path):
        """upload_blob が overwrite=True で呼ばれること。"""
        (tmp_path / "file.txt").write_bytes(b"data")

        container_client = MagicMock()
        blob_client = MagicMock()
        container_client.get_blob_client.return_value = blob_client

        upload_documents(container_client, str(tmp_path), "カテゴリ")

        _, kwargs = blob_client.upload_blob.call_args
        assert kwargs.get("overwrite") is True

    def test_upload_metadata_passed(self, tmp_path):
        """upload_blob にメタデータが渡されること。"""
        (tmp_path / "file.txt").write_bytes(b"data")

        container_client = MagicMock()
        blob_client = MagicMock()
        container_client.get_blob_client.return_value = blob_client

        upload_documents(container_client, str(tmp_path), "モカ")

        _, kwargs = blob_client.upload_blob.call_args
        metadata = kwargs.get("metadata")
        assert "filepath" in metadata
        assert "category" in metadata

    def test_empty_directory(self, tmp_path):
        """空ディレクトリの場合、アップロードが呼ばれないこと。"""
        container_client = MagicMock()
        blob_client = MagicMock()
        container_client.get_blob_client.return_value = blob_client

        upload_documents(container_client, str(tmp_path), "カテゴリ")

        blob_client.upload_blob.assert_not_called()

まとめ

操作が少し面倒なのではないかと思っていましたが、簡単なプロンプトで即座に実行できる点に驚きました。

ただし、生成AIによる結果が必ずしも正しいとは限りません。
それぞれの作業について、現行ソースと生成結果を比較し、問題ないことを確認する必要があると感じました。

リファクタリングについては、開発チーム内でのルールに沿わない修正が行われる可能性もあります。
そのため、プロンプトで前提条件や制限事項を伝えておくことが重要だと思います。

また、今回リファクタリングを実行した際に、既存のコメントが削除されてしまいました。
そのため、事前にソースをcommitしておくなど、履歴管理を行っておくことも重要です。

コメント付与については、コードの動作に関する説明は生成できますが、仕様上の注意点などは自分で追記する必要があります。

テストコードについても、生成された結果に加えて、業務上確認すべき内容のテストも別途用意する必要があります。

生成AIの結果をうのみにするのではなく、開発者をサポートするツールとして活用する意識が大事だと感じました。

とはいえ、非常に便利なツールだと思います。

既存コードの理解や改善の補助として、今後の開発でも活用できそうです。

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