Penso di aver letto la stessa intervista a Bruce Eckel che hai fatto - e mi ha sempre infastidito. In realtà, l'argomentazione ha sostenuto l'argomentazione (se questo è davvero il post di cui stai parlando) Anders Hejlsberg, il genio della SM dietro a .NET e C #.
http://www.artima.com/intv/handcuffs.html
Anche se sono un fan di Hejlsberg e del suo lavoro, questa discussione mi ha sempre reso falso. Fondamentalmente si riduce a:
"Le eccezioni verificate sono cattive perché i programmatori li abusano sempre catturandoli e scartandoli, il che porta a nascondere e ignorare i problemi che altrimenti verrebbero presentati all'utente".
Con "altrimenti presentato all'utente" intendo che se usi un'eccezione di runtime il programmatore pigro lo ignorerà (invece di catturarlo con un blocco catch vuoto) e l'utente lo vedrà.
Il sommario del sommario dell'argomento è che "I programmatori non li useranno correttamente e non usarli correttamente è peggio che non averli" .
C'è del vero in questo argomento e, in effetti, sospetto che la motivazione di Goslings per non mettere le sostituzioni degli operatori in Java provenga da un argomento simile: confondono il programmatore perché sono spesso abusati.
Ma alla fine, trovo un argomento fasullo di Hejlsberg e forse uno post-hoc creato per spiegare la mancanza piuttosto che una decisione ben ponderata.
Direi che mentre l'uso eccessivo di eccezioni controllate è una cosa negativa e tende a condurre a una gestione sciatta da parte degli utenti, ma il loro corretto utilizzo consente al programmatore API di offrire grandi vantaggi al programmatore client API.
Ora il programmatore dell'API deve fare attenzione a non gettare eccezioni verificate in tutto il luogo, o semplicemente infastidiranno il programmatore del client. Il programmatore client molto pigro ricorrerà alla cattura (Exception) {}
mentre Hejlsberg avverte e tutti i benefici andranno persi e ne conseguirà l'inferno. Ma in alcune circostanze, non c'è proprio alcun sostituto per una buona eccezione verificata.
Per me, il classico esempio è l'API di apertura file. Ogni linguaggio di programmazione nella storia dei linguaggi (almeno sui file system) ha un'API da qualche parte che ti consente di aprire un file. E ogni programmatore client che utilizza questa API sa di dover affrontare il caso in cui il file che sta tentando di aprire non esiste. Fammi riformulare: Ogni programmatore client che utilizza questa API dovrebbe sapere che devono occuparsi di questo caso. E c'è il problema: il programmatore API può aiutarli a sapere che dovrebbero occuparsene commentando da soli o possono davvero insistere il cliente lo affronti.
In C il linguaggio dice qualcosa di simile
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
dove fopen
indica il fallimento restituendo 0 e C (stupidamente) ti permette di trattare 0 come un valore booleano e ... Fondamentalmente, impari questo idioma e stai bene. Ma cosa succede se sei un noob e non hai imparato il linguaggio. Quindi, ovviamente, inizi con
f = fopen("goodluckfindingthisfile");
f.read(); // BANG!
e impara nel modo più duro.
Si noti che stiamo parlando solo di linguaggi fortemente tipizzati qui: c'è una chiara idea di cosa sia un'API in un linguaggio fortemente tipizzato: è una smorgasbord di funzionalità (metodi) da usare con un protocollo chiaramente definito per ognuno.
Quel protocollo chiaramente definito è in genere definito da una firma del metodo. Qui fopen richiede di passargli una stringa (o un carattere * nel caso di C). Se gli dai qualcos'altro ottieni un errore di compilazione. Non hai seguito il protocollo: non stai utilizzando correttamente l'API.
In alcune lingue (oscure) anche il tipo restituito fa parte del protocollo. Se provi a chiamare l'equivalente difopen()
in alcune lingue senza assegnarlo a una variabile, otterrai anche un errore di compilazione (puoi farlo solo con le funzioni void).
Il punto che sto cercando di sottolineare è che: in un linguaggio tipicamente statico il programmatore API incoraggia il client a utilizzare correttamente l'API impedendo la compilazione del codice client in caso di errori evidenti.
(In un linguaggio tipizzato in modo dinamico, come Ruby, puoi passare qualsiasi cosa, dire un float, come il nome del file - e verrà compilato. Perché disturbare l'utente con le eccezioni verificate se non hai nemmeno intenzione di controllare gli argomenti del metodo. gli argomenti fatti qui si applicano solo alle lingue tipicamente statiche.)
Quindi, per quanto riguarda le eccezioni controllate?
Bene, ecco una delle API Java che puoi usare per aprire un file.
try {
f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
// deal with it. No really, deal with it!
... // this is me dealing with it
}
Vedi quella cattura? Ecco la firma per quel metodo API:
public FileInputStream(String name)
throws FileNotFoundException
Si noti che FileNotFoundException
è un'eccezione controllata .
Il programmatore API ti sta dicendo questo: "Puoi usare questo costruttore per creare un nuovo FileInputStream ma tu
a) deve passare il nome del file come stringa
b) deve accettare la possibilità che il file non venga trovato in fase di esecuzione "
E questo è il punto per quanto mi riguarda.
La chiave è fondamentalmente ciò che la domanda afferma come "Cose che sono fuori dal controllo del programmatore". Il mio primo pensiero è stato che lui / lei significhi cose che sono fuori dal controllo dei programmatori API . Ma in realtà, le eccezioni controllate se usate correttamente dovrebbero essere realmente per cose che sono fuori dal controllo sia del programmatore client sia del programmatore API. Penso che questa sia la chiave per non abusare delle eccezioni verificate.
Penso che l'apertura del file illustri bene il punto. Il programmatore API sa che potresti dare loro un nome file che risulta essere inesistente al momento in cui viene chiamata l'API e che non saranno in grado di restituirti ciò che volevi, ma dovranno lanciare un'eccezione. Sanno anche che questo accadrà abbastanza regolarmente e che il programmatore del client potrebbe aspettarsi che il nome del file sia corretto al momento in cui ha scritto la chiamata, ma potrebbe essere sbagliato in fase di esecuzione per ragioni indipendenti dalla sua volontà.
Quindi l'API lo rende esplicito: ci saranno casi in cui questo file non esiste al momento in cui mi chiami e tu hai dannatamente meglio affrontarlo.
Ciò sarebbe più chiaro con una contro-causa. Immagina di scrivere un'API per la tabella. Ho il modello di tabella da qualche parte con un'API che include questo metodo:
public RowData getRowData(int row)
Ora come programmatore API so che ci saranno casi in cui alcuni client passano un valore negativo per la riga o un valore di riga al di fuori della tabella. Quindi potrei essere tentato di lanciare un'eccezione controllata e costringere il cliente a gestirlo:
public RowData getRowData(int row) throws CheckedInvalidRowNumberException
(Non lo chiamerei davvero "Controllato" ovviamente.)
Questo è un cattivo uso delle eccezioni verificate. Il codice client sarà pieno di chiamate per recuperare i dati delle righe, ognuno dei quali dovrà utilizzare un tentativo / cattura, e per cosa? Riferiranno all'utente che è stata cercata la riga sbagliata? Probabilmente no, poiché qualunque sia l'interfaccia utente che circonda la mia vista tabella, non dovrebbe consentire all'utente di entrare in uno stato in cui viene richiesta una riga illegale. Quindi è un bug da parte del programmatore client.
Il programmatore API può ancora prevedere che il client codificherà tali bug e dovrebbe gestirlo con un'eccezione di runtime come un IllegalArgumentException
.
Con un'eccezione verificata getRowData
, questo è chiaramente un caso che porterà al programmatore pigro di Hejlsberg semplicemente aggiungendo catture vuote. Quando ciò accade, i valori delle righe illegali non saranno evidenti nemmeno per il tester o il debug dello sviluppatore del client, ma porteranno a errori a catena di cui è difficile individuare l'origine. I razzi Arianne esploderanno dopo il lancio.
Ok, quindi ecco il problema: sto dicendo che l'eccezione selezionata FileNotFoundException
non è solo una buona cosa, ma uno strumento essenziale nella casella degli strumenti dei programmatori API per definire l'API nel modo più utile per il programmatore client. Ma questo CheckedInvalidRowNumberException
è un grosso inconveniente, che porta a una cattiva programmazione e dovrebbe essere evitato. Ma come dire la differenza.
Immagino che non sia una scienza esatta e suppongo che sia alla base e forse giustifichi in una certa misura l'argomento di Hejlsberg. Ma non sono felice di buttare qui il bambino con l'acqua del bagno, quindi permettimi di estrarre alcune regole qui per distinguere le buone eccezioni verificate da quelle cattive:
Fuori dal controllo del cliente o Chiuso vs Aperto:
Le eccezioni verificate devono essere utilizzate solo quando il caso di errore è fuori controllo sia dell'API che del programmatore client. Questo ha a che fare con quanto sia aperto o chiuso il sistema. In un'interfaccia utente limitata in cui il programmatore client ha il controllo, ad esempio, su tutti i pulsanti, i comandi da tastiera ecc. Che aggiungono ed eliminano le righe dalla vista tabella (un sistema chiuso), è un bug di programmazione del client se tenta di recuperare i dati da una riga inesistente. In un sistema operativo basato su file in cui un numero qualsiasi di utenti / applicazioni può aggiungere ed eliminare file (un sistema aperto), è concepibile che il file richiesto dal client sia stato eliminato a loro insaputa, quindi ci si dovrebbe aspettare che lo gestiscano .
Ubiquità:
Le eccezioni verificate non devono essere utilizzate su una chiamata API effettuata frequentemente dal client. Spesso intendo da molti punti del codice client, non frequentemente in tempo. Quindi un codice client non tende a tentare di aprire molto lo stesso file, ma la vista della mia tabella arriva RowData
ovunque con metodi diversi. In particolare, scriverò un sacco di codice come
if (model.getRowData().getCell(0).isEmpty())
e sarà doloroso doverlo avvolgere in try / catch ogni volta.
Informare l'utente:
Le eccezioni verificate devono essere utilizzate nei casi in cui si possa immaginare un utile messaggio di errore che viene presentato all'utente finale. Questo è il "e cosa farai quando succede?" domanda che ho sollevato sopra. Si riferisce anche all'elemento 1. Poiché puoi prevedere che qualcosa al di fuori del tuo sistema API client potrebbe causare la mancata presenza del file, puoi ragionevolmente dirlo all'utente:
"Error: could not find the file 'goodluckfindingthisfile'"
Poiché il tuo numero di riga illegale è stato causato da un bug interno e non per colpa dell'utente, in realtà non ci sono informazioni utili che puoi fornire. Se la tua app non lascia cadere eccezioni di runtime nella console, probabilmente finirà per dare loro un brutto messaggio come:
"Internal error occured: IllegalArgumentException in ...."
In breve, se non pensi che il tuo programmatore client possa spiegare la tua eccezione in un modo che aiuti l'utente, probabilmente non dovresti usare un'eccezione controllata.
Quindi quelle sono le mie regole. Un po 'inventato, e ci saranno senza dubbio delle eccezioni (per favore aiutatemi a perfezionarle se volete). Ma il mio argomento principale è che ci sono casi in FileNotFoundException
cui l'eccezione controllata è una parte importante e utile del contratto API come i tipi di parametro. Quindi non dovremmo rinunciarci solo perché è usato in modo improprio.
Mi dispiace, non intendevo renderlo così lungo e waffly. Vorrei concludere con due suggerimenti:
A: Programmatori API: usa le eccezioni selezionate con parsimonia per preservarne l'utilità. In caso di dubbio, utilizzare un'eccezione non selezionata.
B: Programmatori client: abituati a creare un'eccezione chiusa (google it) all'inizio del tuo sviluppo. JDK 1.4 e versioni successive forniscono un costruttore RuntimeException
per questo, ma puoi facilmente crearne uno tuo. Ecco il costruttore:
public RuntimeException(Throwable cause)
Quindi prendi l'abitudine di quando devi gestire un'eccezione controllata e ti senti pigro (o pensi che il programmatore API sia stato troppo zelante nell'usare l'eccezione controllata in primo luogo), non ingoiare solo l'eccezione, avvolgila e riprovare.
try {
overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
throw new RuntimeException(exception);
}
Inseriscilo in uno dei piccoli template di codice IDE e usalo quando ti senti pigro. In questo modo, se hai davvero bisogno di gestire l'eccezione selezionata, sarai costretto a tornare e occuparsene dopo aver visto il problema in fase di esecuzione. Perché, credimi (e Anders Hejlsberg), non tornerai mai più a quel TODO nel tuo
catch (Exception e) { /* TODO deal with this at some point (yeah right) */}