ブログに戻る

Go が AI の書くコードに最適な言語である理由

小さな言語、1 通りのコードスタイル、厳しいコンパイラ、安定した API、そしてバックエンドをカバーする標準ライブラリ。Go が AI の書くコードに最適な言語である理由を解説します。

Go が AI の書くコードに最適な言語である理由

Go が AI の書くコードに最適な言語なのは、モデルが確実に書けて、書いたものをあなたが確認しやすいからです。言語が小さいので、同じことを書く方法がモデルにとって少なくなります。Go のファイルはすべて同じ形式で整形されるので、出力はモデルが学んだコードと同じ見た目になります。コンパイラは使われていない import や変数を拒否します。Go 1 の互換性の約束により、モデルが何年も前に学んだ API は今でも動きます。そして標準ライブラリがバックエンドの大部分をカバーしているので、モデルが間違えるかもしれないパッケージに頼る場面はほとんどありません。差分の大半をエージェントが書くようになると、こうした性質がレビューにかかる時間を左右します。

要約

  • AI のコードは「ほぼ正しい」という形で失敗するのが普通です。開発者の 66% が、AI ツールに対する最大の不満として「ほぼ正しいけれど完全ではない」コードを挙げています(Stack Overflow Developer Survey、2025 年)。AI が書くコードに最適な言語とは、そうした惜しいミスを最も見つけやすい言語です。
  • Go はモデルの出力を短く、予測しやすく保ちます。仕様のキーワードは 25 個で(Go spec)、Multi-SWE-bench の著者たちは「Go は入力と出力の両方でトークン消費量が比較的少ない。これはおそらくミニマルな構文と明確な規約によるものだ」と述べています(Multi-SWE-bench、2025 年)。
  • コンパイラは警告を出しません。Go は「使われていない変数や import があるプログラムのコンパイルを拒否」し、ビルドを止めるエラーだけを報告します(Go FAQ)。GitHub の Octoverse レポートは「型システムは LLM が生成したコンパイルエラーをパイプラインの早い段階で特定するのに役立つ」と述べています(GitHub Octoverse、2025 年)。
  • 2012 年の Go のコードは今でもコンパイルできます。Go 1 の約束は古いプログラムが「変更なしで」ビルドできることを保証しているので(Go 1 compatibility)、モデルの学習データにあるパターンは有効なままです。
  • エージェントは、自分で実行できるチェックがあるときにいちばんうまく動きます。Anthropic の Claude Code のガイドには「Claude に合否を出せるものを渡せば、ループは自然に閉じる」とあります(Claude Code docs、2026 年)。Go にはそのチェックが言語と一緒に付属しています。go build、go vet、go test -race です。

AI が書くコードのために言語に求められること

AI が書くコードに向いている言語は、次の 4 つの点で優れています。

  1. モデルが確実に書けること。よく使われる書き方が 1 つしかない小さな言語なら、間違える余地が少なくなります。
  2. 人間なしでツールがチェックできること。静的型、厳しいコンパイルエラー、組み込みのテストランナーがあれば、誰かが読む前に悪い出力を拒否できます。
  3. 人間が素早くレビューできること。隠れた制御フローも魔法のような仕組みもないコードなら、レビュアーは差分だけを見てその差分が何をするのかを理解できます。
  4. モデルが学んだことが今も正しいこと。学習データは数か月から数年前のものなので、言語とそのライブラリがこれまでと同じように動き続ける必要があります。

Go は、コーディングアシスタントが登場するずっと前に、大きなチームが取り組む大規模なコードベースのために Google で設計されました。新しくチームに加わった人が Go をすぐに覚えられるようにした設計上の選択は、そのままモデルにとっての書きやすさと、あなたにとってのレビューのしやすさにつながります。

モデルが正しく書ける小さな言語

Go は本番で使われている多くの言語より機能が少なく、そのぶんモデルが気の利いたつもりで間違ったコードを書く余地も少なくなります。

仕様で予約されているキーワードは 25 個です(Go spec)。クラスも継承も、例外も、演算子のオーバーロードも、マクロも、暗黙の数値変換もありません。ループは for だけです。振る舞いは、普通の関数、構造体、小さなインターフェースで表現します。モデルが Go の HTTP ハンドラーを書くとき、妥当な書き方はほんの数通りしかなく、どれも似た見た目になります。キーワードの一覧は、Go のキーワード:25 個の予約語をすべて解説で 1 つずつ確認できます。

言語が小さいということは、タスクあたりのテキストも少なくなるということです。Multi-SWE-bench の著者たちは言語ごとのトークン使用量を測定し、Go が入力と出力の両方で最も少ない部類に入ることを明らかにしました(Multi-SWE-bench、2025 年)。変更あたりのトークンが少なければ、タスクごとのコストが下がり、コンテキストウィンドウにも自分のコードを入れる余裕が増えます。

どの Go のファイルも同じ見た目

gofmt は Go のツールチェーンの一部で、議論の種になるような設定項目はありません。Go FAQ によれば、オープンソースの Go のコードの大多数はこのツールで整形されています(Go FAQ)。そのため、モデルが学んだ Go は珍しいほど均一で、モデルの出力が最初から Go らしいコードになりやすいのもそのためです。

整形はレビューでも役に立ちます。モデルがレイアウトの細部を間違えても、gofmt のコマンド 1 つで直せるので、差分に出てくるのはロジックの変更だけです。タブかスペースか、波括弧をどこに置くかが差分に出ることはありません。命名も同じパターンに従います。エクスポートする名前は大文字で始まり、エラーは最後の戻り値で、context.Context は I/O を行うあらゆる関数の最初の引数です。人間が書いたものでもモデルが書いたものでも、レビュアーはどの Go のファイルでもどこを見ればよいかがわかります。

すべての行を最初にレビューするのはコンパイラ

Go のコンパイラは、テストを実行する前の段階で、AI のミスの一群をまとめて拒否します。リファクタリングのあとにアシスタントが残していきそうな、次のヘルパーを見てください。

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

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

Go では、これはビルドが通りません。

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

どちらのエラーも、モデルが関数を途中まで書き直したときによく残るものです。以前の試行で入れた import と、削除したロジックで使っていた変数です。Go はこれらを意図的にエラーにしています。Go は「目先の便利さを、長期的なビルド速度とプログラムのわかりやすさと引き換えにして」おり、「Go のコンパイラは警告を報告せず、コンパイルを妨げるエラーだけを報告する」のです(Go FAQ)。無視できる警告がないので、エージェントはビルドが通るまで問題を直すしかありません。

よくあるうっかりミスの残りは静的型が捕まえます。int64 が必要な場所に string を渡す、2 つの値を返す関数から 1 つだけ返す、存在しないメソッドを呼ぶ、といったミスはすべてコンパイル時に失敗します。モデルが間違えるものの大半はこれでカバーできます。ETH Zurich と UC Berkeley の研究者は、LLM が生成した TypeScript のコンパイルエラーのうち約 94% が構文エラーではなく型チェックの失敗だったことを明らかにしました(Mündler et al., PLDI、2025 年)。この研究の対象は TypeScript ですが、要点はそのまま Go にも当てはまります。生成されたコードのミスが捕まるのは型チェッカーなのです。

Go はビルドが速いので、このチェックを気軽に実行できます。Go FAQ は「1 台のコンピューターで大きな実行ファイルをビルドするのに、せいぜい数秒しかかからないようにする」ことを目標に掲げています(Go FAQ)。編集のたびにリビルドするエージェントは、1 つのタスクでコンパイラを何十回も実行するので、ビルドが速ければループも短く保てます。このループを回す価値はあります。コンパイラの結果をモデルにフィードバックしたところ、コード補完タスクでのコンパイル成功率は 44.18% から 89.18% に上がりました(Wang et al., ACL、2022 年)。

モデルが何年も前に学んだ Go のコードは今でも動く

モデルが知っているのは、学習データに含まれていた API だけです。Go なら、その知識が古びることはありません。

Go 1 の約束には「Go 1 の仕様に沿って書かれたプログラムは、その仕様が有効な間、変更なしでコンパイルでき、正しく動き続ける」とあります(Go 1 compatibility)。2014 年の net/http のコードは今でもコンパイルできます。モデルが古いリポジトリで見た http.HandleFunc、database/sql、encoding/json のパターンを提案しても、それはちゃんと動きます。2012 年以降の公開された Go のリポジトリは今でもすべて有効な学習データです。

Go がもっと良い書き方を追加したときは、ツールチェーンが古いコードを新しい書き方へ移してくれます。Go チームが Go 1.26 の go fix の modernizer を作った理由の 1 つは AI です。Alan Donovan は、コーディングアシスタントが「(当然ながら)学習に使われた大量の Go のコードに似たスタイルで Go を書く傾向があった。同じ考えを表すもっと新しく良い書き方がある場合でもそうだった」と書いています(Go blog、2026 年)。go fix ./... を実行すると、そうしたパターンが書き換えられます。interface{} は any に、for i := 0; i < n; i++ は for i := range n に、手書きの範囲制限は min と max になります。すべてのアナライザーとその前後の例は Go 1.26 の go fix modernizer にまとめてあります。

バックエンドをカバーする標準ライブラリ

バックエンドサービスに必要なものの大半は Go に付属しています。HTTP のサーバーとクライアント、メソッドとパスパラメータを使ったルーティング(Go 1.22 以降)、JSON、SQL のインターフェース、TLS、暗号、テスト、log/slog による構造化ログは、すべて標準ライブラリにあります。モデルは標準ライブラリの import 行だけで完全な API サービスを作れます。しかもモデルは、これらのパッケージを大量の学習データで見てきています。

これが重要なのは、モデルが実際にパッケージをでっち上げるからです。16 のモデルを調べた USENIX Security 2025 の研究では、商用モデルは少なくとも 5.2%、オープンソースのモデルは 21.7% の割合で存在しないパッケージを提案しており、架空の名前は重複を除いて合計 205,474 個にのぼりました(Spracklen et al., USENIX Security、2025 年)。今では攻撃者がそうした名前を登録しており、PSF の developer-in-residence である Seth Larson はこの手口を「slopsquatting」と名づけました(Socket、2025 年)。モデルが必要としない import は、モデルが間違えることもない import です。

Go のプロジェクトで依存関係を追加するときも、モジュールシステムのおかげでレビューは簡単です。import は短い名前ではなく github.com/jackc/pgx/v5 のようなリポジトリの完全なパスで、go.sum と公開のチェックサムデータベースがすべての依存関係を正確なバイト列に固定します。予期しない依存関係は差分の中で目立ちます。モジュールシステムの詳しい説明は Go は Node.js より安全?にあります。

明示的なコードはレビューしやすい

モデルがコードを書くようになると、あなたの仕事はそれを読むことになります。エラー処理を含め、Go の明示的なスタイルは読むために作られています。

適切に指示されたエージェントなら、リポジトリのメソッドとハンドラーを次のように書きます。

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)
	}
}

それぞれの関数から抜けるすべての経路が、画面上に書かれています。別のファイルを開かなくても、4 つのことを確認できます。クエリがリクエストのコンテキストを使っていること、行が見つからなければ 404 になること、それ以外のデータベースエラーはログに記録され、クライアントに詳細を漏らさずに 500 になること、ラップしたエラーがログ用にユーザー ID を保持していることです。どこか別の場所で投げられ、どこで捕まるのかわからない例外はありません。

同じ明示性のおかげで、処理されなかったエラーも目に見えます。無視されたエラーは、左辺に err がない呼び出しか、明示的な _ = として現れます。さらに errcheck(後のセットアップの節を参照)が、見逃しがちなケースを指摘してくれます。たとえば、チェックされていない json.NewEncoder(w).Encode(v) です。生成されたコードで気をつけたいエラー処理のミスは、Go でよくある 10 のミスにまとめています。

書くのが簡単で、チェックしやすい並行処理

goroutine、チャネル、context.Context によって、Go には並行処理のための小さく一貫したモデルが 1 つあります。ワーカープールや HTTP 呼び出しのファンアウトを書くモデルは、どの Go のコードベースでも使われている同じ少数の部品を使い、その結果は Go のツールチェーンでチェックできます。

データ競合は、並行処理のバグの中でもレビュアーが最も読み流してしまいがちなものです。API キーごとにリクエスト数を数える、次のレート制限ミドルウェアは一見問題なさそうに見えます。

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)
	})
}

HTTP ハンドラーは多数の goroutine で同時に実行されますが、この map にはロックがありません。私たちは Go 1.27 で、このミドルウェアに 50 件の同時リクエストを送るテストを 5 回実行しました。go test -race は 5 回すべてで WARNING: DATA RACE を出して失敗し、スタックトレースは l.hits の行を指していました。レースディテクタはツールチェーンに組み込まれていますが、「実行時に起きた競合しか見つけない」ので、コードを並行して実行するテストが必要です(Go race detector)。そのテストさえあれば、go test -race を実行するエージェントは毎回このバグに気づき、sync.Mutex で直せます。

エージェントが自分で実行できるツール

エージェントが自分の作業をチェックするのに必要なものは、すべて go コマンドに含まれています。事前にインストールや設定をするものはありません。

go vet は、コンパイルは通るものの間違っている可能性が高い「疑わしい構文を Go のソースコードから探して報告」します(cmd/vet)。AI によくあるうっかりミスの 1 つが、引数と合わないフォーマット指定子です。

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 はこうした vet のチェックの一部を自動的に実行するので、テストを実行するエージェントは追加の手間なしにその結果を受け取れます。go build はデフォルトで静的リンクされた単一のバイナリを生成するので(Go FAQ)、エージェントは環境を用意しなくても、変更したばかりのサービスをビルドして実行できます。

Go チームはエージェント向けの機能も直接作っています。Go の言語サーバーである gopls には、v0.20 から「Model Context Protocol(MCP)用の実験的な組み込みサーバー」が含まれています(gopls MCP)。これに接続したエージェントは、テキストから推測する代わりに、エディターが使っているのと同じエンジンに定義や参照、診断結果を問い合わせられます。

AI コーディングエージェント向けに Go リポジトリを整える方法

Go のリポジトリでは、AI に対する安全網の大半を標準のツールチェーンが提供してくれます。セットアップで大事なのは、エージェントに毎回それを実行させることです。

1. チェックをエージェント向けの指示ファイルに書く。ほとんどのコーディングエージェントは、リポジトリのルートにある CLAUDE.md か AGENTS.md のファイルを読みます。AGENTS.md は現在 Linux Foundation 傘下の Agentic AI Foundation が管理しており、6 万を超えるオープンソースプロジェクトで使われています(agents.md、2026 年)。内容は短く具体的にします。

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. golangci-lint を追加する。無視されたエラーを見つける errcheck や staticcheck を含む 100 以上のリンターを、1 つのコマンドにまとめたツールです。エージェントは 5 つのツールの結果ではなく、直すべき失敗の一覧を 1 つだけ受け取れます。

3. テーブル駆動テストをコードの近くに置く。ケースを追加するには行を 1 つ足すだけなので、エージェントが最も正しく拡張しやすいテストです。

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)
			}
		})
	}
}

最初の 2 つのケースは自分で書きましょう。そうすれば、エージェントはあなたの意図をなぞります。そのうえでエッジケースの追加を頼み、追加された内容を読んでください。

4. CI で -race を実行する。エージェントが並行処理のコードを書くなら、レースディテクタはすべての push で最も実行したいチェックです。

5. 大きな生成変更のあとは go fix を実行する。古いイディオムがコードベース全体に広がる前に、現在の書き方へ移せます。

どれも差分を読む代わりにはなりません。ただ、差分を読む時点では、コンパイラ、go vet、リンター、レースディテクタが機械的なミスをすでに弾いています。残るのは判断が必要な部分です。この設計で正しいのか、重要なケースを扱えているのか、といった点です。

LevelUpGo の役割

AI に Go を書かせるのが最もうまくいくのは、Go をレビューできるときです。無視されたコンテキスト、足りないロック、握りつぶされたエラーに気づける必要があります。LevelUpGo では、それを自分でコードを書くことで身につけます。どのレッスンも左側に概念、右側に本物のエディターがあり、先に進むにはコードがコンパイルでき、実際のテストに通らなければなりません。まずは無料の Go Basics コースから始めるか、Go ロードマップの全体を見てみてください。学ぶ側から見た AI の問題については、いつ自分で書くべきかを 今でもコードは手で書くべき? で、キャリアの観点は 2026 年に Go を学ぶ価値はある? で扱っています。

よくある質問

Go は AI が生成するコードに向いていますか?

はい。バックエンドサービス、CLI、インフラについては最も適した言語です。Go は小さいのでモデルの出力が予測しやすく、gofmt がすべてのファイルを 1 つのスタイルに揃え、コンパイラは使われていない import や変数を拒否し、go vet と go test -race はコンパイルが通るバグを見つけます。AI のミスのより多くが自動的に失敗になるので、人間のレビュアーが見つけるべきものが減ります。

なぜ AI モデルにとって Go は書きやすいのですか?

Go にはキーワードが 25 個、公式のフォーマッターが 1 つ、そしてバックエンドの仕事の大半をカバーする標準ライブラリがあります。ある問題を解く方法が少ないので、モデルの出力は学習した Go らしいコードに似たものになります。Multi-SWE-bench の著者たちも、Go のトークン使用量が最も少ない部類に入ることを明らかにしており、その理由をミニマルな構文と明確な規約に求めています。

AI コーディングエージェントには Python より Go のほうが向いていますか?

本番で動くサービスなら、そのとおりです。Go は追加のセットアップなしで、静的型、警告のないコンパイラ、組み込みのレースディテクタ、ただ 1 つのフォーマッターをエージェントに与えます。こうしたチェックによって、AI ツールが生みがちな惜しいミスの多くがビルドの失敗に変わり、エージェントはそれを自分で直します。

AI モデルは存在しない Go パッケージを作り出しますか?

パッケージのハルシネーションに関する主要な研究である Spracklen et al.(USENIX Security 2025)は Python と JavaScript を対象にしているため、Go について公表された割合はありません。Go の標準ライブラリは典型的なバックエンドのより多くの部分をカバーしているので、モデルが間違えうる import は少なくなります。さらに完全な import パスと go.sum のおかげで、予期しない依存関係にレビューで気づきやすくなります。

AI エージェントにもっと良い Go を書かせるにはどうすればよいですか?

エージェントが自分で実行できるチェックを渡しましょう。go build ./...、go vet ./...、go test -race ./...、golangci-lint run を CLAUDE.md か AGENTS.md のファイルに書きます。エージェントが拡張できるテーブル駆動テストを用意し、go fix ./... を実行して古いイディオムを更新します。そのうえで、差分は自分で読んでください。

出典

シニアエンジニアのように Go を書く

ブラウザで学べるインタラクティブなレッスン。最初のレッスンは無料です。

無料レッスンを試すまたは無料アカウントを作成