Slide 1

Slide 1 text

1 Golang再入門 mercari.go #22

Slide 2

Slide 2 text

2 自己紹介 ● twitter: @ques0942 ● mercariのバックエンドエンジニア ● 最近の主戦場はPHPで、mercariの取引機能を担当するチームで働いています ● 1年半ほど前まではGolangがメインで、最近Golangに再入門しました

Slide 3

Slide 3 text

3 今日話すこと ● Golang再入門するときに勉強し直したこと、変化を認識したことの紹介 ○ 初心者向けの内容です ● 目次 ○ 構造的部分型(Golangのインターフェースの使い方 ) ○ エラーハンドリング

Slide 4

Slide 4 text

4 構造的部分型(Golangのインターフェースの使い方) ● PHPなどの公称型とは異なる型システム ○ クラスではなくインターフェースに基づいて型チェックがなされる ○ Pythonのduck typingを静的に行うようなもの ○ 継承、抽象クラスは無いが、構造体を埋め込んで機能を流用することは可能 ● Golang(とかTypeScriptとかClean architecture)に慣れると「機能を使う側が インターフェースを定義する」という感覚になった ○ Javaでコードを書いていたときは、インターフェースは実装しているクラスの付属物だと思っていた ■ クラスを作る側がインターフェースを定義する ○ “Accept interfaces, return structs” とか github/goのwikiにあるレビュー指針 とかを参考に するとインターフェースは呼び出す側のものとして整理されている

Slide 5

Slide 5 text

5 構造的部分型(Golangのインターフェースの使い方) ● 例えばAPIを書くとして、あるリクエストをコントローラーが処理するときにストレージ への読み込み、書き込みが発生する ● このストレージのインターフェースはコントローラーとデータストア、どちらのレイヤー が定義する? ● サンプルコード

Slide 6

Slide 6 text

6 構造的部分型(Golangのインターフェースの使い方) ● 例えば以下のようなパッケージ構 成のとき ○ cmd/main.go ○ controller/controller.go ○ storage/storage.go ● 右のインターフェースはどこに置く? type StringStorage interface { Load(key string) (string, error) Store(key, value string) error }

Slide 7

Slide 7 text

7 構造的部分型(Golangのインターフェースの使い方) package controller type StringStorage interface { Load(key string) (string, error) Store(key, value string) error } type Controller struct { s StringStorage } func New(s StringStorage) *Controller { return &Controller{s: s} } func (c *Controller) Load(key string) (string, error) { return c.s.Load(key) } func (c *Controller) Store(key, value string) error { return c.s.Store(key, value) } ● 今のところインター フェースを利用する controllerパッケージ に置くのが良いと思って います

Slide 8

Slide 8 text

8 構造的部分型(Golangのインターフェースの使い方) ● 依存する機能(メソッド)が明確になる ○ 例えばStringStorageにReplace(key, value)みたいな機能が増えても、 Controllerはその機能 を使うまではインターフェースを変更する必要がない ○ インターフェースに含まれない関数は利用されないので、機能改修時に影響範囲調査が楽になる ● 抽象度の高い方から実装できる ○ ユースケースに近いところから実装 ■ 必要なインターフェースを定義 ● ストレージとかAPIクライアントをそのインターフェースに合うように実装 ○ 契約駆動開発(Contract Driven Development)味がある ○ 結局うまく実装できなくて抽象度の高い側を直す羽目になったりもしますが

Slide 9

Slide 9 text

9 エラーハンドリング ● 昔はpkg/errorsで良かったけど今は? ● 欲しい機能 ○ 任意のエラーを定義して何が起きたかを特定したい ○ スタックトレースを取得して、意図しないエラーがどこでどのように発生したか特定したい ● 検討した候補 ○ errors.New + fmt.Errorf ■ golang1.13からの機能 ■ スタックトレースを保存する機能がない ○ pkg/errors ■ おなじみだがアーカイブされた ○ morikuni/failure ■ mercari社内で利用実績あり

Slide 10

Slide 10 text

10 エラーハンドリング

Slide 11

Slide 11 text

11 欲しい機能(独自エラー) ● アプリケーション独自のエラーを定義したい ○ 例えば ■ UserNotFoundError ■ PermissionDeniedError ■ InvalidValueError ● なぜ? ○ アプリケーションでどんなトラブルが起きたか知りたい ○ 適切なエラーハンドリングがしたい ■ 発生したエラーを識別して、ユーザーに伝える or 適切な緩和処置をしたい

Slide 12

Slide 12 text

12 欲しい機能(スタックトレース) ● エラーには発生したときのスタックトレースが入っていてほしい ● なぜ? ○ エラーテキストは考慮漏れで重複する可能性が有り得る ○ どのような経路で呼び出されたか知りたい

Slide 13

Slide 13 text

13 現時点での私の結論 ● アプリケーションで利用するなら ○ morikuni/failure or pkg/errors ■ どちらも任意のエラーコード定義とスタックトレースの取得が可能 ■ 個人的には ● 仕事ではmorikuni/failures ● 個人開発では使い慣れた pkg/errorsという感じ ■ pkg/errorsはアーカイブされたとはいえそんなに機能追加がいるものでは無いので … ● と思っていたらgo1.20でerrorsに複数エラーをラップできる 新機能が ● スタックトレースが要らないなら ○ errors.New + fmt.Errorf