Blocchi ElseIf-Else ridondanti [duplicato]

4

Questi tipi di blocchi if-elseif-else appaiono dappertutto, e in numero non piccolo (quindi meno sono e meglio è). Ogni volta devo pensare e decidere: Voglio il più semplice o il più completo dei due ... e non voglio più pensarci.

Ad esempio, se mi aspetto solo uno dei tre numeri interi (0, 1, 2) ... non voglio sapere (lanciare o terminare) se ottengo qualcos'altro? (Nel caso dell'esempio n. 2, la funzione do_two () potrebbe essere attivata da qualsiasi valore diverso da 0 o 1). Ovviamente, questo può dipendere se voglio che il mio codice si rompa o continui incontrando un'anomalia, che i test rigorosi dovrebbero comunque rilevare (ma chi può mai essere sicuro?). Ma sto cercando una risposta che sia appropriata in tutti, o nella maggior parte dei casi. Per favore spiega la tua risposta, se il tuo ragionamento è diverso dal mio.

NOTA RIGUARDO AL DUPLICATO: Non penso che questo sia un duplicato per due motivi: le risposte alla domanda suggeriscono di utilizzare le istruzioni switch e l'astrazione del codice. Non uso le istruzioni switch per qualcosa di così piccolo a causa del sovraccarico, non mi piace affatto molto. In secondo luogo, se il codice può essere astratto, potrei comunque variare molto bene per eseguire questi controlli ... più un overhead. La mia domanda è mirata alla necessità di coprire i casi d'angolo usando l'ultimo. La maggior parte delle persone, nella mia esperienza (che è minima), passerebbe all'utilizzo dell'Esempio # 1 se SCRUMing. Penso che il punto migliore (commento - > risposta?) Sia sì, c'è una differenza e dipende dal fatto che sto codificando in modo difensivo.

Esempio 1:

if(!is_null(variable) && is_integer(variable))
{
    if(variable == 0)
        do_zero();
    elseif(variable == 1)
        do_one();
    elseif(variable == 2)
        do_two();
    else
        throw_error();
}


Esempio n. 2:

if(!is_null(variable) && is_integer(variable))
{
    if(variable == 0)
        do_zero();
    elseif(variable == 1)
        do_one();
    else
        do_two();
}
    
posta user58446 19.12.2014 - 21:54
fonte

1 risposta

5

Chiedi a te stesso, qual è il comportamento previsto del tuo metodo quando la variabile non rientra nell'intervallo? Dovrebbe:

  • Disattivalo e non fai niente? Quindi il tuo secondo approccio va bene.

  • Informa il chiamante che la variabile è sbagliata? La mia risposta si concentra su questo caso.

Anche se preferirei il primo approccio, può essere migliorato. Il secondo approccio, d'altra parte, dovrebbe essere evitato, perché rende difficile il debug, poiché non accade nulla quando il valore è al di fuori dell'intervallo consentito.

Se la variabile non rientra nell'intervallo, potresti volerlo sapere presto per terminare (ad esempio lanciando un'eccezione) troppo presto.

Ad esempio, questo codice Python:

def demo(something):
    if something == 0:
        do_zero()
    elif something == 1:
        do_one()
    elif something == 2:
        do_two()
    else:
        raise ValueError("Something is out of range.")

può essere riscritto in questo modo:

def demo(something):
    if something < 0 or something > 2:
        raise ValueError("Something is out of range.")

    if something == 0:
        do_zero()
    elif something == 1:
        do_one()
    elif something == 2:
        do_two()

Il blocco if all'inizio del metodo è chiamato clausola di salvaguardia , una tecnica di refactoring utilizzata per rimuovere condizionali nidificati. Qui, non abbiamo condizionali nidificati, ma è comunque utile spostare la convalida degli argomenti all'inizio del metodo.

A seconda della lingua, potrebbero esserci altre cose da fare. Il più ovvio in alcune lingue, come Python, è la sostituzione di if/elif/else con una mappa. Il metodo precedente diventa:

def demo(something):
    if something < 0 or something > 2:
        raise ValueError("Something is out of range.")

    # Creating the map between the values of 'something' and the corresponding methods to call.
    map = {
        0: do_zero,
        1: do_one,
        2: do_two,
    }

    # Invoking the method.
    map[something]()

Da lì, potresti voler rimuovere la clausola di guardia per ottenere un pezzo di codice più compatto:

def demo(something):
    map = {
        0: do_zero,
        1: do_one,
        2: do_two,
    }

    if something in map:
        map[something]()
    else:
        raise ValueError("Something is out of range.")

Un effetto collaterale positivo è anche la rimozione della duplicazione del codice: non è necessario specificare l'intervallo consentito separatamente dai valori, che può essere la causa di un errore in cui si modifica l'elenco dei valori ma si dimentica di modificare il intervallo corrispondente.

Per ridurre ulteriormente il codice, puoi fare affidamento sul framework. In Python, ad esempio, l'accesso a un valore in un dizionario con una chiave che non esiste genera un'eccezione, il che significa che questa parte di codice:

def demo(something):
    {
        0: do_zero,
        1: do_one,
        2: do_two,
    }[something]()

fa la stessa cosa, tranne che il tipo di eccezione è diverso ( KeyError invece di ValueError ). Dato che il metodo è piccolo ed è facile capire cosa sta succedendo qui quando si riceve KeyError , quest'ultimo refactoring è accettabile. D'altra parte, se hai a che fare con un metodo più grande o se i tipi di eccezioni sono più distanti, evita di fare affidamento sul framework e lancia l'eccezione esplicitamente (e preferibilmente all'inizio).

    
risposta data 30.12.2014 - 16:54
fonte

Leggi altre domande sui tag