
La revue de code humaine ne devrait jamais porter sur un point-virgule mal placé ou une erreur ignorée : les linters attrapent ces défauts mécaniques en une seconde. Ce guide met en place la chaîne de qualité standard de Go : le formatage avec gofmt, la détection de bugs suspects avec go vet, puis l'agrégateur golangci-lint qui regroupe des dizaines d'analyses derrière une seule commande. On termine par le pre-commit et la CI. Public visé : intermédiaire en Go. Tous les exemples sont testés avec Go 1.26 et golangci-lint 2.12.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Formater automatiquement avec
gofmtetgoimports. - Détecter les bugs que le compilateur laisse passer avec
go vet. - Agréger les linters avec
golangci-lintet sa configuration. - Intégrer le lint en pre-commit et en CI.
gofmt : le format non négociable
Section intitulée « gofmt : le format non négociable »Go tranche un débat que d'autres langages laissent ouvert : le formatage n'est pas une question de goût. L'outil gofmt, livré avec Go, impose un style unique. Tout le code Go du monde se ressemble, ce qui supprime les discussions de mise en forme en revue.
# Lister les fichiers mal formatés (sans les modifier)gofmt -l .
# Reformater en place tout le projetgofmt -w .gofmt -l renvoie la liste des fichiers non conformes : une sortie vide signifie que tout est formaté. En pratique, on utilise plutôt goimports, qui fait tout ce que fait gofmt et gère en plus les imports : il ajoute ceux qui manquent et retire ceux qui ne servent plus.
go install golang.org/x/tools/cmd/goimports@latestgoimports -w .go vet : les bugs que le compilateur laisse passer
Section intitulée « go vet : les bugs que le compilateur laisse passer »Le compilateur Go refuse le code qui ne compile pas, mais il accepte du code qui compile et se trompe. go vet, lui aussi livré avec Go, repère une famille de bugs classiques : verbes de format incohérents, boucles mal capturées, structures mal taguées.
Prenons un fichier volontairement fautif :
package main
import ( "fmt" "os")
func main() { // verbe %d avec une chaîne de caractères fmt.Printf("port = %d\n", "8080")
// l'erreur renvoyée par os.Setenv est ignorée os.Setenv("APP_ENV", "prod")
// la première affectation est écrasée sans être lue msg := "bonjour" msg = "au revoir" fmt.Println(msg)}Ce code compile sans broncher. go vet détecte pourtant l'incohérence de format :
go vet ./...main.go:10:21: fmt.Printf format %d has arg "8080" of wrong type stringgo vet attrape le %d appliqué à une string, mais il ne dit rien de l'erreur ignorée ni de l'affectation inutile. Pour ces cas, il faut aller plus loin.
golangci-lint : agréger les linters
Section intitulée « golangci-lint : agréger les linters »go vet ne couvre qu'une partie du terrain. D'autres analyseurs existent, chacun spécialisé : errcheck pour les erreurs non vérifiées, ineffassign pour les affectations mortes, staticcheck pour un large éventail de simplifications et de bugs subtils. Les lancer un par un serait fastidieux. golangci-lint les exécute tous en parallèle, dédoublonne les résultats et présente un rapport unique.
Sur le même fichier fautif, il remonte les trois problèmes d'un coup :
golangci-lint run ./...main.go:13:11: Error return value of `os.Setenv` is not checked (errcheck) os.Setenv("APP_ENV", "prod") ^main.go:10:21: printf: fmt.Printf format %d has arg "8080" of wrong type string (govet) fmt.Printf("port = %d\n", "8080") ^main.go:16:2: ineffectual assignment to msg (ineffassign) msg := "bonjour" ^3 issues:* errcheck: 1* govet: 1* ineffassign: 1Chaque ligne indique le linter responsable entre parenthèses. La commande sort en code d'erreur non nul dès qu'un problème est trouvé, ce qui la rend directement exploitable en CI.
Configurer les linters
Section intitulée « Configurer les linters »Par défaut, golangci-lint active un jeu standard raisonnable. Vous ajustez la sélection dans un fichier .golangci.yml à la racine du projet. Depuis la version 2, il commence par la clé version: "2" :
version: "2"linters: default: standard enable: - errcheck - govet - ineffassign - staticcheck - unused - misspellVérifiez que votre configuration est valide avant de la committer :
golangci-lint config verifyUne sortie vide confirme que le fichier est correct. default: standard conserve le jeu par défaut, et enable y ajoute des linters, ici misspell qui traque les fautes d'orthographe dans les commentaires et chaînes.
Lint en pre-commit et en CI
Section intitulée « Lint en pre-commit et en CI »Un linter n'a de valeur que s'il tourne systématiquement. Deux points de passage le garantissent : avant chaque commit, en local, et à chaque push, en CI.
Hook pre-commit
Section intitulée « Hook pre-commit »Un hook Git bloque le commit si le code n'est pas propre. Créez .git/hooks/pre-commit, rendez-le exécutable :
#!/bin/shset -egofmt -l . | grep . && { echo "Fichiers mal formatés, lancez gofmt -w ."; exit 1; }go vet ./...golangci-lint run ./...Le développeur reçoit le retour avant de pousser, quand la correction coûte le moins cher.
Pipeline CI
Section intitulée « Pipeline CI »En CI, faites échouer le job dès qu'un linter proteste. Voici un workflow GitHub Actions durci, qui s'appuie sur l'image conteneur officielle épinglée par version :
name: linton: [push, pull_request]
permissions: {}
jobs: lint: runs-on: ubuntu-latest container: golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - run: golangci-lint run ./...Les garde-fous à retenir : permissions: {} retire tous les droits par défaut au workflow, persist-credentials: false évite de laisser traîner le jeton Git dans le runner, l'action checkout est épinglée par SHA plutôt que par tag mouvant, et l'image conteneur est épinglée par digest @sha256 (un tag comme v2.12.2 peut être redéployé sur un autre contenu). Ce workflow passe les scanners de pipeline zizmor, poutine et plumber sans alerte. Le guide GitHub Actions détaille cette approche de durcissement.
Bonnes pratiques
Section intitulée « Bonnes pratiques »- Formatez à la sauvegarde avec
goimports: ne committez jamais un fichier mal formaté. - Lancez
go vetetgolangci-linten local avant de pousser, via un hook pre-commit. - Versionnez
.golangci.yml: toute l'équipe partage la même définition de « code propre ». - Épinglez la version de golangci-lint (image ou binaire) : un lint reproductible ne casse pas au gré des mises à jour.
- Traitez un avertissement de linter comme une erreur en CI : le job échoue, la régression n'entre pas.
À retenir
Section intitulée « À retenir »- Le formatage Go n'est pas négociable :
gofmt/goimportsimposent un style unique. go vetattrape des bugs que le compilateur accepte, mais son périmètre est limité.golangci-lintagrège de nombreux linters (errcheck, ineffassign, staticcheck) en une commande.- Un
.golangci.ymlversionné fige la définition de qualité pour toute l'équipe. - Le lint prend sa valeur en pre-commit et en CI, où il bloque toute régression.