Facebook
Twitter
Google+
Kommentare
25

David Müller über goto

Mit Goto verbindet jeder Entwickler der alten Schule eine besondere Hassliebe. Diejenigen, die erst in die Programmierung eingestiegen sind, als goto nicht mehr zwingend erforderlich war bekamen richtigerweise eingetrichtert, dass goto ein schlechter Weg zur Programmstrukturierung ist. Um das ganze mal von der anderen Seite aufzurollen und goto nicht pauschal zu verdammen, hab ich mir ein paar Gedanken gemacht, ob man diesem meistgehassten Sprachfragment nicht doch noch was abgewinnen kann. Witzigerweise weigert sich mein heißgeliebtes Notepad++ sogar in der neusten Version, goto als Teil des PHP-Sprachbestandes anzuerkennen und verweigert ihm das Syntax-Highlighting. Liegt wohl dran, dass PHP erst ab Version 5.3 verfügbar ist – was selbstverständlich auch von hitzigen Diskussionen begleitet wurde. Aber genug der Vorrede, jetzt wird’s Zeit für etwas Code.

<?php
define("LOOPS",40000);
$results=array();

//Test mit Goto
$time=get_timestamp();
$i=0;
top:
echo "<div style='display:none'>Ausgabe Nummer ".$i."</div>";
if ($i++<LOOPS) goto top;
$results['goto']=(get_timestamp()-$time);

//Test mit For
$time=get_timestamp();
for ($i=0;$i<=LOOPS;$i++) echo "<div style='display:none'>Ausgabe Nummer ".$i."</div>";
$results['for']=(get_timestamp()-$time);

//Test mit While
$time=get_timestamp();
$i=0;
while ($i<=LOOPS) {
 echo "<div style='display:none'>Ausgabe Nummer ".$i."</div>";
 $i++;
}

$results['while']=(get_timestamp()-$time);

echo "<pre>".print_r($results,true)."</pre>";

function get_timestamp()
{
  $microtime = explode(" ", microtime());
  $microtime = $microtime[0]+$microtime[1];
  return $microtime;
}

?>

Okay, das Beispiel war zugegebenermaßen eher akademischer Natur, um rein aus Spaß festzustellen, dass Goto den anderen Schleifenvarianten mal locker 30-50% abnimmt. Logisch wird das niemand dazu bewegen, seinen Code gründlich auf Goto zu optimieren, um etwas Performance rauszuquetschen. Trotzdem sehen wir dadurch ganz gut, dass Programmiersprachen (und da ist PHP nicht alleine) mit goto sehr gut umgehen können. Intern (also auf Assembler-Maschinenebene) darf man sich bei einer compilierten Sprache wie C ohnehin fast jedes Sprachfragment auf einen Sprungbefehl zurückgeführt vorstellen. Das sieht dann in etwa so aus:
goto
Ich gehe sehr davon aus, dass das in PHP intern recht ähnlich gehandhabt wird. Es wird also deutlich, dass PHP weniger zu tun hat, wenn der vom User eingegebene Code näher am Endprodukt ist.
Aber kommen wir mal zu dem Punkt, wo selbst scharfe goto-Kritiker etwas wankelmütig werden. Verschachtelte Schleifen oder neudeutsch nested loops. Wir stellen uns mal folgendes Code-Fragment vor

$einwohner = array
(
	array("Stadt1",9.80932, 48.5288, "Landeshauptstadt", array("Peter","Paul")),
	array("Stadt2",9.82886, 48.5419, array("Tom","Thomas")),
	array("Stadt3",9.7135, 48.5315, array("Brunhilde","Siglinde")),
	array("Stadt4",9.74305, 48.4943, "Kreisstadt", array("Siegfried","Samuel")),
	array("Stadt5",9.71299, 48.4591, array("Thomas","Sebastian")),
	array("Stadt6",9.62827, 48.477, array("Christian","Ralf"))
);

Dieses fürchterlich unheitliche Array soll nun nach einem gewissen Siegfried durchsucht werden, um festzustellen ob dieser gute Herr im Array enthalten ist. Wenn man jetzt mal sämtliche Sprachkonstrukte wie array_search außen vor lässt, würde man mit klassischen for-Schleifen in etwa so agieren:

for ($i=0;$i<count($einwohner);$i++)
{
  for ($j=0;$j<count($einwohner[$i]);$j++)
  {
    if (is_array($einwohner[$i][$j]))
    {
      for ($k=0;$k<count($einwohner[$i][$j]);$k++)
      {
        echo "Pruefe ".$einwohner[$i][$j][$k]."...<br />";
        if ($einwohner[$i][$j][$k]=="Siegfried")
        {
          echo "Siegfried gefunden!<br />";
        }
      }
    }
  }
}

Ganz einfach also alle Ebenen des Arrays durchgehen und schauen, ob wir gefunden haben, was wir suchen. Jetzt haben wir aber ein Problem. Siegfried wurde gefunden, wir könnten theoretisch aus allen For-Schleifen heraus und unser wildes Iterieren beenden, wir haben ja unser gewünschtes Ergebnis bereits gefunden.
Nummer 1 (und definitiv sehr unschön! mit einem Boolwert zum herausbreaken aus den äußeren Loops. ):

$found=false;
for ($i=0;$i<count($einwohner);$i++)
{
  if ($found===true) break;
  for ($j=0;$j<count($einwohner[$i]);$j++)
  {
    if ($found===true) break;
    if (is_array($einwohner[$i][$j]))
    {
      for ($k=0;$k<count($einwohner[$i][$j]);$k++)
      {
        echo "Pruefe ".$einwohner[$i][$j][$k]."...<br />";
        if ($einwohner[$i][$j][$k]=="Siegfried")
        {
          echo "Siegfried gefunden!<br />";
          $found=true;
          break;
        }
     }
  }
}

Das ließe sich doch mit goto wesentlich cooler lösen, oder?

for ($i=0;$i<count($einwohner);$i++)
{
  for ($j=0;$j<count($einwohner[$i]);$j++)
  {
    if (is_array($einwohner[$i][$j]))
    {
      for ($k=0;$k<count($einwohner[$i][$j]);$k++)
      {
        echo "Pruefe ".$einwohner[$i][$j][$k]."...<br />";
        if ($einwohner[$i][$j][$k]=="Siegfried")
        {
          echo "Siegfried gefunden!<br />";
          goto ende;
        }
      }
    }
  }
}
ende:

Okay, wenn man ganz cool ist kann man das Thema natürlich auch mit einer Funktion erschlagen:

function sucheSiegfried($einwohner)
{
  for ($i=0;$i<count($einwohner);$i++)
  {
    for ($j=0;$j<count($einwohner[$i]);$j++)
    {
      if (is_array($einwohner[$i][$j]))
      {
        for ($k=0;$k<count($einwohner[$i][$j]);$k++)
        {
          echo "Pruefe ".$einwohner[$i][$j][$k]."...<br />";
          if ($einwohner[$i][$j][$k]=="Siegfried")
          {
            echo "Siegfried gefunden!<br />";
            return true;
          }
        }
      }
    }
  }
  return false;
}

Und jetzt kommt das, was sehr viele Entwickler nicht wissen: Man kann break auch mit einem Parameter aufrufen, der die Level an for-Loops bestimmt, die gebrochen werden sollen.
So ist also break; äquivalent zu break 1; denn es wird eine For-Schleife gebrochen. In unserem Fall möchten wir 3 For-Schleifen durchbrechen, rufen also einfach break 3; auf. Hier nochmal der (meiner Meinung nach coolste) Code für diesen Zweck:

for ($i=0;$i<count($einwohner);$i++)
{
  for ($j=0;$j<count($einwohner[$i]);$j++)
  {
    if (is_array($einwohner[$i][$j]))
    {
      for ($k=0;$k<count($einwohner[$i][$j]);$k++)
      {
        echo "Pruefe ".$einwohner[$i][$j][$k]."...<br />";
        if ($einwohner[$i][$j][$k]=="Siegfried")
        {
          echo "Siegfried gefunden!<br />";
          break 3;
        }
      }
    }
  }
}

Dadurch wird goto zumindest in PHP für den einzig zugestandenen Einsatzzweck überflüssig. Und da auch der „Schleifenersatz“ durch goto zwar performant aber alles andere als praktikabel ist, darf man weiter fleißig auf goto rumhacken – wenn auch mit etwas Fachwissen, um am Stammtisch noch einen draufsetzen zu können. Für andere Sprachen wie C, die solche Features nicht von Haus aus mitbringen, kann goto aber durchaus im einen oder anderen Fall echt nützlich und auch okay sein. Was haltet ihr davon? Fällt euch noch ein Use-Case ein?

David Müller (http://www.d-mueller.de)

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

25 Comments

  1. In PHP gibt es kein „goto“, aber „break“ darf _in_Schleifen_ mit einem Sprungziel verwendet werden, was dafür aber trotzdem auch _nach_ der Schleife liegen muss (soweit ich das in Erinnerung habe).

    Zum Beispiel (Das mit Siegfried):
    Da es sich dabei um ein Tupel, und nicht um ein Array, handelt, geht das einfacher. Ich nehme an, dass ist absichtlich kompliziert, damits dramatischer wirkt?

    foreach ($einweihner as $e) {
    if (in_array(‚Siegfried‘, $e[3]) {
    echo ‚Siegfried gefunden‘;
    break;
    }
    }

    Vermutlich wären Objekte aber sowieso angebracht gewesen.

    Reply
  2. @KingCrunch es ging wohl auch viel mehr um ein verständliches Beispiel, das goto z.B. auch nicht umbedingt die lesbarkeit von Code fördert… Wenn der Autor mit Objekten und den wildesten Array Konstrukten um sich geschmissen hätte, würden sicher viele den Wald vor lauter Bäumen nicht sehen. 😉 Aber der Autor hat mich in meiner „Behauptung“ das Performance nicht umbedingt immer alles ist, noch mals bestärkt. 🙂

    Reply
  3. Ich nutze auch break / continue mit verschiedenen Level, habe dabei aber immer im Ohr, dass der Code wohl an den Stellen nicht der Sauberste ist. Ein Java-Mensch meinte mal zu mir, dass solch ein Sprachkonstrukt Java nicht hat, weil man dort sauber programmiert. Sicherlich überspitzt ausgedrückt, aber die Bemerkung hat seine Berechtigung. Verschachtelte Bedinungen zu brechen ist per Definition böse.

    Ansonsten habe ich bei goto noch im Ohr, dass dies vor allem bei Parsern und Installationsskripten Sinn machen würde. Kann ich mir auch durchaus vorstellen, da ich so etwas aber kaum / gar nicht programmiere, habe ich aber auch kein Beispiel.

    P.S. Ist nicht jeder Funktionsaufruf nicht auch ein goto? 🙂

    Reply
  4. Danke für den interessanten Artikel.

    Für das konkrete Beispiel der Suche in einem Array halte ich die Umsetzung in while-Schleifen für „am saubersten“. Sprünge jeglicher Art sollte man meiner Meinung nach verhindern (dazu zähle ich break, continue, goto und auch ein return mitten in einer Funktion)

    Reply
  5. Ich mag etwas kritisch sein .. aber wenn ich so Code wie „if ($i++<LOOPS) goto top;" les .. dann will ich eigentlich schon nicht mehr über goto diskutieren 😉

    zum FrontController fürs ZF2.0: Ganz ehrlich .. dann weiß ich auch, warum – ich spar mir ne handvoll Funktionen .. und ersetze sie durch goto, Vorteil? 0.0

    Manchmal fragt man sich schon, ob man jedem Kram braucht den *irgendwer* für nützlich hält – natürlich kann ich's sicher mal irgendwo einbauen .. aber ob ich's dann auch gebraucht hab?

    Reply
  6. Der große Unterschied zwischen Funktionsaufruf und goto ist, daß die Funktion zu der Stelle zurückkehrt an der sie aufgerufen wurde. Während man bei einem goto nicht weiss wann der nachfolgende Code drankommt.

    Mein erster Informatikprof hat uns in einer Übung ein kleines, verschachteltes C-Programm auf gotos umbauen lassen.

    Die nächste Woche mussten wir die Lösung unseres Sitznachbarn korrigieren. Ich hab danach nie wieder ein goto angefasst 😉

    Ein Anwendungsfall für goto ist die Programmierung von Zustandsautomaten, die lassen sich so etwas übersichtlicher gestalten. Praktisch probiert hab ich es allerdings auch noch nicht.

    Reply
  7. @ KingCrunch: Logisch hast du recht, ich hab die Array-Struktur ja auch extra so angelegt, dass dein $e[3] nicht funktioniert – Kein Mensch würde in der Praxis ein Array so aufbauen. Man hätte jetzt auch mit irgendwelchen abgefahrenen Matrizenberechnungen ankommen können, das wollte ich mir und euch aber nicht antun.

    @ Ulf Kirsten: Prinzipiell wird tatsächlich auf Maschinenebene (wo wir mal wieder bei den „Compilersprachen“ sind) jeder Unterprogrammaufruf als Sprung (incl. Sicherung der Rücksprungadresse auf dem Stack) realisiert. Da hab ich aber viel zu wenig Ahnung, wie PHP da intern mit umgeht, deswegen hoff ich auf den ebenfalls auf der Ideenschmiede eingetragenen „Wie funktioniert PHP intern?“-Artikel (hochvoten!!!). Wie gestern gelesen habe, tickt PHP ja sowieso ganz anders (siehe Artikel http://schlueters.de/blog/archives/125-Do-not-use-PHP-references.html).

    lg

    Reply
  8. „Und jetzt kommt das, was sehr viele Entwickler nicht wissen: Man kann break auch mit einem Parameter aufrufen, der die Level an for-Loops bestimmt, die gebrochen werden sollen.“

    Ich hoffe das war ein schlechter Scherz. Wer diese grundlegende Syntax nicht kennt, sollte sich nicht „Entwickler“ nennen.

    Reply
  9. Eine kleine Anmerkung: „function get_timestamp()“ aus dem einen Beispiel braucht es in „modernen“ PHPs nicht. microtime(true); erfüllt den selben Zweck.

    Zum Thema „sprachelemente werden auf sprung zurück geführt“ – dasis nicht 100%ig korrekt. In Cwerden sie auf die Befehle der CPU umgesetzt. Wenn es eine CPU gibt die höherwertige Schleifen gibt wird das genutzt. x86 und die meistenandere Architekturen bieten dabeinicht nur „Sprünge“ (JMP) an sondern auch konditionale Sprünge, so wie man die für ein „if“ braucht – alles damit verbunden wie die CPU arbeitet. PHP hat auch solch einen befehlssatz für die „virtuelle CPU“ – dieser Befelssatz heißt Opcode – und diese „virtuelle CPU“ wurdefür PHP entwicklet und kann damit auch spezielle „höhere“ konstrukte die PHP braucht … wobei das nur bei foreach() und switch() relevant ist ….

    Reply
  10. Mal ganz davon abgesehen das es natürlich nur als beispiel dient, aber wirklich „schön“ ist das alles nicht.

    function findSiegfried(array $arr){
    for($i=0;$i<count($arr);$i++){
    if(is_array($arr[$i])){
    findSiegfried($arr[$i];
    }else{
    if($arr[$i] == 'Siegfried'){
    echo "Gotcha";
    return;
    }
    }
    }
    }

    Zum Thema goto kann ich nur sagen das es wie einige andere elemente auch in php es nur ein paar wirkliche ausnahmen gibt wo man es verwenden sollte. (global zählt mittlerweille zb. auch dazu)

    my2c
    Ludwig

    Reply
  11. goto war lange Zeit (vor powershell) die einzige Möglichkeit in der Windows Kommandozeile Schleifen zu erzeugen.

    :setupArgs
    if %1a==a goto doneStart
    set JMETER_CMD_LINE_ARGS=%JMETER_CMD_LINE_ARGS% %1
    shift
    goto setupArgs

    Da bekommt der Begriff ifschleife (if-schleife.de) eine ganz neue Bedeutung.

    Gott, was liebe ich die Unix Shell (allen voran die bash).

    Reply
  12. @ Johannes: Sehr interessant, war ja schonmal ein kleiner Einblick hinter die Fassade. Das mit der „virtuellen CPU“ habe ich so noch nicht gewusst. Muss dir recht geben bezgl. den bedingten Befehlen, der Einfachheit halber bin ich hier von einer Uralt-CPU-Architektur (MU2) ausgegangen. Mit moderneren Prozessoren hast du in ASM u.U. sogar weniger Zeilen als unter C, auf Grund der angesprochenen bedingten Befehle. Trotzdem bleibt es dabei, dass es in Maschinencode vor „branches“ (Sprüngen) nur so wimmelt. Jeder Unterprogrammaufruf wird ja im Endeffekt auf einen Sprung zurückgeführt.

    Reply
  13. Hab diesen Post erst heute gelesen. Also bei allem für und wieder, die Siegfried Problematik schreit geradezu nach Rekursion! Damit kann man die Zahl der Nested Loops auf 1 reduzieren UND vermeidet goto.

    Reply
  14. Habe mir zum Release von 5.3 auch mal Gedanken zum Thema „goto“ gemacht und zumindest für mich – theoretisch, bisher nie ausprobiert – eine Anwendungsmöglichkeit gefunden:

    Ich verkette ziemlich häufig If-Abfragen, die irgendwann zum Ergebnis führen, im Fehlerfall aber einen detailierten Fehlerbericht zurückgeben bzw. speichern sollen. IMHO kann man mit „goto“ den Code somit etwas sauberer strukturieren, da man nicht über X-Ebenen If-Abfragen ausführt, sondern nacheinander und im Fehlerfall könnte man immer mit goto end; ein Errorhandling starten.

    Wie erwähnt, habe ich das Konstrukt nie getestet und kann nur theoretisch die Übersichtlichkeit beurteilen, aber in einer freien Minute werde ich solch eine Case mal aufbauen und mit herkömmlichen Methoden vergleichen.

    Eure Meinung dazu?

    Reply
  15. Mir ist goto aus meiner Frühzeit (Comodore VC20 und cpc464) noch sehr gut bekannt. Ich möchte hier einmal anmerken, das ich immer sehr gut mit goto leben konnte (Funktionten und Klassen in Form von ausgelagerten Codeabschnitten) und jetzt wo ich mich mit php und der gleichen befasse, habe ich diese Möglichkeit schon in dem einen oder anderen Fall vermisst.
    Ich weiß auch nicht was daran unleserlich sein soll.
    Ob ich nun Fuktionen (welche ich auch erst mal schreiben oder Auswendig lernen muss)aufrufe oder ich springe in einen Codeabschnitt,welcher auch noch gut Dokumentiert ist weil selbst entwickelt und ja sogar mit Ueberschrift:. Man muss halt nur damit umgehen können und nicht gleich (weil es einfach ist) mal hier hin mal dahin springen . Ich schreibe solche Blöcke immer gern ans Ende. Da kann ich dann immer nachschaun bzw vorher rein schaun, bevor ich am Hauptcode weiter schreibe.
    An sonsten wen tut es weh keiner ist gezwungen goto zu benutzen! 😉

    Reply
  16. Args, wollte gerade goto verwenden und bekam prompt: syntax error, unexpected T_STRING. Jetzt war auf dem alten Testserver noch eine alte 5.2x Version drauf.

    Ich stand mal auf dem Kriegsfuß mit goto als ich in den 90er ein mittelgroßes Basic Programm (ohne Doku) erweitern musste. Da wimmelte es nur von goto Sprüngen. Selbst setze ich das immer nur im Zusammenhang mit einer Abbruchbedingung ein und springe dann an das Ende einer Funktion bei der nachfolgend nur noch Aufräumarbeiten erledigt werden. Funktioniert problemlos und ist praktisch.

    Reply
  17. Hier noch ein SEHR praktischer Einsatzbereisch:
    Ich nutze Doxygen um meine Dokus zu erstellen. Leider ignoriert Doxygen (Windows 1.7.4) manchmal Klassen die in ‚if‘ Blöcken stehen. Wenn man die Deinition aber mit goto überspringt funktioniert alles zuverläsig. Naja dafür wird dann ein eintrag für das goto label in der doku erstellt aber das lässt sich mit „/// @cond GOTO“ verhindern.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen