エキスパートGo
The Go gopher was designed by Renee French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0
Attributions license. 1
更新日:2017/08/08
自己紹介
メルカリ/ソウゾウ
上田拓也
twitter: @tenntenn
■ コミュニティ活動
Google Cloud Platform User Group (GCPUG) Tokyo
Goビギナーズ
golang.tokyo
Go Conference
■ 業務
GAE/Goでメルカリカウルを作ってます
GoやGCPコミュニティを盛り上げる仕事
Gopherを描く仕事(LINEスタンプ)
2
ソウゾウ エキスパートチーム
技術をアウトプットするところに技術は集まる
■ エキスパートチームとは?
● 50%以上の時間を技術コミュニケーションへの貢献に充てる
■ エキスパートチームの役割
● 社内に新しい技術を取り取り込む
● 社外のコミュニティなどを通じて社会へ還元する
■ エキスパートチームの活動
● カンファレンス・勉強会の開催/運営
● 対外的な講演活動
● 執筆、雑誌への寄稿、インタビュー
● 社内外での担当技術の普及推進
3
@tenntenn
担当:Go・GCP
@mhidaka
担当:Android
メンバー
デブサミ(夏)に登壇しました
4
【A-3】 コミュニティ活動と企業の相互作用
~コミュニティへの貢献と組織活動への還元~
アジェンダ
● 目的と対象者
● Goを知る
● Goの開発ツール
● Goにおける抽象化
● パッケージ構成
● テスト
● ゴールーチンとチャネル
● コンテキスト
● エラー処理
● net/httpパッケージ
5
目的と対象者
6
■ 目的
● Tour of Goでは知れない実践的な話をする
● 業務上で必要になるノウハウを共有
● 何を見れば学習できるのか知る
● Goのエキスパートを増やす
■ 対象者
● 業務上Goが必要になった人
● Go以外の言語は一定以上書ける
● 静的型付け言語を業務で使ったことは無くてもいい
● Tour of Goはクリアしてる
入門者の方へ
7
https://www.slideshare.net/takuyaueda967/2016-go
Goを知る
8
Goとは?
9
Googleが開発しているプログラミング言語
■ 特徴
● シングルバイナリ・クロスコンパイル
● 強力でシンプルな言語設計と文法
● 並行プログラミング
● 豊富な標準ライブラリ群
● 周辺ツールの充実
Goの特徴 − シングルバイナリ・クロスコンパイル −
■ 環境変数のGOOSとGOARCHを指定する
開発環境とは違うOSやアーキテクチャ向けに
クロスコンパイルできる
10
シングルバイナリになるので
動作環境を用意しなくてよい
# Windows(32ビット)向けにコンパイル
$ GOOS=windows GOARCH=386 go build
# Linux(64ビット)向けにコンパイル
$ GOOS=linux GOARCH=amd64 go build
※ go build はGoのソースコードをビルドするコマンド
Goの特徴 − 強力でシンプルな言語設計と文法 −
■ スクリプト言語の書きやすさ
● 冗長な記述は必要ない
■ 型のある言語の厳密さ
● 曖昧な記述はできない
■ 考えられたシンプルさ
● 機能を増やすことで言語を拡張していくこと
はしない
11
Goに入ってはGoに従え
= 言語の思想を理解しよう
Goの特徴 − 並行プログラミング −
■ ゴールーチン
● 軽量なスレッドに近いもの
● goキーワードをつけて関数呼び出し
■ チャネル
● ゴールーチン間のデータのやり取り
● 安全にデータをやり取りできる
12
チャネル
ゴールーチン
A
ゴールーチン
B
データ
データ
// 関数fを別のゴールーチンで呼び出す
go f()
Goの特徴 − 豊富な標準ライブラリ −
■ 標準ライブラリ一覧
 https://golang.org/pkg/
13
net/http HTTPサーバなど
archive, compress zipやgzipなど
crypto 暗号化
encoding JSON, XML, CSVなど
html/template HTMLテンプレート
os, path/filepath ファイル操作など
Goの特徴 − 周辺ツールの充実 −
■ go tool として標準/準標準で提供
■ サードパーティ製のツールも充実
■ IDEによらない独立したツールとして提供
14
go build ビルドを行うコマンド
go test
xxxx_test.goに書かれたテスト
コードの実行
go doc, godoc ドキュメント生成
gofmt, goimports コードフォーマッター
golint コードチェッカー、リンター
gocode コード補完
ドキュメントを読む
15
■ 言語仕様
● コンパクトな言語仕様なので簡単に読める
■ Go Code Review Comments (日本語訳)
● Goらしい書き方が学べる
■ Effective Go
● Code Review Commentsより詳しい内容
■ パッケージドキュメント
● ドキュメントをしっかり読む
※できるかぎり本家を参考にすること
※golang.jpは情報が古いので注意
コミュニティに参加する
■ Goビギナーズ
● https://go-beginners.connpass.com/
● 初心者向けのGoのコミュニティ
■ golang.tokyo
● https://golangtokyo.connpass.com/
● Goの採用企業間で情報共有をするコミュニティ
■ Go Conference
● https://gocon.connpass.com/
● 日本最大のGoのカンファレンス
■ Gophers Slack
● https://invite.slack.golangbridge.org/
● 世界中のGopher(Goのユーザ)が集まる
16
Goの開発ツール
17
エディタは何を使えばいいのか?
18
■ とくに決まったものはない
Gogland
■ JetBrains製のGoのIDE
● https://www.jetbrains.com/go/
● ソウゾウのメンバーの多くが使っている
● 補完が高速
● インタフェース実装へのジャンプ
● リファクタリング機能
● デバッガ
19
詳しくはMercari Tech Blogで
20
http://tech.mercari.com/entry/2017/03/23/124437
コードの書式を揃える
■ gofmt
● 読み方:ごーふむと
● 標準のフォーマッタ
● 絶対に使う
● -s オプションで冗長な書き方をシンプルにできる
■ goimports
● import文を追加/削除してくれる
● 未使用パッケージのimportはエラーなので必須
● フォーマットもかける
● -s オプションがない
21
コードの品質を保つ
■ go vet
● バグである可能性の高いものを検出する
■ golint
● コーディングスタイルをチェックする
■ errcheck
● エラー処理が適切に行われているかチェックする
■ その他のツール
● http://haya14busa.com/ci-for-go-in-end-of-2016/
22
デバッグ
■ GDB
● https://golang.org/doc/gdb
● 有名な古き良きデバッガ
● Go自体にカスタマイズされてるわけではない
■ Delve
● https://github.com/derekparker/delve
● Go専用のデバッガ
● ゴルーチンやチャネルにも対応
■ panicデバッグ
● panicを使ってデバッグ
● スタックトレースが出るので便利
23
リファクタリング
■ gomvpkg
● https://golang.org/x/tools/cmd/gomvpkg
● パッケージ名の変更ができるツール
■ gorename
● https://golang.org/x/tools/cmd/gorename
● 識別子の名前を変更するツール
■ eg
● https://golang.org/x/tools/cmd/eg
● exampleベースのリファクタリングツール
● 参考:https://rakyll.org/eg/
24
Goにおける抽象化
25
型・メソッド・インタフェース
26
■ Goの抽象化はインタフェースで行う
● 抽象化の概念はインタフェースしかない
● インタフェースを正しく理解する必要
● 型とメソッドと深い関係がある
組み込み型
■ 組み込み型
● int,int8,int16,int32,int64
● uint,uint8,uint16,uint32,uint64
● uintptr,byte,rune
● float32,float64
● complex64,complex128
● string
● bool
● error
27
// 組み込み型を基にする
type Int int
// 他のパッケージの型を基にする
type MyWriter io.Writer
// 型リテラルに基にする
type Person struct {
Name string
}
typeを使った型の作成
type <型名> <型リテラル>|<型名>
28
intとIntは別の型として扱われる
typeで新しく型が作れるもの
■ 組み込み型
int, float64, string など
■ 型リテラル
構造体、インタフェース、
マップ、スライス、チャネル、関数 など
■ 名前付きの型
パッケージの内外で作った型
29
別の型として再定義できる
型エイリアス(Go 1.9以上)
■
30
■ 型のエイリアスを定義できる
● 完全に同じ型
● キャスト不要
● エイリアスの方ではメソッド定義はできない
type Applicant = http.Client
■ %Tは同じ元の型名を出す
type Applicant = http.Client
func main() {
fmt.Printf("%T", Applicant{})
}
http.Client
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
メソッドとレシーバ
type で定義した型はメソッドのレシーバにできる
31
Playgroundで動かす
// 100をHex型として代入
var hex Hex = 100
// Stringメソッドを呼び出す
fmt.Println(hex.String())
レシーバにできる型
■ typeで定義した型
● ユーザ定義の型をレシーバにできる
■ パッケージ内の型
● パッケージ内であれば定義ファイルは別でもOK
■ ポインタ型
● レシーバに変更を与えたい場合
■ 参照型
● マップやスライスなどもレシーバにできる
32
レシーバにできない型
■ 組み込み型
● 組み込み型にはメソッドはない
● 設けたい場合はtypeで再定義すれば良い
■ パッケージ外の型
● 型定義とメソッド定義を別パッケージにできない
● パッケージ外の型はtypeで再定義すれば良い
● 埋め込みを使うこともできる
■ インタフェース型
● レシーバにできるのは具象型のみ
33
関数をレシーバにする
■ 関数型を基に型を定義してレシーバにする
34
type Func func() string
func (f Func) String() string {
return f()
}
Playgroundで動かす
レシーバとnilの関係
■ 値がnilの場合でもメソッドは呼べる
35
type Hoge struct{}
func (h *Hoge) Do() {
fmt.Println(h)
}
func main() {
var h *Hoge
h.Do()
h = &Hoge{}
h.Do()
}
Playgroundで動かす
参考:Understanding nil by Francesc Campoy(動画/スライド)
インタフェース
● メソッドのリストを持つ
● メソッドのリストがインタフェースで規定しているものと一致
する型はインタフェースを実装していることになる
36
var s interface {
String() string
}
// インタフェースを実装していることになる
s = Hex(100)
fmt.Println(s.String())
Playgroundで動かす
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
interface{}
■ empty interface
● メソッドリストが空なインタフェース
● つまりどの型の値も実装していることになる
● JavaのObject型のような使い方ができる
37
var v interface{}
v = 100
v = "hoge"
Playgroundで動かす
関数にインタフェースを実装させる
■ 関数にメソッドを持たせる
38
type Func func() string
func (f Func) String() string { return f() }
func main() {
var s fmt.Stringer = Func(func() string {
return "hi"
})
fmt.Println(s)
}
Playgroundで動かす
スライスとインタフェース
■ 実装していてもスライスは互換がない
● コピーするには愚直にforで回すしかない
39
ns := []int{1, 2, 3, 4}
// できない
var vs []interface{} = ns
Playgroundで動かす
インタフェースの実装をチェック
■ コンパイル時に実装しているかチェックする
● インタフェース型の変数に代入してみる
40
type Func func() string
func (f Func) String() string { return f() }
var _ fmt.Stringer = Func(nil)
Playgroundで動かす
型アサーション
インタフェース.(型)
インタフェース型の値を任意の型にキャストする。第2戻り値に
キャストできるかどうかが返る。
41
var v interface{}
v = 100
n,ok := v.(int)
fmt.Println(n, ok)
s,ok := v.(string)
fmt.Println(s, ok)
Playgroundで動かす
型スイッチ
型によって処理をスイッチする
42
var i interface{}
i = 100
switch v := i.(type) {
case int:
fmt.Println(v*2)
case string:
fmt.Println(v+"hoge")
default:
fmt.Println("default")
}
Playgroundで動かす
構造体の埋め込み
■ 構造体に匿名フィールドを埋め込む機能
43
type Hoge struct {
N int
}
// Fuga型にHoge型を埋め込む
type Fuga struct {
Hoge // 名前のないフィールドになる
}
埋め込みとフィールド
■ 埋め込んだ値に移譲(継承とは違う)
44
type Hoge struct {N int}
type Fuga struct {Hoge}
f := Fuga{Hoge{N:100}}
// Hoge型のフィールドにアクセスできる
fmt.Println(f.N)
// 型名を指定してアクセスできる
fmt.Println(f.Hoge.N)
Playgroundで動かす
埋め込みの特徴
■ 型リテラルでなければ埋め込められる
● typeで定義したものや組み込み型
● インタフェースも埋め込められる
■ インタフェースの実装
埋め込んだ値のメソッドもカウント
45
// Stringerを実装
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
// Hex2もStringerを実装
type Hex2 struct {Hex}
Playgroundで動かす
type Stringer interface {
String() string
}
インタフェースと埋め込み
■ 既存のインタフェースの振る舞いを変える
46
type Hoge interface{M();N()}
type fuga struct {Hoge}
func (f fuga) M() {
fmt.Println("Hi")
f.Hoge.M() // 元のメソッドを呼ぶ
}
func HiHoge(h Hoge) Hoge {
return fuga{h} // 構造体作る
}
Mの振る舞いを変える
参考:インタフェースの実装パターン
インタフェースの設計
■ メソッドリストは小さく
● 共通点を抜き出して抽象化しない
● 一塊の振る舞いを一つのインタフェースにする
● トップダウンではなくボトムアップに設計する
■ 型階層はつくれない
● Goでは型階層は作れない
● 抽象化はすべてインタフェース
● 型階層ではなくコンポジットで表現する
47
io.Readerとio.Writer
■ ioパッケージは良いお手本
● 1メソッドしか無いので実装しやすい
● 入出力をうまく抽象化している
○ ファイル、ネットワーク、メモリ etc…
● パイプのように簡単に入出力を繋げられる
48
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
インタフェースを組み合わせる
■ 複雑なインタフェースは埋め込みを使う
● インタフェースでも埋め込みを使うことはできる
● 振る舞いが複数ある場合は埋め込みで組み合わせる
49
type ReadWriteCloser interface {
Reader
Writer
Closer
}
抽象化とリフレクション
■ ジェネリクスの代わりにリフレクション?
● リフレクションはパフォーマンスに影響を与える
● リフレクションは使う箇所をちゃんと選ぶ
● encodingパッケージでは利用されれている
■ インタフェースで代用する
● 型スイッチで代用できる事が多い
● タイプするのが面倒なら自動生成しよう
○ go generateを使う
● 静的解析も選択肢のうちの一つ
50
参考:https://www.slideshare.net/takuyaueda967/2016-go#101
パッケージ構成
51
パッケージ
52
■ 機能ごとにまとめる単位
● packageキーワードで定義
● 複数のファイルに別れてても良い
■ 外部パッケージの利用
● import文で記述する
● goimportsに行わせる
● 標準パッケージとサードパーティ製で分ける
import (
"context"
"fmt"
"go.mercari.io/go-httpdoc"
)
GOPATH
■ GOPATHとは?
 Goのソースコードやビルドされたファイルが入るパス。
importされるパッケージもここから検索される。
53
$GOPATH
├── bin
│ └── fuga
├── pkg
│ └── darwin_amd64
│ └── hoge.a
└── src
├── fuga
│ └── main.go
└── hoge
└── hoge.go
ビルドされた実行可能ファイルが入る
ビルドされたパッケージが入る
pkg/GOARCH/pkgname.a
mainパッケージのGoソース。
src/cmdname/*.go
自作パッケージのGoソース。
src/pkgname/*.go
エクスポート
■ エクスポートする
● 大文字にした識別子がエクスポートされる
● 他のパッケージから利用できるようになる
■ エクスポートするものを適切に選ぶ
● 実装はあまりエクスポートしない
● インタフェースをエクスポートする
● パッケージ変数は多様しない
○ ゴルーチンから利用されることを考える
54
サイクルインポート
55
■ サイクルインポートを起こさないようにする
● サイクルインポートはコンパイルエラー
● 依存関係を適切にする
● 避けるためにパッケージ分けはしない
○ 根本解決にはならない
パッケージA パッケージB
パッケージC
インポート
ベンダリング
■ ライブラリのバージョン管理
● vendor以下に置くとimportで優先される
● バージョン指定はできない
■ 依存関係管理ツール
● 多くのサードパーティ製パッケージがある
○ Godep, glide, gb, gom, etc...
● 標準になりそうなものもある
○ dep
56
テスト
57
go test
■ 単体テストを行うためコマンド
 _test.goというサフィックスの付いた
 ファイルを対象にしてテストを実行
58
# mypkgのテスト行う
$ go test mypkg
ok mypkg 0.007s
# 失敗する場合
$ go test mypkg
--- FAIL: TestHex_String (0.00s)
hex_test.go:11: expect="a" actual="A"
FAIL
FAIL mypkg 0.008s
hex.go ⇒ hex_test.go
go testのオプション(一部)
■ -v
● 詳細を表示する
■ -cpu
● 実行する並列度を指定する
● 複数のコアを使ったテストができる
■ -race
● データの競合が起きないかテストする
■ -cover
● カバレッジを取得する
testingパッケージ
■ testを行うため機能を提供するパッケージ
 *testing.T型のメソッド使う。
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
60
package mypkg_test
import "testing"
func TestHex_String(t *testing.T) {
expect := "a"
actual := mypkg.Hex(10).String()
if actual != expect {
t.Errorf(`expect="%s" actual="%s"`, expect, actual)
}
}
testingパッケージでできること
■ 失敗理由を出力してテストを失敗させる
Error, Errorf, Fatal, Fatalf
■ テストの並列実行
Parallel
go testの-parallelオプションで並列数を指定
■ ベンチマーク
 *testing.B型を使う
■ ブラックボックステスト
testing/quickパッケージ
テスティングフレームを使わない理由
■ アサーションはない
自動でエラーメッセージを作るのではなく、
ひとつずつErrorfを使って自前で作る
■ テストはGoで書く
テストのための新しいミニ言語を作らない
■ 比較演算子だけはツライのでは?
google/go-cmpを使うと良い
 
参考:https://golang.org/doc/faq#assertions
Exampleテスト
■ テストされたサンプル
● Exampleで始まる関数を書く
● Go Docにサンプルとして出る
● // Output: を書くとテストになる
func ExampleHex_String() {
fmt.Println(mypkg.Hex(10))
// Output: a
}
63
テーブル駆動テスト
■ テスト対象のデータを羅列してテストする
64
参考:https://github.com/golang/go/wiki/TableDrivenTests
var flagtests = []struct {
in string
out string
}{
{"%a", "[%a]"},
{"%-a", "[%-a]"},
{"%+a", "[%+a]"},
{"%#a", "[%#a]"},
{"% a", "[% a]"},
}
func TestFlagParser(t *testing.T) {
var flagprinter flagPrinter
for _, tt := range flagtests {
s := Sprintf(tt.in, &flagprinter)
if s != tt.out {
t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)
}
}
}
サブテスト
■ 子テストを実行するしくみ
● Go1.7から導入された
65
func TestAB(t *testing.T) {
t.Run("A", func(t *testing.T) { t.Error("error") })
t.Run("B", func(t *testing.T) { t.Error("error") })
}
go test -v sample -run TestAB/A
=== RUN TestAB
=== RUN TestAB/A
--- FAIL: TestAB (0.00s)
--- FAIL: TestAB/A (0.00s)
sample_test.go:10: error
FAIL
exit status 1
FAIL sample 0.007s
サブテストを指定して実行
サブテストとテーブル駆動テスト
66
参考:Advanced Testing with Go by mitchellh
func TestIsOdd(t *testing.T) {
cases := []*struct {name string; input int; expected bool}{
{name: "+odd", input: 5, expected: true},
{name: "+even", input: 6, expected: false},
{name: "-odd", input: -5, expected: true},
{name: "-even", input: -6, expected: false},
{name: "zero", input: 0, expected: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if actual := IsOdd(c.input); c.expected != actual {
t.Errorf(
"want IsOdd(%d) = %v, got %v",
c.input, c.expected, actual)}})
}
}
テストヘルパー
■ テスト用のヘルパー関数
● ヘルパー関数はエラーを返さない
● *testing.Tを受け取ってテストを落とす
● Go 1.9からはT.Helperを使って情報を補足する
○ https://tip.golang.org/pkg/testing/#T.Helper
67
func testTempFile(t *testing.T) string {
t.Helper()
tf := ioutil.TempFile("", "test")
if err != nil {
t.Fatal("err %s", err)
}
tf.Close()
return tf.Name()
}
テストヘルパーで落ちたことが分かる
■ テストカバレッジの分析
$ go test -coverprofile=profile fmt
$ head profile
mode: set
fmt/format.go:30.13,31.29 1 1
fmt/format.go:31.29,34.3 2 1
fmt/format.go:67.28,69.2 1 1
fmt/format.go:71.33,74.2 2 1
fmt/format.go:77.85,80.11 3 1
fmt/format.go:84.2,85.11 2 1
fmt/format.go:96.2,96.8 1 1
fmt/format.go:80.11,83.3 2 0
fmt/format.go:85.11,86.21 1 1
coverprofile
パッケージ名
テストできるのは
パッケージごと
68
カバレッジの可視化
参考:https://blog.golang.org/cover
$ go tool cover -html=profile
差分テスト
■ 増えていくテスト
● プロジェクト規模と共にテストも増える
● 1/3くらいがテスト
● テスト待ちでマージできない
■ 更新してない部分はテストしたくない
● そんなに更新した箇所は多くない
● gitでバージョン管理しているのに...
うまく差分だけ
テストできないの?
coverprofileの中身
■ テストが依存しているファイルが分かる
mode: set
fmt/format.go:30.13,31.29 1 1
fmt/format.go:31.29,34.3 2 1
fmt/format.go:67.28,69.2 1 1
fmt/format.go:71.33,74.2 2 1
fmt/format.go:77.85,80.11 3 1
...
fmt/scan.go:1181.46,1183.4 1 1
fmt/scan.go:1185.29,1187.9 2 1
fmt/scan.go:1195.27,1197.3 1 1
71
テストで通った箇所
coverprofileで差分テストをする
■ coverprofileの生成
● すべてのパッケージのcoverprofileを生成する
● coverprofileはコミットしておく
■ テストが必要なパッケージを割り出す
● git diff --name-onlyでファイル一覧を取る
● coverprofile内にファイル名が出てるか?
● 出てたらそのパッケージはテスト対象
■ 差分テスト
● テスト対象のパッケージのみテスト
● テストの際にcoverprofileを生成
● coverprofileはコミットしておく
参考:http://qiita.com/tenntenn/items/caafa121b90fc7a53a8a
テストとインタフェース
■ テスタブルなコード
● 外部とつながるような部分はインタフェースにする
○ ネットワーク、ファイル、DBアクセスなど
● モックを作ってテストする
○ Go Mockを使う
■ http://qiita.com/tenntenn/items/24fc34ec0c31f6474e6d
○ インタフェースの埋め込みを使う
■ 必要な部分だけ実装する
73
参考:Golangにおけるinterfaceをつかったテスト技法
環境変数を使う
■ 環境変数を使って切り替える
● os.Getenvで取得できる
● CIでテストを走らせるときに便利
● DBの接続先など環境に依存する値を保存する
■ github.com/jinzhu/configorを使う
74
var Config = struct {
DB string `env:"DB"`
}{}
func main() {
configor.Load(&Config)
fmt.Printf("config: %#v", Config)
}
ゴールーチンとチャネル
75
Concurrency is not Parallelism
■ 並行と並列は別ものである by RobPike
● 並行:Concurrency
● 並列:Parallelism
■ Concurrency
● 同時にいくつかの質の異なることを扱う
■ Parallelism
● 同時にいくつかの質の同じことを扱う
76
並列と並行の違い
■ Concurrency
 同時にいくつかの質の異なることを扱う
■ Parallelism
 同時にいくつかの質の同じことを扱う
77
本を運ぶ
本を燃やす
台車を戻す
本を積む
本を燃やす 本を燃やす 本を燃やす
ゴールーチンとConcurrency
■ ゴールーチンでConcurrencyを実現
● 複数のゴールーチンで同時に複数のタスクをこなす
● 各ゴールーチンに役割を与えて分業する
■ 軽量なスレッドのようなもの
● LinuxやUnixのスレッドよりコストが低い
● 1つのスレッドの上で複数のゴールーチンが動く
■ ゴールーチンの作り方
● goキーワードをつけて関数を呼び出す
78
複数のコアで動くとは限らない
go f()
無名関数とゴールーチン
79
package main
import "fmt"
import "time"
func main() {
go func() {
fmt.Println("別のゴールーチン")
}()
fmt.Println("mainゴールーチン")
time.Sleep(50*time.Millisecond)
}
Sleepしないとすぐに終了する
Playgroundで動かす
ゴールーチン間のデータのやりとり −1−
80
ゴールーチン-main
ゴールーチン-2ゴールーチン-1
go f1() go f2()
ゴールーチン-main
ゴールーチン間のデータのやりとり −2−
81
ゴールーチン-2ゴールーチン-1
変数v
共有の変数を使う?
go f1() go f2()
print(v) v = 100
func main() {
done := false
go func() {
time.Sleep(3 * time.Second)
done = true
}()
for !done {
time.Sleep(time.Millisecond)
}
fmt.Println("done!")
}
ゴールーチン間で共有の変数を使う
82
共有の変数を使う
Playgroundで動かす
ゴールーチン間のデータ競合 −1−
83
ゴールーチン-2ゴールーチン-1
変数v
処理順序が保証されない
ゴールーチン-maingo f1() go f2()
print(v) v = 100
競合
ゴールーチン間のデータ競合 −2−
84
n := 1
go func() {
for i := 2; i <= 5; i++ {
fmt.Println(n, "*", i)
n *= i
time.Sleep(100)
}
}()
http://play.golang.org/p/yqk82u0E4V
for i := 1; i <= 10; i++ {
fmt.Println(n, "+", i)
n += 1
time.Sleep(100)
}
競合
データ競合の解決
■ 問題点
● どのゴールーチンが先にアクセスするか分からない
● 値の変更や参照が競合する
■ 解決方法
● 1つの変数には1つのゴールーチンからアクセスする
● チャネルを使ってゴールーチン間で通信をする
● またはロックをとる(syncパッケージ)
85
"Do not communicate by sharing memory; instead,
share memory by communicating"
チャネルとは?
86
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
ch<-100<-ch
チャネル
100
チャネルの特徴
■ 送受信できる型
● チャネルを定義する際に型を指定する
■ バッファ
● チャネルにバッファを持たせることができる
● 初期化時に指定できる
● 指定しないと容量0となる
■ 送受信時の処理のブロック
● 送信時にチャネルのバッファが一杯だとブロック
● 受信時にチャネル内が空だとブロック
87
送信時のブロック
88
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
受信してくれるまでブロック
ch<-100
チャネル
100
ブロック
受信時のブロック
89
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
送信されるまでブロック
<-ch
チャネル
100
ブロック
チャネルの基本 −1−
90
■ 初期化
■ 送信
■ 受信
ch1 := make(chan int)
ch2 := make(chan int, 10)
n1 := <-ch1
n2 := <-ch2 + 100
容量を指定
ch1 <- 10
ch2 <- 10 + 20
受け取られるまでブロック
一杯であればブロック
送信されまでブロック
空であればブロック
make(chan int, 0)と同じ
チャネルの基本 −2−
91
func main() {
done := make(chan bool) // 容量0
go func() {
time.Sleep(time.Second * 3)
done <- true
}()
<-done
fmt.Println("done")
}
送信されるまでブロック
http://play.golang.org/p/k0sMCYe4PA
複数のチャネルから同時に受信
92
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロック
ブロックされるので
同時に送受信出来ない?
select - case −1−
93
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロックされるので
同時に送受信出来ない?
select
select - case −2−
94
func main() {
ch1 := make(chan int)
ch2 := make(chan string)
go func() { ch1<-100 }()
go func() { ch2<-"hi" }()
select {
case v1 := <-ch1:
fmt.Println(v1)
case v2 := <-ch2:
fmt.Println(v2)
}
}
先に受信した方を処理
http://play.golang.org/p/moVwtEdQIv
nilチャネル
95
func main() {
ch1 := make(chan int)
var ch2 chan string
go func() { ch1<-100 }()
go func() { ch2<-"hi" }()
select {
case v1 := <-ch1:
fmt.Println(v1)
case v2 := <-ch2:
fmt.Println(v2)
}
}
nilの場合は無視される
ゼロ値はnil
http://play.golang.org/p/UcqW6WH0XT
ファーストクラスオブジェクト
■ チャネルはファーストクラスオブジェクト
● 変数に入れれる
● 引数に渡す
● 戻り値で返す
● チャネルのチャネル
■ timeパッケージ
96
http://golang.org/pkg/time/#After
chan chan int など
// 5分間待つ
<-time.After(5 * time.Minute)
5分たったら現在時刻が
送られてくるチャネルを返す
チャネルを引数や戻り値にする
97
func makeCh() chan int {
return make(chan int)
}
func recvCh(recv chan int) int {
return <-recv
}
func main() {
ch := makeCh()
go func() { ch <- 100 }
fmt.Println(recvCh(ch))
}
http://play.golang.org/p/UcqW6WH0XT
双方向チャネル
98
func makeCh() chan int {
return make(chan int)
}
func recvCh(recv chan int) int {
go func() { recv <- 200 }()
return <-recv
}
func main() {
ch := makeCh()
go func() { ch <- 100 }()
fmt.Println(recvCh(ch))
}
http://play.golang.org/p/6gU92C6Q2v
間違った使い方ができる
単方向チャネル
99
func makeCh() chan int {
return make(chan int)
}
func recvCh(recv <-chan int) int {
return <-recv
}
func main() {
ch := makeCh()
go func(ch chan<- int) {
ch <- 100
}(ch)
fmt.Println(recvCh(ch))
}
http://play.golang.org/p/pY4u1PU3SU
受信専用のチャネル
送信専用のチャネル
チャネルのclose
■ closeの挙動
● closeは送信側が行う
● 同じチャネルは2度閉じれない
○ panicが起こる
● 閉じられたチャネルには送信できない
○ panicが起こる
● 受信するとゼロ値とfalseが返ってくる
■ closeを使ったブロードキャスト
● 複数の受信箇所に一気にブロードキャストしたい
● closeした瞬間に受信場所にゼロ値が送られる
● 処理の終了を伝えるのに使われる
100
参考:http://qiita.com/tenntenn/items/dd6041d630af7feeec52
Concurrencyの実現
■ 複数のゴールーチンで分業する
● タスクの種類によってゴールーチンを作る
● Concurrencyを実現
■ チャネルでやりとりする
● ゴールーチン間はチャネルで値を共有する
● 複雑すぎる場合はロックを使うことも
■ for-selectパターン
● ゴールーチンごとに無限ループを作る
● メインのゴールーチンはselectで結果を受信
101
for-selectパターン −1−
102
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
select
for{}for{}
各ゴールーチンで
無限ループを作る
for-selectパターン −2−
103
ゴルーチン-1
for{}
ゴルーチン-2
for{}
ゴルーチン-3
for{}
ゴルーチン-4
for{}
チャネル
チャネル
チャネル
チャネル
syncパッケージ
■ sync.Mutex, sync.RWMutex
● 昔ながらロック
● チャネルを使うと複雑になりすぎる場合に使う
■ sync.WaitGroup
● 複数のゴルーチンの処理を待つのに使う
104
var wg sync.WaitGroup
wg.Add(1)
go func() { /* do something */ wg.Done() }()
wg.Add(1)
go func() { /* do something */ wg.Done() }()
wg.Wait()
golang.org/x/sync/errgroup
■ 複数のゴルーチンの処理を待つ
● 失敗した場合にエラーが取得できる
● 1つでもエラーを起こすとキャンセルされる
105
var eg errgroup.Group
eg.Go(func() error { /*...*/ })
eg.Go(func() error { /*...*/ })
if err := eg.Wait(); err != nil {
log.Fatal(err)
}
参考:http://deeeet.com/writing/2016/10/12/errgroup/
コンテキスト
106
コンテキストとは?
107
■ Contextインタフェース
● 歴史的な背景によりgolang.org/x/net/contextもある
● 現在は標準パッケージのcontextが使われる
○ GAE/Goではまだ使われている
■ Go1.8にはベータとして対応中
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
コンテキストとキャンセル処理
■ コンテキストの主な目的はキャンセル処理
● ゴルーチンをまたいだ処理のキャンセルに使う
108
参考:http://deeeet.com/writing/2016/07/22/context/
bc := context.Background()
t := 50*time.Millisecond
ctx, cancel := context.WithTimeout(bc, tc)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
コンテキストに値を持たせる
■ WithValueで値を持たせる
● 例:キャッシュを充てない
109
参考:http://deeeet.com/writing/2017/02/23/go-context-value/
type withoutCacheKey struct{}
func WithoutCache(c context.Context) context.Context {
if IsIgnoredCache(c) {
return c
}
return context.WithValue(c, withoutCacheKey{}, struct{}{})
}
func IsIgnoredCache(c context.Context) bool {
return c.Value(withoutCacheKey{}) != nil
}
コンテキストの注意点
■ 注意点
● コンテキストは構造体のフィールドなどに保存しない
○ コンテキストはラップされるので値が変わる可能性が
ある
○ 引数で引き回す
● コンテキストにはリクエスト起因のデータのみ保存する
● Valueとして保存する場合のキーは外に公開しない
○ 型を作りエクスポートしない
○ 値を取得するため関数を作る
110
参考:http://sssslide.com/speakerdeck.com/timakin/contextantihatan
エラー処理
111
errorインタフェース
■ error型はインタフェース
112
type error interface {
Error() string
}
エラーハンドリングをまとめる
■ bufio.Scannerの実装が参考になる
● 途中でエラーが発生したらそれ以降の処理を飛ばす
● すべての処理が終わったらまとめてエラーを処理
● それ以降の処理を実行する必要ない場合に使う
● エラーハンドリングが1箇所になる
113
s := bufio.NewScanner(r)
for s.Scan() {
fmt.Println(s.Text())
}
if err := s.Err(); err != nil {
log.Fatail(err)
}
参考:http://jxck.hatenablog.com/entry/golang-error-handling-lesson-by-rob-pike
エラーをまとめる
■ github.com/hashicorp/go-multierrorを使う
● 成功したものは成功させたい
● 失敗したものだけエラーとして報告したい
● N番目の処理は失敗、M番目は成功のように
114
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
return result
エラーに文脈を持たせる
■ github.com/pkg/errorsを使う
● File Not Foundとかでは分かりづらい
● 何をしようとした時にエラーが起きたか知りたい
● どんなパラメータだったのか知りたい
● errors.Wrapを使うとエラーをラップできる
● errors.Causeを使うと元のエラーが取得できる
115
if err := f(s); err != nil {
return errors.Wrapf(err, “f() with %s”, s)
}
振る舞いでエラーをハンドリングする
■ エラーハンドリングは具象型に依存させない
● エラーの種類で処理を分けたい場合がある
● インタフェースを使い振る舞いでハンドリングする
116
参考:http://deeeet.com/writing/2016/04/25/go-pkg-errors/
func IsTemporary(err error) bool {
te, ok := err.(interface {
Temporary() bool
})
return ok && te.Temporary()
}
net/httpパッケージ
117
Hello, net/http パッケージ
■ まずは動かしてみよう
package main
import "fmt"
import "net/http"
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, net/http!")
}
ハンドラ
■ ハンドラ
■ ハンドラの登録
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, net/http!")
}
http.HandleFunc("/", handler)
レスポンスを書き込むWriter
リクエスト
レスポンスの書き込み
/というパターンのパスで来たリクエストを
ハンドリングする関数を登録
119
HTTPサーバの起動
■ ListenAndServe
http.ListenAndServe(":8080", nil)
ホスト名:ポート番号
ホスト名を省略するとlocalhost
HTTPハンドラ
nilだとhttp.DefaultServeMux
Listenするとここで処理が
ブロックされリクエストを待つ
http.Handlerインタフェース
■ ハンドラはインタフェースとして定義される
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ServeHTTPメソッドを持つ型が
ハンドラとして扱われる
http.Handlerの登録
■ http.Handleでhttp.Handlerを登録
func Handle(pattern string,
handler http.Handler)
ServeHTTPメソッドを持つ型が
ハンドラとして扱われる
実際には、ServeHTTPメソッドを
持つ型の具体的な値がくる
http.HandlerFunc
■ http.HandlerFuncとは?
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(
w ResponseWriter, r *Request) {
f(w, r)
}
ServeHTTPメソッドを
関数に実装させるための型
http.HandleFuncは何をしてるのか
■ http.HandleFunc
● 関数をハンドラとして登録する
● 関数をhttp.HandlerFuncに変換する
● http.Handleで登録する
func HandleFunc(
pattern string,
handler func(ResponseWriter, *Request))
Handlerは登録されるもの
Handleは登録する関数
http.ServeMuxについて
■ http.ServeMuxとは?
● 複数のハンドラをまとめる
● パスによって使うハンドラを切り替える
● 自身もhttp.Handlerを実装している
● http.Handleとhttp.HandleFuncはデ
フォルトのhttp.ServeMuxである
http.DefaultServeMuxを使用している
http.ResponseWriterについて
■ http.ResponseWriterインタフェース
● io.Writerと同じWriteメソッドをもつ
○ ResposeWriteを満たすとio.Writerを満たす
● io.Writerとしても振る舞える
○ fmt.Fprint*の引数に取れる
○ json.NewEncoderの引数に取れる
インタフェースなので
モックも作りやすい=テスト簡単
リクエストとコンテキスト
■ *http.Requestから取得する
■ コンテキストを更新する
● 新しい*http.Requestが生成される
● GAE/Goの1.8では現在まともに動かない
○ Google App Engine for GoがGo1.8に対応したので試してみた
127
ctx := req.Context()
req = req.WithConntext(ctx)
ミドルウェアを作る
■ ハンドラより前に行う共通処理
128
type MiddleWare interface {
ServeNext(h http.Handler) http.Handler
}
type MiddleWareFunc func(h http.Handler) http.Handler
func (f MiddleWareFunc) ServeNext(h http.Handler) http.Handler {
return f(h)
}
func With(h http.Handler, ms ...MiddleWare) http.Handler {
for _, m := range ms {
h = w.ServeNext(h)
}
return h
}
ハンドラのテスト
■ net/http/httptestパッケージ
● ハンドラのテストのための機能など提供
● ResponseRecorder
○ ResponseWriterを実装している
● NewRequestメソッド(1.7以上)
○ 簡単にテスト用のリクエストが作れる
ハンドラのテスト例
■ このハンドラをテストする
package main
import "fmt"
import "net/http"
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, net/http!")
}
ハンドラのテスト例
func TestSample(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
handler(w, r)
rw := w.Result()
defer rw.Body.Close()
if rw.StatusCode != http.StatusOK {
t.Fatal("unexpected status code")
}
b, err := ioutil.ReadAll(rw.Body)
if err != nil {t.Fatal("unexpected error)}
const expected = "Hello, net/http!"
if s := string(b); s != expected {
t.Fatalf("unexpected response: %s", s)
}
}
テスト用のReponseWriterと
Requestを作る
go-httpdoc
132
■ テストからAPIドキュメントを生成する
● https://github.com/mercari/go-httpdoc
● HTTPハンドラのテストから生成
● リクエストやレスポンスが記載される
● gRPCやJSON RPCなAPIでも利用可能
リクエスト/レスポンスのテスト
■ リクエストのテストからドキュメントを生成
● Request Bodyをリフレクションでテストしている
● 構造体の任意のフィールドを指定できる
133
validator.RequestBody(t, []httpdoc.TestCase{
{"Name", "tenntenn", "User Name"},
{"Attribute.Birthday", "1986-01-12", "User
birthday YYYY-MM-DD format"},
}, &createUserRequest{})
参考:go-httpdocのexample
Thank you!
twitter: @tenntenn
Qiita: tenntenn
connpass: tenntenn
134

エキスパートGo