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

20

Sto costruendo 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 false? 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 alcuni enum per aiutare l'utente API a gestire tutti gli errori).

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

    
posta Accollativo 12.09.2016 - 09:31
fonte

5 risposte

33

Lanciare un'eccezione è semplicemente un modo aggiuntivo per fare in modo che un metodo restituisca un valore. Il chiamante può controllare un valore di ritorno con la stessa facilità con cui cattura un'eccezione e controlla quello. Pertanto, decidere tra throw e return richiede altri criteri.

Spesso le eccezioni di lancio dovrebbero essere evitate se mettono a repentaglio l'efficienza del tuo programma (la costruzione di un oggetto di eccezione e lo svolgimento dello stack di chiamate è molto più lavoro per il computer di un semplice valore aggiunto). Ma se lo scopo del tuo metodo è caricare un file, il collo di bottiglia sarà sempre l'I / O di rete e di file system, quindi è inutile ottimizzare il metodo di ritorno.

È anche una cattiva idea gettare eccezioni per quello che dovrebbe essere un semplice flusso di controllo (ad esempio quando una ricerca riesce a trovare il suo valore), perché ciò viola le aspettative degli utenti API. Ma un metodo che non riesce a raggiungere il suo scopo è un caso eccezionale (o almeno dovrebbe esserlo), quindi non vedo ragioni per non lanciare un'eccezione. E se lo fai, potresti anche renderlo un'eccezione personalizzata, più informativa (ma è una buona idea renderla una sottoclasse di un'eccezione standard, più generale come IOException ).

    
risposta data 12.09.2016 - 09:39
fonte
26

Non è assolutamente necessario restituire true in caso di successo se non si restituisce false in caso di errore. Come dovrebbe apparire il codice cliente?

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 valore restituito da true è completamente irricevibile per il chiamante. Quindi mantieni il metodo void .

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

  1. Restituisce vero / falso
  2. Utilizza void , lancia (controllata) l'eccezione
  3. restituisce un oggetto risultato intermedio

Ritorno true / false

Questo è usato in alcune vecchie API, per lo più in stile C. Gli svantaggi sono ovvi, non hai idea di cosa sia andato storto. PHP lo fa abbastanza spesso, portando a codice come questo:

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

Nei contesti multi-thread, questo è anche peggio.

Esegui eccezioni (controllate)

Questo è un bel modo per farlo. In alcuni punti, puoi aspettarti un fallimento. Java lo fa con i socket. L'assunto di base è che una chiamata debba avere successo, ma tutti sanno che certe operazioni potrebbero fallire. Le connessioni socket sono tra queste. Quindi il chiamante viene forzato 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.

Restituisci oggetto risultato

Questo è un altro bel modo per gestirlo. È spesso usato per l'analisi o semplicemente per le cose che devono essere convalidate.

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

Bella logica 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 con la possibilità di eccezioni in mente, e quasi sempre useranno le terze opzioni. Quel bene. Ma abbiamo controllato le eccezioni in Java, quindi non vedo alcun motivo non per usarle. Io uso le expetions controllate quando il presupposto di base è che la chiamata dovrebbe suceed (come usare un socket), ma è possibile un errore, e io uso la terza opzione quando è molto poco chiara se la chiamata deve superare (come dati di convalida). Ma ci sono opinioni diverse su questo.

Nel tuo caso, vorrei andare con void + Exception . Ti aspetti che il caricamento del file superi, e quando lo fa, è eccezionale. Ma il chiamante è costretto a gestire tale modalità di errore, e puoi restituire un'eccezione che descriva correttamente quale tipo di errore si è verificato.

    
risposta data 12.09.2016 - 14:11
fonte
5

Questo dipende in realtà dal fatto che un errore sia eccezionale o previsto .

Se un errore non è qualcosa che generalmente ti aspetti, allora è ragionevolmente probabile che l'utente API chiamerà il tuo metodo senza alcuna particolare gestione degli errori. Lanciare un'eccezione consente di far saltare la pila in un posto dove viene notata.

Se invece gli errori sono all'ordine del giorno, dovresti ottimizzare per lo sviluppatore che li sta controllando, e le clausole try-catch sono un po 'più ingombranti di una if/elif o switch serie.

Disegna sempre un'API nella mentalità di qualcuno che la sta usando.

    
risposta data 13.09.2016 - 02:03
fonte
3

Non restituire un valore booleano se non si intende restituire false . Basta fare il metodo void e documentare che genererà un'eccezione ( IOException è adatto) in caso di fallimento.

La ragione di ciò è che se si restituisce un valore booleano, l'utente della propria API può concludere che è in grado di farlo:

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

Naturalmente non funzionerà; cioè, c'è un'incongruenza tra il contratto del tuo metodo e il suo comportamento. Se si lancia un'eccezione controllata, l'utente del metodo deve gestire l'errore:

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}
    
risposta data 12.09.2016 - 14:05
fonte
3

Se il tuo metodo ha un valore di ritorno, lanciare un'eccezione potrebbe sorprendere i suoi utenti. Se vedo un metodo restituire un booleano, sono abbastanza convinto che restituirà true se ha successo e false se non lo è, e strutturerò il mio codice usando una clausola if-else. Se la tua eccezione si baserà comunque su un enum, potresti anche restituire un valore enum, simile a Windows HResults, e mantenere un valore enum per quando il metodo ha successo.

È 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 void, gli utenti cercheranno di riuscirci se non vengono lanciate eccezioni e nessuno tenterà di usare una clausola if-else per deviare il flusso di controllo.

    
risposta data 12.09.2016 - 14:16
fonte