スポンサーリンク
概要
「みんなのGo言語 改訂2版」を読んだので感想を書こうと思います。
以前紹介した「Go言語ハンズオン」に引き続きGo言語関連の書籍の紹介です。
技術書のセールとおすすめ書籍を紹介しています。合わせてご覧ください。
スポンサーリンク
書籍概要
どんな本?
2016年に刊行し好評いただいた「みんなのGo言語」の改訂版です。全章を最新の内容にアップデートし、「第7章 データベースの扱い方」を新規で書き下ろします。ますます注目の集まるGoを現場で使うためのノウハウが1冊に凝縮されています!
(こんな方におすすめ)
・Go言語を使ってみたい方
・Go言語に携わることなった方
- 松木雅幸,mattn,藤原俊一郎,中島大一,上田拓也,牧大輔,鈴木健太 著
- 2019年08月 発行
- 224ページ
- 定価2,398円(税込)
スポンサーリンク
スポンサーリンク
内容のまとめと感想
良かった点
幅広い内容
複数の著者が特定のテーマで解説を行うオムニバス的な内容になっています。
テーマ自体は幅広く、コードの書き方というよりもプラクティス的な内容が多いので、一般的な入門書等で説明されない内容が多いです。
言語の説明に特化している入門書だとこういったプラクティスなどに関してはあまり書かれていない事が多いので、参考になります。
ある程度入門書で基本的なGo言語の書き方をマスターした人が次に読むと参考になるかと思います。
コンパクトで読みやすい
各章とも基本的に説明はコンパクトで、読みやすくなっているので、サラッと読む事ができます。
(この後の気になった点に書いていますが、逆に深い部分は自分で調べたりする必要があります。)
気になった点
全体的に説明や内容は触りだけ
章にもよりますが、全般的に内容は概要レベルのものが多く、より詳細な説明や機能に関しては別途自分で調べる必要があります。
説明が結構あっさりしているので、ピンとこなければググって詳細を調べてみるといった使い方が必要です。
スポンサーリンク
読書ノート(個人的なまとめ)
第1章 Goによるチーム開発のはじめ方とコードを書く上での心得
第1章らしく、Goの環境のセットアップからコードの書き方まで説明する入門的な説明が中心の章です。
Go自体のコードの書き方は殆どなく、環境や設定に関する内容がメインのため先にGo言語ある程度書いた事がある上で読んだ方が良いと思います。
Go Moduleに関してもしっかりとカバーしていて、助かります。
Go標準でフォーマッタやリンターが用意されていて、別のものを用意しなくて良いというのはシンプルで良いなと思いました。
Goにおけるフォルダ構成のプラクティスも書かれていて参考になります。(全然守っていない・・・。)
- コードフォーマッタ
- gofmt
- コードフォーマッタは標準で付属 (gofmt -w xxxx.go)
- 設定項目は無く、常に単一のスタイルを強制する(=>独自のスタイルが乱立しない
- goimports
- コードフォーマット+不要なimport文を削除してくれる (goimports -w xxxx.go)
- importの解決に少し時間がかかる以外には大きい違いはないのでこちらをメインに利用してもよい
- gofmt
- lintツール
- go vetとgolint
- どちらもオフィシャルツール。go vetはデフォルトで入っていて、golintはgo getしてインストール
- go vetはバグの原因になりそうなコードを検出
- golintはコードのスタイルに対する警告
- golintには調整オプションがある(デフォルトは0.8で小さくするとより厳しくなる
- go vetとgolint
- godoc
- ドキュメント閲覧用のツール(go getでインストール)
- godocコマンド実行するとWEBサーバが起動してブラウザでドキュメントが見れる
- ディレクトリ配置
- cmdディレクトリ以下にバイナリビルド用のmainパッケージを配置する
- testdataや_で始まるフォルダはGoのパッケージとみなさないので、ソースコード以外のファイルを格納する
第2章 マルチプラットフォームで動作する社内ツールのつくり方
Go自体はマルチプラットフォーム対応されていますが、その上でもOSに依存してしまうような処理の注意点などが書かれています。
その他にもCUIツールを作る上での便利なパッケージなど、ツールを作る際には役立ちそうな情報がまとまっています。
- ファイルパス
- ファイルやフォルダのパス結合に文字列のセパレータ("/")は使用しない (WindowsのC:¥や空白のあるフォルダで動かなくなる
- path/filepathパッケージを使用して結合する
- OS固有処理
- runtime.GOOSを使用
- if runtime.GOOS == "windows"みたいな感じでOS固有の処理が実装可能
- ファイル分割
- xxx_windows.goというファイルはコンパイル時に特定のOS向けのビルドの時にのみ使用される
- init関数を用意する事でOS初期化処理も実装可能
- runtime.GOOSを使用
- シングルバイナリ
- Go自体はシングルバイナリにビルドされるが、画像などのアセットは個別にデプロイが必要
- statikやpackrというパッケージでリソース自体もバイナリに同梱できる
- ファイルからバイナリのgoのコードをgo generateコマンドで生成してそれを使用する事でシングルバイナリとなる
第3章 実用的なアプリケーションを作るために
実際にアプリケーションを開発するにあたって、リリース後の運用まで視野に入れた説明がされている章です。
バッファリング、コマンド実行など特定の内容に特化したTips的なものが多く、自分が実際に使うユースケースに合うものだけを拾って読むといった方が良いかもしれません。
goroutineを使用した平衡処理のキャンセルやタイムアウトは、実際にgoroutineを使った際には再度読み込んでみようと思いました。
(goroutine自体の細かい説明は無いので、もう少し基礎を勉強しないとダメだなぁと実感)
第4章 コマンドラインツールを作る
Goで実際にCLIツールを作る際のノウハウが書かれている章です。
CLIツール自体のインターフェースの設計から始まり、CLI開発時に便利なパッケージの紹介がされています。
かなりシンプルに必要な情報がまとまっていて読みやすい章です。
CLIツールを作る際にインタフェースの設計に関してはまた見直したいと思いました。
- CLIツールのインターフェース
- シングルコマンドパターン
- オプション引数とファイル名などの引数を渡すシンプルなスタイル。1つのタスクのみを行うようなツールに最適
- 標準のflagパッケージで実現できる
- スタイル:EXEUTABLE [option] [<args>]
- 例:cmd -port 7777
- サブコマンドパターン
- オプション引数と引数だけではなく、大きく動作を変えるサブコマンドを持つ
- サードパーティー製のパッケージが必要(urfave/cliなど)
- 例:EXECUTABLE [option] <subcommand> [<args>]
- 2つ以上の動詞(ログインする、ダウンロードするなど)を持つ場合にはこちらを選ぶと良い
- シングルコマンドパターン
- CLIのリポジトリ構成
- バイナリをメイン成果物とする場合
- ルートディレクトリにmainパッケージを置く
- ライブラリをメインの成果物とする場合
- ライブラリをルートディレクトリに置いて、バイナリはcmd/xxxx/main.goといった形で配置する
- バイナリをメイン成果物とする場合
- 使いやすいツール
- 終了ステータスコードがエラー内容に応じて用意されている
- main関数は終了コードを返すだけにして、Run関数で戻り値を返すというスタイルがおすすめ(コード1参照)
- 標準出力と標準エラー出力を意識して使用する (=> 他のツールとパイプで繋いで使用するため)
- エラー処理としてfmt.Errorf("xxx:%s", err)といった形で元のエラーにメッセージをラップして返すと、わかりやすくなる (コード2参照)
- 終了ステータスコードがエラー内容に応じて用意されている
コード1:(main以外で処理)
1 2 3 4 5 6 7 8 |
func main() { os.Exit(Run(os.Args)) } func Run(args []string) int { // 処理はここに書く return 0 } |
コード2:(エラーをラップして返す)
1 2 3 4 5 6 7 |
func Setup() error { conf, err := ReadConf() if err != nil { return fmt.Errorf("failed to read file: %s", err) } // ... } |
- CLIツールのテスト技法
- CLI特有のチェック内容
- 期待するステータスコードで終了したか
- 期待するメッセージを出力したか
- 前述のmainから呼び出すRun関数を使用すれば実際にバイナリを使用しなくても同じテストが可能
- CLI特有のチェック内容
第5章 The Dark Arts Of Reflection
メタプログラミングを行う上で必要となる、リフレクションを扱う章です。
フレームワークやライブラリなどで汎用的な処理を実装したい人は見てみると良いかも。
リフレクション自体を他の言語で使った事があれば、やっている内容自体は目新しいものはありませんが、Go言語特有の実装方法になっている点などは実際に使用する際には参考になりそうです。
また、パフォーマンスの面からリフレクションが遅いかを説明している点も興味深いです。
第6章 Goのテストに関するツールセット
Goにおけるテストの書き方に関して説明している章です。
1章で説明のあったフォーマッタやリンターと同じくGoでは、標準パッケージでユニットテストや関連するの仕組みが提供されているのが特徴です。
ベンチマークや競合状態の検知用のツールが標準搭載というのには驚きました。
- テストの実行方法
- テスト対象と同じパッケージ内に_test.goとつけたテストコードを配置する。(例.sum.goというコードに対するテストはsum_test.go)
- go test [パッケージ名]でテストを実行
- 関数名はTestから始めて,testingっパッケージをインポートしてtesting.Tをメソッドの引数とする
- この引数に各種テスト操作の機能が提供されている (コード1参照)
- TestableExample
- テストコードとしても実行されて、GoDocにも使用例として記載される手法
- Exampleというメソッド名で、期待値を// Output:<期待値> といった方法でアサートする (コード2参照)
- 順不同なmapなどに対して順序を意識しないでアサートできる、// Unordered output: といった記載もできる
コード1:sum関数のテストコード例
1 2 3 4 5 6 7 8 |
import "testing" // Testというメソッド名で始める func TestSum(t *testing.T) { if sum(1, 2) != 3 { t.Fatal("sum(1,2) shoulb be 3.) } } |
コード2:Testable Exampleのテストコード例
1 2 3 4 |
func ExampleSum() { fmt.Println(sum(1, 2)) // Output: 3 } |
- ベンチマークテスト
- パフォーマンス計測用のテスト機能が標準で用意されている
- 実行時間やメモリのアロケーション量が確認できる
- メソッド名はBenchmarkから始まるものとする
- go test -benchで実行可能
- テスト結果に、実行回数と1回あたりの実行時間が表示 (コード4参照)
- BenchmarkTestHoge-16 505617(実行回数) 2342 ns/op(実行時間)
- パフォーマンス計測用のテスト機能が標準で用意されている
コード3:ベンチマーク例
1 2 3 4 5 6 7 8 |
func BenchmarkTest1Using1(b *testing.B) { b.ResetTimer() // Nはベンチマーク側が用意してくれる値。この回数分をテスト対象をループさせる for i := 0; i < b.N; i++ { // テスト対象の処理を実行 Hoge() } } |
コード4:上記のテストの実行結果
1 2 3 4 5 6 7 |
goos: darwin goarch: amd64 pkg: test_gui/src/rss cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz BenchmarkTestHoge-16 505617 2342 ns/op PASS ok test_gui/src/rss 2.488s |
- 構造体やスライスの内容の一致チェックにはreflect.DeepEqualメソッドを使うと便利
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import ( "reflect" "testing" ) type TestData struct { T1 string T2 int } func TestSum(t *testing.T) { t1 := TestData{"aa", 2} t2 := TestData{"aa", 2} if !reflect.DeepEqual(t1, t2) { t.Errorf("want %#v got %#v", t1, t2) } } |
- RaceDetectorによる競合状態の検出
- 複数のgoroutineによる変数への競合(書き込みと読み込みのタイミングバッディング)を検出するツール
- go test -race xxx, やgo run -raceなど様々な方法で実行可能(前者はユニットテスト、後者は通常のアプリ実行)
- メモリ使用量や実行時間のオーバーヘッドが大きいので、テスト時に実行するのがおすすめ
- TestMainを用いる事で、他のUTであるような前処理と後処理が実装できる
- あくまで全テストメソッドの前処理と後処理になるので、個別のテストメソッドで前処理と後処理が実装したい場合には自前で実装が必要(参考:https://qiita.com/atotto/items/f6b8c773264a3183a53c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func TestMain(m *testing.M) { setup() // 任意の前処理メソッド m.Run() // この処理を書くとこのファイ内のテストメソッドが実行される(下記のTestSum,TestSum2が実行される) shutdown() // 任意の後処理メソッド } func setup() { } func shutdown() { } func TestSum(t *testing.T) { ... } func TestSum2(t *testing.T) { ... } |
- Build Constraintsによるテストの切り替え
- 他のUTフレームワークにもあるような、テストのタグ付け機能みたいなもの
- 各テストコードのpackageの前に// +build <tag> でタグ名を付与する
- go test -tags=<タグ名> で実行する事でそのテストだけ実行される
- モックによるテスト
- Goのテストも一般的なUTと同じく、外部に依存する処理をinterfaceとして定義してテストで差し替え可能にしてテストする
- カバレッジの計測
- go test -cover とオプション引数をつけるだけで計測可能
- 詳細な情報と可視化
- go test -coverprofile=xxxx.out でプロファイルを出力
- go tool cover -html=xxxx.out result.html でレポートを出力
第7章 データベースの扱い方
Goにおける各種RDBMSへのアクセス方法に関して書いてある章です。
最初は標準のパッケージを使用してのアクセスから始まり、OSSのORMを使用した場合の説明が書かれています。
最後はORMを使ってWEBアプリケーションを簡単に作ってみて紹介といった流れが書かれています。
全般的に触りだけの説明が多いので、本格的に使いたい場合は別途学習が必要だと思います。