BloedeFrage schrieb:> Hallo, warum soll ich goto nicht nutzen?
Ich würde sagen, zu diesem Thema findest du bereits jedem Menge
Informationen und Diskussionen im Netz. Hast du schon alle gelesen? Wo
sind noch offene Detailfragen?
1. weils auch ohne geht
2. weils ohne besser geht
3. weils mit schlechter geht
4. weils keiner braucht
5. wers trotzdem benutzt sollte sich über seine Herangehensweise
Gedanken machen, seine Fehler selber suchen und eventuell mal über eine
andere Sprache nachdenken, die einen von vornherein durch das schlichte
"goto-nicht-vorhanden-sein"
vor derartigen Irrwegen bewahrt
BloedeFrage schrieb:> Sämtliche Leute die mir bisher programmieren beigebracht haben
Wie viele haben dir das programmieren beigebracht? Welche
Programmiersprachen hast du dabei erlernt? Haben diese Leute die
Forderung - goto nicht zu verwenden - begründet? Wie wurde die Forderung
begründet? Wie alt bist du? Welchen Schulabschluss hast du? Hast du
einen Hochschulabschluss? Wenn ja, welchen Anschluss hast?
Dieses Dogma hält sich deshalb so gut, weil es deutlich einfacher ist,
zu verlagen, es nicht zu benutzen, als zu verstehen, wie man es richtig
benutzt.
Gernationen von Programmierern haben deshalb nicht einmal versucht zu
verstehen, wie man es richtig benutzt.
Es lohnt sich, die diversen Abhandlungen azuschauen und auch mal einen
Blick in den Linux-Kernel zu werfen anstatt goto unbegründet zu
verteufeln.
In der Tat gibt es wenige gute Anwendungsszenarien für goto, aber es
gibt sie!
BloedeFrage schrieb:> Hallo, warum soll ich goto nicht nutzen?
Weil die Erfahrung lehrt, dass so etwas bei jemandem der es nicht kann,
ganz schnell zu unwartbarem Spaghetticode führt.
Es gibt Einsatzfälle für goto. Aber das sind ganz spezielle Fälle.
Einfach mit goto wie wild drauf los programmieren, führt fast immer ins
Chaos. Speziell bei Anfängern. Ein Profi hingegen braucht so etwas nicht
zu fragen. Der weiß, wann ein goto tatsächlich die einfachste und beste
Lösung ist und wann nicht.
BloedeFrage schrieb:> Sämtliche Leute die mir bisher programmieren beigebracht haben
Als Anfänger tut man gut dran, es zu vermeiden, da es eine relativ hohe
Gefahr birgt, damit schlechten Code zu schreiben.
Es gibt aber Fälle, in denen es durchaus sinnvoll ist. Wer das generell
meidet wie der Teufel das Weihwasser, der hat's aber nicht wirklich
verstanden.
Klaus Kaiser schrieb:> Dieses Dogma hält sich deshalb so gut, weil es deutlich einfacher ist,> zu verlagen, es nicht zu benutzen, als zu verstehen, wie man es richtig> benutzt.
Allerdings. Wenn man sich Richtlinien wie MISRA anschaut, findet man
noch jede Menge weitere Regeln, die genau in die selbe Kerbe schlagen.
> Gernationen von Programmierern haben deshalb nicht einmal versucht zu> verstehen, wie man es richtig benutzt.
Das sehe ich auch immer wieder. Die schlagen die Hände schon über dem
Kopf zusammen, wenn man goto nur erwähnt, aber wenn man genauer
nachhakt, merkt man, daß sie eigentlich gar nicht wissen, warum. Es
werden halt nur die Sprüche derer, von denen man die Sprache gelernt
hat, nachgeplappert, ohne sie zu verstehen.
GENAU aus dem Grund habe ich die Frage gestellt. Und habe jetzt,
zumindest ein paar, brauchbare Einschätzungen erhalten. Es geht also vor
allem um die Lesbarkeit, welche durch (schwer nachzuvollziehende)
Sprünge leidet. Richtig?
>In der Tat gibt es wenige gute Anwendungsszenarien für goto, aber es>gibt sie!
Was wäre denn ein gutes Beispiel? Das Abbrechen einer verschachtelten
Schleife oder Fehlerbehandlung?
BloedeFrage schrieb:> GENAU aus dem Grund habe ich die Frage gestellt. Und habe jetzt,> zumindest ein paar, brauchbare Einschätzungen erhalten. Es geht also vor> allem um die Lesbarkeit, welche durch (schwer nachzuvollziehende)> Sprünge leidet. Richtig?
Mal eine kleine Beispielfunktion (Arduino), die 2 zurückliefert, wenn
man 1 als Parameter übergibt und die 1 zurückliefert, wenn man 2 als
Parameter übergibt und ansonsten eine Fehlermeldung auf Serial ausgibt.
1
inttausche_1_2(inti)
2
// liefert 2 bei Eingabe 1
3
// liefert 1 bei Eingabe 2
4
// liefert sonst i und eine Serial-Fehlermeldung bei allen anderen i
5
{
6
if(i==1)gotoskip_1;
7
if(i==2)gotoskip_2;
8
gotoerror;
9
skip_1:
10
i=2;
11
gotodone;
12
skip_2:
13
i=1;
14
gotodone;
15
error:
16
Serial.print("Falscher Eingabewert: ");
17
Serial.println(i);
18
done:;
19
return(i);
20
}
Schöner geht es doch nun wirklich nicht, oder?
;-)
Bei den uralten BASIC Dialekten ist das mit dem GOTO übrigens dadurch
gekommen, dass pro Programmzeile (das waren Interpretersprachen mit
numerierten Zeilen) nach einer if-Abfrage nur ein einzelner Befehl
ausgeführt werden konnte. Für if-Zweige gab es keine Codeblöcke. Daher
war es dann eben oft so, dass als einziger erlaubter Befehl nach einer
positiven if-Bedingung ein GOTO-Sprung gemacht wurde.
Seit alle Programmiersprachen Codeblöcke nach einer if-Bedingung
erlauben (selbst BASIC kann das seit Jahrzehnten) ist GOTO relativ
überflüssig.
Nützlich ist "goto" ist heute noch ggf. beim Herausspringen aus mehrfach
verschachtelten for-Schleifen, wenn man aus einer der inneren
for-Schleifen direkt herausspringen möchte.
Jürgen schrub:
>Nützlich ist "goto" ist heute noch ggf. beim Herausspringen aus mehrfach>verschachtelten for-Schleifen, wenn man aus einer der inneren>for-Schleifen direkt herausspringen möchte.
Das ist aber keine gute Idee, da mit GOTO herauszuspringen, weil das
jede Menge "Trümmer" hinterläßt.
Bei Bascom gibt es für solche Aktionen diese Befehle:
EXIT FOR
EXIT DO
EXIT WHILE
EXIT SUB
EXIT FUNCTION
*******************************************************************
Exit Paul
NoGoTo schrieb:> 3. weils mit schlechter geht
Ich kenne das auch so. Jeder erzählt es. Mich würde mal eine wirkliche
Abhandlung darüber interessieren wieso es damit schlechter sein soll.
Wirklich Nachteile und nicht nur den Millionsten der sage "das darf man
nicht".
Paul Baumann schrieb:> Das ist aber keine gute Idee, da mit GOTO herauszuspringen, weil das> jede Menge "Trümmer" hinterläßt.>> Bei Bascom gibt es für solche Aktionen diese Befehle:> EXIT FOR
Und aus einer dreifach geschachtelten for-Schleife nach diesem Muster
springst Du mit Deinem Bascon dann wie heraus?
1
voidnestedFor()
2
{
3
for(inti=0;i<255;i++)
4
{
5
for(intj=0;j<255;j++)
6
{
7
for(intk=0;k<255;k++)
8
{
9
if(i==100&&j==20&&k==2)gotoende;
10
}
11
}
12
}
13
ende:;
14
}
Hast Du das "verschachtelt" in meinem Beitrag überlesen oder kannst Du
bei Deinem BASCOM aus einer Dreifachverschachtelung vielleicht
rausspringen indem Du schreibst
EXIT FOR FOR FOR
???
Natürlich gibt es auch unter C-Möglichkeiten, eine dreifach
verschachtelte for-Schleife vorzeitig abzubrechen, ohne goto zu
verwenden. Aber diese mehrfach verschachtelte for-Schleife wäre einer
der wenigen Fälle, wo es mit goto eleganter als ohne goto möglich ist.
132 schrieb:> NoGoTo schrieb:>> 3. weils mit schlechter geht>> Ich kenne das auch so. Jeder erzählt es. Mich würde mal eine wirkliche> Abhandlung darüber interessieren wieso es damit schlechter sein soll.
Der von Jürgen weiter oben gepostete Spaghetti Code reicht dir noch
nicht?
Was ist leichter zu verstehen? Der Code da oben, oder
1
inttausche_1_2(inti)
2
{
3
if(i==1)
4
return2;
5
if(i==2)
6
return1;
7
8
Serial.print("Falscher Eingabewert: ");
9
Serial.println(i);
10
returni;
11
}
oder für die, die sich am Mehrfach-Return stören
1
inttausche_1_2(inti)
2
{
3
intResult;
4
5
if(i==1)
6
Result=2;
7
8
elseif(i==2)
9
Result=1;
10
11
else{
12
Result=i;
13
Serial.print("Falscher Eingabewert: ");
14
Serial.println(i);
15
}
16
17
returnResult;
18
}
Leider ist Jürgens Code von weiter oben (der zur Abschreckung gedacht
war) so ziemlich genau das, was bei einem durchschnittlichen Anfänger
rauskommt, wenn er hemmungslos goto verwendet. Er will bestehenden Code
nicht verändern, muss ihn aber erweitern, also springt er mit einem goto
quer durch die Gegend.
Diese ganzen Konstrukte wie 'if', 'for', 'while', 'else' laufen letzten
Endes immer intern auf einen goto hinaus. Nur verpacken sie das ganze
so, dass dieser implizite goto in einer strukturierten Form vorkommt und
in geordneten Bahnen verläuft. Quasi eine freiwillige
Selbstbeschränkung. Gerade Schleifenkonstrukte sind dafür ja ein
Paradebeispiel: Im Grunde alles durch andere Konstrukte und einem goto
ersetzbar.
Jürgen frug:
>Und aus einer dreifach geschachtelten for-Schleife nach diesem Muster>springst Du mit Deinem Bascon dann wie heraus?
Bascom -mit "M" wie Matratze
Pass gut auf: Das ist nicht mein Bascom, es ist keine spezielle
Version
für mich erstellt worden.
Solche Konstrukte wie Deines verwende ich nicht. Insofern ist mir das
vollkommen Titte!
Es ging darum, auf geordnete Weise aus einer Schleife
herauszuspringen.
Paul
Es gab früher mal so etwas wie ein Struktogramm, und dort konnte man
gotos halt nicht einpflegen zudem ist die Programmierung nicht
"strukturiert"
Im Grunde spricht aber nichts gegen gotos. Ich verwende die auch des
öfteren für Fehlerhandlingsituationen (wie oben auch schon von jemand
anderen angedeutet).
Es ist manchmal einfacher (zu lesen) von mehrerer Stellen aus mit GOTO
an (eine) bestimmte Stele springen, als bsp.weise etliche
verschachtelte IFs zu benutzen.
Gozo schrieb:>>In der Tat gibt es wenige gute Anwendungsszenarien für goto, aber es>>gibt sie!> Was wäre denn ein gutes Beispiel? Das Abbrechen einer verschachtelten> Schleife oder Fehlerbehandlung?
Genau das sind die beiden sinnvollen Anwendungsfälle, die ich kenne.
Paul Baumann schrieb:> Das ist aber keine gute Idee, da mit GOTO herauszuspringen, weil das> jede Menge "Trümmer" hinterläßt.
Was für Trümmer soll das denn hinterlassen?
Paul Baumann schrieb:> Es ging darum, auf geordnete Weise aus einer Schleife herauszuspringen.
Eben nicht. Du antworetest auf:
Jürgen schrub:
>Nützlich ist "goto" ist heute noch ggf. beim Herausspringen aus mehrfach>verschachtelten for-Schleifen, wenn man aus einer der inneren>for-Schleifen direkt herausspringen möchte.
Einfach nur aus einer Schleife rausspringen kann man in C auch mit
break.
Gozo schrieb:> Was wäre denn ein gutes Beispiel? Das Abbrechen einer verschachtelten> Schleife oder Fehlerbehandlung?
Gerade bei der Fehlerbehandlung verwende ich regelmäßig gotos, wenn
nachher noch "aufgeräumt" werden muß. Allerdings gehört Fehlerbehandlung
immer noch zu den Dingen, zu denen ich wenig Informationen über
sinnvolle Konzepte lesen konnte. So sieht bei mir z.B. eine typische
Fehlerbehandlung aus:
1
/* Ganze Seite in EEPROM schreiben
2
* Nur getestet fuer EEPROMS vom Typ 24C02, bei groesseren muss die
3
* Speicheradresse in die I2C-Adresse eingerechnet werden. Nur einzelne
4
* Speicherseiten (16 bytes) werden unterstuetzt.
5
* Wegen des malloc duerfte es eine der langsamsten und speicherintensivsten
Was man auf den ersten Blick sieht, ist daß hier das Paradigma des "nur
ein Rückkehrpunkt" massiv verletzt ist - andererseits ist das ja auch
keine "normale" Funktion, bei der das Funktionsergebnis wichtig ist,
sondern wo nur die Nebenwirkungen gewollt werden und der
Rückgabeparameter nur Hilfsmittel der Kontrolle ist.
Sprich: Ich kenne oft kein besseres Mittel zur Fehlerbehandlung als
gotos.
Viele Grüße
W.T.
[EDIT: OK, gerade hier ist der einzige Zweck des gotos die
Wiedererkennbarkeit, da die einzige Sprungquelle direkt davor liegt.]
Rolf Magnus schrieb:> Jürgen schrub:>>Nützlich ist "goto" ist heute noch ggf. beim Herausspringen aus mehrfach>>verschachtelten for-Schleifen, wenn man aus einer der inneren>>for-Schleifen direkt herausspringen möchte.>> Einfach nur aus einer Schleife rausspringen kann man in C auch mit> break.
Aber nicht, wie er schreib, bei einer "mehrfach verschachtelten
for-Schleife".
Es ist weitgehend egal. In PIC assembler muss man es andauernd
verwenden.
Wichtig ist dass der Quelltext gut lesbar ist, wie dies erreicht wird
ist egal.
Rolf Magnus schrieb:> Gozo schrieb:>>>In der Tat gibt es wenige gute Anwendungsszenarien für goto, aber es>>>gibt sie!>> Was wäre denn ein gutes Beispiel? Das Abbrechen einer verschachtelten>> Schleife oder Fehlerbehandlung?>> Genau das sind die beiden sinnvollen Anwendungsfälle, die ich kenne.
In Java sind diese beiden Probleme durch labeled Statments gelöst. Damit
kann man mit break <label> beliebig verschachtelte Schleifen oder Blöcke
verlassen. Das break <label> stellt – wie auch alle anderen
Kontrollkonstrukte auch – ein verstecktes, aber eingeschränktes (und
damit weniger böses) goto dar und macht das goto fast völlig
überflüssig, weswegen es aus Java längst hinausgeschmissen wurde.
Auch andere etwas neuere Programmiersprachen bieten ähnliche Konstrukte
zum Verlassen verschachtelter Schleifen und zur übersichtlicheren
Fehlerbehandlung.
Es gibt aber noch ein paar weitere Anwendungen für Gotos. Zwei
Beispiele, die mir gerade einfallen:
- State-Machines: Hier werden mitunter Gotos für schnelle
Zustandswechsel ohne eine explizite Zustandsvariable verwendet.
- Source-to-Source-Compiler: Die Generierung von goto-durchsetztem
Spaghetti-Code ist oft einfacher als die Strukturierung des Codes in
while…endwhile, for…endfor, if…else…endif usw. Da der generierte Code
ohnehin nicht dazu gedacht ist, von Menschen gelesen zu werden, stellt
das keinen großen Nachteil dar.
Rolf Magnus schrieb:> Was für Trümmer soll das denn hinterlassen?
Das kommt auch wesentlich auf den Compiler an. Soweit ich weiss gibt es
welche, die einem Goto automatisch (magisch) Aufräumcode voranstellen,
es gibt aber auch welche, die einen aus einer Prozedur herausspringen
lassen ohne den Stack zu bereinigen.
Nicht umsonst gibt es in manchen Firmen qualitätssichernde Vorschriften
wie etwa die, dass eine Funktion immer nur einen Einsprungspunkt und
einen Endpunkt haben darf.
In Assembler darf man sowieso alles, aber das ist keine Entschuldigung
für schlecht strukturierte Programme. Und wenn man tricksen muss, etwa
aus einem Unterprogramm direkt in ein anders springen, sollte man das
sorgfältig kommentieren, aber noch besser es sein lassen.
Georg
Yalu X. schrieb:> In Java sind diese beiden Probleme durch labeled Statments gelöst. Damit> kann man mit break <label> beliebig verschachtelte Schleifen oder Blöcke> verlassen. Das break <label> stellt – wie auch alle anderen> Kontrollkonstrukte auch – ein verstecktes, aber eingeschränktes (und> damit weniger böses) goto dar und macht das goto fast völlig> überflüssig, weswegen es aus Java längst hinausgeschmissen wurde.
.. in Java sollte man break labels auch vermeiden wenn moeglich, weil es
eben so nah am goto ist ;)
Braucht man alles auch nicht wenn der Code sauber strukturiert ist, d.h.
keine geschachtelten Schleifen bzw. keine anderen Kontrollstrukturen die
direkt geschachtelt sind.
Jede Schleife in eine eigene Methode, dann reicht in 99% der Faelle ein
return.
Mladen G. schrieb:> Braucht man alles auch nicht wenn der Code sauber strukturiert ist, d.h.> keine geschachtelten Schleifen bzw. keine anderen Kontrollstrukturen die> direkt geschachtelt sind.
wenn man z.b Bilddaten mit X und Y bearbeitet. Dann sind geschachtelten
Schleifen sehr wohl sinnvoll. Dafür extra eine Funktion aufrufen ist
wohl nicht immer die Lösung.
Außerdem ist eine extra Funktion auch keine Lösung, dann damit hat man
genau das gleiche Problem
1
for(x...){
2
for(y...){
3
if(...)
4
gotoende;
5
}
6
}
7
:ende
mit einer Funktion geht das auch nicht sinnvoller
1
for(x...){
2
if(foo(x)==0)
3
break;
4
}
5
}
dann kann ich auch mit einer extra variable arbeiten
1
booldoExit=false;
2
for(x...){
3
for(y...){
4
if(...){
5
doExit=true;
6
break;
7
}
8
if(doExit)
9
break;
10
}
für mich die die goto lösung die übersichtlichste und vermutlich auch
die schnellste.
Die Version mit der Extra Funktion ist die langsamste.
Warum sollte man auf ein Sprachfeature verzichten, wenn man damit kurzen
und schnellen code schreiben kann?
Peter II schrieb:> wenn man z.b Bilddaten mit X und Y bearbeitet. Dann sind geschachtelten> Schleifen sehr wohl sinnvoll. Dafür extra eine Funktion aufrufen ist> wohl nicht immer die Lösung.
KOmmt auf den Kontext an.
Bei C etc. pp. mag das stimmen.
Bei Java gibt es absooluit keinen Grtund ekine eigenen Methode dafuer zu
nehmen, siehe auch "Clean Code" von Bob Martin dazu..
Peter II schrieb:> für mich die die goto lösung die übersichtlichste und vermutlich auch> die schnellste.> Die Version mit der Extra Funktion ist die langsamste.
In C vielleicht.. in Java sieht das anders aus, da kommt der JIT/Hotspot
besser mit kleinen Methoden zurecht.
Ist aber voreilige Optimierung, in Java sollte definitiv der Quellcode
im vordergrund stehen, nicht Pseudo Optimierungen.
Peter II schrieb:> Warum sollte man auf ein Sprachfeature verzichten, wenn man damit kurzen> und schnellen code schreiben kann?
Weil der Code schlecht zu lesen und warten ist.
Mladen G. schrieb:>> Warum sollte man auf ein Sprachfeature verzichten, wenn man damit kurzen>> und schnellen code schreiben kann?>> Weil der Code schlecht zu lesen und warten ist.
klar wenn du nie goto verwendest, kannst du den code auch nicht warten.
Bei anderen gehört es einfach zur Sprache dazu und ist genauso normal
wie der "?" Operator, und diese Leute haben kein Problem mit der
Wartung.
Peter II schrieb:> klar wenn du nie goto verwendest, kannst du den code auch nicht warten.> Bei anderen gehört es einfach zur Sprache dazu und ist genauso normal> wie der "?" Operator, und diese Leute haben kein Problem mit der> Wartung.
Ich hatte noch nie das Beduerfnis goto zu verwenden, wozu auch?
Sehe keinen einzigen nutzen darin, ausser dass es den Code schlechter
macht.
"Andere Leute" finden zB. auch PHP und Perl super, ich nicht.
Mladen G. schrieb:> Ich hatte noch nie das Beduerfnis goto zu verwenden, wozu auch?> Sehe keinen einzigen nutzen darin, ausser dass es den Code schlechter> macht.
Das es eine code schlechter macht ist ja Ansichtssache. Andere finden
ihn dann besser.
Wenn du aber jeden code mit goto als grundsätzlich schlecht Betrachtest
macht eine weiter Diskussion keine sinn.
http://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c/
Peter II schrieb:> Wenn du aber jeden code mit goto als grundsätzlich schlecht Betrachtest> macht eine weiter Diskussion keine sinn.
Wie gesagt, im Kontext Java ist das immer (fast) immer schlecht, wobei
es ja in Java nur die break Labels gibt und ich diese dann verteufele ;)
goto ist in Java zwar reserviert, aber nciht belegt.
Dein Link zeigt C, da ist das eben ein anderer Kontext.
Wobei man jetzt auch argumentieren kann, dass das Auslagern von
verschachtelten Schleifen in eigene Funktionen den ansonsten knackigen
Algorithmus zerfleddert und er dadurch wiederum schlecht zu lesen und zu
warten ist. Kommt wohl auf den Einzelfall an ...
Mladen G. schrieb:> .. in Java sollte man break labels auch vermeiden wenn moeglich, weil es> eben so nah am goto ist ;)> ...> Jede Schleife in eine eigene Methode, dann reicht in 99% der Faelle ein> return.
Für mein Empfinden ist ein Return aus einer (evtl. verschachtelten)
Schleife schlechterer Stil als ein entsprechender Break, da letzterer
zwar eine oder mehrere Blockgrenzen, der Return aber zusätzlich noch
eine Methodengrenze überspringt.
Dass des von vielen Java-Programmieren anders gesehen wird, liegt
vielleicht auch daran, dass die break-<label>-Anweisungen und die Labels
selbst syntaktisch stark den Gotos bzw. den Labels in C ähneln.
Semantisch gesehen sind aber wesentlich restriktiver.
PHP verwendet beim break statt der Labels eine Zahl, die angibt, um
wieviele Ebenen beim Verlassen von verschachtelten Schleifen nach oben
gesprungen wird. Das gefällt mir syntaktisch besser, hat aber den
Nachteil, dass man bei langen Schleifenrümpfen nicht sofort sieht,
welche der Schleifen denn nun tatsächlich beendet werden und welche noch
weiterlaufen.
Yalu X. schrieb:> Für mein Empfinden ist ein Return aus einer (evtl. verschachtelten)> Schleife schlechterer Stil als ein entsprechender Break, da letzterer> zwar eine oder mehrere Blockgrenzen, der Return aber zusätzlich noch> eine Methodengrenze überspringt.
Wenn die Methode nur diese eine Schleife enthaelt, ist es "sauberer".
Die Clean Code Regeln aus Bob Martins Buch machen Sinn wenn man sie alle
im Kontext sieht.
zB ist es gar kein Problem mehrere returns in einer einzigen Methode zu
haben, denn Methoden haben auch sehr kurz zu sein, wieviele returns
bekommt man in 3-5 Zeilen unter? ;)
Alexander B. schrieb:> Wobei man jetzt auch argumentieren kann, dass das Auslagern von> verschachtelten Schleifen in eigene Funktionen den ansonsten knackigen> Algorithmus zerfleddert und er dadurch wiederum schlecht zu lesen und zu> warten ist. Kommt wohl auf den Einzelfall an ...
Mag sein dass es im Einzelfall anders ist, aber dier generelle Regel nur
eine einzige "Einrueckung" pro Methode zu haben funzt, Ausnahmen fuer
spezielle Algorythmen (die man in Java mit der Lupe suchen muss) kann
man ja machen.
In der Batch-Programmierung ist das gang und gäbe, weil man dort ohne
GOTO nicht weiterkommt:
http://www.robvanderwoude.com/goto.php
Spaghetti mit Tomatensoße
BloedeFrage schrieb:> GENAU aus dem Grund habe ich die Frage gestellt. Und habe jetzt,> zumindest ein paar, brauchbare Einschätzungen erhalten. Es geht also vor> allem um die Lesbarkeit, welche durch (schwer nachzuvollziehende)> Sprünge leidet. Richtig?
Nein, nicht wirklich. Es gab historische Gründe.
Zu der Zeit, als C aufkam, gab es massenweise Homecomputer, auf denen
ein residenter Basic-Interpreter werkelte. Eher "richtige" Computer, wo
CP/M drauf lief, waren teuer bis unerschwinglich.
Der Weg, den man also auf seinem Homecomputer einschlagen mußte, um
irgend ein Programm zum Laufen zu bekommen war also, erstmal Basic zu
lernen und zu benutzen. Basic war eine Sprache, die sehr
zeilennummernorientiert war. Also z.B. ein Unterprogramm-Aufruf hieß
z.B. GOSUB 1000 wobei 1000 die Zeilennummer war. Genauso ging es mit
GOTO 1005 oder so. Schau dir mal ELIZA.BAS an, dann verstehst du wie das
damals ging.
Für ganz einfache Programme war das alles ausreichend, aber nicht für
größere komplexere Programme. Man kann in C auch so ähnlich schreiben
wie in Basic - genauso, wie man auch z.B. in fast jeder
Programmiersprache so ähnlich schreiben kann wie in Fortran.. ;-)
Nun, um nun all die jungen Basic-gewöhnten angehenden Programmiereriche
umzuerziehen, wurde eben damals die Parole formuliert, in C niemals goto
zu benutzen, bei Strafe der Exkommunikation oder Kielholen oder was auch
immer.
Ja, DAMALS. Mittlerweile kennt kaum ein junger Strolch noch Basic, aber
der Drill der damaligen Gulags hält sich weiter in den Köpfen und hat
abenteuerliche Workarounds beschert. Ein häufiges Beispiel ist while(1).
Eigentlich ist sowas wie while ja eine bedingte Schleife und auch genau
dafür gedacht. Daß man sie mit while(1) quasi entartet hat, bloß um ein
simples goto zu vermeiden, zeigt, was für Verrenkungen sich immer bei
dogmatischem Drill einstellen.
Laß dich also von beschränkten Dogmatikern nicht einschüchtern, sondern
verwende eben auch goto, wo es einen echten Sinn ergibt. Das ist
tatsächlich nicht wirklich häufig, aber es kommt vor. Ein simples
Beispiel ist die Grundschleife bei Mikrocontrollern:
int main (void)
{ initialisiere();
immerzu:
machedies();
machedas();
machenochwas();
goto immerzu;
}
Wenn man das mit einem while(1) formuliert, dann meckern manche
Compiler, daß main ja gar kein Ergebnis zurückgibt - zu Recht (im
Prinzip). Also findet man bei solchen Strategen nach der Endlosschleife
noch ein return 0 oder so, um den schieren Formalismus zu befriedigen.
W.S.
Mladen G. schrieb:> zB ist es gar kein Problem mehrere returns in einer einzigen Methode zu> haben, denn Methoden haben auch sehr kurz zu sein, wieviele returns> bekommt man in 3-5 Zeilen unter? ;)
Einverstanden.
Wenn die Methoden aber so kurz sind, muss man verschachtelte Schleifen
auf mehrere Methoden verteilen (das hast du weiter oben wahrscheinlich
auch so gemeint).
Wie kannst du dann aber von der innersten Schleife (die sich in einer
Methode befindet) aus die äußerste (in einer anderen Methode) verlassen?
Das geht doch nur dadurch, dass jede Methode ein entsprechendes Flag
zurückgibt, das in der Schleife der aufrufenden Methode abgefragt und
ggf. weitergereicht wird, und das über mehrere Ebenen.
Das wird doch beliebig kompliziert und trägt sicher nicht zur besseren
Lesbarkeit des Programms bei. Oder wie würdest du bspw. folgende Methode
in drei Methoden mit jeweils einer Schleife aufdröseln?
Georg schrieb:> Rolf Magnus schrieb:>> Was für Trümmer soll das denn hinterlassen?>> Das kommt auch wesentlich auf den Compiler an. Soweit ich weiss gibt es> welche, die einem Goto automatisch (magisch) Aufräumcode voranstellen,> es gibt aber auch welche, die einen aus einer Prozedur herausspringen> lassen ohne den Stack zu bereinigen.>> Nicht umsonst gibt es in manchen Firmen qualitätssichernde Vorschriften> wie etwa die, dass eine Funktion immer nur einen Einsprungspunkt und> einen Endpunkt haben darf.>> In Assembler darf man sowieso alles, aber das ist keine Entschuldigung> für schlecht strukturierte Programme. Und wenn man tricksen muss, etwa> aus einem Unterprogramm direkt in ein anders springen, sollte man das> sorgfältig kommentieren, aber noch besser es sein lassen.>> Georg
Zeig mir mal einen modernen Compiler der es erlaubt aus der Prozdur
(Pascal?) herauszuspringen. Das geht in C schon mal gar nicht.
In Embedded C bekommst du sofort einen Absturz wenn der Stack nicht
stimmt.
Ausserdem koennen C funktionen durchaus viele Endpunkte haben- je nach
Bedingung wird einer zuerst aktiv. Dadurch wird der Quelltext kuerzer
z.b.
1
if(wert==1)return(0);elsereturn(0xff);
kannste in dem Fall auch
1
return(wert);
verwenden, aber es ist nur ein Beispiel, vielleicht ist Wert obskur und
soll bereits in der Funktion umgesetzt werden.
Denk mal darueber nach:
1
i=7;
2
reloop:;
3
*(ch++)=0;if((i--)>0)gotoreloop;
bewirkt auch nichts anderes als eine for schleife.
Mit dem Unterschied dass bei for die Bedingung zuerst abgefragt wird,
hier wird der code innerhalb zumindest einmal abgearbeitet.
ausserdem, wenn du es aus irgeneinem obskurem Grund willst, kannst du
den Wiederholpunkt auch noch von woanders anspringen. Das kann schon
Sinn machen wenn du es mit Bitmaps zu tun hast, die u.U. alignment
erfordern, u.U. aber auch nicht. Mit FOR musst du dann den Code nochmal
wiederholen.
Ja klar mit zu vielen GOTOs versaust du dein Programm (zumindest den
Quelltext).
Yalu X. schrieb:> Wenn die Methoden aber so kurz sind, muss man verschachtelte Schleifen> auf mehrere Methoden verteilen (das hast du weiter oben wahrscheinlich> auch so gemeint).>> Wie kannst du dann aber von der innersten Schleife (die sich in einer> Methode befindet) aus die äußerste (in einer anderen Methode) verlassen?> Das geht doch nur dadurch, dass jede Methode ein entsprechendes Flag> zurückgibt, das in der Schleife der aufrufenden Methode abgefragt und> ggf. weitergereicht wird, und das über mehrere Ebenen.>> Das wird doch beliebig kompliziert und trägt sicher nicht zur besseren> Lesbarkeit des Programms bei. Oder wie würdest du bspw. folgende Methode> in drei Methoden mit jeweils einer Schleife aufdröseln?>>
1
>intloop(){
2
>intx=0,y=0,z=0;
3
>
4
>loopz:
5
>for(z=0;z<10;z++){
6
>for(y=0;y<10;y++){
7
>for(x=0;x<10;x++){
8
>if(condition(x,y,z))
9
>breakloopz;
10
>}
11
>}
12
>}
13
>returnx+y+z;
14
>}
15
>
Das ist ein Beispiel, bei dem es wohl besser waere es nicht in einzelne
Methoden aufzuteilen.
Muss aber ehrlich dazu sagen, dass ich dieses Konstrukt in 14 Jahren als
Hauptberuflicher Java Entwickler noch nie brauchte.
Mit ist schon klar dass es wohl immer Ausnahmen geben wird, deswegen
sehe ich auhc kein Problem mit einer Regel die sagt: "break labels
sollten vermieden werden", weil es eben in den meisten Faellen wahr ist.
Ein aus meiner Sicht wichtiger Aspekt ist (neben der in einigen oben
beschriebenen Fällen der besseren Lesbarkeit und klareren Struktur) goto
erlaubt, kritischen Code sehr kompakt und sehr effizient zu schreiben
bzw. zu kompilieren. Dabei geht es z.B. darum, dass der resultierende
Code möglichst kurz ist (Cache footprint) und der wahrscheinlichste
Codepfad sehr geradlinig ohne Sprünge abläuft (Prefetch queue / branch
prediction). Ein beliebter Trick ist dabei eben, in eine geradlinige
(Assebler-) Codesequenz an beliebiger Stelle mit Goto einspringen zu
können.
Dafür ist goto ein gutes Hilfsmittel, aber dafür gibt es noch andere
Tricks (likely/unlikely bedingungen) und andere "angeblich" sehr böse
sachen (switch/case fallthrough)
Peter II schrieb:> bool doExit = false;> for( x ... ) {> for( y ... ) {> if ( ... ) {> doExit = true;> break;> }> if ( doExit )> break;> }
je nachdem wie die schleifen aufgebaut sind (z.b. eine matrix
durchlaufen oder sowas in der richtung) könnte man das ganze auch
dadurch abbrechen, dass man anstatt doExit = true die einzelnen
abbruchbedingungen der schleifen quasi erzwingt.
sowas in der art
1
for(uint8_t x = 0; x < 128; x++) {
2
for(uint8_t y = 0; y < 128; <++) {
3
if(something) {
4
x = UINT8_MAX;
5
y = UINT8_MAX;
6
break;
7
}
8
}
9
}
bitte nicht auf die konstanten festnageln - es geht ja nur ums
prinzip...
Mladen G. schrieb:> Das ist ein Beispiel, bei dem es wohl besser waere es nicht in einzelne> Methoden aufzuteilen.>> Muss aber ehrlich dazu sagen, dass ich dieses Konstrukt in 14 Jahren als> Hauptberuflicher Java Entwickler noch nie brauchte.
Ein ganz ähnliches Konstrukt ergibt sich, wenn man bspw. in einem
mehrdimensionalen Array ein Element mit einer bestimmten Eigenschaft
suchen möchte. So etwas läuft einem doch öfter über den Weg.
Manche Programmiersprachen bzw. Bibliotheken bieten aber auch die
Möglichkeit, verschachtelte Schleifen wie die obigen eben zu büglen, so
dass nur noch eine einzelne Schleife übrig bleibt, die mit einem
gewöhnlichen break verlassen werden kann. In python sähe das bspw. so
aus:
1
from itertools import product
2
3
def loop():
4
for z, y, x in product(range(10), range(10), range(10)):
5
if condition(x, y, z):
6
break
7
return x + y + z
Das Stichwort dazu lautet "Iteratoren", die es m.W. in ähnlicher Form
auch in der Java-Bibliothek gibt. Vielleicht kann man dort mit den
leidigen verschachtelten Schleifen ähnlich verfahren wie in Python.
Will man auch noch auf den break verzichten, der ja immer noch ein
Bisschen Goto-Charakter hat, kann man das Ganze in Python auch als
Generator-Comprehension schreiben:
1
def loop():
2
return next((x+y+z) for z in range(10) for y in range(10) for x in range(10) if condition(x, y, z))
Diese Schreibweise hat Python übrigns von Haskell geerbt, wo das Genze
noch etwas kompakter geschrieben wird:
1
loop = head [x+y+z | z<-[0..9], y<-[0..9], x<-[0..9], condition x y z]
Da in Haskell die Liste ein Spezialfall einer Monade ist, kann man die
List-Comprehension (der Teil nach "head") auch im Monadenstil schreiben:
1
loop = head $ do
2
z <- [0..9]
3
y <- [0..9]
4
x <- [0..9]
5
guard (condition x y z)
6
return (x + y + z)
Das sieht zwar nicht arg viel anders aus als das vorherige Beispiel,
aber die mit <-[0..9] endenen Zeilen sind jetzt getrennte Actions. Da
sie alle gleich aussehen, liegt es nahe, sie in eine Art Schleife zu
packen:
1
loop = head $ do
2
[x, y, z] <- replicateM 3 [0..9]
3
guard (condition x y z)
4
return (x + y + z)
Die Zahl der verschachtelten Schleifen im ursprünglichen Java-Code
taucht hier in der zweiten Zeile als numerische Konstante (3) auf. Man
kann sie durch eine Variable n ersetzen, wenn man gleichzeitig dafür
sorgt, dass die Funktion condition und die Summe in der letzten Zeile
mit einer variablen Zahl von Operanden klar kommen. Das geschieht am
einfachsten dadurch, dass man die Variablen x, y und z zusammen durch
eine Liste (xs) mit n Elementen ersetzt. Das sieht dann so aus:
1
loop n = head $ do
2
xs <- replicateM n [0..9]
3
guard (condition xs)
4
return (sum xs)
Dieser Code tut tatsächlich im Wesentlichen das Gleiche wie der
ursprüngliche Java-Code:
1
intloop(){
2
intx=0,y=0,z=0;
3
4
loopz:
5
for(z=0;z<10;z++){
6
for(y=0;y<10;y++){
7
for(x=0;x<10;x++){
8
if(condition(x,y,z))
9
breakloopz;
10
}
11
}
12
}
13
returnx+y+z;
14
}
Er ist aber kürzer und die Anzahl der verschachtelten impliziten
Schleifen ist variabel und kann über das Funktionsargument n gesteuert
werden. Der break im Java-Programm verbirgt sich implizit in der
head-Funktion, die das erste Element der durch den do-Block erzeugten
Liste zurückgibt. Da die restlichen Elemente nirgend benutzt werden,
werden sie auch nicht berechnet, was einem Abbruch der verschachtelten
Suchschleife gleichkommt.
Nicht nur der break, sondern auch die expliziten For-Schleifen sind
verschwunden. Sie sind irgendwo in die Tiefen der Laufzeitbibliothek
verlagert worden, ähnlich wie die impliziten Gotos der For-Schleifen in
Java in den Maschinencode der VM verlagert werden, wo sie niemanden
stören.
Diese Beispiel zeigt, dass man explizite Sprünge (Gotos) schrittweise
durch weniger explizite Sprünge ersetzen kann, bis am Schluss auf
Quellcodeebene nur noch linearer Code übrig bleibt. Linearer Code ist
das genaue Gegenteil vom Spaghetti-Code und deswegen nur minimal
fehleranfällig.
Bei der schrittweisen Umwandlung wurde sogar ohne großen Zusatzaufwand
noch Funktionalität hinzugewonnen (Iteration von n statt nur 3
Variablen), die sich mit den klassischen aus der strukturierten
Programmierung bekannten Konstrukten nur schwer realisieren lässt.
Trotzdem hat es sich ein Verückter nicht nehmen lassen, auch für Haskell
ein Goto nachzurüsten:
http://hackage.haskell.org/package/GotoT-transformers-1.0/docs/Control-Monad-Trans-Goto.html
Ich vermute aber, dass es sich hier primär um einen Fall von "Und es
geht doch" ohne allzu viel praktischen Nutzen handelt. Auch für Python
hat jemand so ein Proof-of-Concept-Goto entwickelt. Es spricht immerhin
für die Mächtigkeit einer Programmiersprache, wenn so etwas mit
gewöhnlichen Sprachmitteln ohne Manipulation des Compilers und ohne die
Verwendung eines speziell dafür angefertigten Präprozessors möglich ist.
Martin schrieb:> Es gab früher mal so etwas wie ein Struktogramm
Die Nassi-Shneiderman-Diagramme haben aus gutem Grund ihren Eingang in
die DIN (DIN 66261) gefunden.
Versuch da mal ein "goto" rein zu strukturieren. Ein "Goto" machte fast
jede Programmstruktur unberechenbar (Ausnahme vielleicht das "Exit").
Hallo Konrad,
Konrad S. schrieb:> Ginge das auch mit einem range von 10000?
Jaein.
> Oder anders gefragt: Muss das product im Speicher Platz haben?
Jaein.
Wenn man es wie Yalu mit der range()-Funktion macht, dann müssen die
drei von range zurückgegebenen Listen Platz im Speicher haben. product()
würde dann aber trotzdem jeweils über die Listen iterieren und einen
Iterator bereitstellen, der jeweils nur das akutelle Ergebnis berechnet,
und dann braucht auch nur das aktuelle Ergebnis Platz im Speicher.
Dem geneigten Pythonista steht deswegen neben der Funktion range() auch
noch die Funktion xrange() zur Verfügung, die nicht wie range() eine
Liste, sondern einen Iterator zurückgibt. Wenn man also anstelle von
Yalus Code
1
product(xrange(10), xrange(10), xrange(10))
benutzt, dann wird bei jedem Aufruf von product() immer nur das
aktuelle Ergebnis berechnet und im Speicher allokiert.
HTH,
Karl
Ja klar nun starte ich Eclipse oder MPLABX auf meinem 1GB netbook, was
sind schon 1GB, warte 3 Minuten, die Eingabe ist blockiert, irgenwann
gehts dann sogar, umschalten, naja, warte 20 Sekunden.
desgleichen mit Skype und Web hotmail. Die brauchen FLASH und
Silverlight um dir Werbung zu zeigen die du immer wieder aus versehen
anclickst.
Ist dass nicht so eine Art GOTO gegen deinen Willen? Du clickst die
Scroll Leiste, aber der Browser macht ein neues Fenster auf mit Skype
Werbungsseiten die nutzlos sind und die du schon 20 mal gesehen hast.
Aber sei dir gewiss, der Source code enthaelt garantiert kein einziges
GOTO.
Also ersteht es irgenwie wieder auf? Ach ja, liegt wohl daran das 1GB
zuwenig Speicher ist.
Mit GOTO ist es halt so eine Sache, es ist ein linearer Ablauf, du
befindest dich bei Punkt A, und gehst zu Punkt B aus irgeneinem Grund.
Im realen Leben kannst du das nicht vermeiden, oder wie ist da der
objektorientierte Ansatz?
Anfänger sollten möglichst viel goto verwenden. Denen sollte man die
Schleifen gar nicht beibringen.
Wenn viele goto's verwendet werden, dann habe ich einen sicheren
Arbeitsplatz bis zur Rente.
Ansonsten bin ich die letzten 25 Jahre goto-frei ausgekommen, und ich
habe wirklich viel Code geschrieben.
Naja wie schon oben beschrieben gibt es Anwendungen wo Goto sinnvoll
ist.
Für so richtig eleganten Code braucht man aber natürlich das Comefrom,
das in vielen Sprachen drin ist.
Daniel F. schrieb:> je nachdem wie die schleifen aufgebaut sind (z.b. eine matrix> durchlaufen oder sowas in der richtung) könnte man das ganze auch> dadurch abbrechen, dass man anstatt doExit = true die einzelnen> abbruchbedingungen der schleifen quasi erzwingt.
Das ist aber ein fieser Trick, den ich nicht nutzen würde, nur um goto
vermeiden zu können. Man spart sich zwar die unschöne Statusvariable,
die man auf jeder Ebene nochmal extra abfragen muss, aber elegant ist
das auch nicht. Genau das ist die Sache: Man kann goto immer irgendwie
vermeiden, aber das Ziel sollte nicht sein, goto um jeden Preis
wegzubekommen, sondern guten Code zu schreiben. Und manchmal (wenn auch
selten) ist der mit goto besser.
Mike schrieb:> Ein "Goto" machte fast jede Programmstruktur unberechenbar (Ausnahme> vielleicht das "Exit").
Was soll denn daran unberechnenbar sein? goto springt ja nicht
willkürlich irgendwo hin.
@Takao K.:
Bekanntlich ist goto ja auch für die globale Erwärmung, die Vogelgrippe
und natürlich dafür, daß in China so viele Reissäcke umfallen,
verantwortlich.
Konrad S. schrieb:> @Yalu> Ginge das auch mit einem range von 10000? Oder anders gefragt: Muss das> product im Speicher Platz haben?
Karl hat schon fast alles dazu geschrieben. Um über alle Tripel in
product(range(10000), range(10000), range(10000)) zu iterieren, braucht
man also Speicherplatz für 3·10000=30000 Elemente.
Karl Käfer schrieb:> Wenn man also anstelle von Yalus Codeproduct>> (xrange(10), xrange(10), xrange(10))>> benutzt, dann wird bei jedem Aufruf von product() immer nur das> aktuelle Ergebnis berechnet und im Speicher allokiert.
Das stimmt (leider) nicht ganz.
Zum einen ist in Python 3 range das, was in Python 2 xrange war. Es
liefert also keine Liste, sondern ein Objekt, aus dem sich ein Iterable
gewinnen lässt, das unabhängig vom Argument nur wenig Speicher belegt.
xrange ist deswegen in Python 3 obsolet. Wenn man die range-Funktion von
Python 2 benötigt, kann man diese mit list(range(n)) nachbilden.
Zum anderen (und jetzt kommt das "leider") erzeugt die product-Funktion
aus jedem der Argumente erst einmal eine Liste. Zumindest beim ersten
Argument ist das für mich nicht ganz nachvollziehbar, da dieses Iterable
nur ein einziges Mal durchlaufen wird und es deswegen keine Vorteile
bringt, die einzelnen Werte zu speichern.
Deswegen darf auch keines der Argumente unbegrenzt iterieren, obwohl das
beim ersten Argument (das der äußersten Schleife entspricht) schon
manchmal wünschenswert wäre und in anderen Sprachen, wie z.B. Haskell
auch tatsächlich möglich ist. Dort liefert
Eine Produktfunktion für Python, deren Speicherbedarf unabhängig von der
Größe der übergebenen Iterables ist und die damit auch mit endlosen
Iterables zurechtkommt, könnte in ihrer einfachsten Form so aussehen:
1
def product2(*iterables):
2
if iterables:
3
for x in iterables[0]:
4
for t in product2(*iterables[1:]):
5
yield (x,) + t
6
else:
7
yield ()
Sie ist aber im Vergleich zum Original langsamer, da dieses nicht in
Python, sondern in C programmiert ist.
Moin,
sind ja ganz schön gruselige Goto Konstrukte, die hier aufschlagen.
Jürgen S. schrieb:> void nestedFor()> {> for (int i=0;i<255;i++)> {> for (int j=0;j<255;j++)> {> for (int k=0;k<255;k++)> {> if (i==100 && j==20 && k==2) goto ende;> }> }> }> ende: ;> }
Wozu brauchts an der Stelle ein Goto?
1
voidnestedFor()
2
{
3
for(inti=0;i<255;i++)
4
{
5
for(intj=0;j<255;j++)
6
{
7
for(intk=0;k<255;k++)
8
{
9
if(i==100&&j==20&&k==2)return;
10
}
11
}
12
}
13
}
schein mir an der Stelle übersichtlicher zu sein, da ich nicht noch
extra einem Goto folgen muss um festzustellen, dass aus der Funktion
gesprungen wird. Bei der zweiten Variante ist es sofort ersichtlich.
Walter Tarpan schrieb:> // anderer Code hier> if (status!=0)> goto error;>> return status; // = 0>> error:> I2C_reset(EEPROM_I2C_DEVICE);> return status;> }
1
if(status!=0)
2
{
3
I2C_reset(EEPROM_I2C_DEVICE);
4
}
5
returnstatus;
Wäre an der Stelle auch zu übersichtlich und lesbar (am Rande, status
ist als Name schlecht gewählt :)
W.S. schrieb:> int main (void)> { initialisiere();> immerzu:> machedies();> machedas();> machenochwas();> goto immerzu;> }>> Wenn man das mit einem while(1) formuliert, dann meckern manche> Compiler, daß main ja gar kein Ergebnis zurückgibt - zu Recht (im> Prinzip). Also findet man bei solchen Strategen nach der Endlosschleife> noch ein return 0 oder so, um den schieren Formalismus zu befriedigen.
Sollte der Compiler nicht bei dir auch meckern? du hast ja immerhin
genau so eine Funktion, die einen Rückgabewert erwartet, der du keinen
lieferst.
An der Stelle ist es aber warscheinlich egal, welches Konstrukt man
verwendet ob while(true) oder for(;;) oder goto. Gegen Goto spricht nur
die Andersartigkeit der Klammersetzung. Diese ist ja beim Goto
einzigartig. So ein Konstrukt gibt es ja in C sonst nicht.
Ich meine:
1
intmain()
2
{
3
while(true)
4
{
5
// fancy stuff here
6
}
7
}
8
9
gegenüber
10
11
intmain()
12
{
13
label:
14
// fancy stuff here
15
gotolabel;
16
}
Zweiteres ist man normalerweise nicht gewohnt und muss genauer hin
schauen. Das stört den Lesefluss doch erheblich. Besonders wenn man Goto
normalerweise nicht benutzt.
Goto wird leider überwiegend verwendet um schlechtes Design zu
kaschieren. Wenn man mein unbedingt ein Goto zu brauchen, sollte man
lieber erst mal ein Refactoring anstoßen und erst wenn das nicht hilft
Goto einsetzen.
Grüße,
nicht"Gast" schrieb:> if (status != 0)> {> I2C_reset(EEPROM_I2C_DEVICE);> }> return status;> Wäre an der Stelle auch zu übersichtlich und lesbar (am Rande, status> ist als Name schlecht gewählt :)
genau sowas (aber in einer etwas anderen Form) kann lästige auswirkungen
haben.
nehmen wir folgende Code-sequenz, erstmal in der "optimalen" Variante
ohne Fehlerbehandlung:
1
tu_was();
2
tu_nochwas();
3
du_nochwas_drittes();
4
aufraeumen();
schäön sequentiell, keine Sprünge die dir die Pipeline flushen, da kann
er wie auf dem geraden Autobahnstück mit 300 Sachen drüberbrettern.
nun kommt die lästige Fehlerbehandlung:
1
tu_was();
2
if(!ok){
3
cleanup();
4
return-E_BE_HAPPY;
5
}
6
tu_nochwas();
7
if(!ok){
8
cleanup();
9
return-E_DONT_WORRY;
10
}
11
tu_nochwas_drittes();
12
if(!ok){
13
cleanup();
14
return-E_DONT_KNOW;
15
}
16
cleanup();
17
return0;
ist schon überhaupt nicht mehr geradlinig, dauernde Sprünge (die man
aber mit likely() etwas entschärfen kann), aber, und das ist den
wenigsten bewusst: cleanup() wird schlimmstenfalls vom compiler viermal
inline eingesetzt, damit vervierfacht sich der ICache-Footprint! (ncoh
schlimmer und unübersichtlicher wirds wenn ich nicht eine cleanup()
habe, sondern drei oder mehr verschiedene die je nachdem wie weit er
gekommen ist, aufgerufen werden müssen oder auch nicht)
natürlich kann man das auch anders schreiben:
1
tu_was();
2
if(ok){
3
tu_nochwas();
4
if(ok){
5
tu_nochwas_drittes();
6
}
7
}
8
cleanup();
Das inline-problem ist zwar weg, aber übersichtlicher ists auch nciht,
speziell wenn ich nicht drei sondern mehr solcher if()s habe bin ich
irgendwann am rechten Bildschirmrand... verschiedene Cleanups sind auch
schwer unterzubringen, und sequentiell ists auch nicht gerade...
Was ist so schlimm daran, die ursprüngliche "Autobahn" zu verwenden, und
dort wo notwendig ein "goto out" einzubauen?
nicht"Gast" schrieb:> Goto wird leider überwiegend verwendet um schlechtes Design zu> kaschieren.
Na na na na....
Yalu X. schrieb:> Ein ganz ähnliches Konstrukt ergibt sich, wenn man bspw. in einem> mehrdimensionalen Array ein Element mit einer bestimmten Eigenschaft> suchen möchte. So etwas läuft einem doch öfter über den Weg.
Ich sag mal so: Ich weiss das andere das mal brauchten, selber ging mir
das noch nie so.
Mehrdimensionale Arrays an sich sind etwas mit dem ich sehr selten
direkt in Kontakt komme als Java Entwickler, zumnidest in meinem
Bereich, das wird wohl auch der Gund sein warum ich ein break label noch
nie brauchte.
Michael Reinelt schrieb:> nun kommt die lästige Fehlerbehandlung:tu_was();> if (!ok) {> cleanup();> return -E_BE_HAPPY;> }> tu_nochwas();> if (!ok) {> cleanup();> return -E_DONT_WORRY;> }> tu_nochwas_drittes();> if (!ok) {> cleanup();> return -E_DONT_KNOW;> }> cleanup();> return 0;
Schreib das doch mal mit gotos und zeig mir, dass das lesbarer ist.
Michael Reinelt schrieb:> tu_was();> if(ok) {> tu_nochwas();> if (ok) {> tu_nochwas_drittes();> }> }> cleanup();
Mein Gegenvorschlag:
1
if(!tuwas()
2
||!tuwasanderes()
3
||!tuwasdrittes())
4
){
5
cleanup();
6
returnging_nicht;
7
}
8
9
cleanup();
10
returnging;
wenn man bei tuwas() und co true für ging und false für ging nicht
zurückgibt, ist es ein wenig Übersichtlicher. Allerdings war das
Fehlerhandling ja schon immer der Hinkefuß von C. In C++ gibts ja
Gottseidank Exceptions für so was.
Für die Nörgler: wenn tuwas() 0 zurückgibt, werden die weiteren
Funktionen nicht ausgeführt, da das Ergebnis der logischen Verknüpfung
fest steht.
Michael Reinelt schrieb:> nicht"Gast" schrieb:>> Goto wird leider überwiegend verwendet um schlechtes Design zu>> kaschieren.>> Na na na na....
Hab nicht geschrieben immer :). Die Beispiele hier im Thread bestätigen
mich leider ein wenig.
Gerade im Microcontrollerbereich ist die ROM-Größe oft limitiert.
Wenn es dann zu Ketten kommt, wie im Beispiel von Michael beschrieben,
sammelt sich durch Fehlerhandling schnell einiger Code an.
Wer schon in schwierigen Situation war, am Ende des Projekts zu wenig
ROM-Platz zu haben,kennt die Aktionen, die dann gestartet werden um das
Projekt zu retten. Strukturierte Programmierung ohne Gotos bleibt dann
mit als erstes auf der Strecke, wenn bereits zuvor vernünftig
(platzsparend) progranmmiert wurde.
Nachteil ist natürlich der höhere Aufwand durch schwierigeres Testen.
Wenn allerdings der Compiler (bei uns Keil-C) in einer höheren
Optimierungsstufe betrieben wird, werden solche Aktionen teilweise auch
vom Compiler eingefügt.
Aus meiner Sicht ist an derartigen Anpassungen nichts zu meckern, bei
uns in der Firma ist es nur leider so, dass dieser Code unverändert
immer wieder übernommen wird, auch wenn es keine Platzprobleme mehr
gibt. Hier sollte man sich Gedanken machen, wie dies Optimierungen
später einfach lokalisiert und rückgebaut werden können.
nicht"Gast" schrieb:> Schreib das doch mal mit gotos und zeig mir, dass das lesbarer ist.
fändest du weiter oben schon, aber bitte:
1
tu_was();
2
if(!ok)gotoout;
3
tu_nochwas();
4
if(!ok)gotoout;
5
du_nochwas_drittes();
6
out:
7
aufraeumen();
nicht"Gast" schrieb:> if ( ! tuwas()> || ! tuwasanderes()> || ! tuwasdrittes())> ){> cleanup();
Einverstanden, geht aber nciht immer: tuwas() und co. sind in meinem
Fall nicht als reine Funktionsaufrufe zu verstehen, sondern als
(durchaus längere) Codesequenzen, die nicht so einfach einen Return-Wert
haben
also eher so:
Michael Reinelt schrieb:> fändest du weiter oben schon, aber bitte:> tu_was();> if (!ok) goto out;> tu_nochwas();> if (!ok) goto out;> du_nochwas_drittes();> out:> aufraeumen();
Wie wärs damit:
1
tu_was();
2
if (ok) then {
3
tu_nochwas();
4
if (ok) then {
5
du_nochwas_drittes();
6
}
7
}
8
aufraeumen();
Ich hoffe ich habs richtig geschrieben, im Moment mache ich Pascal, aber
man sieht wohl was gemeint ist.
Georg
Michael Reinelt schrieb:> ..> if (!a) goto out;> tu_nochwas();> mach_was_mit_b();> x=a+b-27;> hallo=x(314)&0xl337;> if (hallo==0) goto out;> du_nochwas_drittes();> x();> y();> z();> out:> aufraeumen();
Alter, das ist ganz übler Basic-Spaghetti-Code. Besser ist hier eine if
else Sequenz.
Michael Reinelt schrieb:> nicht"Gast" schrieb:>> if (){>> cleanup();>> return ging_nicht;>> }>>>> cleanup();>> return ging;>> 2* Cleanup() ge-inlined :-(
:) kleine Umänderung
1
boolallesGut=false;
2
if(aktionginggut)
3
{
4
allesGut=true;
5
}
6
cleanup();
7
returnallesGut;
würd ich echt lieber so schreiben, als Gotos einzubauen
Michael Reinelt schrieb:> also eher so:bereite_vor();> initialisiere();> berechne();> xxx();> hole_a()> if (!a) goto out;> tu_nochwas();> mach_was_mit_b();> x=a+b-27;> hallo=x(314)&0xl337;> if (hallo==0) goto out;> du_nochwas_drittes();> x();> y();> z();> out:> aufraeumen();
Massives Refactoring erforderlich :) Leider ist das nicht wirklich ein
Code aus dem wahren Leben, sondern zusammengestöpselter wirrer Kram.
Dass man da nichts machen kann ist klar.
Nachtrag: Das ist einer der besten Beispiele für Spagetticode und eines
der schlechtesten Beispiele für guten Code, die ich je gesehen hab :)
Thomas schrieb:> Gerade im Microcontrollerbereich ist die ROM-Größe oft limitiert.> Wenn es dann zu Ketten kommt, wie im Beispiel von Michael beschrieben,> sammelt sich durch Fehlerhandling schnell einiger Code an.>> Wer schon in schwierigen Situation war, am Ende des Projekts zu wenig> ROM-Platz zu haben,kennt die Aktionen, die dann gestartet werden um das> Projekt zu retten. Strukturierte Programmierung ohne Gotos bleibt dann> mit als erstes auf der Strecke, wenn bereits zuvor vernünftig> (platzsparend) progranmmiert wurde.>> Nachteil ist natürlich der höhere Aufwand durch schwierigeres Testen.> Wenn allerdings der Compiler (bei uns Keil-C) in einer höheren> Optimierungsstufe betrieben wird, werden solche Aktionen teilweise auch> vom Compiler eingefügt.>> Aus meiner Sicht ist an derartigen Anpassungen nichts zu meckern, bei> uns in der Firma ist es nur leider so, dass dieser Code unverändert> immer wieder übernommen wird, auch wenn es keine Platzprobleme mehr> gibt. Hier sollte man sich Gedanken machen, wie dies Optimierungen> später einfach lokalisiert und rückgebaut werden können.
Ja, leider kann ich das so Unterschreiben. Manchmal ist es einfach so,
dass man nicht den nächst größeren oder gar einen anderen Controller
nehmen kann. Besonders, wenn einem fertigen Gerät immer weitere Features
hinzugefügt werden. Da die Hardware zu ändern kann mal schnell teuer
werden (FCC, Etsi Neuzulassungen). Das dass aber die Ausnahme sein muss
und nicht der Standard, schreibest du ja schon selber.
Grüße,
Michael Reinelt schrieb:> 2* Cleanup() ge-inlined :-(
Du weist aber schon, dass deine Vorlage deutlich mehr Cleanups
beinhaltet
Michael Reinelt schrieb:> nun kommt die lästige Fehlerbehandlung:tu_was();> if (!ok) {> cleanup();> return -E_BE_HAPPY;> }> tu_nochwas();> if (!ok) {> cleanup();> return -E_DONT_WORRY;> }> tu_nochwas_drittes();> if (!ok) {> cleanup();> return -E_DONT_KNOW;> }> cleanup();> return 0;
nicht"Gast" schrieb:> Leider ist das nicht wirklich ein> Code aus dem wahren Leben, sondern zusammengestöpselter wirrer Kram.
Da hast du recht. ich hatte aber gehofft do könntest etwas besser
abstrahieren...
nicht ganz so wirr, aber auch aus dem Zusammenhang gerissen:
[c]
irqe.dest_id = entry->fields.dest_id;
irqe.vector = entry->fields.vector;
irqe.dest_mode = entry->fields.dest_mode;
irqe.trig_mode = entry->fields.trig_mode;
irqe.delivery_mode = entry->fields.delivery_mode << 8;
irqe.level = 1;
irqe.shorthand = 0;
if (irqe.trig_mode != IOAPIC_EDGE_TRIG)
goto out;
spin_lock(&ioapic->lock);
__kvm_ioapic_update_eoi(vcpu, ioapic, vector, trigger_mode);
spin_unlock(&ioapic->lock);
if (!vector)
goto out;
kvm->arch.vioapic = ioapic;
kvm_ioapic_reset(ioapic);
kvm_iodevice_init(&ioapic->dev, &ioapic_mmio_ops);
ioapic->kvm = kvm;
mutex_lock(&kvm->slots_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS,
pic->base_address,
IOAPIC_MEM_LENGTH, &ioapic->dev);
mutex_unlock(&kvm->slots_lock);
if (ret < 0)
goto out;
[c]
Was ich sagen will: Du hast eine nicht ganz so kurze Sequenz fon
Operationen (Zuweisungen, Funktionsaufrufe, berechnungen) die im
normalfall einfach linear von vorne bis hinten durchlaufen werden. Die
Blöcke kannst du auch nciht wie du oben vorgeschlagen hast, mit ||
zusammenhängen. An bestimmten stellen kann was schief gehen (ist aber
die Ausnahme), und da bietet sich ein goto als "Ausstieg" an.
Für mich (und ich bin hier nicht alleine) viel besser lesbar als tiefe
if() verschachtelungen.
nicht"Gast" schrieb:> Du weist aber schon, dass deine Vorlage deutlich mehr Cleanups> beinhaltet
Und wenn du mein kommentar dazu gelesen hättest, wüsstest du dass ich
das absichtlich gemacht habe, um zu zeigen welche nachteile diese
Konstellation hat :-(
Michael Reinelt schrieb:> Und wenn du mein kommentar dazu gelesen hättest, wüsstest du dass ich> das absichtlich gemacht habe, um zu zeigen welche nachteile diese> Konstellation hat :-(
ja, da hast du recht. Bei der ersten Antwort hatte ich es noch gelesen.
Bei der zweiten .. na ja, shame on me :)
Michael Reinelt schrieb:> Da hast du recht. ich hatte aber gehofft do könntest etwas besser> abstrahieren...
Also DAS zu abstrahieren ist echt ne Herausforderung. Da scheitern
sicher auch Leute mit mehr Grips als ich dann.
Dein Code aus dem zweiten Post ist auch nicht der Renner.
Du scheinst zu viele Sachen in deine Funktion zu stecken. Ohne den Rest
zu kennen: Teil das Ganze in kleinere Blöcke auf und du wirst sehen,
dass das Ergebnis deutlich lesbarer als die Gotos sind.
Bsp: statt dem ersten Block
1
if(entry->fields.trig_mode==IOAPIC_EDGE_TRIG)
2
{
3
// copiere das struct in einer funktion
4
// tu den anderen kram, für den das struct gar nicht benötigt wird
5
}
Wenn du mehrere trigger modes hast, pack das lieber in ein switch, dass
dann Funktionen aufruft. Das ist um Welten übersichtlicher, als das, was
du gepostet hast.
Grüße,
Ich möchte hier noch eine andere Möglichkeit zur Diskussion stellen,
Initialisierungssequenzen mit Ausstiegspunkten ohne explizite Gotos und
verschachtelte Ifs in C zu realisieren. In folgendem Beispiel wird
angenommen, dass die drei Init-Funktionen im Fehlerfall einen von 0
verschiedenen Fehlercode liefern. Jeder Fail landet automatisch an der
dem BLOCK_END folgenden Anweisung.
Also das for-"Problem" lässt sich auf mehre Weisen lösen:
1. jede Schleife kann mehrere Abbruchbedingungen haben:
1
bool error = false;
2
3
for(int i = 0; i < 100 && !error;i++)
4
for(int j = 0; j < 100 && !error;j++)
5
for(int k = 0; k < 100 && !error;k++)
6
if(irgendwas)
7
error = true;
Wenn man nach dem Abbruch wieder vor die Schleifen springen muss:
1
bool error = false;
2
3
do
4
{
5
for(int i = 0; i < 100 && !error;i++)
6
for(int j = 0; j < 100 && !error;j++)
7
for(int k = 0; k < 100 && !error;k++)
8
if(irgendwas)
9
error = true;
10
}
11
while(error);
1. Ja, manchmal habe ich das Gefühl, nur 20 Menschen auf diesem Planeten
kennen noch eine do-while-Schleife...
2. Bitte im echten Code jede Schleife mit {} versehen, sonst sieht man
bei solchem Code auch ohne goto nicht mehr durch.
Da ich persönlich for-Schleifen mit mehreren Bedienungen nicht mag, da
man damit einfach nicht rechnet, würde ich die drei for-Schleifen in
while Schleifen verwandeln
1
int i = 0;
2
while(i < 100 && error)
3
{
4
//Zweite Schleife hier hin...
5
6
i++;
7
}
Um nochmal zum Originalthema zurückzukommen. Ich kenne auch sinnvolle
goto's. Ein Beispiel ist eine Art try catch nachzubauen:
1
void function()
2
{
3
if(irengendwasKritisches() == false)
4
goto error:
5
if(irengendwasKritisches2() == false)
6
goto error:
7
if(irengendwasKritisches3() == false)
8
goto error:
9
10
goto end;
11
12
error:
13
14
//Fehlerbehandlung, die immer gleich ist, sonst aber in jede if-Anweisung rein müsste.
> //Fehlerbehandlung, die immer gleich ist, sonst aber in jede
16
> if-Anweisung rein müsste.
17
>
18
>
19
> end:
20
> }
21
>
22
>
>> Schönes Wochenende
Die Variante mit den For-Schleifen finde ich toll, das hab ich so noch
nie gesehen.
Was das letzte Beispiel angeht: Da kannst du auch gleich eine Funktion
MyErrorHandler(ERR_TYPE) aufrufen und hast sogar eine Unterscheidung
dabei. Mit nahezu gleichem Codeaufwand wie beim GOTO.
Daniel
Yalu X. schrieb:> #define BLOCK_BEGIN do {> #define BLOCK_END } while(0)> #define FAIL_IF(x) if(x) break
Ich bin versucht zu sagen "real programmers don't use Pascal", aber ich
verkneifs mir mal ;-)
Grundsätzlich natürlich eine Möglichkeit, nur: warum so viel Aufwand,
nur um ein goto zu tarnen?
nicht"Gast" schrieb:> Dein Code aus dem zweiten Post ist auch nicht der Renner.
ist nicht mein Code :-)
> Du scheinst zu viele Sachen in deine Funktion zu stecken. Ohne den Rest> zu kennen: Teil das Ganze in kleinere Blöcke auf und du wirst sehen,> dass das Ergebnis deutlich lesbarer als die Gotos sind.
nein, werde ich nicht. und das ist nicht mal unbedingt meine Meinung.
Der Code wurde recht wahllos aus den aktuellen Linux-Sourcen rauskopiert
(irgendwas im KVM-Bereich)
Ohne jetzt einen Glaubenskrieg anzetteln zu wollen: Ich hab in meinem
Leben sehr viel Code gelesen, viel guten Code, ein paar echte "perlen",
und leider bei weitem zu viel schlechten Code. Für mich zählt der
Linux-Kernel-Source auf jeden Fall zu den positiven Beispielen. Der Code
ist gut, effizient, leistungsfähig, und durch viele Hände gegangen, und
wurde von sehr vielen Augen gelesen. Goto ist dort alles andere als
verboten, wird aber mit Bedacht eingesetzt.
Du kannst ja mal versuchen, dort zu predigen, wie du das alles viel
besser machen würdest. Gib mir aber bitte vorher Bescheid, den Thread
möchte ich nicht verpassen :-)
Ums nochmal auf den Punkt zu bringen: Goto hat absolut seine
Berechtigung in gewissen Fällen: kritischer Code, kleiner
(Cache-)Footprint, Rücksicht auf die Pipeline sind ein paar davon. Ein
guter Programmierer kennt das, weiß das zu schätzen und kann einschätzen
wann ein goto angebracht ist und wann nicht. Das reflexartige "goto ist
böse" deutet nur auf eins hin: Die Aussage kommt nicht von einem
guten, erfahrenen Programmierer.
nicht"Gast" schrieb:> Sollte der Compiler nicht bei dir auch meckern?
Nein. Er erkennt nämlich direkt zuvor den unbedingten Sprung und weiß
dann, daß das Ende unerreichbar ist.
Ich hab aber aus Klaus Kaisers Link ne Sache, die mir durchaus sinnvoll
erscheint, obwohl sie etwas störrisch aussieht:
...
if (Procedure_A()==fail) goto undo_A;
if (Procedure_B()==fail) goto undo_B;
if (Procedure_C()==fail) goto undo_C;
...
return OK;
undo_C:
Undo_Procedure_C();
undo_B:
Undo_Procedure_B();
undo_A:
Undo_Procedure_A();
return Failed;
De Knackpunkt ist hierbei das korrekte Zurückrollen von den Dingen, die
bereits angerichtet sind. Man versuche das mal stringenter auszudrücken.
W.S.
>Ums nochmal auf den Punkt zu bringen:
oder anders gesagt, reine Geschacksache, was 'besser' lesbar ist.
Und einem Compiler sollte es sowiso egal sein, welche 'switches' er
vorfindet.
Goto in ASM stiftet nur Verwirrung, weil es dort sinngemäss JMP heissen
müsste.
MCUA schrieb:> oder anders gesagt, reine Geschacksache, was 'besser' lesbar ist.W.S. schrieb:> Man versuche das mal stringenter auszudrücken.Jein
ich behaupte, goto macht den Code oft schlechter lesbar, (oft für
"eingeweihte" durchaus auch besser)
Es geht aber nicht nur um die Lesbarkeit, sondern meist darum, dem
Compiler zu erlauben, kompakten, effizienten und damit optimalen Code zu
erzeugen.
In manchen Bereichen (OS Kernel, embedded mit "engen" bedingungen) ist
das notwendig. ich weiss schon, "... the root of all evil", aber das
obige Konstrukt von W.S. ist es wert, es sich anzugewöhnen. Es ist
straight, sauber, optimal, schön.
(Leider wird man damit in der Ausbildung/Uni vermutlich Probleme
bekommen. Liegt aber IMHO daran, dass Ausbildner selten viel
Programmiererfahrung haben, eher Theoretiker sind. und in der Theorie
gibts ja keinen Unterschied zwischen Theorie und Praxis, aber in der
Praxis sehr wohl)
MCUA schrieb:> Goto in ASM stiftet nur Verwirrung, weil es dort sinngemäss JMP heissen> müsste
Wieso, es ist doch egal ob man zur Zieladresse hingeht oder springt...
Wenn sich der Hersteller für eines der beiden oder vllt. noch ein
anderes Mnemonic entschieden hat dann ist es ganz eindeutig was
GOTO/JMP für diesen Prozessortyp bedeutet.
Yalu X. schrieb:> Ich möchte hier noch eine andere Möglichkeit zur Diskussion stellen
Von Sprache neu definieren mittels Präprozessor halt ich gar nichts.
Warum nicht dein Konstrukt so lassen, aber mit do, if und break.
>Es geht aber nicht nur um die Lesbarkeit, sondern meist darum, dem>Compiler zu erlauben, kompakten, effizienten und damit optimalen Code zu>erzeugen.
Den Compiler hat das nicht zu jucken.
Ansonst bleibt es dabei, es ist Geschmacksache (sagt der Andere und
beisst in die Seife).
>Wieso, es ist doch egal ob man zur Zieladresse hingeht oder springt...
natürlich ist es wurscht, aber kein Mensch (ausser die von PIC)
verwenden für JMP Goto in ASM. Warum der Quatsch? (doch nicht um mit
'goto' ein Hochsprachenkonstrukt im ASM zu assoziiren) (allerd. ist bei
PIC (aus Urzeiten) syntakt. vieles anders).
MCUA schrieb:> Den Compiler hat das nicht zu jucken.
Leider sind wir noch nicht im Jahr 2525. Heute macht der Compiler noch
was du tippst, und nicht was du willst.
Michael Reinelt schrieb:> Der Code wurde recht wahllos aus den aktuellen Linux-Sourcen rauskopiert> (irgendwas im KVM-Bereich)
Da hast du mich aber erwischt. Trozdem bleibt das Stück Code scheiße. Es
fehlt einfach der Kontext um das beurteilen zu können. Wenn du die
gesamte Funktion gepostet hättest, würde das sicher anders aussehen,
aber so bleibt es unzusammenhängendes Geschwurbel.
nicht"Gast" schrieb:> Trozdem bleibt das Stück Code scheiße.
Watch your language!
> Es> fehlt einfach der Kontext um das beurteilen zu können. Wenn du die> gesamte Funktion gepostet hättest, würde das sicher anders aussehen,> aber so bleibt es unzusammenhängendes Geschwurbel.
www.kernel.org
Ich sprech dir jetzt einfach mal die Kompetenz ab, das zu beurteilen.
Vermutlich ist es besser du schreibst weiter deine tic-tac-toe programme
ohne goto's ...
<sorry>
MCUA schrieb:> natürlich ist es wurscht, aber kein Mensch (ausser die von PIC)> verwenden für JMP Goto in ASM. Warum der Quatsch? (doch nicht um mit> 'goto' ein Hochsprachenkonstrukt im ASM zu assoziiren) (allerd. ist bei> PIC (aus Urzeiten) syntakt. vieles anders).
Quatsch ist, sich darüber aufzuregen. Ich glaube nicht, dass alle
Hersteller verpflichtet sind, die Nomenklatur von Intel zu übernehmen.
Walter Tarpan schrieb:> if (status!=0)> goto error;>> return status; // = 0>> error:> I2C_reset(EEPROM_I2C_DEVICE);> return status;
Ein hervorragendes Beispiel für die verwendung eines Goto.
Könntest du aber noch deutlich verbessern:
goto unten
oben:
goto abfrage
fallja:
goto entwederoderdochnicht
abfrage:
goto fallja
alsodochdoch:
if (status!=0)
goto error;
goto noerror
error:
I2C_reset(EEPROM_I2C_DEVICE);
return status;
noerror:
return status; // = 0
entwederoderdochnicht:
goto alsodochdoch
unten:
goto oben
BTW genau soetwas ist der Grund warum es für Anfänger verboten ist goto
zu verwenden. Weil dann so ein Mist entsteht und sie es sich angewöhnen,
... oder es sogar noch als sinvolles Beispiel posten. Nirgendwo war es
überflüssiger als in diesem Beispiel.
Sollte man nicht alle anderen Statements, mit denen man bei genügend
Anstrengung Schindluder treiben kann, auch verteufeln. Immer nur auf das
arme GOTO, wie gemein.
Und ist nicht das Beispiel
1
nestedFor()
2
{
3
for(inti=0;i<255;i++)
4
{
5
for(intj=0;j<255;j++)
6
{
7
for(intk=0;k<255;k++)
8
{
9
if(i==100&&j==20&&k==2)return;
10
}
11
}
12
}
13
}
auch ein simuliertes GOTO. Indirekt, über die zuvor auf dem Stack
abgelegte Ziel-Adresse.
Schreibt einer, der die Wahl nicht hat, denn die Sprache, in der er seit
25 Jahren seine Brötchen programmiert, ist GOTG frei. Freizeit Code
auf'm AVR in C hat aber manchmal ein goto, damit jede Version des
Compilers sich beim Optimieren an seine Vorgaben hält. Die Welt ist eben
nicht Schwarz/Weis, sondern hell-/dunkel-grau.
Jeder Assembler hat ein GOTO und die diversen mehrfachen return, break
etc. C-Konstrukte können nur mit GOTO/JUMP in ASM umgesetzt werden.
Jaja, Ein Hochsprachen GOTO ist halt irgendwie gefühlt anders als ein
ASM GOTO.
Zwergenball schrieb:> BloedeFrage schrieb:>> Sämtliche Leute die mir bisher programmieren beigebracht haben>> Wie viele haben dir das programmieren beigebracht? Welche> Programmiersprachen hast du dabei erlernt? Haben diese Leute die> Forderung - goto nicht zu verwenden - begründet? Wie wurde die Forderung> begründet? Wie alt bist du? Welchen Schulabschluss hast du? Hast du> einen Hochschulabschluss? Wenn ja, welchen Anschluss hast?
... und wieso bist du so neugierig?
Deine Fragen haben doch nichts mit der eigentlichen Frage zu tun.
W.S. schrieb:> Ein simples> Beispiel ist die Grundschleife bei Mikrocontrollern:> int main (void)> { initialisiere();> immerzu:> machedies();> machedas();> machenochwas();> goto immerzu;> }> Wenn man das mit einem while(1) formuliert, dann meckern manche> Compiler, daß main ja gar kein Ergebnis zurückgibt - zu Recht (im> Prinzip). Also findet man bei solchen Strategen nach der Endlosschleife> noch ein return 0 oder so, um den schieren Formalismus zu befriedigen.
Compiler-Meckern hin oder her, das hier ist dem Wesen nach eine
Endlos-Schleife und ein while(1) bringt genau das zum Ausdruck. Hier
finde ich das goto deshalb fehl am Platz.
foo schrieb:> Yalu X. schrieb:>> Ich möchte hier noch eine andere Möglichkeit zur Diskussion stellen>> Von Sprache neu definieren mittels Präprozessor halt ich gar nichts.> Warum nicht dein Konstrukt so lassen, aber mit do, if und break.
Weil das do-while eine Pseudoschleife ist und deswegen Verwirrung
stiften könnte.
Aber ich stimme dir trotzdem zu: Solche Makros, die etwas anderes
definieren als Konstanten oder funktionsähnliche Dinge, mag ich
eigentlich auch nicht.
Konrad S. schrieb:> Das ist genau der Fall, wo ich die define so abändern würde:>> #define BLOCK_BEGIN> #define BLOCK_END block_end:> #define FAIL_IF(x) if(x) goto block_end>> Ganz einfach aus dem Grund, weil es dem Wesen nach keine Schleife ist.> Aber lieber schreibe ich offen und ehrlich das goto hin.
Die do-while-Schleife habe ich aus drei Gründen verwendet:
1. Man braucht kein Goto-Label. Somit können auch mehrere BLOCK_BEGIN-
BLOCK_END-Konstrukte nacheinander in einer Funktion verwendet werden,
ohne dass es Konflikte mit Labels gleichen Namens gibt.
2. Mehrere BLOCK_BEGIN-BLOCK_END-Konstrukte nacheinander werden selten
benötigt. Manchmal möchte man sie aber verschachteln können, bspw. in
solchen Fällen:
Klaus Kaiser schrieb:http://programmers.stackexchange.com/questions/154974/is-this-a-decent-use-case-for-goto-in-c/154980#154980
Dank der verwendeten Pseudoschleife geht das problemlos, und der
Compiler kontrolliert dabei die korrekte Verschachtelung.
3. Die Definition von BLOCK_BEGIN und BLOCK_END als Schleife verhindert,
dass sie unsinnig platziert werden, bspw. in unterschiedlichen
Blöcken, in der falschen Reihenfolge (BLOCK_END vor dem BLOCK_BEGIN)
oder in anderen spaghettiverdächtigen Anordnungen. Deren Verwendung
wird damit deutlich eingeschränkt und erlaubt es dem Compiler, einige
Fehler zu erkennen, die bei der Verwendung von Goto unentdeckt
blieben.
Zugegebenermaßen benutze ich diese Makros selber auch nicht, da auch die
sorgfältige Verwendung von Gotos normalerweise nicht zu Problemen führt.
Ich habe sie mir nur für solche Leute ausgedacht, denen die direkte
Verwendung von Gotos in zur Fehlerbehandlung zweifelhaft erscheint und
die – durchaus nachvollziehbar – einen Break dem Goto vorziehen.
Yalu X. schrieb:> Zugegebenermaßen benutze ich diese Makros selber auch nicht, da auch die> sorgfältige Verwendung von Gotos normalerweise nicht zu Problemen führt.
Eben! Und wer kein goto mag und ohne auskommt, der darf auch glücklich
werden. ;-)
W.S. schrieb:> Ich hab aber aus Klaus Kaisers Link ne Sache, die mir durchaus sinnvoll> erscheint, obwohl sie etwas störrisch aussieht:>> ...> if (Procedure_A()==fail) goto undo_A;> if (Procedure_B()==fail) goto undo_B;> if (Procedure_C()==fail) goto undo_C;> ...> return OK;>> undo_C:> Undo_Procedure_C();> undo_B:> Undo_Procedure_B();> undo_A:> Undo_Procedure_A();> return Failed;>> De Knackpunkt ist hierbei das korrekte Zurückrollen von den Dingen, die> bereits angerichtet sind. Man versuche das mal stringenter auszudrücken.
Sanfte Grüße,
Michael Reinelt schrieb:> nicht"Gast" schrieb:>> Trozdem bleibt das Stück Code scheiße.> Watch your language!
Vielen Dank für die Korrektur meiner inneren Einstellung. :)
Ich hab mir mal deine Vorwürfe zu Herzen genommen und mir die aktuellen
Sourcen heruntergeladen. Ich bin mir nicht sicher, ob ich die richtige
Funktion gefunden hab, deswegen poste ich sie mal hier. Allerdings
taucht in ihr das einzige mal im gesamten Source das struct irqe auf.
Wenn ich die Richtige erwischt hab, scheint da wohl doch einer ein
Refactoring betrieben zu haben und hat die Gotos rausgeschmissen.
In anderen Funktionen in dieser Datei (ioapic.c) gibs natürlich auch
noch Gotos. Aber es scheint schon einen gewissen Trend zu geben.
Deine Beurteilung über meine Qualifikation tangiert mich damit auch
nicht mehr wirklich.
An dieser Stelle sei gesagt: Der absolut überwiegende Teil der
Kernelentwickler lässt mich mit Sicherheit wie ein Baby aussehen. Daran
gibts kein Zweifel.
****
Ich hab übrigens in einem anderen Post schon geschrieben, dass es
durchaus Situation gibt (ja, hatte ich auch schon selber), in denen Goto
seinen Sinn hat.
Ich glaub, ich muss mal meine Einstellung klarstellen :)
Bei meinen Coding geht es immer um:
- Lesbarkeit
- Wartbarkeit
- Wiederverwendbarkeit
Die Fehlerfreiheit überragt natürlich alles ^^.
Ich behaupte auch an der Stelle nicht, dass ich so gut bin, dass ich es
immer hinbekomme.
Ich hab schon nach 2 Wochen schon mal ein halbes Programm umstricken
müssen, weil ich es nicht mehr verstanden habe.
Da ich performance unkritische Sachen entwickle, steht das auch gar
nicht auf meiner Agenda. Manchmal musste ich allerdings auch schon in
einige Richtungen optimieren, weil zum Beispiel auf einmal der
Speicherverbrauch epxlodiert ist.
Die Beispiele mit Goto hier im Thread bedeuten für mich bisher immer
eine schlechtere Lesbarkeit weil sie unnötig einen Bruch im Code
darstellen. Geschweifte Klammern zeigen immer schön Start und Ende eines
Scopes an.
Sprungziele von gotos können überall sein und man muss sie erst suchen.
Was ich meine:
1
...
2
if(Procedure_A()==fail)gotoundo_A;
3
if(Procedure_B()==fail)gotoundo_B;
4
if(Procedure_C()==fail)gotoundo_C;
5
...
6
returnOK;
7
8
undo_C:
9
Undo_Procedure_C();
10
undo_B:
11
Undo_Procedure_B();
12
undo_A:
13
Undo_Procedure_A();
14
returnFailed;
warum kann ich meine Funktion nicht so stricken, dass Undo_Procedure_C()
direkt ausgeführt wird wo's fehlschlägt? Dann seh ich sofort, was
passiert. So muss ich erst zum Goto Ziel (vielleicht noch scollen).
btw. ist das wirklich gut, dass im Fehlerfall der Undo Code von allen
drei Methoden ausgeführt wird?
An der Stelle nicht vergessen. Goto kann vorkommen. Ist ja nicht umsonst
in den meisten Sprachen integriert. Der Ottonormal
Durchschnittsprogrammierer tut sich jedoch in dem meisten Fällen eher
schlecht damit und sollte seinen Code lieber so strukturieren, dass er
es nicht braucht.
Damit meine ich nicht die kleinen Beispiele hier, sondern das fängt
schon eine Ebene weite höher im Funktionsaufbau an. Überwiegend kommt
dabei besser lesbarer Code raus.
Wenn die Lesbarkeit aus zwingenden Gründen leiden muss um sein Ziel zu
erreichenm, dann soll es so sein. Vermeiden sollte man das aber in
standard Situationen.
Das auch Profis mit Goto manchmal nicht umgehen können zeigt ja Apple
sehr schön :)
https://gotofail.com/faq.html
nicht"Gast" schrieb:> Wenn ich die Richtige erwischt hab, scheint da wohl doch einer ein> Refactoring betrieben zu haben und hat die Gotos rausgeschmissen.
Es ist der falsche Code. Der von mir zitierte war irgendwo aus dem
Bereich kvm. und er war aktuell (2.6.15)
Aber - bevor du weitersuchst: der tatsächliche Code tut absolut nichts
zur sache! ich wollte damit nur illustrieren, das es nicht immer so
einfach ist wie in den hier gebrachten Beispielen, proc_a() ist eben
nicht nur eine zeile, sondern das können auch viele sein. (Und auf
deinen Vorposter geh ich deshalb erst gar nicht mehr ein - ich
kapituliere)
nicht"Gast" schrieb:> Deine Beurteilung über meine Qualifikation tangiert mich damit auch> nicht mehr wirklich.
War auch weder ernst noch persönlich gemeint - lassen wirs gut sein?
nicht"Gast" schrieb:> Die Beispiele mit Goto hier im Thread bedeuten für mich bisher immer> eine schlechtere Lesbarkeit weil sie unnötig einen Bruch im Code> darstellen. Geschweifte Klammern zeigen immer schön Start und Ende eines> Scopes an.> Sprungziele von gotos können überall sein und man muss sie erst suchen.
Siehst du, ich seh's genau gegenteilig: Das Schöne am Goto ist, dass das
Sprungziel da steht, und ich schnell danach suchen kann; während ich
beim break erst (mühsam) das Ende des Scopes suchen muss. und von der
anderen seite her: wenn ich ein Label sehe, weiss ich dass es
(vermutlich) angesprungen wird - was ich bei einer geschlossenen
geschwungenen Klammer erstmal nicht erkennen kann.
nicht"Gast" schrieb:> warum kann ich meine Funktion nicht so stricken, dass Undo_Procedure_C()> direkt ausgeführt wird wo's fehlschlägt?
Nochmal: weil ich Code nicht duplizieren will. und selbst wenn ich so
klug bin, undo_procedure_c() in eine Funktion auszulagern (was in den
wenigsten Fällen Sinn macht, weil ich gefühlte tausend parameter
übergeben müsste) kommt der Compiler (mein Freund und Helfer) und
dupliziert den Code, weil er den aufruf inline macht. Das will ich
nicht, weil es meinen Cache-Footprint unnötig erhöht.
Ich bin ja auch keineswegs der Meinung, man sollte goto verwenden.
keinesfalls! ich selbst verwende es eher selten.
Was ich aber nicht ausstehen kann, ist das "goto ist böse", und das
sofortige "Der Code ist scheisse, weil ein goto drinnen ist". Das zeigt
nur eines - derjenige hat es nicht verstanden.
Und was ich beängstigend finde - dass obiges offensichtlich gelehrt
wird.
Und wie sich hier in diesem Thread wieder zeigt - so viele fühlen sich
berufen, zu erklären wie man's besser machen sollte. Und keiner
versteht, dass er (in bestimmten Fällen) einfach falsch liegt. Keiner
liest Klaus Kaisers Link, und wenn er gelesen wird, wird er nicht
verstanden.
Traurige Programmierer-Welt :-(
Michael Reinelt schrieb:> War auch weder ernst noch persönlich gemeint - lassen wirs gut sein?
Gerne (Handshake)
Im Grunde reden wir ja mit fast der gleichen Meinung aneinander vorbei
:)
Eigentlich finde ich es ganz gut, dass es nicht gelehrt wird. Anfänger
werden durch so was zu schlechten Code verleitet (Ich hoffe das
bestreitest du nicht :).
Wenn man wirklich mal an den Punkt kommt, an dem man es nicht vermeiden
kann, ist man meistens auch so weit, dass man es vernünftig benutzen
kann.
Übrigens ein 'grep -Ro "goto" | wc -l' über den aktuellen Source
(3.15.6) ergibt die Zahl 117157. Das ist mal krass.
Grüße,
Inzwischen scheint der TO vom Thread plattgewalzt worden zu sein.
Um aber nochmal auf die Ursprungsfrage nachdem "warum" zu kommen,
sind mir folgende mehr oder weniger historische Begründungen
bekannt (gemacht) worden:
1. Das menschliche Gehirn verarbeitet tendenziell Strukturen
besser als zeitliche Abfolgen. Ein Programm, das nur aus
If/While/Abfolgen und Prozeduraufrufen besteht, ist quasi
statisch ("diese Schleife schneidet die Verzeichnisse von den
Pfadnamen ab, diese Prozedur..."). Den Gegenpol dazu bildet
ein rein goto-basiertes 'lineares' Programm, dessen zeitlicher
Ablauf nachvollzogen werden muss, um es zu verstehen.
Beispiel aus dem Alltag wäre ein Tankwart, der einem Touristen
auf der Karte zeigt: "Sie sind hier und wollen nach da" vs.
ohne Karte: "Sie fahren die X-Strasse entlang, bei der dritten
Ampel rechts, dann bis zum Y-Ring, folgen dem bis zu ..."
(zehn weitere Stationen - Tourist hat längst den Faden
verloren).
2. Das goto-Verbot wurde aufgestellt, als die Software-Krise zum
erstenmal bewusst wahrgenommen wurde. Da kam die Forderung auf,
Programme sollten so einfach und modularisiert wie möglich
sein. Goto galt als das Mittel, um Programmteile zu einem
undurchdringlichen Monolithen zu verbinden ("big ball of mud").
3. Parallel zur Software-Krise gab es eine Hype-Phase für die
'formale Verifikation', also den Wunsch, Korrektheit von
Software mathematisch zu beweisen. Die strukturierte
Programmierung kam der vielfach verwendeten Hoare-Logik und
dem Floyd-Invariantenverfahren sehr entgegen, während goto
die Kalküle schnell an ihre Grenzen brachte - umso mehr, als
man damals noch im wesentlichen mit Zettel und Stift bewies.
Dieses Argument sticht heute kaum noch, vor allem weil die
Verifikationstechnik sich als i.a. wirtschaftlich unrentabel
herausstellte.
nicht"Gast" schrieb:> Eigentlich finde ich es ganz gut, dass es nicht gelehrt wird.
Mißverständnis: ich bin nicht dafür dass goto gelehrt wird, aber ich
verwehre mich gegen die Lehre "goto => böse"
> Anfänger> werden durch so was zu schlechten Code verleitet (Ich hoffe das> bestreitest du nicht :).
Nö :-)
nicht"Gast" schrieb:> Übrigens ein 'grep -Ro "goto" | wc -l' über den aktuellen Source> (3.15.6) ergibt die Zahl 117157. Das ist mal krass.
Du findest das krass. Ich nicht ;-)
>> natürlich ist es wurscht, aber kein Mensch (ausser die von PIC)>> verwenden für JMP Goto in ASM. Warum der Quatsch? (doch nicht um mit>> 'goto' ein Hochsprachenkonstrukt im ASM zu assoziiren) (allerd. ist bei>> PIC (aus Urzeiten) syntakt. vieles anders).>Quatsch ist, sich darüber aufzuregen. Ich glaube nicht, dass alle>Hersteller verpflichtet sind, die Nomenklatur von Intel zu übernehmen.
Aber warum (nur bei PICs) Gotu in ASM , wo es doch (verteufelterweise)
in Hochsprachen benutzt wird?
In C macht goto Sinn, weil es oft keine andere vernünftige Möglichkeit
gibt, Resourcen wieder freizugeben. Deshalb ist das auch im Linux-Kernel
so viel vorhanden und dort auch ok. In C++ möchte ich aber mal ein
Beispiel sehen, wo es sinnvoll ist goto zu benutzen.
Die letzten fünf gotos die ich in C++-Code gefunden habe konnte man
durch ein kürzeres und übersichtlicheres nicht-goto-Konstrukt ersetzen.