Introdução ao NoException
1. Visão geral
Às vezes, os blocostry/catch podem resultar em construções de código prolixo ou até mesmo estranhas.
Neste artigo, vamos nos concentrar emNoException which provides concise and handy exception handlers.
2. Dependência do Maven
Vamos adicionarNoException ao nossopom.xml:
com.machinezoo.noexception
noexception
1.1.0
3. Tratamento de exceções padrão
Vamos começar com um idioma comum:
private static Logger logger = LoggerFactory.getLogger(NoExceptionUnitTest.class);
@Test
public void whenStdExceptionHandling_thenCatchAndLog() {
try {
logger.info("Result is " + Integer.parseInt("foobar"));
} catch (Throwable exception) {
logger.error("Caught exception:", exception);
}
}
Começamos alocando umLogger e, em seguida, inserindo um blocotry. Se umException for lançado, nós o registramos:
09:29:28.140 [main] ERROR c.b.n.NoExceptionUnitTest
- Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
4. Lidando com exceções comNoException
4.1. Manipulador de registro padrão
Vamos substituir isso pelo manipulador de exceção padrão deNoException:
@Test
public void whenDefaultNoException_thenCatchAndLog() {
Exceptions
.log()
.run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}
Este código nos fornece quase a mesma saída que acima:
09:36:04.461 [main] ERROR c.m.n.Exceptions
- Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
In its most basic form, NoException provides us with a way to replace try/catch/ exceptions with a single line of code. Ele executa o lambda que passamos pararun(), e se umException for lançado, ele será registrado.
4.2. Adicionando umLogger personalizado
Se observarmos atentamente a saída, veremos que as exceções são registradas como a classe de log, em vez da nossa.
Podemos consertar isso, fornecendo ao nosso logger:
@Test
public void whenDefaultNoException_thenCatchAndLogWithClassName() {
Exceptions
.log(logger)
.run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}
O que nos dá essa saída:
09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest
- Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
4.3. Fornecimento de uma mensagem de registro personalizada
Podemos querer usar uma mensagem diferente da "Exceção Pegada" padrão. We can do this by passing a Logger as the first argument and a String message as the second:
@Test
public void whenDefaultNoException_thenCatchAndLogWithMessage() {
Exceptions
.log(logger, "Something went wrong:")
.run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}
O que nos dá essa saída:
09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest
- Something went wrong:
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
Mas e se quisermos fazer mais do que apenas logExceptions, como inserir um valor de fallback quandoparseInt() falhar?
4.4. Especificando um valor padrão
Exceptions pode retornar um resultado agrupado em umOptional. Vamos mover as coisas para que possamos usá-lo para fornecer um valor padrão se o alvo falhar:
@Test
public void
givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
System.out.println("Result is " + Exceptions
.log(logger, "Something went wrong:")
.get(() -> Integer.parseInt("foobar"))
.orElse(-1));
}
Ainda vemos nossoException:
12:02:26.388 [main] ERROR c.b.n.NoExceptionUnitTest
- Caught exception java.lang.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
Mas também vemos nossa mensagem impressa no console:
Result is -1
5. Criação de um gerenciador de registro personalizado
Até agora, temos um bom método para evitar a repetição e tornar o código mais legível em cenáriostry/catch/log simples. E se quisermos reutilizar um manipulador com um comportamento diferente?
Vamos estender a classeNoException'sExceptionHandler e realizar uma das duas coisas, dependendo do tipo de exceção:
public class CustomExceptionHandler extends ExceptionHandler {
Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
@Override
public boolean handle(Throwable throwable) {
if (throwable.getClass().isAssignableFrom(RuntimeException.class)
|| throwable.getClass().isAssignableFrom(Error.class)) {
return false;
} else {
logger.error("Caught Exception", throwable);
return true;
}
}
}
Ao retornarfalse quando vemos umError ou umRuntimeException, estamos dizendo aExceptionHandler para lançar novamente. Ao retornartrue para todo o resto, indicamos que a exceção foi tratada.
Primeiro, vamos executar isso com uma exceção padrão:
@Test
public void givenCustomHandler_whenError_thenRethrowError() {
CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
customExceptionHandler.run(() -> "foo".charAt(5));
}
Passamos nossa função para o métodorun() em nosso manipulador personalizado herdado deExceptionHandler:
18:35:26.374 [main] ERROR c.b.n.CustomExceptionHandler
- Caught Exception
j.l.StringIndexOutOfBoundsException: String index out of range: 5
at j.l.String.charAt(String.java:658)
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:20)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:10)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
at c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:10)
Esta exceção é registrada. Vamos tentar com umError:
@Test(expected = Error.class)
public void givenCustomHandler_whenException_thenCatchAndLog() {
CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
customExceptionHandler.run(() -> throwError());
}
private static void throwError() {
throw new Error("This is very bad.");
}
E vemos queError foi relançado emmain(), em vez de registrado:
Exception in thread "main" java.lang.Error: This is very bad.
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:15)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:8)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
t c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:8)
Portanto, temos uma classe reutilizável que pode ser usada em todo o projeto para um tratamento consistente de exceções.
6. Conclusão
ComNoException, podemos simplificar o tratamento de exceções caso a caso, com uma única linha de código.
O código pode ser encontrado emthis GitHub project.