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

80c9496c79307b92304f0bb45f728ed2?s=47 tom
September 01, 2018
14k

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

DevFest18 session6 roomA+B

80c9496c79307b92304f0bb45f728ed2?s=128

tom

September 01, 2018
Tweet

Transcript

  1. 6.

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

    lives better. Go言語が目指していることは、Goを利用するプ ログラマを幸せにすること。 1. Goの哲学
  2. 9.

    UNIX Philosophy 1. Goの哲学 • スモールイズビューティフル • 1つのプログラムには1つのことをうまく やらせる ◦

    レイク・フライの様に • ソフトウェアを梃子として使う ◦ よいプログラマはよいコードを書く。偉大な プログラマはよいコードを借りてくる。 • 全てのプログラムはフィルタとして設計 する • 90%の解を求める • etc...
  3. 10.

    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. 15.

    Go Doc 2. 標準パッケージから学ぶ • 公式サイト ◦ https://golang.org/pkg/ • コードからドキュメントが書き出されている

    ◦ go/doc • ドキュメントからコードが追いやすいので身構え ずとも読むことはできる • ?m=all つけると非公開のものも表示される
  5. 17.

    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. 18.

    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. 19.

    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. 20.

    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. 22.

    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. 23.

    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. 24.

    (番外編)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. 27.

    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. 28.

    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. 29.

    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. 30.

    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. 33.

    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. 34.

    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. 35.

    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. 37.

    1年間の振り返り 3. まとめ • 最初標準パッケージを見れていなかった ◦ まだ読むには早い… ◦ 読むの難しそう… ◦

    業務には活かせなそう… 実はそんなことはなく 業務での実装でも十分参考になる
  20. 38.

    例: 新卒1年目エンジニア 3. まとめ • 読み始める前: ◦ 深く考えず全部構造体で書く ◦ 近くにある業務のコードを脳死で真似る

    ↓ 非同期処理を書くのに読み始める • 読み始めた後: ◦ 標準パッケージなどからいいコードを真似る ◦ 振る舞いの求められる最小単位を考える ◦ 責務の境界線を意識する
  21. 40.

    最後に 3. まとめ • 標準パッケージは初心者が読んでも学べる • そして本当に色々なことを教えてくれる ◦ Go Wayな書き方とその効力

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