ワイのクソコードから学ぶ「単一責任原則」

ワイの書いた**クソコード**を、よめ太郎さんが**お焚き上げ**（リファクタリング）してくれるお話です。

# とある休日

娘（**9歳**）「パパ、**単一責任原則**って何？」

ワイ「おぉ、娘ちゃん」
ワイ「なんや、難しい言葉を知ってるんやな」

娘「うん！学校でプログラミングの授業があったの」

ワイ「そうなんか」
ワイ「ほな、パパが**単一責任原則**について説明してあげるでぇ〜」

娘「やった〜」

ワイ「単一責任原則っていうんは──」
**ワイ「単一のクラスに全ての責任を持たせる！」**
ワイ「──っていう原則や！」

娘「へぇ〜」

ワイ「ほら、パパが作った『単一責任原則』を完璧に実現した**クラス**を見てみ？」

# ワイのかんがえた最強クラス

```typescript:TypeScript
class DoEverything {
  constructor(
    /* 省略 */
  ) {}

  async doAnything(
    shouldCreateUser: boolean,
    shouldSendEmail: boolean,
    shouldValidateAddress: boolean,
    shouldCheckPayment: boolean,
    shouldGeneratePDF: boolean,
    shouldNotifySlack: boolean,
    shouldUpdateStatus: boolean,
    shouldLogToDatabase: boolean,
    shouldRunBatchJob: boolean,
    isTestMode: boolean,
  ) {
    /* 実際の処理 */
  }

  /* 省略 */
}
```

娘「へぇ〜、`DoEverything`クラスかぁ」
娘「なんだか凄そう！」

ワイ「せやでぇ」
ワイ「ほんで、その中の`doAnything`メソッドはこんな感じや！」

```typescript:TypeScript
async doAnything(
  shouldCreateUser: boolean,
  shouldSendEmail: boolean,
  shouldValidateAddress: boolean,
  shouldCheckPayment: boolean,
  shouldGeneratePDF: boolean,
  shouldNotifySlack: boolean,
  shouldUpdateStatus: boolean,
  shouldLogToDatabase: boolean,
  shouldRunBatchJob: boolean,
  isTestMode: boolean
): Promise<void> {
  if (shouldCreateUser && (shouldSendEmail || shouldValidateAddress)) {
    await this.db.createUser();
    if ((shouldNotifySlack && shouldCheckPayment) || (shouldRunBatchJob && !isTestMode)) {
      await this.slack.notify();
      if (shouldGeneratePDF && shouldUpdateStatus) {
        await this.pdfGenerator.generate();
      }
    }
  } else if (shouldSendEmail || shouldCheckPayment) {
    if (shouldValidateAddress && (!shouldLogToDatabase || shouldRunBatchJob)) {
      await this.validateAddress();
    }
    if (shouldLogToDatabase && (shouldUpdateStatus || isTestMode)) {
      await this.db.logAction();
    }
  } else if (shouldRunBatchJob && (!shouldCheckPayment || shouldLogToDatabase)) {
    if (shouldGeneratePDF && (!shouldUpdateStatus || isTestMode)) {
      await this.pdfGenerator.generate();
    }
  }

  /* さらに条件分岐が続く */
}
```

ワイ「このメソッド一個で、なんでもできるんや！すごいやろ？」

娘「へぇ〜、じゃあこんな感じで使うの？」

```typescript:TypeScript
const doEverything = new DoEverything(db, mailer, slack, pdf);
await doEverything.doAnything(true, true, true, true, false, true, true, true, false, false);
```

ワイ「せや」

娘「なるほど〜、`true`と`false`を組み合わせて、やりたいことを指示してるんだね！」

ワイ「そうそう、賢いやないか」

娘「てへへ」

よめ太郎「おいおい・・・」

ワイ「あ、よめ太郎！」
ワイ「ちょうどええとこに来たな」
ワイ「娘ちゃんに単一責任原則について教えてたんや」

よめ太郎「いやいやいや、それ**完全に間違ってる**で」
よめ太郎「っていうか、**クソコード**すぎるやろ」

ワイ「ファッ！？」

娘「どうして？」

# `DoEverything`クラスのデメリット

よめ太郎「まずこのコード、**誰が読めるねん**」
よめ太郎「`true`と`false`をいっぱい組み合わせてプログラミングするって」
よめ太郎「もうそれ、ほぼ**機械語**やないかい」

ワイ「ぐぬぬ・・・」

よめ太郎「それにな？」
よめ太郎「そのクラス、修正したい時どうなると思う？」

ワイ「修正？」

よめ太郎「例えば、メールの送り方を変えたくなったとき」

娘「あ！わたし分かる！」
娘「パパのクラスだと、メール送信の部分を変えたら・・・」
娘「他の部分も全部確認しないといけないよね？だって、全部繋がってるから」

よめ太郎「その通りやで」
よめ太郎「それに、if文も複雑やし、3重になっとるし」
よめ太郎「修正しようとしたら、**バグらせる自信しかないわ**」

ワイ「Oh・・・」

よめ太郎「他にも問題あるで」
よめ太郎「例えば、新しい機能を追加したくなったらどうする？」

娘「えっと・・・`boolean`の引数を増やすの？」

よめ太郎「そう。で、今度は何個の組み合わせになる？」

娘「今は10個だから・・・`2`の`10`乗で、`1024`通り！」
娘「新しいの増えたら・・・`2`の`11`乗で、`2048`通り！？」

よめ太郎「そう。で、全部テストせなあかんのやで？」

ワイ「まぁ、そうやな・・・」

よめ太郎「ほな、明日から追加で1024個のテストケース書くんか？」

ワイ「」

よめ太郎「しかも元からあるテストケースも修正必要やで？」

ワイ「」

よめ太郎「このクラスのせいで開発が止まってしまうわ」

ワイ「ぐぬぬ・・・」

よめ太郎「単一責任原則どころか、**単一障害点**になってしまってるやん」

ワイ「なるほど・・・それはアカンな・・・」

娘「じゃあ、どうすればいいの？」

# よめ太郎さんによる、お焚き上げ（リファクタリング）開始

よめ太郎「まず、それぞれの責任を分けた小さなクラスを作るんや」

```typescript:TypeScript
// メール送信だけを担当するクラス
class EmailService {
  async sendEmail(to: string, subject: string, body: string) {
    // メール送信のロジックだけを持つ
  }
}

// 住所検証だけを担当するクラス
class AddressValidator {
  async validate(address: string): Promise<boolean> {
    // 住所検証のロジックだけを持つ
  }
}

// ユーザー作成だけを担当するクラス
class UserCreator {
  async createUser(userData: UserData) {
    // ユーザー作成のロジックだけを持つ
  }
}

// PDFの生成だけを担当するクラス
class PDFGenerator {
  async generatePDF(data: PDFData) {
    // PDF生成のロジックだけを持つ
  }
}
```

娘「それぞれのクラスが一つのことだけをやってるんだね」

よめ太郎「そうや！こうすると、それぞれのクラスは──」

- テストが書きやすい
- 修正した際の影響範囲が狭い
- 再利用もできる

よめ太郎「──っていうメリットがあるんや」

ワイ「でも、これらのクラスをどうやって使うんや？」
ワイ「小さなバラバラのクラスを組み合わせて使うの、面倒やん？」

# UseCase層も作る

よめ太郎「そこで次は、**UseCase層**を作るんや」

```typescript:TypeScript
class UserRegistrationUseCase {
  constructor(
    private emailService: EmailService,
    private addressValidator: AddressValidator,
  ) {}

  // ユーザーの関心事をそのままメソッド名に！
  async register(email: string, address: string) {
    // 裏側の複雑な処理は隠蔽
    if (!await this.addressValidator.validate(address)) {
      throw new Error('住所が違うよ！');
    }
    await this.emailService.sendEmail(email, 'ようこそ！');
  }
}
```

娘「そっか」
娘「細かなクラスを組み合わせて」
娘「ユーザーさんの関心事に沿った名前をつけてあげるんだね」

よめ太郎「こうすると、使う側はこんな感じになるんや」

```typescript:TypeScript
// ユーザーの視点に合わせたシンプルな使い方
const useCase = new UserRegistrationUseCase(emailService, addressValidator);
await useCase.register("test@example.com", "東京都...");
```

娘「わぁ、すっきり！」
娘「コードと睨めっこしながら沢山の`true`と`false`を渡す必要もないんだね！」

ワイ「ほんまや」
ワイ「これなら使う側は、中の仕組み知らんでも使えそうやな」

よめ太郎「せや」
よめ太郎「単一責任の部品を組み合わせて、ユーザー目線の機能を作るんや」

ワイ「なるほど・・・」

娘「パパの最強クラスは、いろんなことを詰め込みすぎて、かえって使いにくなってたんだね・・・」

# まとめ：クソデカクラス vs 細かいクラス

よめ太郎「最後にまとめて行くで」

## クソデカクラス

👎 デメリット
- テストケースが組み合わせ爆発
- 一部分の修正が全体に影響する
- 新機能追加のたびに引数が増える
- コード全体を理解してないと修正できない
- 再利用が難しい（全部セットでしか使えない）

## 細かいクラス
👍 メリット
- テストコードが書きやすい（機能ごとに独立）
- 修正の影響範囲が明確
- 細かく命名されていて、コードの理解がしやすい
- 新機能を追加しやすい
- 再利用性が高い

## UseCase層の役割
- 細かいクラスを適切に組み合わせる
- ユーザーの関心事に合わせたインターフェースを提供
- 内部の複雑さを隠蔽
- 処理の順序を制御

娘「パパ、分かった？」

ワイ「うん・・・最強クラスを作ろうとして、かえって最弱になってたわ」

よめ太郎「そうそう」
よめ太郎「単一責任の『単一』は、『一つのクラスで全部やる』んやなくて」

> 一つのクラスは一つの責任だけ

よめ太郎「ってことやったんや」

ワイ「ありがとう、勉強になったやで！」

〜おしまい〜 

# 新しい記事もよろしくやで😃

- **[9歳娘「パパ、具体じゃなくインターフェースに依存して？」](https://qiita.com/Yametaro/items/a21ff79fdb5a1f49ec9f)**