errors.Wrap
errors.Wrap

この記事は「Eureka Advent Calendar 2020」の19日目の記事です。

こんにちは、2020年今年こそはとダイエットに意気込み、振り返ってみればジムへ行ったのは1回きり、出前を頼んだのは100回以上、見事に5kg体重を増やした山下です。

昨日は同じBackendチームのJamesさんによる「Understanding Allocations in Go」でした。

今回はこちらの記事にあるPairsエンゲージのエラー機構を詳しく説明していきたいと思います。
(本当は面白Goクイズをたくさん出したかったのですが、playgroundをmedium上にうまく埋め込めず断念しました。泣)

概要

  1. エラーは全ての箇所でラップして綺麗にスタックトレースを出力しましょう
  2. エラーレベルやエラーメッセージを付与して汎用性を高めてみましょう

参考実装をアップしておいたので、コードを見た方が手っ取り早い方はこちらをご覧ください。

はじめに

PairsエンゲージはPairsのGoフルスクラッチを経て、新規にGoで作成されています。(アーキテクチャー等の話はこちらの記事にあります。)

新たにアプリケーションを作成するに当たって、既存のアプリケーションのエラーの取り扱いに関する、下記の課題を解決したいという想いがありました。

  • エラーの原因の特定が困難
  • 一度のエラーに関して、複数のエラーログが出力されてしまう
  • 各所でエラーの取り扱いを考えながらコーディングする必要があり面倒

これらの課題を意識しながらエラーの取り扱い方を定めていきました。

errorsパッケージを作成して独自のエラー構造体を定義する

まずはじめに、下記のようなerrorsパッケージを作成しました。
errorsパッケージを独自で作成してそこに処理を集約することで、ビルトインされたerrorsのアップデートに追従するのもかなり楽になります。

まずはerrorの中身を格納する「message」というフィールドを用意します。さらにエラーを階層化するため、「next」というerror型のフィールドとスタックの位置情報を保存するflameというフィールドを用意しています。

Format(f State, c rune)とFormatError(p xerrors.Printer)というメソッドを用意することでfmt.Sprintfで出力した際の表示を変更しています。このあたりはお好きなように変更していただくのが良いと思います。

エラーは全ての箇所でラップして綺麗にスタックトレースを出力する

同じファイル内に下記のようなエラーを生成する関数、および、エラーをラップする関数を用意しましょう。

NewやErrorfはerrors.Newfmt.Errorfと同様に利用すればよいのですが、Wrapに関しては全ての箇所でWrapすることを心がけてください。おそらくこのWrapがプロジェクト内で一番呼ばれる関数になるはずです。

返り値にerrorがあればWrapです。返り値のerrorがnilでなければWrap。

if err := Exec(); err != nil {
return errors.Wrap(err)
}

このルールを守るためには下記のようなreturn時に処理を実行するような書き方はよくないですね

return Exec() // エラーをWrapできていないのでよくない

さて

ここまでできれば課題としてあげていた問題は全て解決できます。

APIでもバッチのタスクでも一番おお外の処理でエラーの出力、またはロギングの処理をしてあげればOKです。

if err := Main(); err != nil {
v := errors.AsAppError(err)
if v == nil {
v = errors.AsAppError(errors.SystemUnknown.Wrap(err, "Unknown error"))
}
fmt.Printf("%+v", v) // or ログ送信等
}

それ以外の箇所では単にラップするだけ。

【出力時一例】

github.com/eure/myapp/server/cmd/batch/cmd.runExec
/Users/yamashitakento/repos/gohome/src/github.com/eure/myapp/server/cmd/batch/cmd/batch_sample.go:38
- github.com/eure/myapp/server/src/app/batch/facades.(*SampleFacade).SampleExec
/Users/yamashitakento/repos/gohome/src/github.com/eure/myapp/server/src/app/batch/facades/sample_facade.go:99
- could not fetch : pager state &{Limit:100 Offset:0 Total:0 Max: Min: Orders:[{Column:id IsAsc:true}] pagingType:1}: :
github.com/eure/myapp/server/src/app/batch/facades.(*SampleFacade).enqueueRecommendationUsers
/Users/yamashitakento/repos/gohome/src/github.com/eure/myapp/server/src/app/batch/facades/sample_facade.go:118
- [crit] [system_default] example error!:
github.com/eure/myapp/server/src/domain/user.(*service).FindUsers

アプリケーションに合わせてより汎用性をもたせる

上述のエラー構造体には level, code, infoMessage といったフィールドも定義されています。それぞれ説明していきます。

  • level

エラーにlevelという概念を持たせることで、不必要なロギングやよりクリティカルな箇所でのエラー検知等に役立ちます。

  • code

エラーごとにユニークな値を持たせることでよりトラッキングしやすくなったり、code指定をルール化することでエラーのグルーピング等も用意になります。codeはstring型でもint型でもどちらでも良いと思いますが、stringならプレフィックスでグルーピングしてエラー発生時のcodeをサンプリングすることでグループごとのエラー頻度等確認することができると思います。

  • infoMessage

これはエラーの構造体に「ユーザーへの表示テキスト」を持たせちゃうというやつです。横着なような気がしますが、実際管理は非常に簡単です。

ちなみにerrorsパッケージは完全にアーキテクチャの階層から独立して置いています。これはどの層にも依存せず汎用的に利用できるようにそうしています。

├── src
│ ├── api
│ ├── app
│ ├── domain
│ └── infra
└── errors

まとめ

今回紹介したエラーパッケージはGo1.12以下のバージョンの時に作成したものです。xerrorsがGoの公式から出ていて今後のアップデートを見越して作成したものであり、Go1.13のリリースの際にはxerrorsの依存を外せる予想でした。しかしスタックフレームの実装は断念され、現在もxerrorsを利用しています。今後もGo2へのプロポーザル等を確認しながら実装を追っていきますが、現時点では特に不便なく運用できています。

また、「うちはこうしている」とか、「もっとこうした方がいいよ」とか、色々意見交換ができたら非常に嬉しいです。

明日からもGoのエンジニアのブログが続きます。
是非ご拝読ください。


目次

  1. 基盤スクラッチ開発の選択
  2. 基盤開発スタート
  3. 基盤開発終盤
  4. 機能開発スタート
  5. 開発完了〜リリース、そして振り返り〜
  6. あとがき

プロローグ

日本一のオンラインデーティングサービスを運営する弊社。

2019年4月、桜の開花とともに新規サービスの概ねの仕様が固まり、いざ開発が始まった。

APIの開発はJimという私の同僚が1人で行う予定であった。

ある日、のんきに帰宅しようとしていると、Jimから呼び出された。

私(山下)


この記事は eureka Advent Calendar 2018 にのり遅れたおまけ記事です。

今年開催されたGoの勉強会 golang.tokyo #18 のDevquiz枠にあったこちらの問題、私は非常に感銘を受けました。

クイズとGoを合わせたらこんなに面白いんですね。
とくに素晴らしいのはplaygroundで実際に実行して、すぐに答え合わせができること。この発想はなかったです。解けた瞬間の気持ちよさは今でも忘れられません。

いてもたってもいられず自分でも作成してみたので、皆様にもやってみていただきたいです。
一発で全て解けたらあなたの勝ち。解けなければ私の勝ちです。(なんの話をしているのでしょうか..)

【例題】関数の戻り値を答えてください。


オリジナルのThe Go gopher(Gopherくん)は、Renée Frenchによってデザインされました。

はじめに

初級者向けです。
Go言語にはオブジェクト思考プログラミングにおける継承は存在しませんが、埋め込み(embedded)を利用して委譲させることはできます。継承がないという言葉が1人歩きしてしまい、無駄なコードを書かないために抑えておきたいポイントをまとめています。

埋め込みとは?

今回はテストケースを書く際の埋め込みの活用方法をサンプルコードで説明します。
まず、下記のUserという構造体を定義します。

package usertype User struct {
FirstName string
LastName string
Gender string
Birthday string
}
func (u *User) Nickname() string {

Kento Yamashita

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store