ma il crash del software del tuo client non è ancora una buona cosa
Sicuramente è una buona cosa.
Volete qualsiasi cosa che lasci il sistema in uno stato indefinito per fermare il sistema perché un sistema indefinito può fare cose cattive come dati corrotti, formattare il disco rigido e inviare e-mail minacciose al presidente. Se non è possibile ripristinare e riportare il sistema in uno stato definito, la causa responsabile è l'arresto anomalo. È esattamente il motivo per cui costruiamo sistemi che si arrestano in modo anomalo piuttosto che separarsi silenziosamente. Ora sicuramente, tutti desideriamo un sistema stabile che non si blocchi mai, ma lo vogliamo davvero solo quando il sistema rimane in uno stato sicuro prevedibile definito.
Ho sentito che le eccezioni come flusso di controllo sono considerate un grave antipattern
È assolutamente vero ma spesso è frainteso. Quando hanno inventato il sistema delle eccezioni avevano paura di interrompere la programmazione strutturata. Programmazione strutturata è per questo che abbiamo for
, while
, until
, break
, e continue
quando tutti abbiamo bisogno, a fare tutto questo, è goto
.
Dijkstra ci ha insegnato che l'uso di goto in modo informale (ovvero saltando ovunque ti piace) rende la lettura del codice un incubo. Quando ci hanno dato il sistema delle eccezioni avevano paura di reinventare il goto. Quindi ci dissero di non "usarlo per il controllo del flusso" sperando di capire. Sfortunatamente, molti di noi non lo fecero.
Stranamente, spesso non abusiamo delle eccezioni per creare il codice spaghetti come facevamo con goto. Il consiglio stesso sembra aver causato più problemi.
Fondamentalmente le eccezioni riguardano il rifiuto di un'ipotesi. Quando chiedi di salvare un file, supponi che il file possa e verrà salvato. L'eccezione che si ottiene quando non può essere dovuta al fatto che il nome è illegale, che l'HD è pieno o che un topo è rosicchiato attraverso il cavo dati. Puoi gestire tutti questi errori in modo diverso, puoi gestirli tutti allo stesso modo o puoi lasciarli fermare il sistema. C'è un percorso felice nel tuo codice in cui le tue assunzioni devono essere vere. In un modo o nell'altro le eccezioni ti portano fuori da quel percorso felice. A rigor di termini, sì, è una sorta di "controllo del flusso", ma non è quello che ti stavano avvertendo. Stavano parlando di sciocchezze come questa :
"Le eccezioni dovrebbero essere eccezionali". Questa piccola tautologia è nata perché i progettisti di sistemi di eccezione hanno bisogno di tempo per creare tracce di stack. Rispetto al saltare in giro, questo è lento. Si consuma tempo CPU. Ma se stai per registrare e arrestare il sistema o almeno interrompere l'elaborazione che richiede molto tempo prima di iniziare quello successivo, hai del tempo per uccidere. Se le persone iniziano a usare le eccezioni "per il controllo del flusso", quei presupposti sul tempo vanno tutti fuori dalla finestra. Quindi "Exceptions dovrebbe essere eccezionale" ci è stato dato davvero come considerazione delle prestazioni.
Molto più importante di così non ci sta confondendo. Quanto tempo hai impiegato per individuare il ciclo infinito nel codice sopra?
NON restituire codici di errore.
... è un buon consiglio quando ci si trova in una base di codice che in genere non utilizza codici di errore. Perché? Perché nessuno ricorderà di salvare il valore restituito e controllare i codici di errore. È ancora una bella convenzione quando sei in C.
OneOf
Stai usando un'altra convenzione. Va bene fintanto che stai organizzando la convention e non semplicemente combattendo un altro. È confuso avere due convenzioni di errore nella stessa base di codice. Se in qualche modo ti sei sbarazzato di tutto il codice che utilizza l'altra convenzione, vai avanti.
Mi piace la convention da solo. Una delle migliori spiegazioni che ho trovato qui * :
Ma per quanto mi piaccia, non lo mescolerò ancora con le altre convenzioni. Scegline uno e mantienilo. 1
1: Voglio dire, non farmi pensare a più di una convenzione contemporaneamente.
Pensieri successivi:
Da questa discussione quello che sto portando via attualmente è il seguente:
- Se ti aspetti che il chiamante immediato rilevi e gestisca l'eccezione per la maggior parte del tempo e continui il suo lavoro, magari attraverso un altro percorso, probabilmente dovrebbe far parte del tipo restituito. Opzionale o OneOf può essere utile qui.
- Se si prevede che il chiamante immediato non prenda l'eccezione per la maggior parte del tempo, emettere un'eccezione per salvare la stupidità di passarla manualmente nello stack.
- Se non sei sicuro di ciò che farà il chiamante immediato, potresti fornire entrambi, come Parse e TryParse.
Non è davvero così semplice. Una delle cose fondamentali che devi capire è cos'è uno zero.
Quanti giorni sono rimasti a maggio? 0 (perché non è maggio. È già giugno).
Le eccezioni sono un modo per rifiutare un'ipotesi, ma non sono l'unico modo. Se usi le eccezioni per rifiutare l'assunto, lasci la strada felice. Ma se hai scelto dei valori per inviare il percorso felice che segnala che le cose non sono così semplici come è stato ipotizzato, puoi rimanere su quel percorso finché può gestire quei valori. A volte 0 è già usato per significare qualcosa, quindi devi trovare un altro valore per mappare la tua ipotesi rifiutando l'idea. Puoi riconoscere questa idea dal suo uso nella buona vecchia algebra . Le monadi possono aiutare in questo, ma non deve sempre essere una monade.
Ad esempio 2 :
IList<int> ParseAllTheInts(String s) { ... }
Riesci a pensare a qualche buona ragione per cui questo deve essere progettato in modo da gettare deliberatamente qualsiasi cosa? Indovina cosa ottieni quando nessun int può essere analizzato? Non ho nemmeno bisogno di dirtelo.
Questo è un segno di un buon nome. Ci dispiace ma TryParse non è la mia idea di un buon nome.
Spesso evitiamo di gettare un'eccezione nel non ottenere nulla quando la risposta potrebbe essere più di una cosa allo stesso tempo, ma per qualche motivo se la risposta è una cosa o niente siamo ossessionati dall'insistere sul fatto che ci dia una cosa o lanci:
IList<Point> Intersection(Line a, Line b) { ... }
Le linee parallele devono davvero causare un'eccezione qui? È davvero così male se questo elenco non conterrà mai più di un punto?
Forse semanticamente non puoi prenderlo. Se è così, è un peccato. Ma forse Monads, che non ha una dimensione arbitraria come te List
, ti farà sentire meglio.
Maybe<Point> Intersection(Line a, Line b) { ... }
Le Monadi sono piccole raccolte per scopi speciali che devono essere utilizzate in modi specifici che evitano di doverle testare. Dovremmo trovare il modo di gestirli indipendentemente da ciò che contengono. In questo modo il percorso felice rimane semplice. Se ti spacchi e metti alla prova ogni Monade che tocchi, li stai usando in modo sbagliato.
Lo so, è strano. Ma è un nuovo strumento (beh, per noi). Dagli un po 'di tempo. I martelli hanno più senso quando smetti di usarli sulle viti.
Se mi concedi, vorrei rispondere a questo commento:
Come mai nessuna delle risposte chiarisce che la monade non è un codice di errore, né OneOf? Sono fondamentalmente diversi e, di conseguenza, la domanda sembra basarsi su un malinteso. (Anche se in una forma modificata è ancora una domanda valida.) - Konrad Rudolph 4 giu 18 alle 14:08
Questo è assolutamente vero. Le monadi sono molto più vicine alle raccolte di eccezioni, bandiere o codici di errore. Realizzano contenitori fini per queste cose se usati con saggezza.
Result
;-)