Dovrebbe essere usato "altro" in situazioni in cui il flusso di controllo lo rende ridondante?


18

A volte inciampo in un codice simile al seguente esempio (ciò che questa funzione fa esattamente non rientra nell'ambito di questa domanda):

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

Come si può vedere, if, else ife le elsedichiarazioni sono utilizzati in combinazione con la returndichiarazione. Questo sembra abbastanza intuitivo per un osservatore occasionale, ma penso che sarebbe più elegante (dal punto di vista di uno sviluppatore di software) eliminare i else-s e semplificare il codice in questo modo:

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

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

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

Modifica: è stato formulato un suggerimento duplicato con questa domanda fornita come riferimento. Credo che la mia domanda tocchi un argomento diverso, poiché non sto chiedendo di evitare dichiarazioni duplicate come presentato nell'altra domanda. Entrambe le domande cercano di ridurre le ripetizioni, sebbene in modi leggermente diversi.


14
Il elseproblema è minore, il problema maggiore è che stai ovviamente restituendo due tipi di dati da una singola funzione, il che rende imprevedibile l'API della funzione.
Andy,

3
E_CLEARLY_NOTADUPE
kojiro,

3
@DavidPacker: almeno due; potrebbero essere tre. -1è un numero, falseè un valore booleano e valuenon è specificato qui, quindi potrebbe essere qualsiasi tipo di oggetto.
Yay 295

1
@ Yay295 Speravo che in valuerealtà fosse un numero intero. Se può essere qualsiasi cosa, allora è anche peggio.
Andy,

@DavidPacker Questo è un punto molto importante, specialmente se sollevato in merito a una domanda sulle buone pratiche. Sono d'accordo con te e Yay295, tuttavia gli esempi di codice servono come esempio per dimostrare una tecnica di codifica non correlata. Tuttavia, tengo presente che questa è una questione importante e non deve essere trascurata nel codice reale.
rinoceronte,

Risposte:


23

Mi piace quello senza elseed ecco perché:

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

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

Odio l'interdipendenza in tutte le sue forme (inclusa la denominazione di una funzione check2()). Isolare tutto ciò che può essere isolato. A volte ne hai bisogno elsema non lo vedo qui.


In genere preferisco anche questo, per il motivo indicato qui. Tuttavia, di solito inserisco un commento alla fine di ogni ifblocco di } //elseper chiarire, durante la lettura del codice, che l'unica esecuzione prevista dopo il ifblocco di codice è se la condizione ifnell'istruzione era falsa. Senza qualcosa del genere, in particolare se il codice all'interno del ifblocco diventa più complesso, potrebbe non essere chiaro a chi mantiene il codice che l'intenzione era di non eseguire mai oltre il ifblocco quando la ifcondizione è vera.
Makyen,

@Makyen sollevi un buon punto. Uno che stavo cercando di ignorare. Credo anche che qualcosa dovrebbe essere fatto dopo il ifper chiarire che questa sezione di codice è stata completata e la struttura è terminata. Per fare questo faccio quello che non ho fatto qui (perché non volevo distrarre dal punto principale del PO apportando altre modifiche). Faccio la cosa più semplice che posso per ottenere tutto ciò senza distrarmi. Aggiungo una riga vuota.
candied_orange,

27

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

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

Qui si dipende chiaramente dal valore di xin tutti e tre i casi. Se omettessi l'ultimo else, questo sarebbe meno chiaro.

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

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

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


6

Preferisco la seconda opzione (separata ifscon no else ife early return) MA che è purché i blocchi di codice siano brevi .

Se i blocchi di codice sono lunghi, è meglio else ifusarli, perché altrimenti non avresti una chiara comprensione dei vari punti di uscita del codice.

Per 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 tal caso, è meglio usarlo else if, memorizzare il codice di ritorno in una variabile e avere solo una 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 si dovrebbe cercare di abbreviare le funzioni, direi che è breve e si esce presto come nel primo frammento di codice.


1
Sono d'accordo con la tua premessa, ma fintanto che discuteremo di come dovrebbe essere il codice, potremmo anche non essere d'accordo sul fatto che i blocchi di codice dovrebbero essere brevi comunque? Penso che sia peggio avere un lungo blocco di codice non suddiviso in funzioni che usare altro se, se dovessi scegliere.
Kojiro,

1
Un argomento curioso. returnè all'interno di 3 righe ifquindi non è così diverso else ifnemmeno dopo 200 righe di codice. Il singolo stile di ritorno che introduci qui è una tradizione di c e di altre lingue che non segnalano errori con eccezioni. Il punto era creare un unico posto per ripulire le risorse. Le lingue di eccezione usano un finallyblocco per questo. Suppongo che questo crei anche un posto dove mettere un punto di interruzione se il tuo debugger non ti consentirà di metterne uno sulle funzioni chiudendo la parentesi graffa.
candied_orange,

4
Non mi piace affatto il compito retVal. Se è possibile uscire in modo sicuro da una funzione, farlo immediatamente, senza assegnare il valore sicuro da restituire a una variabile di una singola uscita dalla funzione. Quando si utilizza il singolo punto di ritorno in una funzione molto lunga, in genere non mi fido degli altri programmatori e finisco per cercare nel resto della funzione anche altre modifiche del valore di ritorno. Fallire velocemente e tornare presto sono, tra le altre, due regole che vivo.
Andy,

2
Secondo me è anche peggio per i blocchi lunghi. Cerco di uscire dalle funzioni il prima possibile, indipendentemente dal contesto esterno.
Andy,

2
Sono qui con DavidPacker. Lo stile di ritorno singolo ci obbliga a studiare tutti i punti di assegnazione e il codice dopo i punti di assegnazione. Per me sarebbe preferibile studiare i punti di uscita di uno stile di rientro anticipato, lungo o corto. Finché la lingua consente finalmente un blocco.
candied_orange,

4

Uso entrambi in circostanze diverse. In un controllo di convalida, ometto l'altro. In un flusso di controllo, uso l'altro.

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 dice di più come se stessi controllando prima tutti i prerequisiti, togliendoli di mezzo e poi passando al codice effettivo che desideri eseguire. In quanto tale, il succoso codice che si desidera veramente eseguire appartiene direttamente all'ambito della funzione; è di cosa tratta veramente la funzione.
Il secondo caso è più simile a entrambi i percorsi: codice valido da eseguire che è altrettanto rilevante per la funzione dell'altro in base a una condizione. Come tali, appartengono a livelli di portata simili tra loro.


0

L'unica situazione che abbia mai visto conta davvero è il 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 returndebba essere aggiunto o Arrays.asListrimosso. Non è possibile risolvere questo problema senza un'analisi più approfondita dei metodi correlati. Puoi evitare questa ambiguità usando sempre blocchi if-else completamente esaustivi. 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 non si aggiunga prima un ritorno esplicito if.

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

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

Avere tre uscite dalla funzione in quel modo è davvero confuso se stai cercando di seguire il codice attraverso e cattive pratiche.

Se si dispone di un unico punto di uscita per la funzione, sono necessari gli altri.

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

In effetti lascia 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;

Abbastanza giusto potresti discutere per un singolo ritorno anticipato sulla convalida dell'input. Evita semplicemente di avvolgere l'intera massa se la tua logica in un if.


3
Questo stile deriva da lingue con gestione esplicita delle risorse. Era necessario un unico punto per liberare risorse allocate. Nelle lingue con gestione automatica delle risorse un singolo punto di ritorno non è così importante. Dovresti piuttosto concentrarti sulla riduzione del numero e dell'ambito delle variabili.
Banthar,

a: nessuna lingua è specificata nella domanda. b: penso che ti sbagli su questo. ci sono molte buone ragioni per un singolo ritorno. riduce la complessità e migliora la leggibilità
Ewan

1
A volte riduce la complessità, a volte aumenta. Vedi wiki.c2.com/?ArrowAntiPattern
Robert Johnson

No, penso che riduca sempre la complessità ciclomatica, vedi il mio secondo esempio. check2 funziona sempre
Ewan

avere il ritorno anticipato è un'ottimizzazione che complica il codice spostando un po 'di codice in un condizionale
Ewan

-3

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

Se l'autore ritenesse che la dichiarazione else fosse necessaria e quindi non comprendesse le implicazioni del flusso di controllo della dichiarazione di ritorno, sicuramente una tale mancanza di comprensione è una delle principali fonti di bug, in questa funzione e in generale.


3
Anche se sono d'accordo con te, questo non è un sondaggio di opinione. Si prega di eseguire il backup dei reclami o gli elettori non ti tratteranno gentilmente.
candied_orange,
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.