Wiki

Orientações técnicas/Práticas de programação

Considerações gerais #

O objetivo deste documento é o de fornecer aos desenvolvedores do Demoiselle algumas orientações a serem seguidas em Java visando as melhores práticas de programação. Tais práticas devem ser seguidas a fim de se garantir um bom nível de qualidade para os códigos, em ambos os termos de desempenho e de manutenibilidade destes.

Tamanho razoável dos códigos fontes #

Se um arquivo de fonte do Java passa a ser grande, mais do que 500 linhas, tente reduzir o seu tamanho através da refatoração de códigos duplicados em um único método ou pela externalização do código para uma outra classe.

Granularidade dos métodos #

Um número razoável de linhas para um método depende de sua complexidade. Um método formado por instruções sequenciais pode ser consideravelmente maior do que um método complexo contendo laços e estruturas condicionais. Se o código sequencial é repetitivo, tal como na inicialização índice a índice de um array, ele pode ser tão longo quanto for necessário. Porém, deve existir uma maneira mais elegante de construi-lo.

Um método deve preferivelmente fazer apenas uma coisa, e o seu nome descreve tal funcionalidade. Se ele efetua diversas tarefas, tenha certeza de que estas estejam refletidas no nome do método. Se isso levar a um nome longo e complicado, é aconselhável rever a estrutura interna do seu código. Por exemplo, se você tiver um método com o nome inicializarPaginaDeGestaoELerListaContas(), provavelmente será possível dividi-lo em dois métodos: inicializarPaginaDeGestao() e lerListaContas().

Utilização dos tipos float e double #

Não utilize os tipos de dados primitivos float e double para cálculos onde a precisão dos números decimais é importante. Por causa da representação interna dos tipos de dados float e double [10], o cálculo usando números de pontos flutuantes não podem ser considerados precisos. Dada esta recomendação da IBM [11], não use os tipos simples float ou double para efetuar cálculos de números decimais onde a precisão é importante. Se necessário, a classe BigDecimal pode ser usada no lugar destes tipos.

Fornecendo acesso a variáveis de instância e de classe #

Não torne uma variável de instância ou de classe pública sem uma boa razão. Frequentemente variáveis de instância não precisam ser explicitamente atribuídas ou obtidas, isso ocorre automaticamente através de chamadas a métodos.

Um exemplo apropriado de variáveis de instância públicas é o caso onde a classe é essencialmente uma estrutura de dados, sem comportamento algum. Em outras palavras, se você tivesse usado uma estrutura ao invés de uma classe (se o Java suportasse "struct"), então é apropriado tornar públicas as variáveis de instância da classe.

Referenciando variáveis e métodos de classe #

Evite usar um objeto para acessar uma variável ou método de classe, isto é, caso possuam o modificador static. Ao invés disso use o nome da classe diretamente. Seguem alguns exemplos:

metodoQualquer();	   // OK 
Classe.metodoQualquer(); // OK 
objeto.metodoQualquer(); // EVITAR!

Constantes #

Constantes numéricas (literais) não deverão ser codificadas diretamente, exceto para os casos -1, 0 e 1, os quais podem aparecer em um laço “for” como valores para um contador.

Atribuições de valores #

Evite atribuir a diversas variáveis um mesmo valor em uma única instrução. Isso dificulta a leitura. Exemplo:

fooBar.fChar = barFoo.lchar = ‘c’; // EVITAR!

Não use operador de atribuição em lugares onde podem ser confundidos facilmente com o operador de igualdade. Por exemplo, considere o caso abaixo:

if (c++ = d++) { ...
}

o código deveria ser escrito dessa outra forma:

if ((c++ = d++) != 0) { ...
}

Além disso, não use atribuições embutidas na tentativa de se melhorar o desempenho em tempo de execução. Isso é trabalho para o compilador e, além do mais, raramente auxilia de verdade. Por exemplo:

d = (a = b + c) + r; // EVITAR!

Isso deve ser escrito dessa forma:

a = b + c; d = a + r;

Importação de pacotes #

Para manter uma boa legibilidade do código, mantenha apenas importações de classes ou outros artefatos Java que estejam de fato sendo referenciados no arquivo em questão. Além disso, recomenda-se não utilizar o nome qualificado de classes internamente no código, optando pelo mecanismo de importação da linguagem Java (cláusula "import").

Procure utilizar o símbolo "" durante a importação dos elementos de um pacote somente nos casos em que aparecerem mais de 10 artefatos a serem importados, preferindo a importação individual.

Utilização de interfaces ao invés de implementações nas declarações #

Onde for possível (quando o objeto que se quer usar implementa uma interface), é melhor declarar a variável como sendo do tipo da interface ao invés do tipo da classe da implementação.

Isso é particularmente aplicável às classes de coleções. Por exemplo, é melhor escrever o código abaixo:

List alunos = new ArrayList(); // PREFERIDO

ao invés disso:

ArrayList alunos = new ArrayList(); // EVITAR

Desta forma é possível alterar posteriormente o tipo de dados real usado com o mínimo de impacto no resto do código (por exemplo, substituir ArrayList por LinkedList).

Utilização do modificador final #

Quando uma variável de classe ou de instância não é modificada em momento algum do ciclo de vida do objeto, declare esta variável com o modificador "final".

Os parâmetros de um método e as variáveis locais também podem ser declarados com o "final".

Redefinição ou sobrecarga de métodos #

Um método redefinido ou ou sobrecarregado (overloaded) deve oferecer a mesma funcionalidade presente na classe ancestral. Tome cuidado para não alterar o significado de um método ao redefini-lo na classe derivada.

Liberação de recursos associados a uma classe #

Não confie ao método finalize() de uma classe a liberação de recursos relacionados a uma instância dela. Este método, presente na classe Object, é invocado pela JVM um pouco antes do garbage collector liberar o espaço de memória alocado para a instância. Porém, não há garantia do momento exato da execução do método finalize(), por exemplo, ele pode ser chamado somente na finalização da aplicação e prender os recursos por todo o período de execução desta. Além disso, a execução deste método depende do algoritmo do garbage collector em questão, sendo desta maneira dependente da implementação da máquina virtual usada (ex: Sun, IBM, BEA).

Cautela na utilização do método equals() #

Preste muita atenção ao comportamento do método equals(). A implementação padrão dele na classe Object simplesmente compara a identidade dos objetos com o operador de igualdade "==". Algumas classes redefinem este método fornecendo-o um significado de igualdade, tal como a classe String, na qual o equals() realiza a comparação de duas strings caracter a caracter. Por outro lado, outras classes mantêm o comportamento original do método.

Segue um exemplo com a classe String:

String s1 = new String("Texto");
 String s2 = new String("Texto");
 
 (s1 == s2);	 // retorna false
 s1.equals(s2); // retorna true

Ao utilizar a classe StringBuffer o resultado é outro:

 StringBuffer sb1 = new StringBuffer("Texto");
 StringBuffer sb2 = new StringBuffer("Texto");
 
 (sb1 == sb2);	   // retorna false
 sb1.equals(sb2); // retorna false

Redefinição do método hashCode() #

Sempre redefina o método hashCode() de uma classe se o método equals() dela tenha sido reimplementado. O método hashCode() é usado pelas aplicações que fazem uso de tabelas de hash, tal como nas classes Hashtable ou HashMap, para armazenar os objetos.

Para ser capaz de encontrar os objetos armazenados em um contêiner baseado em tabelas de hash, e por consequência usando o método hashCode(), é essencial que esta regra seja respeitada:

Se objeto1.equals(objeto2), então objeto1.hashCode() == objeto2.hashCode().

Do mesmo modo, o código hash de um objeto não deve ser alterado durante o seu ciclo de vida, e desta forma no seu cálculo não podem ser usadas informações prováveis de serem modificadas. Sendo assim, recomenda-se que apenas atributos ditos "final" do objeto sejam usados ao se calcular o seu código hash.

Escolha da implementação de listas e mapas #

Priorize a utilização das implementações ArrayList e HashMap sobre Vector e Hashtable a fim de obter melhor desempenho quando acesso sincronizado não for necessário.

De fato Vector e Hashtable oferecem métodos ditos "synchronized", porém eles são custosos e mais lentos de serem executados para garantir essa sincroniza ao controlar a concorrência. Na plataforma Java o Collection Framework traz as classes ArrayList e HashMap, cujas funcionalidades são as mesmas desempenhadas respectivamente por Vector e Hashtable, com a ressalva de que seus métodos não são sincronizados.

Escrevendo o tratamento de exceções #

Nunca deixe vazio um bloco "catch" em instruções do tipo "try-catch", mesmo em fase dedesenvolvimento. O mínimo que se pode fazer é deixar um comentário explicando porque nada foi feito durante o "catch" ou um marcador (ex: "TODO") para lembrar que aquela parte do código não foi ainda finalizada.

Geralmente utilizamos um mecanismo de logging (ex: Log4J), onde pode-se definir um nível (ex: DEBUG, INFO, ERROR) para uma mensagem enviada de dentro do "try-catch".

No bloco "finally" do "try-catch", evite levantar outras exceções ou retornar prematuramente. Como por definição o bloco "finally" é executado em qualquer situação, ele o será no caso de um bloco "catch" ter levantado uma exceção. Se o próprio bloco "finally" levantar uma exceção, esta provavelmente irá esconder uma exceção levantada anteriormente no "catch". Do mesmo modo, se houver uma instrução "return" no bloco "finally", o método irá finalizar normalmente pelo "return" mesmo se o bloco "catch" tiver levantado uma exceção.

Práticas gerais na codificação #

Parênteses #

O uso livre de parênteses é geralmente considerado uma boa prática, principalmente em expressões que utilizam vários operadores com o objetivo de se evitar problemas de precedência. Mesmo que a precedência do operador parece clara para você, ela pode não ser para outros – não assuma que outros programadores conheçam precedência tão bem quanto você. Seguem exemplos:

 if (a == b && c == d)     // EVITAR!
 
 if ((a == b) && (c == d)) // PREFERIDO

Retorno de valores #

Tente fazer com que a estrutura de seu programa reflita a sua real intenção. Por exemplo, o código a seguir:

if (expressaoLogica) {
  return TRUE;
} else { 
  return FALSE;
}

deveria ser escrito desta outra forma:

 return expressaoLogica;

Similarmente, este outro código

if (condição) { 
  return x;
} 
return y;

deveria ser escrito assim:

return (condição ? x : y);

Operador ternário #

Se uma expressão contendo um operador binário aparecer antes do sinal "?" do operador ternário, ela deve estar entre parênteses. Segue um exemplo:

(x >= 0) ? x : -x;

Comentários especiais #

Existem tags especiais de tarefas, ditas Task Tags, que servem justamente para indicar, através de comentários simples, que o código que se segue necessita de alguma correção ou conclusão.

  • TODO: para marcar que há uma tarefa pendente de início ou conclusão no código fonte abaixo do comentário;
  • FIXME: para marcar que há algo incoerente no código e que precisa de correção urgente;
  • XXX: para marcar que existe código fora dos padrões, não elegante ou sem garantia de desempenho, porém que funciona.

Essas tags geralmente são interpretadas pelo ambiente de desenvolvimento e suas indicações aparecem em visões e janelas apropriadas para que o desenvolvedor possa gerenciar o respectivo código defeituoso ou incompleto.

Eis alguns exemplos de utilização dessas tags:

// TODO: Terminar a codificação da leitura do arquivo 
boolean leuArquivo = Classe.abrirArquivo("teste.properties");
// FIXME: Verificar erro durante execução do método 
String resultado = Classe.metodoQualquer(100);
0 Anexos
3649 Visualizações
Média (0 Votos)
A média da avaliação é 0.0 estrelas de 5.
Comentários
Sem comentários ainda. Seja o primeiro.