SOLID - Exemplos do Mundo Real



...

Olá, desenvolvedores! Como vocês estão? Estou muito bem, porém um pouco ausente aqui do blog pois estou trabalhando em mais de um projeto simultaneamente, porém tive um tempo e resolvi escrever sobre um dos assuntos mais importantes para qualquer profissional de tecnologia, os princípios do SOLID!

Para início gostaria de fazer um disclaimer, os exemplos apresentados aqui aconteceram em algum momento da minha carreira, em projetos reais, alguns não foram solucionados da forma que irei apresentar e acabaram dando um grande prejuízo para o projeto.

Outra coisa importante de se destacar é que a fonte das definições dos princípios é diretamente do livro Clean Architecture (Uncle Bob) e com um pouco do que EU enxergo sobre cada princípio.

 

 

SOLID é algo sólido?

 

Calma jovem desenvolvedor, se você nunca estudou sobre isso provavelmente foi o primeiro pensamento que teve ao ler a palavra SOLID.

Vamos lá, SOLID é um acrônimo para 5 princípios de design de software, o objetivo deles são basicamente nos guiar a construir um software que contenha uma arquitetura fácil de entender, reutilizável, eficiente e principalmente FLEXÍVEL.

Por que ser flexível é tão importante? Ora caro desenvolvedor eu te respondo, tenho certeza de que se você já atua efetivamente em um projeto sabe que mudanças de escopo e regras são recorrentes e as vezes (na verdade na maioria delas) essas mudanças podem ser de grande impacto para o modelo de negócio e até mesmo aplicação.

E é aí que o SOLID entra em ação, a partir do momento que sua aplicação não é flexível e bem estruturada você terá problemas para realizar alterações, pode até chegar a um nível de ter que refazer o projeto do ZERO!

E claramente, nós como profissionais de tecnologia não queremos entregar esse tipo de qualidade/produto para nossos clientes e corporação que trabalhamos, afinal, além do produto nosso nome como desenvolvedor competente está em jogo a cada linha de código que escrevemos.

Vou contar uma breve história de um projeto que trabalhei há cerca de 3 anos, eu atuava como consultor freelancer e peguei um projeto de uma corretora de imóveis de Portugal para realizar algumas melhorias de performance, alterações e criar um plano de melhoria estrutural para o projeto.

Pela descrição que me foi dada imaginei um projeto legado, antigo, com no mínimo 5 a 6 anos de vida (e olha que isso não é muito tempo, conheço softwares escritos há mais de 20 anos que funcionam muito bem!), porém eu estava enganado, esse projeto havia sido construído há menos de 2 anos e já estava com problemas assustadores.

O principal problema desse software não era os recursos de performance, era sua arquitetura que foi mal projetada desde o começo, não tinha uma estrutura flexível, seus limites arquiteturais não estavam bem definidos e tinha muito, mas muito código acoplado, era um verdadeiro monstro.

Como um bom guerreiro resolvi tentar enfrentar a fera, consegui com o famoso “jeitinho brasileiro” implementar as alterações de escopo e aumentar em quase 200% a performance, os stakeholders estavam pulando de alegria e o projeto voltara a dar dinheiro a rodo!

Porém o tempo passa e a empresa resolve se modernizar, logo me procuraram novamente após alguns meses, com um plano de implementação de novos requisitos, confiantes que, era só eu mexer os dedos e a mágica aconteceria.

Lembra que eu disse que a estrutura inicial do projeto estava horrível e inflexível? Pois é, isso foi apresentado em uma reunião com 3 investidores que pelo visto tinham muito dinheiro e gostariam de investir no projeto, estava tudo aprovado exceto a resposta do consultor que iria implementar essas alterações, no caso, EU.

E claramente minha resposta foi NÃO, eram alterações grandes demais para uma estrutura tão depreciada, eles ficaram em choque tentando entender como que na primeira implementação tudo ocorreu ok (claramente não ocorreu, foi tudo adaptação técnica, famosa gambiarra) e na segunda implementação era inviável.

O motivo de eu ter dito não, era simples, na primeira implementação o que estava em jogo era pouco dinheiro se comparado ao tamanho da empresa (o projeto estava tão lento na época que eles perderam 70% dos clientes para concorrência), agora o cenário já era outro, eles tinham recuperado boa parcela do mercado e milhares de euros estavam em jogo e eu, não queria ser o responsável pela perca desse montante.

Por fim, eles acabaram procurando outros consultores, e inclusive consultores mais experientes e a resposta da maioria foi igual a minha, NÃO.

Até que aquela empresa encontrou um consultor disponível que aceitou o projeto e acabou torrando 340 MIL EUROS, sim, exatamente.

Por que torrando? Basicamente como a estrutura estava horrível o prazo não foi cumprido, aliás, ele levou o dobro do prazo que foi estipulado pela empresa, além disso ficou pior do que já estava, no fim o projeto foi descontinuado, esse desenvolvedor teve parte de seu nome manchado e a empresa perdeu sócios e investidores.

O que eu consegui tirar de insight disso? Bom basicamente se os princípios do SOLID tivessem sido implementados corretamente, 70% dos problemas daquele projeto não existiriam, a estrutura seria mais flexível e com certeza, aquelas implementações iriam ser tranquilas de serem feitas.

O prazo do projeto não ia ser ultrapassado, o projeto iria realizar mais vendas, os investidores ficariam felizes e todo mundo ganharia mais dinheiro, bom pelo menos é o que eu imagino que aconteceria, claro que existem muitas variáveis nesse caminho.

Porém uma coisa é fato, se a estrutura fosse flexível, alterações seriam fáceis de implementar.

Bom dito tudo isso, vamos entender então o que é essa sopa de letrinhas do SOLID e como ele pode nos ajudar no dia a dia.

 

 

SRP – Princípio da Responsabilidade Única

 

Single Responsability Principle ou Princípio da Responsabilidade Única é o que corresponde ao S do acrônimo SOLID.

É particularmente meu princípio favorito pois ele nos guia a deixar componentes exatamente onde eles devem ficar e mudarem somente por um único motivo.

 

Esse princípio nos diz que:

Um módulo deve ter uma, e apenas uma razão para mudar

 

Esse princípio é um dos menos compreendidos pelos profissionais de software o que acaba gerando muitos problemas.

Todo projeto de software sofre constantes alterações e isso gera mudanças de código em seus componentes (classes, métodos e arquivos).

Cada pedaço do negócio dentro de um software “pertence” respectivamente a uma área dentro da sua corporação, se você parar para pensar, um software de e-commerce, a parte de vendas e pagamentos pertence ao financeiro, a parte de armazenamento de dados ao time de DBA, a parte de relatórios ao time de operações e assim por diante.

 

Então esse princípio pode acabar sendo lido da seguinte forma:

Um módulo deve ser responsável por um, e apenas um ator

 

Onde nesse cenário um ator seria um responsável por uma área de negócio dentro do seu software.

Porém, na maioria das vezes cada área de negócio tem mais de um responsável, então eu gosto de deixar minha própria descrição:

 

Um módulo deve ser responsável por um, e apenas um contexto do negócio

 

Pronto! Agora podemos voltar ao nosso exemplo já dado anteriormente, o componente de pagamentos e vendas só irá ser alterada caso haja alguma alteração em um processo de pagamento ou vendas solicitada pelo time financeiro.

O componente de relatórios só será alterado quando houver única e exclusivamente alguma mudança no processo de geração de relatório solicitado pelo time de operações.

E por fim, o componente de dados só será alterado caso haja alguma solicitação do time de DBA (ou responsável pelos dados/integridade) dentro da corporação.

Por fim, uma alteração no componente de pagamentos NÃO DEVE impactar de forma alguma o componente de relatório e assim sucessivamente.

Só lendo realmente parece difícil de entender e por isso esse é um dos princípios menos compreendido pela comunidade, então vou partir para um exemplo prático, real, que estava presente em um software de uma grande empresa de reparos na qual dei consultoria em 2017!

Era um CRM (software que auxilia no relacionamento empresa-cliente), por lá o cliente conseguia se comunicar diretamente com a empresa e ver o progresso de reparo de peça enviada.

Fui contratado pois os desenvolvedores que estavam dando manutenção nesse sistema na época tinham pouca experiencia e estavam demorando muito para entregar e realizar alterações.

Ao abrir a entidade principal do sistema me deparei com o seguinte cenário.

 

Essa é uma adaptação do código no qual eu tinha me deparado, mas observe bem quantas responsabilidades de diferentes contextos de negócio esse mesmo componente tinha no sistema, certamente, mais de uma.

E qual o problema? Oh cada vez que um desenvolvedor faz isso uma baleia morre!

Pare para pensar, se for solicitado algo em pagamento, você mexe no mesmo componente de reparos e de comunicação, correndo o risco não só de não entregar a alteração solicitada como quebrar o que já estava funcionando.

E era exatamente isso que estava acontecendo, a empresa estava com problemas nas notificações, o método de notificação foi feito a primeira vez para notificar quando o serviço havia sido finalizado e nada mais, em certo momento foi solicitado para notificar ao iniciar o serviço e algum desenvolvedor espertão viu aquele método ali e chamou sem precedentes!

Ok, e como eu poderia resolver o problema de um componente ser responsável por mais de um contexto de negócio? Simples meu jovem desenvolvedor, crie um componente para cada área do negócio!

OBS: Eu deixei os 3 componentes (serviços) no mesmo arquivo só para ficar fácil a visualização, o correto seria em arquivos separados!

 

Agora cada contexto de negócio tem seu componente separado, caso o time de vendas solicite uma alteração no processo de pagamento, essa mudança não terá a chance de afetar os outros contextos, afinal eles estão protegidos por seu próprio componente.

“Ah, mas e se eu tiver 10, 15, 20 contextos, vou precisar ficar caçando componente pela aplicação?”

Calma jovem, tem solução para tudo, você pode abstrair o acesso direto a esses componentes usando o padrão Facade, onde você cria uma classe de faixada que chamam os componentes específicos.

 

“Ok, mas estou em um projeto legado, no qual vários componentes chamam esses métodos, se eu criar um componente para cada contexto vou ter centenas de pontos de alteração?”, novamente, existe solução, você pode externalizar somente a lógica dos métodos e continuar mantendo-os ali (só vejo útil caso esteja em legado ou projeto com a estrutura tão ruim ao ponto de não ser possível refatorar).

 

Ah antes que eu me esqueça, o sistema em questão conseguiu ser refatorado após um longo e caro processo (desenvolvimento de software é caro, principalmente software de qualidade) e como não tenho mais contato com o time, não sei se está na ativa até hoje.

Eu sei, esse foi só o primeiro e o artigo já está bem longo, então vá pegar um copo de café e volte, temos mais 4 princípios ;)

 

 

OCP – Princípio Aberto/Fechado

 

Open Closed Principle ou Princípio Aberto/Fechado é o que corresponde ao O do acrônimo SOLID.

É um princípio que nos guia a deixar nossos componentes extensíveis, pois como já vimos no princípio anterior, quanto menos alteração, menos chance de quebrar as coisas, com componentes extensíveis evitamos mexer neles com frequência!

 

Este princípio nos diz que:

Um artefato de software dever ser aberto para extensão, mas fechado para modificação

 

Como eu já havia dito, ele nos guia na criação de componentes extensíveis para evitar alterações, lembre-se sempre que alterações podem gerar problemas, bons desenvolvedores devem buscar criar códigos com qualidade excepcional a ponto de quando surgir uma nova feature ou solicitação de mudança o código seja alterado o mínimo possível pois ele é extensível!

Bom, de início esse princípio pode parecer complexo, mas fica mais fácil em um exemplo prático.

No início de 2018 eu dei consultoria para uma empresa onde os desenvolvedores estavam querendo criar um projeto que havia sido iniciado a 6 meses do ZERO.

Para os stakeholders isso era um problema, afinal foi gasto muito dinheiro naquele projeto e esse dinheiro não poderia ser jogado fora, para ter certeza que os desenvolvedores não estavam só querendo fazer um projeto novo eles decidiram me chamar para avaliar a estrutura daquele sistema.

Era um sistema interno no qual realiza pagamento dos salários dos funcionários e gerava relatórios gráficos com base nos pagamentos realizados e ganhos durante o ano, era realmente um sistema crítico.

A reclamação principal era que na hora de obter os gráficos o sistema demorava uma eternidade, então o primeiro ponto que fui checar foi o componente que os gerava.

 

Deparar com esse tipo de componente me faz entender por que as baleias do ártico estão morrendo.

O problema de performance foi solucionado otimizando a query que o sistema executava, porém os desenvolvedores demoravam muito para implementar um novo tipo de relatório gráfico e sempre acabava dando problema em outro relatório, ou, em outros cenários os dados vinham de forma errada.

A solução para isso foi aplicar o OCP, perceba que todos os métodos chamam o BuscarDados, então ele pode estar em um componente genérico e extensível.

 

Nosso componente agora é extensível, então agora basta estender ele!

OBS: Novamente coloquei no mesmo arquivo a fim de visualização, sempre separe seus componentes em arquivos diferentes!

 

Veja que agora cada serviço de geração de relatório estende nosso componente base, dessa forma quando for necessária a alteração em um componente, essa alteração não impactará em outro e principalmente, é mais fácil criar relatórios!

Só a fim de curiosidade, depois dessa alteração e outras refatorações arquiteturais o sistema não precisou de mais nenhuma manutenção a nível de software e continua funcionando muito bem até hoje!

 

 

LSP – Princípio de Substituição de Liskov

 

Liskov Substitution Principle ou Princípio de Substituição de Liskov é o que corresponde ao L do acrônimo SOLID.

Esse princípio surgiu com base no estudo de Barabara Liskov em 1988 sobre subtipos, em resumo ele nos guia na hora de criarmos uma herança, sua descrição pode parecer bem confusa.

 

Esse princípio nos diz que:

O que queremos aqui é algo como a seguinte propriedade de substituição: se, para cada objeto o1 de tipo S, houver um objeto o2 de tipo T, o comportamento de P não seja modificado quando o1 for substituído por o2, então S é um subtipo de T

 

Você deve estar pensando, “nossa, não entendi absolutamente nada!”, pois é, nem eu entendi durante alguns anos, porém eu vou resumir toda a história!

Basicamente esse princípio nos guia a criar uma herança correta, onde a classe filha pode ser substituídapor outra classe filha ou pela sua classe base, afinal elas são do mesmo tipo.

 

Eu não gosto dessa descrição do princípio, por isso criei a minha:

Para uma herança entre ClasseA e ClasseB ser bem-sucedida a frase: ClasseB é ClasseA deve fazer sentido!

 

Vamos trazer para o mundo real, no final de 2018 eu dei consultoria para uma empresa local de uma cidade no interior que continha um aplicativo de aluguel de automóveis.

Por sinal o aplicativo estava muito bem-feito, porém a API que esse app consumia era extremamente inflexível, ou melhor, era difícil de criar entidades para o modelo de negócio, foi isso o que me falaram na entrevista e inicial.

Como de costume fui direto olhar onde me reportaram o problema e encontrei a seguinte implementação:

 

Por incrível que pareça o projeto estava aplicando o OCP corretamente, porém esqueceram do LSP.

Vamos ler as heranças apresentadas e ver se faz sentido.

“Carro é um automóvel”, faz todo o sentido, carro se locomove, tem motor e combustível!

“Bicleta é um automovel”, BEEEEEH (escute como se fosse aqueles alertas de televisão), bicicleta se locomove, porém não tem motor e muito menos combustível.

A herança não seguiu o LSP, logo foi uma herança errada, a classe bicicleta implementava métodos vazios.

Nesse mesmo projeto também, encontrei um cenário na parte de assinatura onde o LSP foi implementado corretamente, o que me fez ficar pensando que talvez o arquiteto/desenvolvedor daquele sistema não estava em um bom dia quando implementou aquilo.

 

Vamos analisar as heranças nesse caso.

PessoaFísica é uma Pessoa? Sim!

PessoaJurídica é uma Pessoa? Sim!

 

Então a herança foi bem-sucedida, afinal ambas podem ser substituídas por sua própria classe base!

Nesse projeto a solução foi corrigir as heranças realizadas nas entidades de negócio, felizmente foi um trabalho que durou somente um mês e o código voltou a ser fácil e flexível novamente!

 

 

ISP – Princípio da Segregação de Interface

 

Interface Segregation Principle ou Princípio da Segregação de Interface é o que corresponde ao I do acrônimo SOLID.

Esse princípio nos guia a diminuir o acoplamento e dependências entre nossos componentes, fazendo – os depender somente do que eles realmente precisam.

 

Esse princípio nos diz que:

Nenhum componente deve ser forçado a depender de componentes que ele não precisa.

 

Como eu já havia dito, ele nos guia na redução de dependência entre componentes, esse princípio em específico eu prefiro explicar com exemplos práticos!

Em 2017, fui chamado por um colega que atuava em uma multinacional de Portugal para poder ajudar a otimizar a arquitetura, esse sistema não tinha nenhuma reclamação na qual a estrutura pudesse ser a vilã, porém como um bom desenvolvedor ele gostaria de manter a qualidade, certíssimo!

Lembro-me que ele havia comentado que não gostava muito como a camada de acesso a dados foi criada e como ela se comportava juntamente com os componentes.

Novamente, após algum tempo fui direto onde estava o problema e me deparei com o seguinte trecho.

OBS: Novamente junto por questão de exibição!

 

Oh céus, baleias mortas de novo não.

Note que, além do código estar todo junto (não está segregado em responsabilidades separadas) ao criar uma categoria por exemplo, carregamos o método de criar um pedido e assim sucessivamente.

Isso foi facilmente solucionado quando dividimos isso, fazendo cada componente depender somente do necessário para sua execução!

 

Graças ao ISP as baleias foram salvas! Perceba que agora cada repositório cria e depende somente do que precisa, foram isolados, além disso, foram abstraídos através de interfaces!

Desse artigo, arrisco a dizer que foi o trabalho mais tranquilo que fiz, afinal outros pontos estavam muito bem-feitos!

 

 

DIP – Princípio da Inversão de Dependência

 

Por último e mais importante, o Depedency Inversion Principle ou Princípio da Inversão de Dependência é o que corresponde ao D do acrônimo SOLID.

Esse é o princípio pai da flexibilidade de software, ele nos guia a deixar os componentes o mais flexíveis possível utilizando as práticas de abstração.

 

Esse princípio nos diz que:

É preferível depender de abstrações ao invés de implementações concretas

 

Basicamente, utilizar interfaces e/ou classes abstratas te prove uma flexibilidade maior do que utilizar implementações concretas.

Mas por que inversão de dependência? Vamos ao exemplo prático que você irá entender.

O cenário a seguir é trágico, aconteceu em 2019, eu, junto com outros aspirantes a arquiteto fui chamado para ajudar em um projeto de uma empresa americana que tinha um software para compra e leitura de livros online.

A reclamação do líder técnico da aplicação era que existiam muitos débitos técnicos arquiteturais para serem solucionados, tantos que o projeto já estava com menos de 200% da produtividade no quesito entrega de feature em relação ao início (que havia sido há 2 anos, em 2017), isso claramente era um problema para a empresa, afinal ela tinha que gastar mais que o dobro do que deveria para solicitar uma alteração.

O cenário era uma verdadeira zona de guerra! Não existia padrão, não existia arquitetura, apenas corpos para todos os lados e explosões!

Junto com os outros arquitetos levantei pontos de melhoria, mas já estava sem esperança de salvar aquele projeto, arrisco a dizer, que ele sozinho violava TODOS os princípios demonstrados aqui, de longe o pior software no qual mexi.

Mas vamos focar na parte importante, a inversão de dependência!

No momento no qual eu estava levantando os pontos de melhoria que poderiam ser realizados, ou melhor, os soldados que eu iria conseguir reviver naquela cena de guerra eu me deparei com um trecho parecido com isso:

 

Nesse momento as baleias entraram em extinção!

Perceba que um componente de serviço instancia um componente de repositório, agora imagine o que acontecerá se o construtor desse repositório mudar, exatamente, você terá que mudar em TODAS as instâncias do código.

O componente de serviço tinha no mínimo uns 150 métodos instanciando o componente de repositório, caso houvesse alguma alteração algum pobre desenvolvedor teria que mudar em TODOS os lugares e torcer para que nada quebrasse em outros métodos.

Definitivamente uma tragédia!

Para resolver isso podemos utilizar o DIP, invertendo a dependência direta do componente de serviço, agora ao invés de instanciar diretamente o repositório utilizamos uma abstração dele, veja a seguir.

 

Note que agora quem chama os métodos é a abstração e não a implementação concreta, caso haja uma mudança no construtor a única alteração será no repositório e em NENHUM outro lugar a mais!

Usando a abstração nós invertemos a dependência, um componente de baixo nível deixa de chamar um componente de alto nível reduzindo o acoplamento!

Note também, que recebo a abstração via construtor, o nome disso é injeção de dependência, estou injetando a dependência de repositório no qual meu componente de serviço irá utilizar!

Para poder injetar eu preciso de um container de inversão de controle ou IoC, onde eu vou configurar o que será injetado em quem solicitar essa dependência, o ASP .NET Core tem um container próprio que atende a maioria dos casos!

Enfim, esse projeto tinha mais de 40 repositórios com muitos, mas muitos métodos, sem contar a estrutura desorganizada, componentes inferindo limites arquiteturais e problemas de segurança e conformidade gravíssimos! 

Para poder deixar minimamente utilizável iriamos precisar de 6 longos meses e o financeiro não aprovou o orçamento, afinal, um arquiteto custa caro, N arquitetos/desenvolvedores custam ainda mais!

A decisão foi descontinuar esse sistema e recriar um do zero absoluto, veja, a situação era tão critica que saiu mais barato pagar os desenvolvedores para construir um novo, dessa vez com um profissional de arquitetura acompanhando e auxiliando o time do que refatorar o projeto existente.

 

 

Conclusão

 

Ufa! Que artigo longo, hoje vimos um compilado de pedaços de experiencias que eu passei e como os princípios do SOLID poderiam ter sido aplicados para evitar o mal design de código.

O SOLID é a ponta do Ice Berg da arquitetura, existe um mundo além dele, porém, sozinho evitaria grande parte dos problemas das empresas em relação a estrutura de código.

Gostaria de agradecer por você ler até aqui, acredito que após esse artigo você irá enxergar seus códigos com novos olhos.

Espero que tenha ficado claro a importância da arquitetura e dos pilares do design de software no dia a dia, o projeto que os ignorar está fadado ao fracasso e até mesmo a falência!

Por fim gostaria de dizer que a veracidade dos conceitos veio diretamente do livro Clean Architecture, Robert C. Martin, o criador do SOLID!

OBS: Este livro deveria ser leitura obrigatória para qualquer desenvolvedor, profissional de arquitetura ou não ele te guia a criar um bom software! 

 

As implementações como já dito anteriormente vieram de experiências e memorias da minha carreira.

 

Agradeço a atenção e bons estudos!!!!!

Caso precise entrar em contato, me mande uma mensagem aqui.

Baixe o projeto clicando aqui.