- La repetición de
if err != nil en Go ha sido una gran fuente de quejas en las encuestas de usuarios durante años, pero el equipo de Go decidió no impulsar por ahora cambios de sintaxis para el manejo de errores
- Las propuestas de 2018
check/handle, de 2019 try y de 2024 ? no lograron suficiente consenso, y en especial try recibió fuerte rechazo por su flujo de control oculto
- Según el proceso de propuestas de Go, si no hay un consenso general, normalmente la propuesta se rechaza, y ni siquiera entre los miembros veteranos del equipo de Go en Google hay unanimidad sobre cuál es la mejor dirección en este momento
- La postura de mantener el estado actual considera que Go ya tiene una forma de manejo de errores que funciona, y que una nueva sintaxis generaría costos importantes en estilo de código, depuración, documentación, herramientas y código existente
- El equipo de Go cerrará sin investigación adicional las propuestas abiertas y nuevas cuyo objetivo principal sea la sintaxis de manejo de errores, y se enfocará en otras oportunidades de mejora hasta que exista una comprensión más clara del problema
La vieja queja causada por if err != nil
- Uno de los reclamos más persistentes sobre Go es la verbosidad del código de manejo de errores
- El patrón representativo es el siguiente
x, err := call()
if err != nil {
// handle err
}
- En programas con muchas llamadas a APIs y donde el error simplemente se devuelve,
if err != nil puede terminar dominando el resto del código
- En la función de ejemplo
printSum, de las 10 líneas del cuerpo de la función, 4 parecen trabajo real —incluyendo llamada, impresión y retorno— y las otras 6 parecen ruido
- En la encuesta anual de usuarios de Go, el manejo de errores ha sido durante años una de las principales quejas; por un tiempo la falta de genéricos la superó, pero desde que Go añadió genéricos, el manejo de errores volvió a ocupar el primer lugar
Tres propuestas sintácticas importantes
- El primer intento explícito del equipo de Go comenzó en 2018 como parte del esfuerzo de Go 2, cuando Russ Cox resumió formalmente el problema
- El borrador de diseño de Marcel van Lohuizen se basaba en los mecanismos
check y handle, e incluía análisis de enfoques de otros lenguajes y alternativas
func printSum(a, b string) error {
handle err { return err }
x := check strconv.Atoi(a)
y := check strconv.Atoi(b)
fmt.Println("result:", x + y)
return nil
}
- El enfoque
check/handle se consideró demasiado complejo, y en 2019 apareció una propuesta más simple de try
- La palabra clave similar a
check pasó a ser la función integrada try
- La parte de
handle se eliminó
- Se creó la herramienta tryhard para convertir código existente de manejo de errores al estilo
try
- El issue de GitHub relacionado se debatió intensamente con casi 900 comentarios
func printSum(a, b string) error {
// use a defer statement to augment errors before returning
x := try(strconv.Atoi(a))
y := try(strconv.Atoi(b))
fmt.Println("result:", x + y)
return nil
}
try afectaba el flujo de control al hacer que, cuando había un error, se devolviera desde la función contenedora, y como ese retorno podía ocurrir incluso dentro de expresiones profundamente anidadas, a muchos usuarios les resultó difícil de aceptar
- En ese momento quizá habría sido mejor introducir una nueva palabra clave; hoy además se puede controlar con precisión la versión del lenguaje mediante el archivo
go.mod y directivas por archivo
- La propuesta reciente de Jimmy Frasche vuelve al diseño original de
check/handle intentando resolver algunas de sus desventajas
Reflexión sobre el proceso después de try y la propuesta de ?
- Después de la propuesta
try, Russ Cox revisó el proceso de propuestas en su serie “Thinking about the Go Proposal Process”
- “Go Proposal Process: Large Changes” evalúa que
try debió haber sido un segundo borrador de diseño y no una propuesta con calendario de implementación
- En los años siguientes, el equipo de Go no impulsó cambios sintácticos para el manejo de errores, mientras en la comunidad siguieron llegando propuestas parecidas, interesantes, difíciles de entender o inviables
- Ian Lance Taylor creó un umbrella issue para resumir el estado de las propuestas de mejora del manejo de errores, y también surgió una Go Wiki para reunir comentarios y discusión relacionados
- “go error handling proposals” de Sean K. H. Liao sigue muchas de las propuestas de manejo de errores a lo largo de varios años
- Como el descontento continuó, Ian Lance Taylor presentó en 2024 una propuesta para reducir el boilerplate del manejo de errores usando
?
- La notación estaba tomada del operador
? de Rust
- En un pequeño estudio informal con usuarios, la mayoría de los participantes dedujo correctamente el significado del código Go usando
?
- También se hicieron una herramienta para convertir código Go general a la nueva sintaxis y un prototipo del compilador
func printSum(a, b string) error {
x := strconv.Atoi(a) ?
y := strconv.Atoi(b) ?
fmt.Println("result:", x + y)
return nil
}
- Esta propuesta también se llenó rápidamente de muchos comentarios y sugerencias de ajustes basadas en preferencias, e Ian cerró la propuesta y movió el contenido a una discusión
- Una versión ligeramente ajustada recibió una reacción algo más positiva, pero no obtuvo apoyo amplio
Por qué quieren pausar esto por ahora
- El equipo de Go considera que, en el futuro previsible, debe dejar de intentar resolver el problema sintáctico del manejo de errores
- El proceso de propuestas respalda esta decisión
- El objetivo del proceso de propuestas es alcanzar a tiempo un consenso general sobre el resultado
- Si no se encuentra consenso general en la discusión del issue tracker, la propuesta normalmente se rechaza
- Si no se encuentra consenso ni un siguiente paso, los arquitectos de Go revisan la discusión e intentan lograr un acuerdo interno
- Ninguna propuesta de manejo de errores consiguió apoyo cercano al consenso, y todas fueron rechazadas
- Ni siquiera los miembros veteranos del equipo de Go en Google están unánimemente de acuerdo sobre cuál es la mejor forma de avanzar, y sin un acuerdo fuerte no es razonable seguir adelante
Los argumentos a favor del estado actual y del cambio
- Del lado de mantener el estado actual hay razones prácticas relacionadas con la madurez de Go y el costo para el ecosistema
- Si Go hubiera introducido desde el principio azúcar sintáctica específica para manejo de errores, hoy probablemente habría menos discusión, pero Go ya tiene 15 años y ya cuenta con una forma de manejo de errores que funciona
- Incluso si ahora se encontrara una solución perfecta, la situación podría simplemente cambiar para que quienes queden insatisfechos sean los partidarios del estado actual en vez de quienes impulsan el cambio
- Los genéricos no necesitan ser usados directamente por todos los usuarios, pero una nueva sintaxis de manejo de errores podría hacer que, si no se usa, el código se vea poco idiomático, por lo que en la práctica casi todos tendrían que adoptarla
- Añadir nueva sintaxis también podría chocar con la regla de diseño de Go de no ofrecer varias formas de hacer lo mismo
- La capacidad de redeclaración en la declaración corta de variables
:= se introdujo para resolver problemas surgidos por el manejo de errores
- Sin redeclaración, cada comprobación de error consecutiva habría requerido nombres
err distintos o declaraciones de variables separadas
- Si en ese momento hubiera existido un mejor soporte sintáctico para el manejo de errores, quizá no habría sido necesaria la regla de redeclaración ni su complejidad asociada
- Cuando los errores se enriquecen y manejan correctamente, la proporción de repetición simple disminuye
- En las encuestas de usuarios aparece repetidamente el comentario de que los errores no tienen stack traces
- Es posible usar funciones auxiliares para crear y devolver errores enriquecidos
- Si se agrega información de entrada, como en
fmt.Errorf("invalid integer: %q", a), el peso relativo del boilerplate se reduce
- La biblioteca estándar también puede reducir el boilerplate del manejo de errores
- Va en la línea de “Errors are values” de Rob Pike
- En algunos casos,
cmp.Or puede usarse para manejar varios errores a la vez
- Escribir, leer y depurar son actividades distintas
- Escribir comprobaciones repetitivas de errores es tedioso, pero el autocompletado asistido por IDE y LLM puede generar fácilmente el manejo de errores básico
- Al leer, la verbosidad se nota más, y los IDE podrían ofrecer un toggle para ocultar el código de manejo de errores
- Al depurar, tener un
if separado ya facilita agregar println o poner breakpoints
- Si el manejo de errores queda oculto detrás de
check, try o ?, normalmente podría ser necesario volver a expandirlo a un if, y ese proceso puede complicar la depuración o introducir bugs sutiles
- Un cambio en el lenguaje trae costos no solo de diseño e implementación, sino también de modificar código existente, actualizar documentación y ajustar herramientas
- El equipo de Go es relativamente pequeño y tiene muchas otras prioridades que atender
- Las prioridades y el tamaño del equipo pueden cambiar
- Algunos usuarios de Go con los que habló el equipo en Google Cloud Next 2025 dijeron con fuerza que el lenguaje no debería cambiarse por un mejor manejo de errores
- Dijeron que, al llegar desde otros lenguajes, la falta de sintaxis dedicada para el manejo de errores en Go es lo que más llama la atención, pero que se vuelve menos importante cuando empiezan a escribir código Go más idiomático
- Esta muestra no es lo bastante grande como para ser representativa, pero puede pertenecer a un grupo distinto al de quienes se ven en GitHub
- Los argumentos a favor del cambio siguen siendo válidos
- La falta de un mejor soporte para el manejo de errores sigue siendo la principal queja en las encuestas de usuarios
- Un enfoque centrado solo en reducir caracteres puede ser la dirección equivocada
- Si el manejo básico de errores se hace más visible con una palabra clave y a la vez se elimina el boilerplate de
err != nil, en revisión de código podría ser más fácil verificar si el manejo de errores está presente
- Aún no se sabe suficientemente si el núcleo del problema es solo la verbosidad sintáctica o la verbosidad del buen manejo de errores al construir errores significativos para APIs, desarrolladores y usuarios finales
La decisión del equipo de Go
- Hasta ahora, ningún intento de abordar el manejo de errores ha conseguido suficiente impulso
- El equipo de Go considera que falta una comprensión compartida del problema, e incluso no todos están de acuerdo en que realmente exista un problema
- En el futuro previsible no impulsará cambios sintácticos en el lenguaje para el manejo de errores
- Las propuestas abiertas y las futuras propuestas cuyo objetivo principal sea la sintaxis de manejo de errores se cerrarán sin investigación adicional
- La exploración y discusión de la comunidad no terminó en cambios sintácticos para el manejo de errores, pero sí llevó a varias mejoras del lenguaje Go y de su proceso
1 comentarios
Comentarios de Hacker News
Si el equipo de Go quiere que la gente deje comentarios ligeros del tipo “si hubieran hecho esto, ya estaría resuelto”, primero estaría bien que vieran la página wiki enlazada en el artículo https://go.dev/wiki/Go2ErrorHandlingFeedback y la búsqueda de issues en GitHub https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aerror-handling
Casi seguro que lo que están por proponer no es algo nuevo, y es muy probable que muchas de esas ideas ya se hayan revisado a fondo
Me gusta este enfoque tan honesto del equipo de Go, y todavía disfruto usar Go todos los días en el trabajo
Tampoco vi nada parecido en varias páginas de los issues de GitHub con más comentarios, y sería demasiado asumir que, como el equipo de Go son magos del diseño de lenguajes, necesariamente ya revisaron las soluciones que la gente aquí menciona a la ligera
El equipo de Go cometió el mismo error que Java, es decir, tener tipado estático sin polimorfismo paramétrico, y la raíz de este problema de manejo de errores está ahí, pero da la impresión de que no quieren reconocerlo y corregirlo
Puede sonar grandilocuente o intimidante, pero es una forma elegante y funcionalmente pura de propagar errores hasta donde puedan manejarse sin que se olviden
Para quienes escriben código en Haskell es un enfoque tan arraigado que cuesta entender que no hubiera nadie en la comunidad de Go que lo conociera y le gustara
Agradezco la página y los enlaces, pero resulta confuso que personas tan atentas a su lenguaje hayan pasado por alto una solución tan bien establecida
Casi todos los lenguajes parecen tener una manera mejor de hacerlo, así que quisiera saber si simplemente no logran decidirse o no pueden dejar a todos contentos, o si hay una razón concreta por la que las soluciones de otros lenguajes no encajan en Go
En realidad, en casi todos los casos saben muchísimo más
El amateur piensa ingenuamente que el mejor lenguaje es el que mete la mayor cantidad posible de funciones, y más todavía si incluye las que a él le gustan
Es como alguien que apenas empieza a fabricar cuchillos, ve un cuchillo de chef japonés y lo siente insuficiente, y luego piensa que sería mejor si le añadiera un mango impreso en 3D con ranuras para los dedos, un compartimento secreto, un encendedor y un altavoz Bluetooth
Solo hay que hacer una lista de verificación, discutir y completar cada punto, y luego no quitarlo otra vez salvo que se descubra un error semántico fatal o un hueco de solidez
Cuando todo esté completo, se implementa, y la gente que discutía si debía escribirse
.await,/awaito.await!()vuelve a desaparecerRust funciona así; algunos temas se retrasan más de 10 años, pero al final los puntos se completan y se estabilizan en la nightly más reciente
Si Go no puede resolver un único problema con el que todos chocan de inmediato, aun teniendo varias propuestas bastante pulidas, solo porque no logra elegir una y está esperando a que termine el bikeshedding, entonces ese proceso es una comedia
No es una colección de funciones que se agregan solo por cumplir requisitos de una lista de verificación
¿Rust se habrá convertido en un desastre como C++? ¿Go seguirá siendo un lenguaje atemporal como lo era cuando salió?
En la encuesta, el manejo de errores fue mencionado por el 13%, y también hay personas que prefieren dejarlo tal como está ahora
https://go.dev/blog/survey2024-h1-results
Alguna vez escribí una función de Go poco común donde esperaba que una función interna devolviera un error
Así que, si la función interna no devolvía un error, la función externa tenía que devolver un error y hacer otro procesamiento; y si la función interna devolvía un error, tenía que devolver
nilEn resumen, en vez de
if err != nil { ... }tenía que usarif err == nil { // return an error }, pero por costumbre escribí lo primero y me tomó bastante tiempo depurarloFue porque me volví tan insensible a
if err != nilque mi cerebro ni siquiera consideró la posibilidad de que esa construcción no debía estar ahíPor eso creo que las expresiones comunes necesitan azúcar sintáctico. Si la diferencia entre el extremadamente común
if err != nily el poco frecuenteif err == nilhubiera resaltado más, realmente habría ayudadoif err == nil, le pongo el comentario// invertedpara que destaqueEstaría bien que se manejara a nivel del lenguaje, pero al menos comparto una forma de hacerlo más visible
if fruit != "Apple" { ... }también puede crear exactamente la misma situaciónMe pregunto si existe una solución general para mejorar esto, y parece un poco errado verlo como un problema exclusivo del manejo de errores
No hay nada especial ni único en los errores; son solo un estado como cualquier otro
Si aparece un patrón común como
if err == nil { return ... }, entonces ahora eso también quedaría esparcido por todo el códigoLa solución actual está bien, y parece que a quienes más les disgusta es a quienes recién empiezan con Go o son principiantes
A mi alrededor, a la gente le gusta el manejo de errores “verboso” porque es explícito, claro y fácil de leer
if err != nilcomo si fuera un único símbolo de ligadura pequeño o renderizarlo atenuado en el fondoEntonces cualquier forma distinta de esa cadena exacta, como
if err == nil, destacaría másif err … {Me gusta el manejo explícito de errores de Go
Una función siempre tiene éxito, o puede tener éxito o fallar. Las funciones que siempre tienen éxito son simples, y si una función que puede fallar falla, el código externo no puede continuar en estado de fallo, así que debe manejarlo
Aquí es donde los lenguajes se separan. Muchos lenguajes lanzan excepciones, las van propagando hasta que alguien las atrapa explícitamente y proporcionan algún tipo de stack trace
En Go me gusta que, al escribir código, siempre hay una decisión que tomar: ignorar el error y seguir (
foo, _ := doSomething()), regresar temprano sin información útil (return nil, err), regresar temprano agregando contexto útil, o interpretar el error recibido y bifurcar a partir de esoPor ejemplo, si no se encuentra la fila a modificar en la base de datos, la capa de servicio puede devolver un error de not found y en la API eso se convierte en 404, o una función de borrado idempotente puede interpretar not found como éxito
En Go 2 o en otro lenguaje, estaría bien tener un tipo Result al estilo Rust/Swift en vez de tuplas nulables, y tipos de error mejor tipados y enumerables en vez de usar siempre
errordirectamentePero si se agregara Result encima del retorno idiomático de tuplas de Go 1, habría varias formas de hacer lo mismo y eso generaría confusión y fragmentación, así que encaja mejor en Go 2 o en un lenguaje nuevo
Las capas bajas del stack por lo general no saben qué hacer, así que no deberían manejar el error
La política de “manejar” errores suele terminar siendo una política de envolverlos y devolverlos de nuevo hacia arriba en el stack, y eso se vuelve bastante trabajo rutinario
Go permite ignorar por completo los errores, y como resultado eso puede terminar en crashes
No termino de entender cómo se puede identificar correctamente lo que se necesita para construir software robusto y aun así gustar del manejo de errores de Go
[1]: https://borgo-lang.github.io/ | https://github.com/borgo-lang/borgo
Todos los lenguajes funcionales, muchos lenguajes modernos como Rust, e incluso Java con checked exceptions ofrecen esto
En cualquier lenguaje con genéricos probablemente se puede replicar en gran medida el “manejo de errores” al estilo Go, y seguramente saldría mejor código
Si la respuesta es JavaScript o Python, bueno, ese sí es un patrón de comparación común
Para Go esta es la decisión correcta. Cuando conocí Go al principio no me gustaba el manejo de errores, pero ahora realmente me gusta
Hubo dos cosas que me hicieron cambiar. Leí https://go.dev/blog/errors-are-values y adopté de verdad la perspectiva de que “los errores son valores”, y sobre esa base incluso hice el paquete algo popular https://github.com/stytchauth/sqx
Además, me fui acostumbrando a usar un poco
panic(err)para estados realmente absurdos e inválidosNo hay por qué obligar al código padre a manejar a la fuerza estados absurdos que ni siquiera tendría criterio para resolver, y con uno o dos panic bien puestos puedes eliminar cientos de verificaciones de error en una base de código
Por ejemplo, se puede pensar en algo como si existe un logger predeterminado dentro de ctx
Más bien, es muy probable que mejorara
bash también tiene
-eAntes, cuando empecé a usar C#, pensaba que entender el flujo de try/catch/finally,
using, el anidamiento, qué pasa si hay un error en el catch y qué pasa si lo hay en el finally era algo inteligenteAhora prefiero no tener que pensar en esas cosas
No me gusta la forma en que este texto dice que el problema principal del manejo de errores en Go es que la sintaxis es demasiado verbosa. Eso no me preocupa mucho
Lo más importante es que los errores pueden descartarse silenciosamente o ignorarse por accidente, y que el resultado de una llamada de función no es un valor que se pueda guardar o pasar fácilmente; además, se necesita
errors.Is, y todo el tema de los errores “anidados” es un mecanismo raro en tiempo de ejecución que no encaja bien con el sistema de tiposTambién es difícil hacer
switchsobre errores, la biblioteca estándar usa valores centinela, y la interacción con los genéricos no es buena, así que terminan haciendo falta paquetes como errgroup¿Se me está escapando algo más?
Si fuera un lenguaje con excepciones, nadie haría eso
Han decidido no seguir intentando cambiar la sintaxis del manejo de errores en el futuro previsible, y gracias a eso ahora hay atención disponible para revisar otros problemas, ya sean de errores o de otros temas
La evolución de Go es glacial, y para mucha gente eso no es un bug sino una feature
Da risa la explicación de que “las opiniones de la encuesta que dicen que los errores no tienen stack trace se pueden resolver haciendo que una función auxiliar cree y devuelva un error enriquecido”
Es curioso llamar “manejo de errores” a proporcionar manualmente el stack trace con algo como
if err != nil { return fmt.Errorf("invalid integer: %q", a) }Según la definición del equipo de Go, las excepciones entonces manejan los errores automáticamente. Claro, en los lenguajes que no son C++
Puede ser, pero ¿de verdad hace falta todo eso? ¿Y qué hay del costo del logging?
Creo que un error envuelto de una sola línea que recorte el ruido del framework y del runtime es mucho mejor
Si se envuelve bien, además es muy fácil de buscar, y normalmente se puede rastrear con más eficacia que con un stack trace
Llevo más de 10 años usando Go full-time y nunca he necesitado el ruido verboso de las funciones del runtime o de la pila de llamadas
Desde la perspectiva de un desarrollador de Elixir, esto parece una locura
En Erlang/Elixir normalmente se resuelve haciendo que las funciones devuelvan tuplas
{:ok, result}o{:error, description_or_struct}Si además usas la sentencia
withde Elixir, puedes concentrar el manejo de errores al final y queda mucho más legibleGo podría agregar algo equivalente a una cláusula
withpara seguir encadenando funciones mientras el error sea nil, y dejar abajo una cláusula de manejo de erroreswithEs interesante cómo Go retrasa durante muchísimo tiempo estructuras básicas y obviamente valiosas como genéricos, manejo de errores y gestión de paquetes por falta de consenso en la comunidad
Los genéricos tardaron 13 años desde la publicación como open source; 16 años después todavía no hay manejo de errores; y la gestión de paquetes tardó unos 9 años
Pensarlo bien tiene valor, pero lanzar cosas también lo tiene. La gente que escribe 900 comentarios en GitHub igual va a seguir usando Go, y muy probablemente habría sido mejor que el lenguaje incorporara algo en vez de seguir posponiéndolo
Con una función que tiene varios tipos de retorno no puedes hacer mucho más que asignarla a variables
Pero en Erlang y Elixir es una forma totalmente idiomática y sin carga extra
De hecho, en cierto sentido es incluso más potente que en la familia ML, porque allí sus tipos suma son abiertos
No he seguido esta discusión en detalle, pero no entiendo por qué no adoptan simplemente el enfoque estilo Rust
Esa también es la forma que yo agregué de inmediato a Go después de que tuvo genéricos
En el artículo enlazado solo veo la explicación de que “Rust no tiene algo equivalente a handle, y la conveniencia del operador
?puede hacer que se omita un manejo adecuado”Pero no entiendo por qué que algo sea conveniente significaría ignorar errores
La mitad del problema del enfoque de Go es que no obliga a hacer nada con el resultado y solo obliga mínimamente a revisar errores
x, err := strconv.Atoi("123"); fmt.Println("result:", x)dadeclared and not used: err, pero después de una segunda conversión puede ejecutarse sin problema aunque no reviseserr, porque el valor por defecto dey, que es 0, puede ocultar el problemaIncluso si dejas vacío algo como
if err != nil { }, compila y se ejecuta, y no hay forma de saber que algo salió malSi el valor de retorno fuera un Result, se obligaría a tomar una decisión. Aunque alguien abuse de
!o simplemente propague cómodamente con?sin manejar el caso de error, entonces ¿también van a prohibirpanic?Y por esa extraña obsesión con que todos los tipos deban tener un valor cero definido, tampoco pueden agregar tipos suma
?es cómodo de usar, nadie volverá a envolver erroresEs una lógica muy dudosa
Para empezar, podrían diseñar
?de forma que fomente envolver erroresPor ejemplo, ¿qué aspecto debería tener en Go algo equivalente a
Fromde Rust??tiene poca visibilidad y esconde una bifurcación del flujo de control dentro de una sola sentencia o expresiónEsa es también una de las razones por las que Go eliminó el operador ternario y prefirió una sentencia
ifcon cada rama en una línea separadaTampoco es fácil poner puntos de interrupción, y hace más probable preferir propagar el error tal cual en vez de enriquecerlo o manejarlo
:=era una declaración y asignación de una sola sentencia, pero en la línea 5 del ejemplo, ¿no se está redeclarandoerry no queda el nuevoerrocultando alerranterior?Si es así, parecería que debería fallar con
declared and not used: err, porque la nueva variableerrno se usó¿O si la variable ya existe,
:=simplemente funciona como una asignación normal?Decir que “el hecho de que los errores no tengan rastreo de pila puede resolverse haciendo que una función auxiliar cree y devuelva un error enriquecido” es ser demasiado optimista sobre la realidad
Los lenguajes con rastreo de pila te lo dan gratis, pero en Go hay que implementarlo cada vez
Uno puede ser un desarrollador disciplinado que siempre adjunta detalles, pero no todos los miembros del equipo van a tener esa misma disciplina
Lo mejor del rastreo de pila es que te da la ruta de llamadas hasta el error
Si ocurre un error en un método llamado desde varios lugares, con el rastreo de pila puedes ver de inmediato qué ruta se tomó
Durante muchos años hice trabajo parecido a sysadmin/SRE y resolví muchos problemas; cuando había rastreo de pila, los problemas fáciles se resolvían en 1 o 2 minutos porque la causa era obvia
En Go, si alguien no enriquece el error o reutiliza el mismo mensaje de error, hasta un problema fácil se vuelve un trabajo de deducción y toma más tiempo