Devo tornare da una funzione in anticipo o utilizzare un'istruzione if? [chiuso]

283

Ho spesso scritto questo tipo di funzione in entrambi i formati, e mi chiedevo se un formato è preferito rispetto ad un altro, e perché.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

o

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

Di solito codifico con il primo dato che è il modo in cui il mio cervello lavora durante la codifica, anche se penso di preferire il secondo perché si occupa di qualsiasi errore e lo trovo più facile da leggere

    
posta Rachel 07.07.2017 - 18:43
fonte

19 risposte

379

Preferisco il secondo stile. Prima di tutto, cerca di invitare i casi invalidi, semplicemente uscendo o sollevando le eccezioni come opportuno, inserisci una riga vuota, quindi aggiungi il corpo "reale" del metodo. Lo trovo più facile da leggere.

    
risposta data 11.11.2010 - 20:27
fonte
157

Sicuramente quest'ultimo. Il primo non sembra male in questo momento, ma quando ottieni codice più complesso, non riesco a immaginare che qualcuno possa pensarlo:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

è più leggibile di

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

Ammetto pienamente di non aver mai compreso il vantaggio dei singoli punti di uscita.

    
risposta data 07.07.2017 - 18:44
fonte
31

Dipende - In generale non ho intenzione di andare fuori dal mio modo di provare a spostare un po 'di codice in giro per uscire dalla funzione in anticipo - il compilatore in genere si prenderà cura di quello per me. Detto questo, se ci sono alcuni parametri di base in alto che ho bisogno e non posso continuare diversamente, farò un breakout presto. Allo stesso modo, se una condizione genera un gigantesco blocco if in funzione, lo farò presto anche in caso di breakout.

Ciò detto, se una funzione richiede alcuni dati quando viene chiamata, di solito farò un'eccezione (vedi esempio) invece di tornare indietro.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}
    
risposta data 07.07.2017 - 18:44
fonte
23

Preferisco il rendimento anticipato.

Se hai un punto di ingresso e un punto di uscita, devi sempre seguire l'intero codice nella tua testa fino al punto di uscita (non sai mai se un altro pezzo di codice al di sotto fa qualcos'altro al risultato , quindi devi rintracciarlo fino a quando esiste). Tu fai quello non importa quale ramo determina il risultato finale. È difficile da seguire.

Con una voce e più esiti, ritorni quando hai il risultato e non preoccuparti di rintracciarlo fino in fondo per vedere che nessuno fa nient'altro (perché non ci sarà nient'altro da quando sei tornato) . È come avere il corpo del metodo diviso in più passaggi, che ogni passo con la possibilità di restituire il risultato o lasciare che il prossimo passo tenti la fortuna.

    
risposta data 11.11.2010 - 21:50
fonte
12

Nella programmazione C in cui è necessario pulire manualmente, c'è molto da dire per il ritorno di un punto. Anche se al momento non è necessario pulire qualcosa, qualcuno potrebbe modificare la tua funzione, assegnare qualcosa e aver bisogno di ripulirlo prima di tornare. Se ciò dovesse accadere, sarebbe un lavoro da incubo esaminare tutte le dichiarazioni di ritorno.

Nella programmazione in C ++ hai i distruttori e anche ora le guardie dell'oscilloscopio. Tutti questi devono essere qui per garantire che il codice sia al sicuro, in modo che le eccezioni siano al sicuro, quindi il codice è ben protetto dall'uscita anticipata e quindi non ha uno svantaggio logico ed è puramente un problema di stile.

Non sono abbastanza informato su Java, se verrà chiamato "finally" il codice di blocco e se i finalizzatori possono gestire la situazione di dover garantire che qualcosa accada.

C # Non posso certamente rispondere.

Il linguaggio D ti offre le protezioni di uscita dell'oscuramento incorporate e pertanto è ben preparato per l'uscita anticipata e pertanto non dovrebbe presentare un problema diverso dallo stile.

Ovviamente le funzioni non dovrebbero essere così lunghe all'inizio e se hai un'enorme istruzione switch probabilmente anche il tuo codice è scarsamente fattorizzato.

    
risposta data 18.02.2011 - 14:21
fonte
8

Ritorni anticipati per-the-win. Possono sembrare brutti, ma molto meno brutti dei big if wrapper, specialmente se ci sono più condizioni da controllare.

    
risposta data 11.11.2010 - 20:26
fonte
8

Io uso entrambi.

Se DoSomething è 3-5 righe di codice, il codice sarà semplicemente bello usando il primo metodo di formattazione.

Ma se ha molte più righe di quella, preferisco il secondo formato. Non mi piace quando le parentesi di apertura e chiusura non sono sullo stesso schermo.

    
risposta data 12.11.2010 - 19:10
fonte
7

Una ragione classica per single-entry-single-exit è che altrimenti la semantica formale diventa indicibilmente brutta altrimenti (lo stesso motivo per cui GOTO era considerato dannoso).

In altre parole, è più facile ragionare su quando il software uscirà dalla routine se si ha solo un ritorno. Che è anche un argomento contro le eccezioni.

In genere minimizzo l'approccio di ritorno anticipato.

    
risposta data 11.11.2010 - 20:40
fonte
6

Personalmente, preferisco eseguire i controlli delle condizioni pass / fail all'inizio. Ciò mi consente di raggruppare la maggior parte dei guasti più comuni nella parte superiore della funzione con il resto della logica da seguire.

    
risposta data 11.11.2010 - 20:26
fonte
5

Dipende.

Ritorno anticipato se c'è qualche evidente condizione di punto morto da verificare immediatamente che renderebbe inutile l'esecuzione del resto della funzione. *

Imposta Retval + singolo ritorno se la funzione è più complessa e potrebbe avere più punti di uscita (problema di leggibilità).

* Questo può spesso indicare un problema di progettazione. Se scopri che molti dei tuoi metodi devono controllare qualche stato di external / paramater o altro prima di eseguire il resto del codice, probabilmente è qualcosa che dovrebbe essere gestito dal chiamante.

    
risposta data 11.11.2010 - 21:22
fonte
2

Utilizza un If

Nel libro di Don Knuth su GOTO gli leggo un motivo per avere sempre la condizione più probabile che viene prima in una dichiarazione if. Partendo dal presupposto che questa è ancora un'idea ragionevole (e non una per pura considerazione per la velocità dell'era). Direi che i ritorni anticipati non sono una buona pratica di programmazione, soprattutto considerando il fatto che sono più spesso utilizzati per la gestione degli errori, a meno che il tuo codice abbia più probabilità di fallire che non fallire :-)

Se segui il consiglio di cui sopra, dovresti inserire tale ritorno nella parte inferiore della funzione, e quindi potresti anche non chiamarlo un ritorno lì, basta impostare il codice di errore e restituirlo due righe quindi . In tal modo raggiungere l'1 uscita 1 ideale ideale.

Delphi Specific ...

Sono convinto che questa sia una buona pratica di programmazione per i programmatori Delphi, anche se non ho alcuna prova. Pre-D2009, non abbiamo un modo atomico per restituire un valore, abbiamo exit; e result := foo; o potremmo semplicemente generare eccezioni.

Se dovessi sostituire

if (true) {
 return foo;
} 

per

if true then 
begin
  result := foo; 
  exit; 
end;

potresti semplicemente ammalarti di vederlo in cima a ciascuna delle tue funzioni e preferire

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

ed evita del tutto exit .

    
risposta data 07.07.2017 - 18:46
fonte
1

Sono d'accordo con la seguente affermazione:

I'm personally a fan of guard clauses (the second example) as it reduces the indenting of the function. Some people don't like them because it results in multiple return points from the function, but I think it's clearer with them.

Tratto da questa domanda in stackoverflow .

    
risposta data 23.05.2017 - 14:40
fonte
1

Uso i ritorni anticipati quasi esclusivamente in questi giorni, fino all'estremo. Scrivo questo

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

come

self = [super init];
if (!self)
    return;

// your code here

return self;

ma in realtà non importa. Se hai più di uno o due livelli di nidificazione nelle tue funzioni, devono essere sballati.

    
risposta data 07.07.2017 - 18:48
fonte
1

Preferirei scrivere:

if(someCondition)
{
    SomeFunction();
}
    
risposta data 07.07.2017 - 18:49
fonte
1

Come te, di solito scrivo il primo, ma preferisco l'ultimo. Se ho molti controlli annidati, di solito rifattore al secondo metodo.

Non mi piace come la gestione degli errori viene spostata dal controllo.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Preferisco questo:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something
    
risposta data 07.07.2017 - 18:50
fonte
0

Le condizioni in alto sono chiamate "precondizioni". Inserendo if(!precond) return; , stai elencando visivamente tutte le precondizioni.

L'uso del grande blocco "if-else" può aumentare il sovraccarico del trattino (ho dimenticato la citazione sui rientri a 3 livelli).

    
risposta data 03.08.2016 - 04:41
fonte
-1

Preferisco mantenere le istruzioni se piccole.

Quindi, scegliendo tra:

if condition:
   line1
   line2
    ...
   line-n

e

if not condition: return

line1
line2
 ...
line-n

Scelgo quello che hai descritto come "ritorno anticipato".

Intendiamoci, non mi importa dei ritorni anticipati o di qualsiasi altra cosa, mi piace solo semplificare il codice, abbreviare i corpi delle istruzioni if, ecc.

Se nidificati if e for e while sono orribili , evitali a tutti i costi.

    
risposta data 07.07.2017 - 18:50
fonte
-2

Come altri dicono, dipende. Per le piccole funzioni che restituiscono valori, posso codificare i ritorni anticipati. Ma per funzioni importanti, mi piace avere sempre un posto nel codice in cui so che posso mettere qualcosa che verrà eseguito prima che ritorni.

    
risposta data 11.11.2010 - 20:37
fonte
-2

Pratico fail-fast a livello di funzione. Mantiene il codice coerente e pulito (per me e per quelli con cui ho lavorato). Per questo motivo, torno sempre presto.

Per alcune condizioni spesso controllate, puoi implementare aspetti per questi controlli se utilizzi AOP.

    
risposta data 11.11.2010 - 20:38
fonte

Leggi altre domande sui tag