Publicado em

- 8 min read

SOLID: Pare de criar classes "Canivete Suíço" com o SRP (Single Responsibility Principle)

img of SOLID: Pare de criar classes "Canivete Suíço" com o SRP (Single Responsibility Principle)

Introdução

Fala, dev! Tudo certo?

Se você já sentiu aquele frio na espinha ao abrir uma classe de 2.000 linhas que faz desde o cálculo do frete até o envio de SMS pro cliente, parabéns: você encontrou um violador em série do SRP (Single Responsibility Principle).

Para nos ajudar a evitar cenários de terror como esse, recorremos ao SOLID. Esses princípios funcionam como diretrizes valiosas sobre como organizar funções e estruturas de dados em nosso código. O grande objetivo por trás deles é a criação de estruturas de software que alcancem três metas fundamentais:

  • Tolerem mudanças: O software precisa evoluir sem quebrar a cada alteração.
  • Sejam fáceis de entender: O código deve ser claro para que outros desenvolvedores (ou você mesmo no futuro) possam trabalhar nele sem mistérios.
  • Sejam a base de componentes reutilizáveis: Estruturas bem definidas podem ser aproveitadas em muitos outros sistemas de software, economizando tempo e esforço.

Neste artigo, vamos focar no primeiro e, talvez, mais fundamental desses princípios: o S do acrônimo, que representa o Single Responsibility Principle (Princípio de Responsabilidade Única).

Popularizado por Robert C. Martin (o famoso Uncle Bob), este princípio traz uma definição cirúrgica que deve guiar o design das nossas classes:

Uma classe deve ter um, e apenas um, motivo/razão para mudar.

Muita gente confunde “responsabilidade” com “fazer uma única coisa”. No mundo real, responsabilidade tem a ver com atores ou contextos. Se o seu código atende ao RH e ao Financeiro na mesma classe, quando o RH mudar uma regra, você corre o risco de quebrar o Financeiro. Isso é acoplamento perigoso.

O Erro Comum: A “Classe Canivete Suíço”

Imagine que estamos desenvolvendo um sistema de e-commerce. É muito tentador criar uma classe Pedido que resolve a vida de todo mundo. Olhe este exemplo em Java:

   // ALERTA: Não faça isso em casa!
public class Pedido {

    public void calcularTotal() {
        // Lógica de negócio (Responsabilidade 1)
        System.out.println("Calculando total...");
    }

    public void salvarNoBanco() {
        // Persistência de dados (Responsabilidade 2)
        System.out.println("Salvando pedido no MySQL...");
    }

    public void enviarEmailConfirmacao() {
        // Comunicação/Notificação (Responsabilidade 3)
        System.out.println("Enviando e-mail para o cliente...");
    }
}

Por que isso é ruim?

  1. Dificuldade de manutenção: Se você trocar o banco de dados de MySQL para MongoDB, precisa mexer na classe de Pedido.
  2. Frágil: Um erro na lógica de e-mail pode impedir que o pedido seja salvo.
  3. Difícil de testar: Para testar o cálculo, você acaba carregando dependências de banco e e-mail.

A Solução: Dividir para Conquistar

Para aplicar o SRP, precisamos separar essas responsabilidades em classes distintas, cada uma focada no seu “motivo de mudança”.

1. A Entidade (Negócio)

A classe Pedido agora só entende de… pedidos!

   public class Pedido {
    private List<Item> itens;

    public double calcularTotal() {
        return itens.stream().mapToDouble(Item::getPreco).sum();
    }
}

2. A Persistência (Repositório)

Criamos uma classe específica para lidar com o banco de dados.

   public class PedidoRepository {
    public void salvar(Pedido pedido) {
        // Conecta ao banco e salva
        System.out.println("Salvando pedido no banco de dados...");
    }
}

3. A Comunicação (Serviço de Notificação)

Outra classe cuida exclusivamente do contato com o cliente.

   public class EmailService {
    public void enviarConfirmacao(Pedido pedido) {
        // Lógica de SMTP/API de e-mail
        System.out.println("E-mail enviado com sucesso!");
    }
}

Benefícios Reais

Ao aplicar o SRP no seu projeto, você ganha:

  • Reutilização: O EmailService pode ser usado por outras partes do sistema.
  • Testabilidade: Você consegue criar testes unitários para o cálculo do pedido sem precisar de um servidor de e-mail fake.
  • Clareza: Qualquer dev que entrar no projeto saberá exatamente onde encontrar a lógica de banco de dados.

Dica: Se você estiver com dificuldade de dar um nome simples para sua classe (algo como ProcessadorDePedidoPersistidorEEnviador), é um sinal claro de que ela está fazendo coisa demais!

Aprodundando no Single Responsibility Principle

No livro Arquitetura Limpa (páginas 61 a 67), Robert C. Martin reforça que o Single Responsibility Principle trata de responder a Atores comerciais distintos que solicitam mudanças em contextos diferentes. Misturar essas responsabilidades em uma única classe é a receita para o caos arquitetural.
Para ilustrar isso, seguiremos com um outro exemplo de e-commerce.

O Rascunho do Caos: A Classe “Ordem Monolítica”

Imagine uma classe Order monolítica. Ela é perigosa porque tenta servir a três “senhores” comerciais diferentes ao mesmo tempo: Finanças(CFO), Operações(COO/Logística) e Atendimento(CS).

Misturar essas regras de negócio (cálculo de frete complexo), regras de infraestrutura (etiquetas de envio) e comunicação (e-mails de branding) em um único arquivo cria uma teia de interdependências perigosa.

O diagrama abaixo compara visualmente essa estrutura monolítica com a solução organizada sugerida pela Arquitetura Limpa:

A Classe Ordem Monolítica

Diagrama Comparativo: Parte superior mostra o caos monolítico, onde todos os atores (Finanças, Operações, Atendimento) interferem na mesma classe Order. Parte inferior mostra a solução organizada, onde cada ator possui sua própria classe de serviço dedicada e uma fachada coordena o fluxo.

O Caos em Java (Violação do SRP)

Vamos ver como ficaria esse caos no código Java:

   // A Classe Ordem Monolítica (Violando SRP)
// Esta classe tenta servir a três Atores/Responsabilidades

public class Order {

    // 1. Lógica de Negócio (Servindo FINANÇAS/CFO)
    public double calculateShippingCost() {
        // Lógica complexa de taxação e frete
        System.out.println("Finanças: Calculando frete e impostos...");
        return 0;
    }

    // 2. Lógica de Infraestrutura (Servindo OPERAÇÕES/Logística/COO)
    public String generateManifest() {
        // Lógica de armazém e etiquetas de envio
        System.out.println("Operações: Gerando manifesto de expedição...");
        return "";
    }

    // 3. Comunicação (Servindo ATENDIMENTO/CS)
    public void sendConfirmationEmail() {
        // Lógica de branding e notificação do cliente
        System.out.println("Atendimento: Enviando e-mail de confirmação...");
    }

    // O Vilão: Um método compartilhado
    private void sharedCalculation() {
        // Lógica para obter peso e dimensões (crucial para frete E logística)
        System.out.println("Lógica de cálculo de peso/dimensões compartilhada.");
    }
}

Sintomas da Doença: Interdependência Acidental e Conflitos

Esse design monolítico quebra de duas maneiras perigosas:

  1. Interdependência Acidental: O CFO quer mudar a fórmula de cálculo de peso e dimensões (sharedCalculation) para pagar menos imposto. O COO continua usando a fórmula antiga para logística. A equipe de TI muda a fórmula compartilhada para o CFO e, de repente, os manifestos de logística do COO quebram.
  2. Conflitos de Versão: Se dois desenvolvedores, de times diferentes (um de Finanças e outro de Operações), tentarem alterar essa mesma classe no mesmo commit, você já sabe: conflitos de mesclagem (merge conflicts) pesados e código frágil.

A Solução da Arquitetura Limpa: Separação Cirúrgica

Seguindo o princípio da Arquitetura Limpa, Uncle Bob propõe uma separação direta: cada classe responde a APENAS UM Ator. Separamos o algoritmo de cálculo de frete, do relatório de logística e da notificação do cliente em serviços dedicados. Vamos ver como ficaria essa nova estrutura organizada:

A Solução em Java (SRP Respeitado)

   // A Solução da Arquitetura Limpa (SRP Respeitado)
// Cada classe responde a APENAS UM Ator/Responsabilidade

// 1. Classe para o CFO (Finanças)
public class ShippingRateCalculator {
    public double calculateCost(OrderData data) {
        // Lógica de frete e imposto dedicada
        System.out.println("Finanças: Lógica de custo dedicada.");
        return 0;
    }
}

// 2. Classe para o COO (Operações)
public class ShippingManifestGenerator {
    public String generateManifest(OrderData data) {
        // Lógica de logística dedicada
        System.out.println("Operações: Geração de manifesto dedicada.");
        return "";
    }
}

// 3. Classe para o ATENDIMENTO (CS)
public class ConfirmationEmailNotifier {
    public void sendEmail(OrderData data) {
        // Lógica de notificação dedicada
        System.out.println("Atendimento: Notificação de e-mail dedicada.");
    }
}

// 4. Classe Simples de Dados (Entidade)
public class OrderData {
    // Apenas dados básicos da ordem, sem lógica complexa
}

// 5. Facade (Fachada) opcional para coordenar
public class OrderFacade {
    private final ShippingRateCalculator calculator;
    private final ShippingManifestGenerator manifestGenerator;
    private final ConfirmationEmailNotifier notifier;

    public OrderFacade() {
        this.calculator = new ShippingRateCalculator();
        this.manifestGenerator = new ShippingManifestGenerator();
        this.notifier = new ConfirmationEmailNotifier();
    }

    public double calculateShippingCost(OrderData data) {
        return calculator.calculateCost(data);
    }
}

Essa separação cirúrgica resolve os problemas anteriores:

  • Evita Interdependência Acidental: O algoritmo sharedCalculation() que o CFO quer mudar agora é isolado em ShippingRateCalculator. O COO continua usando sua própria lógica em ShippingManifestGenerator, e os dois não se sobrepõem acidentalmente.
  • Resolve Conflitos de Mesclagem: Desenvolvedores trabalhando para o CFO mexem em ShippingRateCalculator. Desenvolvedores trabalhando para o COO mexem em ShippingManifestGenerator. Arquivos diferentes, zero conflitos.

Conclusão

Dominar o Single Responsibility Principle é o primeiro grande passo para sair do modo “apenas codar” e entrar no modo “arquitetura de software”.

Como vimos nos exemplos em Java, a separação clara de responsabilidades não é preciosismo teórico; é uma estratégia vital para garantir que seu código não colapse sob o peso de novas regras de negócio ou mudanças de infraestrutura. Ao dar às suas classes apenas um motivo para mudar, você blinda seu software contra efeitos colaterais indesejados e facilita absurdamente a criação de testes unitários confiáveis.

No SRP, não pergunte o que sua classe faz; pergunte a quem ela serve. Se ela mistura contextos comerciais diferentes para Finanças, Operações e Atendimento, divida-a. Sua sanidade (e a do seu time) agradecem.

Mas lembre-se: o SRP é apenas a base do alicerce. O acrônimo SOLID tem mais quatro letras poderosas esperando por você.

Nos próximos artigos, vamos explorar como o O (Open/Closed Principle) nos ajuda a estender funcionalidades sem alterar o código existente, e como os princípios L, I e D fecham o ciclo de um design robusto e profissional.

Fique ligado no blog!

📚 Referências

  • Martin, Robert C. Arquitetura Limpa: O Guia do Artesão para Estrutura e Design de Software. 1ª ed. Alta Books, 2019. p. [61-67].