Mit Hilfe von Exceptions robuste Software für stabilen Betrieb schreiben

Keine Kommentare

Eine Studie zeigt, dass die Ursache für nahezu alle kritischen Fehler eine unsachgemäße Fehlerbehandlung ist. Dies kann ich aus meinen eigenen Erfahrungen heraus und für mehrere Projekte bestätigen: die gewünschte Funktionalität wurde implementiert und die Tests überprüfen die Korrektheit der Implementierung. In unterschiedlichem Maße sind negative Testfälle (ungültige Benutzereingaben, erwartete Datei nicht gefunden, …) vorhanden. Doch was ist mit Fehlern (Problem beim Dateizugriff, es existiert schon eine Zeile mit demselben Primärschlüssel, fehlgeschlagene Validierung von XML Schemata, …)? Selten sehe ich solche Testfälle. Wenn während der Test- oder Produktionsphase solche Probleme auftreten UND genügend Informationen zum Verständnis vorliegen UND der Fehler reproduzierbar ist, werden wahrscheinlich Testfälle für diese Probleme aufgenommen.

In diesem Artikel möchte ich darstellen, warum Fehlerbehandlung wichtig ist und wie man vorgehen sollte, aber auch wie man nicht vorgehen sollte. Im Text wird Java zur Demonstration benutzt, aber die Konzepte sind sprachunabhängig.

tl;dr: Benutze Exceptions, sie bieten Vorteile (scheitern schnell und keine Gedanken an den Rückgabewert im Fehlerfall notwendig). Vermeide doppeltes Logging. Beschreibe in Log-Meldungen, was als nächstes passieren wird. Manchmal ist es besser einen null-Wert, welcher ein Problemindikator ist, durch eine Exception zu ersetzten.

Motivation

Wir als Entwickler schreiben Software. Die geforderten Funktionen und Änderungen werden implementiert und irgendwann kommt die Veröffentlichung und damit der Kontakt der Software mit der realen Welt. Die reale Welt ist chaotisch. Zunächst unterscheidet sich die technische Umgebung vom Entwickler PC oder des Continuous Integration (CI) Servers. Dies kann mit Werkzeugen wie puppet reduziert werden, trotzdem gibt es Unterschiede zwischen einem 4 Knoten Cluster und einem 400 Knoten Cluster. Hier nicht zu vergessen ist Software die auf den Computern der Benutzer (z.B. Desktop-Anwendungen) und nicht auf Servern des Softwareunternehmens (Web-Anwendungen) läuft. Der zweite Grund ist, dass die Benutzer deutlich kreativer bei den Eingaben sind, als sich dies das Entwicklungsteam (Product-Owner, Testabteilung, Entwickler) vorgestellt hat. So verarbeitet die Software vielleicht einige unbekannte Eingaben richtig oder vielleicht auch nicht richtig. Der gesamte Raum aller Eingabeparameter ist riesig.

Die Idee ist, solche Probleme so schnell wie möglich zu finden. In der Regel werden die Probleme durch technische Tests (z.B. Performance-Tests auf einem Setup, dass ähnlich dem Produktionssystem ist) oder mit explorativen Tests mit einem erfahrenen Tester gefunden. Es ist auch möglich, die Anzahl und den Zugriff der Benutzer auf die Software zu reduzieren und zu steuern. Zwei übliche Varianten sind Pilotanwender, welche die neue unveröffentlichte Version nutzen möchten oder aber das Vorgehen, dass eine geringe Menge an Anfragen auf die neue Version geleitet werden (mit oder ohne Unterrichtung der Benutzer), gekoppelt mit einer engen Überwachung der neuen Software-Version.

Was ist die Verbindung zur Fehlerbehandlung? Fehler sind einerseits Reaktionen auf nicht unterstützte Eingabeparameter oder eine Laufzeitumgebung, welche bestimmte Annahmen verletzt. Üblicherweise unterstützen Programmiersprachen die Erstellung solcher Fehler und die Reaktion darauf mit Hilfe von Exceptions (Ausnahmen). Exceptions sind für Programmierer eine kostengünstige Variante um mitzuteilen, dass sich einige Daten außerhalb des gültigen Bereiches befinden und damit die Software nicht mehr in der Lage ist, fortzufahren. Man kann Exceptions als günstiges Sicherheitsnetz ansehen, das verhindert, dass die Software weiterarbeitet und fehlerhafte Informationen ausgibt oder speichert. Das normale Vorgehen von Exceptions (hochwandern im Aufruf-Stack, bis ein Handler die Ausnahme fängt) unterstützt diese Sichtweise. Das Konzept ist auch in den Asserts der Programmiersprache C sichtbar.

Wenn …

  • bestätigt ist, dass bestimmte Fehlersituationen im Normalbetrieb auftreten und
  • der Grund für diese Situationen verstanden wurde und
  • solchen Situationen unterstützt werden sollen und
  • die erwarteten Ausgaben bestimmt werden können

ist es möglich das Verhalten anzupassen und damit diese Situation zu unterstützen. Das bedeutet, dass die Software robuster wird, d.h. mit weiteren Eingabeparametern sicher umgehen kann, aber auch, dass die Software komplexer wird. Dies ist also immer abzuwägen.

Vorausgesetzt wird, dass es einen Prozess gibt, der nach Exceptions und Log-Meldungen schaut und das Zeit investiert wird, um diese zu verstehen. Dies ist kurz nach Änderungen (neue Version, Hardwareupgrade, Veränderung der Clustergröße, Veröffentlichung einer neuen Betriebssystemversionen für mobile Geräte, …) besonders wichtig.

Zusammenfassend müssen drei Bedingungen erfüllt sein, um die Qualität der Software zu verbessern:

  1. Es muss eine Motivation für die kontinuierliche Verbesserung geben. Mit dieser Verbesserung wird dem Benutzer ein besseres Erlebnis geliefert, der Projektträger erhält einen höheren Geschäftswert, der Betrieb erhält eine robustere Software und für den Entwickler verbessert sich die Wartbarkeit. Sowohl das Management, als auch die Entwickler, müssen an die kontinuierliche Verbesserung glauben.
  2. Es gibt mindestens einen Feedbackkanal von der laufenden Software zurück zu den Entwicklern. Beispiele sind dafür: Log-Meldungen, die Überwachung (auf mehreren Schichten), Benutzer-Feedback per Telefon oder E-Mail, …. Dies ist kein Problem von Webanwendungen, aber es wird erschwert, wenn die Privatsphäre sehr wichtig ist oder das System nicht ans Netz angebunden ist (wie z.B. eine Aufzugssteuerung).
  3. Auf Feedback kann das Entwicklungsteam auf einfache Art und Weise und in einer angemessenen Zeitspanne reagieren. Wenn Sie durch die gesamte Stadt fahren müssen, um die Software sämtlicher Aufzüge zu aktualisieren, ist das nicht einfach. Ähnliches gilt, wenn Sie beispielsweise zwei Tage nach der Veröffentlichung einer Version einen Fehler finden, aber Sie nur zwei Mal pro Jahr eine neue Version bereitstellen können. Ein agiler Ansatz garantiert diese Bedingung.

Was können wir als Entwickler machen, wenn diese Bedingungen vorhanden sind, um robuste Software zu erstellen, welche gut und sinnvoll auf unbekannte Ereignisse reagiert? Zum Start möchte ich auf die Themen Log-Meldungen und Exception-Behandlung eingehen und zum Abschluss auch noch API-Design behandeln. Wie schon erwähnt, wird Java in den Code-Beispielen benutzt.

Log-Meldungen

Der primäre Zweck der Log-Meldung ist es bei der Analyse eines Problems zu helfen, nachdem dieses aufgetreten ist. Die Log-Meldung muss alle relevanten Informationen enthalten, um das Problem und die Ursache schnell und mit hoher Wahrscheinlichkeit identifizieren zu können. Was sind die Fragen, die eine Log-Meldung beantworten sollte?

  • Was wurde versucht?
  • Was waren die Werte der Parameter?
  • Was war das Ergebnis? Normalerweise die gefangene Exception oder ein Fehlercode
  • Wie reagiert die Methode darauf?
  • Optional: Was sind mögliche Gründe für das Problem?
  • Optional: Was sind mögliche Auswirkungen?

Seit einiger Zeit bevorzuge ich Log-Meldungen die mit “Failed to ” beginnen und aus einem oder mehreren Sätzen bestehen. Das Muster ist dabei “Failed to VERB with/for/of/from OBJECT.“ Einige fiktive Beispiele:

  • WARN: „Failed to create scaled thumbnail file for /tmp/foo.gif. Will return the original file as thumbnail. This may increase the used bandwidth. Saved the original file unter /tmp/bc2384d4-555d-11e5-9913-600308a94de6 for later analysis. Is imagemagick installed and in the PATH?“
  • ERROR: „Failed to get prices for Contract[…] from the backend. Will return null to indicate no-price. Does the monitoring at http://…. show a problem with the backend?“
  • INFO: „Failed to send email about Contract[…] to john.doe@example.com. Will retry 3 more times after a timeout of 2.4s.“
  • INFO: „Succeeded in sending email about Contract[…] to john.doe@example.com after 2 tries.“
  • WARN: „Failed to send email about Contract[…] to john.doe@example.com. No more retries left. The number of emails sent in the monthly report may be off.“
  • INFO: „Failed to get logged in user from the HTTP session. Will send a 401 back. User will have to log in once again. Maybe a timed out session?“
  • WARN: „Failed to send event UserLoggedIn[…] using kafka (server …). Will return false to indicate a problem.“

Was ist mit dem Hinzufügen der Exception-Meldung zur Log-Meldungen? D.h. sollte man Folgendes schreiben?

  LOGGER.error("Failed to FOO with BAR: " + e.getMessage(), e);

Die Vorteile für das Hinzufügen der Meldung ist, dass die Log-Meldung besser zu finden ist (insbesondere wenn grep verwendet wird), denn alle Informationen sind nun in einer Zeile zu finden. Der Nachteil ist, dass das Suchen schwerer wird, da doppelte Treffer gefunden werden. Sind Log-Meldungen strukturiert (z.B. wenn ELK benutzt wird), würde ich empfehlen die Exception-Meldung nicht zu duplizieren.

Ich möchte nun noch zwei andere Aspekte betrachten. Als erstes sollten komplexe Objekte eine eigene toString() Methode, mit den erforderlichen Informationen, bereitstellen. Wenn nicht bekannt ist, welche Informationen relevant sind, ist ein guter Startpunkt alle Felder auszugeben. Wenn Sicherheits- und Datenschutzaspekte dagegen sprechen, muss diese Strategie natürlich angepasst werden. Aus meiner Erfahrung heraus, kann ich den ToStringBuilder des Apache Commons Projekt empfehlen. Hierbei ist zu beachten, dass zirkuläre Referenzen eine endlose Rekursion zur Folge haben.

Der zweite Aspekt ist die Formatierung von Strings in Log-Meldungen. Es gibt dazu mehrere Aspekte:

  • der Umgang mit null
  • der Umgang mit nicht druckbaren Zeichen
  • copy-paste ermöglichen, um einfach einen Test zu erstellen

In der einfachsten Form sieht eine Log-Meldung wie folgt aus:

  LOG.info("Failed to send email to " + email + ".")

Hier gehen Informationen bei null-Werten verloren. Die Meldung „Failed to send email to null.“ könnte durch email==null oder email=”null” verursacht werden. Eine andere Option ist:

  LOG.info("Failed to send email to '" + email + "'.")

aber auch diese Variante hat Probleme mit email==null.

Speziell für die Ausgabe der nicht druckbare Zeichen, sollte man eine Methode (übblicherweise mit dem Namen escape(), quote(), format(), …) verwenden, wie der folgende Code zeigt:

  LOG.info("Failed to send email to " + escape(email) + ".")

Die Methode escape() liefert beispielsweise “<null>” für null-Werte und “\”foo\”” für "foo". Es werden auch nicht druckbare Zeichen, wie z.B. Tabs, ausgegeben. Im besten Fall verwendet das Escaping die Regeln für String Literale, sodas die Log-Meldung schnell für einen neuen Testfall verwendet werden kann.

Was ist mit Exceptions zu tun?

Nehmen wir an, eine Methode wirft eine checked Exception. Wie kann der Aufrufer darauf reagieren? Ich werde im folgenden Varianten skizzieren, klassifizieren und erklären in welchen Fällen diese verwendet werden sollten. Der Entwickler muss auf checked Exceptions reagieren, kann aber auf der anderen Seite unchecked Exceptions ignorieren. Die Reaktion auf unchecked Exceptions unterscheidet sich nicht von der Reaktion auf checked Exeptions und insbesondere können die gleichen Fehler gemacht werden.

Variante 1: catch and ignore

try {
  methodCall();
} catch(IOException e){}

In der Regel ist dies eine schlechte Lösung, weil wahrscheinlich wichtige Informationen verloren gehen. Es gibt jedoch auch einige sinnvolle Fälle für dieses Muster. Ein solcher Fall kann in einem finally-Block sein, um sicher zu stellen, dass die Exception im finally-Block nicht die ursprüngliche Exception (aus dem try-Block) ersetzt. Normalerweise ist die ursprüngliche Exception wichtiger. In solchen oder ähnlichen Fällen benutze ich zwei Sicherheitmechanismen um sicherzustellen, dass die Exception wirklich zu ignorieren war und nicht aus Faulheit ignoriert wurde: der Name der abgefangenen Exception heißt ignored und der catch-Block hat einen Kommentar:

file.flush()
try {
  file.close();
} catch(IOException ignored){
  // there is nothing we can do anymore about it
}

Variante 2: catch and log

try {
  methodCall();
} catch(IOException e){
  LOGGER.warn("Failed to do FOO with BAR.", e);
}

Das Problem wird nicht ignoriert, aber protokolliert. Sollten Sie diesen Muster verwenden? In dieser Form nur in wenigen Stellen. Das Hauptproblem von „catch and ignore“ und „catch and log“ ist, dass der weitere Steuerfluss nicht verändert wird. In Java müssen aber alle Variablen einen Wert haben. Somit sieht man häufig folgenden Code:

String foo = null;
...
try {
  foo = bar.readFoo();
} catch(IOException e){
  LOGGER.warn("Failed to do read FOO with BAR.", e);
}
...
if (foo == null) {
  ...
}

Dieses Codebeispiel erhöht die Arbeit für den nachfolgenden Entwickler, da dieser verstehen muss, in welcher Situation die Variable welchen Wert hat. Eine bessere Alternative ist das folgende Muster.

Variante 3: catch, log and handle

try {
  fetchedContent = fetch(url);
} catch(IOException e){
  LOGGER.warn("Failed to fetch " + url + ". Will use the empty string.", e);
  fetchedContent = "";
}

Hier ist die Behandlung der Exception explizit gemacht und ist innerhalb des catch-Blockes. Idealerweise wird ein neutraler Wert gewählt, welcher keine Änderungen in der restlichen Methode erfordert. Eine Alternative ist frühzeitig die Methode zu verlassen:

try {
  fetchedContent = fetch(url);
} catch(IOException e){
  LOGGER.warn("Failed to fetch " + url + ". Will return null.", e);
  return null;
}

Variante 4: catch and throw enhanced auch bekannt als catch und wrap

Die ursprüngliche Exception wurde gefangen und eine neue Exception wird erstellt. Letztere wird statt der ursprünglichen geworfen. Die ursprüngliche Exception ist der neuen Exception als nested exception bekannt.

try {
  fetchedContent = fetch(url);
} catch(IOException e){
  throw new RuntimeException("Failed to fetch " + url + ".", e);
}

Mit diesem Muster ist es leicht möglich Exceptions miteinander zu verknüpfen, die sich auf verschiedene Schichten der Software (analog zum Call-Stack) beziehen. Dies ist meiner Meinung nach eine sehr wertvolle Funktionalität, da es die Fehlersuche sehr stark erleichtert. Ein Beispiel dafür ist:

Controller: Failed to serve HTTP-requuest […].
caused by Controller: Failed to calculate price for Contract[…]
caused by Service: Failed to validate Contract[…]
caused by Soap: Failed to execute soap call for …
caused by Network: Failed to connect to host …
caused by SslSocket: Failed to verify SSL certificate
caused by Crypto: Wrong passphrase for keystone

Wie sollte eine Meldung für eine neue Exception aussehen? Ganz ähnlich einer Log-Meldung, aber ohne deren Handhabung und Konsequenzen:

  • Was wurde versucht?
  • Was waren die Werte der Parameter?
  • Was war das Ergebnis?
  • Optional: Was sind mögliche Gründe für das Problem?

Es gibt eine Debatte darüber, ob man checked oder unchecked Exceptions benutzen sollte. Ich bevorzuge unchecked Exceptions, aber es gibt wie gesagt aber auch andere Meinungen.

Welche Exception-Klasse soll verwendet werden? Auch dieses Thema wird heiß diskutiert. Meiner Meinung nach ist die Benutzung einer speziellen Exception-Klasse nur dann gerechtfertig, wenn anderer Code auf diese Ausnahme reagiert (diesen Exceptiontyp fängt). Die Klasse kann vom JDK oder einer 3rd Party Quellen kommen oder speziell für diesen Zweck erstellt worden sein. Die letzte Option ist die sicherste, da kein 3rd Party Modul diese Exception erzeugen kann. Meiner Meinung nach ist es ok eine generische Exception zu werfen, wenn es noch keine spezifische Reaktion auf diese Art von Fehler gibt. Bitte beachten Sie, dass dies anders ist, wenn die Software eine öffentliche API (insbesondere für Komponenten die nicht unter der eigenen Kontrolle sind) bereitstellt. In diesem Fall sollten spezifische Exceptions verwenden werden und diese dann auch so dokumentiert sein, dass der Anrufer darauf reagieren kann.

Ein besonderer Fall dieser Variante ist die Umwandlung einer checked Exception in eine unchecked Exception. Dies ist manchmal bei den im JDK mitgelieferten funktionalen Schnittstellen von Java 8 erforderlich.

Variante 5: catch, log and rethrow sowie catch, log and throw enhanced

Die Exception wird abgefangen, protokolliert und die ursprüngliche Exception wird erneut geworfen oder eine neue Exception wird erzeugt und geworfen.

try {
  fetchedContent = fetch(url);
} catch(IOException e){
  LOGGER.warn("Failed to fetch " + url + ".", e);
  throw e;
}

oder

try {
  fetchedContent = fetch(url);
} catch(IOException e){
  LOGGER.warn("Failed to fetch " + url + ".", e);
  throw new RuntimeException("Failed to fetch " + url + ".", e);
}

Kurz gesagt: bitte nicht verwenden. Diese Variante ist der Hauptgrund, dass Exceptions mehrfach geloggt werden. In solch einem Fall ist schwer es die Reihefolge von Ereignissen und die Anzahl der tatsächlichen Fehler zu ermitteln. Wenn diese Variante trotzdem benutzt wird, sollte in der Log-Meldung der Hinweis enthalten sein, dass eine Exception geworfen wird.

Variante 6: do not catch

Die Exception wird nicht gefangen und wandert den Aufrufer-Stack hinauf. Dies ist vergleichbar mit „catch and throw enhanced“ mit den Unterschied, dass keine weiteren Informationen über die Operation mit angehangen werden. Das ist meiner Meinung nach ein Nachteil. Diese Variante ist das Standardverhalten für unchecked Exceptions.

Variante 7: catch and handle

Wie in „Variante 3: catch, log and handle“ beschrieben, jedoch ohne das Logging. Für diese Variante gibt es auch valide Anwendungsfälle. Die Voraussetzung ist hier, dass man als Entwickler den Grund der Exception sicher bestimmen kann. Ein Beispiel könnte wie folgt aussehen:

boolean isInteger(String str) {
  try {
    Integer.parseInt(str);
    return true;
  } catch(NumberFormatException ignored) {
    return false;
  }
}

Welche Variante für welchen Anwendungsfall?

Wenn die speziellen Fällen ausgelassen werden, bleiben folgende Varianten übrig:

  • catch, log and handle
  • catch and throw enhanced
  • do not catch

Wenn die Exception behandelt werden kann, sollte „catch, log and handle“ verwendet werden. Wenn nützliche Informationen aus der aktuellen Methode hinzugefügt werden können, eine höhere Zahl von Problemen zu erwarten ist oder wenn eine unchecked Exception erforderlich ist, sollte “catch and throw enhanced” verwendet werden. In allen anderen Fällen ist “do not catch” die richtige Wahl.

In vielen Fällen erfolgt der Umgang mit Problemen meist am oberen Ende des Aufrufer-Stacks. Wenn wir eine allgemeine Webanwendung mit REST Interface betrachten, wäre die REST API Methode die erste Wahl für die Behandlung. Ich würde jedoch argumentieren, dass der Aufrufer-Stack auch den Client (JavaScript) mit umfasst. D.h. dass das obere Ende des Call-Stacks der JavaScript Eventhandler ist und dies vielleicht der bessere Ort ist, um das Problem zu behandeln (Anzeige einer Fehlermeldung). Man kann also das Senden eines Status-Codes 500, vom Server zum Client, als eine andere Art der Fehlerübertragung im Aufruf-Stack sehen. Dennoch sollte am oberen Ende des Aufrufer-Stack auf dem Server eine Log-Meldung erfolgen, da:

  • Logging innerhalb des Servers zuverlässiger ist
  • keine internen Details übertragen werden sollen
  • es ist der beste Ort um den gesamten HTTP-Request (header und body) für die spätere Analyse zu protokollieren

Normalerweise wird solch eine Funktionalität nicht in allen REST API Methoden implementiert, sondern ein gemeinsamer Exception-Handler verwendet.

Interface Design und Exceptions

Bisher wurde besprochen, wie man auf Exceptions reagiert. Wann jedoch sollten Exceptions geworfen werden? Exceptions sollten ausgelöst werden, wenn die Methode nicht ihre beschriebene Funktionalität ausführen kann. Beispiel:

void sendMessage1(Message message);

Ohne weitere Informationen kann der Entwickler, der diese Methode aufruft, davon ausgehen, dass entweder das Senden der Nachricht erfolgreich war oder eine Exception ausgelöst wird.

/**
 * @return true = message has been send, false = sending failed
 */
boolean sendMessage2(Message message);

In diesem Fall ist nicht gewährleistet, dass der Sendevorgang immer erfolgreich verläuft. Gehen Sie im Fehlerfall davon aus, dass die Methode eine Exception wirft? Nicht wirklich. Wenn diese Methode eine Exception wirft, wäre dies eine zusätzliche Belastung für den Aufrufer, da dieser zwei Dinge zu prüfen hat (den Rückgabewert und die Exception) und damit ist es ein schlechtes Interface-Design. Einschub: Da Wahrheitswerte nicht viele Informationen beinhalten, muss die aufgerufene Methode die eventuell intern entstehende Exception loggen und false zurückliefern.

In Methoden die möglicherweise fehlschlagen, bevorzuge ich diese fehlende Sicherheit im Namen der Methode mit auszudrücken. Zum Beispiel kann man mit tryTo arbeiten:

/**
 * @return true = message has been send, false = sending failed
 */
boolean tryToSendMessage3(Message message);

Dies war ein Beispiel für ein Command. Was ist mit einem Query?

/**
 * Fetches the price from backend
 */
double getPrice1(Contract contract);

Offensichtlich und ähnlich wie sendMessage1() erwartet der Anrufer eine Exception, wenn der Preis nicht berechnet werden kann. Es gibt auch die Variante mit null (dies sollte meiner Meinung immer in der Javadoc erwähnt werden):

/**
 * @return null if the price can be not calculated
 */
Double getPrice2(Contract contract);

Oder mit Optional (dann ohne Javadoc):

Optional<Double> getPrice3(Contract contract);

Ähnlich zu der oberen Variante erwarte ich hier keine Exception, wenn Fehler auftreten, sondern, dass null oder Optional.empty() zurückgegeben wird.

Während des Designs von öffentlichen Methoden und von APIs muss man entscheiden, ob Fehlerzustände ein expliziter Teil der API sind (boolean für sendMessage oder null / Optional.empty() für getPrice()) oder Exceptions verwendet werden. Ich würde vorschlagen mit unchecked Exceptions zu beginnen. Die Gründe sind folgende:

  • die API wird klein gehalten
  • der Aufrufer kann “do no catch” verwenden, und damit seinen anfänglichen Programmieraufwand verringern
  • man muss nicht darüber nachdenken, welcher spezielle Wert für bestimmte Fehlerfälle verwendet werden soll (Sollte null, "" oder Optional.empty() benutzt werden?)
  • da keine speziellen Werte benutzt werden, welche auch Dokumentation erfordern, bedeutet dies gleichzeitig weniger Dokumentationsaufwand

Die Verwendung von Exceptions erlaubt eine schnelle initiale Implementierung um im Anschluss Feedback einzusammeln. Wenn während der kontinuierlichen Verbesserung die Entscheidung getroffen wird, dass alle Aufrufer auf eine bestimmte Situation reagieren sollen, dann kann bzw. sollte sich die Signatur ändern (das Ergebnis in ein Optional-Objekt zu legen, eine kontrollierte Exception hinzuzufügen, …). Hier hilft der Compiler alle noch anzupassenden Aufrufer zu erkennen.

Diese Empfehlung gilt nicht, wenn die zu entwerfende API für einen längeren Zeitraum stabil sein soll oder von mehreren Parteien benutzt wird.

Das Ende

Danke für das Lesen dieses längeren Artikels bis zum Ende. Ich hatte nicht erwartet, dass es so viel über Fehlerbehandlung zu schreiben gibt. Es bleibt ein spannendes und wichtiges Thema, was oft vernachlässigt wird.

Wenn Sie Weiteres zu diesem Thema lesen möchten, kann ich Need Robust Software? Make It Fragile empfehlen. Die anderen Beiträge des Autors sind ebenfalls lesenswert, da sie übliche Annahmen in Frage stellen und damit zum Nachdenken anregen.

Tags

Raimar Falke

Dr. Raimar Falke ist als Senior IT Consultant bei der codecentric AG tätig. Davor hat er als Wissenschaftler die Verbesserung statischer Analyseergebnisse mit Lernverfahren untersucht. Sein aktueller Schwerpunkt ist neben der Unterstützung von Kunden im Zusammenhang mit Atlassian-Produkten die Softwareentwicklung an sich mit all ihren Facetten.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.