Best practice - Avvolgimento se intorno alla chiamata di funzione vs Aggiunta dell'uscita anticipata se la funzione di guardia è attiva


9

So che questo può essere molto specifico per ogni caso d'uso, ma mi trovo a chiedermelo troppo spesso. Esiste una sintassi generalmente preferita.

Non sto chiedendo quale sia l'approccio migliore quando in una funzione, chiedo se dovessi uscire presto o semplicemente non dovrei chiamare la funzione.

Avvolgere se intorno alla chiamata di funzione


if (shouldThisRun) {
  runFunction();
}

Avere se ( guardia ) in funzione

runFunction() {
  if (!shouldThisRun) return;
}

Quest'ultima opzione ha ovviamente il potenziale per ridurre la duplicazione del codice se questa funzione viene chiamata più volte, ma a volte sembra sbagliato aggiungerla qui perché si potrebbe perdere la responsabilità singola della funzione.


Ecco un esempio

Se ho una funzione updateStatus () che aggiorna semplicemente lo stato di qualcosa. Voglio aggiornare lo stato solo se lo stato è cambiato. Conosco i posti nel mio codice in cui lo stato ha il potenziale per cambiare e conosco altri posti in cui è cambiato in modo provocatorio.

Non sono sicuro che sia solo io, ma mi sembra un po 'sporco controllare questa funzione interna poiché voglio mantenere questa funzione il più pura possibile - se la chiamo mi aspetto che lo stato venga aggiornato. Ma non posso dire se è meglio racchiudere la chiamata in un check nei pochi posti in cui so che ha il potenziale per non essere cambiato.



3
@gnat No, quella domanda è essenzialmente 'Qual è la sintassi preferita in un'uscita anticipata' mentre la mia è 'Dovrei uscire presto o non dovrei semplicemente chiamare la funzione'
Matthew Mullin

4
non puoi fidarti degli sviluppatori, anche di te stesso , per controllare correttamente le pre-condizioni della funzione ovunque venga chiamata. per questo motivo, suggerirei che la funzione convalidi internamente tutte le condizioni necessarie se ha la capacità di farlo.
TZHX,

"Voglio aggiornare lo stato solo se lo stato è cambiato" - vuoi aggiornare lo stato (= cambiato) se lo stesso stato è cambiato? Sembra piuttosto circolare. Potete per favore chiarire che cosa intendete con questo, così posso aggiungere un esempio significativo alla mia risposta su questo?
Doc Brown,

@DocBrown Consente ad esempio di voler sincronizzare due diverse proprietà dello stato degli oggetti. Quando uno dei due oggetti cambia, chiamo syncStatuses (), ma questo potrebbe essere attivato per molte diverse modifiche al campo (non solo il campo di stato).
Matthew Mullin,

Risposte:


15

Avvolgere un if intorno a una chiamata di funzione:
questo serve a decidere se la funzione deve essere chiamata a tutti, e fa parte del processo decisionale del programma.

Clausola di guardia in funzione (ritorno anticipato):
serve per proteggere da chiamate con parametri non validi

Una clausola di protezione usata in questo modo mantiene la funzione "pura" (il tuo termine). Esiste solo per garantire che la funzione non si interrompa con dati di input errati.

La logica di chiamare la funzione è a un livello più alto di astrazione, anche se solo in modo giusto. Dovrebbe esistere al di fuori della funzione stessa. Come dice DocBrown, puoi avere una funzione intermedia che esegue questo controllo, per semplificare il codice.

Questa è una buona domanda da porre e rientra nella serie di domande che portano a riconoscere i livelli di astrazione. Ogni funzione dovrebbe operare a un unico livello di astrazione e avere sia la logica del programma che la logica della funzione nella funzione ti sembra sbagliato - questo perché sono a diversi livelli di astrazione. La funzione stessa è di livello inferiore.

Mantenendoli separati, ti assicuri che il tuo codice sarà più facile da scrivere, leggere e mantenere.


Risposta fantastica. Mi piace il fatto che mi dia un modo chiaro di pensarci. L'if dovrebbe essere al di fuori in quanto è "parte del processo decisionale" se la funzione debba essere chiamata in primo luogo. E intrinsecamente non ha nulla a che fare con la funzione stessa. È strano contrassegnare una risposta come corretta, ma tra qualche ora darò un'occhiata di nuovo e lo farò.
Matthew Mullin

Se aiuta, non considero questa una risposta di "opinione". Noto che "sembra" sbagliato, ma questo perché i diversi livelli di astrazione non sono separati. Quello che ho ottenuto dalla tua domanda è che puoi vedere che non è "giusto" ma poiché non stai pensando ai livelli di astrazione, è difficile da quantificare, quindi è qualcosa che fai fatica a esprimere a parole.
Baldrickk,

7

Puoi avere entrambi - una funzione che non controlla i parametri e un'altra che lo fa, in questo modo (forse restituendo alcune informazioni su se la chiamata è stata fatta):

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

In questo modo, puoi evitare la logica duplicata fornendo una funzione riutilizzabile tryRunFunctione mantenendo comunque la tua funzione originale (forse pura) che non effettua il controllo all'interno.

Nota che a volte avrai bisogno di una funzione come tryRunFunctioncon un controllo integrato in modo da poter integrare il controllo in runFunction. Oppure non è necessario riutilizzare nuovamente il controllo in un punto qualsiasi del programma, nel qual caso è possibile lasciarlo rimanere nella funzione di chiamata.

Tuttavia, prova a rendere trasparente al chiamante cosa succede assegnando alle tue funzioni nomi propri. Quindi i chiamanti non devono indovinare o esaminare l'implementazione se devono fare i controlli da soli o se la funzione chiamata lo fa già per loro. Un semplice prefisso come tryspesso può essere sufficiente per questo.


1
Devo ammettere che il linguaggio "tryXXX ()" sembrava sempre un po 'fuori ed è inappropriato qui. Non stai cercando di fare qualcosa in attesa di un probabile errore. Stai aggiornando se è sporco.
user949300,

@ user949300: la scelta di un buon nome o di uno schema di denominazione dipende dal caso d'uso reale, dai nomi di funzioni reali, non da nomi inventati come runFunction. Una funzione simile updateStatus()può essere accompagnata da un'altra funzione simile updateIfStatusHasChanged(). Ma questo è al 100% dipendente dal caso, non esiste una soluzione "unica per tutti", quindi sì, sono d'accordo, il linguaggio "provare" non è sempre una buona scelta.
Doc Brown,

Non c'è qualcosa chiamato "dryRun"? Più o meno arriva a essere un'esecuzione regolare senza effetti collaterali. Come disabilitare gli effetti collaterali è un'altra storia
Laiv

3

Per quanto riguarda chi decide se eseguire, la risposta è, da GRASP , che è l '"esperto di informazioni" che conosce.

Una volta deciso, considera la ridenominazione della funzione per maggiore chiarezza.

Qualcosa del genere, se la funzione decide:

 ensureUpdated()
 updateIfDirty()

Oppure, se il chiamante dovrebbe decidere:

 writeStatus()

2

Vorrei espandere la risposta di @ Baldrickk.

Non esiste una risposta generale alla tua domanda. Dipende dal significato (contratto) della funzione da chiamare e dalla natura della condizione.

Quindi discutiamolo nel contesto della tua chiamata di esempio updateStatus(). Il suo contratto probabilmente è quello di aggiornare un po 'di stato perché è successo qualcosa che ha un'influenza sullo stato. Mi aspetterei che le chiamate a quel metodo siano consentite anche se non c'è un reale cambio di stato e che siano necessarie se c'è un vero cambiamento.

Pertanto, un sito chiamante può saltare una chiamata updateStatus()se sa che (all'interno del suo orizzonte di dominio) non è cambiato nulla di rilevante. Queste sono le situazioni in cui la chiamata dovrebbe essere circondata da un ifcostrutto appropriato .

All'interno della updateStatus()funzione, ci possono essere situazioni in cui questa funzione rileva (dai dati all'interno del suo orizzonte di dominio) che non c'è nulla da fare ed è lì che dovrebbe tornare presto.

Quindi, le domande sono:

  • Dal punto di vista esterno, quando è consentito / richiesto chiamare la funzione, tenendo conto del contratto della funzione?
  • All'interno della funzione, ci sono situazioni in cui può tornare presto senza alcun vero lavoro?
  • La condizione se chiamare la funzione / se tornare presto appartiene al dominio interno della funzione o all'esterno?

Con una updateStatus()funzione, mi aspetto di vedere entrambi, chiamare i siti che non sanno nulla è cambiato, saltare la chiamata e l'implementazione verifica in anticipo le situazioni "nulla è cambiato", anche se in questo modo viene verificata due volte la stessa condizione, entrambi dentro e fuori.


2

Ci sono molte buone spiegazioni. Ma voglio apparire in modo insolito: supponi di usare in questo modo:

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

E devi chiamare un'altra funzione in runFunctionquesto modo:

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

Cosa farai? Copi tutte le convalide dall'alto verso il basso?

someOtherfunction() {
   if (!shouldThisRun) return;
}

Io non la penso così. Pertanto, di solito seguo lo stesso approccio: convalidare gli input e verificare le condizioni nel publicmetodo. I metodi pubblici dovrebbero eseguire le proprie convalide e verificare le condizioni richieste anche per i chiamanti. Ma lascia che i metodi privati ​​facciano i propri affari . Alcune altre funzioni possono chiamare runFunctionsenza eseguire alcuna convalida o verificare alcuna condizione.

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

private runFunction() {
 // do your business.
}
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.