Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

エキスパートGo

14,779 views

Published on

メルカリの社内勉強会で使った資料です。

Published in: Technology
  • Be the first to comment

エキスパートGo

  1. 1. エキスパート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
  2. 2. 自己紹介 メルカリ/ソウゾウ 上田拓也 twitter: @tenntenn ■ コミュニティ活動 Google Cloud Platform User Group (GCPUG) Tokyo Goビギナーズ golang.tokyo Go Conference ■ 業務 GAE/Goでメルカリカウルを作ってます GoやGCPコミュニティを盛り上げる仕事 Gopherを描く仕事(LINEスタンプ) 2
  3. 3. ソウゾウ エキスパートチーム 技術をアウトプットするところに技術は集まる ■ エキスパートチームとは? ● 50%以上の時間を技術コミュニケーションへの貢献に充てる ■ エキスパートチームの役割 ● 社内に新しい技術を取り取り込む ● 社外のコミュニティなどを通じて社会へ還元する ■ エキスパートチームの活動 ● カンファレンス・勉強会の開催/運営 ● 対外的な講演活動 ● 執筆、雑誌への寄稿、インタビュー ● 社内外での担当技術の普及推進 3 @tenntenn 担当:Go・GCP @mhidaka 担当:Android メンバー
  4. 4. デブサミ(夏)に登壇しました 4 【A-3】 コミュニティ活動と企業の相互作用 ~コミュニティへの貢献と組織活動への還元~
  5. 5. アジェンダ ● 目的と対象者 ● Goを知る ● Goの開発ツール ● Goにおける抽象化 ● パッケージ構成 ● テスト ● ゴールーチンとチャネル ● コンテキスト ● エラー処理 ● net/httpパッケージ 5
  6. 6. 目的と対象者 6 ■ 目的 ● Tour of Goでは知れない実践的な話をする ● 業務上で必要になるノウハウを共有 ● 何を見れば学習できるのか知る ● Goのエキスパートを増やす ■ 対象者 ● 業務上Goが必要になった人 ● Go以外の言語は一定以上書ける ● 静的型付け言語を業務で使ったことは無くてもいい ● Tour of Goはクリアしてる
  7. 7. 入門者の方へ 7 https://www.slideshare.net/takuyaueda967/2016-go
  8. 8. Goを知る 8
  9. 9. Goとは? 9 Googleが開発しているプログラミング言語 ■ 特徴 ● シングルバイナリ・クロスコンパイル ● 強力でシンプルな言語設計と文法 ● 並行プログラミング ● 豊富な標準ライブラリ群 ● 周辺ツールの充実
  10. 10. Goの特徴 − シングルバイナリ・クロスコンパイル − ■ 環境変数のGOOSとGOARCHを指定する 開発環境とは違うOSやアーキテクチャ向けに クロスコンパイルできる 10 シングルバイナリになるので 動作環境を用意しなくてよい # Windows(32ビット)向けにコンパイル $ GOOS=windows GOARCH=386 go build # Linux(64ビット)向けにコンパイル $ GOOS=linux GOARCH=amd64 go build ※ go build はGoのソースコードをビルドするコマンド
  11. 11. Goの特徴 − 強力でシンプルな言語設計と文法 − ■ スクリプト言語の書きやすさ ● 冗長な記述は必要ない ■ 型のある言語の厳密さ ● 曖昧な記述はできない ■ 考えられたシンプルさ ● 機能を増やすことで言語を拡張していくこと はしない 11 Goに入ってはGoに従え = 言語の思想を理解しよう
  12. 12. Goの特徴 − 並行プログラミング − ■ ゴールーチン ● 軽量なスレッドに近いもの ● goキーワードをつけて関数呼び出し ■ チャネル ● ゴールーチン間のデータのやり取り ● 安全にデータをやり取りできる 12 チャネル ゴールーチン A ゴールーチン B データ データ // 関数fを別のゴールーチンで呼び出す go f()
  13. 13. Goの特徴 − 豊富な標準ライブラリ − ■ 標準ライブラリ一覧  https://golang.org/pkg/ 13 net/http HTTPサーバなど archive, compress zipやgzipなど crypto 暗号化 encoding JSON, XML, CSVなど html/template HTMLテンプレート os, path/filepath ファイル操作など
  14. 14. Goの特徴 − 周辺ツールの充実 − ■ go tool として標準/準標準で提供 ■ サードパーティ製のツールも充実 ■ IDEによらない独立したツールとして提供 14 go build ビルドを行うコマンド go test xxxx_test.goに書かれたテスト コードの実行 go doc, godoc ドキュメント生成 gofmt, goimports コードフォーマッター golint コードチェッカー、リンター gocode コード補完
  15. 15. ドキュメントを読む 15 ■ 言語仕様 ● コンパクトな言語仕様なので簡単に読める ■ Go Code Review Comments (日本語訳) ● Goらしい書き方が学べる ■ Effective Go ● Code Review Commentsより詳しい内容 ■ パッケージドキュメント ● ドキュメントをしっかり読む ※できるかぎり本家を参考にすること ※golang.jpは情報が古いので注意
  16. 16. コミュニティに参加する ■ 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
  17. 17. Goの開発ツール 17
  18. 18. エディタは何を使えばいいのか? 18 ■ とくに決まったものはない
  19. 19. Gogland ■ JetBrains製のGoのIDE ● https://www.jetbrains.com/go/ ● ソウゾウのメンバーの多くが使っている ● 補完が高速 ● インタフェース実装へのジャンプ ● リファクタリング機能 ● デバッガ 19
  20. 20. 詳しくはMercari Tech Blogで 20 http://tech.mercari.com/entry/2017/03/23/124437
  21. 21. コードの書式を揃える ■ gofmt ● 読み方:ごーふむと ● 標準のフォーマッタ ● 絶対に使う ● -s オプションで冗長な書き方をシンプルにできる ■ goimports ● import文を追加/削除してくれる ● 未使用パッケージのimportはエラーなので必須 ● フォーマットもかける ● -s オプションがない 21
  22. 22. コードの品質を保つ ■ go vet ● バグである可能性の高いものを検出する ■ golint ● コーディングスタイルをチェックする ■ errcheck ● エラー処理が適切に行われているかチェックする ■ その他のツール ● http://haya14busa.com/ci-for-go-in-end-of-2016/ 22
  23. 23. デバッグ ■ GDB ● https://golang.org/doc/gdb ● 有名な古き良きデバッガ ● Go自体にカスタマイズされてるわけではない ■ Delve ● https://github.com/derekparker/delve ● Go専用のデバッガ ● ゴルーチンやチャネルにも対応 ■ panicデバッグ ● panicを使ってデバッグ ● スタックトレースが出るので便利 23
  24. 24. リファクタリング ■ 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
  25. 25. Goにおける抽象化 25
  26. 26. 型・メソッド・インタフェース 26 ■ Goの抽象化はインタフェースで行う ● 抽象化の概念はインタフェースしかない ● インタフェースを正しく理解する必要 ● 型とメソッドと深い関係がある
  27. 27. 組み込み型 ■ 組み込み型 ● int,int8,int16,int32,int64 ● uint,uint8,uint16,uint32,uint64 ● uintptr,byte,rune ● float32,float64 ● complex64,complex128 ● string ● bool ● error 27
  28. 28. // 組み込み型を基にする type Int int // 他のパッケージの型を基にする type MyWriter io.Writer // 型リテラルに基にする type Person struct { Name string } typeを使った型の作成 type <型名> <型リテラル>|<型名> 28 intとIntは別の型として扱われる
  29. 29. typeで新しく型が作れるもの ■ 組み込み型 int, float64, string など ■ 型リテラル 構造体、インタフェース、 マップ、スライス、チャネル、関数 など ■ 名前付きの型 パッケージの内外で作った型 29 別の型として再定義できる
  30. 30. 型エイリアス(Go 1.9以上) ■ 30 ■ 型のエイリアスを定義できる ● 完全に同じ型 ● キャスト不要 ● エイリアスの方ではメソッド定義はできない type Applicant = http.Client ■ %Tは同じ元の型名を出す type Applicant = http.Client func main() { fmt.Printf("%T", Applicant{}) } http.Client
  31. 31. 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())
  32. 32. レシーバにできる型 ■ typeで定義した型 ● ユーザ定義の型をレシーバにできる ■ パッケージ内の型 ● パッケージ内であれば定義ファイルは別でもOK ■ ポインタ型 ● レシーバに変更を与えたい場合 ■ 参照型 ● マップやスライスなどもレシーバにできる 32
  33. 33. レシーバにできない型 ■ 組み込み型 ● 組み込み型にはメソッドはない ● 設けたい場合はtypeで再定義すれば良い ■ パッケージ外の型 ● 型定義とメソッド定義を別パッケージにできない ● パッケージ外の型はtypeで再定義すれば良い ● 埋め込みを使うこともできる ■ インタフェース型 ● レシーバにできるのは具象型のみ 33
  34. 34. 関数をレシーバにする ■ 関数型を基に型を定義してレシーバにする 34 type Func func() string func (f Func) String() string { return f() } Playgroundで動かす
  35. 35. レシーバと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. 36. インタフェース ● メソッドのリストを持つ ● メソッドのリストがインタフェースで規定しているものと一致 する型はインタフェースを実装していることになる 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)) }
  37. 37. interface{} ■ empty interface ● メソッドリストが空なインタフェース ● つまりどの型の値も実装していることになる ● JavaのObject型のような使い方ができる 37 var v interface{} v = 100 v = "hoge" Playgroundで動かす
  38. 38. 関数にインタフェースを実装させる ■ 関数にメソッドを持たせる 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で動かす
  39. 39. スライスとインタフェース ■ 実装していてもスライスは互換がない ● コピーするには愚直にforで回すしかない 39 ns := []int{1, 2, 3, 4} // できない var vs []interface{} = ns Playgroundで動かす
  40. 40. インタフェースの実装をチェック ■ コンパイル時に実装しているかチェックする ● インタフェース型の変数に代入してみる 40 type Func func() string func (f Func) String() string { return f() } var _ fmt.Stringer = Func(nil) Playgroundで動かす
  41. 41. 型アサーション インタフェース.(型) インタフェース型の値を任意の型にキャストする。第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. 42. 型スイッチ 型によって処理をスイッチする 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. 43. 構造体の埋め込み ■ 構造体に匿名フィールドを埋め込む機能 43 type Hoge struct { N int } // Fuga型にHoge型を埋め込む type Fuga struct { Hoge // 名前のないフィールドになる }
  44. 44. 埋め込みとフィールド ■ 埋め込んだ値に移譲(継承とは違う) 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で動かす
  45. 45. 埋め込みの特徴 ■ 型リテラルでなければ埋め込められる ● 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. 46. インタフェースと埋め込み ■ 既存のインタフェースの振る舞いを変える 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の振る舞いを変える 参考:インタフェースの実装パターン
  47. 47. インタフェースの設計 ■ メソッドリストは小さく ● 共通点を抜き出して抽象化しない ● 一塊の振る舞いを一つのインタフェースにする ● トップダウンではなくボトムアップに設計する ■ 型階層はつくれない ● Goでは型階層は作れない ● 抽象化はすべてインタフェース ● 型階層ではなくコンポジットで表現する 47
  48. 48. 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. 49. インタフェースを組み合わせる ■ 複雑なインタフェースは埋め込みを使う ● インタフェースでも埋め込みを使うことはできる ● 振る舞いが複数ある場合は埋め込みで組み合わせる 49 type ReadWriteCloser interface { Reader Writer Closer }
  50. 50. 抽象化とリフレクション ■ ジェネリクスの代わりにリフレクション? ● リフレクションはパフォーマンスに影響を与える ● リフレクションは使う箇所をちゃんと選ぶ ● encodingパッケージでは利用されれている ■ インタフェースで代用する ● 型スイッチで代用できる事が多い ● タイプするのが面倒なら自動生成しよう ○ go generateを使う ● 静的解析も選択肢のうちの一つ 50 参考:https://www.slideshare.net/takuyaueda967/2016-go#101
  51. 51. パッケージ構成 51
  52. 52. パッケージ 52 ■ 機能ごとにまとめる単位 ● packageキーワードで定義 ● 複数のファイルに別れてても良い ■ 外部パッケージの利用 ● import文で記述する ● goimportsに行わせる ● 標準パッケージとサードパーティ製で分ける import ( "context" "fmt" "go.mercari.io/go-httpdoc" )
  53. 53. 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. 54. エクスポート ■ エクスポートする ● 大文字にした識別子がエクスポートされる ● 他のパッケージから利用できるようになる ■ エクスポートするものを適切に選ぶ ● 実装はあまりエクスポートしない ● インタフェースをエクスポートする ● パッケージ変数は多様しない ○ ゴルーチンから利用されることを考える 54
  55. 55. サイクルインポート 55 ■ サイクルインポートを起こさないようにする ● サイクルインポートはコンパイルエラー ● 依存関係を適切にする ● 避けるためにパッケージ分けはしない ○ 根本解決にはならない パッケージA パッケージB パッケージC インポート
  56. 56. ベンダリング ■ ライブラリのバージョン管理 ● vendor以下に置くとimportで優先される ● バージョン指定はできない ■ 依存関係管理ツール ● 多くのサードパーティ製パッケージがある ○ Godep, glide, gb, gom, etc... ● 標準になりそうなものもある ○ dep 56
  57. 57. テスト 57
  58. 58. 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
  59. 59. go testのオプション(一部) ■ -v ● 詳細を表示する ■ -cpu ● 実行する並列度を指定する ● 複数のコアを使ったテストができる ■ -race ● データの競合が起きないかテストする ■ -cover ● カバレッジを取得する
  60. 60. 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) } }
  61. 61. testingパッケージでできること ■ 失敗理由を出力してテストを失敗させる Error, Errorf, Fatal, Fatalf ■ テストの並列実行 Parallel go testの-parallelオプションで並列数を指定 ■ ベンチマーク  *testing.B型を使う ■ ブラックボックステスト testing/quickパッケージ
  62. 62. テスティングフレームを使わない理由 ■ アサーションはない 自動でエラーメッセージを作るのではなく、 ひとつずつErrorfを使って自前で作る ■ テストはGoで書く テストのための新しいミニ言語を作らない ■ 比較演算子だけはツライのでは? google/go-cmpを使うと良い   参考:https://golang.org/doc/faq#assertions
  63. 63. Exampleテスト ■ テストされたサンプル ● Exampleで始まる関数を書く ● Go Docにサンプルとして出る ● // Output: を書くとテストになる func ExampleHex_String() { fmt.Println(mypkg.Hex(10)) // Output: a } 63
  64. 64. テーブル駆動テスト ■ テスト対象のデータを羅列してテストする 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) } } }
  65. 65. サブテスト ■ 子テストを実行するしくみ ● 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. 66. サブテストとテーブル駆動テスト 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)}}) } }
  67. 67. テストヘルパー ■ テスト用のヘルパー関数 ● ヘルパー関数はエラーを返さない ● *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() } テストヘルパーで落ちたことが分かる
  68. 68. ■ テストカバレッジの分析 $ 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
  69. 69. カバレッジの可視化 参考:https://blog.golang.org/cover $ go tool cover -html=profile
  70. 70. 差分テスト ■ 増えていくテスト ● プロジェクト規模と共にテストも増える ● 1/3くらいがテスト ● テスト待ちでマージできない ■ 更新してない部分はテストしたくない ● そんなに更新した箇所は多くない ● gitでバージョン管理しているのに... うまく差分だけ テストできないの?
  71. 71. 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 テストで通った箇所
  72. 72. coverprofileで差分テストをする ■ coverprofileの生成 ● すべてのパッケージのcoverprofileを生成する ● coverprofileはコミットしておく ■ テストが必要なパッケージを割り出す ● git diff --name-onlyでファイル一覧を取る ● coverprofile内にファイル名が出てるか? ● 出てたらそのパッケージはテスト対象 ■ 差分テスト ● テスト対象のパッケージのみテスト ● テストの際にcoverprofileを生成 ● coverprofileはコミットしておく 参考:http://qiita.com/tenntenn/items/caafa121b90fc7a53a8a
  73. 73. テストとインタフェース ■ テスタブルなコード ● 外部とつながるような部分はインタフェースにする ○ ネットワーク、ファイル、DBアクセスなど ● モックを作ってテストする ○ Go Mockを使う ■ http://qiita.com/tenntenn/items/24fc34ec0c31f6474e6d ○ インタフェースの埋め込みを使う ■ 必要な部分だけ実装する 73 参考:Golangにおけるinterfaceをつかったテスト技法
  74. 74. 環境変数を使う ■ 環境変数を使って切り替える ● 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. 75. ゴールーチンとチャネル 75
  76. 76. Concurrency is not Parallelism ■ 並行と並列は別ものである by RobPike ● 並行:Concurrency ● 並列:Parallelism ■ Concurrency ● 同時にいくつかの質の異なることを扱う ■ Parallelism ● 同時にいくつかの質の同じことを扱う 76
  77. 77. 並列と並行の違い ■ Concurrency  同時にいくつかの質の異なることを扱う ■ Parallelism  同時にいくつかの質の同じことを扱う 77 本を運ぶ 本を燃やす 台車を戻す 本を積む 本を燃やす 本を燃やす 本を燃やす
  78. 78. ゴールーチンとConcurrency ■ ゴールーチンでConcurrencyを実現 ● 複数のゴールーチンで同時に複数のタスクをこなす ● 各ゴールーチンに役割を与えて分業する ■ 軽量なスレッドのようなもの ● LinuxやUnixのスレッドよりコストが低い ● 1つのスレッドの上で複数のゴールーチンが動く ■ ゴールーチンの作り方 ● goキーワードをつけて関数を呼び出す 78 複数のコアで動くとは限らない go f()
  79. 79. 無名関数とゴールーチン 79 package main import "fmt" import "time" func main() { go func() { fmt.Println("別のゴールーチン") }() fmt.Println("mainゴールーチン") time.Sleep(50*time.Millisecond) } Sleepしないとすぐに終了する Playgroundで動かす
  80. 80. ゴールーチン間のデータのやりとり −1− 80 ゴールーチン-main ゴールーチン-2ゴールーチン-1 go f1() go f2()
  81. 81. ゴールーチン-main ゴールーチン間のデータのやりとり −2− 81 ゴールーチン-2ゴールーチン-1 変数v 共有の変数を使う? go f1() go f2() print(v) v = 100
  82. 82. func main() { done := false go func() { time.Sleep(3 * time.Second) done = true }() for !done { time.Sleep(time.Millisecond) } fmt.Println("done!") } ゴールーチン間で共有の変数を使う 82 共有の変数を使う Playgroundで動かす
  83. 83. ゴールーチン間のデータ競合 −1− 83 ゴールーチン-2ゴールーチン-1 変数v 処理順序が保証されない ゴールーチン-maingo f1() go f2() print(v) v = 100 競合
  84. 84. ゴールーチン間のデータ競合 −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) } 競合
  85. 85. データ競合の解決 ■ 問題点 ● どのゴールーチンが先にアクセスするか分からない ● 値の変更や参照が競合する ■ 解決方法 ● 1つの変数には1つのゴールーチンからアクセスする ● チャネルを使ってゴールーチン間で通信をする ● またはロックをとる(syncパッケージ) 85 "Do not communicate by sharing memory; instead, share memory by communicating"
  86. 86. チャネルとは? 86 ゴールーチン-main ゴールーチン-2 go f2() ゴールーチン-1 go f1() ch<-100<-ch チャネル 100
  87. 87. チャネルの特徴 ■ 送受信できる型 ● チャネルを定義する際に型を指定する ■ バッファ ● チャネルにバッファを持たせることができる ● 初期化時に指定できる ● 指定しないと容量0となる ■ 送受信時の処理のブロック ● 送信時にチャネルのバッファが一杯だとブロック ● 受信時にチャネル内が空だとブロック 87
  88. 88. 送信時のブロック 88 ゴールーチン-main ゴールーチン-2 go f2() ゴールーチン-1 go f1() 受信してくれるまでブロック ch<-100 チャネル 100 ブロック
  89. 89. 受信時のブロック 89 ゴールーチン-main ゴールーチン-2 go f2() ゴールーチン-1 go f1() 送信されるまでブロック <-ch チャネル 100 ブロック
  90. 90. チャネルの基本 −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)と同じ
  91. 91. チャネルの基本 −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. 92. 複数のチャネルから同時に受信 92 ゴールーチン-main ゴールーチン-2 go f2() ゴールーチン-1 go f1() チャネル-1 チャネル-2 ブロック ブロックされるので 同時に送受信出来ない?
  93. 93. select - case −1− 93 ゴールーチン-main ゴールーチン-2 go f2() ゴールーチン-1 go f1() チャネル-1 チャネル-2 ブロックされるので 同時に送受信出来ない? select
  94. 94. 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
  95. 95. 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
  96. 96. ファーストクラスオブジェクト ■ チャネルはファーストクラスオブジェクト ● 変数に入れれる ● 引数に渡す ● 戻り値で返す ● チャネルのチャネル ■ timeパッケージ 96 http://golang.org/pkg/time/#After chan chan int など // 5分間待つ <-time.After(5 * time.Minute) 5分たったら現在時刻が 送られてくるチャネルを返す
  97. 97. チャネルを引数や戻り値にする 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. 98. 双方向チャネル 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. 99. 単方向チャネル 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 受信専用のチャネル 送信専用のチャネル
  100. 100. チャネルのclose ■ closeの挙動 ● closeは送信側が行う ● 同じチャネルは2度閉じれない ○ panicが起こる ● 閉じられたチャネルには送信できない ○ panicが起こる ● 受信するとゼロ値とfalseが返ってくる ■ closeを使ったブロードキャスト ● 複数の受信箇所に一気にブロードキャストしたい ● closeした瞬間に受信場所にゼロ値が送られる ● 処理の終了を伝えるのに使われる 100 参考:http://qiita.com/tenntenn/items/dd6041d630af7feeec52
  101. 101. Concurrencyの実現 ■ 複数のゴールーチンで分業する ● タスクの種類によってゴールーチンを作る ● Concurrencyを実現 ■ チャネルでやりとりする ● ゴールーチン間はチャネルで値を共有する ● 複雑すぎる場合はロックを使うことも ■ for-selectパターン ● ゴールーチンごとに無限ループを作る ● メインのゴールーチンはselectで結果を受信 101
  102. 102. for-selectパターン −1− 102 ゴールーチン-main ゴールーチン-2 go f2() ゴールーチン-1 go f1() チャネル-1 チャネル-2 select for{}for{} 各ゴールーチンで 無限ループを作る
  103. 103. for-selectパターン −2− 103 ゴルーチン-1 for{} ゴルーチン-2 for{} ゴルーチン-3 for{} ゴルーチン-4 for{} チャネル チャネル チャネル チャネル
  104. 104. 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()
  105. 105. 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. 106. コンテキスト 106
  107. 107. コンテキストとは? 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. 108. コンテキストとキャンセル処理 ■ コンテキストの主な目的はキャンセル処理 ● ゴルーチンをまたいだ処理のキャンセルに使う 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()) }
  109. 109. コンテキストに値を持たせる ■ 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 }
  110. 110. コンテキストの注意点 ■ 注意点 ● コンテキストは構造体のフィールドなどに保存しない ○ コンテキストはラップされるので値が変わる可能性が ある ○ 引数で引き回す ● コンテキストにはリクエスト起因のデータのみ保存する ● Valueとして保存する場合のキーは外に公開しない ○ 型を作りエクスポートしない ○ 値を取得するため関数を作る 110 参考:http://sssslide.com/speakerdeck.com/timakin/contextantihatan
  111. 111. エラー処理 111
  112. 112. errorインタフェース ■ error型はインタフェース 112 type error interface { Error() string }
  113. 113. エラーハンドリングをまとめる ■ 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
  114. 114. エラーをまとめる ■ 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
  115. 115. エラーに文脈を持たせる ■ 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. 116. 振る舞いでエラーをハンドリングする ■ エラーハンドリングは具象型に依存させない ● エラーの種類で処理を分けたい場合がある ● インタフェースを使い振る舞いでハンドリングする 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() }
  117. 117. net/httpパッケージ 117
  118. 118. 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!") }
  119. 119. ハンドラ ■ ハンドラ ■ ハンドラの登録 func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, net/http!") } http.HandleFunc("/", handler) レスポンスを書き込むWriter リクエスト レスポンスの書き込み /というパターンのパスで来たリクエストを ハンドリングする関数を登録 119
  120. 120. HTTPサーバの起動 ■ ListenAndServe http.ListenAndServe(":8080", nil) ホスト名:ポート番号 ホスト名を省略するとlocalhost HTTPハンドラ nilだとhttp.DefaultServeMux Listenするとここで処理が ブロックされリクエストを待つ
  121. 121. http.Handlerインタフェース ■ ハンドラはインタフェースとして定義される type Handler interface { ServeHTTP(ResponseWriter, *Request) } ServeHTTPメソッドを持つ型が ハンドラとして扱われる
  122. 122. http.Handlerの登録 ■ http.Handleでhttp.Handlerを登録 func Handle(pattern string, handler http.Handler) ServeHTTPメソッドを持つ型が ハンドラとして扱われる 実際には、ServeHTTPメソッドを 持つ型の具体的な値がくる
  123. 123. http.HandlerFunc ■ http.HandlerFuncとは? type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP( w ResponseWriter, r *Request) { f(w, r) } ServeHTTPメソッドを 関数に実装させるための型
  124. 124. http.HandleFuncは何をしてるのか ■ http.HandleFunc ● 関数をハンドラとして登録する ● 関数をhttp.HandlerFuncに変換する ● http.Handleで登録する func HandleFunc( pattern string, handler func(ResponseWriter, *Request)) Handlerは登録されるもの Handleは登録する関数
  125. 125. http.ServeMuxについて ■ http.ServeMuxとは? ● 複数のハンドラをまとめる ● パスによって使うハンドラを切り替える ● 自身もhttp.Handlerを実装している ● http.Handleとhttp.HandleFuncはデ フォルトのhttp.ServeMuxである http.DefaultServeMuxを使用している
  126. 126. http.ResponseWriterについて ■ http.ResponseWriterインタフェース ● io.Writerと同じWriteメソッドをもつ ○ ResposeWriteを満たすとio.Writerを満たす ● io.Writerとしても振る舞える ○ fmt.Fprint*の引数に取れる ○ json.NewEncoderの引数に取れる インタフェースなので モックも作りやすい=テスト簡単
  127. 127. リクエストとコンテキスト ■ *http.Requestから取得する ■ コンテキストを更新する ● 新しい*http.Requestが生成される ● GAE/Goの1.8では現在まともに動かない ○ Google App Engine for GoがGo1.8に対応したので試してみた 127 ctx := req.Context() req = req.WithConntext(ctx)
  128. 128. ミドルウェアを作る ■ ハンドラより前に行う共通処理 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 }
  129. 129. ハンドラのテスト ■ net/http/httptestパッケージ ● ハンドラのテストのための機能など提供 ● ResponseRecorder ○ ResponseWriterを実装している ● NewRequestメソッド(1.7以上) ○ 簡単にテスト用のリクエストが作れる
  130. 130. ハンドラのテスト例 ■ このハンドラをテストする 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!") }
  131. 131. ハンドラのテスト例 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を作る
  132. 132. go-httpdoc 132 ■ テストからAPIドキュメントを生成する ● https://github.com/mercari/go-httpdoc ● HTTPハンドラのテストから生成 ● リクエストやレスポンスが記載される ● gRPCやJSON RPCなAPIでも利用可能
  133. 133. リクエスト/レスポンスのテスト ■ リクエストのテストからドキュメントを生成 ● 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
  134. 134. Thank you! twitter: @tenntenn Qiita: tenntenn connpass: tenntenn 134

×