Princípios do Código Sólido na orientação a objetos

Quer ver eu te fazer uma pergunta que te fará pensar o resto do dia? Como olhar para um código e dizer que ele é de qualidade? Essa é sem dúvida uma pergunta que todos nós tentamos responder todos os dias.

Como bem sabemos, podemos olhar trechos de código por vários pontos de vista diferentes: o quão complexo ele é (muitos ifs, muitas linhas), o quão coeso ele é, o quão acoplado ele é, etc. Sendo tão difícil pensar em qualidade de código, vamos tentar mudar o nível. Quando que um sistema tem qualidade interna, ou seja, do ponto de vista de código?

Nós gostamos de sistemas que sejam fáceis de mexer. Ou seja, quando o usuário final pede uma mudança, é relativamente fácil de localizar onde ela deve ser feita e, depois de feita, não há propagação de problemas. 

Para que isso aconteça, o código deve estar bem modularizado; cada classe deve ter sua responsabilidade, e as relações entre elas devem estar bem definidas. É aqui que entra a ideia do código sólido, princípios criados por Michael Feathers e popularizados pelo Uncle Bob há bastante tempo.

A brincadeira com o termo “código sólido” vem do acrônimo SOLID. Cada letra representa um dos 5 princípios de orientação a objetos que nos ajudam a manter o código organizado:

  • Single Responsibility Principle (SRP), ou, Princípio da Responsabilidade Única. Esse princípio diz que as classes devem ser coesas, ou seja, terem uma única responsabilidade. Classes assim tendem a ser mais reutilizáveis, mais simples, e propagam menos mudanças para o resto do sistema.
  • Open Closed Principle (OCP), ou Princípio do Aberto Fechado. Diz que as classes devem poder ter seu comportamento facilmente estendidas quando necessário, por meio de herança, interface e composição. Ao mesmo tempo, não deve ser necessário abrir a própria classe para realizar pequenas mudanças. No fim, o princípio diz que devemos ter boas abstrações espalhadas pelo sistema.
  • Liskov Substitution Principle (LSP), ou Príncipio da Substituição de Liskov. Esse princípio diz que precisamos ter cuidado para usar herança. Herança é um mecanismo poderoso, mas deve ser usado com parcimônia, evitando os casos de Gato-estende-Cachorro, apenas por possuírem algo em comum.
  • Interface Segregation Principle (ISP), ou Princípio da Segregação de Interfaces. Esse princípio diz que nossos módulos devem ser enxutos, ou seja, devem ter poucos comportamentos. Interfaces que tem muitos comportamentos geralmente acabam se espalhando por todo o sistema, dificultando manutenção.
  • Dependency Inversion Principle (DIP), ou Princípio da Inversão de Dependências. Esse princípio diz que devemos sempre depender de abstrações, afinal abstrações mudam menos e facilitam a mudança de comportamento e as futuras evoluções do código.

OpenClosedPrinciple

Se conseguirmos seguir todas essas dicas, teremos código fácil de evoluir. As mudanças serão feitas em pontos específicos, e problemas não serão propagados.

O problema é que todos nós sabemos que não é fácil escrever código seco e sólido. Eu fui bem sucinto na explicação de cada um dos princípios. É possível discutir todos eles com muito mais profundidade.

Vale lembrar também o código seco (ou DRY): o programador não deve espalhar o mesmo código por todo o sistema; ele deve ser reutilizado. DRY vem de “Don’t Repeat Yourself”. Todos nós sabemos que repetição de código é um problema gravíssimo; eles dificultam a manutenção e propagam erros muito rapidamente.

Você pode ver mais sobre isso no meu livro de TDD, onde falo bastante de design de classes, nos cursos online de padrões e de boas práticas do Alura assim como nos cursos de laboratório Java e arquitetura da Caelum.

O que você faz para garantir que seu código seja sempre sólido?

25 Comentários

  1. Rafael Ponte 21/01/2014 at 10:14 #

    Excelente post, Aniche.

    Post simples para atiçar a curiosidade da moçada que curte estudar sobre design de software e boas práticas de orientação a objetos.

    Só uma pequena correção, o nome em inglês do princípio é Liskov substitution principle .

    Aproveita e no trecho do SRP remove o “são” de “[…] Classes assim são tendem a ser mais reutilizáveis […]”.

    Um abraço.

  2. Fabio Masson 21/01/2014 at 10:24 #

    Ótimo post, eu adicionaria também as regras de “Object Calisthenics” nessa história!

  3. Jeferson Viana Perito 21/01/2014 at 11:46 #

    Muito bom o post, mas eu acho que o Bob Martin eh mais conhecido como Uncle Bob do que Uncle Martin =)

  4. Paulo Silveira 21/01/2014 at 11:51 #

    obrigado pessoal, fiz os ajustes recomendados!

  5. Guilherme 21/01/2014 at 13:00 #

    Muito bom o Post !
    Mas não tem como não notar o easter egg do Metal Gear, rs

  6. Fehher 22/01/2014 at 10:10 #

    Comecei a algum tempo a me preocupar com questões relacionada ao design da aplicação.
    Código Sólido esta me ajudando,questões que estão me ajudando a evoluir.

    Mas uma questão que ainda me acomete, Quando diferencio DRY de SRP.

    Obrigado

  7. Mauricio Aniche 22/01/2014 at 12:49 #

    Oi Fehher,

    SRP fala mais sobre coesão.
    DRY diz para você não repetir código por aí, isolar.

    No fim, são coisas bem parecidas, uma coisa leva a outra.

    Faz sentido?

    Um abraço!

  8. Alexandre Aquiles 22/01/2014 at 14:24 #

    Não exatamente sobre duplicação vs. coesão, mas relacionado: Kent Beck discute sobre a relação entre duplicação e dependências no “TDD By Example”.

    “Dependência é o principal problema em desenvolvimento de software. Duplicação é o sintoma.

    Mas, ao contrário da maioria dos problemas na vida, nos quais eliminar os sintomas faz com que um problema mais grave apareça em outro lugar, eliminar duplicação nos programas elimina dependência.”

    Extrapolando um pouco, podemos dizer que duplicação é um dos sintomas de falta de coesão (além de problemas nas dependências).

    E a mágica em design de software é que, eliminando o sintoma (duplicação), a causa (coesão) é atacada.

    Mas para fazer um design de software coeso de verdade, é preciso bastante conhecimento do domínio.

  9. Fehher 22/01/2014 at 17:14 #

    Maurício.

    Você acabou que clarificar isso , já que era uma grande dúvida a respeito.

    Obrigado

  10. Douglas Arantes 24/01/2014 at 11:44 #

    Olá, muito bom o post. Sempre leio artigos sobre design de software.

    Atualmente estou lendo o livro “Object Oriented Analysis and Design – Head First”, o livro aborda os conceitos de SOLID, é bastante didático.
    Mas gostaria de saber algum outro livro que se aprofunda mais em design de software.

    Alguém tem alguma recomendação?

  11. Mauricio Aniche 24/01/2014 at 15:21 #

    Oi Douglas,

    O livro do Bob Martin [1] é uma bíblia sobre o assunto. Além disso, o GOOS [2] também é uma excelente referência.

    São meus favoritos, sem dúvida!

    [1] http://www.amazon.com/Software-Development-Principles-Patterns-Practices/dp/0135974445
    [2] http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627

    Um abraço!

  12. Henrique Adonai 05/02/2014 at 12:02 #

    Muito bom o Post!
    Simples e que deu vontade de aprofundar no assunto!
    parabens!!!

  13. Felipe 06/02/2014 at 11:33 #

    Parabéns pelo Post, estes conceitos são muito importantes e é comum não serem levados a sérios por programadores na posição de sênior dentro das empresas

  14. França 06/02/2014 at 11:33 #

    Interessante post. Mas com relação ao exemplo na explicação sobre o princípio de Liskov, entendo que o mesmo nem deveria se aplicar à Herança, uma vez que não passa no teste Is-A (herança natural). Fazer gato estender cachorro, entendo eu, é um erro no conceito de Herança por si só.
    Já o princípio de Liskov possui um racional mais “sutil”: a herança está correta, em princípio (quadrado estende retângulo), mas, semanticamente, a herança implica em um polimorfismo “forçado”. Exemplo: Preciso sobrescrever os métodos setWidht e setHeight na classe filha (Quadrado) para que ambos alterem ambas propriedades ao mesmo tempo… Salvo engano, o princípio dita que pós-condições não pode ser enfraquecidas nos subtipos.
    Bem, fico com a impressão que o princípio de Liskov torna o polimorfismo bem mais difícil – do ponto de vista de um bom projeto…
    O que vocês acham?
    Obrigado,

  15. Mauricio Aniche 06/02/2014 at 12:55 #

    Oi França,

    Você está certinho. Nesse artigo não quis entrar nos detalhes do princípio de Liskov, pq eles são mais chatos de explicar. Precisarei de um post só pra isso!

    Mas você está mais do que correto!

    Um abraço!

  16. André Valenti 31/03/2014 at 17:24 #

    Esses dias, fiz um projetinho para exemplificar produtor-consumidor para os meus alunos. Fiz em Java, usando Swing, que havia muito tempo eu não usava. Já trabalhei muito com MVC em interfaces gráficas, então, achei que seria mamão com açúcar!

    Foi uma grande surpresa perceber que meu código começou a ficar complexo e exigir gambiarras para ser terminado rápido, a tempo de usar o projeto na aula… Fiquei tão chateado que decidi refazê-lo, usando TDD e os aprendizados da experiência anterior. Coloquei no GitHub: https://github.com/awvalenti/docedeabobora

    Então, respondendo à sua pergunta “O que você faz para garantir que seu código seja sempre sólido?”, acho que um ponto importante é a humildade de admitirmos aquele fato difícil de admitir: não importa quantos anos de experiência você tenha e quanta vontade tenha de fazer as coisas direitinho, seu código às vezes sai muito feio… Aí, coisas como TDD e prototipação podem ajudar bastante!

  17. Marcus 22/04/2014 at 16:06 #

    Post, interessante !

  18. Jorge Eduardo 14/01/2015 at 11:19 #

    Parabéns pelo Post, em minha monografia comento alguns destes princípios!

  19. Arthur 26/11/2015 at 17:13 #

    Previsão de lançar seu livro de SOLID com C#? Existe algum curso, na caelum, alura ou algum outro que trate apenas disso? Boas praticas em desenvolvimento OO?

  20. Maurício Aniche 28/11/2015 at 13:55 #

    Oi Arthur,

    Não está na minha fila. Você acha que seria uma boa ideia? A versão Java aplica-se bem a C#! Se vc ler, não terá dificuldades em entender!

    Um abraço!

  21. ANDRE LUIZ GONCALVES FRANCO 15/06/2016 at 11:37 #

    Olá,
    Já tive situações em que tive que modificar um método. Este método contém 1000 linhas e vários trechos de código repetido. Como estava muito acoplado, alguns trechos de código que modifiquei eu tive que varrer todo o projeto para saber onde mais ele se repetia pra eu poder mudar também.
    Por orientação eu fiz isso mas quis mesmo transformar este trecho em um método. Se o fizesse isso , o que sempre tenho duvidas, como reutilizar da melhor forma possível ?

  22. Maurício Aniche 15/06/2016 at 11:52 #

    Oi André,

    Não há uma resposta mágica pra isso. Cada caso é um caso. Mas com certeza vc precisa fazer algo com esse método gigante!

    Sugiro você ler mais sobre boas práticas de código e padrões de projeto. Eles são boas ferramentas pra combater esses monstros!

  23. Gustavo 11/07/2016 at 15:22 #

    Olá Mauricio, em relação ao principio Open-Closed, por exemplo:

    Ao final de uma Sprint entrego uma classe com a seguinte funcionalidade:

    classe: Pessoa;
    privado
    Codigo;
    Nome;
    Cpf;
    publico
    setCodigo(aCodigo);
    setNome(aNome);
    setCpf(aCpf);

    Vamos supor que daqui a dois meses, em uma nova Sprint o cliente faz uma nova solicitação pedindo para incluir o campo Bairro:
    Aí eu teria:

    classe: PessoaBairro extende Pessoa;
    privado
    Bairro;
    publico
    setBairro(aBairro);

    É assim que eu deveria implementar o novo campo para não ferir o princípio alterando a classe de origem?

  24. Maurício Aniche 11/07/2016 at 15:31 #

    Oi Gustavo,

    Nesse caso, não. Até pq, vc ficaria maluco com tanta classe, certo? 🙂

    A ideia do OCP é mais para quando vc tem comportamentos que mudam com frequência do que dados. Por exemplo, se vc tiver muitos comportamentos sob a classe Pessoa, faz sentido deixá-la “aberta” para isso. No meu livro de OO eu dou bons exemplos!

    Um abraço!

Deixe uma resposta