開発メモ

プログラミング言語Go完全入門

May 17, 2020

1. Goに触れる

  • Goの特徴
    • シンプルで協力な記述
      • 機能を増やすことで言語を拡張していくことはしない
    • 並行プログラミング
      • マルチコア前提,並行処理とガーベッジコレクション
      • goroutine: 軽量スレッド的なもの,goキーワードをつけるだけ,e.g. go f()で関数を並行呼び出し
    • 必要なライブラリやツールが標準で同梱されてる
    • シングルバイナリ・クロスコンパイル
      • コマンドラインツールの開発に向いてる
      • 組み込み・IoTにも (TinyGoという組み込み向けのサブセットもある)
      • フロントエンド向け,wasm以外にもGopherJSというのもある
    • 静的解析しやすい言語
  • Goのバージョン
    • Go1の間は後方互換
    • Go2=Go1.xを拡張していって,後方互換が保てなくなる場合にGo2にするというポリシ
  • 重要そうな技術
    • gRPC
    • gVisor
  • 学習ソース

2. 基本構文

  • 数値をアンダースコアで区切れる
    • e.g. 5_000_000
  • 連続整数をiotaで定義できる
    1const (
    2      a = iota
    3      b
    4      c
    5  )
    6  fmt.Printf("%d, %d, %d", a, b, c) // -> 1, 2, 3
    

3. 関数と型

  • 少し特殊な配列定義方法
1	array1 := [...]int{1, 2, 3}
2	fmt.Println(array1) // [1 2 3]
3
4	array2 := [...]int{5: 50, 10: 100}
5	fmt.Println(array2) // [0 0 0 0 0 50 0 0 0 0 10]
  • arrayとslice

    • arrayは固定長,静的
    • sliceは参照型,可変長,動的
      • sliceの容量はメモリ確保済みのサイズなので拡張可能
      • 容量オーバーしたら基本的に2倍の値を確保
  • slice trick

1	// slice tricks: cut
2	aa := []int{1, 2, 3, 4, 5}
3	fmt.Println(aa) // [1, 2, 3, 4, 5]
4	aa = append(aa[:2], aa[3:]...)
5	fmt.Println(aa) // [1, 2, 4, 5]

4. パッケージ

  • go get => dep => modules (vgo)
  • modulesを使う
    • go mod init でgo.modを生成
    • main作成
       1package main
       2    
       3import (
       4    "fmt"
       5    
       6    "github.com/tenntenn/greeting"
       7)
       8    
       9func main() {
      10    fmt.Println(greeting.Do())
      11}
      
    • go run xxx.go するとgo.modにパッケージ追記されて実行できる
      • runする前にgo mod tidyしてもOK
      • vendorディレクトリ配下にパッケージダウンロードするときはgo mod vendor
    • 後方互換のないバージョン使う場合はimport時の記述を変える
       1package main
       2    
       3import (
       4    "fmt"
       5  "time"
       6    
       7    "github.com/tenntenn/greeting/v2"
       8)
       9    
      10func main() {
      11  fmt.Println(greeting.Do(time.Now()))
      12}
      

5. コマンドラインツール

  • forでファイル処理したいとき→forの中でdeferせず処理を関数化して関数の方でdeferする
  • NG
    1for _, fn := range fileNames {
    2  f, err := os.Open(fn)
    3  if err != nil {
    4    // エラー処理
    5  }
    6  defer f.Close() // fを使った処理
    7}
    
  • OK
    1for _, fn := range fileNames {
    2  err := readFile(fn)
    3  if err != nil { /* エラー処理 */ }
    4}
    
    1func readFile(fn string) error {
    2  f, err := os.Open(fn)
    3  if err != nil { return err }
    4    // fを使った処理
    5  defer f.Close()
    6}
    

6. 抽象化

  • 型スイッチ
 1	// type switch
 2	var i interface{}
 3	i = 100
 4	switch v := i.(type) {
 5	case int:
 6		fmt.Println(v * 2)
 7	case string:
 8		fmt.Println(v + "hoge")
 9	default:
10		fmt.Println("default")
11	} 

8. テストとテスタビリティ

  • Exampleで始まる関数名のテストを書くとGoDocにサンプルとして出力される

  • テーブル駆動テスト + サブテスト

 1func TestIsOdd(t *testing.T) {
 2  cases := []struct {name string; input int; expected bool}{
 3    {name: "+odd", input: 5, expected: true},
 4    {name: "+even", input: 6, expected: false},
 5    {name: "-odd", input: -5, expected: true},
 6    {name: "-even", input: -6, expected: false},
 7    {name: "zero", input: 0, expected: false},
 8  }
 9  
10  for _, c := range cases {
11    c := c
12    t.Run(c.name, func(t *testing.T) {
13      if actual := IsOdd(c.input); c.expected != actual {
14        t.Errorf(
15          "want IsOdd(%d) = %v, got %v",
16          c.input, c.expected, actual)
17      }
18    })
19  }
20}
  • テストデータはtestdataというディレクトリに入れる
    • パッケージとして認識されなくなる

9. ゴールーチンとチャネル

  • goroutineが切り替わるタイミング
    • (ブロックされる)チャネルへの読み書き
    • 待ちが発生するシステムコール
    • time.Sleep
    • メモリ割り当て
    • runtime.Gosched
  • チャネルをcloseすると受信場所にゼロ値が送られる=ブロードキャスト,処理終了通知として使われる

Tagged: #Go