a) Smell a ser reduzido/eliminado: Large Class
b) Ingredientes
Ferramentas
Métrica(s) associada(s): número de linhas de código (Source Lines of Code – SLOC)
Técnicas
- Testes unitários
- Refactoring
Mecânica
- Identificar pontos onde o smell ocorre (PMD com Maven)
- Garantir que todas as ocorrências de código duplicado tenham um nível satisfatório de cobertura de testes (uso do JaCoCo)
- Caso a cobertura não esteja satisfatória, escrever testes unitários que garantam o comportamento (introduzir mais testes com JUnit, acompanhando sua cobertura com JaCoCo)
- Aplicar as técnicas de refactoring
- Rodar métricas novamente para verificar a melhoria
- Repita o passo 1
c) Aplicando a Receita no Projeto Spark
Base de código: Spark (http://www.sparkjava.com/)
Repositório utilizado: https://github.com/carlosaml/spark
Configuração de ferramentas
- Maven utilizado para o build
- PMD configurado para detectar métodos com mais de 50 linhas
- Testes JUnit rodando atráves do Maven
- JaCoCo plugado no Maven para geração de relatórios de cobertura de testes
Aplicação da Receita
Executando o build do Maven, o relatório do PMD acusa que a classe spark.route.SimpleRouteMatcher é muito grande duas vezes.
Como assim duas vezes? Ao olhar o código, percebe-se que a classe SimpleRouteMatcher possui uma classe interna privada, chamada RouteEntry. O primeiro passo então é mover essa classe para o seu próprio arquivo utilizando Move Class.
Rodando o PMD novamente, vemos que agora somente a classe RouteEntry é reportada, portanto focaremos nela a partir de agora.
Ao analisar a classe, nota-se que ambos os métodos chamados em cada uma condições são longos e, consequentemente, não de fácil entendimento.
if (thisPathSize == pathSize) {
return matchesSpecificPaths(thisPathList, pathList, thisPathSize);
} else {
return matchesWildCards(path, thisPathList, pathList, thisPathSize, pathSize);
}
Este é um caso onde Replace Type Code with State/Strategy pode ser aplicado, já que o comportamento da classe é diretamente afetado por um de seus membros, que neste caso específico é path.
Para isso, precisamos aplicar Extract Class em ambos os métodos boolean matchesWildCards(…) e boolean matchesSpecificPaths(…), extraindo cada um para sua classe específica, que chamaremos de WildCardMatcher e SpecificPathMatcher, respectivamente. Com isso, RouteEntry pode simplesmente delegar essas responsabilidades para alguma das duas classes, dependendo de quais os valores de seus atributos.
Com isso, o trecho de código inicialmente mostrado se torna:
if (thisPathSize == pathSize) {
return specificPathMatcher.matchesSpecificPaths(thisPathList, pathList, thisPathSize);
} else {
return wildCardMatcher.matchesWildCards(path, thisPathList, pathList, thisPathSize, pathSize);
}
O resultado disso são dois longos métodos a menos na classe SimpleRouteMatcher e, portanto, uma classe menor, mais simples e de melhor manutenção. O PMD concorda, pois nem SimpleRouteMatcher nem RouteEntry são relatadas como classes grandes.