phase-2-convention について
プロジェクト計画時にコーディング規約を定めるための開発ガイドです。命名、コード構成、環境変数、アーキテクチャを扱い、後のレビューで規約を確認できるようにします。
主な機能
- 01命名とコードスタイルの規則
- 02フォルダーとファイル構成の指針
- 03環境変数の規約
- 04レイヤー構造と依存関係の規則
- 05再利用と重複のチェック
活用シーン
- 01コーディング前の規約策定
- 02環境変数の扱い方の定義
- 03チームでのアーキテクチャ合意
- 04後続フェーズでのコード整合性確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ww-w-ai/ bkit-claude-code --skill phase-2-conventionインストール後、Claude Code で/phase-2-conventionを入力して読み込まれたか確認します。
詳細
フェーズ 2:コーディング規約
コード記述ルールを定義します
目的
コードスタイルの一貫性を保ちます。AI と共同作業する際は特に重要です。AI がコードを書くときに使用するスタイルを明確にします。
このフェーズで行うこと
- 命名規則:変数、関数、ファイル、フォルダー名
- コードスタイル:インデント、引用符、セミコロンなど
- 構造ルール:フォルダー構造、ファイル分割の基準
- パターンの定義:頻繁に使用するコードパターン
成果物
Project Root/
├── CONVENTIONS.md # Full conventions
└── docs/01-plan/
├── naming.md # Naming rules
└── structure.md # Structure rules
PDCA の適用
- Plan:必要な規約項目を特定します
- Design:詳細なルールを設計します
- Do:規約ドキュメントを作成します
- Check:一貫性と実用性を確認します
- Act:最終決定し、フェーズ 3 に進みます
レベル別の適用
| レベル | 適用レベル |
|---|---|
| Starter | 基本(必須ルールのみ) |
| Dynamic | 拡張(API、状態管理を含む) |
| Enterprise | 拡張(サービスごとのルール) |
主な規約項目
命名
- コンポーネント:PascalCase
- 関数:camelCase
- 定数:UPPER_SNAKE_CASE
- ファイル:kebab-case または PascalCase
フォルダー構造
src/
├── components/ # Reusable components
├── features/ # Feature modules
├── hooks/ # Custom hooks
├── utils/ # Utilities
└── types/ # Type definitions
環境変数の規約
設計段階で定義する理由
❌ Organizing env vars just before deployment
→ Missing variables, naming inconsistency, deployment delays
✅ Establish convention at design stage
→ Consistent naming, clear categorization, fast deployment
環境変数の命名規則
| 接頭辞 | 用途 | 公開範囲 | 例 |
|---|---|---|---|
NEXT_PUBLIC_ | クライアントに公開 | ブラウザー | NEXT_PUBLIC_API_URL |
DB_ | データベース | サーバーのみ | DB_HOST、DB_PASSWORD |
API_ | 外部 API キー | サーバーのみ | API_STRIPE_SECRET |
AUTH_ | 認証 | サーバーのみ | AUTH_SECRET、AUTH_GOOGLE_ID |
SMTP_ | メールサービス | サーバーのみ | SMTP_HOST、SMTP_PASSWORD |
STORAGE_ | ファイルストレージ | サーバーのみ | STORAGE_S3_BUCKET |
⚠️ Security Principles
- Never expose anything except NEXT_PUBLIC_* to client
- API keys and passwords must be server-only variables
- Never commit sensitive info in .env files
.env ファイルの構成
Project Root/
├── .env.example # Template (in Git, values empty)
├── .env.local # Local development (Git ignored)
├── .env.development # Development env defaults
├── .env.staging # Staging env defaults
├── .env.production # Production defaults (no sensitive info)
└── .env.test # Test environment
.env.example テンプレート
# .env.example - This file is included in Git
# Set actual values in .env.local
# ===== App Settings =====
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000
# ===== Database =====
DB_HOST=
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
# ===== Authentication =====
AUTH_SECRET= # openssl rand -base64 32
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
# ===== External Services =====
NEXT_PUBLIC_API_URL=
API_STRIPE_SECRET=
SMTP_HOST=
SMTP_USER=
SMTP_PASSWORD=
環境ごとの値の分類
| 変数の種類 | .env.example | .env.local | CI/CD シークレット |
|---|---|---|---|
| アプリの URL | テンプレート | ローカル値 | 環境ごとの値 |
| API エンドポイント | テンプレート | ローカル/開発 | 環境ごとの値 |
| DB パスワード | 空欄 | ローカル値 | ✅ シークレット |
| API キー | 空欄 | テストキー | ✅ シークレット |
| JWT シークレット | 空欄 | ローカル値 | ✅ シークレット |
環境変数の検証
// lib/env.ts - Validate env vars at app startup
import { z } from 'zod';
const envSchema = z.object({
// Required
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
// Optional (with defaults)
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
// Client-exposed
NEXT_PUBLIC_APP_URL: z.string().url(),
});
// Validation and type inference
export const env = envSchema.parse(process.env);
// Type-safe usage
// env.DATABASE_URL ← autocomplete supported
環境変数チェックリスト
-
命名の一貫性
- 接頭辞のルール(NEXT_PUBLIC_、DB_、API_ など)に従う
- UPPER_SNAKE_CASE を使用する
-
ファイル構成
- .env.example(テンプレート)を作成する
- .env.local を .gitignore に登録する
- 環境ごとに .env ファイルを分ける
-
セキュリティ
- 機密情報を分類する
- クライアントに公開する変数を確認する
- シークレットの一覧を整理する(フェーズ 9 のデプロイ用)
クリーンアーキテクチャの原則
設計段階で定義する理由
Clean Architecture = Code resilient to change
❌ Developing without architecture
→ Spaghetti code, multiple file changes for each modification
✅ Define layers at design stage
→ Separation of concerns, easy testing, easy maintenance
4 層アーキテクチャ(推奨)
src/
├── presentation/ # or app/, pages/
│ ├── components/ # UI components
│ ├── hooks/ # State management hooks
│ └── pages/ # Page components
│
├── application/ # or services/, features/
│ ├── use-cases/ # Business use cases
│ └── services/ # API service wrappers
│
├── domain/ # or types/, entities/
│ ├── entities/ # Domain entities
│ ├── types/ # Type definitions
│ └── constants/ # Domain constants
│
└── infrastructure/ # or lib/, api/
├── api/ # API clients
├── db/ # Database connections
└── external/ # External services
各層の責務とルール
| 層 | 責務 | 依存できる層 | 依存できない層 |
|---|---|---|---|
| プレゼンテーション | UI の描画、ユーザーイベント | アプリケーション、ドメイン | インフラストラクチャへの直接依存 |
| アプリケーション | ビジネスロジックの調整 | ドメイン、インフラストラクチャ | プレゼンテーション |
| ドメイン | 中核となるビジネスルール、型 | なし(独立) | すべての外部層 |
| インフラストラクチャ | 外部システムとの接続 | ドメイン | アプリケーション、プレゼンテーション |
依存関係のルール
// ❌ Bad: Presentation directly calls Infrastructure
// components/UserList.tsx
import { apiClient } from '@/lib/api/client'; // Direct import forbidden!
export function UserList() {
const users = apiClient.get('/users'); // ❌
}
// ✅ Good: Presentation → Application → Infrastructure
// hooks/useUsers.ts
import { userService } from '@/services/user.service';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: userService.getList, // ✅ Call through Service
});
}
// components/UserList.tsx
import { useUsers } from '@/hooks/useUsers';
export function UserList() {
const { data: users } = useUsers(); // ✅ Call through Hook
}
ファイルのインポートルール
// ===== Allowed import directions =====
// In presentation/:
import { User } from '@/domain/types'; // ✅ Domain OK
import { useUsers } from '@/hooks/useUsers'; // ✅ Same layer OK
import { userService } from '@/services/user'; // ✅ Application OK
// In application/:
import { User } from '@/domain/types'; // ✅ Domain OK
import { apiClient } from '@/lib/api/client'; // ✅ Infrastructure OK
// In domain/:
// Minimize external imports (pure types/logic only)
// In infrastructure/:
import { User } from '@/domain/types'; // ✅ Domain OK
// ===== Forbidden imports =====
// In domain/:
import { apiClient } from '@/lib/api/client'; // ❌ Infrastructure forbidden
import { Button } from '@/components/ui/button'; // ❌ Presentation forbidden
// In infrastructure/:
import { useUsers } from '@/hooks/useUsers'; // ❌ Presentation forbidden
レベル別の適用
| レベル | アーキテクチャの適用 |
|---|---|
| Starter | シンプルな構造(components、lib) |
| Dynamic | 3〜4 層に分離(推奨構造) |
| Enterprise | 厳格な層分離 + DI コンテナー |
Starter レベルのフォルダー構造
src/
├── components/ # UI components
├── lib/ # Utilities, API
└── types/ # Type definitions
Dynamic レベルのフォルダー構造
src/
├── components/ # Presentation
│ └── ui/
├── features/ # Feature modules (Application + Presentation)
│ ├── auth/
│ └── product/
├── hooks/ # Presentation (state management)
├── services/ # Application
├── types/ # Domain
└── lib/ # Infrastructure
└── api/
Enterprise レベルのフォルダー構造
src/
├── presentation/
│ ├── components/
│ ├── hooks/
│ └── pages/
├── application/
│ ├── use-cases/
│ └── services/
├── domain/
│ ├── entities/
│ └── types/
└── infrastructure/
├── api/
└── db/
フェーズ接続
このフェーズで定義した規約は、後続のフェーズで検証されます。
| 定義(フェーズ 2) | 検証(フェーズ 8) |
|---|---|
| 命名規則 | 命名の一貫性チェック |
| フォルダー構成 | 構成の一貫性チェック |
| 環境変数の規約 | 環境変数の命名チェック |
| クリーンアーキテクチャの原則 | 依存関係の方向チェック |
テンプレート
templates/pipeline/phase-2-convention.template.md を参照してください。
次のフェーズ
フェーズ 3:モックアップ開発 → ルールが決まったので、これから迅速なプロトタイピングを行います
6. 再利用性の原則
6.1 関数設計
汎用関数の作成
// ❌ Handles only specific case
function formatUserName(user: User) {
return `${user.firstName} ${user.lastName}`
}
// ✅ Generic
function formatFullName(firstName: string, lastName: string) {
return `${firstName} ${lastName}`
}
// Usage
formatFullName(user.firstName, user.lastName)
formatFullName(author.first, author.last)
パラメーターの汎用化
// ❌ Tied to specific type
function calculateOrderTotal(order: Order) {
return order.items.reduce((sum, item) => sum + item.price, 0)
}
// ✅ Generalized with interface
interface HasPrice { price: number }
function calculateTotal<T extends HasPrice>(items: T[]) {
return items.reduce((sum, item) => sum + item.price, 0)
}
// Can be used in various places
calculateTotal(order.items)
calculateTotal(cart.products)
calculateTotal(invoice.lineItems)
6.2 コンポーネント設計
コンポーザブルなコンポーネント
// ❌ Hardcoded structure
function UserCard({ user }: { user: User }) {
return (
<div className="card">
<img src={user.avatar} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)
}
// ✅ Composable
function Card({ children, className }: CardProps) {
return <div className={cn("card", className)}>{children}</div>
}
function Avatar({ src, alt }: AvatarProps) {
return <img src={src} alt={alt} className="avatar" />
}
// Use by combining
<Card>
<Avatar src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</Card>
Props の拡張性
// ❌ Limited props
interface ButtonProps {
label: string
onClick: () => void
}
// ✅ Extend HTML attributes
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'outline' | 'ghost'
size?: 'sm' | 'md' | 'lg'
}
// All button attributes available
<Button type="submit" disabled={isLoading}>
Save
</Button>
6.3 抽出基準
関数として抽出するタイミング
1. Same logic used 2+ times
2. Logic is complex enough to need a name
3. Logic that needs testing
4. Can be used in other files
コンポーネントとして抽出するタイミング
1. Same UI pattern repeats
2. Has independent state
3. Is a reusable unit
4. JSX over 50 lines
7. 拡張性の原則
7.1 設定ベースの設計
// ❌ Listing conditionals
function getStatusColor(status: string) {
if (status === 'active') return 'green'
if (status === 'pending') return 'yellow'
if (status === 'error') return 'red'
return 'gray'
}
// ✅ Configuration object
const STATUS_CONFIG = {
active: { color: 'green', label: 'Active' },
pending: { color: 'yellow', label: 'Pending' },
error: { color: 'red', label: 'Error' },
} as const
function getStatusConfig(status: keyof typeof STATUS_CONFIG) {
return STATUS_CONFIG[status] ?? { color: 'gray', label: status }
}
// Adding new status = just add config
7.2 ストラテジーパターン
// ❌ Listing switch statements
function processPayment(method: string, amount: number) {
switch (method) {
case 'card':
// Card payment logic
break
case 'bank':
// Bank transfer logic
break
}
}
// ✅ Strategy pattern
interface PaymentStrategy {
process(amount: number): Promise<Result>
}
const paymentStrategies: Record<string, PaymentStrategy> = {
card: new CardPayment(),
bank: new BankTransfer(),
}
function processPayment(method: string, amount: number) {
const strategy = paymentStrategies[method]
if (!strategy) throw new Error(`Unknown method: ${method}`)
return strategy.process(amount)
}
// Adding new payment method = just add strategy
7.3 プラグイン構造
// Extensible system
interface Plugin {
name: string
init(): void
execute(data: unknown): unknown
}
class PluginManager {
private plugins: Plugin[] = []
register(plugin: Plugin) {
this.plugins.push(plugin)
}
executeAll(data: unknown) {
return this.plugins.reduce(
(result, plugin) => plugin.execute(result),
data
)
}
}
// New feature = add plugin
8. 重複防止チェックリスト
コードを書く前
- utils/ に似た関数はありますか?
- components/ に似たコンポーネントはありますか?
- hooks/ に似たフックはありますか?
- プロジェクト全体を検索しましたか?
コードを書いた後
- 同じコードが 2 か所以上にありますか? → 抽出する
- このコードは別の場所でも使えますか? → 移動する
- ハードコードされた値はありますか? → 定数にする
- 特定の型に依存していますか? → 汎用化する
コメント
Markdown 対応