Programação em geral

Este artigo é dedicado aos detalhes práticos da linguagem Java. Ele discute o tratamento de variáveis locais, o uso de bibliotecas, o uso dos tipos de dados, e dois recursos extralingüísticos: reflection e métodos nativos. Por fim, ele discute otimizações e convenções de nomes.

Item 29: Minimize o escopo de variáveis locais
Minimizando o escopo de variáveis locais você aumenta a legibilidade do código e reduz a probabilidade de erros.
A programação Java, diferentemente do C, permite que você declare variáveis em qualquer lugar que é permitida uma sentença de código. Assim, a melhor forma de minimizar o escopo de uma variável é declará-la onde ela for utilizada pela primeira vez. Desta forma fica mais fácil para o leitor do código lembrar qual é seu tipo e valor inicial, e caso após algum tempo a variável não seja mais utilizada é menos provável que sua declaração seja esquecida no código. Portanto, quase todas as declarações de variáveis locais devem ser seguidas de sua inicialização. Uma exceção é o caso de inicialização dentro de um bloco
try/catch, em que você pode declarar a variável fora do bloco para que ela possa ser acessada pelo resto do método.
No caso de loops, prefira o
for ao while, já que o primeiro permite que seja declarada uma variável que é usada no corpo do loop e também na inicialização, teste e incrementação do valor, enquanto no segundo seria necessário declarar a variável fora do bloco e ela poderia ser reutilizada por engano posteriormente.
Exemplo:
for (int i = 0, n = list.size(); i < n; i++) {
  doSomething(list.get(i));
}

O código acima é uma boa implementação para listas de acesso aleatório, como
ArrayList e Vector, pois é executado mais rápido do que com a utilização de um Iterator. Além disso, são declaradas duas variáveis com o escopo correto.
A última técnica para minimizar o escopo de variáveis é deixar os métodos pequenos e focados. Se você combina duas atividades em um único método, uma variável relevante para uma atividade pode ficar no escopo da parte do código que executa outra atividade, portanto o ideal é separar o método em dois.

Item 30: Conheça e utilize as bibliotecas
Utilizando as bibliotecas padrão, você é beneficiado pelo conhecimento dos experts que as escreveram e a experiência daqueles que as utilizaram antes de você. Por exemplo, muitos programadores geram números aleatórios entre zero e um determinado limite através do seguinte código:
static Random rnd = new Random();
static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}

Este exemplo parece funcionar, porém possui três falhas graves: se n for uma potência de dois pequena, a seqüência de números gerados será repetida em pouco tempo; se n não for uma potência de dois, alguns números serão retornados com maior freqüência que outros; e se o número gerado for Integer.MIN_VALUE e n não for uma potência de 2 o retorno do método será um número negativo. Para corrigir esses problemas seria necessário conhecimentos avançados de matemática, mas este trabalho já foi realizado para você pela pessoa que criou o método Random.nextInt(int) e testado por milhares de pessoas durante vários anos.
A segunda vantagem de utilizar as bibliotecas é não precisar perder tempo escrevendo soluções que não estão diretamente relacionadas ao seu trabalho.
Outra vantagem é que a performance das bibliotecas padrão tendem a aumentar no decorrer do tempo, sem nenhum esforço da sua parte. Como elas são utilizadas por muitas pessoas e em benchmarks da indústria, as organizações que as criam são incentivadas a torná-las mais rápidas.
E a maior vantagem do uso das bibliotecas é destacar o seu código, tornando-o mais legível, fácil de manter e reutilizável por outros desenvolvedores.
Várias funcionalidades são adicionadas à biblioteca em cada release do Java, e vale a pena estar familiarizado com elas para evitar criar código desnecessariamente. Todos os programadores devem conhecer pelo menos o conteúdo do
java.lang, java.util e java.io. As outras podem ser estudadas conforme surgir a necessidade. Uma das funcionalidades que merecem atenção especial é a framework de Collections.

Item 31: Evite float e double se forem necessárias respostas exatas
Os tipos
float e double utilizam aritimética binária de ponto flutuante e foram criados principalmente para cálculos científicos e de engenharia, que requerem aproximações precisas rápidas sobre determinadas magnitudes. Eles não fornecem resultados exatos e devem ser evitados em casos como cálculos monetários.
Um exemplo é o código
System.out.println(1.03 - 0.42); que gera como resultado 0.6100000000000001.
Para cálculos monetários o correto é utilizar
int, long ou BigDecimal. Utilizar os primitivos normalmente é mais rápido e mais prático, mas aí você deve controlar as casas decimais diretamente, por exemplo, utilizando centavos ao invés de reais. Se você preferir que o computador faça este trabalho, utilize o BigDecimal. Se as quantidades não ultrapassarem nove dígitos, você pode usar int; até dezoito dígitos, utilize long; e acima disto será necessário o BigDecimal.

Item 32: Evite Strings quando outros tipos forem mais apropriados
Strings foram criadas para representar texto. Mas quando um dado vem através de um arquivo, da rede ou input do teclado normalmente também está na forma de string. Se esse dado for numérico, deve ser traduzido para o formato numérico apropriado; se for a resposta de uma pergunta do tipo sim ou não, para boolean; e se representa um objeto, deve ser convertido para ele, mesmo que seja necessário criar uma nova classe.
Strings também não são apropriados para tipos "enumerados", agregados (quando são concatenados valores para representar um objeto) ou como chaves para permitir acesso a certa funcionalidade.

Item 33: Cuidado com a performance de concatenação de strings
O operador de concatenação de strings (+) é conveniente para combinar algumas strings em uma única string. Mas usando este operador repetidamente para concatenar n strings requer um tempo quadrático em n, pois strings são imutáveis. Para alcançar uma performance aceitável, utilize a classe
StringBuffer para guardar o conteúdo em construção. Através desta classe a performance é linear, apresentando uma diferença dramática quando o número de concatenações é elevado. Mesmo que o tamanho do StringBuffer não esteja otimizado para receber todo o conteúdo, a performance é bastante superior.
Portanto, não utilize o operador + para combinar mais do que algumas strings se a performance for importante. Utilize o método
append da classe StringBuffer ou trabalhe com uma array de caracteres.

Item 34: Refira-se aos objetos através de suas interfaces
O item 25 dá a dica que você deve utilizar interfaces ao invés de classes como tipos de parâmetros. De forma mais geral, você deve preferir o uso de interfaces ao invés de classes para referir-se a objetos. Portanto, se existir uma interface apropriada, parâmetros, valores de retorno, variáveis e campos devem ser declarados do tipo da interface. O único momento em que você precisa referir-se à classe do objeto é durante sua criação.
Assim, seu programa fica mais flexível, bastando alterar a classe na linha em que é chamado o construtor do objeto. Porém, se a classe original prover alguma funcionalidade especial em seus métodos que não é requerida pelo contrato da interface, é necessário tomar cuidado com o impacto disso no seu código. Por exemplo, você pode declarar
List subscribers = new Vector() e depois mudar der Vector para ArrayList. Ainda que ambos possuam os métodos de List, o primeiro é sincronizado, sendo incorreto fazer a substituição se o seu código depender disso.
Mesmo que hoje não exista previsão da necessidade de alterar a classe, futuramente pode surgir uma nova classe que desempenhe a mesma tarefa melhor ou de forma mais rápida, ficando fácil aproveitá-la caso seu objeto esteja referenciado pela interface.
Por outro lado, caso não exista uma interface apropriada, é correto referir-se ao objeto pela classe. Um exemplo disso são as classes de valores, como
String e BigInteger. Já no caso de frameworks, muitas vezes algumas classes têm como tipo fundamental classes abstratas ao invés de interfaces, portanto o ideal é referenciar o objeto pela classe base. E o último caso em que uma classe é usada diretamente ao invés de sua interface é quando seu código depende de métodos que não estão declarados na interface.

Item 35: Prefira interfaces ao invés de reflection (reflexão)
O mecanismo de reflection, do pacote
java.lang.reflect, oferece acesso programático a construtores, métodos e campos de uma classe. Desta forma é possível que uma classe utilize outra, mesmo que a última ainda não existisse quando a primeira foi compilada. É um mecanismo poderoso, mas tem desvantagens:
- você perde o benefício das verificações de tipo que acontecem em tempo de compilação, incluindo checagem de exceções;
- o código necessário para realizar acesso reflexivo é mais confuso que o tradicional;
- há um impacto de performance (no Java 1.3 era 40 vezes mais lento chamar um método por reflexão do que diretamente, e no Java 1.4 ainda gasta o dobro do tempo do acesso normal).
A reflection foi inserida no Java para ferramentas de construção baseadas em componentes. Portanto, deve ser utilizada em tempo de design. Objetos não devem ser acessados por reflexão em uma aplicação normal em tempo de execução. Apenas algumas aplicações mais sofisticadas são exceções a esta regra, como navegadores de classes, inspetores de objetos, ferramentas de análise de código, sistemas interpretados e RPC.
Você consegue obter a maior parte do benefício de reflection incorrendo apenas um pequeno custo utilizando-a de forma limitada. Por exemplo, se a classe não está disponível em tempo de compilação mas existe uma interface ou superclasse dela que está disponível, a instância é criada por reflexão mas os métodos são acessados normalmente através da superclasse ou interface. Se o construtor não tiver parâmetros não é necessário nem usar o
java.lang.reflect, pois o Class.newInstance provê a funcionalidade necessária.

Item 36: Use métodos nativos moderadamente
O JNI (Java Native Interface) permite que aplicações Java chamem métodos nativos. Eles são utilizados para obter-se acesso a características específicas da plataforma, como registros e trava de arquivos; para ter acesso a bibliotecas de código legado, muitas vezes responsáveis pelo acesso a dados legados; e para melhorar a performance em partes críticas da aplicação. Muitas vezes o Java provê um mecanismo de acesso mesmo para esses casos, como o pacote java.util.prefs para a funcionalidade de registro e a API JDBC para acesso a bancos de dados legados. Desde a release 1.3 do Java, também é raramente aconselhado usar métodos nativos para melhorar a performance, pois para a maioria das tarefas já é possível atingir uma performance comparável ao de métodos nativos utilizando apenas os recursos do Java. Por exemplo, a BigInteger inicialmente era implementada utilizando uma biblioteca aritimética de multiprecisão rápida escrita em C. A partir da versão 1.3 ela foi reescrita totalmente em Java e está mais rápida que a antiga.
O uso de métodos nativos tem sérias desvantagens, pois não são seguros (estão sujeitos a corrupção de memória), são dependentes de plataforma (perdendo portabilidade) e o processo de entrar e sair de métodos nativos causa impacto na performance. Além disso, são mais difíceis de escrever e entender.

Item 37: Otimize moderadamente
Existem três aforismos sobre otimização que todos deveriam saber:
"Mais pecados computacionais são cometidos em nome da eficiência (sem necessariamente alcançá-la) do que por qualquer outra razão - incluindo estupidez cega."
- Willian A. Wulf
"Nós deveríamos esquecer as pequenas eficiências 97% das vezes: otimização prematura é a raiz de todo mal"
- Donald E. Knuth
"Nós seguimos duas regras a respeito de otimização:
Regra 1. Não faça
Regra 2 (apenas para experts). Não faça ainda - ou seja, não até que você tenha uma solução perfeitamente clara e não otimizada"
- M. A. Jackson
Todos esses aforismos são 20 anos mais antigos que o Java e dizem uma grande verdade sobre otimização: é mais provável piorar do que melhorar o código, especialmente se você otimizar de forma prematura. Durante o processo você pode produzir software que não é rápido nem funciona corretamente e é difícil de corrigir. Esforce-se em escrever bons programas, não programas rápidos. Se a arquitetura for boa, será fácil otimizá-lo, pois as informações estarão encapsuladas e a alteração de um módulo individual não afetará o resto do sistema. Porém, é necessário pensar sobre a performance durante o processo de planejamento do sistema, pois uma arquitetura que limita a performance fica difícil de consertar depois que o sistema está pronto. Desta forma, os componentes que especificam a interação entre módulos e com o mundo exterior devem ser planejados cuidadosamente.
Considere as conseqüências das suas decisões da API. Deixar um tipo público mutável, por exemplo, pode requerer grande quantidade de cópia defensiva desnecessariamente. Usar herança em uma classe pública em que composição seria apropriada prende para sempre a classe a sua superclasse, colocando um limite artificial na performance da subclasse. E usar a classe de implementação ao invés da interface em uma API prende à implementação específica, sendo que implementações mais rápidas podem surgir futuramente. De forma geral, um bom planejamento da API é consistente com boa performance. Porém, deformar a API para melhorar a performance é uma má idéia.
Quando o sistema já está funcionando e você decide otimizá-lo, outra dica é medir a performance antes e depois de uma otimização. Em média um programa gasta 80% do tempo em 20% do código, então não adianta perder tempo otimizando uma parte do código que não é o gargalo. Ferramentas de profiling ajudam a identificar qual parte precisa de mais otimização. No caso do Java é particularmente importante medir os efeitos de uma otimização pois ele não tem um modelo de performance bem definido, não ficando bem claro os custos de cada operação primitiva e ocorrendo diferenças significativas de performance em diferentes JVMs.
Outro fator importante durante a otimização é a escolha do algoritmo, pois a otimização em baixo nível não consegue compensar um algoritmo inadequado.

Item 38: Siga as convenções de nomes
Violações de regras de nomes aumentam a dificuldade de entender a API e manter o código, confundindo e irritando outros programadores.
Nomes de pacotes devem ser hierárquicos, com suas partes separadas por pontos. As partes devem ser letras minúsculas e (raramente) números. O nome de qualquer pacote que vai ser utilizado fora de sua organização deve começar com o domínio de internet dela ao contrário (ex.: com.sun). As bibliotecas padrão e pacotes opcionais, cujos nomes começam com java e javax, são exceções a esta regra e não são permitidos esses nomes por usuários. O resto do nome do pacote deve consistir em uma ou mais partes que descrevem o pacote.. As partes devem ser curtas, normalmente com oito ou menos caracteres. Abreviações significativas são encorajadas, por exemplo util no lugar de utilities. Acrônimos também são aceitos, como awt. As partes  geralmente devem ser uma palavra única ou abreviação. Muitos pacotes têm nomes com apenas uma parte adicionalmente ao nome do domínio. Partes adicionais são apropriadas para pacotes maiores, cujo tamanho exige que sejam divididos em uma hierarquia informal (não há suporte lingüístico para uma hierarquia verdadeira).
Nomes de classes e interfaces devem consistir em uma ou mais palavras (substantivos ou frases substantivas), com a primeira letra de cada palavra em letra maiúscula. Abreviações devem ser evitadas, exceto por acrônimos e algumas abreviações comuns como max e min. Não há um consenso sobre acrônimos ficarem inteiros em letra maiúscula ou apenas a primeira letra. Apesar da primeira abordagem ser mais comum, fica mais legível deixar apenas a primeira letra maiúscula, especialmente quando vários acrônimos ocorrem juntos.
Nomes de métodos e campos devem seguir as mesmas convenções tipográficas de classes e interfaces, exceto que a primeira letra deve ser minúscula, mesmo que seja um acrônimo. A única exceção é o caso de constantes, que devem ter seus nomes todos em maiúsculas com as palavras separadas por underscore.
Os métodos normalmente utilizam verbos ou frases verbais como nome. Métodos que retornem um boleano usam o prefixo "is" seguido de um adjetivo ou substantivo, ex: isDigit. Já os que retornam o resultado de uma função ou um atributo do objeto são nomeados com substantivos com ou sem o prefixo "get". O uso do get só é obrigatório se a classe for um JavaBean. Havendo um método para atribuir o valor a um atributo do objeto utiliza-se o prefixo set. Métodos que convertem o tipo de um objeto são chamados toTipo. Métodos que retornam uma "visualização" de um tipo diferente do objeto recebido normalmente são chamados de asTipo. Métodos que retornam um primitivo com o mesmo valor do objeto em que foram chamados recebem o nome de tipoValue. Nomes comuns para static factories são valueOf e getInstance.
Variáveis locais seguem as mesmas convenções de métodos e campos, mas abreviações são permitidas.
Exemplos:
Pacote: com.sun.medialib, com.sun.jdi.event
Classe ou interface: Timer, TimerTask, KeyFactorySpi, HttpServlet
Método ou campo: remove, ensureCapacity, getCrc
Valor constante: VALUES, NEGATIVE_INIFINITY
Variáveis locais: i, xref, houseNumber




Bibliografia:
Bloch, Joshua. Effective Java.

Resumo por: Vanessa Sabino
1