Você conhece os princípios SOLID? Confira neste artigo quais são e como aplicá-los no dia a dia para ter um código limpo na prática!
Tempo de Leitura: 7 minutos
Criar códigos de qualidade durante todo o processo de desenvolvimento é um desafio e tanto na carreira do desenvolvedor de software. Usar boas práticas de programação tem a finalidade de reduzir duplicações de código, a responsabilidade das entidades, a complexidade do código, ocasionando assim uma melhor qualidade no código-fonte.
Na programação orientada a objetos (POO) temos diversos princípios e ferramentas que garantem a qualidade do seu código.
Neste artigo abordaremos o SOLID e como podemos aplicá-lo, na prática.
Princípio SOLID
O Princípio SOLID é um conjunto de cinco princípios fundamentais no desenvolvimento de software, criados por Robert C. Martin, amplamente conhecido como "Uncle Bob".
Esses princípios têm como objetivo principal promover a qualidade no desenvolvimento de software, tornando os sistemas mais fáceis de testar, manter, escalar e entender. O nome SOLID é um acrônimo formado pelas iniciais de cada um dos princípios:
Quais são os princípios SOLID
S - Single Responsibility Principle (Princípio da Responsabilidade Única - SRP):
O SRP estabelece que uma classe deve ter apenas uma razão para mudar. Isso significa que uma classe deve ter uma única responsabilidade ou tarefa no sistema. Quando uma classe tem múltiplas responsabilidades, torna-se mais difícil de entender, testar e manter. A aplicação desse princípio ajuda a manter o código modular e coeso.
O - Open-Closed Principle (Princípio Aberto-Fechado - OCP):
O OCP enfatiza que uma entidade de software (como uma classe ou módulo) deve estar aberta para extensão, mas fechada para modificação. Isso significa que você pode estender o comportamento de uma classe sem alterar seu código-fonte. Isso é alcançado por meio de herança, interfaces e polimorfismo, permitindo que novos recursos sejam adicionados sem afetar o código existente.
L - Liskov Substitution Principle (Princípio de Substituição de Liskov - LSP):
O LSP define que objetos de subclasses devem poder ser substituídos por objetos da classe base sem afetar a integridade do programa. Em outras palavras, se uma classe é uma subclasse de outra, ela deve compartilhar o mesmo contrato ou comportamento. Isso promove a consistência e evita comportamentos inesperados ao usar herança.
I - Interface Segregation Principle (Princípio de Segregação de Interface - ISP):
O ISP sugere que uma interface não deve forçar implementações de métodos que não são relevantes para todas as classes que a implementam. Em vez disso, as interfaces devem ser específicas para cada cliente. Isso evita que as classes sejam sobrecarregadas com métodos desnecessários e promove a coesão.
D - Dependency Inversion Principle (Princípio da Inversão de Dependência - DIP):
O DIP propõe que módulos de alto nível não devem depender de módulos de baixo nível, ambos devem depender de abstrações. Além disso, abstrações não devem depender de detalhes, mas detalhes devem depender de abstrações. Isso promove a flexibilidade e a facilidade de substituição de componentes em um sistema.
Neste artigo, exploraremos cada um desses princípios SOLID com exemplos práticos, destacando a importância de aplicá-los no desenvolvimento de software.
A compreensão e aplicação desses princípios são essenciais para criar sistemas robustos, extensíveis e de fácil manutenção, aspectos críticos no mundo da tecnologia da informação e, como analista de dados web, essa compreensão pode ajudar na avaliação e aprimoramento de sistemas de coleta e análise de dados.
1 - SRP - Single Responsability Principle
O princípio da responsabilidade única determina que uma classe deve ter um e somente um motivo para mudar.
Este princípio declara que uma classe deve ter apenas uma responsabilidade dentro do desenvolvimento de software, ou seja, ela deve ter apenas uma única tarefa ou função para executar.
Quando estamos aprendendo a desenvolver nossos primeiros sistemas, geralmente atribuímos várias funções dentro de uma mesma classe. Em um primeiro momento esta abordagem pode se mostrar eficiente, porém torna-se muito difícil realizar qualquer tipo de alteração sem comprometer o funcionamento da classe.
O que não fazer
A classe Pedido está violando as regras do Single Responsability Principle pois está executando 3 funções diferentes: a regra de negócio, manipulação da base de dados e geração de uma renderização para o usuário.
Aplicando o princípio na classe, teremos o código neste formato:
Com esta refatoração, agora temos 3 classes distintas, sendo que cada uma delas é responsável por apenas uma função.
2 - OCP - Open-closed Principle
O princípio aberto-fechado determina que uma entidade deve estar aberta para a extensão e fechada para a modificação.
Na prática, quando realizamos alguma alteração de recurso ou de comportamento da entidade, devemos estender este novo recurso e comportamento e não fazer alterações no código-fonte.
O que não fazer
Vamos simular um pequeno sistema que realiza o cálculo de preços de um produto, considerando valor e frete. A implementação deste sistema se encontra abaixo:
O método calcula() da classe Calculadora preço verifica as regras de desconto e de frete a partir do meio de pagamento informado. Se o produto for comprado à vista, o desconto é calculado a partir da tabela TabelaPrecoAVista. Caso for comprado à prazo, o desconto é calculado a partir da tabela TabelaPrecoAPrazo.
Além disso, as classes dentro das tabelas implementam uma política de preços baseado no produto e o frete varia por estado:
O problema desta implementação é a complexidade da regra de negócio. Além disso, o acoplamento de código é muito grande, pois uma classe está dependendo da outra para seu funcionamento.
Aplicando o princípio aberto-fechado, teremos um código parecido com este:
Com esta refatoração, a classe CalculadoraDePrecos não precisa conhecer a implementação da política de preços nem do cálculo do frete, pois este é um comportamento que a classe deve ter. Caso seja necessário implementar novos comportamentos, basta adicionarmos este comportamento através de interfaces e implementá-las na classe em questão.
3 - LSP - Liskov Substitution Principle
O princípio da Substituição de Liskov determina que uma classe derivada deve ser substituível por sua classe base.
Na prática, todas as classes filhas (que foram implementadas através de uma herança) devem manter os mesmos comportamentos da classe pai.
Vamos a um exemplo de um código que implementa este princípio:
Como a classe B herda o comportamento da classe A, ao utilizar a função imprimeNome() o código funcionará da mesma forma.
Devemos, no entanto, nos atentar a não violar este princípio, principalmente se:
- Sobrescrever/implementar um método que não faz nada.
- Lançar uma exceção
- Retornar tipos e valores diferentes da classe pai.
Para não violar o LSP, além de estruturar muito bem as suas abstrações, será necessário implementar Injeção de Dependência e também outros princípios do SOLID, como o Open-closed Principle e o Interface Segregation Principle (veremos a seguir).
4 - ISP - Interface Segregation Principle
O princípio de segregação de interface determina que uma classe não deve ser forçada a implementar interfaces que possuem atributos e métodos que não irão utilizar.
Na prática, devemos preferir criar interfaces mais específicas ao invés de interfaces genéricas.
O que não fazer
Vamos simular que estamos desenvolvendo um jogo que possui aves como personagem. Neste jogo implementamos uma interface Aves para abstrair o comportamento deste personagem, e faremos com que as classes implementem esta interface:
Percebam que na classe Galinha não podemos implementar o método setAltitude(), pois a galinha não voa! Isso acontece, pois criamos uma interface genérica para as aves e acabamos violando tanto o ISP quando o LSP.
E como podemos corrigir este problema? Devemos criar interfaces mais específicas, como o exemplo abaixo:
No exemplo acima, movemos o método setAltitude() para a interface IAvesQueVoam. Desta forma conseguimos isolar os comportamentos específicos dos personagens do jogo, respeitando o princípio ISP.
5 - DIP - Dependency Inversion Principle
O princípio da Inversão de Dependência determina que uma classe deve depender de abstrações, mas nunca de implementações.
Uncle Bob define este princípio da seguinte forma:
"Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração."
"Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações."
Complexo não é? Vamos entender este conceito a partir de um exemplo.
O que não fazer
Vamos supor que possuímos uma classe de lembrete de senha que precisa se conectar a uma base de dados, conforme abaixo:
Neste trecho de código vemos que a classe PasswordReminder possui um alto níve de acopamento, pois depende da instância da classe PostSQLConnection para se conectar a base de dados. Se fosse necessário reaproveitar a conexão do banco PostgreSQL precisamos levar a implementação da conexão para a outra classe.
Como aplicamos o Dependency Inversion Principle?
Vamos refatorar o código da classe PasswordReminder para que ele dependa da abstração proposta pela interface de conexão:
Veja que após a refatoração, o construtor da classe PasswordReminder agora depende de uma abstração de conexão ao banco de dados. Para ela, não importa qual banco de dados irá conectar nesta classe, ela precisa somente que faça a conexão a uma base de dados!
Além disso, estamos favorecendo a reusabilidade de código e ainda estamos respeitando os princípios SRP e OCP.
E aí, curtiu esse conteúdo? Deixa nos comentários sua opinião, e vamos trocar ideias lá no fórum também, acesse o link pelo botão abaixo:
Conclusão
A adoção dos princípios SOLID é um passo significativo na jornada para aprimorar a qualidade e a eficiência do nosso software. Ao seguir esses princípios, estamos construindo uma base sólida que torna nosso código mais robusto, escalável, flexível e capaz de se adaptar às mudanças. Isso não apenas facilita a implementação de novos requisitos, mas também simplifica a manutenção contínua do sistema, economizando tempo e recursos no longo prazo.
No início, pode ser um desafio compreender completamente e aplicar cada um desses princípios em nossos processos de desenvolvimento. É natural sentir um pouco de desconforto e incerteza, pois esses conceitos podem parecer complexos. No entanto, é crucial lembrar que a aplicação dos princípios SOLID não é um processo "tudo ou nada". Eles podem ser adaptados de acordo com as necessidades específicas de cada projeto.
Com a prática e a constância, à medida que trabalhamos em diversos projetos e enfrentamos diferentes desafios, nosso entendimento e domínio desses princípios evoluem. Gradualmente, nosso código se torna mais maduro, nossos sistemas se tornam mais resilientes e nossa capacidade de entregar soluções de alta qualidade se aprimora continuamente.
Portanto, é importante não apenas abraçar os princípios SOLID, mas também reconhecer que o desenvolvimento de software é uma jornada de aprendizado contínuo. À medida que aplicamos esses princípios em nossa prática diária, estamos investindo no futuro da nossa equipe de desenvolvimento e na qualidade dos produtos que entregamos aos nossos clientes e usuários.
---