Regola
Le classi dovrebbero avere una responsabilità singola.
Le classi che gestiscono molteplici aspetti violano
il Principio di Responsabilità Singola.
Linguaggi supportati: JS, TS, PY, JAVA, C/C++,
C#, Swift/Objective C, Ruby. PHP, Kotlin,
Scala, Rust, Haskell, Groovy, Dart. Julia,
Elixir, Clojure, OCaml, DelphiIntroduzione
Le classi che fanno troppo diventano dei colli di bottiglia. Una classe che gestisce autenticazione, email e validazione richiede modifiche ogni volta che evolve uno qualsiasi di questi aspetti, rischiando di compromettere funzionalità non correlate. I test richiedono il mocking dell'intera classe anche quando si testa un solo aspetto. Il Principio di Responsabilità Singola stabilisce che una classe dovrebbe avere un solo motivo per cambiare.
Perché è importante
Manutenibilità del codice: Le classi con molteplici responsabilità cambiano più spesso perché l'evoluzione di qualsiasi aspetto influisce sull'intera classe.
Complessità dei test: Testare classi con responsabilità multiple richiede il mocking di tutte le dipendenze, anche per testare una singola funzionalità.
Riutilizzabilità: Non è possibile estrarre una responsabilità senza portarsi dietro tutte le dipendenze. Gli sviluppatori duplicano il codice anziché districare classi con responsabilità multiple.
Coordinamento del team: Più sviluppatori che lavorano sulla stessa classe per funzionalità diverse creano frequenti conflitti di merge. Le classi a responsabilità singola consentono lo sviluppo in parallelo senza conflitti.
Esempi di codice
❌ Non conforme:
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() });
}
}
Perché è sbagliato: Questa classe gestisce operazioni di database, invio di email, logging e caching. Modifiche ai template delle email, ai formati di log o alla strategia di cache richiedono la modifica di questa classe. Testare la creazione di un utente significa effettuare il mock dei servizi email, di analisi e di cache, rendendo i test lenti e fragili.
✅ Conforme:
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;
}
}
Perché questo è importante: Ciascuna classe ha una sola responsabilità chiara: persistenza dei dati, invio di email, logging degli eventi o orchestrazione. Le modifiche ai template delle email influiscono solo su EmailNotificationService. Testare la creazione di utenti può utilizzare semplici stub per le dipendenze. Le classi possono essere riutilizzate in modo indipendente per diverse funzionalità.
Conclusione
Il Principio di Responsabilità Singola non consiste nel rendere le classi il più piccole possibile, ma nel garantire che ciascuna classe abbia un solo motivo chiaro per cambiare. Quando una classe inizia a gestire più aspetti, procedi alla rifattorizzazione estraendo ciascuna responsabilità nella propria classe con un'interfaccia mirata. Questo rende il codice più facile da testare, mantenere ed evolvere senza modifiche a cascata su funzionalità non correlate.

