Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CA 1Day Youth Boot Camp Part of Go

CA 1Day Youth Boot Camp Part of Go

CyberAgent
PRO

November 08, 2022
Tweet

More Decks by CyberAgent

Other Decks in Technology

Transcript

  1. CA 1Day Youth Boot Camp Part of Go Takuma Shibuya

    The Gopher is designed by Renée French
  2. Name Takuma Shibuya CyberAgent/AI事業本部 Go, Rust Kubernetes Compiler Software Architecture

    @sivchari
  3. 1.Goとその特徴 2.Goの様々なライブラリ、機能に慣れよう 3.実践API編 4.Goの最新機能Genericsに触れてみる 5.おわり

  4. 1.業務で使うGoの機能を仕組みから知り、なぜを理解する 2.Goを書くときの引き出しを増やす Goal

  5. Goとその特徴

  6. Goとは • Googleが開発したプログラミング言語 ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  7. Goのリリースサイクル • 2月と8月にメジャーリリース ◦ 最初の3ヶ月は新規機能含めた開発 ◦ 後半はドキュメント、バグ修正 ◦ Development Freezeでは毎月リリースがある(前後あり)

    ▪ Go1.18は3/15 • ベータ版 ◦ ベータでは既存のバグは全て修正 ◦ 未知のバグを発見する目的がある ◦ リリースが早いことは推奨されている ◦ 重要なコード変更は追加のベータ版をリリース • リリース候補版 ◦ リリース候補版はほぼリリース版 ◦ 致命的なバグがある場合に追加のリリース候補版がでる ◦ 発行基準の1つがGoogleが本番ビルドに使用する Go Release Cycle
  8. Goの後方互換性 • 現在はGo1 ◦ Go 1 and the Future of

    Go Programs に説明がある • Go1とGo2 ◦ Go1の仕様に沿って書かれたコードはその仕様の間は正しく実行できることを意図している ◦ 互換性の保証はソースコードレベル ▪ コンパイルしたバイナリレベルでの互換性は保証していない ◦ Go1.18のジェネリクスが導入されても後方互換性が担保されている ◦ Go2は何かしら破壊的な変更が入る可能性があるときにリリースされる ▪ GenericsはGo2で導入される可能性があった ▪ Go2についての詳細な説明は公式から上がっている Go 2 Draft Designs Toward Go 2
  9. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  10. 予約語の少なさ Q. Goの予約語の数は? 1. 45個 2. 38個 3. 25個 4.

    37個
  11. 予約語の少なさ A. Goの予約語の数は? 1. 45個 (Zig) 2. 38個 (JavaScript[ES2022]) 3.

    25個 4. 37個
  12. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  13. シンプルで柔軟な言語設計 Q. Goのパラダイムは? 1. 関数型 2. オブジェクト指向 3. 両方 4.

    どちらとも言えない
  14. シンプルで柔軟な言語設計 Q. Goのパラダイムは? 1. 関数型 2. オブジェクト指向 3. 両方 4.

    どちらとも言えない
  15. シンプルで柔軟な言語設計 Is Go an object-oriented language? Yes and no. •

    型とメソッドがあるため可能ではある • 型(クラス)の継承関係がGoには存在しない • 似たようなものとして埋め込みは提供されている type a struct {} type c interface{} type b struct { a c }
  16. シンプルで柔軟な言語設計 Is Go an object-oriented language? Yes and no. •

    クラスではなく型にメソッドを実装できる type MyInt int func (m *MyInt) inc() { *m++ } func main() { var m MyInt println(m.inc()) }
  17. シンプルで柔軟な言語設計 Go at Google: Language Design in the Service of

    Software Engineering Topic 15: Composition not inheritance • Goでは継承ではなく合成を提供している type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer }
  18. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  19. 豊富な標準パッケージ • DBへ接続したい ◦ database/sql • Web APIを作成したい ◦ net/http

    • Testをかきたい ◦ testing • ASTの操作がしたい ◦ go/ast • 時間の操作や比較をしたい ◦ time
  20. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  21. 豊富な標準ツール • cover ◦ testのカバレッジプロファイルを取れる • doc ◦ パッケージの使い方や説明などを確認できる •

    fix ◦ deprecatedされたAPIを置き換えたりできる(golang.org/x/net/context -> context) • pprof ◦ プログラムのgoroutine実行数などのプロファイルができる • vet ◦ 静的解析で怪しいコードを検知してくれる etc… (asm, compile, link, pack, test2json)
  22. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  23. シングルバイナリ・クロスコンパイル • Goのコードはコンパイル後シングルバイナリになる ◦ バイナリさえあれば処理系などが必要ない ▪ JavaScript -> V8, Node,

    Deno, Bun ▪ Java -> JVM ◦ デプロイ容易性 ◦ コンテナとの相性 ▪ マルチステージビルド • クロスコンパイルによるバイナリ配布が容易 ◦ Apple Silicon(Arm64)でIntel Mac(Amd64)で動くバイナリを配布したい GOOS=linux GOARCH=amd64 go build main.go
  24. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  25. goroutineによる並行プログラム • go キーワードで並行処理のための別スレッド(後述)を容易に生成できる e.g.) 並行に100までプリントしたい! var wg sync.WaitGroup for

    i := 1; i <= 100; i++ { i := i wg.Add(1) go func() { println(i) wg.Done() }() } wg.Wait()
  26. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  27. バージョン管理の容易さ • プログラム言語には様々なバージョンツールがある ◦ npm/yarn ◦ pyenv ◦ composer •

    Goにも存在はするが基本的に必要ない ◦ ライブラリの依存はgo.modとgo mod/go getコマンドが標準で存在する ◦ Go自体のバージョンアップも標準コマンドのみで可能 Go 1.19が使いたい! go install golang.org/dl/[email protected] go1.19 download go1.19 version -> go versuon go1.19 $OS
  28. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  29. 多くのOSSライブラリへの採用 moby(Docker) • Dockerのオリジナル • 最新のDocker Engineと同じリリース containerd • OCI

    Image Format • コンテナイメージの管理など • RPCでやりとり runc • OCI Runtime Specification • コンテナの実行をする
  30. 多くのOSSライブラリへの採用 terraform • HCL(HashiCorp Configuration Language)によるIaC • AWS/GCP/Azure etc.. •

    Custom Providerにより拡張が可能
  31. 多くのOSSライブラリへの採用 kubernetes • Podを最小単位とした構成環境 • デプロイやスケール、ロードバランシングの柔軟性がある • CRDに沿った拡張 • Istio(written

    in Go)などでも登場する
  32. 多くのOSSライブラリへの採用 esbuild • JSのビルドツール • ビルドがwebpackの10倍ほど(公式より) • v1ではないがベータ後期としている

  33. Goの様々なライブラリ 機能に慣れよう

  34. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  35. Goのダウンロード Goの公式からダウンロードする

  36. Dockerのダウンロード Docker Desktopを使うと簡単にダウンロードできる

  37. GitHubからリポジトリをクローンしよう go-rookie-gymをforkする ・ownerを自分のアカウントにしてcreate forkしよう

  38. GitHubからリポジトリをクローンしよう codeからリンクをコピーして git clone <copyしたurl>

  39. 注意点! • ここにあげるものが全てではない • ここにあるものが絶対にいいものではない • 質問はチャットで! • クイズもあるから考えてみよう!

  40. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  41. 早速プログラムを動かそう package main import ( "log" "net/http" ) func main()

    { http.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) { log.Println("Welcome to Go Rookie Gym :)") }) log.Fatal(http.ListenAndServe(":8080", nil)) }
  42. 早速プログラムを動かそう • train-httpserverでgo run main.go • 別のタブで curl localhost:8080/welcome を実行

    • Welcome to Go Rookie Gym :)と出たら成功
  43. JSONを受け取って出力しよう • GoではJSONのエンコード、デコード用に encoding/json がある • フィールドにjsonタグをつけることでリクエストボディをマッピングできる • keyでのマッピングなのでデータ型さえあっていればいい •

    設定しない場合フィールド名をキャメルケースにしてマッチするものにマッピングされる ◦ e.g. Age = “age” FullName = “fullName” type User struct { Name string `json:”name”` MyID int `json:”id”` }
  44. JSONを受け取って出力しよう • Payloadにkey名がnameの文字列を受け取れるようにjsonタグを追加しよう • go run main.goでサーバーを立ち上げる • curl localhost:8080/json

    -d ‘{ “name”: “自分の名前” }` を実行 • { “name”: “Hello 自分の名前” } が返ってくれば成功
  45. http serverのまとめ • net/httpだけで簡単にサーバーを立ち上げることができる • jsonをエンコード、デコードするときはjsonタグを使うと自由にマッピングできる

  46. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  47. interfaceの使い方 • 振る舞いを抽象化する • あらゆる型(interface以外)を代入することができる

  48. interfaceの使い方 • 振る舞いを抽象化する • あらゆる型(interface以外)を代入することができる

  49. 振る舞いを抽象化する https://go.dev/play/p/T77psnc50IN type Hello interface { Hello() } type hello

    struct{} func (h *hello) Hello() { fmt.Println(“Hello from Go”) }
  50. 振る舞いを抽象化する https://go.dev/play/p/TMs_eyxn2uB interfaceを満たしているかチェックできる if _, ok := v.(Hello); ok {

    log.Println("satisfied") }
  51. interfaceの使い方 • 振る舞いを抽象化する • あらゆる型(interface以外)を代入することができる

  52. あらゆる型を代入することができる https://go.dev/play/p/maDUbkHfCvE func main() { var val interface{} val =

    1 val = “a” val = true fmt.Println(val) }
  53. あらゆる型を代入することができる https://go.dev/play/p/8jXj3Evo8AO switch any.(type) { case int: // snip case

    string: // snip case bool: // snip }
  54. あらゆる型を代入することができる • 型によって振る舞いを動的に変更できる ◦ go/ast ▪ astで表現される全てのNodeがPosとEndをという振る舞いを実装しながら固有の実装をもつ ◦ database/sql ▪

    SQLの実行結果をScanするときに型に合わせて振る舞いを変更する
  55. あらゆる型を代入することができる • プロダクトレベルでのユースケースを試してみよう! ◦ train-interfaceに移動 `cd train-interface` ◦ go run

    main.goでサーバーを起動 ◦ 別のタブで curl localhost:8080/ -d '{"id": 1, "name": "a"}' を実行する(1, “a”は好きなのでOK) ◦ go run main.goをしたタブで{1 a}が出力されていたら成功
  56. あらゆる型を代入することができる func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

    // defer r.Body.Close() http.Requestに関してはCloseがいらない var u user body, err := io.ReadAll(r.Body) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if err := json.Unmarshal(body, &u); err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } log.Println(u) w.WriteHeader(http.StatusOK) }) log.Println(http.ListenAndServe(":8080", nil)) }
  57. あらゆる型を代入することができる func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

    // defer r.Body.Close() http.Requestに関してはCloseがいらない var u user body, err := io.ReadAll(r.Body) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if err := json.Unmarshal(body, &u); err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } log.Println(u) w.WriteHeader(http.StatusOK) }) log.Println(http.ListenAndServe(":8080", nil)) }
  58. あらゆる型を代入することができる type user struct { id int `json:"id"` name string

    `json:"name"` }
  59. あらゆる型を代入することができる type user struct { id int `json:"id"` name string

    `json:"name"` } なんで?🤔
  60. あらゆる型を代入することができる type user struct { id int `json:"id"` name string

    `json:"name"` } private field !!
  61. あらゆる型を代入することができる type user struct { ID int `json:"id"` Name string

    `json:"name"` } これで解決?
  62. あらゆる型を代入することができる type user struct { ID int `json:"id"` Name string

    `json:"name"` } 他のパッケージに公開したく ないときは?
  63. あらゆる型を代入することができる • 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L607 u, ut, pv := indirect(v,

    false)
  64. あらゆる型を代入することができる • 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L479 if u, ok := v.Interface().(Unmarshaler);

    ok { return u, nil, reflect.Value{} }
  65. あらゆる型を代入することができる • 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L479 if u, ok := v.Interface().(Unmarshaler);

    ok { return u, nil, reflect.Value{} }
  66. あらゆる型を代入することができる • 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L119 type Unmarshaler interface { UnmarshalJSON([]byte)

    error }
  67. あらゆる型を代入することができる • 実装をみてみよう! https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/enc oding/json/decode.go#L119 type Unmarshaler interface { UnmarshalJSON([]byte)

    error } これを満たせばよさそう!
  68. あらゆる型を代入することができる func (u *user) UnmarshalJSON(b []byte) error { u2 :=

    &struct { ID int Name string }{} if err := json.Unmarshal(b, &u2); err != nil { return err } u.id = u2.ID u.name = u2.Name return nil }
  69. interfaceのまとめ • 振る舞いを抽象化することで利用者側が実装をカスタムできる • 型に合わせて振る舞いを動的に変更できる

  70. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  71. 並行処理と並列処理 並行処理と並列処理の違いを説明できますか? ちょっと考えてみましょう!

  72. 並行処理と並列処理 • 並行処理と並列処理は範囲と点 • 並行処理と並列処理は性質が異なる

  73. 並行処理と並列処理 • 並行処理と並列処理は範囲と点 • 並行処理と並列処理は性質が異なる

  74. 並行処理と並列処理は範囲と点 並行処理(Concurrency) 並列処理(Parallelism)   プロセスB プロセスA 並行処理時間 プロセスA   プロセスB

    プロセスA   プロセスB プロセスA Or
  75. 並行処理と並列処理 Concurrency is not parallelism プログラムでは、並行処理とは独立して実行されるプロセスの合成であり、並列処理とは(おそらく関連 する)計算を同時に実行することです。同時並行とは、一度にたくさんのことを処理することです。並列 処理とは一度にたくさんのことを行うことです。

  76. 並行処理と並列処理は範囲と点 並行処理(Concurrency) 並列処理(Parallelism)   プロセスB プロセスA 並行処理時間 プロセスA   プロセスB

    プロセスA   プロセスB プロセスA Or 同時にたくさん処理する 同時にたくさん扱うor処理する
  77. 並行処理と並列処理 • 並行処理と並列処理は範囲と点 • 並行処理と並列処理は性質が異なる

  78. 並行処理と並列処理は性質が異なる 並行性はコードの性質を指し、並列性は動作しているプログラムの性質を指します。 Ref: Go言語による並行処理

  79. 並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func

    main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) }
  80. 並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func

    main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) }
  81. 並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func

    main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) } • このコードに期待することは並行性?並列 性? • CPUのコアが1だった時に期待するのは 並行性?並列性? • CPUのコアが複数だったら求めるものは並 行性?並列性?
  82. 並行処理と並列処理は性質が異なる https://go.dev/play/p/Hy6oGQdkYoD package main import ( "log" "time" ) func

    main() { go func() { log.Println("goroutine 1") }() go func() { log.Println("goroutine 2") }() time.Sleep(time.Second * 2) } • このコードに期待することは並行性?並列 性? • CPUのコアが1だった時に期待するのは並 行性?並列性? • CPUのコアが複数だったら求めるものは並 行性?並列性? • 並列に実行される可能性を期待している • コードは並行性を期待する • 実行環境が並列性を決定する
  83. goroutineとは? • 簡単に並行処理ができる軽量スレッド • ブロッキング(IO待ちなど)すると自動で別のgoroutineが他の実行可能なスレッド(Go runtime上の スレッド)で実行される • goroutineはGoプログラムの最小実行単位であり、entry pointのfunc

    mainはruntime/proc.go にあるfunc mainでコールされており、proc.goのfunc mainはmain goroutineと呼ばれる FYI https://go.dev/doc/faq#goroutines
  84. goroutineは制御できない • goroutineはcoroutineと似ている • 違いはブロックされると実行可能なcoroutineを別のスレッドに移動する

  85. coroutineとは? • 中断・再開が可能なプログラム Python def hello(): print(‘Hello’, end=’ ‘) yield

    // 1 print(‘World’) yield // 2 h = hello() // 1まで h.__next__() // 1から2まで h.__next__() // 2から最後まで Kotlin fun runMain(): Join = viewModelScope.launch { val world = World() println(“Hello, ${world}) } suspend fun World(): String = withContext(Dispatchers.IO) { delay(2000) return “World” }
  86. goroutineは実行順も制御できない https://www.voidcanvas.com/nodejs-event-loop https://zenn.dev/mmomm/articles/ff83eb49a7b642#%E5%8F% 82%E8%80%83%E8%B3%87%E6%96%99

  87. goroutineは実行順も制御できない setTimeout(() => console.log(4)); setImmediate(() => console.log(6)); process.nextTick(() => console.log(2));

    Promise.resolve().then(() => console.log(3)); console.log(1); require('fs').readFile('sample.txt', (err, data) => { console.log(5); }); https://www.voidcanvas.com/nodejs-event-loop https://zenn.dev/mmomm/articles/ff83eb49a7b642#%E5%8F% 82%E8%80%83%E8%B3%87%E6%96%99
  88. goroutineは制御できない • 開発者側が中断と再開を制御できない • いつ起動し、終了するかも異なる可能性がある • ブロックされたときにGo runtimeがスケジューリングして勝手にやってくれる

  89. Go scheduler • goroutineで発生するIOのブロッキングなどをユーザーが意識せずとも制御してくれる • ユーザーが気にしなければいけないのは何をするか、どこで同期するか • M:Nのグリーンスレッドをスケジューリングしている

  90. Go scheduler • goroutineで発生するIOのブロッキングなどをユーザーが意識せずとも制御してくれる • ユーザーが気にしなければいけないのは何をするか、どこで同期するか • M:Nのグリーンスレッドをスケジューリングしている

  91. スレッドモデル • 1: 1 ◦ マシンのコアを全て利用できる。ユーザースレッドとカーネルスレッドが1:1のため別の操作を挟み たい場合は割り込み処理が必要になりコンテキストスイッチに時間がかかる • N:1 ◦

    複数のユーザースレッドが1つのカーネルスレッドと紐づくためコンテキストスイッチが早いが、ユー ザーレベルのライブラリが必要であり、1つのカーネルにN個のプロセスが紐づくため、並行処理が できない • M:N ◦ マルチコアでマルチスレッドを動かすため、コンテキストスイッチが早く並行、並列処理もできる欲張 りセットだが、実装が複雑になる
  92. 私用PCで何個作れるかチャレンジした package main import ( "log" "runtime" "time" ) func

    main() { defer func() { log.Println(runtime.NumGoroutine()) }() go func() { for { go func() { time.Sleep(time.Second * 60) }() } }() time.Sleep(time.Second * 30) } • 100個 • 5000個 • 10万個 • 1000万個 ヒント: コア数は8です
  93. 私用PCで何個作れるかチャレンジした package main import ( "log" "runtime" "time" ) func

    main() { defer func() { log.Println(runtime.NumGoroutine()) }() go func() { for { go func() { time.Sleep(time.Second * 60) }() } }() time.Sleep(time.Second * 30) } • 100個 • 5000個 • 10万個 • 1000万個 (12319732) ヒント: コア数は8です
  94. Go schedulerの動きをみる • train-goroutine/procsに移動 • run.shを実行 • P0: ~~やG1: ~~などが出てきて最後にSCHED:

    ~~がでれば成功
  95. Go schedulerの動きをみる • GOMAXPROCSというオプションによりコア数を1にしている • 出力結果をみるとP0しかないことがわかる • 内部では複数のMとGという何かが切り替わりながらスケジューリングされている

  96. Go schedulerの簡単な説明 • schdulerのG, M, Pってなに? ◦ Gはgoroutine ◦ Mはgoroutineを実行する場所

    ◦ Pはgoroutineを実行するリソースをもつOSのCPUのようなもの P M G G G G HACKING.md
  97. Go schedulerの動きをみる • つまり内部的にはP0(GOMAXPROCS=1)に対して複数の実行する場所であるMとgoroutineであ るGが存在し実行されていることがわかる • CPUに関係なくGo runtimeの中で複数のプロセスが実行されているということはM:Nであるといえ る

  98. Go channelとは? • 並行に実行している関数同士が特定の方の値を送受信するためのもの • channel<- や <-channelという構文でデータのコミュニケーションをとる

  99. Go channelに触れてみよう • train-goroutine/chanに移動する • go run main.goでプログラムを実行 • 最後にendと出力されれば成功

  100. Go channelに触れてみよう func main() { ch := make(chan int) go

    func() { time.Sleep(time.Second * 2) ch <- 1 }() log.Println("wait") log.Println(<-ch) close(ch) go func() { time.Sleep(time.Second * 2) // closeしても遅れるが初期値が返却されるようになる ch <- 1 }() log.Println(<-ch) // closeを2回するとpanicする // close(ch) log.Println("end") }
  101. Go channelに触れてみよう func main() { ch := make(chan int) go

    func() { time.Sleep(time.Second * 2) ch <- 1 }() log.Println("wait") log.Println(<-ch) close(ch) go func() { time.Sleep(time.Second * 2) // closeしても遅れるが初期値が返却されるようになる ch <- 1 }() log.Println(<-ch) // closeを2回するとpanicする // close(ch) コメントを外して実行してみよう log.Println("end") }
  102. Deadlock package main func main() { ch := make(chan struct{})

    <-ch } • 並行に実行している関数同士が特定の方の値を 送受信するためのもの • 並行に実行し、chに送信しているgo routineが 存在しない • CT(compile time)ではわからずRT(runtime) でしか判明しない
  103. Go channelに触れてみよう • 2回チャネルをcloseするとpanicしてしまうので呼び出し側でcloseすることを意識する • Deadlockには注意する

  104. WaitGroup • このプログラムを実行してみよう package main import “log” func main() {

    go func() { log.Println(“goroutine”) }() }
  105. WaitGroup • goroutineの起動と終了はユーザーが制御することはできない • 複数のgoroutineをバックグラウンドで動かさずどこかで同期したい • 同期のために何かしらブロッキングするためのものが欲しい

  106. WaitGroup • このプログラムを実行してみよう package main import ( "log" "sync" )

    func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() log.Println("goroutine") }() wg.Wait() }
  107. WaitGroup • Add: 終了待ちのgoroutineの数だけ内部のstateをインクリメントする • Done: 終了したgoroutineが呼び内部のstateをデクリメントする • Wait: 内部のstateが0になるまでブロックする

  108. WaitGroupの注意点 • 失敗するコードを実行する ◦ train-goroutine/waitに移動する ◦ go run main.goを実行 ◦

    fatal: errorがでれば一旦は成功
  109. WaitGroupの注意点 Q. なぜ fatal error: all goroutines are asleep -

    deadlock!になってしまう? ヒント 内部のstateは共有されます
  110. WaitGroupの注意点 Q. なぜ fatal error: all goroutines are asleep -

    deadlock!になってしまう? ヒント 内部のstateは共有されます A. sync.WaitGroupがコピーされてしまうから
  111. WaitGroupの注意点 • go vet を実行してみよう ◦ train-goroutine/waitに移動 ◦ go vet

    main.goを実行 ◦ 標準出力にcopies lock value ~が出力されたら成功 go vet: goが提供するtool群の1つでよくあるエラーを静的解析で発見する
  112. WaitGroupの注意点 • go funcの引数をポインタにする go func(wg *sync.WaitGroup) { // カウントを減らす

    defer wg.Done() log.Println(i) }(&wg) sync.WaitGroupを関数に渡すときは 注意!
  113. Mutex • 並行性処理における排他制御の問題 package main import ( "fmt" "time" )

    func main() { c := 0 for i := 0; i < 1000; i++ { go func() { c++ }() } time.Sleep(time.Second) fmt.Println(c) } • 出力されるcは?先に予想しよう ◦ train-goroutine/mutex/raceに移動 ◦ go run main.go ◦ 出力された結果をチャットにコメント
  114. Mutex • 複数のgoroutineがどの数字をインクリメントするかはタイミングによる ◦ Aがcを0から1に ◦ Bがcを1から2に ◦ Cが同じタイミングでcを1から2に

  115. Mutex • mapだとpanicする ◦ Go1.16 から検出するようになった if one goroutine is

    writing to a map, no other goroutine should be reading or writing the map concurrently. If the runtime detects this condition, it prints a diagnosis and crashes the program
  116. Mutex • sync.MutexはこのようなData Raceに対する明示的な排他制御を提供する • sync.Mutexとsync.RWMutexが存在する ◦ sync.Mutexはシンプルなロックを提供する ▪ ReadとWriteがそれぞれでロックをとり別のgoroutineの処理をまつ

    ◦ sync.RWMutexは共有ロックを提供する ▪ ReadとWriteが共有ロックをもち、Writeは別のgoroutineをまつがReadは許可
  117. Mutex • プログラムを実行してみる ◦ train-goroutine/mutexに移動 ◦ go run main.goを実行 ◦

    endと最後に出力されれば成功
  118. Mutex • プロダクトレベルでの使い方 ◦ インメモリキャッシュ ◦ グローバルリソースへのアクセス

  119. select go func() { ch1 <- 1 }() go func()

    { ch2 <- 2 }() select { case v1, ok := <-ch1: // case v2, ok := <-ch2: // default: }
  120. select go func() { ch1 <- 1 }() go func()

    { ch2 <- 2 }() select { case v1, ok := <-ch1: // case v2, ok := <-ch2: // default: } • channelの受け取りを多重化できる • switchのように上から評価されず受け取れたものか ら • どれにも該当しない場合はdefaultへ • defaultがない場合はブロックされる
  121. semaphore • 制御なしでgoroutineを生成すると当然パフォーマンスは下がる • rate limitが存在すると制御しないといけない • train-goroutine配下のコードを実行してみよう ◦ nosemaphoreとsemaphoreを実行し違いを確認してみよう

  122. semaphore • goroutineの実行数を制御することができる ◦ bufferありのchannelを使用する ◦ semaphore packageを使用する

  123. bufferありのchannelを使用する PlayGround ch := make(chan struct{}, 1) for { ch<-struct{}{}

    go func() { defer func() { <-ch }() // }() }
  124. semaphore packageを使用する PlayGround var s *semaphore.Weighted = semaphore.NewWeighted(1) func longProcess(ctx

    context.Context) { // Acquireで取得するとcountが減る // 0になるとブロックする if err := s.Acquire(ctx, 1); err != nil { fmt.Println(err) return } // カウントを戻す defer s.Release(1) fmt.Println("Wait...") time.Sleep(1 * time.Second) fmt.Println("Done") }
  125. errgroup • goroutine で呼ぶ関数の戻り値は受け取ることができない ◦ 内部でerror型を扱いたいけどreturnでerrorを渡せない ◦ 既存の機能を扱う場合error型のchannelを渡すことで対応できる ◦ errorが発生しないケースなどをかんがえるとハンドリングが煩雑になる

  126. errgroup • sync.WaitGroup + error型を返せるgoroutineを利用できる ◦ errgroup.Group.Goでerror型を返すgoroutineを生成できる ◦ errgroup.Group.Waitでsync.Waitのように待ちながら生成したgoroutineでerrorがないか チェックできる

    ◦ errgroup.WithContextで後述のcontextも扱うことができる
  127. errgroupの注意点 • errgroupが取得できるエラーは一番最初に発生したもののみ ◦ 5つのうち2つエラーが発生した場合に全てのエラーを受け取れない ◦ contextによる処理のキャンセルが存在するため • 複数のエラーをまとめたい場合はhashicorp/go-multierrorがある ◦

    基本的にはerrgroupと同じだが、全てのエラーを受け取れる ◦ errgroupと異なりcontextを用いた処理のキャンセルが存在しない
  128. goroutine leak • goroutineはユーザーから制御できない ◦ プロセスとして残り続ける可能性がある ◦ 待ちのプロセスは残り続けるだけだが参照されている変数などがGCされずOOMする

  129. goroutine leak func main() { log.Printf("before leak:%d\n", runtime.NumGoroutine()) leak(nil) time.Sleep(3

    * time.Second) log.Printf("after leak:%d\n", runtime.NumGoroutine()) } func leak(c <-chan string) { // closeされない!! go func() { for cc := range c { log.Println(cc) } }() log.Printf("in leak:%d\n", runtime.NumGoroutine()) } before leak: 1 in leak: 2 after leak: 2
  130. goroutine leak • 対処法 ◦ 後述のcontextとselectなどでしっかりと抜けるようにする ◦ ブロッキングする処理やデーモン、ループ処理は終了する条件を正しくかく

  131. goroutineのまとめ • 容易に並行処理を実行することができるようにGoが提供する軽量スレッド • 排他制御や同期するための標準パッケージを活用する • goroutine leakに注意する

  132. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  133. contextとは contextパッケージの中で定義されているContext型は、デッドライン、キャンセルシグナル、その他のリ クエストに応じた値をAPI境界やプロセス間で伝達します FYI: Context

  134. contextとは contextパッケージの中で定義されているContext型は、デッドライン、キャンセルシグナル、その他のリ クエストに応じた値をAPI境界やプロセス間で伝達します FYI: Context ❓

  135. contextとは • ListenAndServeはリクエストごとにgoroutineを生成する • 実践的に考えるとそれぞれのgoroutineがさらにgoroutineを生成する可能性がある(DB, 外部API へのリクエスト) • 意識せず多くのgoroutineを生成している可能性がある(goroutine leakのリスク)

    • 3rd packageをソースコードレベルで意識する必要が出てくる • Aは処理を止めたいがBは続けたい ◦ channelを使用すると煩雑になる
  136. contextとchannelの比較 • 実際にコードをみてみよう ◦ train-context/why-ctxに移動 ◦ proc1はchannelを使用した例、proc3はcontextを使用した例 ◦ 25行目をコメントアウトしてgoroutine leakが発生していることを確認してみよう

  137. contextとは • contextを使用すると煩雑な処理をシンプルにすることができる • cancelを用いることでAPIリクエストやある処理のキャンセルができる ◦ 10秒以上かかったらキャンセルする ◦ メインプロセスが終了したためgoroutineを全て終了する

  138. context cancel • train-context/ctx-cancelを実行してみよう • context.WithCancelを用いることでcancelCtxとcancelFuncが返ってくる • cancelFuncを呼ぶことでcontext内部のchannelをcloseする • context.Done()経由でcancelされたかどうかを確認できる

  139. context timeout • train-context/ctx-timeoutを実行してみよう • context.WithTimeoutを用いることでtimerCtxとcancelFuncが返ってくる • cancelFuncを呼ぶ、ある秒数が過ぎることでcontext内部のchannelをcloseする • context.Done()経由でcancelされたかどうかを確認できる

  140. context deadline • train-context/ctx-deadlineを実行してみよう • context.WithDeadlineを用いることでtimerCtxとcancelFuncが返ってくる • cancelFuncを呼ぶ、ある時刻から指定秒が過ぎることでcontext内部のchannelをcloseする • context.Done()経由でcancelされたかどうかを確認できる

  141. contextのcancelFuncは必ず呼ぶ • contextのcancelFuncはdeferなどで必ず呼ぶことを忘れない ◦ channelを2回以上cancelするとpanicするがcontextは制御してくれている ◦ Documentにも書かれている ▪ キャンセルしないとcontextがメモリに残り続ける可能性がある ▪

    go vetを使うことでチェックもできる
  142. contextを用いて値を伝播する • contextはAPIのキャンセル以外にも特定の値をkey/valueで伝播させられる ◦ 分散システムでTrace IDを伝播させたい ◦ 認証トークン ◦ ユーザーID

    ◦ Query Cache
  143. contextを用いて値を伝播する ctx := context.WithValue(ctx, “key”, “value) ctx.Value(“key”)

  144. context.Valueのtips • keyにはempty structを使うことでメモリを節約できる ◦ train-context/ctx-valueを実行してみよう ◦ stringは16byte、empty structは0byte •

    context.Valueのkeyはprivateにする ◦ context.Valueに同じkeyをsetすると上書きされる ◦ privateにすることでsetとgetを関数経由でのみ行えるようにできる
  145. contextの親子関係と兄弟関係 • train-context/parent-childを実行してみよう ◦ contextは親子関係、兄弟関係になる可能性がある ◦ 親子関係は親のキャンセルが子へ伝播し逆はない ◦ context.Valueの場合は子から親へ探索する ◦

    兄弟関係のcontextは伝播しない ◦ timeoutは親より短かければそちらが優先される
  146. contextは親子関係、兄弟関係になる context.WithCancel context.WithValue context.WithTimeout context.WithDeadline

  147. キャンセルは親から子へ伝播する context.WithCancel context.WithValue context.WithTimeout context.WithDeadline ❌ ❌ ❌

  148. キャンセルは親から子へ伝播する context.WithCancel context.WithValue context.WithTimeout context.WithDeadline ❌ 子から親への伝播はない

  149. contextの親子関係と兄弟関係 • train-context/parent-childを実行してみよう ◦ contextは親子関係、兄弟関係になる可能性がある ◦ 親子関係は親のキャンセルが子へ伝播し逆はない ◦ context.Valueの場合は子から親へ探索する ◦

    兄弟関係のcontextは伝播しない ◦ timeoutは親より短かければそちらが優先される
  150. context.Valueは子から親へ探索する context.WithValue context.WithValue context.WithValue 1 2 3 4

  151. context.Valueは子から親へ探索する context.WithValue context.WithValue context.WithValue 1 2 3 4 2が欲しい

  152. context.Valueは子から親へ探索する context.WithValue context.WithValue context.WithValue 1 2 3 4 2が欲しい ❌

  153. 兄弟関係は伝播しない context.WithValue context.WithValue context.WithValue 1 2 3 4 4が欲しい ❌

  154. timeoutは短い方が優先される context.WithTimeout 5sec 5sec context.WithTimeout 3sec 3sec 5sec context.WithTimeout 7sec

  155. • structに含めない ◦ contextにセットする値はリクエストスコープに閉じるものにする ▪ structに含めるとリクエストスコープを超えてしまう • トークンの漏洩 • 意図しない値の上書き

    ◦ 第一引数で渡す Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. context contextのアンチパターン
  156. • net/httpは例外 ◦ contextは1.7から追加された ◦ Goは後方置換性を重視しているため、APIレベルでの変更はGo1の段階ではない ◦ net/httpは1Requestごとに1goroutineを生成するためリクエストスコープに絞れるためcontext のアンチパターンは踏まない •

    database/sql ◦ database/sqlはXXXContextのようなAPIを追加した ◦ Transactionスコープの管理をするためにcontext.Contextをstructに含んでいる アンチパターンの例外
  157. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  158. • Goが提供するDB操作を行うための標準パッケージ • 実際に利用する場合はdatabase/sqlと使用したいDBのSQL Driverをインポートしdatabase/sqlを 介して利用する • database/sqlはコネクションプールの管理等を行い特定のDBへの実装は提供していない ◦ 提供しているのはinterface

    ◦ 具体的な実装はinterfaceを満たしたSQL Driverが行う ◦ 公式のwikiには56個掲載されている ◦ 最近GoogleからSpannerのDriverもリリースされた database/sqlとは
  159. • ORMもdatabase/sqlをラップする形で実装している ◦ GORM ▪ GORMがdatabase/sqlを満たすようにSQL Driverを提供している ◦ Ent ▪

    database/sqlの実装をラップして拡張している database/sqlとは
  160. • train-dbに移動 • docker-compose up —build -dでコンテナを起動(起動まで少し時間がかかる) • go run

    main.goを実行 database/sqlに触れてみる
  161. import ( "context" "database/sql" "log" _ "github.com/go-sql-driver/mysql" ) database/sqlに触れてみる

  162. • Blank import インポート宣言は、インポートするパッケージとインポートされるパッケージの依存関係を宣言するもので ある。パッケージがそれ自身を直接または間接的にインポートすることや、エクスポートされた識別子を 一切参照せずにパッケージを直接インポートすることは違法です。パッケージの副作用(初期化)のため だけにパッケージをインポートするには、明示的なパッケージ名として空白の識別子を使用します。 database/sqlに触れてみる

  163. • Blank import インポート宣言は、インポートするパッケージとインポートされるパッケージの依存関係を宣言するもので ある。パッケージがそれ自身を直接または間接的にインポートすることや、エクスポートされた識別子を 一切参照せずにパッケージを直接インポートすることは違法です。パッケージの副作用(初期化)のため だけにパッケージをインポートするには、明示的なパッケージ名として空白の識別子を使用します。 database/sqlに触れてみる

  164. • Goのinitは組み込みの初期化関数 • 依存先のパッケージにinitがある場合先に依存先が解決される • initを同一パッケージで複数書いた場合ファイルの上から順番に解決される • SQL Driverがinitを用いてinterfaceの具象実装をdatabase/sqlから呼べるようにする database/sqlに触れてみる

  165. db, err := sql.Open("mysql", "dsn") if err != nil {

    log.Printf("failed to open a db err = %s", err.Error()) return } if err := db.PingContext(context.Background()); err != nil { log.Printf("failed to ping err = %s", err.Error()) return } database/sqlに触れてみる
  166. Q. DBと最初にコネクションを張るのはどっち? db, err := sql.Open("mysql", "dsn") if err !=

    nil { log.Printf("failed to open a db err = %s", err.Error()) return } if err := db.PingContext(context.Background()); err != nil { log.Printf("failed to ping err = %s", err.Error()) return } database/sqlに触れてみる
  167. A. PingContext db, err := sql.Open("mysql", "dsn") if err !=

    nil { log.Printf("failed to open a db err = %s", err.Error()) return } if err := db.PingContext(context.Background()); err != nil { log.Printf("failed to ping err = %s", err.Error()) return } database/sqlに触れてみる
  168. • sql.OpenはSQL Driverによるが必ずコネクションを張るかはわからない ◦ MySQL Driverはコネクションを張っていない • 疎通確認は必ずPing/PingContextで確認する database/sqlに触れてみる

  169. • 複数レコードを取得する場合はQuery/QueryContextを利用する ◦ 返り値としてRowsが取得できる ▪ Rows.NextでScanするレコードを確認する ▪ Rows.Scanでデータを特定の変数、structのフィールドにマッピングする • 単一レコードを取得する場合はQueryRow/QueryRowContextを利用する

    ◦ 返り値としてRowが取得できる ▪ Row.Scanでデータを特定の変数、structのフィールドにマッピングする • INSERT/UPDATE/DELETE etc..はExec/ExecContextを利用する database/sqlに触れてみる
  170. • トランザクションはBegin/BeginTxで取得する ◦ Query/QueryRow/Execなどを同じように使える ◦ Commitでトランザクションをコミットする ◦ Rollbackでトランザクションをロールバックする database/sqlに触れてみる

  171. • database/sqlを使用する際は同時に使いたいDBのSQL Driverもインポートする • 疎通確認はPingを使う • 発行したいクエリに合わせてメソッドやトランザクションをうまく利用する database/sqlのまとめ

  172. 今回触れる機能 • http server • interface • goroutine • context

    • database/sql • testing
  173. • Goのテストはgo testでUTやBenchmarkなどさまざまなテストを実行できる • ファイルのサフィックスに_testがついているものをテスト対象とする • テストファイルはビルド対象にならない Goにおけるテスト

  174. • Test ◦ プレフィックスにTestがついている ◦ ユニットテストなど • Benchmark ◦ プレフィックスにBenchmarkがついている

    ◦ パフォーマンスの比較などで使う • Fuzz ◦ プレフィックスにFuzzがついている ◦ 初期で与えられた値からランダムな値を生成して入力値からバグを発見する • Example ◦ プレフィックスにExampleがついている ◦ 出力結果のテストやGoDocにサンプルとして載せることができる Goのテストの種類
  175. func TestSum(t *testing.T) { got := Sum(1, 1) want :=

    2 if got != want { t.Errorf(“got %d\n want %d”, got, want) return } } Goのテストサンプル
  176. • Goのtesting packageにはLog、Skip、ヘルパーなどしか存在しない • assertの3rd packageは存在するが標準ではない Goのtestingにはassertがない

  177. FAQ: Why does Go not have assertions? • Assertは便利だがエラーメッセージが自動で出力されるため、レポート内容が適当になる •

    エラーレポートは何が起きたかを書くことが重要なためしっかりと自分で書く • 覚えることを増やすことより丁寧に書き、今後のデバッグの際に役立つようにしよう Goのtestingにはassertがない
  178. • GoではテストをTable Driven Testで書くことが推奨されている • 公式のwikiにも書き方などが存在する • テストケースの入出力を1つのstructにまとめ、すべてのテストケースをforを用いて実行する Table Driven

    Test
  179. • Table Driven Testを実行してみよう ◦ train-testingに移動する ◦ go test tdd_test.go

    main.go -vを実行 ◦ PASSと出力されれば成功 Table Driven Test
  180. tests := []struct { name string args args want int

    wantErr bool } { { name: “success”, args: args { i: 1, j: 2, }, want: 3, wantErr: false, }, } Table Driven Test
  181. tests := []struct { name string args args want int

    wantErr bool } { { name: “success”, args: args { i: 1, j: 2, }, want: 3, wantErr: false, }, } Table Driven Test この1つの塊がテストケース
  182. • 新しいテストケースの追加が容易 • テーブルをみることで入出力から期待する内容を理解できる • structとmapでは実行順序が変わる可能性があるため注意する • Goに限られたテスト手法ではない Table Driven

    Testのポイント
  183. • Add関数は負の値を受け取るとエラーを返す • 実際に入力値が負の値で、エラーケースが正しく実行されることを確認するためのテストケースを書い てみよう テストケースを追加してみよう

  184. • Goにはtesting.Parallelという関数がある • testing.Parallelを使うことで同一パッケージ内のテストを逐次ではなく並行に実行できる Table Driven Testの実行時間を短くする

  185. • Parallel Testを実行してみよう ◦ train-testingに移動 ◦ go test parallel_test.go main.go

    -vを実行 ◦ PASSすれば成功 Table Driven Testの実行時間を短くする
  186. • サブテストをParallelで並行にする際は値をコピーする(goroutineの起動よりもforが早い) • Parallelなサブテストはトップレベルのテストが実行されたあとに処理される Parallel Testの注意点

  187. • go testでも-raceオプションを使うことができる • -raceオプションを有効にすることでデータの競合を検出することができる Testを実行してraceを検出する

  188. • -raceオプションを使ってみよう ◦ train-testingに移動する ◦ go test -race race_test.go main.go

    -vを実行 ◦ WARNING: DATA RACEと出力されれば成功 Testを実行してraceを検出する
  189. • raceするテストではracememを生成したgoroutineとTestRaceのgoroutineどちらからも操作され ている • データの競合を避けるためには排他制御をしてあげる必要がある Testを実行してraceを検出する

  190. • Cleanup ◦ Cleanupを呼び出しているテスト関数の一番最後に呼ばれる ◦ Parallelを使うとトップレベルが先に終了するため絶対に最後にしたい処理はCleanupを使う • Setenv ◦ Cleanupを用いてそのテスト関数だけで使える環境変数をセットできる

    • Tempdir ◦ Cleanupを用いてそのテスト関数だけで使える一時ディレクトリを作成することができる • Helper ◦ ヘルパー関数などを読んだときに呼び出し先の行数でエラーなどが出力されるようになる ◦ テストのレポート情報をより正確に伝えることができる testingパッケージの便利メソッド
  191. 「推測するな、計測せよ」 • AとBの書き方のどちらを採用するとプログラムのパフォーマンスがいいか悩む • 採用する際には誰がみてもわかる説得力が重要 • Benchmark Testを書いて判断に理由を持たせる Benchmarkでパフォーマンスを計測する

  192. • Benchmark Testを実行してみよう ◦ 計測対象はスライスのcapacityを事前に確保したほうがいいのかしない方がいいのかどうか ◦ train-testing/benchに移動する ◦ go test

    -bench . -benchmemを実行 ◦ PASSすれば成功 Benchmarkでパフォーマンスを計測する
  193. • Benchmark Testの結果のみかた ◦ 数字: 実行した回数 ◦ ns/op: 1回あたりの実行にかかった時間 ◦

    B/op: 1回あたりのアロケーションで確保した容量 ◦ allocas/op: 1回あたりのアロケーション回数 → 結果から先に容量を確保した方が断然にいいことがわかる Benchmarkでパフォーマンスを計測する
  194. • ユニットテストなどでは見つけられないエッジケースを発見する • プログラムに対してさまざまな入力を与える • Go本体ではjsonなどのエンコード、デコードのテストで採用されている Fuzzing Testでバグを発見する

  195. • 公式サイトにも説明やチュートリアルが存在する Fuzzing Testでバグを発見する

  196. • Fuzzing Testを実行してみよう ◦ train-testingに移動する ◦ go test -fuzz FuzzCalc

    fuzzing_test.go -fuzztime 10sを実行 ◦ 色々出てきてエラーが吐かれたら成功 Fuzzing Testでバグを発見する
  197. func FuzzCalc(f *testing.F) { f.Add(1, 2, "+") f.Fuzz(func(t *testing.T, v1,

    v2 int, ope string) { _, _ = Calc(v1, v2, ope) }) } func Calc(v1, v2 int, ope string) (int, error) { switch ope { case "+": return v1 + v2, nil case "-": return v1 - v2, nil case "*": return v1 * v2, nil case "/": return v1 / v2, nil } return 0, errors.New("") } Fuzzing Testでバグを発見する
  198. func FuzzCalc(f *testing.F) { f.Add(1, 2, "+") f.Fuzz(func(t *testing.T, v1,

    v2 int, ope string) { _, _ = Calc(v1, v2, ope) }) } func Calc(v1, v2 int, ope string) (int, error) { switch ope { case "+": return v1 + v2, nil case "-": return v1 - v2, nil case "*": return v1 * v2, nil case "/": return v1 / v2, nil } return 0, errors.New("") } Fuzzing Testでバグを発見する もしv2が0なら?
  199. • Fuzzing testはfuzztimeを決めないと無限に実行される ◦ CIに組み込む際は絶対にfuzztimeを設定する • Fuzzing testに失敗した場合testdataに失敗した時の入力値が保存される ◦ 次の実行はこの入力値から実行されるため修正すればPASSされる

    Fuzzing Testのポイント
  200. • Testを書くことは難しい ◦ 外部APIやクラウドサービスをどのようにテストするか ◦ Rate limitを気にしないといけない ◦ API Keyが必要な場合どのように共有する?

    ◦ BQのようにプロセッシングにお金が絡むようなケース Testとmock
  201. • mockを使用してテストする ◦ 関数の呼び出しを期待する引数で呼ばれることを確認し、ダミーの結果を返却する ▪ GetUserという関数をUserIDが1である引数で呼ばれることを期待し、成功した場合Name: Gopherを返却する ◦ mockした関数が期待する引数で呼ばれなかったり、そもそも呼ばれない場合失敗する ◦

    Spyは入出力を記録するだけでMockの呼び出し時点では評価されない ◦ Stubは常に何かしら置き換えたものを返すだけ Testとmock
  202. • gomock ◦ 定義されたinterfaceからmockを自動生成してくれる ◦ mockgenコマンドで生成できる ◦ go generateと一緒につかわれることがおおい ▪

    go generateを実行することでファイルに存在する//go:generate ディレクティブを実行できる Testとmock
  203. • mockを体験してみよう ◦ train-testingに移動してdb.goを見てみよう ◦ go generate db.goを実行する ◦ mockディレクトリとmock_db.goが生成されていれば成功

    ◦ go test db_test.go db.go -vを実行する ◦ PASSすれば成功 Testとmock
  204. ctrl := gomock.NewController(t) mockdb := mock.NewMockDB(ctrl) mockdb.EXPECT().Ping(context.Background()).Retrun(nil) usecase := NewUsecase()

    usecase(mockdb, context.Background()) Testとmock 17行目をコメントアウトして15行目と16 行目のコメントを外してみよう
  205. • assertを使うよりも丁寧なレポートメッセージを書くことを意識しよう • BenchmarkやFuzzingを適切に使うことでソフトウェアの品質をあげよう • mockを上手に使い環境に依存しないテストをかこう testingのまとめ

  206. 実践API編

  207. 基礎編 • グループとユーザーがあるアプリケーションに新規のAPIを3つ追加する ◦ ユーザーの新規登録 ◦ UserIDに紐づくグループを全て返す ◦ グループの新規作成 発展編

    • httptest • mockをつかってUnit Testをかいてみる • JWT認証をいれてみる: secret keyはgo-rookie-gymでsign • Transactionをいれる(ユーザー登録) 実践API編
  208. ユーザーの新規作成: 作成後、ユーザーに紐づくグループも1つ作成する(グループ名はなんでもOK) エンドポイント: /user メソッド: Post リクエストボディ: { “name”: string

    } レスポンス: { “id”: int, “group_id”: int } 実践API編
  209. UserIDに紐づくグループを全て返す エンドポイント: /groups?user_id=xx メソッド: Get リクエストボディ: なし レスポンス: [ {

    “id”: int, “name”: string } ] 実践API編
  210. グループの新規作成 エンドポイント: /group メソッド: POST リクエストボディ: { “user_id”: int, “name”:

    string } レスポンス: { “id”: int, } 実践API編
  211. • cmd/server ◦ GoのAPIサーバーの立ち上げや、DIなどの処理をここにかこう • handler ◦ アプリケーション層、リクエスト、レスポンスの処理を記述する • domain

    ◦ userとgroupのモデリング箇所(今回はstruct定義くらいになっちゃう) ◦ repositoryのIFの定義 • usecase ◦ ビジネスロジックを記述する • infrastructure ◦ 永続化の実装を書く(今回はMySQL) • schema ◦ 今回のテーブル定義が記述されている アーキテクチャの説明
  212. • httptest ◦ Goのtestを理解する • mockをつかってUnit Testをかいてみる ◦ golang/mock ◦

    資料のp.200から見返してみる • JWT認証をいれてみる: secret keyはgo-rookie-gymでsign ◦ golang-jwt/jwt • Transactionをいれる(ユーザー登録) ◦ GoのTransactionでラッパー関数 発展編のポイント
  213. Goの最新機能Genericsに触 れてみる

  214. • プログラミング言語の機能・仕様の1つ • 同じコードでさまざまなデータ型を処理できるようにするもの • 公式のチュートリアルもある func Append[T Stringer] (args

    []T) []string { result := make([]string, len(T)) for i, arg := range args { result[i] = arg.String() } return result } Genericsとは
  215. • 関数と型は型パラメータをもつ func Append[T Stringer] (args []T) []string { result

    := make([]string, len(args)) for i, arg := range args { result[i] = arg.String() } return result } GoのGenericsの原則
  216. • 型パラメータが満たすべき制約はinterfaceを型制約として用いることで実現する func Append[T Stringer] (args []T) []string { result

    := make([]string, len(args)) for i, arg := range args { result[i] = arg.String() } return result } type Stringer interface { String() string } GoのGenericsの原則
  217. • Go1.18から追加されたinterface • 比較可能な値であることを制約とするためのもの • ユースケースとしてはmapへの処理 ◦ mapのkeyが存在するかどうかは ==か!=が用いられる comparable

    interface
  218. • Goにはdefined typeと呼ばれる型定義をおこなう機能がある ◦ e.g. type A int • 型の同一性においての言語仕様はType

    identityに定義されている ◦ 2つの型のどちらかがdefined typeであれば結果は常に異なる ◦ 2つの型のいずれもdefined typeでない場合、同一の場合がある Goにおける型の同一性
  219. Q. 以下のコードの型は同一でしょうか? type MyInt int var a MyInt var b

    MyInt a == b Goにおける型の同一性
  220. A. 同一である type MyInt int var a MyInt var b

    MyInt a == b Goにおける型の同一性
  221. Q. 以下のコードの型は同一でしょうか? type MyInt int type MyMyInt int var a

    MyInt var b MyMyInt a == b Goにおける型の同一性
  222. A. 同一ではない type MyInt int type MyMyInt int var a

    MyInt var b MyMyInt a == b Goにおける型の同一性
  223. Q. 以下のコードの型は同一でしょうか? var a struct { ID int Name string

    } var b struct { ID int `json:”id”` Name string `json:”name”` } var c struct { Name string ID int } Goにおける型の同一性 A. a == b B. b == c C. a == c
  224. A. どれも同一ではない var a struct { ID int Name string

    } var b struct { ID int `json:”id”` Name string `json:”name”` } var c struct { Name string ID int } Goにおける型の同一性 A. a == b B. b == c C. a == c
  225. • interface型に受け入れる型とメソッドを列挙する type A interface { string | int String()

    string } Goの型制約の作り方
  226. • defined typeを書きたい時も全部列挙する? type MyInt int type MyMyInt int type

    A interface { string | int | MyInt | MyMyInt String() string } Goの型制約の作り方
  227. • 結局はint型なんだからまとめたい!!! type MyInt int type MyMyInt int type A

    interface { string | int | MyInt | MyMyInt String() string } Goの型制約の作り方
  228. • ~を使うことでunderlying typeが同じであるものを受けいける型制約を作れる type MyInt int type MyMyInt int type

    A interface { string | ~int String() string } Goの型制約の作り方
  229. • Goの全ての型は対応するunderlying typeをもつ • 1つの型に対して必ずunderlying typeは1つしか存在しない Goのunderlying type

  230. type T Xと宣言された場合 • Tがboolean、数値型、文字列型のような型リテラルの場合はTのunderlying typeはTになる • それ以外の場合はTのunderlying typeはXのunderlying typeになる

    Goのunderlying type
  231. Q. intのunderlying typeは? Q. type MyInt intの時のMyIntのunderlying typeは? Q. type

    MyMyInt MyIntの時のMyMyIntのunderlying typeは? Goのunderlying type
  232. Go1.17まで type Stringer interface { String() string } func fn(args

    []Stringer) []string { // } type MyInt int func (myint MyInt) String() string { return strconv.Itoa(int(i)) } func main() { myints := []MyInt{0, 1, 2} fn(myints) } Genericsのユースケース MyIntはStringerを満たしているが[]MyIntは []Stringerと同一ではないため[]Stringerに castする処理が必要
  233. Go1.18から type Stringer interface { String() string } func fn[T

    Stringer](args []T) []string { // } type MyInt int func (myint MyInt) String() string { return strconv.Itoa(int(i)) } func main() { myints := []MyInt{0, 1, 2} fn(myints) } Genericsのユースケース Genericsによりcastの処理が不要になった
  234. • GoのGenericsはCT(Compile Time)で解決できない場合はビルドに失敗する ◦ C++はRuntimeのときに探索し存在しなければStackTraceでRuntime Errorが出力される • 必要以上にGenericsを導入しない ◦ 必要以上にGenericsを入れることで逆にコードの品質を下げる可能性もある

    Genericsのポイント