it-swarm.it

Devo tornare presto da una funzione o utilizzare un'istruzione if?

Ho spesso scritto questo tipo di funzione in entrambi i formati e mi chiedevo se un formato fosse preferito a 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 poiché questo è il modo in cui il mio cervello funziona durante la codifica, anche se penso di preferire il secondo poiché si occupa di gestire immediatamente qualsiasi errore e trovo più facile da leggere

302
Rachel

Preferisco il secondo stile. Per prima cosa, evita i casi non validi, semplicemente uscendo o sollevando le eccezioni nel modo appropriato, inserendo una riga vuota, quindi aggiungi il corpo "reale" del metodo. Trovo più facile da leggere.

400
Mason Wheeler

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

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 capito il vantaggio dei singoli punti di uscita.

169
Jason Viers

Dipende - In generale non ho intenzione di fare del mio meglio per provare a spostare un sacco di codice per uscire presto dalla funzione - il compilatore generalmente se ne occuperà per me. Detto questo, tuttavia, se nella parte superiore ci sono alcuni parametri di base di cui ho bisogno e che non posso continuare altrimenti, uscirò presto. Allo stesso modo, se una condizione genera un gigantesco blocco if in funzione, avrò lo sblocco anticipato anche a causa di quello.

Detto questo, tuttavia, se una funzione richiede alcuni dati quando viene chiamata, di solito genererò un'eccezione (vedi esempio) anziché restituirla.

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;
}
32
rjzii

Preferisco il ritorno anticipato.

Se hai un punto di ingresso e un punto di uscita, devi sempre tenere traccia dell'intero codice nella tua testa fino al punto di uscita (non sai mai se qualche altra parte di codice sotto fa qualcos'altro al risultato, quindi tu devo rintracciarlo fino a quando esiste). Lo fai senza mater quale ramo determina il risultato finale. Questo è difficile da seguire.

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

24
user7197

Nella programmazione in C dove devi ripulire manualmente c'è molto da dire per il ritorno di un punto. Anche se in questo momento non è necessario ripulire qualcosa, qualcuno potrebbe modificare la funzione, allocare qualcosa e ripulirla prima del ritorno. Se ciò accade, sarà un lavoro da incubo che esamina tutte le dichiarazioni di reso.

Nella programmazione C++ hai distruttori e anche adesso guardie di uscita dall'ambito. Tutto ciò deve essere qui per garantire che il codice sia in primo luogo sicuro dalle eccezioni, quindi il codice è ben protetto contro l'uscita anticipata e quindi farlo non ha alcun lato negativo logico ed è puramente un problema di stile.

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

C # Non posso certamente rispondere.

Il linguaggio D offre protezioni di uscita dell'oscilloscopio integrate adeguate e pertanto è ben preparato per l'uscita anticipata e pertanto non deve presentare un problema diverso dallo stile.

Ovviamente le funzioni non dovrebbero essere così lunghe in primo luogo, e se si dispone di un'enorme istruzione switch, probabilmente anche il codice viene considerato male.

13
CashCow

so entrambi.

Se DoSomething è composto da 3-5 righe di codice, il codice avrà un bell'aspetto usando il primo metodo di formattazione.

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

9
azheglov

Ritorni anticipati per la vittoria. Possono sembrare brutti, ma molto meno brutti dei grandi wrapper if, specialmente se ci sono più condizioni da controllare.

9
STW

Un motivo classico per single-entry-single-exit è che altrimenti la semantica formale diventa indicibilmente brutta altrimenti (stesso motivo per cui GOTO è stato considerato dannoso).

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

In genere minimizzo l'approccio di ritorno anticipato.

8
Paul Nathan

Personalmente, preferisco fare i controlli delle condizioni di superamento/fallimento all'inizio. Ciò mi consente di raggruppare la maggior parte degli errori più comuni nella parte superiore della funzione con il resto della logica da seguire.

7
nathan

Dipende.

Restituzione anticipata se esiste un'evidente condizione di vicolo cieco da verificare immediatamente che renderebbe inutile eseguire il resto della funzione. *

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

* Questo può spesso indicare un problema di progettazione. Se trovi che molti dei tuoi metodi devono controllare alcuni stati esterni/paramater o simili prima di eseguire il resto del codice, è probabilmente qualcosa che dovrebbe essere gestito dal chiamante .

6
Bobby Tables

sa un If

Nel libro di Don Knuth su GOTO l'ho letto per spiegare perché le condizioni più probabili vengono sempre prima in un'istruzione if. Partendo dal presupposto che questa è ancora un'idea ragionevole (e non per pura considerazione della velocità dell'era). Direi che i ritorni anticipati non sono una buona pratica di programmazione, soprattutto considerando il fatto che sono più spesso usati per la gestione degli errori, a meno che il tuo codice non abbia più probabilità di fallire che non fallire :-)

Se segui i consigli di cui sopra, dovresti mettere quel ritorno in fondo alla funzione, e quindi potresti anche non chiamarlo un ritorno lì, basta impostare il codice di errore e restituirlo due righe da qui. In tal modo raggiungendo l'ideale 1 entrata 1 uscita.

Delphi Specific ...

Sono dell'idea che questa sia una buona pratica di programmazione per i programmatori Delphi, sebbene non ne abbia alcuna prova. Prima del 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;
} 

for

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

potresti semplicemente stancarti di vederlo in cima a ciascuna delle tue funzioni e preferisci

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

ed evita del tutto exit.

3
Peter Turner

Sono d'accordo con la seguente dichiarazione:

Personalmente sono un fan delle clausole di guardia (il secondo esempio) in quanto riduce il rientro della funzione. Ad alcune persone non piacciono perché si traducono in più punti di ritorno dalla funzione, ma penso che sia più chiaro con loro.

Tratto da questa domanda in stackoverflow .

2
Toto

Preferirei scrivere:

if(someCondition)
{
    SomeFunction();
}
1
Josh K

Come te, di solito scrivo il primo, ma preferisco l'ultimo. Se ho molti controlli annidati, di solito mi riferisco 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
1
jolt

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 non importa davvero. Se hai più di uno o due livelli di annidamento nelle tue funzioni, devono essere risolti.

1
Dan Rosenstark

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

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

0
Ming-Tang