El comandament go explicat: run, build, test, mod, fmt i vet

Guia pràctica dels comandaments essencials de Go: go run, build, test, fmt, vet i mod. Tot el que necessites per treballar en local i a CI.

Cover for El comandament go explicat: run, build, test, mod, fmt i vet

Quan comences amb Go venint de Java, Python o Node, una de les primeres coses que notes és que no necessites Maven, Gradle, pytest, black ni ESLint. El binari go que s’instal·la amb el llenguatge ja inclou compilador, gestor de dependències, runner de tests, formatador, linter i generador de codi. Tot en un sol comandament.

Això no és un accident. És una decisió de disseny deliberada. L’equip de Go va apostar des del principi per eines estàndard integrades al propi toolchain. Menys configuració, menys debats sobre quina eina fer servir, menys fricció en incorporar algú nou al projecte. Si saps Go, ja saps com compilar, testejar i formatar qualsevol projecte Go.

Aquest article cobreix els subcomandaments que faràs servir cada dia: go run, go build, go test, go fmt, go vet, go mod, go generate, go install i go env. Amb exemples pràctics, flags útils i com combinar-los en un pipeline de CI.


go run: compilar i executar en un pas

go run compila i executa un programa Go sense generar un binari persistent. És l’equivalent a python script.py o node app.js: útil per al desenvolupament ràpid i scripts.

go run main.go

Si el teu main importa altres fitxers del mateix paquet, pots passar diversos fitxers o fer servir el patró de directori:

go run .
go run ./cmd/server

Passar arguments al programa

Els arguments després del fitxer o paquet es passen directament al programa:

go run main.go --port 8080 --env production

Flags útils de go run

# Mostrar els comandaments que executa internament
go run -x main.go

# Compilar sense optimitzacions (útil per a debugging amb Delve)
go run -gcflags=\"-N -l\" main.go

Quan NO fer servir go run

go run compila cada vegada que l’executes. No hi ha caché implícita del binari resultant. Per a un servidor que reinicies 50 vegades al dia, és acceptable. Per a un binari que distribuiràs o desplegues, necessites go build.

go run és per al desenvolupament local. Mai el facis servir en producció ni en un Dockerfile final.


go build: crear binaris

go build compila el teu codi i genera un binari executable. Si no especifiques un nom de sortida, fa servir el nom del mòdul o del directori.

go build -o server ./cmd/server

El binari resultant és estàtic per defecte (en la majoria de casos): no necessita runtime, no requereix que Go estigui instal·lat a la màquina de destí. Copies el binari i funciona.

Cross-compilation

Una de les millors característiques de Go és la compilació creuada. Pots generar binaris per a qualsevol plataforma des de la teva màquina:

# Linux AMD64 (l'opció habitual per a servidors i contenidors Docker)
GOOS=linux GOARCH=amd64 go build -o server-linux ./cmd/server

# macOS ARM (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o server-mac ./cmd/server

# Windows
GOOS=windows GOARCH=amd64 go build -o server.exe ./cmd/server

No necessites una màquina virtual, no necessites Docker, no necessites un CI especial. Només dues variables d’entorn. Això és quelcom que a Java o Python directament no existeix de forma nativa.

Flags de build comuns

# Reduir la mida del binari eliminant informació de debug
go build -ldflags=\"-s -w\" -o server ./cmd/server

# Injectar variables en temps de compilació (versió, commit, data)
go build -ldflags=\"-X main.version=1.2.3 -X main.commit=$(git rev-parse HEAD)\" -o server ./cmd/server

# Build completament estàtic (sense dependències CGO)
CGO_ENABLED=0 go build -o server ./cmd/server

# Veure quins comandaments executa internament
go build -x -o server ./cmd/server

Injectar versió en temps de compilació

Un patró molt comú és definir variables al teu main.go i omplir-les amb -ldflags:

package main

import \"fmt\"

var (
    version = \"dev\"
    commit  = \"none\"
    date    = \"unknown\"
)

func main() {
    fmt.Printf(\"server %s (commit: %s, built: %s)\n\", version, commit, date)
}
go build -ldflags=\"-X main.version=1.2.3 -X main.commit=$(git rev-parse --short HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)\" -o server .

Això et dóna un binari que sap exactament quina versió és i quan es va compilar. Molt útil per a logs i per a diagnòstic en producció.


go test: tests, cobertura i benchmarks

go test és el runner de tests integrat. Llegeix fitxers *_test.go, executa funcions que comencen per Test, Benchmark o Example, i reporta resultats. Sense JUnit, sense pytest, sense configuració.

# Executar tests del paquet actual
go test

# Executar tests de tot el projecte
go test ./...

# Amb output detallat
go test -v ./...

# Executar només tests que coincideixen amb un patró
go test -run TestCreateUser ./internal/user/

Si vols aprofundir en testing, tinc un article dedicat a testing en Go amb més detall sobre table-driven tests, mocks i organització.

Cobertura

# Veure percentatge de cobertura
go test -cover ./...

# Generar fitxer de cobertura per a inspecció detallada
go test -coverprofile=coverage.out ./...

# Veure cobertura línia per línia al navegador
go tool cover -html=coverage.out

# Veure cobertura per funció
go tool cover -func=coverage.out

L’output de -cover et dóna un percentatge per paquet:

ok  	github.com/user/project/internal/user	0.012s	coverage: 87.3% of statements
ok  	github.com/user/project/internal/auth	0.008s	coverage: 92.1% of statements

Benchmarks

Go té suport natiu per a benchmarks. Defineixes funcions Benchmark* als teus fitxers de test:

func BenchmarkParseConfig(b *testing.B) {
    data := loadTestConfig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        ParseConfig(data)
    }
}

I els executes amb:

# Executar benchmarks
go test -bench=. ./...

# Benchmarks amb informació de memòria
go test -bench=. -benchmem ./...

# Només benchmarks, sense tests normals
go test -bench=. -run=^$ ./...

# Executar un benchmark concret
go test -bench=BenchmarkParseConfig ./internal/config/

Output típic:

BenchmarkParseConfig-8    1000000    1052 ns/op    256 B/op    4 allocs/op

Això et diu: s’ha executat un milió de vegades, cada execució ha trigat 1052 nanosegons, ha assignat 256 bytes i ha realitzat 4 allocations. Informació concreta per optimitzar.

Race detector

Go té un detector de race conditions integrat. Activa’l amb -race:

go test -race ./...

Això instrumenta el codi per detectar accessos concurrents a memòria compartida. És més lent, així que no el facis servir en benchmarks, però hauria de ser obligatori a CI.

Flags útils de go test

# Timeout global (per defecte 10 minuts)
go test -timeout 30s ./...

# Executar tests en paral·lel (per defecte GOMAXPROCS)
go test -parallel 4 ./...

# Desactivar caché de tests (força re-execució)
go test -count=1 ./...

# Tests curts (skip de tests pesats marcats amb testing.Short())
go test -short ./...

go fmt: el final dels debats de format

go fmt formata el teu codi segons l’estil oficial de Go. No hi ha configuració, no hi ha opcions, no hi ha .editorconfig ni .prettierrc. Un format. Per a tothom.

# Formatar un fitxer
go fmt main.go

# Formatar tot el projecte
go fmt ./...

A la pràctica, la majoria de projectes fan servir gofmt (el formatador subjacent) o goimports (que a més organitza els imports):

# goimports: formata + organitza imports + afegeix imports que falten
goimports -w .

Per què això importa

A Java tens Checkstyle, SpotBugs, Google Java Format, IntelliJ formatter, cadascun amb la seva configuració. A Python tens Black, YAPF, autopep8, isort. A JavaScript tens Prettier, ESLint, Standard. Cada projecte en tria un, el configura, i sempre hi ha algú que té l’IDE configurat diferent i introdueix canvis de format als diffs.

A Go aquest problema no existeix. go fmt és l’estàndard. Punt. No hi ha debat sobre tabs vs espais (tabs), no hi ha debat sobre on va la clau d’obertura (mateixa línia), no hi ha debat sobre l’amplada màxima de línia (no hi ha límit forçat). El format és part del llenguatge.

Si el teu codi no està formatat amb go fmt, la comunitat ho considera incorrecte. Així de simple.


go vet: anàlisi estàtica integrada

go vet examina el teu codi buscant errors comuns que el compilador no detecta: arguments mal passats a fmt.Printf, variables de loop capturades en goroutines, condicions impossibles, codi inassolible i més.

# Analitzar el paquet actual
go vet

# Analitzar tot el projecte
go vet ./...

Què detecta go vet

Alguns exemples del que troba:

// Printf amb arguments incorrectes
fmt.Printf(\"user: %d\", username) // vet: wrong type for %d

// Copiar un sync.Mutex (error greu de concurrència)
var mu sync.Mutex
mu2 := mu // vet: assignment copies lock value

// Comparació impossible
if x != x { // vet: suspicious comparison
}

// Codi inassolible
func foo() int {
    return 1
    fmt.Println(\"never\") // vet: unreachable code
}

go vet no és un linter complet com golangci-lint, però cobreix els errors més perillosos i és ràpid. A CI hauria d’executar-se sempre.

go vet vs golangci-lint

go vet és un subconjunt. golangci-lint agrupa dotzenes de linters (incloent-hi vet) i permet configurar regles. Per a un projecte seriós, fes servir ambdós:

# A CI: primer el ràpid i estàndard
go vet ./...

# Després l'anàlisi completa
golangci-lint run

go mod: gestió de dependències

go mod és el sistema de mòduls de Go. Gestiona les dependències del teu projecte a través del fitxer go.mod. Si vens d’altres llenguatges: go.mod és el teu pom.xml, package.json o requirements.txt.

Per a una guia completa sobre mòduls, consulta l’article de mòduls en Go. Aquí cobreixo els subcomandaments que faràs servir cada dia.

go mod init

Inicialitza un nou mòdul:

go mod init github.com/usuari/projecte

Això crea un go.mod amb el nom del mòdul i la versió de Go:

module github.com/usuari/projecte

go 1.22

go mod tidy

El comandament que més faràs servir. Analitza els teus imports, afegeix les dependències que falten al go.mod, i elimina les que ja no s’usen:

go mod tidy

Executa’l després d’afegir o treure imports. A CI, una tècnica comuna és verificar que el go.mod i go.sum estiguin actualitzats:

go mod tidy
git diff --exit-code go.mod go.sum

Si hi ha diferències, algú s’ha oblidat d’executar go mod tidy abans de fer push.

go mod download

Descarrega totes les dependències a la caché local sense compilar res:

go mod download

Útil en Dockerfiles per aprofitar la caché de capes:

FROM golang:1.22-alpine AS builder
WORKDIR /app

# Primer copiem només go.mod i go.sum
COPY go.mod go.sum ./
RUN go mod download

# Després el codi (aquesta capa s'invalida més sovint)
COPY . .
RUN go build -o server ./cmd/server

go mod vendor

Copia totes les dependències a un directori vendor/ dins del projecte:

go mod vendor

Això permet builds sense accés a internet i garanteix reproducibilitat. Per compilar usant el vendor:

go build -mod=vendor -o server ./cmd/server

go mod graph i go mod why

Per depurar dependències:

# Veure el graf complet de dependències
go mod graph

# Saber per què una dependència és al teu go.mod
go mod why github.com/lib/pq

go mod why és especialment útil quan veus una dependència a go.sum i no saps qui l’ha introduïda.


go generate: generació de codi

go generate executa comandaments definits en comentaris especials dins del teu codi Go. No és un sistema de build ni un preprocessador: és un mecanisme per executar eines que generen codi Go.

//go:generate stringer -type=Status
//go:generate mockgen -source=repository.go -destination=mock_repository.go
//go:generate protoc --go_out=. --go-grpc_out=. api.proto

Per executar tots els generadors del projecte:

go generate ./...

Casos d’ús comuns

  • Enums amb stringer: Genera mètodes String() per a tipus basats en iota.
  • Mocks amb mockgen: Genera implementacions mock d’interfícies per a tests.
  • Protocol Buffers: Genera codi Go des de fitxers .proto.
  • Embeds SQL: Eines com sqlc generen codi Go type-safe des de queries SQL.

Bones pràctiques amb go generate

  1. Fes commit del codi generat. Qui cloni el teu repo no hauria de necessitar tenir protoc, mockgen o stringer instal·lats per compilar.
  2. Verifica a CI que el codi generat està actualitzat:
go generate ./...
git diff --exit-code

Si hi ha diferències, algú ha modificat el codi font sense regenerar.


go install: instal·lar eines

go install compila i instal·la un binari a $GOPATH/bin (o $GOBIN si el tens definit). És la forma estàndard d’instal·lar eines escrites en Go.

# Instal·lar una eina específica amb versió
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0

# Instal·lar des del projecte actual
go install ./cmd/server

Diferència entre go install i go build

  • go build genera el binari al directori actual (o on indiquis amb -o).
  • go install genera el binari a $GOPATH/bin.

Per a eines CLI que vols tenir disponibles globalment, fes servir go install. Per al teu projecte, fes servir go build.

Gestionar versions d’eines al projecte

Un patró habitual és tenir un fitxer tools.go amb un build tag que mai es compila, només perquè go mod tidy registri les dependències d’eines:

//go:build tools

package tools

import (
    _ \"github.com/golangci/golangci-lint/cmd/golangci-lint\"
    _ \"go.uber.org/mock/mockgen\"
    _ \"golang.org/x/tools/cmd/goimports\"
)

Així les versions queden fixades a go.mod i tot l’equip fa servir les mateixes.


go env: entendre el teu entorn

go env mostra totes les variables d’entorn que afecten el toolchain de Go. És el primer comandament a executar quan alguna cosa no funciona com esperes.

# Veure totes les variables
go env

# Veure una variable concreta
go env GOPATH
go env GOROOT
go env GOOS
go env GOARCH

# Veure en format JSON
go env -json

Variables importants

VariableQuè fa
GOPATHDirectori base per a dependències i binaris instal·lats
GOROOTDirectori d’instal·lació de Go
GOBINOn s’instal·len els binaris amb go install
GOOSSistema operatiu objectiu per a compilació
GOARCHArquitectura objectiu per a compilació
GOPROXYProxy per descarregar mòduls (per defecte https://proxy.golang.org)
GONOSUMCHECKMòduls que no es verifiquen al sumdb
CGO_ENABLEDSi es permet compilar codi C (0 o 1)
GOFLAGSFlags que s’apliquen a tots els comandaments go

Modificar variables de forma persistent

# Canviar el proxy (útil en entorns corporatius)
go env -w GOPROXY=https://goproxy.io,direct

# Desactivar CGO per defecte
go env -w CGO_ENABLED=0

Aquestes configuracions es guarden a $GOPATH/env i persisteixen entre sessions.


Combinant comandaments a CI/CD

Un pipeline de CI per a un projecte Go típic té aquest aspecte:

#!/bin/bash
set -euo pipefail

echo \"=== Verificant format ===\"
gofmt -l . | tee /tmp/fmt-check
if [ -s /tmp/fmt-check ]; then
    echo \"ERROR: fitxers sense formatar\"
    exit 1
fi

echo \"=== Anàlisi estàtica ===\"
go vet ./...

echo \"=== Verificant go.mod ===\"
go mod tidy
git diff --exit-code go.mod go.sum

echo \"=== Verificant codi generat ===\"
go generate ./...
git diff --exit-code

echo \"=== Tests amb race detector ===\"
go test -race -cover -coverprofile=coverage.out ./...

echo \"=== Cobertura ===\"
go tool cover -func=coverage.out

echo \"=== Build ===\"
CGO_ENABLED=0 go build -ldflags=\"-s -w\" -o /tmp/app ./cmd/server

Exemple amb GitHub Actions

name: CI
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Verify formatting
        run: |
          if [ -n \"$(gofmt -l .)\" ]; then
            echo \"Code not formatted:\"
            gofmt -l .
            exit 1
          fi

      - name: Vet
        run: go vet ./...

      - name: Test
        run: go test -race -coverprofile=coverage.out ./...

      - name: Build
        run: CGO_ENABLED=0 go build -ldflags=\"-s -w\" -o app ./cmd/server

Fixa’t en l’ordre: format primer (és instantani i falla ràpid), després vet, després tests, després build. Sense Maven que tarda 30 segons a arrencar, sense npm install de 200 MB. Un projecte Go mitjà passa CI en menys d’un minut.


Taula comparativa: Go tooling vs altres ecosistemes

TascaGoJavaPythonNode.js
Compilargo buildmvn package / gradle buildN/A (interpretat)N/A (interpretat)
Executargo runjava -jar / mvn exec:javapython script.pynode app.js
Testsgo testJUnit + Maven/Gradlepytest / unittestJest / Vitest
Formatargo fmtgoogle-java-format / SpotlessBlack / YAPFPrettier
Lintergo vet + golangci-lintCheckstyle / SpotBugsRuff / Flake8 / PylintESLint
Dependènciesgo modMaven / Gradlepip / Poetry / uvnpm / pnpm / yarn
Coberturago test -coverJaCoCocoverage.py / pytest-covc8 / istanbul
Benchmarksgo test -benchJMHpytest-benchmarkBenchmark.js
Cross-compileGOOS=x GOARCH=y go buildGraalVM native-image (limitat)No natiuNo natiu (pkg)
Generar codigo generateAnnotation processorsNo estàndardNo estàndard

La diferència principal: a Go tot és un únic binari amb una interfície consistent. En altres ecosistemes has d’instal·lar, configurar i mantenir eines separades per a cada tasca.


El que fa diferent el tooling de Go

El go command no és només un compilador. És una declaració sobre com hauria de funcionar el tooling d’un llenguatge. Format sense configuració. Tests sense framework extern. Compilació creuada amb dues variables d’entorn. Gestió de dependències sense fitxer de lock separat (el go.sum es genera automàticament).

Hi ha coses que no cobreix: linting avançat (necessites golangci-lint), hot reload (necessites air o similar), i gestió de releases (necessites goreleaser o scripts). Però la base que ofereix el toolchain estàndard és més completa que la de qualsevol altre llenguatge que hagi fet servir.

Si estàs començant amb Go, dedica una hora a explorar go help i els subcomandaments que hem vist. Aquesta hora t’estalviarà dies de configuració d’eines que en altres ecosistemes dones per descomptades. Si vols una guia per donar els primers passos, comença per com començar amb Go.

Articles relacionats

OshyTech

Enginyeria backend i de dades orientada a sistemes escalables, automatització i IA.

Navegació

Copyright 2026 OshyTech. Tots els drets reservats