mcpField
S
separation-of-concerns

separation-of-concerns

github.com/yanko-belov開発1364 回インストール41 週間前に更新
リポジトリ

separation-of-concerns について

ソフトウェア開発者向けの関心事の分離に関するコーディングガイドです。データ取得、ビジネスロジック、表示、各層の構成を分ける方針を、兆候や例、層ごとの説明とともに示し、テスト性・再利用性・保守性の向上につなげます。

主な機能

  1. 01データ・ロジック・表示の分離
  2. 02関心事混在の兆候の特定
  3. 03レイヤー化されたコード例
  4. 04テストと再利用の利点の説明

活用シーン

  1. 01データ取得と表示を担うコンポーネントのリファクタリング
  2. 02コントローラー内の DB クエリとロジックの分離
  3. 03単体テストとコード再利用の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yanko-belov/code-craft --skill separation-of-concerns

インストール後、Claude Code で/separation-of-concernsを入力して読み込まれたか確認します。

詳細

関心の分離

概要

各コードは一つのことだけを行うべきです。データ、ロジック、プレゼンテーションは分離する必要があります。

関心事が混在すると、テストできず、再利用も保守も難しいコードになります。分離することで、テスト、再利用、明瞭さが実現します。

使用する場面

  • コンポーネントがデータの取得、変換、表示を行っている
  • ビジネスロジックが UI コードに混在している
  • コントローラーにデータベースクエリがある
  • コードの一部を単独でテストするのが難しい

絶対的なルール

NEVER mix data fetching, business logic, and presentation in one place.

例外はありません:

  • 「小さなコンポーネントだから」という理由では不可
  • 「この方法のほうがシンプルだから」という理由では不可
  • 「一度しか使わないから」という理由では不可
  • 「動作するから」という理由では不可

検出:関心事の混在の兆候

一つのファイルで取得+変換+表示を行っている場合は、STOP:

// ❌ VIOLATION: Component does everything
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Data fetching
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        // Business logic / transformation
        const fullName = `${data.firstName} ${data.lastName}`;
        const memberSince = new Date(data.createdAt).toLocaleDateString();
        const isVIP = data.orderCount > 100;
        
        setUser({ ...data, fullName, memberSince, isVIP });
        setLoading(false);
      });
  }, [userId]);
  
  // Presentation
  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="user-profile">
      <h1>{user.fullName}</h1>
      {user.isVIP && <span className="vip-badge">VIP</span>}
      <p>Member since: {user.memberSince}</p>
    </div>
  );
}

問題点:

  • データを取得せずにフォーマットをテストできない
  • 取得ロジックを再利用できない
  • プレゼンテーションを再利用できない
  • コンポーネントが3つの役割を担っている

正しいパターン:レイヤーの分離

// ✅ CORRECT: Separated concerns

// 1. Data fetching (hook)
// hooks/useUser.ts
function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => {
        if (!res.ok) throw new Error('Failed to fetch');
        return res.json();
      })
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);
  
  return { user, loading, error };
}

// 2. Business logic (pure functions)
// utils/userFormatters.ts
interface FormattedUser {
  fullName: string;
  memberSince: string;
  isVIP: boolean;
}

function formatUser(user: User): FormattedUser {
  return {
    fullName: `${user.firstName} ${user.lastName}`,
    memberSince: new Date(user.createdAt).toLocaleDateString(),
    isVIP: user.orderCount > 100,
  };
}

// 3. Presentation (dumb component)
// components/UserCard.tsx
interface UserCardProps {
  fullName: string;
  memberSince: string;
  isVIP: boolean;
}

function UserCard({ fullName, memberSince, isVIP }: UserCardProps) {
  return (
    <div className="user-profile">
      <h1>{fullName}</h1>
      {isVIP && <span className="vip-badge">VIP</span>}
      <p>Member since: {memberSince}</p>
    </div>
  );
}

// 4. Composition (container component)
// pages/UserProfile.tsx
function UserProfile({ userId }) {
  const { user, loading, error } = useUser(userId);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <NotFound />;
  
  const formatted = formatUser(user);
  
  return <UserCard {...formatted} />;
}

分離のメリット

混在分離
フォーマットをテストできないformatUser() を単独でテストできる
取得処理を再利用できないuseUser() をどこでも再利用できる
UI を再利用できないUserCard をどのようなデータでも再利用できる
複雑なコンポーネントが1つシンプルな要素が4つ

レイヤー

1. データレイヤー(フック、サービス)
  • データの取得
  • API 呼び出し
  • 状態管理
  • ビジネスロジックは含めない
2. ロジックレイヤー(純粋関数)
  • データ変換
  • 計算
  • 検証
  • ビジネスルール
3. プレゼンテーションレイヤー(コンポーネント)
  • UI のレンダリング
  • スタイリング
  • ユーザー操作
  • データ取得は行わない
4. コンポジションレイヤー(コンテナー)
  • 各レイヤーを接続する
  • 読み込み状態/エラー状態を処理する
  • データを下位に渡す

プレッシャーに対抗するための指針

1. 「小さなコンポーネントだから」

プレッシャー:「シンプルなケースでは、分離はやりすぎです」

**回答:**小さなものは大きくなります。最初から整理し、整理された状態を保ちましょう。

**対応:**小さなコンポーネントでも分離しましょう。コストはほとんどかかりません。

2. 「この方法のほうがシンプルだから」

プレッシャー:「すべてを一か所にまとめたほうが理解しやすいです」

**回答:**関心事が混在しているとシンプルに見えますが、テスト、デバッグ、変更が困難になります。

**対応:**長期的には、分離したほうがシンプルです。

3. 「一度しか使わないから」

プレッシャー:「このコンポーネントは固有のもので、再利用しません」

**回答:**固有のコンポーネントでも、テスト可能であることは重要です。

**対応:**再利用だけでなく、テスト可能性のためにも分離しましょう。

危険信号 - STOPして再検討する

  • 取得+変換+setState を行う useEffect
  • レンダー関数内のビジネスロジック
  • ルートハンドラー内のデータベースクエリ
  • ユーティリティ関数内の API 呼び出し
  • 100行を超えるコンポーネント

これらはすべて、関心事を分離すべきサインです。

クイックリファレンス

関心事配置場所
API 呼び出しフック/サービス
データ変換純粋関数
ビジネスルール純粋関数
UI のレンダリングプレゼンテーションコンポーネント
要素の接続コンテナーコンポーネント

よくある合理化(すべて誤り)

言い訳実際のところ
「小さなコンポーネントだから」小さなものは大きくなります。今すぐ分離しましょう。
「一緒のほうがシンプル」分離すれば、テストや変更がより簡単になります。
「一度しか使わないから」テスト可能であることは重要です。
「動作するから」動作すること ≠ 保守できること。
「過剰設計だ」これは単なるエンジニアリングです。

要点

データ取得はフックに、ロジックは純粋関数に、UI はコンポーネントに配置しましょう。

分離することで、テスト、再利用、保守が可能になります。コンポーネントは、データの取得、変換、表示のいずれかを行うべきであり、3つすべてを行ってはいけません。

コメント