Article image

GR

Guilherme Rosental16/10/2025 20:43
Compartilhe

Programação Defensiva: Primeiros passos e Boas Práticas em JAVA

  • #Java

Blindando seu Código Contra o Inesperado

Você já imaginou o que acontece quando uma transação bancária falha por um valor nulo ou um cálculo de juros resulta em um número negativo? No mundo das finanças por exemplo, um erro de software não é um simples bug, é um risco real para a aplicação e todo o seu contexto social. Neste artigo, vamos mergulhar no universo da Programação Defensiva em Java, uma abordagem essencial para construir sistemas robustos, seguros e confiáveis.

O que é Programação Defensiva?

Programação defensiva é uma filosofia de design de software que visa garantir o comportamento previsível de um programa mesmo sob condições inesperadas ou com entradas incorretas. Em vez de assumir que tudo vai funcionar como o esperado, o desenvolvedor se antecipa aos problemas.

Pense em seu código como um castelo. A programação defensiva é a construção de muralhas altas, um fosso profundo e portões reforçados. O objetivo não é apenas lidar com ataques externos (dados inválidos de usuários), mas também com falhas internas (bugs e estados inconsistentes).

  • Princípio Central: O código deve se proteger de dados inválidos.
  • Foco: Prevenção de erros antes que eles aconteçam.
  • Resultado: Software mais estável, seguro e fácil de depurar.

Por que a Defesa é Crucial em Aplicações Financeiras com Java?

No setor financeiro, a precisão e a integridade dos dados são inegociáveis. Um NullPointerException em um e-commerce pode derrubar uma página de produto; em um sistema de home broker, pode significar a perda de milhões.

Em nosso exemplo vamos explorar o contexto de uma aplicação financeira. Utilizar Java para finanças exige um nível extra de cuidado, pois a plataforma é robusta, mas a lógica de negócio é complexa. É aqui que a programação defensiva brilha:

  • Integridade dos Dados: Garante que valores monetários nunca sejam negativos ou que transferências não ocorram para contas inexistentes.
  • Prevenção de Falhas em Cascata: Evita que um erro em um pequeno módulo (como o cálculo de taxas) se propague e corrompa todo o sistema.
  • Segurança: Dificulta a exploração de vulnerabilidades, como a injeção de dados maliciosos que poderiam levar a fraudes.

Pilares da Programação Defensiva em Java: Exemplos Práticos

Vamos transformar a teoria em prática. Abaixo estão as técnicas fundamentais para blindar seu código Java, com exemplos focados no domínio financeiro.

1. Validação de Entradas e Pré-condições: O Portão de Entrada

Nunca confie nos dados que chegam aos seus métodos, especialmente se forem públicos. Valide tudo! A forma mais idiomática em Java para lidar com argumentos inválidos é lançar exceções como IllegalArgumentException ou NullPointerException.

Exemplo: Método de transferência bancária.

public class ServicoBancario {

  public void realizarTransferencia(Conta origem, Conta destino, BigDecimal valor) {
      // Defesa contra entradas nulas
      if (origem == null || destino == null || valor == null) {
          throw new IllegalArgumentException("Contas e valor não podem ser nulos.");
      }

      // Defesa contra valores monetários inválidos
      if (valor.compareTo(BigDecimal.ZERO) <= 0) {
          throw new IllegalArgumentException("O valor da transferência deve ser positivo.");
      }

      // Defesa contra estado inconsistente (saldo insuficiente)
      if (origem.getSaldo().compareTo(valor) < 0) {
          throw new SaldoInsuficienteException("Saldo insuficiente na conta de origem.");
      }

      // Lógica de negócio segura após as validações
      origem.sacar(valor);
      destino.depositar(valor);
      System.out.println("Transferência de " + valor + " realizada com sucesso!");
  }
}

2. Imutabilidade: Protegendo Dados Sensíveis

Objetos imutáveis são aqueles cujo estado não pode ser modificado após sua criação. Isso é extremamente poderoso em sistemas financeiros, pois garante que uma transação, uma vez criada, não possa ser alterada acidentalmente.

Exemplo: Uma classe Transacao imutável.

// Classe Transacao imutável
public final class Transacao {

  private final BigDecimal valor;
  private final LocalDateTime data;
  private final String tipo; // "DEBITO", "CREDITO"

  public Transacao(BigDecimal valor, String tipo) {
      // Validação no construtor
      if (valor == null || valor.compareTo(BigDecimal.ZERO) <= 0) {
          throw new IllegalArgumentException("Valor da transação deve ser positivo.");
      }
      this.valor = valor;
      this.tipo = tipo;
      this.data = LocalDateTime.now();
  }

  // Apenas getters, sem setters
  public BigDecimal getValor() {
      return valor;
  }

  public LocalDateTime getData() {
      return data;
  }

  public String getTipo() {
      return tipo;
  }
}

Com essa estrutura, você tem a garantia de que um objeto Transacao representa um fato imutável no tempo, crucial para auditorias e registros.

3. Uso de Optional para Evitar o "Erro de Um Bilhão de Dólares"

O NullPointerException (NPE) é talvez o erro mais comum em Java. O Optional, introduzido no Java 8, é uma ferramenta fantástica para tratar a ausência de um valor de forma explícita e segura.

Exemplo: Buscar um cliente no banco de dados.

public class RepositorioCliente {
  
  // Simula um banco de dados
  private final Map<String, Cliente> clientes = new HashMap<>();

  public Optional<Cliente> buscarPorCpf(String cpf) {
      // Retorna um Optional, deixando explícito que o cliente pode não existir
      return Optional.ofNullable(clientes.get(cpf));
  }
}

// Uso seguro no serviço
public void exibirSaldoCliente(String cpf) {
  RepositorioCliente repositorio = new RepositorioCliente();
  
  // Abordagem defensiva com Optional
  repositorio.buscarPorCpf(cpf)
      .ifPresentOrElse(
          cliente -> System.out.println("Saldo de " + cliente.getNome() + ": " + cliente.getConta().getSaldo()),
          () -> System.out.println("Cliente com CPF " + cpf + " não encontrado.")
      );
}

Essa abordagem força quem chama o método a pensar sobre o caso em que o cliente não existe, eliminando a chance de um NPE.

4. Tratamento de Exceções Robusto

Exceções não são apenas para erros. Elas são o mecanismo de Java para lidar com situações excepcionais. Em programação defensiva, usamos try-catch-finally e try-with-resources para garantir que o sistema permaneça em um estado consistente.

Exemplo: Processar um arquivo de extrato bancário.

public void processarExtrato(String caminhoArquivo) {
  // try-with-resources garante que o reader será fechado, mesmo se ocorrer um erro.
  try (BufferedReader reader = new BufferedReader(new FileReader(caminhoArquivo))) {
      String linha;
      while ((linha = reader.readLine()) != null) {
          // Processa cada linha da transação
          System.out.println("Processando: " + linha);
      }
  } catch (FileNotFoundException e) {
      // Trata o caso específico do arquivo não existir
      System.err.println("Erro: Arquivo de extrato não encontrado em " + caminhoArquivo);
      // Logar o erro
  } catch (IOException e) {
      // Trata outros erros de leitura
      System.err.println("Erro ao ler o arquivo de extrato: " + e.getMessage());
      // Logar o erro e talvez notificar um administrador
  }
}

Refatorando para a Defesa: Uma Classe ContaBancaria

Vamos ver como esses conceitos se unem. Considere a seguinte classe ContaBancaria vulnerável:

Versão Inicial (Frágil):

public class ContaBancaria {
  public double saldo; // Atributo público!

  public void depositar(double valor) {
      this.saldo += valor;
  }

  public void sacar(double valor) {
      this.saldo -= valor;
  }
}

Esta classe é um desastre esperando para acontecer:

  1. saldo é público e pode ser alterado para qualquer valor.
  2. Usa double para dinheiro, o que causa problemas de arredondamento.
  3. Permite depósitos negativos e saques que deixam o saldo negativo.

Versão Refatorada (Defensiva):

public final class ContaBancaria { // Final para evitar herança maliciosa
  
  private BigDecimal saldo; // Privado e usando BigDecimal

  public ContaBancaria() {
      this.saldo = BigDecimal.ZERO;
  }

  public BigDecimal getSaldo() {
      return this.saldo;
  }

  public void depositar(BigDecimal valor) {
      if (valor == null || valor.compareTo(BigDecimal.ZERO) <= 0) {
          throw new IllegalArgumentException("Valor de depósito deve ser positivo.");
      }
      this.saldo = this.saldo.add(valor);
  }

  public void sacar(BigDecimal valor) {
      if (valor == null || valor.compareTo(BigDecimal.ZERO) <= 0) {
          throw new IllegalArgumentException("Valor de saque deve ser positivo.");
      }
      if (this.saldo.compareTo(valor) < 0) {
          throw new SaldoInsuficienteException("Saldo insuficiente para realizar o saque.");
      }
      this.saldo = this.saldo.subtract(valor);
  }
}

A versão refatorada é infinitamente mais segura e representa um modelo de como os fundamentos do Java (encapsulamento, tipos de dados corretos, exceções) são a base da programação defensiva.

Um Mindset, Não Apenas Código

Programação defensiva é mais do que um conjunto de técnicas; é uma mentalidade. É o compromisso de escrever código que não apenas funciona no "caminho feliz", mas que também se comporta de maneira previsível e segura diante do caos.

Ao aplicar validações rigorosas, garantir a imutabilidade, tratar a ausência de valores com Optional e gerenciar exceções de forma robusta, você transforma seu código Java de uma construção frágil em uma verdadeira fortaleza digital. No mundo financeiro, essa é a diferença entre um sistema confiável e um passivo arriscado.

Mas e você?

O que você achou dessas técnicas? Você utiliza outras abordagens de programação defensiva em seus projetos Java? Deixe um comentário abaixo e compartilhe suas experiências!

Referências:

Compartilhe
Comentários (3)
Júlio Siqueira
Júlio Siqueira - 17/10/2025 12:06

Bom dia, Guilherme!

Excelente Artigo, nada melhor do que mergulhar em artigos que ampliam nossa visão sobre tecnologia, boas práticas no desenvolvimento. 💻✨

Cada leitura é uma nova oportunidade de entender melhor o universo da TI, trocar ideias com outros devs e evoluir juntos como comunidade.

DIO Community
DIO Community - 17/10/2025 09:01

Excelente, Guilherme! Que artigo incrível e super completo sobre Programação Defensiva em Java! É fascinante ver como você aborda o tema, mostrando que a Programação Defensiva não é um luxo, mas uma filosofia de design que visa garantir o comportamento previsível de um programa, mesmo sob condições inesperadas.

Você demonstrou as 4 técnicas fundamentais para blindar o código (especialmente em aplicações financeiras): Validação de Entradas (lançando IllegalArgumentException), Imutabilidade (usando final e BigDecimal para dinheiro), Uso de Optional (para evitar o temido NullPointerException) e Tratamento de Exceções Robusto (com try-with-resources).

Qual você diria que é o maior desafio para um desenvolvedor ao trabalhar com um projeto que usa o padrão MVC, em termos de manter a separação de responsabilidades e de evitar o acoplamento entre as três camadas, em vez de apenas focar em fazer a aplicação funcionar?

Edmundo Batista
Edmundo Batista - 16/10/2025 20:56

Boa noite Guilhereme!

Excelente artigo!

Obrigado por compartilhar!