Recommended
PDF
[CEDEC 2021] 運用中タイトルでも怖くない! 『メルクストーリア』におけるハイパフォーマンス・ローコストなリアルタイム通信技術の導入事例
PDF
PPTX
PPTX
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
PPTX
世界一わかりやすいClean Architecture
PDF
PPTX
BuildKitによる高速でセキュアなイメージビルド
PDF
Apache Kafkaって本当に大丈夫?~故障検証のオーバービューと興味深い挙動の紹介~
PDF
コンテナの作り方「Dockerは裏方で何をしているのか?」
PDF
PDF
ODP
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
PDF
PPTX
Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
PDF
Docker入門-基礎編 いまから始めるDocker管理【2nd Edition】
PDF
PDF
PDF
PDF
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
PDF
PDF
PPTX
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
PDF
怖くないSpring Bootのオートコンフィグレーション
PPTX
PDF
PDF
Building the Game Server both API and Realtime via c#
PDF
PPTX
FINAL FANTASY XVにおけるPhoton利用事例 - Photon運営事務局 GTMF 2018 OSAKA / TOKYO
PDF
PDF
More Related Content
PDF
[CEDEC 2021] 運用中タイトルでも怖くない! 『メルクストーリア』におけるハイパフォーマンス・ローコストなリアルタイム通信技術の導入事例
PDF
PPTX
PPTX
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
PPTX
世界一わかりやすいClean Architecture
PDF
PPTX
BuildKitによる高速でセキュアなイメージビルド
PDF
Apache Kafkaって本当に大丈夫?~故障検証のオーバービューと興味深い挙動の紹介~
What's hot
PDF
コンテナの作り方「Dockerは裏方で何をしているのか?」
PDF
PDF
ODP
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
PDF
PPTX
Kubernetesでの性能解析 ~なんとなく遅いからの脱却~(Kubernetes Meetup Tokyo #33 発表資料)
PDF
Docker入門-基礎編 いまから始めるDocker管理【2nd Edition】
PDF
PDF
PDF
PDF
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
PDF
PDF
PPTX
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
PDF
怖くないSpring Bootのオートコンフィグレーション
PPTX
PDF
PDF
Building the Game Server both API and Realtime via c#
PDF
PPTX
FINAL FANTASY XVにおけるPhoton利用事例 - Photon運営事務局 GTMF 2018 OSAKA / TOKYO
Viewers also liked
PDF
PDF
PDF
PDF
008 20151221 Return of Frustrating Easy Domain Adaptation
PDF
研究室輪読 Recommending Investors
for Crowdfunding Projects
PDF
研究室輪読 Feature Learning for Activity Recognition in Ubiquitous Computing
PPTX
Dl hacks輪読: "Unifying distillation and privileged information"
PPTX
[DL Hacks輪読] Semi-Supervised Learning with Ladder Networks (NIPS2015)
PDF
[DL輪読会] Semi-Supervised Knowledge Transfer For Deep Learning From Private Tra...
PPTX
PPTX
JSAI2017:敵対的訓練を利用したドメイン不変な表現の学習
PPTX
[DL輪読会] GAN系の研究まとめ (NIPS2016とICLR2016が中心)
PDF
goパッケージで型情報を用いたソースコード検索を実現する
PDF
PDF
PDF
PDF
PDF
PDF
Go1.8 for Google App Engine
PDF
Mobile Apps by Pure Go with Reverse Binding
Similar to エキスパートGo
PDF
PDF
メルカリ・ソウゾウでは どうGoを活用しているのか?
PDF
PDF
PDF
今日から始めるGopher - スタートGo #0 @GDG名古屋
PDF
PDF
マスターオブゴールーチンアンドチャネル スタートGo #1
PDF
20130824 Lightweight Language "Go" @LL matsuri
PDF
エディタの壁を越えるGoの開発ツールの文化と作成法
PDF
PDF
PPTX
20130228 Goノススメ(BPStudy #66)
PDF
PPTX
PDF
PPTX
Go guide for Java programmer
PDF
PDF
PDF
Microsoft Graph API Library for Go
PDF
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
More from Takuya Ueda
PDF
Goにおけるバージョン管理の必要性 − vgoについて −
PDF
PDF
PDF
PDF
静的解析とUIの自動生成を駆使してモバイルアプリの運用コストを大幅に下げた話
PDF
PDF
PDF
Google Assistant関係のセッションまとめ
PDF
PDF
PDF
Namespace API を用いたマルチテナント型 Web アプリの実践
PDF
PDF
PDF
GAE/GoでLINE Messaging API を使う
PDF
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
エキスパートGo 1. エキスパートGo
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. 1
更新日:2017/08/08
2. 3. ソウゾウ エキスパートチーム
技術をアウトプットするところに技術は集まる
■ エキスパートチームとは?
● 50%以上の時間を技術コミュニケーションへの貢献に充てる
■ エキスパートチームの役割
● 社内に新しい技術を取り取り込む
● 社外のコミュニティなどを通じて社会へ還元する
■ エキスパートチームの活動
● カンファレンス・勉強会の開催/運営
● 対外的な講演活動
● 執筆、雑誌への寄稿、インタビュー
● 社内外での担当技術の普及推進
3
@tenntenn
担当:Go・GCP
@mhidaka
担当:Android
メンバー
4. 5. 6. 目的と対象者
6
■ 目的
● Tour of Goでは知れない実践的な話をする
● 業務上で必要になるノウハウを共有
● 何を見れば学習できるのか知る
● Goのエキスパートを増やす
■ 対象者
● 業務上Goが必要になった人
● Go以外の言語は一定以上書ける
● 静的型付け言語を業務で使ったことは無くてもいい
● Tour of Goはクリアしてる
7. 8. 9. 10. Goの特徴 − シングルバイナリ・クロスコンパイル −
■ 環境変数のGOOSとGOARCHを指定する
開発環境とは違うOSやアーキテクチャ向けに
クロスコンパイルできる
10
シングルバイナリになるので
動作環境を用意しなくてよい
# Windows(32ビット)向けにコンパイル
$ GOOS=windows GOARCH=386 go build
# Linux(64ビット)向けにコンパイル
$ GOOS=linux GOARCH=amd64 go build
※ go build はGoのソースコードをビルドするコマンド
11. Goの特徴 − 強力でシンプルな言語設計と文法 −
■ スクリプト言語の書きやすさ
● 冗長な記述は必要ない
■ 型のある言語の厳密さ
● 曖昧な記述はできない
■ 考えられたシンプルさ
● 機能を増やすことで言語を拡張していくこと
はしない
11
Goに入ってはGoに従え
= 言語の思想を理解しよう
12. Goの特徴 − 並行プログラミング −
■ ゴールーチン
● 軽量なスレッドに近いもの
● goキーワードをつけて関数呼び出し
■ チャネル
● ゴールーチン間のデータのやり取り
● 安全にデータをやり取りできる
12
チャネル
ゴールーチン
A
ゴールーチン
B
データ
データ
// 関数fを別のゴールーチンで呼び出す
go f()
13. Goの特徴 − 豊富な標準ライブラリ −
■ 標準ライブラリ一覧
https://golang.org/pkg/
13
net/http HTTPサーバなど
archive, compress zipやgzipなど
crypto 暗号化
encoding JSON, XML, CSVなど
html/template HTMLテンプレート
os, path/filepath ファイル操作など
14. Goの特徴 − 周辺ツールの充実 −
■ go tool として標準/準標準で提供
■ サードパーティ製のツールも充実
■ IDEによらない独立したツールとして提供
14
go build ビルドを行うコマンド
go test
xxxx_test.goに書かれたテスト
コードの実行
go doc, godoc ドキュメント生成
gofmt, goimports コードフォーマッター
golint コードチェッカー、リンター
gocode コード補完
15. 16. コミュニティに参加する
■ Goビギナーズ
● https://go-beginners.connpass.com/
● 初心者向けのGoのコミュニティ
■ golang.tokyo
● https://golangtokyo.connpass.com/
● Goの採用企業間で情報共有をするコミュニティ
■ Go Conference
● https://gocon.connpass.com/
● 日本最大のGoのカンファレンス
■ Gophers Slack
● https://invite.slack.golangbridge.org/
● 世界中のGopher(Goのユーザ)が集まる
16
17. 18. 19. 20. 21. コードの書式を揃える
■ gofmt
● 読み方:ごーふむと
● 標準のフォーマッタ
● 絶対に使う
● -s オプションで冗長な書き方をシンプルにできる
■ goimports
● import文を追加/削除してくれる
● 未使用パッケージのimportはエラーなので必須
● フォーマットもかける
● -s オプションがない
21
22. コードの品質を保つ
■ go vet
● バグである可能性の高いものを検出する
■ golint
● コーディングスタイルをチェックする
■ errcheck
● エラー処理が適切に行われているかチェックする
■ その他のツール
● http://haya14busa.com/ci-for-go-in-end-of-2016/
22
23. 24. 25. 26. 27. 組み込み型
■ 組み込み型
● int,int8,int16,int32,int64
● uint,uint8,uint16,uint32,uint64
● uintptr,byte,rune
● float32,float64
● complex64,complex128
● string
● bool
● error
27
28. // 組み込み型を基にする
type Int int
// 他のパッケージの型を基にする
type MyWriter io.Writer
// 型リテラルに基にする
type Person struct {
Name string
}
typeを使った型の作成
type <型名> <型リテラル>|<型名>
28
intとIntは別の型として扱われる
29. 30. 型エイリアス(Go 1.9以上)
■
30
■ 型のエイリアスを定義できる
● 完全に同じ型
● キャスト不要
● エイリアスの方ではメソッド定義はできない
type Applicant = http.Client
■ %Tは同じ元の型名を出す
type Applicant = http.Client
func main() {
fmt.Printf("%T", Applicant{})
}
http.Client
31. type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
メソッドとレシーバ
type で定義した型はメソッドのレシーバにできる
31
Playgroundで動かす
// 100をHex型として代入
var hex Hex = 100
// Stringメソッドを呼び出す
fmt.Println(hex.String())
32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 埋め込みの特徴
■ 型リテラルでなければ埋め込められる
● typeで定義したものや組み込み型
● インタフェースも埋め込められる
■ インタフェースの実装
埋め込んだ値のメソッドもカウント
45
// Stringerを実装
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
// Hex2もStringerを実装
type Hex2 struct {Hex}
Playgroundで動かす
type Stringer interface {
String() string
}
46. インタフェースと埋め込み
■ 既存のインタフェースの振る舞いを変える
46
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の振る舞いを変える
参考:インタフェースの実装パターン
47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. go testのオプション(一部)
■ -v
● 詳細を表示する
■ -cpu
● 実行する並列度を指定する
● 複数のコアを使ったテストができる
■ -race
● データの競合が起きないかテストする
■ -cover
● カバレッジを取得する
60. testingパッケージ
■ testを行うため機能を提供するパッケージ
*testing.T型のメソッド使う。
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
60
package mypkg_test
import "testing"
func TestHex_String(t *testing.T) {
expect := "a"
actual := mypkg.Hex(10).String()
if actual != expect {
t.Errorf(`expect="%s" actual="%s"`, expect, actual)
}
}
61. 62. 63. 64. テーブル駆動テスト
■ テスト対象のデータを羅列してテストする
64
参考:https://github.com/golang/go/wiki/TableDrivenTests
var flagtests = []struct {
in string
out string
}{
{"%a", "[%a]"},
{"%-a", "[%-a]"},
{"%+a", "[%+a]"},
{"%#a", "[%#a]"},
{"% a", "[% a]"},
}
func TestFlagParser(t *testing.T) {
var flagprinter flagPrinter
for _, tt := range flagtests {
s := Sprintf(tt.in, &flagprinter)
if s != tt.out {
t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)
}
}
}
65. サブテスト
■ 子テストを実行するしくみ
● Go1.7から導入された
65
func TestAB(t *testing.T) {
t.Run("A", func(t *testing.T) { t.Error("error") })
t.Run("B", func(t *testing.T) { t.Error("error") })
}
go test -v sample -run TestAB/A
=== RUN TestAB
=== RUN TestAB/A
--- FAIL: TestAB (0.00s)
--- FAIL: TestAB/A (0.00s)
sample_test.go:10: error
FAIL
exit status 1
FAIL sample 0.007s
サブテストを指定して実行
66. サブテストとテーブル駆動テスト
66
参考:Advanced Testing with Go by mitchellh
func TestIsOdd(t *testing.T) {
cases := []*struct {name string; input int; expected bool}{
{name: "+odd", input: 5, expected: true},
{name: "+even", input: 6, expected: false},
{name: "-odd", input: -5, expected: true},
{name: "-even", input: -6, expected: false},
{name: "zero", input: 0, expected: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if actual := IsOdd(c.input); c.expected != actual {
t.Errorf(
"want IsOdd(%d) = %v, got %v",
c.input, c.expected, actual)}})
}
}
67. テストヘルパー
■ テスト用のヘルパー関数
● ヘルパー関数はエラーを返さない
● *testing.Tを受け取ってテストを落とす
● Go 1.9からはT.Helperを使って情報を補足する
○ https://tip.golang.org/pkg/testing/#T.Helper
67
func testTempFile(t *testing.T) string {
t.Helper()
tf := ioutil.TempFile("", "test")
if err != nil {
t.Fatal("err %s", err)
}
tf.Close()
return tf.Name()
}
テストヘルパーで落ちたことが分かる
68. ■ テストカバレッジの分析
$ go test -coverprofile=profile fmt
$ head profile
mode: set
fmt/format.go:30.13,31.29 1 1
fmt/format.go:31.29,34.3 2 1
fmt/format.go:67.28,69.2 1 1
fmt/format.go:71.33,74.2 2 1
fmt/format.go:77.85,80.11 3 1
fmt/format.go:84.2,85.11 2 1
fmt/format.go:96.2,96.8 1 1
fmt/format.go:80.11,83.3 2 0
fmt/format.go:85.11,86.21 1 1
coverprofile
パッケージ名
テストできるのは
パッケージごと
68
69. 70. 71. 72. 73. 74. 環境変数を使う
■ 環境変数を使って切り替える
● os.Getenvで取得できる
● CIでテストを走らせるときに便利
● DBの接続先など環境に依存する値を保存する
■ github.com/jinzhu/configorを使う
74
var Config = struct {
DB string `env:"DB"`
}{}
func main() {
configor.Load(&Config)
fmt.Printf("config: %#v", Config)
}
75. 76. Concurrency is not Parallelism
■ 並行と並列は別ものである by RobPike
● 並行:Concurrency
● 並列:Parallelism
■ Concurrency
● 同時にいくつかの質の異なることを扱う
■ Parallelism
● 同時にいくつかの質の同じことを扱う
76
77. 78. 79. 80. 81. 82. func main() {
done := false
go func() {
time.Sleep(3 * time.Second)
done = true
}()
for !done {
time.Sleep(time.Millisecond)
}
fmt.Println("done!")
}
ゴールーチン間で共有の変数を使う
82
共有の変数を使う
Playgroundで動かす
83. 84. ゴールーチン間のデータ競合 −2−
84
n := 1
go func() {
for i := 2; i <= 5; i++ {
fmt.Println(n, "*", i)
n *= i
time.Sleep(100)
}
}()
http://play.golang.org/p/yqk82u0E4V
for i := 1; i <= 10; i++ {
fmt.Println(n, "+", i)
n += 1
time.Sleep(100)
}
競合
85. データ競合の解決
■ 問題点
● どのゴールーチンが先にアクセスするか分からない
● 値の変更や参照が競合する
■ 解決方法
● 1つの変数には1つのゴールーチンからアクセスする
● チャネルを使ってゴールーチン間で通信をする
● またはロックをとる(syncパッケージ)
85
"Do not communicate by sharing memory; instead,
share memory by communicating"
86. 87. 88. 89. 90. チャネルの基本 −1−
90
■ 初期化
■ 送信
■ 受信
ch1 := make(chan int)
ch2 := make(chan int, 10)
n1 := <-ch1
n2 := <-ch2 + 100
容量を指定
ch1 <- 10
ch2 <- 10 + 20
受け取られるまでブロック
一杯であればブロック
送信されまでブロック
空であればブロック
make(chan int, 0)と同じ
91. チャネルの基本 −2−
91
func main() {
done := make(chan bool) // 容量0
go func() {
time.Sleep(time.Second * 3)
done <- true
}()
<-done
fmt.Println("done")
}
送信されるまでブロック
http://play.golang.org/p/k0sMCYe4PA
92. 93. select - case −1−
93
ゴールーチン-main
ゴールーチン-2
go f2()
ゴールーチン-1
go f1()
チャネル-1 チャネル-2
ブロックされるので
同時に送受信出来ない?
select
94. select - case −2−
94
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
95. nilチャネル
95
func main() {
ch1 := make(chan int)
var ch2 chan string
go 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
96. 97. チャネルを引数や戻り値にする
97
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
98. 双方向チャネル
98
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
間違った使い方ができる
99. 単方向チャネル
99
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
受信専用のチャネル
送信専用のチャネル
100. チャネルのclose
■ closeの挙動
● closeは送信側が行う
● 同じチャネルは2度閉じれない
○ panicが起こる
● 閉じられたチャネルには送信できない
○ panicが起こる
● 受信するとゼロ値とfalseが返ってくる
■ closeを使ったブロードキャスト
● 複数の受信箇所に一気にブロードキャストしたい
● closeした瞬間に受信場所にゼロ値が送られる
● 処理の終了を伝えるのに使われる
100
参考:http://qiita.com/tenntenn/items/dd6041d630af7feeec52
101. 102. 103. 104. syncパッケージ
■ sync.Mutex, sync.RWMutex
● 昔ながらロック
● チャネルを使うと複雑になりすぎる場合に使う
■ sync.WaitGroup
● 複数のゴルーチンの処理を待つのに使う
104
var wg sync.WaitGroup
wg.Add(1)
go func() { /* do something */ wg.Done() }()
wg.Add(1)
go func() { /* do something */ wg.Done() }()
wg.Wait()
105. 106. 107. 108. 109. 110. コンテキストの注意点
■ 注意点
● コンテキストは構造体のフィールドなどに保存しない
○ コンテキストはラップされるので値が変わる可能性が
ある
○ 引数で引き回す
● コンテキストにはリクエスト起因のデータのみ保存する
● Valueとして保存する場合のキーは外に公開しない
○ 型を作りエクスポートしない
○ 値を取得するため関数を作る
110
参考:http://sssslide.com/speakerdeck.com/timakin/contextantihatan
111. 112. 113. 114. 115. エラーに文脈を持たせる
■ github.com/pkg/errorsを使う
● File Not Foundとかでは分かりづらい
● 何をしようとした時にエラーが起きたか知りたい
● どんなパラメータだったのか知りたい
● errors.Wrapを使うとエラーをラップできる
● errors.Causeを使うと元のエラーが取得できる
115
if err := f(s); err != nil {
return errors.Wrapf(err, “f() with %s”, s)
}
116. 117. 118. Hello, net/http パッケージ
■ まずは動かしてみよう
package main
import "fmt"
import "net/http"
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, net/http!")
}
119. ハンドラ
■ ハンドラ
■ ハンドラの登録
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, net/http!")
}
http.HandleFunc("/", handler)
レスポンスを書き込むWriter
リクエスト
レスポンスの書き込み
/というパターンのパスで来たリクエストを
ハンドリングする関数を登録
119
120. 121. 122. 123. 124. 125. 126. 127. 128. ミドルウェアを作る
■ ハンドラより前に行う共通処理
128
type MiddleWare interface {
ServeNext(h http.Handler) http.Handler
}
type MiddleWareFunc func(h http.Handler) http.Handler
func (f MiddleWareFunc) ServeNext(h http.Handler) http.Handler {
return f(h)
}
func With(h http.Handler, ms ...MiddleWare) http.Handler {
for _, m := range ms {
h = w.ServeNext(h)
}
return h
}
129. 130. ハンドラのテスト例
■ このハンドラをテストする
package main
import "fmt"
import "net/http"
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello, net/http!")
}
131. ハンドラのテスト例
func TestSample(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
handler(w, r)
rw := w.Result()
defer rw.Body.Close()
if rw.StatusCode != http.StatusOK {
t.Fatal("unexpected status code")
}
b, err := ioutil.ReadAll(rw.Body)
if err != nil {t.Fatal("unexpected error)}
const expected = "Hello, net/http!"
if s := string(b); s != expected {
t.Fatalf("unexpected response: %s", s)
}
}
テスト用のReponseWriterと
Requestを作る
132. 133. リクエスト/レスポンスのテスト
■ リクエストのテストからドキュメントを生成
● Request Bodyをリフレクションでテストしている
● 構造体の任意のフィールドを指定できる
133
validator.RequestBody(t, []httpdoc.TestCase{
{"Name", "tenntenn", "User Name"},
{"Attribute.Birthday", "1986-01-12", "User
birthday YYYY-MM-DD format"},
}, &createUserRequest{})
参考:go-httpdocのexample
134.