Подтвердить что ты не робот

Избегайте проверки, является ли ошибка повторением nil?

В настоящее время я учился, и некоторые из моих кодов выглядят так:

a, err := doA()
if err != nil {
  return nil, err
}
b, err := doB(a)
if err != nil {
  return nil, err
}
c, err := doC(b)
if err != nil {
  return nil, err
}
... and so on ...

Это выглядит неправильно для меня, потому что проверка ошибок занимает большую часть строк. Есть ли лучший способ обработки ошибок? Могу ли я избежать этого с некоторым рефакторингом?

ОБНОВЛЕНИЕ: Спасибо за все ответы. Обратите внимание, что в моем примере doB зависит от a, doC зависит от b и так далее. Поэтому большинство предлагаемых рефакторингов в этом случае не работают. Любое другое предложение?

4b9b3361

Ответ 1

Это распространенная жалоба, и на нее есть несколько ответов.

Вот несколько общих:

1 - Это не так плохо

Это очень распространенная реакция на эти жалобы. Тот факт, что у вас есть несколько дополнительных строк кода в коде, на самом деле не так уж плох. Это всего лишь немного дешевого набора текста и очень легко обрабатывается, когда на стороне чтения.

2 - Это действительно хорошая вещь

Это основано на том, что типизация и чтение этих дополнительных строк - очень хорошее напоминание о том, что на самом деле ваша логика может сбежать в этот момент, и вам нужно отменить любое управление ресурсами, которое вы создали в строках предшествующий этому. Это обычно воспитывается по сравнению с исключениями, которые могут нарушать поток логики неявным образом, заставляя разработчика всегда иметь скрытый путь ошибки. Некоторое время назад я написал более подробную информацию об этом здесь.

3 - Используйте panic/recover

В некоторых конкретных случаях вы можете избежать некоторой части этой работы, используя panic с известным типом, а затем используя recover прямо перед тем, как ваш код пакета выйдет в мир, превратив его в правильную ошибку и возвращая это вместо этого. Этот метод чаще всего используется для разбора рекурсивной логики, такой как (un) маршалисты.

Я лично стараюсь не злоупотреблять этим слишком много, потому что я более точно коррелирую с точками 1 и 2.

4 - Немного реорганизовать код

В некоторых случаях вы можете немного перестроить логику, чтобы избежать повторения.

В качестве тривиального примера:

err := doA()
if err != nil {
    return err
}
err := doB()
if err != nil {
    return err
}
return nil

также может быть организовано как:

err := doA()
if err != nil {
    return err
}
return doB()

5 - Использовать именованные результаты

Некоторые люди используют именованные результаты для выделения переменной err из оператора return. Я бы рекомендовал не делать этого, потому что это экономит очень мало, уменьшает ясность кода и делает логику склонной к тонким проблемам, когда один или несколько результатов определяются перед оператором возврата выписки.

6 - Используйте оператор перед условием if

Как сказал Том Уайльд в комментарии ниже, if в Go принять простой оператор перед условием. Итак, вы можете сделать это:

if err := doA(); err != nil {
    return err
}

Это прекрасная идиома Go и часто используется.

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

Ответ 2

Вы можете использовать именованные возвращаемые параметры, чтобы сократить количество бит

Игровая площадка

func doStuff() (result string, err error) {
    a, err := doA()
    if err != nil {
        return
    }
    b, err := doB(a)
    if err != nil {
        return
    }
    result, err = doC(b)
    if err != nil {
        return
    }
    return
}

После того, как вы программируете в течение нескольких минут, вы поймете, что проверка ошибки для каждой функции заставляет задуматься о том, что она на самом деле означает, если эта функция пойдет не так и как вы должны иметь дело с ней.

Ответ 3

Похоже, вы ошибаетесь, потому что привыкли не обрабатывать ошибки на сайте вызова. Это довольно идиоматично для go, но выглядит как много шаблонов, если вы к этому не привыкли.

Однако он имеет некоторые преимущества.

  • вам нужно подумать о том, как правильно обрабатывать эту ошибку на сайте, где была сгенерирована ошибка.
  • Легко прочитать код, чтобы увидеть каждую точку, в которой код будет прерван и вернуться раньше.

Если вы действительно ошибаетесь, вы можете получить творческий подход для циклов и анонимных функций, но это часто становится сложным и трудным для чтения.

Ответ 4

Если у вас много таких повторных ситуаций, когда у вас есть несколько таких вы можете определить функцию утилиты, как показано ниже:

func validError(errs ...error) error {
    for i, _ := range errs {
        if errs[i] != nil {
            return errs[i]
        }
    }
    return nil
}

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

Пример использования (полная версия в игре):

x, err1 := doSomething(2)
y, err2 := doSomething(3)

if e := validError(err1, err2); e != nil {
    return e
}

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

Ответ 5

Вы можете передать ошибку как аргумент функции

func doA() (A, error) {
...
}
func doB(a A, err error)  (B, error) {
...
} 

c, err := doB(doA())

Я заметил, что некоторые методы в пакете "html/template" делают это, например.

func Must(t *Template, err error) *Template {
    if err != nil {
        panic(err)
    }
    return t
}