GOTO è ancora considerato dannoso? [chiuso]


282

Tutti sono a conoscenza delle Lettere di Dijkstra all'editore: vai alla dichiarazione considerata dannosa (anche qui trascrizione .html e qui .pdf) e da quel momento c'è stata una spinta formidabile per evitare la dichiarazione goto ogni volta che è possibile. Sebbene sia possibile utilizzare goto per produrre codice non tentabile e tentacolare, rimane comunque nei moderni linguaggi di programmazione . Anche la struttura avanzata di controllo della continuazione in Scheme può essere descritta come un goto sofisticato.

Quali circostanze giustificano l'uso di goto? Quando è meglio evitare?

Come domanda di follow-up: C fornisce una coppia di funzioni, setjmp e longjmp, che forniscono la possibilità di andare non solo all'interno del frame dello stack corrente ma all'interno di uno qualsiasi dei frame chiamanti. Questi dovrebbero essere considerati pericolosi come goto? Più pericoloso?


Lo stesso Dijkstra si pentì di quel titolo, di cui non era responsabile. Alla fine di EWD1308 (anche qui .pdf) ha scritto:

Finalmente un racconto per la cronaca. Nel 1968, le Comunicazioni dell'ACM pubblicarono un mio testo sotto il titolo " La dichiarazione goto considerata dannosa ", che negli anni successivi sarebbe stata più frequentemente citata, purtroppo, tuttavia, spesso da autori che non ne avevano visto più titolo, che è diventato una pietra miliare della mia fama diventando un modello: vedremmo tutti i tipi di articoli sotto il titolo "X considerato dannoso" per quasi ogni X, incluso uno intitolato "Dijkstra considerato dannoso". Ma cosa era successo? Avevo presentato un documento sotto il titolo " Un caso contro la dichiarazione goto", che, per accelerare la sua pubblicazione, l'editore si era trasformato in una" lettera all'editore ", e nel frattempo gli aveva dato un nuovo titolo di sua invenzione! L'editore era Niklaus Wirth.

Un articolo classico ben ponderato su questo argomento, da abbinare a quello di Dijkstra, è la Programmazione strutturata con Go to Statements , di Donald E. Knuth. Leggere entrambi aiuta a ristabilire il contesto e una comprensione non dogmatica della materia. In questo documento, l'opinione di Dijkstra su questo caso è riportata ed è ancora più forte:

Donald E. Knuth: Credo che presentando un simile punto di vista non sia affatto in disaccordo con le idee di Dijkstra, dal momento che ha recentemente scritto quanto segue: "Per favore, non cadere nella trappola di credere che io sono terribilmente dogmatico su [il vai alla dichiarazione]. Ho la spiacevole sensazione che gli altri ne stiano facendo una religione, come se i problemi concettuali della programmazione potessero essere risolti con un solo trucco, con una semplice forma di disciplina del codice! "


1
Il goto di C # non è lo stesso del goto di cui parlava Dijkstra, proprio per i motivi di cui parlava Dijkstra. Quel goto è sia dannoso come lo era allora, ma anche molto meno necessario, perché i linguaggi moderni offrono strutture di controllo alternative. C # goto è estremamente limitato.
jalf

25
Le goto sono buone quando aggiungono chiarezza. Se hai un ciclo annidato lungo, può essere meglio uscirne piuttosto che impostare variabili "break" e interrompendole fino a quando non esci.
simendsjo,

28
Se hai un loop nidificato su 4 profondità (non che sia una buona cosa), uscire da tutto richiede l'impostazione di valori temporanei. Un goto qui è molto più chiaro per me e l'IDE dovrebbe facilmente mostrare dove si trova il goto. Detto questo, l'uso di goto dovrebbe essere scarso e, a mio avviso, spostarsi in basso per saltare il codice
simendsjo

7
Ti suggerisco di andare a leggere le novemila e le discussioni taggate goto.
Matti Virkkunen,

5
C'è chiaramente una cosa peggiore dell'uso goto: hackerare strumenti di programmazione strutturati insieme per implementare a goto.

Risposte:


184

Le seguenti dichiarazioni sono generalizzazioni; mentre è sempre possibile invocare l'eccezione, di solito (secondo la mia esperienza e modesta opinione) non vale i rischi.

  1. L'uso illimitato di indirizzi di memoria (GOTO o puntatori non elaborati) offre troppe opportunità per commettere errori facilmente evitabili.
  2. Più modi ci sono per arrivare a una particolare "posizione" nel codice, meno si può essere sicuri di quale sia lo stato del sistema a quel punto. (Vedi sotto.)
  3. Programmazione strutturata L'IMHO si occupa meno di "evitare GOTO" e di fare in modo che la struttura del codice corrisponda alla struttura dei dati. Ad esempio, una struttura di dati ripetuta (ad es. Array, file sequenziale, ecc.) Viene naturalmente elaborata da un'unità di codice ripetuta. Avere strutture integrate (ad esempio while, for, until, for-each, ecc.) Consente al programmatore di evitare il tedio di ripetere gli stessi schemi di codice cliché.
  4. Anche se GOTO è un dettaglio di implementazione di basso livello (non sempre il caso!) È al di sotto del livello che il programmatore dovrebbe pensare. Quanti programmatori bilanciano i loro libretti degli assegni personali in binari grezzi? Quanti programmatori si preoccupano per quale settore sul disco contiene un particolare record, invece di fornire semplicemente una chiave per un motore di database (e in quanti modi le cose potrebbero andare storte se davvero scrivessimo tutti i nostri programmi in termini di settori del disco fisico)?

Note a piè di pagina sopra:

Per quanto riguarda il punto 2, considerare il seguente codice:

a = b + 1
/* do something with a */

Nel punto "fai qualcosa" nel codice, possiamo affermare con amaggiore sicurezza che è maggiore di b. (Sì, sto ignorando la possibilità di overflow di numeri interi non intrappolati. Non impantaniamo un semplice esempio.)

D'altra parte, se il codice avesse letto in questo modo:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

La molteplicità dei modi per arrivare all'etichetta 10 significa che dobbiamo lavorare molto di più per essere sicuri delle relazioni tra ae bin quel momento. (In effetti, nel caso generale è indecifrabile!)

Per quanto riguarda il punto 4, l'intera nozione di "andare da qualche parte" nel codice è solo una metafora. Nulla è veramente "diretto" in nessun punto all'interno della CPU tranne elettroni e fotoni (per il calore residuo). A volte rinunciamo a una metafora per un'altra, più utile, una. Ricordo di aver incontrato (qualche decennio fa!) Una lingua in cui

if (some condition) {
  action-1
} else {
  action-2
}

è stato implementato su una macchina virtuale compilando action-1 e action-2 come routine senza parametri fuori linea, quindi utilizzando un singolo opcode VM a due argomenti che utilizzava il valore booleano della condizione per richiamare l'uno o l'altro. Il concetto era semplicemente "scegli cosa invocare ora" piuttosto che "vai qui o vai lì". Ancora una volta, solo un cambio di metafora.


2
Un buon punto Nei linguaggi di livello superiore, goto non significa nemmeno nulla (prendere in considerazione l'idea di saltare tra i metodi in Java). Una funzione di Haskell può consistere in una singola espressione; prova a saltare fuori da quello con un goto!
Lumaca meccanica

2
Postscript funziona come il tuo esempio del punto 4.
Luser droog

Smalltalk funziona in modo simile all'esempio del punto 4, se per "allo stesso modo" intendi "niente come fanno i linguaggi procedurali". : P Non c'è controllo del flusso nella lingua; tutte le decisioni sono gestite tramite polimorfismo ( truee falsesono di diversi tipi), e ogni ramo di un if / else è sostanzialmente un lambda.
cHao,

Questi sono punti validi, ma alla fine ribadiscono quanto il goto cattivo possa essere "se usato in modo improprio". Break, continue, exit, return, gosub, settimeout, global, include, ecc. Sono tutte tecniche moderne che richiedono di tracciare mentalmente il flusso delle cose e possono essere utilizzate in modo improprio per creare il codice spaghetti e saltare per creare incertezza degli stati variabili. Ad essere sinceri, anche se non ho mai visto un cattivo uso di goto in prima persona, l'ho visto solo usato una o due volte. Questo dice l'affermazione che ci sono quasi sempre cose migliori da usare.
Beejor,

gotoin un moderno linguaggio di programmazione (Vai) stackoverflow.com/a/11065563/3309046 .
Nishanths,

245

Comic GOTO di XKCD

Un mio collega ha detto che l'unica ragione per usare un GOTO è se ti sei programmato così tanto in un angolo che è l'unica via d'uscita. In altre parole, progettazione corretta in anticipo e non sarà necessario utilizzare un GOTO in seguito.

Ho pensato che questo fumetto illustrasse magnificamente "Potrei ristrutturare il flusso del programma, o usare un po 'GOTO" invece ". Un GOTO è un'uscita debole quando hai un design debole. I velociraptor rapiscono i deboli .


41
GOTO può fare "saltare" da un punto arbitrario a un altro punto arbitrario. Velociraptor è saltato da qui dal nulla!
rpattabi,

29
non trovo affatto divertente lo scherzo perché tutti sanno che devi anche collegare prima di poter eseguire.
Kinjal Dixit,

31
Il tuo collega ha torto e ovviamente non ha letto il documento di Knuth.
Jim Balter,

27
Non ho visto la fine del codice offuscato e altrimenti contorto nel corso degli anni messo in atto solo per evitare un goto. @jimmcKeeth, Il fumetto xkcd sopra non stabilisce che il goto sia debole. Sta prendendo in giro l'isteria che lo circonda.

4
Ti rendi conto che il codice sorgente della libreria Delphi contiene istruzioni goto. E questi sono usi appropriati di goto.
David Heffernan,

131

A volte è valido utilizzare GOTO come alternativa alla gestione delle eccezioni all'interno di una singola funzione:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

Il codice COM sembra rientrare in questo schema abbastanza spesso.


29
Sono d'accordo, ci sono casi d'uso legittimi in cui, goto può semplificare il codice e renderlo più leggibile / gestibile, ma sembra che ci sia una sorta di goto-fobia che galleggia intorno ...
Pop Catalin

48
@Bob: è difficile spostare il codice err_cleanup in una subroutine se si puliscono le variabili locali.
Niki,

4
A dire il vero, l'ho usato in COM / VB6 solo perché non avevo nessuna alternativa, non perché fosse un'alternativa. Sono felice oggi con try / catch / finalmente.
Rui Craveiro,

10
@ user4891 Il modo idiomatico C ++ non è provare {} catch () {cleanup; }, ma piuttosto, RAII, dove le risorse che devono essere ripulite vengono fatte nei distruttori. Ogni costruttore / distruttore gestisce esattamente una risorsa.
David Stone,

5
Ci sono due modi per scrivere questo in C senza andare; ed entrambi sono molto più brevi. O: if (f ()) if (g ()) if (h ()) restituisce successo; pulire(); errore di ritorno; oppure: if (f () && g () && h ()) restituisce successo; pulire(); errore di ritorno;
LHP

124

Ricordo di aver usato un goto solo una volta. Avevo una serie di cinque cicli contati annidati e avevo bisogno di essere in grado di uscire dall'intera struttura dall'interno in anticipo a determinate condizioni:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Avrei potuto facilmente dichiarare una variabile break booleana e usarla come parte del condizionale per ogni loop, ma in questo caso ho deciso che un GOTO era altrettanto pratico e altrettanto leggibile.

Nessun velociraptor mi ha attaccato.


83
"Rifattatelo in una funzione e sostituite goto con return :)", e la differenza è? davvero qual è la differenza? non è di ritorno un andare anche a? I ritorni frenano anche il flusso strutturato di come fa goto, e in questo caso lo fanno allo stesso modo (anche se goto può essere usato per cose più cattive)
Pop Catalin,

38
L'annidamento di molti loop è di solito un odore di codice tutto suo. A meno che non si stia facendo, ad esempio, la moltiplicazione di array a 5 dimensioni, è difficile immaginare una situazione in cui alcuni dei loop interni non possano essere utilmente estratti in funzioni più piccole. Come tutte le regole pratiche, suppongo che ci siano alcune eccezioni.
Doug McClean,

5
La sostituzione con un ritorno funziona solo se si utilizza una lingua che supporta i resi.
Loren Pechtel,

31
@leppie: La generazione che si è ribellata gotoe ci ha dato una programmazione strutturata ha anche respinto i primi ritorni, per lo stesso motivo. Dipende da quanto sia leggibile il codice, da quanto chiaramente espresso l'intento del programmatore. La creazione di una funzione per nessun altro scopo se non quello di evitare l'uso di una parola chiave diffamata provoca una cattiva coesione: la cura è peggiore della malattia.
Fango

21
@ButtleButkus: Francamente, è altrettanto male, se non peggio. Almeno con a goto, si può specificare esplicitamente il target. Con break 5;, (1) devo contare le chiusure ad anello per trovare la destinazione; e (2) se la struttura del loop dovesse mai cambiare, potrebbe essere necessario modificare quel numero per mantenere la destinazione corretta. Se ho intenzione di evitare goto, allora il vantaggio dovrebbe essere nel non dover tracciare manualmente cose del genere.
cHao,

93

Abbiamo già discusso di questa discussione e io sostengo il mio punto .

Inoltre, sono stufo di gente che descrivono strutture linguistiche di livello superiore come “ gotoin incognito”, perché chiaramente non hanno ottenuto il punto a tutti . Per esempio:

Anche la struttura avanzata di controllo della continuazione in Scheme può essere descritta come un goto sofisticato.

Questa è una totale assurdità. Ogni struttura di controllo può essere implementata in termini di gotoma questa osservazione è assolutamente banale e inutile. gotonon è considerato dannoso a causa dei suoi effetti positivi ma a causa delle sue conseguenze negative e queste sono state eliminate dalla programmazione strutturata.

Allo stesso modo, dire "GOTO è uno strumento e, come tutti gli strumenti, può essere utilizzato e abusato" è completamente fuori dal comune. Nessun operaio edile moderno userebbe una roccia e affermerebbe che "è uno strumento". Le rocce sono state sostituite da martelli. gotoè stato sostituito da strutture di controllo. Se il muratore fosse bloccato in natura senza un martello, ovviamente avrebbe usato una roccia. Se un programmatore deve usare un linguaggio di programmazione inferiore che non ha la funzione X, beh, ovviamente potrebbe essere necessario usare gotoinvece. Ma se la usa da qualche altra parte invece della funzione linguistica appropriata, chiaramente non ha capito bene la lingua e la usa in modo errato. È davvero così semplice.


82
Naturalmente, l'uso corretto di una roccia non è come un martello. Uno dei suoi usi corretti è una mola, o per affilare altri strumenti. Anche l'umile roccia, se usato correttamente, è un buon strumento. Devi solo trovare l'uso corretto. Lo stesso vale per andare.
Kibbee,

10
Allora, qual è l'uso corretto di Goto? Per ogni caso immaginabile c'è un altro strumento più adatto. E anche la tua pietra abrasiva è attualmente sostituita da strumenti ad alta tecnologia, anche se sono ancora fatti di roccia. C'è una grande differenza tra una materia prima e uno strumento.
Konrad Rudolph,

8
@jalf: Goto certamente non esistono in C #. Vedi stackoverflow.com/questions/359436/…
Jon Skeet l'

51
Sono sgomento che così tante persone approvano questo post. Il tuo post è sembrato efficace solo perché non ti sei mai preso la briga di mettere in discussione quale logica stavi effettivamente eseguendo, quindi non hai notato il tuo errore. Consentimi di parafrasare il tuo intero post: "Esiste uno strumento superiore per un goto in ogni situazione, quindi i goto non dovrebbero mai essere usati." Questo è un bicondizionato logico e come tale l'intero tuo post pone essenzialmente la domanda "Come fai a sapere che esiste uno strumento superiore per un goto in ogni situazione?"
Coding With Style

10
@Coding: No, hai completamente perso l'essenza della pubblicazione. E 'stata una replica piuttosto che un isolato, argomento completo. Ho semplicemente sottolineato l'errore nell'argomento principale "per" goto. Hai ragione nella misura in cui non offro un argomento di gotoper sé - non avevo intenzione di farlo - quindi non ci sono domande.
Konrad Rudolph,

91

Goto è estremamente basso nella mia lista di cose da includere in un programma solo per il gusto di farlo. Ciò non significa che sia inaccettabile.

Goto può essere utile per le macchine statali. Un'istruzione switch in un ciclo è (in ordine di importanza tipica): (a) in realtà non rappresentativo del flusso di controllo, (b) brutto, (c) potenzialmente inefficiente a seconda del linguaggio e del compilatore. Quindi finisci per scrivere una funzione per stato e fare cose come "return NEXT_STATE;" che sembrano addirittura andare.

Certo, è difficile codificare le macchine a stati in un modo che le renda facili da capire. Tuttavia, nessuna di queste difficoltà ha a che fare con l'uso di goto e nessuna di queste può essere ridotta utilizzando strutture di controllo alternative. A meno che la tua lingua non abbia un costrutto "macchina a stati". Il mio no.

In quelle rare occasioni in cui il tuo algoritmo è davvero più comprensibile in termini di un percorso attraverso una sequenza di nodi (stati) collegati da un insieme limitato di transizioni consentite (goto), piuttosto che da qualsiasi flusso di controllo più specifico (loop, condizionali, quant'altro ), quindi ciò dovrebbe essere esplicito nel codice. E dovresti disegnare un bel diagramma.

setjmp / longjmp può essere utile per implementare eccezioni o comportamenti simili a eccezioni. Pur non essendo universalmente elogiate, le eccezioni sono generalmente considerate una struttura di controllo "valida".

setjmp / longjmp sono "più pericolosi" di goto, nel senso che sono più difficili da usare correttamente, non importa comprensibilmente.

Non c'è mai stata, e non ci sarà mai, alcuna lingua in cui scrivere un codice errato sia un po 'difficile. - Donald Knuth.

Eliminare C da C non semplificherebbe la scrittura di un buon codice in C. In realtà, perderebbe piuttosto il punto in cui C dovrebbe essere in grado di agire come un linguaggio assemblatore glorificato.

Successivamente saranno "puntatori considerati dannosi", quindi "digitazione anatra considerata dannosa". Allora chi rimarrà per difenderti quando verranno a portare via il tuo costrutto di programmazione non sicuro? Eh?


14
Personalmente, questo è il commento a cui avrei dato il controllo. Una cosa che vorrei sottolineare ai lettori è che il termine esoterico "macchine a stati" include cose quotidiane come gli analizzatori lessicali. Dai un'occhiata all'output di lex in qualche momento. Pieno di goto.
TED

2
È possibile utilizzare un'istruzione switch all'interno di un ciclo (o gestore di eventi) per eseguire correttamente le macchine a stati. Ho fatto molte macchine a stati senza mai usare jmp o goto.
Scott Whitlock,

11
+1 Quelle frecce sulle macchine a stati mappano più "vicino" che a qualsiasi altra struttura di controllo. Certo, puoi usare un interruttore all'interno di un ciclo - proprio come puoi usare un mucchio di goto invece di un po 'per altri problemi, ma di solito è un'idea; che è il punto centrale di questa discussione.
Edmund,

2
Posso citarti sull'ultimo paragrafo?
Chris Lutz,

2
E, qui nel 2013, abbiamo già colpito (e in qualche modo superato) la fase "puntatori considerati dannosi".
Isiah Meadows,

68

In Linux: usare goto in Kernel Code su Kernel Trap, c'è una discussione con Linus Torvalds e un "nuovo ragazzo" sull'uso dei GOTO nel codice Linux. Ci sono alcuni punti molto buoni lì e Linus vestito con quella solita arroganza :)

Alcuni passaggi:

Linus: "No, ti è stato fatto il lavaggio del cervello da persone CS che pensavano che Niklaus Wirth in realtà sapesse di cosa stava parlando. Non l'ha fatto. Non ha la minima idea."

-

Linus: "Penso che i goto stiano bene e spesso sono più leggibili di grandi quantità di rientri".

-

Linus: "Certo, in lingue stupide come Pascal, dove le etichette non possono essere descrittive, le goto possono essere cattive."


9
È un buon punto come? Stanno discutendo del suo uso in una lingua che non ha nient'altro. Quando stai programmando in assembly, tutti i rami e i salti sono goto. E C è, ed era, un "linguaggio assembly portatile". Inoltre, i passaggi che citi non dicono nulla sul perché pensa che Goto sia buono.
jalf

5
Wow. È deludente da leggere. Penseresti che un grande ragazzo del sistema operativo come Linus Torvalds lo saprebbe meglio di dirlo. Pascal (la vecchia scuola Pascal, non la moderna versione Object) era ciò in cui Mac OS Classic era scritto durante il periodo di 68k, ed era il sistema operativo più avanzato del suo tempo.
Mason Wheeler,

6
@mason Classic Mac OS aveva alcune librerie Pascal (alla fine - il runtime Pascal occupava troppa memoria nei primi Mac) ma la maggior parte del codice core era scritto in Assembler, in particolare la grafica e le routine dell'interfaccia utente.
Jim Dovey,

30
Linus sostiene (esplicitamente, come Rik van Riel in quella discussione) per andare a gestire lo stato di uscita, e lo fa sulla base della complessità che i costrutti alternativi di C porterebbero se fossero invece usati.
Charles Stewart,

21
IMHO Linus ha ragione su questo problema. Il suo punto è che il codice del kernel, scritto in C, che deve implementare qualcosa di simile alla gestione delle eccezioni, è più chiaramente e semplicemente scritto usando un goto. Il linguaggio goto cleanup_and_exitè uno dei pochi "buoni" usi di goto sinistra ora che abbiamo for, whilee ifdi gestire il nostro flusso di controllo. Vedi anche: programmers.stackexchange.com/a/154980
steveha,

50

In C, gotofunziona solo nell'ambito della funzione corrente, che tende a localizzare eventuali bug. setjmpe longjmpsono molto più pericolosi, essendo non locali, complicati e dipendenti dall'implementazione. In pratica, tuttavia, sono troppo oscuri e non comuni per causare molti problemi.

Credo che il pericolo di gotoin C sia notevolmente esagerato. Ricorda che gli gotoargomenti originali risalivano ai tempi di lingue come BASIC vecchio stile, dove i principianti scrivevano il codice spaghetti in questo modo:

3420 IF A > 2 THEN GOTO 1430

Qui Linus descrive un uso appropriato di goto: http://www.kernel.org/doc/Documentation/CodingStyle (capitolo 7).


7
Quando BASIC fu disponibile per la prima volta, non c'erano alternative a GOTO nnnn e GOSUB mmmm come modi per saltare. Costrutti strutturati furono aggiunti in seguito.
Jonathan Leffler,

7
Ti manca il punto ... anche allora non dovevi scrivere spaghetti ... i tuoi GOTO potevano sempre essere usati in modo disciplinato
JoelFan

Vale anche la pena notare che il comportamento di setjmp/ è longjmpstato specificato solo quando sono stati utilizzati come mezzo per saltare in un punto all'interno di un ambito da altri luoghi all'interno dello stesso ambito. Una volta che il controllo lascia l'ambito in cui setjmpviene eseguito, qualsiasi tentativo di utilizzo longjmpsulla struttura creata da setjmpcomporta un comportamento indefinito.
supercat,

9
Alcune versioni di BASIC ti permetterebbero di farlo GOTO A * 40 + B * 200 + 30. Non è difficile capire come sia stato molto utile e molto pericoloso.
Jon Hanna,

1
@Hjulle calcolerebbe l'espressione e quindi passerebbe alla riga di codice con quel numero (i numeri di riga espliciti erano un requisito della maggior parte dei dialetti precedenti). ZX Spectrum Basic era uno che lo avrebbe accettato
Jon Hanna,

48

Oggi è difficile vedere il grosso della GOTOdichiarazione perché le persone della "programmazione strutturata" hanno vinto per lo più il dibattito e le lingue di oggi hanno strutture di flusso di controllo sufficienti per evitare GOTO.

Conta il numero di gotos in un moderno programma C. A questo punto aggiungere il numero di break, continuee returnle dichiarazioni. Inoltre, aggiungere il numero di volte che si utilizza if, else, while, switcho case. Si tratta di quanti GOTOs il tuo programma avrebbe avuto se tu stessi scrivendo in FORTRAN o BASIC nel 1968 quando Dijkstra scrisse la sua lettera.

I linguaggi di programmazione all'epoca mancavano del flusso di controllo. Ad esempio, nell'originale Dartmouth BASIC:

  • IFle dichiarazioni avevano no ELSE. Se ne volevi uno, dovevi scrivere:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Anche se la tua IFaffermazione non aveva bisogno di una ELSE, era comunque limitata a una singola riga, che di solito consisteva in una GOTO.

  • Non c'erano DO...LOOPdichiarazioni. Per i non- FORloop, è stato necessario terminare il loop con un esplicito GOTOo IF...GOTOtornare all'inizio.

  • Non c'era SELECT CASE. Dovevi usare ON...GOTO.

Quindi, si è conclusa con un sacco di GOTOs nel vostro programma. E non potevi dipendere dalla limitazione di GOTOs all'interno di una singola subroutine (perché GOSUB...RETURNera un concetto così debole di subroutine), quindi queste GOTOs potevano andare ovunque . Ovviamente, questo ha reso difficile seguire il flusso di controllo.

Questo è da dove GOTOproviene l'anti- movimento.


Un'altra cosa da notare è che il modo preferito di scrivere codice se uno avesse del codice in un ciclo che dovrebbe essere eseguito raramente sarebbe 420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// 3000 [code for rare condition]// 3230 goto 430. Scrivere codice in questo modo evita eventuali rami o salti nel caso comune della linea principale, ma rende le cose difficili da seguire. Evitare i rami nel codice assembly può essere peggiore, se alcuni rami sono limitati ad esempio a +/- 128 byte e talvolta non hanno coppie complementari (ad esempio "cjne" esiste ma non "cje").
supercat,

Una volta ho scritto del codice per l'8x51 che aveva un interrupt che veniva eseguito una volta ogni 128 cicli. Ogni ciclo aggiuntivo speso nel caso comune di tale ISR ridurrebbe la velocità di esecuzione del codice della linea principale di oltre l'1% (penso che circa 90 cicli su 128 fossero normalmente disponibili sulla linea principale) e qualsiasi istruzione di ramificazione richiederebbe due cicli ( in entrambi i casi di ramificazione e fall-through). Il codice aveva due confronti: uno che normalmente riportava uguale; l'altro, non uguale. In entrambi i casi, il codice dei casi rari era a più di 128 byte di distanza. Quindi ...
supercat,

cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // first_comp_springboard: ajmp first_comp_target// second_comp_fallthrough: .... Non è un modello di codifica molto bello, ma quando contano i singoli cicli, si fanno cose del genere. Naturalmente, negli anni '60, tali livelli di ottimizzazione erano più importanti di oggi, soprattutto perché le moderne CPU richiedono spesso strane ottimizzazioni e i sistemi di compilazione just-in-time potrebbero essere in grado di applicare il codice di ottimizzazione per CPU che non esistevano quando il il codice in questione è stato scritto.
supercat,

34

Vai a può fornire una sorta di stand-in per la gestione "reale" delle eccezioni in alcuni casi. Tener conto di:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

Ovviamente questo codice è stato semplificato per occupare meno spazio, quindi non rimanere troppo impegnato nei dettagli. Ma considera un'alternativa che ho visto troppe volte nel codice di produzione da parte dei programmatori che vanno in modo assurdo per evitare di usare goto:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

Ora funzionalmente questo codice fa esattamente la stessa cosa. In effetti, il codice generato dal compilatore è quasi identico. Tuttavia, nello zelo del programmatore di placare Nogoto (il temuto dio del rimprovero accademico), questo programmatore ha completamente infranto il linguaggio di fondo che il whileciclo rappresenta e ha fatto un numero reale sulla leggibilità del codice. Questo non è migliore

Quindi, la morale della storia è, se ti ritrovi a ricorrere a qualcosa di veramente stupido per evitare di usare goto, allora non farlo.


1
Anche se tendo ad essere d'accordo con quello che stai dicendo qui, il fatto che le breakdichiarazioni siano all'interno di una struttura di controllo chiarisce esattamente cosa fanno. Con l' gotoesempio, la persona che legge il codice deve guardare il codice per trovare l'etichetta, che in teoria potrebbe effettivamente trovarsi davanti alla struttura di controllo. Non ho abbastanza esperienza con il vecchio stile C per giudicare che uno sia decisamente migliore dell'altro, ma ci sono dei compromessi in entrambi i modi.
Fiume Vivian,

5
@DanielAllenLangdon: il fatto che le breaks siano all'interno di un loop chiarisce esattamente che escono dal loop . Non è "esattamente quello che fanno", dal momento che in realtà non esiste un ciclo! Niente in esso ha mai una possibilità di ripetersi, ma non è chiaro fino alla fine. Il fatto che tu abbia un "loop" che non funziona mai più di una volta, significa che le strutture di controllo vengono abusate. Con l' gotoesempio, il programmatore può dire goto error_handler;. È più esplicito e ancor meno difficile da seguire. (Ctrl + F, "error_handler:" per trovare la destinazione. Prova a farlo con "}".)
cHao,

1
Una volta ho visto un codice simile al tuo secondo esempio in un sistema di controllo del traffico aereo, perché "goto non è nel nostro lessico".
QED

30

Donald E. Knuth ha risposto a questa domanda nel libro "Literate Programming", 1992 CSLI. A pag. 17 c'è un saggio " Programmazione strutturata con dichiarazioni goto " (PDF). Penso che l'articolo potrebbe essere stato pubblicato anche in altri libri.

L'articolo descrive il suggerimento di Dijkstra e descrive le circostanze in cui ciò è valido. Ma fornisce anche una serie di contro esempi (problemi e algoritmi) che non possono essere facilmente riprodotti usando solo cicli strutturati.

L'articolo contiene una descrizione completa del problema, la cronologia, esempi ed esempi contrari.


25

Vai considerato utile.

Ho iniziato a programmare nel 1975. Per i programmatori degli anni '70, le parole "goto considerato dannoso" dicevano più o meno che valeva la pena provare nuovi linguaggi di programmazione con moderne strutture di controllo. Abbiamo provato le nuove lingue. Ci siamo convertiti rapidamente. Non siamo mai tornati.

Non siamo mai tornati, ma, se sei più giovane, allora non ci sei mai stato.

Ora, un background in antichi linguaggi di programmazione potrebbe non essere molto utile se non come indicatore dell'età del programmatore. Ciononostante, i programmatori più giovani non hanno questo background, quindi non capiscono più il messaggio che lo slogan "goto considerato dannoso" ha trasmesso al suo pubblico previsto nel momento in cui è stato introdotto.

Gli slogan che non si capiscono non sono molto illuminanti. Probabilmente è meglio dimenticare questi slogan. Tali slogan non aiutano.

Questo particolare slogan tuttavia, "Goto considerato dannoso", ha assunto una vita da non morto.

Non si può abusare di Goto? Risposta: certo, ma allora? Praticamente ogni elemento di programmazione può essere abusato. L'umile, boolad esempio, viene abusato più spesso di quanto alcuni di noi vorrebbero credere.

Al contrario, non riesco a ricordare di aver incontrato un'unica, effettiva istanza di goto abuse dal 1990.

Il problema più grande con goto probabilmente non è tecnico ma sociale. I programmatori che non conoscono molto a volte sembrano pensare che il deprecare goto li faccia sembrare intelligenti. Di tanto in tanto potresti dover soddisfare tali programmatori. Così è la vita.

La cosa peggiore di Goto oggi è che non è abbastanza usato.


24

Attratto da Jay Ballou aggiungendo una risposta, aggiungerò i miei £ 0,02. Se Bruno Ranschaert non l'avesse già fatto, avrei menzionato l'articolo "Programmazione strutturata con dichiarazioni GOTO" di Knuth.

Una cosa che non ho visto discusso è il tipo di codice che, sebbene non esattamente comune, è stato insegnato nei libri di testo di Fortran. Cose come la gamma estesa di un loop DO e subroutine a codice aperto (ricordate, questo sarebbe Fortran II, o Fortran IV, o Fortran 66 - non Fortran 77 o 90). C'è almeno una possibilità che i dettagli sintattici siano inesatti, ma i concetti dovrebbero essere abbastanza precisi. I frammenti in ciascun caso sono all'interno di una singola funzione.

Si noti che l'eccellente ma datato (e fuori catalogo) libro " The Elements of Programming Style, 2nd Edn " di Kernighan & Plauger include alcuni esempi reali di abusi di GOTO dai libri di testo di programmazione della sua epoca (fine anni '70). Il materiale qui sotto non proviene da quel libro, tuttavia.

Gamma estesa per un loop DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Uno dei motivi di tale assurdità era la buona scheda perforata vecchio stile. Potresti notare che le etichette (ben fuori sequenza perché era uno stile canonico!) Sono nella colonna 1 (in realtà, dovevano essere nelle colonne 1-5) e il codice è nelle colonne 7-72 ​​(la colonna 6 era la continuazione colonna marker). Alle colonne 73-80 sarebbe stato assegnato un numero progressivo e c'erano macchine che ordinavano i mazzi di schede perforate in ordine numerico sequenziale. Se avessi il tuo programma su carte sequenziate e avessi bisogno di aggiungere alcune carte (linee) nel mezzo di un ciclo, dovresti ricominciare tutto dopo quelle linee extra. Tuttavia, se hai sostituito una carta con il materiale GOTO, potresti evitare di rimettere in sequenza tutte le carte - hai appena nascosto le nuove carte alla fine della routine con nuovi numeri di sequenza. Consideralo il primo tentativo di "green computing"

Oh, potresti anche notare che sto tradendo e non urlando - Fortran IV è stato scritto normalmente in maiuscolo.

Sottoprogramma a codice aperto

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

Il GOTO tra le etichette 76 e 54 è una versione di goto calcolata. Se la variabile i ha il valore 1, vai alla prima etichetta nell'elenco (123); se ha il valore 2, vai al secondo e così via. Il frammento da 76 al goto calcolato è la subroutine a codice aperto. Era un pezzo di codice eseguito piuttosto come una subroutine, ma scritto nel corpo di una funzione. (Fortran aveva anche funzioni di istruzione - che erano funzioni incorporate che si adattavano su una sola riga.)

Vi erano costrutti peggiori rispetto al goto calcolato: era possibile assegnare etichette alle variabili e quindi utilizzare un goto assegnato. Googling assegnato a goto mi dice che è stato eliminato da Fortran 95. Chalk uno per la rivoluzione della programmazione strutturata che si può dire che sia iniziata in pubblico con la lettera o l'articolo "GOTO Considered Harmful" di Dijkstra.

Senza una certa conoscenza del genere di cose che sono state fatte in Fortran (e in altre lingue, la maggior parte delle quali è giustamente caduta sul ciglio della strada), è difficile per noi nuovi arrivati ​​comprendere la portata del problema con cui Dijkstra stava affrontando. Diamine, non ho iniziato a programmare fino a dieci anni dopo la pubblicazione di quella lettera (ma per un po 'ho avuto la sfortuna di programmare in Fortran IV).


2
Se vuoi vedere un esempio di codice usando goto'in the wild', la domanda Wanted: Working Bose-Hibbard Sort Algorithm mostra alcuni codici (Algol 60) pubblicati nel 1963. Il layout originale non è paragonabile ai moderni standard di codifica. Il codice chiarito (rientrato) è ancora abbastanza imperscrutabile. Le gotodichiarazioni non fanno ne fanno (molto) difficile capire ciò che l'algoritmo è fino a.
Jonathan Leffler,

1
Essendo troppo giovane per aver sperimentato qualcosa di simile alle schede perforate, è stato illuminante leggere del problema di punzonatura. +1
Qix - MONICA È STATA MISTREATA il

20

Non esistono cose come GOTO considerate dannose .

GOTO è uno strumento e, come tutti gli strumenti, può essere utilizzato e abusato .

Esistono, tuttavia, molti strumenti nel mondo della programmazione che hanno la tendenza ad essere abusati più che ad essere utilizzati , e GOTO è uno di questi. la dichiarazione WITH di Delfi è un'altra.

Personalmente non uso nemmeno nel tipico codice , ma ho avuto lo strano uso di GOTO e WITH garantiti e una soluzione alternativa avrebbe contenuto più codice.

La soluzione migliore sarebbe che il compilatore ti avvertisse semplicemente che la parola chiave era contaminata e che dovresti mettere un paio di direttive pragma attorno all'istruzione per sbarazzarti degli avvertimenti.

È come dire ai tuoi figli di non correre con le forbici . Le forbici non sono male, ma un certo uso di esse non è forse il modo migliore per mantenere la salute.


Il problema con GOTO è che rompe importanti invarianti che diamo per scontati con i moderni linguaggi di programmazione. Per fare un esempio, se chiamo una funzione, assumiamo che una volta completata, la funzione restituirà il controllo al chiamante, sia normalmente, sia tramite un eccezionale svolgimento dello stack. Se quella funzione utilizza erroneamente GOTO, ovviamente questo non è più vero. Questo rende molto difficile ragionare sul nostro codice. Non è sufficiente evitare con attenzione l'uso improprio di GOTO. Il problema si verifica ancora se GOTO viene utilizzato in modo improprio dalle nostre dipendenze ...
Jonathan Hartley,

... Quindi, per sapere se possiamo ragionare sulle nostre chiamate di funzione, dobbiamo esaminare ogni riga del codice sorgente di ciascuna delle nostre dipendenze transitive, verificando che non utilizzino in modo improprio GOTO. Quindi, solo l'esistenza di GOTO nella lingua ha rotto la nostra capacità di ragionare con fiducia sul nostro codice, anche se lo usiamo perfettamente (o non lo usiamo mai) da soli. Per questo motivo, GOTO non è solo uno strumento da utilizzare con attenzione. È sistematicamente rotto e la sua esistenza in una lingua è considerata unilateralmente dannosa.
Jonathan Hartley,

Anche se la gotoparola chiave è stata cancellata da C #, il concetto di "saltare qui" esiste ancora in IL. Un loop infinito può essere facilmente costruito senza la parola chiave goto. Se questa mancanza di garanzia che il codice chiamato restituirà, secondo te, significa "incapace di ragionare sul codice", allora direi che non abbiamo mai avuto quella capacità.
Lasse V. Karlsen,

Ah, hai sapientemente trovato la fonte della nostra cattiva comunicazione. In gotoC # non è un vero "goto" nel senso originale della parola. È una versione molto più debole che consente solo di saltare all'interno di una funzione. Il senso in cui sto usando "goto" consente di saltare ovunque nel processo. Quindi, sebbene C # abbia una parola chiave "goto", probabilmente non ha mai avuto, una vera goto. Sì, un vero goto è disponibile a livello di IL, allo stesso modo in cui viene compilato qualsiasi linguaggio fino all'assemblaggio. Ma il programmatore è protetto da ciò, incapace di usarlo in circostanze normali, quindi non conta.
Jonathan Hartley,

Per inciso, l'opinione che sto descrivendo qui non è originariamente mia, ma è il nucleo del documento originale di Dijkstra del 1967, penso, ribattezzato dal suo editore "Goto considerato dannoso", che è diventato un meme così spesso citato per 50 anni proprio perché era così rivoluzionario, penetrante e universalmente accettato.
Jonathan Hartley,

17

Non lo è mai stato, finché sei stato in grado di pensare da solo.


17

Da quando ho iniziato a fare alcune cose nel kernel di Linux, le foto non mi danno fastidio come una volta. All'inizio ero un po 'inorridito nel vedere che (ragazzi del kernel) hanno aggiunto goto al mio codice. Da allora mi sono abituato all'uso delle goto, in alcuni contesti limitati, e ora le userò occasionalmente da solo. In genere, è un goto che passa alla fine di una funzione per eseguire una sorta di pulizia e salvataggio, piuttosto che duplicare la stessa pulizia e il salvataggio in diversi punti della funzione. E in genere, non è qualcosa di abbastanza grande da passare a un'altra funzione, ad esempio liberare alcune variabili localmente (k) mallocate è un caso tipico.

Ho scritto codice che utilizzava setjmp / longjmp solo una volta. Era in un programma di sequencer di batteria MIDI. La riproduzione è avvenuta in un processo separato da tutte le interazioni dell'utente e il processo di riproduzione ha utilizzato la memoria condivisa con il processo dell'interfaccia utente per ottenere le informazioni limitate necessarie per eseguire la riproduzione. Quando l'utente voleva interrompere la riproduzione, il processo di riproduzione faceva semplicemente un lungo "inizio" per ricominciare da capo, piuttosto che qualche complicato svolgimento di qualsiasi cosa si verificasse mentre l'utente voleva che si fermasse. Funzionava alla grande, era semplice e in quel caso non ho mai avuto problemi o bachi ad esso correlati.

setjmp / longjmp hanno il loro posto - ma quel posto è uno che probabilmente non visiterai ma di tanto in tanto.

Modifica: ho appena guardato il codice. In realtà era il siglongjmp () che usavo, non il longjmp (non che fosse un grosso problema, ma avevo dimenticato che esisteva anche il siglongjmp.)


15

Se stai scrivendo una VM in C, si scopre che usando goto calcolate (gcc) in questo modo:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

funziona molto più velocemente dell'interruttore convenzionale all'interno di un loop.


L'unico problema, che non è standard, vero?
Calmarius,

&&op_inccertamente non si compila, perché (a sinistra) si &aspetta un valore, ma (a destra) &restituisce un valore.
Fredoverflow,

7
@FredO: è un operatore GCC speciale. Tuttavia, rifiuterei questo codice in tutte le circostanze tranne quelle più sporche, perché sicuramente non riesco a capire cosa stia succedendo.
Cucciolo

Mentre questo è un esempio (ottimizzazione massima per la velocità) che giustifica sia l'uso di goto che il codice criptico, dovrebbe comunque essere altamente commentato per spiegare la necessità di ottimizzazione, approssimativamente come funziona e il migliore riga per riga commenti che possono essere fatti. Quindi, fallisce ancora, ma perché lascia i programmatori di manutenzione al buio. Ma mi piace l'esempio della VM. Grazie.
MicroserviziDDDD

14

Perché gotopuò essere usato per confondere la metaprogrammazione

Gotoè un'espressione di controllo sia di alto livello che di basso livello e, di conseguenza, non ha un modello di progettazione appropriato adatto alla maggior parte dei problemi.

È di basso livello, nel senso che un goto è un'operazione primitiva che implementa qualcosa di più alto come whileo foreacho qualcosa.

È di alto livello, nel senso che quando viene usato in certi modi prende il codice che viene eseguito in una sequenza chiara, in modo ininterrotto, ad eccezione dei loop strutturati, e lo trasforma in pezzi di logica che sono, con abbastanza gotos, una presa -borsa di logica che viene riassemblata in modo dinamico.

Quindi, c'è un lato prosaico e malvagiogoto .

Il lato prosaico è che un goto che punta verso l'alto può implementare un loop perfettamente ragionevole e un goto che punta verso il basso può fare un perfettamente ragionevole breako return. Certo, un vero while, breako returnsarebbe molto più leggibile, in quanto il povero essere umano non dovrebbe simulare l'effetto del gotoper ottenere il quadro generale. Quindi, una cattiva idea in generale.

La parte malvagia implica una routine che non usa goto per while, break o return, ma la usa per quella che viene chiamata logica degli spaghetti . In questo caso lo sviluppatore goto-felice sta costruendo pezzi di codice da un labirinto di goto, e l'unico modo per capirlo è simularlo mentalmente nel suo insieme, un compito terribilmente faticoso quando ci sono molti goto. Voglio dire, immagina il problema di valutare il codice in cui il elsenon è esattamente un contrario di if, dove i nidificati ifpotrebbero consentire in alcune cose che sono state rifiutate dall'esterno if, ecc. Ecc.

Infine, per coprire davvero l'argomento, dovremmo notare che essenzialmente tutte le prime lingue tranne l'Algol inizialmente rendevano solo le singole dichiarazioni soggette alle loro versioni di if-then-else. Quindi, l'unico modo per fare un blocco condizionale era gotoaggirarlo usando un condizionale inverso. Insano, lo so, ma ho letto alcune vecchie specifiche. Ricorda che i primi computer sono stati programmati in codice binario di macchina, quindi suppongo che qualsiasi tipo di HLL sia stato un salvavita; Immagino che non fossero troppo esigenti riguardo esattamente a quali caratteristiche HLL avevano.

Dopo aver detto tutto ciò che usavo per inserirne uno gotoin ogni programma che ho scritto "solo per infastidire i puristi" .


4
+1 per irritare i puristi! :-)
Lumi,

10

Negare l'uso dell'istruzione GOTO ai programmatori è come dire a un falegname di non usare un martello perché potrebbe danneggiare il muro mentre sta martellando un chiodo. Un vero programmatore sa come e quando usare un GOTO. Ho seguito alcuni di questi cosiddetti "Programmi strutturati". Ho visto questo codice Horrid solo per evitare di usare un GOTO, che avrei potuto sparare al programmatore. Ok, in difesa dell'altra parte, ho visto anche del vero codice spaghetti, e anche quei programmatori dovrebbero essere sparati.

Ecco solo un piccolo esempio di codice che ho trovato.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------O----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

3
DO CRT 'È corretto? (S / N): ': INPUT YORN FINO A YORN =' Y 'O YORN =' N '; ecc.
joel.neely,

5
Anzi, ma soprattutto, un vero programmatore sa quando non usare un goto- e sa perché . Evitare un costrutto linguistico tabù perché lo ha detto $ Programming_guru, questa è la vera definizione della programmazione cargo-cult.
Piskvor lasciò l'edificio il

1
È una buona analogia. Per guidare un chiodo senza danneggiare il substrato, non eliminare il martello. Piuttosto, usi un semplice strumento noto come un pugno di chiodi. Questo è un perno metallico con un'estremità affusolata che ha una rientranza cava sulla punta, per accoppiarsi saldamente con la testa del chiodo (questi strumenti sono disponibili in diverse dimensioni per diversi chiodi). L'altra estremità smussata del punzone per unghie è colpita da un martello.
Kaz,


7

Il documento originale dovrebbe essere considerato come "GOTO incondizionato considerato dannoso". In particolare, sosteneva una forma di programmazione basata su costrutti condizionali ( if) e iterativi ( while), piuttosto che sul test-and-jump comune al codice iniziale. gotoè ancora utile in alcune lingue o circostanze, dove non esiste una struttura di controllo adeguata.


6

L'unico posto in cui concordo sul fatto che Goto possa essere utilizzato è quando è necessario gestire gli errori e ogni particolare punto in cui si verifica un errore richiede una gestione speciale.

Ad esempio, se stai afferrando risorse e stai usando semafori o mutex, devi afferrarle in ordine e dovresti sempre rilasciarle in modo opposto.

Alcuni codici richiedono uno schema molto strano di acquisizione di queste risorse e non si può semplicemente scrivere una struttura di controllo facilmente gestibile e comprensibile per gestire correttamente sia l'acquisizione che il rilascio di queste risorse per evitare deadlock.

È sempre possibile farlo bene senza goto, ma in questo caso e pochi altri Goto è in realtà la soluzione migliore principalmente per leggibilità e manutenibilità.

-Adamo


5

Un moderno utilizzo di GOTO è il compilatore C # per creare macchine a stati per gli enumeratori definiti dal rendimento.

GOTO è qualcosa che dovrebbe essere usato dai compilatori e non dai programmatori.


5
Chi pensi che crei esattamente i compilatori?
comunicare il

10
Compilatori, ovviamente!
Seiti,

3
Penso che significhi "GOTO è qualcosa che dovrebbe essere usato solo dal codice emesso da un compilatore".
Simon Buchan,

Questo è un caso contrario goto. Dove potremmo usare gotoin una macchina a stati codificata a mano per implementare un enumeratore, possiamo semplicemente usare yieldinvece.
Jon Hanna,

attiva molte stringhe (per impedire la compilazione in if-else) con compilazioni di casi predefinite per passare all'istruzione goto.
M.kazem Akhgary,

5

Fino a quando C e C ++ (tra gli altri colpevoli) avranno etichettato le interruzioni e continuerà, goto continuerà ad avere un ruolo.


Quindi etichettare break o continue sarebbe diverso da goto come?
Matthew Whited,

3
Non consentono salti totalmente arbitrari nel flusso di controllo.
DrPizza,

5

Se GOTO stesso fosse malvagio, i compilatori sarebbero malvagi, perché generano JMP. Se saltare in un blocco di codice, specialmente seguendo un puntatore, fosse intrinsecamente malvagio, l'istruzione RETurn sarebbe malvagia. Piuttosto, il male è in potenziale abuso.

A volte ho dovuto scrivere app che dovevano tenere traccia di un numero di oggetti in cui ogni oggetto doveva seguire una sequenza intricata di stati in risposta agli eventi, ma il tutto era decisamente single-thread. Una sequenza tipica di stati, se rappresentata in pseudo-codice sarebbe:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Sono sicuro che questo non è nuovo, ma il modo in cui l'ho gestito in C (++) è stato quello di definire alcune macro:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Quindi (supponendo che lo stato sia inizialmente 0) la macchina a stati strutturati sopra si trasforma nel codice strutturato:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

Con una variazione su questo, ci possono essere CALL e RETURN, quindi alcune macchine a stati possono agire come subroutine di altre macchine a stati.

È insolito? Sì. Ci vuole un po 'di apprendimento da parte del manutentore? Sì. Questo apprendimento ripaga? Credo di si. Potrebbe essere fatto senza GOTO che saltano in blocchi? No.


1
Evitare una caratteristica della lingua per paura è un segno di danno cerebrale. Anche questo è più bello dell'inferno.
Vector Gorgoth,

4

Lo evito dal momento che un collega / manager metterà sicuramente in dubbio il suo utilizzo o in una revisione del codice o quando si imbattono in esso. Mentre penso che abbia degli usi (ad esempio il caso della gestione degli errori), ti scontrerai con qualche altro sviluppatore che avrà qualche tipo di problema.

Non ne vale la pena.


La cosa bella di Try ... I blocchi di cattura in C # è che si occupano di ripulire lo stack e altre risorse allocate (chiamate svolgendo lo stack) mentre l'eccezione si trasforma in un gestore di eccezioni. Questo rende Try ... Catch molto meglio di Goto, quindi se hai Try ... Catch, usalo.
Scott Whitlock,

Ho una soluzione migliore: racchiudi il pezzo di codice che vuoi estrarre in un 'do {...} while (0);' ciclo continuo. In questo modo, puoi saltare allo stesso modo di un goto senza l'overhead del ciclo try / catch (non so su C #, ma in C ++ provare è a basso costo e catch è ad alto costo, quindi sembra eccessivo per lanciare un'eccezione in cui sarebbe sufficiente un semplice salto).
Jim Dovey,

10
Jim, il problema è che non è altro che un modo stupidamente rotondo di ottenere un goto.
Coding With Style

4

In realtà mi sono trovato costretto a usare un goto, perché letteralmente non riuscivo a pensare a un modo migliore (più veloce) per scrivere questo codice:

Avevo un oggetto complesso e avevo bisogno di fare qualche operazione su di esso. Se l'oggetto fosse in uno stato, allora potrei fare una versione rapida dell'operazione, altrimenti dovrei fare una versione lenta dell'operazione. Il fatto era che in alcuni casi, nel mezzo dell'operazione lenta, era possibile rendersi conto che ciò avrebbe potuto essere fatto con l'operazione rapida.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

Questo era in un pezzo di codice dell'interfaccia utente in tempo reale critico per la velocità, quindi sinceramente penso che un GOTO fosse giustificato qui.

Hugo


Il modo non GOTO sarebbe quello di utilizzare una funzione fast_calculations, che comporta un certo sovraccarico. Probabilmente non si nota nella maggior parte dei casi, ma come hai detto, questo era critico per la velocità.
Kyle Cronin,

1
Beh, non è sorprendente. Tutto il codice che ha linee guida sulle prestazioni assolutamente folli infrange sempre praticamente tutte le migliori pratiche. Le migliori pratiche sono per la raggiungibilità e la manutenibilità, non per spremere altri 10 millisecondi o risparmiare 5 byte in più di RAM.
Jonathon

1
@JonathonWisnoski, gli usi legittimi di goto eliminano anche quantità insane di codice spagetti che si intromettono con i nidi di variabili di un topo per tenere traccia di dove stiamo andando.
vonbrand,

4

Quasi tutte le situazioni in cui un goto può essere usato, puoi fare lo stesso usando altri costrutti. Goto viene comunque utilizzato dal compilatore.

Personalmente non lo uso mai esplicitamente, non ne ho mai bisogno.


4

Una cosa che non ho visto da nessuna delle risposte qui è che una soluzione "goto" è spesso più efficiente di una delle soluzioni di programmazione strutturata spesso menzionate.

Considera il caso di molti cicli annidati, in cui l'uso di 'goto' invece di un mucchio di if(breakVariable)sezioni è ovviamente più efficiente. La soluzione "Metti i tuoi loop in una funzione e usa return" è spesso totalmente irragionevole. Nel caso probabile che i loop utilizzino variabili locali, ora è necessario passarli tutti attraverso i parametri delle funzioni, gestendo potenzialmente carichi di ulteriori mal di testa che ne derivano.

Consideriamo ora il caso di pulizia, che mi sono usato abbastanza spesso, ed è così comune da presumibilmente essere stato responsabile della struttura try {} catch {} non disponibile in molte lingue. Il numero di controlli e variabili extra che sono necessari per realizzare la stessa cosa sono molto peggiori delle una o due istruzioni per fare il salto e, di nuovo, la soluzione di funzione aggiuntiva non è affatto una soluzione. Non puoi dirmi che è più gestibile o più leggibile.

Ora lo spazio del codice, l'utilizzo dello stack e i tempi di esecuzione potrebbero non essere abbastanza sufficienti in molte situazioni per molti programmatori, ma quando ci si trova in un ambiente incorporato con solo 2 KB di spazio di codice con cui lavorare, 50 byte di istruzioni extra per evitare uno chiaramente definito 'goto' è semplicemente ridicolo, e questa non è una situazione così rara come credono molti programmatori di alto livello.

L'affermazione che "goto è dannoso" è stata molto utile per passare alla programmazione strutturata, anche se è sempre stata un'eccessiva generalizzazione. A questo punto, l'abbiamo sentito tutti abbastanza da diffidare di usarlo (come dovremmo). Quando è ovviamente lo strumento giusto per il lavoro, non dobbiamo averne paura.


3

Puoi usarlo per rompere da un ciclo profondamente annidato, ma il più delle volte il tuo codice può essere refactored per essere più pulito senza cicli profondamente annidati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.