Methoden mit Rückgabewert - mehrere return-Anweisungen

Hi,
bin gerade auf ein PDF einer Uni gestoßen, in der Punkte aufgezählt werden die bei Aufgaben zu Punkteabzug führen.
Ein Punkt lautet:
“Jede Methode mit Rückgabewert hat genau eine return-Anweisung”

Macht aber doch nicht immer Sinn oder?
Oft habe ich nämlich Methoden wie diese:

public boolean methode(int x) {

     for(int i : array) {
          if(i == x) return true;
     }

     return false;

}

Kompletter Quatsch oder steckt da was hinter?

Da steckt was dahinter:
[ol]
[li]Lesbarkeit:[/li]Wenn das Codebeispiel länger (und komplexer) wird kann man die returns leicht übersehen.
[li]Wartbarkeit: [/li]Das für mich der wichtigere Grund…
Bei deinem Beispiel kannst Du die Zeilen 4, 5 und 6 nicht mit dem automatisierten Refactoring “extract method” in eine neue Methode verschieben, weil der Rückgabewert für den false-Fall in dieser neuen Methode nicht definiert wäre.
[/ol]

bye
TT

du kannst an dieser Stelle auch einen boolean verändern und die Schleife abbrechen,
so dass am Ende Rückgabe des booleans mit true oder false ausreicht,

das ist schlicht ein Stil der Programmierung, ihn als einzigen zu verkaufen ist objektiv schlecht, aber typische Krankheit

welchen Stil man mag ist diskutabel,
in komplizierten Methoden mit verschiedenen Verschachtelungen und mehreren return verteilt kann es auch unübersichtlich werden,
Logging auch nicht so leicht (wollte man in deine Methode “Methode methode mit Parameter … fertig mit Rückgabewert …” ergänzen, dann an zwei Stellen nötig)
aber da zählt wohl die goldenste Methodenregeln: nicht so kompliziert werden, bei Bedarf eher sinnvoll aufspalten, was aber auch nicht immer automatisch einfach klappt

Hmm, alles klar.
Ich werd’s mir mal merken.
Danke :wink:

Tolles Beispiel! :slight_smile:

Nur wenn man „schlechten“ Code hat, hier konkret sehr lange Methoden.

Das sind veraltete Regeln nicht mehr als ein return Statement zu haben.

Wenn du deinen Code aendern wuerdest um nur ein return Statement zu haben wuerde er laenger werden und weniger uebersichtlich :wink:

Methoden kurz halten, dann noch kuerzer, dann ist es egal wie viele return statements, uebertrieben ausgedrueckt.
BoB Martin erklaert das recht gut in seinem Buch „Clean Code“.

Kurz:
Mach so weiter, bis auf dem richtigen Weg, vergiss den veralteten Quark von nur einem return Statement pro Methode, stammt aus einer Zeit als es hiess „eine Methode sollte auf den Bildschirm passen“.

*** Edit ***

[quote=Timothy_Truckle;103735]Wartbarkeit:
Das für mich der wichtigere Grund…
Bei deinem Beispiel kannst Du die Zeilen 4, 5 und 6 nicht mit dem automatisierten Refactoring „extract method“ in eine neue Methode verschieben, weil der Rückgabewert für den false-Fall in dieser neuen Methode nicht definiert wäre.[/quote]
Das einzige Refactoring das hier u.U. sinnvoll sein koennte, waere die if Bedingung in eine Methode auslagern.

[quote=maki]Das einzige Refactoring das hier u.U. sinnvoll sein koennte, waere die if Bedingung in eine Methode auslagern.[/quote]Wenn man nur das simple Beispiel betrachtet hast Du recht, aber im wahren Leben ist es oft anders.

bye
TT

[QUOTE=Timothy_Truckle;103742]Wenn man nur das simple Beispiel betrachtet hast Du recht, aber im wahren Leben ist es oft anders.
[/QUOTE]
… „schlechter“ Code eben, s.o. :wink:

Meine das ernst mit der Methodenlänge, wenn sie zu lang werden versucht man das eben durch so obskure Regeln zu kaschieren, löst das eigentliche Problem aber nicht sondern ist nur ein Workaround :slight_smile:

[quote=maki]Meine das ernst mit der Methodenlänge, wenn sie zu lang werden versucht man das eben durch so obskure Regeln zu kaschieren,[/quote]Mein Punkt ist: Methoden entwickeln sich und sind idR. nicht von Anfang an zu lang. Wenn man dann dem Punkt erreicht hat kann das Misachten dieser Regel verhindern, dass die IDE beim Refactoring unterstützen kann.

bye
TT

[QUOTE=Timothy_Truckle;103744]Mein Punkt ist: Methoden entwickeln sich und sind idR. nicht von Anfang an zu lang. Wenn man dann dem Punkt erreicht hat kann das Misachten dieser Regel verhindern, dass die IDE beim Refactoring unterstützen kann.

bye
TT[/QUOTE]
Grundsätzlich gebe ich dir ja recht, aber in diesem konkreten Fall sehe ich das anders, zwei returns sind besser weil kürzer weil einfacher.

Lange Methoden führen u.a. oft zu inline Kommentaren usw., da sind mir 2 return statements lieber, ausserdem braucht die neue methode auch mind. eines, besser zwei return statements :wink:

Bin ich wohl in der Minderheit: ich versuche immer, nur ein return zu haben. Egal wie kurz, egal was los ist. Sind die Hirnfunktionen für wichtigeres frei - und beim späteren Hacken - (man will den Rückgabewert doch anders haben, was ziemlich oft vorkommt) kann man in der vorletzen Zeile was ändern und muss nicht fünfzehn Ausbrecher jagen. Dazu das einfachere Refactoring.

Ja, es wird manchmal klobig, aber ist mir halt lieber so.

public boolean methode(int x) {
     boolean kommtvor = false;
     int i=0;
     while(!kommtvor){
         kommtvor = (array[i++]==x);
     }
     return kommtvor;
 }

Die Verlängerung ist IMHO nicht wirklich relevant. In meinen Augen ist eine Methode mit 5 return-Anweisungen mit komplizierten, verschachtelten Kontrollstrukturen einfach hässlich. Ist aber nicht wirklich relevant, denn wer Methoden mit mehr als 30 oder 40 Zeilen hat, der macht eh was falsch.

ich bin eher makis Meinung. an Bleiglanz bsp sieht man, dass es schwierig wird die moeglichen Werte von “kommtvor” folgen zu koennen. Gibt es nur ein return, so muss man nicht Methode durchgehen und sehen wie und wo welche Variable ueberhaupt returned wird und wie und wo sie ihren Wert aendert. Bei einem sofortigen ausstieg sieht man sofort was die Rueckgabe ist.
Ausserdem ist die Gefahr ausserdem dass zb nachdem “kommtvor” gesetzt ist man vll abhaenig von dessen wert noch weitere Berechnung hat. Steigt man nicht vorher bei dem gegensaetzlichen wert aus, so muss man den folgenden Code dann wieder in ein if packen etc

Also, verschiedenen Leute, verschiedenen Meinungen, ausser dass alle gemeinsam haben, wenn man kurze Methoden verwendet ist das Problem an sich marginal

Sehe das hier wie maki. Lieber versuchen die Methode klein zu halten und das kann man imho ganz gut, indem man z.B. inline-Kommentare durch Methoden ersetzt. Also ich hätte keine Lust jedes mal meine generierte equals umzuschreiben:

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Test test = (Test) o;

        if (a != test.a) return false;
        if (b != test.b) return false;
        if (c != test.c) return false;

        return true;
    }

klar kann man das potentiell vereinfachen. Aber die Lesbarkeit erhöht das nicht zwangsläufig.

Das Argument zieht aber nur, wenn es „zu kompliziert“ ist, die Methode durchzugehen, und das sollte eben eigentlich nicht der Fall sein.

Ah, Hallelujah. Da ist ja ein return, da wird das gleich zurückgegeben. Brauche ich das restliche Gewirr nicht mehr durchgehen.

Ich sehe das eher so, dass bei späteren Modifikationen einfach am Anfang was reingehackt wird (komplizierte Bedingung für den Sonderfall + sofortiges return). Zum Beispiel von mir selbst, wegen Faulheit und so. Nach der dritten oder vierten Verbesserung dieser Art werde ich dann unruhig.

EDIT:
Für den OT: oft kann man das „Problem“ durch Verkürzen einfach zum verschwinden bringen.

Arrays.asList(array).contains(x);

[QUOTE=Bleiglanz]Für den OT: oft kann man das „Problem“ durch Verkürzen einfach zum verschwinden bringen.

Arrays.asList(array).contains(x);
```[/QUOTE]
damit verschiebt man das Problem nur an eine andere Methode, in diesem Fall vorhanden, nicht nochmal, aber dort dasselbe Problem

geht letztlich (für ArrayList) hinein in
```    public int indexOf(Object elem) {
	if (elem == null) {
	    for (int i = 0; i < size; i++)
		if (elementData**==null)
		    return i;
	} else {
	    for (int i = 0; i < size; i++)
		if (elem.equals(elementData**))
		    return i;
	}
	return -1;
    }

mal abgesehen davon dass

        System.out.println(Arrays.asList(array).contains(5));

false ausgibt, Vorsicht :wink:

Gewissermassen hat Slater recht,

andererseits muss man auch Java 8 betrachten. Was zu folgendem führt.

return Stream.of(array).anyMatch(a -> x == a);

Ich kann ja sagen, wie mir das weitergegeben wurde. Schreibt man in if- oder for- nicht return, dann zusätzliche Variable-Wert, der im Kopf der Schleife geprüft werden muss, oder ein break (a); , wobei das zB nach Deijkstra vermieden werden sollte, oder wie BG meinte, man prüft nur die Variable-Wert und setzt ihn ständig innerhalb der while-/for- neu. Alles nicht ganz so toll. Allerdings Methoden wirklich nicht länger als ~ 5 kurze Zeilen. Halte dich an die pdf.

“Jede Methode hat nur genau ein return.” - das würde doch bei rekursiven Methoden schon mal überhaupt nicht funktionieren, denn schließlich muss einmal die Abbruch-Bedingung ein definiertes Ergebnis returnen und zum anderen muss auch das Ergebnis an den Call zurückgegeben werden. Eine rekursive Methode mit nur einem return; nämlich dem der Abbruch-Bedingung würde ja nur dann funktionieren wenn man direkt auf den Membern des Objektes arbeiten würde, und wie “clean code” das dann ist oder eben nicht, darüber kann man sich bestimmt genau so gut streiten.

Als einfaches Beispiel : Fakultät rekursiv

  1. return : n==0 -> return 1;
  2. return : return n*fac(n-1);

Ich wüsste jetzt gar nicht wie man das mit nur einem return; machen sollte. Und wer jetzt mit “iterativ” kommt : das zählt nicht als Lösung =P.

Rekursive Methoden sollte man nach Möglichkeit nicht verwenden. Java kann damit meist nicht vernünftig umgehen, Stackoverflow etc.
Bei Sprachen mit Tail Call Optimization sieht das anders aus.

In Java gibt es mit dem Ternären Operator ein IF-Mit-Rückgabewert.

return (n==0)? 1: n * fac(n-1);

ansonsten kann man auch locale variablen nutzen

  Number result;
  if(n > 0) {
    result = n * fac(n.decrement());
  } else {
    result = 1
  }
  return result;
}

Na, ist doch super, weil mir als Verwender es völlig egal ist ob die aufgerufene Methode mehrfache return verwendet. Das ist doch der Grundgedanken um überhaupt „kurze“ Methoden schreiben zu können, Teile auslagern, hauptsache die Methode tut das was sie soll.

Mehrfache returns interessieren mich höchstens als Instandhalter von Quellcode und da bin ich persönlich für frühzeitige Abbrüche durch return. Ein Punkt sind „Guard-clauses“, ich finde es viel leichter eine Menge von „Vorbedingungen“ festzulegen als komplizierte if-else Konstrukte in der eigentlichen Aufgabe selbst zu verwenden. Des Weiteren mag ich frühzeitiges return, da die Möglichkeit verringert wird das ich aus Unachtsamkeit den Rückgabewert ändere wenn ich irgendwo dazwischen Code hinzufüge. Es wird auch die Möglichkeit eingeschränkt das vll. unnötiger Code durchlaufen wird bzw. unnötige Variablen deklariert werden. Ich muss mir auch keine komplizierte Verzweigungsstruktur merken sondern nur welche Bedingung zum return führt. Ich muss mir nicht merken welche Werte der Rückgabewert denn hatte annehmen können wenn ich am Ende der Methode angelangt bin.

Zu deinen Beispielen. Das primitive Arrays mit Arrays.asList „merkwürdig“ sind ist nicht neu (primitive Arrays erzeugen eine List<primitiv>). Und die indexOf-Methode ist schon ziemlich alt, wahrscheinlich würde heute etwas in folgendem Stil rauskommen.

        for(int i = 0; i < size; i++){
            if(Objects.equals(elementData**, elem)){
                return i;
            }
        }
        return -1;
    }```



[QUOTE=Unregistered;103796][..]andererseits muss man auch Java 8 betrachten. Was zu folgendem führt.
```return Stream.of(array).anyMatch(a -> x == a);```[/QUOTE]

Der Code ist vll. nicht ganz das was du bezwecken wolltest, was du da baust ist ein Stream von int[]. D.h. a ist vom Typ int[]. Die Stream.of-Methode verhält ähnlich wie Arrays.asList für primitive Arrays. Einen Stream der Elemente eines Arrays funktioniert nur für nicht primitive Werte oder es wird Arrays.stream() verwendet. Da gibt es jedoch nur Methoden die int, long und double akzeptieren da keine Streamimplementierung für die restlichen primitiven Werte existiert.

Mal ein einfaches Beispiel (nur so aus dem Kopf)

Eine Methode liefert ein Ergebnis E zurück. Es gibt zig einzelne return-Anweisungen, weil es viele Verzweigungen gibt.

Jede Modifikation (der String E soll so und so aussehen, die Zahl E nicht vorzeichenbehaftet sondern der absolutbetrag, wenn E dies und jenes, dass lieber E’ zurückgeben, …) führt zu ziemlichem Gewürge.

Mich hat es jedenfalls schon oft gefreut, am Ende der Methode ein einziges return zu haben - wo man vor der Rückgabe noch Modifikationen machen kann, die völlig unabhängig von der Komplexität der Berechnung von E sind.