Werden Exceptions geworfen, so sollten für die Fehlerauswertung möglichst viele (ausreichende) Informationen zur Verfügung stehen.
Es muss nachvollziebar sein, wo und unter welchen Umständen (Kontext) ein Fehler auftrat. Mit einem einfachen throw new RuntimeException("Eine
Meldung")
ist das in der Regel nicht zu erreichen.
Ferner ist eine Einheitlichkeit des Fehlerhandlings über die Anwendung anzutreben. Damit wird es auch einfacher, Fehler automatisiert aufzuspühren und auszuwerten.
Siehe auch “Maximize Human Participation” from “Patterns for Fault Tolerant Software” (https://www.oreilly.com/library/view/patterns-for-fault/9780470319796/)
In der Regel werden unchecked Exceptions verwendet. Checkt Exceptions werden nur in bestimmten Ausnahmefällen verwendet. Diese können sein:
Vergleiche auch “Effective Java 3rd Edition”:
@Transactional
Annotation von spring-tx und ejb behandeln checked und uncheked Exceptions unterschiedlich
Spezielle Exceptions bzw. eine Hierarchie von Exceptions werden nur dann erzeugt, wenn diese Exceptions eine spezielle Behandlung
in einen catch
Block haben sollten / könnten.
In der Regel werden beim Erzeugen oder catch / rethrow von Exceptions keine Logausgaben gemacht. Logausgaben werden gemacht, wenn eine Exception gefangen und nicht weitergeworfen wird. Dies macht meiste der übergeordnete Exceptionhandler.
In der Regel wird eine ContextedRuntimeException
mit individuellem Errorkode verwendet. Dies hat folgende Vorteile:
Errorkodes sind über die Anwendung eindeutig. Sie setzten sich aus einem modul-spezifischem Präfix und einem modul-eindeutigem
Kode zusammen, z.B. MYMODULE_ABCX1
. Die Errorkodes werden in einem modulspezifischem enum
im root-Package des Moduls definiert.
Neu erzeugte und geworfene ContextedRuntimeException
sollten ausreichend Kontextinformationen für Fehlerauswertung bereitstellen. Mit
addContextValue
kann man beliebig viele Kontextwerte ergänzen.
throw new ContextedRuntimeException(MY_ERROR_CODE)
.addContextValue("myLabel", myValue)
.addContextValue("otherLabel", otherValue);
Mittels catch
und throw
kann man zu vorhandenen ContextedRuntimeException
zusätzliche Kontextwerte ergänzen. Dies
kann an solchen Stellen nütlich sein, an denen für die Fehlerauswertung relevante Informationen zur Verfügung stehen.
try {
...
catch (ContextedRuntimeException e) {
e.addContextValue("key", myValue);
throw e;
}
oder etwas kompakter:
try {
...
catch (ContextedRuntimeException e) {
throw e.addContextValue("key", myValue);
}
Exception Wrapping sollte weitgehend vermieden weden. Speziell bei mehrfachem Exception Wrapping wird der Stacktrace sehr unübersichtlich, teilweise werden wichtige Informationen abgeschnitten. Die ursprüngliche Exception ist nicht mehr auf den ersten Blick identifizierbar.
Bei ContextedRuntimeException
ist Exception Enhancement zu bevorzugen.
Lokales Error Handling findet statt, wenn an der entsprechenden Stelle die Exception korrekt behandelt werden kann.
Beim Übergang an Schnittstellen bei denen es nicht vorgesehen ist, dass Exception geworfen werden, muß ein Exception Handling stattfinden.
Beispiele
Bei einem Rest-Aufruf wird im Falle eines Fehlers folgende Header zurückgeliefert:
X-HEADER-ERROR-CODES
: Liste der ErrorkodesX-HEADER_CID
: Eine Korrelation IDDer Spring RestControllerAdvice
RestExceptionHandler
verarbeitet ContextedRuntimeException
und setzt die entsprechenden
Header.
Hinweis: Zum Handling von Spring MVC Exceptions benutzt man eine Subklasse von org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
.
Details dazu siehe Javadoc der Klasse.