Em muitos programas, especialmente nos primeiros contatos com orientação a objetos, é comum surgir a dúvida: onde devem ficar as regras de validação? Na aplicação? Dentro da classe de domínio? Neste post, você vai entender, usando um exemplo prático, como o tratamento de exceções com try e catch em Java ajuda a organizar responsabilidades, tornar o código mais claro e reforçar os princípios de encapsulamento, coesão, e delegação na construção de sistemas.
O problema: cálculo de financiamento com regras de negócio
Os dados de um financiamento são:
- Valor total do financiamento.
- Valor pago na entrada.
- Número de parcelas.
Deseja-se fazer um programa que, informados os dados de um financiamento, mostre quanto é o valor de cada prestação do financiamento. O valor da prestação é o valor total menos o valor de entrada, dividido pelo número de parcelas.
Exemplo:
Entrada:
- Valor total = 1000
- Valor da entrada = 300
- Quantidade de prestações = 10
Saída = 70 (pois descontando 300 de entrada ao valor total de 1000, sobra 700, que dividido em 10 prestações resulta em parcelas de 70 cada)
O programa ainda deve verificar as seguintes regras, informando uma mensagem de erro caso alguma regra seja violada:
- O valor da entrada deve ser pelo menos 20% do valor total do financiamento.
- O número mínimo de parcelas deve ser 6.
Aqui surge uma pergunta importante: onde essas validações devem ficar? Na aplicação? Ou dentro do próprio objeto de domínio?
A solução ruim: validação espalhada na aplicação
Primeiro, vejamos uma implementação possível.
public class Financiamento {
public Double valorTotal;
public Double entrada;
public Integer parcelas;
public Financiamento(double valorTotal, double entrada, int parcelas) {
this.valorTotal = valorTotal;
this.entrada = entrada;
this.parcelas = parcelas;
}
public double prestacao() {
return (valorTotal - entrada) / parcelas;
}
}public class ProgramaPrincipal {
public static void main(String[] args) {
double valorTotal = 1000.0;
double entrada = 300.0;
int parcelas = 10;
if (entrada < valorTotal * 0.2) {
System.out.println("A entrada deve ser pelo menos 20% do valor total");
}
else if (parcelas < 6) {
System.out.println("O número mínimo de parcelas deve ser 6");
}
else {
Financiamento f = new Financiamento(valorTotal, entrada, parcelas);
System.out.println(f.prestacao());
}
}
}Por que essa solução é ruim?
Existem alguns problemas importantes aqui:
- A lógica de validação está na aplicação (ProgramaPrincipal) e não no domínio (Financiamento).
- O objeto Financiamento pode ser criado em estado inválido se alguém esquecer de validar.
- O uso de if-else encadeados não é a forma mais clara e escalável de expressar regras de negócio.
- A responsabilidade está espalhada: parte da regra está fora da classe que representa o conceito.
Em orientação a objetos, o objeto deve ser responsável por manter sua própria consistência. Se um financiamento não pode existir com entrada menor que 20% ou com menos de 6 parcelas, então essa regra pertence ao próprio financiamento.
A solução boa: validação no domínio com exceções
Agora veja uma abordagem mais adequada.
public class Financiamento {
public Double valorTotal;
public Double entrada;
public Integer parcelas;
public Financiamento(double valorTotal, double entrada, int parcelas) {
if (entrada < valorTotal * 0.2) {
throw new RuntimeException("A entrada deve ser pelo menos 20% do valor total");
}
if (parcelas < 6) {
throw new RuntimeException("O número mínimo de parcelas deve ser 6");
}
this.valorTotal = valorTotal;
this.entrada = entrada;
this.parcelas = parcelas;
}
public double prestacao() {
return (valorTotal - entrada) / parcelas;
}
}public class ProgramaPrincipal {
public static void main(String[] args) {
double valorTotal = 1000.0;
double entrada = 300.0;
int parcelas = 10;
try {
Financiamento f = new Financiamento(valorTotal, entrada, parcelas);
System.out.println(f.prestacao());
}
catch (RuntimeException e) {
System.out.println(e.getMessage());
}
}
}Por que essa solução é melhor?
Agora temos melhorias importantes:
- O próprio objeto Financiamento garante sua consistência.
- Não é possível criar um financiamento inválido.
- A aplicação apenas tenta usar o objeto e trata possíveis erros.
- As regras de negócio estão centralizadas no domínio.
Essa é uma aplicação prática de encapsulamento: o objeto protege seu próprio estado.
Entendendo o papel do try e catch
Quando usamos throw, estamos interrompendo o fluxo normal do programa e sinalizando que algo deu errado.
O bloco try indica que estamos executando um trecho de código que pode gerar uma exceção.
O bloco catch captura essa exceção e permite que tratemos o erro de forma controlada.
No nosso exemplo:
- Se os dados forem válidos, o financiamento é criado e a prestação é exibida.
- Se alguma regra for violada, a exceção é lançada.
- O
catchintercepta a exceção e exibe a mensagem apropriada.
Esse mecanismo separa claramente:
- Regra de negócio (domínio)
- Tratamento de erro (camada de aplicação)
Assista a aula em vídeo com o prof. Dr. Nelio Alves no Youtube:
Conclusão: exceções como ferramenta de modelagem
Tratamento de exceções em Java não serve apenas para evitar que o programa "quebre". Ele é uma ferramenta de modelagem.
Ao lançar exceções dentro do domínio, estamos dizendo:
"Este objeto não pode existir nesse estado."
Isso torna o código:
- Mais seguro
- Mais organizado
- Mais alinhado com os princípios de orientação a objetos
- Mais fácil de evoluir
Entender try, catch e throw é entender como proteger a integridade do seu modelo de negócio. E essa é uma das diferenças fundamentais entre código que apenas funciona e código bem projetado.
