PrincípioIniciante6 min de leitura9 seções2002, Data aproximada

SRP — Responsabilidade Única

Um módulo deve ter apenas uma razão para mudar — ou seja, deve responder a um único interessado do negócio, e não acumular decisões que evoluem por motivos diferentes.

Em 10 segundos

Uma razão para mudar, não uma tarefa só. Se a área fiscal, o time de banco e o pessoal de relatório pedem alterações no mesmo arquivo, ele tem três responsabilidades — mesmo que todos os métodos falem de 'pedido'.

Antes de ter nome

A ideia de coesão — juntar o que muda pelo mesmo motivo — vem do 'structured design' de Constantine e Yourdon, nos anos 1970.

  • #solid
  • #coesao
  • #acoplamento
  • #design
Analogia

O canivete suíço na oficina

O canivete suíço é genial para viagem e péssimo para uma oficina. Quando a chave de fenda entorta, você troca a peça inteira — inclusive a tesoura, que estava ótima. Na oficina, cada ferramenta é separada porque cada uma se desgasta no seu ritmo e por motivos próprios. Nada impede que fiquem na mesma gaveta; o que não podem é ser o mesmo objeto.

01Conceito

O problema

O acoplamento mais caro não é o técnico, é o organizacional: quando duas áreas do negócio precisam alterar o mesmo código, as mudanças de uma quebram as expectativas da outra.

Uma classe que serve a vários interessados vira ponto de encontro de decisões não relacionadas — e cada release fica mais arriscado.

A formulação original de Robert Martin é frequentemente citada errado. 'Fazer uma coisa só' é uma simplificação que leva a classes anêmicas com um método cada. A definição que ele mesmo passou a usar é mais útil: um módulo deve ser responsável perante um, e apenas um, ator.

Ator aqui é papel do negócio: o setor fiscal, o time de infraestrutura, o departamento comercial. Dois métodos que parecem irmãos podem pertencer a atores diferentes — e é essa diferença, não a semelhança técnica, que determina a separação.

O princípio é, no fundo, sobre coesão: reunir o que muda junto e separar o que muda por motivos distintos. É o mesmo raciocínio que sustenta a arquitetura hexagonal (domínio separado de infraestrutura) e o OCP (isolar o que varia) — SRP é a versão em escala de classe.

02Conceito

Como identificar os atores

Na prática, o teste que funciona é histórico: olhe quem pediu as últimas mudanças naquele arquivo e por quê.

O histórico do controle de versão é a melhor fonte. Se os commits de um arquivo alternam entre 'ajuste na alíquota', 'migração para o novo banco' e 'novo layout da nota', os três atores estão declarados ali, com data e autor.

Outro sinal é o conflito de merge recorrente entre pessoas de times diferentes no mesmo arquivo. Isso é o princípio sendo violado de forma visível — e custa tempo real, não apenas elegância.

O sinal mais simples é linguístico: se descrever a classe exige um 'e' ('cuida do pedido e calcula imposto e gera PDF'), cada 'e' costuma ser um ator. Nomes honestos denunciam o problema antes de qualquer análise.

Sem o padrão × com o padrão

Um módulo por razão de mudar

  • Pedido guarda a regra de negócio
  • CalculadoraImpostos muda com a legislação
  • RepositorioPedidos muda com o banco
  • EmissorNota muda com o layout

O custo é mais arquivos e a necessidade de compor as peças em algum lugar — normalmente um caso de uso, que passa a ser onde a coordenação vive.

A separação não reduz a quantidade de código: ela alinha as fronteiras do sistema com as fronteiras da organização, de forma que uma mudança de negócio toque um arquivo só.
Como se organiza
  • Pedidoregra de negócio — muda com o produto

  • CalculadoraImpostosator: área fiscal

  • RepositorioPedidosator: time de dados

  • EmissorNotaator: área contábil

Quatro caixas lado a lado, não aninhadas: nenhuma contém a outra, e cada uma responde a um interessado diferente. O Pedido é usado pelas três, mas não sabe de nenhuma.
03Anatomia

Navegue pelas camadas

Camada 1/3O ator

Camada 1

O ator

quem pede a mudança

Papel do negócio que motiva alterações: área fiscal, jurídico, marketing, infraestrutura. É a unidade de análise do princípio — não a funcionalidade, não a camada técnica.

No código

// 'a área fiscal mudou a alíquota' → CalculadoraImpostos

Se violar: quando dois atores dependem do mesmo arquivo, uma mudança pedida por um pode quebrar o que o outro esperava — sem que ninguém tenha errado.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// ANTES: tres interessados diferentes na mesma classe
class PedidoRuim {
  itens: { preco: number; qtd: number }[] = [];

  total(): number {                    // regra de negocio
    return this.itens.reduce((s, i) => s + i.preco * i.qtd, 0);
  }
  calcularImposto(): number {          // muda quando a legislacao muda
    return this.total() * 0.18;
  }
  salvar(): void {                     // muda quando o banco muda
    console.log("INSERT INTO pedidos ...");
  }
  gerarPdfNota(): string {             // muda quando o layout muda
    return "%PDF-1.4 ...";
  }
}

// DEPOIS: cada motivo de mudanca no seu lugar
class Pedido {
  constructor(readonly itens: { preco: number; qtd: number }[]) {}
  total(): number {
    return this.itens.reduce((s, i) => s + i.preco * i.qtd, 0);
  }
}

// muda quando a LEGISLACAO muda
class CalculadoraImpostos {
  calcular(p: Pedido): number {
    return p.total() * 0.18;
  }
}

// muda quando a PERSISTENCIA muda
class RepositorioPedidos {
  salvar(p: Pedido): void {
    console.log("INSERT INTO pedidos ...", p.total());
  }
}

// muda quando o LAYOUT da nota muda
class EmissorNota {
  emitir(p: Pedido): string {
    return "%PDF-1.4 total=" + p.total();
  }
}
06No mundo real

Casos de uso

Separar cálculo fiscal do modelo de pedido

Cenário

Uma reforma tributária muda as regras de imposto a cada trimestre, enquanto o modelo de pedido permanece estável há dois anos.

Como o padrão entra

O cálculo fiscal sai para um módulo próprio, versionado por vigência. Alterações fiscais deixam de tocar a classe que o resto do sistema usa para tudo.

Trade-off: Passa a ser necessário decidir onde o imposto é aplicado e garantir que ninguém esqueça de chamá-lo — o acoplamento sumiu, mas surgiu a obrigação de compor corretamente.

Extrair envio de e-mail de serviço de cadastro

Cenário

O serviço que cria usuários também monta e envia o e-mail de boas-vindas, e o time de marketing pede ajustes semanais no texto.

Como o padrão entra

O envio vira um módulo separado, acionado por evento. Marketing altera o conteúdo sem que ninguém encoste na regra de criação de conta.

Trade-off: A operação deixa de ser atômica: o usuário pode ser criado e o e-mail falhar. Isso exige decidir explicitamente o que acontece nesse caso, decisão que o acoplamento anterior escondia.

Dividir um controller que virou orquestrador

Cenário

Um endpoint de checkout valida entrada, aplica desconto, chama o gateway, grava e dispara notificação — quinhentas linhas que quatro times editam.

Como o padrão entra

Cada preocupação vira um colaborador e o controller passa a apenas coordenar. Os conflitos de merge caem porque cada time trabalha no seu arquivo.

Trade-off: Entender o fluxo completo passa a exigir abrir cinco arquivos em vez de um. Sem bons nomes e um ponto de composição legível, a clareza perdida pode superar o ganho.

Passo a passoComo aplicar, passo a passo
  1. 1

    Nomear os atores

    Pergunte quem pede mudança neste arquivo: fiscal, dados, layout, marketing. Cada resposta diferente é um candidato a módulo.

  2. 2

    Separar por razão de mudar

    O que muda junto fica junto; o que muda por motivo distinto sai. O critério é o interessado, não a contagem de métodos.

  3. 3

    Compor num ponto só

    Um caso de uso ou serviço de aplicação orquestra as peças. Sem isso, cada chamador remonta a sequência do seu jeito.

O SRP que só escondeu os métodos

Compila, roda, passa no code review. E quebra depois.

A classe 'emagreceu' na API pública: imposto e PDF viraram private. Parece organizado — mas os três atores ainda moram no mesmo arquivo e ainda conflitam no mesmo diff.

// "Aplicamos SRP": imposto e PDF viraram metodos privados.
// Os tres atores continuam no mesmo arquivo — so mudaram de endereco.
class Pedido {
  itens: { preco: number; qtd: number }[] = [];

  total(): number {
    return this.itens.reduce((s, i) => s + i.preco * i.qtd, 0);
  }

  salvar(): void {
    const imposto = this.calcularImposto(); // ator: fiscal
    db.insert({ total: this.total(), imposto }); // ator: dados
    this.gerarPdf();                            // ator: layout
  }

  private calcularImposto(): number { return this.total() * 0.18; }
  private gerarPdf(): string { return "%PDF-1.4 ..."; }
}

// Reforma tributaria, migracao de banco e novo layout da nota
// ainda abrem o MESMO arquivo. O principio nao e sobre private.

O que quebra

  • Na reforma tributáriaO PR de alíquota ainda toca a classe que salva no banco e gera PDF — o risco de regressão não mudou.
  • No mergeTimes diferentes continuam conflitando no mesmo arquivo; só o nome dos métodos privados mudou.
  • No testeTestar o total ainda arrasta persistência e geração de nota, porque tudo continua acoplado por dentro.

A saída: Extrair de verdade: CalculadoraImpostos, RepositorioPedidos e EmissorNota em módulos distintos. private não é fronteira — módulo e ator são.

08Cuidado

Armadilhas

  1. Confundir com 'faz uma coisa só'

    Levar a leitura literal ao extremo produz classes de um método, e o sistema vira uma nuvem de peças minúsculas onde nenhuma responde sozinha a uma pergunta de negócio. O critério é razão para mudar; uma classe coesa com dez métodos que servem ao mesmo ator está correta.

  2. Separar antes de saber os eixos

    Sem histórico de mudanças, dividir é adivinhação — e a divisão errada é pior que a ausência de divisão, porque cria fronteiras que toda mudança precisa atravessar. Em código novo, é razoável deixar junto até que os motivos de mudança se revelem.

  3. Modelo anêmico como efeito colateral

    Levar toda a lógica para fora das entidades em nome do SRP deixa o domínio como um saco de getters e setters, com as regras espalhadas em serviços. Regra de negócio que pertence ao pedido deve ficar no pedido; o que sai são as responsabilidades de outros atores.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Uma classe muda por motivos que nada têm a ver entre si.
  • Times diferentes conflitam no mesmo arquivo.
  • Descrever a classe exige um 'e' no meio da frase.

Evite quando

3
  • Dividir por contagem de linhas ou métodos.
  • Separar antes de conhecer os eixos reais de mudança.
  • Fragmentar código pequeno e estável.

Testar o que ficou

3 armadilhas de SRP — Responsabilidade Única viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • ferramentas Unix (grep, sort, wc)A filosofia 'faça uma coisa bem feita': cada utilitário tem uma única razão para existir, e você os combina por pipe em vez de inchar um só.
  • controller × service × repositoryA divisão clássica do backend separa quem fala HTTP, quem tem a regra de negócio e quem fala com o banco — cada camada muda por um motivo.
  • custom hooks no ReactExtrair a lógica de estado para um hook próprio tira do componente a responsabilidade que não era de renderização — uma razão de cada vez.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Uma razão para mudar por módulo.
class CalculadoraTotal { total(itens) { /* ... */ } }

O que ele cobra

Nenhum salto a mais entre a chamada e o efeito.

  • Mais módulos e arquivos, cada um com uma responsabilidade, aumentam a navegação
  • Dividir cedo demais, antes de saber o que muda junto, cria fronteiras no lugar errado

Não vale se: o módulo é pequeno e coeso e não há dois interessados distintos puxando-o — quebrar por quebrar só espalha o que estava junto.

Onde isto aparece

Continue por aqui