Slide 1

Slide 1 text

Goらしいコードを 業務でも書くために TOKYO JP, SEPT 01 2018 Kohki Ikeuchi DeNA Co., Ltd. tom

Slide 2

Slide 2 text

● tom ● @Kender_09 ● golan.tokyo運営お手伝い ● DeNA2017年新卒入社 ○ GAE/Goで開発 自己紹介

Slide 3

Slide 3 text

今日話すこと ● Goらしさ ● 業務でも活用できる 標準パッケージの 実装コードを紹介

Slide 4

Slide 4 text

Goの哲学 1. 標準パッケージから学ぶ インターフェース 非同期処理 テスト まとめ 2. 2.1. 2.2. 2.3. 3.

Slide 5

Slide 5 text

Goの哲学 SECTION 1.

Slide 6

Slide 6 text

ROB PIKE Go's purpose is to make its designers' programming lives better. Go言語が目指していることは、Goを利用するプ ログラマを幸せにすること。 1. Goの哲学

Slide 7

Slide 7 text

Go Philosophy 1. Goの哲学 + 現実世界の問題解決が目的 + 大規模なソフトウェア開発を行う人のための言語 https://talks.golang.org/2012/splash.article

Slide 8

Slide 8 text

{ Go Design Simplicity. シンプルで 各言語機能が 理解しやすい 1. Goの哲学

Slide 9

Slide 9 text

UNIX Philosophy 1. Goの哲学 ● スモールイズビューティフル ● 1つのプログラムには1つのことをうまく やらせる ○ レイク・フライの様に ● ソフトウェアを梃子として使う ○ よいプログラマはよいコードを書く。偉大な プログラマはよいコードを借りてくる。 ● 全てのプログラムはフィルタとして設計 する ● 90%の解を求める ● etc...

Slide 10

Slide 10 text

Go Way 1. Goの哲学 ● GoをGoらしく使えれば最大限に活用できる ● コードフォーマットはfmtやvetを ○ https://golang.org/doc/effective_go.html ● シンプルであることを心がける ○ https://go-proverbs.github.io/ ● これができればSOLIDの原則を満たせるかも ○ https://dave.cheney.net/2016/08/20/solid-go-design

Slide 11

Slide 11 text

Go Wayを身に付けたいなら、 標準パッケージを参考にしながら 学んでいくと良いよ 強い人

Slide 12

Slide 12 text

そう言われても どれを読めば 何から読めば 業務に活かせるのか 1. Goの哲学

Slide 13

Slide 13 text

独断と偏見で Web API開発の 1年間で為になった パッケージを簡単に紹介 1. Goの哲学

Slide 14

Slide 14 text

標準パッケージ から学ぶ SECTION 2.

Slide 15

Slide 15 text

Go Doc 2. 標準パッケージから学ぶ ● 公式サイト ○ https://golang.org/pkg/ ● コードからドキュメントが書き出されている ○ go/doc ● ドキュメントからコードが追いやすいので身構え ずとも読むことはできる ● ?m=all つけると非公開のものも表示される

Slide 16

Slide 16 text

インターフェース SECTION 2.1.

Slide 17

Slide 17 text

net/http 2.1. 標準パッケージから学ぶ - インターフェース 使い方 http.Handle("/foo", fooHandler) http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, Gopher") }) log.Fatal(http.ListenAndServe(":8080", nil)) 標準のWebサーバ HandleはHandlerを登録 HandleFuncは関数を登録 指定したパスへ リクエストが来ると 登録した処理が実行される

Slide 18

Slide 18 text

net/http 2.1. 標準パッケージから学ぶ - インターフェース type Handler interface { ServeHTTP(ResponseWriter, *Request) } type redirectHandler struct { url string code int } func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) { Redirect(w, r, rh.url, rh.code) } type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } http.Handler HandlerはServeHTTPを 持つことと定義 構造体(struct)でも 関数(func)でもServeHTTP を持てばHandlerとして振る 舞う

Slide 19

Slide 19 text

net/http 2.1. 標準パッケージから学ぶ - インターフェース func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) } func (mux *ServeMux) Handle(pattern string, handler Handler) { // 省略 } http.HandleFunc HandleFuncは引数の関数 をHandlerとして振る舞える HandlerFuncに型キャスト してる

Slide 20

Slide 20 text

net/http 2.1. 標準パッケージから学ぶ - インターフェース type methodHandler map[string]http.Handler func (m methodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h, ok := m[r.Method]; ok { h.ServeHTTP(w, r) return } http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) } func Build() *http.ServeMux { mux := http.NewServeMux() mux.Handle("/1.0.0/hoge", methodHandler{ "POST": http.HandlerFunc(Hoge), }) return mux } 自分でも使ってみる mux.HandleはHandlerで あれば登録できる メソッドとしてServeHTTPを もつのでHandlerとして振る 舞う

Slide 21

Slide 21 text

{ net/http 学びポイント インターフェースの定義の仕方 インターフェースを満たす構造 体の作り方 インターフェースを満たす関数 の作り方 2.1. 標準パッケージから学ぶ - インターフェース

Slide 22

Slide 22 text

io 2.1. 標準パッケージから学ぶ - インターフェース type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } type ReadWriter interface { Reader Writer } type WriteCloser interface { Writer Closer } io.Reader, io.Writer, io.Closer インターフェースもコンポジ ションできる できる限り小さい単位でイン ターフェースを定義 必要に応じで埋め込み

Slide 23

Slide 23 text

fmt 2.1. 標準パッケージから学ぶ - インターフェース type Stringer interface { String() string } package time func (l *Location) String() string { return l.get().name } // おまけ type Cat struct{} func (c Cat) Cry() { fmt.Println(c) } func (c Cat) String() string { return "nyaaa" } type OctoCat struct { Cat } func (o OctoCat) String() string { return "approve" } func main() { var octo OctoCat fmt.Println(octo) // “approve” 拡張が適用されている octo.Cry() // “nyaaa” Cry内で呼ばれるStringは Catの } Stringer 文字列の出力もインターフェー スで定義 元々の振る舞いも変更できる 拡張にはオープン 自身の振る舞いはクローズ

Slide 24

Slide 24 text

(番外編)go-cloud 2.1. 標準パッケージから学ぶ - インターフェース switch *cloud { case "gcp": b, err = setupGCP(ctx, *bucketName) case "aws": b, err = setupAWS(ctx, *bucketName) } func setupGCP(ctx context.Context, bucket string) (*blob.Bucket, error) { creds, err := gcp.DefaultCredentials(ctx) if err != nil { return nil, err } c, err := gcp.NewHTTPClient(gcp.DefaultTransport(), gcp.CredentialsTokenSource(creds)) if err != nil { return nil, err } return gcsblob.OpenBucket(ctx, bucket, c) } https://github.com/google/go-cloud aws, gcpどちらでも同じコー ドで書くことができる setupは各時用意 その後はプラットフォームを 意識せず同じコード 技術的制約に対して有効的 に活用できる

Slide 25

Slide 25 text

{ インターフェース 学びポイント インターフェースは小さく 振る舞いを受け継ぐ 埋め込みを活用する オープン・クローズド 2.1. 標準パッケージから学ぶ - インターフェース

Slide 26

Slide 26 text

非同期処理 SECTION 2.2.

Slide 27

Slide 27 text

io.Pipe 2.2. 標準パッケージから学ぶ - 非同期処理 func main() { r, w := io.Pipe() go func() { fmt.Fprint(w, "some text to be read\n") w.Close() }() buf := new(bytes.Buffer) buf.ReadFrom(r) fmt.Print(buf.String()) } 使い方 io.Pipe()でReadCloserと WriteCloserを作成 通常のReader Writer同様 書き込み読み込みが可能 Writerに書き込まれClose されるのをReaderは待つ

Slide 28

Slide 28 text

io.Pipe 2.2. 標準パッケージから学ぶ - 非同期処理 type pipe struct { wrMu sync.Mutex wrCh chan []byte rdCh chan int once sync.Once done chan struct{} rerr atomicError werr atomicError } type PipeReader struct { p *pipe } type PipeWriter struct { p *pipe } func Pipe() (*PipeReader, *PipeWriter) { p := &pipe{ wrCh: make(chan []byte), rdCh: make(chan int), done: make(chan struct{}), } return &PipeReader{p}, &PipeWriter{p} } func Pipe pipeから PipeReader(Reader)と PipeWriter(Writer) 外部から好きに操作されたく ないものは公開しない チャネルなどが意識せずに使 われ非同期処理が実現

Slide 29

Slide 29 text

io.Pipe 2.2. 標準パッケージから学ぶ - 非同期処理 func (p *pipe) Write(b []byte) (n int, err error) { select { case <-p.done: // 閉じられたとき return 0, p.writeCloseError() default: p.wrMu.Lock() defer p.wrMu.Unlock() } for once:=true; once||len(b)> 0; once=false { select { case p.wrCh <- b: // wrChが待ち受けていたら nw := <-p.rdCh b = b[nw:] n += nw case <-p.done: return n, p.writeCloseError() } } return n, nil } Write func (p *pipe) Read(b []byte) (n int, err error) { select { case <-p.done: return 0, p.readCloseError() default: } select { case bw := <-p.wrCh: // wrChへきたら nr := copy(b, bw) p.rdCh <- nr return nr, nil case <-p.done: return 0, p.readCloseError() } } Read pipeにメソッドが実装 p.doneでクローズされているか確認 バイト配列のチャネルp.wrChでの受け渡し

Slide 30

Slide 30 text

io.Pipe 2.2. 標準パッケージから学ぶ - 非同期処理 func (p *pipe) CloseWrite(err error) error { if err == nil { err = EOF } p.werr.Store(err) p.once.Do(func() {close(p.done) }) return nil } func (p *pipe) CloseRead(err error) error { if err == nil { err = ErrClosedPipe } p.rerr.Store(err) p.once.Do(func() { close(p.done) }) return nil } Close p.done(chan struct{})を closeすることで閉じたこと を伝える p.once(sync.Once)で1度 だけしか閉じられないことを 保証

Slide 31

Slide 31 text

{ io.Pipe 学びポイント チャネルの使い方 非公開の構造体 カプセル化 io.Writer, io.Reader io.Closerの実装の仕方 2.2. 標準パッケージから学ぶ - 非同期処理

Slide 32

Slide 32 text

テスト SECTION 2.3.

Slide 33

Slide 33 text

TDT Table-driven tests 2.3. 標準パッケージから学ぶ - テスト package strings_test type SplitTest struct { s string sep string n int a []string } var splittests = []SplitTest{ {"", "", -1, []string{}}, {abcd, "", 2, []string{"a", "bcd"}}, {abcd, "", 4, []string{"a", "b", "c", "d"}}, {faces, "", -1, []string{"☺", "☻", "☹"}}, {faces, "", 17, []string{"☺", "☻", "☹"}}, {"☺�☹", "", -1, []string{"☺", "�", "☹"}}, {abcd, "a", 0, nil}, {faces, "☹", -1, []string{"☺☻", ""}}, {faces, "~", -1, []string{faces}}, } strings func TestSplit(t *testing.T) { for _, tt := range splittests { a := SplitN(tt.s, tt.sep, tt.n) if !eq(a, tt.a) { t.Errorf("Split(%q, %q, %d) = %v; want %v", tt.s, tt.sep, tt.n, a, tt.a) Continue } if tt.n == 0 { Continue } // 省略 } } テストに利用する構造体を定義 テストケースを配列で用意 配列をfor文で回して実行 場合によってt.Runを利用してsubtests

Slide 34

Slide 34 text

Run Test Func 2.3. 標準パッケージから学ぶ - テスト package strings_test // Execute f on each test case. func runIndexTests(t *testing.T, f func(s, sep string) int, funcName string, testCases []IndexTest) { for _, test := range testCases { actual := f(test.s, test.sep) if actual != test.out { t.Errorf("%s(%q,%q) = %v; want %v", funcName, test.s, test.sep, actual, test.out) } } } func TestIndex(t *testing.T) { runIndexTests(t, Index, "Index", indexTests) } func TestLastIndex(t *testing.T) { runIndexTests(t, LastIndex, "LastIndex", lastIndexTests) } func TestIndexAny(t *testing.T) { runIndexTests(t, IndexAny, "IndexAny", indexAnyTests) } strings TDTをしやすくテスト実行関数を用意ができる テストケース × テスト実行関数

Slide 35

Slide 35 text

Test Package 2.3. 標準パッケージから学ぶ - テスト package time import ( "Sync" ) func ResetLocalOnceForTest() { localOnce = sync.Once{} localLoc = Location{} } var ( ForceZipFileForTesting = forceZipFileForTesting ParseTimeZone = parseTimeZone SetMono = (*Time).setMono GetMono = (*Time).mono ErrLocation = errLocation ) export_time.go Testは別パッケージ package ***_test 必要な非公開機能は export_***.goから

Slide 36

Slide 36 text

まとめ SECTION 3.

Slide 37

Slide 37 text

1年間の振り返り 3. まとめ ● 最初標準パッケージを見れていなかった ○ まだ読むには早い… ○ 読むの難しそう… ○ 業務には活かせなそう… 実はそんなことはなく 業務での実装でも十分参考になる

Slide 38

Slide 38 text

例: 新卒1年目エンジニア 3. まとめ ● 読み始める前: ○ 深く考えず全部構造体で書く ○ 近くにある業務のコードを脳死で真似る ↓ 非同期処理を書くのに読み始める ● 読み始めた後: ○ 標準パッケージなどからいいコードを真似る ○ 振る舞いの求められる最小単位を考える ○ 責務の境界線を意識する

Slide 39

Slide 39 text

読み方 3. まとめ ● 悩んでいるのに似た機能から読んでみる ○ 非同期処理に悩んでいたら、非同期処理を提供 しているio.Pipeをみてみるなど ● 準標準パッケージや後から実装された機能を 読んでみる ○ 従来ある機能で拡張するように書かれていること が多い ○ 読みやすいことが多い(気がする

Slide 40

Slide 40 text

最後に 3. まとめ ● 標準パッケージは初心者が読んでも学べる ● そして本当に色々なことを教えてくれる ○ Go Wayな書き方とその効力 ● 紹介したのは氷山の一角 ○ 参考になるコードは至る所にある ● Go Wayは枷ではない ○ 解決のために利用していることを忘れない

Slide 41

Slide 41 text

標準パッケージを 読むのは楽しい!! ご静聴ありがとうございました