文法はシンプルで学びやすいという特徴を持つGo言語。システム開発の現場でも使われる機会が増えていますが、複雑な要件を実現するにはプログラミング言語が提供するシンプルな構成要素(文法やライブラリ)を、さまざまに組み合わせます。 本書は「よりGoらしく書くには」「実用的なアプリケーションを書くには」といった観点からGoを使うポイントを紹介します。 構造体やインタフェース、またJSON、CSVファイル、Excel、固定長ファイルの扱い方、ログやテスト、環境構築など、また初版の発行後、Goに追加されたジェネリクスについても紹介。現場に即した幅広いトピックについて、「Goらしいプログラムの書き方」を、その背景と共に教えてくれる先輩のような書籍です。
実用 Go言語 第2版
―システム開発の現場で知っておきたいアドバイス
渋川 よしき、辻 大志郎、真野 隼記、後藤 玲雄 著
- TOPICS
- Programming
- 発行年月日
- 2025年12月
- PRINT LENGTH
- 496
- ISBN
- 978-4-8144-0136-9
- FORMAT
目次
まえがき
1章 「Goらしさ」に触れる
1.1 変数やパッケージ、メソッドなどに名前を付けるには
1.1.1 変数名
1.1.2 パッケージ名
1.1.3 インタフェース名
1.1.4 レシーバー名
1.2 定数の使い方
1.2.1 型のない定数を定義する
1.2.2 型付きconst変数を定義する
1.2.3 他言語との違い
1.2.4 定数でerror型のインスタンスを提供する
1.3 iotaを用いて列挙型を実現する
1.3.1 それぞれ、ユニークな値を持つ定数を定義
1.3.2 iotaの挙動
1.3.3 組み合わせてフラグとして利用する定数の実現
1.3.4 iotaを使うべきでないとき
1.3.5 文字列として出力可能にする
1.4 Goのエラーを扱う
1.4.1 Goのエラーは値
1.4.2 panicは使わない
1.4.3 Goのエラーハンドリング
1.4.4 他の言語との違い
1.5 変数は短縮形式:=とvarのどちらを使うべきか
1.6 関数のオプション引数
1.6.1 別名の関数によるオプション引数
1.6.2 構造体を利用したオプション引数
1.6.3 ビルダーを利用したオプション引数
1.6.4 Functional Optionパターンを使ったオプション引数
1.6.5 どの実装方法を選択すべきか
1.7 プログラムを制御する引数
1.7.1 コマンドライン引数
1.7.2 環境変数
1.8 メモリ起因のパフォーマンス低下を解消する
1.8.1 スライスのメモリ確保を高速化する
1.8.2 マップのメモリ確保を高速化する
1.8.3 deferの落とし穴
1.9 文字列の結合方法
1.10 日時の取り扱い
1.10.1 日時のtime.Timeの取得
1.10.2 時間を表すtime.Duration
1.10.3 ウェブフロントエンドとのデータの交換
1.10.4 翌月を計算するときのはまりどころ
1.11 まとめ
2章 定義型
2.1 型を定義してデータの不整合を防止する
2.1.1 Goにおける型の定義
2.2 既存のデータ型を拡張する
2.2.1 メソッドを追加して組み込み型を拡張する
2.2.2 拡張した組み込み型で、元となる基底型の挙動を利用する
2.2.3 ファクトリー関数を用意して定義した型を使いやすくする
2.3 定義型を作成してアプリケーションドメインに対応する
2.3.1 スライスへの型定義
2.3.2 値への型定義
2.3.3 列挙への型定義
2.3.4 構造体への型定義
2.4 型の変換
2.4.1 型変換(type conversion)によって型をキャストする
2.5 機密情報を扱うフィールドを定義して出力書式をカスタマイズする
2.5.1 実装方法
3章 構造体
3.1 構造体の基本的な使い方
3.2 構造体をインスタンス化する3つの方法
3.2.1 ファクトリー関数
3.3 構造体にメソッドを定義する
3.3.1 値レシーバーとポインターレシーバーのどちらを使えば良いか
3.3.2 レシーバーはnilでもメソッドは呼べる
3.3.3 インスタンスからメソッドを取り出して関数型として使う
3.3.4 クロージャを使ってメソッドを再現する
3.3.5 ジェネリクスとメソッド
3.4 構造体の埋め込みで共通部分を使い回す
3.5 タグを使って構造体にメタデータを埋め込む
3.5.1 タグの記法
3.5.2 タグを使った構造体へのデータの書き込み
3.5.3 タグを使った構造体からのデータの読み込み
3.5.4 タグのまとめ
3.6 構造体を設計するポイント
3.6.1 ポインター型として扱う必要があるケース
3.6.2 値として扱える場合
3.6.3 ミュータブルな構造体とイミュータブルな構造体
3.6.4 ゼロ値の動作を保証するかどうか
3.6.5 実装方法を選択するポイント
3.7 空の構造体を使ってゴルーチン間での通知を行う
3.8 構造体のメモリ割り当てを高速化する
3.9 構造体とオブジェクト指向のクラスの違いを知る
3.9.1 構造体の用途
3.9.2 構造体の埋め込みは継承ではない
3.9.3 テンプレートメソッドパターンではなく、ストラテジーパターン
3.9.4 あえてオーバーライドを実装する
3.9.5 そもそも全員が共通して納得するオブジェクト指向の定義はない
4章 インタフェース
4.1 柔軟なコードを書くインタフェースの利用法
4.1.1 まとめ
4.2 あらゆる型のデータを格納するany
4.3 インタフェースのキャスト
4.3.1 型アサーション・型スイッチの基本の書き方
4.3.2 他のメソッドを持っているかの問い合わせ
4.3.3 スライスのキャスト
4.4 インタフェースの合成
4.5 実装を切り替えるためのさまざまな方法
4.5.1 基本の分岐
4.5.2 中間の方法
4.5.3 インタフェース利用と設計のポイント
4.5.4 まとめ
5章 ジェネリクス
5.1 単独の値に対するジェネリクス
5.1.1 min/max
5.1.2 cmpパッケージ
5.1.3 まとめ
5.2 スライスに対する操作
5.2.1 ソート
5.2.2 探索
5.2.3 比較・状態を調べる
5.2.4 スライスをインライン加工する
5.2.5 コピーする
5.2.6 まとめ
5.3 マップに対する操作
5.3.1 同一性の比較
5.3.2 加工
5.3.3 コピー
5.3.4 ジェネリクスを使った関数の利用
5.3.5 まとめ
5.4 イテレーター
5.4.1 イテレーターを利用する
5.4.2 イテレーターを実装する
5.4.3 まとめ
5.5 ジェネリクスの定義
5.5.1 ジェネリクスのメリット
5.5.2 実装方法
5.5.3 型推論
5.5.4 関数以外のジェネリクス
5.5.5 言語ごとの機能の違い
5.5.6 まとめ
6章 エラーハンドリング
6.1 エラーの書き方
6.1.1 errors.Newを使ったシンプルなエラー
6.1.2 fmt.Errorfを使ったフォーマット付きエラー
6.1.3 独自のエラー型
6.2 エラーのラップとアンラップ
6.2.1 エラーのラップ
6.2.2 エラーのアンラップ
6.2.3 カスタムエラー型のラップとアンラップ
6.2.4 エラーメッセージのベストプラクティス
6.2.5 エラーの色々な比較
6.3 エラーハンドリングの基本テクニック
6.3.1 呼び出し元に関数の引数などの情報を付与してエラーを返す
6.3.2 ログを出力して処理を継続する
6.3.3 リトライを実施する
6.3.4 リソースをクローズする
6.3.5 複数のエラーをまとめる方法
6.4 ライブラリとアプリケーションのエラー設計
6.4.1 ライブラリを実装する場合にエラー処理で注意する点ポイント
6.4.2 アプリケーションを実装する場合にエラー処理で注意するポイント
6.5 スタックトレース
6.5.1 スタックトレースの出し方
6.5.2 例外処理とスタックトレースの注意点
6.6 エラーのチェック忘れを予防する
6.6.1 kisielk/errcheckの利用
6.6.2 %w利用箇所の制限
7章 パッケージ、モジュール
7.1 プロジェクト構成の事前知識
7.1.1 パッケージ
7.1.2 パッケージのimportが循環してはいけない
7.1.3 親パッケージと子パッケージは独立したパッケージ
7.1.4 モジュール
7.1.5 セマンティックバージョニング
7.1.6 相対パスでのimportはできない
7.1.7 internal
7.1.8 無視されるフォルダ
7.1.9 go gettable
7.1.10 GOPATH
7.2 Go Modulesで開発環境のパッケージを管理する
7.2.1 Go Modulesの概要
7.3 Goプロジェクトのライフサイクル
7.4 Goプロジェクトのモジュール内のパッケージ構成
7.4.1 最低限のパッケージ構成
7.4.2 パッケージを階層化する
7.4.3 ドメイン/レイヤー vs レイヤー/ドメイン
7.4.4 開発時に使うツールの追加
7.4.5 まとめ
7.5 Go Modulesの実践的な使い方
7.5.1 複数のモジュールを同時に扱う
7.5.2 プライベートリポジトリのモジュールを参照する方法
7.5.3 フォークしたモジュールを参照する方法
7.5.4 モジュールのキャッシュ
7.5.5 依存するモジュールの可用性の考慮
7.6 静的なプラグイン機構を実現する
7.6.1 プラグイン機構の要件
7.6.2 静的リンクを使ったプラグイン機構
7.7 初期化の順序を制御する
7.7.1 パッケージの読み込み順
7.7.2 パッケージ内部の初期化の順序
8章 Goプログラミングの環境を整備する
8.1 エディタとIDE
8.1.1 Visual Studio Code
8.1.2 VS Codeの便利な機能
8.1.3 GoLand
8.2 ランタイムのサポート
8.2.1 Go言語自体のサポート
8.3 Linterを使ってコードを静的解析する
8.3.1 go vet
8.3.2 golangci-lint
8.3.3 エディタに組み込む
8.4 よく使われるビルドフラグ
8.4.1 バイナリサイズを小さくする
8.4.2 ビルド時のパスを削除する
8.4.3 環境変数CGO_ENABLEDでCGOを制御する
8.4.4 ビルドのバイナリにバージョン番号を埋め込む
8.4.5 ビルド時に対象のOS/アーキテクチャを指定する
8.5 CI(継続的インテグレーション)を導入する
8.5.1 GitHub Actions
8.6 フォーマッター
8.6.1 公式のフォーマッター
8.6.2 サードパーティー製のフォーマッター
8.6.3 EditorConfigを導入する
8.7 Makefileを導入する
8.7.1 Makefileの書き方
8.8 .gitignoreの準備
8.9 プロキシのある環境における開発環境の整備
8.9.1 プロキシサーバーの設定
8.9.2 Gitにおける設定
8.9.3 環境変数での設定
9章 さまざまなデータフォーマット
9.1 JSONファイルを扱う
9.1.1 基本的なエンコードとデコード
9.2 CSVファイルを扱う
9.2.1 CSV形式のファイルを読み込む
9.2.2 CSV形式でファイルに書き込む
9.2.3 BOM付きファイルの扱い
9.2.4 Shift-JIS(Windows-31J)を扱うには
9.2.5 コメントアウトされた行をスキップしたい
9.2.6 CSV行を構造体で表現する
9.2.7 encoding/csvの設定をgocarina/gocsvに引き継ぐ
9.2.8 CSVのエンコード/デコードを拡張する
9.2.9 巨大なCSVファイルを扱いたい場合(逐次処理で書き込みたい場合)
9.2.10 マルチレイアウトCSVを処理したい
9.3 Microsoft Excelファイルを扱う
9.3.1 Excelファイルに対する書き込み・読み込み
9.3.2 複数レコードを書き込む方法
9.3.3 Excelファイルを読み取る
9.3.4 構造体へのマッピング付きで読み取る
9.4 固定長データを扱う
9.4.1 固定長データファイルとは
9.4.2 標準ライブラリで固定長データを扱う
9.4.3 固定長の各カラムを構造体のメンバーで表現したい
9.4.4 固定長データに関するさまざまな考慮事項
10章 Goとリレーショナルデータベース
10.1 データベースの基本的な利用法
10.1.1 データベースに接続する
10.1.2 クエリーを発行する
10.2 トランザクションを扱うには
10.2.1 シンプルに実装する方法
10.2.2 deferを使ってロールバックを行う方法
10.2.3 トランザクションラッパーでトランザクション制御を実装と分離する方法
10.3 コネクションプールのパラメーターをチューニングする
10.4 クエリーをキャンセルする
10.4.1 コンテキスト付きのdatabase/sql関数を使用する
10.5 アプリケーションでクエリーをロギングする
10.5.1 ドライバーを使ってクエリーをロギング
10.5.2 ラッパーのドライバーを使用してクエリーをロギング
10.6 大量のデータをバッチインサート
10.6.1 プリペアードステートメントを使う
10.6.2 バッチインサートを使う
10.6.3 データベース組み込みの関数を使う
10.6.4 まとめ
10.7 共通カラムをうまく扱うには
10.7.1 共通カラムでよく利用される 項目
10.7.2 共通カラムのアプリケーションからの扱い方
10.7.3 共通カラムを構造体の埋め込みで扱う
10.7.4 設計の方針
10.8 データベースアクセスをともなう実装のテスト
10.8.1 Dockerを使ったテスト
10.8.2 Testcontainersを使ったテスト
10.8.3 モックを使ったテスト
10.9 サードパーティーのライブラリを使ったあれこれ
10.9.1 サードパーティーライブラリの分類
10.9.2 スキーマドリブンでアプリケーションを開発
10.9.3 クエリードリブンでアプリケーションを開発
10.9.4 クエリーのロギング
11章 HTTPサーバー
11.1 現代のウェブアプリケーションサーバーの役割
11.2 ウェブプログラミングの基本
11.2.1 HTTPサーバーを実装する
11.2.2 ルーター
11.2.3 JSONデータの読み書き
11.2.4 リクエストのバリデーション
11.2.5 必須チェックのハマりどころ
11.3 HTTPのリクエストのパース
11.3.1 クエリーのパース
11.3.2 ファイルのアップロードの処理
11.4 Middlewareパターンを使って処理を分離する
11.4.1 Middlewareの仕組み
11.4.2 ステータスコードのキャプチャ
11.4.3 ハンドラー内部でのpanicの防御
11.4.4 DBのトランザクション制御
11.4.5 タイムアウト設定
11.4.6 レートリミット(速度制限)
11.4.7 まとめ
11.5 シングルページアプリケーションの静的ファイルを配信する
11.5.1 フロントエンドの開発
11.5.2 バックエンドの作成
11.6 APIドキュメントを生成する
11.7 上級のトピック
11.7.1 グレースフルシャットダウン
11.7.2 クライアントからの切断を知る
12章 HTTPクライアント
12.1 net/httpを使ったHTTPクライアントの基本
12.1.1 http.Get
12.1.2 http.Post
12.1.3 http.Clientを作成してリクエスト
12.2 RoundTripperインタフェースによって処理を分離する
12.2.1 RoundTripperでロギングする
12.2.2 RoundTripperで認証認可
12.2.3 RoundTripperでリトライ
12.3 リトライ時に考慮するべき点
12.3.1 すべてをリトライしない
12.3.2 Exponential backoff
12.3.3 Retry-Afterヘッダーによる待機時間
12.3.4 リトライするための待機処理にtime.Sleep()を使わない
12.4 プロキシサーバーを突破する
12.4.1 net/httpのプロキシサーバー設定
12.4.2 SSL証明書エラーが出る場合のプロキシサーバー対応
13章 ログとオブザーバビリティ
13.1 ログをめぐる、出力の仕組みの変化
13.2 ログに出力すべき内容を決めるには
13.2.1 どんな目的でログを利用するか
13.2.2 ログ出力の 項目
13.2.3 出力頻度
13.2.4 ログとセキュリティ事故
13.2.5 クラウド時代のログサービス
13.3 標準ライブラリでログを出力する
13.3.1 ユニットテストの中のログ出力
13.3.2 ログ出力のカスタマイズ
13.4 構造化ログを出力する
13.4.1 log/slogの基本とログレベル
13.4.2 属性をカスタマイズする
13.4.3 さまざまな情報を付与する
13.4.4 ウェブサービスのミドルウェアでログを出力する
13.5 エラーとログ出力
13.5.1 log.Fatal()とpanic()の違い
13.5.2 これらの関数で強制終了をしても良い場所
13.6 net/httpのエラーログをカスタマイズする
13.7 分散システムの動作を確認するには
13.7.1 Instrumentation/Exporter
13.7.2 分散トレース
14章 テスト
14.1 もっとも基本的なテストの書き方
14.2 Table Driven Testを実装する
14.2.1 Table Driven Test とは
14.2.2 Table(テストケース)
14.2.3 テストの実行
14.2.4 テストの評価
14.3 テストに実行前後の処理を追加する
14.4 ヘルパー関数
14.4.1 ヘルパー関数を_test.goというテストコードの中に書く
14.4.2 テストヘルパー用のパッケージを作成する
14.5 ウェブサーバーのハンドラーをテストする
14.5.1 サーバー全体のテスト
14.5.2 ハンドラー単体のテスト
14.6 テストの落とし穴の回避
14.6.1 テストの分割
14.6.2 テストの順序依存の排除
14.6.3 キャッシュの削除
14.6.4 時間がかかるようになったテストへの対処
14.7 モックとの付き合い方
14.8 testifyを使う
14.9 構造体の比較にgo-cmpを使う
14.9.1 go-cmpを使う理由
14.9.2 go-cmpのTips
14.9.3 まとめ
14.10 テストが書きにくいものをテストする
14.10.1 シンプルな入出力のテスト
14.10.2 変換が必要な入出力のテスト
14.10.3 さらに複雑な入出力のテスト
14.10.4 時刻をともなうテスト
14.10.5 まとめ
14.11 品質を保証するテストを実装する
14.11.1 ペアワイズ法を使ったテストパターンの考慮もれ防止
14.11.2 カバレッジ
14.11.3 プロパティベーステスト
14.11.4 まとめ
14.12 go testでベンチマークを取る
14.13 ドキュメントに出力するExampleをコードに記述する
15章 クラウドとGo
15.1 コンテナの起動
15.1.1 コンテナとDocker
15.1.2 開発におけるコンテナの活用
15.1.3 compose.yamlのサンプル
15.1.4 docker image pullとプロキシ
15.1.5 まとめ
15.2 コンテナ用イメージの作成
15.2.1 Dockerを使ったイメージのビルド
15.2.2 Cloud Native Buildpacksとkoによるイメージの作成
15.2.3 まとめ
15.3 クラウドサービスにデプロイする
15.3.1 Amazon ECSへのデプロイ
15.3.2 AWS LambdaでWebサーバーを動かす
15.3.3 Cloud RunサービスでWebサーバーを動かす
16章 エンタープライズなGoアプリケーションと並行処理
16.1 並行処理の基本を知る
16.1.1 ゴルーチン
16.1.2 チャネル
16.1.3 Goの並行処理の内部実装
16.2 同時に動作するスレッドを1つに制限する
16.2.1 ゴルーチンセーフ
16.3 ゴルーチンプールを使った複数タスクの実行
16.3.1 タスクの分量がわからない場合
16.3.2 あらかじめタスクの分量がわかっている場合
16.3.3 処理速度を制限する
16.3.4 ボトルネックと、バッファー付きチャネルの効能
16.4 チャネルのブロッキングを中断する(何も起きていないことを検知する)
16.4.1 受信の方法
16.5 ゴルーチン間のイベント伝達
16.5.1 Goにおける例外処理:コンテキスト
16.5.2 context.Contextを使ったエラー通知
16.6 処理の分岐と待ち合わせをしたい(ファンアウト・ファンイン)
16.6.1 sync.WaitGroup
16.7 処理の待ち合わせをしたい(Future/Promiseパターン)
16.8 ウェブサービスでセッションの情報を共有する
16.8.1 子側のコンテキストで追加された情報を親コンテキストで読み取る
16.8.2 まとめ
16.9 ゴルーチンリークの見つけ方
16.9.1 ループによるリーク
16.9.2 ループ以外のチャネル待ちによるリーク
16.9.3 テストで検知する
付録A 駆け足で学ぶGoの基礎
A.1 Hello World
A.2 リテラル・変数宣言
A.2.1 シャドーイング
A.3 名前
A.4 コメント
A.5 型と変換
A.6 ポインター
A.7 ゼロ値
A.8 スライス
A.9 マップ
A.10 制御構文
A.10.1 if
A.10.2 forループ
A.10.3 switch
A.11 関数
A.11.1 deferで後処理の関数の実行予約
A.12 エラー処理
A.13 構造体
A.14 ライブラリのインポート
A.15 ウェブアプリケーション
A.16 まとめ
付録B Goの最新情報を知るための情報源
B.1 信頼できる情報源
B.2 Goの学習に使える教材
B.3 GoDocの読み方
B.4 まとめ
あとがき
索 引