Goバックエンド開発者の面接質問:実践ガイド

Milad Bonakdar
著者
Goバックエンド面接に向けて、goroutine、channel、context、エラー処理、テスト、HTTPサーバー、システム設計の質問を実践的に確認します。
はじめに
Goバックエンドの面接では、シンプルで信頼できるサービスを作れるかがよく見られます。goroutine と channel、context によるキャンセル、明示的なエラー処理、テスト、HTTPミドルウェア、設計上のトレードオフが中心です。良い回答は、機能の説明だけでなく「いつ使わないか」まで言えます。
このガイドで短く実践的な回答を練習し、それぞれを実際の経験に結びつけてください。作ったAPI、デバッグしたworker pool、修正したrace condition、デプロイ時に安全に停止したサービスなどが良い例です。
Go のコアコンセプト
1. Go は Java や Python などの他の言語と何が異なりますか?
回答: Go は読みやすく保守しやすいシステムコードのために設計されています。面接では次の点を押さえると実務的です。
- シンプルさ: Go は継承やメソッドオーバーロードを避け、合成、小さな interface、明確な package 境界を重視します。
- 並行処理: goroutine、channel、
sync、contextにより、バックエンドの並行処理を明示的に扱えます。 - コンパイル: Go は起動が速くデプロイしやすいネイティブバイナリを生成します。
- 標準ライブラリ:
net/http、context、encoding/json、testingだけでも、多くのバックエンド作業をカバーできます。
希少度: 一般的 難易度: 簡単
2. 配列とスライスの違いを説明してください。
回答:
- 配列: 同じ型の要素の固定サイズのシーケンス。サイズは型の一部です(例:
[5]intは[10]intとは異なります)。これらは値型です。ある配列を別の配列に割り当てると、すべての要素がコピーされます。 - スライス: 基になる配列への動的で柔軟なビュー。配列へのポインタ、長さ、および容量で構成されます。スライスは参照のようなものです。スライスを関数に渡すと、データ全体をコピーせずに、基になる要素を変更できます。
希少度: 一般的 難易度: 簡単
3. Go でのインターフェースの仕組みについて説明してください。暗黙的な実装とは何ですか?
回答: Go のインターフェースは、メソッドシグネチャのコレクションです。
- 暗黙的な実装: Java や C# とは異なり、型はインターフェースを実装することを明示的に宣言しません(
implementsキーワードはありません)。型がインターフェースで宣言されているすべてのメソッドを定義している場合、そのインターフェースを自動的に実装します。 - ダックタイピング: 「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルである。」これにより、定義が実装から分離され、コードの柔軟性が向上し、テストのためにモックするのが簡単になります。
希少度: 一般的 難易度: 普通
4. defer キーワードとは何ですか?また、どのように機能しますか?
回答:
defer は、関数が戻る直前に実行される関数呼び出しをスケジュールします。これは、ファイルのクローズ、mutex のアンロック、またはデータベース接続のクローズなど、リソースのクリーンアップによく使用されます。
- LIFO 順: 遅延呼び出しは、後入れ先出し(Last-In-First-Out)の順序で実行されます。
- 引数の評価: 遅延関数の引数は、呼び出しが実行されるときではなく、
deferステートメントが実行されるときに評価されます。
例:
希少度: 一般的 難易度: 簡単
並行処理
5. Goroutine について説明し、OS スレッドとの違いを説明してください。
回答:
- Goroutine: Go ランタイムによって管理される軽量スレッド。これらは、動的に拡大および縮小する小さなスタック(例:2KB)から始まります。数千の Goroutine を単一の OS スレッドで実行できます。
- OS スレッド: カーネルによって管理され、固定された大きなスタック(例:1MB)を持ち、コンテキストスイッチングはコストがかかります。
- M:N スケジューリング: Go ランタイムは、M 個の Goroutine を N 個の OS スレッドに多重化し、ユーザー空間で効率的にスケジューリングを処理します。
希少度: 非常に一般的 難易度: 普通
6. チャネルとは何ですか?バッファリングされたチャネルとバッファリングされていないチャネルの違いは何ですか?
回答: チャネルは、Goroutine が通信して実行を同期できる型付きのパイプです。
- バッファリングされていないチャネル: 容量がありません。送信操作は、受信側が準備できるまでブロックされます。受信操作も同様です。これらは強力な同期を提供します。
- バッファリングされたチャネル: 容量があります。送信操作は、バッファがいっぱいの場合にのみブロックされます。受信操作は、バッファが空の場合にのみブロックされます。これらは、送信側と受信側をある程度分離します。
希少度: 一般的 難易度: 普通
7. Go での競合状態をどのように処理しますか?
回答: 競合状態は、複数の Goroutine が同時に共有メモリにアクセスし、少なくとも 1 つのアクセスが書き込みである場合に発生します。
- 検出: 組み込みの競合検出器を使用します:
go run -raceまたはgo test -race。 - 防止:
- チャネル: 「メモリを共有することによって通信するのではなく、通信することによってメモリを共有する。」
- Sync パッケージ:
sync.Mutexまたはsync.RWMutexを使用して、クリティカルセクションをロックします。 - アトミック操作:
sync/atomicを単純なカウンタまたはフラグに使用します。
希少度: 一般的 難易度: 難しい
8. select ステートメントは何に使用されますか?
回答:
select ステートメントを使用すると、Goroutine は複数の通信操作を待機できます。いずれかのケースが実行可能になるまでブロックし、そのケースを実行します。複数のケースの準備ができている場合は、ランダムに 1 つを選択します。
- タイムアウト:
time.Afterを使用して実装できます。 - ノンブロッキング操作: 他のケースの準備ができていない場合、
defaultケースは select をノンブロッキングにします。
例:
希少度: 普通 難易度: 普通
エラー処理と堅牢性
9. Go でのエラー処理はどのように機能しますか?
回答:
Go はエラーを値として扱います。関数は、例外をスローする代わりに、error 型(通常は最後の戻り値として)を返します。
- エラーの確認: 呼び出し元は、エラーが
nilであるかどうかを明示的に確認する必要があります。 - カスタムエラー:
errorインターフェース(単一のError() stringメソッドを持つ)を実装することにより、カスタムエラー型を作成できます。 - ラッピング: Go 1.13 では、元のエラーを検査するために(
errors.Isおよびerrors.Asを使用して)保持しながら、コンテキストを追加するためにエラーラッピング(fmt.Errorf("%w", err))が導入されました。
希少度: 一般的 難易度: 簡単
10. Panic と Recover とは何ですか?いつ使用する必要がありますか?
回答:
- Panic: 通常の制御フローを停止し、パニックを開始します。これは例外に似ていますが、回復不能なエラー(例:nil ポインタの逆参照、インデックス範囲外)のために予約する必要があります。
- Recover: パニックになっている Goroutine の制御を取り戻す組み込み関数。これは
defer関数内でのみ役立ちます。 - 使用法: 通常の制御フローには一般的に推奨されません。予想されるエラー状態には
error値を使用します。Panic/recover は、真に例外的な状況、またはクラッシュがサーバー全体をダウンさせるのを防ぐためにライブラリ/フレームワーク内で主に使用されます。
希少度: 普通 難易度: 普通
システム設計とバックエンド
11. Go Web アプリケーションをどのように構成しますか?
回答: Go は構造を強制しませんが、一般的な標準は「Standard Go Project Layout」です。
/cmd: メインアプリケーション(エントリポイント)。/pkg: 外部アプリケーションで使用しても問題ないライブラリコード。/internal: プライベートアプリケーションおよびライブラリコード(Go コンパイラによって強制されます)。/api: OpenAPI/Swagger 仕様、プロトコル定義。/configs: 構成ファイル。- クリーンアーキテクチャ: アプリをテスト可能で保守可能にするために、関心をレイヤー(配信/ハンドラー、ユースケース/サービス、リポジトリ/データ)に分離します。
希少度: 一般的 難易度: 普通
12. context パッケージはどのように機能し、なぜ重要ですか?
回答:
context パッケージは、API境界やgoroutineをまたいで、deadline、キャンセルシグナル、リクエストスコープの値を運びます。
- キャンセル: クライアントが切断したり親処理がキャンセルされたりしたら、下流の処理は
ctx.Done()を監視して停止すべきです。 - タイムアウト: DBクエリや外部API呼び出しには
context.WithTimeoutやcontext.WithDeadlineを使い、返された cancel 関数を多くの場合defer cancel()で呼びます。 - 値: optionalな引数や大きなオブジェクトではなく、リクエストに紐づくメタデータだけを入れます。
希少度: 非常に一般的 難易度: 難しい
13. 依存性注入とは何ですか?また、Go でどのように行われますか?
回答: 依存性注入(DI)は、オブジェクトが依存する他のオブジェクトを受け取る設計パターンです。
- Go では: 通常、インターフェースを介して、データベース接続やロガーなどの依存関係を struct のコンストラクタまたはファクトリ関数に渡すことによって実装されます。
- 利点: コードをよりモジュール化してテスト可能にします(実際の DB をモックと簡単に交換できます)。
- フレームワーク: 手動 DI はシンプルさのために推奨されますが、複雑なグラフには
google/wireやuber-go/digなどのライブラリが存在します。
希少度: 普通 難易度: 普通
データベースとツール
14. Go での JSON の処理方法を教えてください。
回答:
Go は encoding/json パッケージを使用します。
- Struct タグ:
`json:"field_name"`のようなタグを使用して、struct フィールドを JSON キーにマップします。 - Marshal: Go struct を JSON 文字列(バイトスライス)に変換します。
- Unmarshal: JSON データを Go struct に解析します。
- ストリーミング:
json.Decoderとjson.Encoderは、データのストリームを処理するため、大きなペイロードに適しています。
希少度: 一般的 難易度: 簡単
15. よく使用する Go ツールをいくつか教えてください。
回答:
go mod: 依存性管理。go fmt: コードを標準スタイルにフォーマットします。go vet: 疑わしい構造がないかコードを調べます。go test: テストとベンチマークを実行します。pprof: CPU とメモリの使用量を分析するためのプロファイリングツール。delve: Go 用のデバッガ。
希少度: 一般的 難易度: 簡単
高度なトピックとベストプラクティス
16. Go のジェネリクスとは何ですか?また、いつ使用する必要がありますか?
回答: ジェネリクス(Go 1.18 で導入)を使用すると、特定の型ではなく、型のセットで動作する関数とデータ構造を記述できます。
- 型パラメータ: 角括弧
[]を使用して定義されます。例:func Map[K comparable, V any](m map[K]V) ... - 制約: 許可される型のセットを定義するインターフェース(例:
any、comparable)。 - 使用法: generics は、set、helper、collection utility のように、同じアルゴリズムやデータ構造が本当に型に依存しない場合に使います。まず通常のコードで書き、重複が明確になってから型パラメータを追加します。小さな interface の方が振る舞いを表しやすいなら、interface を優先します。
希少度: 一般的 難易度: 中程度
17. Go でのテーブル駆動テストについて説明してください。
回答: テーブル駆動テストは、テストケースが struct のスライス(「テーブル」)として定義される Go で推奨されるパターンです。各 struct には、入力引数と予想される出力が含まれています。
- 利点:
- テストロジックとテストデータの明確な分離。
- 新しいテストケースを簡単に追加できます(テーブルに行を追加するだけです)。
- どの入力が失敗したかを正確に示す明確な失敗メッセージ。
- 例:
希少度: 一般的 難易度: 簡単
18. Go HTTP サーバーのミドルウェアパターンとは何ですか?
回答:
ミドルウェアは、次のハンドラーに制御を渡す前に、事前処理または事後処理ロジックを実行するために http.Handler をラップする関数です。
- シグネチャ:
func(next http.Handler) http.Handler - ユースケース: ロギング、認証、パニックリカバリ、レート制限、CORS。
- チェーニング: ミドルウェアは連鎖させることができます(例:
Log(Auth(Handler)))。
例:
希少度: 非常に一般的 難易度: 普通
19. Go サーバーでグレースフルシャットダウンを実装するにはどうすればよいですか?
回答: グレースフルシャットダウンは、サーバーが新しいリクエストの受け入れを停止しますが、終了する前にアクティブなリクエストの処理を完了することを保証します。
- メカニズム:
os/signalを使用して OS シグナル(SIGINT、SIGTERM)をリッスンします。- クリーンアップウィンドウ(例:5〜10 秒)を許可するために
context.WithTimeoutを作成します。 http.Serverでserver.Shutdown(ctx)を呼び出します。- DB 接続およびその他のリソースを閉じます。
- 重要性: デプロイメント中のデータ損失とクライアントエラーを防ぎます。
希少度: 一般的 難易度: 難しい
20. 通常の map と Mutex の代わりに sync.Map をいつ使用する必要がありますか?
回答:
sync.Map は、標準ライブラリの同時実行セーフな map 実装です。
- ユースケース:
- キャッシュの競合: 特定のキーのエントリが 1 回だけ書き込まれ、何度も読み取られる場合(例:遅延ロードキャッシュ)。
- 互いに素なキー: 複数の Goroutine が互いに素なキーのセットのエントリを読み取り、書き込み、上書きする場合。
- トレードオフ: 一般的なユースケース(頻繁な読み取り/書き込み更新)では、
sync.RWMutexで保護された通常のmapの方が高速であり、(sync.Mapはanyを使用するため)型安全性が向上します。
希少度: まれ 難易度: 難しい


