Questo giustifica le dichiarazioni goto?

15

Mi sono imbattuto in questa domanda un secondo fa e sto tracciando parte del materiale: C'è un nome per il costrutto break n?

Questo sembra essere un modo inutilmente complesso per le persone che devono istruire il programma per uscire da un ciclo a doppio nidificato:

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 ai libri di testo piace dire che le dichiarazioni goto sono il diavolo, e non mi piacciono affatto, ma questo giustifica un'eccezione alla regola?

Sto cercando risposte che si occupino di n-nested per loops.

NOTA: Se rispondi sì, no o in una posizione intermedia, le risposte completamente mentali sono benvenute non . Soprattutto se la risposta è no, quindi fornire un buon motivo legittimo perché (che non è comunque troppo lontano dai regolamenti dello Stack Exchange).

    
posta Panzercrisis 06.02.2014 - 22:53
fonte

7 risposte

33

L'apparente necessità di una dichiarazione "go-to" deriva da hai scelto espressioni condizionali scadenti per i loop .

dichiari di volere che il ciclo esterno continui con i < 10 lungo e il più interno a continuare fino a j > 0 .

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

Se dici ai loop le tue vere intenzioni per iniziare , 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;
        }
    }
}
    
risposta data 07.02.2014 - 00:09
fonte
34

In questo caso, puoi rifattorizzare il codice in una routine separata e quindi solo return dal ciclo interno. Ciò annullerebbe la necessità di uscire dal ciclo esterno:

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

    return false;
}
    
risposta data 06.02.2014 - 23:07
fonte
6

No, non penso che sia necessario un goto . 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;
        }
    }
}

Avendo &&!broken su entrambi i loop, puoi uscire da entrambi i loop quando i==j .

Per me, questo approccio è preferibile. Le condizioni del ciclo sono estremamente chiare: i<10 && !broken .

Una versione funzionante può essere vista qui: link

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);
}

Output:

loop breaks when i==j==1
2
0
    
risposta data 06.02.2014 - 23:12
fonte
3

La risposta breve è "dipende". Io sostengo di scegliere per quanto riguarda la leggibilità e la comprensibilità del tuo codice. Il modo goto potrebbe essere più leggibile rispetto al tweaking della condizione, o viceversa. Si potrebbe anche tener conto del principio meno sorpresa, delle linee guida utilizzate nel negozio / progetto e della coerenza della base di codice. Ad esempio, goto per lasciare switch o per la gestione degli errori è una pratica comune nel codice base del kernel di Linux. Nota inoltre che alcuni programmatori potrebbero avere problemi a leggere il tuo codice (sollevato con il mantra "goto is evil" o semplicemente non appreso perché il loro insegnante lo pensa) e alcuni potrebbero addirittura "giudicarti".

In generale, un goto che va oltre nella stessa funzione è spesso accettabile, specialmente se il salto non è troppo lungo. Il tuo caso d'uso di lasciare un ciclo annidato è uno degli esempi noti di "non così male" goto.

    
risposta data 07.02.2014 - 17:07
fonte
2

Nel tuo caso, goto è abbastanza di un miglioramento che sembra allettante, ma non è tanto un miglioramento che vale la pena di infrangere la regola contro l'uso di questi nella tua base di codice.

Pensa al tuo futuro revisore: lui / lui dovrà pensare, "Questa persona è un programmatore cattivo o è in realtà un caso in cui il goto è valsa la pena? Lasciami prendere 5 minuti per decidere questo per me stesso o per la ricerca su Internet. " Perché mai lo faresti al tuo futuro programmatore quando non puoi usare goto interamente?

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

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

  • usa strutture di controllo più complesse, come un flag booleano, per uscire da entrambi i cicli
  • inserisci il tuo ciclo interno all'interno della propria 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 ciclo non è così coeso che comunque non dovrebbe essere la sua routine.

A proposito, la migliore discussione su questo che ho visto è il Code Complete di Steve McConnell. Se ti piace pensare in modo critico a questi problemi, consiglio vivamente di leggere, anche cover-to-cover, nonostante la sua immensità.

    
risposta data 07.02.2014 - 00:32
fonte
1

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

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

In generale, la proscrizione rispetto a goto è una generalità basata sulla natura intesa di goto e il costo dell'utilizzo. Parte dell'ingegneria del software sta prendendo decisioni di implementazione. La giustificazione di tali decisioni si verifica ponderando i costi con i benefici e ogni volta che i benefici superano i costi dell'implementazione giustificata. La polemica sorge a causa di diverse valutazioni, sia di costi che di benefici.

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

    
risposta data 07.02.2014 - 11:02
fonte
-1

Non penso che questo caso giustifichi un'eccezione, in quanto può essere scritta 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
}
    
risposta data 06.02.2014 - 23:04
fonte

Leggi altre domande sui tag