Qual è il vantaggio di terminare if ... else if costruisce con una clausola else?


136

La nostra organizzazione ha una regola di codifica richiesta (senza alcuna spiegazione) che:

if ... else if costrutti dovrebbe essere terminato con una clausola else

Esempio 1:

if ( x < 0 )
{
   x = 0;
} /* else not needed */

Esempio 2:

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
}

Quale custodia per bordi è progettata per gestire?

Ciò che mi preoccupa anche della ragione è che l' esempio 1 non ha bisogno di un, elsema l' esempio 2 lo fa. Se il motivo è la riutilizzabilità e l'estensibilità, penso che elsedovrebbe essere usato in entrambi i casi.


30
Forse chiedi alla tua azienda il motivo e il vantaggio. A prima vista, costringe il programmatore a pensarci e aggiungere un commento "nessuna azione richiesta". Lo stesso ragionamento dietro (e almeno tanto controverso quanto) alle eccezioni verificate di Java.
Thilo,

32
L'esempio 2 è in realtà un buon esempio di dove assert(false, "should never go here")potrebbe avere senso
Thilo,

2
La nostra organizzazione ha regole simili ma non così granulari. Lo scopo nel nostro caso è duplice. Innanzitutto, coerenza del codice. In secondo luogo, nessuna stringa libera / leggibilità. Richiedere il resto, anche quando non necessario, aggiunge chiarezza al codice anche quando non è documentato. Richiedere un altro è qualcosa che ho fatto in passato, anche quando non necessario, solo per essere sicuro di aver tenuto conto di tutti i possibili risultati nell'app.
Luvn Jesus

4
Puoi sempre sconfiggere se con if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. Oppure potresti semplicemente seguire tali regole, molte delle quali sono sciocche, semplicemente perché ti viene richiesto.
Jim Balter,

3
@Thilo sono un po 'in ritardo, ma nessuno ha colto l'errore: non c'è alcuna indicazione che l'altro non dovrebbe mai accadere, solo che non dovrebbe avere effetti collaterali (che sembra normale quando si eseguono i < 0controlli), quindi l'asserzione sta andando bloccare il programma su quello che è probabilmente il caso più comune in cui i valori sono nei limiti previsti.
Loduwijk,

Risposte:


148

Come menzionato in un'altra risposta, questo è tratto dalle linee guida sulla codifica MISRA-C. Lo scopo è la programmazione difensiva, un concetto che viene spesso utilizzato nella programmazione mission-critical.

Cioè, ogni if - else ifdeve finire con un elsee ogni switchdeve finire con undefault .

Ci sono due ragioni per questo:

  • Codice autocompattante. Se si scrive un elsema lasciatelo vuoto vuol dire: "Ho sicuramente considerato lo scenario in cui né ifelse if sono vere".

    Non scrivere un elselì significa: "o ho considerato lo scenario in cui ifné nessuno dei due else ifè vero, oppure ho completamente dimenticato di prenderlo in considerazione e c'è potenzialmente un grosso bug proprio qui nel mio codice".

  • Ferma il codice in fuga. Nel software mission-critical, è necessario scrivere programmi robusti che tengano conto anche di ciò che è altamente improbabile. Quindi potresti vedere un codice simile

    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }

    Questo codice sarà completamente estraneo ai programmatori di PC e agli informatici, ma ha perfettamente senso nel software mission-critical, perché rileva il caso in cui il "mybool" è diventato corrotto, per qualsiasi motivo.

    Storicamente, potresti temere il danneggiamento della memoria RAM a causa dell'EMI / del rumore. Questo non è un grosso problema oggi. Molto più probabilmente, il danneggiamento della memoria si verifica a causa di bug altrove nel codice: puntatori a posizioni errate, bug di array fuori limite, overflow dello stack, codice in fuga ecc.

    Quindi la maggior parte delle volte, codice come questo ritorna a schiaffeggiarti quando hai scritto bug durante la fase di implementazione. Significa che potrebbe anche essere usato come una tecnica di debug: il programma che stai scrivendo ti dice quando hai scritto dei bug.


MODIFICARE

Per quanto riguarda il motivo per cui elsenon è necessario dopo ogni singolo if:

Un if-elseo if-else if-elsecopre completamente tutti i possibili valori che una variabile può avere. Ma una semplice ifdichiarazione non è necessariamente lì per coprire tutti i possibili valori, ha un uso molto più ampio. Molto spesso desideri semplicemente verificare una determinata condizione e, se non viene soddisfatta, non fare nulla. Quindi non è semplicemente significativo scrivere una programmazione difensiva per coprire il elsecaso.

Inoltre, ingombrerebbe completamente il codice se scrivessi uno spazio vuoto elsedopo ogni uno if.

MISRA-C: 2012 15.7 non fornisce alcun motivo per cui elsenon è necessario, afferma semplicemente:

Nota: elsenon è richiesta un'istruzione finale per if un'istruzione semplice .


6
Se la memoria è danneggiata, mi aspetto che si rovini molto più di un semplice mybool, incluso forse il codice di controllo stesso. Perché non aggiungi un altro blocco che verifica che il tuo if/else if/elsecompilato sia quello che ti aspetti? E poi un altro per verificare anche il verificatore precedente?
Oleg V. Volkov,

49
Sospiro. Senti ragazzi, se non avete assolutamente altra esperienza oltre alla programmazione desktop, non c'è bisogno di fare commenti su qualcosa di cui ovviamente non avete esperienza. Ciò accade sempre quando si discute di una programmazione difensiva e si ferma un programmatore di PC. Ho aggiunto il commento "Questo codice sarà completamente estraneo ai programmatori di PC" per un motivo. Non si programma software critici per la sicurezza da eseguire su computer desktop basati su RAM . Periodo. Il codice stesso risiederà nella ROM flash con checksum ECC e / o CRC.
Lundin,

6
@Deduplicator In effetti (a meno che non myboolabbia un tipo non booleano, come nel caso precedente prima che C ne ottenesse la propria bool; quindi il compilatore non avrebbe assunto l'assunto senza un'ulteriore analisi statica). E in tema di 'Se scrivi un altro ma lo lasci vuoto, significa: "Ho sicuramente considerato lo scenario in cui né se né altro se sono vere".': La mia prima reazione è assumere che il programmatore abbia dimenticato di inserire il codice l'altro blocco, altrimenti perché un altro blocco vuoto è seduto lì? Un // unusedcommento sarebbe appropriato, non solo un blocco vuoto.
JAB

5
@JAB Sì, il elseblocco deve contenere una sorta di commento se non c'è codice. Pratica comune per vuoto elseè un unico punto e virgola oltre a un commento: else { ; // doesn't matter }. Poiché non vi è alcun motivo per cui qualcuno dovrebbe semplicemente digitare un singolo punto e virgola rientrato su una riga a sé stante. Pratica simile è talvolta usato in cicli vuoto: while(something) { ; // do nothing }. (codice con interruzioni di riga, ovviamente. Quindi i commenti non li permettono)
Lundin,

5
Vorrei sottolineare che questo codice di esempio con due IF e altro può andare anche altro nel solito software desktop in ambienti multi-thread, quindi il valore di mybool potrebbe essere cambiato proprio nel mezzo
Iłya Bursov

61

La tua azienda ha seguito la guida alla codifica MISRA. Esistono alcune versioni di queste linee guida che contengono questa regola, ma da MISRA-C: 2004 :

Regola 14.10 (obbligatorio): Tutto se ... altrimenti se i costrutti devono essere terminati con una clausola else.

Questa regola si applica ogni volta che un'istruzione if è seguita da una o più istruzioni if; l'ultimo finale ifdeve essere seguito da una else dichiarazione. Nel caso di ifun'istruzione semplice , else non è necessario includere l' istruzione. Il requisito per un finaleelse dichiarazione è la programmazione difensiva. La elsedichiarazione deve intraprendere le azioni appropriate o contenere un commento adeguato sul motivo per cui non viene intrapresa alcuna azione. Ciò è coerente con il requisito di avere una defaultclausola finale in una switchdichiarazione. Ad esempio questo codice è una semplice istruzione if:

if ( x < 0 )
{
 log_error(3);
 x = 0;
} /* else not needed */

mentre il codice seguente mostra un if , else ifcostrutto

if ( x < 0 )
{
 log_error(3);
 x = 0;
}
else if ( y < 0 )
{
 x = 3;
}
else /* this else clause is required, even if the */
{ /* programmer expects this will never be reached */
 /* no change in value of x */
}

Nel MISRA-C: 2012 , che sostituisce la versione del 2004 ed è l'attuale raccomandazione per nuovi progetti, esiste la stessa regola ma è numerata 15.7 .

Esempio 1: in un singolo programmatore di istruzioni if ​​potrebbe essere necessario controllare n numero di condizioni ed eseguire una singola operazione.

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

In un uso regolare eseguire un'operazione non è necessario per tutto il tempo in cui ifviene utilizzato.

Esempio 2: qui il programmatore controlla n numero di condizioni ed esegue più operazioni. Nell'uso regolare if..else ifè come switchpotrebbe essere necessario eseguire un'operazione come predefinita. Quindi elseè necessario l' utilizzo secondo lo standard misra

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}
else if(condition_1 || condition_2 || ... condition_n)
{
  //operation_2
}
....
else
{
   //default cause
}

Le versioni attuali e passate di queste pubblicazioni sono disponibili per l'acquisto tramite il webstore MISRA ( via ).


1
Grazie, la tua risposta è tutto il contenuto della regola di Misra, ma mi aspetto una risposta per la mia confusione nella modifica della parte della domanda.
Trevor,

2
Buona risposta. Per curiosità, quelle guide dicono qualcosa su cosa fare se ti aspetti che la elseclausola sia irraggiungibile? (Lasciare invece la condizione finale, gettare un errore, forse?)
jpmc26,

17
Voterò il voto per plagio e collegamenti che violano il copyright. Modificherò il post in modo che sia più chiaro quali sono le tue parole e quali sono le parole di MISRA. Inizialmente questa risposta non era altro che una copia / incolla grezza.
Lundin,

8
@TrieuTheVan: le domande non devono spostare obiettivi . Assicurati che la tua domanda sia completa prima di pubblicarla.
TJ Crowder,

1
@ jpmc26 No, ma il punto di MISRA non è darti codice stretto, è darti codice sicuro. Qualsiasi compilatore in questi giorni ottimizzerà comunque il codice irraggiungibile, quindi non ci sono svantaggi.
Graham,

19

Ciò equivale a richiedere un caso predefinito in ogni switch.

Questo extra ridurrà la copertura del codice del tuo programma.


Nella mia esperienza con il porting del kernel Linux o del codice Android su diverse piattaforme molte volte facciamo qualcosa di sbagliato e in logcat vediamo alcuni errori come

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
        printk(" \n [function or module name]: this should never happen \n");

        /* It is always good to mention function/module name with the 
           logs. If you end up with "this should never happen" message
           and the same message is used in many places in the software
           it will be hard to track/debug.
        */
}

2
Qui è dove __FILE__e le __LINE__macro sono utili per rendere facile la posizione di origine se il messaggio viene mai stampato.
Peter Cordes,

9

Solo una breve spiegazione, da quando l'ho fatto tutto circa 5 anni fa.

Non esiste (con la maggior parte delle lingue) alcun requisito sintattico per includere elseun'istruzione "null" (e non necessaria){..} ) e nei "piccoli programmi semplici" non è necessario. Ma i veri programmatori non scrivono "semplici piccoli programmi" e, altrettanto importante, non scrivono programmi che verranno usati una volta e poi scartati.

Quando si scrive un if / else:

if(something)
  doSomething;
else
  doSomethingElse;

sembra tutto semplice e difficilmente si vede nemmeno il punto di aggiungere {..}.

Ma un giorno, tra qualche mese, qualche altro programmatore (non commetteresti mai un errore del genere!) Dovrà "migliorare" il programma e aggiungere una dichiarazione.

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

Improvvisamente si doSomethingElsedimentica che dovrebbe essere in una elsegamba.

Quindi sei un buon programmatore e lo usi sempre {..}. Ma scrivi:

if(something) {
  if(anotherThing) {
    doSomething;
  }
}

Va tutto bene fino a quando quel nuovo ragazzo non fa una modifica di mezzanotte:

if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

Sì, è formattato in modo errato, ma lo è anche metà del codice nel progetto, e il "formatter automatico" viene messo in discussione da tutte le #ifdefdichiarazioni. E, naturalmente, il vero codice è molto più complicato di questo esempio di giocattolo.

Sfortunatamente (o no), sono stato fuori da questo genere di cose per alcuni anni, quindi non ho in mente un nuovo esempio "reale" - quanto sopra è (ovviamente) inventato e un po 'hokey.


7

Questo viene fatto per rendere il codice più leggibile, per riferimenti successivi e per chiarire, a un revisore successivo, che i casi rimanenti gestiti dall'ultimo else, non fanno nulla , in modo che non vengano trascurati in qualche modo a prima vista.

Questa è una buona pratica di programmazione, che rende il codice riutilizzabile ed estensibile .


6

Vorrei aggiungere - e in parte contraddire - le risposte precedenti. Mentre è certamente comune usare if-else se in un modo simile a un interruttore che dovrebbe coprire l'intera gamma di valori pensabili per un'espressione, non è affatto garantito che qualsiasi gamma di possibili condizioni sia completamente coperta. Lo stesso si può dire del costrutto switch stesso, quindi il requisito di utilizzare una clausola di default, che cattura tutti i valori rimanenti e può, se non diversamente richiesto, essere usato come garanzia di asserzione.

La domanda stessa presenta un buon contro-esempio: la seconda condizione non si riferisce affatto a x (motivo per cui spesso preferisco la variante if-based più flessibile rispetto alla variante switch-based). Dall'esempio è ovvio che se viene soddisfatta la condizione A, x deve essere impostato su un determinato valore. Se A non viene rispettato, viene verificata la condizione B. Se viene raggiunto, allora x dovrebbe ricevere un altro valore. Se non si incontrano né A né B, allora x dovrebbe rimanere invariato.

Qui possiamo vedere che un ramo vuoto altro dovrebbe essere usato per commentare l'intenzione del programmatore per il lettore.

D'altra parte, non riesco a capire perché ci debba essere una clausola else in particolare per l'ultima e più intima dichiarazione if. In C, non esiste un 'altro if'. C'è solo se e altro. Invece, secondo MISRA, il costrutto dovrebbe essere formalmente rientrato in questo modo (e avrei dovuto mettere le parentesi graffe aperte sulle loro stesse linee, ma non mi piace):

if (A) {
    // do something
}
else {
    if (B) {
        // do something else (no pun intended)
    }
    else {
        // don't do anything here
    }
}

Quando MISRA chiede di mettere parentesi graffe attorno a ogni ramo, allora si contraddice citando "se ... altrimenti se costruisce".

Chiunque può immaginare la bruttezza degli alberi profondamente nidificati se altro, vedi qui in una nota a margine . Ora immagina che questo costrutto possa essere arbitrariamente esteso ovunque. Quindi chiedere una clausola else alla fine, ma non altrove, diventa assurdo.

if (A) {
    if (B) {
        // do something
    }
    // you could to something here
}
else {
    // or here
    if (B) { // or C?
        // do something else (no pun intended)
    }
    else {
        // don't do anything here, if you don't want to
    }
    // what if I wanted to do something here? I need brackets for that.
}

Quindi sono sicuro che le persone che hanno sviluppato le linee guida MISRA avevano in mente l'intenzione di cambiare se altro.

Alla fine, scende a loro per definire esattamente cosa si intende con un "if ... else if construct"


5

Il motivo di base è probabilmente la copertura del codice e l'altro implicito: come si comporterà il codice se la condizione non è vera? Per test autentici, è necessario un modo per vedere che si è verificato con la condizione false. Se tutti i casi di test che hai attraversano la clausola if, il tuo codice potrebbe avere problemi nel mondo reale a causa di una condizione che non hai testato.

Tuttavia, alcune condizioni potrebbero essere come l'esempio 1, come in una dichiarazione dei redditi: "Se il risultato è inferiore a 0, immettere 0" Devi ancora fare un test in cui la condizione è falsa.


5

Logicamente ogni test implica due rami. Cosa fai se è vero e cosa fai se è falso.

Per quei casi in cui una delle filiali non ha funzionalità, è ragionevole aggiungere un commento sul perché non deve avere funzionalità.

Questo può essere di beneficio per il prossimo programmatore di manutenzione. Non dovrebbero cercare troppo lontano per decidere se il codice è corretto. Puoi in qualche modo cacciare l'Elefante .

Personalmente, mi aiuta in quanto mi costringe a guardare il caso altro e valutarlo. Potrebbe essere una condizione impossibile, nel qual caso potrei lanciare un'eccezione in caso di violazione del contratto. Può essere benigno, nel qual caso un commento può essere sufficiente.

Il tuo chilometraggio può variare.


4

Il più delle volte quando hai solo una singola ifaffermazione, è probabilmente uno dei motivi come:

  • Controlli di protezione funzionale
  • Opzione di inizializzazione
  • Filiale di elaborazione opzionale

Esempio

void print (char * text)
{
    if (text == null) return; // guard check

    printf(text);
}

Ma quando lo fai if .. else if, è probabilmente uno dei motivi come:

  • Scatola di commutazione dinamica
  • Fork di elaborazione
  • Gestione di un parametro di elaborazione

E nel caso in cui tu if .. else ifcopra tutte le possibilità, in quel caso il tuo ultimo if (...)non è necessario, puoi semplicemente rimuoverlo, perché a quel punto gli unici valori possibili sono quelli coperti da quella condizione.

Esempio

int absolute_value (int n)
{
    if (n == 0)
    {
        return 0;
    }
    else if (n > 0)
    {
        return n;
    }
    else /* if (n < 0) */ // redundant check
    {
        return (n * (-1));
    }
}

E nella maggior parte di questi motivi, è possibile che qualcosa non rientri in nessuna delle categorie nella tua if .. else if, quindi la necessità di gestirli in una elseclausola finale , la gestione può essere effettuata attraverso una procedura di livello aziendale, una notifica dell'utente, un meccanismo di errore interno, ..eccetera.

Esempio

#DEFINE SQRT_TWO   1.41421356237309504880
#DEFINE SQRT_THREE 1.73205080756887729352
#DEFINE SQRT_FIVE  2.23606797749978969641

double square_root (int n)
{
         if (n  > 5)   return sqrt((double)n);
    else if (n == 5)   return SQRT_FIVE;
    else if (n == 4)   return 2.0;
    else if (n == 3)   return SQRT_THREE;
    else if (n == 2)   return SQRT_TWO;
    else if (n == 1)   return 1.0;
    else if (n == 0)   return 0.0;
    else               return sqrt(-1); // error handling
}

Questa elseclausola finale è abbastanza simile a poche altre cose in lingue come Javae C++, come:

  • default caso in un'istruzione switch
  • catch(...)che viene dopo tutti i catchblocchi specifici
  • finally in una clausola try-catch

2

Il nostro software non era mission-critical, ma abbiamo anche deciso di utilizzare questa regola a causa della programmazione difensiva. Abbiamo aggiunto un'eccezione di lancio al codice teoricamente irraggiungibile (switch + if-else). E ci ha salvato molte volte poiché il software si è guastato rapidamente, ad esempio quando è stato aggiunto un nuovo tipo e ci siamo dimenticati di cambiare uno o due if-else o passare. Come bonus ha reso super facile trovare il problema.


2

Bene, il mio esempio riguarda un comportamento indefinito, ma a volte alcune persone cercano di essere fantasiose e falliscono duramente, dai un'occhiata:

int a = 0;
bool b = true;
uint8_t* bPtr = (uint8_t*)&b;
*bPtr = 0xCC;
if(b == true)
{
    a += 3;
}
else if(b == false)
{
    a += 5;
}
else
{
    exit(3);
}

Probabilmente non ti aspetteresti mai di avere ciò boolche non è truefalse, tuttavia può accadere. Personalmente credo che questo sia un problema causato da una persona che decide di fare qualcosa di elegante, ma un'ulteriore elsedichiarazione può prevenire ulteriori problemi.


1

Attualmente sto lavorando con PHP. Creazione di un modulo di registrazione e un modulo di accesso. Sto semplicemente usando if e else. Nessun altro se o qualcosa di inutile.

Se l'utente fa clic sul pulsante di invio -> passa all'istruzione if successiva ... se il nome utente è inferiore alla quantità di caratteri "X", avvisa. Se ha esito positivo, controlla la lunghezza della password e così via.

Non è necessario un codice aggiuntivo come un altro se ciò potrebbe annullare l'affidabilità per il tempo di caricamento del server per controllare tutto il codice aggiuntivo.

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.