まず、慣れ親しんだ try-catch スタイルの拡張バージョンで、明らかに jimt の回答と PeterSO の回答から借りています。
package main
import "fmt"
// Some dummy library functions with different signatures.
// Per idiomatic Go, they return error values if they have a problem.
func f1(in string) (out int, err error) {
return len(in), err
}
func f2(in int) (out int, err error) {
return in + 1, err
}
func f3(in int) (out float64, err error) {
return float64(in) + .5, err
}
func main() {
inval := "one"
// calc3 three is the function you want to call that does a computation
// involving f1, f2, and f3 and returns any error that crops up.
outval, err := calc3(inval)
fmt.Println("inval: ", inval)
fmt.Println("outval:", outval)
fmt.Println("err: ", err)
}
func calc3(in string) (out float64, err error) {
// Ignore the following big comment and the deferred function for a moment,
// skip to the comment on the return statement, the last line of calc3...
defer func() {
// After viewing what the fXp function do, this function can make
// sense. As a deferred function it runs whenever calc3 returns--
// whether a panic has happened or not.
//
// It first calls recover. If no panic has happened, recover returns
// nil and calc3 is allowed to return normally.
//
// Otherwise it does a type assertion (the value.(type) syntax)
// to make sure that x is of type error and to get the actual error
// value.
//
// It does a tricky thing then. The deferred function, being a
// function literal, is a closure. Specifically, it has access to
// calc3's return value "err" and can force calc3 to return an error.
// A line simply saying "err = xErr" would be enough, but we can
// do better by annotating the error (something specific from f1,
// f2, or f3) with the context in which it occurred (calc3).
// It allows calc3 to return then, with this descriptive error.
//
// If x is somehow non-nil and yet not an error value that we are
// expecting, we re-panic with this value, effectively passing it on
// to allow a higer level function to catch it.
if x := recover(); x != nil {
if xErr, ok := x.(error); ok {
err = fmt.Errorf("calc3 error: %v", xErr)
return
}
panic(x)
}
}()
// ... this is the way you want to write your code, without "breaking
// the flow."
return f3p(f2p(f1p(in))), nil
}
// So, notice that we wrote the computation in calc3 not with the original
// fX functions, but with fXp functions. These are wrappers that catch
// any error and panic, removing the error from the function signature.
// Yes, you must write a wrapper for each library function you want to call.
// It's pretty easy though:
func f1p(in string) int {
v, err := f1(in)
if err != nil {
panic(err)
}
return v
}
func f2p(in int) int {
v, err := f2(in)
if err != nil {
panic(err)
}
return v
}
func f3p(in int) float64 {
v, err := f3(in)
if err != nil {
panic(err)
}
return v
}
// Now that you've seen the wrappers that panic rather than returning errors,
// go back and look at the big comment in the deferred function in calc3.
それで、あなたはもっと簡単に頼んだことに抗議するかもしれませんが、これはそうではありません. 全体として引数はありませんが、ライブラリ関数がすべてエラー値を返し、エラー値なしで関数呼び出しを連鎖させたい場合、利用可能な解決策はライブラリ関数をラップすることであり、ラッパーは非常に薄くて簡単に記述できます。他の唯一の難しい部分は遅延関数ですが、これは学習して再利用できるパターンであり、ほんの数行のコードです。
このソリューションは頻繁に使用されるものではないため、あまり宣伝したくありません。ただし、これは有効なパターンであり、適切な使用例がいくつかあります。
jimt が述べたように、エラー処理は大きなテーマです。「Go でエラー処理を行う良い方法は何ですか?」「本全体」の基準に失敗するという問題を除いて、SOにとっては良い質問です。Go でのエラー処理については、1 冊の本を想像できます。
代わりに、エラー値を消去しようとするのではなく、単にエラー値を処理し始めると、しばらくすると、これを行うことの利点を理解し始めるという私の一般的な観察を提供します。ここで使用したようなおもちゃの例で if ステートメントの冗長なはしごのように見えるものは、実際のプログラムで最初に記述したときには、if ステートメントの冗長なはしごのように見えるかもしれません。しかし、実際にこれらのエラーを処理する必要がある場合、コードに戻ると、突然、実際のエラー処理コードで具体化するのを待っているスタブとして表示されます。エラーの原因となったコードがすぐそこにあるため、何をすべきかがわかります。あいまいな低レベルのエラー メッセージがユーザーに表示されないようにし、代わりに意味のあるものを表示することができます。プログラマーとして、デフォルトのことを受け入れるのではなく、正しいことを行うように求められます。
より包括的な回答については、まず、記事Error Handling and Go を参考にしてください。Go-Nutsのメッセージを検索すると、この問題に関する長い議論もそこにあります。標準ライブラリ内の関数は相互にかなり頻繁に呼び出しを行うため (驚き)、標準ライブラリのソース コードにはエラー処理の例が多数含まれています。これらは、エラー値を処理するこのプログラミング スタイルを推進している Go の作成者によってコードが記述されているため、学習するのに優れた例です。