ルール
クラス すべき 持つ 単一の 責任を持つべきである。
クラスは 処理 複数の 関心 違反する
単一 責任 原則。
サポート言語: JS、 TS、 PY、 JAVA、 C/C++、
C#、 Swift/Objective C、 Ruby. PHP、 Kotlin、
Scala、 Rust、 Haskell、 Groovy、 Dart。 Julia,
Elixit、 クロジュール、 OCaml、 デルファイはじめに
機能が多すぎるクラスはボトルネックとなる。認証、メール送信、バリデーションを扱うクラスは、いずれかの領域が進化するたびに変更が必要となり、無関係な機能の破損リスクを生む。テストでは、一部をテストする場合でもクラス全体をモックする必要がある。単一責任の原則は、クラスが変更される理由は一つだけであるべきと定めている。
なぜそれが重要なのか
コードの保守性:複数の責任を持つクラスは、いずれかの関心の進化がクラス全体に影響するため、より頻繁に変更される。
テストの複雑性:複数の責任を持つクラスのテストでは、たとえ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;
}
}
なぜこれが重要なのか: 各クラスには明確な責任が一つずつ割り当てられています:データの永続化、メール送信、イベント記録、またはオーケストレーションです。メールテンプレートの変更は メール通知サービスユーザー作成のテストでは、依存関係に対して単純なスタブを使用できます。クラスは異なる機能間で独立して再利用可能です。
結論
単一責任の原則は、クラスを可能な限り小さくすることではなく、各クラスが変更される明確な理由を一つだけ持つことを保証することです。クラスが複数の関心事を処理し始めたら、それぞれの責任を焦点を絞ったインターフェースを持つ独自のクラスに抽出することでリファクタリングします。これにより、無関係な機能間に変更が波及することなく、コードのテスト、保守、進化が容易になります。
.avif)
