Introducción: Por qué el código legacy en Go requiere un enfoque especial
Go (Golang) es famoso por su simplicidad y rendimiento, pero incluso en él, con el tiempo aparece código legacy: paquetes monolíticos, variables globales, falta de interfaces y pruebas. La refactorización de dicho código no es simplemente "reescribir de forma bonita", sino una tarea estratégica de la que dependen la estabilidad y la velocidad de desarrollo.
En este artículo, veremos técnicas avanzadas de refactorización de código legacy en Go: desde la extracción de módulos hasta la inyección segura de interfaces. Aprenderá cómo convertir "código espagueti" en una arquitectura mantenible sin detener la producción.
1. Diagnóstico y mapeo del código legacy
Antes de la refactorización, es necesario comprender la estructura actual. En Go, existen herramientas y enfoques integrados para ello:
- Análisis de dependencias: use
go mod graphygo list -jsonpara visualizar las relaciones entre paquetes. - Búsqueda de variables globales:
grep -r "var " *.go— a menudo son la raíz de los problemas. - Identificación de "olores de código": funciones largas, duplicación, falta de manejo de errores.
// Ejemplo: búsqueda de todas las variables globales en el proyectopackage main
import ( "fmt" "go/parser" "go/token" "os" "path/filepath")
func findGlobals(root string) { filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || filepath.Ext(path) != ".go" { return nil } fset := token.NewFileSet() f, _ := parser.ParseFile(fset, path, nil, parser.AllErrors) for _, decl := range f.Decls { if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.VAR { fmt.Printf("Variable global en %s: %v", path, genDecl.Specs) } } return nil })}Importante: antes de la refactorización, cree una instantánea completa de las pruebas (incluso si no las hay, escriba al menos pruebas smoke para las rutas críticas).
2. Extracción estratégica de módulos: del monolito a los paquetes
El código legacy a menudo contiene un paquete gigante main o utils. La refactorización avanzada comienza con una extracción granular:
- Extracción por responsabilidad: divida el código en capas (handler, service, repository).
- Creación de paquetes internos: use
internal/para ocultar la implementación. - Interfaces como contratos: no cree interfaces por adelantado — extráigalas de implementaciones concretas.
// Antes de la refactorización: todo en un solo archivopackage main
type User struct { ID int Name string}
func SaveUser(db *sql.DB, u User) error { _, err := db.Exec("INSERT INTO users ...", u.ID, u.Name) return err}
// Después de la refactorización: división en paquetes// internal/repository/user.gopackage repository
type User struct { ID int Name string}
type UserRepository struct { db *sql.DB}
func (r *UserRepository) Save(u User) error { _, err := r.db.Exec("INSERT INTO users ...", u.ID, u.Name) return err}Consejo: use el patrón "Strangler Fig" — reemplace gradualmente las funciones antiguas por nuevas, sin eliminar el código antiguo de inmediato.
3. Eliminación de estados globales e inyección de dependencias
Las variables globales en Go son la principal causa de inestabilidad e imposibilidad de realizar pruebas. La técnica avanzada es la inversión de control (IoC) a través de constructores e interfaces.
// Problema: logger y configuración globalesvar logger *log.Loggervar config *Config
func Init() { logger = log.New(os.Stdout, "", log.LstdFlags) config = loadConfig()}
// Solución: inyección de de