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


304

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


9
Sono un po 'in ritardo per questa discussione, quindi non inserirò una risposta; Ci ho pensato anche due anni fa: lecterror.com/articles/view/code-formatting-and-readability Trovo che il secondo sia più facile da leggere, modificare, mantenere e eseguire il debug. Ma forse sono solo io :)
dottor Hannibal Lecter il


4
ora questa domanda è un ottimo esempio di domanda basata sull'opinione pubblica
Rudolf Olah,

2
e se non ci fossero prove assolute in una o nell'altra direzione? Quando vengono fornite argomentazioni sufficienti in una direzione e in un'altra e c'è una votazione se la risposta è corretta, la rende molto utile. Trovo che domande come questa siano dannose per il valore di questo sito.
FS

9
E mi piacciono le domande e le risposte basate sull'opinione. Mi dicono quale maggioranza preferisce e ciò mi permette di scrivere il codice per la lettura degli altri.
Zygimantas,

Risposte:


402

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


7
Smalltalk chiama queste "clausole di guardia". Almeno questo è ciò che Kent Beck li chiama in Smalltalk Best Practice Patterns; Non so se sia un linguaggio comune.
Frank Shearar,

154
Uscire presto ti consente di estrarre roba dal tuo stack mentale limitato. :)
Joren,

50
Ho già sentito questo chiamato "il modello dei buttafuori": sbarazzarsi di casi cattivi prima che entrino nella porta.
RevBingo,

14
Andrei persino lontano e direi che questa è SOLO la cosa giusta da fare.
Oliver Weiler il

38
Inoltre non continui ad aggiungere rientri se ti sbarazzi dei casi limite all'inizio.
Doppelgreener,

170

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.


20
Il vantaggio di un singolo punto di uscita è che ... esiste un solo punto di uscita! Con il tuo esempio, ci sono diversi punti che potrebbero tornare. Con una funzione più complessa, che potrebbe trasformarsi in un punto di caccia all'uscita quando cambia il formato del valore di ritorno. Certo, ci sono momenti in cui forzare un singolo punto di uscita non ha senso.
Giovanni L

71
Le grandi funzioni di @JohnL sono il problema, non più punti di uscita. A meno che tu non stia lavorando in un contesto in cui una chiamata di funzione aggiuntiva rallenterà immensamente il tuo codice, ovviamente ...
Dan Rosenstark,

4
@Yar: True, ma il punto è valido. Non proverò a convertire nessuno, e stavo solo sottolineando il vantaggio di scegliere il minor numero possibile di punti di uscita (e l'esempio di Jason era comunque una specie di uomo di paglia). La prossima volta che ti stai strappando i capelli cercando di capire perché SomeFunction a volte restituisce valori strani, forse ti piacerebbe poter aggiungere una chiamata di registrazione appena prima del ritorno. Molto più facile da eseguire il debug se c'è solo uno dei bugger! :)
JohnL

76
@Jason Viers Come se un singolo return value;aiutasse!?! Quindi bisogna cacciare una mezza dozzina value = ..., con lo svantaggio che non si è mai sicuri che il valore non verrà modificato tra questo incarico e il ritorno finale. Almeno un ritorno immediato è chiaro che nulla cambierà più il risultato.
Sjoerd,

5
@Sjoed: io secondo Sjoerd qui. Se si desidera registrare il risultato, è possibile accedere al sito del chiamante. Se vuoi conoscere il motivo, devi accedere ad ogni punto di uscita / assegnazione in modo che entrambi siano identici in questo aspetto.
Matthieu M.,

32

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, scoppierò presto. Allo stesso modo, se una condizione genera un ifblocco gigante in funzione, anch'io lo farò scoppiare presto.

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

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

9
E se il risultato finale è mantenibile, a chi importa quale stile è stato selezionato?
Jeff Siver,

3
@Jeff Siver - Quindi perché questa tende ad essere una domanda di stile "guerra santa", alla fine della giornata si tratta di preferenze personali e qualunque cosa la guida di stile interna dica.
rjzii,

1
La chiave da asporto qui è che sta lanciando un'eccezione invece di tornare presto. Il valore di ritorno non deve essere riproposto per i controlli di validità. E se avessi avuto varie condizioni e volessi far sapere al codice usando il metodo perché non è riuscito? Improvvisamente potresti restituire dati aziendali reali, niente (risultato vuoto) o molte stringhe, codici, numeri diversi ... da un singolo metodo. Giusto per descrivere perché non è riuscito. No grazie.
DanMan,

che dire della complessità ciclomatica come suggerisce il mio compilatore? non è meglio non annidare il codice se uno può aiutarlo?
l --''''''--------- '' '' '' '' '' ''

24

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.


13

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.


1
C ++ consente qualcosa chiamato optmization del valore di ritorno che consente al compilatore di omettere sostanzialmente l'operazione di copia che si verificherebbe normalmente quando si restituisce un valore. Tuttavia, in varie condizioni è difficile da fare e più valori di ritorno è uno di questi casi. In altre parole, l'utilizzo di più valori restituiti in C ++ può effettivamente rallentare il codice. Questo sicuramente vale per MSC, e dato che sarebbe abbastanza complicato (se non impossibile) implementare RVO con molteplici possibili valori di ritorno, è probabilmente un problema in tutti i compilatori.
Eamon Nerbonne,

1
In C, basta usare gotoe, possibilmente, un ritorno in due punti. Esempio (formattazione del codice non possibile nei commenti):foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
mirabilos

1
Invece di un goto preferisco "Metodo di estrazione". Quando pensi di dover implementare una variabile del valore di ritorno o un goto, nel tentativo di assicurarti che il tuo codice di pulizia sia sempre chiamato, è un odore che devi suddividere in più funzioni. Ciò consente di semplificare il codice condizionale complesso utilizzando le clausole di protezione e altre tecniche di restituzione anticipata, garantendo comunque che il codice di pulizia venga sempre eseguito.
BrandonL White

"qualcuno potrebbe modificare la tua funzione" - questa è una spiegazione così ridicola per il processo decisionale. Chiunque può fare qualsiasi cosa in futuro. Ciò non significa che dovresti fare qualcosa di specifico oggi per impedire a qualcuno di rompere le cose in futuro.
Victor Yarema,

Chiamato per rendere il codice mantenibile. Nel mondo reale il codice è scritto per scopi commerciali e talvolta uno sviluppatore in seguito deve modificarlo. Al momento ho scritto quella risposta anche se avevo patchato CURL per introdurre la cache e ricordare il disordine degli spaghetti che era che aveva davvero bisogno di una corretta riscrittura nel moderno C ++
CashCow

9

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


9

Uso entrambi.

Se DoSomethingsono 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.


2
Bellissimo no! Troppa rientranza!
JimmyKane,

@JimmyKane C'è solo tanta indentazione che può accadere in 3-5 righe, soprattutto perché sono necessarie 2 (?) Righe per livello, il resto viene indentato: uno per la struttura di controllo e l'inizio del blocco, uno per la fine del blocco.
Deduplicatore

8

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).

Detto in altro modo, è 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.


ma per uno strumento di analisi formale è possibile sintetizzare una funzione esterna con la semantica di cui lo strumento ha bisogno e mantenere il codice leggibile dall'uomo.
Tim Williscroft,

@ Tim. Dipende da quanto tempo vuoi fare e da cosa stai analizzando. Trovo SESE abbastanza leggibile se il programmatore era sano di mente sulle cose.
Paul Nathan,

Il mio atteggiamento nei confronti dell'analisi è modellato dalle ottimizzazioni del progetto Sé. Il 99% delle chiamate a metodi dinamici può essere risolto ed eliminato staticamente. quindi puoi analizzare un po 'di codice lineare. Come programmatore di lavoro, posso presumere in modo affidabile che la maggior parte del codice su cui lavorerò sia stato scritto da programmatori medi. Quindi non scriveranno codice molto carino.
Tim Williscroft,

@ Tim: abbastanza giusto. Anche se mi sento in dovere di sottolineare che i linguaggi statici hanno ancora molto da giocare.
Paul Nathan,

Un motivo che non regge di fronte alle eccezioni. Ecco perché l'uscita singola è ottima in C, ma non ti compra nulla in C ++.
Peter

7

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.


6

Dipende.

Restituzione anticipata se è presente qualche ovvia 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 scopri 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
Quando scrivo codice che può essere condiviso, il mio mantra è "Assumi niente. Non fidarti di nessuno". Dovresti sempre convalidare i tuoi input e qualsiasi stato esterno da cui dipendi. Meglio gettare un'eccezione che possibilmente corrompere qualcosa perché qualcuno ti ha dato dati errati.
TMN,

@TMN: buon punto.
Tavoli Bobby,

2
Il punto chiave qui è che in OO si lancia un'eccezione , non si ritorna . I ritorni multipli possono essere negativi, i lanci di più eccezioni non sono necessariamente un odore di codice.
Michael K,

2
@MichaelK: un metodo dovrebbe costituire un'eccezione se non è possibile soddisfare la post-condizione. In alcuni casi, un metodo dovrebbe uscire presto perché la post-condizione è stata raggiunta anche prima dell'avvio della funzione. Ad esempio, se si invoca un metodo "Imposta etichetta di controllo" per modificare l'etichetta di un controllo in, l'etichetta Freddella finestra è già Frede impostando il nome del controllo sul suo stato attuale si forzerebbe un ridisegno (che, sebbene potenzialmente utile in alcuni casi, essere fastidioso in quello a portata di mano), sarebbe perfettamente ragionevole avere il metodo set-name early-exit se i nomi vecchi e nuovi corrispondono.
supercat

3

Usa 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.

Specifico Delphi ...

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

per

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 exittutto.


2
Con nuovi Delphis, potrebbe essere abbreviato in if true then Exit(foo);uso spesso la tecnica per inizializzare prima resultdi nilo FALSE, rispettivamente, quindi controllare tutte le condizioni di errore e solo Exit;se è soddisfatta. Il caso di successo resultviene quindi (in genere) impostato da qualche parte alla fine del metodo.
JensG,

sì, mi piace quella nuova funzionalità, anche se sembra che sia solo un piacere placare i programmatori Java, la prossima cosa che sai che ci lasceranno definire le nostre variabili all'interno delle procedure.
Peter Turner,

E programmatori C #. Sì, a dire il vero, lo troverei davvero utile in quanto riduce il numero di righe tra la dichiarazione e l'uso (IIRC ha anche qualche metrica per questo, ho dimenticato il nome).
JensG,

2

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 .


+1 Per le clausole di guardia. Preferisco anche una guardia orientata positivamente, ad esempio: if (condition = false) restituisce invece di if (! Condition) return
JustinC

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


Sono d'accordo. Il rientro è ciò che causa problemi nella lettura. Meno rientro è, meglio è. Il tuo semplice esempio ha lo stesso livello di rientro ma quel primo esempio diventerà sicuramente più rientri che richiedono più potenza cerebrale.
user441521

1

Preferirei scrivere:

if(someCondition)
{
    SomeFunction();
}

2
eww. È quella pre-validazione? O è in un mehtod dedicato alla validazione DoSomeFunctionIfSomeCondition?
STW,

In che modo questo separa le tue preoccupazioni?
appena l'

2
Stai violando l'incapsulamento rendendo esterna l'implementazione della funzione (la sua logica di dipendenza).
TMN,

1
Se questo viene eseguito in un metodo pubblico e SomeFunction () è un metodo privato nella stessa classe, potrebbe andare bene. Ma se qualcun altro sta chiamando SomeFunction () dovresti duplicare i controlli lì. Trovo che sia meglio fare in modo che ogni metodo si occupi di ciò di cui ha bisogno per fare il suo lavoro, nessun altro dovrebbe saperlo.
Per Wiklander, l'

2
Questo è sicuramente lo stile che Robert C. Martin propone in "Codice pulito". Una funzione dovrebbe fare solo una cosa. Tuttavia, in "Modelli di attuazione", suggerisce Kent Beck, delle due opzioni proposte nel PO, la seconda è migliore.
Scott Whitlock,

1

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

0

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

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


2
Che cosa? Non puoi fare un ritorno anticipato in Java? C #, VB (. NET e 6) e apparentemente Java (cosa che supponevo abbia fatto, ma ho dovuto cercare da quando non uso il linguaggio da 15 anni), tutti permettono di tornare presto. quindi non accusare "linguaggi fortemente tipizzati" di non avere la funzione. stackoverflow.com/questions/884429/...
ps2goat

-1

Preferisco mantenere se le dichiarazioni sono piccole.

Quindi, scegliendo tra:

if condition:
   line1
   line2
    ...
   line-n

e

if not condition: return

line1
line2
 ...
line-n

Sceglierei quello che hai descritto come "ritorno anticipato".

Intendiamoci, non mi importa dei ritorni anticipati o altro, mi piace davvero semplificare il codice, abbreviare i corpi delle istruzioni if, ecc.

Se nidificati se e per e mentre sono orribili , evitali a tutti i costi.


-2

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


-2

Mi alleno a livello di insuccesso a livello di funzione. Mantiene il codice coerente e pulito (per me e quelli con cui ho lavorato). Per questo motivo torno sempre presto.

Per alcune condizioni spesso controllate, è possibile implementare aspetti per tali controlli se si utilizza AOP.

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.