Как обработать несколько ошибок в го? - PullRequest
0 голосов
/ 22 марта 2019

Какой самый идиоматичный способ обработки нескольких ошибок в go?

Стоит ли пытаться обернуть ошибку и вернуть оба?

if err := foo(bar, obj); err != nil {
    // how to do I avoid losing this error?
    err := l.fixup(obj)
    if err != nil {
        //  but this error is the not the one from foo?
    }
    return err
}
return l.fixup(obj)

Ответы [ 4 ]

1 голос
/ 23 марта 2019

Вы можете добавить контекст к исходной ошибке, используя функцию Wrap из этого замечательного пакета от Дейва Чейни. https://github.com/pkg/errors

errors.Wrap функция возвращает новую ошибку, которая добавляет контекст к исходной ошибке.

func Wrap(cause error, message string) error

в вашем случае это будет:

if cause := foo(bar, obj); cause != nil {
    err := l.fixup(obj)
    if err != nil {
        return errors.Wrap(cause, err.Error())
    }
    return cause
}
return l.fixup(obj)
0 голосов
/ 22 марта 2019

Если вы должны объединить ошибки и вернуть их обратно, все зависит от того, что означает ваша ошибка и о какой вы хотите сообщить вызывающей стороне.Обычно, когда возникновение ошибки не должно останавливать путь и следует вызов, такой как foo здесь, затем fixup, вы регистрируете первую ошибку и возвращаете вторую, поскольку она, вероятно, наиболее важна длячто делает ваша функция.

Существуют также пакеты для переноса ошибок, так что вы можете создать ошибку из нескольких ошибок.

Существует стандартный пакет с fmt.Errorf, в котором можно собрать несколько ошибок.

Существует также https://github.com/hashicorp/go-multierror, который позволяет сохранить несколько ошибок в ошибке.

В вашем случае, если вы хотите, чтобы всплыли оба сообщения об ошибках, я бы сделал что-то вроде этого:

err := foo(bar, obj)

if fixupErr := l.fixup(obj); fixupErr != nil {
    if err != nil {
        return fmt.Errorf("foo err: %s\nfixup err: %s\n", err, fixupErr)
    }
    return fixupErr
}
return err
0 голосов
/ 22 марта 2019

Вы звоните код l.fixup(obj), несмотря ни на что.Если foo(bar, obj) возвращает ошибку, выполняется некоторая обработка и вызывается l.fixup(obj) - в противном случае вызывается только l.fixup(obj).Следовательно, ваш код может быть перестроен следующим образом:

// err will only be valid within the if-then-else-construct
if err := foo(bar, obj); err != nil {
    // handle error from foo(bar,obj)
    // you can even return it, if you wanted to
    // For the sake of this example, we simply log it
    log.Println("Executing foo: %s", err)
}
return l.fixup(obj)

Более того, вы можете использовать тот факт, что error - это интерфейс для вашего преимущества, если вы хотите различить ошибку, потенциально возвращаемую fooили l.fixup.Вы можете сделать это, создав типизированную ошибку для одного (или обоих) и оценить тип ошибки, используя так называемый переключатель типа.

package main

import (
    "errors"
    "fmt"
)

// FooError is the error to be returned by foo
type FooError struct {
    Bar string
}

// Error implements the interface
func (f FooError) Error() string {
    return fmt.Sprintf("%s: interface is nil", f.Bar)
}

// dummy foo func
func foo(bar string, in interface{}) error {
    if in == nil {
        return FooError{Bar: bar}
    }
    return nil
}

// dummy fixup func
func fixup(in interface{}) error {
    if in == nil {
        return errors.New("Interface is nil")
    }
    return nil
}

// a wrapper, containing a variation of above code
func wrap(bar string) error {
    if err := foo(bar, nil); err != nil {
        // handle error from foo(bar,obj)
        // you can even return it, if you wanted to
        return err
    }
    return fixup(nil)
}

func main() {
    err := wrap("test")

    // The type switch itself
    switch err.(type) {
    case FooError:
        // We have a FooError, so we can deal with it accordingly
        fmt.Println("Foo Error:",err)
    default:
        // Every other error is handled by the default block
        fmt.Println("Std Error:",err)
    }
}

Однако это не совсем верно,Если вызывается foo и возвращение ошибки предотвращает выполнение чего-либо еще в вашей логике , а не , вместо этого может быть допустимо паниковать.

0 голосов
/ 22 марта 2019

Метод fixup вызывается в обоих путях кода в вопросе. Упростите код, вызвав fixup вне оператора if.

Если вы хотите, чтобы ошибка из foo имела приоритет над ошибкой из fixup, сделайте именно это.

err1 := foo(bar, obj)
err2 := l.fixup(obj)
if err1 != nil {
   return err1
} 
return err2
...