5 puntos por GN⁺ 2025-08-23 | 1 comentarios | Compartir por WhatsApp
  • Varias decisiones de diseño del lenguaje Go se tomaron de forma innecesaria o ignorando experiencia previa ya existente
  • El problema de gestión del alcance de las variables de error dificulta la legibilidad del código y la búsqueda de bugs
  • En varios aspectos, como la dualidad de nil, el uso de memoria y la portabilidad del código, aparecen diseños poco intuitivos y alejados de la realidad
  • Las limitaciones de la sentencia defer y la forma en que la biblioteca estándar maneja situaciones excepcionales dificultan garantizar la seguridad ante excepciones
  • Los problemas acumulados, como la gestión de memoria y el deficiente manejo de UTF-8, están afectando negativamente a largo plazo la calidad de los codebases en Go

Crítica de largo plazo al lenguaje Go

La falta de intuición en el alcance de las variables de error

  • La sintaxis de Go amplía innecesariamente el alcance de la variable de error (err), aumentando la posibilidad de errores
    • En el código de ejemplo, la variable err permanece viva durante toda la función y se reutiliza, lo que perjudica la legibilidad y mantenibilidad del código
    • Incluso desarrolladores con experiencia sufren malentendidos y pérdida de tiempo al rastrear bugs debido a estos problemas de alcance
    • La sintaxis no permite limitar correctamente esas variables a un ámbito más local

Dos formas de nil

  • En Go existe la confusión de que nil se comporta de manera distinta en tipos interface y tipos puntero
    • Como en el ejemplo de abajo, aunque se asigne nil a s (puntero) e i (interface), s==i se evalúa de manera distinta, mostrando un comportamiento inconsistente
    • Este es el tipo de problema que normalmente se quiere evitar al manejar null, y deja ver una falta de reflexión suficiente en el diseño

Límites de la portabilidad del código

  • El uso de comentarios para compilación condicional es claramente ineficiente en términos de mantenibilidad y portabilidad
    • Si alguna vez se ha creado software verdaderamente portable, es fácil ver que este enfoque es engorroso y propenso a errores
    • Se ignoró la experiencia acumulada históricamente en portabilidad de código y casos prácticos reales
    • Para más detalles, puede consultarse Go programs are not portable

La falta de claridad sobre la propiedad en append

  • La relación de propiedad entre la función append y los slices no es clara, lo que dificulta predecir el comportamiento del código
    • A través del ejemplo, resulta difícil saber de antemano qué efecto real tendrá sobre el original hacer append al slice dentro de la función foo
    • Aumentan así las “quirks” del lenguaje que hay que memorizar, lo que termina provocando errores

Diseño insuficiente de la sentencia defer

  • No ofrece un soporte claro para liberar recursos como sí lo hace el principio RAII (Resource Acquisition Is Initialization)
    • A diferencia de las sentencias estructuradas de gestión de recursos en Java y Python, en Go no queda claro qué recursos deben liberarse con defer
    • Como muestra el ejemplo con archivos, incluso hay que lidiar manualmente con problemas de double-close, y no queda claro cuál es el orden ni la forma correctos de liberar recursos

Manejo de excepciones en la biblioteca estándar

  • Go es una estructura que no soporta excepciones explícitas (exception), pero aun así siguen ocurriendo situaciones excepcionales como panic
    • En algunos casos, panic ni siquiera termina por completo el programa, sino que queda absorbido
    • Existen patrones en la biblioteca estándar (fmt.Print, servidores HTTP, etc.) que ignoran excepciones, por lo que no es posible garantizar una verdadera seguridad ante excepciones
    • Al final, escribir código seguro ante excepciones sigue siendo indispensable, pero no se pueden usar excepciones directamente

UTF-8 y las cadenas

  • Aunque se meta cualquier dato binario arbitrario en el tipo string, Go funciona sin hacer una validación especial
    • Puede ocurrir que nombres de archivo creados antes de la codificación UTF-8 simplemente se omitan silenciosamente
    • En tareas como respaldos esto puede causar pérdida de datos importantes, y refleja un enfoque simplista que no toma en cuenta situaciones reales de trabajo

Límites de la gestión de memoria

  • Es difícil tener control directo sobre el uso de RAM, y la confiabilidad del GC (garbage collection) también tiene límites
    • El uso de memoria de Go crece y eso termina convirtiéndose a largo plazo en problemas de costo y rendimiento
    • En entornos con múltiples instancias y contenedores, realmente surgen problemas de costos y escalabilidad

Conclusión: había caminos mejores

  • Aunque ya existían diseños de lenguajes que habían demostrado su eficacia, Go los ignoró en muchos aspectos
    • A diferencia de los problemas de los primeros borradores de Java, cuando Go fue lanzado ya existían enfoques mejores

Material de referencia

1 comentarios

 
GN⁺ 2025-08-23
Comentarios de Hacker News
  • He usado Go desde antes de la versión 1.0 en casi todos mis trabajos de tiempo completo. Es simple para que los miembros del equipo aprendan lo básico y, en general, funciona de forma estable. Casi nunca hay de qué preocuparse al actualizar a una versión reciente de Go, y la mayoría de las funciones útiles ya vienen incluidas. La velocidad de compilación es atractiva. La concurrencia es un poco complicada, pero si le dedicas tiempo, se vuelve buena para expresar el flujo de datos. El sistema de tipos suele ser conveniente, aunque a veces resulta verboso. En general, es una herramienta confiable. Pero sí termino identificándome con varias de las críticas mencionadas en el artículo. Está claro que en Go hay partes donde desarrolladores de la vieja escuela se aferraron demasiado a los principios y dejaron pasar comodidades prácticas. Claro, esta es solo mi impresión, y también pienso que si hubieran resuelto todas las desventajas quizá hoy sería peor. También quiero mencionar que en los últimos años se siente un ambiente más abierto a corregir rarezas. Hubo una época en la que jamás habría imaginado que agregarían genéricos o iteradores personalizados. Las críticas sobre RAM y portabilidad me parecen más bien quejas personales. Estaría bien que mejorara, pero es rarísimo que el GC cause problemas graves en la mayoría de los programas, y depurar tampoco es tan difícil. Además, Go soporta casi todas las plataformas importantes. Aun así, sigo sintiéndome incómodo con la forma en que maneja errores y nil. Extraño seguido sintaxis como Result[Ok, Err] u Optional[T]

    • Yo más bien creo que Go no fue terco con los principios, sino que se obsesionó con la conveniencia de resolver rápido los problemas inmediatos. No analizó el problema de fondo ni lo resolvió correctamente, sino que dio la impresión de abandonar el espíritu de “Not Invented Here” y armar cosas improvisadas sobre la marcha. El API del sistema de archivos de Go es un ejemplo representativo. Si se necesita una función para abrir archivos, simplemente hacen algo como func Open(name string) (*File, error) y listo. Pero ¿qué pasa si el nombre del archivo no está en UTF-8? Como durante 5 años no apareció ese problema, no le prestaron atención

    • Muchas veces siento que los principios de diseño de Go están demasiado concentrados en la meta de “hacer que el compilador sea fácil de construir y que compile rápido”. Es una estructura enfocada más en el compilador y la compilación que en la comodidad del desarrollador

    • Después de 20 años, en un trabajo nuevo empecé a usar Go en serio por primera vez como lenguaje compilado. Será cuestión de gustos, pero honestamente me llegó a generar rechazo mientras lo usaba. No hay valores por defecto para argumentos, no me gusta cómo maneja errores y no hay stack traces decentes en producción. La sintaxis orientada a objetos se ve mal porque hay que poner referencias raras en cada función. Los punteros también me pesan. Al final se siente como volver a técnicas viejas de C/C++. Es exactamente el ambiente de programación que tenía en la universidad por ahí de 1999

    • En cuanto a concurrencia, según mi experiencia Go es el único sistema donde el propio lenguaje maneja de forma natural el paralelismo en entornos con CPU multinúcleo. Gracias a la fórmula oficial de goroutine/channel estilo CSP, la lógica de concurrencia se expresa de forma intuitiva. Python da dolores de cabeza por el GIL y por bibliotecas async difíciles de entender. C, C++, Java y otros requieren bibliotecas adicionales fuera del lenguaje, así que no es fácil razonar sobre concurrencia a nivel del lenguaje. Por eso creo que go encaja perfectamente para servidores HTTP o servicios. En mi experiencia no hay una alternativa comparable

    • Desde la perspectiva del desarrollador, sentí que la ergonomía, es decir, la estandarización y la consistencia, era perfecta. Incluso entre varios codebases de microservicios no hay que preocuparse por estilos distintos, ni hace falta discutir sobre formato. Eso sí, cuando Go elige su forma estándar, parece que a veces insistió demasiado en un estilo antiguo. Los desarrolladores de hoy esperan más métodos funcionales como map/filter, pero Go solo ofrece bucles con riesgo de errores de índice. Tampoco tiene un sistema de tipos tan inteligente como TypeScript. El manejo de errores es incómodo. Entiendo la preocupación de que agregar estas funciones aumente los “usos creativos pero malos”, pero también se siente que es difícil convencer a la generación de JS de usar go

  • Llevo más de 5 años dedicado a un proyecto grande en Golang, y cuando te toca construir componentes donde hay que minimizar al máximo el uso de memoria, te topas seguido con las partes flojas de Go. El GC no limpia lo bastante rápido o la fragmentación del heap se vuelve grave (porque Go no tiene un garbage collector compactante). Por eso tratamos de evitar por completo las asignaciones, pero eso vuelve muy fácil introducir bugs. Depurar también es extremadamente difícil. Aunque saques un perfil del heap, solo te muestra los objetos que sobrevivieron; no ves la basura acumulada real ni el detalle de la fragmentación, así que terminas adivinando. Por ejemplo, una función X puede aparecer como si solo asignara 1 KB en el heap, pero si se llama repetidamente dentro de un bucle, puede generar decenas de MB de basura. Entonces preasignamos buffers estáticos y los reutilizamos, pero eso complica los problemas de ownership y deja huecos como append. A veces incluso hay que reimplementar la librería estándar. Sé que nuestro caso no es el más común, pero de verdad da la sensación de estar peleando contra el lenguaje, y eso decepciona

    • En ese caso, puede ser menos doloroso sacar la memoria fuera del heap. Claro, al ser un lenguaje con GC no es sencillo, pero antes que forzar en Go código demasiado estilo C++/Rust, mejor cambiar esa parte directamente a uno de esos lenguajes

    • Creo que elegir go para una situación así fue, en sí mismo, un error de elección de lenguaje. Mi opinión es que C/C++/Rust/Zig eran más adecuados

    • Se comenta que el nuevo garbage collector "Green Tea" podría ayudar. No está centrado solo en memoria, pero sí usa un algoritmo de marcado paralelo que maneja mejor objetos cercanos en memoria. Se puede revisar más información aquí

    • Se estaba llevando a cabo el experimento de arena, pero actualmente está suspendido. Aun así, vale la pena echarle un ojo

    • Perdón porque esto no ayuda mucho, pero viendo la situación actual creo que la elección de lenguaje fue completamente equivocada. Supongo que quizá están usando go a la fuerza por una política interna de lenguaje oficial. Es común que las grandes empresas solo aprueben producción con lenguajes ampliamente usados

  • Todavía no entiendo por qué defer en Go solo funciona a nivel de función y no de scope léxico. Me di cuenta de eso procesando archivos dentro de un bucle: cuando la lista de archivos creció, defer no cerraba los handles hasta el final de la función y eso terminó provocando un crash. Los desarrolladores de Go a mi alrededor me dijeron que envolviera el cuerpo del bucle en una función anónima. Fuera de algunos detalles menores, Go sí se siente agradable, tiene una sintaxis eficiente y además ayuda a frenar la cultura de “lucirse” innecesariamente. Reescribí a gran escala un proyecto de C# en Go y, aunque tenía solo una décima parte de las funciones, el código terminó siendo incluso más corto. En vez de forzar asignaciones de GC, orienta a usar valores predeterminados con buen rendimiento, y la generación de código integrada para tareas como serialización es cómoda. A diferencia de la sintaxis de C#, que intenta reemplazar todo con el lenguaje, en Go el ambiente parece ser: SQL se escribe como SQL, y gRPC se maneja con la especificación de protobuf

    • A veces se necesita defer a nivel de scope léxico, y a veces a nivel de función. Por ejemplo, si en un bucle quieres abrir varios archivos y mantenerlos abiertos hasta que termine la función, necesitas el scope de función. Ahora mismo es de función, pero cuando hace falta scope léxico puedes envolverlo en una func. Si solo soportara scope léxico y necesitaras scope de función, no estaría claro qué hacer

    • Tiene ventajas como evitar un nivel extra de indentación sin necesidad de una función envoltorio, que su comportamiento está relacionado con el call stack o el stack unwinding, y que desde la perspectiva del estilo goto fail de C resulta natural. Claro, cuando usas defer dentro de un bucle sí es un poco incómodo tener que envolverlo aparte en una función

    • He usado tanto lenguajes con nivel de bloque como con defer a nivel de función, y a veces me gustaría poder usar defer a nivel de función incluso dentro de condicionales

    • No creo que haya una razón especialmente profunda, y en realidad me pregunto si de verdad importa tanto

    • En C# también puedes trabajar con SQL o con especificaciones protobuf. La diferencia es que hay otras opciones disponibles

  • Go tiene muchas desventajas, pero dentro de la categoría de lenguajes para servidor, no siento que haya otro tan equilibrado como este. Es más rápido que Node o Python, y también me parece mejor su sistema de tipos. Tiene una barrera de entrada más baja que Rust, y su librería estándar y tooling son excelentes. Me gusta su sintaxis simple y que prácticamente obligue a una sola manera de hacer las cosas. El manejo de errores sí tiene problemas, pero aun así me parece mejor que en Node, donde cualquier cosa puede caer en un catch. Me pregunto si habrá otro lenguaje mejor que cumpla con todos esos criterios. No me considero fanático de Go; durante mi carrera he hecho mucho backend con Node, pero últimamente estoy probando Go de manera experimental

    • En realidad siento que todas esas ventajas también podrían aplicarse igual a Java o C#

    • Me molesta un poco que se llame “Node” a un lenguaje de programación. Node es un runtime de JavaScript, y hoy en día una parte considerable de los proyectos que corren en Node están escritos en TypeScript. O sea, aunque digas Node, no queda claro cuál es el lenguaje usado. Si tomamos TypeScript como referencia, hasta siento que su sistema de tipos es más productivo que el de Go. Se podría hacer la misma afirmación frente a Rust

    • La mayoría de los lenguajes tienen incomodidades a su manera. Go destaca en rendimiento, portabilidad y también en runtime/ecosistema. Por otro lado, tiene desventajas como punteros nil, zero value, falta de destructores y ausencia de macros (la carencia de macros en Go hace que se abuse de la generación de código para rodearlo). Hay lenguajes mejores (por ejemplo, Rust), pero en esos casos todo se vuelve mucho más complejo que en Go. Eso ocurre porque los creadores de Go pusieron la simplicidad como máxima prioridad

    • Considerando el desarrollo reciente del sistema de tipos de Python, diría que ya está muy por delante de Go. Solo comparando structural typing, Python resulta más impresionante

    • Creo que el sistema de tipos de Go es bastante insuficiente

  • Una vez extendí un static site generator hecho en Go. El código era muy claro y fácil de leer, pero por huecos del lenguaje tenía poca extensibilidad. Incluso cambios simples exigían desarmar con dificultad varias partes del código. Es complicado manejar distintos niveles de encapsulación y abstracción, y la abstracción se sacrifica en nombre de la “simplicidad”. La abstracción es la forma más importante de crear código fácil de extender, y Go elige simplicidad en lugar de extensibilidad. En general, los programas en Go se sienten como una “simplicidad sin extensibilidad”. La gente insiste en que Go simplemente es así, pero por mi experiencia eso no me convence. Al menos la “experiencia de desarrollo” no fue mala

    • Las conversaciones sobre Go siempre se sienten extrañas. Si lo criticas, la respuesta suele ser “ese lenguaje es así” y ya, como si hubiera que aceptarlo sin más. Dicen que la simplicidad es una fortaleza, pero me pregunto si de verdad es más simple tener que escribir tú mismo un bucle solo para sacar la lista de claves de un map

    • Me gustaría preguntar si de verdad se puede lanzar una crítica así tan fácilmente después de haber usado Go solo un rato. Yo he trabajado desde 2015 con muchos codebases grandes de Go, de millones de líneas, y en varios equipos. La extensibilidad de Go no es especialmente inferior a la de C, C# o Java. Go tiende a preferir la claridad por encima de la expresividad. Por eso terminas usando menos capas de abstracción y escribiendo de forma más concreta y explícita. Pero no creo que eso implique que sea imposible de extender. El diseño modular y extensible no es algo que dé el lenguaje, sino algo que el desarrollador aprende a hacer. El código que tocaste simplemente estaba mal diseñado; no era un límite del lenguaje Go

  • He usado Go durante años, y aunque con cosas pequeñas puedes construir rápido, a medida que escala terminas sufriendo muchísimas pequeñas incomodidades. Depurar es especialmente una pesadilla: si hay una X sin usar (algo que pasa siempre cuando comentas temporalmente una parte mientras depuras), ni siquiera compila. También es molesto todo el formalismo innecesario, los nombres de archivo especiales y los nombres de campos reservados. Los panic escondidos en la librería estándar y las copias inesperadas al heap también son lentas y fastidiosas. Casi toda la parte “mágica” de Go nace de reutilizar a la fuerza mecanismos ya existentes, como nombres de archivo especiales o mayúsculas/minúsculas. Si de verdad querían expresar algo como “public”, podrían haber escrito pub, pero de forma extraña se mantuvieron tercos. Ahora que la IA ha mejorado tanto, cuando en Rust tengo un problema de tipos o del borrow checker, le pregunto a la IA y lo resuelvo rápido, así que termina siendo mucho más agradable. Ya no hace falta perder tiempo buscando en documentación o en SO como antes

    • No he entrado de lleno en Rust recientemente, pero cuando lo probé un poco en diciembre me sorprendió lo bien que la IA responde sobre Rust. Como tiene mucha sintaxis detallada e información de tipos explícita, la IA a veces lo resuelve mejor que una persona

    • Cuando te quejas en Go de que en depuración te topas con errores de compilación, en el mundo Go te regañan diciendo que “uses bien las herramientas”. Aplican los principios con un extremismo que incomoda

    • Una vez le mencioné esta incomodidad de depuración a uno de los creadores de Go, y ni siquiera entendió el problema. Me decepcionó porque me pareció demasiado amateur. Por cierto, la IA en realidad maneja peor Go. Aunque sea un lenguaje relativamente simple, ChatGPT da mejor soporte para Java, C# y Python

  • Personalmente no me gusta Go y le veo muchas desventajas decisivas, pero está claro por qué sigue siendo tan popular. Go es relativamente rápido y, gracias al modelo basado en goroutines, permite escribir servicios de alta concurrencia estables y confiables con facilidad sin recurrir a multithreading tradicional. Cuando Google lanzó Go, casi no existían lenguajes similares que fueran populares, estáticos y compilados. Incluso ahora, el único competidor en una posición parecida es Java (que ahora ya soporta virtual threads). Los lenguajes con async/await prometen algo parecido, pero en la práctica traen mucha complejidad (evitar bloqueos en tareas asíncronas, function coloring, etc.). Erlang también es otra categoría. Al final, pese a tantas desventajas, sigue siendo popular por las goroutines y por el peso del nombre de Google

    • Poco a poco la JVM está cerrando la brecha con Go. Va mejorando con proyectos como virtual threads, zgc, lilliput, Leyden y Valhalla. El cambio de Java 8 a 25 ha sido enorme. Parece que en adelante será aún más conveniente

    • La explicitud y simplicidad de Go son muy adecuadas para la programación asistida por LLM. Incluso código viejo de Go 1.x sigue funcionando tal cual en versiones actuales

    • De hecho, dentro de Google usan Java con virtual threads mucho más a menudo que Go

    • Me da curiosidad cuál consideran que sería el “lenguaje moderno” más adecuado para proyectos nuevos

  • Me gustó Go desde antes del lanzamiento 1.0, pero no comparto eso de que “todavía no lo lograron”. Claro que hay desventajas y quejas, pero también pienso que cuando los fundadores abandonan un proyecto se vuelve difícil mantener una visión central y existe el riesgo de que el lenguaje empeore. También creo que haberlo posicionado solo como “lenguaje de servidor” terminará empujando a la gente hacia Rust, Python y otros. Hubo una época en que también se burlaban de Visual Basic, pero al final quienes lo necesitaban igual lo usaban bien

  • Cuando se examinan con cuidado los textos críticos sobre las desventajas de Go, en realidad muchas veces no tratan temas tan graves. Casi siempre son técnicamente correctos, pero menores. En cambio, los problemas de diseño de lenguaje realmente serios serían zero value, la falta de soporte para constructores, el mal manejo de null, la mutabilidad por defecto, un sistema de tipos que no fue pensado para genéricos, int sin precisión arbitraria y slice con ownership ambiguo (issue relacionada 1, issue relacionada 2). También son desventajas la falta de sum types y la ausencia de interpolación de strings

  • Puede que esté sesgado al punto de haber escrito un libro sobre Go, pero como alguien que lo ha usado por más de 10 años, al principio de verdad me pareció muy fresco. Tiene menos boilerplate que Java, es fácil de aprender y su rendimiento es razonable. No existe el mejor lenguaje, y según el caso habrá opciones más adecuadas, pero para el trabajo típico de backend es una elección de la que no te arrepientes

    • Así como en casa me gusta usar un Dremel para DIY o carpintería, Go también es una herramienta fácil y poco intimidante. Un Dremel es accesible, así que puedes ponerte a trabajar rápido sin necesidad de sacar una gran sierra. Pero si usas solo un Dremel, el trabajo puede tardar 5 veces más y el resultado quedar tosco. Para no olvidar que, cuando el resultado importa de verdad, hay que elegir la herramienta correcta, a esto le llamo el “efecto Dremel” y lo uso como recordatorio para mí mismo