🎯

Claude Code中心の開発のためのPythonテンプレートの設計

に公開

はじめに

こんにちは。動詞です。

最近、Claude Codeを使った開発に重点を置いたPythonのプロジェクトテンプレートを作成しました。

しばらく運用しながら改善を重ねてきましたが、そろそろ使えるレベルになってきたので、覚書も兼ねてその設計について説明しようと思います。

https://github.com/discus0434/python-template-for-claude-code

Claude Code向けの設計

このテンプレートを作る上で意識したのは、Claude Codeが指示しなくても思い通りに動いてくれること、変な動きをしないことです。
そのために今回整備したものは、ざっくりと以下の2つに分けられます。

  • 標準的な実装ルールの提示と支援ツールの導入
  • デバッグのループを回しやすい仕組み

そして、これらをうまくClaude Codeに伝え、活用してくれるように、以下のような工夫をしました。

  • 実装ルールを提示するだけではなく、モデルケースをリポジトリ内に配置し、適宜参照できるようにする
  • 使って欲しい道具を使いやすくし、使うための豊富な例やドキュメントを整備する

標準的な実装ルールの提示と支援ツールの導入

Claude Codeが一貫したルールに従った実装を生成できるようにするために、まずは標準的な実装とは何かをしっかりと説明した上で、プロジェクトに静的解析ツールを用いたガードレールを設置しました。

まずは実装ルールの提示です。

命名規則・ディレクトリ構造

プロジェクトは、以下のようなディレクトリ構造を基本に組み立てるように指示しています。

src/
├── project_name/         # メインパッケージ
│   ├── core/             # コアロジック
│   ├── utils/            # ユーティリティ
│   ├── __init__.py
│   └── types.py          # 型ヒントを集約
├── tests/                # テストコード
│   ├── unit/             # 単体テスト
│   ├── property/         # プロパティベーステスト
│   ├── integration/      # 統合テスト
│   └── conftest.py   # pytest設定
...

特にtypes.pyを独立して設置しておくことで、型ヒントを重視すべきことを明確にしています。

命名規則についても、以下のように定義しています。

- **命名規則**:
  - クラス: PascalCase
  - 関数/変数: snake_case
  - 定数: UPPER_SNAKE_CASE
  - プライベート: 先頭に `_`

型ヒント

型ヒントについては、Python3.10以降機能が充実してきているTypedDictやPython3.12で導入されたtype構文を積極的に活用するようにしています。

Pythonは3.8以前と3.9以降、3.12以降で型ヒントのスタイルが大きく変わってきますが、特に指示しない場合LLMは3.8以前と3.9以降の構文をごちゃまぜで使用してきます。

そして、ruffの静的解析で3.8以前の構文はエラーになってしまい修正が面倒なので、3.12以降のモダンな構文の使用を強制させ、スタイルが混ざらないようにすることを狙っています。

支援ツールの導入

pyright

pyrightはbasicモードで動作します。以前は厳格であるほどよいと思っていたのでmypyのstrictモードを採用していましたが、実際にはClaude Codeがどハマりしてしまうことが多かったのである程度緩めました。

[tool.pyright]
include = ["src", "tests"]
exclude = [".venv", "**/__pycache__"]
pythonVersion = "3.12"
pythonPlatform = "All"
typeCheckingMode = "standard"

# Type checking behavior
strictParameterNoneValue = false

# 重要なエラー(必ずエラーとして報告)
reportMissingImports = "error"
reportIncompatibleMethodOverride = "error"
reportIncompatibleVariableOverride = "error"
reportOverlappingOverload = "error"
reportUnusedCoroutine = "error"
reportMatchNotExhaustive = "error"
reportUnusedImport = "error"
reportDuplicateImport = "error"

# 開発時に役立つ警告レベル
reportUnusedClass = "warning"
reportUnusedFunction = "warning"
reportUnusedVariable = "warning"
reportInconsistentConstructor = "warning"
reportUninitializedInstanceVariable = "warning"
reportUnnecessaryIsInstance = "warning"
reportUnnecessaryCast = "warning"
reportUnnecessaryComparison = "warning"
reportUnnecessaryTypeIgnoreComment = "warning"

# 開発を妨げない程度に無効化
reportMissingTypeStubs = "none"
reportMissingSuperCall = "none"
reportPrivateUsage = "none"
reportImplicitStringConcatenation = "none"
reportInvalidStubStatement = "none"
reportIncompleteStub = "none"
reportUnsupportedDunderAll = "none"

ruff

ruffもpyrightと同様に、以前はコードの一貫性を保つために設定をかなり強めに設定していましたが、こちらも同様にClaude Codeがどハマりしてしまうことが多かったのである程度緩めました。

[tool.ruff.lint]
exclude = [".venv"]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # pyflakes
    "I",      # isort
    "B",      # flake8-bugbear
    "SIM",    # flake8-simplify
    "RUF",    # Ruff-specific rules
    "TCH",    # flake8-type-checking
    "PTH",    # flake8-use-pathlib
    "PL",     # Pylint
]
ignore = [
    "B007",
    "C401",
    "C408",
    "F821",
    "F841",
    "G004",
    "T201",
    "ANN101",
    "ANN102",
    "RUF001",
    "RUF002",
    "RUF003",
    "RUF012",
    "PTH123",
    "PERF203",
    "PERF401",
    "PLC0206",
    "PLC2401",
    "PLR0911",
    "PLR0912",
    "PLR0913",
    "PLR0915",
    "PLR2004",
    "PLW2901",
    "SIM108",
]

pre-commit

コミット時や、pre-commitフックを呼び出して実行した際に、以下のようなチェックを一括で行うようにしています。

  • ruffによるフォーマット・リント
  • pyrightによる型チェック
  • pip-auditとbanditによるセキュリティスキャン
  • pytestによる自動テスト実行

この呼び出しは関数実装時などのたびに行うよう指示しているので、Claude Codeが生成したコードが常に一定の品質基準を満たすことをある程度保証できます。

Makefile

Claude Codeが実行するコマンドに迷わなくてもいいように、ruff、pyright、pytest、GitHub CLIのような、使って欲しいコマンドをMakefileにまとめています。

format:
	uv run ruff format . --config=pyproject.toml

lint:
	uv run ruff check . --fix --config=pyproject.toml

typecheck:
	uv run pyright

test:
	uv run pytest

check: format lint typecheck test

check-all:
	uv run pre-commit run --all-files

pr:
    gh pr create --title "$(TITLE)" --body "$(BODY)" --label "$(LABEL)"

また、make helpで、Makefileのコマンド一覧を表示できるようにしています。

これは普通に人間が使う上で便利なので、当然Claude Codeにとっても良い感じだと思います。多分。

デバッグの支援

コーディング支援エーアイを使っていて個人的に一番ストレスが溜まるのは、生成されたコードで問題が発生した時、エーアイが原因を修正できずに見当外れなデバッグのループを回し続け、コードをグチャグチャに破壊していく無間地獄に陥ることです。

Claude Codeは設計が良いのか、それが比較的解消されているように見えますが、全くそうならないというわけでもないので、Claude Code自身が素早く原因を特定・修正できるよう支援してあげる仕組みが重要です。

そのために、きめ細やかなログを標準的に実装させたり、人間にとっては多少やり過ぎな多重のテストを書かせたりして、デバッグのループを回しやすい仕組みを整備しています。

ログ

要件として、すべてのコードに実行時のログの出力を実装することを必須としています。

これは、Claude Codeがバグの原因を特定する際、原因がありそうな処理周辺にロガーをアドホックに導入してからデバッグを開始する挙動がよく見られるためです。

細かいログ出力を最初から体系的に実装させることで、実行 → 動作確認のイテレーションを1〜2回減らすことが狙いです。

なお、ログ出力が多すぎてトークン消費量が激増することを防ぐため、詳細なログ出力はDEBUGレベルでのみ行うようにしています。

テスト

テスト戦略は3層構造になっています。

  1. 単体テスト: 基本的な機能テスト
  2. プロパティベーステスト: Hypothesisを使用した網羅的なテスト
  3. 統合テスト: コンポーネント間の連携テスト

特にプロパティベーステストは、Claude Codeが横着をしてテストを甘めに書くことを防ぐことを期待しています。

# template/tests/property/test_helpers_property.py から
class TestTypeConversionsProperty:
    """Property-based tests for type conversions and edge cases."""

    @given(
        size=st.integers(min_value=0, max_value=1000),
        chunk_size=st.integers(min_value=1, max_value=100),
    )
    def test_プロパティ_様々なリストサイズで正しく動作(
        self,
        size: int,
        chunk_size: int,
    ) -> None:
        """様々なサイズのリストで chunk_list が正しく動作することを検証。"""
        items = list(range(size))
        chunks = chunk_list(items, chunk_size)

        # 結果の検証
        if size == 0:
            assert chunks == []
        else:
            assert len(chunks) == (size + chunk_size - 1) // chunk_size

            # 全要素が含まれているか
            flattened = [item for chunk in chunks for item in chunk]
            assert flattened == items

Hypothesisが様々なパターンのテストデータを自動生成してテストを実行するので、完全ではないにせよ、Claude Codeが安易なハードコーディングでテストを乗り切ろうとする振る舞いを対策することができます。

モデルケースの提供

ここまでの指示はCLAUDE.mdの文章や静的解析・テスト設計により抽象的に与えていますが、どうしてもこれだけでは伝わりきっていない実装のパターンがあります(ログの出力レベルがどこまでDEBUGでどこらへんからINFOなのかとか)。また、文章での指示だけでは必ずしもルールを守ってくれない場合もあります。

そこで、より一層強固に細かい実装の指示を伝達するために、モデルケースとしてサンプルパッケージの実装とテスト例を配置しました。

以下がモデルケースのディレクトリ構造です。

template/
├── src/template_package/     # サンプルのパッケージ
│   ├── types.py              # 型定義の例
│   ├── core/example.py       # クラス・関数実装の例
│   └── utils/
│       ├── helpers.py        # ユーティリティ関数の例
│       ├── logging_config.py # ログ設定の例
│       └── profiling.py      # パフォーマンス測定の例
└── tests/                    # テストコードの例

以下にモデルケースのコード例を少し紹介します。

ログ実装の例

カスタムのstructlogを使用したロガーを実装し、DEBUGモードではかなり詳細なログが出力されるようにしています。

# template/src/template_package/core/example.py から
def process_data(
    data: list[ItemDict],
    processor: DataProcessor,
    *,
    validate: bool = True,
) -> list[ItemDict]:
    """Process data using a processor.

    ...(省略)
    """
    logger.debug(
        "Processing data",
        item_count=len(data),
        validation_enabled=validate,
        processor_type=processor.__class__.__name__,
    )

    if validate and not data:
        logger.error(
            "Data validation failed",
            reason="empty data",
            validation_enabled=True,
        )
        raise ValueError("Data cannot be empty")

    logger.debug(
        "Calling processor",
        processor_class=processor.__class__.__name__,
        input_count=len(data),
    )
    result = processor.process(data)
    logger.info(
        "Data processing completed",
        input_count=len(data),
        output_count=len(result),
        items_modified=len(result) - len(data) if len(result) != len(data) else 0,
        processing_status="success",
    )

    return result

型ヒントの実装例

上でも述べたように、型ヒントはできる限りモダンな構文を使用することで、Python3.8以前の構文が混入することを防ぎます。

# template/src/template_package/types.py から

# Status types
type ProcessorStatus = Literal["success", "error", "pending"]
type ValidationStatus = Literal["valid", "invalid", "skipped"]


# Common data structures
class ItemDict(TypedDict):
    """Typed dictionary for item data."""

    id: int
    name: str
    value: int


class ItemDictWithStatus(ItemDict):
    """Extended item dictionary with status."""

    status: ProcessorStatus
    processed: bool

そして、このような実装を以下のようにCLAUDE.mdにて参照するよう促します。また、念のために参照用として編集しないように伝えます。

## `template/`ディレクトリにあるモデルケースの参照

@template/ ディレクトリには、Python開発のベストプラクティスを示すモデルコードが含まれています。実装時の参考として積極的に活用してください。

### モデルコード参照の推奨場面

1. **新しいクラスや関数を実装する際**
   - @template/src/project_name/core/example.py で適切な型ヒント、docstring、エラーハンドリングを確認
   - @template/src/project_name/types.py で型定義のパターンを確認

2. **ユーティリティ関数を作成する際**
   - @template/src/project_name/utils/helpers.py で関数の構造、エラー処理、ロギングを確認

3. **テストを書く際**
   - @template/tests/unit/ で単体テストの書き方を確認
   - @template/tests/property/ でプロパティベーステストの例を確認
   - @template/tests/conftest.py でフィクスチャの実装例を確認

4. **ロギングを実装する際**
   - @template/src/project_name/utils/logging_config.py で設定例を確認
   - 各モジュールでのロガー使用例を確認

新しいコードを書く際は、まず`template/`内の類似例を確認し、パターンを踏襲し、プロジェクト固有の要件に合わせて調整してください。
`template/`は削除せず、常に参照可能な状態を維持します。

トークン消費量は確実に増加しますが、より具体的に実装ルールを守ってくれるという意味でも、Few-shot Learning的な文脈でも効果が出ていることが実感できています。

その他の工夫

ここまで挙げていた以外でも、Claude Codeがうまく動作するような仕組みを整備しています。

主にドキュメントの整理方法や自動更新の仕組みですが、このあたりはまだ模索中の部分も多いためサラッと紹介します。

専門ガイドのモジュール化

CLAUDE.mdには全てのタスクに共通するガイドラインを記載していますが、その時々によって読み込む必要性が変わってくるドキュメントに関しては、docs/ディレクトリに配置し、CLAUDE.mdから動的に読み込むようにしています。

#### 機械学習プロジェクト
機械学習プロジェクトの場合、@docs/ml-project-guide.md を参照してください。

#### バックエンドAPI プロジェクト
FastAPI を使用したバックエンドプロジェクトの場合、@docs/backend-project-guide.md を参照してください。

こうすることによって無駄なトークン消費量を減らし、性能が低下しないようにすることを期待しています。

余談ですが、最近Claude Codeはドキュメントの参照を@path/to/fileのように記述するそうです。

https://docs.anthropic.com/en/docs/claude-code/memory#claude-md-imports

CLAUDE.mdを自動更新するような仕組み(WIP)

Claude Codeの意味不明な挙動は結構な割合でCLAUDE.mdのdeprecatedな記述に起因する気がしているので、自分はClaude Codeで開発をするときかなりの時間をCLAUDE.mdの盆栽に費やしています。

ここも将来的には自動化したいところですが、まだ模索中です。現在は以下のようなトリガーでCLAUDE.mdを更新するよう指示を入れています。

  • 仕様の追加・変更があった場合
  • 新しい依存関係の追加時
  • 同じ質問が2回以上発生した場合
  • 新しいエラーパターンを2回以上確認した場合

人間のユーザビリティについて

Claude Codeのためのテンプレートといっても、最終的には人間の開発者が使うものなので、その点についてもある程度の工夫をしています。

セットアップの自動化

自分はリモートサーバーで開発をすることがほとんどで、一から環境をセットアップする機会が多いので、以下のようなコマンドを用意しています。

make setup

このコマンド一つで、このテンプレートでの開発に使用するツールのセットアップが自動で完了します。

  • プロジェクト名の更新
  • Python環境の構築
  • Claude Codeのインストール
  • GitHub CLIの設定
  • 依存関係のインストール
  • pre-commitフックの設定

チートシート

README.mdには、Claude Codeの全機能を活用するためのチートシートが含まれています。

#### REPLモード内スラッシュコマンド
/memory                        # メモリ管理・プロジェクト記憶の表示/編集
#                              # CLAUDE.mdへのクイックアクセス(行頭で#)
/add-dir <path>                # 作業ディレクトリを追加

#### 使用例
claude --model opus --verbose --add-dir ./src --add-dir ./tests "プロジェクト全体をレビューして"

Claude Codeのコマンドは結構忘れるので、普通に便利です。

おわりに

もし使ってみて改善点やフィードバックがあれば、ぜひGitHub IssuesやXで教えてください。プルリクエストも大歓迎です。

特に自動更新周りは発展途上なので、これからも継続的にアップデートしていきます。

参考にした資料

Discussion