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.
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.
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.
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.
Pedidoregra de negócio — muda com o produto
CalculadoraImpostosator: área fiscal
RepositorioPedidosator: time de dados
EmissorNotaator: área contábil
Navegue pelas camadas
Camada 1/3O ator
use ← →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' → CalculadoraImpostosSe 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.
Estrutura
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();
}
}# ANTES: tres interessados diferentes na mesma classe
class PedidoRuim:
def __init__(self, itens):
self.itens = itens
def total(self): # regra de negocio
return sum(i["preco"] * i["qtd"] for i in self.itens)
def calcular_imposto(self): # muda quando a legislacao muda
return self.total() * 0.18
def salvar(self): # muda quando o banco muda
print("INSERT INTO pedidos ...")
def gerar_pdf_nota(self): # muda quando o layout muda
return "%PDF-1.4 ..."
# DEPOIS: cada motivo de mudanca no seu lugar
class Pedido:
def __init__(self, itens):
self.itens = itens
def total(self):
return sum(i["preco"] * i["qtd"] for i in self.itens)
class CalculadoraImpostos: # muda com a LEGISLACAO
def calcular(self, p):
return p.total() * 0.18
class RepositorioPedidos: # muda com a PERSISTENCIA
def salvar(self, p):
print("INSERT INTO pedidos ...", p.total())
class EmissorNota: # muda com o LAYOUT
def emitir(self, p):
return f"%PDF-1.4 total={p.total()}"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.
- 1
Nomear os atores
Pergunte quem pede mudança neste arquivo: fiscal, dados, layout, marketing. Cada resposta diferente é um candidato a módulo.
- 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
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.
Armadilhas
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.
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.
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.
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
- OCP — Aberto/FechadoMódulos devem ser abertos para extensão e fechados para modificação: adicionar comportamento novo deveria significar escrever código novo, não reabrir e alterar o que já funciona.
- ISP — Segregação de InterfacesNenhum cliente deve ser forçado a depender de métodos que não usa: é melhor várias interfaces pequenas e específicas do que uma grande e genérica.
- Arquitetura HexagonalIsola o domínio no centro e o conecta ao mundo externo por portas (interfaces) e adaptadores (implementações), tornando infraestrutura um detalhe plugável.