Fehlerhandling bei POJOs

flossy

Mitglied
Auf welcher Ebene wird üblicherweise das Fehlerhandling beim Manipulieren von POJOs implementiert?
Überprüfe ich innerhalb meiner "Daten-Klasse", ob alles in Ordnung ist, oder prüft die darauf zugreifende Klasse aktiv?

Also, so: ?

Java:
  private int eineIntVariable = 0; // 0 sei der unzulässige wert
  public int getValue(){
     return eineIntVariable;
  }
  public void setValue( int val ){
    this.eineIntVariable = val;
  }


oder so: ?
Java:
  private int eineIntVariable = 0; // 0 sei der unzulässige wert
  public int getValue(){
     if(eineIntVariable == 0 ){
        throw new Exception("Es wurde kein Wert gesetzt");
     } 
     return eineIntVariable;
  }
  public void setValue( int val ){
     if(val == 0){
        throw new Exception("Der wert 0 ist ungültig");
     } 
    this.eineIntVariable = val;
  }
oder so: ?
Java:
  private int eineIntVariable = 0; // 0 sei der unzulässige wert
  public int getValue() throws Exception {
     if(eineIntVariable == 0) {
        throw new Exception("Es wurde kein Wert gesetzt");
     } 
     return eineIntVariable;
  }
  public void setValue( int val ) throws Exception {
     if(val == 0){
        throw new Exception("Der wert 0 ist ungültig");
     } 
    this.eineIntVariable = val;
  }
 

Wildcard

Top Contributor
Üblicherweise sollte sich ein Modell selbst konsitent halten können. Ob dies nun durch Exceptions, einen Validierungsvisitor, eine Validation Strategy, oder sonstwas geschieht hängt maßgeblich von der Anwendung ab. War nur ein Beispiel, aber nur zur Sicherheit: eine generische Exception ist eine schlechte Wahl. Lieber eine IllegalArgumentException oder etwas domainspezifisches.
 

musiKk

Top Contributor
Getter sollten meiner Meinung nach niemals Exceptions werfen. Der Zustand eines falschen Wertes muss vorher festgestellt werden. Wie Wildcard sagt: IllegalArgumentException in Konstruktor und/oder Setter wird oft verwendet.

Generell ist es imho nicht verkehrt, wenn beide (also Aufrufer und Aufgerufener) prüfen müssen, ob die Argumente zugelassen sind. Im JavaDoc zum Setter sollte vermerkt werden, welche Werte zugelassen sind und welche nicht (als Vertrag bezeichnet). Daran muss sich der Aufrufer halten, ansonsten ist der Vertrag verletzt und man darf sich nicht über eine IllegalArgumentException oder NPE wundern.
 
S

SlaterB

Gast
Getter sollten meiner Meinung nach niemals Exceptions werfen.
getter die nur getten sind ja eher überflüssig,
ein getter kann auch Lazy-Initialization machen, dann sind Exceptions möglich
oder getFirst() wenn eine interne Liste leer ist

oder getSelected() wenn nichts selektiert ist, wobei das ein akzeptabler Zustand ist, aber mit isSelected() geprüft werden kann,
 

flossy

Mitglied
Getter sollten meiner Meinung nach niemals Exceptions werfen. Der Zustand eines falschen Wertes muss vorher festgestellt werden.
Wie Wildcard sagt: IllegalArgumentException in Konstruktor und/oder Setter wird oft verwendet.
Hmm...dann werfen die Setter Exceptions, die Getter aber nicht. Spricht, die "Validierung" findet auf zwei unterschiedlichen Abstraktionsebenen statt. Ist das nicht etwas unsauber?

Generell ist es imho nicht verkehrt, wenn beide (also Aufrufer und Aufgerufener) prüfen müssen, ob die Argumente zugelassen sind.
Das ist sicherlich eine sichere Vorgehensweise, doch ist es zum Einen nötig und zum Anderen sauber?

Im JavaDoc zum Setter sollte vermerkt werden, welche Werte zugelassen sind und welche nicht (als Vertrag bezeichnet). Daran muss sich der Aufrufer halten, ansonsten ist der Vertrag verletzt und man darf sich nicht über eine IllegalArgumentException oder NPE wundern.

Ja, sich an eine API zu halten ist vernünftig. Doch was passiert, wenn es leichte Änderungen einer API gibt? Wenn ich eine stabile und komplexe Software entwickeln möchte, reicht es wahrscheinlich nicht aus zu sagen "man muss sich halt an die API halten"!?. Anders gefragt: Wie konstruiert man den Code so, dass bei einer Teiländerung nicht plötzlich das ganze System abstürzt, sondern aussagekräftige und nachvollziehbare Fehlermeldungen erzeugt werden?

SlaterB hat gesagt.:
getter die nur getten sind ja eher überflüssig,
ein getter kann auch Lazy-Initialization machen, dann sind Exceptions möglich
oder getFirst() wenn eine interne Liste leer ist
Nun, wenn ich nach einer Liste frage, die noch nicht initialisiert worden ist, kann ich einfach eine leere Liste erstellen und zurückgeben. Doch was ist, wenn ich einen Wert abfragen möchte, von dem der weitere Programmablauf abhängt? Wenn ich dann einfach einen Wert annehme, der gar nicht aktiv gesetzt worden ist, führt das unter Umständen zu einem nicht nachvollziehbarem Verhalten. Sprich, wenn es wirklich notwendig ist, dass ein Wert gesetzt worden ist, dies dann aber nicht der Fall ist, dann sollte ich meiner Ansicht nach eine Exception werfen, oder?


Wildcard hat gesagt.:
Üblicherweise sollte sich ein Modell selbst konsitent halten können.
ja, das ist wahrscheinlich eine der wichtigsten Grundsätze.

Wildcard hat gesagt.:
zur Sicherheit: eine generische Exception ist eine schlechte Wahl. Lieber eine IllegalArgumentException oder etwas domainspezifisches.
ja, das stimmt, eine generische Exception ist meist tatsächlich wertlos.

Wenn ich mal eure Meinungen als Code zusammenfasse, dann sähe die Sache so aus:
Java:
public class EinePOJOKlasse {

  private int eineIntVariable = 0; // 0 sei der unzulässige wert

  public int getValue()  {
     return eineIntVariable;
  }

  public void setValue( int val ) {
     if(val == 0){
        throw new IllegalArgumentException("Der wert 0 ist ungültig");
     } 
    this.eineIntVariable = val;
  }
}
Java:
public class EineUebergeordneteKlasse {

  public void tueWas() throws EineEigeneException {
    EinePOJOKlasse pojo = new  EinePOJOKlasse();
    int val = pojo.getValue();
    if(val == 0){
       log.error("unzulässiger wert");
       throw new EineEigeneException();
    }
    else{
      log.info("prima hat geklappt")
    }
  }
}

Ich bin nicht der erfahrene Meister, doch irgendwie sieht das etwas "vermischt" und chaotisch aus aus, oder?
 

Wildcard

Top Contributor
Eine Klasse darf sich immer auf die API verlassen, da diese den Vertrag eingeht.
WEnn pojo also sagt das bei getValue niemals 0 geliefert wird, dann ist dieser Code unnötig:
Java:
    int val = pojo.getValue();
    if(val == 0){
       log.error("unzulässiger wert");
       throw new EineEigeneException();
    }

Wenn sich pojo nicht an diesen Vertrag hält, decken das die Unit Tests auf.
 

musiKk

Top Contributor
getter die nur getten sind ja eher überflüssig,

Nur machen das trotzdem irgendwie 99% aller Getter.

Hmm...dann werfen die Setter Exceptions, die Getter aber nicht. Spricht, die "Validierung" findet auf zwei unterschiedlichen Abstraktionsebenen statt. Ist das nicht etwas unsauber?

Getter und Setter sind ja nicht zwei verschiedene Ebenen. Das Model ist nur eine Ebene. Validierung sollte eben nicht beim Lesen, sondern beim Schreiben (oder in einem separaten Validierungsschritt) stattfinden.

Das ist sicherlich eine sichere Vorgehensweise, doch ist es zum Einen nötig und zum Anderen sauber?

Wenn Du Dein zweites Code-Beispiel meinst, dann haben wir aneinander vorbeigeredet. Alles, was im Setter nicht zugelassen wird, muss im Getter auch nicht geprüft werden. Das kann da durchaus auch in der Dokumentation vermerkt werden à la "kann niemals 0 sein" o. ä.

Ja, sich an eine API zu halten ist vernünftig. Doch was passiert, wenn es leichte Änderungen einer API gibt? Wenn ich eine stabile und komplexe Software entwickeln möchte, reicht es wahrscheinlich nicht aus zu sagen "man muss sich halt an die API halten"!?.

Sagen wir mal so. Wenn zu viel Logik in den Gettern und Settern steckt, dann ist das Design vielleicht auch zu überdenken. Im Normalfall sind die abgesehen von der input validation recht dumm. Wenn sich bei den zugelassenen Werten etwas ändert, dann ist die Software eben noch nicht stabil. Wer eine Alpha-Version einer Bibliothek einsetzt, muss durchaus damit rechnen, dass es Änderungen in der API gibt.

Nun, wenn ich nach einer Liste frage, die noch nicht initialisiert worden ist, kann ich einfach eine leere Liste erstellen und zurückgeben. Doch was ist, wenn ich einen Wert abfragen möchte, von dem der weitere Programmablauf abhängt? Wenn ich dann einfach einen Wert annehme, der gar nicht aktiv gesetzt worden ist, führt das unter Umständen zu einem nicht nachvollziehbarem Verhalten. Sprich, wenn es wirklich notwendig ist, dass ein Wert gesetzt worden ist, dies dann aber nicht der Fall ist, dann sollte ich meiner Ansicht nach eine Exception werfen, oder?

Klingt nach einer IllegalStateException; Du sagst also, zu einem bestimmten Zeitpunkt darf eine gewisse Methode nicht aufgerufen werden, weil vorher etwas anderes gemacht werden muss. So etwas ist meiner Meinung nach besser zu vermeiden. Wenn die Reihenfolge von Methodenaufrufen wichtig ist, führt das oft zu schwer bedien- und wartbarem Code. Stell Dir vor, Du hast drei Methoden, die in einer ganz bestimmten Reihenfolge aufgerufen werden müssen. Nun soll noch eine dazu, die von ein paar Werten abhängt. Jetzt musst Du Dir genau überlegen, zu welchem Zeitpunkt welche Werte schon bekannt sind und welche nicht.

Egal was gesagt wird: Ausnahmen gibt es (fast) immer. Diese Aussagen kann man nicht pauschalisieren. Es kann garantiert zu jeder Aussage mit wenig Mühe ein Gegenbeispiel konstruiert werden. Ich habe nichts gegen Grundsatzdiskussionen, aber wenn es zu abstrakt ist (und ich finde, das hier ist schon recht abstrakt), sind die Aussagen eben aufgrund der vielen Besonderheiten einzelner Fälle häufig nur bedingt brauchbar.
 

flossy

Mitglied
Validierung sollte eben nicht beim Lesen, sondern beim Schreiben (oder in einem separaten Validierungsschritt) stattfinden.
[...]
Alles, was im Setter nicht zugelassen wird, muss im Getter auch nicht geprüft werden. Das kann da durchaus auch
in der Dokumentation vermerkt werden à la "kann niemals 0 sein" o. ä.
Gut, das macht in gewisser Weise Sinn, wenn ich konsequent beim Schreiben validiere. Doch solange man einen Zustand noch nicht gesetzt hat, hat man beim Abfragen einen undefinierten Rückgabewert.
Demnach müsste man (wenn keine Validierung beim Getter stattfindet) permanent daran denken, den abgefragten Wert zu validieren, so wie es Wildcard geschrieben hat.

Sagen wir mal so. Wenn zu viel Logik in den Gettern und Settern steckt, dann ist das Design vielleicht auch zu überdenken.
ja, da ist was drann. Vielleicht sollte man für kritische POJOs eine extra Methode/Klasse zur Validerung schreiben? Dann hätte man etwa sowas:
Java:
    EinePojoKlasse pojo = new EinePojoKlasse();
    doSomethingWithIt(pojo);
    // dann kommt der Bereich, wo die Richtigkeit entscheidend ist
    try{
       validate(pojo);
       int val = pojo.getValue();
    }
    catch(PojoNotValidException e){
        log.error(e);
        handleError();
    }
 

flossy

Mitglied
Ich habe nichts gegen Grundsatzdiskussionen, aber wenn es zu abstrakt ist (und ich finde, das hier ist schon recht abstrakt), sind die Aussagen eben aufgrund der vielen Besonderheiten einzelner Fälle häufig nur bedingt brauchbar.
Gut, abstrakt wollt ich es auch behandeln, in der Hoffnung, ein allgemeines "Best Practice" zu erlernen.
Es scheint jedoch so, dass der Kontext entscheidender dafür ist und es keine einfachen Regeln wie "bei Situation A immer Löung B vorziehen" gibt.

Daher einmal etwas konkretes:
Ich habe eine Bibliothek für ein TCP-basiertes Protokoll geschrieben. Es funktioniert soweit alles ganz gut. Nun möchte ich durch ein "Refacturing" die Qualität verbessern und den Code säubern/aufräumen.
Zusätzlich schreibe ich jede Menge Uni-Tests, um auch hier Fehler vorzubeugen. Dabei frage ich mich, wo und wie ein sauberes Fehlerhandling gestaltet werden kann. Als Beispiel gibt es ein Nachrichtenobjekt, welches die Bibliothek zur Verfügung stellt. Nun möchte ich die Situation abfangen, in der ein Entwickler aus-versehen entscheidende variablen nicht gesetzt hat.

Java:
public class Message extends BasicMessage implements MessageInterface{

  @Override
  public MessageInterface.Command getCommand() {
      return this.command;
  }

  // ...und ein paar weitere methoden

}

Diese Nachricht kann nur sinnvoll weiter verarbeitet werden, wenn der Befehl gesetzt worden ist.
Wo fange ich dies nun am Besten ab? Beim Senden?

Java:
public class Server extends AbstractServer {

    public void sendMessage(MessageInterface msg, Socket client) {

      // hier die Nachricht validieren?
      try{
          validateMessage(msg);
          byte[] byteMessage = this.createByteMessage(msg);
          this.sendBytes(client, byteMessage);
      }
      catch(NoValidMessageException e)
         // ... handling
      }

	// ..weitere Methoden
}

Daher dachte ich, es wäre doch das Beste, wenn ich direkt beim Nachrichten Getter eine Exception werfe. Also an der Stelle, wo es als aller erstes entdeckt werden könnte, oder?
 

Ähnliche Java Themen

Neue Themen


Oben