Reduzindo o Tamanho de Classes

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

Mecânica

  1. Identificar pontos onde o smell ocorre (PMD com Maven)
  2. Garantir que todas as ocorrências de código duplicado tenham um nível satisfatório de cobertura de testes (uso do JaCoCo)
  3. 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)
  4. Aplicar as técnicas de refactoring
  5. Rodar métricas novamente para verificar a melhoria
  6. 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.

Eliminando código duplicado

a) Smell a ser reduzido/eliminado: Duplicated Code

b) Ingredientes

Ferramentas

Métrica(s) associada(s): número de tokens duplicados (Duplicate Code)

Técnicas

  • Testes unitários
  • Refactoring
    • Move Class/Field/Method
    • Extract Class/Method/Subclass/Superclass
    • Pull Up Method/Field/Constructor Body
    • Form Template Method
    • Consolidate Duplicate Conditional Fragments

Mecânica

  1. Identificar pontos onde o smell ocorre (PMD com Maven)
  2. Garantir que todas as ocorrências de código duplicado tenham um nível satisfatório de cobertura de testes (uso do JaCoCo)
  3. 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)
  4. Aplicar as técnicas de refactoring
  5. Rodar métricas novamente para verificar a melhoria
  6. Repita o passo 1

c) Aplicando a Receita no Projeto log4j

Base de código: log4j (http://logging.apache.org/log4j/)

Repositório utilizado: https://github.com/carlosaml/log4j

Configuração de ferramentas

  • Maven utilizado para o build
  • PMD configurado para detectar métodos com mais de 6 parâmetros
  • 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 CPD acusa três longos trechos de código, todas entre as seguintes classes:

  • org/apache/log4j/pattern/LogEvent.java
  • org/apache/log4j/spi/LoggingEvent.java

Olhando o relatório de cobertura do JaCoCo, podemos notar que ambas classes possuem cobertura de testes satisfatória, o suficiente para prosseguirmos com a receita.

Analisando as duas classes percebe-se que grande parte do comportamento é similar. Portanto, o objetivo será extrair uma classe base a partir das duas.

1o round: classe LogEvent

Aplicamos Extract Superclass, gerando a classe LogEventBase.

Em seguida, aplicamos Move Field nos seguintes campos:

  • logger
  • ndc
  • mdcCopy
  • ndcLookupRequired
  • mdcCopyLookupRequired
  • message
  • renderedMessage
  • threadName
  • throwableInfo

E Extract Method nos seguintes métodos:

  • getMessage()
  • getNDC()
  • getMDC(key)
  • getMDCCopy()
  • getRenderedMessage()
  • getThreadName()
  • getThrowableStrRep()

2o round: classe LoggingEvent

A classe passa a estender LogEventBase, criada no round anterior.

Devido a isso, métodos redudantes (idênticos em LoggingEvent e LogEventBase) podem ser simplesmente removidos de LoggingEvent.

3o round: novamente a classe LogEvent

Executamos Pull Up Field nos seguintes membros, movendo-os para LogEventBase:

  • TO_LEVEL
  • TO_LEVEL_PARAMS
  • methodCache
  • fqnOfCategoryClass
  • level
  • timeStamp
  • locationInfo

E também executamos Pull Up Method nos seguintes métodos:

  • readLevel(ois)
  • readObject(ois)
  • writeObject(ops)
  • writeLevel(ops)
  • setProperty(propName, propValue)
  • getProperty(key)
  • locationInformationExists()
  • getTimeStamp()
  • getPropertyKeySet()
  • getProperties()
  • getFQNOfLoggerClass()

4o round: classe LoggingEvent, novamente

Aplicamos Pull Up Field em:

  • startTime
  • categoryName

E Pull Up Method em:

  • getStartTime()
  • getLocationInformation()
  • getLevel()
  • getLoggerName()
  • getLogger()
  • getThrowableInformation()
  • removeProperty(propName)

Com isso eliminamos a duplicação entre as classes, visto que uma delas apenas necessita existir para manter compatibilidade com versões anterior devido ao número de caracteres no nome. Uma péssima razão para duplicar toda a classe.

Reduzindo o Tamanho de Listas de Parâmetros de um Método

a) Smell a ser reduzido/eliminado: Long Parameter List

b) Ingredientes

Ferramentas

Métrica(s) associada(s): número de parâmetros por método (Number of Parameters in Method – PAR)

Técnicas

Mecânica

  1. Identificar pontos onde o smell ocorre (PMD com Maven)
  2. Garantir que todas as ocorrências de código duplicado tenham um nível satisfatório de cobertura de testes (uso do JaCoCo)
  3. 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)
  4. Aplicar as técnicas de refactoring
  5. Rodar métricas novamente para verificar a melhoria
  6. Repita o passo 1

c) Aplicando a Receita no Projeto ArgoUML

Base de código: ArgoUML (http://argouml.tigris.org/)

Repositório utilizado: https://github.com/carlosaml/argouml

Configuração de ferramentas

  • Maven utilizado para o build
  • PMD configurado para detectar métodos com mais de 6 parâmetros
  • 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 o seguinte método recebe muitos parâmetros:

private String toString(Object modelElement, boolean useGuillemets, 

            boolean showVisibility, boolean showMultiplicity, boolean showTypes,

            boolean showInitialValues, boolean showProperties)

Portanto, utilizaremos ele como base para a aplicação desta receita.

Primeiramente verificamos a cobertura de testes dos chamadores deste método (pelo fato do mesmo ser privado), que é apenas um:

public String toString(Object modelElement, NotationSettings settings)

O relatório de cobertura de testes existente não passa a confiança necessária para que o método possa ser refatorado. Para melhorar este cenário, escreve-se mais testes para o método público.

Para isso, a classe TestAttributeNotationUml, contendo cinco testes unitários, é adicionada.

Com isso eleva-se a cobertura de testes e consequentemente consegue-se autorização para refatorar o método toString que conta com muitos parâmetros.

Procurando pelos usos do método em questão, vemos que existe apenas um e que este apenas extrai os valores de algumas propriedades do objeto NotationSettings recebido e os passa adiante. Isso define claramente um ótimo candidato para Preserve Whole Object, já que muitos dos parâmetros do método são fortemente relacionados.

Para isso, substituímos todos estes parâmetros:

  • boolean useGuillemets
  • boolean showVisibility
  • boolean showMultiplicity
  • boolean showTypes
  • boolean showInitialValues
  • boolean showProperties

Por apenas um:

  • NotationSettings settings

E mudamos a única chamada do método para que passe diretamente a instância de NotationSettings.

Com isso o PMD já não acusa mais o método em seu relatório.

Porém, o método recém refatorado conta com somente um uso, o que não faz sentido devido ao fato do mesmo ser privado. Para tanto, podemos aplicar Inline Method e eliminar o método privado em questão.

Reduzindo o Tamanho de Métodos

a) Smell a ser reduzido/eliminado: Long method

b) Ingredientes

Ferramentas

Métrica(s) associada(s) número de linhas por método (Method Lines of Code – MLOC)

 Técnicas

Mecânica

  1. Identificar pontos onde o smell ocorre (PMD com Maven)
  2. Garantir que todas as ocorrências de código duplicado tenham um nível satisfatório de cobertura de testes (uso do JaCoCo)
  3. 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)
  4. Aplicar as técnicas de refactoring
  5. Rodar métricas novamente para verificar a melhoria
  6. 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 o método boolean matchPath(String path) da classe spark.route.SimpleRouteMatcher é muito longo. Utilizaremos ele como base para a aplicação desta receita.

O método em questão é privado e chamado apenas por outro método privado, boolean matches(HttpMethod httpMethod, String path). Este, por sua vez, é utilizado por dois métodos públicos: RouteMatch findTargetForRequestedRoute(HttpMethod httpMethod, String path) e List<RouteMatch> findTargetsForRequestedRoute(HttpMethod httpMethod, String path).

Olhando o relatório de cobertura do JaCoCo, podemos notar que ambos métodos possuem 100% de cobertura de teste, o que nos dá plena segurança em realizar mudanças nessa classe.

Grande parte do comprimento do método se dá devido a existência de bastante código dentro do condicional if (thisPathSize == pathSize) {} else {}. Neste caso o refactoring que melhor se adapta é o Decompose Conditional.

Ao analisar o código mais profundamente, percebe-se que o condicional existe para tratar paths específicos de maneira diferente de “wildcards”. Com isso, podemos extrair dois diferentes métodos, um para cada situação: matchesSpecificPaths para o primeiro caso e matchesWildCards para o segundo. Com isso, todos os métodos da classe se tornam menores e consequentemente mais manuteníveis. Abaixo, temos o resultado do relatório do PMD, executado novamente, garantindo a remoção do smell.