Funzione che restituisce true / false vs. void in caso di successo e genera un'eccezione in caso di errore


22

Sto creando un'API, una funzione che carica un file. Questa funzione non restituirà nulla / nulla se il file è stato caricato correttamente e genera un'eccezione in caso di problemi.

Perché un'eccezione e non solo falsa? Perché all'interno di un'eccezione posso specificare il motivo dell'errore (nessuna connessione, nome file mancante, password errata, descrizione file mancante, ecc.). Volevo creare un'eccezione personalizzata (con qualche enum per aiutare l'utente dell'API a gestire tutti gli errori).

È una buona pratica o è meglio restituire un oggetto (con un valore booleano all'interno, un messaggio di errore opzionale e l'enum per errori)?


5
Possibile duplicato del valore magico
moscerino del

59
Vero e falso vanno bene insieme. Vuoto ed eccezione vanno bene insieme. Vero ed eccezione vanno insieme come gatti e tasse. Un giorno potrei leggere il tuo codice. Per favore, non farmi questo.
candied_orange,

4
Mi piacciono abbastanza i modelli in cui viene restituito un oggetto che incapsula il successo o il fallimento. Ad esempio, Rust ha Result<T, E>dove Tè il risultato se ha esito positivo ed Eè l'errore se non ha esito positivo. Lanciare un'eccezione (può essere - non così sicuro in Java) può essere costoso in quanto comporta lo svolgimento dello stack di chiamate, ma la creazione di un oggetto Exception è economica. Ovviamente, attenersi ai modelli stabiliti del linguaggio che si sta utilizzando, ma non combinare booleani ed eccezioni come questa.
Dan Pantry,

4
Stai attento qui. Potresti essere diretto in una tana di coniglio. Quando il tuo programma è in grado di gestire un problema, di solito è più semplice rilevarlo e gestirlo controllando le condizioni preliminari. Raramente si rileva un'eccezione, si esaminano i dati nel codice e si riprova dopo aver risolto il problema. Come tale, raramente è prezioso avere moltissimi tipi di eccezione. Se stai cercando di registrare i problemi, questo ha molto più senso, ma non aspettarti di scrivere un sacco di blocchi catch con una quantità significativa di codice in essi.
jpmc26,

4
@DanPantry In Java, lo stack di chiamate viene verificato durante la creazione dell'eccezione, non durante il lancio. fillInStackTrace();viene chiamato nel super costruttore, nella classeThrowable
Simon Forsberg,

Risposte:


35

Generare un'eccezione è semplicemente un modo aggiuntivo per fare in modo che un metodo restituisca un valore. Il chiamante può verificare un valore restituito con la stessa facilità con cui intercettare un'eccezione e verificarlo. Pertanto, decidere tra throwe returnrichiede altri criteri.

Generare eccezioni dovrebbe essere spesso evitato se mette a repentaglio l'efficienza del programma (costruire un oggetto di eccezione e srotolare lo stack di chiamate è molto più lavoro per il computer che semplicemente spingere un valore su di esso). Ma se lo scopo del tuo metodo è caricare un file, allora il collo di bottiglia sarà sempre l'I / O della rete e del file system, quindi è inutile ottimizzare il metodo di ritorno.

È anche una cattiva idea generare eccezioni per quello che dovrebbe essere un semplice flusso di controllo (ad es. Quando una ricerca ha successo trovandone il valore), poiché ciò viola le aspettative degli utenti API. Ma un metodo che non riesce a raggiungere il suo scopo è un caso eccezionale (o almeno dovrebbe essere), quindi non vedo alcun motivo per non lanciare un'eccezione. E se lo fai, potresti anche renderlo un'eccezione personalizzata e più informativa (ma è una buona idea renderlo una sottoclasse di un'eccezione standard, più generale come IOException).


7
Se è un risultato previsto che una richiesta di caricamento di un file fallisca, sarei sorpreso da un'eccezione generata nella mia faccia (specialmente se c'è un valore restituito nella firma del metodo).
hoffmale,

1
Si noti che il motivo per cui è stata estesa un'eccezione standard è che molti chiamanti (probabilmente anche la maggior parte) non scriveranno codice per tentare di risolvere il problema. Di conseguenza, la maggior parte dei chiamanti vorrà un semplice "cogliere questo grande gruppo di eccezioni" senza doverli elencare tutti esplicitamente.
jpmc26,

4
@gbjbaanb Ecco perché dovrebbero essere usate le eccezioni quando c'è davvero una situazione non risolvibile. Quando si tenta di aprire un file e il file non può essere letto, questo è un buon caso per un'eccezione. Quando si verifica l'esistenza del file, è necessario utilizzare un valore booleano perché è probabile che il file non sia presente.
Malcolm,

21
Kilian sta dicendo, nello slogan ricorsivo "le eccezioni sono per situazioni eccezionali", situazioni eccezionali in cui la funzione non può raggiungere il suo scopo. Se la funzione deve leggere un file e non è possibile leggere il file, ciò è eccezionale . Se la funzione dovrebbe dirti se esiste o meno un file (non importa il potenziale TOCTOU), il file inesistente non è eccezionale perché la funzione può ancora fare quello che dovrebbe fare. Se si suppone che la funzione affermi che esiste un file, allora non esiste è eccezionale perché questo significa "asserire".
Steve Jessop,

10
Si noti che l'unica differenza tra una funzione che indica se esiste un file e una funzione che afferma l'esistenza di un file è se il caso del file inesistente o meno è eccezionale. Le persone a volte parlano come se fosse una proprietà della condizione di errore, indipendentemente dal fatto che fosse "eccezionale". Non è una proprietà della condizione di errore, è una proprietà combinata della condizione di errore e lo scopo della funzione in cui si verifica. Altrimenti non verremmo mai rilevate eccezioni.
Steve Jessop,

31

Non c'è assolutamente alcun motivo per tornare trueal successo se non si ritorna falseal fallimento. Come dovrebbe essere il codice client?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

In questo caso, il chiamante ha comunque bisogno di un blocco try-catch, ma poi può scrivere meglio:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

Quindi il truevalore restituito è completamente irrilevante per il chiamante. Quindi mantieni il metodo void.


In generale, ci sono tre modi per progettare le modalità failre.

  1. Restituisce vero / falso
  2. Usa void, lancia (selezionata) eccezione
  3. restituisce un oggetto risultato intermedio .

Ritorno true/false

Viene utilizzato in alcune API più vecchie, per lo più in stile c. I lati negativi sono ovvi, non hai idea di cosa sia andato storto. PHP lo fa abbastanza spesso, portando a un codice come questo:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

In contesti multi-thread, questo è ancora peggio.

Genera (controllato) eccezioni

Questo è un bel modo di farlo. In alcuni punti, puoi aspettarti un fallimento. Java lo fa con i socket. Il presupposto di base è che una chiamata dovrebbe avere successo, ma tutti sanno che alcune operazioni potrebbero non riuscire. Le connessioni socket sono tra queste. Quindi il chiamante viene costretto a gestire l'errore. è un bel design, perché si assicura che il chiamante gestisca effettivamente l'errore e fornisce al chiamante un modo elegante per gestire l'errore.

Restituisce l'oggetto risultato

Questo è un altro bel modo di gestirlo. Viene spesso utilizzato per l'analisi o semplicemente cose che devono essere convalidate.

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

Logica piacevole e pulita anche per il chiamante.

C'è un po 'di dibattito su quando usare il secondo caso e quando usare il terzo. Alcune persone credono che le eccezioni dovrebbero essere eccezionali e che non si dovrebbe progettare tenendo presente la possibilità di eccezioni e che quasi sempre useranno le terze opzioni. Va bene. Ma abbiamo verificato le eccezioni in Java, quindi non vedo alcun motivo per non usarle. Uso le espulsioni controllate quando il presupposto di base è che la chiamata dovrebbe avere successo (come l'utilizzo di un socket), ma l'errore è possibile e utilizzo la terza opzione quando è molto chiaro se la chiamata dovrebbe continuare (come la convalida dei dati). Ma ci sono opinioni diverse su questo.

Nel tuo caso, andrei con void+ Exception. Ti aspetti che il caricamento del file abbia successo e, in caso contrario, è eccezionale. Ma il chiamante è costretto a gestire quella modalità di errore e puoi restituire un'eccezione che descrive correttamente il tipo di errore.


5

Questo dipende dal fatto che un fallimento sia eccezionale o previsto .

Se un errore non è qualcosa che ti aspetti generalmente, è probabile che l'utente dell'API chiamerà semplicemente il tuo metodo senza alcuna gestione speciale degli errori. Lanciare un'eccezione consente di far bollire la pila in un punto in cui viene notato.

Se d'altra parte gli errori sono all'ordine del giorno, è necessario ottimizzare per lo sviluppatore che li sta verificando e le try-catchclausole sono un po 'più complicate di una if/elifo una switchserie.

Progetta sempre un'API nella mentalità di qualcuno che la sta utilizzando.


1
Nel mio caso, come qualcuno ha sottolineato, dovrebbe essere un errore eccezionale che l'utente API non può caricare il file.
Accollativo,

4

Non restituire un valore booleano se non intendi mai tornare false. Basta fare il metodo voide documentare che genererà un'eccezione ( IOExceptionè adatto) in caso di errore.

Il motivo è che se restituisci un valore booleano, l'utente dell'API può concludere che può farlo:

if (fileUpload(file) == false) {
    // Handle failure.
}

Questo non funzionerà ovviamente; cioè c'è un'incongruenza tra il contratto del metodo e il suo comportamento. Se si genera un'eccezione selezionata, l'utente del metodo deve gestire l'errore:

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}

3

Se il tuo metodo ha un valore di ritorno, il lancio di un'eccezione potrebbe sorprendere i suoi utenti. Se vedo un metodo restituire un valore booleano, sono abbastanza convinto che tornerà vero se ha successo e falso in caso contrario, e strutturerò il mio codice usando una clausola if-else. Se la tua eccezione si baserà comunque su un enum, puoi anche restituire un valore enum, simile a Windows HResults, e mantenere un valore enum per quando il metodo ha esito positivo.

È anche fastidioso avere un'eccezione di lancio del metodo quando non è il passaggio finale di una procedura. Dover scrivere un try-catch e deviare il flusso di controllo sul blocco catch è una buona ricetta per gli spaghetti, e dovrebbe essere evitato.

Se stai andando avanti con le eccezioni, prova invece a restituire il vuoto, gli utenti scopriranno che avrà successo se non vengono generate eccezioni e nessuno proverà a utilizzare una clausola if-else per deviare il flusso di controllo.


1
L'enum era solo un'idea per aiutare l'utente dell'API a gestire diversi tipi di errori senza analizzare il messaggio di errore. Quindi dal tuo punto di vista nel mio caso è meglio restituire l'enum e aggiungere un enum per "OK".
Accollativo,
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.