Por qué Go optó por la devolución explícita de errores y por panic-recover en lugar de lanzar excepciones con try-catch

Go intencionalmente no admite try-catch, sino únicamente la sintaxis panic-recover. Esto ha generado el descontento de numerosos desarrolladores acostumbrados al manejo de errores mediante try-catch. Entonces, ¿por qué no incluir try-catch? La razón es que try-catch presenta varios problemas.
try-catch-finally
La sentencia try-catch es una construcción para manejar las situaciones de error y de excepción que pueden producirse durante la ejecución del programa (en tiempo de ejecución). Asimismo, la cláusula finally contiene el código que debe ejecutarse incondicionalmente, independientemente de si se produjo una excepción o no.
Manejo de errores y responsabilidad
En las décadas de los 80 y los 90, el manejo de errores era muy sencillo. Los mensajes de error se limitaban a «disco flexible lleno», «no hay disquete en la unidad» y «sin permiso de escritura en el disco flexible». En aquella época, cuando se producía un error, los desarrolladores lo propagaban mediante throw hasta el punto de manejo de errores, centralizándolo así. En este contexto, la sentencia try-catch funcionaba de manera eficaz.
Sin embargo, con el paso del tiempo la situación cambió. La lógica de negocio se volvió compleja, surgieron las bases de datos y las transacciones, era necesario interpretar numerosos mensajes de solicitud al invocar múltiples API a través de la red e, incluso, con la llegada de la programación concurrente, hubo que manejar errores en hilos distintos del principal.
Los errores dejaron de poder gestionarse en un único lugar y ningún punto aislado podía asumir la responsabilidad de todos ellos. Es aquí donde try-catch presenta un problema grave.
La delegación de responsabilidad en try-catch
En pocas palabras, try-catch es un mecanismo mediante el cual el componente que origina un error transfiere la responsabilidad de su tratamiento (la gestión posterior) a alguien más. Ese destinatario puede ser el bloque catch, el método padre, el padre del padre, el padre del padre del padre... o cualquier otro. Dicho de otro modo, en un mundo donde el manejo de errores se vuelve cada vez más numeroso y complejo, la estrategia que adopta try-catch es precisamente «alguien lo hará». Observemos el siguiente código.
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}
El problema de este código es que no está claro quién se encarga de procesar printStackTrace, y tampoco es posible saber en qué línea se produjo el error. Además, cuanta más lógica haya dentro del bloque try, peor se vuelve la situación. Paradójicamente, cuanto más complejo se vuelve el desarrollo, más se han acostumbrado los desarrolladores a try-catch, un mecanismo que delega responsabilidades; dejan de reflexionar sobre el tratamiento de errores, su sentido de responsabilidad se debilita y, finalmente, olvidan la esencia del manejo de errores y excepciones. Entonces, ¿cómo resolvió golang este problema?
panic, recover
Una de las ventajas de Go es que cuenta con diversos sistemas destinados a no llevar al desarrollador por malos caminos, sino a convertirlo en un buen desarrollador. panic-recover es un buen ejemplo de ello.
A primera vista, panic-recover no parece diferir de try-catch, pero se distingue en que, cuando surge un problema, no traslada la responsabilidad hacia el exterior. En Go, cuando se produce un panic, en lugar de devolver ese valor hacia fuera, el problema debe resolverse en el lugar donde se originó el panic. Esto obliga al desarrollador a asumir la responsabilidad del manejo de errores y lo induce a reflexionar con mayor profundidad sobre dónde, quién y cómo debe tratarse dicho error. Se trata de garantizar la autonomía del usuario, dejándole, al mismo tiempo, margen para pensar.
Además, la elección de la palabra panic es sumamente acertada, ya que, a diferencia de try-recover, el término panic-recover por sí solo presiona al desarrollador a emplearlo únicamente ante errores evidentes y no ante simples situaciones excepcionales. De forma natural, el desarrollador evita abusar de recover y lo utiliza solo cuando es necesario. Esto contribuye en gran medida a escribir código conciso en Go.