Ladder if-else che dovrebbe catturare tutte le condizioni - dovrebbe essere aggiunta una clausola finale ridondante?


10

Questa è una cosa che sto facendo molto ultimamente.

Esempio:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else if (i > current) {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

Spesso la scala if / else è significativamente più complicata di questa ...

Vedi la clausola finale? È ridondante. La scala dovrebbe in definitiva catturare tutte le possibili condizioni. Quindi potrebbe essere riscritto in questo modo:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

Questo è il modo in cui scrivevo codice, ma non mi piace questo stile. La mia lamentela è che la condizione in cui verrà eseguita l'ultima parte del codice non è evidente dal codice. Ho quindi iniziato a scrivere esplicitamente questa condizione per renderla più evidente.

Però:

  • Scrivere esplicitamente l'ultima condizione esaustiva è la mia idea, e ho brutte esperienze con le mie idee - di solito la gente mi grida quanto sia orribile quello che sto facendo - e (a volte molto) più tardi scopro che era davvero subottimale;
  • Un suggerimento sul perché questa potrebbe essere una cattiva idea: non applicabile a Javascript, ma in altre lingue, i compilatori tendono ad emettere avvisi o errori sul controllo del raggiungimento della fine della funzione. Suggerire di fare qualcosa del genere potrebbe non essere troppo popolare o sto sbagliando.
    • I reclami del compilatore mi hanno fatto talvolta scrivere la condizione finale in un commento, ma immagino che farlo sia orribile poiché i commenti, a differenza del codice, non hanno alcun effetto sulla semantica del programma reale:
    } else { // i > current
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }

Mi sto perdendo qualcosa? O va bene fare quello che ho descritto o è una cattiva idea?


+1 per la risposta eccellente di @ Christophe che la ridondanza nel codice aumenta le possibilità di difetti. C'è anche un problema di efficienza molto minore . Per ampliarlo, per quanto riguarda l'esempio di codice in questione, potremmo scrivere la serie di if-else-if come se fosse un if separato senza altri, ma generalmente non lo faremmo dal momento che if-else fornisce al lettore la nozione di alternative esclusive piuttosto che una serie di condizioni indipendenti (che sarebbero anche meno efficienti, dal momento che ciò dice che tutte le condizioni dovrebbero essere valutate anche dopo una partita).
Erik Eidt,

@ErikEidt> poiché ciò dice che tutte le condizioni dovrebbero essere valutate anche dopo una corrispondenza a +1, a meno che non si ritorni da una funzione o "si rompa" da un ciclo (che non è il caso nell'esempio precedente).
Darek Nędza,

1
+1, bella domanda. Per inciso, potrebbe interessarti che in presenza di NaN l'esempio non sia esaustivo.
monocell

Risposte:


6

Entrambi gli approcci sono validi. Ma diamo un'occhiata più da vicino a pro e contro.

Per una ifcatena con condizioni insignificanti come qui, non importa davvero:

  • con un finale else, è ovvio per il lettore scoprire in quale condizione viene attivato l'altro;
  • con un finale else if, è ovvio per il lettore che non elseè necessario alcun ulteriore poiché hai coperto tutto.

Tuttavia, ci sono molte ifcatene che si basano su condizioni più complesse, combinando stati di più variabili, forse con un'espressione logica complessa. In questo caso è meno ovvio. E qui la conseguenza di ogni stile:

  • finale else: sei sicuro che uno dei rami sia preso. Se succede che hai dimenticato un caso, passerà attraverso l'ultimo ramo, quindi durante il debug, se l'ultimo ramo è stato scelto e ti aspettavi qualcos'altro, capirai rapidamente.
  • final else if: è necessario derivare la condizione ridondante da codificare, e questo crea una potenziale fonte di errore con il reisk di non coprire tutti i casi. Inoltre, se hai perso un caso non verrà eseguito nulla e potrebbe essere più difficile scoprire che mancava qualcosa (ad es. Se alcune variabili che ti aspettavi di impostare mantengono i valori delle iterazioni precedenti).

Quindi la condizione ridondante finale è una fonte di rischio. Ecco perché preferirei suggerire di andare in finale else.

Modifica: codifica ad alta affidabilità

Se stai sviluppando pensando all'elevata affidabilità, potresti essere interessato a un'altra variante: completare il tuo finale esplicito ridondante else ifcon un finale elseal fine di cogliere eventuali situazioni impreviste.

Questa è codifica difensiva. È raccomandato da alcune specifiche di sicurezza come SEI CERT o MISRA . Alcuni strumenti di analisi statica implementano anche questo come una regola che viene sistematicamente controllata (questo potrebbe spiegare gli avvisi del compilatore).


7
E se dopo la condizione di ridondanza finale aggiungessi un altro che genera sempre un'eccezione che dice "Non dovevi raggiungermi!" - Allevierà alcuni dei problemi del mio approccio?
Gaazkam,

3
@gaazkam sì, questo è uno stile di programmazione molto difensivo. Quindi è ancora necessario calcolare la condizione finale, ma per catene complesse soggette a errori, lo scopriresti almeno rapidamente. L'unico problema che vedo con questa variante è che è un po 'eccessivo per il caso ovvio.
Christophe,

@gaazkam Ho modificato la mia risposta per indirizzare anche la tua idea aggiuntiva menzionata nel tuo commento
Christophe,

1
@gaazkam Lanciare un'eccezione è buono. In tal caso, non dimenticare di includere il valore imprevisto nel messaggio. Potrebbe essere difficile riprodurlo e il valore imprevisto può fornire un indizio sull'origine del problema.
Martin Maat,

1
Un'altra opzione è quella di inserire un assertin finale else if. La tua guida di stile può variare a seconda che si tratti di una buona idea. (La condizione di un assertnon dovrebbe mai essere falsa, a meno che il programmatore non abbia sbagliato. Quindi questo almeno utilizza la funzione per lo scopo previsto. Ma così tante persone lo abusano che molti negozi l'hanno vietato del tutto.)
Kevin

5

Qualcosa che manca finora nelle risposte dipende dal tipo di fallimento meno dannoso.

Se la tua logica è buona, non importa cosa fai, il caso importante è cosa succede se hai un bug.

Ometti il ​​condizionale finale: l'opzione finale viene eseguita anche se non è la cosa giusta da fare.

Devi semplicemente aggiungere il condizionale finale: non esegue alcuna opzione, a seconda della situazione ciò potrebbe semplicemente significare che qualcosa non viene visualizzato (danno basso) o potrebbe significare un'eccezione di riferimento null in un punto successivo (che potrebbe essere un debug dolore.)

Aggiungete il condizionale finale e un'eccezione: genera.

Devi decidere quale di queste opzioni è la migliore. Nel codice di sviluppo lo considero un gioco da ragazzi: prendi il terzo caso. Tuttavia, probabilmente imposterei circle.src su un'immagine di errore e circle.alt su un messaggio di errore prima di lanciare - nel caso in cui qualcuno decida di disattivare le asserzioni in seguito, ciò fallirà in modo innocuo.

Un'altra cosa da considerare: quali sono le opzioni di recupero? A volte non hai un percorso di recupero. Quello che penso sia il massimo esempio di questo è il primo lancio del razzo Ariane V. Si è verificato un errore non rilevato / 0 (in realtà un overflow di divisione) che ha provocato la distruzione del booster. In realtà a quel punto il codice che si era schiantato non serviva a nulla, era diventato discutibile nell'istante in cui i booster strap-on si erano accesi. Una volta che si accendono l'orbita o il boom, fai il meglio che puoi, gli errori non possono essere permessi. (Se il razzo si smarrisce a causa di ciò, il ragazzo della sicurezza della distanza gira la sua chiave.)


4

Quello che raccomando è usare una assertdichiarazione nel tuo altro finale, in uno di questi due stili:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else {
        assert i > current
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

O un'asserzione di codice morto:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else if (i > current) {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    } else {
        assert False, "Unreachable code"
    }
}

Lo strumento di copertura del codice può spesso essere configurato per ignorare il codice come "asserisci Falso" dal rapporto di copertura.


Inserendo la condizione in un'asserzione, si documenta in modo esplicito la condizione di un ramo in modo esplicito, ma a differenza di un commento, la condizione di asserzione può effettivamente essere verificata e fallirà se si mantengono le asserzioni abilitate durante lo sviluppo o sulla produzione (generalmente consiglio di mantenere abilitate le asserzioni in produzione se non influiscono troppo sulle prestazioni).


1
Non mi piace la tua prima opzione, la seconda è molto più chiara che è un caso che dovrebbe essere impossibile.
Loren Pechtel,

0

Ho definito una macro "asserita" che valuta una condizione e in una build di debug rientra nel debugger.

Quindi, se sono sicuro al 100% che una delle tre condizioni deve essere vera, scrivo

If condition 1 ...
Else if condition 2 .,,
Else if asserted (condition3) ...

Ciò rende abbastanza chiaro che una condizione sarà vera e non è necessario alcun ramo aggiuntivo per un'asserzione.


-2

Consiglio di evitare del tutto altro . Utilizzare ifper dichiarare cosa deve gestire il blocco di codice e terminare il blocco uscendo dalla funzione.

Ciò si traduce in un codice molto chiaro:

setCircle(circle, i, { current })
{
    if (i == current)
    {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
        return
    }
    if (i < current)
    {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
        return
    }
    if (i > current)
    {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
        return
    }
    throw new Exception("Condition not handled.");
}

Il finale ifè ovviamente ridondante ... oggi. Può diventare molto importante pensare se / quando qualche futuro sviluppatore riorganizza i blocchi. Quindi è utile lasciarlo lì dentro.


1
In realtà non è molto chiaro perché ora devo considerare se l'esecuzione del primo ramo potrebbe cambiare l'esito del secondo test. Più ritorni multipli inutili. Inoltre possibilità che nessuna condizione sia vera.
gnasher729,

In realtà scrivo se affermazioni come questa quando mi aspetto intenzionalmente più casi possono essere prese. È particolarmente utile nelle lingue con istruzioni switch che non consentono di fallire. Penso che se le scelte si escludono a vicenda, dovrebbe essere scritto in modo che sia ovvio che i casi si escludono a vicenda (usando altro). E se non è scritto in questo modo, l'implicazione è che i casi non si escludono a vicenda (è possibile fallire).
Jerry Jeremiah,

@ gnasher729 Capisco perfettamente la tua reazione. Conosco alcune persone molto intelligenti che hanno avuto problemi con "evitare altro" e "ritorno anticipato" ... all'inizio. Ti incoraggio a prenderti del tempo per abituarti all'idea e approfondirla, perché queste idee, oggettivamente e misurabilmente, riducono la complessità. Il ritorno anticipato in realtà affronta il tuo primo punto (è necessario considerare se un ramo potrebbe cambiare un altro test). E la "possibilità che nessuna condizione sia vera" esiste ancora a prescindere; con questo stile, si ottiene un'ovvia eccezione piuttosto che un difetto logico nascosto.
John Wu,

Ma la complessità ciclomatica di entrambi gli stili non è esattamente la stessa? cioè uguale al numero di condizioni
gaazkam
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.