Olá, pessoal

SOLID Principles

Neste post darei início a uma série que falará sobre os princípios SOLID, os quais são boas práticas vindas de décadas de experiência em engenharia de software.

Estes 5 princípios passaram a ser chamados de SOLID após a popularização dos mesmos por meio do Uncle Bob (os 5 princípios fazem parte do famoso livro “Agile Principles, Patterns and Practices”).

Mas afinal, por que CINCO princípios? Porque SOLID é um acrônimo – americanos adoram acrônimos – onde cada letra corresponde a um princípio:

[S]ingle Responsability Principle
[O]pen/Closed Principle
[L]iskov Substitution Principle
[I]nterface Segregation Principle
[D]ependency Inversion Principle

Estes princípios, quando bem aplicados, ajudam a eliminar os design-smells do nosso código, permitindo maior facilidade de manutenção e extensão.

Neste primeiro post, falarei sobre o SRP, ou Princípio da Responsabilidade Única.

DEFINIÇÃO

Este princípio nada mais é do que uma perspectiva diferente para um dos mais fundamentais princípios da orientação a objetos: a coesão.

Sua definição diz que: “Uma classe deve ter somente uma razão para mudar”.

Vamos tentar entender o que isso significa e eventuais problemas causados pela violação deste princípio.

ALGUNS PROBLEMAS

Uma classe com mais de um motivo para mudar possui mais de uma responsabilidade, ou seja, não é coesa. Temos vários problemas com isso:

– Dificuldade de compreensão e, portanto, dificuldade de manutenção.

– Dificuldade de reuso.

– Com responsabilidades entrelaçadas em uma mesma classe, pode ficar difícil alterar uma dessas responsabilidades sem comprometer as outras (rigidez) e pode acabar quebrando outras partes do software (fragilidade).

– Acoplamento alto, ou seja, a classe tem um número excessivo de dependências, e portanto fica mais sujeita a mudanças em decorrência de alterações em outras classes (novamente a fragilidade).

EXEMPLOS COMUNS DE VIOLAÇÃO

Imaginem uma classe de negócio Pedido:

public class Pedido
{
    public void AdicionarProduto(Produto produto, int quantidade) { }
    public decimal CalcularTotal() { }
    public void GerarPlanilhaExcel() { }
}

No exemplo acima, temos uma quebra do SRP de uma forma bem explícita, uma vez que temos responsabilidades que deveriam ser de componentes distintos do software. Enquanto os dois primeiros métodos fazem sentido para o domínio do qual Pedido faz parte, o último está relacionado à exibição de dados em um formato específico, o que faz mais sentido em camadas superiores, como de Aplicação ou de UI.

Em um projeto com várias classes seguindo esse “padrão”, fica difícil – ou impossível – manter a coesão em um nível mais alto: em nível de componentes. Em outras palavras, o software acaba sendo um emaranhado de classes sem um divisão clara de camadas.

De forma mais prática: chega um momento onde fica impossível separar determinadas classes em uma class library devido à referência circular. Também fica complicado fazer o deploy de componentes isolados por haver dependências demais entre eles.

Outros exemplos comuns são: (1) classes que misturam negócio e persistência (Pedido, por exemplo, contém métodos que sabem incluir, alterar e excluir pedidos, fazendo com o que mesmo seja acoplado com classes como SqlConnection ou ainda algum ORM); (2) view models que apresentam regras de negócio (mais sobre view model [aqui]).

Obs.: não vale a pena falar muito das classes “God”, que possuem milhares de linhas de código, métodos gigantescos e um número enorme de dependências, pois isso vocês não fazem, certo? 🙂

UM EXEMPLO MENOS ÓBVIO

Nem sempre é fácil identificar várias responsabilidades em uma mesma classe. Eu diria que na maioria das vezes não é. Aliás, atribuir responsabilidades é uma das principais tarefas de um programador OO.

Mesmo que uma classe de negócio esteja fazendo apenas tarefas relacionadas ao seu domínio, ela pode estar fazendo coisas demais.

Vejamos o seguinte exemplo:


public class Cliente
{
    // dados do cliente, como Nome, CPF, etc.
    // outros métodos

    public decimal CalcularDescontoPara(Venda venda)
    {
        if (venda.FormaDePagamento == FormaDePagamento.AVista)
        {
            if (venda.Total > 2000m)
                return venda.Total * 0.2;
            return venda.Total * 0.1;
        }
        return 0m;
    }
}

Observem acima que o método “CalcularDescontoPara” não manipula nenhum dado da classe Cliente, ou seja, nenhuma informação do cliente é necessária para se determinar o valor do desconto.

Sendo assim, esta classe possui pelo menos duas razões para mudar: uma quando houver alteração na lógica de negócio referente a um Cliente e outra quando houver alguma alteração na lógica de uma Venda.

Certamente, faz mais sentido que este método seja da classe Venda!

CONCLUSÃO

O SRP é um dos princípios mais importantes que existe na orientação a objetos. Lembrando-se dele, você poderá projetar classes menores, mais coesas e de mais fácil entendimento.

Quando falamos de responsabilidades e coesão estamos tocando em dois pontos-chave da OO, os quais renderiam muito mais assunto. Para não prolongar demais este artigo, prometo voltar em outras ocasiões, trazendo mais dicas de como evitar que classes possuam diversas responsabilidades.

Até lá!

[]s
—————————–

Toda a série:
Princípio da Responsabilidade Única (SRP)
Princípio do Aberto/Fechado (OCP)
Princípio da Substituição de Liskov (LSP)
Princípio da Segregação de Interface (ISP)
Princípio da Inversão de Dependência (DIP)