Go言語といえば mattnさん、mattnさんといえばGo言語。それくらいには mattnさんが書かれるGo言語の書籍には信頼がありますが、そんな mattnさんが新たに出版された書籍が「Go言語プログラミングエッセンス」です。しかも単著。これは買うしかないですよね。そして読むしかないですよね。なので読んでみた感想です。
はじめのほうには Go言語がなぜ作られたのか、どういう言語なのかというところ、または文法的なところが書かれていました。mattnさんはブログもされていて、たまにポストされるんですが、私でもわかるような書き口で書いてくださっていて助かるな~といつも思っているんですが、その書き口がこの書籍にも反映されていて、内容がスラスラ入ってくる感じになってます。
この書籍のいいところは、具体的なコードが多く書かれていて手を動かしながら理解できる点が良いと思いました。また、簡単なWebアプリケーションやCLI題材によく使われるパッケージの紹介等もされていてよかったです。
あとは、Go言語に限らないテクニックなんじゃないかな?と思う部分もあり、たとえば「推測するな計測せよ」なんていう思想はほかの言語でも通用しそうですよね。そういったプログラミングをする上での大事な考え方も学べて良い本だなぁ~と思いました。
個人的に今後に役立ちそうだなぁと思った部分の抜き書き
go get でバージョン指定
go get github.com/mattn/go-runewidth@v0.0.12
のようにするとバージョンを指定して go get できる
最新バージョンにバグがあったり、破壊的な変更がされていたりする時にこういう対応をするとよい
go.mod と同じディレクトリにあるvendorというディレクトリに置かれたモジュールのソースコードは優先して参照されます
以下のコマンドを実行すると、go.mod と go.sum に書かれたモジュールが一括で vendor ディレクトリにダウンロードできます
$ go mod vendor
おまじない
おまじないとして次のようにインターフェースIの型を持つ変数_に型fooのポインタ型nilを代入しておくことで、わざわざコンパイルをしなくても、IDEがエラーを検知して間違いに気づくことができます
recoverの使いどころ
通常、意図しないpanicが起きうるということは、そのプログラムにはバグがあることを意味するため、不用意にrecoverを呼び出すべきではありません。プログラムは落ちるべくして落ちたほうがよく、それを止めるべきではありません。
Explicit is better than implicit, Simple is betetr than complex
明示的であることは暗黙的であることよりも優れている。単純であることは複雑であることよりも優れている
Goにおける日付時刻の書式
「1月2日3時4分5秒2006年」と連番になっており、おおよそ表4.1のように理解すると覚えやすくなっています。
書式 |
連番 |
意味 |
1 |
1 |
月 |
01 |
1 |
月のゼロ埋め |
2 |
2 |
日 |
02 |
2 |
日のゼロ埋め |
3 |
3 |
時 |
03 |
3 |
時のゼロ埋め |
15 |
3 |
時のゼロ埋め(24時間表記) |
4 |
4 |
分 |
04 |
4 |
分のゼロ埋め |
5 |
5 |
秒 |
05 |
5 |
秒のゼロ埋め |
2006 |
6 |
年 |
+007 |
7 |
タイムゾーン |
JST |
- |
タイムゾーン |
timeパッケージに標準で用意されている書式
取り扱いづらそうに見えますが、多くの場合はtimeパッケージに標準で用意されている固定の書式名を使うことができます
書式名 |
フォーマット |
Layout |
01/02 03:04:05PM '06 -0700 |
ANSIC |
Mon Jan \_2 15:04:05 2006 |
UnixDate |
Mon Jan \_2 15:04:05 MST 2006 |
RubyDate |
Mon Jan 02 15:04:05 -0700 2006 |
RFC822 |
02 Jan 06 15:04 MST |
RFC822Z |
02 Jan 06 15:04 -0700 |
RFC850 |
Monday, 02-Jan-06 15:04:05 MST |
RFC1123 |
Mon, 02 Jan 2006 15:04:05 MST |
RFC1123Z |
Mon, 02 Jan 2006 15:04:05 -0700 |
RFC3339 |
2006-01-02T15:04:05Z07:00 |
RFC3339Nano |
2006-01-02T15:04:05.999999999Z07:00 |
Kitchen |
3:04PM |
Stamp |
Jan \_2 15:04:05 |
StampMilli |
Jan \_2 15:04:05.000 |
StampMicro |
Jan \_2 15:04:05.000000 |
StampNano |
Jan \_2 15:04:05.000000000 |
DateTime |
2006-01-02 15:04:05 |
DateOnly |
2006-01-02 |
TimeOnly |
15:04:05 |
time.ParseDuration
d, err := time.ParseDuration("3s")
3秒であれば time.Second * 3 と書くことができます。この 3s や 4m といった記法は、ユーザーから経過時間を指定してもらう際にとても便利で、例えばUNIXの sleep コマンドにも採用されています
context.Context
context.Context は習慣的に関数の第一引数として渡され、仮想の関数に引き渡されます。
どこに書くかいつも悩んでいたのでこういう慣習を知れたのはよかった
tag付ビルド
これらのソースコードは -tags cat フラグを付けてビルドすると cat.go が有効になり、dog.go は無効になります。
$ go build -tags cat
Functional Options Pattern
Server struct に公開フィールドとして Timeout や Logger を足したり、SetTimeout や SetLogger メソッドを追加したりすることはできます。しかし、利用者にタイムアウト値やロガーを任意のタイミングで変更させたくはありません。できれば New で指定した初期値から変更させないように制限したいですね。
Golang Functional Options Pattern | Golang Cafe
DDDとの親和性高そう
少し混乱するかもしれませんが、Goのメソッド呼び出しは、第一引数にレシーバに持った関数の呼び出しと同義であるといえます。
type I int
func (i I) Add(n int) I {
return i + I(n)
}
func main() {
n = 1
fmt.Println(n.Add(2))
fmt.Println(I.Add(n, 2))
}
I.Add(n, 2)
なんて呼び出しができるということを初めて知った
goroutine
goroutine は「ファイル I/O やネットワーク通信でブロックしている間にもランタイムが選んだ CPUコアで別の処理を平行して行うことで単純な並列処理よりも効率的に処理を行う」を主題にしているランタイムの機能です
これを知っているとゴルーチンをむやみやたらに使うことも減りそう
channel by range
ch := make(chan []byte)
for _, b := range ch {
}
channel を for で使えるの初めて知った
テストのパッケージ
テストをする際には、パッケージ名の指定に2つの方法があります。
- テスト対象と同じパッケージ名称でテストコードを書く
- テスト対象とは別のパッケージ名称でテストコードを書く
前者はパッケージ名称が同じですので、private関数もテストすることができます。一方、後者はあえて別名を付けることにより、privateな変数や関数にアクセスできないテストを書くことがるため、このパッケージの使用者と同じ実装をサンプルコードとして明示することができます
t.Skip / t.Skipf
テストによっては特定のOSでは実施できないものもあります。そういった場合には t.Skip または t.Skipf を使うことができます。
t.Short
-short を付けて実行した場合には testing.Short() が true を返します。各テストで -short のときにはスキップしたい場合には、t.SkipNow() を呼び出します。
t.Parallel
テストを並列実行したい場合に使うらしい
もう一つの注意点は、Table Driven Tests を平行で実行する際にはループの中で test変数を束縛することです。
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
got := Add(test.lhs, test.rhs)
if got != test.want {
t.Errorf("%v: want %v, but %v:", test.name, test.want, got)
}
}
}
★部分の処理を入れないと、タイミングによってはすべてのテストが tests に含まれる最後の項目を参照してしまいます。
これは、今後注意しなくてもよくなるかも?なぜなら、loopvar という機能が今後入る可能性があるから。そのあたりも、mattnさんが Forkwell の Youtubeで話されていた。
Go言語プログラミングエッセンス - Forkwell Library#27 - YouTube
Goの標準外のパッケージ
flag.Duration / flag.DurationVar
第四章で time.Duration を紹介しましたが、これを flag として受け取ることができます
flag.Text / flag.TextVar
encoding.TextMarshaler は MarshalTextを、encoding.TextUnmarshaler は UnmarshalText メソッドを持ったインターフェースです。
type TestMarshaler interface {
MarshalText() (text []byte, err error)
}
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}
これらインターフェースを実装した型を flag パッケージで扱うことができます。
独自の型をテキストでパースできるのはよさそう
echo でフォームデータを型にバインドするときに便利に使えるメソッドがあるみたい。
Binding | Echo
フォーム以外にも、クエリ文字列なんかもあるみたいで便利そう。
また、プリミティブな型以外にもバインドできる echo.CustomFunc()
っていうのがあってこれも便利そうだった。
この書籍では inputタグでPOSTされる時刻フォーマットをパースする目的で使われていた。
私もこの問題に直面したことがあって、その時は別の方法で対処したんだけど、今後はこちらを使ったほうが見やすいコードになりそうだなと思った。
テンプレート処理
echo でテンプレートを扱いには Render メソッドを持った実装を e.Renderer に設定する必要があります。
e.Renderer = &Template {
templates: template.Must(template.New("").
Funcs(template.FuncMap{
"FormatDateTime": formatDateTime,
}).ParseFS(templates, "templates/*")),
}
こうすることで、echoからは以下のようにindexテンプレートにvalueを渡し、FormatDateTime関数を呼び出せるようになります。
return c.Render(http.StatusBadRequest, "index", value)
{{FormatDateTime .}}
echo にテンプレート渡せるのも知らんかったし、テンプレートにカスタム関数を追加できるのも知らんかった。
モジュールのメジャーバージョンアップ
ただし、go get -u は、モジュールのメジャーバージョンアップは行いません。メジャーバージョンアップをするには以下を実行します
$ go mod edit -replace=モジュール名@v=モジュール名@v
ent/ent
Ent (ent/ent) は Facebook 社が開発しているORMです。
今度何かDB関連の個人プロジェクトを作成するときに使ってみようかな。
まとめ
Go言語プログラミングエッセンスの読書感想ブログを書きました。
信頼の mattnさんの書籍で、予想通り良書でした。
Go言語のチュートリアルを一通りやって、次に読む書籍としてよいかと思いますので皆さんも読んでみてはいかがでしょうか?