Привет! В Go только и разговоров про обработку ошибок. Лучшее чтиво по этой теме - это Google StyleGuide.
Сегодня в посте хочу подсветить проблемы обработки ошибок, что я вижу:
-
Ошибка не отменяет значение. Можно взять значение без проверки ошибки. В Java в случае исключения мы теряем доступ к возвращаемому значению. В функциональных ЯП мы тут защищаемся типами. В Go надо делать правила или добавлять линтер. Иначе можно использовать значение без проверки err != nil.
got, gotErr := do()
fmt.Println("Никто не запретит использовать got, пусть у меня и ошибка", got)
// Кроме linter =]
-
Должен ли быть сценарий ошибкой? Если у нас в БД нет данных, должен это быть nil ответ, либо лучше сделать ошибку ErrNoRows? Для меня это все еще вопрос без однозначного ответа. Я скорее за второй вариант, но на 1 тоже могу согласиться.
func fetchCurrentUser(ctx context.Context) (*User, error) {
return nil, nil
}
// or
func fetchCurrentUser(ctx context.Context) (*User, error) {
return nil, myerrors.ErrNotFound
}
-
Использовать error или comma-ok? Тоже вопрос, на который я не знаю ответа. Для себя я решил, что использую comma-ok, если функция с поведением а-ля map. Но прав ли я?
func GetDetail(key string) (string, error) {
return nil, myerrors.ErrNotFound
}
// or
func GetDetail(key string) (string, bool) {
return nil, false
}
-
Что делать, если обработка ошибки это и есть позитивный сценарий? В Go принято логику happy path держать на нулевом уровне вложенности. Вложенность - это ошибка, отклонение от нормы. Что делать, если метод не про норму? Допустим мне надо вызвать метод, а в случае ошибки вызвать другой fallback и так несколько раз. У меня получится вложенность на каждый fallback. Читать такое будет сложнее (как минимум непривычнее). Для себя я это решаю комментом в начале метода, где подсвечиваю, что обработка ошибок в основном сценарии.
gotErr := do()
if gotErr != nil {
gotFallbackErr1 := doFallback1()
if gotFallbackErr1 != nil {
gotFallbackErr2 := doFallback2()
if gotFallbackErr2 != nil {
return gotFallbackErr2
}
}
}
// or
gotErr := do()
if gotErr == nil {
return nil
}
gotFallbackErr1 := doFallback1()
if gotFallbackErr1 == nil {
return nil
}
gotFallbackErr2 := doFallback2()
if gotFallbackErr2 != nil {
return gotFallbackErr2
}
Как видите, вопросы еще остались, но я с ними разбираюсь
😂