Aikido

Por que as classes devem seguir o princípio da responsabilidade única

Legibilidade

Regra
Aulas devem ter responsabilidade responsabilidade.
As classes que lidam com várias preocupações viola
o Responsabilidade Princípio .

Idiomas suportados: JS, TS, PY, JAVA, C/C++,
C#, Swift/Objective C, Ruby. PHP, Kotlin, 
Scala, Rust, Haskell, Groovy, Dart. Julia,
Elixit, Klojure, OCaml, Delphi

Introdução

Classes que fazem demais tornam-se gargalos. Uma classe que lida com autenticação, e-mails e validação exige mudanças sempre que qualquer preocupação evolui, arriscando quebras em funcionalidades não relacionadas. Testar exige a simulação (mocking) da classe inteira, mesmo ao testar apenas um aspecto. O Princípio da Responsabilidade Única afirma que uma classe deve ter apenas uma razão para mudar.

Por que isso importa

Manutenibilidade do código: Classes com múltiplas responsabilidades mudam com mais frequência porque a evolução de qualquer preocupação afeta a classe inteira.

Complexidade de teste: Testar classes com múltiplas responsabilidades exige o mocking de todas as dependências, mesmo para testar uma única funcionalidade.

Reutilização: Não é possível extrair uma responsabilidade sem trazer todas as dependências. Desenvolvedores duplicam código em vez de desvincular classes com múltiplas responsabilidades.

Coordenação da equipe: Múltiplos desenvolvedores trabalhando na mesma classe para diferentes funcionalidades criam conflitos de merge frequentes. Classes de responsabilidade única permitem o desenvolvimento paralelo sem conflitos.

Exemplos de código

❌ Não-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() });
    }
}

Por que está errado: Esta classe lida com operações de banco de dados, envio de e-mail, logging e caching. Alterações em templates de e-mail, formatos de logging ou estratégia de cache exigem a modificação desta classe. Testar a criação de usuários significa simular serviços de e-mail, analytics e cache, tornando os testes lentos e frágeis.

✅ Compatível:

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;
    }
}

Por que isso importa: Cada classe tem uma responsabilidade clara: persistência de dados, envio de e-mail, registro de eventos ou orquestração. Alterações nos modelos de e-mail afetam apenas EmailNotificationService. O teste de criação de usuário pode usar stubs simples para dependências. As classes podem ser reutilizadas independentemente em diferentes funcionalidades.

Conclusão

O Princípio da Responsabilidade Única não é sobre tornar as classes o menor possível, é sobre garantir que cada classe tenha uma razão clara para mudar. Quando uma classe começa a lidar com múltiplas preocupações, refatore extraindo cada responsabilidade para sua própria classe com uma interface focada. Isso torna o código mais fácil de testar, manter e evoluir sem mudanças em cascata em funcionalidades não relacionadas.

FAQs

Dúvidas?

Como identificar quando uma classe tem muitas responsabilidades?

Procure por classes com múltiplos motivos para mudar. Se a modificação da lógica de e-mail, do formato de logging e do esquema do banco de dados exigirem a alteração da mesma classe, ela tem muitas responsabilidades. Verifique os nomes dos métodos: se eles cobrem verbos não relacionados como `sendEmail()`, `logEvent()` e `validateData()` na mesma classe, isso é um sinal de alerta. Classes com mais de 300-400 linhas frequentemente indicam múltiplas responsabilidades, embora o tamanho por si só não seja definitivo.

Dividir classes não cria mais arquivos e complexidade?

Mais arquivos não significa mais complexidade. Dez classes focadas de 50 linhas cada são mais fáceis de entender do que uma classe de 500 linhas que lida com tudo. A chave é que cada classe seja simples e tenha um propósito claro. A navegação em IDEs modernas torna a contagem de arquivos irrelevante. A redução da complexidade vem da capacidade de raciocinar sobre cada classe de forma independente, sem considerar preocupações não relacionadas.

E quanto às classes que naturalmente precisam coordenar múltiplas operações?

A coordenação é, por si só, uma responsabilidade. Uma classe UserService pode orquestrar chamadas para UserRepository, EmailService e EventLogger sem implementar essas preocupações diretamente. Este é o padrão orquestrador ou facade. A diferença é que o orquestrador delega a classes especializadas em vez de implementar múltiplas preocupações diretamente. É um código de 'cola' (glue code) enxuto, não lógica de negócio.

Como esse princípio se aplica a classes utilitárias com métodos estáticos?

Classes utilitárias são particularmente propensas a violar o princípio da responsabilidade única, pois é fácil continuar adicionando métodos estáticos não relacionados. Uma classe StringUtils pode começar com auxiliares de formatação, mas crescer para incluir validação, parsing, criptografia e codificação. Divida-as em classes utilitárias focadas, como StringFormatter, StringValidator e StringEncoder. Cada uma terá um conjunto coeso de operações relacionadas.

Como refatorar classes existentes que violam este princípio?

Comece identificando responsabilidades distintas dentro da classe. Extraia a mais fácil primeiro para uma nova classe, atualize os testes e verifique se tudo funciona. Repita iterativamente em vez de tentar uma grande refatoração. Use o padrão strangler fig: crie novas classes de responsabilidade única e mova gradualmente o código da classe antiga. Assim que a classe antiga estiver vazia ou mínima, descontinue-a. Cada etapa deve ser um incremento funcional e testável.

Responsabilidade única significa método único?

Não. Uma classe pode ter múltiplos métodos, desde que todos estejam relacionados à mesma responsabilidade. Uma classe UserRepository pode ter métodos create(), update(), delete() e findById() porque todos servem à única responsabilidade de persistência de dados do usuário. Os métodos são variações coesas da mesma preocupação, não preocupações separadas empacotadas juntas.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.