frankx: Rückgabewerte und Fehlerbehandlung allgemein bzw. am Beispiel

Hellihello,

mal angenommen, ich bau mir eine Klasse, die (eine) CSV-Dateie(n) einlesen soll und dann als mehrdimensionales Array (List of Hash) zurück gibt.

$my_csv_reader = new My_CSV_Reader("file_name.csv")
$table_as_list_of_hash = $my_csv_reader->get_table_as_list_of_hash();

Jetzt unterscheide ich mal drei Fälle:

1. Der Dateipfad existiert nicht. Was gibt dann die Funktion zurück? Am besten ein leeres Array? Sollte sie immer ein Array zurückgeben? In PHP-Documentor wird für jede Funktion ja zB. ein @return angegeben, also der Variablentyp, den die Funktion zurückgibt. Wie unterscheide ich aber dann das von

2. Dateipfad existiert, dass File ist aber leer bzw. enthält nur leere Einträge?

Dass die Funktion ->get_table_as_list_of_hash() in Fall 1. oder 2. dann selbst anfängt zu sprechen wie mit
echo "file nicht vorhanden" oder echo "file leer" scheint mir nicht besonderes glücklich. Die Ausgabengestaltung ist ja nicht ihre Aufgabe. Genauso lese ich ja auch immer wieder dass die("file nicht vorhanden") keine adäquate Fehlerbehandlung sei, das leuchtet ja auch ein.

Man könnte natürlich auf die Fallunterscheidung 1./2. verzichten und weiß immerhin: entweder ist die Tabelle leer oder sie existiert nicht. Oder man baut weitere Funktionen ein wie

->exists_file()
->is_empty()

die dann abgefragt werden könnten von "Controller", wenn das Rückgabe-Array leer wäre?

Dank für etwaige Tipps im Voraus und Gruß,

frankx

--
tryin to multitain  - Globus = Planet != Welt
    1. Der Dateipfad existiert nicht. Was gibt dann die Funktion zurück?

    Einen falschen Wert, kombiniert mit einer Fehlermeldung, die man abfragen kann.

    1. Dateipfad existiert, dass File ist aber leer bzw. enthält nur leere Einträge?

    Leeres File: wie 1.
    Leere Einträge: leeres Array.

    Dass die Funktion ->get_table_as_list_of_hash() in Fall 1. oder 2. dann selbst anfängt zu sprechen wie mit
    echo "file nicht vorhanden" oder echo "file leer" scheint mir nicht besonderes glücklich.

    Sprechen nur in Form von abfragbaren Fehlermeldungen. Man kann noch bei der instantiierung ein flag setzen lassen und Warnungen protokollieren bzw. auswerfen (wäre bei einem leeren file evtl. sinnvoll)

    Man könnte natürlich auf die Fallunterscheidung 1./2. verzichten und weiß immerhin: entweder ist die Tabelle leer oder sie existiert nicht. Oder man baut weitere Funktionen ein wie

    ->exists_file()
    ->is_empty()

    Dann würde die Klasse nur einen Teil ihrer Aufgaben erledigen. Sowas wie "wenn file existiert und nicht leer ist, dann instantiiere Klasse" finde ich doof.

    Meine Meinung.

    ~JJ

    1. Hellihello JJ,

      1. Der Dateipfad existiert nicht. Was gibt dann die Funktion zurück?
        Einen falschen Wert, kombiniert mit einer Fehlermeldung, die man abfragen kann.

      "falscher Wert" gleich "leeres Array", oder?

      1. Dateipfad existiert, dass File ist aber leer bzw. enthält nur leere Einträge?
        Leeres File: wie 1.

      Jau.

      Leere Einträge: leeres Array.

      Dass die Funktion ->get_table_as_list_of_hash() in Fall 1. oder 2. dann selbst anfängt zu sprechen wie mit
      echo "file nicht vorhanden" oder echo "file leer" scheint mir nicht besonderes glücklich.
      Sprechen nur in Form von abfragbaren Fehlermeldungen. Man kann noch bei der instantiierung ein flag setzen lassen und Warnungen protokollieren bzw. auswerfen (wäre bei einem leeren file evtl. sinnvoll)

      Ah, so war das unten gemeint.

      Man könnte natürlich auf die Fallunterscheidung 1./2. verzichten und weiß immerhin: entweder ist die Tabelle leer oder sie existiert nicht. Oder man baut weitere Funktionen ein wie

      ->exists_file()
      ->is_empty()
      Dann würde die Klasse nur einen Teil ihrer Aufgaben erledigen. Sowas wie "wenn file existiert und nicht leer ist, dann instantiiere Klasse" finde ich doof.

      Ich hatte das jetzt so gedacht:

        
      $my_csv_reader = new My_CSV_Reader("file_name.csv");  
      $my_table_as_list_of_hash = $my_csv_reader->get_table_as_list_of_hash();  
        
      // und dann  
        
      if (!count($my_table_as_list_of_hash)) {  
       if (!$my_csv_reader->exists_file()) {  
         echo "Datei existiert nicht"; //bzw. an View weiterreichen  
       }  
       elseif (!$my_csv_reader->is_empty()) {  
         echo "Datei ist leer"; //bzw. an View weiterreichen  
       }  
       else {  
         echo "Die Liste ist leer, keine Ahnung warum"; //bzw. an View weiterreichen  
       }  
        
      }  
        
      
      

      Dank und Gruß,

      frankx

      --
      tryin to multitain  - Globus = Planet != Welt
  1. Hallo frankx,

    Fehlerbehandlung über Rückgabewerte ist meist eine ziemlich unsaubere Angelegenheit. Erstmal musst Du für verschiedene Fehler unterschiedliche Rückgabewerte reservieren oder mit so Hacks wie globalen Fehlervariablen arbeiten und dann wird auch noch der aufrufende Code komplizierter, weil man diese Rückgabewerte abprüfen muss.
    Glücklicherweise wurde ein Konzept entwickelt, um das Problem zu umgehen: Exceptions.

    Du musst Dir also erstmal überlegen, welche der auftretenden Situationen ein Fehler ist, bei dem (jedenfalls typischerweise) nicht der normale Programmablauf weiter verfolgt werden kann.

    Vermutlich ist "Datei leer" in Ordnung und das Programm kann da weiter arbeiten mit einem leeren Array, "Datei existiert nicht" oder sonstige Zugriffsfehler hingegen deuten auf ein Problem, z.B. einen falsch angegebenen Dateinamen hin, müssen also anders behandelt werden --> Wirf in dem Fall eine Exception.
    Die PHP-API arbeitet wahrscheinlich in solchen Fällen oft mit Rückgabewerten, sie ist aber auch nicht gerade ein Beispiel für gutes API-Design.

    Grüße

    Daniel

    1. Hellihello Daniel,

      Du musst Dir also erstmal überlegen, welche der auftretenden Situationen ein Fehler ist, bei dem (jedenfalls typischerweise) nicht der normale Programmablauf weiter verfolgt werden kann.

      Vermutlich ist "Datei leer" in Ordnung und das Programm kann da weiter arbeiten mit einem leeren Array, "Datei existiert nicht" oder sonstige Zugriffsfehler hingegen deuten auf ein Problem, z.B. einen falsch angegebenen Dateinamen hin, müssen also anders behandelt werden --> Wirf in dem Fall eine Exception.

      Die PHP-API

      Wieso API? (Application Programming Interface)?

      arbeitet wahrscheinlich in solchen Fällen oft mit Rückgabewerten, sie ist aber auch nicht gerade ein Beispiel für gutes API-Design.

      Rückgabewerte für was? Und wenn das kein "gutes API-Design" ist, kann man das in den eigenen Programmierungen entsprechend anpassen?

      Dank und Gruß,

      frankx

      --
      tryin to multitain  - Globus = Planet != Welt
      1. echo $begrüßung;

        arbeitet wahrscheinlich in solchen Fällen oft mit Rückgabewerten, sie ist aber auch nicht gerade ein Beispiel für gutes API-Design.

        Rückgabewerte für was?

        Rückgabewerte, um Fehler zu signalisieren. Ein sehr bekanntes Beispiel ist mysql_query(). Diese Funktion gibt eine Ressourcenkennung zurück. (So ist es auch in der Dokumentation zur Funktionssignatur angeführt.) Im Fehlerfall gibt es stattdessen aber ein false. (Das ist nur im Fließtext dokumentiert.)

        Und wenn das kein "gutes API-Design" ist, kann man das in den eigenen Programmierungen entsprechend anpassen?

        Du kannst dir um alles Wrapper schreiben, die die Rückgabewerte auswerten und dann Exceptions werfen. Ob das sinnvoll ist oder den Aufwand rechtfertigt, ...

        echo "$verabschiedung $name";

    2. Hellihello Daniel,

      Die PHP-API arbeitet wahrscheinlich in solchen Fällen oft mit Rückgabewerten, sie ist aber auch nicht gerade ein Beispiel für gutes API-Design.

      http://www.w3schools.com/php/php_exception.asp

      darin:

      Proper exception code should include:

      1. Try - A function using an exception should be in a "try" block. If the exception does not trigger, the code will continue as normal. However if the exception triggers, an exception is "thrown"
         2. Throw - This is how you trigger an exception. Each "throw" must have at least one "catch"
         3. Catch - A "catch" block retrieves an exception and creates an object containing the exception information

        
        
      <?php  
      //create function with an exception  
      function checkNum($number)  
       {  
       if($number>1)  
        {  
        throw new Exception("Value must be 1 or below");  
        }  
       return true;  
       }  
        
      //trigger exception in a "try" block  
      try  
       {  
       checkNum(2);  
       //If the exception is thrown, this text will not be shown  
       echo 'If you see this, the number is 1 or below';  
       }  
        
      //catch exception  
      catch(Exception $e)  
       {  
       echo 'Message: ' .$e->getMessage();  
       }  
      ?>  
      
      

      Dank und Gruß,

      frankx

      --
      tryin to multitain  - Globus = Planet != Welt
    3. Hellihello Daniel die Dritte,

      Glücklicherweise wurde ein Konzept entwickelt, um das Problem zu umgehen: Exceptions.

      Damit "verlasse" ich aber die "Control", oder?

      Nicht selten kommt es doch vielleicht vor, dass man

      1. entweder den Inhalt (also hier die Tabelle) anzeigen möchte
      oder
      2. Mitteilen möchte: "die Datei hat keinen Inhalt"
      oder eben
      3. Es existieren überhaupt keine Daten derart.

      Alles drei sind doch quasi oder wie auch immer gennannt User-Messages bzw. würden in dem Fall in einer User-View angezeigt werden.

      Eine "Exception" aber würde ja in der lesenden (Model) - Klasse dann die Control außen vor lassen. Die aber wiederum würde doch nach o.g. Überlegungen eben lediglich eine Fallunterscheidung (content 1, 2, 3) vornehmen und das an die View weitergeben. So hätte ich jetzt gedacht.

      Dass überhaupt ein Fehler oder eine gravierende Ausnahme entstünde könnte ich in dem Fall ja abfangen, indem die lesende Klasse testet, ob ein File diese Namens überhaupt vorhanden ist (is_file()).

      ???

      Dank und Gruß,

      frankx

      --
      tryin to multitain  - Globus = Planet != Welt
      1. Hallo Robert,

        Dass überhaupt ein Fehler oder eine gravierende Ausnahme entstünde könnte ich in dem Fall ja abfangen, indem die lesende Klasse testet, ob ein File diese Namens überhaupt vorhanden ist (is_file()).

        nein. Das ist Auswertung von Rückgabewerten, kein Exception-Handling.

        Bei Exception-Handling bekommst beim Versuch, eine nicht vorhandene Datei zu öffnen, so etwas wie eine FileNotFoundException (gilt auch für andere Sprachen, nicht nur Java). Du rechnest selbstverständlich mit einer solchen Exception und reagierst im catch-Bereich entsprechend. So kannst Du gezielt zwischen Datei nicht gefunden und Berechtigungsproblemen oder sonstigen Fehlern unterscheiden.

        Leider ist in PHP5 die eingebaute Unterstützung für Exception-Handling sehr gering. Die Gründe dafür hat Dir dedlfix dargelegt.

        Freundliche Grüße

        Vinzenz

      2. echo $begrüßung;

        Glücklicherweise wurde ein Konzept entwickelt, um das Problem zu umgehen: Exceptions.

        Damit "verlasse" ich aber die "Control", oder?

        Was auch immer du unter Control verstehst ... Wenn eine Exception geworfen wurde wird der Programmfluss unterbrochen und beim sich zuständig fühlenden catch weitergemacht.

        Nicht selten kommt es doch vielleicht vor, dass man

        1. entweder den Inhalt (also hier die Tabelle) anzeigen möchte

        Rückgabewert: Inhalt

        1. Mitteilen möchte: "die Datei hat keinen Inhalt"

        Rückgabewert: Inhalt, aber leer. Muss man gegebenenfalls im normalen Programmfluss fallunterscheiden. Wenn leerer Inhalt aber als Fehler eingestuft ist, dann Exception werfen.

        1. Es existieren überhaupt keine Daten derart.

        Wenn die Datei nicht existiert und das als Fehler eingestuft ist, dann gibts eine Exception. Wenn das ein normaler Zustand ist, z.B. beim allerersten Aufruf nach der Installation, dann entspricht das wohl eher Fall 2.

        Alles drei sind doch quasi oder wie auch immer gennannt User-Messages bzw. würden in dem Fall in einer User-View angezeigt werden.

        Ob eine Anzeige erfolgen soll oder nicht, sollte keine Entscheidung der Fehler feststellenden Komponente sein. Die weiß ja nicht, in welcher Umgebung sie läuft, und wie darin die Fehlermeldung präsentiert werden soll, oder auch nicht. Sie signalisiert nur, dass etwas passiert ist.

        Eine "Exception" aber würde ja in der lesenden (Model) - Klasse dann die Control außen vor lassen. Die aber wiederum würde doch nach o.g. Überlegungen eben lediglich eine Fallunterscheidung (content 1, 2, 3) vornehmen und das an die View weitergeben. So hätte ich jetzt gedacht.

        Ja, Exceptions sind eine Art Fallunterscheidung, aber sie unterscheiden nicht zwischen zwei gewollten Zuständen sondern zwischen gewollten Zuständen und Ausnahmen.

        Dass überhaupt ein Fehler oder eine gravierende Ausnahme entstünde könnte ich in dem Fall ja abfangen, indem die lesende Klasse testet, ob ein File diese Namens überhaupt vorhanden ist (is_file()).

        Es kommt immer darauf an, wie du bestimmte Zustände definierst. Ist es eine Ausnahme oder ist es normales Verhalten? Der Sinn von Exceptions ist es, Ausnahmebehandlung aus dem normalen Programmfluss fernzuhalten, damit sich dieser auf das Wesentliche konzentrieren kann und der Lesefluss nicht ständig durch Fehlerbehandlung unterbrochen wird.

        echo "$verabschiedung $name";

      3. Hallo frankx,

        Dass überhaupt ein Fehler oder eine gravierende Ausnahme entstünde könnte ich in dem Fall ja abfangen, indem die lesende Klasse testet, ob ein File diese Namens überhaupt vorhanden ist (is_file()).

        Ja, es gibt im wesentlichen drei Möglichkeiten, mit Ausnahmen umzugehen:
        1. Die Ausnahme vorherzuerkennen
        2. Spezielle Rückgabewerte
        3. Exceptions

        1. Kann manchmal sinnvoll sein, z.B. wenn es einfach deutlich effizienter ist, ein paar Voraussetzungen zu prüfen als erstmal mit einer größeren Berechnung zu beginnen und dann einen Fehler zu bekommen. Das Problem dabei ist, dass es nicht immer geht (jemand kann die Datei ja zwischen Abfrage und Öffnen löschen) und dass man es für jeden einzelnen Anwendungsfall überprüfen muss.
        2. Dazu habe ich ja schon zwei Dinge gesagt, es wird umständlich, die Rückgabewerte auszuwerten und man muss die Fehlerbehandlung genau da machen, wo der Fehler auch auftritt.
        3. Hat eigentlich nur Vorteile ;-)

        Beispiel:

        Mit vorherigem Überprüfen: funktioniert nicht wirklich):
        if (file1.exists() && file2.exists() && file3.exists()) {
           data1 = parse(file1);
           data2 = parse(file2);
           data3 = parse(file3);
           ...
        } else {
          // Fehlerbehandlung
        }

        Mit Rückgabewert:
        data1 = parse(file1);
        if (!data1) {
          // Fehlerbehandlung
        }
        data2 = parse(file2);
        if (!data2) {
          // Fehlerbehandlung
        }
        data3 = parse(file3);
        if (!data3) {
          // Fehlerbehandlung
        }

        Mit Exceptions:
        try {
           data1 = parse(file1);
           data2 = parse(file2);
           data3 = parse(file3);
        } catch (FileNotFound e) {
          // Fehlerbehandlung
        }

        Die Komplexität der Fehlerbehandlung bei 1. und 2. ist schon relativ hoch, bei einen etwas komplizierteren Ablauf wird es aber noch viel schlimmer. Im 2. Fall wird sogar der eigentliche Ablauf mit der Fehlerbehandlung vermischt. Muss man die Fehlerbehandlung sogar noch über mehrere Funktionen hinweg delegieren, bricht das totale Chaos aus.

        Die Exceptions-Variante ist dagegen sehr simpel. Man braucht erstmal nur für jeden Fehlertyp (Wobei man durch Vererbung Fehler zusammenfassen kann und z.B. alle IO-Fehler gemeinsam behandeln) einen catch-Block und das war es. In PHP macht man die Fehlerunterscheidung wahrscheinlich nicht über mehrere Blöcke, sondern durch Abfrage des Typs. Das ist aber kein wesentlicher Unterschied.

        Man muss einen Fehler auch nicht sofort verarbeiten sondern kann das in aufrufenden Funktionen tun, ohne dass man dafür wiederum Rückgabewerte verwenden muss.

        Grüße

        Daniel

        1. Hellihello Zusammen und merci,

          Man muss einen Fehler auch nicht sofort verarbeiten sondern kann das in aufrufenden Funktionen tun, ohne dass man dafür wiederum Rückgabewerte verwenden muss.

          Wie kann ich denn aus Funktion foo, die Funktion bar aufruft, auf das in Funktion bar getrhrowte new Exception - Objekt zugreifen? Oder missverstandichwas?

          Dank und Gruß,

          frankx

          --
          tryin to multitain  - Globus = Planet != Welt
          1. Hellihello

            Man muss einen Fehler auch nicht sofort verarbeiten sondern kann das in aufrufenden Funktionen tun, ohne dass man dafür wiederum Rückgabewerte verwenden muss.

            Wie kann ich denn aus Funktion foo, die Funktion bar aufruft, auf das in Funktion bar getrhrowte new Exception - Objekt zugreifen? Oder missverstandichwas?

              
            <?php  
            class FileReader{  
              private $file;  
              private $fileDir='fileDir/';  
              public function __construct($file){  
                if(!file_exists("{$this->fileDir}{$file}.php")){  
                  throw new Exception('File '.$file.' not found');  
                }  
                $this->file=$file;  
              }  
              public function getContent(){  
                if(!$content=file_get_contents("{$this->fileDir}{$this->file}.php")){  
                  throw new Exception('Unable to read file contents');  
                }  
                return $content;  
              }  
            }  
              
              
            try{  
              $fr=new FileReader('sample_file'); // potential error condition  
              // echo file content  
              echo $fr->getContent(); // potential error condition  
            }  
            catch(Exception $e){  
              echo $e->getMessage();  
              exit();  
            }  
            
            

            aus: http://www.devshed.com/c/a/PHP/Error-Handling-in-PHP-Introducing-Exceptions-in-PHP-5/1/

            Dank und Gruß,

            frankx

            Dank und Gruß,

            frankx

            --
            tryin to multitain  - Globus = Planet != Welt
    4. echo $begrüßung;

      Die PHP-API arbeitet wahrscheinlich in solchen Fällen oft mit Rückgabewerten, sie ist aber auch nicht gerade ein Beispiel für gutes API-Design.

      Kein Wunder, PHP ist ja im Wesentlichen auch nicht objektorientiert aufgebaut. Aus Kompatibilität zur Historie finden sich Exceptions nur in eineigen wenigen neueren Modulen. Der große Rest ist aus Funktionen gebaut, und die liefern Fehlerzustände meist über Rückgabewerte.

      echo "$verabschiedung $name";

    5. Hallo,

      Fehlerbehandlung über Rückgabewerte ist meist eine ziemlich unsaubere Angelegenheit.

      da kann man unterschiedlicher Meinung sein - ich finde, Status- oder Fehlerinformationen über das Funktionsergebnis zurückzugeben, ist die sauberste Methode.
      Ich gestalte meine Funktionen immer so, dass sie
       a) in eingfachen Fällen das gewünschte Resultat oder einen klar definierten Fehlerwert liefern
       b) oder grundsätzlich nur einen Statuscode zurückgeben (in der Regel 0 für erfolgreiche Ausführung), und ihre "Nutzlast" über Argumente, die als Referenz übergeben werden.

      und dann wird auch noch der aufrufende Code komplizierter, weil man diese Rückgabewerte abprüfen muss.

      Das muss man im Prinzip sowieso. Ob man eine Handvoll Exceptions bastelt oder ein Funktionsergebnis auf ein paar konkrete Werte abprüft, läuft ungefähr auf dasselbe hinaus.

      Glücklicherweise wurde ein Konzept entwickelt, um das Problem zu umgehen: Exceptions.

      Genau das führt meiner Ansicht nach zu schwer durchschaubarem "Spaghetti-Code". Exceptions vermeide ich, wenn immer es geht, weil -da stimme ich frankx aus vollem Herzen zu- sie aus dem regulären Programm- oder Kontrollfluss ausbrechen.

      Die PHP-API arbeitet wahrscheinlich in solchen Fällen oft mit Rückgabewerten, sie ist aber auch nicht gerade ein Beispiel für gutes API-Design.

      Das einzig Negative ist da meiner Ansicht nach, dass der Rückgabewert bei vielen Funktionen je nach Situation entweder die "Nutzlast" transportiert oder eine Fehlerinformation. Macht man das aber konsequent, ist das eine saubere Sache.

      Schönes Wochenende,
       Martin

      --
      Ich liebe Politiker auf Wahlplakaten.
      Sie sind tragbar, geräuschlos, und leicht wieder zu entfernen.
        (Loriot, deutscher Satiriker)
      1. Hallo Martin,

        Du hast sowieso eine etwas ungewöhnliche Programmierphilosophie, ich schiebe das mal auf zu viel Assembler ;-)

        Ich gestalte meine Funktionen immer so, dass sie
        a) in eingfachen Fällen das gewünschte Resultat oder einen klar definierten Fehlerwert liefern
        b) oder grundsätzlich nur einen Statuscode zurückgeben (in der Regel 0 für erfolgreiche Ausführung), und ihre "Nutzlast" über Argumente, die als Referenz übergeben werden.

        Bei b) verwendest Du also keine Funktionen im eigentlichen Sinne. Bei solchen "Prozeduren" hat man z.B. auch nicht mehr die Möglichkeit, sie direkt ineinander einzusetzen. Du scheinst einen recht wenige funktionalen Programmierstil zu pflegen.

        Das muss man im Prinzip sowieso. Ob man eine Handvoll Exceptions bastelt oder ein Funktionsergebnis auf ein paar konkrete Werte abprüft, läuft ungefähr auf dasselbe hinaus.

        Man muss aber die eigentliche Berechnung nicht durch Fehlerbehandlung "verschmutzen". Ein Fehler führt zum Ausbruch aus dem normalen Ablauf und man kann den Fehler direkt behandeln. Zudem ist es eben einfach Fehlerbehandlung weiter zu reichen. Bei Rückgabewerten hat jede Funktion ihre eigene Fehler-Konvention.
        Ich hab durchaus auch schon in Sprachen bzw mit Bibliotheken programmiert, wo das so gehandhabt wird. Oft führt das sogar dazu, dass der Fehlerrückgabewert einfach ignoriert und mit diesen Daten dann irgendwie weitergearbeitet wird.
        Man kann es manchmal mit Exceptions übertreiben. Das ist meist dann der Fall, wenn ein Fehler eben kein Abbrechen (eines Teils) des normalen Programmablaufes bedeutet.

        Genau das führt meiner Ansicht nach zu schwer durchschaubarem "Spaghetti-Code". Exceptions vermeide ich, wenn immer es geht, weil -da stimme ich frankx aus vollem Herzen zu- sie aus dem regulären Programm- oder Kontrollfluss ausbrechen.

        Was machst Du, wenn Du eine in einem Programm lesen musst, aber das nicht kannst? Musst Du da nicht aus dem regulären Kontrollfluss ausbrechen?

        Macht man das aber konsequent, ist das eine saubere Sache.

        Mit diesem "Fehlercodes über den Rückgabewert, Daten über Referenzen"-Ansatz geht es vermutlich halbwegs. Schließlich hat man dann den Rückgabewert zum Fehlerpfad umfunktioniert und damit einen Teil des Exceptions-Konzepts quasi schon umgesetzt.
        Man muss diesen natürlich immer noch selbst weiterreichen und vermischt normalen mit Fehlerkontrollfluss. Programme dieser Art finde ich eigentlich meist umständlicher als nötig.

        Grüße

        Daniel

        1. Moin Daniel,

          Du hast sowieso eine etwas ungewöhnliche Programmierphilosophie, ich schiebe das mal auf zu viel Assembler ;-)

          ach, nur kein Neid! Ich weiß, Assembler ist quasi das Reinheitsideal der Programmierung. :-)

          b) oder grundsätzlich nur einen Statuscode zurückgeben (in der Regel 0 für erfolgreiche Ausführung), und ihre "Nutzlast" über Argumente, die als Referenz übergeben werden.
          Bei b) verwendest Du also keine Funktionen im eigentlichen Sinne. Bei solchen "Prozeduren" hat man z.B. auch nicht mehr die Möglichkeit, sie direkt ineinander einzusetzen.

          Ja, das stimmt natürlich. Das muss man beim Design berücksichtigen. Oft ist dieses Ineinander-Einsetzen aber vom Ablauf her sowieso nicht sinnvoll. Wenn doch, schmeißt man sich natürlich Knüppel zwischen die Beine, wenn man sich das selbst vereitelt.

          Du scheinst einen recht wenige funktionalen Programmierstil zu pflegen.

          Im Gegenteil. Wo immer ich die Möglichkeit sehe, ein Häufchen thematisch abgeschlossenen Code in eine Funktion auszulagern, dann tu ich das - außer wenn ich dieses Stück Programmcode dann wieder nur einmal nutze. In diesem Fall lasse ich's lieber inline.

          Das muss man im Prinzip sowieso. Ob man eine Handvoll Exceptions bastelt oder ein Funktionsergebnis auf ein paar konkrete Werte abprüft, läuft ungefähr auf dasselbe hinaus.
          Man muss aber die eigentliche Berechnung nicht durch Fehlerbehandlung "verschmutzen".

          Die eingestreuten try...catch-Blöcke empfindest du nicht als Verschmutzung?

          Ein Fehler führt zum Ausbruch aus dem normalen Ablauf und man kann den Fehler direkt behandeln.

          Ja, und die Fehlerbehandlung findet (im Programmcode) woanders statt als der reguläre Ablauf. Deswegen sagte ich, ich finde das Konzept unübersichtlich und manchmal schwer zu verfolgen.

          Zudem ist es eben einfach Fehlerbehandlung weiter zu reichen.

          Ja, eben:

          ~~~ function DoThis(blah, blubb, blaeh)
             { DoSomethingWith(blaeh);
               return (DoThat(blah+1, blubb));
             }

            
          Da ist die Fehlerbehandlung doch wunderbar weitergereicht. Andernfalls macht man eben sowas:  
            
            ~~~
          if (error=FunctionThatMightFail())  
             { // z.B. Aufruf einer zentralen Fehlerbehandlung  
               // oder Abfrage mit switch (error)  
             }  
            // weiter im normalen Programmfluss
          

          Bei Rückgabewerten hat jede Funktion ihre eigene Fehler-Konvention.

          Dann ist das Gesamtkonzept schlecht durchdacht. Wenn ich Module schreibe, die die Gesamtheit der Funktionalität zu einem bestimmten Thema bündeln, dann verende ich auch im ganzen Modul einheitliche Bezeichner, Fehlercodes und Aufrufkonventionen.

          Ich hab durchaus auch schon in Sprachen bzw mit Bibliotheken programmiert, wo das so gehandhabt wird. Oft führt das sogar dazu, dass der Fehlerrückgabewert einfach ignoriert und mit diesen Daten dann irgendwie weitergearbeitet wird.

          Das sollte man natürlich vermeiden - oder man muss *ganz genau* wissen, was man tut.

          Was machst Du, wenn Du eine in einem Programm lesen musst, aber das nicht kannst? Musst Du da nicht aus dem regulären Kontrollfluss ausbrechen?

          Keine Ahnung - diesem Satz fehlt anscheindend etwas. Ich verstehe ihn nicht. ;-)

          Man muss diesen natürlich immer noch selbst weiterreichen und vermischt normalen mit Fehlerkontrollfluss. Programme dieser Art finde ich eigentlich meist umständlicher als nötig.

          Ansichtssache. Ich finde es durchaus hilfreich, alle Kontrollflusspfade - den normalen wie auch den Fehlerfall - direkt zu sehen.

          Schönes Wochenende noch,
           Martin

          --
          Wer im Steinhaus sitzt, soll nicht mit Gläsern werfen.
          1. Hallo Martin,

            Im Gegenteil. Wo immer ich die Möglichkeit sehe, ein Häufchen thematisch abgeschlossenen Code in eine Funktion auszulagern, dann tu ich das - außer wenn ich dieses Stück Programmcode dann wieder nur einmal nutze. In diesem Fall lasse ich's lieber inline.

            Ich meinte nicht, dass Du Dein Programm nicht strukturierst. Mit "Funktionalem Programmierstil" meinte ich etwas, wie es im Extremfall in funktionalen Sprachen auftritt. Man drückt da ja letzten Endes alles aus, indem man Funktionen zusammensetzt. In imperativen Sprachen kann das natürlich ineffizient sein, weshalb man es da nicht immer durchziehen kann. Außerdem unterstützt die Syntax das je nach Sprache natürlich nicht so.

            Die eingestreuten try...catch-Blöcke empfindest du nicht als Verschmutzung?

            Naja ich muss sie ja meistens eben nicht einstreuen, oft reicht ein Block für eine ganze Funktion (evtl. mit 2-3 catch-Blöcken). Nur in Programmteilen, wo Fehler sehr oft kompensiert werden können, kommen vielleicht mal mehrere try-catch-Blöcke vor, da gibt es aber eigentlich keinen Unterschied zum Prüfen des Rückgabewertes außer der Sytax.
            Zwischen error = foo(); if (error) {...; return;} und try{foo()} catch(Exception e) {...}; ist ja nun wirklich kein Unterschied.

            Ja, und die Fehlerbehandlung findet (im Programmcode) woanders statt als der reguläre Ablauf. Deswegen sagte ich, ich finde das Konzept unübersichtlich und manchmal schwer zu verfolgen.

            Hm ok, wenn man es schlecht macht. Wenn man es gut macht, kann man den normalen Ablauf schön erkennen und dann noch nachsehen, was bei einem Fehler passiert.

            Ja, eben:

            ~~~

            function DoThis(blah, blubb, blaeh)

            { DoSomethingWith(blaeh);
                 return (DoThat(blah+1, blubb));
               }

            
            >   
            
            Ja, weil DoThat die gleichen Fehlercodes hat, wie DoThis und weil das Ergebnis direkt zurückgegeben wird. Aber aus folgendem Code:  
              
            function DoThis(a, b, c) {  
               return 5 \* DoThat(a + DoThat2(b, c), 3);  
            }  
            Wobei DoThat und DoThat2 aus verschiedenen Modulen/Biblotheken kommen und jeweils einen unterschiedlichen Fehler mit -1 signalisieren wird z.B:  
            function DoThis(a, b, c) {  
              int result = DoThat2(b, c);  
              if (result == -1) {  
                return -1;  
              }  
              result = DoThat(a + result, 3);  
              if (result == -1) {  
                return -2;  
              }  
              return 5 \* result;  
            }  
            Hier findet jetzt auch noch die besagte Vermischung von Fehlercode und Daten statt. Das könnte man natürlich über Referenzen/out-Parameter o.ä. umgehen, bei Dir sähe das also wohl so aus:  
            function DoThis(a, b, c) {  
              int result;  
              if (DoThat2(b, c, out result) == -1) {  
                return -1;  
              }  
              if (DoThat(a + result, 3, out DoThat2(b, c, out result) == -1) {  
                return -2;  
              }  
              return 5 \* result;  
            }  
              
            Mit Exceptions bräuchte man überhaupt keine zustätzlichen Code, will man die Exceptions durch andere ersetzen, was manchmal sinnvoll ist und in etwa dieser Umwandlung von Fehlercodes entspricht, tut man sowas:  
            function DoThis(a, b, c) {  
                try {  
                    return 5 \* DoThat(a + DoThat2(b, c), 3);  
                } catch (Exception1 e) {  
                   throw new MyException(e);  
                } catch (Exception2 e) {  
                   throw new MyException(e);  
                }  
            }  
            Das ist wesentlich kompakter, außerdem ist es egal, ob man DoThat oder DoThat2 je einmal oder zehnmal verwendet.  
              
              
            
            >   ~~~
            
            if (error=FunctionThatMightFail())  
            
            >    { // z.B. Aufruf einer zentralen Fehlerbehandlung  
            >      // oder Abfrage mit switch (error)  
            >    }  
            >   // weiter im normalen Programmfluss
            
            

            Ja, genau diese Konstrukte sind die, die den Code kompliziert machen. Wenn man sie tatsächlich braucht, sind Exceptions ja wie gesagt gleichwertig verwendbar.

            Dann ist das Gesamtkonzept schlecht durchdacht. Wenn ich Module schreibe, die die Gesamtheit der Funktionalität zu einem bestimmten Thema bündeln, dann verende ich auch im ganzen Modul einheitliche Bezeichner, Fehlercodes und Aufrufkonventionen.

            Ja für ein Modul, sobald man mehrere, fremde Module verwendet, klappt das leider nicht mehr. Exceptions erzwingen da auch eine Art Minimalkonvention, was auch schon etwas Wert ist.

            Das sollte man natürlich vermeiden - oder man muss *ganz genau* wissen, was man tut.

            Menschen machen Fehler, entweder schlicht aus Unwissen, aufgrund schlechter Dokumentation eines Moduls oder eben einfach, weil es jedem manchmal passiert. Entscheidend ist also, was im Fehlerfall passiert. Eine Exception führt jedenfalls dazu, dass man den Ort mit falscher/fehlender Fehlerbehandlung gut lokalisieren kann. Man kann sogar teilweise Typprüfung dafür verwenden, wobei ich jetzt nicht auch noch deren Vor- und Nachteile diskutieren möchte ;-)

            Was machst Du, wenn Du eine Datei in einem Programm lesen musst, aber das nicht kannst? Musst Du da nicht aus dem regulären Kontrollfluss ausbrechen?

            So besser? ;-)

            Grüße

            Daniel

            1. Hi Daniel,

              Im Gegenteil. Wo immer ich die Möglichkeit sehe, ein Häufchen thematisch abgeschlossenen Code in eine Funktion auszulagern, dann tu ich das - außer wenn ich dieses Stück Programmcode dann wieder nur einmal nutze. In diesem Fall lasse ich's lieber inline.
              Ich meinte nicht, dass Du Dein Programm nicht strukturierst. Mit "Funktionalem Programmierstil" meinte ich etwas, wie es im Extremfall in funktionalen Sprachen auftritt.

              also eher das konsequente Schema
                Ergebnis = Funktion(Argumentliste)
              im mathematischen Sinn einer Funktion?

              [Codebeispiel]
              Hier findet jetzt auch noch die besagte Vermischung von Fehlercode und Daten statt.

              Genau. Wir waren uns ja schon einig, dass diese Vermischung böse[tm] ist.

              ~~~

              if (error=FunctionThatMightFail())

              { // z.B. Aufruf einer zentralen Fehlerbehandlung
                   // oder Abfrage mit switch (error)
                 }
                // weiter im normalen Programmfluss

              
              > Ja, genau diese Konstrukte sind die, die den Code kompliziert machen. Wenn man sie tatsächlich braucht, sind Exceptions ja wie gesagt gleichwertig verwendbar.  
                
              Das ist deine Meinung, die sollst du bitte auch haben - ich finde, wie gesagt, die andere Variante leichter nachzuvollziehen. Aber ich habe ja schon öfter erwähnt, dass ich für gewöhnlich annähernd auf CPU-Ebene denke. Immerhin war 6502-Assembler für mich die Einstiegsdroge in die Welt des Programmierens. Hochsprachen wie Pascal und C habe ich erst sehr viel später gelernt, sehe aber in Gedanken immer noch in etwa den Maschinencode, der meinen C-Anweisungen entspricht.  
                
              
              > > Das sollte man natürlich vermeiden - oder man muss \*ganz genau\* wissen, was man tut.  
              > Menschen machen Fehler, entweder schlicht aus Unwissen, aufgrund schlechter Dokumentation eines Moduls oder eben einfach, weil es jedem manchmal passiert. Entscheidend ist also, was im Fehlerfall passiert.  
                
              Richtig. Mit dem "ganz genau wissen" meinte ich, dass man bei genauer Kenntnis des verwendeten Codes auch ein kontrollierbares Verhalten im Fehlerfall ausnutzen kann. Das ist zwar nicht die feine Art, wird aber gelegentlich gemacht.  
                
              
              > Eine Exception führt jedenfalls dazu, dass man den Ort mit falscher/fehlender Fehlerbehandlung gut lokalisieren kann.  
                
              Kann man? Man weiß nur, \*dass\* irgendwo im try-Block ein Fehler aufgetreten ist, aber nicht genau wo und warum.  
                
              
              > > > Was machst Du, wenn Du eine Datei in einem Programm lesen musst, aber das nicht kannst? Musst Du da nicht aus dem regulären Kontrollfluss ausbrechen?  
              > So besser? ;-)  
                
              Ja, so besser. :-)  
              Natürlich muss ich dann (vermutlich) aus dem normalen Ablauf ausbrechen. Ich meinte aber eigentlich das Lesen und Nachvollziehen des Programmcodes, nicht das Ausführen. Für die CPU ist es herzlich wurscht, welche Technik ich verwende, weil der daraus resultierende Code doch immer irgendwie ähnlich ist.  
                
              Ciao,  
               Martin  
              
              -- 
              Arzt:    Gegen Ihr Übergewicht hilft wohl nur noch Gymnastik.  
              Patient: Sie meinen, Kniebeugen und so?  
              Arzt:    Nein, Kopfschütteln. Immer dann, wenn Ihnen jemand was zu essen anbietet.
              
              1. Hallo Martin,

                also eher das konsequente Schema
                  Ergebnis = Funktion(Argumentliste)
                im mathematischen Sinn einer Funktion?

                Ja, meinetwegen auch noch (Ergebnisliste) = Funktion(Argumentliste) im Sinne einer vektorwertigen Funktion. Mag daran liegen, dass ich z.Z. öfter Programme schreibe, die auf einem mathematischen Modell aufsetzen. Und wenn man das erstmal so durchdacht hat, kann man es auch sehr bequem so implementieren.

                Das ist deine Meinung, die sollst du bitte auch haben - ich finde, wie gesagt, die andere Variante leichter nachzuvollziehen.

                Evtl. liegt es natürlich daran, was man nachvollziehen will. Will man die Logik des Programms verstehen oder wie es technisch abgearbeitet wird. Ich gehöre zu den Leuten, die am liebsten nur ein abstraktes Modell in den Compiler füttern würden, um ein Programm zu erzeugen. Funktionale Programmiersprachen kommen an sowas teilweise recht na dran.
                Natürlich ist es wichtig, auch zu wissen, was da passiert bis auf die unterste Ebene, aber um dann ein Programm zu schreiben, muss ich mir das zumindest nicht für jede Zeile überlegen ;-)

                Hochsprachen wie Pascal und C habe ich erst sehr viel später gelernt, sehe aber in Gedanken immer noch in etwa den Maschinencode, der meinen C-Anweisungen entspricht.

                Ja, bei diesen Sprachen geht das noch, bei Java o.ä. geht es schon weniger. Bei funktionalen oder gar logischen Sprachen funktioniert es praktisch gar nicht mehr, weil man nicht einmal mehr so genau weiß, wann was berechnet wird, manche Sprachen und Compiler können auch automatisch parallelisieren, da weiß man dann noch nicht mal unbedingt, was so alles gleichzeitig passiert. Man kann natürlich gegen so etwas sein, weil man gerne jedes Detail kontrolliert. Abstrakter zu denken und zu programmieren ermöglicht aber eben einfach, komplexere Dinge zu beherrschen bzw. weniger Zeit dafür zu brauchen. Jedenfalls, wenn man mit der Abstraktion klar kommt, was zumindest beim funktionalen und logischen Kram auch nicht jede Manns Sache ist ;-)

                Kann man? Man weiß nur, *dass* irgendwo im try-Block ein Fehler aufgetreten ist, aber nicht genau wo und warum.

                Ich meinte den Fall, dass es gar keinen try-Block gibt. Da wird eben nicht weiter gearbeitet, sondern das Programm gleich an der Fehlerstelle abgebrochen (und nicht da, wo es vielleicht mal irgendwo dann kracht als Resultat). Wenn man einen try-Block verwendet, weiß man zugegebenermaßen nicht genau, an welcher Stelle der Fehler auftritt. Da gibt es dann mehrere Fälle: Entweder muss man es gar nicht wissen, man weiß es, weil die Exception deklariert werden muss und der Compiler einem die Stellen sagt, man weiß es, weil es dokumentiert ist (ok Theorie ;-) oder man weiß es, weil man den Fehler gleich so lokal abfängt, wie nötig ist.
                Ehrlich gesagt ist das normalerweise kein Problem.

                Mir ist übrigens noch ein Beispiel eingefallen, dass ohne einen Exception-Mechanismus fast gar nicht lösbar ist:
                Das durchreichen von Fehlern, die praktisch überall auftreten können und lokal nicht verarbeitbar sind.
                Beispiel: Stack oder Heap voll. Diese Fehler können praktisch überall auftreten, dennoch will man sie sicher nicht überall per Rückgabewert signalisieren, da man sonst den Rückgabewert für nichts anderes mehr nutzt. Es kann aber durchaus sinnvoll sein, so einen Fehler abzufangen, beispielsweise in einem Serverprogramm. Sollte der beim Abarbeiten einer Aufgabe in eine Endlosrekursion laufen oder der Speicher ausgehen, gibt es keinen Grund nicht einfach die Aufgabe zu verwerfen und so weiter zu arbeiten. Es lassen sich da sicher noch andere Beispiele finden.

                Natürlich muss ich dann (vermutlich) aus dem normalen Ablauf ausbrechen. Ich meinte aber eigentlich das Lesen und Nachvollziehen des Programmcodes, nicht das Ausführen.

                Klar ich auch. Du musst das ja Programmieren und Nachvollziehen, aber in diesem Punkt werden wir uns wohl kaum einigen ;-)

                Grüße

                Daniel

  2. Hellihello Zusammen,

    statt eine immerhin konventionell definierte Exception zu thrown, die ja im minimalen Falle "lediglich" die mitgeworfenen Nachricht der Funktion transportiert, wäre vermutlich das u.g. sowas wie eine selfmade-exception-handling, oder? Der Funktion eine eigene message-variable mitgeben, die sie im unerwünschten Falle mit einer entsprechenden Nachricht versähe?

    Vermutlich muss man sich hier fragen, ob das gegenüber einer thrown Exception einen Vorteil hat. Immerhin muss der Funktion ja noch eine extra public-function sowie eine extra private-variable zugeordnet werden, wenn man das nicht ererben lassen möchte.

      
      
    <?php  
    /**  
     * File contains three classes (My_CSV_Reader, Test_View and Test_Control)  
     * to figure out cute error-handling/exception-handling  
     */  
      
    /**  
     * Reads CSV file and converts it to list of hash  
     * if filepath does not exists or file cant be opened a message is set  
     */  
    class My_CSV_Reader  
    {  
     /**  
      * filepath of CSV file  
      */  
     private $file_path = "";  
      
     /**  
      * raw formatted table read from CSV  
      */  
     private $table_from_csv = array();  
      
     /**  
      * table as list of hash  
      */  
     private $table = array();  
      
     /**  
      * message if file cant be read  
      */  
     private $message = "";  
      
     function __construct ($file_path) {  
      
      // check if filepath exists  
      if (is_file($file_path)) {  
      
       // set paramter to local class var_dump  
       $this->file_path = $file_path;  
      
       // try if file can be read  
       if ($this->_get_CSV()) {  
        // let it be converted to list of hash  
        $this->_set_table();  
       }  
      } else { //set error message  
       $this->message = "Filepath $file_path does not exist";  
      }  
     }  
      
      /**  
      * sets class var table_from_csv if file can be read  
      * @return boolean  
      */  
     private function _get_CSV() {  
      if ($file_handle = fopen ($this->file_path,"r")) {  
       while ( ($this->table_from_csv[] = fgetcsv ($file_handle)) !== FALSE ) {}  
       fclose ($file_handle);  
       return true;  
      } else {  
       $this->message = "Sorry, could not open file ".$this->file_path." though it exists";  
       return false;  
      }  
     }  
      
      /**  
      * converts csv table format to list of hash  
      * @return boolean  
      */  
     private function _set_table() {  
      foreach ($this->table_from_csv as $row_nr => $row) {  
       if ($row_nr === 0 || !is_array($row)) continue;  
       foreach ($row as $col_nr => $cell_content) {  
        $my_row[$table_from_csv[0][$col_nr]] = $cell_content;  
       }  
       $this->table[] = $my_row;  
      }  
      return true;  
     }  
      
     /**  
      *  returns table (list of hash)  
      * @return array  
      */  
     public function get_table() {  
      return $this->table;  
     }  
      
     /**  
      *  returns table (list of hash)  
      * @return array  
      */  
     public function get_message() {  
      return $this->message;  
     }  
    } //My_CSV_Reader  
      
      
    /**  
     * checks if table was read properly and selects appropiate View  
     */  
    class Test_Control  
    {  
     function __construct()  
     {  
      $my_csv_reader =  new My_CSV_Reader("test.csv");  
      if ($my_csv_reader->get_table()) {  
       new My_View_Table($my_csv_reader->get_table());  
      } else {  
       new My_View_Error($my_csv_reader->get_message());  
      }  
     }  
    } //Test_Control  
      
    // Test it  
    new Test_Control();  
      
    // Simulate View class for test purpose  
    class My_View_Error  
    {  
     function __construct($message) {  
      echo "$message";  
     }  
    }  
    
    

    Dank und Gruß,

    frankx

    --
    tryin to multitain  - Globus = Planet != Welt