domingo, 20 de novembro de 2011

Performance em aplicações Java. O problema é você!


O título dessa postagem já é um “spoiler”, mas o que eu quero dizer realmente com “O problema é você”?

Antes de sair apontando o dedo para o pessoal de rede dizendo que o servidor não presta ou o cabeamento está cheio de baratas (provavelmente o servidor é ultrapassado e o cabeamento está cheio de baratas), mas antes de culpar o servidor, aponte o dedo para o espelho, e melhore esse código feio que está fazendo!
Pense que em uma aplicação, não é apenas o “front-end” que precisa ser bonito, a área por traz das cortinas tem que ser mais bonita ainda. Você gosta quando precisa arrumar um programa que o código foi feito por um estagiário do período cenozoico? Pois bem, provavelmente o próximo programador que olhar para o seu código vai ter a mesma impressão: “P.q.p! Esse código deve ter passado pelas mãos sujas de um estagiário!”. Particularmente eu não gosto de assinar um código feio, só coloco meu nome em um código depois de arrumá-lo e deixá-lo legível.

IMPORTANTE: Código feio e gambiarra são coisas extremamente diferentes, gambiarras fazem partes da vida de qualquer programador, seja ele estagiário ou especialista, porém só porque precisamos fazer uma gambiarra não significa que devemos deixar o código em hebraico. Mas o que tem a ver código legível com deixar o programa mais rápido?


Tudo!

Uma cultura de “código limpo” e “código bonito” leva a uma outra cultura, de “programa rápido”, pois você se acostuma a fazer um código melhor, que consequentemente, funcionará de forma mais rápida. Além de um fator importante, a performance do programador que for arrumar ou melhorar o programa. Com certeza quem for mexer no código-fonte desse programa vai terminar a alteração muito mais rápido se não precisar utilizar o tradutor do google para traduzir de “hebraico para javanês”. Mas o que, especificamente, pode ser feito para deixar a aplicação mais rápida? Siga os passos abaixo, que verá a diferença.
Em alguns casos, o usuário não perceberá a diferença, mas você sim, porque o servidor vai parar de cair e você vai parar de ser acordado as três da madrugada para dar “stop/start” na aplicação.

Laços FOR
Entenda que “for” convencional é mal, “foreach” e “iterator” são bons. Em um laço for convencional, é necessário criar vários objetos para controle do loop e do próprio conteúdo da lista, e isso consome mais processamento e memória do que um foreach ou iterator, porque esses dois já trabalham de uma forma que, se bem utilizados, não há a necessidade de criar objetos de controle. Exemplo de foreach:
ArrayList<Objeto> lista = new ArrayList<Objeto>(Numeros.CEM);
for(Objeto variavel : lista) {
System.out.println(variável.getNome());
}
Exemplo de Iterator
ArrayList<String> array = new ArrayList<String>(Numeros.CEM);
Iterator<String> it = array.iterator();
while(it.hasNext()) {
System.out.println(it.toString());
}
Métodos dentro de laços 
Isso não se faz, a cada iteração do loop um método de terceiros é consultado. Se o valor não muda, é melhor atribuí-lo à uma variável, diminuindo o tempo de processamento do laço. Exemplo de como não fazer:
 ArrayList lista = new ArrayList();
for (int i = 0; i < lista.size(); i++) {
System.out.println(lista.get(i));
}
Nesse exemplo, de como não fazer, a chamada ao método “lista.size()” será efetuada em cada iteração do laço, que pode ser while ou do..while, e se o laço possuir dez mil iterações, serão dez mil chamadas ao método que contará sempre os dez mil registros da lista.


Exemplo de como se deve fazer, economizando processamento.
ArrayList<String> array = new ArrayList<String>(Numeros.CEM);
int tamanho = array.size();
int x = Numeros.ZERO;
while(x < tamanho) {
}
for (int i = 0; i < tamanho; i++) {
}
Imports não utilizados.
Sempre que passar por uma classe, remova os imports não utilizados, pode ser manualmente ou com o atalho “CTRL+SHIFT+O” do eclipse. Apesar de não parecer, os imports inutilizados ajudam a deixar a classe mais pesada, por que quando ela é compilada (pelo javac), todas as classes importadas são compiladas juntas.
Isso também vale para imports com * no pacote, é bem difícil você utilizar todas as classes de um determinado pacote, e provavelmente você não vai querar compilar todas as classes de um pacote junto com a sua, ou quer?

Objetos e Tipos Primitivos
Sempre que possível, não utilize objetos (Integer, Double, Float) mas sim tipos primitivos (int, double, float) que são mais leves. Quando for trabalhar com faixas numéricas pequenas, utilize também um tipo pequeno, por exemplo o byte para apenas códigos 1, 2, 3, etç. Use o short para faixas de 0 a 32767 (ou negativos nessa mesma faixa). Um objeto exige muito mais espaço na memória do que um tipo primitivo, por exemplo, o int só precisa de uma faixa na memória para guardar o valor da variável, já o Integer precisa de um espaço bem maior, para guardar a classe Integer, que possui além do valor, várias outras variáveis e métodos internos. Quando houver a necessidade de trabalhar com um int que nunca pode ser nulo, impeça o usuário de acionar algum método sem preencher o campo da tela.

Listas sem objeto ou tamanho.
Sempre que utilizar uma lista, indique de que tipo é essa lista, dessa forma economizamos memória e processamento, não sendo necessário criar objetos e fazer o cast em cada item dessa lista.
Sempre defina um tamanho inicial para a lista. Quando não informamos um tamanho para a lista, o compilador entende que seu tamanho inicial será 10. Quando essa lista ultrapassa os 10 itens, o compilador cria uma nova lista com mais 10 itens, ou seja, 20 itens, e copia a primeira lista para a segunda lista, se ela ultrapassar os 20 itens, o compilador cria uma nova lista com mais 10 itens, ou seja, 30 itens, e copia todos os objetos da segunda lista para a terceira lista. Se você não definir um tamanho e sua lista possuir mil objetos, esse processo irá se repetir 100 vezes, consumindo memória e processamento. Para contornar esse problema, sempre defina um tamanho inicial para suas listas, e esse número deve se aproximar do tamanho que a lista terá.
Por exemplo, se eu estiver criando uma lista departamentos de uma empresa, vou criar uma lista com tamanho inicial igual a 100, por que não existem milhares de departamentos em uma empresa. Se eu estiver criando uma lista de funcionários de uma empresa multi-nacional, vou definir um tamanho inicial de 1000 para a minha lista, fazendo isso, mesmo que a lista possua dez mil objetos, eu já diminui o processamento em cem vezes, por que ela não vai precisar ser copiada de 10 em 10, mas sim de 1000 em 1000.


Exemplo de como não fazer:

            ArrayList lista = new ArrayList();

 Exemplo de como se deve fazer:

            ArrayList<Objeto> lista = new ArrayList<Objeto>(Numeros.CEM);

Pesquisas e ordenações em Listas
“Não tente reinventar a roda”, não há a necessidade de criar um algoritmo com laços e variáveis para fazer uma pesquisa ou ordenar uma lista, a Sun já criou formas mais fáceis e leves para isso. Algoritmos como quicksort, bubblesort, binariesort, só são utilizados para aprender lógica de programação e em linguagens estruturadas, em orientação a objetos só se deve recorrer a este tipo de algoritmo em casos “extremamente extremos”, onde não existe nenhuma outra possibilidade de se resolver o problema utilizando a própria linguagem ou o framework.
Para fazer pesquisas, use o MAP (ou similares), que já foi criado para esse tipo de funcionalidade.
Para ordenar uma lista, implemente o Comparator.

Exemplo de MAP.
No próprio for, onde você recupera ou cria os dados, você pode criar um MAP no lugar do List ou ArrayList que já está utilizando. O MAP funciona no esquema “chave / valor”. Na chave você define o termo que será pesquisado, e no valor você insere o objeto inteiro.

Criação de um MAP: 

                Map<Integer, String> mapa = new HashMap<Integer, String>();
                for (Objeto obj : minhaLista) {
                           mapa.put(obj.getCpf(), obj);
                }

Lembre-se de utilizar para “chave” alguma coisa que não se repita, como CPF, por exemplo. Também é possível utilizar algum controle próprio, como números, ou qualquer outra coisa.
Para pesquisar alguma coisa nesse mapa, deve-se verificar se existe algum objeto com a chave informada (containsKey), e se existir, pega esse objeto, simples assim. Nem precisa de loop.

                if(mapa.containsKey(111222333)) {
                          Objeto = mapa.get(111222333);
                }

Para ordenar uma lista, de forma simples e “direta”.

                ArrayList<Objeto> lista = new ArrayList<Objeto>(Numeros.CEM);
                Collections.sort(lista, new Comparator<Objeto>() {
                                public int compare(Objeto r1, Objeto r2) {
                                                if (r1.getIdade() < r2.getIdade()) {
                                                                return Numeros.MENOS_UM;
                                                } else if (r1.getIdade() > r2.getIdade()) {
                                                                return Numeros.UM;
                                                } else {
                                                                return Numeros.ZERO;
                                                }
                                }
                });

Explicando, eu tenho uma lista qualquer e quero ordená-la, então eu utilizo o método sort da classe Collections, esse método precisa da lista que será ordenada e de um “Comparator” dessa lista, você pode criar uma classe ou método que retorne esse comparator ou pode cria-lo na hora, como fiz no exemplo acima.
O comparator precisa ter um método compare que recebe dois objetos, do mesmo tipo de objeto da lista, esse método compare deve retornar -1 se o primeiro objeto for menor, ou deve retornar 1 se o primeiro objeto for maior, ou deve retornar zero se os dois forem iguais. Você que define o que vai decidir se um objeto é maior ou menor, você pode comparar o que quiser, qualquer uma das variáveis do objeto, o que o sort precisa é dos retornos -1, 1 e 0.

Strings e Constantes.
Utilizar strings diretamente nos métodos é mau. Utilizar constantes em uma classe própria para constantes é bom.
Exemplo de como não fazer:
                     String texto = “alguma coisa”;
                     int retorno = 0;

Exemplo de como fazer:
       Classe de constantes (ViewConstants, Números, etç.) String texto = ViewConstants.TEXTO;
onde TEXTO está marcado como “public static final String” na classe de constantes Int retorno = Numeros.ZERO; onde ZERO está marcado como “public static final int” na classe de constantes numéricas Se você tem uma classe muito popular, que é acessada muitas vezes, e dentro dessa classe tem varias variáveis e objetos, sempre que a classe for acessada novos objetos e variáveis são criados, ocupando mais e mais memória do servidor. Para evitar isso, crie uma classe para armazenar todas as strings e constantes de sua aplicação, mas lembre-se de sempre marcar suas constantes com “static final”, dessa forma as variáveis serão criadas apenas uma vez, e utilizadas por todas as classes da aplicação, sem que mais memória seja utilizada.

Concatenação de Strings
Sempre que é utilizada a concatenação de Strings, novos endereços da memória são separados para essa tarefa, e esses endereços só são liberados quando o método ou a classe morre, se o garbage collector fizer seu trabalho assim que o método ou classe morrer, dessa forma, para evitar o uso excessivo de memória, aconselha-se utilizar o StringBuffer para esse trabalho.

Exemplo de como não fazer:
                    String texto = “texto1”;
                    texto += “ ”;
                    texto += “texto2”;

Exemplo de como se deve fazer, economizando memória. 
                   StringBuffer texto = new StringBuffer();
                   texto.append(“texto1”);
                   texto.append(“ ”);
                   texto.append(“texto2”);

No “exemplo de como não fazer”, se você concatenar a string “A” com a string “B”, e depois com a string “C”, e com a string “D” e assim por diante, você estará ocupando um largo espaço na memória, pois a cada concatenação, uma nova String é criada, para armazenar o resultado da sua soma. O processo é similar ao da copia da lista, explicado anteriormente.
Utilizando o StringBuffer, você gasta apenas o endereço que o StringBuffer já está utilizando, não sendo necessário criar várias outras variáveis de apoio.

Comentários Javadoc e in-line
Essa prática não desempenha nenhum papel fundamental na performance do sistema, mas garante uma boa performance dos programadores que estiverem mexendo no código, conforme mencionado nas primeiras linhas desse post, pois um método complicado que estiver bem comentado sempre será mais fácil de entender, não precisando perder tempo para descobrir o que um IF, um for, uma variável ou o próprio método está representando.
Os comentários, podem ser colocados na assinatura do método, em forma de javadoc e também antes de cada código, utilizando comentários de linha.
Saiba mais sobre javadoc aqui. Exemplo: /**
*
  * Nome: Fazer alguma coisa
  * Propósito: Esse método recebe um “Objeto” devidamente preenchido e verifica se
  * deve ser gravado completo ou se deve ser feita uma gravação individual no banco de dados.
  * @param Objeto, esse objeto precisa que a variável idade de cada indivíduo esteja preenchida,
  * recomenda-se utilizar a validação na tela, não deixando chegar nenhum nulo no java
  *
*/
public void doSomethig(Objeto param) {
      //Verifica se é para gravar todos ou um de cada vez
      if(param.getIdade() > ClasseSistema.getXpto()) {
               ServiceImplQualquer.gravarInformacoes(param);
      } else {
             //Grava os objetos um a um, mas precisa que a idade de cada objeto
            //esteja preenchida, utilizando a validação da tela
               for (ObjetoX objetoX : param.getLista()) {
                      ServiceImplQualquer.gravarUmDeCada(objetoX);
              }
    }
}

0 comentários:

Postar um comentário

Alguma dúvida?

 

© 2009Java Erro | by TNB