Ciò giustifica le dichiarazioni goto?


15

Mi sono imbattuto in questa domanda un secondo fa e sto estraendo parte del materiale da lì: esiste un nome per il costrutto "break n"?

Questo sembra essere un modo inutilmente complesso per le persone di dover istruire il programma a uscire da un doppio ciclo annidato:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

So che i libri di testo amano dire che le dichiarazioni goto sono il diavolo, e non mi piacciono affatto, ma ciò giustifica un'eccezione alla regola?

Sto cercando risposte che si occupano di n-nested per loop.

NOTA: Sia che tu risponda sì, no, o in qualche punto intermedio, le risposte completamente chiuse non sono benvenute. Soprattutto se la risposta è no, allora fornisci un motivo valido e legittimo (che non è comunque troppo lontano dai regolamenti di Stack Exchange).


2
Se riesci a scrivere la condizione del ciclo interno come for (j = 10; j > 0 && !broken; j--)allora non avresti bisogno break;dell'istruzione. Se sposti la dichiarazione di brokenprima dell'inizio del ciclo esterno, allora se ciò potesse essere scritto come for (i = 0; i < 10 && !broken; i++)allora penso che non break; sia necessario.
FrustratedWithFormsDesigner,

8
Dato l'esempio proviene da C # - il riferimento C # su goto: goto (Riferimenti per C #) - "L'istruzione goto è utile anche per uscire da loop profondamente nidificati".

OMG, non ho mai saputo che c # avesse una dichiarazione goto! Le cose che impari su StackExchange. (Non ricordo l'ultima volta che ho desiderato una capacità di dichiarazione goto, non sembra mai venir fuori).
John Wu,

6
IMHO dipende davvero dalla tua "religione di programmazione" e dalla fase lunare attuale. Per me (e la maggior parte delle persone intorno a me) va bene usare un goto per uscire da nidi profondi se il loop è piuttosto piccolo, e l'alternativa sarebbe un controllo superfluo di una variabile bool introdotta solo per interrompere la condizione del loop. Questo ancora di più quando vuoi sfondare a metà del corpo o quando dopo il ciclo più interno c'è del codice che dovrebbe controllare anche il bool.
PlasmaHH,

1
@JohnWu gotoè effettivamente richiesto in uno switch C # per passare a un altro caso dopo aver eseguito qualsiasi codice all'interno del primo caso. Questo è l'unico caso che ho usato goto, però.
Dan Lyons,

Risposte:


33

L'apparente necessità di una dichiarazione di riferimento nasce da te che hai scelto espressioni condizionali povere per i loop .

Afferma di volere che il ciclo esterno continui per tutto il tempo i < 10e quello più interno continui per tutto il tempo j > 0.

Ma in realtà non è quello che volevi , semplicemente non hai detto ai loop la vera condizione che volevi che valutassero, quindi vuoi risolverlo usando break o goto.

Se dici ai loop le tue vere intenzioni per cominciare , allora non c'è bisogno di pause o goto.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

Vedi i miei commenti sulle altre risposte. Il codice originale viene stampato ialla fine del ciclo esterno, quindi cortocircuitare l'incremento è importante per rendere il codice equivalente al 100%. Non sto dicendo che qualcosa nella tua risposta sia errato, volevo solo sottolineare che interrompere immediatamente il loop era un aspetto importante del codice.
pswg

1
@pswg Non vedo alcun codice stampare ialla fine del ciclo esterno nella domanda di OP.
Tulains Córdova,

OP ha fatto riferimento al codice dalla mia domanda qui , ma ha lasciato fuori quella parte. Non ti ho votato, BTW. La risposta è del tutto corretta per quanto riguarda la scelta corretta delle condizioni del loop.
pswg

3
@pswg Immagino che se davvero vuoi davvero giustificare un goto, puoi risolvere un problema che ha bisogno di uno, d'altra parte, sto programmando professionalmente da due decenni e non ho un singolo problema del mondo reale che ha bisogno uno.
Tulains Córdova,

1
Lo scopo del codice non era fornire un caso d'uso valido per goto(sono sicuro che ce ne sono di migliori), anche se ha generato una domanda che sembra usarlo come tale, ma per illustrare l'azione di base break(n)in linguaggi che non hanno non supportarlo.
pswg

34

In questo caso, è possibile trasformare il codice in una routine separata e quindi solo returndal ciclo interno. Ciò negherebbe la necessità di uscire dal circuito esterno:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

2
Vale a dire che sì, quello che mostri è un modo inutilmente complesso per rompere da loop nidificati, ma no, c'è un modo per refactoring che non rende un goto più attraente.
Roger,

2
Solo una nota a margine: nel codice originale stampa idopo la fine del ciclo esterno. Quindi, per riflettere davvero questo iè il valore importante, qui dovrebbe return true;e return false;dovrebbero essere entrambi return i;.
pswg

+1, stavo per pubblicare questa risposta e credo che questa dovrebbe essere la risposta accettata. Non posso credere che la risposta accettata sia stata accettata perché quel metodo funziona solo per un sottoinsieme specifico di algoritmi di loop nidificati e non rende necessariamente il codice molto più pulito. Il metodo in questa risposta funziona generalmente.
Ben Lee,

6

No, non credo gotosia necessario. Se lo scrivi in ​​questo modo:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Avere &&!brokensu entrambi i loop ti consente di uscire da entrambi i loop quando i==j.

Per me, questo approccio è preferibile. Le condizioni di loop sono estremamente chiari: i<10 && !broken.

Una versione funzionante è disponibile qui: http://rextester.com/KFMH48414

Codice:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Produzione:

il ciclo si interrompe quando i == j == 1
2
0

Perché anche utilizzare brokennel vostro primo esempio a tutti ? Basta usare la pausa sull'anello interno. Inoltre, se devi assolutamente usarlo broken, perché dichiararlo al di fuori dei loop? Dichiaralo all'interno del iloop. Per quanto riguarda il whileloop, per favore non usarlo. Sinceramente penso che riesca a essere ancora meno leggibile rispetto alla versione goto.
luiscubal,

@luiscubal: brokenviene utilizzata una variabile denominata perché non mi piace usare l' breakistruzione (tranne nel caso di switch, ma questo è tutto). Viene dichiarato all'esterno del loop esterno nel caso in cui si desideri interrompere TUTTI i loop quando i==j. Hai ragione, in generale, brokendeve solo essere dichiarato prima del ciclo più esterno che si interromperà.
FrustratedWithFormsDesigner,

Nel tuo aggiornamento, i==2alla fine del ciclo. La condizione di successo dovrebbe anche cortocircuitare l'incremento se deve essere equivalente al 100% a break 2.
pswg,

2
Personalmente preferisco broken = (j == i)piuttosto che if e, se il linguaggio lo consente, inserendo la dichiarazione spezzata all'interno dell'inizializzazione del ciclo esterno. Anche se è difficile dire queste cose per questo esempio specifico, poiché l'intero ciclo è un no-op (non restituisce nulla e non ha effetti collaterali) e se fa qualcosa, probabilmente lo fa all'interno dell'if (anche se mi piace ancora di broken = (j ==i); if broken { something(); }più personalmente)
Martijn,

@Martijn Nel mio codice originale viene stampato idopo la fine del ciclo esterno. In altre parole, l'unica cosa veramente importante è quando esattamente esce dal circuito esterno.
pswg

3

La risposta breve è "dipende". Consiglio di scegliere la leggibilità e la comprensibilità del codice. Il modo di andare in goto potrebbe essere più leggibile che modificare la condizione, o viceversa. Potresti anche prendere in considerazione il principio della minima sorpresa, le linee guida utilizzate nel negozio / progetto e la coerenza della base di codice. Ad esempio, goto per lasciare switch o per la gestione degli errori è una pratica comune nella base di codice del kernel Linux. Nota anche che alcuni programmatori potrebbero avere problemi a leggere il tuo codice (o sollevato con il mantra "goto is evil", o semplicemente non appreso perché il loro insegnante la pensa così) e alcuni di loro potrebbero persino "giudicarti".

In generale, è spesso accettabile andare avanti nella stessa funzione, soprattutto se il salto non è troppo lungo. Il tuo caso d'uso di lasciare un ciclo annidato è uno dei noti esempi di goto "non così malvagio".


2

Nel tuo caso, goto è abbastanza un miglioramento che sembra allettante, ma non è tanto un miglioramento che vale la pena infrangere la regola contro il loro utilizzo nella tua base di codice.

Pensa al tuo futuro recensore: dovrà pensare: "Questa persona è un cattivo programmatore o è in realtà un caso in cui valeva la pena andare al goto? Lasciami prendere 5 minuti per decidere da solo o ricercarlo sul Internet." Perché mai lo faresti al tuo programmatore futuro quando non puoi usare goto interamente?

In generale questa mentalità si applica a tutto il codice "cattivo" e persino lo definisce: se funziona ora, ed è buono in questo caso, ma crea uno sforzo per verificare che sia corretto, cosa che in questo periodo un goto farà sempre, quindi non farlo fallo.

Detto questo, sì, hai prodotto uno dei casi leggermente più spinosi. Potresti:

  • utilizzare strutture di controllo più complesse, come una bandiera booleana, per uscire da entrambi i circuiti
  • inserisci il tuo ciclo interno nella sua routine (probabilmente ideale)
  • metti entrambi i loop in una routine e ritorna invece di rompere

È molto difficile per me creare un caso in cui un doppio loop non è così coeso che non dovrebbe comunque essere la sua routine.

A proposito, la migliore discussione di questo che ho visto è il Codice completo di Steve McConnell . Se ti piace pensare criticamente a questi problemi, ti consiglio vivamente di leggere, anche da copertina a copertina nonostante la sua immensità.


1
I nostri programmatori di lavoro leggono e comprendono il codice. Non solo codice semplicistico, a meno che questa non sia la natura della tua base di codice. Ci sono, e ci saranno sempre eccezioni alla generalità (non alla regola) contro l'uso di goto a causa del costo compreso. Ogni volta che il vantaggio supera il costo è quando goto dovrebbe essere usato; come nel caso di tutte le decisioni di codifica nell'ingegneria del software.
dietbuddha,

1
@dietbuddha vi è un costo nel violare le convenzioni di codifica del software accettate che dovrebbero essere adeguatamente prese in considerazione. Vi è un costo per aumentare la diversità delle strutture di controllo utilizzate in un programma anche se questa struttura di controllo può essere adatta a una situazione. Vi è un costo nel rompere l'analisi statica del codice. Se vale ancora la pena, allora chi sono io per discutere.
Djechlin,

1

TLD; DR: No in particolare, sì in generale

Per l'esempio specifico che hai usato direi di no per gli stessi motivi che molti altri hanno sottolineato. Puoi facilmente riformattare il codice in una forma equivalentemente buona che elimina la necessità di goto.

In generale, la prescrizione contro gotoè una generalità basata sulla natura compresa gotoe sul costo del suo utilizzo. Parte dell'ingegneria del software sta prendendo decisioni di implementazione. La giustificazione di tali decisioni si verifica ponderando il costo rispetto ai benefici e ogni volta che i benefici superano i costi, l'attuazione è giustificata. La controversia sorge a causa di valutazioni diverse, sia in termini di costi che di benefici.

Il punto è che ci saranno sempre delle eccezioni in cui a gotoè giustificato, ma solo per alcuni a causa di valutazioni diverse. È un peccato che molti ingegneri semplicemente escludano le tecniche nell'ignoranza intenzionale perché le leggono su Internet piuttosto che prendersi il tempo per capire il perché.


Potete consigliarmi qualche articolo sull'istruzione goto che spieghi ulteriormente il costo?
Thys

-1

Non credo che questo caso giustifichi un'eccezione, poiché può essere scritto come:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
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.