Las mentiras que nos decimos para seguir usando Golang (2022)
(fasterthanli.me)- Las respuestas repetidas ante las críticas a Go —“las grandes empresas también lo usan”, “también tiene cosas buenas”, “la exactitud cuesta caro”— pueden ocultar cómo los defectos de diseño del lenguaje se acumulan como costos a largo plazo
- Go tiene fortalezas en su runtime asíncrono, GC y herramientas para gestión de paquetes, refactorización y compilación cruzada, pero eso implica aceptar también su toolchain personalizada, sistema de build, convención de llamadas, GC única y hasta su ecosistema
- La ausencia de sum types, el poco control sobre la mutabilidad, el zero value y el comportamiento distinto de
nilen slices, maps y channels hacen que el compilador no pueda garantizar muchas invariantes - Sin cgo es difícil integrar Go con otros lenguajes y herramientas; con cgo aparecen las reglas de GC y punteros, la inclusión del runtime y el peso de grandes bibliotecas estáticas, por lo que el límite de red termina siendo el punto de integración más realista
- La idea de “hacer solo el prototipo en Go y reescribirlo después” puede en la práctica aumentar el costo operativo por evitar la reescritura, acoplarse al código Go existente y depender de linters, documentación y validación manual
Lógicas defensivas que se repiten ante las críticas a Go
- Desde el artículo de 2020 I want off Mr Golang’s Wild Ride, las reacciones alrededor de las críticas a Go se siguen repitiendo
- Que el caso de Windows no es un área donde Go destaque
- Que no se hablan de las cosas buenas de Go
- Que no se entienden los compromisos que Go eligió
- Que si grandes empresas usan Go, no puede ser tan malo
- Que el costo de modelar un problema “correctamente” es alto, así que discutir exactitud no tiene sentido
- Que como Rust también tiene defectos, las críticas a Go quedan invalidadas
- El punto central no es si Go tiene ventajas, sino si estas lógicas defensivas repetidas para seguir usándolo ocultan sus costos reales
La forma de atacar primero la legitimidad del crítico
- Cuando aparece un feedback que uno no quiere escuchar, si se asume que quien lo dio es incompetente o tiene motivos ocultos, ya no hace falta revisar el contenido
- Detectar un problema no siempre requiere un nivel altísimo de experiencia, y un desarrollador senior incluso puede ignorar incomodidades a las que ya se acostumbró
- Un desarrollador junior todavía no se acostumbra a lo extraño, así que puede notar antes la incomodidad y hacer preguntas
- El valor del feedback debería evaluarse no por quién lo dice, sino por si realmente hay un problema
Los límites de usar “otras empresas también lo usan” como argumento
- Ver qué usan otras empresas es útil para descubrir tecnologías, pero no alcanza como justificación para adoptarlas
- Los blogs técnicos de empresa normalmente no pueden mostrar por completo el costo de adopción
- Muchas veces se escriben después de que la decisión ya fue tomada
- También buscan mejorar la imagen de la empresa y ayudar en contratación
- Las críticas técnicas fuertes suelen venir más de personas que de compañías
- El uso de Go en Tailscale se parece más al caso de un equipo experto en Go que conoce el costo y puede asumirlo
- netaddr.IP: a new IP address type for Go muestra las limitaciones de Go al representar direcciones IPv4 o IPv6
- Hey linker, can you spare a meg? trata problemas que requieren conocimiento profundo del linker de Go
- El dolor que se ve en
netaddr.IPestá ligado a varias restricciones de Go- Go no tiene sum types, así que es incómodo expresar un tipo “IPv4 o IPv6”
- Un slice se usa como estructura genérica de datos con un costo de 24 bytes en máquinas de 64 bits
- Como no hay sobrecarga de operadores, aparece una distinción como
a == bfrente aa.equals(b) - No hay soporte para datos inmutables, así que hay que pasar copias y cuidar cambios internos
- Es difícil crear un
newtypeopaco, por lo que se necesita recurrir a paquetes separados e interfaces
- Equipos como Tailscale pueden trabajar a fondo con el funcionamiento interno y los casos límite de Go, pero no todas las organizaciones tienen expertos en Go ni el presupuesto de ingeniería de Google o Tailscale
Lo que Go realmente hace bien
- Go ofrece un runtime asíncrono bastante bueno, defaults con opiniones fuertes, un GC moderno con dos perillas de ajuste y una buena toolchain
- Como el runtime asíncrono está en el centro del lenguaje, hay herramientas que incluso podrían dar envidia a desarrolladores de Rust
- Es fácil volcar los backtraces de todas las goroutines en ejecución
- Puede detectar deadlocks
- Tiene profiler propio
- Hace menos evidente el problema del function coloring
- Las herramientas para gestión de paquetes, refactorización y compilación cruzada pueden sentirse al inicio como una mejora frente a
pkg-config, autotools y CMake del ecosistema C - Pero no se trata de elegir solo la parte conveniente del runtime de Go
- Toolchain personalizada
- Sistema de build
- Convención de llamadas
- GC única
- Componentes estándar incluidos y ecosistema
- Incluso decisiones accidentales del diseño del lenguaje vienen en el paquete
Vacíos en el sistema de tipos y en el diseño del lenguaje
- Go pone en el centro un runtime para implementar TCP, HTTP, TLS, HTTP/2, DNS y servicios web, con una forma familiar para desarrolladores de Google acostumbrados a Java/C/C++/Python
- Como en C, el manejo de errores no se trata como algo especialmente importante, y mucho estado mutable queda a cargo del desarrollador mediante
ifyelse - Como en Java, se desdibuja la diferencia entre valor y referencia, y en el sitio de llamada es difícil saber si un valor será modificado
- Según si el receiver del método es
Ao*A, la variable localademainpuede cambiar o no
- Según si el receiver del método es
- Es difícil restringir con claridad mutabilidad e inmutabilidad mediante tipos, así que al pasar referencias existe el riesgo de que se modifiquen valores internos o de que se retengan demasiado tiempo y no se liberen
- Go no previene suficientemente grupos de errores como estos
- Copiar un mutex por error y dejarlo inutilizado
- No inicializar campos de un struct y terminar con zero values
- Propagar
nil, cero o strings vacíos incorrectos hasta partes profundas del sistema
El zero value y los límites de “si tienes cuidado, no pasa nada”
- Aunque agregar un nuevo campo a un struct cambia en la práctica la forma de entrada de una función, el compilador no impide omitir ese campo nuevo en un composite literal
- En el ejemplo, se agrega
b int32aParams, pero si la llamada solo pasaa: 47, se ejecuta conb=0 - En cambio, si se agregara directamente un argumento a la función, faltaría un argumento y habría error de compilación
- En el ejemplo, se agrega
- Uno de los principios de Go es que el zero value sea útil, pero su significado real cambia según el tipo
- Si se llama
lensobre un slicenil, da 0 y se comporta como un slice vacío - Si se inserta un valor en un map
nil, ocurrepanic: assignment to entry in nil map
- Si se llama
- En los channels también hay reglas aparte
- Enviar a un channel
nilbloquea para siempre - Recibir de un channel
nilbloquea para siempre - Enviar a un channel cerrado provoca panic
- Recibir de un channel cerrado devuelve de inmediato el zero value
- Enviar a un channel
- Como Go no tiene sum types, es difícil expresar en tipos una entrada que “solo acepta una de varias opciones conocidas”, y el enfoque se acerca más al “solo ten cuidado” de C
- Decir que, como no se pueden evitar todos los problemas, entonces no vale la pena evitar ni siquiera algunos, es una falsa dicotomía que termina trasladando a la atención manual problemas donde el sistema de tipos sí podría ayudar
Comparar con Rust no es debatir un “reemplazo perfecto”
- No se parte de la idea de que Rust sea perfecto; más bien, Go y Rust pueden verse como casos de éxito distintos
- El éxito de Go depende mucho de los componentes incluidos y de defaults con opinión fuerte
- El éxito de Rust depende mucho de su adopción gradual y de la facilidad para integrarse con otros lenguajes
- En la práctica, los casos de adopción de Rust se parecen más a una migración gradual que a un reemplazo total
- Firefox incluye varios componentes clave en Rust dentro de una gran base de código en C++
- Android reimplementó el stack de Bluetooth en Rust
- Código criptográfico en Rust terminó dentro de Python, y código HTTP en Rust entró como uno de varios backends de curl
- El patch de Rust para el kernel de Linux sigue mejorando de forma iterativa
- Los usuarios de Rust también hablan abiertamente de sus tiempos de build, funciones faltantes del lenguaje y otros defectos
- El punto no es elegir obligatoriamente entre Go o Rust, sino ver qué problemas se reducen cuando se puede expresar en tipos la mutabilidad, la vida útil y la abstracción
- Incluso en Rust no siempre se modela todo de forma estricta
- Por ejemplo, salvo que se trate de una herramienta de bajo nivel como
ls, se puede usar camino para trabajar solo con paths UTF-8 - En manejo de errores, se pueden enumerar solo algunos variants importantes y dejar el resto como
Other,InternaloUnknown - Llamar a unwrap() sobre
Option<T>es una forma de afirmar explícitamente que el valor existe
- Por ejemplo, salvo que se trate de una herramienta de bajo nivel como
Go se parece más a una isla separada de otros ecosistemas
- Sin usar cgo, Go está lejos del ensamblador general, los linkers, debuggers, verificadores de memoria y convenciones de llamada habituales
- Go se parece más a un lenguaje de closed-world que C/C++, y es más hostil al FFI que Node.js, Python o Ruby
- Esta diferencia también tiene ventajas
- Se puede perfilar internamente el stack de TLS y HTTP del mismo modo que la lógica de negocio
- Contrasta con lenguajes dinámicos donde el stack trace se detiene en OpenSSL
- El runtime puede encargarse del non-blocking I/O y del scheduling
- Pero eso también dificulta reutilizar muchas herramientas existentes y conocimiento institucional, además de volver complicada la integración entre Go y otros lenguajes
- Cada forma de integración con Go tiene su costo
- Si Go llama a C, además del costo del límite FFI, hace falta rastrear manualmente descriptores para no romper el GC
- Si otro lenguaje llama a Go, hay que incluir todo el runtime de Go junto con el GC, lo que trae bibliotecas estáticas grandes y carga operativa
- El mejor límite de integración con Go tiende a ser un límite de red
- RPC sobre TCP como HTTP/1 estilo REST, JSON-RPC o gRPC suele ser menos doloroso si se puede absorber la latencia
- Aun así, hay que mantener invariantes en ambos lados, y del lado de Go el compilador no las garantiza si no se usa de forma constante un paquete de validación
La trampa de pensar en Go solo para prototipos
- Como Go es fácil de aprender y de contratar, existe la idea de construir primero un prototipo y, si luego se complica, reescribir o traer especialistas
- Pero en la práctica casi no existe el “código descartable”
- Reescribir toma tiempo
- Es difícil coordinar una transición fluida
- Se pueden perder detalles
- Se frena el desarrollo de nuevas funciones
- Hay que volver a entrenar al equipo para la nueva tecnología
- Cuantos más componentes en Go se acumulan, más razones hay para seguir eligiendo Go por la interacción con el código existente
- Los linters ayudan en parte, pero no pueden igualar a un compilador de un lenguaje que se toma en serio esos problemas desde el diseño, y además ralentizan el desarrollo
- La complejidad que no está en el lenguaje se traslada al codebase
- Las invariantes que no se expresan en tipos deben escribirse manualmente en código
- Solo con la firma de una función es difícil saber si los datos se modifican, si se retienen, si se acepta zero value, si se inicia una goroutine, si puede haber un channel
nilo qué tipos reales se pueden pasar ainterface{} - Se termina dependiendo de la documentación, y la documentación tiene tanto costo de actualización como costo de no actualizarse
Las “mentiras” que quedan al final
- Lo que uno se dice para seguir usando Go puede resumirse así
- Si otros lo usan, entonces también nos conviene a nosotros
- Quien plantea preocupaciones es un elitista o una persona grosera
- El atractivo runtime asíncrono y el GC compensan todos los demás defectos
- Cada defecto de diseño del lenguaje, tanto por separado como en conjunto, está bien
- Se puede superar con “tener cuidado”, linters y más reviewers
- Como es fácil de escribir, también es fácil desarrollar software de producción
- Como el lenguaje es simple, todo lo demás también lo es
- Solo lo usaremos un poco o al principio, y será fácil salir de ahí
- Siempre podremos reescribirlo después
- Si la herramienta elegida no elimina la complejidad, esa complejidad se traslada a otros desarrolladores, operadores o clientes
- Que un valor
nilllegue a un lugar donde no debería y termine despertando a alguien de madrugada en producción es otra repetición de Billion Dollar Mistake
2 comentarios
Aunque apenas llevo un instante, casi fugaz, metido de lleno en Go, no sé si me corresponde dejar una opinión, pero... Go tiene ventajas y desventajas muy claras, así que tanto quienes lo eligen como quienes lo evitan parecen tener razones bastante definidas. Personalmente, no creo que haya que compararlo con Rust; me parece más correcto compararlo con Kotlin (Java).
Las goroutines de Go son realmente excelentes, pero no son magia. Sobre todo en backend, en proyectos pequeños que usan solo un MySQL, esta concurrencia puede ser bastante difícil de gestionar. Cosas como el agotamiento de recursos de MySQL o la gestión del pool, que en un runtime de JS/TS no suelen requerir tanta atención, resultan más complicadas de lo que parece. Al final, en esta situación la base de datos se vuelve el cuello de botella, así que parte de la ventaja de Go en concurrencia se diluye. (El I/O asíncrono o el event loop de un runtime JS/TS podrían incluso ser más adecuados). Se nota enseguida si pruebas con una herramienta como
heyy le metes-c 100.Y aunque tiene un GC excelente, eso no significa que puedas pasarte punteros a objetos a la ligera y desentenderte de lo que pasa después. Todo tiene trade-offs, pero en Go también conviene, si es posible, pasar los objetos pequeños por copia de valor para que se procesen de inmediato al terminar la función. Tal vez estoy atrapado en una forma de pensar anticuada, pero no debería abordar los punteros con tanta facilidad desde una perspectiva de eficiencia, como haría en C/C++.
Tener que devolver
errorcasi cada vez que una función retorna, y revisar cada vez conif err != nil {}es realmente molesto, pero esto es una ventaja. Sale más barato quetry catch. Y la palabra clavedefer, que cumple un papel parecido al definally {}, también es excelente. Está bueno no tener que preocuparse por el momento exacto de liberar recursos. También me gusta que con solo la biblioteca estándar puedas armar de inmediato un muy buen servidor backend (1.23 o superior). Y, sobre todo, lo mejor es que si compilas para el OS de destino, no necesitas otro runtime ni instalaciones previas.No llevo tanto tiempo usando Go, pero siento que ya me extendí demasiado con una opinión tan personal, así que lo dejo acá jaja. ¡A mí me gusta Go, y también me gustan otros lenguajes!
Opiniones de Hacker News
Este artículo señala bien muchas desventajas de Go, pero no creo que el manejo explícito de errores sea una de ellas.
Siempre me ha disgustado el manejo de excepciones porque agrega una capa de “magia” que es demasiado fácil de romper y, aunque no sea un argumento técnicamente perfecto, por décadas de experiencia prefiero que los errores se manejen justo en el punto donde ocurren.
Se puede decir que Rust lo hace con más elegancia, y en proyectos personales prefiero Rust, pero en proyectos grandes por los que entran y salen desarrolladores de distintos niveles de experiencia, la filosofía de Go me parece bastante razonable entre los enfoques modernos de manejo de errores.
En mi región, Go se está adoptando mucho más que otros lenguajes “nuevos” gracias a su simplicidad. No es el mejor lenguaje, pero tiene mucho criterio incorporado que evita que te hagas daño, así que a menudo termina siendo la mejor opción como lenguaje de propósito general.
No hay trazas de pila y hay que envolver los errores, así que no solo hay que crear mensajes de error únicos, sino también mensajes de envoltura únicos en cada punto de llamada, para poder hacer
grepsobre los mensajes e inferir algo parecido a una traza de pila.La “tupla de retorno” es un artefacto extraño que existe solo por los errores, y ni siquiera se pueden usar tuplas en otras partes del lenguaje. Por las reglas incómodas de inicialización de variables, tarde o temprano terminas usando la variable
errequivocada.Go debería haber tenido enumeraciones, o debería haber incluido mejor azúcar sintáctica como el
?de Rust en vez de esa extraña “tupla de retorno” dedicada a los errores. La forma actual es tediosa y propensa a errores.Go tiene opiniones fuertes, pero demasiadas veces parecen opiniones destinadas a proteger al lenguaje de las críticas. Por desgracia, para que un lenguaje de programación se vuelva popular también importa el marketing, es decir, las mentiras, y eso funciona.
Las excepciones son un
gotoespantoso que cualquier biblioteca puede lanzar dentro de mi código, y casi nunca se manejan correctamente. Incluso después de que una excepción queda “manejada”, la aplicación suele quedar en un estado desconocido, imposible de razonar.Los errores de Go por lo general son fáciles de depurar incluso para ingenieros junior, pero la causa real de una excepción de Java puede ser difícil de identificar incluso para un principal experimentado.
En cambio, todavía no he visto a nadie familiarizado con tipos suma como Maybe, Option o Result que considere que el enfoque de Go sea aunque sea un poco aceptable.
Si al leer la lógica de un método concreto el 75% es ruido de errores y solo el 25% son líneas necesarias para entender el comportamiento real, resulta difícil de leer.
Cuando veo una base de código por primera vez, quiero entender primero qué intenta lograr, antes que los casos límite de error; después quiero ver cómo se garantiza que eso sea correcto.
En este aspecto, el manejo de errores de Go falla bastante. No es que el manejo explícito de errores sea malo; el problema es la insistencia en mezclar línea por línea el camino exitoso con el manejo de errores. También se puede manejar explícitamente en una sección dedicada al manejo de errores.
La parte con la que sí coincido es que te obliga a pensar en todas las posibilidades que el código puede generar. Pero esto es una cuestión más cercana a C que a otros lenguajes.
Al abstraer código, a veces no hace falta manejar el error de inmediato, o uno quiere manejarlo más abajo en la pila de llamadas.
Se sacrifica mucha legibilidad para que el lenguaje te obligue a seguir una forma concreta.
Rust y Go son muy distintos, y parece que la gente quiere un punto intermedio que hoy no existe
Es decir, un lenguaje con garbage collection, relativamente simple y que compile a binarios enlazados estáticamente, pero con un sistema de tipos parecido al de Rust y cosas como tipos resto
Sintácticamente, Gleam y Kotlin se acercan un poco, pero no del todo. Me gusta Rust, pero creo que es demasiado complejo para mucha gente que, aunque tiene la capacidad de construir cosas, no estudió computación ni trabaja como programadora
Si usas el lenguaje solo de vez en cuando, no recuerdas qué es una vtable ni cuándo y cómo se libera un valor. No existe el “lenguaje perfecto”, pero tanto Go como Rust aportaron cosas excelentes, y ojalá alguien se inspire en ambos para crear un lenguaje simple que pueda usarse ampliamente
En la práctica, Kotlin es muy complejo, y uno termina gastando tiempo intentando ser inteligente. Hay 1000 maneras de hacer las cosas, y tiene sobrecarga de operadores, proxies, properties, companion objects, excepciones y tipos de resultado
Suponiendo que usas Gradle, el sistema de build es casi una tortura para alguien acostumbrado a
go buildLa API de corrutinas se siente al mismo tiempo más compleja y más limitada que las goroutines. Es más estructurada, pero menos flexible y requiere más esfuerzo de uso
El control de acceso también es incómodo. No hay forma de hacer que un tipo sea solo del paquete: o es solo del archivo o es público para todo el módulo. Así que la superficie de API crece sin querer, o no puedes dividir la complejidad entre varios archivos
Kotlin/Native y Kotlin/JVM son, en la práctica, cosas distintas. Kotlin/JVM es maduro, pero corre sobre la JVM, así que desaparece parte de los casos de uso para los que sacarías Rust
El ecosistema de Kotlin/Native es muy débil y la documentación también es escasa. En el issue tracker hay bugs que dan miedo
Kotlin/Native ni siquiera puede distribuir bibliotecas solo con código fuente, así que necesitas un CI complejo que compile binarios para todos los sistemas operativos y arquitecturas. O terminas no distribuyendo bibliotecas, y eso también parece empeorar aún más el ecosistema débil
Igual que se agregaron genéricos, bastaría con agregar exactamente tres cosas: Result, un contenedor genérico para errores; Optional, para un mejor manejo de nil; y un poco de azúcar sintáctica como el operador Elvis
La gran ventaja que explica casi por sí sola el éxito de Go es que lo creó una compañía enorme, lo usó directamente y tenía recursos para crear buenas herramientas y bibliotecas open source. El hecho de que ya se use y tenga bibliotecas parece ser el factor decisivo más grande para el éxito de un lenguaje
La sintaxis básica es muy directa, ya tiene una adopción enorme, el ecosistema es grande, el runtime sigue mejorando y también se puede compilar a WASM
A eso se le quitan las partes raras, como demasiada flexibilidad para redefinir cosas, abuso del ámbito global y rarezas con los números, y se le agregan tipos, tipos suma/producto, Result, Maybe, decimal, etc.
Y si se incorporan un estilo donde todo sea una expresión, pattern matching, funcionalidades funcionales como currying y una biblioteca estándar completa, podría salir un lenguaje muy cercano a lo que mucha gente quiere
Otro gran avance que me gustaría ver en las herramientas de desarrollo es renovar HTML para que incluya controles avanzados de verdad y permita estilizarlos fácilmente con CSS inline. Eso reduciría la cantidad de JS necesaria en el cliente
Creo que esas dos cosas aumentarían mucho la productividad de programación en el desarrollo relacionado con la web
Con
dotnet publish -p:PublishAot=truese puede compilar a un binario nativo estático. En F# hay que usar los métodosConsole.*en lugar deprint*, porqueprinttiene reflexión no acotada internamente debido a la salida estructural del especificador de formato%A, lo que afecta negativamente el tamaño del binario y hace que el compilador se quejeEn particular, F# es recomendable como una “alternativa a Rust más fácil y orientada a negocios”. Está orientado a expresiones, y las uniones discriminadas, la inferencia de tipos HM completa y la aplicación gradual de tipos encajan muy bien. El análisis de datos y el modelado de dominios también son muy agradables
Para programación de sistemas, C# es una opción. Ofrece excelentes primitivas de concurrencia, interoperabilidad nativa rápida y a veces casi sin costo, binarios nativos más pequeños que los de Go y muchas API de bajo nivel, incluido SIMD portable
Go no encaja bien en muchas de estas tareas, o directamente no puede hacerlas sin rodeos. .NET está ganando popularidad en la comunidad no relacionada con juegos en esta área, y también están apareciendo muchas bibliotecas de alto rendimiento. Además, a diferencia de los lenguajes de nicho propuestos en el comentario hermano, se beneficia de un ecosistema existente enorme, así que no hace falta hacer todo el trabajo de base por cuenta propia
Go: “¡Soy un lenguaje simple!”
El usuario usa Go durante un tiempo
Usuario: “¡Te odio, eres un lenguaje simple!”
Tal vez porque ya tengo más de 50, me gustan los lenguajes simples
Esta “crítica” no me parece muy equilibrada, y en tecnología todo es una cuestión de compromisos; un juicio sin equilibrio me parece débil
Claro que mi conclusión es distinta: https://www.inkmi.com/blog/why-we-chose-go-over-rust-for-our...
El artículo da muchos ejemplos de inconsistencias molestas del lenguaje, y eso es lo contrario de la simplicidad
Me gustan las charlas de Rob Pike sobre Go, y algunas me abrieron los ojos. Pero ojalá el Go que veo en la práctica se pareciera mucho más al Go que sus fans describen en abstracto
En cuanto empiezas a usar Go, queda claro que, pese a las apariencias, no es simple. Tiene mucha complejidad oculta y muchas trampas. Ej.: https://archive.ph/WcyF4
Aun así es un lenguaje útil y se usa bastante, pero no es “simple”
Creo que un lenguaje simple debería tener un alto grado de consistencia. Pocas reglas, pero universales y coherentes en todas partes. Pero Go está lleno de rarezas, inconsistencias y rodeos
Un buen ejemplo es la omisión de tipos. Se puede hacer en declaraciones de arrays, slices y maps, pero no en structs. Permitirlo también en structs tendría varias ventajas de legibilidad, y además habilitaría parámetros con nombre mediante argumentos de structs anónimos, lo que podría ser un diseño mucho mejor que el rodeo feo que se usa hoy
Claro, la mayoría de las alternativas razonables a Go también son peores desde este punto de vista. Cosas como Python, Ruby y JS
Me pregunto qué motiva a alguien a escribir una diatriba tan larga contra cualquier lenguaje. Incluso algunos puntos válidos del texto no están escritos con un tono constructivo, así que lo llamaría ataque
¿Qué lenguaje no puede ser criticado?
¿Es un texto escrito para sentirse mejor culpando al lenguaje por el fracaso de ese proyecto? ¿O es que no entiende que no todos piensan y trabajan de la misma manera, y que algo que a alguien le resulta insoportablemente molesto a otra persona casi ni le llama la atención?
Los lenguajes que no satisfacen necesidades reales normalmente desaparecen solos y casi no necesitan ayuda
Go, aunque sea distinto de lenguajes “más sofisticados”, funcionó muy bien en los proyectos que me encontré. Espero que al autor no lo obliguen a trabajar con Go. El resto de la comunidad seguirá construyendo servicios, dando feedback al equipo de Go y viendo cómo el lenguaje evoluciona lentamente sin romper producción
La complejidad esencial no desaparece por cerrar los ojos
Si eliges no preocuparte por la complejidad, simplemente se la estás pasando a otros desarrolladores de la organización, al personal de operaciones, a los clientes, a alguien. Ahora ellos tienen que rodear tus supuestos para que todo funcione sin problemas
Últimamente yo suelo ser ese “alguien”, y ya estoy cansado
Que Go haya funcionado muy bien en los proyectos que encontraste es algo esperable. C, C++, PHP y JavaScript también funcionan. En nuestro mundo, muchísimas cosas “funcionan”. Agregar un carril más también funciona, y un Estado policial también funciona
Pero ¿no podría funcionar muchísimo mejor otra cosa?
Si lo escribió, probablemente fue porque el autor original sintió que sus ideas no estaban lo suficientemente representadas “en el mundo”. Yo tampoco soy fan de Go, así que en 2022 la popularidad de Go me resultaba desconcertante. Parecía que se ignoraban desventajas bastante evidentes
Ya casi nadie escribe largos posts de blog sobre que PHP es un mal lenguaje, porque eso ya se trató demasiado
Que este post de blog no esté escrito de la forma que te gustaría no significa que no tenga valor para otras personas
Todo lo que Go hacía se presentaba como esencial, lo mejor y la única forma de resolver ese problema, sin importar si era la implementación más básica o algo que otros lenguajes ya hacían mejor
Todo lo que Go no hacía se trataba como innecesario, o como una conspiración de Big Complexity para volvernos adictos al azúcar sintáctico a los usuarios no iluminados de no-Go. Cosas como un manejo adecuado de null, manejo de errores, menos boilerplate, genéricos, o soluciones consistentes que no creen casos especiales para todo
Incluso en este hilo sigue el ataque a hombres de paja. Cuando sale el manejo de errores, en vez de admitir el hecho evidente de que el manejo de errores de Go podría ser mucho mejor, sacan algo como JavaScript. Como si quien critica Go pensara que JavaScript es la cima del manejo de errores
Cada vez que trabajo con otro lenguaje, aunque Go no sea un lenguaje perfecto, siempre me dan ganas de volver
Me gusta que, literalmente, simplemente funciona. Instalás Go, bajás el código, escribís código y listo
No hace falta averiguar qué versión, runtime, configuración, herramienta de build o gestor de paquetes usar. Simplemente lo instalás y usás Go
Probablemente el único otro lenguaje que ofrece una experiencia similar sea Rust
Tal vez sea una mentira que me digo a mí mismo, pero cada vez que uso Python, TypeScript o Java, solo quiero escribir código y termino temiendo programar porque tengo que depurar o entender problemas de configuración, gestores de paquetes, herramientas de build y versiones
Cada vez que leo críticas a Go pienso lo mismo. Aun así voy a seguir usando Go
Entiendo que hay muchos problemas que en teoría podrían documentarse fácilmente y que en la práctica la gente se los encuentra seguido, pero aun así, en el trabajo real, sigo sintiendo que es uno de los mejores lenguajes de programación
Algunas de las cosas que suelen enumerarse como desventajas no las comparto; por ejemplo, me gusta la forma de manejar errores explícitamente en todas partes. Con otras partes estoy de acuerdo, pero no me molestan mucho más que las desventajas de otros lenguajes de programación
Me da algo de pena que las personas especialmente sensibles a las áreas en las que Go no tuvo éxito probablemente se pasen la vida quejándose de eso
Pero cuando elijo qué lenguaje usar para un proyecto, no aplico una gran lógica racional sobre “cuál es el mejor lenguaje”. Elijo uno que me quede bien, con el que sienta que puedo escribir buen software de forma constante y que sea agradable para trabajar
Como alguien que valora la corrección, me gustaría que ese lenguaje se pareciera más a Rust, pero por ahora no es así. Tampoco odio Rust, pero no es el lenguaje al que recurro cuando tengo prisa
Ya se escribieron suficientes artículos sobre lo terrible que es Go. Al menos ahora entiendo un poco cómo se sentía ser fan de PHP hace unos años. Es una exageración, pero en mi opinión no una exageración enorme
Vas a usar Go
Y te vas a arrepentir
Pero para entonces ya será demasiado tarde y estarás atado a él
El autor plantea un argumento bastante convincente y muy difícil de refutar. En muchos de los casos enumerados explícitamente y en muchas situaciones laborales donde una empresa técnicamente poco competente usa una tecnología inadecuada, Go es una mala elección y te vas a arrepentir de haberlo puesto en producción
Las empresas deberían ceñirse a herramientas aburridas. Si es un proyecto personal, por supuesto, hacé lo que quieras
Fuera de eso, veo la simplicidad como una fortaleza. Se siente bien de usar, es fácil de aprender y, en general, su rendimiento es bastante decente. No lo elegiría para todas las situaciones, pero hay muchas en las que es una buena herramienta
Del mismo modo, Java y Python también me gustan hasta cierto punto, y manejo ambos bastante bien. Estos lenguajes son buenas herramientas para aquello para lo que fueron pensados
No entiendo por qué la gente se obsesiona tan apasionadamente con los lenguajes de programación. Los lenguajes son herramientas. Podés preferir algunas herramientas, pero eso no invalida las que no te gustan
if err != nilme golpean la caraHubo muchas buenas propuestas para solucionar este problema, pero una parte extremadamente conservadora y ruidosa de la comunidad mató todas las propuestas con feedback casi vitriólico
Ahora el equipo de Go les tiene miedo psicológico a esas personas y abandonó la pelea por mejorar el manejo de errores
En todas las encuestas a desarrolladores de Go, la comunidad vota de forma abrumadora por mejorar el manejo de errores, pero una minoría de extremistas ha descarrilado todo avance
Go adoptó la actitud arrogante de decidir por vos qué funciones vas a necesitar y te dio muy pocas cosas con las que trabajar
Discusiones de la época:
(130 puntos, 148 comentarios) https://news.ycombinator.com/item?id=34188528
(748 puntos, 544 comentarios) https://news.ycombinator.com/item?id=31205072
Hay una parte que dice: “Empecé a preocuparme mucho más por la semántica que por la sintaxis, así que tampoco miré Zig, Nim, Odin, etc. Ya no me interesa un ‘C mejor’”
Me parece que el texto es bastante extenso. La parte de que Go es “accidental” es una tontería, porque Rust también es igual de “accidental” en cuanto a origen y diseño
Algo que me llamó la atención es que Nim de ninguna manera es un “C mejor”. Tiene recolección de basura y también se puede usar conteo de referencias. Si querés, podés usarlo como un C mejor
El sistema de tipos de Nim evita muchos de los problemas que tiene Go, aunque no es tan estricto como Rust
Al final, se escribió y desplegó mucho software en Go, corre rápido, tiene poco downtime, en general es efectivo y parece tener pocos problemas de seguridad. Creo que hay muchos menos proyectos de software desplegados en Rust, y Firefox sigue siendo más de 95% C++
No tengo intereses en esta pelea, y en el trabajo uso Python y C++ con satisfacción. Aunque haya mucho menos software escrito en Rust, aun así se puede decir que se ha escrito y desplegado mucho software en Rust. Tampoco se puede dejar de lado la inversión de las grandes empresas
0: https://x.com/m_ou_se/status/1599173341117435905
No entiendo por qué Go siempre se compara con Rust. La comparación más adecuada es Java
La razón por la que la comparación entre Go y Rust es más común, pero menos útil, probablemente sea que ambos forman parte de una nueva ola de lenguajes y que ambos, por defecto, generan binarios enlazados estáticamente
En situaciones donde Java es una buena elección, Go es una elección realmente mala. No solo el lenguaje es limitado, sino que su ecosistema también es muy limitado comparado con Java
Mantengo Go, C# y TypeScript como mis lenguajes principales, y esta combinación ofrece un alcance excelente. Si algún día tengo 6 meses en los que pueda aceptar una caída de productividad, o aparece un proyecto con requisitos de calidad bajos, agregaré Rust
Aprender el lenguaje toma 1 o 2 semanas, pero aprender a hacer ingeniería dentro del ecosistema lleva tiempo
La mayoría de las empresas donde el rendimiento no es una gran preocupación no usan C++ para servicios de red. Si Rust ya hubiera sido maduro, habría sido una buena opción, pero los tiempos de compilación, que fueron uno de los problemas centrales al inicio, podrían ser aún peores en bases de código Rust
De hecho, he visto casos donde se usa Go en vez de Rust, y la razón principal fue que Go compila mucho más rápido
Si preguntas si Rust y Go son lo suficientemente distintos como para elegirse en casos diferentes, la respuesta es sí. Justamente por eso se consideran juntos y luego se elige uno
En un momento estaba buscando un Python “mejor”. Quería un lenguaje más simple y seguro que C/C++, pero más rápido que Python y, sobre todo, capaz de generar un binario único
Por eso miré de todo, desde Rust hasta lenguajes menos conocidos como Hare
Go debería haber sido la elección obvia, pero por alguna razón que no puedo explicar no me gustó su sintaxis. Rust lo entiendo. Usa muchos caracteres especiales difíciles de recordar y de teclear en teclados que no son QWERTY, y tiene otros puntos dolorosos. En varios Lisp, el problema eran los paréntesis y la notación polaca inversa. Pero no puedo racionalizar por qué no me gusta Go
Al final decidí compilar mi código Python con Nuitka. No sé si mejora la velocidad, pero ahora puedo ofrecer un binario
También estoy mirando cada vez más C#, porque la compilación AOT está mejorando. Pero no me gusta lo verboso que es en el modo predeterminado ni que esté demasiado atado a Windows
Nim y Crystal me gustaron mucho, pero sus comunidades pequeñas fueron una barrera. Aun así, me impresiona mucho lo que Nim logra con una comunidad tan pequeña, y terminé pensando que es un lenguaje excelente
Voy a intentar motivarme para aprender alguno de los lenguajes mencionados
Hoy en día es bastante agradable de usar
También existe otro enfoque: abandonar la C-API y construir un puente hacia lenguajes como Go o Rust
Trabajé durante varios años en py2many. Me gustaría recibir feedback sobre este enfoque