Princípios SOLID: Single Responsibility

1. O Princípio da Responsabilidade Única (SRP) na Arquitetura

O Princípio da Responsabilidade Única (SRP — Single Responsibility Principle) é o primeiro dos cinco princípios SOLID, formalizado por Robert C. Martin. Sua definição clássica estabelece que uma classe deve ter apenas um motivo para mudar. No contexto da arquitetura de software, essa definição se expande: cada módulo, componente ou camada deve ter uma única responsabilidade bem definida dentro do sistema.

O SRP é fundamental para a arquitetura porque determina como o software responde a mudanças. Quando uma classe ou módulo acumula múltiplas responsabilidades, uma alteração em qualquer delas pode quebrar funcionalidades aparentemente não relacionadas. Isso gera um efeito cascata de retrabalho e aumenta exponencialmente o custo de manutenção.

É importante distinguir responsabilidade única de código pequeno. Um método de três linhas que realiza três tarefas distintas viola o SRP, enquanto uma classe de duzentas linhas focada exclusivamente em uma única responsabilidade está perfeitamente alinhada ao princípio. O tamanho é consequência, não objetivo.

2. Identificando Responsabilidades Múltiplas no Código

Os sinais de violação do SRP são claros: classes com muitos métodos públicos não relacionados, dependências excessivas de frameworks externos e dificuldade crescente para nomear a classe de forma precisa. Uma classe chamada GerenciadorDeTudo é um alerta vermelho.

Considere o exemplo clássico de violação:

class Pedido {
    calcularTotal() { /* regras de negócio */ }
    validarItens() { /* validação */ }
    salvarNoBanco() { /* persistência */ }
    enviarEmailConfirmacao() { /* notificação */ }
    gerarRelatorioPDF() { /* geração de relatório */ }
}

Esta classe viola o SRP porque possui pelo menos cinco motivos para mudar: alteração nas regras de cálculo, mudança no esquema do banco, novo template de e-mail, novo formato de relatório, etc. Ferramentas de análise estática como SonarQube e linters de design ajudam a detectar esse acoplamento excessivo através de métricas de complexidade ciclomática e dependências.

3. Coesão vs Acoplamento: A Base do SRP

O SRP promove alta coesão (métodos fortemente relacionados dentro de uma mesma classe) e baixo acoplamento (dependências mínimas entre classes distintas). Quando uma classe tem responsabilidade única, seus métodos naturalmente compartilham dados e objetivos comuns, facilitando a compreensão e manutenção.

A relação com a complexidade acidental é direta: responsabilidades múltiplas introduzem complexidade acidental — aquela que não pertence ao domínio do problema, mas surge da má organização do código. Um sistema que respeita o SRP reduz drasticamente esse tipo de complexidade.

A métrica LCOM (Lack of Cohesion of Methods) quantifica esse conceito. LCOM alto indica que os métodos de uma classe compartilham poucos atributos, sugerindo múltiplas responsabilidades. Ferramentas como JDepend e NDepend calculam automaticamente essa métrica, orientando refatorações.

4. Aplicando SRP na Camada de Domínio

A camada de domínio é onde o SRP tem maior impacto. Regras de negócio precisam estar isoladas de preocupações técnicas como persistência, serialização ou interface com usuário.

Exemplo de refatoração — antes da aplicação do SRP:

class Pedido {
    itens: List<Item>
    desconto: float

    calcularTotal() {
        total = 0
        for item in itens:
            total += item.preco * item.quantidade
        return total * (1 - desconto)
    }

    salvar() {
        conexao = Database.conectar("servidor:3306")
        query = "INSERT INTO pedidos VALUES (...)"
        conexao.executar(query)
    }
}

Após aplicar SRP, separamos responsabilidades:

class Pedido {
    itens: List<Item>
    desconto: float

    calcularTotal() {
        total = 0
        for item in itens:
            total += item.preco * item.quantidade
        return total * (1 - desconto)
    }
}

class PedidoRepository {
    salvar(pedido: Pedido) {
        conexao = Database.conectar("servidor:3306")
        query = "INSERT INTO pedidos VALUES (...)"
        conexao.executar(query)
    }
}

Agora, Pedido contém apenas regras de negócio, enquanto PedidoRepository gerencia persistência. Mudanças no banco afetam apenas o repositório; mudanças nas regras de cálculo afetam apenas a entidade.

5. SRP em Padrões Arquiteturais

O SRP se manifesta de forma natural em arquiteturas modernas:

  • MVC: Model (regras de negócio), View (apresentação), Controller (orquestração) — cada um com responsabilidade única.
  • Clean Architecture: Cada círculo concêntrico tem responsabilidade distinta — entidades, casos de uso, adaptadores, frameworks.
  • Arquitetura Hexagonal: Portas e adaptadores separam o núcleo do domínio da infraestrutura externa.

Exemplo de arquitetura em camadas respeitando SRP:

// Camada de Domínio
class Usuario {
    validarEmail(): boolean { ... }
    alterarSenha(nova: string): void { ... }
}

// Camada de Aplicação (Casos de Uso)
class RegistrarUsuario {
    executar(dados: DadosRegistro): Resultado {
        // orquestra validação, persistência, notificação
    }
}

// Camada de Infraestrutura
class UsuarioRepository {
    salvar(usuario: Usuario): void { ... }
}

class EmailService {
    enviarBoasVindas(usuario: Usuario): void { ... }
}

Cada classe tem exatamente uma responsabilidade, e as dependências fluem das camadas externas para o centro.

6. Armadilhas e Mitos Comuns

O mito mais comum é interpretar "responsabilidade única" como "uma classe deve fazer uma única coisa". Na prática, uma classe pode ter vários métodos, desde que todos sirvam ao mesmo propósito coeso. Uma classe CalculadoraFinanceira pode ter calcularJuros, calcularAmortizacao e calcularPrestacao — todas servem ao propósito de cálculos financeiros.

Outra armadilha é a divisão excessiva. Criar uma classe para cada operação mínima leva ao design fragmentado, onde a lógica de negócio se espalha por dezenas de arquivos, dificultando a compreensão do fluxo completo. O SRP busca equilíbrio: responsabilidades claras, mas não atomização cega.

Há também um trade-off com desempenho. Muitas classes pequenas podem aumentar a indireção e o número de chamadas de método, impactando performance em sistemas críticos. Nesses casos, é aceitável violar temporariamente o SRP em regiões de hot path, desde que documentado e isolado.

7. Refatoração Guiada pelo SRP

Refatorar código legado para respeitar o SRP segue um processo incremental:

  1. Identifique classes com múltiplas responsabilidades — procure métodos que acessam diferentes subsistemas (banco, arquivo, rede, UI).
  2. Extraia responsabilidades secundárias — crie novas classes para cada responsabilidade distinta.
  3. Injete dependências — use injeção de dependência para conectar as partes.
  4. Teste cada unidade isoladamente.

Exemplo de refatoração de um módulo de relatórios:

// Código original — violação
class RelatorioVendas {
    gerarCSV() { /* formata dados */ }
    gerarJSON() { /* formata dados */ }
    conectarBanco() { /* acesso a dados */ }
    enviarPorEmail() { /* notificação */ }
}

// Após SRP
class RelatorioVendas {
    gerarCSV(): string { ... }
    gerarJSON(): string { ... }
}

class VendasRepository {
    consultarVendas(periodo): List<Venda> { ... }
}

class EmailService {
    enviarRelatorio(destinatario, arquivo): void { ... }
}

A testabilidade é um benefício colateral direto. Classes SRP têm dependências explícitas e escopo limitado, permitindo testes unitários focados sem necessidade de mocks complexos.

8. SRP e Manutenibilidade de Longo Prazo

O impacto do SRP na evolução do software é profundo. Quando cada responsabilidade está isolada, mudanças são localizadas e seguras. Uma alteração na regra de cálculo de impostos não afeta a geração de relatórios nem a persistência. Isso reduz drasticamente o risco de regressões.

O SRP estabelece a base para o princípio Open/Closed (OCP). Módulos com responsabilidade única são naturalmente mais fáceis de estender sem modificar — você pode adicionar um novo formato de relatório criando uma nova classe, sem alterar as existentes.

Um estudo de caso hipotético ilustra bem: um sistema de e-commerce que violou SRP tinha uma classe Pedido com 4000 linhas, responsável por cálculo, validação, estoque, faturamento e logística. Cada nova funcionalidade exigia semanas de análise de impacto. Após refatoração guiada pelo SRP, o mesmo sistema passou a ter 15 classes coesas, cada uma com menos de 200 linhas. O tempo médio para implementar novas features caiu 70%, e a taxa de bugs em produção reduziu drasticamente.

A manutenibilidade de longo prazo é o maior legado do SRP. Sistemas que respeitam esse princípio envelhecem com graça — novas equipes conseguem entender e modificar o código com segurança, e o custo de evolução permanece previsível ao longo dos anos.

Referências