Si dovrebbe utilizzare "else" in situazioni in cui il flusso di controllo lo rende ridondante?

18

A volte mi imbatto in codice simile al seguente esempio (ciò che questa funzione fa esattamente fuori dallo scopo di questa domanda):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

Come puoi vedere, le istruzioni if , else if e else vengono utilizzate insieme all'istruzione return . Questo sembra abbastanza intuitivo per un osservatore casuale, ma penso che sarebbe più elegante (dal punto di vista di uno sviluppatore di software) eliminare il else -s e semplificare il codice in questo modo:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

Questo ha senso, poiché tutto ciò che segue un'istruzione return (nello stesso ambito) non verrà mai eseguito, rendendo il codice sopra semanticamente uguale al primo esempio.

Quale dei precedenti si adatta meglio alle buone pratiche di codifica? Ci sono degli svantaggi per entrambi i metodi per quanto riguarda la leggibilità del codice?

Modifica: è stato fatto un suggerimento doppio con questo domanda fornita come riferimento. Credo che la mia domanda tocchi un argomento diverso, poiché non sto chiedendo di evitare dichiarazioni duplicate come presentate nell'altra domanda. Entrambe le domande cercano di ridurre le ripetizioni, anche se in modi leggermente diversi.

    
posta rhino 10.12.2016 - 17:18
fonte

7 risposte

23

Mi piace quello senza else ed ecco perché:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

Perché farlo non ha infranto nulla che non avrebbe dovuto.

Interdipendenza da odio in tutte le sue forme (inclusa la denominazione di una funzione check2() ). Isolare tutto ciò che può essere isolato. A volte hai bisogno di else ma non lo vedo qui.

    
risposta data 10.12.2016 - 18:00
fonte
27

Penso che dipenda dalla semantica del codice. Se i tre casi dipendono l'uno dall'altro, pronunciarlo esplicitamente. Ciò aumenta la leggibilità del codice e rende più facile la comprensione per chiunque altro. Esempio:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

Qui dipende chiaramente dal valore di x in tutti e tre i casi. Se ometti l'ultimo else , questo sarebbe meno chiaro.

Se i tuoi casi non dipendono direttamente l'uno dall'altro, omettalo:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

È difficile dimostrarlo in un semplice esempio senza contesto, ma spero che tu capisca il mio punto.

    
risposta data 10.12.2016 - 23:55
fonte
6

Preferisco la seconda opzione (separato ifs senza else if e presto return ) MA che è purché i blocchi di codice siano brevi .

Se i blocchi di codice sono lunghi, è meglio usare else if , perché altrimenti non avrai una chiara comprensione delle numerose uscite che il codice ha.

Ad esempio:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

In questo caso, è meglio usare else if , memorizzare il codice di ritorno in una variabile e avere una sola frase di ritorno alla fine.

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

Ma come ci si dovrebbe sforzare affinché le funzioni siano brevi, direi di tenerlo breve e uscire presto come nel primo frammento di codice.

    
risposta data 10.12.2016 - 19:12
fonte
4

Uso entrambi in circostanze diverse. In un controllo di convalida, ometto il resto. In un flusso di controllo, io uso il resto.

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

vs

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

Il primo caso è più simile a quello che stai verificando per primi tutti i prerequisiti, togliendoli di mezzo, quindi passando al codice effettivo che desideri eseguire. In quanto tale, il codice succoso che vuoi veramente eseguire appartiene direttamente alla portata della funzione; è di cosa tratta veramente la funzione.
Il secondo caso si legge più come se entrambi i percorsi fossero codice valido da eseguire che è altrettanto rilevante per la funzione dell'altro in base ad alcune condizioni. In quanto tali, appartengono a livelli di ambito simili tra loro.

    
risposta data 12.12.2016 - 21:39
fonte
0

L'unica situazione che abbia mai visto è davvero un codice come questo:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

C'è chiaramente qualcosa di sbagliato qui. Il problema è che non è chiaro se return debba essere aggiunto o Arrays.asList rimosso. Non è possibile risolvere questo problema senza un'analisi più approfondita dei metodi correlati. Puoi evitare questa ambiguità usando sempre i blocchi if else completi. Questo:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

non verrà compilato (in lingue controllate staticamente) a meno che tu non aggiunga un rendimento esplicito in if .

Alcune lingue non consentono nemmeno il primo stile. Cerco di usarlo solo in un contesto strettamente imperativo o per precondizioni nei metodi lunghi:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}
    
risposta data 11.12.2016 - 16:38
fonte
-2

Avere tre uscite dalla funzione del genere è davvero confuso se stai cercando di seguire il codice e le cattive pratiche.

Se hai un singolo punto di uscita per la funzione, allora sono necessari gli Els.

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

In effetti consente di andare ancora oltre.

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

Giusto abbastanza potresti argomentare per un singolo ritorno anticipato sulla convalida dell'input. Basta evitare di avvolgere l'intero bulk se la tua logica in un if.

    
risposta data 11.12.2016 - 16:26
fonte
-3

Preferisco omettere la dichiarazione else ridondante. Ogni volta che lo vedo (in revisione del codice o refactoring) mi chiedo se l'autore ha capito il flusso di controllo e che potrebbe esserci un bug nel codice.

Se l'autore riteneva che la dichiarazione else fosse necessaria e quindi non comprendeva le implicazioni del flusso di controllo dell'istruzione return, sicuramente tale mancanza di comprensione è una delle principali fonti di bug, in questa funzione e in generale.

    
risposta data 10.12.2016 - 22:16
fonte

Leggi altre domande sui tag