実用 Go言語

―システム開発の現場で知っておきたいアドバイス

[cover photo]
TOPICS
Programming
発行年月日
PRINT LENGTH
464
ISBN
978-4-87311-969-4
FORMAT
Print PDF EPUB
Ebook
3,960円
Ebookを購入する
Print
3,960円

業務プログラミングの現場でも採用されるようになってきたGo言語。文法はシンプルで学びやすいという特徴を持っていますが、複雑な要件を実現するには、プログラミング言語が提供する構成要素(文法やライブラリ)をさまざまに組み合わせる必要があります。 本書は、そんなGoを使う上でのポイントを単なる文法詳解ではなく「よりGoらしく書くには」「実用的なアプリケーションを書くには」といった観点から紹介します。 構造体やインタフェースの使い方からJSON、CSVファイル、Excel、固定長ファイルの扱い方、またログやテスト、環境構築など現場に即した幅広いトピックについて、「Goらしいプログラムの書き方」をその背景と共に教えてくれる先輩のような書籍です。

関連ファイル

目次

まえがき

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.1 0日時の取り扱い
        1.10.1 日時のtime.Timeの取得
        1.10.2 時間を表すtime.Duration 
        1.10.3 ウェブフロントエンドとのデータの交換 
        1.10.4 翌月を計算するときのはまりどころ 
     1.1 1まとめ

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 あえてオーバーライドを実装する

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 errors.New
        5.1.2 fmt.Errorf 
        5.1.3 独自のエラー型 
        5.1.4 エラーのラップ、アンラップ 
        5.1.5 エラーの色々な比較 
        5.1.6 スタックトレースをどう出すのか 
     5.2 エラーハンドリングの基本テクニック 
        5.2.1 呼び出し元に関数の引数などの情報を付与してエラーを返す 
        5.2.2 ログを出力して処理を継続する 
        5.2.3 リトライを実施する 
        5.2.4 リソースをクローズする 
        5.2.5 複数のエラーをまとめる方法 
     5.3 エラーのチェック忘れを予防する
        5.3.1 kisielk/errcheckの利用
        5.3.2 %w利用箇所の制限

6章 パッケージ、モジュール 
     6.1 プロジェクト構成の事前知識 
        6.1.1 パッケージ 
        6.1.2 パッケージのimportが循環してはいけない
        6.1.3 親パッケージと子パッケージは独立したパッケージ 
        6.1.4 モジュール 
        6.1.5 セマンティックバージョニング
        6.1.6 相対パスでのimportはできない
        6.1.7 internal 
        6.1.8 無視されるフォルダ 
        6.1.9 go gettable
        6.1.10 GOPATH
     6.2 Go Modulesで開発環境のパッケージを管理する
        6.2.1 Go Modulesの概要
     6.3 Goプロジェクトのライフサイクル
     6.4 Goプロジェクトのモジュール内のパッケージ構成 
        6.4.1 最低限のパッケージ構成 
        6.4.2 パッケージを階層化する
        6.4.3 ドメイン/レイヤー vsレイヤー/ドメイン 
        6.4.4 開発時に使うツールの追加 
        6.4.5 まとめ
     6.5 Go Modulesの実践的な使い方 
        6.5.1 1リポジトリ、マルチモジュール構成 
        6.5.2 プライベートリポジトリのモジュールを参照する方法
        6.5.3 フォークしたモジュールを参照する方法 
        6.5.4 モジュールのキャッシュ 
        6.5.5 依存するモジュールの可用性の考慮 
     6.6 静的なプラグイン機構を実現する 
        6.6.1 プラグイン機能の要件 
        6.6.2 静的リンクを使ったプラグイン機構 
     6.7 初期化の順序を制御する 
        6.7.1 パッケージの読み込み順 
        6.7.2 パッケージ内部の初期化の順序

7章 Goプログラミングの環境を整備する
     7.1 エディタとIDE
        7.1.1 Visual Studio Code
        7.1.2 Visual Studio Codeの便利な機能
        7.1.3 GoLand
     7.2 ランタイムのサポート
        7.2.1 Go言語自体のサポート
     7.3 Linterを使ってコードを静的解析する
        7.3.1 go vet
        7.3.2 golangci-lint 
        7.3.3 エディタに組み込む 
     7.4 よく使われるビルドフラグ 
        7.4.1 バイナリサイズを小さくする 
        7.4.2 ビルド時のパスを削除する
        7.4.3 環境変数CGO_ENABLEDでCGOを制御する 
        7.4.4 ビルドのバイナリにバージョン番号を埋め込む
        7.4.5 ビルド時に対象のOS/アーキテクチャを指定する
     7.5 CI(継続的インテグレーション)を導入する
        7.5.1 GitHub Actions
        7.5.2 CircleCI
     7.6 EditorConfigを導入する 
        7.6.1 EditorConfigの導入方法
     7.7 Makefileを導入する 
        7.7.1 Makefileの書き方
     7.8 gitignoreの準備 
     7.9 プロキシのある環境における開発環境の整備 
        7.9.1 プロキシサーバーの設定
        7.9.2 Gitにおける設定 
        7.9.3 環境変数での設定

8章 さまざまなデータフォーマット
     8.1 JSONファイルを扱う 
        8.1.1 基本的なエンコードとデコード
     8.2 CSVファイルを扱う
        8.2.1 CSV形式のファイルを読み込む
        8.2.2 CSV形式でファイルに書き込む
        8.2.3 BOM付きファイルの扱い 
        8.2.4 Shift-JIS(Windows-31J)を扱うには 
        8.2.5 コメントアウトされた行をスキップしたい
        8.2.6 CSV行を構造体で表現する
        8.2.7 encoding/csvの設定をgocarina/gocsvに引き継ぐ
        8.2.8 CSVのエンコード/デコードを拡張する 
        8.2.9 巨大なCSVファイルを扱いたい場合(逐次処理で書き込みたい場合)
        8.2.10 マルチレイアウトCSVを処理したい
     8.3 Microsoft Excelファイルを扱う
        8.3.1 Excelファイルに対する書き込み・読み込み 
        8.3.2 複数レコードを書き込む方法
        8.3.3 Excelファイルを読み取る 
        8.3.4 構造体へのマッピング付きで読み取る 
     8.4 固定長データを扱う 
        8.4.1 固定長データファイルとは 
        8.4.2 標準ライブラリで固定長データを扱う 
        8.4.3 固定長の各カラムを構造体のメンバーで表現したい 
        8.4.4 固定長データに関するさまざまな考慮事項 

9章 Goとリレーショナルデータベース
     9.1 データベースの基本的な利用法 
        9.1.1 データベースに接続する 
        9.1.2 クエリーを発行する 
     9.2 トランザクションを扱うには 
        9.2.1 シンプルに実装する方法 
        9.2.2 deferを使ってロールバックを行う方法 
        9.2.3 トランザクションラッパーでトランザクション制御を実装と分離する方法 
     9.3 コネクションプールのパラメーターをチューニングする 
     9.4 クエリーをキャンセルする
        9.4.1 コンテキスト付きのdatabase/sql関数を使用する 
     9.5 アプリケーションでクエリーをロギングする 
        9.5.1 ドライバーを使ってクエリーをロギング 
        9.5.2 ラッパーのドライバーを使用してクエリーをロギング 
     9.6 大量のデータをバッチインサート 
        9.6.1 プリペアードステートメントを使う 
        9.6.2 バッチインサートを使う 
        9.6.3 データベース組み込みの関数を使う 
        9.6.4 まとめ 
     9.7 共通カラムをうまく扱うには 
        9.7.1 共通カラムでよく利用される項目 
        9.7.2 共通カラムのアプリケーションからの扱い方 
        9.7.3 共通カラムを構造体の埋め込みで扱う 
        9.7.4 設計の方針 
     9.8 データベースアクセスをともなう実装のテスト 
        9.8.1 本物のデータベースを使ったテスト 
        9.8.2 モックを使ったテスト 
     9.9 サードパーティーのライブラリを使ったあれこれ 
        9.9.1 サードパーティーライブラリの分類 
        9.9.2 スキーマドリブンでアプリケーションを開発 
        9.9.3 クエリードリブンでアプリケーションを開発 
        9.9.4 クエリーのロギング 

10章 HTTPサーバー
     10.1 現代のウェブアプリケーションサーバーの役割 
     10.2 ウェブプログラミングの基本
        10.2.1 HTTPサーバーを実装する
        10.2.2 JSONデータの読み書き 
        10.2.3 リクエストのバリデーション 
        10.2.4 必須チェックのハマりどころ
     10.3 HTTPのリクエストのパース 
        10.3.1 クエリーのパース 
        10.3.2 ファイルのアップロードの処理 
     10.4 ルーター
        10.4.1 標準ライブラリのhttp.ServeMux 
        10.4.2 サードパーティー製のルーター 
        10.4.3 まとめ
     10.5 Middlewareパターンを使って処理を分離する
        10.5.1 Middlewareの仕組み 
        10.5.2 ステータスコードのキャプチャ
        10.5.3 ハンドラー内部でのpanicの防御
        10.5.4 DBのトランザクション制御 
        10.5.5 タイムアウト設定 
        10.5.6 レートリミット(速度制限) 
        10.5.7 まとめ
     10.6 シングルページアプリケーションの静的ファイルを配信する 
        10.6.1 フロントエンドの開発 
        10.6.2 バックエンドの作成
     10.7 APIドキュメントを生成する

11章 HTTPクライアント
     11.1 net/httpを使ったHTTPクライアントの基本
        11.1.1 http.Get
        11.1.2 http.Post
        11.1.3 http.Clientを作成してリクエスト
     11.2 RoundTripperインタフェースによって処理を分離する
        11.2.1 RoundTripperでロギングする
        11.2.2 RoundTripperで認証認可
        11.2.3 RoundTripperでリトライ 
     11.3 リトライ時に考慮するべき点 
        11.3.1 すべてをリトライしない
        11.3.2 Exponential backoff
        11.3.3 Retry-Afterヘッダーによる待機時間
        11.3.4 リトライするための待機処理にtime.Sleepを使わない 
     11.4 プロキシサーバーを突破する
        11.4.1 net/httpのプロキシサーバー設定
        11.4.2 SSL証明書エラーがでる場合のプロキシサーバー対応

12章 ログとオブザーバビリティ
     12.1 ログをめぐる、出力の仕組みの変化
     12.2 ログに出力すべき内容を決めるには 
        12.2.1 どんな目的でログを利用するか 
        12.2.2 ログ出力の項目 
        12.2.3 出力頻度 
        12.2.4 ログとセキュリティ事故 
        12.2.5 クラウド時代のログサービス
     12.3 標準ライブラリでログを出力する 
        12.3.1 ユニットテストの中のログ出力 
        12.3.2 ログ出力のカスタマイズ
     12.4 構造化ログを出力する
        12.4.1 zerologの基本とログレベル
        12.4.2 zerologの基本: さまざまな情報 
        12.4.3 ログにエラーコードを埋め込む 
        12.4.4 ウェブサービスのミドルウェアでログを出力する 
     12.5 エラーとログ出力
        12.5.1 log.Fatal()とpanic()の違い 
        12.5.2 これらの関数で強制終了をしても良い場所 
     12.6 net/httpのエラーログをカスタマイズする 
     12.7 分散システムの動作を確認するには
        12.7.1 Instrumentation/Exporter 
        12.7.2 分散トレース

13章 テスト
     13.1 Goのテストの書き方の基礎
     13.2 Table Driven Testを実装する
        13.2.1 Table Driven Testとは
        13.2.2 Table(テストケース) 
        13.2.3 テストの実行 
     13.3 テストに事前事後の処理を追加する 
     13.4 ヘルパー関数
        13.4.1 ヘルパー関数を_test.goというテストコードの中に書く 
        13.4.2 テストヘルパー用のパッケージを作成する 
     13.5 ウェブサーバーのハンドラーをテストする 
        13.5.1 サーバー全体のテスト 
        13.5.2 ハンドラー単体のテスト 
     13.6 テストの落とし穴の回避 
        13.6.1 テストの分割 
        13.6.2 テストの順序依存の排除 
        13.6.3 キャッシュの削除 
        13.6.4 時間がかかるようになったテストへの対処
     13.7 testifyを使う
     13.8 構造体の比較にgo-cmpを使う
        13.8.1 go-cmpを使う理由
        13.8.2 go-cmpのTips 
        13.8.3 まとめ
     13.9 テストが書きにくいものをテストする 
        13.9.1 シンプルな入出力のテスト 
        13.9.2 変換が必要な入出力のテスト 
        13.9.3 さらに複雑な入出力のテスト 
        13.9.4 時刻をともなうテスト 
        13.9.5 まとめ 
     13.1 0品質を保証するテストを実装する 
        13.10.1 ペアワイズ法を使ったテストパターンの考慮もれ防止 
        13.10.2 カバレッジ 
        13.10.3 プロパティベーステスト 
        13.10.4 まとめ
     13.1 1 go testでベンチマークを取る
     13.1 2ドキュメントに出力するExampleをコードに記述する

14章 クラウドとGo
     14.1 コンテナの起動
        14.1.1 コンテナとDocker 
        14.1.2 開発での使い方
        14.1.3 docker-compose.yamlのサンプル
        14.1.4 docker image pullとプロキシ 
        14.1.5 まとめ 
     14.2 コンテナ用イメージの作成 
        14.2.1 Dockerを使ったイメージのビルド
        14.2.2 Cloud Native Buildpacksとkoによるイメージの作成 
        14.2.3 まとめ 
     14.3 クラウドサービスにデプロイする
        14.3.1 Amazon ECSへのデプロイ
        14.3.2 AWS LambdaでWebサーバーを動かす
        14.3.3 Cloud RunでWebサーバーを動かす

15章 クラウドのストレージ 
     15.1 AWS S3
        15.1.1 AWS SDK for Go v2
        15.1.2 Go CDK
     15.2 Amazon DynamoDB
        15.2.1 APIを使ってDynamoDBにアクセスする

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.6.2 errgroup.Group
     16.7 処理の待ち合わせをしたい(Future/Promiseパターン) 
     16.8 ウェブサービスでセッションの情報を共有する 
        16.8.1 子側のコンテキストで追加された情報を親コンテキストで読み取る 
     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 まとめ

あとがき
索引