terça-feira, 18 de março de 2014

Lack of Cohesion in Methods 4 (LCOM4)


LCOM 4 é uma métrica sobre coesão de métodos de uma classe, servindo para identificarmos classes suspeitas de violação do princípio SRP (Single Responsability Principle). Apesar de ser muito importante, algumas ferramentas de análise de código deixam de medi-la corretamente.




Só para relembrar, aqui vai novamente a "Mandala" do Nirvana do Código Fonte:



Código coeso significa duas coisas: Classes coesas e Métodos coesos. Para que uma classe seja considerada coesa, é necessário que obedeça (no mínimo) o princípio da Responsabilidade Única, segundo o C2 (http://c2.com/cgi/wiki?SingleResponsibilityPrinciple):

  • Cada responsabilidade deveria ser uma classe separada, porque cada responsabilidade é um eixo de mudanças;
  • Uma classe deveria ter uma, e somente uma, razão para ser alterada;
  • Se uma mudança nas regras de negócio causar mudanças em uma classe, então uma mudança no esquema do banco de dados, na Interface do usuário, no formato do relatório, ou em qualquer outro segmento do sistema, não deveria forçar essa classe a ser alterada;

Classes com múltiplas responsabilidades são mais vulneráveis à "Propagação de alterações", favorecendo o "Britleness", além de serem mais difíceis de manter, testar e estender.

O jQana, nossa ferramenta de análise de código, tem um "parser" para LCOM4, além de uma definição completa sobre a métrica.

A métrica verifica quantos "componentes conectados" existem dentro de uma classe. Um "Componente conectado" é um conjunto de membros (variáveis de instância, de classe e métodos) que está logicamente conectado. Se uma classe tem mais de um componente, então pode ser que esteja violando o SRP, e deve ser verificada.

Vamos mostrar o Gist de uma classe "boa":



Se traçarmos um gráfico de blocos, vemos que só existe um único "Componente conectado" nessa classe:

Veja que podemos traçar um caminho desde o método "a" até o método "e", incluindo TODOS os membros da classe, mesmo que os métodos não se invoquem diretamente. Isso pode significar que TODOS fazem parte de um único componente lógico.

Agora, vamos fazer uma pequena mudança no código fonte. Eis o novo Gist:



Ao remover o uso da variável "x" pelo método "c", nós desconectamos o componente, criando dois componentes que não se "falam" através de nenhum membro da classe em comum. Eis o gráfico:

Existem várias definições na Internet sobre LCOM, e, em especial, LCOM4. A da ferramenta "Aviosto" é uma das mais utilizadas. Porém, existe um "Paper" de Martin Hitz e Behzad Montazeri, que propõe a nova métrica, sendo a melhor referência acadêmica sobre o assunto.

Método de cálculo

A definição de LCOM4 é:

E = {∈V×V | (∃ i∈IX: (m accesses i)∧(n accesses i)) ∨ (m calls n) ∨ (n calls m)},

No jQana, nós calculamos a métrica criando uma lista de membros da classe, e depois analisamos quem usa o quê. Para isso, seguimos algumas regras práticas:

  • Construtores são ignorados, pois tendem a inicializar todas as variáveis, o que pode reduzir o LCOM4;
  • Inner classes e anonymous classes são ignoradas;
  • Métodos herdados, marcados com "@Override" são ignorados;
  • Métodos órfãos, geralmente invocados sem conexão com outros métodos, são ignorados, embora possam denotar violação do SRP, geram muitos falsos positivos;
  • Getters e Setters são considerados como variáveis;
  • Métodos sobrecarregados (Overload) são considerados o mesmo método;

Falsos positivos


Ao usarmos frameworks de desenvolvimento, é comum que algumas classes de estereótipo forte (UI, Banco de Dados etc), apresentem LCOM4 maior que 1, e, na verdade, estão violando o SRP mesmo. Porém, não há muito o que ser feito, pois a Complexidade Acidental do framework exige aquele tipo de interface.

Um exemplo disso são as classes "ManagedBean", exigidas pelo Javaserver Faces. Como atendem a uma ou mais páginas, podem conter vários "Componentes conectados" dentro de si, o que não representa um problema, mas uma característica daquele framework.

Classes criadas por geradores de código, classes de "contexto", e até mesmo "entidades" podem gerar falsos positivos mesmo.

O ideal é você analisar as classes sob suspeita, de modo a verificar se pode fatorá-las em classes distintas ou não.