- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに:2025年(ねん)、なぜGoなのか?
- 1. Go基礎(きそ):型(かた)、構造体(こうぞうたい)、メソッド、ポインタ
- 2. インターフェースとコンポジション
- 3. 並行性(へいこうせい):ゴルーチンとチャネル
- 4. 並行性(へいこうせい)パターン
- 5. エラー処理(しょり)
- 6. ジェネリクス(Go 1.18+)
- 7. テスト
- 8. REST API構築(こうちく)
- 9. gRPCサービス構築(こうちく)
- 10. CLIツール開発(かいはつ)
- 11. プロダクションデプロイ
- 12. Go vs Rust vs Java比較(ひかく)
- 13. 面接(めんせつ)質問(しつもん)15選(せん)
- 14. 実践(じっせん)クイズ
- 参考資料(さんこうしりょう)
はじめに:2025年(ねん)、なぜGoなのか?
Kubernetes、Docker、Terraform、Prometheus、Istio — クラウドネイティブエコシステムの中核(ちゅうかく)ツールはすべてGoで書(か)かれています。2025年(ねん)のStack Overflow調査(ちょうさ)でGoは「最(もっと)も学(まな)びたい言語(げんご)」3位(い)、「最(もっと)も高年収(こうねんしゅう)の言語(げんご)」4位(い)にランクインし、Indeed基準(きじゅん)でGo開発者(かいはつしゃ)の求人(きゅうじん)は前年比(ぜんねんひ)15%増加(ぞうか)しました。
Goが選(えら)ばれる理由(りゆう)は明確(めいかく)です:
- シンプルさ: キーワード25個(こ)、クラスなし、継承(けいしょう)なし
- 並行性(へいこうせい): ゴルーチンとチャネルで数百万(すうひゃくまん)の同時(どうじ)タスク処理(しょり)
- 高速(こうそく)コンパイル: 数百万行(すうひゃくまんぎょう)のコードも数秒(すうびょう)でコンパイル
- 単一(たんいつ)バイナリ: 依存関係(いぞんかんけい)のない静的(せいてき)バイナリでデプロイが極(きわ)めてシンプル
- パフォーマンス: C/C++に近(ちか)い実行速度(じっこうそくど)、Java/Pythonよりメモリ効率的(こうりつてき)
Goを使用する企業とプロジェクト:
┌───────────────────────────────────────────────────────────┐
│ Google - 内部インフラ、gRPC、Kubernetes原著者 │
│ Uber - 高性能ジオフェンシングサービス(Go書換え) │
│ Cloudflare - エッジプロキシ、DNS、Workersランタイム │
│ Twitch - リアルタイムチャット(数百万同時接続) │
│ Docker - コンテナランタイム全体がGo │
│ Terraform - HashiCorp全製品がGo │
│ CockroachDB - 分散SQLデータベース │
│ Dropbox - パフォーマンス重要サービスをPythonからGoへ │
└───────────────────────────────────────────────────────────┘
このガイドでは、Goの基礎(きそ)文法(ぶんぽう)から並行性(へいこうせい)パターン、プロダクションレベルサーバー構築(こうちく)、CLIツール開発(かいはつ)、Docker最適化(さいてきか)まで体系的(たいけいてき)にカバーします。
1. Go基礎(きそ):型(かた)、構造体(こうぞうたい)、メソッド、ポインタ
1.1 基本型(きほんがた)とゼロ値(ち)
Goのすべての変数(へんすう)は宣言時(せんげんじ)にゼロ値(ち)で初期化(しょきか)されます:
package main
import "fmt"
func main() {
// 基本型とゼロ値
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // "" (空文字列)
// 短縮宣言(型推論)
name := "gopher" // string
age := 10 // int
pi := 3.14 // float64
active := true // bool
// 定数
const MaxRetries = 3
const (
StatusOK = 200
StatusError = 500
)
fmt.Println(i, f, b, s, name, age, pi, active)
}
1.2 スライス vs 配列(はいれつ)
配列(はいれつ)は固定(こてい)サイズで、スライスは動的(どうてき)サイズの配列(はいれつ)ビューです:
// 配列: サイズが型の一部
var arr [5]int // [0, 0, 0, 0, 0]
matrix := [2][3]int{{1,2,3}, {4,5,6}}
// スライス: 動的、実務で99%使用
slice := []int{1, 2, 3}
slice = append(slice, 4, 5) // [1, 2, 3, 4, 5]
// makeで作成(長さ、容量)
buf := make([]byte, 0, 1024) // 長さ0、容量1024
// スライシング(元の配列を共有するので注意!)
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2, 3] - 元のビュー
sub[0] = 99 // originalも変更: [1, 99, 3, 4, 5]
// 安全なコピー
copied := make([]int, len(original))
copy(copied, original)
1.3 マップ
// マップ作成
m := map[string]int{
"apple": 5,
"banana": 3,
}
// 存在確認 (comma okパターン)
val, ok := m["cherry"]
if !ok {
fmt.Println("cherry not found")
}
// イテレーション(順序保証なし)
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
// 削除
delete(m, "apple")
1.4 構造体(こうぞうたい)とメソッド
type User struct {
ID int
Name string
Email string
CreatedAt time.Time
}
// 値レシーバーメソッド(読み取り専用)
func (u User) FullName() string {
return u.Name
}
// ポインタレシーバーメソッド(変更可能)
func (u *User) UpdateEmail(email string) {
u.Email = email
}
// コンストラクタパターン(Goにはコンストラクタがないので関数で代替)
func NewUser(name, email string) *User {
return &User{
Name: name,
Email: email,
CreatedAt: time.Now(),
}
}
1.5 ポインタ
Goのポインタはcとは異(こと)なりポインタ演算(えんざん)ができないため安全(あんぜん)です:
func main() {
x := 42
p := &x // アドレス取得
fmt.Println(*p) // デリファレンス: 42
*p = 100
fmt.Println(x) // 100
// nilポインタチェック
var ptr *int
if ptr != nil {
fmt.Println(*ptr)
}
}
// 値渡し vs ポインタ渡し
func doubleValue(n int) { n *= 2 } // 元の値不変
func doublePointer(n *int) { *n *= 2 } // 元の値変更
2. インターフェースとコンポジション
2.1 暗黙的(あんもくてき)インターフェース(ダックタイピング)
GoのインターフェースはJava/C#と異(こと)なり明示的(めいじてき)な宣言(せんげん)が不要(ふよう)です。メソッドを実装(じっそう)すると自動的(じどうてき)にインターフェースを満(み)たします:
// インターフェース定義
type Writer interface {
Write(p []byte) (n int, err error)
}
type Reader interface {
Read(p []byte) (n int, err error)
}
// インターフェース合成
type ReadWriter interface {
Reader
Writer
}
// MyBufferは明示的な宣言なしにWriterを満たす
type MyBuffer struct {
data []byte
}
func (b *MyBuffer) Write(p []byte) (int, error) {
b.data = append(b.data, p...)
return len(p), nil
}
// io.Writerを受け取る関数にMyBufferを渡せる
func SaveToWriter(w Writer, content string) error {
_, err := w.Write([]byte(content))
return err
}
2.2 小(ちい)さなインターフェースの力(ちから)
Go標準(ひょうじゅん)ライブラリの核心(かくしん)哲学(てつがく)は小(ちい)さなインターフェースです:
Go標準ライブラリ コアインターフェース:
┌──────────────────────────────────────────────────┐
│ io.Reader - Read(p []byte) (n, err) │
│ io.Writer - Write(p []byte) (n, err) │
│ io.Closer - Close() error │
│ fmt.Stringer - String() string │
│ error - Error() string │
│ sort.Interface - Len, Less, Swap │
│ http.Handler - ServeHTTP(w, r) │
│ json.Marshaler - MarshalJSON() ([]byte, err) │
└──────────────────────────────────────────────────┘
// 実用例: どのReaderでも処理可能
func CountLines(r io.Reader) (int, error) {
scanner := bufio.NewScanner(r)
count := 0
for scanner.Scan() {
count++
}
return count, scanner.Err()
}
// ファイル、HTTPレスポンス、文字列などすべて可能
lines1, _ := CountLines(os.Stdin)
lines2, _ := CountLines(resp.Body)
lines3, _ := CountLines(strings.NewReader("hello\nworld"))
2.3 構造体(こうぞうたい)エンベディング(コンポジション)
Goは継承(けいしょう)の代(か)わりにエンベディングでコードを再利用(さいりよう)します:
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
// DogはAnimalをエンベッド(継承ではなくコンポジション)
type Dog struct {
Animal // エンベディング
Breed string
}
func main() {
d := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Labrador",
}
fmt.Println(d.Speak()) // Animalのメソッドがプロモート
fmt.Println(d.Name) // Animalのフィールドに直接アクセス
}
3. 並行性(へいこうせい):ゴルーチンとチャネル
3.1 ゴルーチン
ゴルーチンはGoランタイムが管理(かんり)する軽量(けいりょう)スレッドです。OSスレッドが約(やく)1MBスタックなのに対(たい)し、ゴルーチンは約(やく)2KBから開始(かいし)します:
func main() {
// ゴルーチン開始: goキーワードを付けるだけ
go func() {
fmt.Println("Hello from goroutine!")
}()
// 1000個のゴルーチンを同時実行
for i := 0; i < 1000; i++ {
go func(id int) {
fmt.Printf("Worker %d\n", id)
}(i) // iを引数で渡す(クロージャ変数キャプチャに注意)
}
time.Sleep(time.Second)
}
3.2 チャネル
チャネルはゴルーチン間(かん)でデータを安全(あんぜん)に渡(わた)すパイプです:
func main() {
// バッファなしチャネル(同期)
ch := make(chan string)
go func() {
ch <- "hello" // 送信(受信者が来るまでブロック)
}()
msg := <-ch // 受信(送信者が来るまでブロック)
fmt.Println(msg)
// バッファ付きチャネル(非同期)
buffered := make(chan int, 3)
buffered <- 1 // ブロックされない(バッファに余裕)
buffered <- 2
buffered <- 3
// buffered <- 4 // ここでブロック(バッファ満杯)
}
// プロデューサー・コンシューマーパターン
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) // 送るデータがもうない
}
func consumer(ch <-chan int) {
for val := range ch { // closeされるまで受信
fmt.Println(val)
}
}
3.3 Select文(ぶん)
selectは複数(ふくすう)のチャネル操作(そうさ)を同時(どうじ)に待機(たいき)します:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "from ch2"
}()
// 先に準備できたチャネルから受信
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
case <-time.After(3 * time.Second):
fmt.Println("timeout!")
}
}
3.4 syncパッケージ
// WaitGroup: 複数のゴルーチン完了を待機
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d done\n", id)
}(i)
}
wg.Wait() // すべてのゴルーチンが完了するまで待機
}
// Mutex: 共有リソースの保護
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
// Once: 一度だけ実行
var once sync.Once
var instance *Database
func GetDB() *Database {
once.Do(func() {
instance = connectDB()
})
return instance
}
3.5 context.Context
contextはゴルーチンのライフタイムを管理(かんり)し、キャンセルシグナルとタイムアウトを伝播(でんぱ)します:
func fetchURL(ctx context.Context, url string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return "", err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
func main() {
// 3秒タイムアウト
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := fetchURL(ctx, "https://api.example.com/data")
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Request timed out!")
return
}
fmt.Println(result)
}
4. 並行性(へいこうせい)パターン
4.1 ファンアウト / ファンイン
複数(ふくすう)のゴルーチンが作業(さぎょう)を分散処理(ぶんさんしょり)(ファンアウト)し、結果(けっか)を1つにまとめる(ファンイン)パターン:
func fanOut(input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
channels[i] = process(input)
}
return channels
}
func process(input <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range input {
out <- n * n
}
}()
return out
}
func fanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
merged := make(chan int)
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for val := range c {
merged <- val
}
}(ch)
}
go func() {
wg.Wait()
close(merged)
}()
return merged
}
4.2 ワーカープール
func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for job := range jobs {
result := processJob(job)
results <- result
}
}(i)
}
go func() {
wg.Wait()
close(results)
}()
}
4.3 パイプライン
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
// パイプライン組み合わせ
func main() {
nums := generator(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
squares := square(nums)
for result := range squares {
fmt.Println(result)
}
}
4.4 レートリミッターとセマフォ
// レートリミッター
func rateLimitedFetch(urls []string, rps int) {
ticker := time.NewTicker(time.Second / time.Duration(rps))
defer ticker.Stop()
for _, url := range urls {
<-ticker.C // 毎秒rps回だけ実行
go fetch(url)
}
}
// セマフォパターン(同時実行数制限)
func semaphorePattern(tasks []Task, maxConcurrent int) {
sem := make(chan struct{}, maxConcurrent)
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1)
sem <- struct{}{} // スロット獲得
go func(t Task) {
defer wg.Done()
defer func() { <-sem }() // スロット返却
process(t)
}(task)
}
wg.Wait()
}
4.5 errgroupパターン
import "golang.org/x/sync/errgroup"
func fetchAll(ctx context.Context, urls []string) ([]string, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]string, len(urls))
for i, url := range urls {
i, url := i, url
g.Go(func() error {
body, err := fetchURL(ctx, url)
if err != nil {
return fmt.Errorf("fetching %s: %w", url, err)
}
results[i] = body
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
5. エラー処理(しょり)
5.1 基本(きほん)エラーパターン
Goは例外(れいがい)(exception)の代(か)わりに値(あたい)でエラーを返(かえ)します:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func readConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config %s: %w", path, err)
}
return config, nil
}
5.2 センチネルエラーとカスタムエラー
// センチネルエラー(パッケージレベル変数)
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
)
// カスタムエラー型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}
// errors.Is / errors.Asの使用
func handleError(err error) {
if errors.Is(err, ErrNotFound) {
// 404処理
}
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field: %s, Message: %s\n", valErr.Field, valErr.Message)
}
}
5.3 Defer、Panic、Recover
// defer: 関数終了時に逆順で実行(リソースクリーンアップに必須)
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
data, err := io.ReadAll(f)
return string(data), err
}
// panic/recover: 本当に回復不可能な状況でのみ使用
func safeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
fn()
return nil
}
6. ジェネリクス(Go 1.18+)
6.1 基本構文(きほんこうぶん)
func Map[T any, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
6.2 型(かた)制約(せいやく)
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// ジェネリック構造体
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
7. テスト
7.1 テーブル駆動(くどう)テスト
Goの慣用的(かんようてき)テストパターン:
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero", 0, 0, 0},
{"mixed", -1, 5, 4},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d, want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
7.2 HTTPテスト
func TestGetUserHandler(t *testing.T) {
handler := http.HandlerFunc(GetUserHandler)
req := httptest.NewRequest("GET", "/users/123", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
var user User
err := json.NewDecoder(rec.Body).Decode(&user)
require.NoError(t, err)
assert.Equal(t, "123", user.ID)
}
7.3 ベンチマークとファジング
func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(20)
}
}
func FuzzParseJSON(f *testing.F) {
f.Add([]byte(`{"name": "test"}`))
f.Fuzz(func(t *testing.T, data []byte) {
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return
}
_, err := json.Marshal(result)
if err != nil {
t.Errorf("re-marshal failed: %v", err)
}
})
}
go test ./... # 全テスト
go test -v -run TestUser ./pkg/user # 特定テスト
go test -bench=. ./... # ベンチマーク
go test -fuzz=FuzzParseJSON ./... # ファジング
go test -race ./... # レースディテクター
go test -cover ./... # カバレッジ
8. REST API構築(こうちく)
8.1 Chiルーター + ミドルウェア
package main
import (
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
type Server struct {
router *chi.Mux
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
s := &Server{
router: chi.NewRouter(),
db: db,
}
s.routes()
return s
}
func (s *Server) routes() {
s.router.Use(middleware.Logger)
s.router.Use(middleware.Recoverer)
s.router.Use(middleware.Timeout(30 * time.Second))
s.router.Route("/api/v1", func(r chi.Router) {
r.Get("/health", s.handleHealth)
r.Route("/users", func(r chi.Router) {
r.Get("/", s.handleListUsers)
r.Post("/", s.handleCreateUser)
r.Route("/{userID}", func(r chi.Router) {
r.Get("/", s.handleGetUser)
r.Put("/", s.handleUpdateUser)
r.Delete("/", s.handleDeleteUser)
})
})
})
}
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
9. gRPCサービス構築(こうちく)
9.1 Protocol Buffers定義(ていぎ)
syntax = "proto3";
package user.v1;
option go_package = "gen/user/v1;userv1";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc StreamUpdates(StreamUpdatesRequest) returns (stream UserEvent);
}
message User {
string id = 1;
string name = 2;
string email = 3;
int64 created_at = 4;
}
9.2 gRPCサーバー実装(じっそう)
type userServer struct {
userv1.UnimplementedUserServiceServer
repo UserRepository
}
func (s *userServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
user, err := s.repo.GetByID(ctx, req.Id)
if err != nil {
if errors.Is(err, ErrNotFound) {
return nil, status.Error(codes.NotFound, "user not found")
}
return nil, status.Error(codes.Internal, "internal error")
}
return &userv1.GetUserResponse{User: toProtoUser(user)}, nil
}
// サーバーストリーミング
func (s *userServer) StreamUpdates(req *userv1.StreamUpdatesRequest, stream userv1.UserService_StreamUpdatesServer) error {
for event := range s.eventCh {
if err := stream.Send(event); err != nil {
return err
}
}
return nil
}
10. CLIツール開発(かいはつ)
10.1 Cobra + Viper
var rootCmd = &cobra.Command{
Use: "mytool",
Short: "A powerful CLI tool",
}
var deployCmd = &cobra.Command{
Use: "deploy [environment]",
Short: "Deploy application",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
env := args[0]
image, _ := cmd.Flags().GetString("image")
replicas, _ := cmd.Flags().GetInt("replicas")
dryRun, _ := cmd.Flags().GetBool("dry-run")
fmt.Printf("Deploying to %s: image=%s, replicas=%d, dry-run=%v\n",
env, image, replicas, dryRun)
return nil
},
}
func init() {
deployCmd.Flags().StringP("image", "i", "", "Container image (required)")
deployCmd.Flags().IntP("replicas", "r", 1, "Number of replicas")
deployCmd.Flags().Bool("dry-run", false, "Dry run mode")
deployCmd.MarkFlagRequired("image")
rootCmd.AddCommand(deployCmd)
}
11. プロダクションデプロイ
11.1 Dockerマルチステージビルド(5MBバイナリ)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM scratch
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# ビルド結果サイズ比較
# Go scratch: ~5-10MB
# Go alpine: ~15MB
# Node.js: ~200MB+
# Java Spring: ~300MB+
# Python: ~150MB+
11.2 Graceful Shutdown
func main() {
srv := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
go func() {
log.Printf("Server starting on :8080")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down gracefully...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Forced shutdown: %v", err)
}
log.Println("Server stopped")
}
11.3 pprofプロファイリング
import _ "net/http/pprof"
func main() {
go func() {
log.Println(http.ListenAndServe(":6060", nil))
}()
}
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 # CPU
go tool pprof http://localhost:6060/debug/pprof/heap # メモリ
go tool pprof http://localhost:6060/debug/pprof/goroutine # ゴルーチン
12. Go vs Rust vs Java比較(ひかく)
┌─────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ 項目 │ Go │ Rust │ Java │
├─────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 学習曲線 │ 低い(1-2週間) │ 高い(3-6ヶ月) │ 中程度(1-2ヶ月)│
│ コンパイル速度 │ 非常に速い │ 遅い │ 普通 │
│ 実行速度 │ 速い │ 非常に速い │ 速い(JIT) │
│ メモリ管理 │ GC │ 所有権システム │ GC │
│ 並行性 │ ゴルーチン/Chan │ async/tokio │ Virtual Thread │
│ バイナリサイズ │ 5-15MB │ 1-5MB │ 100MB+(JRE) │
│ エコシステム │ クラウド/インフラ │ システム/WASM │ エンタープライズ │
│ エラー処理 │ 値返却 │ Result/Option │ 例外 │
│ ジェネリクス │ 1.18+(基本) │ 強力 │ 完全サポート │
│ 主要用途 │ マイクロサービス │ OS、ゲーム │ 大規模エンタ │
│ │ CLI、DevOps │ エンジン │ プライズ │
└─────────────────┴──────────────────┴──────────────────┴──────────────────┘
Goが適(てき)している場合(ばあい): マイクロサービス、APIサーバー、CLIツール、DevOpsツール Rustが適(てき)している場合(ばあい): システムプログラミング、ゲームエンジン、組込(くみこ)み Javaが適(てき)している場合(ばあい): 大規模(だいきぼ)エンタープライズ、Androidアプリ
13. 面接(めんせつ)質問(しつもん)15選(せん)
基礎(きそ)
-
Goでスライスと配列(はいれつ)の違(ちが)いは? 配列(はいれつ)は固定(こてい)サイズで型(かた)の一部(いちぶ)です(例(れい):
[5]int)。スライスは動的(どうてき)サイズの配列(はいれつ)ビューで、内部的(ないぶてき)にポインタ、長(なが)さ、容量(ようりょう)を持(も)ちます。 -
Goに継承(けいしょう)がない理由(りゆう)は?コード再利用(さいりよう)はどうする? 構造体(こうぞうたい)エンベディング(コンポジション)を使用(しよう)します。「継承(けいしょう)よりコンポジション」の原則(げんそく)を言語(げんご)レベルで強制(きょうせい)します。
-
nilインターフェースとnilポインタを持(も)つインターフェースの違(ちが)いは? インターフェースは(type, value)ペアです。両方(りょうほう)nilならnilインターフェース。typeがあるがvalueがnilならnon-nilインターフェースです。
-
deferの実行(じっこう)順序(じゅんじょ)と注意点(ちゅういてん)は? LIFO(後入先出(あといれさきだし))順(じゅん)で実行(じっこう)されます。defer時点(じてん)の引数値(ひきすうち)がキャプチャされるため、ループでのdefer使用(しよう)に注意(ちゅうい)が必要(ひつよう)です。
-
Goのゼロ値(ち)哲学(てつがく)を説明(せつめい)してください。 すべての変数(へんすう)が宣言時(せんげんじ)に有効(ゆうこう)なデフォルト値(ち)を持(も)つため、初期化(しょきか)されていない変数(へんすう)のバグを防(ふせ)ぎます。
並行性(へいこうせい)
-
ゴルーチンがOSスレッドより軽(かる)い理由(りゆう)は? ゴルーチンは約(やく)2KBスタックで開始(かいし)(OSスレッドは1MB+)、GoランタイムのM:Nスケジューラが数千(すうせん)のゴルーチンを少数(しょうすう)のOSスレッドにマッピングします。
-
バッファチャネルとアンバッファチャネルの違(ちが)いは? アンバッファチャネルは送信(そうしん)と受信(じゅしん)が同期的(どうきてき)に会(あ)う必要(ひつよう)があります。バッファチャネルは容量(ようりょう)分(ぶん)だけ非同期(ひどうき)送信(そうしん)可能(かのう)です。
-
ゴルーチンリークが発生(はっせい)する状況(じょうきょう)と防止法(ぼうしほう)は? チャネルに送受信(そうじゅしん)する相手(あいて)がいない場合(ばあい)や、contextキャンセルを処理(しょり)しない場合(ばあい)に発生(はっせい)します。context.WithCancel/Timeoutの使用(しよう)が鍵(かぎ)です。
-
デッドロックの条件(じょうけん)とGoでの検出方法(けんしゅつほうほう)は? Goランタイムがすべてのゴルーチンがブロックされたことを検出(けんしゅつ)してpanicします。go vetとrace detectorも役立(やくだ)ちます。
-
sync.Mutex vs sync.RWMutexの使(つか)い分(わ)け基準(きじゅん)は? 読(よ)み取(と)りが書(か)き込(こ)みよりはるかに多(おお)い場合(ばあい)はRWMutexが有利(ゆうり)です。複数(ふくすう)のゴルーチンが同時(どうじ)に読(よ)み取(と)れるためです。
上級(じょうきゅう)
-
context.Contextの主要(しゅよう)な用途(ようと)3つは? キャンセル伝播(でんぱ)(Cancel)、タイムアウト(Timeout/Deadline)、値渡(あたいわた)し(WithValue)。HTTPハンドラーとDB呼出(よびだ)しに常(つね)に使用(しよう)すべきです。
-
GoのGC特性(とくせい)は? 並行(へいこう)マーク&スイープGCを使用(しよう)し、STW時間(じかん)が1ms未満(みまん)です。GOGC環境変数(かんきょうへんすう)で調整可能(ちょうせいかのう)です。
-
ジェネリクス導入前後(どうにゅうぜんご)のコードの違(ちが)いは? 導入前(どうにゅうまえ):interfaceと型(かた)アサーションまたはコード生成(せいせい)が必要(ひつよう)。導入後(どうにゅうご):型(かた)パラメータでコンパイル時(じ)型安全性(かたあんぜんせい)を提供(ていきょう)。
-
Goでの依存性注入(いぞんせいちゅうにゅう)(DI)はどうする? フレームワークなしでコンストラクタ関数(かんすう)を通(つう)じてインターフェースを注入(ちゅうにゅう)します。wire(Google)、fx(Uber)などのDIフレームワークもあります。
-
Goモジュールのindirect依存関係管理(いぞんかんけいかんり)は? go.modのindirectコメントは直接(ちょくせつ)インポートしない依存関係(いぞんかんけい)です。
go mod tidyで整理(せいり)し、go mod vendorでベンダリングできます。
14. 実践(じっせん)クイズ
Q1: このコードの出力(しゅつりょく)は?
func main() {
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second)
}
答(こた)え: Go 1.22以前(いぜん)ではほとんど3, 3, 3が出力(しゅつりょく)されます。クロージャがループ変数(へんすう)iを参照(さんしょう)し、ゴルーチン実行時(じっこうじ)にはすでにループが終了(しゅうりょう)してi=3です。Go 1.22からは各(かく)反復(はんぷく)でループ変数(へんすう)が新(あたら)しく作成(さくせい)され、0, 1, 2(順序(じゅんじょ)不定(ふてい))が出力(しゅつりょく)されます。
Q2: このコードでデッドロックが発生(はっせい)する理由(りゆう)は?
func main() {
ch := make(chan int)
ch <- 42
fmt.Println(<-ch)
}
答(こた)え: アンバッファチャネルへの送信(そうしん)は受信者(じゅしんしゃ)が来(く)るまでブロックされます。メインゴルーチンがch <- 42でブロックされ、<-chに到達(とうたつ)できないためデッドロックです。解決策(かいけつさく):別(べつ)のゴルーチンで送信(そうしん)するかバッファチャネルを使用(しよう)。
Q3: nilインターフェース問題(もんだい) - なぜerr != nilがtrueなのか?
type MyError struct{}
func (e *MyError) Error() string { return "error" }
func doSomething() error {
var err *MyError = nil
return err
}
func main() {
err := doSomething()
fmt.Println(err == nil) // false!
}
答(こた)え: doSomething()はnilポインタを持(も)つ*MyError型(がた)のインターフェースを返(かえ)します。インターフェースは(type=*MyError, value=nil)なのでnilではありません。解決策(かいけつさく):関数(かんすう)から明示的(めいじてき)にreturn nilを返(かえ)します。
Q4: WaitGroup使用時(しようじ)のよくある間違(まちが)いは?
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
go func(id int) {
wg.Add(1) // 間違った位置!
defer wg.Done()
fmt.Println(id)
}(i)
}
wg.Wait()
}
答(こた)え: wg.Add(1)がゴルーチン内(ない)にあるため、wg.Wait()がどのAddよりも先(さき)に実行(じっこう)される可能性(かのうせい)があります。正(ただ)しい方法(ほうほう)はゴルーチン開始前(かいしまえ)にwg.Add(1)を呼(よ)び出(だ)すことです。
Q5: contextキャンセルが伝播(でんぱ)する仕組(しく)みを説明(せつめい)してください。
答(こた)え: context.WithCancelは親(おや)コンテキストのDoneチャネルを監視(かんし)する子(こ)コンテキストを作成(さくせい)します。親(おや)がキャンセルされると子(こ)のDoneチャネルも閉(と)じられます。このチェーンは最上位(さいじょうい)まで伝播(でんぱ)し、1つのキャンセルでリクエストツリー全体(ぜんたい)をクリーンアップできます。ゴルーチンはselect文(ぶん)でctx.Done()を確認(かくにん)して適切(てきせつ)に終了(しゅうりょう)すべきです。
参考資料(さんこうしりょう)
- Go公式ドキュメント
- Effective Go
- Go by Example
- Go Concurrency Patterns (Rob Pike)
- Advanced Go Concurrency Patterns
- The Go Programming Language (Donovan & Kernighan)
- Concurrency in Go (Katherine Cox-Buday)
- Go Wiki: Table Driven Tests
- Go Wiki: Code Review Comments
- Uber Go Style Guide
- Go Module Reference
- Go Generics Tutorial
- pprofガイド
- Chi Router
- gRPC Go公式ガイド
- Cobra CLIライブラリ
- BubbleTea TUIフレームワーク