Boas Práticas de Programação em Java

29/05/2024

Boas Práticas de Programação em Java

Escrever código em Java que seja limpo, eficiente e fácil de manter é um objetivo essencial para qualquer desenvolvedor. Este artigo discute algumas das melhores práticas de programação em Java, abrangendo padrões de design, convenções de codificação e outras recomendações essenciais.

  1. Convenções de Codificação
        
        Adotar convenções de codificação consistentes é fundamental para a legibilidade e manutenção do código. As principais convenções incluem:

  • Nomenclatura :
  • Classes: Use CamelCase. Exemplo: CustomerOrder.
  • Métodos: Use camelCase. Exemplo: calculateTotalPrice().
  • Variáveis: Use camelCase. Exemplo: orderTotal.
  • Constantes: Use ALL_UPPER_CASE com underscores. Exemplo: MAX_SIZE.   
  • Indentação:

    • Utilize espaços ou tabulações de forma consistente. Geralmente, quatro espaços por nível de indentação é o padrão.
  • Comentários:

    • Use comentários Javadoc para documentar classes e métodos públicos.
    • Comente o código onde necessário para explicar "o porquê", não apenas "o que".

       Exemplo de boas convenções de codificação:


  1. /** 
  2.  * Classe que representa um pedido de cliente. 
  3.  */  
  4. public class CustomerOrder {  
  5.     private String customerName;  
  6.     private List<OrderItem> items;  
  7.     private static final int MAX_ITEMS = 100;  
  8.   
  9.     /** 
  10.      * Calcula o preço total do pedido. 
  11.      *  
  12.      * @return o preço total. 
  13.      */  
  14.     public double calculateTotalPrice() {  
  15.         double total = 0.0;  
  16.         for (OrderItem item : items) {  
  17.             total += item.getPrice();  
  18.         }  
  19.         return total;  
  20.     }  
  21. }  

2. Padrões de Design

Padrões de design são soluções comprovadas para problemas comuns no desenvolvimento de software. Alguns dos padrões mais utilizados em Java incluem:

  • Singleton: Garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a essa instância.


  1. public class Singleton {  
  2.     private static Singleton instance;  
  3.   
  4.     private Singleton() {  
  5.         // Construtor privado para evitar instanciamento externo  
  6.     }  
  7.   
  8.     public static Singleton getInstance() {  
  9.         if (instance == null) {  
  10.             instance = new Singleton();  
  11.         }  
  12.         return instance;  
  13.     }  
  14. }  

        .Factory Method: Define uma interface para criar objetos, mas deixa as subclasses decidirem qual classe instanciar.


  1. public interface Product {  
  2.     void use();  
  3. }  
  4.   
  5. public class ConcreteProductA implements Product {  
  6.     @Override  
  7.     public void use() {  
  8.         System.out.println("Using Product A");  
  9.     }  
  10. }  
  11.   
  12. public class ConcreteProductB implements Product {  
  13.     @Override  
  14.     public void use() {  
  15.         System.out.println("Using Product B");  
  16.     }  
  17. }  
  18.   
  19. public class ProductFactory {  
  20.     public static Product createProduct(String type) {  
  21.         switch (type) {  
  22.             case "A"return new ConcreteProductA();  
  23.             case "B"return new ConcreteProductB();  
  24.             defaultthrow new IllegalArgumentException("Unknown product type");  
  25.         }  
  26.     }  
  27. }  

        .Observer: Define uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente.


  1. public interface Observer {  
  2.     void update();  
  3. }  
  4.   
  5. public class ConcreteObserver implements Observer {  
  6.     @Override  
  7.     public void update() {  
  8.         System.out.println("Observer has been updated");  
  9.     }  
  10. }  
  11.   
  12. public class Subject {  
  13.     private List<Observer> observers = new ArrayList<>();  
  14.   
  15.     public void addObserver(Observer observer) {  
  16.         observers.add(observer);  
  17.     }  
  18.   
  19.     public void removeObserver(Observer observer) {  
  20.         observers.remove(observer);  
  21.     }  
  22.   
  23.     public void notifyObservers() {  
  24.         for (Observer observer : observers) {  
  25.             observer.update();  
  26.         }  
  27.     }  
  28. }  


3. Práticas de Codificação Limpa

  • Métodos Pequenos e Focados:

    • Cada método deve realizar uma única tarefa bem definida. Métodos longos e multifacetados são difíceis de entender e manter.
  • Evitar Codificação Duplicada:

    • Utilize abstrações, herança e composição para evitar duplicação de código. Código duplicado é mais difícil de manter.
  • Tratamento de Exceções:

    • Trate exceções de forma apropriada, fornecendo informações úteis e evitando capturar exceções genéricas. Use exceções específicas sempre que possível.

  1. try {  
  2.     // Código que pode lançar exceção  
  3. catch (SpecificException e) {  
  4.     // Tratamento específico  
  5. catch (AnotherException e) {  
  6.     // Tratamento específico  
  7. catch (Exception e) {  
  8.     // Tratamento genérico como último recurso  
  9.     e.printStackTrace();  
  10. }  

  • Utilizar Bibliotecas e Frameworks Confiáveis:

    • Não reinventar a roda. Utilize bibliotecas e frameworks amplamente adotados e bem mantidos. Exemplo: Spring Framework para desenvolvimento web e empresarial.

4. Boas Práticas de Testes

  • Testes Unitários:

    • Escreva testes unitários para todas as partes críticas do código. Utilize frameworks como JUnit e Mockito para testes automatizados.
  • Cobertura de Testes:

    • A cobertura de testes deve ser alta, mas não sacrifique a qualidade pelo percentual de cobertura. Priorize testar lógica crítica e cenários edge.
  • Testes de Integração:

    • Além de testes unitários, escreva testes de integração para garantir que diferentes partes do sistema funcionem bem juntas. Utilize ferramentas como Testcontainers para testes com dependências externas.

Exemplo de um teste unitário com JUnit:


  1. import static org.junit.jupiter.api.Assertions.*;  
  2. import org.junit.jupiter.api.Test;  
  3.   
  4. public class CustomerOrderTest {  
  5.   
  6.     @Test  
  7.     public void testCalculateTotalPrice() {  
  8.         CustomerOrder order = new CustomerOrder();  
  9.         order.addItem(new OrderItem("Item1"10.0));  
  10.         order.addItem(new OrderItem("Item2"20.0));  
  11.   
  12.         double expectedTotal = 30.0;  
  13.         assertEquals(expectedTotal, order.calculateTotalPrice(), 0.001);  
  14.     }  
  15. }  

5. Documentação e Comentários

  • Documentação Javadoc:

    • Documente suas classes e métodos utilizando Javadoc. Isso ajuda na geração de documentação automática e facilita o entendimento do código por outros desenvolvedores.

  1. /** 
  2.  * Classe que representa um pedido de cliente. 
  3.  */  
  4. public class CustomerOrder {  
  5.     // ...  
  6. }  

  • Comentários de Código:

    • Use comentários para explicar o "porquê" do código, não o "como". Comentários excessivos podem ser prejudiciais, mas explicações sobre decisões de design são valiosas.

Conclusão

Seguir boas práticas de programação em Java resulta em código que é mais legível, fácil de manter e menos propenso a erros. A adoção de padrões de design apropriados, convenções de codificação e uma abordagem disciplinada para testes são componentes essenciais de um código de alta qualidade. Lembre-se de que a consistência e a clareza são fundamentais para a colaboração em equipe e a longevidade do software.