ルール
クラスは 単一責任であるべきです。
複数の 関心事を扱うクラスは、 単一責任の原則に違反します。
対応言語: 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つ持つことを保証することです。クラスが複数の関心事を扱い始めたら、各責任を焦点を絞ったインターフェースを持つ独自のクラスに抽出してリファクタリングします。これにより、無関係な機能にわたる連鎖的な変更なしに、コードのテスト、保守、進化が容易になります。

