Pacotes?
Já falamos sobre pacotes em vários artigos aqui. Se você tem dúvida sobre o conceito, eu recomendo a leitura de "Camadas x Pacotes", e também pode consultar outras fontes, como "http://pt.wikipedia.org/wiki/Pacote", ou mesmo "http://pt.wikipedia.org/wiki/Diagrama_de_pacotes".Dependência?
Um relacionamento de dependência pode existir entre dois elementos (Classes ou Interfaces) ou entre Pacotes, o que acontece quando um elemento de um pacote faz referência a um elemento de outro pacote. Esta referência pode ser:- Instanciar uma Classe de outro Pacote;
- Invocar um método estático de uma Classe de outro Pacote;
- Definir uma variável ou um parâmetro de método com um tipo definido em outro Pacote;
A dependência pode ser mais forte ou mais fraca. Dependências de compilação são mais fortes, e, como o nome diz, exigem a presença do elemento alvo (a classe ou interface da qual dependem), no caminho de construção (build path) para que possam ser compiladas. Este seria um exemplo:
...
MinhaClasse xpto = new MinhaClasse;
...
Se a Classe "MinhaClasse" não estiver no "build path", não conseguiremos compilar esse código. Agora, temos a dependência fraca, também chamada de "Runtime dependency", que não exige a presença do elemento alvo no "build path" para compilação, porém, pode causar erros, caso ele não esteja presente no "build path" no momento da execução do código. Veja um exemplo:
package com.obomprogramador.teste;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class RuntimeClient {
public void test() {
try {
Class clazz = Class.forName("com.obomprogramador.teste.RuntimeDep");
Object obj1 = clazz.newInstance();
Method metodo = obj1.getClass().getMethod("calcular");
metodo.invoke(obj1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Neste exemplo, podemos compilar nossa classe "RuntimeClient" sem problemas, mesmo que a classe "com.obomprogramador.teste.RuntimeDep" não esteja no "build path". Porém, ao tentarmos executar, vamos tomar uma "Exception" do tipo "ClassNotFoundException", caso a classe não esteja no "build path".
Manutenibilidade, testabilidade e flexibilidade
Como eu já disse aqui ("O nirvana do bom código fonte"), existem três capacidades que são afetadas diretamente pela maneira como os Pacotes estão relacionados:- Manutenibilidade: A facilidade em realizar manutenções corretivas ou evolutivas em determinado código fonte;
- Testabilidade: A facilidade em escrever testes abrangentes (aumentar a Cobertura de testes) em um determinado código fonte;
- Flexibilidade: A facilidade em adaptar um software às mudanças nos requisitos ou às novas funcionalidades;
O que prejudica estas três capacidades? Certamente, a Complexidade excessiva do código fonte pode comprometer as três. Porém, existem mais problemas, por exemplo, o alto Acoplamento entre seus elementos, geralmente causado por baixa Coesão ou por Complexidade Acidental, pode causar o efeito de "Brittleness", que é a propagação de alterações.
Propagação de alterações é quando temos que mexer em uma pequena funcionalidade, digamos, de apenas uma única classe, porém, somos obrigados a alterar vários elementos em função disto.
A necessidade de estudar bem as dependências entre pacotes é exatamente para melhorar estes três indicadores, fundamentais para reduzir a Dívida Técnica de um software.
Acoplamentos
Acoplamento é o termo que designa uma dependência entre pacotes. Temos dois tipos:- Acoplamentos Aferentes (Ca ou "Afferent Couplings"): É a quantidade de outros pacotes que dependem das classes de determinado pacote. Indica a responsabilidade do pacote perante o Software, como um todo;
- Acoplamentos Eferentes (Ce ou "Efferent Couplings"): É a quantidade de outros pacotes, que as classes de determinado pacote dependem. Indica a independência de determinado pacote, com relação ao resto do Software;
É muito importante analisarmos com calma a questão dos acoplamentos. O que significa? Significa que um pacote com Ca muito alto (eu sei, é meio "fuzzy logic") tem uma responsabilidade muito grande, talvez até mesmo múltiplas responsabilidades, e pode ser uma grande fonte de riscos.
Um pacote com Ce muito alto indica que é muito dependente de outros pacotes, logo, pode ser mais sujeito à propagação de alterações, o que aumenta o risco de manutenção do Software.
Instabilidade
É a vulnerabilidade de um pacote com relação à propagação de alterações. É uma métrica proposta por Robert C. Martin, e é calculada segundo a fórmula: I = Ce / (Ce + Ca). Um pacote com valor de instabilidade próximo a Zero significa que pouco instável, ou seja, mais estável, já, um pacote com valor próximo a Um significa que é muito instável, ou seja, muito sujeito à alterações.Abstrações
Abstração é a representação de um comportamento através de um elemento abstrato, seja uma Classe Abstrata ou uma Interface. Implementação é a criação de uma Classe Concreta (instanciável) a partir de uma Abstração.Eis um exemplo de abstração:
public interface Veiculo {
boolean ligar();
}
E eis um exemplo de implementação:public class Carro implements Veiculo {
public boolean ligar() {
...
}
}
O uso de Abstrações, especialmente nas dependências entre pacotes, aumenta a manutenibilidade e flexibilidade do software, nos permitindo Injetar classes concretas sem necessidade de alteração de código fonte.O ideal é termos "Camadas de abstração" em nosso software, de tal forma que isolem ao máximo possível a propagação de alterações. Se desejarmos substituir uma Implementação, podemos fazê-lo apenas substituindo a classe Concreta (a implementação) a ser utilizada, sem necessidade de alteração de código fonte.
Existem alguns princípios de projeto Orientado a Objetos que definem isso:
- Interface Segregation Principle - ISP (Princípio da Segregação de Interfaces): A dependência de uma Classe para outra deve ser baseada na menor interface possível;
- Dependency Inversion Principle - DIP (Princípio da Inversão de Dependências): As classes não devem depender de implementações, mas de abstrações (interpretação livre);
Um pacote que não contenha Abstrações é considerado mais Concreto.
Princípios aplicados ao projeto de pacotes
A Arquitetura de um software visa aumentar suas "qualidades" (ou "capacidades"), ou seja, o grau em que atendem aos "Requisitos não funcionais" de um projeto.
E existem alguns princípios que se aplicam ao projeto de pacotes, de modo a garantir que a Arquitetura seja adequada.
Além dos três princípios que falamos anteriormente, Reuse Release Equivalence Principle (REP), Common Closure Principle (CCP) e Common Reuse Principle (CRP) (veja "Camadas e Pacotes"), temos alguns outros a estudar:
Acyclic Dependencies Principle - ADP (Princípio das Dependências Acíclicas): A dependência entre pacotes não pode conter ciclos.
Dependência circular
Dependência cíclica
Stable Dependencies Principle - SDP (Princípio das Dependências Estáveis): A dependência entre pacotes deve caminhar sempre da direção da estabilidade. Um pacote somente deve depender de pacotes mais estáveis do que ele.
A dependência que o "Pacote 1" tem do "Pacote 2" é inadequada, pois o "Pacote 2" é mais instável que o próprio "Pacote 1", e isto pode provocar propagação de alterações.
Stable Abstractions Principle - SAP (Princípio das Abstrações Estáveis): Pacotes que são totalmente estáveis, devem ser totalmente abstratos, e pacotes que são totalmente instáveis, devem ser totalmente concretos.
Os pacotes 5 e 6 são totalmente estáveis, logo, devem ser totalmente compostos por Abstrações.
Estes três princípios melhoram a Manutenibilidade, Testabilidade e Flexibilidade do software, aumentando ao máximo sua qualidade geral. Quando dependemos de Abstrações e estas são estáveis, podemos evitar a propagação de alterações no software.
Medindo a adequação da Arquitetura
Uma métrica muito importante, porém muito desprezada pelos Arquitetos de software é a "Distance from Main Sequence" - DMS (Distância da Sequência Principal), que mede o quanto um Pacote se afasta dos três princípios que citamos.Existe uma função linear que representa o equilíbrio entre Abstração e Instabilidade: A + I = 1. Ferramentas como o JDepend, nos permitem medir nossos pacotes e saber o quanto estão próximos ou afastados desta linha ideal. Um pacote com valor 1 significa que está muito afastado deste ideal, logo, deveria ser analisado para possível refatoração.
Nenhum comentário:
Postar um comentário