2009-05-25 10 views
7

Spero solo che il seguente non sembra vi piace jabber ridondante :)
In ogni caso, non v'è che:Style domanda sul pezzo di codice esistente (C/C++)

for (p = fmt; *p; p++) { 
    if (*p != '%') { 
     putchar(*p); 
     continue; 
    } 
    switch (*++p) { 
     /* Some cases here */ 
     ... 
    } 
} 

E mi chiedevo perché lo scrittore (Kernighan/Ritchie) ha utilizzato lo continue nell'istruzione if.
ho pensato che fosse per il semplice motivo che egli ritiene che sarebbe stato più elegante che rientri l'intera switch sotto un else dichiarazione, cosa ne pensi?

risposta

15

Probabilmente. Il cervello umano ha uno spazio limitato nello stack, rendendo difficile gestire strutture profondamente annidate. Tutto ciò che appiattisce le informazioni che ci aspettiamo di analizzare rende più facile la comprensione.

Allo stesso modo, io di solito preferisco questo:

bool foo(int arg) 
{ 
    if(!arg) { 
     /* arg can't be 0 */ 
     return false; 
    } 

    /* Do some work */ 
    return true; 
} 

A tal:

bool foo(int arg) 
{ 
    if(!arg) { 
     /* arg can't be 0 */ 
     return false; 
    } else { 
     /* Do some work */ 
     return true; 
    } 
} 

O peggio, a questo:

bool foo(int arg) 
{ 
    if(arg) { 
     /* Do some work */ 
     return true; 
    } else { 
     /* arg can't be 0 */ 
     return false; 
    } 
} 

Nell'ultimo esempio, la parte che fa il lavoro potrebbe essere piuttosto lungo. Quando il lettore arriva alla clausola else, potrebbe non ricordare come è arrivato.

Mettere il bail out condizioni come vicino all'inizio aiuta ad assicurare che le persone che cercano di richiamare le funzioni avranno una buona idea di ciò che immette la funzione si aspetta.

Inoltre, come altri hanno sottolineato, il continuo chiarisce che non è necessario leggere ulteriormente nel codice all'interno del ciclo per determinare se ulteriori operazioni vengono eseguite dopo quel punto per questo caso, rendendo il codice più facile da seguire . Di nuovo, meno cose costringi il lettore a tenere traccia di, meglio è.

1

ci sono sempre molti modi per scrivere il codice come questo -

Mettere l'intero interruttore all'interno un'istruzione else sarebbe perfettamente valido. Suppongo che il motivo per cui l'abbiano fatto in quel modo ~ potrebbe essere stato proprio come pensavano in quel momento:

"se il valore in p non è uguale a '%', metti poi avanti."

Se si dispone di passare sotto un altro, potrebbe non essere stato così evidente allo scrittore che si stava saltando per la prossima iterazione in quel caso specifico.

Questo è del tutto personali scelte di stile, però. Non mi preoccuperei troppo - scrivilo in un modo che ha più senso per te e la tua squadra.

1

Sono d'accordo. Ma non puoi considerarlo come una "semplice ragione", in realtà è una buona ragione, perché riduce l'intera complessità del codice. Rendendolo più breve e più facile da leggere e capire.

2

E 'solo in modo molto più facile da leggere quando è messo in questo modo.

Abbiamo finito qui con questa iterazione del ciclo? Sì?Quindi, passiamo allo e proseguiamo con lo alla successiva iterazione.

+1

Questo è discutibile - dipende dal fatto che questo idioma sia comune nel codice. Se non lo è, ha lo svantaggio di essere un modo un po 'insolito per controllare un loop e un potenziale problema di manutenzione/origine bug. –

+0

sì, è abbastanza corretto. Le persone non abituate a questo idioma possono essere confuse di fronte a mantenerlo. È proprio quello a cui sono abituato e che le euristiche mi hanno insegnato è meglio. Per me è più facile perdersi in blocchi nidificati di if-then-else, quindi notare una pausa; o continua; –

1

Se si utilizza un else poi tutto dentro le else ha bisogno di essere rientrati:

if() 
{ 
    doA(); 
} 
else 
{ 
    doB(); 
    if() 
    { 
    doC(); 
    } 
    else 
    { 
    doD() 
    } 
} 

Se si utilizza continue allora non hanno bisogno di rientrare:

if() 
{ 
    doA() 
    continue; 
} 
doB(); 
if() 
{ 
    doC(); 
    continue; 
} 
doD(); 

Inoltre, continue mezzi che posso smettere di pensare a quel caso: ad esempio, se vedo else allora forse ci sarà più elaborazione del caso '%' più avanti nel ciclo, cioè alla fine dello else dichiarazione; mentre nel vedere continue so immediatamente che l'elaborazione della custodia '%' nel ciclo è completamente finita.

1

Il motivo più probabile è che il numero switch che segue sia piuttosto lungo - questo sembra l'analisi del formato printf.

2

Penso che avrebbe avuto motivi sufficienti per indentare il codice sotto l'interruttore, e il rientro dell'intera carne della funzione è abbastanza dispendioso di spazio orizzontale. Al momento in cui il codice è stato scritto, immagino che 80 larghezze di caratteri fossero ancora popolari.

Non credo sia difficile da capire, ma penso che sia abbastanza bello parlare di cosa NON si fa immediatamente, e quindi di GTFO.

9

Perché con il continuare è chiaro che il codice viene eseguito per questa iterazione del ciclo. Se fosse stato utilizzato un altro, dovevi anche verificare se non c'è alcun codice dopo il resto.

Penso che sia generalmente una buona abitudine uscire da un contesto il prima possibile perché questo porta a un codice molto più chiaro.


Ad esempio:

if(arg1 == NULL) 
    return; 

if(arg2 == NULL) 
    return; 

//Do some stuff 

vs.

if(arg1 != null) 
{ 
    if(arg2 != null) 
    { 
    //Do some stuff 
    } 
} 
+1

+1 Codice chiaro. Oltre a questo, quando il codice viene successivamente modificato non devi pensare a tutti i casi di errore perché sono stati trattati in alto, sai che devi solo occuparti dei casi validi. Sento che è molto elegante scrivere codice in questo modo - ho avuto esperienza con la versione "altro" ed era uno standard nella mia altra società - ad es. solo una roba di ritorno ... – stefanB

1

Ci potrebbe essere più che una ragione per continuare/rompere un ciclo. Quindi sarebbe guardare prossimo:

loop 
{ 
    if (cond1) 
    { 
     if (cond2) 
     { 
     if (cond2) 
     { 
      more conditions... 
     } 
     } 
    } 
    else 
    { 
     the loop action 
    } 
} 

IMHO non è così elegante e leggibile come il ciclo nel tuo esempio, ad esempio:

loop 
{ 
    if (cond1) 
     continue; 
    if (cond2) 
     continue; 
    if (cond2) 
     continue; 
    if(more conditions...) 
     continue; 

    the loop action 
} 

E non hanno nemmeno bisogno di capire tutte le strutture di tutti " se "s (potrebbe essere molto più complesso) capire la logica del loop.

P.S. solo per il caso: non penso che gli autori abbiano pensato a come scrivere questo ciclo, lo hanno appena scritto :)

-2

Beh, ho scritto programmi C per circa 11 anni e ho dovuto leggere 5 volte il tuo pezzo di codice per capirlo!

Kernighan e Ritchie erano attivi negli anni sessanta. A quel tempo, essere in grado di capire un pezzo di codice non era rilevante. Essere in grado di scrivere il codice che si adattava a 16 Ko era.

Quindi non sono sorpreso.C è un linguaggio terribile quando i tuoi robot da cucina sono K & R. Basta guardare a realloc: chi potrebbe conoscere un codice del genere oggi? Negli anni '60, era di gran moda, ma ora è spaventoso, almeno: o)

+2

K & R uscì nel 1978, non negli anni '60, ed è generalmente considerato un ottimo scritto, conciso e ben scritto. La chiarezza del codice era molto rilevante: alla conferenza sulla tecnologia del software NATO del 1968 un decennio prima, i partecipanti concordavano sul fatto che il software stava diventando così complesso da provocare una "crisi software". L'esempio di codice in discussione utilizza gli idiomi C (e C++) molto comuni. –

+0

Bene, basta guardare a realloc. Un vero pezzo di merda per gli standard odierni. Se ti piace K e R, cool, hai solo 2 decenni di ritardo! Tutto il meglio! – SRO

0

Mi atteno agli insegnamenti di Dijkstra: goto è dannoso. E continuare/rompere sono i fratellini di goto.

Se il problema è che si sta indentando troppo il codice, la soluzione non sta mettendo un ciclo continuo, ma riducendo la complessità separando il codice in diverse funzioni o pensando a un modo migliore di organizzarlo.

Per esempio, @Kamarey frammento sarebbe ancora più chiaro come questo:

loop 
{ 
    if (!(cond1 || 
     cond2 || 
     cond2 || 
     ...)) 
    { 
     the loop actions; 
    } 
} 

o esempio @Ori Pessach potrebbe essere espresso come:

bool foo(int arg) 
{ 
    if(arg) { 
     /*do some work*/ 
    } 

    return arg != 0; 
} 

In breve, di solito non riesco a trovare un buon motivo per usare costrutti di programmazione non strutturati (magari per alcuni codici di pulizia in un momento molto limitato ...)

+0

Rispetto quello che dici. So che Dijkstra ha espresso la sua obiezione sull'uso di GOTO, ma ha detto qualcosa su continue/break? Trovo che queste dichiarazioni semplificano il codice. –

+0

A quanto ho capito, E.D. obiettato a _arbitrary_ GOTOs. continua/interruzione sono abbastanza limitati in ciò che possono essere i loro obiettivi; GOTO d'altra parte può potenzialmente andare ovunque. – mlp