2016年度 go研修
TRANSCRIPT
Go入門ver. 2017/04
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.
アジェンダ
■ 自己紹介
■ Goの紹介
■ Goの基本
■ 型・メソッド・インタフェース
■ ゴールーチン・チャネル
■ ネットワークプログラミング
■ go test と testingパッケージ
■ ハンズオン
2
自己紹介
上田拓也twitter: @tenntenn
■ コミュニティ活動Google Cloud Platform User Group (GCPUG) TokyoGoビギナーズgolang.tokyoGo Conference
3
Goとは?
Googleが開発しているプログラミング言語
■ 特徴
● シングルバイナリ・クロスコンパイル
● 強力でシンプルな言語設計と文法
● 並行プログラミング
● 豊富な標準ライブラリ群
● 周辺ツールの充実
Goの紹介/Goとは? 5
Goの特徴 − シングルバイナリ・クロスコンパイル −
■ 環境変数のGOOSとGOARCHを指定する
開発環境とは違うOSやアーキテクチャ向けに
クロスコンパイルできる
Goの紹介/Goの特徴 − シングルバイナリ・クロスコンパイル − 6
# Windows(32ビット)向けにコンパイル$ GOOS=windows GOARCH=386 go build
# Linux(64ビット)向けにコンパイル$ GOOS=linux GOARCH=amd64 go build
シングルバイナリになるので動作環境を用意しなくてよい
go buildはコンパイルするコマンド
Goの特徴 − 強力でシンプルな言語設計と文法 −
■ スクリプト言語の書きやすさ
● 冗長な記述は必要ない
■ 型のある言語の厳密さ
● 曖昧な記述はできない
■ 考えられたシンプルさ
● 機能を増やすことで言語を拡張していくことはしない
Goの紹介/Goの特徴 − 強力でシンプルな言語設計と文法 − 7
Goに入ってはGoに従え= 言語の思想を理解しよう
Goの特徴 − 並行プログラミング −
■ ゴールーチン
● 軽量なスレッドに近いもの● goキーワードをつけて関数呼び出し
■ チャネル
● ゴールーチン間のデータのやり取り
● 安全にデータをやり取りできる
Goの紹介/Goの特徴 − 並行プログラミング − 8
チャネルゴールーチン
Aゴールーチン
B
データ
データ
go f()
Goの特徴 − 周辺ツールの充実 −
■ go tool として標準/準標準で提供
■ サードパーティ製のツールも充実
■ IDEによらない独立したツールとして提供
Goの紹介/Goの特徴 − 周辺ツールの充実 − 9
go build ビルドを行うコマンド
go testxxxx_test.goに書かれたテスト
コードの実行
go doc, godoc ドキュメント生成
gofmt, goimports コードフォーマッター
golint コードチェッカー、リンター
gocode コード補完
Goの特徴 − 豊富な標準ライブラリ −
■ 標準ライブラリ一覧
https://golang.org/pkg/
Goの紹介/Goの特徴 − 豊富な標準ライブラリ − 10
net/http HTTPサーバなど
archive, compress zipやgzipなど
crypto 暗号化
encoding JSON, XML, CSVなど
html/template HTMLテンプレート
os, path/filepath ファイル操作など
Goの勉強するには?
■ コミュニティ
● gophers-slack○ 世界中のGopherが集まるチャット
● Qiita #Go○ Go言語の初心者が見ると幸せになれる場所
■ 書籍
● The Go Programing Language(日本語)
● みんなのGo言語
Goの紹介/Goの勉強するには? 11
A Tour of Goをやろう
■ A Tour of Go● Goのチュートリアル
● Web上で実行できる
● Basicsにチャレンジしてみよう
Goの基本/A Tour of Goをやろう 13
繰り返し:for
■ Goの繰り返しはforのみ
Goの基本/繰り返しfor 14
// いつものforfor i := 0; i <= 100; i++ {}
// while的な使い方for i <= 100 {}
// 無限ループfor {}
()はいらない
分岐:if
■ 条件式の前に代入文などが書ける
Goの基本/分岐:if 15
// いつものifif a == 0 {}
// 代入文を書くif a := f(); a > 0 {fmt.Println(a)
} else {fmt.Println(2*a)
}()はいらない
分岐:switch
■ caseに式が書ける■ breakは書かなくてよい
Goの基本/分岐:switch 16
switch a {case 1:
fmt.Println("a is 1")default:
fmt.Println("default")}swtich {
case a == 1:fmt.Println("a is 1")
}
caseをまたぐ際には、fallthroughを使う
何もしないとbreakになる
Goのインストール
■ インストール方法
Goの公式サイトからダウンロード
■ ソースコードからビルドする
Goの公式サイトを参考にする。
Goの基本/Goのインストール 17
Go1.5以上はビルドにGoが必要
そのため1.4のバイナリを入れておく
GOPATH
■ GOPATHとは?
Goのソースコードやビルドされたファイルが入るパス。
importされるパッケージもここから検索される。
Goの基本/GOPATH 18
$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
go tool − go install −
■ go install ビルドして、GOPATH以下に配置するコマンド。
Goの基本/go tool − go install − 19
$ export GOPATH=`pwd`$ go install fuga
$GOPATH├── bin│ └── fuga├── pkg│ └── darwin_amd64│ └── hoge.a└── src ├── fuga │ └── main.go └── hoge └── hoge.go
go install によって生成されたファイル
手元で試してみよう!(src/fugaとsrc/hogeだけを使用)
go tool − go get −
■ go get パッケージをダウンロードしてビルドしてGOPATH以下に配置
するコマンド。
Goの基本/go tool − go install − 20
$ export GOPATH=`pwd`$ go get github.com/nsf/termbox-go
└── src └── github.com ├── mattn │ └── go-runewidth │ ... │ └── runewidth_windows.go └── nsf └── termbox-go ... └── terminfo_builtin.go
依存するパッケージもインストールされる
.├── pkg│ └── darwin_amd64│ └── github.com│ ├── mattn│ │ └── go-runewidth.a│ └── nsf│ └── termbox-go.a
手元で試してみよう!
型の種類型・メソッド・インタフェース/型の種類 22
組み込み型 int, float64, string など
配列[100]int など
要素の型と要素数は固定
スライス[]int など
要素の型、要素数は可変。
マップmap[string]int など
連想配列。
構造体struct { a int } などフィールドのリストを持つ
インタフェースinterface { m() int } などメソッドのリストを持つ
配列 −1−
[要素数]要素の型
型・メソッド・インタフェース/配列 −1− 23
var a [3]int
a[1] = 10
b := [...]int{1, 2, 3}
for i, n := range b {fmt.Println(i,"/",len(b),"=>", n)
}
Playgroundで動かす
要素数が違えば別の型
...で初期値の要素に合わせる
添字と値で繰り返せる
配列 −2−
値でコピーされる
型・メソッド・インタフェース/配列 −2− 24
a := [3]int{1, 2, 3}
b := a
a[0] = 10
for i := range a {
fmt.Println(a[i], b[i])
}
参照がコピーされるわけではない
10, 12, 23, 3
関数に渡した場合も同様に値がコピーされる
Playgroundで動かす
2つ目は省略可
スライス −1−
[]要素の型
make([]要素の型, 要素数[, キャパシティ])
型・メソッド・インタフェース/スライス −1− 25
a := make([]int, 3, 10)
fmt.Println(a, len(a), cap(a))
b := []int{1, 2, 3}
for i, n := range b {
fmt.Println(i,"/",len(b),"=>", n)
}
Playgroundで動かす
スライスでもrangeは使える
スライス −2−
スライスの背後には配列がある
型・メソッド・インタフェース/スライス −2− 26
a := [...]int{1, 2, 3, 4}b := a[1:3]fmt.Println(b, len(b), cap(b))
Playgroundで動かす
1 2 3 4a[0] a[1] a[2] a[3]
b[0] b[1]
len(b) = 2
cap(b) = 3配列
スライス
スライス −3−
append(スライス, 要素...) スライス
型・メソッド・インタフェース/スライス −3− 27
var a []int // nil
for i := 0; i <= 10; i++ {
a = append(a, i * 10)
fmt.Println(len(a), cap(a), a)
}
Playgroundで動かす
appendした際にcapを超えた場合は
新しく配列が確保される
スライス −4−
■ 課題1
配列とスライスをそれぞれ関数の引数や戻り値に
した場合の挙動の違いを考えてみよう。
■ 課題2
スライスへの任意位置への挿入、削除を実装して
みよう。
型・メソッド・インタフェース/スライス −4− 28
// スライスaとbを結合c := append(a, b...)
Playgroundで動かす
可変長引数にスライスを展開
マップ
map[キーの型]値の型
make(map[キーの型]値の型[, キャパシティ])
型・メソッド・インタフェース/マップ 29
a := make(map[string]int)a["c"] = 100n, ok := a["c"]fmt.Println(n, ok)
b := map[string]int{"c":2, "d":4}for k, v := range b {fmt.Println(k, v)
}
Playgroundで動かす
値が存在すればnはその値、okはtrue存在しなければ、nはゼロ値、okはfalse
キーと値で繰り返せる
構造体
フィールドのリストを持つデータ構造。フィールドの型は任意の型を指定できる。構造体のゼロ値は、フィールドすべてがゼロ値の構造体。
型・メソッド・インタフェース/構造体 30
a := struct{N ints string
}{N: 100,s: "hoge",
}fmt.Printf("%#v\n", a)fmt.Println(a.N, a.s)
Playgroundで動かす
構造体リテラル
型情報
フィールド
typeを使った型の作成
type <型名> <型リテラル>|<既存の型>
型・メソッド・インタフェース/typeを使った型の作成 31
// 組み込み型に名前をつけるtype Int int
// 他のパッケージの型に名前をつけるtype MyWriter io.Writer
// 型リテラルに名前をつけるtype Person struct {Name string
}
intとIntは別の型として扱われる
typeで名前が付けれるもの
■ 組み込み型
int, float64, string など
■ 型リテラル構造体、インタフェース、
マップ、スライス、チャネル、関数 など
■ 名前付きの型パッケージの内外で作った型
型・メソッド・インタフェース/typeで名前を付けれるもの 32
別の型として再定義できる
メソッド −1−type で定義した型はメソッドのレシーバにできる
型・メソッド・インタフェース/メソッド −1− 33
type Hex intfunc (h Hex) String() string { return fmt.Sprintf("%x", int(h))}
// 100をHex型として代入var hex Hex = 100// Stringメソッドを呼び出すfmt.Println(hex.String())
Playgroundで動かす
メソッド −2−
■ レシーバにできるもの
● 名前の付いた型typeで定義したもの
● パッケージ内の型のみパッケージ外の型はtypeで再定義する
● ポインタ型も含むレシーバに変更を与えたい場合
レシーバも引数と同じ扱い
型・メソッド・インタフェース/メソッド −2− 34
type Hex int など
type S bufio.Scanner など
func (p *Hoge) M() {...
} など
メソッド −3−
■ 課題3
関数にメソッドを設けてみよう。var f func(string) int
■ 課題4
レシーバをポインタにしてみてレシーバに変更を与
えてみよう。構造体以外も試してみよう。
■ 課題5
レシーバがnilの場合の挙動を試してみよう。
型・メソッド・インタフェース/メソッド −3− 35
インタフェース
● メソッドのリストを持つ
● メソッドのリストがインタフェースで規定しているものと一致
する型はインタフェースを実装していることになる
型・メソッド・インタフェース/インタフェース 36
var s interface {String() string
}s = Hex(100)fmt.Println(s.String())
type Hex intfunc (h Hex) String() string { return fmt.Sprintf("%x", int(h))}
Playgroundで動かす
インタフェースを実装していることになる
型とメソッドとインタフェース
■ 既存の型にもインタフェースを実装● 後づけで実装させる
● メソッドリストさえ一致してればよい
■ 構造体以外も実装可能● typeで宣言すればメソッドが設けられる
● メソッドリストさえ一致してればよい
型・メソッド・インタフェース/型とメソッドとインタフェース 37
インタフェースの活用
■ 1つのメソッドしか持たない● io.Writer: Writeメソッド
● io.Reader: Readメソッド
■ 標準パッケージで多く使われている● fmt, net, bytes, encoding, bufio, os ...● ファイルやネットワークのコネクション
● 抽象度の高いインタフェース
型・メソッド・インタフェース/インタフェースの活用 38
インタフェースはGoの良い言語機能の1つ
インタフェース
■ 課題6
interface{} という型はどういう特徴を持つ型
か説明してください。
■ 課題7
インタフェース型を1つ作り、組み込み型、構造体
型、関数型にそれぞれ実装させてみましょう。ま
た、作ったインタフェース型を引数に取る関数を
作ってみましょう。
型・メソッド・インタフェース/インタフェース 39
型アサーション
インタフェース.(型)インタフェース型の値を任意の型にキャストする。第2戻り値に
キャストできるかどうかが返る。
型・メソッド・インタフェース/型アサーション 40
var v interface{}v = 100
n,ok := v.(int)fmt.Println(n, ok)
s,ok := v.(string)fmt.Println(s, ok)
Playgroundで動かす
型スイッチ
型によって処理をスイッチする
型・メソッド・インタフェース/型スイッチ 41
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で動かす
インタフェース
i = "hoge"も試してみよう
埋め込み −1−構造体に匿名フィールドを埋め込む機能
型・メソッド・インタフェース/埋め込み −1− 42
type Hoge struct {N int
}
// Fuga型にHoge型を埋め込むtype Fuga struct {Hoge // 名前のないフィールドになる
}
埋め込み −2−埋め込んだ値に移譲(継承とは違う)
型・メソッド・インタフェース/埋め込み −2− 43
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で動かす
埋め込みの特徴
■ 型リテラルでなければ埋め込められる● typeで定義したものや組み込み型
● インタフェースも埋め込められる
■ インタフェースの実装埋め込んだ値のメソッドもカウント
型・メソッド・インタフェース/埋め込みの特徴 44
// Stringerを実装type Hex intfunc (h Hex) String() string {
return fmt.Sprintf("%x", int(h))}// Hex2もStringerを実装type Hex2 struct {Hex}
type Stringer interface {String() string
}
Playgroundで動かす
インタフェースと埋め込み
■ 既存のインタフェースの振る舞いを変える
型・メソッド・インタフェース/インタフェースと埋め込み 45
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の振る舞いを変える
インタフェースと埋め込み
■ 課題8
以下のコードは有効でしょうか?Playgroundで動かして確認しましょう。
■ 課題9
前のスライドの例を実際にPlaygroundで動かして
挙動を確認しよう。
型・メソッド・インタフェース/インタフェースと埋め込み 46
type Hoge struct {N int}type Fuga struct {Hoge}f := Fuga{Hoge{100}}var _ Hoge = f _は変数を使用しないときに使う記法
HiHogeの戻り値の型がHogeにできる理由は?
参考:インタフェースの実装パターン
Concurrency is not Parallelism
■ 並行と並列は別ものである by RobPike● 並行:Concurrency● 並列:Parallelism
■ Concurrency● 同時にいくつかの質の異なることを扱う
■ Parallelism● 同時にいくつかの質の同じことを扱う
ゴールーチン・チャネル/Concurrency is not Parallelism 48
並列と並行の違い
■ Concurrency 同時にいくつかの質の異なることを扱う
■ Parallelism 同時にいくつかの質の同じことを扱う
ゴールーチン・チャネル/並行と並列の違い 49
本を運ぶ本を燃やす
台車を戻す
本を積む
本を燃やす 本を燃やす 本を燃やす
ゴールーチンとConcurrency
■ ゴールーチンでConcurrencyを実現● 複数のゴールーチンで同時に複数のタスクをこなす
● 各ゴールーチンに役割を与えて分業する
■ 軽量なスレッドのようなもの● LinuxやUnixのスレッドよりコストが低い
● 1つのスレッドの上で複数のゴールーチンが動く
■ ゴールーチンの作り方● goキーワードをつけて関数を呼び出す
ゴールーチン・チャネル/ゴールーチンとConcurrency 50
複数のコアで動くとは限らない
go f()
無名関数とゴールーチンゴールーチン・チャネル/無名関数とゴールーチン 51
package mainimport "fmt"import "time"func main() {
go func() {fmt.Println("別のゴールーチン")
}()fmt.Println("mainゴールーチン")time.Sleep(50*time.Millisecond)
}Sleepしないとすぐに終了する
http://play.golang.org/p/jy1HWriRTS
ゴールーチン間のデータのやりとり −1−ゴールーチン・チャネル/ゴールーチン間のデータのやりとり −1− 52
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
ゴールーチン間のデータのやりとり −2−ゴールーチン・チャネル/ゴールーチン間のデータのやりとり −2− 53
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
変数v
print(v) v = 100
共有の変数を使う?
ゴールーチン間で共有の変数を使うゴールーチン・チャネル/ゴールーチン間で共有の変数を使う 54
func main() {done := falsego func() {time.Sleep(3 * time.Second)done = true
}()for !done {time.Sleep(time.Millisecond)
}fmt.Println("done!")
}
共有の変数を使う
http://play.golang.org/p/mGSOaq4mcr
ゴールーチン間のデータ競合 −1−ゴールーチン・チャネル/ゴールーチン間のデータ競合 −1− 55
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
変数v
print(v) v = 100
処理順序が保証されない
競合
ゴールーチン間のデータ競合 −2−ゴールーチン・チャネル/ゴールーチン間のデータ競合 −2− 56
n := 1go func() {
for i := 2; i <= 5; i++ {fmt.Println(n, "*", i)n *= itime.Sleep(100)
}}()
http://play.golang.org/p/yqk82u0E4V
for i := 1; i <= 10; i++ {fmt.Println(n, "+", i)n += 1time.Sleep(100)
}
競合
データ競合の解決
■ 問題点● どのゴールーチンが先にアクセスするか分からない● 値の変更や参照が競合する
■ 解決方法● 1つの変数には1つのゴールーチンからアクセスする● チャネルを使ってゴールーチン間で通信をする● またはロックをとる(syncパッケージ)
ゴールーチン・チャネル/データ競合の解決 57
"Do not communicate by sharing memory; instead, share memory by communicating"
チャネルの特徴
■ 送受信できる型● チャネルを定義する際に型を指定する
■ バッファ● チャネルにバッファを持たせることができる
● 初期化時に指定できる
● 指定しないと容量0となる
■ 送受信時の処理のブロック● 送信時にチャネルのバッファが一杯だとブロック
● 受信時にチャネル内が空だとブロック
ゴールーチン・チャネル/チャネルの特徴 59
送信時のブロック
ゴールーチン・チャネル/送信時のブロック 60
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
受信してくれるまでブロック
ch<-100
チャネル
100ブロック
受信時のブロック
ゴールーチン・チャネル/受信時のブロック 61
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
送信されるまでブロック
<-ch
チャネル
100ブロック
チャネルの基本 −1−ゴールーチン・チャネル/チャネルの基本 −1− 62
■ 初期化
■ 送信
■ 受信
ch1 := make(chan int)ch2 := make(chan int, 10)
n1 := <-ch1n2 := <-ch2 + 100
容量を指定
ch1 <- 10ch2 <- 10 + 20
受け取られるまでブロック
一杯であればブロック
送信されまでブロック
空であればブロック
make(chan int, 0)と同じ
チャネルの基本 −2−ゴールーチン・チャネル/チャネルの基本 −2− 63
func main() {done := make(chan bool) // 容量0go func() {time.Sleep(time.Second * 3)done <- true
}()<-donefmt.Println("done")
}
送信されるまでブロック
http://play.golang.org/p/k0sMCYe4PA
複数のチャネルから同時に受信
ゴールーチン・チャネル/複数のチャネルから同時に受信 64
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロック
ブロックされるので同時に送受信出来ない?
select - case −1−ゴールーチン・チャネル/select-case −1− 65
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロックされるので同時に送受信出来ない?
select
select - case −2−ゴールーチン・チャネル/select - case −2− 66
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
nilチャネル
ゴールーチン・チャネル/nilチャネル 67
func main() {ch1 := make(chan int)var ch2 chan stringgo 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
ファーストクラスオブジェクト
■ チャネルはファーストクラスオブジェクト● 変数に入れれる
● 引数に渡す
● 戻り値で返す
● チャネルのチャネル
■ timeパッケージ
ゴールーチン・チャネル/ファーストクラスオブジェクト 68
http://golang.org/pkg/time/#After
chan chan int など
// 5分間待つ
<-time.After(5 * time.Minute)
5分たったら現在時刻が
送られてくるチャネルを返す
チャネルを引数や戻り値にする
ゴールーチン・チャネル/チャネルを引数や戻り値にする 69
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
双方向チャネル
ゴールーチン・チャネル/双方向チャネル 70
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
間違った使い方ができる
単方向チャネル
ゴールーチン・チャネル/単方向チャネル 71
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
受信専用のチャネル
送信専用のチャネル
タイピングゲーム
■ 課題1090秒以内に予め用意された5つの文章を入力させ、すべて入力
できた場合は"OK"と出力し、タイムオーバーの場合は"Time Over"と表示するプログラムを作ってください。なお、入力ミスし
た場合は正解するまで同じ文章を入力させてください。
ヒント:
time.After, bufio.Scanner
ゴールーチン・チャネル/タイピングゲーム 72
Concurrencyの実現
■ 複数のゴールーチンで分業する● タスクの種類によってゴールーチンを作る
● Concurrencyを実現
■ チャネルでやりとりする● ゴールーチン間はチャネルで値を共有する
● 複雑すぎる場合はロックを使うことも
■ for-selectパターン● ゴールーチンごとに無限ループを作る
● メインのゴールーチンはselectで結果を受信
ゴールーチン・チャネル/Concurrencyの実現 73
for-selectパターン −1−ゴールーチン・チャネル/for-selectパターン −1− 74
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
select
for{}for{}
各ゴールーチンで無限ループを作る
for-selectパターン −2−ゴールーチン・チャネル/for-selectパターン −2− 75
ゴルーチン-1
for{}
ゴルーチン-2
for{}
ゴルーチン-3
for{}
ゴルーチン-4
for{}
チャネル
チャネル
チャネル
チャネル
GopherでConcurrency
■ 課題11前のスライドのGopherたちが本を燃やす様子をプログラムで表
現してみてください。なお、1冊の本を燃やしたり、本を積んだ
り、台車を運んだりするのに、それなりに時間がかかることを想
定し、適度にtime.Sleepで処理を止めて見ましょう。
■ 課題12課題11で燃やす本の量をどんどん増やした場合に、どうス
ケールすれば処理速度を落とさずに本を燃やせるでしょうか?
ゴールーチン・チャネル/GopherでConcurrency 76
# データ競合のチェックgo run -race main.go
netパッケージ −サーバ−ネットワークプログラミング/netパッケージ −サーバー− 78
p, a := "tcp", ":8080"ln, err := net.Listen(p, a)if err != nil {...}
for {conn, err := ln.Accept()if err != nil {...}go handle(conn)
}
エラー処理
エラー処理
netパッケージ −クライアント−ネットワークプログラミング/netパッケージ −クライアント− 79
p, a := "tcp", ":8080"conn, err := net.Dial(p, a)if err != nil {...}
// 書き込みfmt.Fprintln(conn, "hello")
エラー処理
手元で試してみよう!(クライアント、サーバ)
グループチャットも作ってみよう!
(プログラミング言語Goの8.10も参考になります。)
net/httpパッケージネットワークプログラミング/net/httpパッケージ 80
h := func(w http.ReponseWriter,r *http.Request) {
fmt.Fprintf(w, "hello")
}http.HandleFunc("/hello", h)
const a = ":8080"http.ListenAndServe(a, nil)
手元で試してみよう!
Android上でサーバを動かす81ネットワークプログラミング/Android上でサーバを動かす
Youtubeで見る コード
母艦のシェル
adb shell
端末
タイピングゲーム2
■ 課題13課題11のタイピングゲームを改造し、netパッケージを使って
立てたtcpのサーバから問題となる文章と制限時間を取得し、そ
の時間内にタイピングできるかを競うゲームにしてみよう。
■ 課題14上記のプログラムをサーバとクライアントで実行可能ファイルを
分けずに、1つの実行ファイルでサーバもクライアントも実現しよ
う。また、問題の出題を交互に行えるようにしよう。
ネットワークプログラミング/タイピングゲーム2 82
go testgo test と testingパッケージ/go test 84
■ testを行うためコマンド _testというサフィックスの付いた goファイルを対象にしてテストを実行
# mypkgのテスト行う$ go test mypkgok mypkg 0.007s# 失敗する場合$ go test mypkg--- FAIL: TestHex_String (0.00s)
hex_test.go:11: expect="a" actual="A"
FAILFAIL mypkg 0.008s
hex.go ⇒ hex_test.go
go testのオプション(一部)
■ -v詳細を表示する。
■ -cpu実行する並列度を指定する。複数のコアを使ったテストができる。
■ -raceデータの競合が起きないかテストする。
■ -coverカバレッジを取得する。
go test と testingパッケージ/go testのオプション(一部) 85
testingパッケージ
■ テストを行うため機能を提供するパッケージ *testing.T型のメソッド使う。
go test と testingパッケージ/testingパッケージ 86
package mypkg_testimport "testing"import "mypkg"func TestHex_String(t *testing.T) {
expect := "a"actual := mypkg.Hex(10).String()if actual != expect {
t.Errorf(`expect="%s" actual="%s"`, expect, actual)}
}
type Hex intfunc (h Hex) String() string {
return fmt.Sprintf("%x", int(h))}
mypkg.go
mypkg_test.go
testingパッケージでできること
■ 失敗理由を出力してテストを失敗させる Error(), Errorf(), Fatal(), Fatalf()
■ テストの並列実行Parallel()go testの-parallelオプションで並列数を指定
■ ベンチマーク *testing.B型を使う
■ ブラックボックステストtesting/quickパッケージ
go test と testingパッケージ/testingパッケージでできること 87
testingパッケージでできないこと
■ アサーションはない自動でエラーメッセージを作るのではなく、
ひとつずつErrorfを使って自前で作る
■ テストはGoで書くテストのための新しいミニ言語を作らない
■ 比較演算子だけはツライのでは?reflect.DeepEqualを使うと良い
go test と testingパッケージ/testingパッケージでできないこと 88
かなり薄いテストパッケージ
Go Mock
■ インタフェースのモックを作るツールgithub.com/golang/mock/gomockメソッドが呼ばれているかなどがテストできる。標準パッケージではなく、サブプロジェクト。
func TestSample(t *testing.T) {ctrl := gomock.NewController(t)defer ctrl.Finish()m := mock.NewMockSample(ctrl)m.EXPECT().Method("hoge").Return(1)t.Log("result:", m.Method("hoge"))
}
参考:Go Mockでインタフェースのモックを作ってテストする
go test と testingパッケージ/Go Mock 89
Goのコンセプト
■ 実現する手段は少ないほうが良い 機能が増えると複雑さが増える。
■ 暗黙的で曖昧な記述をさせない エラーにつながる,暗黙の型変換や
不使用の変数等の宣言は許さない。
■ 不要なものは避ける 不要な型の宣言の排除など,
タイプ数をできるだけ減らすように。
■ コンセプトに一貫性を持たせる コンセプトにずれる言語仕様は入れない。
go test と testingパッケージ/Goのコンセプト 90
“Simplicity is Complicated” by Rob Pike
シンプルさと強力さ
■ シンプルな機能を組み合わせる シンプルな機能を組み合わせて,複雑な問題に対処する
⇒シンプルだが強力さも十分ある
go test と testingパッケージ/シンプルさと強力さ 91
シンプル簡潔さ
強力さ表現力
品質の良いコードが作りやすい
Goのコード品質を高める要素
■ シンプルな文法と言語設計 可読性の高い文法と複雑になりにくい言語仕様。
■ 型階層がない 複雑な型の階層が存在せず不要な型ができにくい。
■ コンパイルによるエラー検出 静的型付け言語なのでバグがコンパイル時に分かる。 バグになり得る箇所がコンパイルエラーになる。 (型不一致,未使用の変数など)
■ コードフォーマッタ 標準のコードフォーマッタ(gofmt)がある。
■ テスト 標準のテストツール(go test)がある
go test と testingパッケージ/Goのコード品質を高める要素 92
コンパイルエラーになるもの −1−
■ 型の不一致
go test と testingパッケージ/コンパイルエラーになるもの −1− 93
var n int = 100var m float64 = 1.5
// エラーvar a int = n + m
// OKvar b int = n + int(m)
コンパイルエラーになるもの −2−
■ 未使用の変数/パッケージ
go test と testingパッケージ/コンパイルエラーになるもの −2− 94
import ("fmt" // エラー_ "io" // OK
)
func main() {var n int = 100 // エラー_ = 200 // OK
}
コンパイルエラーになるもの −3−
■ インタフェースの未実装(型の不一致)
go test と testingパッケージ/コンパイルエラーになるもの −3− 95
type Hex intfunc (h Hex) Str() string {
return fmt.Sprintf("%x", int(h))}
// エラーvar _ fmt.Stringer = Hex(100)
type Stringer interface {
String() string}
コンパイルエラーになるもの −4−
■ 曖昧な記述
go test と testingパッケージ/コンパイルエラーになるもの −4− 96
type Hoge struct{ N int }type Piyo struct{ N int }type Foo struct {
HogePiyo
}func main() {
f := Foo{Hoge{100}, Piyo{200}}fmt.Println(f.N) // エラーfmt.Println(f.Hoge.N) // OK
}
Goとテスト
■ 言語のコンセプトを守る● 実現する手段は少なく
⇒ テストの為のミニ言語を入れない。
● 暗黙的で曖昧な記述をさせない
⇒ アサーションで自動でエラーメッセージを作らない。
コンテキストにあったエラーメッセージを作る。
■ コンパイルエラーで検出できるコンパイルで検出できるものはテストは不要。
コンパイルでは検出できないものに集中できる。
■ テストが良いサンプルテストがGoで書かれてるので良いサンプルになる。
go test と testingパッケージ/Goとテスト 97
FAQを読もう!
ドキュメントとテスト
■ テストされたサンプル
func ExampleHex_String() {fmt.Println(mypkg.Hex(10))// Output: a
}
テストファイルにExampleで始まる関数を書くとサンプルとして扱われる。
// Output:を書くとテスト対象になる。
go test と testingパッケージ/ドキュメントとテスト 98
言語標準ツールの強み
■ ツール間で連携が取りやすい標準ツールなので、他のツールと連携が取りやすい
■ メンテが保証される バグが放置されたり、メンテされなかったりしない
■ みんなが共通に使うその言語を使っているユーザ間で、共通の知識になる
テストツールも言語標準のメリットは大きい
go test と testingパッケージ/言語標準ツールの強み 99
課題
■ 課題15hogeパッケージのテスト書いてみましょう。
■ 課題16Exapleテストを書いてみましょう。
また、godocを使ってドキュメント生成してみましょう。
go test と testingパッケージ/課題 100
# godocをインストールしよう$ go get golang.org/x/tools/cmd/godoc$ $GOPATH/bin/godoc --http=":8080"
reflectパッケージとは?
■ 何ができるのか?
● 実行時に型情報を取得
● 任意の型の変数に値を入れる
● 構造体のフィールドのタグを取得する
■ どこで使われてるの?● encodingパッケージ● ORマッパーなど
リフレクション/reflectパッケージとは? 102
encodingパッケージでの利用
● JSONなどのシリアライズされたものを構造体に変換する際に使用される
type Person struct {Name string `json:"name"`Aget int `json:"age"`
}
{"name": "Gopher","age": 4
}
Goの構造体:
JSON例:
structタグで対応付ける
リフレクション/encodingパッケージでの利用
templateパッケージでの利用
● HTMLなどに任意の型の値を埋め込むために使われる
Hello, {{.Name}}!! Hello, Gopher!!
Person {Name: “Gopher”,Age: 4,
}
テンプレート 出力
データの埋込み
Execute
リフレクション/templateパッケージでの利用
Value型とType型
■ Value型
● 任意の値を表す型
● 値への操作をメソッドで提供
● reflect.ValueOf()で取得できる
■ Type型● 任意の型を表す型● 型に関する操作をメソッドで提供
● reflect.TypeOf()で取得できる
105リフレクション/Value型とType型
変数に値を入れる106
var n intfmt.Println(n) // 0
vp := reflect.ValueOf(&n)v := vp.Elem()if v.CanSet() {v.SetInt(100)
}
fmt.Println(n) // 100http://play.golang.org/p/HkJPjQsP8o
リフレクション/変数に値を入れる
interface{}としてポインタを渡す
■ interface{}として、任意の型のポインタを受け取る
func set(p, v interface{}) {pv := reflect.ValueOf(p) // ポインタ
vv := reflect.ValueOf(v) // 設定する値
pv.Elem().Set(vv)}
リフレクション/interface{}としてポインタを渡す
構造体のリフレクション109
s := struct{A string `k:"v"`; b int
}{"a", 1}
v := reflect.ValueOf(&s).Elem()println(v.FieldByName("A").CanSet())println(v.FieldByName("b").CanSet())
f1, ok := v.Type().FieldByName("A")println(ok, f1.PkgPath, f1.Tag.Get("k"))
f2, _ := v.Type().FieldByName("b")println(f2.PkgPath)
http://play.golang.org/p/NkwP3KSjDu
リフレクション/構造体のリフレクション
リフレクションの注意点
■ 容易にpanicが起きる
● ValueとTypeで実体によって対応していないメソッドを呼ぶとpanicになる○ 例:int型の値のValueに対してLen()を呼ぶ
● Kindで適切に分岐してpanicを避ける
■ 実行時間がかかる
● 実行時に解析するのでコストが大きい● 静的解析を用いる選択肢もある
リフレクション/リフレクションの注意点
ソースコードの静的解析とは?112
■ ソースコードを実行せずに解析すること
● ソースコードから抽象構文木(AST)などを取得して解析する
● 静的型付け言語だと、静的解析で型情報が取得できる
● 逆は実行して解析する動的解析
静的解析/ソースコードの静的解析とは?
Goで静的解析をすると何が嬉しいのか?
● リファクタリングツール○ 変数の宣言位置や使用箇所の抽出○ パッケージの解析
● コードジェネレーター○ コメントによるアノテーションの抽出○ コードフォーマッタ
● 処理系○ 抽象構文木(AST)の解析○ 定数の扱い
113
静的型付け言語なので静的解析でも多くの事が知れる
静的解析/Goで静的解析をすると何が嬉しいのか?
開発ツールとソースコードの静的解析114
■ 開発ツールの多くは静的解析を行っている● gofmt/goimports
○ コードフォーマッター● go vet/golint
○ コードチェッカー、リンター● guru
○ 静的解析
● gocode○ コード補完
● errcheck○ エラー処理のチェック
● gorename/gomvpkg○ リファクタリングツール
静的解析/開発ツールとソースコードの静的解析
■ 標準パッケージで静的解析の機能を提供
goパッケージ115
go/ast 抽象構文木(AST)を提供
go/build パッケージに関する情報を集める
go/constant 定数に関する型を提供
go/doc ドキュメントをASTから取り出す
go/format コードフォーマッタの機能を提供
go/importer コンパイラに適したImporterを提供
go/parser 構文解析の機能を提供
go/printer ASTの表示機能を提供
go/scanner 字句解析の機能を提供
go/token トークンに関する型を提供
go/types 型チェックに関する機能を提供
静的解析/goパッケージ
静的解析の流れ116
ソースコード
トークン
抽象構文木(AST)
型情報
構文解析
字句解析
型チェック
go/scannergo/token
go/parsergo/ast
go/typesgo/constant
静的解析/静的解析の流れ
字句解析 - go/scanner,go/token
■ 文字列をトークンにしていく
● 空白などを取り除き、意味のある単位=トークンにしていく作業
117
v + 1
IDENT ADD INT
トークン
ソースコード
静的解析/字句解析
構文解析 - go/parser,go/ast
■ トークンを抽象構文木(AST)にしていく
● プログラムの構造を持たせる
118
v + 1
IDENT ADD INT
ソースコード
+
v 1
BinaryExpr
Ident BasicLit
トークン
抽象構文木(AST)
静的解析/構文解析
型チェック - go/types,go/constant
■ 型チェックを行う
● 識別子の解決● 型の推論● 定数の評価
119
n := 100 + 200
fmt.Println(n)
定数の評価=300
型の推論-> int
識別子の解決
識別子の解決-> fmtパッケージ
静的解析/型チェック
抽象構文木(ASt)の取得
■ go/parserパッケージの関数を使う
● ParseExpr,ParseExprFrom○ 式をパースする○ ParseExprはParseExprFromを簡易版
● ParseFile○ ファイル単位でパースする
● ParseDir○ ディレクトリ単位でパースする○ 中でParseFileを呼んでいる
120静的解析/抽象構文木(AST)の取得
式のASTを取得する
■ 式を構文解析する
■ ParseExprFromでも書ける
121
expr, err := parser.ParseExpr(`v + 1`)if err != nil {
/* エラー処理 */}/* exprを解析する処理 */
fset := token.NewFileSet() // ファイル情報src := []byte(`v + 1`)f := "" // ファイル名(式なので不要)m := 0 // モード(式なので不要)expr, err := parser.ParseExprFrom(fset, f, s, m)
静的解析/式のASTを取得する
token.FileSetとは?
■ ファイル中の位置情報を記録する為の型
● 位置情報は数値で表される● 複数のファイル間で一意の値● 各ファイルのoffsetが記録されている● パースする際に記録されていく
122
token.FileSetは出力引数としてParse系の関数に渡す
静的解析/token.FileSetとは?
ファイルからASTを取得する
■ 完全なGoのソースコードを構文解析する
123
const src = `package mainvar v = 100func main() {
fmt.Println(v+1)}`
fs := token.NewFileSet()f, err := parser.ParseFile(fs, "my.go", src, 0)if err != nil {
/* エラー処理 */}/* f を解析する処理
引数はparse.ExprFromと同じ構成
srcがnilだとファイル名
でファイルを開く
解析するファイルの中身
静的解析/ファイルからASTを取得する
Hello, WorldのASTの構成124
package mainimport "fmt"func main() {
fmt.Println("Hello, 世界")}
ast.File
ast.File
ast.GenDecl
ast.FuncDecl
ast.CallExpr
Goの抽象構文木(AST)を手入力してHello, Worldを作るhttp://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c
Playgroundで動かす
静的解析/Hello, WorldのASTの構成
ASTをトラバースする
■ ast.Inspectを使う
125
n, _ := parser.ParseExpr(`v + 1`)ast.Inspect(n, func(n ast.Node) bool {
if n != nil { fmt.Printf("%T\n", n) }return true
})printer.Fprint(os.Stdout, token.NewFileSet(), n)
*ast.BinaryExpr*ast.Ident*ast.BasicLitv + 1
+
v 1
構文解析
抽象構文木(AST)を探索
抽象構文木(AST)を出力
BinaryExpr
Ident BasicLit
Playgroundで動かす
ast.Walkというのもある
静的解析/ASTをトラバースする
ASTをトラバースする
■ 再帰を使ってトラバースする
126
func traverse(n ast.Node) {switch n := n.(type) {
case *ast.Indent:fmt.Println(n.Name)
case *ast.BinaryExpr:traverse(n.X)traverse(n.Y)
case *ast.UnaryExpr:traverse(n.X)
}}
識別子の場合は名前を出力
二項演算式の場合は
各項を探索
単項演算式の場合は
項を探索
型でswitchする
https://play.golang.org/p/5SOdiy420p
静的解析/ASTをトラバースする
参考資料
■ goパッケージで簡単に静的解析して世界を広げよう ● コードジェネレータ
○ ASTを取得する方法を調べる○ 抽象構文木(AST)をトラバースする○ 抽象構文木(AST)をいじってフォーマットをかける○ Goの抽象構文木(AST)を手入力してHello, Worldを作る○ go-app-builderのソースコードを読む
● リファクタリングツール○ gorenameをライブラリとして使う○ Goのスコープについて考えてみよう○ go/typesパッケージを使い変数名をリネームしてみる
● 処理系○ 簡単な式の評価機を作ってみる○ 【実践goパッケージ】文字列から複素数型の値をパースする○ もっと楽して式の評価器を作る
127静的解析/参考資料
ハンズオンの説明
■ リポジトリ● https://github.com/tenntenn/gohandson/tree/master/i
mgconv/ja
■ コマンドラインツール
● ターミナルで動くプログラム● 画像を変換するツール
# 50%に縮小して、JPEGにする$ imgconv -resize 50% a.png b.jpg
ハンズオン/ハンズオンの説明 129
ドキュメントを読もう
■ パッケージドキュメント● https://golang.org/pkg● 標準パッケージの使い方が書いてある
■ FAQ● https://golang.org/doc/faq● なぜGoに◯◯がないのか?など
■ 言語仕様
● https://golang.org/ref/spec公式ドキュメントを読もう!!
ハンズオン/ドキュメントを読もう 130
Go Playground
■ Go Playground● http://play.golang.org/● Web上でGoを実行できる● Share機能で、SNSで共有したり質問する
ハンズオン/Go Playground 131