Folgendes Beispiel ist komplett erdacht, mir geht es nur um die Architektur und würde gerne wissen, was ihr davon haltet.
Ich habe eine Klasse Data:
//viele Felder mit Gettern für irgendwelche Werte
}```
Und eine Methode `public Data loadDataFromSomewhere()`, die eine Instanz dieser Klasse zurückliefert. Sagen wir, diese Methode gibt `null` zurück, wenn die Instanz nicht geladen werden kann, was im normalen Ablauf durchaus möglich ist (bspw. kein Internet oder sowas).
Nun möchte ich als Aufrufer gerne darüber informiert werden, warum die Methode null zurück gibt. Und da kam mir folgende Idee: Könnte man nicht statische Instanzen der Klasse zurückgeben, wobei jeweils eine Instanz für einen Fehler-Zustand steht? Quasi eine aufgebohrte Version des NullObject-Patterns.
```public class DataLoader{
public static Data NO_CONNECTION = new Data(); // empty instance
public static Data TIMEOUT = new Data(); // empty instance
public MyClass loadDataFromSomewhere(){
//... do stuff
if(noConnection) return NO_CONNECTION;
if(timedOut) return TIMEOUT;
// ... create result
return result;
}
public static boolean isInvalid(Data instance){
return instance == null || instance == NO_CONNECTION || instance == TIMEOUT;
}
}```
Die Abfrage auf `null` würde dann durch einen Aufruf von `isInvalid(Data )` ersetzen werden.
Jetzt die Frage an euch: Ist das eine saubere Lösung? Wie würdet ihr es machen? Freu mich auf die Diskussion :)
Erstens weil eine Exception „teuer“ ist (bspw. auf mobilen Endgeräten) , und ich zweitens ungerne eine Exception für etwas werfen möchte, was zur Laufzeit durchaus „normal“ (also keine Ausnahme ) sein kann. Das Internet darf weg sein.
Zudem finde ich persönlich das Abfangen einer Exception sehr viel unübersichtlicher als die Überprüfung eines Rückgabewertes. Aber da das mein persönlicher Geschmack ist, würde ich das jetzt nicht als Argument in den Ring werfen.
Wenn es sich um eine Anwendung handelt die eine Internetverbindung benötigt, dann ist „keine Verbindung“ eine Ausnahme.
Inwiefern unübersichtlich? Als Autor der Methode kannst Du doch genau spezifizieren welche Exceptions auftreten können. Der Nutzer muss dann gar nicht prüfen und kann einfach auf die spezifischen Exceptions reagieren - oder diese ignorieren.
*** Edit ***
Im obigen Entwurf, verstehe ich den Mehrwert der (mehreren) zusätzlichen Objekte nicht und warum static? Inwieweit ergibt sich für den Methodenaufrufer einen Unterschied zwischen NO_CONNECTION und TIMEOUT?
[QUOTE=_Michael;71834]Diesbzgl. habe ich keine Erfahrung.[/QUOTE]Exceptions sind immer „teuer“, nur auf mobilen Geräten fällt es eher auf als auf PCs. Daher mein Beispiel.
[QUOTE=_Michael;71834]Wenn es sich um eine Anwendung handelt die eine Internetverbindung benötigt, dann ist „keine Verbindung“ eine Ausnahme.[/QUOTE]Das war ein Beispiel, wenn auch vielleicht kein optimal gewähltes. Es geht darum, dass die Methode nicht gewünschte Instanz zurückgibt, und das dies im normalen Ablauf völlig okay ist.
[QUOTE=_Michael;71834]Inwiefern unübersichtlich? Als Autor der Methode kannst Du doch genau spezifizieren welche Exceptions auftreten können. Der Nutzer muss dann gar nicht prüfen und kann einfach auf die spezifischen Exceptions reagieren - oderdiese ignorieren.[/QUOTE]Ich möchte den Nutzer ja nicht zwingen, den Grund für das Fehlen der Instanz erfragen zu müssen (was bei einer Exception der Fall wäre). Es kann völlig okay sein, einfach keine gültige Instanz bekommen zu haben. Falls man aber wissen möchte, wodran es lag, gibt einem die Instanz eben Auskunft darüber, was schief gelaufen ist. Würde ich eine Exception werfen, müsste der Nutzer diese behandeln, was den Aufruf-Code unnötig verkompliziert.
Das Exceptions eine Alternative im Fehlerfall sind, ist mir durchaus bewusst. Ich möchte deren Einsatz oder Komplexität hier auch gar nicht weiter diskutieren, da ich gerne Kommentare und Diskussion zum oben vorgestellten Konzept haben möchte ;). Und gerade ein „Vorteil“ des Designs ist es, dass man sich im Einzelfall Sonderinformationen holen kann, dies allgemein aber nicht tun muss (was bei Exceptions nicht der Fall ist).
[QUOTE=_Michael;71834]Im obigen Entwurf, verstehe ich den Mehrwert der (mehreren) zusätzlichen Objekte nicht und warum static? Inwieweit ergibt sich für den Methodenaufrufer einen Unterschied zwischen NO_CONNECTION und TIMEOUT?[/QUOTE]MyClass soll ein beliebiges Daten-Object sein, dass irgendwelche Daten beinhaltet. Ich denke ich werde es mal umbenennen … fertig. MyClass heißt jetzt Data. Hoffe damit wird es klarer.
Geht es dir um den Benutzer der Anwendung oder den Benutzer einer Bibliothek (also auch Entwickler)?
Den Nutzer einer Anwendung würde ich über ein internes Logging- oder Messaging-System benachrichtigen.
Bei Übertragungen oder ähnlichem ist es üblich eine Response-Klasse zu erstellen, welche das erfragte Objekt als Attribut enthält und gleichzeitig einen Status-Code und eine Message enthält.
du gibst ja schon bei manchen ausnahmen eine spezielle instanz zurueck. Die Frage ist, woher weiss der nutzer dass es sich hierbei um einen Fehler eigentlich handelt ? Wenn er isInvalid oder so danach nicht aufruft hat er eine simple instanz von MyClass und macht damit was er will.
Dann waere es sinnvoll dass die Methode einen Rueckgabentyp hat, der das Ergebnis (instanz) und die Information traegt, ob alles ok ist bzw moegliche Exceptions haelt. ODer einfach ein simpler enumtyp wie NO_CONNECTION, TIMEOUT, WAS_AUCH_IMMER_FALSCH geht. Dann liegt es in der verantwrotung des nutzers per enum abfrage zu sehen ob alles gut ging oder nicht und darauf reagieren
[quote=inv_zim;71846]Geht es dir um den Benutzer der Anwendung oder den Benutzer einer Bibliothek (also auch Entwickler)?[/quote]Mit Nutzer meine ich den Nutzer der Methode, also den Entwickler.
In deinem Beispiel würde ich einfach null zurückgeben und den Grund ins log schreiben.
Für den Fall, dass du im Code Entscheidungen anhand des Fehlers treffen musst und du keine Exceptions nehmen willst: Dann würde ich auch null zurückgeben [einen Logeintrag verfassen] und ein Feld in der Klasse (hier: Dataloader) setzen, das abgerufen werden kann. Dieses gibt dann aber entweder ein enum oder Konstanten zurück.
also LEO meint zum Thema Exception: Ausnahmebedingung/Fehler/… Wenn also das Internet weg ist, dann ist das eine Ausnahme. Zumal Du schon gesagt hast das die Daten mal nicht vorhanden sind, im Regelfall aber schon. Wenn nicht, dann ist das eine Ausnahmen (LEO erwähnte ich ja schon).
Außerdem was bedeutet teuer? 5 ms extra in einer GUI Applikation - der Benutzer wird sich dann schon über das Einfrieren der GUI beschweren Wenn mir mal jemand das Argument „Exception sind teuer“ vernüftig erklären kann, dann wäre ich sehr dankbar.
Zudem finde ich persönlich das Abfangen einer Exception sehr viel unübersichtlicher als die Überprüfung eines Rückgabewertes. Aber da das mein persönlicher Geschmack ist, würde ich das jetzt nicht als Argument in den Ring werfen.
stimmt - sonst stolpert da noch jemand im Ring drüber :o) - außerdem wirds unübersichtlich wenn alle ihre Argumente in den Ring werfen
Ich denke, hier wird auf das Erzeugen des StackTrace angespielt. Dies ist etwas Arbeit im Hintergrund. Und das Mitführen des StackTrace führt auch dazu, dass eine Exception mehr RAM verbraucht, als ein einfaches Objekt (wie hier das Null-Object bspw.). Aber ob das jetzt wirklich „teuer“ ist, sei mal dahin gestellt…
Und zum Thema: Ich denke, dass bei dem hier angedachten Anwendungsfalkl das Null-Object Pattern überstrapaziert ist. Das Null-Object sollte schon ein Objekt sein, dass fachlich einen Sinn macht. Z.B. kann eine leere Collection oder ein leerer String fachlich Sinn machen und eine gute Alternative zum returnen von null sein. Ein leeres Datenobjekt kann ich mir irgendwie nicht sinnvoll vorstellen. Das riecht mir zu sehr nach „magic values“.
Die Idee ist nicht schlecht, aber sie bringt leider ein paar Nachteile mit sich, wo man abwägen muss, ob man das möchte. Ich denke deine Lösung wäre zu speziell und hat viel Fehlerpotenzial, was das Risiko von Programmierfehlern erhöht, die sehr schwer zu entdecken sein können. Man muss ja wissen, dass man Data nicht auf null prüfen darf, sondern die statische Methode DataLoader.isInvalid(Data) benutzen muss! Für mich wäre es intuitiver dann eher eine isValid() Instanzmethode in Data anzubieten.
Denn wenn ich als Entwickler das Data Objekt irgendwo im Code benutzen möchte, muss ich wissen, dass ich dieses Objekt mit einer Klasse DataLoader und speziell mit der Methode isInvalid() prüfen muss. Sehr umständlich finde ich.
Ich finde es gut, dass du versuchst die teuren Exceptions zu ersetzen. Aber ich denke du wirst das gerade in deinen Beispielen kaum verhindern können, gerade für TIMEOUTs oder NOCONNECTION werfen doch viele Libs ohnehin ihre eigenen Exceptions.
Allerdings finde ich, dass es sich lohnt von Fall zu Fall abzuwägen, ob man teures Exception Handling nutzt oder doch lieber eine sparsamere Fehlerbehandlung implementiert. Aber die Komplexität sollte nicht zu sehr erhöht werden, sonst leiden andere wichtige Eigenschaften deines Programms: Verständlichkeit, Erweiterbarkeit, Wartbarkeit, Testbarkeit, etc.
Exceptions waren jedenfalls frueher (ich vermute mal, das ist immer noch so), deshalb so „teuer“, weil das Erzeugen des Stacktraces und zurecksetzen des Stacks furchtbar teuer ist. Natuerlich faellt das beim Werfen einer oder zweier Exceptions niemals auf - aber in einem Loop kann sowas der Killer schlechthin sein. Z.B. wenn man versucht mit Integer.parseInt + Exceptionhandling festzustellen, ob Werte in einer CSV Datei valide sind. Ganz schlechte Idee
Ansonsten ist die Idee net so dumm, immerhin sieht man immer oefter Code, wo anstatt null zurueckliefern ein neutrales Element (NullData um beim Beispiel oben zu bleiben) zurueckgeliefert wird.
Ich persoenlich mag sowas aber nicht so besonders, denn ich bin Fan von „Fail Fast“. Soll heissen, lieber bekomm ich ne NullPointerException eher, als eine falsche Berechnung spaeter irgendwann. Im Fall von oben wuerd ich null zurueckliefern und ein @Nullable an die Methode packen - da meckert dann eine vernuenftige IDE, sobald man den Check auf null vergessen hat.
ist sicher auch eine Sache der Situation. Ich liefere gerade in einem Projekte einen Benutzer mit gar keinen Rechten (Rechte.NONE) zurück (wenn der Benutzer nicht gefunden wurde), anstatt null. Ansonsten bin ich auch für Exceptions.
In anderen Java nahen Programmiersprachen, ich sehe auf dich Scala, hört man immer auch von Monads.
Im geschilderten Fall könnte man dann vom sogenannten “Maybe Monad” sprechen.
Wies genau funktioniert habe ich noch nicht ganz durchschaut, hab mich aber auch nicht großartig damit befaßt.
Vorteil ist, dass ein Objekt zurückgegeben werden kann, dass nicht “validiert” werden muß und auf dem dennoch Methoden aufgerufen werden können, sowie ein vernünftiges Ergebnis geliefert wird.