👋

Tidy First 第6章 凝縮度

に公開

リーダブルコードがジュニアエンジニア向けならば、Tidy Firstはミドルエンジニア以上向けであると思う。
この記事はそのTidy Firstの第6章の内容をわかりやすい文章で私なりにまとめました

「Tidy First?」から学ぶ:散らばったコードを整頓する技術

コードを読んでいて、「これを変更したい」と思ったとき、対象がいろんな場所に散らばっていてイライラした経験はありませんか?

たとえば、仕様変更に対応しようとしたら、ある関数、別のファイル、さらに別のディレクトリにあるクラス……。変更のために、あちこちを確認しなければならない。その過程で迷子になる。そして、時間を消耗してしまう。

こうした状況に対して、Kent Beckの『Tidy First?』はシンプルながら強力な解決策を提案しています。

現実のコード:散らばった変更対象

想像してみてください。「ユーザー登録時にメール送信機能を追加する」という、よくある仕様変更を実装する場面です。

// src/controllers/UserController.ts
export class UserController {
  async register(req: Request, res: Response) {
    const userData = req.body;
    const user = await this.userService.createUser(userData);
    
    // TODO: ここにメール送信を追加する必要がある
    // でも、EmailServiceはどこにあるんだっけ?
    res.json({ success: true, user });
  }
}

// src/services/notification/EmailService.ts - 全然違うディレクトリ
export class EmailService {
  async sendWelcomeEmail(email: string, name: string) {
    // メール送信処理...
    // TODO: 新しいテンプレートを追加する必要がある
  }
}

// src/models/User.ts - またまた別の場所
export interface User {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
  // TODO: メール送信状態のフィールドを追加する必要がある
}

// src/components/auth/UserRegistrationForm.tsx - フロントエンドも変更
export function UserRegistrationForm() {
  const [loading, setLoading] = useState(false);
  
  const handleSubmit = async (userData: any) => {
    setLoading(true);
    // TODO: メール送信中の表示を追加する必要がある
    await registerUser(userData);
    setLoading(false);
  };
  
  return (
    // JSX...
  );
}

一つの機能追加のために、4つの異なる場所にあるファイルを変更する必要があります。各ファイルの場所を思い出し、変更点を把握し、テストを書き、デプロイメントを考慮する……。

これは明らかに効率が悪い状況です。

🔄 Kent Beckの提案:「並び替える」だけでも変わる

『Tidy First?』では、このような状況に対して驚くほどシンプルな解決策を提案しています:

変更対象の要素が隣り合うようにコードを並べ替える

レベル1:ファイル内での整頓

// Before: 関連する関数が離れている
export class UserService {
  async createUser(userData: CreateUserRequest) {
    // ユーザー作成処理
  }
  
  async sendNotification(message: string) {
    // 通知送信処理
  }
  
  async validateUserData(userData: CreateUserRequest) {
    // createUserと関連するバリデーション
  }
  
  async logActivity(action: string) {
    // ログ出力処理
  }
  
  async generateWelcomeEmail(user: User) {
    // createUserと関連するメール生成
  }
}

// After: 関連する関数を隣接させる
export class UserService {
  // ユーザー作成関連の処理をグルーピング
  async createUser(userData: CreateUserRequest) {
    const validatedData = await this.validateUserData(userData);
    const user = await this.saveUser(validatedData);
    const emailContent = await this.generateWelcomeEmail(user);
    return { user, emailContent };
  }
  
  async validateUserData(userData: CreateUserRequest) {
    // バリデーション処理(隣接)
  }
  
  async generateWelcomeEmail(user: User) {
    // メール生成処理(隣接)
  }
  
  // その他の処理
  async sendNotification(message: string) {
    // 通知送信処理
  }
  
  async logActivity(action: string) {
    // ログ出力処理
  }
}

レベル2:ディレクトリ構造での整頓

// Before: 技術的な分類で分散している
src/
├── utils/
│   ├── emailValidator.ts
│   ├── passwordHasher.ts
│   ├── dateFormatter.ts
│   └── stringUtils.ts
├── hooks/
│   ├── useUserRegistration.ts
│   ├── useEmailValidation.ts
│   └── useAuth.ts
├── components/
│   ├── UserForm.tsx
│   ├── EmailInput.tsx
│   └── PasswordField.tsx
└── api/
    ├── userApi.ts
    ├── emailApi.ts
    └── authApi.ts

// After: 機能別にグルーピング
src/
├── features/
│   └── user-registration/
│       ├── components/
│       │   ├── UserForm.tsx
│       │   ├── EmailInput.tsx
│       │   └── PasswordField.tsx
│       ├── hooks/
│       │   ├── useUserRegistration.ts
│       │   └── useEmailValidation.ts
│       ├── utils/
│       │   ├── emailValidator.ts
│       │   └── passwordHasher.ts
│       ├── api/
│       │   └── userApi.ts
│       └── __tests__/
│           └── user-registration.test.ts
└── shared/
    ├── utils/
    │   ├── dateFormatter.ts
    │   └── stringUtils.ts
    └── hooks/
        └── useAuth.ts

レベル3:リポジトリ間での整頓

関係の深いコードが複数のリポジトリに分散している場合は、変更前に同じリポジトリにまとめることも検討しましょう。

この「整頓」がもたらす効果

1. 認知負荷の軽減

関連するコードが近くにあることで、頭の中で情報を整理しやすくなります。

2. 変更の影響範囲の可視化

どの部分が関連しているかが一目でわかるため、変更の影響を把握しやすくなります。

3. テストの効率化

関連するテストも同じ場所にまとめることで、テストの実行と保守が楽になります。

4. チーム開発の円滑化

他の開発者が変更内容を理解しやすくなり、コードレビューも効率的になります。

💡 「結合を減らす」ことが最終目標だとしても…

「だったら最初から結合を減らしたほうがいいのでは?」と思うかもしれません。確かに、それができるなら素晴らしいです。

しかし、現実のコードでは、まず結合を見つけて理解し、必要な変更を見極める必要があります。そのプロセスで、変更対象を物理的に近づけることは、大きな助けになります。

分離が困難な現実的な理由

完璧な分離が理想だとしても、以下のような理由で現実的でない場合があります:

知識面での困難
「どうやって分離すればいいかわからない」
複雑に絡み合った依存関係を解きほぐすには、経験とスキルが必要です。

時間・予算面での困難
「やり方はわかるが、今はその時間がない」
大規模リファクタリングには時間がかかりますが、プロジェクトには締切があります。

組織面での困難
「チームがこれ以上の変化を受け入れられない」
技術的には可能でも、チームの負荷やリスク許容度を考慮すると現実的でない場合があります。

🧭 「Tidy First」の実践的なアプローチ

Kent Beckが提案する「Tidy First」は、完璧を目指すのではなく、少しずつ前進するための現実的かつ効果的な考え方です。

ステップ1:まず整頓

  • 関連するコードを物理的に近くに配置
  • ファイル内、ディレクトリ内、リポジトリ内での整理
  • 比較的低リスクで実行可能

ステップ2:変更の実施

  • 整頓されたコードで変更作業を実施
  • 関連する処理が近くにあるため、作業効率が向上
  • 変更の影響範囲が把握しやすい

ステップ3:継続的な改善

  • 整頓によって見えてきた改善点を次回に活かす
  • 段階的なリファクタリングの機会を見つける
  • より良い設計への道筋を発見

まとめ

コードが散らばっていると、変更は苦痛になる

まず整頓して、関係するコードを近づけよう

並び替えだけでも、驚くほど作業がしやすくなる

結合を減らすのは理想。でも、まずは「Tidy First」

Kent Beckの『Tidy First?』が提案するこのアプローチは、リファクタリングやモダンなソフトウェア開発において、シンプルながら強力なツールになります。

「変更の前に整頓を」が習慣になれば、コードともっと仲良くなれるかもしれません。完璧な設計を一度に実現しようとせず、まずは手の届く範囲から始めてみませんか?


Kent Beck著『Tidy First?』は、日々のコーディングをより効率的で楽しいものにするための実践的な指南書です。この記事で紹介した「整頓」のアプローチは、その一部に過ぎません。より詳しい内容は、ぜひ原著をお読みください。

Discussion