go1.7のbeta1がでたので、軽く個人的に気になるところを触ったメモ
以下、環境はOSXで、公式で配布されている go1.7beta1.darwin-amd64.tar.gz を使っています
目玉としては、生成されるバイナリの高速化や、GCの改善などがありますが、日常業務でそんなにGoのパフォーマンスに不満をもつ機会もないので、それ以外で気になったものを中心に試しました。
リリース予定は、順調にいけば8月です。
関連リンク
ダウンロード
https://golang.org/dl/#go1.7beta1
リリースノート
https://tip.golang.org/doc/go1.7
ビルドスピード
比較に使ったのは、仕事で使っているコードベースのビルド
generateされたコードも含めて、13万行ほどです。
go1.6
> /usr/bin/time -p go build cmd/tuner_server/main.go
real 18.80
user 30.14
sys 2.76
go1.7 beta-1
> /usr/bin/time -lp go build cmd/tuner_server/main.go
real 17.39
user 39.76
sys 4.32
ちょっと遅くなった... コードベースが大きいので、ほぼIOのスピードな感じはする。
ちなみに、バイナリサイズは、
version | time |
---|---|
go1.6.1 | 35M |
go1.7 beta-1 | 27M |
かなりスリムになりました!
context パッケージ
今まで、 golang.org/x/net/context
配下 にあった、 context
パッケージが、go 本体ライブラリに統合された。
context
パッケージの役割については、Goの並行パターン:コンテキスト (Go Concurrency Pattern: Context) などを参照。
それと同時に、 *http.Request
に Context() context.Context
メソッドと、 WithContext(context.Context) *http.Request
メソッドが追加された。
今まで、いろいろなフレームワークが、主にミドルウェアから各ハンドラへの情報の引き渡しのために、独自のコンテキスト構造を持っていたが、1.7からはデフォルトのcontextを使うことで、ミドルウェアの型を、func(http.Handler) http.Handler
に統一できてすっきりする。
今までは各フレームワーク感で型が統一されてないので、ミドルウェアの再利用性が低かったけど、 nodeの connect みたいなミドルウェアを集めたパッケージがいろいろと出てくるんじゃないかと思われし、各種フレームワークも context パッケージを使うように落ち着くんじゃないかなぁと個人的には思っている。
context の使い方サンプル
package main
import (
"context"
"fmt"
"net"
"net/http"
"net/http/httptest"
)
var middlewareKey = "m"
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, middlewareKey, "string set in middleware")
cr := r.WithContext(ctx)
next.ServeHTTP(w, cr)
})
}
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// デフォルトで context に紐付いている値たち
// サーバーがlistenしてるアドレス
_ = ctx.Value(http.LocalAddrContextKey).(net.Addr)
// サーバー自体のオブジェクト
_ = ctx.Value(http.ServerContextKey).(*http.Server)
// ミドルウェアでリクエストに関連付けた値を取得する
fmt.Println(ctx.Value(middlewareKey))
})
ts := httptest.NewServer(middleware(handler))
defer ts.Close()
http.Get(ts.URL)
}
テストの subset 機能
テストの、サブセット機能がサポートされた
package main
import "testing"
func TestMain(t *testing.T) {
t.Parallel()
t.Run("Sub1", func(t *testing.T) {
t.Run("SubA", func(t *testing.T) {
})
t.Run("SubB", func(t *testing.T) {
})
})
t.Run("Sub2", func(t *testing.T) {
t.Run("SubA", func(t *testing.T) {
})
t.Run("SubB", func(t *testing.T) {
})
})
}
のようなテストを書き go test -v
すると
=== RUN TestMain
=== RUN TestMain/Sub1
=== RUN TestMain/Sub1/SubA
=== RUN TestMain/Sub1/SubB
=== RUN TestMain/Sub2
=== RUN TestMain/Sub2/SubA
=== RUN TestMain/Sub2/SubB
--- PASS: TestMain (0.00s)
--- PASS: TestMain/Sub1 (0.00s)
--- PASS: TestMain/Sub1/SubA (0.00s)
--- PASS: TestMain/Sub1/SubB (0.00s)
--- PASS: TestMain/Sub2 (0.00s)
--- PASS: TestMain/Sub2/SubA (0.00s)
--- PASS: TestMain/Sub2/SubB (0.00s)
PASS
ok /path-to-package 0.008s
のようになる
-run
オプションの指定
Goのテストは、-run オプションで正規表現にマッチしたもののみ実行できるが、このオプションをsubsetになっているテストにも適用できる。
指定する場合は、階層毎に、/
で区切り、正規表現で指定する。
上の例だと以下の様な感じ。
2階層目が Sub2 のものだけ実行
> go test -v -run .*/Sub2
=== RUN TestMain
=== RUN TestMain/Sub2
=== RUN TestMain/Sub2/SubA
=== RUN TestMain/Sub2/SubB
--- PASS: TestMain (0.00s)
--- PASS: TestMain/Sub2 (0.00s)
--- PASS: TestMain/Sub2/SubA (0.00s)
--- PASS: TestMain/Sub2/SubB (0.00s)
PASS
ok /path-to-package 0.008s
3階層目が SubB のものだけ実行
> go test -v -run .*/.*/SubB
=== RUN TestMain
=== RUN TestMain/Sub1
=== RUN TestMain/Sub1/SubB
=== RUN TestMain/Sub2
=== RUN TestMain/Sub2/SubB
--- PASS: TestMain (0.00s)
--- PASS: TestMain/Sub1 (0.00s)
--- PASS: TestMain/Sub1/SubB (0.00s)
--- PASS: TestMain/Sub2 (0.00s)
--- PASS: TestMain/Sub2/SubB (0.00s)
PASS
ok /path-to-package 0.007s
テストの構造化と、ピンポイントで走らせてサイクルを回すのがやりやすくなりそう。