|
| Tipo de Comentário | Uso | Exemplo |
| Documentação | Use os comentários de documentação imediatamente antes de declarações de interfaces, classes, funções de membro e campos para documentá-los. Esses comentários serão processados pelo javadoc. Veja a seguir como criar documentação externa para uma classe. |
/** Cliente: é qualquer pessoa ou organização a quem vendemos serviços e produtos. @author S.W. Ambler */ |
| Estilo C | Use os comentários de estilo C para documentar linhas de código que não são mais aplicáveis, mas que você ainda quer manter caso os usuários mudem de idéia ou porque você talvez queira desativá-los temporariamente durante um processo de depuração. |
/* Esse código foi desativado por B. Gustafsson no dia 4 de junho de 1999, porque foi substituído pelo código precedente. Exclua-o após dois anos caso ainda não seja aplicável. . . . (o código-fonte) */ |
| Linha única | Use comentários em uma única linha internamente nas funções de membro para documentar a lógica do negócio, as seções do código e as declarações de variáveis temporárias. |
// Aplicar desconto de 5% a todas as // faturas acima de R$ 1.000,00 em função de // campanha geral de descontos iniciada em // fevereiro de 1995. |
É importante que sua organização defina um padrão para mostrar como os comentários de estilo C e os de uma única linha serão usados e siga-o de forma consistente. Use um tipo para documentar a lógica do negócio e outro para documentar o código antigo. Use os comentários de uma única linha para a lógica do negócio, pois você pode incluir a documentação na mesma linha do código (esse procedimento é conhecido como inserção em linha). Use os comentários de estilo C para documentar o código antigo e desnecessário, pois eles permitem desativar várias linhas de código de uma só vez. Como os comentários de estilo C são bastante semelhantes aos comentários de documentação, para evitar confusão, não os use em outros locais.
Cuidado com Comentários de Fim de Linha [MCO93] é contra o uso de comentários in-line, também conhecidos como comentários de fim de linha. McConnell sugere que os comentários sejam alinhados à direita do código, para que não interfiram na estrutura visual do código. Como conseqüência, costuma ser difícil formatá-los e, "se você usar muitos deles, será preciso muito tempo para alinhá-los. Esse tempo não é dedicado a conhecer melhor o código, mas apenas à tarefa entediante de pressionar a barra de espaço ou a tecla de tabulação". Ele também destaca que os comentários de fim de linha são difíceis de serem mantidos, pois, quando o código contido na linha aumenta, ele esbarra no comentário de fim de linha e, se você o estiver alinhando, terá de fazer o mesmo com todo o resto.
No Sun Java Development Kit (JDK) está incluído um programa denominado javadoc, que processa arquivos de código Java e produz documentação externa para os programas Java na forma de arquivos HTML. O programa javadoc suporta um número limitado de marcas, palavras reservadas que marcam o início de uma seção da documentação. Consulte a documentação do javadoc JDK para obter mais detalhes.
Marca Usada para Finalidade @author name Classes, Interfaces Indica os autores de determinado trecho do código. Use uma marca por autor. @deprecated Classes, Funções de Membro Indica que a API da classe ficou obsoleta e, portanto, não deve mais ser usada. @exception name description Funções de Membro Descreve as exceções que uma função de membro aciona. Use uma marca por exceção e informe o nome completo da classe referente à exceção. @param name description Funções de Membro Usada para descrever um parâmetro passado para uma função de membro, inclusive seu tipo ou classe e sua utilização. Use uma marca por parâmetro. @return description Funções de Membro Descreve o valor de retorno (se houver) de uma função de membro. Indique o tipo ou a classe e os possíveis usos do valor de retorno. @since Classes, Funções de Membro Indica o tempo de existência do item, ou seja, desde o JDK 1.1. @see ClassName Classes, Interfaces, Funções de Membro, Campos Gera um link de hipertexto na documentação para a classe especificada. Você pode - e provavelmente deve - usar um nome de classe totalmente qualificado. @see ClassName#member functionName Classes, Interfaces, Funções de Membro, Campos Gera um link de hipertexto na documentação para a função de membro especificada. Você pode - e provavelmente deve - usar um nome de classe totalmente qualificado. @version text Classes, Interfaces Indica as informações sobre versão de determinado trecho do código. A maneira como você documenta o código causa grande impacto tanto na sua produtividade quanto na produtividade de todos que posteriormente serão responsáveis pela manutenção e melhoria do código. Com a documentação do código no início do processo de desenvolvimento, sua produtividade será maior porque você será forçado a pensar sobre a lógica empregada antes de aplicá-la ao código. Além disso, quando você retorna ao código que escreveu dias ou semanas antes, pode determinar facilmente o que tinha em mente ao escrevê-lo, pois os procedimentos já estão documentados.
Nunca se esqueça de que o código escrito hoje poderá ser usado por muitos anos e provavelmente será mantido e melhorado por outra pessoa. Você deve procurar criar um código legível e compreensível, porque esses fatores facilitam a manutenção e a implementação de melhorias.
As funções de membro devem ser nomeadas através de uma descrição completa, que combine letras maiúsculas e minúsculas, sendo que a primeira letra de toda palavra não-inicial deve ser maiúscula. Também é comum usar um verbo forte e ativo para a primeira palavra de uma função de membro.
Exemplos:
openAccount()
printMailingLabel()
save()
delete()
Esta convenção resulta em funções de membro cuja finalidade pode ser freqüentemente determinada por seus nomes. Embora envolva um pouco mais de digitação por parte do desenvolvedor, porque geralmente os nomes são longos, essa convenção é conveniente por aumentar a compreensão do código.
No próximo capítulo, abordaremos mais detalhadamente os acessos, isto é, funções de membro que obtêm e definem os valores de campos (campos ou propriedades). As convenções de nomeação para os acessos, entretanto, estão resumidas a seguir.
Getters são funções de membro que retornam o valor de um campo. A palavra "get" deve ser usada como prefixo do nome do campo, a menos que o campo seja boolean. Nesse caso, o prefixo do nome será "is", e não "get".
Exemplos:
getFirstName()
getAccountNumber()
isPersistent()
isAtEnd()
De acordo com essa convenção de nomeação, fica óbvio que uma função de membro retornará um campo de um objeto. No caso de boolean getters, fica óbvio que o valor retornado será verdadeiro ou falso. Outra vantagem é que esse padrão segue as convenções de nomeação usadas pelo Beans Development Kit (BDK) para as funções de membro getter. [DES97] A principal desvantagem é que "get" é um termo supérfluo e requer digitação adicional.
Convenção de nomeação alternativa para Getters: has e can
Uma alternativa viável, baseada em convenções lingüísticas adequadas, é usar "has" ou "can", em vez de "is" para os boolean getters. Por exemplo, nomes de getters como hasDependents() e canPrint() são bastante expressivos durante a leitura do código. O problema dessa abordagem é que o BDK não selecionará essa estratégia de nomeação (por enquanto). Você pode renomear essas funções de membro como isBurdenedWithDependents() e isPrintable().
Os setters, também conhecidos como mutantes, são funções de membro que modificam os valores de um campo. Use a palavra "set" como prefixo para o nome do campo, seja qual for o tipo de campo.
Exemplos:
setFirstName(String aName)
setAccountNumber(int anAccountNumber)
setReasonableGoals(Vector newGoals)
setPersistent(boolean isPersistent)
setAtEnd(boolean isAtEnd)
Com essa convenção de nomeação, fica óbvio que uma função de membro definirá o valor de um campo de um objeto. Outra vantagem desse padrão é que ele segue as convenções de nomeação usadas pelo BDK para as funções de membro setter. [DES97] A principal desvantagem é que "set" é um termo supérfluo e requer digitação adicional.
Os construtores são funções de membro que executam a inicialização necessária quando um objeto é criado pela primeira vez. Os construtores sempre recebem o mesmo nome da classe a que pertencem. Por exemplo, um construtor da classe Customer será Customer(). O uso de letras maiúsculas e minúsculas é o mesmo.
Exemplos:
Customer()
SavingsAccount()
PersistenceBroker()
Essa convenção de nomeação é definida pela Sun Microsystems e deve ser seguida rigorosamente.
Para garantir um bom design no qual você diminua o acoplamento entre classes, adote como regra prática geral ser o mais restritivo possível ao definir a visibilidade de uma função de membro. Se uma função de membro não precisa ser pública, defina-a como protegida. Se ela não precisar ser protegida, defina-a como privada.
Visibilidade Descrição Uso Adequado pública Uma função de membro pública pode ser disparada por outra função de membro em outro objeto ou classe. Quando a função de membro tem de ser acessada por objetos e classes fora da hierarquia de classes na qual a função de membro está definida. protegida Uma função de membro protegida pode ser disparada por qualquer função de membro da classe na qual ela está definida ou por qualquer subclasse dessa classe. Quando a função de membro fornece comportamento que é necessário internamente à hierarquia de classes, mas não externamente. privada Uma função de membro privada só pode ser disparada por outras funções de membro da classe na qual ela está definida, mas não nas subclasses. Quando a função de membro oferece comportamento que é específico para a classe. As funções de membro privadas geralmente são o resultado da refatoração (ou reorganização) do comportamento de outras funções de membro contidas na classe para encapsular um comportamento específico.
A maneira como você documenta uma função de membro costuma ser um fator decisivo para determinar se ela será entendida e, portanto, se poderá ser mantida e ampliada.
Toda função de membro Java deve incluir algum tipo de cabeçalho (denominado documentação da função de membro) na parte superior do código-fonte que documente todas as informações que são vitais para sua compreensão. Essas informações incluem, entre outras, as seguintes questões:
- O que a função de membro executa e por quê. Ao documentar o que uma função de membro executa, você permite que outros usuários determinem se o código poderá ser reutilizado. Documentar os motivos pelos quais ela executa permite que outros usuários insiram seu código no contexto. Você permite também que outros determinem se uma nova mudança deverá ser realmente efetuada em um trecho do código (talvez o motivo para a nova mudança entre em conflito com o motivo pelo qual o código foi escrito inicialmente).
- Qual função de membro deve ser passada como parâmetro. Também é preciso indicar quais parâmetros (se houver) devem ser passados para uma função de membro e como eles serão usados. Essas informações são necessárias para que os outros programadores saibam quais informações devem ser passadas para uma função de membro. A marca @param do javadoc, abordada na seção 2.2.2 - Uma rápida visão geral de javadoc, é usada para esse fim.
- O que é retornado por uma função de membro. Documente o que a função de membro retorna (caso ela ofereça algum tipo de retorno) para que outros programadores possam usar o valor ou o objeto de retorno corretamente. A marca @return do javadoc, abordada na seção 2.2.2 -Uma rápida visão geral de javadoc, é usada para esse fim.
- Erros conhecidos. Qualquer problema pendente com uma função de membro deve ser documentado para que os outros desenvolvedores conheçam os pontos fracos e as dificuldades da função de membro. Se um erro se aplicar a mais de uma função de membro de uma classe, ele deverá ser documentado para a classe, e não para a função de membro.
- Todas as exceções que uma função de membro aciona. Documente toda e qualquer exceção que seja acionada por uma função de membro para que outros programadores saibam o que o código precisará obter. A marca @exception do javadoc, abordada na seção 2.2.2 - Uma rápida visão geral de javadoc, é usada para esse fim.
- Decisões de visibilidade. Se você achar que sua opção de visibilidade para uma função de membro será questionada pelos outros desenvolvedores (talvez você tenha criado uma função de membro como pública, mesmo que nenhum outro objeto a dispare por enquanto), documente sua decisão. Isso tornará seu raciocínio claro para os outros desenvolvedores, evitando que eles percam tempo preocupando-se por que você fez algo questionável.
- Como uma função de membro altera o objeto. Indique se uma função de membro altera um objeto. Por exemplo, a função de membro withdraw() de uma conta bancária modifica o saldo dessa conta. Essas informações são necessárias para que outros programadores Java saibam exatamente como o disparo de uma função de membro afetará o objeto de destino.
- Evite o uso de cabeçalhos que contenham informações como autor, números de telefone, datas de criação/modificação e localização de unidades (ou nome de arquivo), porque elas ficam rapidamente obsoletas. Coloque os avisos de direitos autorais no fim da unidade. Por exemplo, os leitores não querem percorrer duas ou três páginas de texto que não são úteis para a sua compreensão do programa, nem querem passar por texto que não contenha qualquer informação específica do programa, como um aviso de direitos autorais. Evite o uso de barras verticais ou quadros e caixas fechados, pois eles apenas criam confusão visual e são difíceis de manter a consistência. Use uma ferramenta de gerenciamento de configuração para manter um histórico de unidades.
- Exemplos de como disparar a função de membro, se apropriado. Uma das maneiras mais fáceis de determinar como um trecho do código funciona é verificar um exemplo. Considere a possibilidade de incluir um ou dois exemplos sobre como disparar uma função de membro.
- Precondições e pós-condições aplicáveis. Precondição é uma restrição segundo a qual uma função de membro funcionará corretamente. Pós-condição é uma propriedade ou afirmativa que será verdadeira depois de concluída a execução de uma função de membro. [MEY88] As precondições e pós-condições descrevem de várias formas as suposições feitas durante a criação de uma função de membro [AMB98] e definem com precisão os limites de uso dessas funções.
- Todas as questões de simultaneidade. Simultaneidade é um conceito novo e complexo para muitos desenvolvedores e, na melhor das hipóteses, é um tópico antigo e complexo para programadores com experiência em simultaneidade. Em suma, se você usar as características de simultaneidade da programação Java, deverá documentá-las integralmente. [LEA97] sugere que, quando uma classe inclui funções de membro sincronizadas e não sincronizadas, você deve documentar o contexto de execução em que se baseia a função de membro, principalmente se ela requer acesso irrestrito para que os outros desenvolvedores possam utilizá-la com segurança. Se um setter (uma função de membro que atualiza um campo) de uma classe que implementa a interface Runnable não estiver sincronizado, você deverá documentar os motivos pelos quais não está. Por fim, caso você sobreponha ou sobrecarregue uma função de membro e altere sua sincronização, também deve documentar o motivo.
- Você só deve documentar aquilo que contribua para a clareza do código. Não é preciso documentar todos os fatores descritos acima para toda e qualquer função de membro, porque nem todos os fatores são aplicáveis a todas as funções de membro. Entretanto, vários deles devem ser documentados para cada função de membro criada.
Além da documentação de função de membro, você também precisa incluir comentários que descrevam o seu trabalho. O objetivo é facilitar a compreensão, a manutenção e a implementação de melhorias nas funções de membro.
Há dois tipos de comentários que podem ser usados para documentar as especificações internas do código: os comentários de estilo C ( /* e */ ) e os comentários em uma única linha ( // ). Como abordado anteriormente, você deve considerar a possibilidade de escolher um estilo de comentários para documentar a lógica do negócio do código e outro para comentar os códigos não necessários. Utilize comentários em uma única linha para a lógica do negócio, pois esse estilo pode ser usado para linhas de comentários inteiras e para comentários in-line que continuem até o fim de uma linha de código. Use os comentários de estilo C para documentar linhas de código desnecessário, pois eles facilitam a retirada de várias linhas do código com apenas um comentário. Além disso, como os comentários de estilo C são muito semelhantes aos comentários de documentação, sua utilização pode causar confusão, reduzindo a compreensão do código. Sendo assim, não os utilize com freqüência.
Internamente, você sempre deve documentar o seguinte:
- Estruturas de controle. Descreva cada estrutura de controle, como sentenças de comparação e ciclos. Você não deve precisar ler o código inteiro em uma estrutura de controle para determinar o que ele executa. Basta verificar uma ou duas linhas de comentários que o antecedem.
- O que o código executa e por quê. Ao verificar um trecho do código, você sempre pode saber o que ele faz. Contudo, se o código não for óbvio, raramente será possível definir o motivo de determinada ação. Por exemplo, você pode verificar uma linha de código e determinar facilmente que um desconto de 5% está sendo aplicado ao valor total de um pedido. Isso é fácil. O que não é fácil é perceber POR QUE esse desconto está sendo aplicado. Obviamente, há uma regra de negócio que determina a aplicação do desconto. Portanto, essa regra deve ser pelo menos mencionada no código, para que os outros desenvolvedores possam entender o motivo.
- Variáveis locais. Embora esse assunto seja abordado mais detalhadamente no Capítulo 5, cada variável local definida em uma função de membro deve ser declarada em sua própria linha de código e, geralmente, deve ter um comentário in-line que descreva seu uso.
- Código difícil ou complexo. Se você perceber que não é possível reescrevê-lo ou caso não tenha tempo para isso, documente completamente os códigos complexos de uma função de membro. Como regra prática geral, se o código não for óbvio, é preciso documentá-lo.
- A ordem de processamento. Se houver instruções no código que precisem ser executadas em uma ordem definida, garanta que sejam documentadas [AMB98]. Não há nada pior do que efetuar uma simples modificação em um trecho do código para depois perceber que ele não funciona mais. As horas investidas na procura do problema significam que você só conseguiu desordenar tudo.
- Documente as chaves de fechamento. Com freqüência, você verá que há estruturas de controle contidas em outras estruturas de controle que, por sua vez, também estão contidas em estruturas de controle. Embora você deva evitar escrever códigos desse tipo, pode haver situações em que seja preciso fazê-lo. O problema é que fica confuso determinar a qual chave de finalização o caractere } pertence, a qual estrutura de controle. A boa notícia é que alguns editores de código suportam um recurso que, quando você seleciona uma chave de abertura, a chave de fechamento é automaticamente destacada. A má notícia é que nem todos os editores suportam esse recurso. De acordo com a nossa experiência, marcar as chaves de finalização com um comentário in-line, como //end if, //end for, //end switch, facilita bastante a compreensão do código.
Esta seção aborda várias técnicas que ajudam a separar os desenvolvedores profissionais de outros tipos de codificadores. As técnicas são:
- Documentar o código.
- Criar parágrafos ou recuos no código.
- Usar espaço em branco.
- Seguir a regra dos 30 segundos.
- Criar linhas de comandos simples e curtas.
- Especificar a ordem das operações.
Lembre-se de que, se não valer a pena documentar o código, não valerá a pena mantê-lo.[NAG95] Se você aplicar corretamente os padrões e as diretrizes de documentação propostos neste documento, obterá códigos de melhor qualidade.
Uma maneira de melhorar a legibilidade de uma função de membro é criar parágrafos ou recuos no código dentro do escopo de um bloco de código. Qualquer código contido entre chaves - os caracteres { e } - compõe um bloco. A idéia básica é que o código contido em um bloco seja uma unidade uniformemente recuada.
A convenção Java sugere que a chave de abertura seja colocada na linha seguinte ao proprietário do bloco e que a chave de fechamento seja recuada em um nível. O importante, segundo [LAF97], é que a sua organização escolha um estilo de recuo e se atenha a ele. Use o mesmo estilo de recuo que o ambiente de desenvolvimento Java usa para o código que ele gera.
Algumas linhas vazias (denominadas espaço em branco) adicionadas ao código Java podem aumentar a sua legibilidade, dividindo-o em pequenas seções de fácil assimilação. [VIS96] sugere o uso de uma única linha vazia para separar grupos lógicos do código (como estruturas de controle), com duas linhas vazias para separar as definições de função de membro. Sem o espaço em branco, a leitura e a compreensão ficam mais difíceis.
Os outros programadores devem conseguir entender o que a função de membro executa, bem como o motivo e a maneira dessa execução em menos de 30 segundos. Se isso não for possível, é sinal de que o código é muito difícil de manter e deve ser melhorado. Trinta segundos, nada mais. A prática demonstra que, se uma função de membro ocupa mais de uma tela, provavelmente ela é muito grande.
O código deve ter uma execução por linha. Na época das placas de inserção, fazia sentido tentar o máximo de funcionalidade em uma única linha de código. Sempre que você tentar mais de uma execução em uma única linha de código, estará dificultando o entendimento. Por que fazer isso? Queremos facilitar a compreensão do código para também facilitar sua manutenção e melhoria. Assim como uma função de membro deve desempenhar apenas uma ação, você também deve incluir apenas uma ação em uma única linha de código.
Além disso, você deve escrever códigos que possam ser visualizados na tela [VIS96]. Você não deve precisar rolar a janela de edição para a direita a fim de ler toda a linha de código, inclusive do código que usa comentários in-line.
Uma maneira realmente fácil de aumentar a legibilidade do código é usar parênteses para especificar a ordem exata das operações no código Java [NAG95] e [AMB98]. Se você precisa saber a ordem das operações de uma linguagem para entender o código-fonte, é sinal de que há algo muito errado. Isso é um problema principalmente nas comparações lógicas, em que AND e OR e diversos comparadores são usados em conjunto. Se você usar linhas de comandos simples e curtas, como sugerido anteriormente, isso não será mais um problema.
O termo campo usado aqui se refere a um campo que o BDK chama de propriedade [DES97]. Campos são dados que descrevem um objeto ou uma classe. Os campos podem ser tipos de dados básicos (como seqüências de caracteres ou flutuantes) ou podem ser um objeto (como um cliente ou uma conta bancária).
Use um descritor completo para nomear os campos, [GOS96] e [AMB98], explicando o que cada campo representa. Campos que são conjuntos (como matrizes ou vetores) devem receber nomes no plural para indicar que representam vários valores.
Exemplos:
firstName
zipCode
unitPrice
discountRate
orderItems
Para nomes de componentes (widgets de interface), use um descritor completo pós-fixado pelo tipo de widget. Esse procedimento torna mais fácil a identificação da finalidade do componente e de seu tipo, facilitando inclusive sua localização em uma lista. Como muitos ambientes de programação visual oferecem listas de todos os componentes de um applet ou aplicativo, pode ser confuso se todos os itens forem nomeados como button1, button2 e assim por diante.
Exemplos:
okButton
customerList
fileMenu
newFileMenuItem
4.1.1.1 Alternativas para a nomeação de componentes: notação húngara
A "Notação Húngara" [MCO93] baseia-se no princípio de que um campo deve ser nomeado usando a seguinte abordagem: xEeeeeeEeeeee, onde x indica o tipo de componente e EeeeeEeeeee é o descritor lingüístico completo.
Exemplos:
pbOk
lbCustomer
mFile
miNewFile
os desenvolvedores podem julgar rapidamente seu tipo e modo de uso. A principal desvantagem é que a notação pré-fixada se torna inconveniente quando você tem muitos tipos de widgets e não usa a convenção de nomeação com descritores completos.A principal vantagem é ser um padrão comum do mercado para o código C++, que já é adotado por muitas pessoas. A partir do nome da variável,
Esta é basicamente uma combinação das outras duas alternativas e resulta em nomes como okPb, customerLb, fileM e newFileMi. A principal vantagem é que o nome do componente indica o tipo de widget e que os widgets do mesmo tipo não são agrupados em uma lista alfabética. A principal desvantagem é não usar uma descrição completa, o que dificulta a memorização do padrão, já que se desvia da norma.
Seja qual for a convenção escolhida, você deverá criar uma lista de nomes "oficiais" de widgets. Por exemplo, ao nomear botões, você usa Button, PushButton, b ou pb? Crie uma lista e coloque-a à disposição de todos os desenvolvedores Java da sua organização
Em Java, as constantes valores que não mudam são geralmente implementadas como campos de classes finais estáticos. A convenção reconhecida é o uso de palavras completas, todas em maiúsculas, separadas por sublinhados [GOS96].
Exemplos:
MINIMUM_BALANCE
MAX_VALUE
DEFAULT_START_DATE
A principal vantagem dessa convenção é ajudar a fazer a diferenciação entre constantes e variáveis. Veremos mais adiante neste documento que você pode aumentar significativamente a flexibilidade e a manutenibilidade do código não definindo constantes. Prefira definir funções de membro getter que retornem o valor das constantes.
Um conjunto (como uma matriz ou um vetor) deve receber um nome no plural que represente os tipos de objetos armazenados pela matriz. O nome deve ser um descritor completo, com a primeira letra das palavras não-iniciais em maiúscula.
Exemplos:
customers
orderItems
aliases
A principal vantagem dessa convenção é ajudar a fazer a diferenciação entre campos que representam vários valores (conjuntos) e aqueles que representam valores únicos (não-conjuntos).
Quando os campos são declarados como protegidos, existe a possibilidade de eles serem acessados diretamente pelas funções de membro das subclasses, aumentando de forma eficaz o acoplamento em uma hierarquia de classes. Como esse procedimento dificulta a manutenção e a implementação de melhorias nas classes, deve ser evitado. Os campos nunca devem ser acessados diretamente. Para acessá-los, use funções de membro de acesso (veja abaixo).
Visibilidade Descrição Uso Adequado pública Um campo público pode ser acessado por outra função de membro em outro objeto ou classe. Não crie campos públicos. protegida Um campo protegido pode ser acessado por qualquer função de membro da classe na qual ele está declarado ou por qualquer função de membro definida em subclasses dessa classe. Não crie campos protegidos. privada Um campo privado só pode ser acessado por funções de membro da classe na qual ele está definido, mas não nas subclasses. Todos os campos devem ser privados e acessados por funções de membro getter e setter (acessos). Os campos que não são persistentes (que não serão salvos em armazenamento permanente) devem ser marcados como estáticos ou transientes [DES97]. Isso os torna compatíveis com as convenções do BDK.
Ocultamento de nome se refere à prática de atribuir a uma variável local, a um argumento ou a um campo um nome igual (ou semelhante) ao de outro com escopo mais amplo. Por exemplo, se um campo denominado firstName, não crie um parâmetro ou variável local denominado firstName, ou parecido com firstNames ou fistName. Isso dificultará a compreensão do código e ele ficará sujeito a erros, porque os outros desenvolvedores, ou até mesmo você, não lerão o código corretamente enquanto ele estiver sendo modificado, tornando mais difícil a detecção de erros.
Todos os campos devem ser bem documentados para que os outros desenvolvedores possam entendê-los. Para ser eficiente, é preciso documentar:
- Sua descrição. É preciso descrever os campos para que as pessoas saibam como usá-los.
- Todas as invariantes aplicáveis. Invariantes de um campo são as condições que sempre são verdadeiras a respeito do campo. Por exemplo, uma invariante sobre o campo dayOfMonth pode ser que seu valor esteja entre 1 e 31 (obviamente você pode ser mais complexo com essa invariante e restringir o valor do campo de acordo com o mês e o ano). Documentando as restrições sobre o valor de um campo, é possível definir importantes regras de negócios que tornam mais fácil entender como o código funciona.
- Exemplos: Para os campos que têm regras de negócios complexas associadas a eles, você deve fornecer diversos exemplos para facilitar sua compreensão. Um exemplo geralmente é como um bom quadro: vale mil palavras.
- Questões de simultaneidade. Simultaneidade é um conceito novo e complexo para muitos desenvolvedores. Na melhor das hipóteses, é um tópico antigo e complexo para programadores com experiência em simultaneidade. O resultado final é que, se você usar as características de simultaneidade da programação Java, deverá documentá-las integralmente.
- Decisões de visibilidade. Se você tiver declarado que a visibilidade de um campo pode ser tudo menos privada, documente o motivo dessa sua opção. A visibilidade de campo foi abordada na seção 4.2 - Visibilidade de campo e o uso de funções de membro de acesso para suportar o encapsulamento será abordado na seção 4.4 - Uso de funções de membro de acesso. O importante é que é preciso um motivo realmente bom para não declarar uma variável como privada.
Além das convenções de nomeação, a manutenibilidade dos campos é obtida pelo uso adequado de funções de membro de acesso, ou seja, funções de membro que oferecem a funcionalidade necessária para atualizar um campo ou acessar seu valor. As funções de membro de acesso podem ser de dois tipos: setters (também conhecidos como mutantes) e getters. O setter modifica o valor de uma variável, enquanto o getter a obtém.
Embora as funções de membro de acesso usadas sobrecarreguem o código, como os compiladores Java foram otimizados para sua utilização, essa afirmativa não é mais verdadeira. Os acessos ajudam a ocultar detalhes de implementação da classe. Usando no máximo dois pontos de controle para acessar uma variável (um setter e um getter), você aumenta a manutenibilidade da classe diminuindo os pontos em que as mudanças precisam ser efetuadas. A otimização de códigos Java será abordada na seção 9.3 - Otimização de Códigos Java.
Um dos padrões mais importantes que podem ser impostos pela sua organização é o uso de acessos. Alguns desenvolvedores não querem usar funções de membro de acesso para evitar digitação adicional. Por exemplo, para um getter, é preciso digitar "get" e "()" antes e depois do nome do campo. O importante é que a manutenibilidade e a extensibilidade proporcionadas pelos acessos justificam plenamente seu uso.
Os acessos são o único meio de acessar campos. Um conceito-chave sobre a utilização apropriada de funções de membro de acesso é que as ÚNICAS que podem trabalhar diretamente com um campo são as funções de membro de acesso. Sim, é possível acessar diretamente um campo privado nas funções de membro da classe em que o campo está definido. Não faça isso, todavia, pois você aumentará o acoplamento na classe.
"Um bom design de programa procura isolar partes do programa de influências externas desnecessárias, não-intencionais ou indesejadas. Os modificadores de acesso (acessos) fornecem meios explícitos e verificáveis para a linguagem controlar esses contatos". [KAN97]
As funções de membro de acesso aumentam a manutenibilidade das classes da seguinte forma:
- Atualização de campos. Existem pontos únicos para atualização de cada campo, o que facilita sua modificação e teste. Em outras palavras, os campos são encapsulados.
- Obtenção de valores de campos. Você tem controle absoluto de como os campos são acessados e por quem.
- Obtenção dos valores de constantes e dos nomes de classes. Com o encapsulamento do valor de constantes e dos nomes de classe nas funções de membro getter, quando esses valores e nomes são alterados, você só precisa atualizar o valor no getter, e não todas as linhas de código em que a constante ou o nome é usado.
- Inicialização de campos. O uso da inicialização lenta garante que os campos sejam sempre inicializados e que essa inicialização só ocorrerá quando necessário.
- Redução do acoplamento entre uma subclasse e suas superclasses. Quando as subclasses acessam campos herdados apenas através de suas respectivas funções de membro de acesso, é possível alterar a implementação de campos na superclasse sem que isso afete suas subclasses, reduzindo de forma eficaz o acoplamento entre elas. Os acessos diminuem o risco de uma "classe base frágil", em que as mudanças efetuadas em uma superclasse se propagam até suas subclasses.
- Encapsulamento de mudanças nos campos. Se as regras de negócios pertencentes a um ou mais campos forem alteradas, possivelmente você poderá modificar os acessos para garantir os mesmos recursos de antes da mudança, o que facilita a resposta a novas regras de negócios.
- Simplificação de questões de simultaneidade. [LEA97] destaca que as funções de membro setter apontam somente um local para incluir notifyAll caso haja esperas baseadas no valor desse campo. Com esse procedimento, fica mais fácil optar por uma solução de simultaneidade.
- O ocultamento de nomes deixa de ser um problema. Embora você deva evitar o ocultamento de nomes, atribuindo às variáveis locais os mesmos nomes dos campos, o uso de acessos para sempre acessar campos significa que você pode nomear as variáveis locais como desejar. Não se preocupe com o ocultamento de nomes de campo, porque eles nunca serão acessados diretamente.
O único momento em que você talvez não use acessos é quando o tempo de execução for prioridade máxima. Entretanto, esse é um caso muito raro e só ocorre quando um maior acoplamento no aplicativo justifica essa medida.
Os nomes das funções de membro getter devem incluir "get" + nome do campo. Se o campo representar um valor boolean (verdadeiro ou falso), o nome do getter será "is" + nome do campo. Os nomes das funções de membro setter devem ser "set" + nome do campo, seja qual for o tipo de campo ([GOS96] e [DES97]). O nome de campo é sempre uma combinação de letras maiúsculas e minúsculas, com letra maiúscula no início de todas as palavras. Essa convenção de nomeação é usada de forma consistente no JDK e é necessária para o desenvolvimento de beans.
Exemplos:
| Campo | Tipo | Nome do getter | Nome do setter |
firstName |
string |
getFirstName() |
setFirstName() |
address |
Address object |
getAddress() |
setAddress() |
persistent |
boolean |
isPersistent() |
setPersistent() |
customerNo |
int |
getCustomerNo() |
setCustomerNo() |
orderItems |
Array of OrderItem objects |
getOrderItems() |
setOrderItems() |
Os acessos podem ser usados para outros fins além de coletar e definir os valores de campos de instância. Esta seção aborda como aumentar a flexibilidade do código usando acessos para:
- inicializar os valores dos campos
- acessar valores constantes
- acessar conjuntos
- acessar vários campos simultaneamente
4.4.3.1 Inicialização lenta
![]()
As variáveis precisam ser inicializadas antes de serem acessadas. Há duas vertentes de pensamento sobre inicialização: inicializar todas as variáveis no momento em que o objeto é criado (abordagem tradicional) ou inicializá-las quando forem usadas pela primeira vez.
A primeira abordagem usa funções de membro especiais que são disparadas quando o objeto é criado (denominadas construtores). Embora funcione, essa abordagem está comprovadamente mais sujeita a erros. Ao adicionar uma nova variável, você poderá facilmente se esquecer de atualizar os construtores.
Uma abordagem alternativa é a inicialização lenta, em que os campos são inicializados por suas funções de membro getter, como demonstrado a seguir. Observe como uma função de membro setter é usada na função de membro getter. Note que a função de membro verifica se o número da ramificação é zero. Se for, ela o definirá com o valor padrão apropriado.
/** Answers the branch number, which is the leftmost
four digits of the full account number.
Account numbers are in the format BBBBAAAAAA.
*/
protected int getBranchNumber()
{
if( branchNumber == 0)
{
// The default branch number is 1000, which
// is the main branch in downtown Bedrock.
setBranchNumber(1000);
}
return branchNumber;
}
A inicialização lenta é comumente usada para campos que, na verdade, são outros objetos armazenados no banco de dados. Por exemplo, quando você cria um novo item de estoque, não precisa extrair nenhum tipo de item de estoque do banco de dados que tenha sido definido como padrão. Em vez disso, use a inicialização lenta para definir esse valor na primeira vez em que ele é acessado, de forma que você só leia o objeto de tipo de item de estoque proveniente do banco de dados quando e se for necessário.
Essa abordagem é vantajosa para objetos com campos que não são regularmente acessados. Por que incorrer na sobrecarga de recuperar no armazenamento persistente algo que não será utilizado?
Sempre que a inicialização lenta for usada em uma função de membro getter, documente o motivo do valor padrão, como demonstrado no exemplo acima. Ao fazer isso, você acaba com o mistério sobre como os campos são usados no código, o que melhora tanto a manutenibilidade quanto a extensibilidade.
4.4.3.2 Acessos para constantes
O idioma Java comum é implementar valores constantes como campos finais estáticos. Essa abordagem é ideal para as "constantes", que devem permanecer estáveis. Por exemplo, a classe Boolean implementa dois campos finais estáticos denominados TRUE e FALSE, que representam duas instâncias dessa classe. Também faz sentido para uma constante como DAYS_IN_A_WEEK cujo valor provavelmente nunca será alterado.
Entretanto, muitas "constantes" denominadas constantes do negócio mudam com o passar do tempo, devido a mudanças nas regras de negócios. Considere o seguinte exemplo: o Archon Bank of Cardassia (ABC) sempre insiste para que uma conta tenha um saldo mínimo de R$ 500,00 para que possa obter lucro. Para essa implementação, podemos adicionar um campo estático denominado MINIMUM_BALANCE à classe Account que será usada nas funções de membro para calcular os juros. Embora possa funcionar, não é flexível. O que ocorre se as regras de negócios forem alteradas e vários tipos de conta tiverem diferentes saldos mínimos, talvez R$ 500,00 na poupança e somente R$ 200,00 na conta corrente? O que ocorre se as regras de negócios precisarem ser alteradas para um saldo mínimo de R$ 500,00 no primeiro ano, R$ 400,00 no segundo, R$ 300,00 no terceiro e assim por diante? Talvez a regra tenha de passar para R$ 500,00 no verão e somente R$ 250,00 no inverno? Pode ser que uma combinação de todas essas regras tenha de ser implementada no futuro.
A questão é que a implementação de constantes como campos não é flexível. Uma solução bem melhor é implementar constantes como funções de membros getter. No exemplo acima, uma função de membro estática (classe) getMinimumBalance() é muito mais flexível do que um campo estático denominado MINIMUM_BALANCE, porque podemos implementar as várias regras de negócios nessa função de membro e na subclasse corretamente para os vários tipos de conta.
/** Get the value of the account number. Account numbers are in the followingformat: BBBBAAAAAA, where BBBB is the branch number andAAAAAA is the branch account number.*/public long getAccountNumber(){return ( ( getBranchNumber() * 100000 ) + getBranchAccountNumber() );}/**Set the account number. Account numbers are in the followingformat: BBBBAAAAAA where BBBB is the branch number andAAAAAA is the branch account number.*/public void setAccountNumber(int newNumber){setBranchAccountNumber( newNumber % 1000000 );setBranchNumber( newNumber / 1000000 );}
Outra vantagem de getters constantes é que eles ajudam a aumentar a consistência do código. Considere o código acima; ele não funciona corretamente. Um número de conta é a concatenação do número da agência e do número de conta da agência. Ao testar o código, descobrimos que a função de membro setter setAccountNumber() não atualiza os números de conta de agência corretamente. Ela usa os três dígitos à esquerda, e não quatro. Isso ocorre porque usamos 1.000.000 em vez de 100.000 para extrair o campo branchAccountNumber. Se tivéssemos usado uma única fonte para esse valor (o getter constante getAccountNumberDivisor(), como veremos a seguir), o código seria mais consistente e teria funcionado.
/**Returns the divisor needed to separate the branch account number from thebranch number within the full account number.Full account numbers are in the format BBBBAAAAAA.*/public int getAccountNumberDivisor(){return ( (long) 1000000);}/**Get the value of the account number. Account numbers are in the followingformat: BBBBAAAAAA, where BBBB is the branch number andAAAAAA is the branch account number.*/public long getAccountNumber(){return ( ( getBranchNumber() * getAccountNumberDivisor() ) + getBranchAccountNumber() );}/**Set the account number. Account numbers are in the followingformat: BBBBAAAAAA where BBBB is the branch number andAAAAAA is the branch account number.*/public void setAccountNumber(int newNumber){setBranchAccountNumber( newNumber % getAccountNumberDivisor() );setBranchNumber( newNumber / getAccountNumberDivisor() );}Usando acessos para as constantes, diminuímos a probabilidade de erros e, ao mesmo tempo, aumentamos a manutenibilidade do sistema. Quando o layout de um número de conta é alterado (e sabemos que em algum momento ele será alterado), talvez seja mais fácil alterar o código, porque ocultamos e centralizamos as informações necessárias para criar ou dividir números de conta.
4.4.3.3 Acessos para conjuntos
A principal finalidade dos acessos é encapsular o acesso aos campos a fim de reduzir o acoplamento no código. Conjuntos (como matrizes e vetores), por serem mais complexos do que campos com valores únicos, tendem naturalmente a ter outras implementações além das funções de membro padrão getter e setter. Como é possível adicionar e remover entre conjuntos, as funções de membro de acesso precisam ser incluídas para esse fim. Inclua as seguintes funções de membro de acesso onde for apropriado para um campo que é um conjunto:
Tipo de Função de Membro Convenção de nomeação Exemplo Getter para o conjunto getCollection() getOrderItems()Setter para o conjunto setCollection() setOrderItems()Inserir um objeto no conjunto insertObject() insertOrderItem()Excluir um objeto do conjunto deleteObject() deleteOrderItem()Criar e adicionar um novo objeto no conjunto newObject() newOrderItem()A vantagem dessa abordagem é que o conjunto fica totalmente encapsulado, permitindo que você o substitua posteriormente por uma outra estrutura, como uma lista vinculada ou uma árvore B.
4.4.3.4 Acesso simultâneo a diversos campos
Um dos pontos fortes das funções de membro de acesso é que eles permitem a imposição eficaz de regras de negócios. Considere, por exemplo, uma hierarquia de classes de formas. Cada subclasse de Forma conhece sua posição através do uso de dois campos -"xPosition e yPosition" - e pode ser movida na tela em um plano bidimensional, disparando a função de membro move(Float xMovement, Float yMovement). Para o que queremos, não faz sentido mover uma forma ao longo de um eixo por vez. Pelo contrário, a movimentação será feita ao longo do eixo x e y simultaneamente (é aceitável um valor de 0.0 para qualquer parâmetro da função de membro move()). Isso faz com que a função de membro move() tenha de ser pública, mas as funções de membro setXPosition() e setYPosition() devem ser privadas, sendo disparadas pela função de membro move() adequada.
Uma implementação alternativa seria introduzir uma função de membro setter que atualize os campos de uma só vez, como mostrado a seguir. As funções de membro setXPosition() e setYPosition() devem permanecer privadas para que não sejam disparadas diretamente pelas classes ou subclasses externas (veja a seguir como é importante indicar em alguma documentação que elas não devem ser disparadas diretamente).
/** Set the position of the shape */protected void setPosition(Float x, Float y){setXPosition(x);setYPosition(y);}/** Set the x position. Important: Invoke setPosition(), not this member function. */private void setXPosition(Float x){xPosition = x;}/** Set the y position of the shapeImportant: Invoke setPosition(), not this member function.*/private void setYPosition(Float y){yPosition = y;}
Sempre procure manter os acessos protegidos, para que somente as subclasses possam acessar os campos. Só quando uma "classe externa" precisar de acesso a um campo é que o respectivo getter ou setter deverá ser público. É comum a função de membro getter ser pública e a setter, protegida.
Há momentos em que você precisa de setters privados para garantir certas invariantes. Por exemplo, uma classe Pedido pode ter um campo que represente um conjunto de instâncias de OrderItem e um segundo campo denominado orderTotal, que corresponde ao valor total do pedido. O campo orderTotal é a soma ou todos os subtotais dos itens pedidos. As únicas funções de membro que devem atualizar o valor de orderTotal são as que manipulam o conjunto de itens pedidos. Pressupondo que essas funções de membro sejam todas implementadas em Pedido, setOrderTotal() deve ser privada, mesmo que seja mais do que provável que getOrderTotal() seja pública.
Os campos estáticos, também conhecidos como campos de classe, devem ter valores válidos, pois você não pode supor que as instâncias de uma classe serão criadas antes que um campo estático seja acessado.
Uma variável local é um objeto ou item de dados definidos no escopo de um bloco, geralmente uma função de membro. O escopo de uma variável local é o bloco no qual ela está definida. Os padrões importantes de codificação para as variáveis locais enfatizam:
- Convenções de nomeação
- Declarações e convenções de documentação
Em geral, as variáveis locais são nomeadas de acordo com as mesmas convenções usadas para os campos. Em outras palavras, usam descritores lingüísticos completos com a primeira letra de qualquer palavra não-inicial em maiúscula.
Contudo, por questões de conveniência, essa convenção de nomeação não é tão rígida para alguns tipos específicos de variáveis locais:
- Fluxos
- Contadores de ciclo
- Objetos de exceção
Quando há um único fluxo de entrada e/ou saída sendo aberto, usado e fechado em uma função de membro, a convenção comum é usar in e out para nomear esses fluxos, respectivamente [GOS96]. No caso de um fluxo usado tanto para entrada como para saída, deve ser usado o nome inOut.
Uma alternativa comum para essa convenção de nomeação, embora entre em conflito com as recomendações da Sun, é usar os nomes inputStream, outputStream e ioStream, em vez de in, out e inOut, respectivamente.
Como os contadores de ciclo costumam ser usados para variáveis locais e por serem aceitos em C/C++, é aceitável o uso de i, j ou k para contadores de ciclo na programação Java [GOS96]. Se você usar nomes para os contadores de ciclo, use-os de forma consistente.
Uma alternativa comum é usar nomes como loopCounter ou simplesmente counter. No entanto, o problema dessa abordagem é que nomes como counter1 e counter2 costumam ser encontrados em funções de membro que requerem mais de um contador. O importante é que i, j, k funcionam como contadores. São rápidos de digitar e, em geral, são aceitos.
Como o tratamento de exceções também é muito comum na codificação Java, o uso da letra e para uma exceção genérica é considerado aceitável [GOS96].
Há várias convenções para a declaração e a documentação de variáveis locais em Java. As convenções são:
- Declarar uma variável local por linha de código. Isso está consistente com uma instrução por linha de código e possibilita documentar cada variável com um comentário in-line.
- Documentar variáveis locais com um comentário in-line. O uso de comentários in-line é um estilo no qual um comentário em uma única linha, denotado por //, vem logo após um comando na mesma linha de código (procedimento este denominado comentário de fim de linha). Documente para que, onde e por que uma variável local deve ser usada. Assim será mais fácil entender o código.
- Usar variáveis locais para um único fim. Sempre que você usa uma variável local por mais de um motivo, você efetivamente diminui a coesão e dificulta a compreensão. Isso também aumenta a probabilidade de introduzir erros no código causados por efeitos colaterais inesperados de valores anteriores de uma variável local no início do código. Com certeza, a reutilização de variáveis locais é mais eficiente, pois é necessário alocar menos memória. No entanto, a reutilização de variáveis locais diminui a manutenibilidade do código e o fragiliza. Geralmente, não vale a pena arriscar apenas por não precisar alocar mais memória.
As variáveis locais declaradas entre as linhas de código (por exemplo, no escopo de uma instrução if) podem ser difíceis de serem localizadas pelas pessoas que não estão familiarizadas com o código.
Uma alternativa é declarar variáveis locais imediatamente antes de serem usadas pela primeira vez, em vez de declará-las no início do código. Como as funções de membro devem ser mesmo curtas, consulte a seção 3.5.5 - Criar linhas de comandos simples e curtas. Voltar ao início do código para determinar a finalidade da variável local não é um procedimento de todo mau.
Os padrões importantes para parâmetros e argumentos para funções de membro se concentram em como eles são nomeados e documentados. O termo parâmetro é usado para referir-se a um argumento da função de membro.
Os parâmetros devem ser nomeados de acordo com as mesmas convenções usadas para as variáveis locais. Assim como com as variáveis locais, existe o problema do ocultamento de nomes.
Exemplos:
customer
inventoryItem
photonTorpedo
in
e
Uma alternativa viável da Smalltalk é usar as convenções de nomeação para variáveis locais com "a" ou "an" na frente do nome. O acréscimo de um "a" ou "an" ajuda a destacar o parâmetro em relação a variáveis locais e campos, além de evitar o problema de ocultamento de nomes. Essa é a abordagem mais aconselhável.
Exemplos:
aCustomer
anInventoryItem
aPhotonTorpedo
anInputStream
anException
Os parâmetros de uma função de membro são documentados no cabeçalho da função de membro usando a marca @param do javadoc. Descreva o seguinte:
- Para que ele deve ser usado. Documente o objetivo de usar o parâmetro para que os outros desenvolvedores entendam o contexto total de utilização do parâmetro.
- Restrições ou precondições. Se um intervalo de valores referentes a um parâmetro não for aceito para uma função de membro, o disparador dessa função de membro deverá ser informado. Talvez uma função de membro aceite apenas números positivos ou seqüências com menos de cinco caracteres.
- Exemplos: Se não estiver completamente óbvio o que um parâmetro deve ser, forneça um ou mais exemplos na documentação.
Em vez de especificar uma classe (como Objeto) para um tipo de parâmetro, especifique uma interface (como Runnable), se apropriado. A vantagem é que essa abordagem, dependendo da situação, pode ser mais específica (Runnable é mais específico do que Objeto) ou pode vir a ser uma melhor maneira de suportar polimorfismo. Em vez de insistir em manter um parâmetro como instância de uma classe em determinada hierarquia, especifique que ele suportará uma interface específica, sugerindo que ele só precisa ser polimorficamente compatível com o que você deseja.
Este capítulo enfoca padrões e diretrizes para classes, interfaces, pacotes e unidades de compilação. Uma classe é um template a partir do qual objetos são instanciados (criados). As classes contêm a declaração de campos e funções de membro. As interfaces são a definição de uma assinatura comum, incluindo funções de membro e campos, que uma classe que implementa uma interface deve suportar. Um pacote é um conjunto de classes relacionadas. Por fim, uma unidade de compilação é um arquivo de código-fonte no qual as classes e interfaces são declaradas. Como Java permite o armazenamento de unidades de compilação em um banco de dados, uma unidade de compilação individual pode não estar diretamente relacionada a um arquivo de código-fonte físico.
Os padrões que são importantes para classes baseiam-se em:
- convenções de nomeação
- convenções de documentação
- convenções de declaração
- a interface pública e protegida
A convenção Java padrão usa um descritor lingüístico completo, com a primeira letra em maiúscula e uma combinação de maiúsculas e minúsculas no resto do nome. ([GOS96] e [AMB98])
Os nomes de classe devem estar no singular.
Exemplos:
Customer
Employee
Order
OrderItem
FileStream
String
As seguintes informações devem ser exibidas nos comentários de documentação imediatamente antes da definição de uma classe:
- A finalidade da classe. Os desenvolvedores precisam saber qual é o objetivo geral de uma classe para que possam determinar se ela atende às suas necessidades. Crie o hábito de documentar todos os aspectos favoráveis sobre uma classe, como, por exemplo, se ela faz parte de um padrão ou se há alguma limitação interessante a respeito de sua utilização [AMB98]?
- Erros conhecidos. Se houver problemas pendentes com uma classe, eles devem ser documentados para que outros desenvolvedores entendam os pontos fracos e as dificuldades relacionados a essa classe. Além disso, o motivo para não corrigir o erro também precisa ser documentado. Lembre-se de que um erro é específico para uma única função de membro. Portanto, ele deve ser associado diretamente à função de membro.
- O histórico de desenvolvimento ou manutenção da classe. É bastante comum a inclusão de uma tabela de histórico com datas, autores e sumários das mudanças efetuadas em uma classe. Dessa forma, os programadores de manutenção têm idéia das modificações sofridas por uma classe, além de saber quem fez o quê na classe.
- Documentar invariantes aplicáveis. Uma invariante é um conjunto de afirmativas sobre uma instância ou classe que devem ser verdadeiras em todos os momentos de "estabilidade". Nesse contexto, momento de estabilidade é definido como o período anterior ao disparo de uma função de membro no objeto ou classe ou o período imediatamente posterior ao disparo de uma função de membro [MEY88]. A documentação de invariantes de uma classe fornece aos outros desenvolvedores informações importantes sobre como uma classe pode ser usada.
- A estratégia de simultaneidade. Qualquer classe que implemente a interface Runnable deve ter sua estratégia de simultaneidade totalmente descrita. A programação simultânea é um tópico complexo e novo para muitos programadores. Por isso, invista um pouco mais de tempo a fim de garantir que as pessoas compreendam o seu trabalho. É importante documentar a estratégia de simultaneidade e o motivo pelo qual essa estratégia foi escolhida. As estratégias comuns de simultaneidade [LEA97] incluem:
- objetos sincronizados
- objetos de bloqueio
- objetos guardados
- objetos versionados
- controladores de política de simultaneidade
- aceitantes
Uma maneira de facilitar a compreensão das classes é declará-las de maneira consistente. A abordagem Java comum é declarar uma classe nesta ordem:
- funções de membro públicas
- campos públicos
- funções de membro protegidas
- campos protegidos
- funções de membro privadas
- campos privados
[LAF97] destaca que os construtores e finalize() devem ser listados primeiro, talvez porque sejam as primeiras funções de membro a serem verificadas por outro desenvolvedor que queira saber como a classe é usada. Além disso, como o padrão é declarar campos como privados, a ordem de declaração realmente se resume a:
construtores
finalize()funções de membro públicas
funções de membro protegidas
funções de membro privadas
campos privados
Em cada agrupamento de funções de membro, é comum listá-los em ordem alfabética. Muitos desenvolvedores preferem listar as funções de membro estáticas em cada agrupamento primeiro, depois as funções de membro de instância e, em seguida, em cada um desses dois subagrupamentos, listar as funções de membro em ordem alfabética. Essas duas abordagens são válidas. Basta você escolher uma e adotá-la.
Um dos fundamentos em um design orientado a objetos é minimizar a interface pública de uma classe. Há vários motivos para isso:
- Facilidade de aprendizado. Para saber como usar uma classe, você só precisa conhecer sua interface pública. Quanto menor for a interface pública, mais fácil será conhecer a classe.
- Acoplamento reduzido. Sempre que a instância de uma classe envia uma mensagem para uma instância de outra classe ou diretamente para a própria classe, as duas classes ficam acopladas. Minimizar a interface pública implica minimizar as oportunidades de acoplamento.
- Maior flexibilidade. Está diretamente relacionada a acoplamento. Sempre que você quiser mudar a maneira como uma função de membro é implementada na interface pública (talvez você queira modificar o que é retornado pela função de membro), provavelmente terá de modificar o código que dispara a função de membro. Quanto menor for a interface pública, maior será o encapsulamento e, portanto, maior a flexibilidade.
Está claro que vale a pena minimizar a interface pública. O que não está tão claro é a necessidade de também minimizar a interface protegida. A idéia básica é que, do ponto de vista de uma subclasse, as interfaces protegidas de todas as superclasses são efetivamente públicas. Qualquer função de membro na interface protegida pode ser disparada por uma subclasse. Portanto, você deve minimizar a interface protegida de uma classe pelos mesmos motivos que minimiza a interface pública.
7.1.4.1 Definir primeiro a interface pública
![]()
Os desenvolvedores mais experientes definem a interface pública de uma classe antes de começarem a codificá-la.
- Primeiro, se você não sabe quais serviços ou comportamentos serão executados por uma classe, terá algum trabalho de design a realizar.
- Depois, você poderá substituir a classe rapidamente para que os outros desenvolvedores que a utilizam possam pelo menos trabalhar com o stub até a classe "real" ser desenvolvida.
- Em terceiro lugar, essa abordagem oferece um framework inicial para a criação da classe.
Os padrões que são importantes para as interfaces baseiam-se em:
- Convenções de nomeação
- Convenções de documentação
A convenção Java é nomear as interfaces com letras maiúsculas e minúsculas combinadas, sendo que a primeira letra de cada palavra deve ser maiúscula. A convenção Java mais aconselhável para o nome de uma interface é usar um adjetivo descritivo (como Runnable ou Cloneable), embora substantivos descritivos (como Singleton ou DataInput) também sejam comuns [GOS96].
7.2.1.1 Alternativa
![]()
Use a letra "I" como prefixo para o nome da interface. Como [COA97] sugere, acrescentar a letra "I" ao início de um nome de interface resulta em nomes como ISingleton ou IRunnable. Essa abordagem ajuda a fazer a diferenciação entre nomes de interface e nomes de classe e de pacote. Essa possível convenção de nomeação é preferível pelo simples fato de facilitar a leitura dos diagramas de classes, algumas vezes denominados modelos de objeto. A principal desvantagem é que as interfaces existentes, como Runnable, não são nomeadas de acordo com essa abordagem. Essa convenção de nomeação de interface também é popular na arquitetura COM/DCOM da Microsoft.
As seguintes informações devem ser exibidas nos comentários de documentação imediatamente antes da definição de uma interface:
- Informar o objetivo. Antes de outros desenvolvedores usarem uma interface, eles precisam saber qual é o conceito que ela encapsula. Em outras palavras, eles precisam saber qual é o seu objetivo. Um teste muito bom para saber se é preciso definir uma interface é identificar o grau de facilidade ou dificuldade com que você descreve sua finalidade. Se você tiver dificuldades para descrevê-la, provavelmente nem precisa da interface. Como o conceito de interfaces é novo na linguagem Java, as pessoas ainda não têm experiência com o seu uso adequado e estão propensas a utilizarem as interfaces em excesso.
- Como as interfaces devem e não devem ser usadas. Os desenvolvedores precisam saber como uma interface deve ser usada e como ela não deve ser usada [COA97].
Como a assinatura das funções de membro é definida em uma interface, é necessário seguir as convenções de documentação apropriadas abordadas em Capítulo 3 para cada assinatura de função de membro.
Os padrões que são importantes para os pacotes baseiam-se em:
- convenções de nomeação
- convenções de documentação
Há várias regras associadas à nomeação de pacotes. Por ordem, as regras são:
- Identificadores são separados por pontos. Para garantir a legibilidade dos nomes de pacote, a Sun sugere que os identificadores dos nomes de pacote sejam separados por pontos. Por exemplo, o nome de pacote java.awt é formado por dois identificadores: java e awt.
- Os pacotes padrão de distribuição Java da Sun começam com o identificador "java". A Sun reservou-se esse direito para que os pacotes padrão Java sejam nomeados com consistência, seja qual for o fornecedor do ambiente de desenvolvimento Java.
- Os nomes de pacotes locais começam com um identificador cujas letras não são todas maiúsculas. Os pacotes locais são usados internamente na organização e não serão distribuídos para outras organizações. Alguns exemplos desses nomes de pacote são persistence.mapping.relational e interface.screens.
- Os nomes de pacotes globais começam com o nome de domínio da Internet para sua organização invertido. Um pacote que será distribuído para várias organizações deve incluir o nome de domínio da organização original, com o tipo de domínio de nível superior em letras maiúsculas. Por exemplo, para distribuir os pacotes anteriores, eles devem ser nomeados como com.rational.www.persistence.mapping.relational e com.rational.www.interface.screens.0
Você deve manter um ou mais documentos externos que descrevam a finalidade dos pacotes desenvolvidos pela sua organização. Para cada pacote, documente:
- Os fundamentos do pacote. Outros desenvolvedores precisam saber sobre o que se refere o pacote para que possam determinar se irão usá-lo e, caso ele seja um pacote compartilhado, se pretendem melhorá-lo ou ampliá-lo.
- As classes contidas no pacote. Inclua uma lista das classes e interfaces contidas no pacote com uma rápida descrição de uma linha para cada item, para que os outros desenvolvedores saibam o que o pacote contém.
Dica: Crie um arquivo HTML usando o nome do pacote e insira-o no diretório do pacote. O arquivo deve ter extensão .html.
Os padrões e as diretrizes para as unidades de compilação baseiam-se em:
- Convenções de nomeação
- Convenções de documentação
Uma unidade de compilação, nesse caso um arquivo de código-fonte, deve receber o nome da classe ou da interface principal em que está declarada. Use para o pacote ou para a classe o mesmo nome do arquivo, com a mesma aplicação de letras maiúsculas e minúsculas. O nome do arquivo deve ter extensão .java.
Exemplos:
Customer.java
Singleton.java
SavingsAccount.java
Embora você deva se esforçar para ter somente uma declaração de classe ou de interface por arquivo, há momentos em que faz mais sentido definir várias classes (ou interfaces) no mesmo arquivo. Se a única finalidade da classe B é encapsular a funcionalidade necessária apenas à classe A, a classe B deve ser exibida no mesmo arquivo de código-fonte da classe A. Como conseqüência, as convenções de documentação a seguir se aplicarão a um arquivo de código-fonte, e não especificamente a uma classe:
- Para arquivos com várias classes, liste cada classe. Se um arquivo contiver mais de uma classe, forneça uma lista das classes e uma breve descrição de cada uma.
- O nome do arquivo e/ou informações de identificação. O nome do arquivo deve ser incluído na parte superior. A vantagem é que, se o código for impresso, você saberá qual é o arquivo-fonte para o código.
- Informações de direitos autorais. Se aplicável, indique informações de direitos autorais para o arquivo. É comum indicar o ano do direito autoral e o nome da pessoa ou da organização que detém esse direito. Lembre-se de que o autor do código pode não ser o detentor do direito autoral.
Como filosofia geral, use exceções apenas para erros: erros de lógica e de programação, erros de configuração, dados corrompidos, esgotamento de recursos e assim por diante. Como regra geral, em condições normais e na ausência de sobrecarga ou falha de hardware, os sistemas não devem provocar exceções.
- Use exceções para tratar erros de lógica e de programação, erros de configuração, dados corrompidos e esgotamento de recursos.
Relate as exceções usando o mecanismo de registro adequado assim que possível e inclua o momento em que ocorreram.
- Diminua o número de exceções exportadas de uma determinada abstração.
Em sistemas de grande porte, o tratamento de um volume grande de exceções em cada nível dificulta a leitura e a manutenção do código. Às vezes, o processamento de exceções inibe o processamento normal.
Há várias maneiras de diminuir o número de exceções:
- Exporte apenas algumas exceções, mas ofereça "diagnósticos" primários que permitam consultar a abstração com falhas ou o objeto inválido para obter informações mais detalhadas sobre a natureza do problema ocorrido.
- Adicione estados "excepcionais" aos objetos e forneça informações primárias para verificar explicitamente a validade dos objetos.
- Não use exceções para eventos freqüentes e previsíveis.
Há várias inconveniências no uso de exceções para representar condições que não são claramente erros:
- É confuso.
- Geralmente provoca alguma ruptura no fluxo de controle, que fica mais difícil de ser entendido e mantido.
- Dificulta a depuração do código, pois a maioria dos depuradores em nível de fonte sinalizam todas as exceções, por padrão.
Por exemplo, não use uma exceção como forma de valor extra retornado por uma função (como Value_Not_Found em uma pesquisa). Use um procedimento com um parâmetro "externo", introduza um valor especial que corresponda a Not_Found ou inclua o tipo retornado em um registro com um discriminante Not_Found.
- Não use exceções para implementar estruturas de controle.
Este é um caso especial da regra anterior: as exceções não devem ser usadas como forma de instrução "goto".
- Certifique-se de que os códigos de status tenham valores apropriados.
Ao usar código de status retornado por subprogramas como um parâmetro "externo", verifique se há um valor atribuído a esse parâmetro fazendo com que essa seja a primeira instrução executável no corpo do subprograma. Sistematicamente, coloque todos os status como sucesso ou como fracasso, por padrão. Pense em todas as possíveis saídas do subprograma, inclusive os controladores de exceção.
- Realize verificações de segurança localmente. Não espere que o cliente o faça.
Se houver a possibilidade de um subprograma produzir resultados errados caso não seja informada determinada entrada, instale o código no subprograma para detectar e relatar entradas inválidas de maneira controlada. Não se baseie em comentários que solicitam que o cliente informe os valores apropriados. É praticamente garantido que mais cedo ou mais tarde o comentário será ignorado, resultando em erros de difícil depuração caso os parâmetros inválidos não sejam detectados.
Este capítulo descreve diversos padrões e diretrizes importantes que são gerais o suficiente para merecerem um capítulo próprio.
Se você adquirir ou reutilizar qualquer pacote ou biblioteca de classes Java de uma fonte externa, certifique-se de que seja 100% Java [SUN97]. Com a imposição desse padrão, você terá certeza de que o que está sendo reutilizado funcionará em todas as plataformas nas quais queira implantá-lo. É possível obter classes, pacotes ou applets Java de várias fontes, seja uma empresa de desenvolvimento de terceiros especializada em bibliotecas Java ou outro departamento ou equipe de projeto da organização.
A instrução de importação permite o uso de curingas para indicar os nomes das classes. Por exemplo, a instrução
import java.awt.*;aciona todas as classes do pacote java.awt de uma vez. Na realidade, isso não é completamente verdadeiro. O que realmente ocorre é que todas as classes do pacote java.awt serão inseridas no seu código quando ele for compilado. As classes não utilizadas não serão inseridas. Embora pareça ser uma boa característica, ela diminui a legibilidade do código. Uma melhor abordagem é qualificar integralmente o nome das classes usadas pelo código [LAF97]; [VIS96]. O exemplo abaixo mostra uma melhor maneira de importar classes:
import java.awt.Colorimport java.awt.Buttonimport java.awt.Container
A otimização do código é uma das últimas tarefas a serem consideradas pelos programadores, e não a primeira. Deixando a otimização para o fim, você otimizará apenas o código que precisar ser otimizado. Com freqüência, uma pequena porcentagem do código resulta em grande parte do tempo de processamento e esse é o código que você deverá otimizar. Um erro clássico cometido por programadores inexperientes é tentar otimizar todo o código, mesmo aquele que já é executado com bastante rapidez.
- Não perca tempo otimizando códigos que não são importantes para ninguém!
Qual o objetivo da otimização do código? Como [KOE97] destaca, os fatores mais importantes são carga fixa e desempenho em grandes entradas. O motivo disso é simples: a carga fixa domina a velocidade do tempo de execução para pequenas entradas, enquanto o algoritmo domina para grandes entradas. Segundo Koenig, um programa que funcione bem com pequenas e grandes entradas provavelmente funcionará bem com entradas de médio porte.
Os desenvolvedores que precisam criar software que funcione em várias plataformas de hardware e/ou sistemas operacionais devem estar cientes das idiossincrasias existentes em várias plataformas. Operações que pareçam muito demoradas (como, por exemplo, a maneira como a memória e os buffers são manipulados) geralmente demonstram variações substanciais entre plataformas. É comum concluir que o código deve ser otimizado diferentemente para cada plataforma.
Outra questão a ser considerada durante a otimização do código é a prioridade dos usuários, porque as pessoas se incomodarão com certas demoras, dependendo do contexto. Por exemplo, os usuários provavelmente ficarão mais felizes com uma tela que apareça e, após oito segundos, carregue dados, em vez de uma tela que só apareça após cinco segundos para carregar os dados. Em outras palavras, a maioria dos usuários se dispõe a esperar um pouco mais, desde que eles obtenham algum feedback imediato essa é uma informação importante na hora de otimizar o código.
- Nem sempre você precisa fazer com que o código seja executado com mais rapidez para otimizá-lo na presença dos usuários.
Embora a otimização possa significar uma diferença entre o sucesso e o fracasso do aplicativo, nunca se esqueça de que é bem mais importante que o código funcione corretamente. Lembre-se de que um software lento mas que funcione é sempre preferível a um software rápido que não funcione.
Teste orientado a objetos é um tópico importante, que tem sido totalmente ignorado pelos responsáveis pelo desenvolvimento de objetos. A realidade é que você ou outra pessoa terá de testar o software criado, seja qual for a linguagem escolhida. Um equipamento de teste é o conjunto de funções de membro, algumas incorporadas nas próprias classes (denominadas testes internos) e outras em classes de teste especializadas usadas para testar o aplicativo.
- Prefixe todos os nomes de funções de membro de teste com o termo "test". Dessa forma, você poderá encontrar rapidamente todas as funções de membro de teste no código. A vantagem dessa prefixação é facilitar a separação entre as funções de membro de teste e o código-fonte antes de compilar sua versão de produção.
- Nomeie todas as funções de membro de teste de função de membro de forma consistente. O teste de método consiste em verificar se uma única função de membro é executada como definido. Todas as funções de membro do teste de função de membro devem ser nomeadas de acordo com o formato "testMemberFunctionNameForTestName". Por exemplo, as funções de membro do equipamento de teste para experimentar withdrawFunds() devem incluir testWithdrawFundsForInsufficientFunds() e testWithdrawFundsForSmallWithdrawal(). Se você tem uma série de testes para withdrawFunds(), pode preferir criar uma função de membro denominada testWithdrawFunds(), que dispare todos eles.
- Nomeie todas as funções de membro de teste de classe de forma consistente. O teste de classe consiste em verificar se uma única classe é executada como definido. Todas as funções de membro de teste de classe devem ser nomeadas de acordo com o formato "testSelfForTestName". Por exemplo, as funções de membro de equipamento de teste para experimentar a classe Conta testSelfForSimultaneousAccess() e testSelfForReporting().
- Crie um único ponto para disparar os testes de uma classe. Desenvolva uma função de membro estática denominada testSelf() que dispare todas as funções de membro de teste de método e de classe.
- Documente as funções de membro do equipamento de teste. Documente as funções de membro do equipamento de teste. A documentação deve incluir uma descrição do teste, assim como os resultados que se espera obter com ele.
Ter um documento de padrões não faz com que você seja automaticamente mais produtivo como desenvolvedor. Para ser bem-sucedido, você deve optar por ser mais produtivo, o que significa que esses padrões devem ser aplicados efetivamente.
As dicas a seguir o ajudarão a usar os padrões e as diretrizes de codificação Java descritos neste documento com mais eficiência.
- Conheça os padrões. Dedique algum tempo a entender por que cada padrão e diretriz aumenta a produtividade. Por exemplo, não declare cada variável local em sua própria linha apenas porque as diretrizes assim indicam. Faça-o por ter entendido que esse procedimento aumenta a compreensão do código.
- Acredite neles. Conhecer cada padrão é um início, mas você também precisa acreditar neles. Seguir os padrões não deve ser algo que você faz quando tem tempo, mas sim porque acredita ser essa a melhor maneira de desenvolver códigos.
- Siga esses padrões durante a codificação, e não como pós-reflexão. O código documentado é mais fácil de ser entendido enquanto está sendo escrito e depois de ter sido escrito. Campos e funções de membro nomeados com consistência facilitam o trabalho de desenvolvimento e manutenção. Códigos legíveis também facilitam o trabalho de desenvolvimento e manutenção. O importante é entender que, ao seguir os padrões, você aumenta a produtividade das tarefas de desenvolvimento e facilita a manutenção posterior do código (aumentando, portanto, a produtividade dos desenvolvedores de manutenção). Se você escrever um código legível desde o início, irá se beneficiar disso enquanto estiver escrevendo-o.
- Inclua esses padrões em seu processo de avaliação de qualidade. Parte de uma inspeção de código deve ser garantir que o código-fonte siga os padrões adotados pela sua organização. Use-os como base a partir da qual você treina e orienta os desenvolvedores para serem mais eficientes.
- Programe para pessoas, não para máquinas. O principal objetivo dos esforços de desenvolvimento deve ser criar um código fácil para que outras pessoas o entendam. Se ninguém é capaz de compreendê-lo, ele não é bom. Use convenções de nomeação. Documente o código. Crie parágrafos.
- Primeiro o design, depois o código. Você já se deparou com uma situação na qual parte do código em que o programa se baseia precisa ser alterado? Talvez um novo parâmetro precise ser passado para uma função de membro ou uma classe precise ser dividida em outras classes. Quanto trabalho adicional foi necessário para garantir que o código funcionasse com a versão reconfigurada do código modificado? Qual foi o seu grau de satisfação? Você se perguntou se alguém parou e pensou sobre isso antes, ao escrever o código, para que isso não viesse a acontecer, ou se o DESIGN deveria ter sido criado em primeiro lugar? Claro que sim. Se você parar para pensar em como irá escrever o código antes de realmente começar a fazê-lo, provavelmente levará menos tempo para fazê-lo. Além do mais, possivelmente você reduzirá o impacto de futuras mudanças sobre o código por pensar sobre elas logo de início.
- Desenvolvimento em pequenos passos. O desenvolvimento em pequenos passos criar algumas funções de membro, testá-las e criar outras funções de membro é bem mais eficaz do que escrever uma grande quantidade de código de uma só vez e depois tentar corrigi-lo. É mais fácil testar e corrigir dez linhas de código do que cem. Na verdade, pode-se afirmar que é possível programar, testar e corrigir cem linhas de código em incrementos de dez linhas em menos da metade do tempo do que seria necessário para escrever um único bloco de código de cem linhas com o mesmo tipo de trabalho.
O motivo disso é simples. Quando você testa o código e encontra um erro, quase sempre esse erro está no código novo que acabou de ser escrito (pressupondo, obviamente, que o resto do código esteja bem sólido). Erros podem ser detectados muito mais rapidamente em uma pequena seção de código do que em uma grande. O desenvolvimento gradual em pequenos passos reduz o tempo médio de detecção de erros, o que, por sua vez, diminui o tempo geral de desenvolvimento.
- O código deve ser simples. A escrita de códigos complexos pode ser intelectualmente satisfatória, mas, se outras pessoas não puderem entendê-lo, ele não é bom. A primeira vez que alguém, até mesmo você, for solicitado a modificar um trecho de um código complexo para corrigir um erro ou melhorar o código, é provável que o código tenha que ser reescrito. Na verdade, é provável que você já tenha tido de reescrever o código de outros justamente devido à dificuldade de compreendê-lo. O que você pensou do desenvolvedor original ao reescrever o código? Achou que ele era um gênio ou um idiota? Escrever códigos que precisem ser refeitos posteriormente não é algo que nos cause orgulho. Portanto, tenha como regra manter a simplicidade.
- Aprenda padrões comuns, antipadrões e idiomas. Existe uma variedade de padrões e antipadrões de processos, design e análise (além de idiomas de programação) disponíveis para ajudá-lo a aumentar a produtividade de desenvolvimento. Consulte [AMB98] e [AMB99] para obter mais informações.
Este capítulo resume as diretrizes mencionadas aqui para sua conveniência. Ele está organizado em vários sumários de uma só página e contém os padrões de codificação Java, reunidos por tópico. Os tópicos são:
- Convenções de nomeação Java
- Convenções de documentação Java
- Convenções de codificação Java
Antes de resumir o resto dos padrões e diretrizes descritos neste documento, é importante reiterar a principal diretiva:
Se você não adotar determinado padrão, documente o motivo. Todos os padrões, exceto este, podem ser desobedecidos. Se agir assim, documente o motivo de sua atitude, as possíveis implicações e as condições que podem/devem ocorrer antes que o padrão possa ser aplicado a essa situação.
A não ser por algumas exceções abordadas a seguir, use sempre descritores lingüísticos completos para nomeações. Use letras minúsculas em geral, mas a primeira letra maiúscula para os nomes de classe e interface, assim como para a primeira letra de palavras não iniciais.
Conceitos Gerais:
- Use descritores lingüísticos completos.
- Use a terminologia aplicável ao domínio em questão.
- Combine letras maiúsculas e minúsculas para facilitar a leitura dos nomes.
- Não abuse das formas abreviadas; use-as de forma inteligente.
- Evite nomes longos (menos de 15 caracteres é o ideal).
- Evite nomes semelhantes ou cuja única diferença sejam as letras maiúsculas ou minúsculas.
- Evite sublinhados.
Item Convenção de Nomeação Exemplo Argumentos /
parâmetrosUse uma descrição completa do valor/objeto que está sendo passado, possivelmente prefixando o nome com "a" ou "an". O importante é escolher uma abordagem e ater-se a ela. customer, account, - ou - aCustomer, anAccountCampos /
campos /
propriedadesUse uma descrição completa do campo, com a primeira letra minúscula e a primeira letra de qualquer palavra não inicial em maiúscula. firstName, lastName, warpSpeedFunções de membro boolean getter Todos os boolean getters devem ser prefixados com a palavra "is". Se você seguir o padrão de nomeação para campos boolean descritos acima, basta usar o nome do campo. isPersistent(), isString(), isCharacter()Classes Use uma descrição completa, com as primeiras letras de todas palavras em maiúscula. Customer, SavingsAccountArquivos de unidades de compilação Use o nome da classe ou da interface. Se houver mais de uma classe no arquivo do que a classe principal, use a extensão ".java" para indicar que é um arquivo de código-fonte. Customer.java,SavingsAccount.java,Singleton.javaComponentes /
widgetsUse uma descrição completa que indique para que o componente é usado, com o tipo de componente concatenado no fim. okButton, customerList,fileMenuConstrutores Use o nome da classe. Customer(), SavingsAccount()Destruidores Java não possui destruidores, mas dispara a função de membro finalize() antes de um objeto ser coletado para a lixeira. finalize()Exceções O uso da letra "e" geralmente é aceito para representar exceções. eCampos finais estáticos (constantes) Use todas as letras maiúsculas nas palavras separadas por sublinhado. O melhor é usar funções de membro getter finais estáticas, pois aumentam bastante a flexibilidade. MIN_BALANCE, DEFAULT_DATEFunções de membro getter Use "get" como prefixo do nome do campo que está sendo acessado. getFirstName(), getLastName(),getWarpSpeeed()Interfaces Use uma descrição completa do conceito que a interface encapsula, com as primeiras letras de todas as palavras maiúsculas. É comum usar nomes que terminem em "able", "ible" ou "er", mas não é obrigatório. Runnable, Contactable,Prompter, SingletonVariáveis locais Use descrições completas, com a primeira letra minúscula, mas não oculte campos e campos existentes. Por exemplo, um nome denominado "firstName" não tem uma variável local denominada "firstName". grandTotal, customer,newAccountContadores de ciclo É geralmente aceito o uso das letras i, j ou k ou o nome counter. i, j, k, counterPacotes Use descrições completas, com letras maiúsculas e minúsculas combinadas, sendo que a primeira letra de cada palavra deve ser maiúscula e todo o resto deve estar em letras minúsculas. No caso de pacotes globais, inverta o nome do seu domínio na Internet e concatene o nome do pacote de acordo. java.awt,com.ambysoft.www.persistence.mappingFunções de Membro Use uma descrição completa do que a função de membro executa, começando com um verbo ativo quando possível e mantendo a primeira letra minúscula. openFile(), addAccount()Funções de membro setter Use "set" como prefixo para o nome do campo que está sendo acessado. setFirstName(), setLastName(),setWarpSpeed()
Uma regra prática muito boa sobre documentação é você se perguntar se já conhece o código e "que informações são necessárias para você realmente entender o código em um tempo razoável".
Conceitos Gerais:
- Comentários contribuem para a clareza do código.
- Se seu programa não merece ser documentado, provavelmente não merece ser executado.
- Evite enfeites, ou seja, não use comentários como faixas.
- Mantenha a simplicidade dos comentários.
- Crie a documentação antes de escrever o código.
- Documente não apenas o que está sendo feito, mas também por que está sendo feito.
A tabela a seguir descreve os três tipos de comentários Java e os usos sugeridos para cada um deles.
Tipo de Comentário Uso Exemplo Documentação Use os comentários de documentação imediatamente antes de declarações de interfaces, classes, funções de membro e campos para documentá-los. Esses comentários serão processados pelo javadoc. Veja a seguir como criar documentação externa para uma classe. /**
Cliente: é qualquer pessoa ou organização a quem vendemos serviços e produtos.
@author S.W. Ambler
*/Estilo C Use os comentários de estilo C para documentar linhas de código que não são mais aplicáveis, mas que você ainda quer manter caso os usuários mudem de idéia ou porque você talvez queira desativá-los temporariamente durante um processo de depuração. /*
O comentário desse código foi criado por B. Gustafsson, no dia 4 de junho de 1999, e foi substituído pelo código precedente. Exclua-o após dois anos caso ainda não seja aplicável.
. . . (o código-fonte)
*/Linha única Use comentários em uma única linha internamente nas funções de membro para documentar a lógica do negócio, as seções do código e as declarações de variáveis temporárias. // Aplicar desconto de 5% a todas as faturas
// acima de R$ 1.000,00 como definido pela
// campanha geral de descontos de Sarek iniciada em
// fevereiro de 1995.
A tabela a seguir resume o que deve ser documentado sobre cada trecho do código Java escrito.
Item O que Deve Ser Documentado Argumentos /
parâmetrosO tipo de parâmetro Para que deve ser usado
Restrições ou precondições
Exemplos
Campos /
campos / propriedadesDescrição Documente todas as invariantes aplicáveis
Exemplos
Questões de simultaneidade
Decisões de visibilidade
Classes A finalidade da classe Erros conhecidos
O histórico de desenvolvimento ou manutenção da classe
Documente as invariantes aplicáveis
A estratégia de simultaneidade
Unidades de compilação Cada classe ou interface definida na classe, com uma breve descrição O nome do arquivo e/ou informações de identificação
Informações de direitos autorais
Função de membro getter Documente o motivo de usar a inicialização lenta, se aplicável Interfaces A finalidade Como devem e não devem ser usadas
Variáveis locais Uso ou finalidade Funções de Membro: documentação O que a função de membro executa e por quê Qual função de membro deve ser passada como parâmetro
O que é retornado por uma função de membro
Erros conhecidos
Todas as exceções que uma função de membro aciona
Decisões de visibilidade
Como uma função de membro altera o objeto
Inclua um histórico das mudanças de código
Exemplos de como disparar a função de membro, se apropriado
Precondições e pós-condições aplicáveis
Funções de Membro: comentários internos Estruturas de controle O que o código executa e por quê
Variáveis locais
Código difícil ou complexo
A ordem de processamento
Pacote Os fundamentos do pacote As classes contidas no pacote
Muitas convenções e padrões são vitais para a manutenibilidade e melhoria do código Java. Em 99,9% das vezes, é mais importante desenvolver programas para pessoas, e não para máquinas. Garantir que o código seja compreendido por outras pessoas é o fator de maior importância.
Alvo da Convenção Convenção Funções de membro de acesso Considere a possibilidade de usar a inicialização lenta para campos no banco de dados Use os acessos para obter e modificar todos os campos
Use acessos para "constantes"
No caso de conjuntos, adicione funções de membro para inserir e remover itens
Sempre que possível, os acessos devem ser protegidos, e não públicos
Campos Os campos sempre devem ser declarados como privados Não acesse os campos diretamente; use funções de membro de acesso
Não use campos finais estáticos (constantes); use funções de membro de acesso
Não oculte nomes
Sempre inicialize campos estáticos
Classes Minimize as interfaces públicas e protegidas Defina a interface pública para uma classe antes de iniciar sua codificação
Declare os campos e as funções de membro de uma classe na seguinte ordem:
- construtores
- finalize()
- funções de membro públicas
- funções de membro protegidas
- funções de membro privadas
- campo privado
Variáveis locais Não oculte nomes Declare uma variável local por linha de código
Documente variáveis locais com um comentário in-line
Declare variáveis locais imediatamente antes de usá-las
Use variáveis locais para um único fim
Funções de Membro Documente o código Crie parágrafos no código
Use espaço em branco: uma linha antes das estruturas de controle e duas antes das declarações de funções de membro
Uma função de membro deve poder ser compreendida em menos de trinta segundos
Crie linhas de comandos simples e curtas
Restrinja ao máximo a visibilidade de uma função de membro
Especifique a ordem das operações
[AMB98] Ambler, S.W. (1998). Building Object Applications That Work: Your Step-By-Step Handbook for Developing Robust Systems with Object Technology. Nova York: SIGS Books/Cambridge University Press. [COA97] Coad, P. and Mayfield, M. (1997). Java Design: Building Better Apps & Applets. Upper Saddle River, NJ: Prentice Hall Inc. [DES97] DeSoto, A. (1997). Using the Beans Development Kit 1.0 February 1997: A Tutorial. Sun Microsystems. [GOS96] Gosling, J., Joy, B., Steele, G. (1996). The Java Language Specification. Reading, MA: Addison Wesley Longman Inc. [GRA97] Grand, M. (1997). Java Language Reference. Sebastopol, CA: O. Reilly & Associates, Inc. [KAN97] Kanerva, J. (1997). The Java FAQ. Reading, MA: Addison Wesley Longman Inc. [KOE97] Koenig, A. (1997). The Importance--and Hazards--of Performance Measurement. Nova York: SIGS Publications, Journal of Object-Oriented Programming, janeiro de1997, 9(8), pp. 58-60. [LAF97] Laffra, C. (1997). Advanced Java: Idioms, Pitfalls, Styles and Programming Tips. Upper Saddle River, NJ: Prentice Hall Inc. [LEA97] Lea, D. (1997). Concurrent Programming in Java: Design Principles and Patterns. Reading, MA: Addison Wesley Longman Inc. [MCO93] McConnell, S. (1993). Code Complete: A Practical Handbook of Software Construction. Redmond, WA: Microsoft Press. [MEY88] Meyer, B. (1988). Object-Oriented Software Construction. Upper Saddle River, NJ: Prentice Hall Inc. [NAG95] Nagler, J. (1995). Coding Style and Good Computing Practices. http://wizard.ucr.edu/~nagler/coding_style.html [SUN96] Sun Microsystems (1996). javadoc The Java API Documentation Generator. Sun Microsystems. [SUN97] Sun Microsystems (1997). 100% Pure Java Cookbook for Java Developers: Rules and Hints for Maximizing the Portability of Java Programs. Sun Microsystems. [VIS96] Vision 2000 CCS Package and Application Team (1996). Coding Standards for C, C++, and Java. http://v2ma09.gsfc.nasa.gov/coding_standards.html
100%: Representa um "selo de aprovação" efetivo da Sun que determina que um applet, aplicativo ou pacote Java será executado em QUALQUER plataforma que suporte Java VM.
Acesso: Uma função de membro que modifica ou retorna o valor de um campo. Também conhecido como modificador de acesso. Consulte Getter e Setter.
Padrão de análise: Um padrão de modelagem que descreve uma solução para um problema do negócio ou do domínio.
Antipadrão: Uma abordagem para solucionar um problema comum que, com o tempo, demonstra ser incorreta ou ineficaz.
Argumento: Consulte parâmetro.
BDK: Beans Development Kit
Bloco: Um conjunto de zero ou mais instruções entre chaves.
Chaves: Os caracteres { e }, conhecidos respectivamente como chave de abertura e de fechamento, são usados para definir o início e o fim de um bloco.
Classe: Uma definição (ou um template) a partir da qual objetos são instanciados.
Teste de classe: O ato de garantir que uma classe e suas instâncias (objetos) realizem o que foi definido.
CMVC: Gerenciamento de Configuração e Controle de Versão
Unidade de compilação: Um arquivo de código-fonte, físico ou "virtual", armazenado respectivamente em disco ou em um banco de dados, no qual as classes e interfaces são declaradas.
Componente: Um widget de interface como uma lista, um botão ou uma janela.
Getter constante: Uma função de membro getter que retorna o valor de uma "constante" que, por sua vez, pode ser codificado ou calculado, se necessário.
Construtor: Uma função de membro que executa a inicialização necessária quando um objeto é criado.
Confinamento: Um objeto contém outros objetos com os quais ele colabora para executar seus comportamentos. Para isso, podem ser usadas classes internas (JDK 1.1+) ou instâncias de outras classes agregadas em um objeto (JDK 1.0+).
CPU: Unidade central de processamento
Comentários de estilo C: Um formato Java para comentários, /* & */, adotado na linguagem C/C++ e que pode ser usado para criar comentários em várias linhas. Geralmente usado para documentar linhas de código desnecessárias ou indesejadas durante o teste.
Padrão de design: Um padrão de modelagem que descreve uma solução para um problema de design.
Destruidor: Uma função de membro da classe C++ usada para remover um objeto da memória depois que ele deixa de ser necessário. Como o Java gerencia sua própria memória, esse tipo de função de membro não é necessário. No entanto, o Java suporta uma função de membro que é conceitualmente semelhante, denominada finalize().
Comentários de documentação: Um formato Java para comentários, /** & */, que pode ser processado pelo javadoc para fornecer documentação externa para um arquivo de classes. A principal documentação para interfaces, classes, funções de membro e campos deve incluir comentários.
Campo: Uma variável (um tipo de dados literal ou outro objeto) que descreve uma classe ou uma instância de uma classe. Campos de instância descrevem objetos (instâncias), enquanto campos estáticos descrevem classes. Os campos também são conhecidos como variáveis de campo e propriedades.
finalize(): Uma função de membro disparada automaticamente durante a coleta de lixo antes de um objeto ser removido da memória. A finalidade dessa função de membro é fazer a limpeza necessária, como fechar arquivos abertos.
Coleta de lixo: O gerenciamento automático da memória, no qual objetos que não são mais referenciados são automaticamente removidos da memória.
Getter: Um tipo de função de membro de acesso que retorna o valor de um campo. Um getter pode ser usado para fornecer o valor de uma constante, cuja implementação geralmente é aconselhável como campo estático, por ser uma abordagem mais flexível.
HTML: Linguagem de marcação de hipertexto, um formato padrão do mercado para a criação de páginas da Web.
Recuo: Consulte criação de parágrafos.
Comentários in-line: O uso de um comentário para documentar uma linha de código-fonte. O comentário fica na mesma linha, logo depois do código. Comentários de uma única linha geralmente são usados para esse fim, embora os comentários de estilo C também possam ser empregados.
Interface: A definição de uma assinatura comum, incluindo funções de membro e campos, que deve ser suportada por uma classe que implemente uma interface. As interfaces promovem o polimorfismo por composição.
E/S: Entrada/saída
Invariante: Um conjunto de afirmativas sobre uma instância ou uma classe que deve ser verdadeira em todos os momentos de "estabilidade", como os períodos antes e depois do disparo de uma função de membro no objeto ou na classe.
Java: Uma linguagem de desenvolvimento orientada a objetos e padrão do mercado, que é adequada para o desenvolvimento de aplicativos para a Internet e aplicativos que devem operar em diversos tipos de plataformas.
javadoc: Um utilitário incluído no JDK que processa um arquivo de código-fonte Java e produz um documento externo, em formato HTML, descrevendo o conteúdo do arquivo de código-fonte de acordo com os comentários de documentação no arquivo de código.
JDK: Java Development Kit
Inicialização lenta: Técnica segundo a qual um campo é inicializado em sua função de membro getter correspondente na primeira vez em que ele é necessário A inicialização lenta é usada quando um campo não é muito necessário e requer muito espaço de memória para ser armazenado ou requer leitura no armazenamento permanente.
Variável local: Uma variável definida no escopo de um bloco, geralmente uma função de membro. O escopo de uma variável local é o bloco no qual ela está definida.
Função de membro: Um trecho do código executável associado a uma classe ou às instâncias de uma classe. Uma função de membro pode ser encarada como o equivalente orientado a objetos de uma função.
Assinatura de função de membro: Consulte assinatura.
Teste de método: O ato de garantir que uma função de membro seja executada como está definido.
Ocultamento de nome: Refere-se à prática de usar o mesmo nome - ou pelo menos um nome semelhante - para um campo, uma variável ou um argumento como pertencente a um escopo mais amplo. O exagero mais comum de ocultamento de nome é nomear uma variável local como um campo de instância. O ocultamento de nome deve ser evitado, pois dificulta a compreensão do código e propicia a criação de erros.
Sobrecarga: Uma função de membro fica sobrecarregada quando é definida mais de uma vez na mesma classe (ou em uma subclasse). A única diferença está na assinatura de cada definição.
Sobreposição: Uma função de membro é sobreposta quando é redefinida em uma subclasse e tem a mesma assinatura da definição original.
Pacote: Um conjunto de classes relacionadas.
Criação de parágrafos: Técnica para recuar o código no escopo de um bloco de código por uma unidade, geralmente uma tabulação horizontal, para diferenciá-la do código que está fora do bloco. A criação de parágrafos ajuda a melhorar a legibilidade do código.
Parâmetro: Um argumento passado para uma função de membro. Um parâmetro pode ser de um tipo definido, como uma seqüência de caracteres, um inteiro ou um objeto.
Pós-condição: Uma propriedade ou afirmativa que será verdadeira depois de concluída a execução de uma função de membro.
Precondição: Uma restrição segundo a qual uma função de membro funcionará corretamente.
Propriedade: Consulte campo.
Setter: Uma função de membro de acesso que define o valor de um campo.
Assinatura: A combinação do tipo de parâmetros (se houver algum) e a ordem em que devem ser passados para uma função de membro. Também conhecida como assinatura de função de membro.
Comentários em uma única linha: Um formato Java para comentários, // , adotado na linguagem C/C++, que é comumente usado para a documentação da função de membro interna sobre a lógica do negócio.
Marcas: Uma convenção para marcar seções específicas de comentários de documentação que serão processadas pelo javadoc para produzir comentários de aparência profissional. @see e @author são alguns exemplos de marcas.
Equipamento de teste: Um conjunto de funções de membro para testar o código.
UML: Linguagem Unificada de Modelagem, que é uma notação de modelagem padrão do mercado.
Visibilidade: Técnica usada para indicar o nível de encapsulamento de uma classe, um campo ou uma função de membro. As palavras-chave pública, protegida e privada podem ser usadas para definir a visibilidade.
Espaço em branco: Linhas vazias, espaços e tabulações adicionados ao código para aumentar sua legibilidade.
Widget: Consulte componente.
|
Rational Unified Process |