go/análisis: el framework modular de análisis estático del equipo Go, en pequeños bloques reutilizables

Dev & Código Jul 26, 2026Añadir a favoritos

go/análisis: el framework modular de análisis estático del equipo Go, en pequeños bloques reutilizables
Ilustración : Momiji Shirogane

Bajo el capó de `go vet`, `staticcheck` y compañía: el equipo de Go mantiene un *framework* oficial que permite escribir un *linter* en unas pocas decenas de líneas.

El caso concreto

Hereda un proyecto Go de 200 000 líneas. La revisión de código permite, desde hace seis meses, un patrón que provoca fugas de goroutines: un context.WithTimeout creado sin defer cancel(). Nadie lo detecta al leer el código. go vet no lo señala. Añadir una regla en el linter interno llevaría, en otro lenguaje, una pequeña semana.

En Go, se escribe un Analyzer en una tarde. Porque el equipo del lenguaje mantiene desde hace tiempo un paquete casi discreto pero fundamental: golang.org/x/tools/go/analysis.

Por qué es importante

El paquete go/analysis (« Marco de Análisis de Go ») no es nuevo: está publicado y documentado en pkg.go.dev/golang.org/x/tools/go/analysis, pero sigue apareciendo en Hacker News (última mención el 2026-07-26) porque muchos desarrolladores de Go aún lo desconocen. Sin embargo, es la infraestructura sobre la que gira el ecosistema de análisis estático del lenguaje: go vet se basa en él, staticcheck (Dominik Honnef) lo utiliza, gopls (el servidor de lenguaje oficial) conecta sus diagnósticos, y la inmensa mayoría de los linters modernos en CI —encabezados por golangci-lint— no son más que colecciones de Analyzer apilados.

La apuesta del framework es simple: un analizador = una struct. Esta struct declara su nombre, su documentación, sus dependencias hacia otros analizadores, y una función Run que recibe un objeto *analysis.Pass (el código parseado, el verificador de tipos, los archivos, los comentarios) y reporta sus diagnósticos mediante pass.Report. Eso es todo.

Cómo funciona bajo el capó

Tres ideas sostienen la arquitectura:

  1. Modularidad por composición. Un analizador puede declarar Requires: []*analysis.Analyzer{inspect.Analyzer}: el framework resuelve el grafo de dependencias e inyecta el resultado de los analizadores previos en Pass.ResultOf. Resultado: tu analizador de negocio no vuelve a parsear los archivos, consume el resultado de inspect (que, a su vez, recorre el AST tipado una sola vez para toda la cadena).

  2. Un solo « driver », múltiples arranques. Escribes tu Analyzer. singlechecker.Main(myAnalyzer) lo convierte en un binario CLI autónomo, multichecker.Main(a1, a2, a3) lo convierte en un binario multi-chequeos, unitchecker.Main(...) lo conecta en go vet -vettool=.... Sin código de pegamento: el mismo código del Analyzer funciona localmente, en CI, en el IDE vía gopls, y en golangci-lint.

  3. Corrección automática tipada. Un diagnóstico puede llevar SuggestedFix: reemplazos textuales anclados en posiciones token.Pos. gopls los aplica con un clic en el editor. Esto permite, por ejemplo, que staticcheck proponga «reemplazar errors.Is(err, sql.ErrNoRows) por una verificación más idiomática» y que el IDE realice la sustitución correctamente.

Para dar una idea de magnitud: singlechecker —la función que transforma un *Analyzer en un binario completo— cabe en un solo archivo. El esqueleto mínimo de un linter Go publicable es, a grandes rasgos, unas 40 líneas, incluyendo la documentación y las pruebas. Compárese con escribir un plugin de ESLint desde cero: no es la misma escala.

Lo que cambia en la práctica

Tres usos que se ven en proyectos reales:

  • Reglas internas de la empresa. «Prohibido importar directamente net/http, hay que pasar por nuestro wrapper de tracing.» Treinta líneas, un Analyzer que inspecciona los *ast.ImportSpec, una prueba con analysistest.Run sobre un archivo de fixture, y la regla está conectada en CI al día siguiente.
  • Migraciones masivas de código. Refactor a gran escala (renombrar una API obsoleta en 500 archivos): un Analyzer con SuggestedFix convierte la operación en go vet -vettool=./monfix -fix ./.... Sin intervención humana en el proceso.
  • Análisis de flujo de datos personalizado. El framework proporciona paquetes complementarios —ctrlflow (grafo de control), buildssa (representación SSA del código Go)— sobre los que se pueden conectar análisis de flujo de datos no triviales. Detectar fugas de sql.Rows no cerradas, por ejemplo, se hace seriamente a partir de la SSA, no del AST bruto.

Las limitaciones que debemos conocer

Dos puntos ciegos a tener en cuenta:

  • Análisis intra-package. Por defecto, un Analyzer ve un paquete a la vez. Los análisis inter-packages (whole-program) requieren trabajo adicional: es posible con los «hechos» (analysis.Fact), que permiten a un analizador exportar hechos sobre los símbolos de un paquete para que sean reutilizados cuando otro paquete lo importa, pero es un nivel más complejo de escribir.
  • Sin ecosistema de plugins dinámicos. No se «conecta» un Analyzer en go vet mediante configuración en tiempo de ejecución: se compila en un binario. Es una elección (Go no gusta de plugins dinámicos), no un olvido.

Para recordar

  • go/analysis es la API oficial para todo lo relacionado con el análisis estático en Go, desde el linter interno hasta la migración automática de código.
  • Un analizador = una struct con un Run(*Pass). El resto (CLI, integración con go vet, integración con gopls, CI) te lo dan hecho.
  • Si aún escribes tus reglas de calidad de código en shell o con sed, esa tarde está bien invertida.
Resources

Artículo producido por inteligencia artificial, revisado bajo control editorial humano.

Nuestra redacción
¿Te ha resultado útil este artículo?

3 personas han valorado este artículo

Me gusta
K
Kaito KuroganeSenior Dev Writer
Senior polyvalent developer, backend Go + frontend TS, open source contributor.
Compartir:
LIVERadio Geek Kitsune
Toca para escuchar, el mismo sonido para todos
0··
// Programación
// all stations
// compartir un tema →
Secciones
Explorar
Información