coding-rules - コーディング規約
プロジェクト共通のコーディングルール。
- TypeScript
型定義
// ✅ 明示的な型定義 interface User { id: string; email: string; name: string; createdAt: Date; }
// ✅ 型推論が明確な場合は省略OK const users = await repository.findAll(); // 戻り値の型は関数から推論
// ❌ any は使わない const data: any = response.json();
// ✅ unknown を使って安全に処理 const data: unknown = await response.json(); if (isUser(data)) { console.log(data.email); }
Null/Undefined
// ✅ Optional chaining const email = user?.profile?.email;
// ✅ Nullish coalescing const name = user.name ?? 'Anonymous';
// ❌ 非推奨 const name = user.name || 'Anonymous'; // 空文字もfalsy
- インポート順序
// 1. 外部ライブラリ import { useState } from 'react'; import { z } from 'zod';
// 2. 内部モジュール(エイリアス) import { db } from '@/db'; import { User } from '@/types';
// 3. 相対インポート import { helper } from './utils'; import styles from './styles.module.css';
- 命名規則
種類 規則 例
変数・関数 camelCase getUserById , isActive
定数 UPPER_SNAKE_CASE MAX_RETRY_COUNT
クラス・型 PascalCase UserService , CreateUserInput
ファイル kebab-case user-service.ts
Reactコンポーネント PascalCase UserCard.tsx
環境変数 UPPER_SNAKE_CASE DATABASE_URL
Boolean命名
// ✅ is/has/can/should プレフィックス const isActive = true; const hasPermission = user.role === 'admin'; const canEdit = isOwner || isAdmin; const shouldRefetch = isStale && !isLoading;
- 関数
単一責任
// ❌ 複数の責任 async function processUser(userId: string) { const user = await db.select().from(users).where(eq(users.id, userId)); await sendEmail(user.email); await updateLastLogin(userId); return user; }
// ✅ 分割 async function getUser(userId: string) { return db.select().from(users).where(eq(users.id, userId)); }
async function notifyUser(email: string) { await sendEmail(email); }
async function recordLogin(userId: string) { await updateLastLogin(userId); }
早期リターン
// ❌ ネスト深い function processData(data: Data | null) { if (data) { if (data.isValid) { if (data.items.length > 0) { return data.items.map(process); } } } return []; }
// ✅ 早期リターン function processData(data: Data | null) { if (!data) return []; if (!data.isValid) return []; if (data.items.length === 0) return [];
return data.items.map(process); }
- エラーハンドリング
// ✅ カスタムエラークラス class ValidationError extends Error { constructor( message: string, public field: string ) { super(message); this.name = 'ValidationError'; } }
// ✅ 適切なエラー処理 try { await riskyOperation(); } catch (error) { if (error instanceof ValidationError) { return apiError(error.message, { status: 400 }); } console.error('Unexpected error:', error); return apiError('Internal error', { status: 500 }); }
- コメント
// ✅ WHYを説明 // Stripe APIの制限により、100件ずつバッチ処理する必要がある const BATCH_SIZE = 100;
// ❌ WHATを説明(コードを読めばわかる) // ユーザーを取得する const user = await getUser(id);
// ✅ TODO/FIXME は issue番号付き // TODO(#123): キャッシュ実装後に削除 // FIXME(#456): 競合状態の対応が必要
- React/Next.js
Server vs Client Components
// Server Component (デフォルト) // - データフェッチ // - 機密情報アクセス // - バンドルサイズ削減
// Client Component ('use client') // - useState, useEffect // - イベントハンドラ // - ブラウザAPI
// ✅ 最小限のクライアントコンポーネント 'use client'; export function LikeButton({ postId }: { postId: string }) { const [liked, setLiked] = useState(false); return <button onClick={() => setLiked(!liked)}>Like</button>; }
Props
// ✅ 型定義 interface UserCardProps { user: User; onEdit?: (id: string) => void; className?: string; }
export function UserCard({ user, onEdit, className }: UserCardProps) { // ... }
- Git コミット
Conventional Commits
feat: 新機能追加 fix: バグ修正 docs: ドキュメント refactor: リファクタリング test: テスト chore: その他
例
feat: add user authentication fix: resolve login redirect loop docs: update API documentation refactor: extract validation logic to separate module test: add unit tests for UserService chore: update dependencies