Aqui está uma lista de 10 práticas de codificação Java mais sutis que as regras eficazes de Josh Bloch. Comparado à lista de Josh Bloch, fácil de aprender e se concentrar em situações diárias, esta lista conterá situações envolvendo coisas incomuns no design da API/SPI, o que pode ter um grande impacto.
Eu os encontrei enquanto escrevi e mantendo Jooq (SQL modelou DSL interno em Java). Como DSL interno, o Jooq desafia os compiladores e genéricos do Java, na maior extensão, combinando genéricos, parâmetros e sobrecargas mutáveis, que Josh Bloch pode não recomendar uma API tão ampla.
Deixe -me compartilhar com você 10 práticas recomendadas sutis para codificação Java:
1. Lembre -se do destruidor de C ++
Lembra -se do destruidor de C ++? Não se lembra? Então você tem muita sorte porque não precisa depurar os vazamentos de memória devido à memória alocada após a exclusão do objeto não ser liberada. Obrigado ao Sun/Oracle pelo mecanismo de coleta de lixo implementado!
No entanto, o destruidor fornece uma característica interessante. Entende a ordem de alocação inversa à memória livre. Lembre -se de que esse é o caso em Java, quando você opera a sintaxe do destruidor de classe:
Existem vários outros casos de uso. Aqui está um exemplo concreto de como implementar o SPI de alguns ouvintes de eventos:
@OverridePublic Void Antes do EventContext e) {super.beevent (e); // super código antes do meu código} @OverridePublic Void AfterEvent (EventContext e) {// Super Code após meu código super.afterevent (e);} O infame problema gastronômico do filósofo é outro bom exemplo de por que isso importa. Para perguntas sobre refeições filósofas, verifique o link:
http://adit.io/postss/2013-05-11-the-dining-filosophers-problem-with-ron-swanson.html
Regra : sempre que você usa antes/depois, aloce/livre, leve/retorne a semântica para implementar a lógica, considere se deve executar operações após/livre/retornar em ordem inversa.
Forneça aos clientes métodos SPI que facilitem a injeção de comportamento personalizado em sua biblioteca/código. Cuidado com que seus julgamentos de evolução do SPI podem confundi -lo, fazendo você pensar que você (não) pretende precisar de parâmetros adicionais. Obviamente, as funções não devem ser adicionadas muito cedo. Mas depois de publicar seu SPI, depois de decidir seguir a versão semântica, você realmente se arrependerá de adicionar um parâmetro único estúpido ao seu SPI quando perceber que, em alguns casos, pode precisar de outro parâmetro:
Interface EventListener {// Mensagem Void Bad (Mensagem String);}E se você precisar de um ID da mensagem e fonte de mensagem? A evolução da API impedirá que você adicione parâmetros aos tipos acima. Obviamente, com o Java 8, você pode adicionar um método de defensor que "defende" suas más decisões de design nos primeiros dias:
interface EventListener {// Bad padrão void message (string message) {message (message, null, null); } // Melhorar? Mensagem void (mensagem de string, ID inteiro, fonte de medição);} Observe que, infelizmente, o método do Defender não pode usar o modificador final.
Mas seria muito melhor usar objetos de contexto (ou objetos de parâmetro) do que poluir seu SPI usando muitos métodos.
interface messageContext {string message (); Id ID (); MessageRce Source ();} interface EventListener {// Awesome! Mensagem vazia (contexto MessageContext);} Você pode evoluir a API MessagEContext com mais facilidade do que o EventListner SPI, porque poucos usuários o implementarão.
Regra : Sempre que um SPI for especificado, considere usar um objeto de contexto/parâmetro em vez de escrever métodos com parâmetros fixos.
Nota : Também é uma boa idéia trocar os resultados por meio de um tipo de Messageresult dedicado, que pode ser construído usando a API do construtor. Isso aumentará bastante a flexibilidade da evolução do SPI.
Os programadores de balanço geralmente pressionam apenas algumas teclas de atalho para gerar centenas ou milhares de aulas anônimas. Na maioria dos casos, não dói fazê -lo enquanto a interface for seguida e o ciclo de vida do subtipo SPI não for violado. Mas não use classes anônimas, locais ou internas com frequência por um motivo simples - eles salvarão referências a classes externas. Porque não importa para onde eles vão, as categorias externas precisam seguir. Por exemplo, se a operação externa de uma classe local for imprópria, todo o gráfico de objeto passará por alterações sutis, o que pode causar vazamento de memória.
Regra: Antes de escrever aulas anônimas, locais ou internas, pense duas vezes sobre se você pode convertê-lo em classes estáticas ou comuns de nível superior, evitando maneiras de devolver seus objetos a um domínio de nível externo.
Nota: Use aparelhos duplos encaracolados para inicializar objetos simples:
novo hashmap <string, string> () {{put ("1", "a"); put ("2", "b");}}Este método utiliza o inicializador da instância descrito na especificação JLS §8.6. Parece bom na superfície, mas não é recomendado. Porque se você usar um objeto hashmap completamente independente, a instância nem sempre reterá referências a objetos externos. Além disso, isso permitirá que o carregador de classe gerencie mais classes.
O ritmo de Java8 está se aproximando. Com o Java 8, traz expressões Lambda, goste ou não. Embora seus usuários de API possam gostar, é melhor garantir que eles possam usá -lo o mais rápido possível. Portanto, a menos que sua API receba tipos simples de "escalares", como int, longa, string e data, deixe sua API receber SAM o mais rápido possível.
O que é Sam? Sam é um único método abstrato [tipo]. Também conhecido como interface de função, em breve será anotado como @functionInterface. Isso corresponde à regra 2, o EventListener é na verdade um SAM. O melhor SAM possui apenas um parâmetro, pois isso simplificará ainda mais a escrita das expressões Lambda. Imagine escrever
ouvintes.add (c -> System.out.println (c.message ()));
Para substituir
ouvintes.add (new EventListener () {@Override public void message (MessageContext c) {System.out.println (c.message ())); }});Imagine lidar com XML em Joox. Joox contém muito Sam:
$ (documento) // Encontre elementos com um ID. Encontre (c -> $ (c) .id ()! = null) // Encontre os elementos de seus filhos.
Regras: Seja gentil com seus usuários de API, comece a escrever interfaces SAM/funções a partir de agora .
Nota: Existem muitos blogs interessantes sobre expressões Java8 Lambda e API de coleções aprimoradas:
Escrevi 1 ou 2 artigos sobre Java Nulls e também expliquei a introdução de novas classes opcionais em Java 8. De um ponto de vista acadêmico ou prático, esses tópicos são bastante interessantes.
Embora o NULL e o NullPointerException ainda sejam uma falha no Java nesta fase, você ainda pode projetar uma API que não terá nenhum problema. Ao projetar APIs, você deve evitar retornar o máximo possível, porque seus usuários podem chamar o método em uma cadeia:
inicialize (someargument) .calculate (dados) .dispatch ();
Como pode ser visto no código acima, nenhum método deve retornar nulo. De fato, o uso de nulo é geralmente considerado uma heterogeneidade comparável. Biblioteca como JQuery ou Joox abandonou completamente o NULL em objetos iteráveis.
NULL é geralmente usado na inicialização tardia. Em muitos casos, a inicialização tardia também deve ser evitada sem afetar gravemente o desempenho. De fato, se a estrutura de dados envolvida for muito grande, a inicialização do atraso deve ser usada com cautela.
Regra: os métodos devem evitar o retorno nulo sempre que forem. O NULL é usado apenas para representar a semântica de "não -inicializado" ou "não existe".
Embora não consiga retornar um método com um valor nulo em alguns casos, nunca retorne uma matriz vazia ou uma coleção vazia! Consulte o método java.io.file.list (), ele foi projetado assim:
Este método retorna uma matriz de strings para todos os arquivos ou diretórios no diretório especificado. Se o diretório estiver vazio, a matriz retornada estará vazia. Retorne nulo se o caminho especificado não existir ou ocorrer um erro de E/S.
Portanto, esse método é geralmente usado assim:
Diretório de arquivos = // ... if (diretório.isdirectory ()) {string [] list = diretório.list (); if (list! = null) {for (string file: list) {// ...}}}Você acha que a verificação nula é necessária? A maioria das operações de E/S produzirá IoExceptions, mas esse método retorna apenas nulo. O NULL não pode armazenar mensagens de erro de E/S. Portanto, esse design tem as três deficiências a seguir:
Se você olhar para o problema da perspectiva das coleções, uma matriz ou coleção vazia é a melhor implementação de "não existe". Retornar uma matriz ou coleção vazia é de pouco significado prático, a menos que seja usado para inicialização atrasada.
Regra: a matriz ou coleção retornada não deve ser nula.
O benefício do HTTP é apátrida. Todos os estados relevantes são transferidos em cada solicitação e resposta. Esta é a essência da nomeação de descanso: transferência de estado (transferência de estado representacional). Também é ótimo fazer isso em Java. Pense nisso da perspectiva da regra 2 quando o método receber o objeto de parâmetro do estado. Se o estado for transmitido através de esse objeto, em vez de operar o estado de fora, as coisas serão mais fáceis. Tome o JDBC como exemplo. O exemplo a seguir lê um cursor de um programa armazenado.
Callablestatement s = conexão.preparecall ("{? = ...}"); // manipulação verbosa do estado da declaração: s.RegisteroutParameter (1, cursor); s.SetString (2, "abc"); s.execute () resultSet Rs = s.getOxt (1); // parbote ";Isso torna a API do JDBC tão estranha. Cada objeto é com estado e difícil de operar. Especificamente, existem dois problemas principais:
Regras: Mais implementações no estilo funcional. Estado de transferência através de parâmetros de método. Muito poucos estados de objetos operacionais.
Esta é uma maneira relativamente fácil de operar. Em sistemas de objetos relativamente complexos, você pode obter melhorias significativas de desempenho, desde que primeiro faça julgamentos iguais no método iguals () de todos os objetos:
@OverridePublic boolean é igual (objeto outro) {if (this == Outros) retorna true; // Outra lógica do julgamento da igualdade ...}Observe que outras verificações de curto-circuito podem envolver verificações de valor nulo, portanto, também devem ser adicionadas:
@OverridePublic boolean é igual (objeto outro) {if (this == Outros) retorna true; if (outros == null) retorna false; // Resto da lógica da igualdade ...}Regra: use circuitos curtos em todos os seus métodos iguais () para melhorar o desempenho.
Algumas pessoas podem discordar disso, porque tornar o método padrão para a final é contrário ao hábito dos desenvolvedores de Java. Mas se você tiver controle completo sobre o código, certamente é correto tornar o método padrão para Final:
Isso é especialmente adequado para métodos estáticos; nesse caso, a "sobreposição" (na verdade oclusão) dificilmente funciona. Recentemente, me deparei com um exemplo de um método estático de sombreamento muito ruim no Apache Tika. Dê uma olhada:
O TikainputStream estende o taggedInputStream para mascarar seu método estático get () com uma implementação relativamente diferente.
Ao contrário dos métodos convencionais, os métodos estáticos não podem ser substituídos, porque a chamada está ligada à chamada do método estático no tempo de compilação. Se você tiver azar, pode acidentalmente obter o método errado.
Regra: se você tiver controle completo da sua API, faça o maior número possível de métodos.
Em ocasiões especiais, você pode usar o método de parâmetro variável "aceitar tudo" para receber um objeto ... parâmetro:
Void Acepall (objeto ... tudo);
Escrever esse método traz uma pequena sensação de JavaScript ao ecossistema Java. Obviamente, você pode limitar o tipo real com base na situação real, como string…. Como você não quer limitar muito, você pode pensar que é uma boa ideia substituir o objeto por T genérico:
Void Acepall (T ... tudo);
Mas não. Sempre será inferido como um objeto. De fato, você pode pensar que os genéricos não podem ser usados nos métodos acima. Mais importante, você pode pensar que pode sobrecarregar o método acima, mas não pode:
Void Acepall (t ... tudo); void aceall (mensagem de string, t ... tudo);
Parece que você pode opcionalmente passar uma mensagem de string para o método. Mas o que acontece com esta chamada?
aceitação ("mensagem", 123, "ABC");O compilador infere t como <? estende -se serializável e comparável <? >>, que tornará a chamada pouco clara!
Portanto, sempre que você tiver uma assinatura "aceita tudo" (mesmo que seja genérica), você nunca será capaz de sobrecarregá-la. Os consumidores da API podem deixar o compilador "acidentalmente" escolher o método "correto" quando tiver sorte. Mas também é possível usar o método Aceitar-All ou não chamar qualquer método.
Regra : Evite assinaturas de "aceitar tudo", se possível. Caso contrário, não sobrecarregue esse método.
Java é uma besta. Ao contrário de outras línguas mais idealistas, evoluiu lentamente para o que é hoje. Isso pode ser uma coisa boa, porque na velocidade do desenvolvimento de Java, já existem centenas de avisos, e esses avisos só podem ser compreendidos por anos de experiência.
Fique atento para mais das dez principais listas sobre este tópico!
Link original: Jooq Tradução: ImportNew.com - LIKEN
Link de tradução: http://www.importnew.com/10138.html
[Por favor, mantenha o link original da fonte, o tradutor e a tradução ao reimprimir. ]