Devo seguire il percorso normale o fallire presto?

73

Dal libro codice completo viene la seguente citazione:

"Put the normal case after the if rather than after the else"

Ciò significa che le eccezioni / deviazioni dal percorso standard devono essere inserite nel caso else .

Ma Il programmatore pragmatico ci insegna a "crash presto" (pagina 120) .

Quale regola dovrei seguire?

    
posta jao 20.05.2014 - 09:02
fonte

7 risposte

189

"Crash early" non riguarda la riga di codice che viene prima in formato testo. Ti dice di rilevare gli errori nella prima fase possibile dell'elaborazione , in modo da non prendere inavvertitamente decisioni e calcoli basati su uno stato già difettoso.

In un costrutto if / else , viene eseguito solo uno dei blocchi, quindi nessuno dei due può essere considerato un passaggio "precedente" o "successivo". Come ordinarli è quindi una questione di leggibilità, e "fallire presto" non entra nella decisione.

    
risposta data 20.05.2014 - 09:19
fonte
116

Se la tua dichiarazione else contiene solo codice di errore, molto probabilmente non dovrebbe essere lì.

Invece di fare questo:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

fai questo

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

Non vuoi nidificare profondamente il tuo codice semplicemente per includere la verifica degli errori.

E, come tutti hanno già affermato, i due consigli non sono contraddittori. Uno riguarda l' ordine di esecuzione , l'altro riguarda l'ordine del codice .

    
risposta data 20.05.2014 - 16:21
fonte
27

Dovresti seguire entrambi.

Il "Crash early" / fail early advice significa che dovresti testare i tuoi input per possibili errori il prima possibile.
Ad esempio, se il tuo metodo accetta una dimensione o un conteggio che dovrebbe essere positivo (> 0), allora l'avviso di errore iniziale significa che testerai per quella condizione proprio all'inizio del tuo metodo piuttosto che aspettare che l'algoritmo produca risultati senza senso.

Il consiglio per mettere il caso normale prima significa che se si prova per una condizione, allora il percorso più probabile dovrebbe venire prima. Questo aiuta a migliorare le prestazioni (poiché la previsione del ramo del processore sarà più corretta) e la leggibilità, perché non è necessario saltare blocchi di codice quando si cerca di capire cosa sta facendo la funzione nel caso normale. > Questo consiglio non si applica realmente quando si verifica una condizione preliminare e si esegue immediatamente il salvataggio (utilizzando i valori di asserzione o costrutti if (!precondition) throw ), poiché non è possibile ignorare la gestione degli errori durante la lettura del codice.

    
risposta data 20.05.2014 - 09:23
fonte
18

Penso che @JackAidley abbia già detto il succo , ma lascia che lo formuli in questo modo:

senza eccezioni (ad es. C)

Nel flusso di codice regolare, hai:

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

Nel caso "error out early", il tuo codice improvvisamente legge:

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

Se noti questo pattern - un return in un blocco else (o anche if ), rielaboralo immediatamente in modo che il codice in questione non abbia un blocco else :

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

Nel mondo reale ...

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

Questo evita che il nesting sia troppo profondo e soddisfi il caso "break out early" (aiuta a mantenere la mente - e il flusso del codice - clean) e non violano la "metti la cosa più probabile nella parte if " perché semplicemente non c'è nessuna parte else .

C e pulizia

Ispirato da una risposta a una domanda simile (che ha sbagliato), ecco come si pulisce con C. Puoi utilizzare uno o due punti di uscita lì, ecco uno per due punti di uscita:

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

Puoi comprimerli in un punto di uscita se c'è meno pulizia da fare:

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

Questo uso di goto va perfettamente bene, se puoi gestirlo; il consiglio di stare fuori usando goto è diretto a persone che non possono ancora decidere da soli se un uso è buono, accettabile, cattivo, codice spaghetti o qualcos'altro.

Eccezioni

Quanto sopra parla di lingue senza eccezioni, che preferisco di gran lunga me stesso (posso usare la gestione degli errori espliciti molto meglio e con molto meno sorpresa). Per citare igli:

<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

Ma ecco un suggerimento su come farlo bene in una lingua con eccezioni e quando vuoi usarle bene:

errore di ritorno di fronte alle eccezioni

Puoi sostituire la maggior parte dei primi return s generando un'eccezione. Tuttavia , il tuo flusso di programma normale , ovvero qualsiasi flusso di codice in cui il programma non ha incontrato, beh, un'eccezione ... una condizione di errore o somesuch, non aumenta eventuali eccezioni.

Questo significa che ...

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

... va bene, ma ...

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

... non lo è. Fondamentalmente, un'eccezione non è un elemento del flusso di controllo . Ciò inoltre rende le operazioni strane ("quei programmatori Java ™ ci dicono sempre che queste eccezioni sono normali") e possono ostacolare il debug (ad es. Dire all'IDE di interrompere qualsiasi eccezione). Le eccezioni spesso richiedono che l'ambiente runtime si distolga dallo stack per produrre traceback, ecc. Ci sono probabilmente più ragioni per non farlo.

Questo si riduce a: in una lingua che supporta le eccezioni, usa ciò che corrisponde alla logica e allo stile esistenti e ti sembra naturale. Se scrivi qualcosa da zero, prendi questo in anticipo. Se stai scrivendo una biblioteca da zero, pensa ai tuoi consumatori. (Non usare mai abort() in una libreria ...) Ma qualsiasi cosa tu faccia, non fare come una regola generale un'eccezione generata se l'operazione continua (più o meno) normalmente dopo di essa.

consiglio generale wrt. Eccezioni

Cerca di ottenere tutto l'uso in programma delle eccezioni concordate prima dall'intero team di sviluppo. Fondamentalmente, programmali. Non usarli in abbondanza. A volte, anche in C ++, Java ™, Python, un errore di ritorno è migliore. A volte non lo è; usali con il pensiero.

    
risposta data 21.05.2014 - 09:51
fonte
3

Secondo me, "Condizioni di guardia" è uno dei modi migliori e più semplici per rendere leggibile il codice. Odio davvero quando vedo if all'inizio del metodo e non vedo il codice else perché è fuori dallo schermo. Devo scorrere verso il basso solo per vedere throw new Exception .

Metti i controlli all'inizio in modo che il codice di lettura della persona non debba saltare su tutto il metodo per leggerlo ma invece scansionarlo sempre dall'alto verso il basso.

    
risposta data 23.05.2014 - 11:30
fonte
2

(@ mirabilos ' risposta è eccellente, ma ecco come penso alla domanda per raggiungere la stessa conclusione :)

Sto pensando a me stesso (oa qualcun altro) leggendo il codice della mia funzione in seguito. Quando leggo la prima riga, non posso avanzare alcuna ipotesi sul mio input (ad eccezione di quelli che non controllerò comunque). Quindi il mio pensiero è "Ok, so che farò delle cose con i miei argomenti, ma prima li puliamo", cioè uccidiamo i percorsi di controllo in cui non sono di mio gradimento. "Ma allo stesso tempo , Non vedo il caso normale come qualcosa che è condizionato, voglio sottolineare il suo essere normale, quindi

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}
    
risposta data 24.05.2014 - 20:32
fonte
-3

Questo tipo di ordinamento delle condizioni dipende dalla criticità della sezione di codice in questione e se ci sono impostazioni predefinite che possono essere utilizzate.

In altre parole:

A. sezione critica e nessun valore predefinito = > Fail Early

B. sezione non critica e valori predefiniti = > Utilizza i valori predefiniti in altro parte

C. casi intermedi = > decidere per caso secondo necessità

    
risposta data 23.05.2014 - 13:17
fonte

Leggi altre domande sui tag