Regola
Classi dovrebbero avere una sola responsabilità.
Le classi che gestiscono più aspetti viola
il Principio Responsabilità .
Linguaggi supportati: JS, TS, PY, JAVA, C/C++,
C#, Swift/Objective C, Ruby. PHP, Kotlin,
Scala, Rust, Haskell, Groovy, Dart. Julia,
Elixit, Klojure, OCaml, DelphiIntroduzione
Le classi che svolgono troppe funzioni diventano dei colli di bottiglia. Una classe che gestisce l'autenticazione, le e-mail e la convalida richiede modifiche ogni volta che uno di questi aspetti subisce un'evoluzione, con il rischio di compromettere funzionalità non correlate. Il testing richiede di simulare l'intera classe anche quando si testa un solo aspetto. Il principio della responsabilità unica stabilisce che una classe debba avere un solo motivo per cambiare.
Perché è importante
Manutenibilità del codice: le classi con responsabilità multiple vengono modificate più spesso, poiché l'evoluzione di qualsiasi aspetto influisce sull'intera classe.
Complessità dei test: per testare classi con più responsabilità è necessario simulare tutte le dipendenze, anche solo per testare una singola funzionalità.
Riutilizzabilità: non è possibile estrarre una singola responsabilità senza portare con sé tutte le dipendenze. Gli sviluppatori preferiscono duplicare il codice piuttosto che districare le classi con responsabilità multiple.
Coordinamento del team: quando più sviluppatori lavorano sulla stessa classe per implementare funzionalità diverse, si verificano frequenti conflitti di unione. Le classi a responsabilità singola consentono lo sviluppo 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 le operazioni sul database, l'invio delle e-mail, la registrazione degli eventi e la memorizzazione nella cache. Qualsiasi modifica ai modelli di e-mail, ai formati di registrazione degli eventi o alla strategia di memorizzazione nella cache richiede la modifica di questa classe. Testare la creazione di un utente comporta la simulazione dei servizi di posta elettronica, di analisi dei dati e della 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é è importante: Ogni classe ha una responsabilità ben definita: la persistenza dei dati, l'invio delle e-mail, la registrazione degli eventi o l'orchestrazione. Le modifiche ai modelli di e-mail influiscono solo su Servizio di notifica via e-mail. Per testare la creazione degli utenti è possibile utilizzare semplici stub per le dipendenze. Le classi possono essere riutilizzate in modo indipendente in diverse funzionalità.
Conclusione
Il principio della responsabilità unica non consiste nel rendere le classi il più piccole possibile, ma nel garantire che ogni classe abbia un unico motivo chiaro per essere modificata. Quando una classe inizia a gestire più aspetti, è opportuno rifattorizzarla estraendo ciascuna responsabilità in una classe a sé stante con un'interfaccia mirata. Ciò rende il codice più facile da testare, mantenere e far evolvere senza che le modifiche si ripercuotano a cascata su funzionalità non correlate.

