発売されたのは約1年前だけど,最近仕事で Golang を書いているので,実践的なテクニックを学ぶために今さらながら「みんなのGo言語」を読んだ.WEB+DB PRESS のようなカジュアルさのある本で,1日でサクッと読むことができた.読み終わった後に,学んだことを実際に試してみたりもして,とにかく即戦力のある1冊だと感じた.
golint -min_confidence
今までも golint は使っていたけど,各項目に Confidence というレベル設定があって -min_confidence
オプションで閾値を決められるというのは知らなかった.golint の README にも書いてなかったけど,実際に golint.go
を読んだら実装があり,デフォルトは 0.8 になっていた.もっと厳しくても良いのではないかなと思って,さっそく 0.6 に変更した.0.8 → 0.6 に変更したことによって追加で検知できたのは以下で,エラーメッセージの1文字目も小文字にするのがお作法なのかぁー,という気持ちだった.
error strings should not be capitalized or end with punctuation or a newline
golint -set_exit_status
デフォルトのまま golint を実行すると,コマンドの終了ステータスが 0 で返ってきてしまうため,今までは CircleCI で以下のようなワークアラウンド実装をしていた.
test:
override:
- test -z "$(golint $(glide novendor) | tee /dev/stderr)"
しかし -set_exit_status
オプションを使うと,指摘が出た場合に 1 が返ると本書に書いてあった.よって,以下のように書き換えることができて非常に助かった.
test:
override:
- golint -set_exit_status $(glide novendor)
Makefile
最初は golint と go vet を circle.yml
に直接書いていたけど,僕のチームに Golang のアドバイザーとして入ってもらっているメンバーに「Makefile で書きましょう」と教えてもらって,今は Makefile を使っている.まさに同じことが本書にも書かれていて,参考になった.なお make help
の部分は本書で紹介されていた make2help は使ってなくて,以下の記事で紹介されていた方法を使っている.
postd.cc
ザッとこのような感じになり,CircleCI でも make lint
を実行するだけで良いという状態になっている.
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
setup: ## Setup some tools
go get -u github.com/Masterminds/glide
go get -u github.com/golang/lint/golint
lint: ## Run the Golint in all directories without /vendor
go vet $$(glide novendor)
golint -min_confidence 0.6 -set_exit_status $$(glide novendor)
goroutine の停止
goroutine をどのように停止するかという話があり,まさに似たような実装を書く予定があって,チャネルをクローズするイメージで考えていたので,Go 1.7 以降だと context を使うのが推奨であると知れたのは非常に良かった.
実際に本書に載っているコードを写経して動作確認をしてみて,これだけだとあまりチャネルをクローズする実装と変わらないと思ったけど,最後に context.WithTimeout
を使えば,タイムアウトの考慮もできるようになるメリットがあるという紹介があった.
とは言え,本書だけだと記載が少なく概要レベルの紹介だったため,以下の記事を読んで,より理解を深められたような気がする.
ベンチマーク
テストの章では,ベンチマークの機能を知った.実際に写経をして試したりもした.
cat.go
package cat
import "bytes"
func cat(ss ...string) string {
var r string
for _, s := range ss {
r += s
}
return r
}
func buf(ss ...string) string {
var b bytes.Buffer
for _, s := range ss {
b.WriteString(s)
}
return b.String()
}
cat_test.go
package cat
import "testing"
func seed(n int) []string {
s := make([]string, 0, n)
for i := 0; i < n; i++ {
s = append(s, "a")
}
return s
}
func bench(b *testing.B, n int, f func(...string) string) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
f(seed(n)...)
}
}
func BenchmarkConcatenate(b *testing.B) {
benchCases := []struct {
name string
n int
f func(...string) string
}{
{"Cat", 3, cat},
{"Buf", 3, buf},
{"Cat", 100, cat},
{"Buf", 100, buf},
{"Cat", 10000, cat},
{"Buf", 10000, buf},
}
for _, c := range benchCases {
b.Run(fmt.Sprintf("%s%d", c.name, c.n),
func(b *testing.B) { bench(b, c.n, c.f) })
}
}
サブベンチマークという機能を使って書くことで,テーブルドリブンに書けるのは良かった.以下のような結果となり,文字列結合をする要素数が多くなればなるほど bytes.Buffer
を使った方がパフォーマンスが良いということを確認することができた.すぐに仕事に活かせる知見だった.
$ go test -bench .
BenchmarkConcatenate/Cat3-8 10000000 164 ns/op 54 B/op 3 allocs/op
BenchmarkConcatenate/Buf3-8 10000000 181 ns/op 163 B/op 3 allocs/op
BenchmarkConcatenate/Cat100-8 300000 5794 ns/op 7520 B/op 100 allocs/op
BenchmarkConcatenate/Buf100-8 1000000 1938 ns/op 2160 B/op 4 allocs/op
BenchmarkConcatenate/Cat10000-8 200 7414202 ns/op 53327801 B/op 10000 allocs/op
BenchmarkConcatenate/Buf10000-8 10000 188879 ns/op 211424 B/op 11 allocs/op
PASS
その他
Windows の考慮,リフレクションの話などは,一通り読んだけど,実際に使う場面がありそうなときに改めて読み直そうかなと思っている.CLI の話は前に Togoo を実装したときに学んだ内容が多く含まれていて,復習の意味も兼ねて知識を整理することができた.
読んでいて少し気になったこと
初版だからある程度はしょうがないとは思うけど,少し誤植が多いなとは思った.また,誤植ではないものの,文字間隔が広くなっているのが気になる行もあった.これは組版の問題だとは思うけども.誤植一覧は以下に公開されていた.
まとめ
- 仕事でさっそく使えるような,実践的なテクニックが学べる1冊だった
- 実装のテクニックだけではなく,OSS として公開されているパッケージが多く紹介されているのも良かった
関連記事
紹介されていた「プログラミング言語Go」も購入したので,さっそく読むぞ!
suzuken.hatenablog.jp