Componentes são as menores entidades que podem ser implementadas em um sistema (MARTIN, 2017). Esses componentes podem ser reunidos em um executável, em um único arquivo ou implementados de forma independente como plugins separados carregados dinamicamente. Utilizando qualquer uma das formas de implementação, desde que um componente tenha sido bem projetado, ele tem a capacidade de ser desenvolvido e implementado de forma independente.
Fazendo uma breve retrospectiva na história dos componentes, nota-se que na década de 1980 as bibliotecas eram carregadas nos programas por meio de ligadores, que nada mais eram do que segmentos compiláveis e recarregáveis. Entretanto, com o desenvolvimento tecnológico, o aumento da complexidade dos programas e o uso de linguagens de alto nível, o tempo de compilação desses módulos era muito elevado e estava prestes a se tornar inviável. Contudo, no fim da década de 1980 os discos começaram a diminuir e ficar mais rápidos, diminuindo bastante esse tempo de compilação. Nos meados dos anos 1990, o tempo gasto com ligação diminuiu bastante, em alguns casos passou de 1 hora para alguns segundos. Os computadores e dispositivos ficaram tão rápidos que se tornou possível fazer a ligação na hora do carregamento, podendo ligar arquivos ou bibliotecas compartilhadas em segundos e a partir daí executar o programa. Foi assim que surgiu a arquitetura e o plugin de componentes (MARTIN, 2017).
Os componentes tem alguns princípios, a seguir veremos sobre os princípios de coesão e acoplamento.
Coesão de Componentes
Decidir quais classes pertencem a quais componentes é uma decisão importante e que faz uso de princípios de engenharia de software (MARTIN, 2017).
Os princípios da coesão de componentes apresentados são os seguintes:
REP - Reuse/Release Equivalence Principle (ou Princípio da Equivalência do Reúso/Release em português)
CCP - Common Closure Principle (ou Princípio do Fechamento Comum em português)
CRP - Common Reuse Principle (ou Princípio do Reúso Comum em português)
A seguir será analisado cada um dos três em detalhe.
REP: Princípio da Equivalência do Reúso/Release
Esse princípio fala que para se reutilizar componentes de software estes devem ser rastreados por um processo e recebam números de release. Sem esses números não seria possível garantir a compatibilidade dos componentes uns com os outros. Outra importância desse número é para que os desenvolvedores saibam quando serão lançados novos releases e quais mudanças eles contemplarão. Por isso, deve-se gerar notificações e produzir documentos para que o usuário tome a decisão de migrar para o novo ou continuar com o antigo.
Analisando esse princípio por intermédio da arquitetura de software pode-se concluir que os módulos e classes formados em um componente devem fazer parte de um grupo coeso, de forma que o componente não seja uma mistura de classes e módulos aleatórios, mas sim ter um tema ou propósito que todos os módulos compartilhem. MARTIN (2017) expressa que as classes e os módulos agrupados em um componente devem poder ter um release em conjunto, pois ao compartilhar a mesma versão, o mesmo rastreamento de release e estarem na mesma documentação facilita o entendimento tanto do autor quanto dos usuários.
CCP: Princípio do Fechamento Comum
O Princípio do Fechamento Comum discorre que deve-se reunir em componentes as classes que mudam pelas mesmas razões e nos mesmos momentos, e separar em diferentes componentes as classes que mudam em momentos distintos e por diferentes razões (MARTIN, 2017).
Esse princípio é uma reformulação do Princípio de Responsabilidade Única (SRP) visto no artigo sobre SOLID aplicando-o para componentes. Assim como SRP afirma que uma classe não deve ter muitos motivos para mudar, o CCP declara que um componente não deve ter muitos motivos para mudar.
Quando é necessário fazer uma mudança em um código, o ideal é que essas mudanças ocorram em apenas um componente, não em vários. Assim, é necessário implementar novamente apenas o componente modificado, sem alterar os demais. Por isso, o CCP alega que todas as classes que têm alta probabilidade de mudar pelas mesmas razões, sejam reunidas no mesmo lugar. Se duas ou mais classes são fortemente ligadas e sempre mudam juntas, elas devem pertencer ao mesmo componente, para assim, reduzir a quantidade de trabalho de reimplantar, revalidar e fazer release do software.
O CCP também se relaciona com mais um princípio do SOLID, o Princı́pio Aberto/Fechado (OCP) - também visto no artigo sobre SOLID - sendo o termo ”fechado” usado em ambos com o mesmo sentido. O OCP afirma que as classes devem ser fechadas para modificações, mas abertas para extensões. Por não ser possível ter um fechamento completo, as classes são projetadas de modo que fiquem fechadas para a maioria dos tipos de mudanças esperados ou observados. O CCP desenvolve essa definição ao reunir em um mesmo componente as classes fechadas para os mesmos tipos de mudanças, de forma que ao ocorrer uma modificação nos requisitos ela tenha uma grande chance de se limitar a uma quantidade reduzida de componentes.
CRP: Princípio do Reúso Comum
Este princípio atesta que as classes e módulos que inclinam-se a ser reutilizados juntos pertencem a um mesmo componente (MARTIN, 2017). Devido ao fato das classe raramente serem reutilizadas isoladamente é esperado que diversas classes que fazem parte da abstração reutilizável colaborem umas com as outras. Portanto, segundo o CRP, essas classes devem pertencer a um mesmo componente, tendo elas várias dependências entre si.
Além de indicar quais classes devem ser reunidas em um componente, o CRP aponta quais classes não devem ser reunidas em um componente. Quando há dependência de um componente, é preciso ter certeza que essa dependência se aplica a todas as classes de um componente, e que as classes de um componente são tão interligadas que se torna impossível depender de uma e não depender de todas. Caso isso não seja aplicado, será preciso implementar novamente mais componentes do que o necessário.
Demonstrando de forma prática considera-se um componente A e um componente B. B faz uso de A, criando uma dependência entre eles. Mesmo que B precise apenas de uma classe de A, sempre que A for modificada B precisará de modificações correspondentes. Logo, se A fizer uma modificação em uma classe que B não utiliza, ainda assim, B precisará ser modificado, recompilado, revalidado e reimplementado. Essas modificações em B são desnecessárias, pois a classe alterada não afeta em nada seu funcionamento, mas devido ao seu acoplamento em A é preciso ter esse trabalho desnecessário. Logo, as classe que não tem uma forte ligação não devem estar no mesmo componente.
Diagrama de Tensão para Coesão dos Componentes
Como visto ao longo dos tópicos anteriores, os três princípios diferem muito entre si. O REP e o CCP tendem a aumentar a quantidade de componentes e o CRP tende a diminuí-los. Sobre a interação dos princípios entre si, MARTIN (2017) apresenta um diagrama de tensão, no qual as suas bordas descrevem o custo de abandonar o princípio do vértice oposto.
O arquiteto de software precisa encontrar uma posição no triângulo de tensão da figura acima de forma a satisfazer as demandas atuais do desenvolvimento e saber que essas demandas mudarão com o tempo. Ao focar no REP e no CRP, percebe-se que muitos componentes são impactados ao realizarem mudanças simples. Ao focar mais no CCP e no REP, fará com que sejam gerados muitos releases desnecessários. MARTIN (2017) declara que geralmente os projetos tendem a começar do lado direito do triângulo e, à medida que amadurecem, passam para o lado esquerdo.
Acoplamento de Componentes
Sobre o acoplamento dos componentes também existem três princípios que abordam os relacionamentos entre os componentes. Os princípios são os seguintes:
ADP - Acyclic Dependencies Principle (ou Princípio das Dependências Acíclicas em português)
SDP - Stable-Dependency Principle (ou Princípio de Dependências Estáveis em português)
SAP - Stable-Abstractions Principle (ou Princípio de Abstrações Estáveis em português)
A seguir será analisado cada um deles.
ADP: Princípio das Dependências Acíclicas
Um dos maiores erros de desenvolvedores com pouca experiência é ter várias pessoas trabalhando em um mesmo código. Por causa dessa falta de divisão de um sistema é muito comum que a medida em que um problema é resolvido por um desenvolvedor, outro problema surge na parte de responsabilidade de outra pessoa.
Para evitar que isso aconteça é preciso particionar o ambiente de desenvolvimento em componentes passíveis de release (MARTIN, 2017). Assim, cada componente se torna um ambiente de trabalho sob a responsabilidade de um desenvolvedor ou de uma equipe. Ao fazer este componente funcionar é gerada uma versão para ser usada pelas outras equipes, após isto, os desenvolvedores voltam a trabalhar em suas áreas privadas até ter uma outra versão. As demais equipes podem decidir se já começam a usar o novo release ou continuam com o antigo. Dessa forma, as mudanças de um componente não precisam ter efeito imediato nas outras equipes, sendo responsabilidade de cada uma decidir quando adaptar os seus componentes para o novo release.
A estrutura de dependências dos componentes de um sistema que siga esse princípio deve ser um grafo direcionado, onde os nós são os componentes, e as arestas direcionadas são as relações de dependência.
Ao analisar o diagrama de dependência acima (adaptado de MARTIN (2017)), nota-se que independentemente de qual componente comece é impossível voltar para o componente inicial. Essa estrutura é um grafo acíclico direcionado.
Caso haja um novo requisito que mude o comportamento de uma classe, é possível que sejam gerados ciclos de dependência, fazendo com que os problemas citados no início desta seção apareçam. Se existir um ciclo no sistema é preciso quebrá-lo para voltar a ter um grafo acíclico direcionado.
Existem dois modos de quebrar esses ciclos (MARTIN, 2017). O primeiro é aplicando o Princípio da Inversão de Dependências, no qual pode ser criada uma interface com os métodos que uma determinada classe precisa e em seguida herdá-la em outra classe, de forma que seja feita a inversão de dependências e assim quebrar o ciclo. O segundo modo é criar um componente que ambas as classes que formam o ciclo dependam, e mover as classes essenciais em ambos para esse novo componente. Com isso, as classes passam a depender desse novo componente e o ciclo é quebrado.
SDP: Princípio de Dependências Estáveis
O Princípio de Dependências Estáveis afirma que os projetos não podem ser completamente estáticos, que alguma volatilidade é necessária para manter o projeto. Ao utilizar o Princípio do Fechamento Comum são criados componentes sensíveis a algumas mudanças, mas não a outras. Alguns desses componentes são criados para serem voláteis, logo espera-se que eles mudem (MARTIN, 2017).
Ao utilizar o Princípio de Dependências Estáveis garante-se que um módulo difícil de mudar não dependa de um componente volátil, pois isso faria com que o componente volátil se tornasse difícil de alterar. A estabilidade de algo está relacionada com o trabalho necessário para fazer uma mudança. Aplicado a software, existem diversos fatores que tornam um componente difícil de mudar, como por exemplo o tamanho, a clareza e a complexidade. Uma maneira segura de fazer com que um componente seja difícil de mudar é fazer com que outros componentes dependam dele, já que isso requer muito trabalho para conciliar mudanças com todos os seus dependentes (MARTIN, 2017).
Contudo, nem todos os componentes devem ser estáveis, pois isso deixaria o sistema imutável. É necessário projetar a estrutura de componentes de forma a ter componentes estáveis e instáveis. A figura (adaptada de MARTIN (2017)) a seguir mostra que os componentes instáveis dependem de um componente estável.
SAP: Princípio de Abstrações Estáveis
Este princípio estabelece uma relação entre estabilidade e abstração. Ele afirma que um componente estável também deve ser abstrato para que a estabilidade não impeça a sua extensão, e que um componente instável deve ser concreto, posto que sua instabilidade faz com que o seu código concreto seja facilmente modificado. Por consequência, para um componente ser estável ele deve possuir interfaces e classes abstratas de modo que possa ser estendido (MARTIN, 2017).
Conclusão
Após a análise dos princípios de coesão de componentes, percebe-se que há uma variedade complexa de coesão, pois equilibrar as forças opostas envolvidas na reutilização e no desenvolvimento não é uma tarefa trivial. É preciso saber que esse equilíbrio é dinâmico e o que é adequado para o sistema hoje pode não ser o ideal no futuro.
Ao analisar os princípios de acoplamento, temos uma visão de como eles também influenciam o trabalho em equipe. O código é vivo e durante o desenvolvimento há a inserção de dependências por membros dos times, e esses princípios nos guiam para termos uma integração sem tantos problemas futuros.
Ao criar ou utilizar componentes, não se deve apenas pensar em solucionar o problema técnico, e sim como sua solução vai se comportar junto com os componentes já existentes no sistema. Existem diversos fatores a serem pensados na hora da criação ou uso de um novo componente, então deve se analisar previamente qual seu propósito e qual a melhor maneira de o implementar na solução.
Referências
- MARTIN, Robert C. Clean Architecture: A Craftsman’s Guide to Software Structure and Design. 1st. ed. USA: Prentice Hall Press, 2017. ISBN 0134494164.
Top comments (0)