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
- Priorize a estruturação de pacotes por área temática e não por nível técnico.
- 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.
- Trate os pacotes como se tivessem uma API imutável - não exponha mecanismos internos (classes) destinados apenas ao processamento interno.
- Não exponha classes que devem ser usadas somente dentro do pacote.
Aulas
Estático
- Não permita a criação de uma classe estática. Sempre crie um construtor privado.
- As classes estáticas devem permanecer imutáveis, não permitir subclasses ou classes multithread.
- 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
- Escolha composição em vez de herança.
- Não defina campos protegidos . Em vez disso, especifique um método de acesso seguro .
- Se uma variável de classe puder ser marcada como final , faça-o.
- Se a herança não for esperada, torne a classe final .
- Marque um método como final se não for esperado que as subclasses possam substituí-lo.
- 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
- 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).
- Evite usar excessivamente a interface .
- 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.
- "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 .
- 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
- 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() ).
- 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 .
- Se um método de limpeza explícito não foi chamado e o finalizador fechou os recursos, registre este erro.
- 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.- 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); } - 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()); } } - 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.
- Todo mundo adora PECS ! ( Produtor-estende, Consumidor-super )
- Use Foo para o produtor T.
- 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
-
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.
-
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 } } } -
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.
-
Apoie o uso de exceções padrão.
-
Use exceções em vez de códigos de retorno.
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
}
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
- 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.
- Data ( não imutável ) - Use a nova API Date-Time, que é thread-safe.
- SimpleDateFormat - Use a nova API Date-Time, que é thread-safe.
- Prefira usar classes java.util.concurrent.atomic em vez de tornar as variáveis voláteis .
- 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.
- As classes atômicas agrupam variáveis voláteis em uma interface mais conveniente.
- Entenda os casos de uso em que volátil é apropriado . (ver artigo )
- 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
- 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.
- 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) .
- Prefira executores e threads quando o processamento paralelo for necessário.
- É sempre recomendável especificar seu próprio thread factory personalizado para gerenciar a configuração dos threads criados ( mais detalhes aqui ).
- 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;
});
- 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 .
- Prefira a sincronização com um objeto interno em vez de uma classe, pois os usuários podem sincronizar com sua classe/instância.
- Sempre sincronize vários objetos na mesma ordem para evitar conflitos.
- 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.
- Lembre-se de que a palavra-chave sincronizada não é considerada parte da assinatura do método e, portanto, não será herdada.
- 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
- Use coleções paralelas Java-5 em código multithread sempre que possível. São seguros e possuem excelentes características.
- Se necessário, use CopyOnWriteArrayList em vez de sincronizadoList.
- 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.
- Sempre retorne uma cópia da sua coleção, evitando modificar sua lista externamente com new ArrayList (list) .
- 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
- Escolha lambdas em vez de classes anônimas.
- Escolha referências de método em vez de lambdas.
- Use enums em vez de constantes int.
- Evite usar float e double se forem necessárias respostas precisas; em vez disso, use BigDecimal como Money.
- Escolha tipos primitivos em vez de primitivos em caixa.
- Você deve evitar usar números mágicos em seu código. Use constantes.
- 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.
- 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!
GO TO FULL VERSION