Il linguaggio Go di Google non ha eccezioni come scelta di design e Linus della fama di Linux ha definito le eccezioni merda. Perché?
Il linguaggio Go di Google non ha eccezioni come scelta di design e Linus della fama di Linux ha definito le eccezioni merda. Perché?
Risposte:
Le eccezioni rendono davvero facile scrivere codice in cui un'eccezione generata interromperà le invarianti e lascerà gli oggetti in uno stato incoerente. Essenzialmente ti costringono a ricordare che la maggior parte delle affermazioni che fai può potenzialmente lanciare e gestirlo correttamente. Farlo può essere complicato e controintuitivo.
Considera qualcosa di simile come un semplice esempio:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Supponendo che la FrobManager
volontà delete
del FrobObject
, questo sembra ok, giusto? O forse no ... Immagina quindi se uno dei due FrobManager::HandleFrob()
o operator new
lancia un'eccezione. In questo esempio, l'incremento di m_NumberOfFrobs
non viene annullato. Pertanto, chiunque utilizzi questa istanza di Frobber
avrà un oggetto potenzialmente danneggiato.
Questo esempio può sembrare stupido (ok, mi sono dovuto sforzare un po 'per costruirne uno :-)), ma la conclusione è che se un programmatore non pensa costantemente alle eccezioni e si assicura che ogni permutazione di stato venga annullata ogni volta che ci sono lanci, ti metti nei guai in questo modo.
Ad esempio, puoi pensarlo come pensi ai mutex. All'interno di una sezione critica, fai affidamento su diverse istruzioni per assicurarti che le strutture dati non siano danneggiate e che gli altri thread non possano vedere i tuoi valori intermedi. Se una qualsiasi di queste affermazioni non viene eseguita casualmente, finisci in un mondo di dolore. Ora togli i blocchi e la concorrenza e pensa a ciascun metodo in questo modo. Pensa a ogni metodo come una transazione di permutazioni sullo stato dell'oggetto, se vuoi. All'inizio della chiamata al metodo, l'oggetto dovrebbe essere in stato pulito e alla fine dovrebbe esserci anche uno stato pulito. Nel mezzo, la variabile foo
potrebbe non essere coerente conbar
, ma il tuo codice alla fine lo rettificherà. Ciò che significa eccezioni è che qualsiasi tua dichiarazione può interromperti in qualsiasi momento. Spetta a te in ogni singolo metodo farlo correttamente e tornare indietro quando ciò accade, o ordinare le tue operazioni in modo che i lanci non influenzino lo stato dell'oggetto. Se sbagli (ed è facile commettere questo tipo di errore), il chiamante finisce per vedere i tuoi valori intermedi.
Metodi come RAII, che i programmatori C ++ amano menzionare come la soluzione definitiva a questo problema, fanno molto per proteggersi da questo. Ma non sono una pallottola d'argento. Ti assicurerà di rilasciare risorse al momento del lancio, ma non ti libera dal dover pensare alla corruzione dello stato dell'oggetto e ai chiamanti che vedono valori intermedi. Quindi, per molte persone, è più facile a dirsi, in base allo stile di codifica, senza eccezioni . Se limiti il tipo di codice che scrivi, è più difficile introdurre questi bug. Se non lo fai, è abbastanza facile commettere un errore.
Sono stati scritti interi libri sulla codifica sicura delle eccezioni in C ++. Molti esperti hanno sbagliato. Se è davvero così complesso e ha così tante sfumature, forse è un buon segno che devi ignorare quella caratteristica. :-)
Il motivo per cui Go non ha eccezioni è spiegato nelle domande frequenti sulla progettazione del linguaggio Go:
Le eccezioni sono una storia simile. Sono stati proposti numerosi progetti per le eccezioni, ma ciascuno aggiunge una complessità significativa al linguaggio e al tempo di esecuzione. Per loro stessa natura, le eccezioni abbracciano funzioni e forse anche goroutine; hanno implicazioni ad ampio raggio. C'è anche preoccupazione per l'effetto che avrebbero sulle biblioteche. Sono, per definizione, eccezionali ma l'esperienza con altri linguaggi che li supportano mostra che hanno un profondo effetto sulle specifiche della libreria e dell'interfaccia. Sarebbe bello trovare un design che permetta loro di essere davvero eccezionali senza incoraggiare errori comuni a trasformarsi in un flusso di controllo speciale che richiede a ogni programmatore di compensare.
Come i generici, le eccezioni rimangono una questione aperta.
In altre parole, non hanno ancora capito come supportare le eccezioni in Go in un modo che ritengono soddisfacente. Non stanno dicendo che le eccezioni siano negative di per sé ;
AGGIORNAMENTO - maggio 2012
I progettisti di Go sono ora scesi dalla recinzione. Le loro FAQ ora dicono questo:
Riteniamo che l'accoppiamento di eccezioni a una struttura di controllo, come nell'idioma try-catch-latest, si traduca in codice contorto. Inoltre tende a incoraggiare i programmatori a etichettare troppi errori ordinari, come la mancata apertura di un file, come eccezionali.
Go ha un approccio diverso. Per la semplice gestione degli errori, i resi multivalore di Go semplificano la segnalazione di un errore senza sovraccaricare il valore restituito. Un tipo di errore canonico, insieme alle altre funzionalità di Go, rende la gestione degli errori piacevole ma abbastanza diversa da quella delle altre lingue.
Go ha anche un paio di funzioni integrate per segnalare e recuperare da condizioni davvero eccezionali. Il meccanismo di ripristino viene eseguito solo come parte dello stato di una funzione che viene abbattuto dopo un errore, il che è sufficiente per gestire la catastrofe ma non richiede strutture di controllo aggiuntive e, se usato correttamente, può dare come risultato un codice di gestione degli errori pulito.
Per i dettagli, vedere l'articolo Rinvia, panico e ripristino.
Quindi la risposta breve è che possono farlo in modo diverso utilizzando il ritorno multivalore. (E comunque hanno una forma di gestione delle eccezioni.)
... e Linus della fama di Linux ha definito le eccezioni merda.
Se vuoi sapere perché Linus pensa che le eccezioni siano una schifezza, la cosa migliore è cercare i suoi scritti sull'argomento. L'unica cosa che ho rintracciato finora è questa citazione che è incorporata in un paio di e-mail su C ++ :
"L'intera faccenda della gestione delle eccezioni C ++ è fondamentalmente non funzionante. È particolarmente difettosa per i kernel."
Noterai che sta parlando di eccezioni C ++ in particolare e non di eccezioni in generale. (E C ++ eccezioni non hanno apparentemente alcuni problemi che li rendono difficile da usare correttamente.)
La mia conclusione è che Linus non ha definito le eccezioni (in generale) "schifo"!
Le eccezioni non sono male di per sé, ma se sai che succederanno spesso, possono essere costose in termini di prestazioni.
La regola pratica è che le eccezioni dovrebbero segnalare condizioni eccezionali e che non dovresti usarle per il controllo del flusso del programma.
Non sono d'accordo con "lanciare eccezioni solo in una situazione eccezionale". Sebbene sia generalmente vero, è fuorviante. Le eccezioni riguardano le condizioni di errore (errori di esecuzione).
Indipendentemente dalla lingua che utilizzi, prendi una copia delle Linee guida per la progettazione del framework : convenzioni, idiomi e modelli per le librerie .NET riutilizzabili (2a edizione). Il capitolo sul lancio di eccezioni è senza pari. Alcune citazioni dalla prima edizione (la seconda è al mio lavoro):
Ci sono pagine di note sui vantaggi delle eccezioni (coerenza API, scelta della posizione del codice di gestione degli errori, maggiore robustezza, ecc.) C'è una sezione sulle prestazioni che include diversi modelli (Tester-Doer, Try-Parse).
Le eccezioni e la gestione delle eccezioni non sono male. Come qualsiasi altra caratteristica, possono essere utilizzati in modo improprio.
Dal punto di vista di golang, immagino che non avere la gestione delle eccezioni mantenga il processo di compilazione semplice e sicuro.
Dal punto di vista di Linus, capisco che il codice del kernel è TUTTO su casi d'angolo. Quindi ha senso rifiutare le eccezioni.
Le eccezioni hanno senso nel codice dove va bene rilasciare l'attività corrente sul pavimento e dove il codice caso comune ha più importanza della gestione degli errori. Ma richiedono la generazione di codice dal compilatore.
Ad esempio, vanno bene nella maggior parte del codice di alto livello rivolto agli utenti, come il codice dell'applicazione web e desktop.
Le eccezioni in sé e per sé non sono "cattive", è il modo in cui le eccezioni a volte vengono gestite che tende ad essere cattive. Esistono diverse linee guida che possono essere applicate quando si gestiscono le eccezioni per alleviare alcuni di questi problemi. Alcuni di questi includono (ma sicuramente non sono limitati a):
Option<T>
invece di null
oggi. Ad esempio, è stato appena introdotto in Java 8, prendendo spunto da Guava (e altri).
Gli argomenti tipici sono che non c'è modo di dire quali eccezioni usciranno da un particolare pezzo di codice (a seconda della lingua) e che sono troppo simili a goto
s, rendendo difficile tracciare mentalmente l'esecuzione.
http://www.joelonsoftware.com/items/2003/10/13.html
Sicuramente non c'è consenso su questo tema. Direi che dal punto di vista di un programmatore C hard-core come Linus, le eccezioni sono decisamente una cattiva idea. Tuttavia, un tipico programmatore Java si trova in una situazione molto diversa.
setjmp
/ longjmp
roba, che è piuttosto brutta.
Le eccezioni non sono male. Si adattano bene al modello RAII di C ++, che è la cosa più elegante di C ++. Se hai già un sacco di codice che non è sicuro dalle eccezioni, allora sono cattivi in quel contesto. Se stai scrivendo software di basso livello, come il sistema operativo Linux, allora sono pessimi. Se ti piace sporcare il tuo codice con una serie di controlli di ritorno degli errori, allora non sono utili. Se non si dispone di un piano per il controllo delle risorse quando viene generata un'eccezione (fornita dai distruttori C ++), allora sono cattivi.
Un ottimo caso d'uso per le eccezioni è quindi ...
Supponiamo che tu stia lavorando a un progetto e che ogni controller (circa 20 diversi principali) estenda un singolo controller di superclasse con un metodo di azione. Quindi ogni controller fa un sacco di cose diverse l'una dall'altra chiamando gli oggetti B, C, D in un caso e F, G, D in un altro. Le eccezioni vengono in soccorso qui in molti casi in cui c'erano tonnellate di codice di ritorno e OGNI controller lo gestiva in modo diverso. Ho distrutto tutto quel codice, ho lanciato l'eccezione corretta da "D", l'ho catturato nel metodo di azione del controller della superclasse e ora tutti i nostri controller sono coerenti. In precedenza D restituiva null per MULTIPLI diversi casi di errore di cui vogliamo informare l'utente finale ma che non abbiamo potuto e non l'ho fatto '
sì, dobbiamo preoccuparci di ogni livello e di eventuali ripuliture / perdite di risorse, ma in generale nessuno dei nostri controller aveva risorse per ripulire dopo.
grazie a dio abbiamo avuto delle eccezioni o sarei stato sottoposto a un enorme refactoring e avrei perso troppo tempo su qualcosa che dovrebbe essere un semplice problema di programmazione.
Teoricamente sono davvero pessimi. Nel perfetto mondo matematico non puoi ottenere situazioni eccezionali. Guarda i linguaggi funzionali, non hanno effetti collaterali, quindi praticamente non hanno fonte per situazioni non eccezionali.
Ma la realtà è un'altra storia. Abbiamo sempre situazioni "inaspettate". Ecco perché abbiamo bisogno di eccezioni.
Penso che possiamo pensare alle eccezioni come allo zucchero di sintassi per ExceptionSituationObserver. Ricevi solo notifiche di eccezioni. Niente di più.
Con Go, penso che introdurranno qualcosa che affronterà situazioni "inaspettate". Posso immaginare che cercheranno di farlo sembrare meno distruttivo come eccezioni e più come logica dell'applicazione. Ma questa è solo una mia ipotesi.
Il paradigma di gestione delle eccezioni di C ++, che costituisce una base parziale per quello di Java e, a sua volta .net, introduce alcuni buoni concetti, ma presenta anche alcune gravi limitazioni. Una delle principali intenzioni di progettazione della gestione delle eccezioni è quella di consentire ai metodi di garantire che soddisfino le loro condizioni successive o di generare un'eccezione, e anche di garantire che avvenga qualsiasi pulizia che deve avvenire prima che un metodo possa uscire. Sfortunatamente, i paradigmi di gestione delle eccezioni di C ++, Java e .net non riescono a fornire alcun mezzo valido per gestire la situazione in cui fattori imprevisti impediscono l'esecuzione della pulizia prevista. Questo a sua volta significa che si deve rischiare che tutto si arresti bruscamente se accade qualcosa di inaspettato (l'approccio C ++ alla gestione di un'eccezione si verifica durante lo svolgimento dello stack),
Anche se la gestione delle eccezioni sarebbe generalmente buona, non è irragionevole considerare inaccettabile un paradigma di gestione delle eccezioni che non fornisce un buon mezzo per gestire i problemi che si verificano durante la pulizia dopo altri problemi. Questo non vuol dire che un framework non possa essere progettato con un paradigma di gestione delle eccezioni che possa garantire un comportamento ragionevole anche in scenari di più guasti, ma nessuno dei linguaggi o framework principali può ancora farlo.
Non ho letto tutte le altre risposte, quindi questa potrebbe essere già stata menzionata, ma una critica è che causano l'interruzione dei programmi in lunghe catene, rendendo difficile rintracciare gli errori durante il debug del codice. Ad esempio, se Foo () chiama Bar () che chiama Wah () che chiama ToString (), quindi inserire accidentalmente i dati sbagliati in ToString () finisce per apparire come un errore in Foo (), una funzione quasi completamente non correlata.
Ok, risposta noiosa qui. Immagino che dipenda davvero dalla lingua. Dove un'eccezione può lasciare indietro le risorse allocate, dovrebbero essere evitate. Nei linguaggi di scripting semplicemente abbandonano o saltano in eccesso parti del flusso dell'applicazione. Questo è di per sé antipatico, ma sfuggire a errori quasi fatali con eccezioni è un'idea accettabile.
Per la segnalazione degli errori generalmente preferisco i segnali di errore. Tutto dipende dall'API, dal caso d'uso e dalla gravità o se la registrazione è sufficiente. Inoltre sto cercando di ridefinire il comportamento e throw Phonebooks()
invece. L'idea è che le "eccezioni" sono spesso vicoli ciechi, ma una "rubrica" contiene informazioni utili sul ripristino degli errori o percorsi di esecuzione alternativi. (Non ho ancora trovato un buon caso d'uso, ma continua a provare.)
Per me la questione è molto semplice. Molti programmatori utilizzano il gestore delle eccezioni in modo inappropriato. Più risorse linguistiche sono migliori. Essere in grado di gestire le eccezioni è positivo. Un esempio di cattivo uso è un valore che deve essere un intero non essere verificato, o un altro input che può dividere e non essere controllato per la divisione di zero ... la gestione delle eccezioni può essere un modo semplice per evitare più lavoro e pensieri, il programmatore potrebbe voler fare una scorciatoia sporca e applicare una gestione delle eccezioni ... L'affermazione: "un codice professionale NON fallisce MAI" potrebbe essere illusoria, se alcuni dei problemi elaborati dall'algoritmo sono incerti per sua natura. Forse nelle situazioni sconosciute per natura è bene entrare in gioco il gestore delle eccezioni. Le buone pratiche di programmazione sono oggetto di dibattito.