Una funzione dovrebbe usare ritorni prematuri o avvolgere tutto in clausole if? [duplicare]

20

Quale è meglio? Vedo pro e contro per entrambi, quindi non posso davvero decidere su quale si debba attenersi.

Wrap in if clausola

function doIt() {
   if (successfulCondition) {
      whenEverythingGoesWell();
   }
}
  • Pro : mostra l'intenzione del programmatore tramite indentazione.
  • Con : l'indentazione può diventare davvero profonda se hai bisogno di cortocircuitare molte volte. Ad esempio, doThirdThing() richiede il successo di doSecondThing() , che a sua volta richiede il successo di doFirstThing() . Questo accade molto nello sviluppo web in cui molti servizi web non sono affidabili.

Ritorno prematuro

function doIt() {
   if (!successfulCondition) {
      return;
   }
   whenEverythingGoesWell();
}
  • Pro : i checkin di Subversion sarebbero succinti. A volte, vedo i colleghi avvolgere funzioni molto lunghe in una clausola if. L'intero shebang viene registrato e rende difficile la lettura di Subversion.
  • Con : richiede di leggere l'intera funzione per capire i vari percorsi di esecuzione.
posta JoJo 30.08.2011 - 01:09
fonte

7 risposte

32

Martin Fowler parla del refactoring "Replace Nested Conditional with Guard Clauses" e in generale sostiene la chiarezza di abbandonare anticipatamente i parametri errati mettendo l'intero metodo in una clausola else (e vedi anche link ., che indica che il caso tradizionale di uscire solo alla fine era in parte per la pulizia delle risorse).

Penso che sia abbastanza semplice e prevedibile quando trovo un ritorno all'inizio di una funzione, ma di tanto in tanto mi mancano seppellirli nel mezzo, quindi evito di mettere i ritorni nel mezzo a meno che non sia davvero brutto per evitarli.

    
risposta data 30.08.2011 - 01:24
fonte
11

Ritorno prematuro

Il ritorno anticipato è il modo in cui raccomando di scrivere le funzioni. Questo chiarisce all'inizio della funzione che se si hanno parametri non validi, questo terminerà presto e non elaborerà ulteriormente nella funzione. Ciò riduce anche la complessità ciclomatica.

Jeff Atwood (sopra) descrive metodi eccellenti per ridurre la complessità ciclomatica.

function doIt() {
   if (!successfulCondition) {
      return;
   }
   whenEverythingGoesWell();
}

Con: Requires you to read the whole function to figure out the various run paths.

Questo Con può e dovrebbe essere facilmente risolto con refactoring minore.

Refactor i tuoi metodi in piccole funzioni succinte che eseguono operazioni discrete. Questo trasformerà la tua funzione whenEverythingGoesWell in una lista di chiamate di funzioni private che leggeranno come pseudoCode.

Esempio

function MugOfCoffee doIt(int param1, string param2) {
   if (!successfulCondition(param1, param2) 
      return;

   boilKettle();
   getMilkFromFridge();

   getMugsFromCuppboard();

   if(userHasCream())
      pourMilkIntoMug();

   putCoffeeInMug();
   purHotWaterIntoMug();

   return new MugOfCoffee();
}

private bool successfulCondition(int param1, string param2)
{
   // Validation logic here
   return true;
}

Almeno ciascun nome di funzione rappresenta un passo in un metodo più grande e si legge come se fosse in pseudocodice. Se il metodo pourHotWaterIntoMug cambia, viene accoppiato liberamente dalla funzione doIt e così sarà molto più facile da leggere.

    
risposta data 30.08.2011 - 01:24
fonte
5

Oh caro, questo è stato oggetto di corsi universitari sulla purezza del programma e sugli standard di codifica aziendale, dal momento che Adam era un piccoletto.

Il motivo principale per un singolo punto di ritorno è che significa che non devi cacciare la fonte cercando di trovare uscite nascoste (o così è richiesto).

Ciò significa in realtà che puoi essere pigro quando leggi la fonte.

Ora, l'idea di essere pigri quando leggi la fonte è che puoi concentrarti solo sui bit che pensi "contino". Qualunque cosa sia.

A causa di tutto ciò, l'uso di singoli ritorni può rendere estremamente complicato il flusso del programma / funzione e la codifica. (Mi ricorda il codice che ho scritto anni fa, in cui una funzione cresceva di 10 righe di codice MOLTO oscuro e orribile per evitare un "goto").

A volte, fare cose "cattive" è giusto perché rende il risultato più semplice e facile da capire. Quindi se questo significa usare il goto occasionale, o usare più ritorni, allora lo fai. Ma solo quando l'alternativa è peggio. Questo può richiedere un pensiero attento e potrebbe essere un po 'un giudizio.

In questi giorni tendo ad usare più rendimenti praticamente come ovvio. La ragione è che l'approccio "pigro" alla lettura del codice generalmente non ti porta da nessuna parte. Hai bisogno di leggere una funzione / metodo dall'inizio alla fine, quindi i ritorni multipli sono piuttosto evidenti - e doppiamente così se metti dei grandi commenti contro ciascuno (cosa che faccio). Quindi l'argomento pigro in realtà non regge l'acqua.

Angolo Nitpickers: solo perché ho menzionato Gotos non mi rende un cowboy o un pazzo. Penso di essere riuscito a usare "goto" due volte negli ultimi 25 anni. Il punto rimane, tuttavia: Se è la soluzione migliore, usalo.

    
risposta data 30.08.2011 - 01:18
fonte
1

A volte hai problemi con entrambi gli approcci. Potresti essere tentato di inserire una serie di clausole di guardia ma allo stesso tempo potresti avere risorse che devi essere sicuro di ripulire.

Qualcosa che faccio quando ci sono molte cose possibili da fare prima di arrivare alla carne del metodo, specialmente quando voglio avere un'uscita ordinata è per noi un ciclo di do / loop con una pausa garantita alla fine. Questa è una specie di uscita prematura di compromesso. Invece di uscire dal metodo e invece di nidificare molti, molti se blocchi, hai un blocco do / loop da cui puoi uscire se succede qualcosa di brutto.

Sembra qualcosa del genere ...

public bool YourMethod()
{
    do    // For bailing, not for looping.
    {
        if (FirstBadThingHappens)
        {   // Do any logging or other follow up.
            break;
        }
        ...
        if (NthBadThingHappens)
        {   // And so forth...
            break;
        }
        // If we're still here then it must be OK to proceed...
        DoTheMeatOfTheMethod();

    } while(false)   // Always only one pass
    // Do whatever needs doing to wrap up cleanly...
    CleanUpResourcesOrWhatever();
    return bResult;
}
    
risposta data 19.09.2011 - 05:22
fonte
0

Buona domanda, una che mi sono spesso chiesto. Ma rispondo sempre che uscire è più pulito dell'onere della logica condizionale. Assicurati solo che il tuo intento sia chiaro, è nel tuo esempio.

Inoltre: asserzioni ben ponderate e ben posizionate sono a volte più chiare e più efficaci in tali situazioni - assicurati di gestire in modo appropriato il potenziale errore di asserzione nel tuo chiamante.

function doIt () {

  Debug.Assert(successfulCondition,"condition not met");
  whenEverythingGoesWell();

}

    
risposta data 19.09.2011 - 04:42
fonte
-1

Personalmente ho trovato il modo migliore (che tu hai intitolato "Wrap in if clausola") per essere il mio modo preferito. Se ho bisogno di mitigare il tuo con riguardo all'indentazione profonda, uso semplicemente un flag booleano per tenere traccia se doFirstThing () è stato completato con successo. Per me, è tutto sulla leggibilità del codice. Trovo difficile rintracciare più ritorni, invece di occuparmi di un ulteriore ulteriore indentazione.

Tuttavia, alcune situazioni potrebbero richiedere un livello più alto di prestazioni e quindi trarrebbero vantaggio da un "Ritorno prematuro". In quelle situazioni avresti un altro pro e contro da pesare.

La tua decisione dovrebbe essere basata su: 1. Cosa rende il codice più facile da leggere ?; 2. Quanto è importante l'ottimizzazione delle prestazioni? Non posso darti un'unica risposta perché sono necessari altri dettagli sul tuo utilizzo.

    
risposta data 30.08.2011 - 01:19
fonte
-1

Il mio voto è per NON effettuare i ritorni a premi.

Perché? Principalmente mantenibilità. I percorsi di codice sono un disagio doloroso e debugging di qualcuno (o il mio) 6 mesi dopo è molto utile se riesci a spezzare il punto in fondo al metodo dicendo "indipendentemente dal percorso, questo è quello che sto tornando ... è questo che ci si aspetta? ".

    
risposta data 30.08.2011 - 04:00
fonte

Leggi altre domande sui tag