Eccezioni: "cosa è successo" vs "cosa fare"


19

Usiamo le eccezioni per consentire al consumatore del codice di gestire in modo utile comportamenti imprevisti. Di solito si creano eccezioni attorno allo scenario "cosa è successo" FileNotFound(ad esempio , non siamo riusciti a trovare il file specificato) o ZeroDivisionError(non siamo stati in grado di eseguire 1/0operazioni).

Cosa succede se esiste la possibilità di specificare il comportamento previsto del consumatore?

Ad esempio, immagina di avere fetchrisorse che eseguono richieste HTTP e restituiscono dati recuperati. E invece di errori come ServiceTemporaryUnavailableo RateLimitExceededvorremmo solo RetryableErrorsuggerire al consumatore che dovrebbe semplicemente ritentare la richiesta e non preoccuparsi di errori specifici. Quindi, stiamo sostanzialmente suggerendo un'azione al chiamante: il "cosa fare".

Non lo facciamo spesso perché non conosciamo tutti i casi d'uso dei consumatori. Ma immagina che sia un componente specifico che conosciamo il miglior corso di azioni per un chiamante - quindi dovremmo quindi utilizzare l'approccio "cosa fare"?


3
HTTP non lo fa già? 503 è una mancata risposta temporanea, quindi il richiedente deve riprovare, 404 è un'assenza fondamentale, quindi non ha senso riprovare, 301 significa "spostato in modo permanente", quindi è necessario riprovare, ma con un indirizzo diverso, ecc.
Kilian Foth il

7
In molti casi, se davvero sappiamo "cosa fare", possiamo semplicemente fare in modo che il computer lo faccia automaticamente e l'utente non deve nemmeno sapere che qualcosa è andato storto. Suppongo che ogni volta che il mio browser riceve un 301 passa semplicemente al nuovo indirizzo senza chiedermelo.
Ixrec,

@Ixrec - ha avuto anche la stessa idea. tuttavia, il consumatore potrebbe non voler attendere un'altra richiesta e ignorare l'articolo o fallire completamente.
Roman Bodnarchuk,

1
@RomanBodnarchuk: non sono d'accordo. È come dire che una persona non dovrebbe aver bisogno di conoscere il cinese per parlare cinese. HTTP è un protocollo e sia il client che il server dovrebbero conoscerlo e seguirlo. Ecco come funziona un protocollo. Se solo una parte lo conosce e lo rispetta, non puoi comunicare.
Chris Pratt,

1
Onestamente sembra che tu stia cercando di sostituire le tue eccezioni con un blocco catch. Ecco perché siamo passati alle eccezioni - non più if( ! function() ) handle_something();, ma potendo gestire l'errore da qualche parte in cui conosci effettivamente il contesto chiamante - cioè dire a un client di chiamare un amministratore di sistema se il tuo server fallisce o ricaricare automaticamente se la connessione si interrompe, ma avvisa nel caso in cui il chiamante sia un altro microservizio. Lascia che i blocchi di cattura gestiscano la cattura.
Sebb,

Risposte:


47

Ma immagina che sia un componente specifico che conosciamo il miglior modo di agire per un chiamante.

Questo quasi sempre fallisce per almeno uno dei tuoi chiamanti, per il quale questo comportamento è incredibilmente irritante. Non dare per scontato di conoscerlo meglio. Dì ai tuoi utenti cosa sta succedendo, non quello che pensi che dovrebbero fare al riguardo. In molti casi è già chiaro quale dovrebbe essere un corso di azione sensato (e, in caso contrario, dare un suggerimento nel manuale dell'utente).

Ad esempio, anche le eccezioni fornite nella tua domanda dimostrano la tua ipotesi errata : a ServiceTemporaryUnavailableequivale a "riprovare più tardi", ed RateLimitExceededequivale a "guai là rilassati, magari aggiusta i parametri del tuo timer e riprova tra qualche minuto". Ma l'utente potrebbe anche voler attivare una sorta di allarme ServiceTemporaryUnavailable(che indica un problema del server), e non per RateLimitExceeded(che non lo fa).

Dagli la scelta .


4
Sono d'accordo. Il server dovrebbe solo trasmettere correttamente le informazioni. D'altro canto, la documentazione dovrebbe delineare chiaramente il giusto modo di agire in tali casi.
Neil,

1
Un piccolo difetto in questo è che per alcuni hacker, alcune eccezioni possono dire loro molto su ciò che sta facendo il tuo codice e possono usarlo per capire come sfruttarlo.
Pharap,

3
@Pharap Se i tuoi hacker hanno accesso all'eccezione stessa anziché a un messaggio di errore, sei già perso.
corsiKa

2
Mi piace questa risposta, ma manca ciò che considero un requisito di eccezioni ... se sapessi come recuperare, non sarebbe un'eccezione! Un'eccezione dovrebbe essere solo quando non puoi fare qualcosa al riguardo: input non valido, stato non valido, sicurezza non valida - non puoi risolverli a livello di codice.
corsiKa

1
Concordato. Se insisti nell'indicare se un nuovo tentativo è possibile, puoi sempre semplicemente ereditare le relative eccezioni concrete RetryableError.
sapi,

18

Avvertimento! Il programmatore C ++ arriva qui con idee forse diverse su come gestire le eccezioni cercando di rispondere a una domanda che riguarda sicuramente un'altra lingua!

Data questa idea:

Ad esempio, immagina di avere una risorsa di recupero, che esegue la richiesta HTTP e restituisce i dati recuperati. E invece di errori come ServiceTemporaryUnavailable o RateLimitExceeded vorremmo solo sollevare un RetryableError che suggerisce al consumatore che dovrebbe semplicemente riprovare la richiesta e non preoccuparsi di errori specifici.

... una cosa che suggerirei è che potresti confondere le preoccupazioni relative alla segnalazione di un errore con azioni da intraprendere per rispondere ad esso in un modo che potrebbe degradare la generalità del tuo codice o richiedere molti "punti di traduzione" per le eccezioni .

Ad esempio, se modello una transazione che comporta il caricamento di un file, potrebbe non riuscire per una serie di motivi. Forse il caricamento del file comporta il caricamento di un plug-in che non esiste sul computer dell'utente. Forse il file è semplicemente corrotto e si è verificato un errore durante l'analisi.

Indipendentemente da ciò che accade, supponiamo che il corso dell'azione sia segnalare ciò che è accaduto all'utente e chiedergli cosa vuole fare al riguardo ("riprovare, caricare un altro file, annullare").

Thrower vs. Catcher

Tale linea di condotta si applica indipendentemente dal tipo di errore riscontrato in questo caso. Non è incorporato nell'idea generale di un errore di analisi, non è incorporato nell'idea generale di non riuscire a caricare un plug-in. È incorporato nell'idea di incontrare tali errori durante il preciso contesto di caricamento di un file (la combinazione di caricamento di un file e errore). Quindi, in genere, lo vedo, parlando rozzamente, come la catcher'sresponsabilità di determinare il corso dell'azione in risposta a un'eccezione generata (es: sollecitare l'utente con le opzioni), non il thrower's.

Detto in altro modo, i siti in cui le throweccezioni in genere mancano di questo tipo di informazioni contestuali, soprattutto se le funzioni che generano sono generalmente applicabili. Anche in un contesto totalmente degeneralizzato quando hanno queste informazioni, finisci per accaparrarti in termini di comportamento di recupero inserendole nel throwsito. I siti che catchsono quelli che generalmente hanno la maggior quantità di informazioni disponibili per determinare una linea di condotta e ti danno un posto centrale per modificare se tale linea di azione dovesse mai cambiare per quella determinata transazione.

Quando inizi a provare a generare eccezioni non riportando più ciò che è sbagliato ma cercando di determinare cosa fare, ciò potrebbe degradare la generalità e la flessibilità del tuo codice. Un errore di analisi non dovrebbe sempre portare a questo tipo di prompt, ma varia in base al contesto in cui viene generata tale eccezione (la transazione in cui è stata generata).

The Blind Thrower

In generale, gran parte del design della gestione delle eccezioni spesso ruota attorno all'idea di un lanciatore cieco. Non sa come verrà catturata l'eccezione o dove. Lo stesso vale per forme ancora più vecchie di recupero degli errori usando la propagazione manuale degli errori. I siti che riscontrano errori non includono un corso d'azione dell'utente, ma incorporano solo le informazioni minime per segnalare il tipo di errore riscontrato.

Responsabilità invertite e generalizzazione del ricevitore

Pensando più attentamente a questo, stavo cercando di immaginare il tipo di base di codice in cui questo potrebbe diventare una tentazione. La mia immaginazione (forse sbagliata) è che la tua squadra sta ancora giocando il ruolo di "consumatore" qui e implementando anche la maggior parte del codice chiamante. Forse hai molte transazioni disparate (molti tryblocchi) che possono incorrere tutti negli stessi insiemi di errori e tutti dovrebbero, dal punto di vista del design, portare a un corso uniforme di azione di recupero.

Tenendo conto dei saggi consigli della Lightness Races in Orbit'sbuona risposta (che penso provenga davvero da una mentalità avanzata orientata alla biblioteca), potresti essere ancora tentato di gettare eccezioni "cosa fare", solo più vicino al sito di recupero delle transazioni.

Potrebbe essere possibile trovare qui un sito intermedio e comune di gestione delle transazioni che centralizzi effettivamente le preoccupazioni "cosa fare" ma nel contesto della cattura.

inserisci qui la descrizione dell'immagine

Ciò si applicherebbe solo se è possibile progettare un qualche tipo di funzione generale utilizzata da tutte queste transazioni esterne (es: una funzione che immette un'altra funzione da chiamare o una classe base di transazione astratta con comportamento scavalcabile che modella questo sito di transazione intermedio che esegue la sofisticata cattura ).

Tuttavia, si potrebbe essere responsabili della centralizzazione del corso d'azione dell'utente in risposta a una varietà di possibili errori, e comunque nel contesto della cattura piuttosto che del lancio. Esempio semplice (pseudocodice Python-ish, e non sono minimamente uno sviluppatore Python esperto quindi potrebbe esserci un modo più idiomatico di farlo):

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

[Speriamo con un nome migliore di general_catcher]. In questo esempio, è possibile passare a una funzione contenente l'attività da eseguire ma beneficiare comunque del comportamento di cattura generalizzato / unificato per tutti i tipi di eccezioni a cui si è interessati e continuare a estendere o modificare la parte "cosa fare" ti piace da questa posizione centrale e comunque in un catchcontesto in cui questo è in genere incoraggiato. Soprattutto, possiamo impedire ai siti di lancio di preoccuparsi di "cosa fare" (preservando la nozione di "lanciatore alla cieca").

Se non trovi nessuno di questi suggerimenti qui utili e c'è una forte tentazione di gettare comunque eccezioni "cosa fare", sii principalmente consapevole del fatto che questo è almeno molto anti-idiomatico, oltre a scoraggiare potenzialmente una mentalità generalizzata.


2
+1. Non ho mai sentito parlare dell'idea di "Blind Thrower" come tale prima, ma si adatta a come penso alla gestione delle eccezioni: affermare che si è verificato l'errore, non pensare a come dovrebbe essere gestito. Quando sei responsabile dell'intero stack è difficile (ma importante!) Separare le responsabilità in modo pulito, e la chiamata è responsabile della notifica dei problemi e il chiamante per la gestione dei problemi. La chiamata sa solo cosa gli è stato chiesto di fare, non perché. La gestione degli errori dovrebbe essere fatta nel contesto del "perché", quindi: nel chiamante.
Sjoerd Job Postmus,

1
(Inoltre: non credo che la tua risposta sia specifica per C ++, ma applicabile alla gestione delle eccezioni in generale)
Sjoerd Job Postmus

1
@SjoerdJobPostmus Yay thanks! "Blind Thrower" è solo un'analogia sciocca che mi è venuta in mente qui - non sono molto intelligente o veloce nel digerire concetti tecnici complessi, quindi spesso voglio trovare piccole immagini e analogie per cercare di spiegare e migliorare la mia comprensione di cose. Forse un giorno potrò provare a lavorare a modo mio per scrivere un piccolo libro di programmazione pieno di molti disegni di cartoni animati. :-D

1
Heh, questa è una bella piccola immagine. Un piccolo personaggio dei cartoni animati che indossa una benda sugli occhi e lancia eccezioni a forma di baseball, incerto su chi li catturerà (o anche se saranno catturati), ma adempiendo al proprio dovere di lanciatore cieco.
Blacklight Shining il

1
@DrunkCoder: per favore non vandalizzare i tuoi post. Abbiamo già abbastanza materiale di borken su Internet. Se hai una buona ragione per la cancellazione, segnala il tuo post per l'attenzione del moderatore e fai il tuo caso.
Robert Harvey,

2

Penso che la maggior parte delle volte sarebbe meglio passare argomenti alla funzione dicendogli come gestire quelle situazioni.

Ad esempio, considera una funzione:

Response fetchUrl(URL url, RetryPolicy retryPolicy);

Posso passare RetryPolicy.noRetries () o RetryPolicy.retries (3) o altro. Nel caso di un errore ripetibile, consulterà la politica per decidere se riprovare o meno.


Tuttavia, ciò non ha molto a che fare con il ritorno delle eccezioni al sito della chiamata. Stai parlando di qualcosa di un po 'diverso, il che va bene ma non fa davvero parte della domanda ..
Lightness Races con Monica

@LightnessRacesinOrbit, al contrario. Lo sto presentando come alternativa all'idea di restituire eccezioni al sito di chiamata. Nell'esempio del PO, fetchUrl genererebbe una RetryableException e sto dicendo invece che dovresti dire a fetchUrl quando dovrebbe riprovare.
Winston Ewert,

2
@WinstonEwert: Anche se sono d'accordo con LightnessRacesinOrbit, vedo anche il tuo punto, ma lo considero un modo diverso di rappresentare lo stesso controllo. Ma considera che probabilmente vorresti passare new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)o qualcosa del genere, perché RateLimitExceededpotrebbe essere necessario gestirlo diversamente ServiceTemporaryUnavailable. Dopo averlo scritto, il mio pensiero è: meglio lanciare un'eccezione, perché dà un controllo più flessibile.
Sjoerd Job Postmus,

@SjoerdJobPostmus, penso che dipenda. Potrebbe essere la logica che limita la velocità e riprova da allora nella tua libreria, nel qual caso penso che il mio approccio abbia senso. Se ha più senso lasciarlo al tuo interlocutore, lancia.
Winston Ewert,
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.