Vorzeitiges Verlassen einer Funktion/Schleife mit Return - Guter Programmierstil oder nicht?

  • Schönen guten Tag, allerseits.

    Da ich noch Anfänger bin, habe ich eine grundsätzliche Frage zur Programmierung:
    Ich erinnere mich noch, daß man den GOTO-Befehl aus vielen Programmiersprache gestrichen hat, weil dessen Benutzung als äußerst unsauber galt.
    Nun sehe ich aber immer wieder in diversen Skrpts, dass dort "Return" benutzt wird, um eine Funktion/Schleife vorzeitig zu verlassen. Ist das nicht auch einfach nur ein "getarnter" GOTO-Befehl?

    Konkret stellte sich für mich das Problem, dass ich eine Funktion in einem Programm habe, die von unterschiedlichen Stellen aufgerufen wird und je nach Aufruf "unterschiedlich weit" durchlaufen werden soll.
    Noch konkreter: Wenn man das Programm per Doppelklick auf die exe startet, sollen per "FileInstall" 10 Dateien entpackt werden. Wird das programm aber mit einem Argument gestartet (Rechtsklick aus dem Kontextmenü), sollen nur die ersten 3 Dateien aus der Liste entpackt werden.

    Hier mal ein allgemeines Beispiel, um zu zeigen, was ich meine:

    Spoiler anzeigen
    [autoit]

    ;==> Benutzung von "Return"
    For $i = 0 to 3
    Local $satz = ""
    _Return($i)
    MsgBox(0, "_Return(" & $i & ")", $satz)
    Next
    _Return(1234) ;==> Bei Aufruf mit beliebigem Argument läuft die Schleife bis zum Ende durch
    MsgBox(0, "_Return(1234)", $satz)

    [/autoit] [autoit][/autoit] [autoit]

    ;==> Das Gleiche mit "Switch"
    For $i = 0 to 3
    Local $satz = ""
    _Switch($i)
    MsgBox(0, "_Switch(" & $i & ")", $satz)
    Next
    _Switch(1234) ;==> beliebiges Argument
    MsgBox(0, "_Switch(1234)", $satz)

    [/autoit] [autoit][/autoit] [autoit]

    Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _Return($eject)
    $satz = "Dies ist "
    If $eject = 0 Then Return

    [/autoit] [autoit][/autoit] [autoit]

    $satz &= "ein unvollständiger "
    If $eject = 1 Then Return

    [/autoit] [autoit][/autoit] [autoit]

    $satz &= "Satz, der "
    If $eject = 2 Then Return

    [/autoit] [autoit][/autoit] [autoit]

    $satz &= "nun doch "
    If $eject = 3 Then Return

    [/autoit] [autoit][/autoit] [autoit]

    $satz &= "vollständig ist."
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Switch($eject)
    Switch $eject
    Case 0
    $satz = "Dies ist"
    Case 1
    $satz = "Dies ist ein unvollständiger"
    Case 2
    $satz = "Dies ist ein unvollständiger Satz, der"
    Case 3
    $satz = "Dies ist ein unvollständiger Satz, der nun doch"
    Case Else
    $satz = "Dies ist ein unvollständiger Satz, der nun doch vollständig ist."
    EndSwitch
    EndFunc

    [/autoit]


    Was ist "sauberer"? Return oder Switch/Case?

    P.s.: Mir ist klar, dass man diese Spezielle Aufgabe auch eleganter lösen kann, aber das soll ja nur ein Beispiel sein...

  • In dem Beispiel oben ist definitiv Switch ordentlicher.
    Normalerweise verlässt man die Funktion nur vor dem eigentlichen Ende mit Return, wenn ein Fehler auftritt (aka Return SetError(...)).

    Und... Was denkst du, wie diese Schleife hier in einer tieferen Systemebene aussieht?

    [autoit]


    While $a < 5
    $a += 1
    WEnd

    [/autoit]

    In C:

    Code
    while(a < 5)
    {
    a++;
    }

    Und dann noch tiefer, in ASM:

    Code
    loop_while_head:
    cmp a,5
    jae loop_while_foot
    inc a
    jmp loop_while_head
    loop_while_foot:


    (Keine Gewährleistung auf diesen ASM-Code! :D)

    Letztendlich ist jede Konstruktion eigentlich nur JMP/GOTO.^^

    lg

  • In diesem Fall wäre Switch definitiv die elegantere Lösung.
    Außerdem solltest du Return benutzen um Werte zu übertragen und keine globalen Variablen ändern, oder zumindest nur in seltenen Fällen. (Das sollte bei dir eigentlich sowieso nicht funktionieren, da $satz außerhalb der Funktionen als lokal deklariert wurde, diese somit eigentlich keinen Zugriff darauf haben sollten.)

  • ExitLoop/Return etc. ist nicht direkt mit GoTo vergleichbar... In manchen Situationen ist es übersichtlicher und schneller eine Schleife direkt zu verlassen, wenn der nachfolgende Code in diesem Moment sowieso nicht benötigt wird. Wenn du aber eine Schleife hast, die sowieso immer komplett durchlaufen werden muss und nur von einer Bedingung beendet wird, macht es mehr Sinn die Schleifenbedingung entscheiden zu lassen (also z.B. "While $bTest = True).
    Solange du nicht wild durch seltsam verschachtelte Schleifen springst oder dein bestes gibst GOTO, mit ExitLoop und ContinueLoop, wieder zum Leben zu erwecken, solltest du diese Befehle verwenden können, ohne dass dir jemand deswegen auf die Füße tritt ;).
    Ich finde das hängt von der Situation ab (und auch ein bisschen von deiner eigenen Meinung ^^).
    ExitLoop/Return/ContinueLoop springen immer zu bestimmten logischen Punkten im Code die du nur durch umliegende Mechanismen definieren kannst. Bei GOTO setzt du einfach Sprungmarken und kannst zu jeder beliebigen Zeile im Code springen die du direkt im Befehl definierst.

    Zu deiner zweiten Frage: In diesem Fall ist die zweite Variante mit "Switch" nicht nur (meiner Meinung nach) sauberer, sondern auch geringfügig schneller.

  • Erstmal danke für die rege Beteiligung :thumbup:

    Meiner Meinung nach ist in dem konkreten Beispiel oben und auch bei meinem Programm mit FileInstall der Vorteil, dass man nach dem Return nur den Code hinzufügen muß, der neu ist und nicht wie bei Switch/Case jedesmal den gesamten Code wiederholen muß...

    Also sollte man eurer Meinung nach, den Code lieber mehrmals wiederholen (pro Case Anweisung) als nur den Neuen hinzuzufügen?

    Zu deiner zweiten Frage: In diesem Fall ist die zweite Variante mit "Switch" nicht nur (meiner Meinung nach) sauberer, sondern auch geringfügig schneller.


    Hast du das gemessen oder weiß man sowas einfach?

  • Meiner Meinung nach ist in dem konkreten Beispiel oben und auch bei meinem Programm mit FileInstall der Vorteil, dass man nach dem Return nur den Code hinzufügen muß, der neu ist und nicht wie bei Switch/Case jedesmal den gesamten Code wiederholen muß.

    Ich würde auch lieber das mit if und return nehmen. Switch ist nur bei entweder-oder Auswahlen wirklich sinnvoll, bei aufeinanderfolgenden Abfragen wird if verwendet.
    Mit Switch könnte man das auch so lösen: (unübersichtlich)

    Spoiler anzeigen
    [autoit]

    Func _Switch($eject)
    Local $satz = ""
    Switch $eject
    Case 3
    $satz = " nun doch"
    ContinueCase
    Case 2
    $satz = " Satz, der"&$satz
    ContinueCase
    Case 1
    $satz = " ein unvollständiger"&$satz
    ContinueCase
    Case 0
    $satz = "Dies ist"&$satz

    [/autoit] [autoit][/autoit] [autoit]

    Case Else
    $satz = "Dies ist ein unvollständiger Satz, der nun doch vollständig ist."
    EndSwitch
    Return $satz
    EndFunc

    [/autoit]


    Hast du das gemessen oder weiß man sowas einfach?


    Bei den if-abfragen muss jedes Mal der Wert der Variable neu ausgelesen werden und dafür muss in der Liste aller Variablen nachgeschlagen werden, welcher Wert dazu gehört steht.
    Bei Switch wird der Wert einmal bestimmt und dann nur noch verglichen.


    EDIT:


    Letztendlich ist jede Konstruktion eigentlich nur JMP/GOTO.^^

    Ja klar, aber letztendlich ist goto/jmp unübersichtlich, weil man nicht sofort weiß, wann und warum gesprungen wird.
    Hat man hingegen While da stehen, weiß man, dass man eine Schleife hat, die solange gilt, wie die Bedingung zutrifft.
    Bei goto kann als Schleife, if-Abfrage, Exception und möglicherweise sogar als Funktionsaufruf bzw. -rücksprung fungieren, man sieht aber nicht sofort, was es sein soll und wann es auftritt.

  • Das Beispiel im Startpost ist schon ein Sonderfall.
    Ich glaube da würde ich auch mit If ... then Return arbeiten.

    Allgemein versuche ich das vorzeitige verlassen einer Funktion nurnoch fürs Errormanagment zu verwenden.
    Wenn man bei jeder Gelegenheit aus den Funktionen springt ergibt das, gerade bei größeren Skripten/Funktionen, leicht Spaghetti-Code, der mit einigem Abstand nurnoch schwer lesbar ist.

    Ein Beispiel was ich für sehr unsauber halte:

    [autoit]


    Func _Demo($i_zahl)

    [/autoit][autoit][/autoit][autoit]

    switch $i_tagl
    case 1
    Return $i_zahl
    case 2
    Return $i_zahl*'$i_zahl
    endswitch
    endfunc

    [/autoit]


    besser:

    [autoit]


    Func _Demo($i_zahl)
    local $ret
    switch $i_tagl
    case 1
    $ret=$i_zahl
    case 2
    $ret=$i_zahl*'$i_zahl
    endswitch
    return $ret
    endfunc

    [/autoit]
  • In diesem Fall wäre Switch definitiv die elegantere Lösung.
    Außerdem solltest du Return benutzen um Werte zu übertragen und keine globalen Variablen ändern, oder zumindest nur in seltenen Fällen. (Das sollte bei dir eigentlich sowieso nicht funktionieren, da $satz außerhalb der Funktionen als lokal deklariert wurde, diese somit eigentlich keinen Zugriff darauf haben sollten.)


    Das Skript oben ist getestet und funktioniert einwandfrei :)
    Aber das sollte ja, wie gesagt, nur als Beispiel dienen. In meinem konreten "Problem" mit FileInstall ging es auch nicht darum, Variablen zu ändern, sondern darum, unterschiedliche Einspung- bzw. Aussprungpunkte zu erstellen.


    Das der Code hässlich ist, sehe sogar ich :rofl:

    Bei den if-abfragen muss jedes Mal der Wert der Variable neu ausgelesen werden und dafür muss in der Liste aller Variablen nachgeschlagen werden, welcher Wert dazu gehört steht.
    Bei Switch wird der Wert einmal bestimmt und dann nur noch verglichen.


    Verstanden und verinnerlicht, danke!

    Local außerhalb einer Funktion -> Global. ^^


    Verstanden und verinnerlicht, danke!
    Aber, ums nochmals zu betonen, bei mir gings nicht um Variablen, sondern um Aussprungspunkte...

  • @James/Friesel

    Zitat von James1337


    Außerdem solltest du Return benutzen um Werte zu übertragen und keine globalen Variablen ändern, oder zumindest nur in seltenen Fällen. (Das sollte bei dir eigentlich sowieso nicht funktionieren, da $satz außerhalb der Funktionen als lokal deklariert wurde, diese somit eigentlich keinen Zugriff darauf haben sollten.)


    Local außerhalb einer Funktion -> Global. ^^

  • Ich höre auch oft GoTo sei schmieren Programmierung. Als ich dann mal nachgefragt habe wieso kommt eig. immer nur das Argument Übersicht des Quellcodes. Du weißt irgendwann nicht mehr wo wie was und wann passiert.

    Ich arbeite zu 99% mit Schleifen und Switch.

    So Far

    Grüße Yaerox

    Grüne Hölle

  • @James/Friesel


    Local außerhalb einer Funktion -> Global. ^^


    Aber aber aber... 8|
    Ich dachte immer alle Variablen außerhalb von Funktionen sind Global, es sei den man verwendet Local. So langsam schwindet auch mein letztes bisschen Vertrauen in AutoIt... :S

  • Ich dachte immer alle Variablen außerhalb von Funktionen sind Global, es sei den man verwendet Local.


    Wo ist der Unterschied?
    Vor welchem höheren Definitionsbereich willst du denn eine Variable mit Local verstecken wenn es darüber nichts mehr gibt?
    Du kannst es gerne als Local definieren - ändert aber nichts daran dass es eine globale Variable ist.

  • Exakt dafür verwendet man doch Local IN Funktionen...

    Du möchtest außerhalb von Funktionen eine Variable als lokal definieren damit du in Funktionen (wahrscheinlich dann ohne Angabe von Local) denselben Namen vergeben kannst?
    Das ist irgendwie von hinten durch die Brust ins Auge...

    • Offizieller Beitrag

    Man könnte zum Beispiel in Funktionen die selben Variablennamen verwenden, da man in Funktionen keinen Zugriff auf die Variablen hätte.


    Das kannst du doch. Wenn du eine Globale Variable $X hast und deklarierst in einer Funktion ebenfalls $X aber als Local, dann verwendet die Funktion die lokale Variable. Ich persönlich halte es für nicht sehr glücklich identische Variablennamen in unterschiedlichen Scopes zu nutzen. Dann muß man beim Lesen des Codes gerade in größeren Funktionen zwingend drauf achten was wurde wo deklariert und zum Anderen hat man dann innerhalb der Funktion keinen Zugriff auf eine gleichnamige Globale Variable.

  • Du möchtest außerhalb von Funktionen eine Variable als lokal definieren damit du in Funktionen (wahrscheinlich dann ohne Angabe von Local) denselben Namen vergeben kannst?

    Nein, ich verwende Local in und außerhalb von Funktionen. Es ging mir nur um den Fehlenden Unterschied zwischen Local und Global (außerhalb von Funktionen). Aber dann passt ja trotzdem alles, ich war nur kurzzeitig verwirrt.

    Das kannst du doch. Wenn du eine Globale Variable $X hast und deklarierst in einer Funktion ebenfalls $X aber als Local, dann verwendet die Funktion die lokale Variable.

    Danke, ich war nur etwas durch das sinnlose Local außerhalb von Funktionen verwirrt. ^^

    Ich persönlich halte es für nicht sehr glücklich identische Variablennamen in unterschiedlichen Scopes zu nutzen.

    Naja, Variablen wie $i verwende ich zum Beispiel für fast jede For-Schleife...

    • Offizieller Beitrag

    Naja, Variablen wie $i verwende ich zum Beispiel für fast jede For-Schleife...


    Zählervariablen gehören natürlich nicht in die Betrachtung und haben i.A. nur die geplante Lebensdauer des Schleifendurchlaufs. Leider bestehen sie in AutoIt auch ausserhalb der jeweiligen Schleife weiter, was ich als wenig sinnvoll betrachte. Mir würde eine Scopeabgrenzung für jeden Scriptblock (If..EndIf; Do..Until, For..Next usw.) wesentlich besser gefallen.

  • Ich persönlich halte es für nicht sehr glücklich identische Variablennamen in unterschiedlichen Scopes zu nutzen.

    Deshalb fangen möglichst alle lokalen Variablen bei mir an mit $_, außer $i1, $i2, ... in For-Schleifen.