Abbiamo ancora un caso contro l'istruzione goto? [duplicare]

26

In un articolo recente , Andrew Koenig scrive:

When asked why goto statements are harmful, most programmers will say something like "because they make programs hard to understand." Press harder, and you may well hear something like "I don't really know, but that's what I was taught." For that reason, I'd like to summarize Dijkstra's arguments.

Quindi mostra due frammenti di programma, uno senza goto e uno con goto :

if (n < 0)
    n = 0;

Assuming that n is a variable of a built-in numeric type, we know that after this code, n is nonnegative.

Suppose we rewrite this fragment:

if (n >= 0) goto nonneg;
n = 0;
nonneg: ;

In theory, this rewrite should have the same effect as the original. However, rewriting has changed something important: It has opened the possibility of transferring control to nonneg from anywhere else in the program.

Ho sottolineato la parte con cui non sono d'accordo. I linguaggi moderni come C ++ fanno non consentono a goto di trasferire il controllo arbitrariamente. Ecco due esempi:

  1. Non puoi saltare a un'etichetta definita in una funzione diversa.
  2. Non puoi saltare l'inizializzazione della variabile.

Ora considera di comporre il tuo codice di minuscole funzioni che aderiscono al principio di responsabilità singola:

int clamp_to_zero(int n)
{
    if (n >= 0) goto n_is_not_negative:
    n = 0;
n_is_not_negative:
    return n;
}

L'argomento classico contro l'istruzione goto è che il controllo potrebbe essere trasferito da ovunque all'interno del tuo programma all'etichetta n_is_not_negative , ma questo semplicemente non è (e non è mai stato) vero in C ++ . Se lo provi, riceverai un errore del compilatore, perché le etichette hanno un ambito. Il resto del programma non vede nemmeno il nome n_is_not_negative , quindi non è possibile saltare lì. Questa è una garanzia statica!

Ora, non sto dicendo che questa versione sia migliore allora quella senza goto , ma per rendere quest'ultima espressiva come la prima, dovremmo almeno inserire un commento, o ancora meglio, un'affermazione:

int clamp_to_zero(int n)
{
    if (n < 0)
        n = 0;
    // n is not negative at this point
    assert(n >= 0);
    return n;    
}

Nota che in pratica ottieni l'asserzione gratuita nella versione goto , perché la condizione n >= 0 è già scritta nella riga 1 e n = 0; soddisfa banalmente la condizione. Ma questa è solo un'osservazione casuale.

Mi sembra che "non usare goto s!" è uno di quei dogmi come "non usare più return s!" che derivano da un momento in cui il problema reale erano funzioni di centinaia o anche migliaia di righe di codice.

Quindi, abbiamo ancora un caso contro l'istruzione goto, a parte il fatto che non è particolarmente utile? Non ho scritto un goto in almeno un decennio, ma non è che stavo scappando terrorizzato ogni volta che ne ho incontrato uno. 1 Idealmente, mi piacerebbe vedere un argomento strong e valido contro goto s che ancora regge quando si aderiscono ai principi di programmazione stabiliti per il codice pulito come l'SRP. "Puoi saltare ovunque" non è (e non è mai stato) un argomento valido in C ++, e in qualche modo non mi piace insegnare cose che non sono vere.

1: Inoltre, non sono mai stato in grado di resuscitare nemmeno un singolo velociraptor , indipendentemente da quanti goto Ho provato: (

    
posta fredoverflow 17.12.2011 - 09:32
fonte

10 risposte

32

I problemi di Goto sono non . Non sono mai stati. Il problema sono i programmatori che usano goto in modo tale da rendere il codice estremamente difficile da seguire e, eventualmente, posizionare il codice in uno stato indeterminato (ricorda, non tutte le lingue saranno rigorose come lo è C ++). Goto's è solo la pistola - ma il programmatore è quello che lo punta ai suoi piedi e preme il grilletto.

In passato ho usato goto in occasioni in cui ho concluso che era la migliore linea d'azione (o, più spesso, il corso meno rischioso). Non c'è niente di sbagliato nel farlo finché sei consapevole delle possibili conseguenze. Ma i nuovi programmatori non hanno necessariamente quel senso e possono fare un gran casino di cose. Quindi sospetto sia molto più semplice insegnare loro a non usare mai gotos.

    
risposta data 17.12.2011 - 09:59
fonte
20

Ideally, I would like to see a strong and valid argument against gotos that still holds when you adhere to established programming principles for clean code

goto può essere utilizzato al posto di if , a switch , for , while , break , return e molte, molte altre caratteristiche del linguaggio che servono tutte diverse scopi. Ma c'è una ragione per cui i linguaggi hanno acquisito così tanti costrutti di ramificazione e di looping: rendono più facile capire cosa fa il codice. L'utilizzo di goto abbandona questa importante caratteristica e ti lascia con il codice simile ad Assembler da decifrare.

Un goto è quindi inferiore a tutti quei costrutti del linguaggio strutturato.

Ho seguito questo dibattito di goto per quasi due decenni, e in C ++ non ho ancora visto un pezzo di codice che potrebbe essere reso più facile da comprendere applicando un goto , che non sarebbe nemmeno più facile da capire applicando RAII, introducendo funzioni aggiuntive (in linea) o usando uno dei costrutti sopra menzionati. (È diverso per C, che non ha RAII.)

    
risposta data 17.12.2011 - 12:21
fonte
14

IMHO

if (n < 0)
  n = 0;

rende perfettamente chiaro che dopo questo, n >= 0 , quindi un commento che spiega questo violerebbe il principio di DRY e un'asserzione che considererei assolutamente sciocca o confusa: l'autore avrebbe potuto affrontare qualche strano bug di compilatore / ottimizzazione in questo punto ???

Considero la variante con goto più difficile da capire rispetto a if . Questo ovviamente può essere semplicemente l'effetto di non essere abituati a vedere goto s nel codice; ma poi di nuovo, la maggior parte dei miei colleghi (attuali e futuri) non lo sono, quindi probabilmente si sentono allo stesso modo. Pertanto, anche se mi fossi abituato a goto , renderebbe il codice più difficile da mantenere a lungo termine.

It seems to me that "don't use gotos!" is one of those dogmas like "don't use multiple returns!" that stem from a time where the real problem were functions of hundreds or even thousand of lines of code.

Tutti i programmi legacy che ho visto finora contengono molte, molte funzioni di centinaia (o talvolta anche migliaia) di linee di codice. Quindi sono molto felice che almeno non contengano goto s :-) Hai ragione che in un piccolo metodo pulito goto non può creare un grosso problema; tuttavia, se provi a disegnare una linea fuzzy come "puoi usare goto nelle funzioni più corte di n lines", verrà inevitabilmente usata da sviluppatori "intelligenti". Per non parlare del fatto che le funzioni tendono a crescere nel tempo; cosa fai quando la tua funzione originariamente breve e pulita gonfia per raddoppiarne le dimensioni? Rimuova il goto s poi, o refactoring? Il tuo successore dopo qualche anno eliminerà il goto s, o anche il refactor del codice? ... OTOH La regola di Dijkstra è chiara e ha molto meno potenziale da abusare.

Ultimo ma non meno importante, il nome della tua funzione non esprime il suo intento; rinominandolo ad es. make_non_negative non lascerebbe dubbi su ciò che fa.

YMMV.

    
risposta data 17.12.2011 - 09:48
fonte
11

C'è niente sbagliato nell'usare correttamente goto. Ad esempio:

for(int i = 0; i <= 10; ++i)
{
   for(int j = 0; j <= 10; ++j)
   {
      // ..
      if(condition)
        goto end;
      // ..
   }
}

end:

Il problema sta usando goto in un modo che non ha senso come nel tuo esempio:

if (n >= 0) goto nonneg;
n = 0;
nonneg: ;

Non l'ho mai visto in codice reale (e ho visto un sacco di cose sgradevoli) quindi non so cosa ci sia di tanto in ballo e odio.

Utilizzare goto in questo modo equivale a:

for(int i = 0; n < 0 && i < 1; ++i)
    n = -5;

invece di

if(n < 0)
    n = -5;

Questo non significa che for sia cattivo, proprio come goto non lo è.

    
risposta data 17.12.2011 - 13:56
fonte
7

I tuoi esempi non sono molto convincenti. Ci sono molti altri casi in cui goto è molto meglio di qualsiasi altra opzione.

Uno sta implementando macchine virtuali con l'estensione goto calcolata in GCC (vedi interprete bytecode OCaml ad esempio).

Un altro caso (molto ampio) è la generazione del codice. Ad esempio, è molto più efficiente (e molto più facile da comprendere) generare un codice parser Packrat da un livello superiore, PEG dichiarativo, usando goto per uscire dai rami di analisi non riusciti. Se la tua lingua può essere utilizzata come target per la generazione di codice o se è un linguaggio meta che consente di implementare nuove funzionalità linguistiche, goto è un must.

E un terzo caso-stato degli automi. Qui, goto è una rappresentazione naturale di un concetto di spostamento da uno stato a un altro e quindi un'implementazione basata su goto è molto più leggibile di qualsiasi altra cosa.

Ma sfortunatamente a molti programmatori è stato insegnato un odio quasi religioso e la paura del goto , e per questo motivo nessuno degli argomenti di cui sopra è abbastanza convincente per questo lotto.

    
risposta data 17.12.2011 - 15:37
fonte
4

È già stato detto che cose come if e while e for sono semplicemente di fantasia goto s (anche se ben strutturate e comprensibili).

Ma la cosa interessante è che molti goto s appaiono dove idealmente useremmo qualcosa come return . @ La risposta di Krelp è un buon esempio:

... start of big function
for(int i = 0; i <= 10; ++i)
{
   for(int j = 0; j <= 10; ++j)
   {
      // ..
      if(condition)
        goto end;
      // ..
   }
}    
end:
... rest of big function

Nell'occasione molto rara ho usato goto (temporaneamente) in cui mi sarebbe piaciuto usare le funzioni annidate leggere. Sarebbe bello poter inserire questi cicli di for all'interno di una funzione annidata e sostituire goto con return .

void big_function() {
    ... start of big function
    // next few lines declare a nested function
    void nested_function() { // shall be permitted access to the locals in the parent function
        for(int i = 0; i <= 10; ++i)
        {
           for(int j = 0; j <= 10; ++j)
           {
              // ..
              if(condition)
                return;
              // ..
           }
        }    
    }
    nested_function(); // actually call it
    ... rest of big function
}

Mi piacerebbe davvero vedere questo genere di cose in C e C ++, forse Lambdas lo permetterà. In effetti, forse le funzioni globali dovrebbero essere deprecate e tutto dovrebbe essere visto come un lambda! (PS: alcuni compilatori C non consentono già qualcosa del genere?)

    
risposta data 17.12.2011 - 14:24
fonte
4

Quindi dobbiamo considerare gli argomenti del grande uomo. Vedi una trascrizione del suo documento originale per riferimenti.

My first remark is that, although the programmer’s activity ends when he has constructed a correct program, the process taking place under control of his program is the true subject matter of his activity, for it is this process that has to effectuate the desired effect, it is this process that in its dynamic behaviour has to satisfy the desired specifications. Yet, once the program has been made, the “making” of the corresponding process is delegated to the machine.

Quindi è il tempo di esecuzione che è importantissimo.

My second remark is that our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed. For that reason we should do (as wise programmers aware of our limitations) our utmost best to shorten the conceptual gap between the static program and the dynamic process, to make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible.

(la mia enfasi)

Più in particolare, il nostro obiettivo è garantire che la nostra immagine concettuale del programma in esecuzione corrisponda al effettivo per quanto possibile .

E infine

... The unbridled use of the go to statement has as an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress. ...

Secondo me nulla è cambiato e non cambierà mai. goto fa rende più difficile descrivere l'avanzamento del processo . E poiché è stato anche provato che qualsiasi algoritmo che utilizza goto può essere riscritto per non usarlo, il mio caso si riposa.

Solo perché puoi usare goto in un modo meno dannoso che non viola troppe regole non significa che dovrebbe usa goto . Dijkstra aveva ragione, ed è ancora, devi massimizzare la semplicità del tuo codice. Abbastanza semplice non è accettabile.

    
risposta data 17.12.2011 - 14:45
fonte
0

Il tuo esempio è vero solo in funzioni banali, dove posso vedere tutto in una volta. Nelle funzioni più lunghe, non è così banale.

La dichiarazione if garantisce che non ci siano controlli di controllo nell'ambito di una dichiarazione . Ho solo bisogno di comprendere quella frase per sapere che non succede nulla di divertente. L'istruzione goto non garantisce alcun controllo di controllo nell'ambito di una funzione . Pertanto, è intrinseco che goto sia più difficile da leggere di if .

    
risposta data 17.12.2011 - 14:40
fonte
0

Sto usando linee guida per la codifica che permettono a goto (in Pascal: GOTO exit;) di abbandonare una procedura o una funzione in caso di qualche grave errore, o quando nessun'altra elaborazione è possibile per qualsiasi motivo. Solo per saltare alla fine di una routine, ripulire parte del disordine (liberare memoria, sbloccare risorse) e quindi uscire. Infatti, quando si include una procedura o un modello di funzione nell'origine per codificare una nuova routine, l'etichetta "exit" viene dichiarata per impostazione predefinita.

Informazioni sui rendimenti multipli: sono favorevole all'utilizzo di una variabile locale per assegnare il valore di ritorno a. Questo può essere fatto su più posizioni. Alla fine della funzione, è richiesto un solo ritorno. Insieme a goto, questo può rendere il codice più leggibile, perché non sono necessarie tante istruzioni IF annidate per arrivare alla fine della routine.

Tuttavia, ci saranno sempre persone che ti dicono di non usarlo, ma quelle sono nella maggior parte dei casi quelle che non hanno bisogno di mantenere il codice.

    
risposta data 17.12.2011 - 15:30
fonte
-1

C'è una semplice ragione per cui i goto non sono necessari. È perché i numeri di riga non sono più disponibili. Il motivo originale per aggiungere istruzioni goto al linguaggio di programmazione di base erano i numeri di riga. Il problema era che i numeri di riga erano fissi e non si potevano cambiare i numeri. Goto era necessario quando si esauriscono i numeri di linea disponibili. Riempi tutte le righe da 1 a 10 e aggiungendo una nuova riga all'interno di tale intervallo è necessario sostituire la riga 7 con l'istruzione goto alla riga 330, spostando la vecchia linea 7 alla riga 330, quindi scrivendo un codice a 331-380 e quindi aggiungendo goto da 380 alla riga 8. Ora se hai migliaia di blocchi come questo, il programma sarà un po 'orribile e difficile da seguire.

    
risposta data 17.12.2011 - 14:05
fonte

Leggi altre domande sui tag