È una cattiva pratica usare il ritorno all'interno di un metodo void?


92

Immagina il seguente codice:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

Va bene usare un ritorno all'interno di un metodo void? Ha qualche penalità sulle prestazioni? Oppure sarebbe meglio scrivere un codice come questo:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
c#  return  void 

1
Che dire di: void DoThis () {if (isValid) DoThat (); }
Dscoduc

30
immaginare il codice? Perché? È proprio lì! :-D
STW

Questa è una buona domanda, penso sempre che sia una buona pratica usare return; per uscire dal metodo o dalla funzione. Soprattutto in un metodo di data mining LINQ che ha più risultati IQueryable <T> e tutti dipendono l'uno dall'altro. Se uno di loro non ha risultati, avvisa ed esci.
Cheung

Risposte:



33

C'è un altro ottimo motivo per usare guards (al contrario del codice annidato): se un altro programmatore aggiunge codice alla tua funzione, sta lavorando in un ambiente più sicuro.

Prendere in considerazione:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

contro:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

Ora, immagina che un altro programmatore aggiunga la riga: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

Ovviamente questo è un caso semplicistico, ma il programmatore ha aggiunto un arresto anomalo al programma nella prima istanza (codice annidato). Nel secondo esempio (uscita anticipata con guardie), una volta superata la guardia, il tuo codice è al sicuro dall'uso involontario di un riferimento nullo.

Certo, un grande programmatore non commette errori come questo (spesso). Ma prevenire è meglio che curare: possiamo scrivere il codice in modo da eliminare completamente questa potenziale fonte di errori. La nidificazione aggiunge complessità, quindi le best practice consigliano di refactoring del codice per ridurre la nidificazione.


Sì, ma d'altra parte, diversi livelli di annidamento, con le loro condizioni, rendono il codice ancora più incline ai bug, la logica più difficile da tracciare e, cosa più importante, più difficile da eseguire il debug. Le funzioni piatte sono un male minore, IMO.
Skrim

18
Sto discutendo a favore della nidificazione ridotta! :-)
Jason Williams il

Sono d'accordo con questo. Inoltre, dal punto di vista del refactoring, è più facile e sicuro eseguire il refactoring del metodo se obj diventa uno struct o qualcosa che puoi garantire non sarà nullo.
Phil Cooper

18

Cattiva pratica ??? Non c'è modo. In effetti, è sempre meglio gestire le convalide ritornando dal metodo al più presto se le convalide falliscono. Altrimenti risulterebbe in un'enorme quantità di se e altri nidificati. Terminare anticipatamente migliora la leggibilità del codice.

Controlla anche le risposte a una domanda simile: devo usare l'istruzione return / continue invece di if-else?


8

Non è una cattiva pratica (per tutti i motivi già indicati). Tuttavia, maggiori sono i rendimenti che si hanno in un metodo, più è probabile che venga suddiviso in metodi logici più piccoli.


8

Il primo esempio sta usando un'istruzione guard. Da Wikipedia :

Nella programmazione di computer, una guardia è un'espressione booleana che deve restituire true se l'esecuzione del programma deve continuare nel ramo in questione.

Penso che avere un gruppo di guardie all'inizio di un metodo sia un modo perfettamente comprensibile per programmare. Fondamentalmente sta dicendo "non eseguire questo metodo se qualcuno di questi è vero".

Quindi in generale vorrebbe questo:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

Penso che sia molto più leggibile quindi:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}

3

Non ci sono penalità sulle prestazioni, tuttavia la seconda parte di codice è più leggibile e quindi più facile da mantenere.


Russell non sono d'accordo con la tua opinione ma non avresti dovuto essere votato a favore. +1 per uniformarlo. A proposito, credo che un test booleano e un ritorno su una singola riga seguita da una riga vuota sia una chiara indicazione di cosa sta succedendo. ad esempio il primo esempio di Rodrigo.
Paul Sasik

Non sono d'accordo con questo. L'aumento della nidificazione non migliora la leggibilità. Il primo pezzo di codice utilizza un'istruzione "guard", che è uno schema perfettamente comprensibile.
cdmckay

Anch'io non sono d'accordo. Le clausole di guardia che vengono salvate anticipatamente da una funzione sono generalmente considerate una buona cosa al giorno d'oggi per aiutare un lettore a comprendere l'implementazione.
Pete Hodgson,

2

In questo caso, il tuo secondo esempio è un codice migliore, ma questo non ha nulla a che fare con il ritorno da una funzione void, è semplicemente perché il secondo codice è più diretto. Ma il ritorno da una funzione void va benissimo.


0

Va perfettamente bene e nessuna "penalizzazione delle prestazioni", ma non scrivere mai un'istruzione "if" senza parentesi.

Sempre

if( foo ){
    return;
}

È molto più leggibile; e non presumerai mai accidentalmente che alcune parti del codice siano all'interno di tale istruzione quando non lo sono.


2
leggibile è soggettivo. imho, qualsiasi cosa aggiunta al codice che non sia necessaria lo rende meno leggibile ... (devo leggere di più, e poi mi chiedo perché è lì e spreco tempo cercando di assicurarmi che non mi manchi qualcosa) ... ma questo è il mio opinione soggettiva
Charles Bretana

10
Il motivo migliore per includere sempre le parentesi graffe è meno sulla leggibilità e più sulla sicurezza. Senza le parentesi graffe è molto facile per qualcuno in seguito correggere un bug che richiede istruzioni aggiuntive come parte del if, non prestare abbastanza attenzione e aggiungerle senza aggiungere anche le parentesi graffe. Includendo sempre le parentesi graffe, questo rischio viene eliminato.
Scott Dorman

2
Silky, premi invio prima del tuo {. Questo allinea il tuo {con il tuo }nella stessa colonna, il che aiuta notevolmente la leggibilità (molto più facile trovare le parentesi graffe aperte / chiuse corrispondenti).
Imagist

1
@Imagist lo lascio alle preferenze personali; ed è fatto nel modo che preferisco :)
Noon Silk

1
Se ogni parentesi graffa chiusa è abbinata a una parentesi graffa aperta che è posta allo stesso livello di rientro , allora distinguere visivamente quali ifaffermazioni richiedono parentesi graffe chiuse sarà facile, e quindi avere ifistruzioni che controllano una singola affermazione sarà sicuro. Spingere indietro la parentesi graffa aperta sulla linea con il ifsalva una linea di spazio verticale su ogni istruzione multipla if, ma richiederà l'uso di una linea di parentesi graffa chiusa altrimenti non necessaria.
supercat

0

Non sarò d'accordo con tutti voi giovani whippersnapper su questo.

Usare il ritorno nel mezzo di un metodo, vuoto o meno, è una pessima pratica, per ragioni che sono state articolate abbastanza chiaramente, quasi quarant'anni fa, dal compianto Edsger W. Dijkstra, a partire dalla nota "Dichiarazione GOTO Considered Harmful ", e proseguendo in" Programmazione strutturata ", di Dahl, Dijkstra e Hoare.

La regola di base è che ogni struttura di controllo e ogni modulo dovrebbero avere esattamente un'entrata e un'uscita. Un ritorno esplicito nel mezzo del modulo infrange questa regola e rende molto più difficile ragionare sullo stato del programma, il che a sua volta rende molto più difficile dire se il programma è corretto o meno (cheèuna proprietà molto più forte di "se sembra funzionare o no").

"Dichiarazione GOTO considerata dannosa" e "Programmazione strutturata" hanno dato il via alla rivoluzione "Programmazione strutturata" degli anni '70. Questi due pezzi sono i motivi per cui abbiamo if-then-else, while-do e altri costrutti di controllo espliciti oggi, e perché le dichiarazioni GOTO nei linguaggi di alto livello sono nell'elenco delle specie in pericolo. (La mia opinione personale è che devono essere nell'elenco delle specie estinte.)

Vale la pena notare che il Message Flow Modulator, il primo pezzo di software militare che MAI ha superato i test di accettazione al primo tentativo, senza deviazioni, rinunce o parole "sì, ma", è stato scritto in una lingua che non aveva nemmeno un'istruzione GOTO.

Vale anche la pena ricordare che Nicklaus Wirth ha cambiato la semantica dell'istruzione RETURN in Oberon-07, l'ultima versione del linguaggio di programmazione Oberon, rendendolo un pezzo finale della dichiarazione di una procedura tipizzata (cioè una funzione), piuttosto che una istruzione eseguibile nel corpo della funzione. La sua spiegazione del cambiamento diceva che lo aveva fatto proprio perché il modulo precedente ERA una violazione del principio di una sola uscita della programmazione strutturata.


2
@ John: abbiamo superato l'ingiunzione di Dykstra sui resi multipli proprio nel momento in cui abbiamo superato Pascal (la maggior parte di noi, comunque).
John Saunders

I casi in cui sono richiesti più resi sono spesso segni che un metodo sta cercando di fare troppo e dovrebbe essere ridotto. Non mi spingerò fino a John con questo, e un'istruzione return come parte della convalida dei parametri potrebbe essere un'eccezione ragionevole, ma capisco da dove viene l'idea.
kyoryu

@nairdaen: C'è ancora polemica sulle eccezioni in quel trimestre. La mia linea guida è questa: se il sistema in fase di sviluppo DEVE risolvere il problema che ha causato la condizione eccezionale originale, e non mi dispiace far incazzare il ragazzo che dovrà scrivere quel codice, lancio un'eccezione. Poi vengo sgridato in una riunione, perché il ragazzo non si è preoccupato di catturare l'eccezione e l'app si è bloccata durante il test, e spiego PERCHÉ deve risolvere il problema e le cose si sistemano di nuovo.
John R. Strohm,

C'è una grande differenza tra dichiarazioni di guardia e gotos. Il male di gotos è che possono saltare ovunque, quindi può essere molto confuso da svelare e ricordare. Le dichiarazioni Guard sono l'esatto opposto: forniscono una voce controllata a un metodo, dopo di che sai che stai lavorando in un ambiente "sicuro", riducendo il numero di cose che devi considerare mentre scrivi il resto del codice (ad es. "So che questo puntatore non sarà mai nullo, quindi non ho bisogno di gestire quel caso in tutto il codice").
Jason Williams il

@ Jason: La domanda originale non riguardava specificamente le dichiarazioni di guardia, ma le dichiarazioni di ritorno casuale nel mezzo di un metodo. L'esempio fornito nell'esempio sembrava essere una guardia. La questione chiave è che, nel sito di restituzione, vuoi essere in grado di ragionare su ciò che il metodo ha fatto o non ha fatto, e i resi casuali lo rendono più difficile, ESATTAMENTE per gli stessi motivi per cui i GOTO casuali lo rendono più difficile. Vedi: Dijkstra, "Dichiarazione GOTO Considered Harmful". Sul lato della sintassi, cdmckay ha fornito, in un'altra risposta, la sua sintassi preferita per le guardie; Non sono d'accordo con la sua opinione su quale forma sia più leggibile.
John R. Strohm,

0

Durante l'utilizzo delle protezioni, assicurati di seguire alcune linee guida per non confondere i lettori.

  • la funzione fa una cosa
  • le guardie vengono introdotte solo come prima logica nella funzione
  • la parte non testata contiene l' intento principale della funzione

Esempio

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;

-3

Genera un'eccezione invece di restituire nulla quando l'oggetto è nullo ecc.

Il tuo metodo si aspetta che l'oggetto non sia nullo e non è così, quindi dovresti lanciare un'eccezione e lasciare che il chiamante lo gestisca.

Ma il ritorno anticipato non è una cattiva pratica altrimenti.


1
La risposta non risponde alla domanda. La domanda è un metodo vuoto, quindi non viene restituito nulla. Inoltre, i metodi non hanno parametri. Ottengo il punto di non restituire null se il tipo restituito è un oggetto ma ciò non si applica a questa domanda.
Luke Hammer
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.