Contesto storico: Dovremmo ricordare che Dijkstra ha scritto Goto Considered Harmful nel 1968, quando molti programmatori usavano goto
in sostituzione della programmazione strutturata (if
, while
, for
, eccetera.).
Sono passati 44 anni ed è raro trovare questo uso di goto
nella natura selvaggia. La programmazione strutturata ha già vinto, molto tempo fa.
Analisi del caso:
Il codice di esempio ha questo aspetto:
SETUP...
again:
COMPUTE SOME VALUES...
if (cmpxchg64(ptr, old_val, val) != old_val)
goto again;
La versione strutturata ha questo aspetto:
SETUP...
do {
COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);
Quando guardo la versione strutturata, penso subito "è un loop". Quando guardo il goto
versione, la considero come una linea retta con un caso "riprova" alla fine.
Il goto
versione ha entrambi SETUP
e COMPUTE SOME VALUES
sulla stessa colonna, il che sottolinea che la maggior parte delle volte il flusso di controllo passa attraverso entrambi. La versione strutturata mette SETUP
e COMPUTE SOME VALUES
su colonne diverse, il che sottolinea che il controllo può passare attraverso di esse in modo diverso.
La domanda qui è che tipo di enfasi vuoi mettere nel codice? Puoi confrontarlo con goto
per la gestione degli errori:
Versione strutturata:
if (do_something() != ERR) {
if (do_something2() != ERR) {
if (do_something3() != ERR) {
if (do_something4() != ERR) {
...
Vai alla versione:
if (do_something() == ERR) // Straight line
goto error; // |
if (do_something2() == ERR) // |
goto error; // |
if (do_something3() == ERR) // |
goto error; // V
if (do_something4() == ERR) // emphasizes normal control flow
goto error;
Il codice generato è fondamentalmente lo stesso, quindi possiamo pensarlo come un problema tipografico, come l'indentazione.
Ottima domanda, e penso che solo l'autore (i) possa fornire una risposta definitiva. Aggiungerò la mia piccola speculazione dicendo che avrebbe potuto iniziare usandolo per la gestione degli errori, come spiegato da @Izkata e le porte erano quindi aperte per usarlo anche per i loop di base.
L'utilizzo della gestione degli errori è legittimo nella programmazione dei sistemi, secondo me. Una funzione alloca gradualmente la memoria man mano che procede e, se si verifica un errore, goto
l'etichetta appropriata per liberare le risorse in ordine inverso da quel punto.
Quindi, se l'errore si verifica dopo la prima allocazione, passerà all'ultima etichetta di errore, per liberare solo una risorsa. Allo stesso modo, se l'errore si verifica dopo l'ultima allocazione, passerà alla prima etichetta di errore e verrà eseguito da lì, liberando tutte le risorse fino alla fine della funzione. Tale modello per la gestione degli errori deve ancora essere utilizzato con attenzione, specialmente quando si consiglia di modificare il codice, valgrind e unit test. Ma è probabilmente più leggibile e gestibile rispetto agli approcci alternativi.
Una regola d'oro per usare goto
è quello di evitare il cosiddetto codice spaghetti. Prova a tracciare linee tra ogni goto
dichiarazione e la rispettiva etichetta. Se hai attraversato le linee, beh, hai attraversato una linea :). Tale uso di goto
è molto difficile da leggere e una fonte comune di bug difficili da rintracciare, poiché si troverebbero in linguaggi come BASIC che si basavano su di esso per il controllo del flusso.
Se fai solo un semplice loop non otterrai linee incrociate, quindi è ancora leggibile e gestibile, diventando in gran parte una questione di stile. Detto questo, poiché possono essere eseguiti altrettanto facilmente con le parole chiave del ciclo fornite dalla lingua, come hai indicato nella tua domanda, la mia raccomandazione sarebbe comunque quella di evitare di utilizzare goto
for loop, semplicemente perché il for
, do/while
o while
i costrutti sono più eleganti per design.
Nel caso di questo esempio, sospetto che si trattasse di adattare il supporto SMP a un codice originariamente scritto in modo non sicuro per SMP. Aggiunta di un goto again;
path è molto più semplice e meno invasivo rispetto alla ristrutturazione della funzione.
Non posso dire che mi piaccia molto questo stile, ma penso anche che sia fuorviante evitare goto
per ragioni ideologiche. Un caso speciale di goto
usage (diverso da questo esempio) è dove goto
viene utilizzato solo per andare avanti in una funzione, mai indietro. Questa classe di usi non risulta mai in costrutti di loop derivanti da goto
ed è quasi sempre il modo più semplice e chiaro per implementare il comportamento necessario (che di solito consiste nel ripulire e restituire in caso di errore).