Introduction à NoException

Introduction à NoException

1. Vue d'ensemble

Parfois, les blocstry/catch peuvent entraîner des constructions de code verbeuses ou même maladroites.

Dans cet article, nous allons nous concentrer surNoException which provides concise and handy exception handlers.

2. Dépendance Maven

Ajoutons lesNoException à nospom.xml:


    com.machinezoo.noexception
    noexception
    1.1.0

3. Gestion des exceptions standard

Commençons par un langage courant:

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);
    }
}

Nous commençons par allouer unLogger puis en entrant un bloctry. Si unException est lancé, nous le consignons:

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. Gestion des exceptions avecNoException

4.1. Gestionnaire de journalisation par défaut

Remplaçons cela par le gestionnaire d'exceptions standard deNoException:

@Test
public void whenDefaultNoException_thenCatchAndLog() {
    Exceptions
      .log()
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

Ce code nous donne presque la même sortie que ci-dessus:

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. Il exécute le lambda que nous transmettons àrun(), et si unException est lancé, il est enregistré.

4.2. Ajout d'unLogger personnalisé

Si nous examinons attentivement la sortie, nous constatons que les exceptions sont consignées en tant que classe de journalisation, au lieu de la nôtre.

Nous pouvons résoudre ce problème en fournissant à notre enregistreur:

@Test
public void whenDefaultNoException_thenCatchAndLogWithClassName() {
    Exceptions
      .log(logger)
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

Ce qui nous donne cette sortie:

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. Fourniture d'un message de journal personnalisé

Nous souhaitons peut-être utiliser un message différent de celui par défaut «Exception interceptée». 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")));
}

Ce qui nous donne cette sortie:

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)
...

Mais que se passe-t-il si nous voulons faire plus que simplement enregistrerExceptions, comme insérer une valeur de secours lorsqueparseInt() échoue?

4.4. Spécification d'une valeur par défaut

Exceptions peut renvoyer un résultat encapsulé dans unOptional. Déplaçons les choses afin que nous puissions l'utiliser pour fournir une valeur par défaut en cas d'échec de la cible:

@Test
public void
  givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
    System.out.println("Result is " + Exceptions
      .log(logger, "Something went wrong:")
      .get(() -> Integer.parseInt("foobar"))
      .orElse(-1));
}

Nous voyons toujours nosException:

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)
...

Mais nous voyons aussi notre message imprimé sur la console:

Result is -1

5. Création d'un gestionnaire de journalisation personnalisé

Jusqu'à présent, nous avons une méthode intéressante pour éviter les répétitions et rendre le code plus lisible dans de simples scénariostry/catch/log. Que faire si nous voulons réutiliser un gestionnaire avec un comportement différent?

Étendons la classeExceptionHandler deNoException et exécutons l'une des deux opérations suivantes en fonction du type d'exception:

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;
        }
    }
}

En renvoyantfalse lorsque nous voyons unError ou unRuntimeException, nous disons àExceptionHandler de relancer. En renvoyanttrue pour tout le reste, nous indiquons que l'exception a été gérée.

Tout d'abord, nous allons exécuter ceci avec une exception standard:

@Test
public void givenCustomHandler_whenError_thenRethrowError() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> "foo".charAt(5));
}

Nous passons notre fonction à la méthoderun() dans notre gestionnaire personnalisé hérité 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)

Cette exception est enregistrée. Essayons avec unError:

@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.");
}

Et nous voyons que leError a été relancé dansmain(), plutôt que journalisé:

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)

Nous avons donc une classe réutilisable qui peut être utilisée dans tout un projet pour une gestion cohérente des exceptions.

6. Conclusion

AvecNoException, nous pouvons simplifier la gestion des exceptions au cas par cas, avec une seule ligne de code.

Le code se trouve dansthis GitHub project.