Избегайте проверки, является ли ошибка повторением 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 и так далее. Поэтому большинство предлагаемых рефакторингов в этом случае не работают. Любое другое предложение?
Ответы
Ответ 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
}