Retour au blog

Pourquoi Go est le meilleur langage pour le code écrit par l'IA

Un petit langage, un seul style de code, un compilateur strict, des API stables et une bibliothèque standard qui couvre le backend. Pourquoi Go est le meilleur langage pour le code écrit par l'IA.

Pourquoi Go est le meilleur langage pour le code écrit par l'IA

Go est le meilleur langage pour le code écrit par l'IA, car un modèle peut l'écrire de façon fiable et vous pouvez vérifier ce qu'il a écrit. Le langage est petit, donc un modèle dispose de peu de manières d'écrire la même chose. Tous les fichiers Go sont formatés de la même façon, si bien que la sortie ressemble au code dont le modèle a appris. Le compilateur rejette les imports et les variables inutilisés. La promesse de compatibilité de Go 1 garantit que les API apprises par un modèle il y a des années fonctionnent encore aujourd'hui. Enfin, la bibliothèque standard couvre l'essentiel d'un backend, donc le modèle a rarement besoin d'un package qu'il risquerait de mal utiliser. Dès qu'un agent écrit la majeure partie du diff, ce sont ces propriétés qui déterminent le temps que vous passez en revue de code.

En bref

  • Le code écrit par l'IA échoue le plus souvent parce qu'il est presque juste. 66 % des développeurs déclarent que leur principale frustration avec les outils d'IA est un code « presque juste, mais pas tout à fait » (Stack Overflow Developer Survey, 2025). Le meilleur langage pour le code écrit par l'IA est celui où ces erreurs de peu sont les plus faciles à repérer.
  • Go garde la sortie des modèles courte et prévisible. La spécification compte 25 mots-clés (Go spec), et les auteurs de Multi-SWE-bench ont constaté que Go « présente une consommation de tokens relativement faible, en entrée comme en sortie, probablement grâce à sa syntaxe minimaliste et à ses conventions claires » (Multi-SWE-bench, 2025).
  • Le compilateur n'émet aucun avertissement. Go « refuse de compiler les programmes contenant des variables ou des imports inutilisés » et ne signale que les erreurs qui bloquent le build (Go FAQ). Le rapport Octoverse de GitHub indique que « les systèmes typés aident à repérer plus tôt dans le pipeline les erreurs de compilation générées par les LLM » (GitHub Octoverse, 2025).
  • Du code Go écrit en 2012 compile toujours. La promesse de Go 1 permet aux anciens programmes de compiler « sans modification » (Go 1 compatibility), donc les patterns présents dans les données d'entraînement d'un modèle restent valides.
  • Les agents travaillent le mieux avec une vérification qu'ils peuvent lancer eux-mêmes. Les recommandations d'Anthropic pour Claude Code : « Donnez à Claude quelque chose qui produit un succès ou un échec, et la boucle se referme d'elle-même » (Claude Code docs, 2026). Go fournit ces vérifications avec le langage : go build, go vet et go test -race.

Ce qu'un langage doit offrir au code écrit par l'IA

Un langage convient au code écrit par l'IA lorsqu'il obtient de bons résultats sur quatre points :

  1. Le modèle peut l'écrire de façon fiable. Un petit langage avec un style commun unique laisse moins de possibilités de se tromper.
  2. Des outils peuvent le vérifier sans intervention humaine. Le typage statique, des erreurs de compilation strictes et un exécuteur de tests intégré rejettent une mauvaise sortie avant que quiconque ne la lise.
  3. Une personne peut le relire rapidement. Un code sans flux de contrôle caché et sans magie permet à un relecteur de comprendre ce que fait un diff à partir du seul diff.
  4. Ce que le modèle a appris reste vrai. Les données d'entraînement datent de plusieurs mois ou années, donc le langage et ses bibliothèques doivent continuer à fonctionner comme avant.

Go a été conçu chez Google pour de grandes bases de code sur lesquelles travaillent de grandes équipes, bien avant l'existence des assistants de code. Les choix qui permettaient à un nouveau membre de l'équipe de prendre Go en main facilement permettent aussi à un modèle de l'écrire facilement, et à vous de le relire.

Un petit langage que les modèles maîtrisent

Go a moins de fonctionnalités que la plupart des langages utilisés en production, ce qui laisse à un modèle moins d'occasions d'écrire quelque chose d'astucieux et de faux.

La spécification réserve 25 mots-clés (Go spec). Il n'y a ni classes ni héritage, ni exceptions, ni surcharge d'opérateurs, ni macros, ni conversions numériques implicites. Les boucles s'écrivent avec for et rien d'autre. Le comportement vient de simples fonctions, de structs et de petites interfaces. Quand un modèle écrit un handler HTTP en Go, il n'existe qu'une poignée de façons raisonnables de le faire, et elles se ressemblent toutes. Go Keywords: All 25 Reserved Words Explained passe en revue la liste complète.

Un petit langage signifie aussi moins de texte par tâche. Les auteurs de Multi-SWE-bench ont mesuré la consommation de tokens selon les langages et ont classé Go parmi les plus faibles, en entrée comme en sortie (Multi-SWE-bench, 2025). Moins de tokens par modification, c'est une tâche moins coûteuse et plus de place dans la fenêtre de contexte pour votre propre code.

Tous les fichiers Go se ressemblent

gofmt fait partie de la toolchain Go, et il n'a aucun réglage sur lequel débattre. D'après la FAQ de Go, la grande majorité du code Go open source est passée par cet outil (Go FAQ). Le code Go dont un modèle a appris est donc d'une uniformité inhabituelle, et c'est pourquoi la sortie des modèles ressemble généralement à du Go idiomatique dès le premier essai.

Le formatage aide aussi pendant la revue. Si le modèle se trompe sur un détail de mise en page, gofmt le corrige en une commande, donc vos diffs montrent des changements de logique et jamais des débats entre tabulations et espaces ou sur la position des accolades. Le nommage suit la même logique. Les noms exportés commencent par une majuscule, l'erreur est la dernière valeur de retour, et context.Context est le premier paramètre de tout ce qui fait des I/O. Un relecteur sait où regarder dans n'importe quel fichier Go, qu'il ait été écrit par une personne ou par un modèle.

Le compilateur relit chaque ligne en premier

Le compilateur de Go rejette toute une catégorie d'erreurs de l'IA avant même l'exécution d'un test. Prenons cette fonction utilitaire qu'un assistant pourrait laisser derrière lui après un refactoring :

example.gogo
import (
	"net/http"
	"strings"
)

func countItems(r *http.Request) int {
	n := 0
	items := r.URL.Query()["item"]
	return len(items)
}

En Go, elle ne compile pas :

example.texttext
./handler.go:5:2: "strings" imported and not used
./handler.go:9:2: declared and not used: n

Ces deux erreurs sont typiques des restes qu'un modèle laisse lorsqu'il réécrit une fonction à moitié : un import d'une tentative précédente, une variable issue d'une logique supprimée. Go en fait des erreurs à dessein. Il échange « la commodité à court terme contre la vitesse de build et la clarté des programmes à long terme », et « le compilateur Go ne signale pas d'avertissements, seulement des erreurs qui empêchent la compilation » (Go FAQ). Sans avertissement à ignorer, l'agent doit corriger le problème pour que le build passe.

Le typage statique intercepte le reste des erreurs courantes. Passer une string là où un int64 est attendu, renvoyer une seule valeur depuis une fonction qui en renvoie deux ou appeler une méthode qui n'existe pas : tout cela échoue à la compilation. Cela couvre l'essentiel de ce que les modèles font de travers. Des chercheurs de l'ETH Zurich et de l'UC Berkeley ont constaté qu'environ 94 % des erreurs de compilation dans du TypeScript généré par des LLM étaient des échecs de vérification de types et non des erreurs de syntaxe (Mündler et al., PLDI, 2025). L'étude portait sur TypeScript, et la conclusion s'applique directement à Go : c'est le vérificateur de types qui intercepte les erreurs générées.

La vitesse de build de Go rend cette vérification peu coûteuse. La FAQ de Go fixe comme objectif qu'« il ne devrait pas falloir plus de quelques secondes pour compiler un gros exécutable sur un seul ordinateur » (Go FAQ). Un agent qui recompile après chaque modification lance le compilateur des dizaines de fois par tâche, et des builds rapides gardent cette boucle serrée. La boucle en vaut la peine : renvoyer les résultats du compilateur à un modèle a fait passer le taux de compilation réussie de 44,18 % à 89,18 % sur une tâche de complétion de code (Wang et al., ACL, 2022).

Le code Go appris par un modèle il y a des années fonctionne toujours

Un modèle ne connaît que les API présentes dans ses données d'entraînement. Avec Go, ces connaissances ne se périment pas.

La promesse de Go 1 dit que « les programmes écrits selon la spécification Go 1 continueront à compiler et à s'exécuter correctement, sans modification, pendant toute la durée de vie de cette spécification » (Go 1 compatibility). Du code net/http de 2014 compile toujours. Quand un modèle propose des patterns http.HandleFunc, database/sql ou encoding/json vus dans d'anciens dépôts, ils fonctionnent. Chaque dépôt Go public depuis 2012 reste une donnée d'entraînement valide.

Quand Go introduit une meilleure façon d'écrire quelque chose, la toolchain met l'ancien code à jour pour vous. L'équipe Go a conçu les modernizers de go fix de Go 1.26 en partie en pensant à l'IA. Alan Donovan a écrit que les assistants de code « avaient tendance (sans surprise) à produire du code Go dans un style proche de la masse de code Go utilisée pendant l'entraînement, même lorsqu'il existait des façons plus récentes et meilleures d'exprimer la même idée » (Go blog, 2026). Lancer go fix ./... réécrit ces patterns : interface{} devient any, for i := 0; i < n; i++ devient for i := range n, et les bornages écrits à la main deviennent min et max. Go fix modernizers in Go 1.26 liste chaque analyseur avec des exemples avant/après.

Une bibliothèque standard qui couvre le backend

L'essentiel de ce dont un service backend a besoin est livré avec Go. Serveurs et clients HTTP, routage avec méthodes et paramètres de chemin (depuis Go 1.22), JSON, interfaces SQL, TLS, cryptographie, tests et logs structurés avec log/slog : tout se trouve dans la bibliothèque standard. Un modèle peut construire un service d'API complet avec uniquement des lignes import de la bibliothèque standard, et les modèles ont vu ces packages dans une très grande quantité de données d'entraînement.

C'est important, car les modèles inventent bel et bien des packages. Une étude de USENIX Security 2025 portant sur 16 modèles a montré que les modèles commerciaux suggéraient des packages inexistants dans au moins 5,2 % des cas, et les modèles open source dans 21,7 % des cas, avec au total 205 474 noms inventés uniques (Spracklen et al., USENIX Security, 2025). Des attaquants enregistrent désormais ces noms, une technique que Seth Larson, developer-in-residence à la PSF, a baptisée « slopsquatting » (Socket, 2025). Chaque import dont un modèle n'a pas besoin est un import qu'il ne peut pas rater.

Quand un projet Go ajoute une dépendance, le système de modules la rend facile à relire. Les imports sont des chemins de dépôt complets comme github.com/jackc/pgx/v5 plutôt que des noms courts, et go.sum, associé à la base de checksums publique, fixe chaque dépendance à l'octet près. Une dépendance inattendue saute aux yeux dans le diff. Is Go Safer Than Node.js? détaille le système de modules.

Un code explicite se relit facilement

Quand un modèle écrit le code, votre travail consiste à le lire. Le style explicite de Go, gestion des erreurs comprise, est pensé pour la lecture.

Un agent bien guidé écrit une méthode de repository et un handler comme ceci :

example.gogo
var ErrNotFound = errors.New("user not found")

func (s *Store) FindUser(ctx context.Context, id string) (User, error) {
	var u User
	err := s.db.QueryRowContext(ctx,
		`SELECT id, email FROM users WHERE id = $1`, id,
	).Scan(&u.ID, &u.Email)
	if errors.Is(err, sql.ErrNoRows) {
		return User{}, ErrNotFound
	}
	if err != nil {
		return User{}, fmt.Errorf("find user %s: %w", id, err)
	}
	return u, nil
}

func getUser(users finder) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		u, err := users.FindUser(r.Context(), r.PathValue("id"))
		switch {
		case errors.Is(err, ErrNotFound):
			http.Error(w, "not found", http.StatusNotFound)
			return
		case err != nil:
			slog.Error("get user", "err", err)
			http.Error(w, "internal error", http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "%s\n", u.Email)
	}
}

Chaque chemin de sortie de chaque fonction est visible à l'écran. Vous pouvez vérifier quatre choses sans ouvrir un autre fichier : la requête utilise le contexte de la requête HTTP, une ligne absente devient un 404, les autres erreurs de base de données sont journalisées et transformées en 500 sans divulguer de détails au client, et l'erreur enveloppée conserve l'ID de l'utilisateur pour la ligne de log. Aucune exception ne risque d'être levée ailleurs et interceptée on ne sait où.

La même explicitation rend visibles les erreurs ignorées. Une erreur ignorée apparaît comme un appel sans err à gauche ou avec un _ = explicite, et errcheck (voir la section de configuration plus bas) signale celles qui passent facilement inaperçues, comme un json.NewEncoder(w).Encode(v) non vérifié. 10 Common Go Mistakes to Avoid recense les erreurs de gestion des erreurs à surveiller dans le code généré.

Une concurrence simple à écrire et facile à vérifier

Les goroutines, les channels et context.Context offrent à Go un modèle unique, petit et cohérent pour le travail concurrent. Un modèle qui écrit un pool de workers ou un fan-out d'appels HTTP utilise les quelques mêmes briques que toutes les bases de code Go, et la toolchain Go peut vérifier le résultat.

Les data races sont le bug de concurrence qu'un relecteur risque le plus de laisser passer. Ce middleware de limitation de débit, qui compte les requêtes par clé d'API, semble correct au premier coup d'œil :

example.gogo
type Limiter struct {
	hits map[string]int
	max  int
}

func (l *Limiter) Wrap(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		key := r.Header.Get("X-API-Key")
		l.hits[key]++
		if l.hits[key] > l.max {
			http.Error(w, "too many requests", http.StatusTooManyRequests)
			return
		}
		next.ServeHTTP(w, r)
	})
}

Les handlers HTTP s'exécutent sur de nombreuses goroutines en même temps, et cette map n'est protégée par aucun verrou. Nous avons lancé cinq fois sous Go 1.27 un test qui envoie 50 requêtes concurrentes à travers le middleware. go test -race a échoué aux cinq exécutions avec WARNING: DATA RACE et une stack trace pointant sur la ligne l.hits. Le race detector est intégré à la toolchain, et il « ne détecte que les races qui se produisent à l'exécution », il lui faut donc un test qui exécute le code de façon concurrente (Go race detector). Avec ce test en place, un agent qui lance go test -race trouve le bug à chaque exécution et peut le corriger avec un sync.Mutex.

Des outils que l'agent peut lancer lui-même

Tout ce dont un agent a besoin pour vérifier son propre travail est fourni avec la commande go. Rien à installer ni à configurer au préalable.

go vet « examine le code source Go et signale les constructions suspectes » qui compilent mais sont probablement fausses (cmd/vet). Une erreur typique de l'IA est un verbe de format qui ne correspond pas à son argument :

example.gogo
func showUser(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	fmt.Fprintf(w, "user %d", id)
}
example.texttext
handler.go:10:23: fmt.Fprintf format %d has arg id of wrong type string

go test exécute automatiquement une partie de ces vérifications de vet, donc un agent qui lance les tests en bénéficie sans rien faire de plus. go build produit par défaut un seul binaire lié statiquement (Go FAQ), donc l'agent peut compiler et lancer le service qu'il vient de modifier sans lui préparer d'environnement.

L'équipe Go développe aussi directement pour les agents. gopls, le language server de Go, inclut depuis la v0.20 « un serveur expérimental intégré pour le Model Context Protocol (MCP) » (gopls MCP). Un agent qui s'y connecte peut demander des définitions, des références et des diagnostics au même moteur que celui de votre éditeur, au lieu de deviner à partir du texte.

Configurer un dépôt Go pour les agents de code IA

Un dépôt Go tire l'essentiel de son filet de sécurité face à l'IA de la toolchain standard. La configuration consiste à s'assurer que l'agent la lance à chaque fois.

1. Inscrivez les vérifications dans un fichier d'instructions pour l'agent. La plupart des agents de code lisent un fichier CLAUDE.md ou AGENTS.md à la racine du dépôt. AGENTS.md est désormais géré par l'Agentic AI Foundation sous l'égide de la Linux Foundation et utilisé par plus de 60 000 projets open source (agents.md, 2026). Gardez-le court et concret :

example.markdownmarkdown
## Checks (run before every commit)
- go build ./...
- go vet ./...
- go test -race ./...
- golangci-lint run

## Conventions
- Standard library first. Ask before adding a dependency.
- Wrap errors with fmt.Errorf("context: %w", err). Never discard an error.
- Pass context.Context as the first argument to anything that does I/O.

2. Ajoutez golangci-lint. Il regroupe plus d'une centaine de linters derrière une seule commande, dont errcheck pour les erreurs ignorées et staticcheck. L'agent obtient une seule liste d'échecs à corriger au lieu de cinq outils.

3. Gardez des tests pilotés par table à proximité du code. Ce sont les tests qu'un agent étend le plus facilement sans se tromper, car ajouter un cas revient à ajouter une ligne :

example.gogo
func TestGetUser(t *testing.T) {
	tests := []struct {
		name     string
		finder   fakeFinder
		wantCode int
	}{
		{"found", fakeFinder{user: User{ID: "42", Email: "[email protected]"}}, http.StatusOK},
		{"missing", fakeFinder{err: ErrNotFound}, http.StatusNotFound},
		{"db down", fakeFinder{err: errors.New("connection refused")}, http.StatusInternalServerError},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mux := http.NewServeMux()
			mux.HandleFunc("GET /users/{id}", getUser(tt.finder))
			rec := httptest.NewRecorder()
			mux.ServeHTTP(rec, httptest.NewRequest("GET", "/users/42", nil))
			if got := rec.Result().StatusCode; got != tt.wantCode {
				t.Errorf("status = %d, want %d", got, tt.wantCode)
			}
		})
	}
}

Écrivez vous-même les deux premiers cas pour que l'agent reproduise votre intention. Demandez-lui ensuite d'ajouter les cas limites et lisez ce qu'il ajoute.

4. Lancez -race en CI. Quand un agent écrit du code concurrent, le race detector est la vérification que vous voulez le plus voir tourner à chaque push.

5. Lancez go fix après de gros changements générés. Il remplace les anciens idiomes par les idiomes actuels avant qu'ils ne se propagent dans la base de code.

Rien de tout cela ne remplace la lecture du diff. Cela signifie simplement qu'au moment où vous le lisez, le compilateur, go vet, le linter et le race detector ont déjà rejeté les erreurs mécaniques, et qu'il ne reste que le jugement : est-ce la bonne conception, et le code gère-t-il les cas qui comptent ?

La place de LevelUpGo

Laisser l'IA écrire du Go fonctionne le mieux quand vous savez relire du Go. Vous devez repérer le contexte ignoré, le verrou manquant et l'erreur avalée. LevelUpGo vous l'apprend en vous faisant écrire le code vous-même : chaque leçon présente le concept à gauche et un véritable éditeur à droite, et votre code doit compiler et passer de vrais tests pour avancer. Commencez par le cours gratuit Go Basics, ou consultez la feuille de route Go complète. Pour la question de l'IA vue du côté de l'apprenant, Should You Still Write Code by Hand? explique quand taper le code soi-même, et Is Go Worth Learning in 2026? aborde l'intérêt pour votre carrière.

Questions fréquentes

Go est-il adapté au code généré par l'IA ?

Oui, et pour les services backend, les CLI et l'infrastructure, c'est le meilleur choix. Go est assez petit pour que la sortie des modèles reste prévisible, gofmt maintient chaque fichier dans un style unique, le compilateur rejette les imports et variables inutilisés, et go vet et go test -race détectent des bugs qui compilent. Davantage d'erreurs de l'IA échouent automatiquement, ce qui en laisse moins à repérer pour un relecteur humain.

Pourquoi Go est-il facile à écrire pour les modèles d'IA ?

Go compte 25 mots-clés, un formateur officiel unique et une bibliothèque standard qui couvre l'essentiel du travail backend. Il existe peu de façons de résoudre un problème donné, donc la sortie d'un modèle ressemble au Go idiomatique dont il a appris. Les auteurs de Multi-SWE-bench ont aussi classé Go parmi les langages les plus sobres en tokens, ce qu'ils attribuent à sa syntaxe minimale et à ses conventions claires.

Go vs Python : Go est-il meilleur pour les agents de code IA ?

Pour les services qui tournent en production, oui. Go donne à un agent le typage statique, un compilateur sans avertissements, un race detector intégré et un formateur unique, sans configuration supplémentaire. Ces vérifications transforment bon nombre des erreurs de peu que produisent les outils d'IA en échecs de build que l'agent corrige de lui-même.

Les modèles d'IA inventent-ils des packages Go ?

La principale étude sur l'hallucination de packages, Spracklen et al. (USENIX Security 2025), a testé Python et JavaScript, il n'existe donc pas de taux publié pour Go. La bibliothèque standard de Go couvre une plus grande partie d'un backend typique, ce qui réduit le nombre d'imports qu'un modèle pourrait rater, et les chemins d'import complets associés à go.sum rendent une dépendance inattendue facile à repérer en revue.

Comment amener un agent IA à écrire un meilleur code Go ?

Donnez-lui des vérifications qu'il peut lancer. Placez go build ./..., go vet ./..., go test -race ./... et golangci-lint run dans un fichier CLAUDE.md ou AGENTS.md. Gardez des tests pilotés par table que l'agent peut étendre, et lancez go fix ./... pour mettre à jour les idiomes obsolètes. Relisez ensuite le diff vous-même.

Sources

Écrivez du Go comme un ingénieur senior

Des leçons interactives dans votre navigateur. Les premières sont gratuites.

Essayer une leçon gratuiteOu créer un compte gratuit