Join Weeyble on Slack. ( weeyble.slack.com )
事前に登録をお願いします
channel: #go
- 今回が初参加の方 はいらっしゃいますか?
- 初回(2019/11/20), 第2回(2019/12/04), 第3回(2019/12/18), 第4回(2020/01/08), 第5回(2020/01/22)
- ローカル環境でGo言語が動作する状態ですか?
- インストール作業はサポートできないです...
- Go 言語 の 基本的な文法 = A Tour of Go (Exercise を除く) レベル の 理解が不安な方 はいらっしゃいますか?
- ローカル環境でGo言語が動作すること
- つっきー さん
- @ohtsuchi
- 経歴: フリーランス の Java サーバサイド 開発者
- Go言語の実務経験は無し...
- Go 詳しい方、教えて下さい...
- Go言語の実務経験は無し...
- 使用エディタ
- JetBrains 社の
IntelliJ IDEA Ultimate
+Go
Plugin (GoLand
と同等のはず?)
- JetBrains 社の
第2回, 第3回 は ハンズオン手順毎に 細かく箇条書き
-> 面倒になってきたので、 第4回 予習メモ 以降 は概要のみ
前回の資料を参照
本 gist 資料 で概要を解説
↓
教材を ハンズオン形式で 解説 (時間無いので出来るだけコピペで済ませます)
1時間くらい経ったら途中、休憩を入れます(5分〜10分)
12. Select
↓
13. Reflection
↓
(番外編). 「Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る」 第8章(8.3 8.4) 解説
- 以前
9. Dependency Injection
で既出 ( 第4回 予習メモ )- 後の章
15. Context
でもまた出ます
- 後の章
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc
型 は 関数 「func(ResponseWriter, *Request)
」 型 を 基底 とする 型- -> 関数 「
func(ResponseWriter, *Request)
」 はHandlerFunc
型 に 変換可能
- -> 関数 「
HandlerFunc
型 はServeHTTP
メソッド を持つServeHTTP
メソッド は 関数自身(=f
) を呼び出す
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
HandlerFunc
型 はServeHTTP
メソッド を持つ- ->
Handler
interface を満たす - ->
Handler
型 の変数に 代入可能
- ->
HandlerFunc
と Handler
の コード例: Go Playground
- 2個のURL を引数に取る
Racer
関数 の test を行う
- 2つ の URL に対して
http.Get
して 応答が返ってきた 時間が短い方の URL を返す - 2つ の URL 共に timeout 以内に 応答が返ってこない場合は
error
を返す
http.Get
で 実際のWebサイトにアクセスしている
解決策: モック HTTP サーバー (httptest.Server
) に対して test
httptest.NewServer
関数func NewServer(handler http.Handler) *Server
Handler
を引数 に取って*Server
を返す 関数
racer_test.go
fastServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
fastURL := fastServer.URL
// この URL に対して `http.Get` 実行
- test では
httptest.Server
のURL
に対してhttp.Get
する
- 補足: A Tour of Go: Defer 参照
> defer へ渡した関数の引数は、すぐに評価されますが、
> その関数自体は呼び出し元の関数がreturnするまで実行されません
- 補足: A Tour of Go: Stacking defers 参照
> defer へ渡した関数が複数ある場合、その呼び出しはスタック( stack )されます
> 呼び出し元の関数がreturnするとき、 defer へ渡した関数は LIFO(last-in-first-out) の順番で実行されます
// makeDelayedServer の中で httptest.NewServer 実行
fastServer := makeDelayedServer(0 * time.Millisecond)
// Server 生成のすぐ直後に defer で Close() を記述
defer fastServer.Close()
keep the instruction near where you created the server
(server を 作成した場所の近くに 指示を保管します)
v1 終了
- 2つの URL を順番に
http.Get
するのではなく、 同時に実行 する http.Get(url)
部分を goroutine 化- (1) 関数 内で最初に channel 作成
- (2) goroutine 起動
- (3) 関数 から channel を 戻り値 で返す
- 下記
ping
関数 は 戻り値 を返して 直ぐに終了 する
- 下記
- (4) 「処理」は goroutine 内で 非同期で 行う. 処理が終了したら goroutine 内で channel を close
func ping(url string) chan struct{} {
ch := make(chan struct{}) // (1)
go func() { // (2)
http.Get(url)
close(ch) // (4)
}()
return ch // (3)
}
- 補足: 上記 (1) 〜 (4) のパターンは 書籍 Go言語による並行処理 でよく見かけます
struct{}
the smallest data type available from a memory perspective
(メモリの観点から利用可能な 最小のデータ型)- 補足: The empty struct | Dave Cheney 参照
fmt.Println(unsafe.Sizeof(s)) // prints 0
make(chan struct{})
make
を使用して chan を作成make
使わずにvar ch chan struct{}
で宣言しただけの場合:- chan のゼロ値(=
nil
) -> 「nil channel
」 は送信も受信も block される ので ❌ - 補足: 「
nil channel
」の使いみちは 以下のサイト や 書籍 参照- 例1) Why are there nil channels in Go? - justforfunc - Medium の
Merging channels
の例 - 例2) 書籍 Go言語による並行処理 の 4.9 tee チャネル (p123)
- 例1) Why are there nil channels in Go? - justforfunc - Medium の
- chan のゼロ値(=
func Racer(a, b string) (winner string) {
select {
case <-ping(a): // (1)
return a // (3)
case <-ping(b): // (2)
return b // (3)
}
}
- 上記
Racer
関数 の処理の順番- (1) 1つ目の url(
a
) を 引数としてping
関数 を呼び出し.ping
関数 の 戻り値 の channel が直ぐに返ってくる.case
で channel からの 受信 を待ち受け
- (2) 1つ目の url(
b
) を 引数としてping
関数 を呼び出し.- (1) と同じ
- (3)
case
で (1) と (2) の channel に対して goroutine から 先に送信された値 を受信- 戻り値 として
a
またはb
を返す
- 戻り値 として
- (1) 1つ目の url(
select
- 複数の channel を待機
The first one to send a value "wins" and the code underneath the case is executed.
(値を送信する 最初のものが「勝ち」、case
の下のコードが実行されます)- 補足: @tenntenn さん作のスライド: 「 Goroutineと channelから はじめるgo言語 」 の p21 〜 参照
- 補足: A Tour of Go: Select 参照
> 複数ある case のいずれかが準備できるようになるまでブロックし、準備ができた case を実行します
> もし、複数の case の準備ができている場合、 case はランダムに選択されます
- 補足: 以下の処理 でも
select
を使用します- 以下の 「タイムアウト処理 (
case <-time.After(10 * time.Second):
)」 や 15. Context
の 「 context の キャンセル処理 (case <-ctx.Done():
)」
- 以下の 「タイムアウト処理 (
v2 終了
- 補足:
ping
関数の呼び出しにどれだけ時間がかかっているか確認
func ping(url string) chan struct{} {
defer trace(fmt.Sprintf("url=%s", url))() // 追加
// 中略
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() {
log.Printf("exit %s (%s)", msg, time.Since(start))
}
}
trace
関数は 書籍 プログラミング言語Go の「 第5章 関数 」の「 5.8 遅延関数呼び出し 」 (p166) より抜粋- The Go Programming Language - Alan A. A. Donovan, Brian W. Kernighan - Google ブックス 参照
すべて表示
リンク ->ページ >>
リンク に移動すると 表示されなかった ページ も表示されるはず?
- github (gopl.io/ch5/trace/main.go) でソースが公開されている
- The Go Programming Language - Alan A. A. Donovan, Brian W. Kernighan - Google ブックス 参照
- serverA, serverB 共に「処理が Timeout(=
10
秒) を超えたらerror
を返す 」という test code を追加
serverA := makeDelayedServer(11 * time.Second)
serverB := makeDelayedServer(12 * time.Second)
// 中略
_, err := Racer(serverA.URL, serverB.URL)
serverA
(11秒) もserverB
(12秒) も 処理が10
秒を超える ためerror
が返る
case <-time.After(10 * time.Second):
を追加Racer
関数 の 戻り値 にerror
追加- タイムアウト していない 場合は
nil
を返す - タイムアウト した 場合は
error
(fmt.Errorf("timed out waiting for %s and %s", a, b)
) を返す- 1つ目 の 戻り値 は
""
(string
の ゼロ値)
- 1つ目 の 戻り値 は
- タイムアウト していない 場合は
func Racer(a, b string) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(10 * time.Second):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
What we can do is make the timeout configurable.
(できることは timeout を構成可能にすることです)Racer
関数 の 引数 にtime.Duration
追加
func Racer(a, b string, timeout time.Duration) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
同じ コンパイルエラー が 2箇所で
not enough arguments in call to Racer
have (string, string)
want (string, string, time.Duration)
not enough arguments in call to Racer
have (string, string)
want (string, string, time.Duration)
呼び出し元の test code ( [1] 正常系の test と [2] timeout の test ) で 第3引数 を指定していないため
[1] 正常系の test (timeout しない) の場合:
got, err := Racer(slowURL, fastURL)
- 従来通り 2個の引数 で 呼び出し したい
- 2個の引数 で 呼び出し した場合は デフォルト値 (
10 * time.Second
) を適用 したい- 正常系の test では 10秒かからずに 直ぐに終了するため デフォルト値 のままでも大丈夫
- しかし go言語 には デフォルト引数 は 無い
[2] timeout の test の場合:
_, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
Racer
の timeout の test は時間がかかる(デフォルト10
秒)ので、ConfigurableRacer
の 第3引数 に20ms
を渡して testRacer(server.URL, server.URL, 20*time.Millisecond)
という書き方はできない- go言語 は オーバーロード をサポートしていない ため
- 引数が異なる場合は 別の関数名 で定義する必要有り
Racer(a, b string)
とConfigurableRacer(a, b string, timeout time.Duration)
変更内容:
var tenSecondTimeout = 10 * time.Second
- timeout の デフォルト値 = 10秒 を外出し (変数名
tenSecondTimeout
がイケてない...) - 補足:
var
->const
に変更可能.time.Second
(=time.Duration
型) は 基底型 がint64
のため (=type Duration int64
)- 補足: A Tour of Go: Constants 参照
> 定数は、文字(character)、文字列(string)、boolean、数値(numeric)のみで使えます
func Racer(a, b string) (winner string, error error) {
return ConfigurableRacer(a, b, tenSecondTimeout)
}
func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
- 3個の引数の
Racer
関数 ->ConfigurableRacer
に 変更 func Racer(a, b string) (winner string, error error) {
- 新たに 従来通り 2個の引数 の
Racer
関数 を定義ConfigurableRacer
関数 の 第3引数 に デフォルト値 を渡して実行
- 新たに 従来通り 2個の引数 の
v3 終了
以下、 12. Select
に記述はありませんが、補足として追記
Functional Option Pattern
- go 言語で デフォルト引数, オプショナル引数 を 実現させるための パターンの1つ
- golang で汎用的な任意数のオプションを取るメソッドの作り方
> DialOption を参考に実装すると以下のようになります
> また、 Foo メソッド内も for 文で回すだけのシンプルな実装です
> そして肝となる WithXXX ですが、
> こちらは JavaScript などでよく見かけるクロージャの仕組みを利用しています。
- Go言語でpythonやscalaのようなDefault引数を実現する - Qiita
- golangの関数のオプション引数を実現する | 69log
type RacerConfig struct {
timeout time.Duration
}
type RacerOption func(*RacerConfig)
func WithTimeout(d time.Duration) RacerOption {
return func(c *RacerConfig) {
c.timeout = d
}
}
RacerOption
- 「 デフォルト値 を集めた
struct
(=RacerConfig
) 」 の ポインタ型 を 引数 に取る 関数 なのがポイント
- 「 デフォルト値 を集めた
-var tenSecondTimeout = 10 * time.Second
+const defaultTimeout = 10 * time.Second
- 変数名がイケてないので変更. (この変更は
Functional Option Pattern
とは無関係)
-func Racer(a, b string) (winner string, error error) {
- return ConfigurableRacer(a, b, tenSecondTimeout)
-}
-
-func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, error error) {
- 2個 引数 の
Racer
関数は削除 ConfigurableRacer
->Racer
に 関数名 を戻す ↓- 3個目 の 引数 の定義を 可変長引数(
...
) に変更 ↓
+func Racer(a, b string, options ...RacerOption) (winner string, error error) {
+ config := RacerConfig{timeout: defaultTimeout}
+
+ for _, option := range options {
+ option(&config)
+ }
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
- case <-time.After(timeout):
+ case <-time.After(config.timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
got, _ := Racer(slowURL, fastURL) // 変更無し
-_, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
+_, err := Racer(server.URL, server.URL, WithTimeout(20*time.Millisecond))
ConfigurableRacer
ではなくRacer
の 第3引数 に20ms
を渡して test
type RacerConfig struct {
timeout time.Duration
}
type RacerOption func(*RacerConfig)
func WithTimeout(d time.Duration) RacerOption {
return func(c *RacerConfig) {
c.timeout = d
}
}
const defaultTimeout = 10 * time.Second
func Racer(a, b string, options ...RacerOption) (winner string, error error) {
config := RacerConfig{timeout: defaultTimeout}
for _, option := range options {
option(&config)
}
// 中略...
}
(以下、第6回(2020/02/05) 発表後に追記)
- Golang で関数のデフォルト引数を指定する - 長生村本郷Engineers'Blog
> Functional Option Pattern にもう一手間加える
> Option を interface で定義することで、どの様な型にでも呼び出せるのを利用しつつ、 Apply メソッドを定義して、 configs の上書きを図る、という算段です
- Do not fear first class functions | Dave Cheney
> interface.Apply
> an interface with one method and a function are equivalent
(「 1つのメソッドを持つ interface 」と「 関数 」は 同等です)
type RacerConfig struct {
timeout time.Duration
}
type RacerOption interface {
Apply(*RacerConfig)
}
type timeout time.Duration
func (t timeout) Apply(c *RacerConfig) {
c.timeout = time.Duration(t)
}
func WithTimeout(d time.Duration) RacerOption {
return timeout(d)
}
const defaultTimeout = 10 * time.Second
func Racer(a, b string, options ...RacerOption) (winner string, error error) {
config := RacerConfig{timeout: defaultTimeout}
for _, option := range options {
option.Apply(&config)
}
// 以下、 別解 (1) と同じ
}
RacerOption
- 「 デフォルト値 を集めた
struct
(=RacerConfig
) 」 の ポインタ型 を 引数 に取る 関数Apply
を定義した interface なのがポイント
- 「 デフォルト値 を集めた
- 別解 (1) の
option(&config)
を ->option.Apply(&config)
に変更
type RacerConfig struct {
timeout time.Duration
}
-type RacerOption func(*RacerConfig)
+type RacerOption interface {
+ Apply(*RacerConfig)
+}
+type timeout time.Duration
+func (t timeout) Apply(c *RacerConfig) {
+ c.timeout = time.Duration(t)
+}
func WithTimeout(d time.Duration) RacerOption {
- return func(c *RacerConfig) {
- c.timeout = d
- }
+ return timeout(d)
}
const defaultTimeout = 10 * time.Second
func Racer(a, b string, options ...RacerOption) (winner string, error error) {
config := RacerConfig{timeout: defaultTimeout}
for _, option := range options {
- option(&config)
+ option.Apply(&config)
}
// 以下、 別解 (1) と同じ
}
-> DialOption
(v1.27.1) の実装 参照
type DialOption interface {
apply(*dialOptions)
}
type RacerConfig struct {
timeout time.Duration
}
type RacerOption interface {
Apply(*RacerConfig)
}
type funcRacerOption struct {
f func(*RacerConfig)
}
func (fro *funcRacerOption) Apply(c *RacerConfig) {
fro.f(c)
}
func newFuncRacerOption(f func(*RacerConfig)) *funcRacerOption {
return &funcRacerOption{
f: f,
}
}
func WithTimeout(d time.Duration) RacerOption {
return newFuncRacerOption(func(c *RacerConfig) {
c.timeout = d
})
}
// 以下、 別解 (2) と同じ
type RacerConfig struct {
timeout time.Duration
}
type RacerOption interface {
Apply(*RacerConfig)
}
+type funcRacerOption struct {
+ f func(*RacerConfig)
+}
+func (fro *funcRacerOption) Apply(c *RacerConfig) {
+ fro.f(c)
+}
+func newFuncRacerOption(f func(*RacerConfig)) *funcRacerOption {
+ return &funcRacerOption{
+ f: f,
+ }
+}
-type timeout time.Duration
-
-func (t timeout) Apply(c *RacerConfig) {
- c.timeout = time.Duration(t)
-}
-
func WithTimeout(d time.Duration) RacerOption {
- return timeout(d)
return newFuncRacerOption(func(c *RacerConfig) {
c.timeout = d
})
}
// 以下、 別解 (2) と同じ
- Go API のための再利用可能で型安全なオプションの実装方法 - Frasco
> API(GET や PUT)ごとに違った引数の型を定義することにより解決したくなります
> 例えば、Get を受け付ける GetOption 、Put のみを受け付けられる PutOption を定義する、などです
> ここで注意すべきことは、関数が *Option インターフェースが埋め込まれた無名のインターフェースを返していることです
- v1
Refactor
,defer
の実装終了時.Synchronising processes
の前.- production code と test code の両方をリファクタリング. 共通部分を
measureResponseTime
,makeDelayedServer
関数 に 外出し.
- v2
Synchronising processes
-ping
-select
の実装終了時.Timeouts
の前.
- v3
- 最終形
(番外編). 「Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る」 第8章(8.3 8.4) 解説
※ 今回はここまで
次回 -> 第7回(2020/02/19) 実施です (第7回(2020/02/19) 発表メモ)