Aikido

クラスが単一責任の原則に従うべき理由

可読性

ルール
クラスは 単一責任であるべきです。 
複数の 関心事を扱うクラスは、 単一責任の原則に違反します。 

対応言語: JS, TS, PY, JAVA, C/C++,
C#, Swift/Objective C, Ruby, PHP, Kotlin, 
Scala, Rust, Haskell, Groovy, Dart, Julia,
Elixit, Klojure, OCaml, Delphi

はじめに

多くの機能を担うクラスはボトルネックになります。認証、メール、検証を処理するクラスは、いずれかの懸念事項が変化するたびに変更が必要となり、無関係な機能に破損が生じるリスクがあります。テストでは、1つの側面をテストする場合でも、クラス全体をモックする必要があります。単一責任の原則は、クラスが変更されるべき理由を1つだけ持つべきであると述べています。

なぜ重要なのか

コードの保守性:複数の責務を持つクラスは、いずれかの関心の進化がクラス全体に影響を与えるため、より頻繁に変更されます。

テストの複雑さ: 複数の責任を持つクラスをテストするには、1つの機能をテストする場合でも、すべての依存関係をモックする必要があります。

再利用性: すべての依存関係を伴わずに、1つの責任を抽出することはできません。開発者は、多重責任クラスを解きほぐすよりも、コードを複製する傾向があります。

チーム連携: 複数の開発者が異なる機能のために同じクラスで作業すると、頻繁にマージコンフリクトが発生します。単一責任のクラスは、コンフリクトなしに並行開発を可能にします。

コード例

❌ 非準拠:

class UserManager {
    async createUser(userData) {
        const user = await db.users.insert(userData);
        await this.sendWelcomeEmail(user.email);
        await this.logEvent('user_created', user.id);
        await cache.set(`user:${user.id}`, user);
        return user;
    }

    async sendWelcomeEmail(email) {
        const template = this.loadEmailTemplate('welcome');
        await emailService.send(email, template);
    }

    async logEvent(event, userId) {
        await analytics.track(event, { userId, timestamp: Date.now() });
    }
}

誤っている理由: このクラスは、データベース操作、メール送信、ロギング、およびキャッシュを処理します。メールテンプレート、ロギング形式、またはキャッシュ戦略の変更はすべて、このクラスの修正を必要とします。ユーザー作成のテストは、メールサービス、分析、およびキャッシュをモックすることを意味し、テストを遅く、脆いものにします。

✅ 準拠済み:

class UserRepository {
    async create(userData) {
        return await db.users.insert(userData);
    }
}

class EmailNotificationService {
    async sendWelcomeEmail(email) {
        const template = await this.templateLoader.load('welcome');
        return await this.emailSender.send(email, template);
    }
}

class UserEventLogger {
    async logCreation(userId) {
        return await this.analytics.track('user_created', {
            userId,
            timestamp: Date.now()
        });
    }
}

class UserService {
    constructor(repository, emailService, eventLogger, cache) {
        this.repository = repository;
        this.emailService = emailService;
        this.eventLogger = eventLogger;
        this.cache = cache;
    }

    async createUser(userData) {
        const user = await this.repository.create(userData);
        await Promise.all([
            this.emailService.sendWelcomeEmail(user.email),
            this.eventLogger.logCreation(user.id),
            this.cache.set(`user:${user.id}`, user)
        ]);
        return user;
    }
}

これが重要である理由: 各クラスは、データ永続化、メール送信、イベントロギング、またはオーケストレーションという1つの明確な責任を持ちます。メールテンプレートへの変更は、~のみに影響します。 EmailNotificationService。ユーザー作成のテストでは、依存関係にシンプルなスタブを使用できます。クラスは異なる機能間で独立して再利用できます。

まとめ

単一責任の原則は、クラスを可能な限り小さくすることではなく、各クラスが変更される明確な理由を1つ持つことを保証することです。クラスが複数の関心事を扱い始めたら、各責任を焦点を絞ったインターフェースを持つ独自のクラスに抽出してリファクタリングします。これにより、無関係な機能にわたる連鎖的な変更なしに、コードのテスト、保守、進化が容易になります。

よくある質問

ご質問がありますか?

クラスの責務が多すぎる状況をどのように特定しますか?

変更する複数の理由があるクラスを探してください。メールロジック、ロギング形式、データベーススキーマの変更がすべて同じクラスの変更を必要とする場合、そのクラスは多くの責務を負いすぎています。メソッド名を確認してください。もし同じクラス内でsendEmail()、logEvent()、validateData()のような無関係な動詞をカバーしている場合、それは危険信号です。300〜400行を超えるクラスは、多くの場合、複数の責務を示していますが、サイズだけでは決定的ではありません。

クラスを分割すると、より多くのファイルと複雑さが生み出されませんか?

ファイル数が増えても、必ずしも複雑さが増すわけではありません。500行の単一クラスで全てを処理するよりも、それぞれ50行の明確な目的を持つ10個のクラスの方が理解しやすいでしょう。重要なのは、各クラスがシンプルで明確な目的を持っていることです。最新のIDEでは、ファイル数はもはや関係ありません。複雑さの軽減は、関連性のない懸念事項を考慮せずに、各クラスについて独立して推論できることから得られます。

複数の操作を自然に調整する必要があるクラスについてはどうですか?

調整自体が責任です。UserServiceクラスは、UserRepository、EmailService、EventLoggerへの呼び出しを、それらの懸念事項自体を実装することなくオーケストレーションできます。これはオーケストレーターパターンまたはファサードパターンです。違いは、オーケストレーターが複数の懸念事項を直接実装するのではなく、専門のクラスに委譲することです。それは薄いグルーコードであり、ビジネスロジックではありません。

この原則は、静的メソッドを持つユーティリティクラスにどのように適用されますか?

ユーティリティクラスは、無関係な静的メソッドを簡単に追加し続けられるため、単一責任の原則に違反しやすい傾向があります。StringUtilsクラスは、フォーマットヘルパーから始まり、検証、解析、暗号化、エンコーディングを含むように肥大化する可能性があります。これらをStringFormatter、StringValidator、StringEncoderのような、より焦点を絞ったユーティリティクラスに分割してください。それぞれが関連する操作のまとまりのあるセットを持っています。

この原則に違反している既存のクラスを、どのようにリファクタリングしますか?

まず、クラス内の明確な責務を特定します。最も簡単なものから新しいクラスに抽出し、テストを更新し、すべてが機能することを確認します。大規模なリファクタリングを試みるのではなく、反復的に繰り返します。ストラングラーフィグパターンを使用し、新しい単一責務のクラスを作成して、古いクラスからコードを徐々に移動させます。古いクラスが空になるか最小限になったら、非推奨にします。各ステップは、動作し、テスト可能なインクリメントであるべきです。

単一責任は単一のメソッドを意味しますか?

いいえ。クラスは、すべてが同じ責務に関連している限り、複数のメソッドを持つことができます。UserRepositoryクラスは、ユーザーデータの永続化という単一の責務を果たすため、create()、update()、delete()、findById()メソッドを持つかもしれません。これらのメソッドは、同じ関心の凝集性のあるバリエーションであり、別々の関心をまとめたものではありません。

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です | スキャン結果は32秒で表示されます。