Einführung in NoException

Einführung in NoException

1. Überblick

Manchmal könnentry/catch-Blöcke zu ausführlichen oder sogar umständlichen Codekonstrukten führen.

In diesem Artikel konzentrieren wir uns aufNoException which provides concise and handy exception handlers.

2. Maven-Abhängigkeit

Fügen wir dieNoException zu unserenpom.xml hinzu:


    com.machinezoo.noexception
    noexception
    1.1.0

3. Standard-Ausnahmebehandlung

Beginnen wir mit einer allgemein verbreiteten Redewendung:

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

Wir beginnen mit der Zuweisung vonLogger und geben dann einentry-Block ein. Wenn einException geworfen wird, protokollieren wir es:

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. Behandlung von Ausnahmen mitNoException

4.1. Standard-Protokollierungshandler

Ersetzen wir dies durch den Standard-Ausnahmebehandler vonNoException:

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

Mit diesem Code erhalten wir fast die gleiche Ausgabe wie oben:

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. Es führt das Lambda aus, das wir anrun() übergeben, und wenn einException geworfen wird, wird es protokolliert.

4.2. Hinzufügen eines benutzerdefiniertenLogger

Wenn wir uns die Ausgabe genau ansehen, stellen wir fest, dass Ausnahmen anstelle unserer als Protokollierungsklasse protokolliert werden.

Wir können das beheben, indem wir unseren Logger bereitstellen:

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

Was gibt uns diese Ausgabe:

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. Bereitstellen einer benutzerdefinierten Protokollnachricht

Möglicherweise möchten wir eine andere Nachricht als die Standardnachricht "Gefangene Ausnahme" verwenden. 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")));
}

Was gibt uns diese Ausgabe:

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

Was aber, wenn wir mehr als nurExceptions protokollieren möchten, z. B. einen Fallback-Wert einfügen, wennparseInt() fehlschlägt?

4.4. Angeben eines Standardwerts

Exceptions kann ein inOptional eingeschlossenes Ergebnis zurückgeben. Verschieben wir die Dinge, damit wir einen Standardwert bereitstellen können, wenn das Ziel fehlschlägt:

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

Wir sehen immer noch unsereException:

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

Wir sehen aber auch unsere Nachricht auf der Konsole:

Result is -1

5. Erstellen eines benutzerdefinierten Protokollierungshandlers

Bisher haben wir eine gute Methode, um Wiederholungen zu vermeiden und Code in einfachentry/catch/log-Szenarien besser lesbar zu machen. Was ist, wenn wir einen Handler mit einem anderen Verhalten wiederverwenden möchten?

Erweitern wir die KlasseNoException ErweiternsExceptionHandler und führen je nach Ausnahmetyp eines von zwei Dingen aus:

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

Indem wirfalse zurückgeben, wenn wirError oderRuntimeException sehen, weisen wirExceptionHandler an, erneut zu werfen. Indem wirtrue für alles andere zurückgeben, geben wir an, dass die Ausnahme behandelt wurde.

Zunächst führen wir dies mit einer Standardausnahme aus:

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

Wir übergeben unsere Funktion an die Methoderun() in unserem benutzerdefinierten Handler, der vonExceptionHandler: geerbt wurde

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)

Diese Ausnahme wird protokolliert. Versuchen wir es mit einemError:

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

Und wir sehen, dassError erneut inmain() geworfen und nicht protokolliert wurde:

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)

Wir haben also eine wiederverwendbare Klasse, die für die konsistente Ausnahmebehandlung in einem gesamten Projekt verwendet werden kann.

6. Fazit

MitNoException können wir die Ausnahmebehandlung von Fall zu Fall mit einer einzigen Codezeile vereinfachen.

Der Code ist inthis GitHub project angegeben.