Introducción: El eterno debate en el mundo de Go
\n\nGo es un lenguaje que rompió los moldes. No perdona la complejidad excesiva, pero al mismo tiempo le da al desarrollador una libertad increíble. Todo el que empieza a escribir en Go (o migra desde Python o Java), tarde o temprano se topa con una pared: «¿Y cómo se hace correctamente? ¿Escribir como en Java con clases y herencia o como en Haskell con funciones sin efectos secundarios?»
\n\nSpoiler: Go no es un lenguaje puramente orientado a objetos ni puramente funcional. Es pragmático. Pero esto no significa que no puedas (y debas) usar ambos paradigmas. En este artículo analizaremos cómo combinar el enfoque funcional y el orientado a objetos en Go para que tu código sea legible, comprobable y rápido. No daremos lecciones, sino que veremos ejemplos reales y antipatrones.
\n\nSi buscas las mejores prácticas de Go y quieres entender cómo escribir código limpio en este lenguaje, estás en el lugar correcto.
\n\n1. Enfoque orientado a objetos en Go: Sin clases, pero con alma
\n\nMuchos creen que como Go no tiene class, entonces no tiene POO. Es un mito. La POO en Go se basa en dos pilares: estructuras (structs) e interfaces (interfaces). No hay herencia, pero hay composición, que los creadores del lenguaje consideran más flexible.
Estructuras y métodos
\nPuedes definir métodos para cualquier tipo. Esta es la base del código orientado a objetos.
\n\npackage main\n\nimport "fmt"\n\n// Definimos una interfaz (contrato)\ntype Notifier interface {\n Send(message string) error\n}\n\n// Estructura que implementa la interfaz\ntype EmailNotifier struct {\n Email string\n}\n\n// Método para EmailNotifier\nfunc (e EmailNotifier) Send(message string) error {\n fmt.Printf("Enviando email a %s: %s\\n", e.Email, message)\n return nil\n}\n\n// Otra estructura\ntype SMSNotifier struct {\n Phone string\n}\n\nfunc (s SMSNotifier) Send(message string) error {\n fmt.Printf("Enviando SMS a %s: %s\\n", s.Phone, message)\n return nil\n}\n\n// Función que acepta cualquier Notifier (polimorfismo)\nfunc AlertAll(notifiers []Notifier, msg string) {\n for _, n := range notifiers {\n n.Send(msg)\n }\n}\n\nfunc main() {\n notifiers := []Notifier{\n EmailNotifier{Email: "user@example.com"},\n SMSNotifier{Phone: "+1234567890"},\n }\n AlertAll(notifiers, "¡El servidor se cayó!")\n}\n\nVentajas de la POO en Go:
\n- \n
- Polimorfismo a través de interfaces. Escribes una vez la función
AlertAll, y funciona con cualquier tipo que implementeNotifier. Esto es bajo acoplamiento. \n - Estado. Las estructuras son ideales para modelos de datos (User, Order, Config). \n
- Encapsulación. Los campos en minúscula son privados, en mayúscula son públicos. Simple y efectivo. \n
Desventajas:
\n- \n
- Es fácil caer en el «estilo Java»: crear jerarquías profundas, aunque Go no las soporta. \n
- La mutabilidad (cambio de estado) dificulta las pruebas y la depuración. \n
2. Programación funcional en Go: Pureza para la velocidad
\n\nEl estilo funcional en Go no es cálculo lambda, sino más bien un conjunto de principios: mínimos efectos secundarios, inmutabilidad, funciones como argumentos. Con la llegada de los genéricos (Go 1.18+), este estilo se ha vuelto más potente.
\n\nFunciones puras e inmutabilidad
\nLa regla principal de la programación funcional: la función recibe datos, devuelve nuevos datos, pero no cambia nada externamente.
\n\npackage main\n\nimport "fmt"\n\n// Mal: mutamos el slice de entrada (efecto secundario)\nfunc BadAddPrefix(users []string, prefix string) {\n for i := range users {\n users[i] = prefix + users[i]\n }\n}\n\n// Bien: devolvemos un nuevo slice (función pura)\