Domine SOLID: 5 Princípios Essenciais para Código Melhor

Imagine um prédio sem alicerce: bonito por fora, mas📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. um caos por dentro. Assim é o código que ignora SOLID! Este artigo vai te ensinar 5 princípios arquiteturais que separam o código profissional do "funciona, mas não mexe!". Usaremos exemplos reais de sistemas de pagamento e catálogo de produtos🎲 Desafio: Analise Dados de Vendas com LINQ e Coleções!🎲 Desafio: Analise Dados de Vendas com LINQ e Coleções!Aprenda a usar coleções e LINQ em C# para analisar vendas, filtrar dados e extrair insights estratégicos que otimizem decisões e impulsionem seu negócio. para você dominar cada conceito na prática.

Por outro lado, imagine ter de caçar cada bug em seu código como se fosse procurar agulhas num palheiro. Isso acontece quando📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. o sistema está desorganizado e difícil de entender. O conjunto de princípios conhecidos como SOLID ajuda a estruturar o código de forma clara e modular, diminuindo (muito!) as dores de cabeça na manutenção. Cada princípio aborda um ponto essencial de boas práticas🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!Aprenda a dominar operadores aritméticos em C# com exemplos práticos, técnicas de cálculo e dicas para evitar erros e maximizar resultados., tornando o software mais fácil de ampliar, corrigir e evoluir.

A seguir, vamos explorar cada princípio com exemplos do mundo real e📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. mostrar como aplicá-los em C#.

📌 Índice🔗

SRP: O Faxineiro do Código 🧹🔗

Problema🤝 GitHub Básico: Versionamento para Iniciantes!🤝 GitHub Básico: Versionamento para Iniciantes!Descubra como o GitHub facilita colaboração, versionamento e organização de código com este tutorial prático e essencial para desenvolvedores iniciantes. Comum: Uma classe com 20 métodos🧠 Métodos em C#: Como Criar Funções que Não São Só Enfeites!🧠 Métodos em C#: Como Criar Funções que Não São Só Enfeites!Otimize seu código em C# com métodos inteligentes. Aprenda práticas de reutilização, sobrecarga e escopo para melhorar a clareza e a eficiência. que fazem desde cálculos financeiros até enviar e-mails.

Solução SRP (Single Responsibility Principle):

O SRP diz que cada classe ou módulo🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!Aprenda a dominar operadores aritméticos em C# com exemplos práticos, técnicas de cálculo e dicas para evitar erros e maximizar resultados. deve ter uma única responsabilidade. Em outras palavras, uma classe🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. deve ter apenas um motivo para mudar. Se ela for responsável por mais de uma coisa, será mais difícil de manter, pois qualquer alteração em uma área pode impactar outra.

Imagine uma classe que valida pedidos de compra, emite nota fiscal e ainda envia e-mail de confirmação. Quando algo mudar no processo de emissão de e-mail, você precisaria mexer na mesma classe🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. que trata da validação? Isso gera confusão e acoplamento desnecessário.

// Classe com várias responsabilidades (possível violação do SRP)
public class PedidoService
{
    public void CriarPedido()
    {
        // Lógica de criação do pedido
    }
    public bool ValidarPedido()
    {
        // Lógica de validação do pedido
        return true;
    }
    public void EnviarEmailConfirmacao()
    {
        // Lógica para enviar e-mail
    }
}
// Aplicando SRP: separar responsabilidades
public class PedidoServiceCorreto
{
    private readonly ValidadorPedido _validador;
    private readonly EmailService _emailService;
    public PedidoServiceCorreto(ValidadorPedido validador, EmailService emailService)
    {
        _validador = validador;
        _emailService = emailService;
    }
    public void CriarPedido()
    {
        // Lógica de criação
        // Chama validador e email se necessário
        if (_validador.Validar())
        {
            // Persistência do pedido
            _emailService.EnviarConfirmacao();
        }
    }
}
public class ValidadorPedido
{
    public bool Validar()
    {
        // Faz toda a validação
        return true;
    }
}
public class EmailService
{
    public void EnviarConfirmacao()
    {
        // Manda um e-mail
    }
}

No exemplo “correto”, cada classe tem uma função🧠 Métodos em C#: Como Criar Funções que Não São Só Enfeites!🧠 Métodos em C#: Como Criar Funções que Não São Só Enfeites!Otimize seu código em C# com métodos inteligentes. Aprenda práticas de reutilização, sobrecarga e escopo para melhorar a clareza e a eficiência. clara: ValidadorPedido só valida, EmailService só cuida de e-mail, e📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. PedidoServiceCorreto orquestra o fluxo.

Benefícios:

OCP: Evolua sem Quebrar o Existente 🚧🔗

Exemplo Prático📝 Logging com Serilog: Registre Tudo como um Detetive de Bugs!📝 Logging com Serilog: Registre Tudo como um Detetive de Bugs!Aprenda a usar Serilog em .NET para registrar logs estruturados, identificar erros e enriquecer informações, transformando seu código num enigma solucionável.: Sistema de descontos que precisa adicionar📦 List<T>: Dinamismo além dos Arrays!📦 List<T>: Dinamismo além dos Arrays!Descubra como utilizar List<T> em C# de forma eficiente. Aprenda a criar, manipular e otimizar listas para diferentes cenários com exemplos práticos. novos tipos sem alterar código existente.

OCP significa que as classes🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. devem estar abertas para extensão, mas fechadas para🔄 Loops em C#: Repita Tarefas sem Enlouquecer (Com for e while!)🔄 Loops em C#: Repita Tarefas sem Enlouquecer (Com for e while!)Descubra como automatizar repetições em C# utilizando loops for e while com exemplos práticos que evitam erros e otimizam seu código. Aprenda mais! modificação. Em vez de mexer na classe original para adicionar📦 List<T>: Dinamismo além dos Arrays!📦 List<T>: Dinamismo além dos Arrays!Descubra como utilizar List<T> em C# de forma eficiente. Aprenda a criar, manipular e otimizar listas para diferentes cenários com exemplos práticos. novas funcionalidades, você a estende (por meio de herança ou outras técnicas) sem alterar o código já existente.

Imagine um sistema de pagamento que, inicialmente, só aceita cartão de crédito. Se quisermos adicionar📦 List<T>: Dinamismo além dos Arrays!📦 List<T>: Dinamismo além dos Arrays!Descubra como utilizar List<T> em C# de forma eficiente. Aprenda a criar, manipular e otimizar listas para diferentes cenários com exemplos práticos. boleto bancário, não deveremos mexer diretamente nas regras internas da classe de pagamento, mas sim estender esse comportamento:

// Exemplo que viola OCP (modificar sempre que surgir novo tipo de pagamento)
public class PagamentoService
{
    public void Pagar(string tipoPagamento)
    {
        if (tipoPagamento == "Cartao")
        {
            // Lógica para cartão
        }
        else if (tipoPagamento == "Boleto")
        {
            // Lógica para boleto
        }
        // A cada novo tipo de pagamento, novo else if...
    }
}
// Aplicando OCP com extensão
public abstract class Pagamento
{
    public abstract void ExecutarPagamento();
}
public class PagamentoCartao : Pagamento
{
    public override void ExecutarPagamento()
    {
        // Pagamento via cartão
    }
}
public class PagamentoBoleto : Pagamento
{
    public override void ExecutarPagamento()
    {
        // Pagamento via boleto
    }
}
public class PagamentoServiceCorreto
{
    public void Pagar(Pagamento pagamento)
    {
        pagamento.ExecutarPagamento();
    }
}

Agora, ao adicionar📦 List<T>: Dinamismo além dos Arrays!📦 List<T>: Dinamismo além dos Arrays!Descubra como utilizar List<T> em C# de forma eficiente. Aprenda a criar, manipular e otimizar listas para diferentes cenários com exemplos práticos. “PagamentoPIX”, basta criar uma nova classe PagamentoPIX estendendo de Pagamento, sem tocar no restante do código.

Vantagens:

LSP: Herança que Não Engana 👪🔗

Armadilha Clássica: Classes🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. filhas que mudam comportamento inesperado do pai.

O princípio de substituição de Liskov diz que qualquer classe filha🧬 Herança: Reutilize Código sem Copiar e Colar (como um Jedi)!🧬 Herança: Reutilize Código sem Copiar e Colar (como um Jedi)!Aprenda a utilizar herança em C# para criar hierarquias de classes, reaproveitar código e manter projetos organizados de forma simples e escalável. deve poder substituir a classe base sem quebrar o código. Isso significa que, onde se espera uma instância da classe pai, você deve poder usar a classe filha🧬 Herança: Reutilize Código sem Copiar e Colar (como um Jedi)!🧬 Herança: Reutilize Código sem Copiar e Colar (como um Jedi)!Aprenda a utilizar herança em C# para criar hierarquias de classes, reaproveitar código e manter projetos organizados de forma simples e escalável. tranquilamente.

Um caso simples é quando uma subclasse altera comportamentos de forma a quebrar a lógica original. Por exemplo, se uma classe🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. Retangulo é estendida por Quadrado, mas📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. Quadrado faz tudo de uma forma que viola as expectativas de cálculo de área. Na teoria, “Quadrado” deveria ser um “Retângulo” especial, mas se a implementação deturpa as propriedades⚡ Propriedades: Get e Set com Elegância (e sem Campos Privados Bagunçados)!⚡ Propriedades: Get e Set com Elegância (e sem Campos Privados Bagunçados)!Aprenda como utilizar propriedades em C# para encapsular dados, validar informações e manter um código organizado, seguro e de fácil manutenção. (como altura e largura serem sempre iguais à força), você pode ter comportamentos inconsistentes.

public class Retangulo
{
    public virtual double Largura { get; set; }
    public virtual double Altura { get; set; }
    public double CalcularArea()
    {
        return Largura * Altura;
    }
}
public class Quadrado : Retangulo
{
    // Tentativa de forçar altura e largura iguais
    public override double Largura
    {
        set { base.Largura = base.Altura = value; }
        get { return base.Largura; }
    }
    public override double Altura
    {
        set { base.Altura = base.Largura = value; }
        get { return base.Altura; }
    }
}

Na prática, se alguém depender de definir alturas e larguras separadamente e📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. espera cálculos específicos, Quadrado pode gerar comportamentos inesperados🕵️♂️ Detecção de Anomalias: Identifique Comportamentos Estranhos!🕵️♂️ Detecção de Anomalias: Identifique Comportamentos Estranhos!Domine a detecção de anomalias com C# e ML.NET aplicando técnicas para identificar falhas e fraudes em dados de forma prática e assertiva.. Para evitar isso, às vezes é mais seguro criar uma hierarquia diferente (ex.: uma interface comum, mas classes separadas).

Regra de Ouro: "Se parece um pato, mas📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. precisa bater asas diferente, não é um pato!"

ISP: Interfaces Específicas > Mega Contratos 📜🔗

Erro🎲 Desafio: Crie um Sistema de Login com Tratamento de Erros Robusto!🎲 Desafio: Crie um Sistema de Login com Tratamento de Erros Robusto!Aprenda a criar um sistema de login robusto em C#, com tratamento de erros adequado, validação e segurança para evitar vulnerabilidades. Frequente: Interfaces📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!Descubra como as interfaces em C# funcionam como contratos que garantem implementações flexíveis e robustas, facilitando o design e testes de sistemas. com métodos que os clientes não usam.

O ISP incentiva a separação de interfaces📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!Descubra como as interfaces em C# funcionam como contratos que garantem implementações flexíveis e robustas, facilitando o design e testes de sistemas. em múltiplas interfaces📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!Descubra como as interfaces em C# funcionam como contratos que garantem implementações flexíveis e robustas, facilitando o design e testes de sistemas. pequenas e específicas, ao invés de ter uma única interface gigante que force classes a implementar métodos que não usam. Isso torna o código mais limpo e facilita testes.

Suponha que você tenha uma interface📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!Descubra como as interfaces em C# funcionam como contratos que garantem implementações flexíveis e robustas, facilitando o design e testes de sistemas. ICadastro que obriga a assinar métodos de cadastro de cliente, produto🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!Aprenda a dominar operadores aritméticos em C# com exemplos práticos, técnicas de cálculo e dicas para evitar erros e maximizar resultados. e fornecedor. Uma classe responsável só por clientes será obrigada a implementar métodos de fornecedor sem precisar deles realmente. É desperdiçar código.

// Exemplo "inflado"
public interface ICadastro
{
    void CadastrarCliente();
    void CadastrarProduto();
    void CadastrarFornecedor();
}
// Aplicando ISP
public interface ICadastroCliente
{
    void CadastrarCliente();
}
public interface ICadastroProduto
{
    void CadastrarProduto();
}
public interface ICadastroFornecedor
{
    void CadastrarFornecedor();
}
public class CadastroCliente : ICadastroCliente
{
    public void CadastrarCliente()
    {
        // Lógica de cadastro de cliente
    }
}

Com interfaces📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!📜 Interfaces: Contratos que Garantem a Ordem no Universo OOP!Descubra como as interfaces em C# funcionam como contratos que garantem implementações flexíveis e robustas, facilitando o design e testes de sistemas. segregadas, cada classe foca no que realmente precisa fazer.

Quando📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. Usar:

DIP: Inverta o Fluxo de Controle 🔄🔗

Cenário: Classe🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. alta acoplada a implementações concretas.

O DIP afirma que módulos de alto nível não devem depender de módulos🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!🔢 Operadores Aritméticos: Faça Cálculos como uma Calculadora Humana!Aprenda a dominar operadores aritméticos em C# com exemplos práticos, técnicas de cálculo e dicas para evitar erros e maximizar resultados. de baixo nível, mas📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. ambos devem depender de abstrações. Essencialmente, você não gostaria que uma classe🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. "importante" conhecesse detalhes de implementações concretas.

Imagine que sua camada de serviço “depende diretamente” de uma classe concreta de repositório🤝 GitHub Básico: Versionamento para Iniciantes!🤝 GitHub Básico: Versionamento para Iniciantes!Descubra como o GitHub facilita colaboração, versionamento e organização de código com este tutorial prático e essencial para desenvolvedores iniciantes. para salvar dados. Se você quiser mudar a forma de persistência (ex.: de SQL para NoSQL), terá que refatorar todo o módulo. A inversão de dependência sugere que ambos dependam de uma interface ou abstração.

// Sem DIP: o serviço conhece implementação concreta
public class UsuarioService
{
    private UsuarioRepository _repo = new UsuarioRepository();
    public void Salvar(Usuario usuario)
    {
        _repo.Salvar(usuario);
    }
}
// Com DIP: usando abstração (IUsuarioRepository)
public interface IUsuarioRepository
{
    void Salvar(Usuario usuario);
}
public class UsuarioRepository : IUsuarioRepository
{
    public void Salvar(Usuario usuario)
    {
        // Implementação concreta
    }
}
public class UsuarioServiceCorreto
{
    private readonly IUsuarioRepository _repo;
    public UsuarioServiceCorreto(IUsuarioRepository repo)
    {
        _repo = repo;
    }
    public void Salvar(Usuario usuario)
    {
        _repo.Salvar(usuario);
    }
}

Agora, se precisar trocar UsuarioRepository por UsuarioRepositoryNoSQL, basta injetar a nova implementação em UsuarioServiceCorreto.

Vantagens:

Resumo e Desafio Prático 🏆🔗

SOLID em 1 Minuto:

PrincípioObjetivoExemplo Prático
SRPFoco únicoSeparar lógica de negócio de persistência
OCPExtensibilidadeUsar estratégias para cálculos
LSPHerança seguraNão alterar comportamento esperado
ISPInterfaces leanCriar contratos específicos
DIPInversão de controleDepender de abstrações

Desafio: Refatore esta classe🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!🏗️ Classes vs. Structs: Quando Usar Cada Uma (e Não Quebrar a Cabeça)!Descubra como escolher entre classes e structs em C#. Aprenda sobre alocação de memória, passagem por valor e referência, e performance nesta explicação clara. aplicando SOLID:

public class ProcessadorDeArquivos {
    public void LerArquivo(string caminho) { /* ... */ }
    public void ProcessarDados() { /* ... */ }
    public void SalvarNoBanco() { /* ... */ }
    public void EnviarEmailErro() { /* ... */ }
}

Dica: Pense em:

Quando📊 Behavior-Driven Development: Testes que Todo Mundo Entende!📊 Behavior-Driven Development: Testes que Todo Mundo Entende!Descubra como o BDD transforma testes em linguagens acessíveis. Aprenda a usar SpecFlow em C# para criar testes claros, colaborativos e sem ambiguidades. você adota esses cinco princípios, seu código se torna mais modular, testável e fácil de dar manutenção. Em outras palavras, você deixa de ser o “caçador de bugs” e passa a ser o “arquiteto do código”, criando soluções mais sólidas e evolutivas.

Autor: Marcelo V. Souza - Engenheiro de Sistemas e Entusiasta em IoT e Desenvolvimento de Software, com foco em inovação tecnológica.

Referências🔗

Compartilhar artigo

Artigos Relacionados