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

CA 1Day Youth Boot Camp Part of Go

CyberAgent
November 08, 2022

CA 1Day Youth Boot Camp Part of Go

CyberAgent

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

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

    ▪ Go1.18は3/15 • ベータ版 ◦ ベータでは既存のバグは全て修正 ◦ 未知のバグを発見する目的がある ◦ リリースが早いことは推奨されている ◦ 重要なコード変更は追加のベータ版をリリース • リリース候補版 ◦ リリース候補版はほぼリリース版 ◦ 致命的なバグがある場合に追加のリリース候補版がでる ◦ 発行基準の1つがGoogleが本番ビルドに使用する Go Release Cycle
  4. 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
  5. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  6. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  7. シンプルで柔軟な言語設計 Is Go an object-oriented language? Yes and no. •

    型とメソッドがあるため可能ではある • 型(クラス)の継承関係がGoには存在しない • 似たようなものとして埋め込みは提供されている type a struct {} type c interface{} type b struct { a c }
  8. シンプルで柔軟な言語設計 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()) }
  9. シンプルで柔軟な言語設計 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 }
  10. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

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

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

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

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

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

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

    ◦ 2022年9月の時点でGo1.19がリリース ◦ 2月と8月にリリースされる • Goの特徴 ◦ 予約語の少なさ ◦ シンプルで柔軟な言語設計 ◦ 豊富な標準パッケージ ◦ 豊富な標準ツール ◦ シングルバイナリ・クロスコンパイル ◦ goroutineによる並行プログラミング ◦ バージョン管理の容易さ ◦ 多くのOSSライブラリへの採用
  17. Goとは • Googleが開発したオープンソースプロジェクト ◦ 2007年9月、Robert Griesemer, Rob Pike, Ken Thompsonによって開発、2009年11月に発表

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

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

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

    Image Format • コンテナイメージの管理など • RPCでやりとり runc • OCI Runtime Specification • コンテナの実行をする
  21. 早速プログラムを動かそう 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)) }
  22. JSONを受け取って出力しよう • GoではJSONのエンコード、デコード用に encoding/json がある • フィールドにjsonタグをつけることでリクエストボディをマッピングできる • keyでのマッピングなのでデータ型さえあっていればいい •

    設定しない場合フィールド名をキャメルケースにしてマッチするものにマッピングされる ◦ e.g. Age = “age” FullName = “fullName” type User struct { Name string `json:”name”` MyID int `json:”id”` }
  23. 振る舞いを抽象化する https://go.dev/play/p/T77psnc50IN type Hello interface { Hello() } type hello

    struct{} func (h *hello) Hello() { fmt.Println(“Hello from Go”) }
  24. あらゆる型を代入することができる • プロダクトレベルでのユースケースを試してみよう! ◦ 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}が出力されていたら成功
  25. あらゆる型を代入することができる 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)) }
  26. あらゆる型を代入することができる 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)) }
  27. あらゆる型を代入することができる type user struct { ID int `json:"id"` Name string

    `json:"name"` } 他のパッケージに公開したく ないときは?
  28. あらゆる型を代入することができる 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 }
  29. 並行処理と並列処理は範囲と点 並行処理(Concurrency) 並列処理(Parallelism)   プロセスB プロセスA 並行処理時間 プロセスA   プロセスB

    プロセスA   プロセスB プロセスA Or 同時にたくさん処理する 同時にたくさん扱うor処理する
  30. 並行処理と並列処理は性質が異なる 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) }
  31. 並行処理と並列処理は性質が異なる 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) }
  32. 並行処理と並列処理は性質が異なる 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のコアが複数だったら求めるものは並 行性?並列性?
  33. 並行処理と並列処理は性質が異なる 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のコアが複数だったら求めるものは並 行性?並列性? • 並列に実行される可能性を期待している • コードは並行性を期待する • 実行環境が並列性を決定する
  34. 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” }
  35. 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
  36. スレッドモデル • 1: 1 ◦ マシンのコアを全て利用できる。ユーザースレッドとカーネルスレッドが1:1のため別の操作を挟み たい場合は割り込み処理が必要になりコンテキストスイッチに時間がかかる • N:1 ◦

    複数のユーザースレッドが1つのカーネルスレッドと紐づくためコンテキストスイッチが早いが、ユー ザーレベルのライブラリが必要であり、1つのカーネルにN個のプロセスが紐づくため、並行処理が できない • M:N ◦ マルチコアでマルチスレッドを動かすため、コンテキストスイッチが早く並行、並列処理もできる欲張 りセットだが、実装が複雑になる
  37. 私用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です
  38. 私用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です
  39. Go schedulerの簡単な説明 • schdulerのG, M, Pってなに? ◦ Gはgoroutine ◦ Mはgoroutineを実行する場所

    ◦ Pはgoroutineを実行するリソースをもつOSのCPUのようなもの P M G G G G HACKING.md
  40. 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") }
  41. 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") }
  42. Deadlock package main func main() { ch := make(chan struct{})

    <-ch } • 並行に実行している関数同士が特定の方の値を 送受信するためのもの • 並行に実行し、chに送信しているgo routineが 存在しない • CT(compile time)ではわからずRT(runtime) でしか判明しない
  43. 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() }
  44. WaitGroupの注意点 Q. なぜ fatal error: all goroutines are asleep -

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

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

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

    defer wg.Done() log.Println(i) }(&wg) sync.WaitGroupを関数に渡すときは 注意!
  48. 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 ◦ 出力された結果をチャットにコメント
  49. 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
  50. select go func() { ch1 <- 1 }() go func()

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

    { ch2 <- 2 }() select { case v1, ok := <-ch1: // case v2, ok := <-ch2: // default: } • channelの受け取りを多重化できる • switchのように上から評価されず受け取れたものか ら • どれにも該当しない場合はdefaultへ • defaultがない場合はブロックされる
  52. 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") }
  53. 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
  54. 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を関数経由でのみ行えるようにできる
  55. • 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のアンチパターン
  56. 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に触れてみる
  57. 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に触れてみる
  58. 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に触れてみる
  59. • Test ◦ プレフィックスにTestがついている ◦ ユニットテストなど • Benchmark ◦ プレフィックスにBenchmarkがついている

    ◦ パフォーマンスの比較などで使う • Fuzz ◦ プレフィックスにFuzzがついている ◦ 初期で与えられた値からランダムな値を生成して入力値からバグを発見する • Example ◦ プレフィックスにExampleがついている ◦ 出力結果のテストやGoDocにサンプルとして載せることができる Goのテストの種類
  60. 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のテストサンプル
  61. FAQ: Why does Go not have assertions? • Assertは便利だがエラーメッセージが自動で出力されるため、レポート内容が適当になる •

    エラーレポートは何が起きたかを書くことが重要なためしっかりと自分で書く • 覚えることを増やすことより丁寧に書き、今後のデバッグの際に役立つようにしよう Goのtestingにはassertがない
  62. • Table Driven Testを実行してみよう ◦ train-testingに移動する ◦ go test tdd_test.go

    main.go -vを実行 ◦ PASSと出力されれば成功 Table Driven Test
  63. 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
  64. 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つの塊がテストケース
  65. • Parallel Testを実行してみよう ◦ train-testingに移動 ◦ go test parallel_test.go main.go

    -vを実行 ◦ PASSすれば成功 Table Driven Testの実行時間を短くする
  66. • -raceオプションを使ってみよう ◦ train-testingに移動する ◦ go test -race race_test.go main.go

    -vを実行 ◦ WARNING: DATA RACEと出力されれば成功 Testを実行してraceを検出する
  67. • Cleanup ◦ Cleanupを呼び出しているテスト関数の一番最後に呼ばれる ◦ Parallelを使うとトップレベルが先に終了するため絶対に最後にしたい処理はCleanupを使う • Setenv ◦ Cleanupを用いてそのテスト関数だけで使える環境変数をセットできる

    • Tempdir ◦ Cleanupを用いてそのテスト関数だけで使える一時ディレクトリを作成することができる • Helper ◦ ヘルパー関数などを読んだときに呼び出し先の行数でエラーなどが出力されるようになる ◦ テストのレポート情報をより正確に伝えることができる testingパッケージの便利メソッド
  68. • Benchmark Testの結果のみかた ◦ 数字: 実行した回数 ◦ ns/op: 1回あたりの実行にかかった時間 ◦

    B/op: 1回あたりのアロケーションで確保した容量 ◦ allocas/op: 1回あたりのアロケーション回数 → 結果から先に容量を確保した方が断然にいいことがわかる Benchmarkでパフォーマンスを計測する
  69. • Fuzzing Testを実行してみよう ◦ train-testingに移動する ◦ go test -fuzz FuzzCalc

    fuzzing_test.go -fuzztime 10sを実行 ◦ 色々出てきてエラーが吐かれたら成功 Fuzzing Testでバグを発見する
  70. 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でバグを発見する
  71. 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なら?
  72. • gomock ◦ 定義されたinterfaceからmockを自動生成してくれる ◦ mockgenコマンドで生成できる ◦ go generateと一緒につかわれることがおおい ▪

    go generateを実行することでファイルに存在する//go:generate ディレクティブを実行できる Testとmock
  73. 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 行目のコメントを外してみよう
  74. 基礎編 • グループとユーザーがあるアプリケーションに新規のAPIを3つ追加する ◦ ユーザーの新規登録 ◦ UserIDに紐づくグループを全て返す ◦ グループの新規作成 発展編

    • httptest • mockをつかってUnit Testをかいてみる • JWT認証をいれてみる: secret keyはgo-rookie-gymでsign • Transactionをいれる(ユーザー登録) 実践API編
  75. • cmd/server ◦ GoのAPIサーバーの立ち上げや、DIなどの処理をここにかこう • handler ◦ アプリケーション層、リクエスト、レスポンスの処理を記述する • domain

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

    資料のp.200から見返してみる • JWT認証をいれてみる: secret keyはgo-rookie-gymでsign ◦ golang-jwt/jwt • Transactionをいれる(ユーザー登録) ◦ GoのTransactionでラッパー関数 発展編のポイント
  77. • 関数と型は型パラメータをもつ 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の原則
  78. • 型パラメータが満たすべき制約は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の原則
  79. • Goにはdefined typeと呼ばれる型定義をおこなう機能がある ◦ e.g. type A int • 型の同一性においての言語仕様はType

    identityに定義されている ◦ 2つの型のどちらかがdefined typeであれば結果は常に異なる ◦ 2つの型のいずれもdefined typeでない場合、同一の場合がある Goにおける型の同一性
  80. A. 同一である type MyInt int var a MyInt var b

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

    MyInt var b MyMyInt a == b Goにおける型の同一性
  82. 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
  83. 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
  84. • defined typeを書きたい時も全部列挙する? type MyInt int type MyMyInt int type

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

    interface { string | int | MyInt | MyMyInt String() string } Goの型制約の作り方
  86. Q. intのunderlying typeは? Q. type MyInt intの時のMyIntのunderlying typeは? Q. type

    MyMyInt MyIntの時のMyMyIntのunderlying typeは? Goのunderlying type
  87. 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する処理が必要
  88. 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の処理が不要になった