JavaRush /Blogue Java /Random-PT /Pausa para café #56. Um guia rápido para melhores prática...

Pausa para café #56. Um guia rápido para melhores práticas em Java

Publicado no grupo Random-PT
Fonte: DZone Este guia inclui as melhores práticas e referências Java para melhorar a legibilidade e a confiabilidade do seu código. Os desenvolvedores têm uma grande responsabilidade de tomar as decisões certas todos os dias, e a melhor coisa que pode ajudá-los a tomar as decisões certas é a experiência. E embora nem todos tenham vasta experiência em desenvolvimento de software, todos podem aproveitar a experiência de outros. Preparei para você algumas recomendações que ganhei com minha experiência com Java. Espero que eles ajudem você a melhorar a legibilidade e a confiabilidade do seu código Java.Pausa para café #56.  Um guia rápido para melhores práticas em Java - 1

Princípios de programação

Não escreva código que simplesmente funcione . Esforce-se para escrever um código que possa ser mantido – não apenas por você, mas por qualquer outra pessoa que possa trabalhar no software no futuro. Um desenvolvedor gasta 80% do seu tempo lendo código e 20% escrevendo e testando código. Portanto, concentre-se em escrever código legível. Seu código não precisa de comentários para que ninguém entenda o que ele faz. Para escrever um bom código, existem muitos princípios de programação que podemos usar como diretrizes. Abaixo listarei os mais importantes.
  • • KISS – Significa “Keep It Simple, Stupid”. Você pode notar que os desenvolvedores no início de sua jornada tentam implementar designs complexos e ambíguos.
  • • SECO - “Não se repita”. Tente evitar duplicatas, colocando-as em uma única parte do sistema ou método.
  • YAGNI - “Você não vai precisar disso.” Se de repente você começar a se perguntar: “Que tal adicionar mais (recursos, código, etc.)?”, provavelmente precisará pensar se realmente vale a pena adicioná-los.
  • Código limpo em vez de código inteligente - Simplificando, deixe seu ego de lado e esqueça de escrever código inteligente. Você quer um código limpo, não um código inteligente.
  • Evite a otimização prematura – O problema da otimização prematura é que você nunca sabe onde estarão os gargalos no programa até que eles apareçam.
  • Responsabilidade única - Cada classe ou módulo de um programa deve se preocupar apenas em fornecer um bit de funcionalidade específica.
  • Composição em vez de herança de implementação - Objetos com comportamento complexo devem conter instâncias de objetos com comportamento individual, em vez de herdar uma classe e adicionar novos comportamentos.
  • A ginástica com objetos são exercícios de programação elaborados como um conjunto de 9 regras .
  • Fail fast, stop fast - Este princípio significa interromper a operação atual quando ocorrer algum erro inesperado. A conformidade com este princípio leva a uma operação mais estável.

Pacotes

  1. Priorize a estruturação de pacotes por área temática e não por nível técnico.
  2. Dê preferência a layouts que promovam o encapsulamento e a ocultação de informações para proteção contra uso indevido, em vez de organizar aulas por motivos técnicos.
  3. Trate os pacotes como se tivessem uma API imutável - não exponha mecanismos internos (classes) destinados apenas ao processamento interno.
  4. Não exponha classes que devem ser usadas somente dentro do pacote.

Aulas

Estático

  1. Não permita a criação de uma classe estática. Sempre crie um construtor privado.
  2. As classes estáticas devem permanecer imutáveis, não permitir subclasses ou classes multithread.
  3. As classes estáticas devem ser protegidas contra mudanças de orientação e devem ser fornecidas como utilitários, como filtragem de listas.

Herança

  1. Escolha composição em vez de herança.
  2. Não defina campos protegidos . Em vez disso, especifique um método de acesso seguro .
  3. Se uma variável de classe puder ser marcada como final , faça-o.
  4. Se a herança não for esperada, torne a classe final .
  5. Marque um método como final se não for esperado que as subclasses possam substituí-lo.
  6. Se um construtor não for necessário, não crie um construtor padrão sem lógica de implementação. Java fornecerá automaticamente um construtor padrão se nenhum for especificado.

Interfaces

  1. Não use o padrão de interface de constantes porque ele permite que as classes implementem e poluam a API. Use uma classe estática. Isso tem o benefício adicional de permitir que você faça uma inicialização de objeto mais complexa em um bloco estático (como preencher uma coleção).
  2. Evite usar excessivamente a interface .
  3. Ter uma e apenas uma classe que implementa uma interface provavelmente levará ao uso excessivo de interfaces e causará mais danos do que benefícios.
  4. "Programa para a interface, não para a implementação" não significa que você deva agrupar cada uma de suas classes de domínio com uma interface mais ou menos idêntica; ao fazer isso, você está quebrando o YAGNI .
  5. Mantenha sempre as interfaces pequenas e específicas para que os clientes conheçam apenas os métodos que lhes interessam. Confira o ISP do SOLID.

Finalizadores

  1. O objeto #finalize() deve ser usado criteriosamente e apenas como meio de proteção contra falhas ao limpar recursos (como fechar um arquivo). Sempre forneça um método de limpeza explícito (como close() ).
  2. Em uma hierarquia de herança, sempre chame finalize() do pai em um bloco try . A limpeza da classe deve estar em um bloco final .
  3. Se um método de limpeza explícito não foi chamado e o finalizador fechou os recursos, registre este erro.
  4. Se um logger não estiver disponível, use o manipulador de exceções do thread (que acaba passando um erro padrão que é capturado nos logs).

Regras gerais

Declarações

Uma afirmação, geralmente na forma de uma verificação de pré-condição, impõe um contrato do tipo “falhe rápido, pare rápido”. Eles devem ser amplamente utilizados para identificar erros de programação o mais próximo possível da causa. Condição do objeto:
  • • Um objeto nunca deve ser criado ou colocado em um estado inválido.
  • • Em construtores e métodos, sempre descreva e aplique o contrato usando testes.
  • • A palavra-chave Java assert deve ser evitada , pois pode ser desativada e geralmente é uma construção frágil.
  • • Use a classe de utilitário Assertions para evitar condições if-else detalhadas para verificações de pré-condições.

Genéricos

Uma explicação completa e extremamente detalhada está disponível no Java Generics FAQ . Abaixo estão cenários comuns dos quais os desenvolvedores devem estar cientes.
  1. Sempre que possível, é melhor usar inferência de tipo em vez de retornar a classe/interface base:

    // MySpecialObject o = MyObjectFactory.getMyObject();
    public  T getMyObject(int type) {
    return (T) factory.create(type);
    }

  2. Se o tipo não puder ser determinado automaticamente, incorpore-o.

    public class MySpecialObject extends MyObject {
     public MySpecialObject() {
      super(Collections.emptyList());   // This is ugly, as we loose type
      super(Collections.EMPTY_LIST();    // This is just dumb
      // But this is beauty
      super(new ArrayList());
      super(Collections.emptyList());
     }
    }

  3. Curingas:

    Use um curinga estendido quando estiver obtendo apenas valores de uma estrutura, use um super curinga quando estiver colocando apenas valores em uma estrutura e não use um curinga quando estiver fazendo as duas coisas.

    1. Todo mundo adora PECS ! ( Produtor-estende, Consumidor-super )
    2. Use Foo para o produtor T.
    3. Use Foo para o consumidor T.

Solteiros

Um singleton nunca deve ser escrito no estilo de padrão de design clássico , o que é adequado em C++, mas não apropriado em Java. Mesmo que seja adequadamente seguro para threads, nunca implemente o seguinte (seria um gargalo de desempenho!):
public final class MySingleton {
  private static MySingleton instance;
  private MySingleton() {
    // singleton
  }
  public static synchronized MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }
}
Se a inicialização lenta for realmente desejada, então uma combinação dessas duas abordagens funcionará.
public final class MySingleton {
  private MySingleton() {
   // singleton
  }
  private static final class MySingletonHolder {
    static final MySingleton instance = new MySingleton();
  }
  public static MySingleton getInstance() {
    return MySingletonHolder.instance;
  }
}
Spring: Por padrão, um bean é registrado com escopo singleton, o que significa que apenas uma instância será criada pelo contêiner e conectada a todos os consumidores. Isso fornece a mesma semântica de um singleton normal, sem qualquer limitação de desempenho ou vinculação.

Exceções

  1. Use exceções verificadas para condições corrigíveis e exceções de tempo de execução para erros de programação. Exemplo: obter um número inteiro de uma string.

    Ruim: NumberFormatException estende RuntimeException, portanto, destina-se a indicar erros de programação.

  2. Não faça o seguinte:

    // String str = input string
    Integer value = null;
    try {
       value = Integer.valueOf(str);
    } catch (NumberFormatException e) {
    // non-numeric string
    }
    if (value == null) {
    // handle bad string
    } else {
    // business logic
    }

    Uso Correto:

    // String str = input string
    // Numeric string with at least one digit and optional leading negative sign
    if ( (str != null) && str.matches("-?\\d++") ) {
       Integer value = Integer.valueOf(str);
      // business logic
    } else {
      // handle bad string
    }
  3. Você tem que lidar com exceções no lugar certo, no lugar certo no nível do domínio.

    MANEIRA ERRADA - A camada de objeto de dados não sabe o que fazer quando ocorre uma exceção no banco de dados.

    class UserDAO{
        public List getUsers(){
            try{
                ps = conn.prepareStatement("SELECT * from users");
                rs = ps.executeQuery();
                //return result
            }catch(Exception e){
                log.error("exception")
                return null
            }finally{
                //release resources
            }
        }}
    

    MANEIRA RECOMENDADA - A camada de dados deve simplesmente relançar a exceção e passar a responsabilidade de tratar ou não a exceção para a camada correta.

    === RECOMMENDED WAY ===
    Data layer should just retrow the exception and transfer the responsability to handle the exception or not to the right layer.
    class UserDAO{
       public List getUsers(){
          try{
             ps = conn.prepareStatement("SELECT * from users");
             rs = ps.executeQuery();
             //return result
          }catch(Exception e){
           throw new DataLayerException(e);
          }finally{
             //release resources
          }
      }
    }

  4. Geralmente, as exceções NÃO devem ser registradas no momento em que são emitidas, mas sim no momento em que são efetivamente processadas. As exceções de log, quando lançadas ou relançadas, tendem a preencher os arquivos de log com ruído. Observe também que o rastreamento de pilha de exceções ainda registra onde a exceção foi lançada.

  5. Apoie o uso de exceções padrão.

  6. Use exceções em vez de códigos de retorno.

Igual e HashCode

Há uma série de questões a serem consideradas ao escrever métodos adequados de equivalência de objeto e código hash. Para facilitar o uso, use equals e hash de java.util.Objects .
public final class User {
 private final String firstName;
 private final String lastName;
 private final int age;
 ...
 public boolean equals(Object o) {
   if (this == o) {
     return true;
   } else if (!(o instanceof User)) {
     return false;
   }
   User user = (User) o;
   return Objects.equals(getFirstName(), user.getFirstName()) &&
    Objects.equals(getLastName(),user.getLastName()) &&
    Objects.equals(getAge(), user.getAge());
 }
 public int hashCode() {
   return Objects.hash(getFirstName(),getLastName(),getAge());
 }
}

Gestão de recursos

Maneiras de liberar recursos com segurança: A instrução try-with-resources garante que cada recurso seja fechado no final da instrução. Qualquer objeto que implemente java.lang.AutoCloseable, que inclui todos os objetos que implementam java.io.Closeable , pode ser usado como recurso.
private doSomething() {
try (BufferedReader br = new BufferedReader(new FileReader(path)))
 try {
   // business logic
 }
}

Use ganchos de desligamento

Use um gancho de desligamento que é chamado quando a JVM é encerrada normalmente. (Mas não será capaz de lidar com interrupções repentinas, como devido a uma queda de energia) Esta é uma alternativa recomendada em vez de declarar um método finalize() que só será executado se System.runFinalizersOnExit() for verdadeiro (o padrão é falso) .
public final class SomeObject {
 var distributedLock = new ExpiringGeneralLock ("SomeObject", "shared");
 public SomeObject() {
   Runtime
     .getRuntime()
     .addShutdownHook(new Thread(new LockShutdown(distributedLock)));
 }
 /** Code may have acquired lock across servers */
 ...
 /** Safely releases the distributed lock. */
 private static final class LockShutdown implements Runnable {
   private final ExpiringGeneralLock distributedLock;
   public LockShutdown(ExpiringGeneralLock distributedLock) {
     if (distributedLock == null) {
       throw new IllegalArgumentException("ExpiringGeneralLock is null");
     }
     this.distributedLock = distributedLock;
   }
   public void run() {
     if (isLockAlive()) {
       distributedLock.release();
     }
   }
   /** @return True if the lock is acquired and has not expired yet. */
   private boolean isLockAlive() {
     return distributedLock.getExpirationTimeMillis() > System.currentTimeMillis();
   }
 }
}
Permita que os recursos se tornem completos (e também renováveis), distribuindo-os entre servidores. (Isso permitirá a recuperação de uma interrupção repentina, como uma queda de energia.) Veja o exemplo de código acima que usa ExpiringGeneralLock (um bloqueio comum a todos os sistemas).

Data hora

Java 8 apresenta a nova API Date-Time no pacote java.time. Java 8 introduz uma nova API Date-Time para resolver as seguintes deficiências da antiga API Date-Time: não threading, design pobre, manipulação complexa de fuso horário, etc.

Paralelismo

Regras gerais

  1. Cuidado com as bibliotecas a seguir, que não são seguras para threads. Sempre sincronize com objetos se eles forem usados ​​por vários threads.
  2. Data ( não imutável ) - Use a nova API Date-Time, que é thread-safe.
  3. SimpleDateFormat - Use a nova API Date-Time, que é thread-safe.
  4. Prefira usar classes java.util.concurrent.atomic em vez de tornar as variáveis ​​voláteis .
  5. O comportamento das classes atômicas é mais óbvio para o desenvolvedor médio, enquanto volátil requer uma compreensão do modelo de memória Java.
  6. As classes atômicas agrupam variáveis ​​voláteis em uma interface mais conveniente.
  7. Entenda os casos de uso em que volátil é apropriado . (ver artigo )
  8. Usar chamável quando uma exceção verificada é necessária, mas não há tipo de retorno. Como Void não pode ser instanciado, ele comunica a intenção e pode retornar null com segurança .

Fluxos

  1. java.lang.Thread deve estar obsoleto. Embora este não seja oficialmente o caso, em quase todos os casos o pacote java.util.concurrent fornece uma solução mais clara para o problema.
  2. Estender java.lang.Thread é considerado uma má prática - implemente Runnable e crie um novo thread com uma instância no construtor (regra de composição sobre herança) .
  3. Prefira executores e threads quando o processamento paralelo for necessário.
  4. É sempre recomendável especificar seu próprio thread factory personalizado para gerenciar a configuração dos threads criados ( mais detalhes aqui ).
  5. Use DaemonThreadFactory em Executors para threads não críticos para que o pool de threads possa ser encerrado imediatamente quando o servidor for encerrado ( mais detalhes aqui ).
this.executor = Executors.newCachedThreadPool((Runnable runnable) -> {
   Thread thread = Executors.defaultThreadFactory().newThread(runnable);
   thread.setDaemon(true);
   return thread;
});
  1. A sincronização Java não é mais tão lenta (55–110 ns). Não evite isso usando truques como bloqueio de verificação dupla .
  2. Prefira a sincronização com um objeto interno em vez de uma classe, pois os usuários podem sincronizar com sua classe/instância.
  3. Sempre sincronize vários objetos na mesma ordem para evitar conflitos.
  4. A sincronização com uma classe não bloqueia inerentemente o acesso aos seus objetos internos. Sempre use os mesmos bloqueios ao acessar um recurso.
  5. Lembre-se de que a palavra-chave sincronizada não é considerada parte da assinatura do método e, portanto, não será herdada.
  6. Evite sincronização excessiva, pois isso pode levar a um desempenho ruim e a conflitos. Use a palavra-chave sincronizada estritamente para a parte do código que requer sincronização.

Coleções

  1. Use coleções paralelas Java-5 em código multithread sempre que possível. São seguros e possuem excelentes características.
  2. Se necessário, use CopyOnWriteArrayList em vez de sincronizadoList.
  3. Use Collections.unmodifiable list(...) ou copie a coleção ao recebê-la como parâmetro para new ArrayList(list) . Evite modificar coleções locais fora da sua turma.
  4. Sempre retorne uma cópia da sua coleção, evitando modificar sua lista externamente com new ArrayList (list) .
  5. Cada coleção deve ser agrupada em uma classe separada, portanto agora o comportamento associado à coleção tem um início (por exemplo, métodos de filtragem, aplicação de uma regra a cada elemento).

Diversos

  1. Escolha lambdas em vez de classes anônimas.
  2. Escolha referências de método em vez de lambdas.
  3. Use enums em vez de constantes int.
  4. Evite usar float e double se forem necessárias respostas precisas; em vez disso, use BigDecimal como Money.
  5. Escolha tipos primitivos em vez de primitivos em caixa.
  6. Você deve evitar usar números mágicos em seu código. Use constantes.
  7. Não retorne Nulo. Comunique-se com seu cliente de método usando `Optional`. O mesmo para coleções – retorne matrizes ou coleções vazias, não nulos.
  8. Evite criar objetos desnecessários, reutilize objetos e evite limpeza desnecessária do GC.

Inicialização lenta

A inicialização lenta é uma otimização de desempenho. É usado quando os dados são considerados “caros” por algum motivo. No Java 8 temos que usar a interface funcional do provedor para isso.
== Thread safe Lazy initialization ===
public final class Lazy {
   private volatile T value;
   public T getOrCompute(Supplier supplier) {
       final T result = value; // Just one volatile read
       return result == null ? maybeCompute(supplier) : result;
   }
   private synchronized T maybeCompute(Supplier supplier) {
       if (value == null) {
           value = supplier.get();
       }
       return value;
   }
}
Lazy lazyToString= new Lazy<>()
return lazyToString.getOrCompute( () -> "(" + x + ", " + y + ")");
Por enquanto é tudo, espero que tenha sido útil!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION