第5章:保守性・スケーラビリティを支える設計パターン
はじめに:vibe codingの「暗黒面」
vibe codingには、魅力的な「暗黒面」があります。
それは、動くコードが簡単に手に入るということです。Claudeに適当に指示を出せば、数分で動くものができてしまいます。これは素晴らしいことですが、同時に危険でもあります。
なぜ危険か?
動くコードは、必ずしも良いコードではないからです。保守しにくい、スケールしない、セキュリティホールがある、そんなコードでも「動く」ことは動きます。そして、問題は後になってから表面化します。
- 「機能追加しようとしたら、どこを変えればいいか分からない...」
- 「ユーザーが増えたら、急にアプリが遅くなった...」
- 「セキュリティ監査で大量の指摘を受けた...」
この章では、長期的に保守しやすく、スケーラブルなコードベースを維持するための設計パターンを学びます。vibe codingの力を、「使い捨てのプロトタイプ」ではなく「本番で長く運用できるプロダクト」に昇華させましょう。
Hooks機能:品質を自動で担保する
Hooksとは何か
Hooksは、Claude Codeの特定のイベントに対して自動的にスクリプトを実行する仕組みです。
人間の開発者なら、「ファイルを保存したらlintを実行する」「コミット前にテストを走らせる」といった習慣を持っているでしょう。しかし、Claudeは指示しなければこれらを忘れることがあります。
Hooksを設定すれば、Claudeが何かをするたびに、自動的に品質チェックが走るようになります。これにより、「Claudeが変なコードを書いてしまった」という問題を早期に発見できます。
Hooksの種類
Claude Codeには、以下のイベントでHooksを発動させられます:
| イベント | タイミング | 主な用途 |
|---|---|---|
SessionStart |
セッション開始時 | 環境チェック、ブランチ確認 |
PreToolUse |
ツール実行前 | 危険なコマンドのブロック |
PostToolUse |
ツール実行後 | lint、フォーマット、テスト |
Notification |
通知発生時 | 外部サービスへの通知 |
Hooksの設定方法
Hooksは.claude/settings.jsonに記述します:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/check-branch.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint:fix"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-dangerous-commands.sh"
}
]
}
]
}
}
実践的なHooks設定例
1. セッション開始時のブランチチェック
mainブランチやdevelopブランチで直接作業を始めてしまうのを防ぎます。
.claude/hooks/check-branch.sh:
#!/bin/bash
CURRENT_BRANCH=$(git branch --show-current)
PROTECTED_BRANCHES=("main" "develop" "master")
for branch in "${PROTECTED_BRANCHES[@]}"; do
if [ "$CURRENT_BRANCH" == "$branch" ]; then
echo "⚠️ WARNING: You are on the '$branch' branch."
echo "Please create a feature branch before making changes."
echo ""
echo "Run: git checkout -b feature/your-feature-name"
exit 1
fi
done
echo "✅ Branch check passed: $CURRENT_BRANCH"
2. ファイル編集後の自動lint
Claudeがファイルを編集するたびに、自動でlintとフォーマットを実行します。
.claude/settings.json(該当部分):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint:fix && npm run format"
}
]
}
]
}
}
これにより、Claudeが書いたコードは常にプロジェクトのスタイルガイドに準拠します。
3. 危険なコマンドのブロック
rm -rf /やgit push --force mainといった、取り返しのつかないコマンドを実行前にブロックします。
.claude/hooks/block-dangerous-commands.sh:
#!/bin/bash
# 環境変数からコマンドを取得(Claude Codeが設定)
COMMAND="$CLAUDE_TOOL_INPUT"
# 危険なパターン
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf /*"
"git push --force main"
"git push -f main"
"git push --force origin main"
"DROP DATABASE"
"DROP TABLE"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
echo "🚫 BLOCKED: Dangerous command detected"
echo "Command: $COMMAND"
echo "Pattern: $pattern"
echo ""
echo "This command has been blocked for safety."
exit 1
fi
done
echo "✅ Command safety check passed"
4. テスト実行の自動化
重要なファイルが変更されたとき、自動でテストを実行します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/run-affected-tests.sh"
}
]
}
]
}
}
.claude/hooks/run-affected-tests.sh:
#!/bin/bash
# 変更されたファイルに関連するテストを実行
CHANGED_FILE="$CLAUDE_FILE_PATH"
# srcディレクトリのファイルが変更された場合のみテスト実行
if [[ "$CHANGED_FILE" == src/* ]]; then
echo "🧪 Running tests for changed file..."
# Jest の --findRelatedTests オプションを使用
npm test -- --findRelatedTests "$CHANGED_FILE" --passWithNoTests
if [ $? -ne 0 ]; then
echo "❌ Tests failed! Please fix before continuing."
exit 1
fi
echo "✅ Tests passed"
fi
おすすめの自動許可コマンド設定
なぜ自動許可が必要か
Claude Codeは、ファイルの編集やコマンドの実行など、さまざまなツールを使用します。デフォルトでは、これらのツールを使用するたびにユーザーの許可を求めます。
これは安全性のためですが、開発中に毎回許可を出すのは煩雑です。特に信頼できる操作については、自動で許可するよう設定することで、開発フローがスムーズになります。
推奨する自動許可ツール
以下のツールは、コードベースを破壊するリスクが低く、自動許可しても安全です:
| ツール | 説明 | リスク |
|---|---|---|
Write |
ファイルの新規作成 | 低(Gitで追跡可能) |
Edit |
既存ファイルの編集 | 低(Gitで差分確認可能) |
WebSearch |
Web検索 | 極低(読み取り専用) |
WebFetch |
URLからコンテンツ取得 | 極低(読み取り専用) |
Task |
サブエージェントの起動 | 低(並列調査に便利) |
Skill |
スキルの実行 | 低(定義済み処理の実行) |
なぜ自動許可しても安全なのか
WriteやEditを自動許可することに不安を感じるかもしれません。しかし、Gitでプロジェクトを管理している前提であれば、これらの操作は安全です。
Gitが提供するセーフティネット:
- すべての変更が追跡される: Claudeがどのファイルを作成・編集したかは
git statusで一目瞭然 - 差分を確認できる:
git diffで変更内容を詳細に確認してからコミット - いつでも元に戻せる: 問題があれば
git checkoutやgit restoreで即座にロールバック - 変更履歴が残る: 「いつ、何を変更したか」がコミットログに記録される
つまり、Gitで管理している限り、Claudeがファイルを編集しても取り返しのつかない状態にはなりません。
逆に言えば、Gitで管理していないプロジェクトでは自動許可は危険です。必ずGitリポジトリ内で作業しましょう。第3章で解説した「Gitでの管理の重要性」も参照してください。
設定方法
.claude/settings.jsonに以下を追加します:
{
"permissions": {
"allow": [
"Write",
"Edit",
"WebSearch",
"WebFetch",
"Task",
"Skill"
]
}
}
自動許可すべきでないツール
一方、以下のツールは慎重に扱うべきです:
| ツール | 理由 |
|---|---|
Bash |
任意のシェルコマンドを実行できるため、意図しない変更が起きる可能性 |
Bashについては、Hooksの「危険なコマンドのブロック」と組み合わせるか、実行内容を都度確認する運用がおすすめです。
条件付きルール:コンテキストに応じた指示
なぜ条件付きルールが必要か
プロジェクトが大きくなると、ディレクトリごとに異なるルールが必要になることがあります。
たとえば:
src/api/ではセキュリティを特に厳しくチェックしたいsrc/components/ui/ではアクセシビリティを重視したいtests/ではテストのベストプラクティスに従いたい
これらのルールをすべてCLAUDE.mdに書くと、関係ないファイルを編集するときにもノイズになります。
条件付きルールは、特定のファイルやディレクトリにのみ適用されるルールを定義する仕組みです。
条件付きルールの設定
.claude/rules/ディレクトリにMarkdownファイルを配置します。各ファイルのFrontmatterで、適用対象をglob パターンで指定します。
例1:API セキュリティルール
.claude/rules/api/security.md:
---
globs: ["src/app/api/**/*.ts", "src/routes/**/*.ts"]
---
# API Security Rules
このディレクトリのコードを書くときは、以下のルールに**必ず**従うこと。
## 認証チェック
- すべてのエンドポイントで認証状態を確認する
- 認証不要のエンドポイントは明示的にコメントで理由を記載
## 入力バリデーション
- すべてのリクエストボディをZodスキーマで検証
- パスパラメータ、クエリパラメータも検証する
- バリデーションエラーは400を返す
## エラーハンドリング
- 内部エラーの詳細をレスポンスに含めない
- スタックトレースは本番環境で非表示
## レート制限
- 認証エンドポイントには厳しいレート制限
- 一般的なエンドポイントにも適度な制限
## SQLインジェクション対策
- 直接SQLクエリを書かない
- 必ずORMまたはプリペアドステートメントを使用
例2:UIコンポーネントルール
.claude/rules/ui/accessibility.md:
---
globs: ["src/components/**/*.tsx", "src/app/**/*.tsx"]
---
# UI Accessibility Rules
## 基本原則
- すべてのインタラクティブ要素にaria-label または適切なラベルを付ける
- キーボードナビゲーションを考慮する
- 色だけで情報を伝えない
## 画像
- すべての`<img>`に意味のあるalt属性を付ける
- 装飾的な画像は`alt=""`を使用
## フォーム
- `<label>`と`<input>`を正しく関連付ける
- エラーメッセージは視覚的にもスクリーンリーダーにも伝わるように
## フォーカス管理
- フォーカス可能な要素には視覚的なフォーカスインジケーターを表示
- モーダルを開いたらフォーカスをモーダル内に移動
例3:テストコードルール
.claude/rules/tests/best-practices.md:
---
globs: ["tests/**/*.test.ts", "**/*.spec.ts", "**/*.test.tsx"]
---
# Testing Best Practices
## テストの構造
- Arrange-Act-Assert パターンに従う
- 1つのテストでは1つのことだけを検証
## 命名規則
- テスト名は「〜すべき」という形式で書く
- 例: "should return user when valid ID is provided"
## モック
- 外部APIは必ずモック
- データベースはテスト用のインメモリDBまたはモック
- 時間に依存するテストは時間をモック
## 非同期テスト
- async/await を使用
- タイムアウトを適切に設定
- フレイキーテスト(不安定なテスト)を避ける
## カバレッジ
- 新機能は80%以上のカバレッジを目指す
- エッジケースのテストを忘れない
マルチClaude並列ワークフロー
並列開発の必要性
プロジェクトが大きくなると、複数の機能を同時に開発したい場面が出てきます。
従来の開発なら、複数のエンジニアが別々のブランチで作業します。vibe codingでも、複数のClaude Codeセッションで並行作業ができれば、開発速度は大幅に向上します。
しかし、単純に複数のターミナルでClaude Codeを起動すると、同じファイルを編集してコンフリクトが発生する可能性があります。
Git Worktreesを使った並列開発
Git Worktreesは、1つのリポジトリに複数の作業ディレクトリを持つGitの機能です。これを使えば、同じリポジトリの異なるブランチを、別々のディレクトリで同時に開けます。
セットアップ手順:
# 元のリポジトリ(main作業用)
cd ~/projects/my-app
# 機能Aの作業用ワークツリーを作成
git worktree add ../my-app-feature-a -b feature/auth
# 機能Bの作業用ワークツリーを作成
git worktree add ../my-app-feature-b -b feature/payments
これで、以下のようなディレクトリ構成になります:
~/projects/
├── my-app/ # メインリポジトリ(main ブランチ)
├── my-app-feature-a/ # 認証機能開発用(feature/auth ブランチ)
└── my-app-feature-b/ # 決済機能開発用(feature/payments ブランチ)
各ワークツリーでClaude Codeを起動:
# ターミナル1
cd ~/projects/my-app-feature-a
claude
# ターミナル2(別ウィンドウ)
cd ~/projects/my-app-feature-b
claude
これで、2つのClaude Codeセッションが、それぞれ独立したブランチで作業できます。ファイルの衝突を心配する必要はありません。
並列開発のベストプラクティス
1. 機能の境界を明確にする
並列で開発する機能は、なるべく依存関係のない独立した機能を選びましょう。同じファイルを編集する必要がある機能を並列開発すると、マージ時にコンフリクトが発生します。
2. 共通コードは先に整備
複数の機能が使う共通コンポーネントやユーティリティは、先にmainブランチにマージしておきます。各機能ブランチはそれを使うだけにします。
3. 定期的にmainをマージ
長期間mainからブランチを切ったままにすると、マージが大変になります。数日おきにmainの変更を各機能ブランチにマージしましょう。
# 機能ブランチで
git fetch origin
git merge origin/main
4. ワークツリーのクリーンアップ
機能が完成してマージしたら、ワークツリーを削除します:
git worktree remove ../my-app-feature-a
git branch -d feature/auth
スケーラブルなコード構成
機能ベースのディレクトリ構造
プロジェクトが大きくなったとき、技術レイヤーごとの構成はスケールしません:
# 悪い例:技術レイヤーごとの構成
src/
├── components/ # すべてのコンポーネント
│ ├── Button.tsx
│ ├── Modal.tsx
│ ├── UserCard.tsx
│ ├── PaymentForm.tsx
│ └── ... (100個以上のファイル)
├── hooks/ # すべてのフック
├── services/ # すべてのサービス
└── utils/ # すべてのユーティリティ
この構成では、機能を追加・修正するたびに複数のディレクトリを行き来する必要があり、関連するコードがバラバラになります。
機能ベースの構成を採用しましょう:
# 良い例:機能ベースの構成
src/
├── features/
│ ├── auth/ # 認証機能
│ │ ├── components/
│ │ │ ├── LoginForm.tsx
│ │ │ └── SignupForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ ├── types.ts
│ │ └── index.ts # 公開APIのみエクスポート
│ │
│ ├── payments/ # 決済機能
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── index.ts
│ │
│ └── users/ # ユーザー管理機能
│ └── ...
│
├── shared/ # 共通コード
│ ├── components/ # 汎用UIコンポーネント
│ ├── hooks/ # 汎用フック
│ ├── utils/ # ユーティリティ
│ └── types/ # 共通型定義
│
└── app/ # ルーティング(Next.js App Router等)
この構成のメリット:
- コロケーション: 関連するコードが近くにある
- 独立性: 機能ごとに独立してテスト・デプロイできる
- スケーラビリティ: 新機能は新しいディレクトリを追加するだけ
- チーム分担: 機能ごとにチームを分けられる
公開APIの制御
機能ディレクトリのindex.tsでは、外部に公開するものだけをエクスポートします:
// src/features/auth/index.ts
// 公開するコンポーネント
export { LoginForm } from './components/LoginForm';
export { SignupForm } from './components/SignupForm';
// 公開するフック
export { useAuth } from './hooks/useAuth';
// 公開する型
export type { User, AuthState } from './types';
// 内部実装(authService等)はエクスポートしない
これにより、機能の内部実装を変更しても、公開APIが変わらなければ他の部分に影響しません。
Claudeへの構成の伝え方
この構成をClaudeに理解させるには、CLAUDE.mdに以下のように書きます:
## Architecture
### Directory Structure
機能ベースのディレクトリ構成を採用しています。
- `src/features/`: 機能ごとのモジュール
- 各機能は components/, hooks/, services/ を持つ
- `index.ts` で公開APIのみをエクスポート
- `src/shared/`: 複数機能で使う共通コード
- `src/app/`: ルーティング(Next.js App Router)
### Adding New Features
新しい機能を追加するときは:
1. `src/features/` に新しいディレクトリを作成
2. 必要なサブディレクトリ(components, hooks, services)を作成
3. `index.ts` で公開APIを定義
4. `docs/ARCHITECTURE.md` の機能一覧を更新
データベース設計の原則
マイグレーション管理
vibe codingでデータベースを扱うとき、マイグレーションの管理は特に重要です。
Claudeは指示されれば直接SQLを実行できますが、本番環境ではそれは危険です。すべてのスキーマ変更はマイグレーションファイルとして記録しましょう。
# CLAUDE.md に追加
## Database
### Migrations
データベーススキーマを変更するときは、必ずマイグレーションファイルを作成すること。
```bash
npm run db:migrate:create -- --name add_user_profile
直接SQLを実行してスキーマを変更してはいけない。
Schema Documentation
スキーマを変更したら docs/ARCHITECTURE.md のER図も更新すること。
### インデックス戦略
パフォーマンスの問題は、アプリケーションがスケールしてから表面化します。最初から適切なインデックスを設計しましょう。
```markdown
# SKILL: database-patterns
## Index Guidelines
### 必ずインデックスを張る場所
- 外部キー
- WHERE句で頻繁に使うカラム
- ORDER BY で使うカラム
- ユニーク制約のあるカラム
### インデックスを張りすぎない
- 更新が頻繁なテーブルはインデックスを最小限に
- カーディナリティ(値の種類)が低いカラムは効果が薄い
- 複合インデックスの順序に注意(左から使われる)
### 確認方法
```sql
EXPLAIN ANALYZE SELECT ...
---
## 長期セッションの落とし穴:忘却への対策
### Claude Codeの「記憶」の仕組み
Claude Codeとの会話が長くなると、セッション内でも**重要な文脈を忘れる**ことがあります。これはLLMの「コンテキストウィンドウ」という制約によるものです。
特に注意が必要なのが**Compact Conversation(会話の圧縮)**です。Claude Codeは、コンテキストウィンドウがいっぱいになると、古い会話を自動的に要約・圧縮します。この際、以下のような情報が失われる可能性があります:
- 設計上の重要な決定事項
- 「この方法は試したが失敗した」という試行錯誤の記録
- プロジェクト固有の制約や前提条件
- 途中で確認したファイルの詳細内容
### 忘却が引き起こす問題
**よくあるパターン:**
1. 「さっき説明した設計方針と違う実装をしている...」
2. 「失敗したアプローチを再び提案してきた...」
3. 「CLAUDE.mdに書いてあるルールを無視している...」
4. 「途中で合意した仕様と異なるものを作っている...」
これらは、長時間のセッションや複雑な作業で特に発生しやすくなります。
### 対策1:重要な決定はドキュメントに残す
会話の中で行った**重要な設計決定**は、すぐにドキュメントに書き出しましょう:
あなた:今の設計決定を docs/plans/current-feature.md に追記してくれ。 特に以下を記録して: - 採用した方針とその理由 - 不採用にした案とその理由 - 今後の注意点
会話が圧縮されても、ドキュメントには残ります。Claudeは必要に応じてそのファイルを読み直せます。
### 対策2:定期的にコンテキストを再確認させる
長いセッションでは、定期的にClaudeに現状を確認させます:
あなた:ここまでの作業内容を整理してくれ: 1. 完了したタスク 2. 現在取り組んでいるタスク 3. 守るべき制約(CLAUDE.mdの内容も再確認して)
これにより、Claudeは重要なファイルを読み直し、文脈を再構築できます。
### 対策3:セッションを適度に区切る
1つのセッションで大量の作業をするより、**機能単位やタスク単位でセッションを区切る**方が安全です。
新しいセッションを開始すると、ClaudeはCLAUDE.mdを最初から読み直します。圧縮された曖昧な記憶より、フレッシュな状態で始める方が確実です。
```bash
# 機能Aの実装完了 → コミット → セッション終了
# ↓
# 新しいセッション開始 → 機能Bの実装
対策4:LESSONS.mdの活用
第2章で紹介したdocs/LESSONS.mdは、忘却対策としても機能します。
セッション中に発生した問題や学びを即座に記録しておけば、次のセッション(または同じセッション内でも)でClaudeが参照できます:
あなた:今の問題と解決策を LESSONS.md に追記して。
次回同じ問題が起きたときの参考になるように。
忘却のサインを見逃さない
以下のような兆候が見られたら、忘却が起きている可能性があります:
- 同じ質問を繰り返し聞いてくる
- 以前と矛盾する提案をしてくる
- CLAUDE.mdのルールに違反する
- 完了済みのタスクを再度やろうとする
こうした場合は、「CLAUDE.mdを読み直して」「docs/plans/xxx.mdを確認して」と明示的に指示しましょう。
まとめ:品質は設計で決まる
この章で学んだパターンをまとめます:
1. Hooks による自動化
- セッション開始時のブランチチェック
- ファイル編集後の自動lint
- 危険なコマンドのブロック
- テストの自動実行
2. 条件付きルール
- ディレクトリごとに異なるルールを適用
- セキュリティ、アクセシビリティ、テストなど
3. 並列開発
- Git Worktreesで複数のClaude Codeを同時実行
- 独立した機能を並行開発
4. スケーラブルな構成
- 機能ベースのディレクトリ構造
- 公開APIの明示的な制御
- マイグレーション管理
5. 自動許可設定
- 安全なツール(Write, Edit, WebSearch等)を自動許可
- 危険なツール(Bash)は慎重に扱う
6. 長期セッションの管理
- 重要な決定はドキュメントに残す
- 定期的にコンテキストを再確認
- セッションを適度に区切る
これらのパターンは、最初から導入するのがベストです。後から導入するのは、リファクタリングコストがかかります。
vibe codingの力を「動くコードを素早く作る」だけでなく、「長期的に保守しやすいコードを素早く作る」に昇華させましょう。
次の章では、筆者が実際にドキュメント駆動開発からSKILLSシステムへ移行した実践記をお届けします。
