Codierung Lesbarkeit Returns

Disclaimer: Dieser Thread wurde aus dem alten Forum importiert. Daher werden eventuell nicht alle Formatierungen richtig angezeigt. Der ursprüngliche Thread beginnt im zweiten Post dieses Threads.

Codierung Lesbarkeit Returns
Hey hät mal ne Frage zur Lesbarkeit von Code im bezug auf Return anweisungen.
Habe mit nem Informatiker geredet und er meinte das man bei methoden die return anweisung aufgrund der übersichtlichkeit nur am ende einer methode hat, und nicht in eventuellen verzweigungen. Würd mich mal interessieren wie das so die erfahrenen Programmierer hier sehen. Vor und nachteile usw. :slight_smile:

Beispiel für return im Methodeninneren:
foo(){
if(){return bla;
}
else{
}
if(){return;
}

return blubb;
}

Beispiel für return am ende:

foo(){
if(){blubb = bla;
}
else{
}
if(){blubb = blabla;
}

return blubb;
}


Da ist der Lehrstuhl 2 genau der richtige Ansprechpartner :stuck_out_tongue:
Ein Tipp:

(code=java)
… //dein Programmtext
(/code)

macht deinen Post übersichtlich.

PS. Ersetze die runden Klammern durch eckige!


Es kommt ein bisschen darauf an. Grundsätzlich würde ich sagen. Es ist Geschmackssache und du wirst Leute finden, die dir erzählen man solle unbedingt das eine machen und andere, die das Gegenteil behaupten.

Ich persönlich bevorzuge die Regel “Verlasse die Methode so früh wie möglich”.
D.h. mein Code sieht oft so aus:

void myMethod()
{
    if( !condition )
        return -1;

    if( finished )
        return 0;
    
    // do some stuff

   return 0;
}

Das ganze erhöht in meinen Augen die Lesbarkeit des Codes deutlich, weil man nicht irgendwelche if-Verzweigungen aufbauen muss, die nur existieren, weil man die Methode nicht per return verlassen möchte. In deinem ersten Beispiel könntest du zum Beispiel das else komplett weglassen, weil klar ist, wenn der Code überhaupt bis dahin kommt, muss die Bedinung falsch sein (sonst hätte es früher ein return gegeben).

Bei Schleifen ist das ganze etwas kniffliger. Grundsätzlich arbeite ich in 80% der Fälle auch da mit return innerhalb des Schleifenrumpfs, weil ich es übersichtlich finde. Hier kann man allerdings dagegen argumentieren, dass der Compiler eine Schleife besser optimieren kann, wenn es genau einen Eingang und einen Ausgang aus der Schleife gibt. Allerdings muss dazu schon auch das restliche Codedesign extrem gut optimiert sein, damit sich diese Optimierungsvorteile auch wirklich auszahlen (wenn ich sowieso schon ineffizienten Code geschrieben habe, dann hilft es mir auch nichts, wenn der Compiler ihn optimieren kann ;)).

Der eigentliche Grund für eine return-Anweisung pro Methode kommt übrigens nicht aus der Übersichtlichkeit (wie gesagt meiner Meinung nach ist es anders meist übersichtlicher, weil man bei einem einzigen return dann oft viele verschachtelte if-Blöcke hat), sondern vielmehr aus Programmiersprachen, in denen du dich selbst um das Memory-Management o.ä. kümmern musst. Wenn du beispielsweise in C oder C++ Speicher allokierst, musst du diesen irgendwann auch wieder freigeben, was einige zusätzliche Zeilen benötigt. Wenn du nun mehrere return-Aufrufe in deiner Methode hast, passiert es leicht, dass du an einer Stelle vergisst entsprechenden Speicher wieder aufzuräumen und schon hast Speicherlecks.
Und da kann Code dann plötzlich richtig hässlich werden, weil an verschiedenen Methodenausgängen, verschiedener Speicher freigegeben werden muss (weil manche Variablen vllt. erst später erstellt werden würden). Da Java dir das Speichermanagement abnimmt, ist dieser Punkt hier nicht so wichtig und daher kann man Methoden auch so schnell verlassen, wie möglich. Allerdings muss man auch aufpassen, wenn man zum Beispiel in der parallelen Programmierung irgendwelche Locks anfordert und mit diesen arbeitet und somit alle anderen Prozessoren aussperrt und diese warten müssen. Leicht ist es auch da passiert, dass man mal vergisst ein Lock wieder freizugeben und schon läuft das Programm nicht mehr weiter, weil alle Prozessoren darauf warten, dass endlich die Locks freigegeben werden.

Es kommt also einfach auf die Situation an. Allerdings finde ich, dass man in der Regel so arbeiten sollte, wie man selbst am besten damit zurecht kommt, weil es meist nicht zu besserem Code führt wenn man sich zwanghaft an irgendwelche Empfehlungen hält, die mal von irgendjemandem (möglicherweise in anderem Kontext) gegeben wurden. Natürlich sollte man für Tipps und Tricks offen sein, aber wenn man sich für etwas entscheidet, sollte man auch selbst davon überzeugt sein und wissen, warum man es tut und was man überhaupt tut. Sonst endet man im Chaos, weil man seinen eigenen Code nicht mehr versteht.

3 Likes

Über diesen Punkt kann man sich vermutlich trefflich streiten, IMHO ist es aber Schwachsinn.

Ich habe lieber zusätzliche [m]return[/m]s in einer Funktion als endlose Verschachtelung.

Vergleiche (fiktives Bespiel):

public static int TreeWalker(Node node) {
    int result = 0;
    if (node.subTreeIsInteresting()) {
        for (Node child : node.Children()) {
            result += TreeWalker(child);

            if (child.workRequired()) {
                for (Worker w : workerList) {
                    result += w.prepare(child);
                    result += w.process(child);
                    result += w.finish(child);
                }
            }
        }
    }
    return result;
}

vs

public static int TreeWalker(Node node) {
    int result = 0;
    if (!node.subTreeIsInteresting())
        return result;

    for (Node child : node.Children()) {
        result += TreeWalker(child);

        if (!child.workRequired())
            continue;

        for (Worker w : workerList) {
            result += w.prepare(child);
            result += w.process(child);
            result += w.finish(child);
        }
    }
    return result;
}
2 Likes

Die Zeiten in denen man als Programmierer schlauer sein musste als der Compiler sind größtenteils vorbei. In vielen Fällen generiert der Compiler besseren Code als man als Programmierer mit der Hand schreiben würde. Das Argument „so optimierts der Compiler besser“ habe ich deswegen für mich persönlich in die Mottenkiste der Mythen gestopft und ignoriere ich grundsätzlich beim programmieren.

Sollte die Laufzeit in einem Hotspot dann doch irgendwann mal relevant werden kann man immer noch mit Profiling, gezielten Optimierungen und evtl. sogar Verbesserungen des Compilers daran arbeiten.

Meines Wissens kommt die Regel „ein return pro Methode“ aus Zeiten, in denen Sprachen das auch so erwartet haben (und nicht der Compiler implizit die entsprechende Transformation für dich macht, was mittlerweile bei jedem Compiler der Fall ist).

Bezüglich des aufräumens habe ich kürzlich Code vorbeiscrollen sehen, der GCCs [m]attribute((cleanup(cleanup_func)))[/m] benutzt, um das Problem zu umgehen (was natürlich inherent unportabel ist). Beispiel hier, dazu passende Definition von [m]free[/m] hier.

Persönlich bevorzuge ich mehrere returns pro Methode.


Es gibt zB MISRA, was returns am Ende vorschreibt. Das halte ich aber in der Form wie das da praktiziert wird fuer Quark, da gleichzeitig goto verboten wird. Man kann recht lesbaren Code ohne extrem tief verschachtelte if-Kaskaden erzeugen indem man im Fehlerfall per goto an eine passende Sprungmarke am Ende springt:

int beispiel(...) {
        int ret = 0;
        ret = initialisierung_A();
        if (ret) goto error_A;
        ret = initialisierung_B();
        if (ret) goto error_B;
        ...
        
        ret = dinge_tun();
        
        deinitialiserung_B();
error_B:
        deinitialisierung_A();
error_A:
        return ret;
}

Das ist lesbarer als tief verschachtelte ifs und vermeidet, dass man fuer die Fehlerbehandlung die deinitialisierung (zB close, free, …) mehrfach kopieren muss.

Wenn man das aber nicht machen kann, weil irgendwer goto verboten hat finde ich fruehe mehrfache returns am besten.


Da sei nochmal der Vorteil von C++ erwähnt, wo das close / delete / … gar nicht nötig ist, wenn deine Ressourcen in ein Objekt gekapselt sind, das sich per Destruktor aufräumt, egal auf welche Weise man die Methode verlässt :wink:


Siehe mein Post zu [m]attribute((cleanup(cleanup_func)))[/m], das genau das tut. Ob man sich dafür, dass das Feature im Standard der Sprache ist allerdings C++ antun möchte bleibt dahingestellt.


Ja, genau, wenn…

Ich behaupte mal bei mindestens 80% des vorhandenen C+±Codes landet man wieder bei einer Mischung aus „der Destruktor machts“ und „man muss selber aufraeumen“, weil Teile des Codes den man verwendet sich an die Konvention halten und Teile nicht. Ausserdem braucht man mindestens noch Smartpointer, damits mit Kram auf dem Heap richtig funktioniert.