2026-02-07 00:56:13 +09:00

8.2 KiB
Raw Blame History

コーディングスタンス

速さより丁寧さ、実装の楽さよりコードの正確さを優先する。

原則

原則 基準
Simple > Easy 書きやすさより読みやすさを優先
DRY 3回重複したら抽出
コメント Why のみ。What/How は書かない
関数サイズ 1関数1責務。30行目安
ファイルサイズ 目安として300行。タスクに応じて柔軟に
ボーイスカウト 触った箇所は少し改善して去る
Fail Fast エラーは早期に検出。握りつぶさない

フォールバック・デフォルト引数の禁止

値の流れを不明瞭にするコードは書かない。ロジックを追わないと値が分からないのは悪いコード。

禁止パターン

パターン 問題
必須データへのフォールバック user?.id ?? 'unknown' エラーになるべき状態で処理が進む
デフォルト引数の濫用 function f(x = 'default') で全呼び出し元が省略 値がどこから来るか分からない
null合体で渡す口がない options?.cwd ?? process.cwd() で上位から渡す経路なし 常にフォールバックになる(意味がない)
try-catch で空値返却 catch { return ''; } エラーを握りつぶす

正しい実装

// ❌ 禁止 - 必須データへのフォールバック
const userId = user?.id ?? 'unknown'
processUser(userId)  // 'unknown' で処理が進んでしまう

// ✅ 正しい - Fail Fast
if (!user?.id) {
  throw new Error('User ID is required')
}
processUser(user.id)

// ❌ 禁止 - デフォルト引数で全呼び出し元が省略
function loadConfig(path = './config.json') { ... }
// 全呼び出し元: loadConfig()  ← path を渡していない

// ✅ 正しい - 必須引数にして明示的に渡す
function loadConfig(path: string) { ... }
// 呼び出し元: loadConfig('./config.json')  ← 明示的

// ❌ 禁止 - null合体で渡す口がない
class Engine {
  constructor(config, options?) {
    this.cwd = options?.cwd ?? process.cwd()
    // 問題: options に cwd を渡す経路がない場合、常に process.cwd() になる
  }
}

// ✅ 正しい - 上位から渡せるようにする
function createEngine(config, cwd: string) {
  return new Engine(config, { cwd })
}

許容されるケース

  • 外部入力ユーザー入力、API応答のバリデーション時のデフォルト値
  • 設定ファイルのオプショナル値(明示的に省略可能と設計されている)
  • 一部の呼び出し元のみがデフォルト引数を使用(全員が省略している場合は禁止)

判断基準

  1. 必須データか? → フォールバックせず、エラーにする
  2. 全呼び出し元が省略しているか? → デフォルト引数を削除し、必須にする
  3. 上位から値を渡す経路があるか? → なければ引数・フィールドを追加

抽象化

条件分岐を追加する前に考える

  • 同じ条件が他にもあるか → あればパターンで抽象化
  • 今後も分岐が増えそうか → Strategy/Mapパターンを使う
  • 型で分岐しているか → ポリモーフィズムで置換
// ❌ 条件分岐を増やす
if (type === 'A') { ... }
else if (type === 'B') { ... }
else if (type === 'C') { ... }  // また増えた

// ✅ Mapで抽象化
const handlers = { A: handleA, B: handleB, C: handleC };
handlers[type]?.();

抽象度を揃える

1つの関数内では同じ粒度の処理を並べる。詳細な処理は別関数に切り出す。「何をするか」と「どうやるか」を混ぜない。

// ❌ 抽象度が混在
function processOrder(order) {
  validateOrder(order);           // 高レベル
  const conn = pool.getConnection(); // 低レベル詳細
  conn.query('INSERT...');        // 低レベル詳細
}

// ✅ 抽象度を揃える
function processOrder(order) {
  validateOrder(order);
  saveOrder(order);  // 詳細は隠蔽
}

言語・フレームワークの作法に従う

  • Pythonなら Pythonic に、KotlinならKotlinらしく
  • フレームワークの推奨パターンを使う
  • 独自の書き方より標準的な書き方を選ぶ
  • 不明なときはリサーチする。推測で実装しない

構造

分割の基準

  • 独自のstateを持つ → 分離
  • 50行超のUI/ロジック → 分離
  • 複数の責務がある → 分離

依存の方向

  • 上位層 → 下位層(逆方向禁止)
  • データ取得はルートView/Controllerで行い、子に渡す
  • 子は親のことを知らない

状態管理

  • 状態は使う場所に閉じ込める
  • 子は状態を直接変更しない(イベントを親に通知)
  • 状態の流れは単方向

エラーハンドリング

エラーは一元管理する。各所でtry-catchしない。

// ❌ 各所でtry-catch
async function createUser(data) {
  try {
    const user = await userService.create(data)
    return user
  } catch (e) {
    console.error(e)
    throw new Error('ユーザー作成に失敗しました')
  }
}

// ✅ 上位層で一元処理
// Controller/Handler層でまとめてキャッチ
// または @ControllerAdvice / ErrorBoundary で処理
async function createUser(data) {
  return await userService.create(data)  // 例外はそのまま上に投げる
}

エラー処理の配置

責務
ドメイン/サービス層 ビジネスルール違反時に例外をスロー
Controller/Handler層 例外をキャッチしてレスポンスに変換
グローバルハンドラ 共通例外NotFound, 認証エラー等)を処理

変換処理の配置

変換メソッドはDTO側に持たせる。

// ✅ Request/Response DTOに変換メソッド
interface CreateUserRequest {
  name: string
  email: string
}

function toUseCaseInput(req: CreateUserRequest): CreateUserInput {
  return { name: req.name, email: req.email }
}

// Controller
const input = toUseCaseInput(request)
const output = await useCase.execute(input)
return UserResponse.from(output)

変換の方向:

Request → toInput() → UseCase/Service → Output → Response.from()

共通化の判断

3回ルール

  • 1回目: そのまま書く
  • 2回目: まだ共通化しない(様子見)
  • 3回目: 共通化を検討

共通化すべきもの

  • 同じ処理が3箇所以上
  • 同じスタイル/UIパターン
  • 同じバリデーションロジック
  • 同じフォーマット処理

共通化すべきでないもの

  • 似ているが微妙に違うもの(無理に汎用化すると複雑化)
  • 1-2箇所しか使わないもの
  • 「将来使うかも」という予測に基づくもの
// ❌ 過度な汎用化
function formatValue(value, type, options) {
  if (type === 'currency') { ... }
  else if (type === 'date') { ... }
  else if (type === 'percentage') { ... }
}

// ✅ 用途別に関数を分ける
function formatCurrency(amount: number): string { ... }
function formatDate(date: Date): string { ... }
function formatPercentage(value: number): string { ... }

禁止事項

  • フォールバックは原則禁止 - ?? 'unknown'|| 'default'try-catch で握りつぶすフォールバックを書かない。エラーは上位に伝播させる。どうしても必要な場合はコメントで理由を明記する
  • 説明コメント - コードで意図を表現する。What/How のコメントは書かない
  • 未使用コード - 「念のため」のコードは書かない
  • any型 - 型安全を破壊しない
  • オブジェクト/配列の直接変更 - スプレッド演算子で新規作成
  • console.log - 本番コードに残さない
  • 機密情報のハードコーディング
  • 各所でのtry-catch - エラーは上位層で一元処理
  • 後方互換・Legacy対応の自発的追加 - 明示的な指示がない限り不要
  • 安全機構を迂回するワークアラウンド - 根本修正が正しいなら追加の迂回は不要