Goエラー処理テクニック[終了]


108

Goを始めたばかりです。私のコードにはこれがたくさんあり始めています:

   if err != nil {
      //handle err
   }

またはこれ

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

Goでエラーをチェックして処理するための優れたイディオム/戦略/ベストプラクティスはありますか?

明確にするために編集します。私は、Goチームがより良いものを思いつくのではない、と言っているわけではありません。私はそれを正しく行っているのか、それともコミュニティが考案したいくつかのテクニックを見逃していないのかと尋ねています。皆さんありがとう。


4
いいえ、実際にはありません。これはよく議論されるトピックであり、賢明なトピックです。多くの進化の提案もありました。チームの答えは、よく書かれたコードでは問題にならないはずだということです。
DenysSéguret2013


この関連する質問は、これと実際には同じではないことに注意してください。答えは具体的すぎる。
DenysSéguret2013

この煩わしさには根拠もあります。プログラムを高速に作成するのが難しくなりますが、エラーを再スローするだけでバグを作成するのも難しくなります。
DenysSéguret2013

Andrew GerrandとBrad FitzpatrickがGoでHTTP / 2クライアントの始まりをほぼ同じように書いていることがわかります youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

回答:


61

あなたのコードは慣用的であり、私の意見では、それは利用可能なベストプラクティスです。確かに反対する人もいますが、これはGolangの標準ライブラリ全体で見られるスタイルだと私は主張します。言い換えると、Goの作成者はこの方法でエラー処理を記述しています。


12
「Goの作者はこの方法でエラー処理を記述しています。」私にはいいですね。
gmoore 2013

「確かにそう思わない人もいます:今日のベストプラクティスではないと誰かが言うかどうかはわかりません。構文糖やその他の変更を求める人もいますが、今日では、深刻なコーダーがそれ以外の方法でエラーをチェックすることはないと思います。
DenysSéguret2013

@dystroy:わかりました。「it sux」言う人もいれば、「エラーは戻り値で処理される70年代スタイル」と呼ぶ人もいます。、など;-)
zzzz

2
@jnmlこの方法でエラーを処理することは、非常に物議を醸しているトピックである言語設計の問題です。幸いにも、選択できる言語は数十あります。
fuz

4
私を殺すのは、すべての単一の関数呼び出しに同じパターンがどのように使用されるかです。これは一部の場所でコードをかなりうるさくし、情報を失うことなくコードを単純化するために構文糖を求めています。これは本質的に簡潔さの定義です(冗長性よりも優れた属性だと私は思いますが、それは間違いなく論争です)ポイント)。原則は正しいですが、構文は私見で望まれる多くを残します。しかし不平を言うことは禁じられているので、私は今私の救急処置液を飲むだけにします;-)
Thomas

30

この質問が寄せられてから6か月後、Rob PikeがErrors are Valuesというタイトルのブログ投稿を書きました。

そこで彼は、OPによって提示された方法でプログラムする必要はないと主張し、標準ライブラリのいくつかの場所で、それらが異なるパターンを使用することについて言及しています。

もちろん、エラー値に関する一般的なステートメントは、それがnilであるかどうかをテストすることですが、エラー値を使用して他にできることは無数にあり、それらの他のいくつかを適用すると、プログラムがよりよくなり、ボイラープレートの多くが排除されますこれは、すべてのエラーがrote ifステートメントでチェックされた場合に発生します。

...

言語を使用してエラー処理を簡素化します。

しかし、覚えておいてください:何をするにせよ、常にエラーをチェックしてください!

それは良い読み物です。


ありがとう!それをチェックします。
gmoore 2015年

記事は素晴らしいです。基本的に、失敗した状態になる可能性のあるオブジェクトを紹介します。そうであれば、それを使用して行ったすべてのことを無視して、失敗した状態を維持します。私にはほとんどモナドのように聞こえます。
Waterlink 2015年

@Waterlinkステートメントは意味がありません。少し目を細めれば、状態のあるものはすべてほぼモナドです。それをen.wikipedia.org/wiki/Null_Object_patternと比較する方が便利だと思います。
user7610 2016年

@ user7610、フィードバックをありがとう。私だけが同意することができます。
ウォーターリンク2016年

2
パイク:「覚えておいてください:何をするにせよ、常にエラーをチェックしてください!」-これは80年代です。エラーはどこでも発生する可能性があり、プログラマーの負担をやめ、ピートのために例外を採用します。
Slawomir 2016年

22

どちらも慣用的なコードであるというjnmlの回答に同意し、以下を追加します。

あなたの最初の例:

if err != nil {
      //handle err
}

複数の戻り値を処理する場合、より慣用的です。例えば:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

2番目の例は、err値のみを扱う場合の優れた省略表現です。これは、関数がのみを返すerror場合、または以外の戻り値を意図的に無視した場合に適用されますerror。例として、これは、書き込まれたバイト数(場合によっては不要な情報)とを返す関数ReaderWriter関数で使用されることがありintますerror

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

2番目の形式は、if初期化ステートメントの使用と呼ばれます

したがって、ベストプラクティスに関しては、私が知る限り(「errors」パッケージを使用して、必要なときに新しいエラーを作成することを除いて)、Goでエラーを知るために必要なほとんどすべてのことを説明しました。

編集:あなたが本当に例外なしで生きることできないとわかったら、あなたはそれらを真似ることができますdeferpanic&とrecover


4

合理化されたエラー処理とGo関数のキューを介したパイプ処理のためのライブラリを作成しました。

ここで見つけることができます:https : //github.com/go-on/queue

コンパクトで冗長な構文のバリエーションがあります。次に短い構文の例を示します。

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

リフレクションを利用するため、パフォーマンスのオーバーヘッドが少しあることに注意してください。

また、これは慣用的なgoコードではないため、独自のプロジェクトで使用するか、チームで使用することに同意した場合に使用します。


3
あなたこれ行うことができるからといって、それが良い考えだとは限りません。これは、おそらく読みにくい(意見)以外は、責任連鎖パターンに似ています。「慣用的な囲碁」ではないことをお勧めします。でも面白い。
Steven Soroka 2014年

2

golangや他の言語でエラーを処理するための「戦略」は、エラーを処理するのに十分なコールスタックになるまで、エラーをコールスタックに伝播し続けることです。そのエラーを早めに処理しようとした場合、コードを繰り返すことになります。処理が遅すぎると、コードの一部が壊れてしまいます。Golangを使用すると、特定の場所でエラーを処理しているのか、エラーを伝達しているのかが非常に明確になるので、このプロセスが非常に簡単になります。

エラーを無視する場合は、単純な_でこの事実が非常にはっきりとわかります。それを処理している場合は、ifステートメントで確認するため、処理しているエラーの正確なケースは明確です。

上記の人々が言っ​​たように、エラーは実際には単なる通常の値です。これはそのように扱います。


2

Goの神々は、Go 2でのエラー処理のための「ドラフトデザイン」を公開しています。これは、エラーイディオムを変更することを目的としています。

概要設計

彼らはユーザーからのフィードバックを求めています!

フィードバックウィキ

簡単に言うと、次のようになります。

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

更新:ドラフト設計は多くの批判を受けたため、最終的な解決策の可能性のメニューを使用して、Go 2エラー処理を検討するための要件をドラフトしました。


1

ほとんどの業界では、golangドキュメントのエラー処理とGoで言及されている標準ルールに従っています。また、プロジェクトのドキュメント生成にも役立ちます。


これは基本的にリンクのみの回答です。回答にコンテンツを追加して、リンクが無効になった場合でも回答が役立つようにすることをお勧めします。
Neo

貴重なコメントありがとうございます。
pschilakanti 2017

0

以下は、Goのエラー処理を削減するための私の見解です。サンプルは、HTTP URLパラメータを取得するためのものです。

https://blog.golang.org/errors-are-valuesから派生したデザインパターン)

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

複数の可能なパラメータに対してそれを呼び出すと、以下のようになります:

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

これは特効薬ではありません。欠点は、複数のエラーが発生した場合、最後のエラーしか取得できないことです。

しかし、この場合は比較的反復的でリスクが低いため、考えられる最後のエラーを取得できます。


-1

あなたはすることができますなエラー処理コードをあなたのエラーをクリーンアップ(エラーは値ですので、あなたはここに注意する必要があり)、あなたがエラーを処理するために、渡されたエラーで呼び出す関数を記述します。その場合、毎回「if err!= nil {}」と書く必要はありません。繰り返しますが、これはコードをクリーンアップするだけですが、それが物事を行う慣用的な方法だとは思いません。

ここでも、ちょうどあなたがいるのでできますが意味するものではありませんはず


-1

goerrは関数でエラーを処理できます

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}

いや。このようなエラー処理ロジックの複雑化/非表示は、IMOには適していません。結果のコード(goerrによるソースの前処理が必要)は、慣用的なGoコードよりも読み取り/理由がわかりません。
Dave C

-4

エラーを正確に制御する必要がある場合、これは解決策ではないかもしれませんが、私にとって、ほとんどの場合、エラーはショーストッパーです。

そのため、代わりに関数を使用します。

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)

このようなメッセージはではstderrなくに送信する必要があるためstdoutlog.Fatal(err)またはを使用してくださいlog.Fatalln("some message:", err)mainまれに、プログラム全体を終了する(つまり、関数/メソッドからエラーを返す、中止しない)ように決定する以外にほとんど何もないので、これを実行したいのですが、明示的に実行するほうがよりクリーンで優れています(つまり、if err := someFunc(); err != nil { log.Fatal(err) })何をしているのかが不明確な「ヘルパー」関数を使用するのではなく(「Err」という名前は適切ではありません。プログラムを終了する可能性を示しません)。
Dave C

新しいことを学んだ!ありがとう@DaveC
Gon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.