[Go]Errorはどのように処理しますか?


Don’t just check errors, handle them gracefully
Errorsは、チェックだけでなく、優雅に処理します.
よく読んだ後に原文と翻訳.私が理解している内容に基づいて要約したいと思います.
すべての説明とソースコードは原文と翻訳を参照しています.

2つのエラーを処理する方法と1つのエラーを処理する方法


悪い方法1:sentinelerror(見張りエラー)

if err == 특정_문자열_혹은_{
}
アルゴリズムでは,終了条件や再処理できない場合を表す値をsentinel valueと呼ぶ.たとえば、次のコードの-1はsentinelvalueです.
int find(int arr[], size_t len, int val)
{
    for (int i = 0; i < len; i++)
        if (arr[i] == val)
            return i;
    return -1; // not found
}
原文ではこのsentinel(番人)という言葉をerrorと組み合わせてsentinelerrorという言い方を用いた.io.EOF、syscallパッケージで使用されるsyscall.ENOENTのような低レベルのエラーはsentinelエラーに属します.
前哨エラー処理エラーが使用されている場合、呼び出し関数の一方はif error == 특정값演算によりエラーを処理する.
この「特定値」は様々な問題を引き起こす.エラーを確認するには、必ず呼び出し元に「特定の値」を入力する必要があります.すなわち,パッケージ間に依存性が生じる.
あるいは、このsentinel errorを処理する論理においてerrorを返すインタフェースが定義されている場合、インタフェースの実装者はerrorのみを返す制約がある.
これらの異なる問題のため、哨兵の誤りはできるだけ避けるべきだ.

悪い方法2:errortypes


errorを特定の構造体にし、errorに追加のcontext情報を含ませる方法.
type MyError struct {
        Msg string
        File string
        Line int
}
前哨エラーとは異なり、他のコンテキスト情報を通常のエラーにカプセル化することでエラーを処理できます.
しかしerrortype方式にも前哨errorがもたらす問題がある.
処理errorのパケットは定義されたMyErrorパケットに依存する.また,このerrortypeを利用すればするほど依存関係が強くなり,最終的には臭いapiが発生する.
エラータイプも避けるべきエラー処理です.

ベストプラクティス:不透明なエラー(不透明なエラー)

if err != nil {
	return err
}
筆者は,誤り処理の答えとして不透明な誤りを提案した.
エラーが発生した場合、opaqueエラーが直接送信されます.ここでerrorを処理するためにいくつかのデバイスを追加するだけです.
まず,動作ベースの誤った断言を追加した.
エラーを表示し、タスク呼び出し元がタスクを再実行する必要があるかどうかを判断する論理があるとします.
通常のopaquerorでは、errorが表示されず、関数が再呼び出されます.しかし,errorが挙動法を実現すれば,この論理を実行することができる.
type temporary interface {
        Temporary() bool
}
 
// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
        te, ok := err.(temporary)
        return ok && te.Temporary()
}
IsTemporay()関数の内部では、一時的なメソッドが実装されている場合は呼び出されます.そうでない場合はスキップされます.同時に、依存性の問題を解決するために、任意のエラーをIsTemporry()関数に渡すことができます.
次のデバイスはContextの追加です.
goは、github.com/pkg/errorsパケットのWrap()およびCause()関数を介して、ContextをErrorに追加または復元することができる.
次のコードはcontextを追加したopaquerorの強力な機能を示しています.
package main
import (
    "github.com/pkg/errors"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
)
func ReadFile(path string) ([]byte, error) {
        f, err := os.Open(path)
        if err != nil {
								// os.Open에서 발생한 err에 "open failed"라는 context가 추가되었다.
                return nil, errors.Wrap(err, "open failed")
        }
        defer f.Close()
        buf, err := ioutil.ReadAll(f)
        if err != nil {
								// os.Open에서 발생한 err에 "read failed"라는 context가 추가되었다.
                return nil, errors.Wrap(err, "read failed")
        }
        return buf, nil
}
func ReadConfig() ([]byte, error) {
    home := os.Getenv("HOME")
    config, err := ReadFile(filepath.Join(home, ".settings.xml"))
// ReadFile에서 발생한 err에 "could not read config"라는 context가 추가되었다.
    return config, errors.Wrap(err, "could not read config")
}
func main() {
    _, err := ReadConfig()
    if err != nil {
            fmt.Println(err)
            os.Exit(1)
    }
}
上のReadAll()で問題が発生した場合、main()関数は次の結果を出力します.
could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory
このメッセージでは、どこでエラーが発生したかを一目で見ることができます.

注意事項


エラーは一度だけ処理しましょう。


書き込み()関数はlogです.Println()レコードerrを呼び出し、errを返します.
この場合、write関数を呼び出す関数は、errを再受信して処理する.このプロシージャをmain()関数に遡ると、エラー・ログが非常に乱雑になり、不要な操作が繰り返されます.
func Write(w io.Writer, buf []byte) error {
        _, err := w.Write(buf)
        if err != nil {
                // annotated error goes to log file
                log.Println("unable to write:", err)
 
                // unannotated error returned to caller
                return err
        }
        return nil
}

n/a.結論


opaque errorでerrorを処理します.
中間でerrorをチェックする必要がある場合、動作実施者としてerrorの値ではなくerrorをチェックする.
errors.Wrapでerrorにコンテキスト情報を追加します.(エラーを確認する必要がある場合はerrors.Causeに戻ります.)
errorも共通APIの一部です.この点を覚えておき,依存性をできるだけ減らす方向にコードを記述する.

Reference

  • https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
  • http://cloudrain21.com/golang-graceful-error-handling