- La verbosidad del manejo de errores en Go ha estado durante mucho tiempo entre las principales quejas de los usuarios
- Se debatieron y probaron varias propuestas de mejora sintáctica (por ejemplo, check/handle, try, el operador ?, etc.), pero todas fueron rechazadas por falta de consenso suficiente en la comunidad
- Entre las consideraciones principales están el amplio impacto en código, herramientas y documentación que tendría cualquier cambio del lenguaje, junto con el principio de Go de mantener su simplicidad característica
- Debido a la claridad del enfoque actual, su facilidad para depurar y la preferencia de algunos usuarios, hay pocos argumentos convincentes para introducir un cambio sintáctico
- No hay planes de cambiar la sintaxis del manejo de errores en el futuro previsible, y todas las propuestas relacionadas se cerrarán sin más investigación
Planteamiento del problema de la verbosidad en el manejo de errores de Go
- Una de las quejas históricas sobre Go es que la sintaxis para manejar errores es excesivamente verbosa
- Un ejemplo representativo es el patrón
if err != nil, que aparece repetidamente en el código
- Cuantas más llamadas a APIs requiere un programa, más evidente se vuelve este patrón, al punto de que el código de manejo de errores puede terminar ocupando más espacio que la lógica principal
- En las encuestas anuales a usuarios, esta queja sigue apareciendo entre las más mencionadas
Consulta con la comunidad y propuestas iniciales
- El equipo de Go ha seguido investigando mejoras para el manejo de errores, dando gran importancia a la retroalimentación de la comunidad
- En las discusiones del proyecto Go 2 de 2018, Russ Cox resumió formalmente el núcleo del problema del manejo de errores
- Allí apareció la propuesta del mecanismo
check y handle de Marcel van Lohuizen
- También se incluyó una comparación con lenguajes similares y una revisión de distintas alternativas
- Aunque este enfoque realmente hacía el código más conciso, no se adoptó debido al aumento de complejidad
La propuesta de try y lo que vino después
- En 2019 se propuso una función integrada
try mucho más simplificada
- Llevaba a código la funcionalidad de
check, omitiendo handle
- La propuesta fue criticada por ocultar el flujo de control y terminó descartándose ante la reacción negativa de la comunidad
- Esta experiencia dejó claro el riesgo de presentar propuestas cerradas sin suficiente retroalimentación
- Confirmó la importancia de recoger opiniones más amplias desde las primeras etapas de diseño cuando se trata de cambios grandes
Intentos adicionales y propuestas diversas
- La comunidad siguió presentando de forma constante numerosas variantes y enfoques alternativos para el manejo de errores
- Ian Lance Taylor organizó la situación en un umbrella issue, y se siguieron recopilando casos en el Go Wiki y en blogs
- En 2024 surgió una propuesta para aplicar el operador
? tomado de Rust
- En pruebas de usabilidad a pequeña escala hubo comentarios de que resultaba intuitivo, pero tampoco se logró consenso entre opiniones diversas
Estancamiento de la discusión y conclusión
- Aunque hubo más de tres propuestas oficiales o semioficiales y cientos de propuestas de la comunidad, todas fueron rechazadas por falta de suficiente apoyo o consenso
- Ni siquiera el grupo interno de arquitectos de Go tiene una posición unificada sobre la dirección a seguir
- Hasta que cambie la situación o surja un consenso claro, se decidió detener por completo los intentos de modificar la sintaxis del manejo de errores
Principales argumentos a favor de mantener el enfoque actual
- Si se hubiera incorporado azúcar sintáctica desde el diseño inicial del lenguaje, quizá no habría controversia, pero hoy existe un ecosistema acostumbrado a un estilo usado durante 15 años
- Introducir nueva sintaxis inevitablemente genera preocupación por una brecha de estilo y pérdida de consistencia entre código antiguo y nuevo
- También encaja con la filosofía de diseño de Go de no hacer lo mismo de varias maneras y con su énfasis en la simplicidad y la consistencia
- Incluso la posibilidad de redeclaración con la declaración corta de variables (
:=) fue un cambio secundario derivado del manejo de errores
- La sintaxis explícita para manejar errores (mediante
if) ofrece ventajas intuitivas para leer código, depurar y establecer breakpoints
- Además, cualquier cambio en el lenguaje implica una carga considerable en términos de alcance y costo real (código, documentación, herramientas, etc.)
Mejoras alternativas y dirección futura
- Fortalecer la biblioteca estándar (por ejemplo, con la incorporación de
cmp.Or) puede ayudar a reducir parte del código repetitivo
- Con IDEs y herramientas de desarrollo como plegado de código, autocompletado y uso de LLMs, la verbosidad puede mitigarse en cierta medida en la práctica
- Entre grupos importantes de usuarios de Go (por ejemplo, asistentes a Google Cloud Next), predominan las posturas contrarias a la necesidad de cambiar el lenguaje
- A medida que aumenta el uso de Go, el problema de la verbosidad tiende a sentirse menos en la práctica
Argumentos a favor de la necesidad de mejorar la sintaxis
- Con base en la retroalimentación de usuarios, sigue existiendo demanda de mejorar la sintaxis del manejo de errores
- Una sintaxis que no solo reduzca caracteres, sino que también aumente la claridad podría contribuir a mejorar la calidad y la seguridad del código
- Hace falta investigación más precisa sobre el manejo de errores que cumple una función real, y no solo sobre la verificación simple de errores
Conclusión final y política futura
- Reconociendo que hasta ahora no ha habido consenso claro ni cambios sustanciales, se declara que en el futuro previsible se detendrán todas las discusiones y propuestas de cambios sintácticos del lenguaje para el manejo de errores
- El proceso de debate e investigación realizado hasta ahora contribuyó indirectamente a mejorar el ecosistema y los procesos de Go
- Si en el futuro surge una definición más clara del problema y un consenso más sólido, la discusión podría reabrirse
- Por ahora, la intención es priorizar mantener la solidez y simplicidad propias de Go
1 comentarios
Comentario de Hacker News
Si alguien quiere sugerir fácilmente que el equipo de Go podría haber tomado otro camino, ojalá primero revise el wiki de Go2ErrorHandlingFeedback y la búsqueda de issues en GitHub. Casi todas las ideas propuestas ya se discutieron seriamente, y como usuario que agradece el enfoque transparente del equipo de Go, me da mucho gusto usar Go todos los días
El borrador de diseño menciona C++, Rust y Swift, pero cuesta encontrar referencias a
do-notation/for-comprehensions/monadic-letde lenguajes funcionales como Haskell, Scala u OCaml, que es lo que yo buscaba. El equipo de Go parece maestro en diseño de lenguajes, pero al final se topa con las limitaciones de un tipado estático sin polimorfismo paramétrico, como Java, y por eso no logra resolver bien el problema del manejo de errores. Creo que esto viene de la arquitectura base del lenguajeAunque el documento está escrito por gente inteligente y muy experimentada, me sorprende muchísimo que no se mencione en ningún lado una solución como las mónadas
Maybe/Eitherde Haskell y el operadorbind(do-notation). En la práctica no es algo difícil ni pedante, y aun así es una manera muy elegante y probada de propagar errores con seguridad. No entiendo por qué la comunidad de Go no lo incorporó. Agradezco que esta página exista, pero es difícil entender que hayan pasado por alto una solución tan conocidaCasi todos los lenguajes ofrecen distintos enfoques mejores, así que me pregunto por qué este problema resalta tanto justo en Go. No sé si simplemente no hay consenso, o si hay alguna característica propia de Go que hace que las soluciones de otros lenguajes no encajen
Algo que se ve seguido en las críticas a Go es que personas relativamente no expertas suelen asumir que los desarrolladores de Go entienden menos de lenguajes que ellas. En realidad, en la mayoría de los casos los desarrolladores de Go tienen mucha más experiencia y saben muchísimo más. Quien no es experto tiende a pensar que un lenguaje con más características necesariamente es mejor, pero pasa por alto que lo importante es mantener un buen equilibrio general
Creo que los usuarios se benefician del conservadurismo de Go al ser tan cuidadoso con agregar nuevas funciones al lenguaje. En Swift, por ejemplo, hay tantos cambios de features que aprenderlo se vuelve difícil, y hasta en Macs recientes a veces pasa que ni un proyecto simple compila. Como las palabras clave siguen aumentando y cambiando, Swift pierde continuidad de uso; en comparación, la consistencia es una fortaleza de Go
Una vez me tocó una situación excepcional en la que una función de Go esperaba que ocurriera un error dentro de una función interna, y si la función interna no daba error, entonces la función principal debía tratarlo como error. En esa estructura poco común tuve que ramificar con
if err == nil, pero por costumbre escribíif err != nil, y como estaba demasiado habituado al patrón de siempre, tardé bastante en encontrar el fallo. Pensé que si el lenguaje ofreciera una distinción sintáctica entre el frecuenteif err != nily el raroif err == nil, se podrían evitar más errores asíif err == nil, le pongo un comentario// invertedpara resaltar el patrón. Estaría bien que el lenguaje lo manejara automáticamente, pero por ahora así al menos se distingue mejorif err == nil { return ... }podría verse todavía más raro en el código. Mucha gente prefiere el manejo de errores actual de Go porque es claro y fácil de leerif fruit != "Apple", así que en el fondo no sería un problema exclusivo del manejo de errores, sino de bifurcación de estados en general. Al final, un error también se trata como cualquier otro valor de estadoif err != nilcomo si fuera un símbolo especial, para que se mezcle naturalmente con el fondo y llame menos la atención, y hacer que soloif err == nil, que es distinto, destaque másif err … {de forma abreviadaMe gusta el manejo explícito de errores en Go. Simplemente entiendo que una función o siempre tiene éxito (
minimal error) o puede fallar. Si una función puede fallar, hay que manejarlo sí o sí antes de pasar al siguiente paso. En muchos lenguajes, con el manejo de excepciones, cuando ocurre un error se va lanzando por la pila hasta que algúncatchlo atrapa, y al final solo te dice dónde ocurrió el error sin dar pistas realmente útiles. En Go tienes opciones claras: 1) ignorar el error 2) devolver de inmediato si hay error 3) envolver el error y agregar información útil 4) interpretar un error específico y manejarlo por rama (por ejemplo, convertirlo en 404). En Go2 me gustaría probar un tipoResult<Value, Failure>o tipos de error más específicos y enumerables. Me parece más adecuado introducir eso en Go 2 para mantener la compatibilidad con Go 1Al principio no me convencía el manejo de errores de Go, pero después de leer el post del blog errors-are-values y empezar a usar
panic(err)en los lugares adecuados, terminé muy satisfecho. Para estados anómalos que el código padre no debería manejar directamente, usarpanicme permitió reducir mucho las ramas de error innecesarias en el código. Esta forma de gestionar errores me ha servido bastante en el trabajo real@, y que bash tiene técnicas de gestión como-etry/catch/finallyen C# me pareció novedoso, pero ahora prefiero una lógica simple como la de Go. Incluso una mayor cantidad de líneas de código (Loc) puede ser una ventaja si el flujo queda más claroAnte la afirmación de que la verbosidad deja de importar cuando realmente se manejan los errores, surge una respuesta juguetona preguntando si generar manualmente stack traces de verdad cuenta como “manejar” el error. Según la definición de Go, ¿entonces una excepción (
exception) también estaría siendo manejada?No me gusta que este artículo trate el problema del manejo de errores en Go solo como “la sintaxis es verbosa”. Creo que los problemas reales son más bien: 1) los errores pueden omitirse silenciosamente o ignorarse por accidente 2) no se pueden pasar o almacenar resultados de funciones tan fácilmente como valores 3) errores anidados como
errors.Isencajan de forma incómoda con el sistema de tipos 4) es difícil hacer switching sobre errores 5) hay mucho uso de valores centinela en la biblioteca estándar 6) no se lleva bien con genéricos y eso crea necesidad de paquetes auxiliaresEn Elixir (y Erlang), las funciones normalmente devuelven una tupla
{:ok, result}o{:error, description}. Gracias a la sintaxiswithde Elixir, el manejo de errores puede agruparse al final del bloque y eso mejora mucho la legibilidad. Si Go incorporara algo parecido a una sentenciawith, podría encadenar ejecuciones solo mientras el error seanily dejar un bloque manejador de errores al final, con una lectura más claraNo entiendo por qué no siguen directamente el estilo de Rust. Sobre todo ahora que ya hay genéricos, debería ser posible implementar algo parecido rápidamente. No me convence la crítica de que el operador
?de Rust fomenta ignorar errores. En la práctica, en Go hay montones de casos donde se ignora el valor de retorno de error sin que el compilador se queje. Para evitar errores de verdad, habría que forzar un retorno de tipoResultal estilo Rust. Si el problema es que por comodidad genera controversia, entonces también habría que prohibirpanic, ¿no? Es una postura bastante fuerteResultporque no tiene sum types y además su diseño peculiar exige zero values para todos los tipos?” implicaría “ya no voy a usar errores envueltos”, hay una contrarespuesta: en realidad sí puede diseñarse una comodidad de ese tipo que además fomente el wrappingCreo que un lenguaje no debe diseñarse como si fuera una lista de casillas por marcar al adoptar features al estilo Rust, sino dentro de una coherencia general. Aunque se marquen todas las casillas de la lista, eso no significa que automáticamente encaje con la esencia del lenguaje
committee), su sintaxis se volvió difícil de leer y menos consistente