Perché Go ha scelto la restituzione esplicita degli errori e panic-recover al posto del lancio di eccezioni try-catch

Go deliberatamente non supporta try-catch e offre solo la sintassi panic-recover. Questo ha suscitato le lamentele di numerosi sviluppatori abituati alla gestione degli errori tramite try-catch. Perché, allora, non include try-catch? Il motivo è che try-catch presenta diversi problemi.
try-catch-finally
Il costrutto try-catch (trycatch) è un costrutto destinato a gestire le situazioni di errore e le eccezioni che possono verificarsi durante l'esecuzione del programma (runtime). Inoltre, il blocco finally contiene il codice che deve essere eseguito incondizionatamente, indipendentemente dal fatto che si sia verificata un'eccezione.
Gestione degli errori e responsabilità
Negli anni '80 e '90 la gestione degli errori era molto semplice. I messaggi di errore si limitavano a "disco floppy pieno", "nessun disco floppy nell'unità" e "nessun permesso di scrittura sul disco floppy", e gli sviluppatori di allora, quando si verificava un errore, lo propagavano (throw) fino al punto di gestione degli errori, centralizzandone il trattamento. In questo contesto, il costrutto try-catch funzionava in modo efficiente.
Tuttavia, con il passare del tempo, la situazione è cambiata. La logica di business è diventata complessa, sono comparsi database e transazioni, e attraverso la rete si chiamano numerosissime API, con la necessità di interpretare un gran numero di messaggi di richiesta; infine, con l'avvento della programmazione concorrente, è divenuto necessario gestire gli errori in thread diversi da quello principale.
Gli errori sono diventati troppo complessi per essere gestiti in un unico punto, e nessun singolo luogo può più farsi carico di tutti gli errori. È qui che il try-catch incontra un problema serio.
La delega di responsabilità del try-catch
Il try-catch è, in sintesi, un modo con cui il soggetto che genera un errore scarica la responsabilità (il trattamento successivo) dell'errore su qualcun altro. Tale soggetto può essere il blocco catch, il metodo chiamante, il chiamante del chiamante, o ancora qualcun altro più in alto nella catena... In altre parole, in un mondo in cui la gestione degli errori diventa sempre più numerosa e complessa, la strategia scelta dal try-catch è proprio "qualcuno se ne occuperà". Osserviamo il codice seguente.
1try {
2 data = readFile("hello.txt");
3 structuredData = parseData(data);
4 insertDBStatus(structuredData[1]);
5 startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7 e.printStackTrace();
8}
Il problema di questo codice è che non è chiaro quale soggetto si occupi di gestire il printStackTrace, né è possibile sapere in quale istruzione si sia verificato l'errore. Inoltre, più logica viene inserita all'interno del blocco try, più il problema peggiora. Paradossalmente, però, più lo sviluppo diventa complesso, più gli sviluppatori si sono assuefatti al try-catch, che scarica le responsabilità, abbandonando la riflessione sulla gestione degli errori, perdendo il senso di responsabilità e, alla fine, dimenticando l'essenza stessa della gestione di errori ed eccezioni. Come ha quindi risolto questo problema il golang?
panic, recover
Uno dei punti di forza di Go è che dispone di vari meccanismi volti a non spingere lo sviluppatore su una cattiva strada, ma a farne un buon sviluppatore. Anche panic-recover è un esempio in questo senso.
Panic e recover, a prima vista, non sembrano differire da try-catch, ma se ne distinguono per il fatto che, quando si verifica un problema, la responsabilità non viene trasferita all'esterno. In Go, quando si verifica un panic, il valore non viene rinviato all'esterno, ma il problema deve essere risolto nel punto in cui il panic si è verificato. Ciò attribuisce allo sviluppatore la responsabilità della gestione degli errori, inducendolo a riflettere con maggiore profondità su dove, da chi e in che modo quell'errore debba essere gestito. È come garantire autonomia allo sviluppatore lasciandogli al tempo stesso spazio per riflettere.
Inoltre, la scelta del termine panic è altrettanto eccellente: a differenza di try-recover, il solo nome panic-recover impone allo sviluppatore la pressione di usarlo esclusivamente in situazioni di errore evidente, e non in semplici situazioni eccezionali. Naturalmente, lo sviluppatore evita di abusare di recover e lo utilizza soltanto dove è necessario. Ciò è di grande aiuto nello scrivere codice conciso in Go.