SOLID para devs de alta performance

Vamos falar de SOLID pelo olhar do que acredito ser um “Dev de alta performance”, o nome desta série de artigos. São quatro pilares que acredito pautar um “Dev de alta performance”:

  • Seja profundo.
  • A prática leva a perfeição.
  • Tenha propósito.
  • Tenha método.

Agora de volta ao SOLID!

Nesse artigo você vai ver:

  • Orientação a Objetos (OO)
  • GRASP
  • Sintomas de Design Apodrecido
  • Finalmente: SOLID 
  • SRP – The Single Responsibility Principle
  • OCP – The Open-Closed Principle
  • Afinal, o que é aberto-fechado?
  • LSP – The Liskov Substitution Principle
  • ISP – The Interface Segregation Principle
  • O problema com interfaces gordas
  • DIP – The Dependency Inversion Principle

Orientação a Objetos (OO)

Quando falamos de SOLID rapidamente podemos ver associações a “Orientação a objetos”. Você sabe quem é o pai ou o principal criador do termo Orientação a objetos?

Ele ainda está vivo e seu nome é Alan Kay.

É incrível. Nós usamos, falamos e debatemos sobre o trabalho de alguém em vida. Ele pode ver o quanto impactou o mundo e a vida das pessoas.

A primeira coisa que acredito ser importante é: Devemos ser profundos.

Nesse sentido, o que é Orientação a Objetos?

Uma definição clássica é: “O termo orientação a objetos significa organizar o mundo real como uma coleção de objetos que incorporam estrutura de dados e um conjunto de operações que manipulam estes dados”.

Eu costumo dizer que programar é filosofar. Para nós, é abstrair o mundo real e “materializar” no mundo digital.

Vamos falar de SOLID mas será que temos profundidade sobre Orientação a Objetos, ou OO para os íntimos?

Sugiro conhecer bem Orientação a Objetos antes de mergulhar em SOLID, no entanto vamos apresentar alguns conceitos mínimos de OO antes de iniciarmos sobre SOLID.

Abstração

Orientação a Objetos é acima de tudo abstração. Compreender o mundo real dado um cenário e transpor para objetos que interagem com outros objetos em meio a processos.

Herança

Os objetos, como dito anteriormente, são abstrações, de forma que no mundo real existem derivações de A para B e que características de A se repetem em B. Podemos dizer assim que B herda de A.

Encapsulamento

Os objetos possuem características e ações, podendo ainda serem características de outros objetos. A deliberação dos acessos dos objetos entre objetos e suas características e ações são o encapsulamento.

Polimorfismo

Objetos possuem ações. Dado que objetos diferentes possuem a mesma ação, esta ação pode ocorrer de forma diferente.

Para entender SOLID para poder pôr em prática de forma coerente no mundo real, acredito que também é necessário termos um mínimo de conhecimento de algumas outras ferramentas, dentre elas aqui quero enfatizar GRASP.

GRASP

General responsibility assignment software patterns (ou principles), abreviado GRASP, consiste em diretrizes para atribuir responsabilidade a classes e objetos em OOP”.

Assim como o famoso GoF (Gang of Four) temos o GRASP, mas existem outros.

No final esses padrões são ferramentas dentro da nossa caixa de ferramentas que precisamos saber usar. De forma que, ao precisar apertar um parafuso, saibamos qual o seu tamanho e chave usar só de olhar para ele.

Agora vamos conhecer alguns pattern dentro do GRASP: 

Controller

Como seu nome sugere, ele controla. Mas é na delegação da atividade que ele se destaca. Seu papel é saber quem deve executar a ação baseada no “evento” que recebe.

Creator

De forma geral, a ideia aqui é entender quem tem a responsabilidade ou a quem seria interessante ter a responsabilidade de criar um determinado objeto.

Indirection e Low Coupling

Naturalmente são dois, mas faz total sentido entender que a essência de um é a mesma do outro. 

O uso de Indirection possibilita o baixo acoplamento. O Indirection vai agir como uma camada/objeto que permite tirar a preocupação da intereção de forma direta entre participantes desta intereção. 

Associado a isso, podemos usar o baixo acoplamento (Low coupling) para relacionar os objetos por interfaces, por exemplo.

High Cohesion

Esse ponto é muito debatido. Eu gosto de pensar em coesão funcional. Porquê entenda, algo coeso é coeso em relação há algo. Nesse sentido, penso em Alta Coesão (High Cohesion) em relação a papéis e ações. 

Quanto mais definido o papel de uma classe e a aplicabilidade dela, mais coesa funcionalmente ela é.

Information Expert

Como o nome sugere, aqui delegamos a responsabilidade a quem é o especialista da informação (Information Expert). O que naturalmente casa bastante com Alta Coesão.

Pure Fabrication

Uma classe cuja finalidade é prover objetos e não necessariamente faz parte do domínio (negócio).

Polimorfismo

As variações das ações mudam de acordo com as implementações sendo garantida a relação por uma dependência em comum.

Protected variations

Uma vez que usamos polimorfismo, aqui vamos proteger as variações por meio de uma interface, por exemplo. Ficando a cargo das implementações as variações de comportamento e protegendo todas de forma independente.

Achismos

Em 2003, Uncle Bob escreveu:

“O que é design Orientado a Objetos? Sobre o que é tudo isso? Quais são seus benefícios? Quais são seus custos? 

Pode parecer bobagem fazer essas perguntas em uma época em que virtualmente todo desenvolvedor de software está usando algum tipo de linguagem orientada a objetos. No entanto, a questão é importante porque, me parece, a maioria de nós usa essas linguagens sem saber porquê e sem saber como tirar o máximo proveito delas.”

Estamos em 2021 e eu vejo as palavras de Uncle Bob mais atuais do que nunca.

Sem achismos

O que vamos abordar em SOLID tem suas bases em um artigo de Uncle Bob de 2000. Posteriormente Michael Feathers criaria o acrônimo mundialmente conhecido SOLID.

Durante as últimas duas décadas vimos surgir muitos artigos sobre SOLID. Muitas pessoas falaram e falam sobre o tema, mas eu me pergunto: Será que essas pessoas leram os artigos originais de Uncle Bob?

Essa pergunta me vem à cabeça em virtude de uma segunda pergunta que farei mais abaixo.

Sintomas de Design Apodrecido

Os princípios que vamos ver a seguir do SOLID são uma resposta proposta por Uncle Bob para o que ele chamou de “Symptoms of Rotting Design” ou em tradução livre “Sintomas de Design Apodrecido”.

Existem quatro sintomas segundo o autor, você sabe quais são eles?

Um software apodrece de dentro pra fora por vários motivos. Segundo Uncle Bob “Changing Requirements (Requisições de mudança)” associadas a baixo conhecimento e preparo dos times técnicos, além de um software fortemente acoplado que ele vai atribuir a “Dependency Management (gestão de dependências)”. 

Essas duas causas geram quatro sintomas:

1 – Rigidity

Difícil de mudar. Toda mudança gera várias outras mudanças.

2 – Fragility

Frágil de tal maneira que mudanças quebram outras partes do sistema.

3 – Immobility

Escrito de uma forma que o código não-reutilizável e ou nem seus módulos.

4 – Viscosity

Aqui ele aponta duas formas: 

1 —Viscosity of the design

Quando fazer algo “errado”, que quebra o Design original, é mais fácil do que manter o design original.

2 —  Viscosity of the environment

Quando o ambiente, a arquitetura é projetada de uma forma que “furar a arquitetura” seja mais fácil que seguir a arquitetura. Ou mesmo questões relacionadas à Builds, versionamento de código e questões relacionadas ao ambiente.

Finalmente: SOLID 

Agora que sabemos a pergunta que gerou a resposta de Uncle Bob, vamos nos aprofundar em SOLID! Lembrando, a sigla SOLID saiu dos seus princípios: 

  • (SRP) The Single Responsibility Principle
  • (OCP) The Open-Closed Principle
  • (LSP) The Liskov Substitution Principle
  • (ISP) The Interface Segregation Principle
  • (DIP) The Dependency Inversion Principle

Agora vamos ver cada princípio em detalhe:

SRP – The Single Responsibility Principle

Em seu paper original Uncle Bob escreveu:

“Ninguém, exceto o próprio Buda deve assumir a responsabilidade de revelar segredos ocultos … Este princípio foi descrito na obra de Tom DeMarco1 e Meilir Page-Jones2. Eles chamaram de coesão. Eles definiram coesão como a relação funcional dos elementos de um módulo. Neste capítulo, mudaremos um pouco esse significado e relacionaremos a coesão às forças que fazem com que um módulo, ou uma classe, mude.”

E aqui entra a famosa frase que todo mundo já viu dezenas ou centenas de vezes:

 “A CLASS SHOULD HAVE ONLY ONE REASON TO CHANGE.” 

Mas convenhamos, é muita subjetividade na nossa área, não acham? 

Subjetividade e o princípio de responsabilidade única

Eu acredito que existe muita subjetividade quando falamos de padrões e aqui em específico no SOLID, vou adotar métricas mais objetivas. 

Assim, temos o caso que tratamos aqui, a responsabilidade única e como dito por Uncle Bob “Uma classe deve ter apenas uma razão para mudar”. Mas e se eu disser que uma classe representa um CRUD e muda quando as regras desse CRUD mudam não é razoável?

Esse é um dos problemas atuais quando falamos de responsabilidade única: a subjetividade!

Por isso, proponho dois conceitos para determinar a responsabilidade de uma classe. Baseado em Uncle Bob, eu adicionei mais exatidão a questão a seguir:

1 — Uma classe deve ter uma responsabilidade de forma que seu arquivo não possua 20% a mais do que o valor determinado de linhas. Esse valor default eu vou determinar em 150, mas fica flexível a vocês entenderem o seu cenário e aplicarem esse valor. 

Dessa forma eu digo que uma classe que possua menos que o valor default -20% de linhas é uma classe responsabilizada adequadamente. 

Uma classe que possua entre -20% a +20% que valor default de linhas é uma classe perigosa. 

Já uma classe que possua mais de 20% a mais do valor default de linhas é uma classe que deve ser investigada.

2 — Associado a métrica de linhas de código, assumo que toda classe deve ter apenas um papel, mas pode ser parte de um ou mais fluxos de processos de forma que seu papel seja sua única razão de mudar.

Um exemplo é uma classe que implementa a interface Converter do Spring. Ela tem seu papel bem definido, que é converter um objeto em outro, e pode ser utilizada em diversos fluxos que seja necessária a conversão do objeto. Embora possa atuar em vários fluxos, sua única razão de mudar é o seu papel, converter um objeto em outro.

OCP – The Open-Closed Principle

“Entidades de software (classes, módulos, funções etc.) devem estar abertas para extensão, mas fechadas para modificações.”

Quem aqui já assistiu Vikings? Eu parei na última temporada, com medo de não gostar do final, então parei enquanto ainda estava gostando, rs. Sem spoilers nos comentários, hein! 

Mas como disse Ivar, não o de Vikings, mas Ivar Jacobson:

“Todos os sistemas mudam durante seus ciclos de vida. Isto deve ser tido em mente ao desenvolver sistemas que deverão durar mais do que a primeira versão.”

Se você pensou quem é Ivar Jacobson, isso é pra você: “Ivar Hjalmar Jacobson é um cientista da computação sueco. Concluiu seu mestrado em engenharia eletrônica no Chalmers Institute of Technology de Gotemburgo em 1962 e um Ph.D. no Royal Institute of Technology de Estocolmo em 1985”

Em resumo, ele é apenas um senhor de 81 anos que já era Ph.D antes de muitos de nós nascermos.

Bertrand Meyer em 1988 cunhou o que vamos ver agora: o famoso princípio do aberto-fechado. Provavelmente vocês imaginavam que era de Uncle Bob, mas é de Meyer a famosa frase inicial e a autoria do princípio do aberto-fechado. Uncle escreve baseado em Meyer.

Afinal, o que é aberto-fechado?

Aberto para extensão e fechado para mudanças. Os requisitos mudam, então como a minha aplicação não vai mudar seu código?

O que precisamos entender aqui é:

Meu software está saudável se ao precisar alterar um requisito eu sei o impacto da minha alteração, tenho um escopo de alteração definido e limitado condizente com a mudança no negócio.

Ok, mas o que eu preciso para alcançar isso?

“Em português, Abstração. Em inglês, abstraction. Em francês…”

Então agora vou definir três verdades sobre OCP na minha humilde opinião:

1 — As relações entre os artefatos devem ter ao menos uma camada de abstração.

2 — Cada relação direta entre artefatos é potencialmente um risco e deve ser vista com atenção.

Por exemplo, quando utilizamos o padrão Bridge e temos nosso “Service” e “ServiceImpl”. Ao relacionar com o Resources ou Controller fazemos por meio da abstração, nesse caso em Java a Interface.

Observação: Isso não deve quebrar o SRP, ou seja, devem ser aplicadas abstrações de forma exponencial ao crescimento do software. Claro, mantendo os arquivos coesos e com responsabilidades únicas. Dica: “Interfaces funcionais” vão ajudar muito aqui.

LSP – The Liskov Substitution Principle

“O que se deseja aqui é algo como a seguinte propriedade de substituição: se para cada objeto O1 do tipo S existe um objeto O2 do tipo T, tal que, para todos os programas P definidos em termos de T, o comportamento de P fica inalterado quando O1 é substituído por O2, então S é um subtipo de T.”

Barbara Liskov cunhou este princípio em 1988.

Uncle Bob retrata em seu paper o que considera uma violação do OCP (princípio aberto-fechado):

“Tal função viola o princípio Aberto-Fechado porque deve ser modificado sempre que uma nova derivada da classe base é criada.”

Assim, fica claro que uma relação entre artefatos deve desprezar sua parte concreta de tal forma que a abstração se mantenha inviolável. Em outras palavras, eu devo poder substituir o SUBTIPO pelo TIPO ou TIPO pelo SUBTIPO e a aplicação deve permanecer tendo o comportamento esperado.

“Tudo está conectado, o fim é o começo e o começo é o fim”.

Quem aqui já assistiu ou conhece a série Dark? rsrsrs

Para garantir o princípio da substituição de Liskov, eu conceituo três pontos:

1 — Toda relação deve ser garantida independente do nível hierárquico do maior para menor desprezando o conhecimento de seus herdeiros.

Nesse ponto estamos falando de herança. Por tanto, eu vou definir que deve ser evitada e sugiro composição, mas em alguns casos pontuais pode, ou até deve, ser utilizada.

2 — Toda relação deve ser garantida por meio de abstração prioritariamente.

Aqui falo explicitamente sobre o alicerce do OCP.

3 — Toda relação deve ser passível de adições sem que prejudique as relações existentes.

Aqui mais uma vez toca no OCP, afinal “tudo está conectado” rsrsrs… Seja estendendo uma classe (quando estritamente necessário) ou implementando uma abstração, jamais deve comprometer o código existente.

ISP – The Interface Segregation Principle

“Clients should not be forced to depend upon interfaces that they do not use.” 

Precisamos entender melhor essa frase.

Client e Servidor, nesse contexto, se refere a quem serve as regras e quem cumpre as regras. Nesse sentido, o que Uncle Bob diz é que uma vez que um cliente vai ser regido por um servidor, ele não deve ser obrigado a implementar ações que não utiliza.

Eu gosto muito desse princípio. Eu costumo definir seu conceito com duas frases:

1- Menos é mais.

2- Necessário, somente o necessário.

O problema com interfaces gordas

Como disse Uncle Bob “This principle deals with the disadvantages of “fat” interfaces. Classes that have “fat” interfaces are classes whose interfaces are not cohesive

O problema que ISP quer resolver é o que Uncle Bob vai chamar de Interface Gorda ou Poluída. Existem vários exemplos, mas quero trazer algo do nosso dia a dia.

Pense em uma interface de um CRUD, por exemplo. Nela você tem inúmeras ações de responsabilidades diferentes que vão desde criar, alterar, deletar e até consultar diversas formas inclusive. 

Calma, eu já cometi esse deslize também, já construí APIs com uma única interface CRUD(Service) que possuíam todos os métodos do CRUD. O ponto central aqui é a coesão pela visão do Uncle Bob como especificado no SRP e é justamente para ele que vamos voltar e que vai nos dar as diretrizes de como garantir este princípio aplicado para os relacionamentos cliente/servidor.

Coesão, precisamos falar de coesão

Esse é um ponto que abre muita margem para discussões e que diferentes autores vão ter seu próprio significado dentro do contexto do que estão trazendo para a mesa. Aqui se refere a SRP, ou seja, “às forças que fazem com que um módulo, ou uma classe, mude.” como disse Uncle Bob.

Agora que entendemos o conceito e as entrelinhas, como damos corpo a esse princípio e como podemos nos guiar quando vermos nossos clientes/servidores se estão de acordo?

1 — Todo princípio deve respeitar os demais.

2 — Entenda Interface como Abstração. Dessa forma, evite ao máximo servidores concretos, mas se necessário utilize sem ferir outros princípios.

3 — Uma classe concreta nunca deve ser obrigada a utilizar uma abstração que não use.

Voltando ao CRUD(Service)

Para finalizar, vamos resolver o problema que falei do CRUD(Service). Na prática criamos uma interface para cada “ação abstrata” do CRUD.

CreateDomainService -> CreateDomainServiceImpl

AlterDomainService -> AlterDomainServiceImpl

GetDomainService -> GetDomainServiceImpl

DeleteDomainService -> DeleteDomainServiceImpl

Injetamos as interfaces no nosso Resources e bingo. Perceba que se estamos trabalhando com uma API e temos nosso Resources que gerencia nossas URIs é convencionado a usar DI/IoC no caso do Spring, por exemplo.

Aqui eu deixo uma “polêmica”:

Todos esses “services” estão como atributos de classe, mas eles deveriam ser? Aqui eu deixo essa polêmica que eu vou abordar em outro artigo.

DIP – The Dependency Inversion Principle

“Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações; Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.”

Vamos viajar para a década de 90, mais precisamente no ano que o Brasil levou o tetracampeonato na Copa do Mundo. Uncle Bob escreveu pela primeira vez sobre DIP em 1994 e o assunto continua atual.

O que Uncle Bob quer dizer é:

“Do maior para menor, da teoria para a prática.” Carly, 2020 rsrsrs

Trazendo para nosso mundo, as camadas de nossas APIs da mais externa para a mais interna devem se relacionar nesse sentido de dependência. A relação entre essas camadas deve se dar por abstrações.

Basicamente se seguirmos o SOLID até aqui o DIP será seguido naturalmente, em especial OCP e LSP.

Desde o início eu falei sobre subjetividade e é aqui que algo simples se torna complexo.

“O que torna um design rígido, frágil e difícil de reutilizar. É a interdependência de subsistemas dentro desse design. Um design é rígido se não puder ser alterado facilmente.”

Uncle Bob em seu paper original traz sua visão de que as dependências podem gerar enormes problemas e propõe sua resposta ao problema apresentado como dito acima.

Mas ele também entende que dependências são necessárias e que existem dois tipos de dependências, as boas e as ruins.

Quanto mais difícil é para uma classe mudar, melhor que ela seja uma dependência. Ele vai chamar classes assim de “classes responsáveis”.

“Sua aplicação não deve depender de classes concretas.”

Mas isso é impossível. Sim, por isso o conceito de dependências boas e ruins.

Uma vez que sua classe vai precisar depender de classes concretas, devem ser analisados OCP e LSP. Se ainda for necessário uma dependência concreta, que seja uma boa dependência. Assim, escolha e analise as dependências concretas de forma que sejam classes responsáveis, classes que dificilmente venham a mudar.

SOLID: crucial para Devs de alta performance

Bem, por fim chegamos ao fim. Espero que possamos ter ido além da superfície quando falamos de SOLID e que a partir deste conteúdo você possa ter insumo para diante de perguntas e problemas em seu projeto, decidir se faz sentido aplicar ou não SOLID e que para além de SOLID, possa ter inspirado a conhecer mais sobre GRASP e se aprofundar sobre outros temas e assuntos.

Que a mensagem principal contida nas entrelinhas e que inspiraram este artigo possa ter sido absorvida, a mensagem de que precisamos ser mais profundos sobre aquilo que nos propomos a trabalhar e fazer do nosso trabalho uma busca constante por excelência.

Bibliografia e referências

Código limpo: habilidades práticas do Agile Software – Robert C. Martin (Uncle Bob).

Design Principles and Design Patterns – Robert C. Martin (Uncle Bob).

OO Design Quality Metrics: An Analysis of Dependencies – Robert C. Martin (Uncle Bob).

The Principles of OOD – Robert C. Martin (Uncle Bob).

Paper “SRP: The Single Responsibility Principle” – Robert C. Martin (Uncle Bob). Disponibilizado gratuitamente pelo autor na Google Drive.

Paper The Open-Closed Principle – Robert C. Martin (Uncle Bob). Disponibilizado gratuitamente pelo autor na Google Drive.

Paper The Liskov Substitution Principle – Robert C. Martin (Uncle Bob). Disponibilizado gratuitamente pelo autor na Google Drive.

Paper The Interface Segregation Principle – Robert C. Martin (Uncle Bob). Disponibilizado gratuitamente pelo autor na Google Drive.

Programação Orientada a Objetos: uma introdução – Julio Cesar Bessa Monqueiro.

GRASP: padrões para atribuição de responsabilidades – Profa. Dra. Elisa Yumi Nakagawa. 

Padrões para atribuir responsabilidades: Alta Coesão – Projeto de Software Orientado a Objeto (UFCG)

The SOLID Principles of Software Design – Robert C. Martin (Uncle Bob).

Site oficial Uncle Bob Martin

Michael Feathers Blog Oficial

O que significa Orientação a objetos? – Macoratti.net

.NET – Design Patterns – Identificando e aplicando padrões – Macoratti.net

Robert C. Martin – Wikipédia.

SOLID – Wikipédia.

GRASP – Wikipédia.

Capa do artigo SOLID para devs de alta performance Com um teclado com várias luzes coloridas
Foto Carly Oliveira
Engenheiro de Software
Casado e pai de um casal, acredito que programar é filosofar e por tanto, é na busca das perguntas e ciente da ausência de certezas que busco trilhar o caminho da engenharia de software.

Este site utiliza cookies para proporcionar uma experiência de navegação melhor. Consulte nossa Política de Privacidade.