Best Practice Ausgabe über direkte Ausgabe oder try-catch?

derFabi95

Mitglied
Hallo zusammen,
aktuell habe ich den Fall, dass innerhalb einer Methode viele verschiedene if-Abfragen existieren. Es darf jedoch nur eine ausgeführt werden.
Wie ist nun die best practice um Nachrichten auszugeben?
Hier mal die Optionen, die ich so sehe:

[CODE lang="java" title="Möglichkeit 1"]public static void main(String[] args) {
if(eineBedingung) {
System.out.println("Fehler 1 :-(");
return;
}

if(nochEineBedingung) {
System.out.println("Fehler 2 :-(");
return;
}

if(nochEineDritteBedingung) {
System.out.println("Fehler 3 :-(");
return;
}

//Andere Sachen...
}[/CODE]
[CODE lang="java" title="Möglichkeit 2"]public static void main(String[] args) {
try {
if(eineBedingung) {
throw new MeineException("Fehler 1 :-(");

if(nochEineBedingung)
throw new MeineException("Fehler 2 :-(");

if(nochEineDritteBedingung)
throw new MeineException("Fehler 3 :-(");

//Andere Sachen... ggf auch mit möglichen Exceptions
} catch (MeineException e) {
System.out.println(e.getMessage());
} catch(NumberFormatException e) {
//Nur als Beispiel
}
}[/CODE]

Möglichkeit 1 ist zwar Syntaktisch meiner Meinung nach schöner und vermutlich auch schneller, bläht jedoch den Code möglicherweise sehr auf, vor allem wenn viele Bedingungen abgefragt werden oder dies generell bei mehreren Methoden gemacht wird.

Wie ist es hier am besten?
 
Exception nur für wirkliche Ausnahmen. Alles, was normale Erwartung ist, sollte ohne Exception erfolgen. Oder in anderen Worten: Nutze keine Exceptions zur Steuerung des normalen Programmflusses.

Daher ist die erste Variante die übliche in einem normalen Ablauf. Exceptions sind mehr wenn es bedeutet, dass der Entwickler zu blöd war (Typisch für z.B. NullPointerException oder OutOfBoundsException, die nicht abgefangen werden sollten sondern die einfach mit einer Fehlerkorrektur im Code zu tun haben) oder dass eben etwas passiert ist, das nicht zu erwarten war.

Somit hängt es von dem genauen Problem ab wie man darauf reagieren sollte. "Exceptions only for exceptional behaviour" wäre wohl die Aussage auf Englisch.
 
Wenn es aber, wie die Nachrichten vermuten lassen, wirklich Fehler sind, können Exceptions durchaus das passende sein.

Aber dann natürlich nicht in der Methode selbst, sondern außerhalb fangen.
Ich sehe schon, hier gibt es unterschiedliche Ansichten.
Dann bitte, the stage is yours!
Würde mich jedenfalls interessieren wie die einzelnen Ansichten sind.

Außerhalb würde ich das natürlich fangen, aber in meinem konkreten Fall findet dies in einem eigenen (asynchronen) Thread statt - weshalb ich es da fangen muss.
 
Zuletzt bearbeitet:
So unterschiedlich sind die Ansichten doch gar nicht. Die Frage ist, was @mrBrown als Fehler bezeichnet. Das eine Methode mit ungültigen Werten aufgerufen wird, ist ja auch ein Fall, den ich als "pro Exceptions" ansehen würde.

Da wir nicht wissen können, um was es sich bei Dir genau handelt, kann aber halt keine klare Aussage getroffen werden.

aber in meinem konkreten Fall findet dies in einem eigenen (asynchronen) Thread statt - weshalb ich es da fangen muss.
Also wenn Du die Exceptions innerhalb der gleichen Methode fängst, dann wird es einfach unsinnig. Den Code so, wie Du ihn in Möglichkeit 2 gezeigt hast, würde wohl durch kein Code Review kommen 🙂
 
So unterschiedlich sind die Ansichten doch gar nicht. Die Frage ist, was @mrBrown als Fehler bezeichnet. Das eine Methode mit ungültigen Werten aufgerufen wird, ist ja auch ein Fall, den ich als "pro Exceptions" ansehen würde.

Da wir nicht wissen können, um was es sich bei Dir genau handelt, kann aber halt keine klare Aussage getroffen werden.


Also wenn Du die Exceptions innerhalb der gleichen Methode fängst, dann wird es einfach unsinnig. Den Code so, wie Du ihn in Möglichkeit 2 gezeigt hast, würde wohl durch kein Code Review kommen 🙂
Danke!
Vielleicht erkläre ich es noch etwas konkreter:
Der Nutzer wird aufgefordert etwas einzugeben.
Nun wird ein asynchroner Thread gestartet und über Datenbankqueries (JDBC) mittels WHERE versucht ein zu dem Wert gehöriges Tupel zu laden.
Ist nun kein solches Tupel vorhanden, wird der Fehler ausgegeben und die weitere Ausführung des Threads unterbrochen.
Falls das Tupel doch vorhanden ist werden weitere Bedingungen geprüft - z.B. ob der Nutzer Berechtigungen hat. Auch hier müssten dann entsprechende Fehlermeldungen gezeigt und die Ausführung unterbrochen werden.

Also lieber wirklich über souot/syso und return das Ganze abbrechen? Wie gesagt, mein Gedanke dahinter war primär Zeilen einzusparen um Übersichtlichkeit zu gewährleisten.
 
Also generell sind da mehrere Dinge zu trennen. Du hast da ja einiges an unterschiedlichen Aufgaben und die gehören nicht zusammen. Ausgabe, Datenbankabfragen, irgendwelche Checks.... (Bei den SOLID Principles wäre dies dann das "S" mit Single Responsibility. An der Stelle auch einfach der Hinweis auf Uncle Bob - auf YouTube findet man auch einiges von ihm. Ich habe da auch eine Playlist zusammen gestellt.)

Dann ist eine ungültige Benutzereingabe eine normale Sache. Das ist keine Ausnahmesituation. Daher erst einmal aus meiner Sicht kein Grund für eine Exception sondern einfach nur ein normaler Ablauf, der in einem Programm vorkommt.

Und dann sind es einfache if (...) return ....; Zeilen, die diese Validierung machen. Und die Methode hat dann Rückgaben, die dann irgendwas anderes triggern, z.B. eine Ausgabe.
 
Also generell sind da mehrere Dinge zu trennen. Du hast da ja einiges an unterschiedlichen Aufgaben und die gehören nicht zusammen. Ausgabe, Datenbankabfragen, irgendwelche Checks.... (Bei den SOLID Principles wäre dies dann das "S" mit Single Responsibility. An der Stelle auch einfach der Hinweis auf Uncle Bob - auf YouTube findet man auch einiges von ihm. Ich habe da auch eine Playlist zusammen gestellt.)

Dann ist eine ungültige Benutzereingabe eine normale Sache. Das ist keine Ausnahmesituation. Daher erst einmal aus meiner Sicht kein Grund für eine Exception sondern einfach nur ein normaler Ablauf, der in einem Programm vorkommt.

Und dann sind es einfache if (...) return ....; Zeilen, die diese Validierung machen. Und die Methode hat dann Rückgaben, die dann irgendwas anderes triggern, z.B. eine Ausgabe.
Klar, ich habe das schon gekapselt - die DB-Interaktionen werden über eine Service-Klasse usw ausgeführt. Letztendlich frage ich was das betrifft in der obersten Methode im Thread eh nur auf if(loadedTuple == null) ...
Dort müsste dann eben die Fehlermeldung und das return stattfinden.
Dann setze ich das nicht mit Exceptions um - danke!
 
Also generell sind da mehrere Dinge zu trennen. Du hast da ja einiges an unterschiedlichen Aufgaben und die gehören nicht zusammen. Ausgabe, Datenbankabfragen, irgendwelche Checks.... (Bei den SOLID Principles wäre dies dann das "S" mit Single Responsibility. An der Stelle auch einfach der Hinweis auf Uncle Bob - auf YouTube findet man auch einiges von ihm. Ich habe da auch eine Playlist zusammen gestellt.)

Dann ist eine ungültige Benutzereingabe eine normale Sache. Das ist keine Ausnahmesituation. Daher erst einmal aus meiner Sicht kein Grund für eine Exception sondern einfach nur ein normaler Ablauf, der in einem Programm vorkommt.

Und dann sind es einfache if (...) return ....; Zeilen, die diese Validierung machen. Und die Methode hat dann Rückgaben, die dann irgendwas anderes triggern, z.B. eine Ausgabe.
Ach, da komme ich doch noch zu einem Punkt bzgl. Exceptions werfen:
In meiner Service-Klasse habe ich eine Methode persist(...), eine loadById(...).
Beide werfen Exceptions wenn etwas grundlegend Fehlschlägt, z.B. die DB-Verbindung.


Wenn nun aber die loadById() kein passendes Tupel findet, bin ich nicht sicher ob es dann besser ist eine Exception zu werfen (wobei manchmal die Bedingung auch ist dass das Tupel NICHT in der DB existiert) oder doch lieber einfach null returnen.
Jetzt wo ich das schreibe kommt mir der Gedanke ich werfe eine Exception bei load wenn nichts gefunden wird und mach eine NICHT-Bedinung über eine neue Funktion isPersisted. Oder doch anders?
Also etwa so:
Java:
public Klasse loadById(int id) throws Exception {
    ...
    return klasse;
}

public boolean isPersisted(Klasse klasse) {
    boolean persisted = false;
    try{
        loadById(klasse.getId());
        persisted = true;
    } catch(Exception e) {}
    return persisted;
}
 
Zuletzt bearbeitet:
Ich versuche es einfach mal an dem Datenbank-Hinweis von Dir zu erläutern:

Wenn die Datenbank Probleme hat, dann gibt es zwei Möglichkeiten:
a) Entwickler doof
b) Admin doof
==> Exception ist durchaus in Ordnung.

Falsche Eingabe: Das ist eine Regel konforme Nutzung. Wenn Du es dem Nutzer ermöglichst, etwas falschen einzugeben, dann ist es das Problem vom Entwickler. (Gibt ja genug andere Möglichkeiten, den Anwender zu unterstützen. Auto-Vervollständigung, nur bestimmte Zeichen erlauben u.s.w.

Der Unterschied ist dabei aber natürlich ein ganz einfacher:
Der Benutzer nutzt die Applikation einfach. Entwickler und Admin sind aber welche, die da aber nicht einfach nur nutzen sondern die bauen etwas auf incl. testen das um es dann bereit zu stellen. Da sind Exceptions in Ordnung, denn das ist halt wirklich ein Zeichen von dem oben genannten a) oder b) und da sollten diese aktiv werden:
Bei a) sollte der Entwickler in sich gehen und das Problem lösen durch eben Änderungen am Code und
bei b) sollte der Admin die Konfiguration anpassen, damit es geht.

Das ist natürlich extrem einfach dargestellt. Aber es läuft im Prinzip immer auf so etwas hinaus.

Man kann da aber auch ganz einfache Argumentationen bringen wie: Exception ist immer damit verbunden, dass ein Stacktrace erstellt werden muss und so. Das sind unnötige Aufwände. Die bremsen Deinen Code aus. Das will schlicht niemand.

Edit: Die Konsequenz daraus ist halt, dass man dieses Mittel nicht für den Programm Flow nutzt wenn es um einfache programmatischen Dinge geht. Die Auswertung einer Benutzereingabe ist normaler Ablauf im Programm. Unerwartete Fehler bei Konfiguration oder in der Entwicklung selbst gehören aber nicht zum eigentlichen Ablauf des Programmes. Das ist eine Ausnahme und da macht dann auch eine Ausnahme-Behandlung Sinn. Denn Du willst diese Möglichkeit ja nicht ständig und überall ggf. abfangen. Bei sowas kann es sogar Sinn machen, den Ablauf gnadenlos ganz abzubrechen oder zumindest mehrere Ebenen durchlaufen zu lassen.

Der Nutzer gibt was ein, das geht dann über einen Controller hin in mehrere Model Klassen um da irgendwo im Repository zu merken: Die Datenbank ist nicht erreichbar, eine Datei kann nicht geschrieben werden oder irgend sowas... Das willst Du in den ganzen Ebenen nicht behandeln. Aber auf Controller Ebene ist es wichtig: Der Controller will dann evtl. agieren und sagen: "Änderungen können nicht gespeichert werden - an anderer Stelle speichern?" oder so. Da erkennt man dann auch den Sinn der Exception. Und die will man auch nicht nutzen. Spätestens in so Fällen wo mehrere Ebenen zurück gegangen werden merkt man ja, dass da viele ifs mit Fehler-Rückgabe notwendig wären .... Halt auf jeder Ebene.
 
Zuletzt bearbeitet von einem Moderator:
Ich versuche es einfach mal an dem Datenbank-Hinweis von Dir zu erläutern:

Wenn die Datenbank Probleme hat, dann gibt es zwei Möglichkeiten:
a) Entwickler doof
b) Admin doof
==> Exception ist durchaus in Ordnung.

Falsche Eingabe: Das ist eine Regel konforme Nutzung. Wenn Du es dem Nutzer ermöglichst, etwas falschen einzugeben, dann ist es das Problem vom Entwickler. (Gibt ja genug andere Möglichkeiten, den Anwender zu unterstützen. Auto-Vervollständigung, nur bestimmte Zeichen erlauben u.s.w.

Der Unterschied ist dabei aber natürlich ein ganz einfacher:
Der Benutzer nutzt die Applikation einfach. Entwickler und Admin sind aber welche, die da aber nicht einfach nur nutzen sondern die bauen etwas auf incl. testen das um es dann bereit zu stellen. Da sind Exceptions in Ordnung, denn das ist halt wirklich ein Zeichen von dem oben genannten a) oder b) und da sollten diese aktiv werden:
Bei a) sollte der Entwickler in sich gehen und das Problem lösen durch eben Änderungen am Code und
bei b) sollte der Admin die Konfiguration anpassen, damit es geht.

Das ist natürlich extrem einfach dargestellt. Aber es läuft im Prinzip immer auf so etwas hinaus.

Man kann da aber auch ganz einfache Argumentationen bringen wie: Exception ist immer damit verbunden, dass ein Stacktrace erstellt werden muss und so. Das sind unnötige Aufwände. Die bremsen Deinen Code aus. Das will schlicht niemand.
Da hast du natürlich absolut Recht, danke für die Erkärung.
Wie wäre denn der Way-To-Go bei meiner Idee eine Nachricht weiter oben?
Ist ein try-catch in so einer isPersisted-Methode best practice oder wie würde man das besser designen?
 
Ich hatte noch ein größeres Edit angehängt. Evtl. ist das ein Gedanke, der Dir bei der Entscheidungsfindung hilft.
 
Wie gesagt, mein Gedanke dahinter war primär Zeilen einzusparen um Übersichtlichkeit zu gewährleisten.
Eigentlich ist es genau andersrum: (global) weniger Zeilen führen meist zu weniger Übersichtlichkeit 🙂



Dann ist eine ungültige Benutzereingabe eine normale Sache. Das ist keine Ausnahmesituation. Daher erst einmal aus meiner Sicht kein Grund für eine Exception sondern einfach nur ein normaler Ablauf, der in einem Programm vorkommt.
Falsche Eingabe: Das ist eine Regel konforme Nutzung. Wenn Du es dem Nutzer ermöglichst, etwas falschen einzugeben, dann ist es das Problem vom Entwickler. (Gibt ja genug andere Möglichkeiten, den Anwender zu unterstützen. Auto-Vervollständigung, nur bestimmte Zeichen erlauben u.s.w.

Würde ich zT sogar anders sehen 🙂

Das kommt ganz auf die konkrete Modellierung an, was geeigneter ist, und Exception können da der bessere Weg sein.
Den "Normalablauf" würd ich in dem Fall dann sehen, wenn die Methode ihr "Ziel" noch erreichen kann, und wenn die vorher abbrechen muss, wäre das eine "Ausnahme" – wobei das aus Methoden- und aus Nutzer-Sicht und auf unterschiedlichen Ebenen durchaus unterschiedlich sein kann.

Um bei obigem Beispiel zu bleiben: Wenn die Methode irgendeinen Attribut der Entität ändern soll, ist der Normalablauf, dass es geändert wird – und alles, was es scheiten lässt, sind Ausnahmen. Wenn der Nutzer keine Rechte hat, kann der normale Ablauf nicht durchgeführt werden, genauso wenn der Nutzer eine nicht existierende ID eingibt, ergo Exception werfen.
Ausnahmen kann der Programmierer dabei ja sogar aktiv verhindern, in dem ich alle Bedingungen vorher prüfe – wäre für mich noch ein Grund dafür, dass es in der Methode eine Ausnahmesituation ist.


Ein schönes Beispiel dafür, dass generell beides geht, je nach Semantik, ist Queue, die für die wichtigsten Methoden jeweils zwei Varianten bietet: eine mit, und eine ohne Exception.


Um mal direkt hierauf einzugehen:
Außerhalb würde ich das natürlich fangen, aber in meinem konkreten Fall findet dies in einem eigenen (asynchronen) Thread statt - weshalb ich es da fangen muss.
Auch asynchrone Methoden können Exceptions werfen, und die lassen sich zB über Future oder CompletionStage abfragen.

Angenommen, die Fehlermeldung soll irgendwann über GUI statt über CLI ausgegeben werden, anhängig davon, wie der Nutzer das Programm gestartet hat. Steht das out.print direkt in der Methode, hast du keine Wahl, es wird immer dort ausgegeben. Man kann dann natürlich in der Methode noch mehr Pfade einbauen, und das dort direkt in der GUI ausgeben – aber das will man eher nicht, da es das ganze einerseits aufbläht und andererseits ziemlich koppelt. Im Idealfall zieht man die Ausgabe also aus der Methode raus, die kümmert sich dann nur noch um die Logik.
Die Fehler muss man dafür aber irgendwie nach außen "mitteilen".
 
Wenn die Methode irgendeinen Attribut der Entität ändern soll, ist der Normalablauf, dass es geändert wird – und alles, was es scheiten lässt, sind Ausnahmen.
Der Punkt ist unstrittig. Eine Methode validiert seine Argumente und wirft ggf. eine IllegalArgumentException oder so.

Aber das ist nicht der Punkt, um den es geht. Denn diese Exception ist auf einem Level mit der NullPointerException. Das ist also auch eine "Entwickler ist doof" Exception.

So eine Exception sollte also nicht für den Programmablauf genutzt / gefangen werden sondern es sollte vorab geprüft werden.
==> Damit ist im Programmablauf keinerlei Exception mehr notwendig.

Das ist auch sinnvoll, denn die Validierung gehört ja mit zur Eingabe. Das andere gehört zu der Verarbeitung.

Was ist denn die Aufgabe der Methode? Aufgabe der Methode ist rein das Setzen des Wertes. Nicht die Validierung! Das ist zwar eine Notwendigkeit, aber eben mit "Entwickler ist doof" Exception. Die Methode ist eigentlich nicht zur Validierung gedacht (aus meiner Sicht).
Wenn ich sowas für sinnvoll erachte (Komplexe Anforderung), dann baue ich dafür eine separate Methode.

Beispiel Scanner: Was machen viele? scanner.nextInt() und die mögliche Exception, wenn eben keine Zahl eingegeben wurde, wird gefangen.
Aber die Entwickler haben das vermutlich ähnlich gesehen: Die Validierung ist separat: hasNextInt() gibt es.
(Wobei das Beispiel nur für das Handling herhalten kann - im Code bin ich nicht ganz zufrieden - beim OpenJDK 8 Source riecht es etwas nach doppeltem Code aber ich muss gestehen, dass ich mir das noch nicht zu sehr im Detail angesehen habe. Die Komplexität ist halt doch etwas höher. Aber ich hätte da erwartet, dass nextInt eben hasNextInt intern nutzt, was aber nicht der Fall ist. Aber das ist dann auch wieder ein anderes Thema.

Und auch beim Scanner wird die Eingabe geprüft:
Es gibt natürlich ein try / catch bezüglich NumberFormatException, aber es wird vorab mit einem Regulären Ausdruck das Format geprüft. Diese Exception sollte also nie geworfen werden.

Das wäre noch einmal eine Ausführung zu meiner Sicht. In der Hoffnung, dass es verständlich wurde und ich nicht wieder zu oberflächlich geblieben bin.

Für den TE aber ganz wichtig: Es geht hier - wie du merkst - teilweise um Feinheiten in der Frage: Was mache ich wann wieso. Da geht es also nicht mehr um grobe Patzer a. la. "Falsch / Richtig" sondern man geht schon durchaus tiefer und man bezieht dann mehr Element aus dem Clean Code Bereich hinein.
 

Zurück
Oben