Marcio Cunha

Código duplicado? Como aplicar princípios SOLID na prática

Descubra como refatorar trechos complexos de código procedural em estruturas orientadas a objetos limpas, testáveis e desacopladas usando os princípios SOLID.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • A duplicação de código em rotinas procedurais gera rigidez estrutural que dificulta alterações futuras e multiplica pontos de falha.
  • O princípio da responsabilidade única isola comportamentos distintos para garantir que cada classe mude por apenas um motivo.
  • Injetar dependências diretamente nas estruturas desacopla os módulos e viabiliza a criação de testes unitários isolados.
  • A abertura para extensão e o fechamento para modificação permitem adicionar funcionalidades sem reescrever o código existente.
  • A refatoração gradual transforma blocos lineares e complexos em classes coesas, legíveis e fáceis de manter.

O perigo silencioso do código procedural repetido

Todo programador já se deparou com aquele arquivo gigantesco onde dezenas de linhas de código se repetem com pequenas variações. Na programação procedural tradicional, o foco está em uma sequência linear de instruções que executam tarefas passo a passo. No entanto, quando um requisito de negócio muda, essa abordagem cobra o seu preço, exigindo alterações em múltiplos lugares e aumentando o risco de bugs. Para entender como mitigar esse problema, vale notar que a manutenibilidade do sistema depende diretamente da clareza estrutural, um desafio semelhante ao enfrentado ao estruturar bases complexas ou até ao estruturar microsserviços quando se migra um monolito gigante sem dor para arquiteturas mais modulares.

Na prática, isso significa que quanto mais lógica procedural acumulamos dentro de uma única função, mais difícil se torna testar o sistema. Um erro comum é misturar regras de cálculo, acesso a banco de dados e envio de notificações em um único bloco monolítico. Quando o código cresce sem uma divisão clara de papéis, o software perde flexibilidade e a equipe gasta mais tempo corrigindo falhas colaterais do que criando novas funcionalidades.

Identificando sintomas de acoplamento excessivo

O acoplamento excessivo ocorre quando diferentes partes do software dependem fortemente umas das outras de maneira rígida. Se alteramos a estrutura de uma tabela no banco de dados e isso quebra a lógica de exibição na tela, temos um forte indicativo de que as fronteiras arquiteturais foram rompidas. Esse cenário costuma vir acompanhado de funções longas, cheias de condicionais aninhadas que tentam adivinhar todas as possibilidades de execução em um único fluxo.

Para ilustrar esse cenário, imagine uma rotina procedural que processa pagamentos e dispara e-mails de confirmação no mesmo bloco de código. Se precisarmos trocar o provedor de e-mail ou adicionar uma nova regra de desconto, teremos que mexer em uma linha de código altamente sensível que já funciona. É justamente nesse ponto que a aplicação inteligente dos conceitos de orientação a objetos se torna indispensável para desacoplar as responsabilidades.

Aplicando a responsabilidade única para separar tarefas

O primeiro passo para organizar a casa é aplicar o conceito de que cada parte do sistema deve ter apenas um motivo para mudar. Na prática, separe a lógica de negócio pura das regras de infraestrutura, como conexões de rede ou persistência de dados. Em vez de uma função gigante que faz tudo, criamos classes especializadas onde cada uma executa uma única tarefa específica com perfeição.

Abaixo temos um exemplo clássico de código procedural inicial, repleto de responsabilidades misturadas:

// Código procedural acoplado e difícil de testar
function processarPedido($pedido) {
    // Validação
    if (empty($pedido['itens'])) {
        throw new Exception('Pedido vazio');
    }
    
    // Cálculo de preço
    $total = 0;
    foreach ($pedido['itens'] as $item) {
        $total += $item['preco'];
    }
    
    // Salva no banco
    $db = new PDO('mysql:host=localhost;dbname=loja', 'user', 'pass');
    $stmt = $db->prepare('INSERT INTO pedidos (total) VALUES (?)');
    $stmt->execute([$total]);
    
    // Envia e-mail
    mail($pedido['email'], 'Confirmação', 'Seu pedido foi processado.');
}

Esse bloco faz validação, cálculo, persistência e envio de mensagens tudo junto. Qualquer mudança no formato do banco ou no servidor de e-mail exige alterar esta mesma função, violando a coesão do sistema.

Refatorando em classes coesas e desacopladas

Para resolver o problema anterior, separamos as responsabilidades em pequenas classes bem definidas. Dessa forma, a validação fica em um componente próprio, o cálculo de valores em outro, e a persistência utiliza uma interface que abstrai o banco de dados. Essa separação drástica reduz a duplicidade e permite que cada classe seja testada de forma isolada.

Veja como fica a estrutura refatorada utilizando conceitos orientados a objetos e injeção de dependências:

// Classes coesas e desacopladas
class ValidadorPedido {
    public function validar(array $pedido): void {
        if (empty($pedido['itens'])) {
            throw new Exception('Pedido vazio');
        }
    }
}

class CalculadoraPreco {
    public function calcular(array $itens): float {
        $total = 0;
        foreach ($itens as $item) {
            $total += $item['preco'];
        }
        return $total;
    }
}

class ProcessadorPedido {
    private $validador;
    private $calculadora;

    public function __construct(ValidadorPedido $validador, CalculadoraPreco $calculadora) {
        $this->validador = $validador;
        $this->calculadora = $calculadora;
    }

    public function executar(array $pedido): float {
        $this->validador->validar($pedido);
        return $this->calculadora->calcular($pedido['itens']);
    }
}

Com essa abordagem, se precisarmos alterar a regra de cálculo de impostos ou mudar a biblioteca de envio de mensagens, o impacto é restrito apenas à classe correspondente. A manutenibilidade do software salta para um novo patamar.

Garantindo extensibilidade sem modificar o núcleo

Outro ganho fundamental ao aplicar estes princípios é a capacidade de estender o comportamento do sistema sem precisar reescrever o código existente. Quando utilizamos interfaces para definir contratos de comunicação entre os módulos, podemos plugar novas funcionalidades apenas criando novas classes que implementam essas mesmas interfaces, mantendo a estabilidade geral da aplicação.

Essa flexibilidade é essencial em ambientes dinâmicos onde regras de negócio mudam constantemente. Em vez de lotar os arquivos com condicionais do tipo if/else para verificar qual comportamento executar, delegamos essa escolha para o polimorfismo, tornando o código mais limpo, elegante e resiliente a alterações inesperadas.

Considerações finais sobre a evolução do código

Refatorar código procedural antigo exige paciência e disciplina, mas os benefícios a médio e longo prazo superam amplamente o esforço inicial. Ao dividir grandes blocos de lógica em estruturas orientadas a objetos coesas, a equipe ganha velocidade e segurança para entregar novas features sem medo de quebrar o sistema legado. A adoção dessas práticas transforma bases de código frágeis em ecossistemas de software robustos, testáveis e preparados para o crescimento sustentável.