am 12. Januar 2010
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:

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)