logotipo

22 de dezembro de 2023

Princípios SOLID

Padrões de código

O princípio SOLID é mais um da sopa de letrinhas de boas práticas na hora de desenvolver algum software. Ele nos diz como organizar e interconectar classes e estruturas de dados ao nível de “módulo”, ou seja, acima dos detalhes de funções ou sintaxes do código, porém abaixo do nível da arquitetura geral do software. O termo classe não se refere apenas a linguagens orientadas a objetos, mas também a pequenos conjuntos de dados e funções. O objetivo desses princípios é:

  1. Tolerar mudanças;
  2. Tornar de fácil entendimento;
  3. Possibilitar o uso desses componentes em vários outros sistemas;

Em geral, SOLID contribui muito para a qualidade final do código, e apesar de não ser regras estritas — você não precisa seguir tudo à risca —, é muito importante pelo menos estar ciente desses princípios.

Meme do sniper do Team Fortress 2

Princípio de Responsabilidade Única

O Princípio de Responsabilidade Única (Single Responsibility Principle) nos diz que cada unidade de código (função/classe) deve ter uma e apenas uma responsabilidade, e deve cumprir sua função muito bem. Além disso, devem ser responsáveis por não mais que um único “ator”. Ator é uma parte da sua regra de negócios, que pode requerer uma mudança no sistema.

Por exemplo, uma função que calcula o salário e benefícios de vários funcionários numa empresa pode ser compartilhada quando o cálculo é o mesmo. Mas se, por acaso, apenas um dos cargos mudar o cálculo e alterar essa função, a mudança de uma parte do sistema vai refletir em todos os cargos.

Então podemos dizer que esse princípio é resumido em:

Um módulo deve ter apenas uma responsabilidade.

Ou ainda:

Um módulo deve ser responsável por apenas um “ator”.

Atores diferentes que têm requerimentos parecidos podem precisar de um módulo idêntico, entretanto, há um risco de duplicação acidental. Quando um precisar mudar, o outro pode ser afetado sem que ninguém perceba inicialmente.

Princípio Aberto/Fechado

“Open-Closed Principle” nos diz que uma entidade do software (classe, módulo, etc.) deve ser fechada para modificações, mas aberta para extensão.

Caso esse princípio não seja bem executado, estender o código para criar novas funcionalidades (e sempre vai ter que fazer esse tipo de modificação, hora ou outra) ficará exponencialmente mais complicado. Imagina cada vez que adicionar algo novo ter que modificar o código já escrito.

Uma maneira de aplicar esse princípio é usar interfaces para abstrair comportamentos e definir contratos entre as partes do sistema. Dessa forma, se precisar adicionar ou estender algo, você só vai se preocupar em cumprir com a interface que suas regras de negócios nem ao menos saberão das mudanças.

Princípio da substituição de Liskov

“The Liskov Substitution Principle” foi introduzido em 1987 por Barbara Liskov. O princípio é mais ou menos assim: “se S é um subtipo de T, então os objetos do tipo T em um programa podem ser substituídos por objetos do tipo S sem alterar o comportamento desejado”. Em outras palavras, uma subclasse pode substituir a sua classe base.

Nesse sentido temos a famosa frase: “e algo parece com um pato, nada como um pato, anda como um pato, mas precisa de baterias, certamente você tem uma abstração problemática”.

Princípio de Segregação de Interfaces

O Princípio de Segregação de Interfaces — The Interface Segregation Principle — é bem simples: não force uma unidade de código a usar algo que não precisa. Para tanto, ao invés de criar uma grande interface de vários contratos diferentes, você pode dividir em várias interfaces pequenas e específicas.

Por exemplo, em detrimento de criar uma grande interface de métodos para diversas operações no banco de dados de usuários, você pode criar uma para cada operação; IAddUser, IReadUser, IVerifyUser, ICheckEmailInUse, etc. Depois você usa isso sob-medida.

Princípio de Inversão de Dependência

O Princípio de Inversão de Dependência — The Dependency Inversion Principle — é, na minha opinião, a parte mais legal de SOLID. Em resumo, você deve depender de abstrações e nunca de classes concretas.

Se você precisa de um validador de e-mail em um controller de login, não é recomendado instanciar uma classe concreta dentro desse controller. Isso deixa o código dependente entre suas partes. Apesar desse exemplo ser simples, essa prática vai demandar mais trabalho quando precisar trocar alguma parte do código, pois o controller agora depende do validador (note a direção da seta abaixo).

Controller → ConcreteEmailValidator

Uma mudança de validador de email vai encadear em uma mudança no código do controller.

O mais correto é criar um contrato IEmailValidator. Ao instanciar o controller, você injeta a dependência usando o construtor. Com isso, temos algo muito importante: ambas as classes concretas vão depender de uma abstração, e você pode trocar de validador a qualquer momento, contanto que ele siga a interface IEmailValidator, o controller não vai diferenciar.

ControllerIEmailValidatorConcreteEmailValidator

Em última análise:

  • Não se refira a classes concretas.
  • Não derive de classes concretas.
  • Não sobrescreva funções concretas.
  • Nunca mencione o nome de nada concreto e volátil.
  • Classes concretas devem depender de abstrações.

Referências

Clean Architecture: A Craftsman's Guide to Software Structure and Design

Voltar para todos os artigos