Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Goらしいコードを業務でも書くために
tom
September 01, 2018
18
14k
Goらしいコードを業務でも書くために
DevFest18 session6 roomA+B
tom
September 01, 2018
Tweet
Share
Featured
See All Featured
Designing on Purpose - Digital PM Summit 2013
jponch
106
5.6k
Pencils Down: Stop Designing & Start Developing
hursman
112
9.8k
Support Driven Design
roundedbygravity
86
8.5k
Music & Morning Musume
bryan
35
4.2k
Building Applications with DynamoDB
mza
83
4.7k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
7
1.1k
How New CSS Is Changing Everything About Graphic Design on the Web
jensimmons
213
11k
Designing the Hi-DPI Web
ddemaree
272
32k
Mobile First: as difficult as doing things right
swwweet
213
7.5k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
39
13k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
105
16k
Building Adaptive Systems
keathley
25
1.1k
Transcript
Goらしいコードを 業務でも書くために TOKYO JP, SEPT 01 2018 Kohki Ikeuchi DeNA
Co., Ltd. tom
• tom • @Kender_09 • golan.tokyo運営お手伝い • DeNA2017年新卒入社 ◦ GAE/Goで開発
自己紹介
今日話すこと • Goらしさ • 業務でも活用できる 標準パッケージの 実装コードを紹介
Goの哲学 1. 標準パッケージから学ぶ インターフェース 非同期処理 テスト まとめ 2. 2.1. 2.2.
2.3. 3.
Goの哲学 SECTION 1.
ROB PIKE Go's purpose is to make its designers' programming
lives better. Go言語が目指していることは、Goを利用するプ ログラマを幸せにすること。 1. Goの哲学
Go Philosophy 1. Goの哲学 + 現実世界の問題解決が目的 + 大規模なソフトウェア開発を行う人のための言語 https://talks.golang.org/2012/splash.article
{ Go Design Simplicity. シンプルで 各言語機能が 理解しやすい 1. Goの哲学
UNIX Philosophy 1. Goの哲学 • スモールイズビューティフル • 1つのプログラムには1つのことをうまく やらせる ◦
レイク・フライの様に • ソフトウェアを梃子として使う ◦ よいプログラマはよいコードを書く。偉大な プログラマはよいコードを借りてくる。 • 全てのプログラムはフィルタとして設計 する • 90%の解を求める • etc...
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
Go Wayを身に付けたいなら、 標準パッケージを参考にしながら 学んでいくと良いよ 強い人
そう言われても どれを読めば 何から読めば 業務に活かせるのか 1. Goの哲学
独断と偏見で Web API開発の 1年間で為になった パッケージを簡単に紹介 1. Goの哲学
標準パッケージ から学ぶ SECTION 2.
Go Doc 2. 標準パッケージから学ぶ • 公式サイト ◦ https://golang.org/pkg/ • コードからドキュメントが書き出されている
◦ go/doc • ドキュメントからコードが追いやすいので身構え ずとも読むことはできる • ?m=all つけると非公開のものも表示される
インターフェース SECTION 2.1.
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は関数を登録 指定したパスへ リクエストが来ると 登録した処理が実行される
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として振る 舞う
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に型キャスト してる
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として振る 舞う
{ net/http 学びポイント インターフェースの定義の仕方 インターフェースを満たす構造 体の作り方 インターフェースを満たす関数 の作り方 2.1. 標準パッケージから学ぶ
- インターフェース
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 インターフェースもコンポジ ションできる できる限り小さい単位でイン ターフェースを定義 必要に応じで埋め込み
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 文字列の出力もインターフェー スで定義 元々の振る舞いも変更できる 拡張にはオープン 自身の振る舞いはクローズ
(番外編)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は各時用意 その後はプラットフォームを 意識せず同じコード 技術的制約に対して有効的 に活用できる
{ インターフェース 学びポイント インターフェースは小さく 振る舞いを受け継ぐ 埋め込みを活用する オープン・クローズド 2.1. 標準パッケージから学ぶ -
インターフェース
非同期処理 SECTION 2.2.
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は待つ
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) 外部から好きに操作されたく ないものは公開しない チャネルなどが意識せずに使 われ非同期処理が実現
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での受け渡し
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度 だけしか閉じられないことを 保証
{ io.Pipe 学びポイント チャネルの使い方 非公開の構造体 カプセル化 io.Writer, io.Reader io.Closerの実装の仕方 2.2.
標準パッケージから学ぶ - 非同期処理
テスト SECTION 2.3.
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
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をしやすくテスト実行関数を用意ができる テストケース × テスト実行関数
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から
まとめ SECTION 3.
1年間の振り返り 3. まとめ • 最初標準パッケージを見れていなかった ◦ まだ読むには早い… ◦ 読むの難しそう… ◦
業務には活かせなそう… 実はそんなことはなく 業務での実装でも十分参考になる
例: 新卒1年目エンジニア 3. まとめ • 読み始める前: ◦ 深く考えず全部構造体で書く ◦ 近くにある業務のコードを脳死で真似る
↓ 非同期処理を書くのに読み始める • 読み始めた後: ◦ 標準パッケージなどからいいコードを真似る ◦ 振る舞いの求められる最小単位を考える ◦ 責務の境界線を意識する
読み方 3. まとめ • 悩んでいるのに似た機能から読んでみる ◦ 非同期処理に悩んでいたら、非同期処理を提供 しているio.Pipeをみてみるなど • 準標準パッケージや後から実装された機能を
読んでみる ◦ 従来ある機能で拡張するように書かれていること が多い ◦ 読みやすいことが多い(気がする
最後に 3. まとめ • 標準パッケージは初心者が読んでも学べる • そして本当に色々なことを教えてくれる ◦ Go Wayな書き方とその効力
• 紹介したのは氷山の一角 ◦ 参考になるコードは至る所にある • Go Wayは枷ではない ◦ 解決のために利用していることを忘れない
標準パッケージを 読むのは楽しい!! ご静聴ありがとうございました