Una funzione dovrebbe avere una sola dichiarazione di reso?

Ci sono buone ragioni per cui è una pratica migliore avere solo una dichiarazione di ritorno in una funzione?

O è bene tornare da una funzione non appena è logicamente corretto farlo, nel senso che potrebbero esserci molte dichiarazioni di ritorno nella funzione?

Spesso ho diverse dichiarazioni all’inizio di un metodo per tornare per situazioni “facili”. Ad esempio, questo:

public void DoStuff(Foo foo) { if (foo != null) { ... } } 

… può essere reso più leggibile (IMHO) come questo:

 public void DoStuff(Foo foo) { if (foo == null) return; ... } 

Quindi sì, penso che sia corretto avere più “punti di uscita” da una funzione / metodo.

Nessuno ha menzionato o citato codice completo, quindi lo farò.

17.1 ritorno

Riduci al minimo il numero di ritorni in ogni routine . È più difficile capire una routine se, leggendola in fondo, non si è consapevoli della possibilità che sia ritornata da qualche parte sopra.

Utilizzare un ritorno quando migliora la leggibilità . In alcune routine, una volta che si conosce la risposta, si desidera restituirla immediatamente alla routine chiamante. Se la routine è definita in modo tale da non richiedere alcuna pulizia, la restituzione immediata implica la necessità di scrivere altro codice.

Direi che sarebbe incredibilmente sconsigliabile decidere arbitrariamente su più punti di uscita poiché ho trovato che la tecnica è utile in pratica più e più volte , infatti ho spesso rifatto il codice esistente in più punti di uscita per maggiore chiarezza. Possiamo confrontare i due approcci in questo modo:

 string fooBar(string s, int? i) { string ret = ""; if(!string.IsNullOrEmpty(s) && i != null) { var res = someFunction(s, i); bool passed = true; foreach(var r in res) { if(!r.Passed) { passed = false; break; } } if(passed) { // Rest of code... } } return ret; } 

Confronta questo con il codice in cui sono consentiti più punti di uscita: –

 string fooBar(string s, int? i) { var ret = ""; if(string.IsNullOrEmpty(s) || i == null) return null; var res = someFunction(s, i); foreach(var r in res) { if(!r.Passed) return null; } // Rest of code... return ret; } 

Penso che quest’ultimo sia considerevolmente più chiaro. Per quanto posso dire la critica dei punti di uscita multipli è un punto di vista piuttosto arcaico in questi giorni.

Al momento sto lavorando a un codice base in cui due delle persone che ci lavorano si iscrivono ciecamente alla teoria del “single point of exit” e posso dirti che, per esperienza, è una pratica orribile orribile. Rende il codice estremamente difficile da mantenere e ti mostrerò il perché.

Con la teoria del “single point of exit”, si finisce inevitabilmente con il codice che assomiglia a questo:

 function() { HRESULT error = S_OK; if(SUCCEEDED(Operation1())) { if(SUCCEEDED(Operation2())) { if(SUCCEEDED(Operation3())) { if(SUCCEEDED(Operation4())) { } else { error = OPERATION4FAILED; } } else { error = OPERATION3FAILED; } } else { error = OPERATION2FAILED; } } else { error = OPERATION1FAILED; } return error; } 

Questo non solo rende il codice molto difficile da seguire, ma ora dice che in seguito devi tornare indietro e aggiungere un’operazione tra 1 e 2. Devi indentare praticamente l’intera funzione, e buona fortuna assicurandoti tutto le tue condizioni if ​​/ else e le parentesi sono abbinate correttamente.

Questo metodo rende la manutenzione del codice estremamente difficile e soggetta a errori.

La programmazione strutturata dice che dovresti avere sempre una dichiarazione di ritorno per funzione. Questo per limitare la complessità. Molte persone, come Martin Fowler, sostengono che è più semplice scrivere funzioni con più dichiarazioni di ritorno. Presenta questa argomentazione nel classico libro di refactoring che ha scritto. Funziona bene se segui il suo altro consiglio e scrivi piccole funzioni. Sono d’accordo con questo punto di vista e solo i puristi rigorosi della programmazione strutturata aderiscono a dichiarazioni di ritorno singole per funzione.

Come nota Kent Beck quando discute le clausole di guardia nei Pattern di implementazione, facendo sì che una routine abbia un singolo punto di entrata e di uscita …

“era quello di prevenire la confusione ansible quando si entrava e usciva da molti luoghi nella stessa routine, era logico quando applicato a FORTRAN o ai programmi in linguaggio assembly scritti con molti dati globali in cui persino capire quali istruzioni erano state eseguite era un duro lavoro. con metodi piccoli e soprattutto dati locali, è inutilmente conservativo. ”

Trovo che una funzione scritta con le clausole di protezione sia molto più facile da seguire di una lunga serie annidata di istruzioni if then else .

In una funzione che non ha effetti collaterali, non ci sono buone ragioni per avere più di un singolo ritorno e dovresti scriverli in uno stile funzionale. In un metodo con effetti collaterali, le cose sono più sequenziali (indicizzate nel tempo), quindi si scrive in uno stile imperativo, usando l’istruzione return come comando per interrompere l’esecuzione.

In altre parole, quando ansible, favorisci questo stile

 return a > 0 ? positively(a): negatively(a); 

oltre questo

 if (a > 0) return positively(a); else return negatively(a); 

Se ti ritrovi a scrivere diversi livelli di condizioni nidificate, probabilmente c’è un modo in cui puoi refactoring, usando ad esempio l’elenco dei predicati. Se scopri che i tuoi if e i suoi sono distanti tra loro sintatticamente, potresti volerli suddividere in funzioni più piccole. Un blocco condizionale che copre più di una schermata di testo è difficile da leggere.

Non esiste una regola dura e veloce applicabile a tutte le lingue. Qualcosa come avere un’unica dichiarazione di ritorno non renderà il tuo codice buono. Ma un buon codice tenderà a permetterti di scrivere le tue funzioni in questo modo.

L’ho visto negli standard di codifica per C ++ che erano un hang-over di C, come se tu non avessi RAII o altra gestione automatica della memoria, quindi devi pulire per ogni ritorno, che significa sia taglia e incolla del clean-up o del goto (logicamente lo stesso di “finally” nelle lingue gestite), entrambi considerati cattivi. Se le tue pratiche utilizzano puntatori e raccolte intelligenti in C ++ o in un altro sistema di memoria automatico, non c’è una ragione valida per farlo, e diventa tutto di leggibilità e di più di una sentenza.

Mi piace l’idea che le dichiarazioni di ritorno nel mezzo della funzione siano sbagliate. Puoi usare i ritorni per build alcune clausole di guardia nella parte superiore della funzione e, naturalmente, dire al compilatore cosa restituire alla fine della funzione senza problemi, ma i ritorni nel mezzo della funzione possono essere facili da perdere e possono rendere la funzione più difficile da interpretare.

Ci sono buone ragioni per cui è una pratica migliore avere solo una dichiarazione di ritorno in una funzione?

, ci sono:

  • L’unico punto di uscita offre un posto eccellente per affermare le tue condizioni post.
  • Essere in grado di mettere un breakpoint del debugger su un unico ritorno alla fine della funzione è spesso utile.
  • Meno ricavi significa meno complessità. Il codice lineare è generalmente più semplice da capire.
  • Se provare a semplificare una funzione a un singolo ritorno causa complessità, allora è un incentivo per il refactoring di funzioni più piccole, più generali e più facili da capire.
  • Se sei in una lingua senza distruttori o se non utilizzi RAII, un singolo ritorno riduce il numero di posti da pulire.
  • Alcune lingue richiedono un singolo punto di uscita (ad esempio, Pascal e Eiffel).

La domanda è spesso posta come una falsa dicotomia tra rendimenti multipli o affermazioni profondamente radicate. C’è quasi sempre una terza soluzione che è molto lineare (senza nidificazione profonda) con un solo punto di uscita.

Aggiornamento : Apparentemente le linee guida MISRA promuovono anche l’uscita singola .

Per essere chiari, non sto dicendo che è sempre sbagliato avere più ritorni. Ma date soluzioni altrimenti equivalenti, ci sono molti buoni motivi per preferire quello con un singolo ritorno.

Avere un singolo punto di uscita fornisce un vantaggio nel debug, perché consente di impostare un singolo punto di interruzione alla fine di una funzione per vedere quale valore verrà effettivamente restituito.

In generale, provo ad avere un solo punto di uscita da una funzione. Ci sono volte, tuttavia, che in questo modo si finisce per creare un corpo di funzione più complesso del necessario, nel qual caso è meglio avere più punti di uscita. Deve essere davvero un “giudizio” basato sulla complessità risultante, ma l’objective dovrebbe essere il minor numero ansible di punti di uscita senza sacrificare la complessità e la comprensibilità.

No, perché non viviamo più negli anni ’70 . Se la tua funzione è abbastanza lunga da rendere più ritorni un problema, è troppo lungo.

(A parte il fatto che qualsiasi funzione multi-linea in una lingua con eccezioni avrà comunque più punti di uscita.)

La mia preferenza sarebbe per l’uscita singola a meno che non complichi davvero le cose. Ho scoperto che in alcuni casi, più punti esistenti possono mascherare altri problemi di progettazione più significativi:

 public void DoStuff(Foo foo) { if (foo == null) return; } 

Nel vedere questo codice, vorrei subito chiedere:

  • ‘Foo’ è mai nullo?
  • In tal caso, quanti client di “DoStuff” chiamano mai la funzione con null “foo”?

A seconda delle risposte a queste domande potrebbe essere questo

  1. il controllo è inutile in quanto non è mai vero (cioè dovrebbe essere un’affermazione)
  2. il controllo è molto raro vero e quindi potrebbe essere meglio cambiare quelle funzioni specifiche del chiamante in quanto dovrebbero probabilmente intraprendere comunque qualche altra azione.

In entrambi i casi di cui sopra, il codice può probabilmente essere rielaborato con un’asserzione per garantire che “pippo” non sia mai nullo e che i chiamanti pertinenti siano cambiati.

Ci sono altri due motivi (penso specifico al codice C ++) dove esistono più esiti in grado di avere un effetto negativo . Sono le dimensioni del codice e le ottimizzazioni del compilatore.

Un object C ++ non POD in scope all’uscita di una funzione avrà il suo distruttore chiamato. Laddove ci sono diverse dichiarazioni di ritorno, potrebbe essere il caso che ci siano oggetti diversi nello scope e quindi l’elenco dei distruttori da chiamare sarà diverso. Il compilatore deve quindi generare codice per ogni dichiarazione di ritorno:

 void foo (int i, int j) { A a; if (i > 0) { B b; return ; // Call dtor for 'b' followed by 'a' } if (i == j) { C c; B b; return ; // Call dtor for 'b', 'c' and then 'a' } return 'a' // Call dtor for 'a' } 

Se la dimensione del codice è un problema, allora potrebbe essere qualcosa che vale la pena di evitare.

L’altro problema riguarda “Named Return Value OptimiZation” (alias Copy Elision, ISO C ++ ’03 12.8 / 15). C ++ consente a un’implementazione di saltare la chiamata al costruttore di copie se può:

 A foo () { A a1; // do something return a1; } void bar () { A a2 ( foo() ); } 

Prendendo il codice così com’è, l’object ‘a1’ è costruito in ‘pippo’ e quindi il suo costrutto di copia sarà chiamato a build ‘a2’. Tuttavia, copia elision consente al compilatore di build ‘a1’ nella stessa posizione sullo stack come ‘a2’. Non è quindi necessario “copiare” l’object quando la funzione ritorna.

Punti di uscita multipli complicano il lavoro del compilatore nel tentativo di rilevarlo, e almeno per una versione relativamente recente di VC ++ l’ottimizzazione non ha avuto luogo dove il corpo della funzione ha avuto più ritorni. Vedere Ottimizzazione del valore restituito con nome in Visual C ++ 2005 per ulteriori dettagli.

Avere un singolo punto di uscita riduce la complessità ciclomatica e quindi, in teoria , riduce la probabilità che introduci bug nel codice quando lo cambi. La pratica, tuttavia, tende a suggerire che è necessario un approccio più pragmatico. Pertanto, tendo a mirare ad avere un singolo punto di uscita, ma consento al mio codice di averne diversi se è più leggibile.

Mi sforzo di usare solo una dichiarazione di return , poiché in un certo senso genererà un odore di codice. Lasciatemi spiegare:

 function isCorrect($param1, $param2, $param3) { $toret = false; if ($param1 != $param2) { if ($param1 == ($param3 * 2)) { if ($param2 == ($param3 / 3)) { $toret = true; } else { $error = 'Error 3'; } } else { $error = 'Error 2'; } } else { $error = 'Error 1'; } return $toret; } 

(Le condizioni sono arbritarie …)

Più sono le condizioni, più grande diventa la funzione, più difficile è leggere. Quindi, se sei in sintonia con l’odore del codice, te ne accorgerai e vorresti rifattorizzare il codice. Due possibili soluzioni sono:

  • Ritorni multipli
  • Refactoring in funzioni separate

Resi multipli

 function isCorrect($param1, $param2, $param3) { if ($param1 == $param2) { $error = 'Error 1'; return false; } if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; } if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; } return true; } 

Funzioni separate

 function isEqual($param1, $param2) { return $param1 == $param2; } function isDouble($param1, $param2) { return $param1 == ($param2 * 2); } function isThird($param1, $param2) { return $param1 == ($param2 / 3); } function isCorrect($param1, $param2, $param3) { return !isEqual($param1, $param2) && isDouble($param1, $param3) && isThird($param2, $param3); } 

Certo, è più lungo e un po ‘disordinato, ma nel processo di refactoring della funzione in questo modo, abbiamo

  • creato un numero di funzioni riutilizzabili,
  • reso la funzione più leggibile, e
  • il focus delle funzioni è sul perché i valori sono corretti.

Direi che dovresti avere tutto il necessario o che rendono il codice più pulito (come le clausole di salvaguardia ).

Personalmente non ho mai sentito / visto alcuna “best practice” che affermi che dovresti avere una sola dichiarazione di ritorno.

Per la maggior parte, tendo ad uscire da una funzione il più presto ansible sulla base di un percorso logico (le clausole di guardia sono un eccellente esempio di questo).

Credo che i ritorni multipli di solito siano buoni (nel codice che scrivo in C #). Lo stile single-return è un holdover da C. Ma probabilmente non stai codificando in C.

There is no law requiring only one exit point for a method in all programming languages . Some people insist on the superiority of this style, and sometimes they elevate it to a “rule” or “law” but this belief is not backed up by any evidence or research.

More than one return style may be a bad habit in C code, where resources have to be explicitly de-allocated, but languages such as Java, C#, Python or JavaScript that have constructs such as automatic garbage collection and try..finally blocks (and using blocks in C#), and this argument does not apply – in these languages, it is very uncommon to need centralised manual resource deallocation.

There are cases where a single return is more readable, and cases where it isn’t. See if it reduces the number of lines of code, makes the logic clearer or reduces the number of braces and indents or temporary variables.

Therefore, use as many returns as suits your artistic sensibilities, because it is a layout and readability issue, not a technical one.

I have talked about this at greater length on my blog .

There are good things to say about having a single exit-point, just as there are bad things to say about the inevitable “arrow” programming that results.

If using multiple exit points during input validation or resource allocation, I try to put all the ‘error-exits’ very visibly at the top of the function.

Both the Spartan Programming article of the “SSDSLPedia” and the single function exit point article of the “Portland Pattern Repository’s Wiki” have some insightful arguments around this. Also, of course, there is this post to consider.

If you really want a single exit-point (in any non-exception-enabled language) for example in order to release resources in one single place, I find the careful application of goto to be good; see for example this rather contrived example (compressed to save screen real-estate):

 int f(int y) { int value = -1; void *data = NULL; if (y < 0) goto clean; if ((data = malloc(123)) == NULL) goto clean; /* More code */ value = 1; clean: free(data); return value; } 

Personally I, in general, dislike arrow programming more than I dislike multiple exit-points, although both are useful when applied correctly. The best, of course, is to structure your program to require neither. Breaking down your function into multiple chunks usually help 🙂

Although when doing so, I find I end up with multiple exit points anyway as in this example, where some larger function has been broken down into several smaller functions:

 int g(int y) { value = 0; if ((value = g0(y, value)) == -1) return -1; if ((value = g1(y, value)) == -1) return -1; return g2(y, value); } 

Depending on the project or coding guidelines, most of the boiler-plate code could be replaced by macros. As a side note, breaking it down this way makes the functions g0, g1 ,g2 very easy to test individually.

Obviously, in an OO and exception-enabled language, I wouldn't use if-statements like that (or at all, if I could get away with it with little enough effort), and the code would be much more plain. And non-arrowy. And most of the non-final returns would probably be exceptions.

In breve;

  • Few returns are better than many returns
  • More than one return is better than huge arrows, and guard clauses are generally ok.
  • Exceptions could/should probably replace most 'guard clauses' when possible.

You know the adage – beauty is in the eyes of the beholder .

Some people swear by NetBeans and some by IntelliJ IDEA , some by Python and some by PHP .

In some shops you could lose your job if you insist on doing this:

 public void hello() { if (....) { .... } } 

The question is all about visibility and maintainability.

I am addicted to using boolean algebra to reduce and simplify logic and use of state machines. However, there were past colleagues who believed my employ of “mathematical techniques” in coding is unsuitable, because it would not be visible and maintainable. And that would be a bad practice. Sorry people, the techniques I employ is very visible and maintainable to me – because when I return to the code six months later, I would understand the code clearly rather seeing a mess of proverbial spaghetti.

Hey buddy (like a formsr client used to say) do what you want as long as you know how to fix it when I need you to fix it.

I remember 20 years ago, a colleague of mine was fired for employing what today would be called agile development strategy. He had a meticulous incremental plan. But his manager was yelling at him “You can’t incrementally release features to users! You must stick with the waterfall .” His response to the manager was that incremental development would be more precise to customer’s needs. He believed in developing for the customers needs, but the manager believed in coding to “customer’s requirement”.

We are frequently guilty for breaking data normalization, MVP and MVC boundaries. We inline instead of constructing a function. We take shortcuts.

Personally, I believe that PHP is bad practice, but what do I know. All the theoretical arguments boils down to trying fulfill one set of rules

quality = precision, maintainability and profitability.

All other rules fade into the background. And of course this rule never fades:

Laziness is the virtue of a good programmer.

I lean towards using guard clauses to return early and otherwise exit at the end of a method. The single entry and exit rule has historical significance and was particularly helpful when dealing with legacy code that ran to 10 A4 pages for a single C++ method with multiple returns (and many defects). More recently, accepted good practice is to keep methods small which makes multiple exits less of an impedance to understanding. In the following Kronoz example copied from above, the question is what occurs in //Rest of code… ?:

 void string fooBar(string s, int? i) { if(string.IsNullOrEmpty(s) || i == null) return null; var res = someFunction(s, i); foreach(var r in res) { if(!r.Passed) return null; } // Rest of code... return ret; } 

I realise the example is somewhat contrived but I would be tempted to refactor the foreach loop into a LINQ statement that could then be considered a guard clause. Again, in a contrived example the intent of the code isn’t apparent and someFunction() may have some other side effect or the result may be used in the // Rest of code… .

 if (string.IsNullOrEmpty(s) || i == null) return null; if (someFunction(s, i).Any(r => !r.Passed)) return null; 

Giving the following refactored function:

 void string fooBar(string s, int? i) { if (string.IsNullOrEmpty(s) || i == null) return null; if (someFunction(s, i).Any(r => !r.Passed)) return null; // Rest of code... return ret; } 

One good reason I can think of is for code maintenance: you have a single point of exit. If you want to change the format of the result,…, it’s just much simpler to implement. Also, for debugging, you can just stick a breakpoint there 🙂

Having said that, I once had to work in a library where the coding standards imposed ‘one return statement per function’, and I found it pretty tough. I write lots of numerical computations code, and there often are ‘special cases’, so the code ended up being quite hard to follow…

Multiple exit points are fine for small enough functions — that is, a function that can be viewed on one screen length on its entirety. If a lengthy function likewise includes multiple exit points, it’s a sign that the function can be chopped up further.

That said I avoid multiple-exit functions unless absolutely necessary . I have felt pain of bugs that are due to some stray return in some obscure line in more complex functions.

I’ve worked with terrible coding standards that forced a single exit path on you and the result is nearly always unstructured spaghetti if the function is anything but trivial — you end up with lots of breaks and continues that just get in the way.

Single exit point – all other things equal – makes code significantly more readable. But there’s a catch: popular construction

 resulttype res; if if if... return res; 

is a fake, “res=” is not much better than “return”. It has single return statement, but multiple points where function actually ends.

If you have function with multiple returns (or “res=”s), it’s often a good idea to break it into several smaller functions with single exit point.

My usual policy is to have only one return statement at the end of a function unless the complexity of the code is greatly reduced by adding more. In fact, I’m rather a fan of Eiffel, which enforces the only one return rule by having no return statement (there’s just a auto-created ‘result’ variable to put your result in).

There certainly are cases where code can be made clearer with multiple returns than the obvious version without them would be. One could argue that more rework is needed if you have a function that is too complex to be understandable without multiple return statements, but sometimes it’s good to be pragmatic about such things.

If you end up with more than a few returns there may be something wrong with your code. Otherwise I would agree that sometimes it is nice to be able to return from multiple places in a subroutine, especially when it make the code cleaner.

Perl 6: Bad Example

 sub Int_to_String( Int i ){ given( i ){ when 0 { return "zero" } when 1 { return "one" } when 2 { return "two" } when 3 { return "three" } when 4 { return "four" } ... default { return undef } } } 

would be better written like this

Perl 6: Good Example

 @Int_to_String = qw{ zero one two three four ... } sub Int_to_String( Int i ){ return undef if i < 0; return undef unless i < @Int_to_String.length; return @Int_to_String[i] } 

Note this is was just a quick example

I vote for Single return at the end as a guideline. This helps a common code clean-up handling … For example, take a look at the following code …

 void ProcessMyFile (char *szFileName) { FILE *fp = NULL; char *pbyBuffer = NULL: do { fp = fopen (szFileName, "r"); if (NULL == fp) { break; } pbyBuffer = malloc (__SOME__SIZE___); if (NULL == pbyBuffer) { break; } /*** Do some processing with file ***/ } while (0); if (pbyBuffer) { free (pbyBuffer); } if (fp) { fclose (fp); } } 

This is probably an unusual perspective, but I think that anyone who believes that multiple return statements are to be favoured has never had to use a debugger on a microprocessor that supports only 4 hardware breakpoints. 😉

While the issues of “arrow code” are completely correct, one issue that seems to go away when using multiple return statements is in the situation where you are using a debugger. You have no convenient catch-all position to put a breakpoint to guarantee that you’re going to see the exit and hence the return condition.

The more return statements you have in a function, the higher complexity in that one method. If you find yourself wondering if you have too many return statements, you might want to ask yourself if you have too many lines of code in that function.

But, not, there is nothing wrong with one/many return statements. In some languages, it is a better practice (C++) than in others (C).