はじめに
本記事は、以下の X 投稿で紹介されていた Go のロードマップを土台に、各ステップで「何をやるか」「なぜ重要か」「最小限のコード例」「次に読むべきリソース」を日本語でまとめ直したものです。
出典: ↓
筆者自身は Go の基礎構文をひととおり触った段階で、これから実務レベルへ引き上げていきたい立場です。同じように「基礎はなんとなく書けるが、実務で書けと言われると手が止まる」層をメインターゲットにしています。Go 未経験の方は 1 から、ある程度書ける方は気になるステップだけ拾い読みしていただく想定です。
各ステップは独立して学べますが、1 → 12 の順で積み上げると「個人開発で動くもの」から「本番で壊れにくいもの」へと自然に遷移できる構成になっています。
1. 基本構文を押さえる
最初のステップは、構文・変数・ループ・関数・パッケージ・モジュールといった言語の土台です。ここは「Go Tour」と公式ドキュメントを一周するのが最短ルートです。
他言語経験者が特に意識すべきポイントは次の 3 つです。
-
:=とvarの使い分け - 複数戻り値 (特に
value, errパターン) -
go mod init/go mod tidyを使ったモジュール管理
package main
import "fmt"
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("result:", result)
}
推奨リソース
2. Go の「本当の強み」を理解する: struct・interface・composition・embedding
Go にはクラスも継承もありません。代わりに struct による合成 (composition) と interface による振る舞いの抽象化 を組み合わせて設計します。ここを身体に叩き込めるかどうかで、Go らしいコードが書けるかどうかが決まります。
- struct は「データの入れ物」
- interface は「メソッドの集合」
- embedding は「別の struct の機能を埋め込んで使い回す」
type Logger interface {
Log(msg string)
}
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(msg string) {
fmt.Println("[console]", msg)
}
// embedding: Service が Logger の機能をそのまま持つ
type Service struct {
Logger
name string
}
func main() {
s := Service{Logger: ConsoleLogger{}, name: "users"}
s.Log("service started") // 埋め込みによりそのまま呼べる
}
推奨リソース
- Go by Example: Interfaces
- 書籍『プログラミング言語Go』(Alan A. A. Donovan / Brian W. Kernighan)
3. ポインタをマスターする
ポインタは「値のコピーを避けたいとき」「受け取った側で変更を反映したいとき」に使います。Go では明示的に * / & を扱いますが、C のような危険なポインタ演算はありません。
実務でよく出る論点は次の 2 つです。
- レシーバを値にすべきか、ポインタにすべきか
- nil ポインタ参照をどう防ぐか
type Counter struct {
count int
}
// ポインタレシーバ: 呼び出し元の値が変更される
func (c *Counter) Increment() {
c.count++
}
// 値レシーバ: コピーが渡されるので変更は反映されない
func (c Counter) Value() int {
return c.count
}
レシーバは「struct のサイズが大きい」「フィールドを書き換える」いずれかに該当するならポインタ、そうでなければ値で良い、というのが一般的な指針です。
推奨リソース
4. Goroutine と Channel: Go を選ぶ最大の理由
Go が他言語と差別化される一番の理由が、軽量スレッドである goroutine と、それらの間でデータをやり取りする channel です。go キーワード一つで並行処理が走り、channel で安全に値を受け渡しできます。
まずは「goroutine はバックグラウンドで動くもの」「channel はパイプのようなもの」というイメージで触ってみましょう。
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- j * 2
_ = id
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan int, 5)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
fmt.Println(<-results)
}
}
推奨リソース
5. エラーハンドリング: wrap・custom error・context
Go のエラーは例外ではなく「戻り値」です。つまりエラーは呼び出し元が明示的に扱う必要があります。実務で重要なのは次の 3 つです。
-
fmt.Errorf("...: %w", err)による エラーラッピング -
errors.Is/errors.Asによる判定 - 独自エラー型によるドメイン固有の表現
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findUser(id int) error {
// 下位層のエラーを「何をしようとしていたか」でラップする
return fmt.Errorf("findUser(id=%d): %w", id, ErrNotFound)
}
func main() {
err := findUser(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("user not found")
}
}
「ログに出してから再 return する」と同じエラーが何度も出力されるので、ログを出すか wrap して返すかはレイヤーで統一するのがおすすめです。
推奨リソース
6. CLI ツールを作る
CLI は「仕様が小さく」「入出力が単純で」「自分で使える」ため、Go の練習題材として非常に優秀です。標準の flag パッケージで十分動きますが、サブコマンドやヘルプ整形が欲しくなったら cobra や urfave/cli を使いましょう。
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "world", "name to greet")
flag.Parse()
fmt.Printf("hello, %s\n", *name)
}
お題に迷ったら、普段の業務で「地味に面倒だがスクリプトを書くほどでもない作業」を CLI 化してみるのが良いです。ログ整形ツール、git のブランチ一括削除、API を叩いて整形するクライアントなど。
推奨リソース
7. データを扱う: ファイル・JSON・YAML・HTTP クライアント
実務ではまず間違いなく、外部とのデータ入出力を扱います。最低限の武器は以下です。
-
os/io/bufioによるファイル操作 -
encoding/jsonによる JSON シリアライズ・デシリアライズ -
gopkg.in/yaml.v3などによる YAML -
net/httpによる HTTP クライアント
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
resp, err := http.Get("https://example.com/users/1")
if err != nil {
return err
}
defer resp.Body.Close()
var u User
if err := json.NewDecoder(resp.Body).Decode(&u); err != nil {
return err
}
構造体タグ (json:"id" の部分) は、API のフィールド命名規約と Go の命名規約を橋渡しする重要な仕組みです。
推奨リソース
8. REST API を作る: Gin / Fiber + routing・middleware・validation
CLI で Go に慣れたら、いよいよ Web API です。標準の net/http だけでも書けますが、ルーティングやミドルウェアを楽にするために Gin や Fiber が広く使われています。
最低限押さえたいのは以下です。
- ルーティング (HTTP メソッド + パス → ハンドラ)
- ミドルウェア (認証・ロギング・CORS など)
- リクエストバリデーション (
go-playground/validatorなど) - レスポンスの構造体統一
package main
import "github.com/gin-gonic/gin"
type CreateUserReq struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var req CreateUserReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(201, gin.H{"name": req.Name})
})
r.Run(":8080")
}
推奨リソース
9. 並行処理を一歩進める: worker pool・pipeline・fan-in / fan-out・rate limit
ステップ 4 で基本を押さえたら、実務でよく出てくる並行処理パターンに進みます。
- Worker pool: 決まった数のワーカーにタスクを分配する
-
Pipeline:
stage1 → stage2 → stage3のように channel で繋ぐ - Fan-out / Fan-in: 処理を並列化して結果を集約する
-
Rate limiting:
time.Tickerやgolang.org/x/time/rateで流量を絞る
import "golang.org/x/time/rate"
limiter := rate.NewLimiter(rate.Limit(10), 1) // 10 req/sec
for _, url := range urls {
if err := limiter.Wait(ctx); err != nil {
return err
}
go fetch(url)
}
並行処理を書くときは、必ず context.Context によるキャンセル伝播をセットで考えます。キャンセルできない goroutine は、いつか本番で goroutine リークを起こします。
推奨リソース
10. データベース層: PostgreSQL + GORM もしくは sqlx + コネクションプール
バックエンドである以上、DB は避けて通れません。Go では主に 2 派あります。
どちらを選んでも、コネクションプールの設定 (SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime) と、トランザクション・context タイムアウトの扱いは必ず押さえましょう。
db, err := sql.Open("postgres", dsn)
if err != nil {
return err
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
個人的には、SQL を「書ける・読める」段階に到達してから ORM を検討するのがおすすめです。最初から ORM 任せにすると、N+1 問題や実行計画の話で詰まりやすいです。
推奨リソース
11. テストと品質: unit test・table test・benchmark・integration test
Go のテストは標準の testing パッケージだけでほぼ完結します。追加で入れるとしても stretchr/testify くらいです。
- Unit test: 関数単位の最小テスト
- Table-driven test: 入力と期待値の組を配列で定義して回す (Go の定番スタイル)
-
Benchmark:
go test -bench=.で実行時間を計測 -
Integration test: DB や外部 API を含む結合テスト (
testcontainers-goが便利)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.expected {
t.Errorf("got %d, want %d", got, tt.expected)
}
})
}
}
推奨リソース
12. 本番投入の準備: Docker・ログ・graceful shutdown・設定管理・可観測性
「動くコード」から「本番で壊れないサービス」に進むステップです。ここまで来ると、言語の話よりも運用の話が多くなります。
- Docker: マルチステージビルドで軽量なイメージを作る
-
構造化ログ:
log/slog(Go 1.21+) やzapで JSON ログを吐く -
Graceful shutdown:
SIGTERMを受けたら進行中のリクエストを待ってから終了 -
設定管理: 環境変数ベースが基本 (
envconfig/viper) - 可観測性: OpenTelemetry でメトリクス・トレース・ログを統合
srv := &http.Server{Addr: ":8080", Handler: handler}
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("forced shutdown:", err)
}
推奨リソース
補足ステップ: さらに伸ばすために
有名パッケージのソースコードを読む
ドキュメントだけでは学べない「Go らしい書き方」は、有名 OSS のコードを読むのが一番早いです。おすすめは以下です。
-
net/http(標準ライブラリ) -
gin-gonic/gin(ルーティング・ミドルウェアの実装) -
golang-migrate/migrate(DB マイグレーション) -
spf13/cobra(CLI フレームワーク)
実践プロジェクトを 3 つ作る
知識を定着させるには「自分で最後まで作る」ことが必要です。以下の 3 つは、バックエンドの典型課題を網羅できる良い題材です。
- 認証サービス: JWT・セッション・OAuth — API 設計と状態管理の練習
- バックグラウンドワーカー: キュー (Redis, RabbitMQ) からジョブを取り出して処理 — 並行処理とエラーリトライの練習
- スクレイパー: 複数サイトを並列でクロール — HTTP クライアント・レート制御・永続化の練習
おわりに
ロードマップをひととおり並べてみて、改めて「Go らしさ」は 言語機能のシンプルさ + 並行処理 + 運用のしやすさ の 3 点に集約されるな、と感じました。
個人的にはステップ 4 (goroutine / channel) と 9 (並行処理パターン) を腹落ちさせることが、他言語からの移行組にとっての最大の山場だと思っています。逆にそこを超えてしまえば、残りのステップは「一般的なバックエンド開発を Go でやる」という話に収束します。
読みながら気になったステップから手を動かしていただければと思います。