Open35

【Go】ゴリラと学ぶGo言語入門

svunsvun

for のラベル

LOOP : for {
    for{
        break LOOP
    }
}
  • 通常のbreakは一つのfor文から脱出できる
  • ラベルをつけておくとラベルの外まで脱出できる
svunsvun

fallthrought

  • Goはforやswitchの中で、breakを書かずとも処理が終了する言語
  • fallthroughtを使えば処理の続行ができる
svunsvun

独自の型

  • type hoge int でint型のhogeという型を作成できる
svunsvun

型変換

  • 文字列を数値に変換する例
package main

import (
	"fmt"
	"strconv"
)

func main() {
	str := "5"
	change, _ := strconv.Atoi(str)
	fmt.Println(change)
}

svunsvun
  • 文字(Aなど)を数値に変換すると「0」が返される
package main

import (
	"fmt"
	"strconv"
)

func main() {
	str := "B"
	change, _ := strconv.Atoi(str)
	fmt.Println(change) // 0
}

svunsvun
  • 本来なら、エラー処理してあげるべき
package main

import (
	"fmt"
	"log"
	"strconv"
)

func main() {
	str := "B"
	change, err := strconv.Atoi(str)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(change)
}

svunsvun

構造体

  • 値をまとめておけるやつ
  • typeで構造体の値を定義しておけば、都度定義する必要はない
svunsvun
package main

import (
	"fmt"
)

type Gorilla struct {
	name string
	age  int
}

func main() {
	var gorilla = Gorilla{
		name: "Gorilla",
		age:  27,
	}
	fmt.Println(gorilla.name)
	fmt.Println(gorilla.age)
}

svunsvun

というか

列挙する時の最後にも「,」つけていいタイプの言語だ

svunsvun

メソッド

package main

import (
	"fmt"
)

type Gorilla struct {
	name string
}

func (g Gorilla) Uho() {
	fmt.Println(g.name, g.Banana())
}

func (g Gorilla) Banana() string {
	return "バナナ食べたい"
}

func main() {
	var gorilla = Gorilla{
		name: "Gorilla",
	}

	gorilla.Uho()
}

svunsvun
  • 「func (g Gorilla) Uho() { 」のgは省略できる
  • 省略した場合、構造体にアクセスできないだけ
  • 単に、何かの値を返したい時とかに有用
svunsvun

ポインタ

  • 「*」で構造体の内部が書き換えられるようになるよ
package main

import (
	"fmt"
)

type Gorilla struct {
	Name string
}

func (g *Gorilla) setName(name string) {
	g.Name = name
}

func (g *Gorilla) Uho() {
	fmt.Println(g.Name, g.Banana())
}

func (g Gorilla) Banana() string {
	return "バナナ食べたい"
}

func main() {
	var g = Gorilla{
		Name: "Gorilla",
	}
	g.setName("GoriGorilla")
	fmt.Println(g.Name)
}

svunsvun

メソッドの実態

  • 第一引数に型と変数を受け取る関数である

コンパイル前

func (g *Gorilla) setName(name string) {

コンパイル後

func setName(g *Gorilla, name string) {
svunsvun

ポインタ型

  • ポインタ型のゼロ値はnil
  • 代入するとヌルポ的なことになる
  • 値を取得するときは&を使う
var i int ;
var ip *int;
ip = &i
fmt.Println(ip)
  • 更新は*を使う
func main() {
	var ip = new(int)
         fmt.Println(*ip) // 0
	*ip = 4
	fmt.Println(*ip) // 4
         fmt.Println(ip) // 0x140000a6018
}
svunsvun
  • 講座の中の図がとてもイメージ湧きやすいので是非〜
svunsvun

インターフェース

  • 実装すべきメソッドを強制できるもの
  • implementsに該当するものは無い
svunsvun

アサーション

  • 「型。(他の型)」で型変換できる
  • アサーション時の第二引数でエラーが受取れる!!!
svunsvun

エラーについて

  • try catch 的な考えは無い
  • エラー文を自分で生成するスタイル
svunsvun

並行処理

func main() {
	go func() {
		fmt.Println("waited")
	}()
	fmt.Println("wait...")
	time.Sleep(1 * time.Second)
	fmt.Println("go!!!!!")
}
  1. wait...
  2. waited
  3. 一秒止まる
  4. go!!!!!
svunsvun

他の処理を待ちたいとき

  • WaitGroupを使用する
  • wg.Add(n)でn個待っているという宣言をする
  • 下記は「wait1...」「wait2...」をそれぞれ待つことを宣言する
func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		time.Sleep(1 * time.Second)
		fmt.Println("wait1...")
		wg.Done()
	}()
	wg.Add(1)
	go func() {
		time.Sleep(5 * time.Second)
		fmt.Println("wait2...")
		wg.Done()
	}()
	fmt.Println("waited")
	time.Sleep(1 * time.Second)
	wg.Wait()
	fmt.Println("go!!!!!")
}
svunsvun

wg.Add(2)としたときの例

func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		time.Sleep(1 * time.Second)
		fmt.Println("wait1...")
		wg.Done()
	}()
	go func() {
		time.Sleep(5 * time.Second)
		fmt.Println("wait2...")
		wg.Done()
	}()
	fmt.Println("waited")
	time.Sleep(1 * time.Second)
	wg.Wait()
	fmt.Println("go!!!!!")
}
  • つまり、「wg.Add(n)であればwg.Done()の数はn個」である必要がある
  • 「wg.Done()」はdeferを使用すれば安全だね!
svunsvun
  • Goはデッドロックを感知してくれる!すごい!
svunsvun
  • 並行処理内のエラーはerrGroup.Groupで取り扱う
  • インポートに手こずっているので詳しくは後で
svunsvun

Goルーチン間のデータのやり取り

概要

  • チャネルを使えば、Goルーチン間のデータのやり取りができる
  • make(chan 型) で作成する
  • 「チャネル変数 <-送りたい値」でデータを送ることができる
  • 「<-チャネル変数」でデータを受け取る
  • close(チャネル変数) でチャネルを閉じる。閉じたらゼロ値が送られる

無限ループ対策

  • done<-colose( structs{}) で空の構造体を持たせたものを用意
  • 空の構造体はサイズがゼロである。それゆえに、メモリ上にデータが展開されない。なので省エネ。
svunsvun

複数のチャネルを待つ時

  • selectを使用する
  • switch文みたいな感覚
  • 参照

ポイントは、「select文は上から順番に評価されない」こと。
チャネルへの送受信は実行可能かを判断して、可能であれば実行される。

つまり、送信の場合は「キャパシティ(バッファ)がいっぱいになっていないか」、受信の場合は、「チャネルに値が送信されたか、チャネルが閉じられたか」を確認し、処理を進める準備が出来ていれば 実行される。

もし、どのチャネルも準備が出来ていなければ、defaultが実行される。もしdefaultを省略していたら>select文全体がブロックされる。

svunsvun

「レジ待ち」

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	player_A_waited_second := make(chan int)
	player_B_waited_second := make(chan int)
	ch := make(chan struct{})
	go func() {
		waitSecond := rand.Intn(7)
		fmt.Println("player_A = ", waitSecond)
		time.Sleep(time.Duration(waitSecond) * time.Second)
		waitSecond2 := rand.Intn(7)
		fmt.Println("player_A = ", waitSecond2)
		time.Sleep(time.Duration(waitSecond2) * time.Second)
		player_A_waited_second <- waitSecond + waitSecond2
		close(ch)
	}()

	go func() {
		waitSecond := rand.Intn(7)
		fmt.Println("player_B = ", waitSecond)
		time.Sleep(time.Duration(waitSecond) * time.Second)
		waitSecond2 := rand.Intn(7)
		fmt.Println("player_B = ", waitSecond2)
		time.Sleep(time.Duration(waitSecond2) * time.Second)
		player_B_waited_second <- waitSecond + waitSecond2
		close(ch)
	}()

	select {
	case s := <-player_A_waited_second:
		fmt.Println("player_A waited ...", s)
	case s := <-player_B_waited_second:
		fmt.Println("player_B waited ...", s)
	}

	fmt.Println("Done!!!!!")
}

svunsvun

コンテキストについて

  • 特定の処理をキャンセルする
package main

import (
	"context"
	"time"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				// cancel()が来たらここ
				println("cancel doing")
			default:
				// 6秒間はここ
				println("do something")
			}
			time.Sleep(1 * time.Second)
		}
	}(ctx)

	// 6秒待ってキャンセル
	time.Sleep(6000 * time.Millisecond)
	cancel()
	time.Sleep(1 * time.Second)

}
svunsvun

テストに書き方

  • 簡単に出力を返す例
  • 「go test ./... 」で実行できる
package main

import "testing"

func Echo(name string) string {
	return name 
}

func TestEcho(t *testing.T) {
	want := "gorilla"
	got := Echo(want)
	if got != want {
		t.Fatalf("unexpected result. want=%q, got=%q", want, got)
	}
}

  • 成功例
ok      Users/is/dev-env/go/go/go       0.430s
  • 失敗例(echo関数で返す値に"a"を追加したとき)
    main_test.go:13: unexpected result. want="gorilla", got="gorillaa"
FAIL
FAIL    Users/is/dev-env/go/go/go       0.410s
FAIL
svunsvun
  • -v オプションで詳細を表示
=== RUN   TestEcho
--- PASS: TestEcho (0.00s)
PASS
ok      Users/is/dev-env/go/go/go       0.290s
svunsvun
  • 後続の処理を実行するとき「t.Errorf」
  • 後続の処理を実行しないとき「t.Fatalf」
svunsvun
  • 「t.Run()」でテストケースに名前がつけられる
  • 「t.Prallel()」並行でテストできる
svunsvun
  • -coverオプションで網羅率が確認できる
  • ifやswitchのような命令網羅率(C0)しか出来ない
  • ベンチマークテストもできる!