Quando usarlo in condizioni diverse? [duplicare]

13

Quando usare altro in condizioni?

1)

a)

long int multiplyNumbers(int n)
{
    if (n >= 1) {
        return n*multiplyNumbers(n-1);
    } else {
        return 1;
    }
}

o

b)

long int multiplyNumbers(int n)
{
    if (n >= 1) {
        return n*multiplyNumbers(n-1);
    } 

    return 1;
}

2)

a)

int max(int num1, int num2) {
   int result;

   if (num1 > num2) {
      result = num1;
   } else {
      result = num2;
   }

   return result; 
}

o

b)

int max(int num1, int num2) {

   if (num1 > num2) {
      return num1;
   } else {
      return num2;
   }
}

o

c)

int max(int num1, int num2) {
   if (num1 > num2) {
      return num1;
   } 

   return num2;
}

C'è una regola su quando usare altrimenti?

Le dichiarazioni if with else richiedono più memoria? Da un lato, sono più leggibili, ma d'altro canto, troppa nidificazione è di scarsa lettura.

Se lancio delle eccezioni, allora è meglio non usare altro, ma se queste sono operazioni condizionali ordinarie come nei miei esempi?

    
posta zohub 19.01.2018 - 19:56
fonte

6 risposte

42

Posso dirti quale I userei e per quali ragioni, comunque penso che ci saranno tante opinioni quanti membri della comunità qui ...

1) Né. Vorrei scrivere:

long int multiplyNumbers(int n)
{
    if (n < 1) {
        return 1;
    }
    return n*multiplyNumbers(n-1);
}

Questo perché mi piace l'idea di "uscita anticipata" nei casi speciali, e questo mi ricorda quello scenario. La regola generale è: se devi gestire un caso speciale o un caso generale, gestisci prima il caso speciale. Presumibilmente, il caso speciale (ad esempio, errore o condizione limite) verrà gestito rapidamente e il resto del codice non è ancora troppo lontano dalla condizione "se", in modo che non sia possibile ignorare alcune righe di codice per esaminarlo. Rende anche più facile la lettura della gestione del caso generale, perché è sufficiente scorrere verso il basso il corpo della funzione.

In altre parole, invece di:

void foo(int bar)
{
    if (!special_case_1) {
        if (!special_case_2) {
            return handle_general_case();
        }
        else {
            return handle_special_case_2();
        }
    }
    else {
        return handle_special_case_1();
    }
}

Sto sostenendo di scrivere questo:

int foo(int bar)
{
    if (special_case_1) {
        return handle_special_case_1();
    }
    if (special_case_2) {
        return handle_special_case_2();
    }
    return handle_general_case();
}

Noterai che, se è fattibile, preferisco non introdurre qui una variabile aggiuntiva 'risultato', principalmente perché poi non ho bisogno di preoccuparmi se è inizializzato o meno. (Se lo si introduce, o lo si inizializza in alto, solo per essere sovrascritto in ogni ramo, o non lo si fa, e quindi si può dimenticare di inizializzarlo in qualche ramo. Inoltre, anche se lo si inizializza in ogni ramo, il tuo analizzatore di codice statico potrebbe non vedere questo e potrebbe lamentarsi senza motivo.)

2) Vorrei usare (b) (con la dichiarazione di 'risultato' gettata via). Questo perché in questo caso non posso dire che sia num1 < num2 o num1 > = num2 è un caso speciale. Sono uguali ai miei occhi e quindi (b) è preferito su (c). (Ho già spiegato perché, in questo caso, non introdurrei una variabile aggiuntiva, quindi (a) è ai miei occhi inferiore a (b).)

    
risposta data 19.01.2018 - 20:33
fonte
15

Non esiste una regola dura e veloce, ma qui alcune linee guida di base che chiedo al mio team di seguire:

  • Evita if if profondamente annidati
  • Disporre i blocchi di codice in modo che siano vicini alla condizione che li interessa
  • Per la maggior parte dei controlli di convalida, assegni nulli o logica di programmazione tipica, puoi evitare completamente le dichiarazioni di else utilizzando il modello di guardia.
  • Per la business logic-- dove non stai solo controllando le condizioni del bordo ma stai scegliendo il percorso da seguire-- è più importante rappresentare la logica nel codice in un modo che non sia facilmente mappato alle specifiche. Quindi se la specifica contiene altre istruzioni (o frecce, in un diagramma di flusso), è OK averle nel codice.

Semplice esempio. Invece di

if (condition1)
{
    if (condition2)
    {
        DoSomething();
        return result;
    }
    else
    {
        return errorCode;
    }
}
else
{
    return errorCode;
}

È più facile da leggere:

if (!condition1) return errorCode;
if (!condition2) return errorCode;
DoSomething();
return result;
  1. Abbiamo ridotto il livello di nidificazione. Le persone che leggono il codice non devono combinare le condizioni nella loro testa per capire la logica.

  2. L'azione associata a ciascuna espressione condizionale si trova accanto all'istruzione if che determina se verrà eseguita. Le persone che leggono il codice non dovranno tenere traccia delle parentesi graffe per capire quale codice è influenzato da quale condizione, e non dovranno scorrere su e giù per capire il codice.

  3. Questo approccio segue il schema di guardia che riduce complessità ciclica , che ha dimostrato di ridurre i tassi di difetti.

Vedi anche:

Come evitare se catene

Modello di codice per avere la minore complicazione

Codice freccia di appiattimento

    
risposta data 19.01.2018 - 21:43
fonte
10

Non esiste una regola universale e troverai persone ragionevoli in disaccordo sul fatto che avere ritorni multipli sia sempre una buona idea.

Nei casi in cui è possibile rendere il codice altrettanto semplice con un singolo ritorno, questo è preferibile. Per esempio:.

int max(int num1, int num2) {
   return (num1 > num2) ? num1 : num2;
}

Nei casi in cui è possibile semplificare il codice utilizzando più ritorni, penso che sia accettabile se segue l'espressione "uscita anticipata":

long int multiplyNumbers(int n)
{
    if (n < 1) {
        return 1;
    } 
    return n*multiplyNumbers(n-1);
}

Cioè, hai alcuni casi speciali che possono essere gestiti nella parte superiore della funzione, saltando la logica più complessa. Ma a parte questo, consiglierei di non avere più ritorni o ritorni all'interno di blocchi annidati, poiché rende la logica più difficile da seguire in funzioni più complesse.

    
risposta data 19.01.2018 - 20:27
fonte
4

Per quanto riguarda l'uso della memoria:

  • Questo non è qualcosa di cui dovresti preoccuparti.
  • No, in generale, non dovrebbe esserci una differenza.
  • Il compilatore cambierà le cose come meglio crede, la leggibilità è davvero l'unico problema qui.

Non penso che ci sia una risposta definitiva assoluta. Tuttavia, ci sono alcuni aspetti che potresti voler prendere in considerazione.

Esempio 1

Preferirei tornare presto, se la condizione rappresenta una specie di caso speciale.

unsigned factorial(unsigned n)
{
    if (n <= 1) {
        return 1;  // this is a special case
    }

    return n * factorial(n-1);  // this is the "normal" case
}

Se hai due casi e non riesci a decidere quale caso è speciale, un approccio if/else potrebbe avere più senso.

Esempio 2

Ci saranno delle eccezioni, ma vorrei evitare di dichiarare una variabile che non viene utilizzata fino a più tardi. Quindi sceglierei 2a) su 2b). 2c) ha senso se num1 > num2 è qualcosa di speciale, come descritto sopra.

    
risposta data 19.01.2018 - 20:29
fonte
1

Non esiste una regola fissa. Con l'esperienza, scoprirai varie regole che applicherete in vari casi. Nel tuo esempio, hai due rami che sono simmetrici e semplici, quindi basta usare if (...) {return x; } else {return y; }

Cose da considerare: a volte hai una funzione più ampia con alcuni casi speciali facili da eliminare per primi. se (condizione semplice) return x; se (un'altra condizione semplice) ritorna y; seguito da un sacco di codice.

Un'altra cosa da considerare: per il debug, potresti voler impostare un breakpoint quando la funzione ritorna, quindi vuoi solo una dichiarazione di ritorno. (Se si dispone di un debugger che consente di interrompere quando la funzione restituisce, anche con più dichiarazioni di reso, si è fortunati).

E non vuoi le sequenze nidificate profondamente se / else, con il resto del primo se a circa mezzo miglio di distanza, in modo da organizzare il tuo codice di conseguenza.

    
risposta data 19.01.2018 - 22:04
fonte
1

Come altri hanno già detto: di solito è preferibile mantenere il percorso felice nel modo meno articolato possibile e tornare presto quando necessario.

Su una nota a margine, in entrambi questi casi semplici, consiglierei di usare un'espressione condizionale, poiché questo è praticamente il caso d'uso ideale per questo:

1) return (n < 1) ? 1 : n*multiplyNumbers(n-1);

2) return (num2 < num1) ? num1 : num2;

Eviterei sicuramente il metodo 2.a. È lungo e la variabile temporanea è un disordine extra senza alcun vantaggio di leggibilità (ad esempio non ha un nome informativo).

    
risposta data 20.01.2018 - 23:02
fonte

Leggi altre domande sui tag