Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Goらしいコードを業務でも書くために

tom
September 01, 2018
15k

 Goらしいコードを業務でも書くために

DevFest18 session6 roomA+B

tom

September 01, 2018
Tweet

Transcript

  1. ROB PIKE Go's purpose is to make its designers' programming

    lives better. Go言語が目指していることは、Goを利用するプ ログラマを幸せにすること。 1. Goの哲学
  2. UNIX Philosophy 1. Goの哲学 • スモールイズビューティフル • 1つのプログラムには1つのことをうまく やらせる ◦

    レイク・フライの様に • ソフトウェアを梃子として使う ◦ よいプログラマはよいコードを書く。偉大な プログラマはよいコードを借りてくる。 • 全てのプログラムはフィルタとして設計 する • 90%の解を求める • etc...
  3. 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
  4. Go Doc 2. 標準パッケージから学ぶ • 公式サイト ◦ https://golang.org/pkg/ • コードからドキュメントが書き出されている

    ◦ go/doc • ドキュメントからコードが追いやすいので身構え ずとも読むことはできる • ?m=all つけると非公開のものも表示される
  5. 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は関数を登録 指定したパスへ リクエストが来ると 登録した処理が実行される
  6. 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として振る 舞う
  7. 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に型キャスト してる
  8. 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として振る 舞う
  9. 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 インターフェースもコンポジ ションできる できる限り小さい単位でイン ターフェースを定義 必要に応じで埋め込み
  10. 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 文字列の出力もインターフェー スで定義 元々の振る舞いも変更できる 拡張にはオープン 自身の振る舞いはクローズ
  11. (番外編)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は各時用意 その後はプラットフォームを 意識せず同じコード 技術的制約に対して有効的 に活用できる
  12. 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は待つ
  13. 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) 外部から好きに操作されたく ないものは公開しない チャネルなどが意識せずに使 われ非同期処理が実現
  14. 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での受け渡し
  15. 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度 だけしか閉じられないことを 保証
  16. 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
  17. 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をしやすくテスト実行関数を用意ができる テストケース × テスト実行関数
  18. 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から
  19. 1年間の振り返り 3. まとめ • 最初標準パッケージを見れていなかった ◦ まだ読むには早い… ◦ 読むの難しそう… ◦

    業務には活かせなそう… 実はそんなことはなく 業務での実装でも十分参考になる
  20. 例: 新卒1年目エンジニア 3. まとめ • 読み始める前: ◦ 深く考えず全部構造体で書く ◦ 近くにある業務のコードを脳死で真似る

    ↓ 非同期処理を書くのに読み始める • 読み始めた後: ◦ 標準パッケージなどからいいコードを真似る ◦ 振る舞いの求められる最小単位を考える ◦ 責務の境界線を意識する
  21. 最後に 3. まとめ • 標準パッケージは初心者が読んでも学べる • そして本当に色々なことを教えてくれる ◦ Go Wayな書き方とその効力

    • 紹介したのは氷山の一角 ◦ 参考になるコードは至る所にある • Go Wayは枷ではない ◦ 解決のために利用していることを忘れない