quarta-feira, 6 de abril de 2011

Qualidade na Prática - Enums no lugar de Constantes - Parte II

Dando continuidade à pequena série sobre enums como alternativa às constantes, neste post vamos falar um pouco mais da técnica e de como ela pode deixar o código mais limpo e, consequentemente, mais claro. Um código mais claro facilita a manutenção, que corresponde a maior parte do ciclo de vida de um software.

Como a pista na primeira parte indica, estou usando o livro do Joshua Bloch como referencia principal nesta solução e para escrever este post. Então vamos ver como a solução para o problema da carga de dados ficou mais elegante.

Relembrando o problema, precisamos ler os dados de um arquivo CSV e fazer a carga para um repositório. No post anterior, tivemos problemas  para relacionar o índice do array, o nome do cluster no repositório e o impacto. Mais detalhes aqui.

Mas o que é um Enum? Nas palavras de Bloch:
An enumerated type is a type whose legal values consist of a fixed set of constants, such as the season of the year, the planets in the solar system...
Um exemplo básico de um Enum é demonstrado abaixo:

public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}

Bom, vamos incrementar um pouco essa estrutura para que comece a nos ajudar na solução.

package enums;

public enum Cluster {
    RIO_DE_JANEIRO(2), SAO_PAULO(3), FORTALEZA(4), PORTO_ALEGRE(5), MANAUS(6);
   
    private Integer index;

    Cluster(Integer index){
        this.index = index;
    }

    public Integer getIndex() {
        return index;
    }
}

Neste exemplo, já replicamos o relacionamento INDICE - CONSTANTE presente na solução anterior, com uma pequena(?) diferença: as constantes estão em um artefato que vai cuidar delas com carinho.

Vamos ao segundo problema. A relação nome do Cluster - INDICE. Para resolver este problema, vamos sobrescrever o método toString(). Joshua Bloch diz para considerarmos a implementação de um método fromString() quando sobrescrevermos o método toString(). Nesta consideração, devemos lembrar que todo código não utilizado é muda.

O enum abaixo contém o código que implementa fromString() para qualquer enum que tenha apenas uma representação em String para cada constante. Este código foi retirado do Effective Java.

package enums;

import java.util.HashMap;
import java.util.Map;

public enum Cluster {
    RIO_DE_JANEIRO(2) {
        @Override
        public String toString() {
            return "Rio de Janeiro";
        }
    }, SAO_PAULO(3) {
        @Override
        public String toString() {
            return "São Paulo";
        }
    }, FORTALEZA(4) {
        @Override
        public String toString() {
            return "Fortaleza";
        }
    }, PORTO_ALEGRE(5) {
        @Override
        public String toString() {
            return "Porto Alegre";
        }
    }, MANAUS(6) {
        @Override
        public String toString() {
            return "Manaus";
        }
    }, CUIABA(7) {
        @Override
        public String toString() {
            return "Cuiabá";
        }
    };

    private Integer index;

    @Override
    public abstract String toString();

    Cluster(Integer index){
        this.index = index;
    }
   
    private static final Map stringToEnum = new HashMap();
   
    static {
        for (Cluster cluster: values()) {
            stringToEnum.put(cluster.toString(), cluster);
        }
    }
   
    public static Cluster fromString(String nomeCluster) {
        return stringToEnum.get(nomeCluster);
    }

    public Integer getIndex() {
        return index;
    }
}


Pronto. Relacionamos cada constante ao seu respectivo nome no repositório.

Restou um ultimo problema para resolver: recuperar os dados do arquivo e informar o impacto. O leitor trabalha com o Enum da seguinte forma:

        Cluster[] clusters = Cluster.values();
        for (Cluster cluster : clusters) {
            mapaDeClusters.put(cluster, partes[cluster.getIndice()].equals("1"));
        }

Bem, lembrando o primeiro post, 1 indica impacto, 0 não e "partes" é o array resultado do split de uma linha do csv.

Com constantes, para alimentarmos este Map, precisaríamos incluir um por um. Outro ponto importante é que não parametrizamos mais o Map com um Integer que tornava obscuro o seu sentido. Agora temos uma estrutura que representa alguma coisa no nosso sistema. Isso fica mais claro agora, quando vamos recuperar os dados do Map.

Map mapaDeClustersImpactados = impactadorCluster.getMapaDeClusters();
Set setKey = mapaDeClustersImpactados.keySet();

for (Cluster nomeCluster : setKey) {
    if (mapaDeClustersImpactados.get(nomeClusters)) {
        ClusterImpactado impactado = clusterService.getByDescricao(nomeClusters.toString());
       
        if (!impactado.isEmpty()) {
            demanda.addClusterImpactado(impactado);
        }
    }
}

Lembrando que ImpactadorCluster é a estrutura que o leitor devolve.

Desta forma, temos uma solução mais elegante, pois a manutenção é de extrema facilidade. O ato de colocar ou tirar dados do mapa de clusters está completamente desacoplado da complexidade dos indices, nomes e tudo que pode envolver a leitura e transformação do CSV. Se a ordem dos índices mudar ou for necessário acrescentar outro cluster, só precisamos mudar o enum. Se algum nome mudar, também só precisamos mudar o enum.

O ponto frágil deste código é o altíssimo acoplamento de toString() com os dados do repositório. Este acoplamento no momento é importante para lidar com a natureza no arquivo de entrada, que não possui um padrão definido. Com o enum, obrigamos a sobrescrita do método toString(), já que se não o fizermos receberemos um erro de compilação.

O poder dos "enumerated types" é maior que essa demonstração prática, sendo um recurso interessante para o uso de estratégias para substituir switches e if's.

Os dados do exemplo que poderiam representar quebra de sigilo são fictícios ou foram omitidas as suas implementações.

Referência: Effective Java 2nd Edition - Joshua Bloch

Nenhum comentário:

Postar um comentário