Como encontrar a causa raiz de uma exceção em Java
1. Introdução
É muito comum em Java trabalhar com exceções aninhadas, pois elas podem nos ajudar a rastrear a origem de um erro.
Quando lidamos com esses tipos de exceções, às vezeswe may want to know the original problem that caused the exception so our application can respond differently for each case. Isso é especialmente útil quando trabalhamos com estruturas que agrupam as exceções de raiz em suas próprias.
Neste breve artigo, mostraremos como obter a exceção de causa raiz usando Java simples, bem como bibliotecas externas, comoApache Commons LangeGoogle Guava.
2. Um aplicativo de calculadora de idade
Nosso aplicativo será uma calculadora de idade que nos informa quantos anos uma pessoa tem a partir de uma determinada data recebida comoString no formato ISO. Lidaremos com 2 casos de erro possíveis ao analisar a data: uma data mal formatada e uma data futura.
Vamos primeiro criar exceções para nossos casos de erro:
static class InvalidFormatException extends DateParseException {
InvalidFormatException(String input, Throwable thr) {
super("Invalid date format: " + input, thr);
}
}
static class DateOutOfRangeException extends DateParseException {
DateOutOfRangeException(String date) {
super("Date out of range: " + date);
}
}
Ambas as exceções herdam de uma exceção pai comum que tornará nosso código um pouco mais claro:
static class DateParseException extends RuntimeException {
DateParseException(String input) {
super(input);
}
DateParseException(String input, Throwable thr) {
super(input, thr);
}
}
Depois disso, podemos implementar a classeAgeCalculator com um método para analisar a data:
static class AgeCalculator {
private static LocalDate parseDate(String birthDateAsString) {
LocalDate birthDate;
try {
birthDate = LocalDate.parse(birthDateAsString);
} catch (DateTimeParseException ex) {
throw new InvalidFormatException(birthDateAsString, ex);
}
if (birthDate.isAfter(LocalDate.now())) {
throw new DateOutOfRangeException(birthDateAsString);
}
return birthDate;
}
}
Como podemos ver, quando o formato está errado, envolvemos oDateTimeParseException em nossoInvalidFormatException. personalizado
Por fim, vamos adicionar um método público à nossa classe que recebe a data, analisa-a e calcula a idade:
public static int calculateAge(String birthDate) {
if (birthDate == null || birthDate.isEmpty()) {
throw new IllegalArgumentException();
}
try {
return Period
.between(parseDate(birthDate), LocalDate.now())
.getYears();
} catch (DateParseException ex) {
throw new CalculationException(ex);
}
}
Conforme mostrado, estamos envolvendo as exceções novamente. Neste caso, nós os envolvemos em umCalculationException que devemos criar:
static class CalculationException extends RuntimeException {
CalculationException(DateParseException ex) {
super(ex);
}
}
Agora, estamos prontos para usar nossa calculadora, passando-a qualquer data no formato ISO:
AgeCalculator.calculateAge("2019-10-01");
E se o cálculo falhar, seria útil saber qual era o problema, não é? Continue lendo para descobrir como podemos fazer isso.
3. Encontre a causa raiz usando o Java simples
A primeira maneira que usaremos para encontrar a exceção de causa raiz é porcreating a custom method that loops through all the causes until it reaches the root:
public static Throwable findCauseUsingPlainJava(Throwable throwable) {
Objects.requireNonNull(throwable);
Throwable rootCause = throwable;
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
rootCause = rootCause.getCause();
}
return rootCause;
}
Observe que adicionamos uma condição extra em nosso loop para evitar loops infinitos ao lidar com causas recursivas.
Se passarmos um formato inválido para nossoAgeCalculator, obteremosDateTimeParseException como a causa raiz:
try {
AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
assertTrue(findCauseUsingPlainJava(ex) instanceof DateTimeParseException);
}
No entanto, se usarmos uma data futura, obteremos umDateOutOfRangeException:
try {
AgeCalculator.calculateAge("2020-04-04");
} catch (CalculationException ex) {
assertTrue(findCauseUsingPlainJava(ex) instanceof DateOutOfRangeException);
}
Além disso, nosso método também funciona para exceções não aninhadas:
try {
AgeCalculator.calculateAge(null);
} catch (Exception ex) {
assertTrue(findCauseUsingPlainJava(ex) instanceof IllegalArgumentException);
}
Nesse caso, obtemos umIllegalArgumentException, pois passamosnull.
4. Encontre a causa raiz usando Apache Commons Lang
Agora vamos demonstrar como encontrar a causa raiz usando bibliotecas de terceiros em vez de escrever nossa implementação personalizada.
Apache Commons Lang fornece uma classeExceptionUtils que fornece alguns métodos utilitários para trabalhar com exceções.
Usaremos o métodogetRootCause() com nosso exemplo anterior:
try {
AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
assertTrue(ExceptionUtils.getRootCause(ex) instanceof DateTimeParseException);
}
Temos a mesma causa raiz de antes. O mesmo comportamento se aplica aos outros exemplos que listamos acima.
5. Encontre a causa raiz usando goiaba
A última maneira que vamos tentar é usando Guava. Semelhante ao Apache Commons Lang, ele fornece uma classeThrowables com um método utilitáriogetRootCause().
Vamos tentar com o mesmo exemplo:
try {
AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
assertTrue(Throwables.getRootCause(ex) instanceof DateTimeParseException);
}
O comportamento é exatamente o mesmo que nos outros métodos.
6. Conclusão
Neste artigo, demonstramos como usar exceções aninhadas em nosso aplicativo e implementamos um método utilitário para encontrar a exceção de causa raiz.
Também mostramos como fazer o mesmo usando bibliotecas de terceiros, como Apache Commons Lang e Google Guava.
Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.