Методы обработки ошибок Go
Я только начинаю с Go. Мой код начинает иметь много этого:
if err != nil {
//handle err
}
или
if err := rows.Scan(&some_column); err != nil {
//handle err
}
Есть ли хорошие идиомы/стратегии/лучшие практики для проверки и обработки ошибок в Go?
РЕДАКТИРОВАТЬ, чтобы прояснить: я не дергаюсь или не предлагаю, чтобы команда Go придумала что-то лучшее. Я спрашиваю, правильно ли я делаю это, или я пропустил какую-то технику, с которой столкнулось сообщество. Спасибо всем.
Ответы
Ответ 1
Ваш код идиоматичен, и, на мой взгляд, это лучшая практика. Некоторые наверняка не согласятся, но я бы сказал, что это стиль, который можно увидеть во всех стандартных библиотеках в Голанге. Другими словами, авторы Go записывают обработку ошибок таким образом.
Ответ 2
Через шесть месяцев после того, как этот вопрос был задан, Роб Пайк написал сообщение в блоге под названием Ошибки - значения.
В нем он утверждает, что вам не нужно программировать так, как это описано в OP, и упоминает несколько мест в стандартной библиотеке, где они используют другой шаблон.
Конечно, общий оператор, включающий значение ошибки, заключается в проверке, является ли оно нулевым, но есть множество других вещей, которые можно сделать с значением ошибки, а применение некоторых из этих других вещей может улучшить вашу программу, шаблона, который возникает, если каждая ошибка проверяется с помощью инструкции rote if.
...
Используйте этот язык, чтобы упростить обработку ошибок.
Но помните: что бы вы ни делали, всегда проверяйте свои ошибки!
Хорошо прочитано.
Ответ 3
Я согласен с jnml ответом, что они оба являются идиоматическим кодом и добавляют следующее:
Ваш первый пример:
if err != nil {
//handle err
}
более идиоматичен при работе с более чем одним возвращаемым значением. например:
val, err := someFunc()
if err != nil {
//handle err
}
//do stuff with val
Второй пример - это хорошая стенография, когда речь идет только о значении err
. Это применяется, если функция возвращает только error
, или если вы намеренно игнорируете возвращаемые значения, отличные от error
. В качестве примера это иногда используется с функциями Reader
и Writer
, которые возвращают int
количество записанных байтов (иногда ненужную информацию) и error
:
if _, err := f.Read(file); err != nil {
//handle err
}
//do stuff with f
Вторая форма называется если инструкция инициализации.
Итак, что касается лучших практик, насколько я знаю (за исключением использования "errors" для создания новых ошибок, когда вы в них нуждаетесь), вы почти все, что вам нужно, чтобы знать ошибки в Go!
EDIT: Если вы обнаружите, что действительно не можете жить без исключений, вы можете имитировать их defer
, panic
и recover
.
Ответ 4
Я создал библиотеку для упрощенной обработки ошибок и прохождения через очередь функций Go.
Вы можете найти его здесь: https://github.com/go-on/queue
Он имеет компактный и многословный синтаксический вариант.
Ниже приведен пример короткого синтаксиса:
import "github.com/go-on/queue/q"
func SaveUser(w http.ResponseWriter, rq *http.Request) {
u := &User{}
err := q.Q(
ioutil.ReadAll, rq.Body, // read json (returns json and error)
)(
// q.V pipes the json from the previous function call
json.Unmarshal, q.V, u, // unmarshal json from above (returns error)
)(
u.Validate, // validate the user (returns error)
)(
u.Save, // save the user (returns error)
)(
ok, w, // send the "ok" message (returns no error)
).Run()
if err != nil {
switch err {
case *json.SyntaxError:
...
}
}
}
Помните, что накладные расходы незначительны, поскольку
использует отражение.
Также это не идиоматический код go, поэтому вы захотите использовать его в своих проектах или если ваша команда согласна
используя его.
Ответ 5
"Стратегия" для обработки ошибок в golang и на других языках заключается в постоянном распространении ошибок в стеке вызовов до тех пор, пока вы не достаточно высоки в стеке вызовов, чтобы справиться с этой ошибкой. Если вы попытались обработать эту ошибку слишком рано, значит, вы, скорее всего, повторите код. Если вы справитесь с этим слишком поздно, вы сломаете что-то в своем коде. Golang делает этот процесс очень легким, так как он делает его максимально понятным, обрабатываете ли вы ошибку в определенном месте или распространяете ее.
Если вы проигнорируете ошибку, простой _ будет ясно раскрывать этот факт. Если вы обрабатываете его, то какой именно случай ошибки вы обрабатываете, ясен, поскольку вы будете проверять его в выражении if.
Как и люди, упомянутые выше, ошибка на самом деле является просто нормальным значением. Это рассматривает ее как таковую.
Ответ 6
Боги Go опубликовали "черновой вариант" для обработки ошибок в Go 2. Он призван изменить идиому ошибок:
Обзор и дизайн
Они хотят отзывов от пользователей!
Обратная связь вики
Вкратце это выглядит так:
func f() error {
handle err { fmt.Println(err); return err }
check mayFail()
check canFail()
}
ОБНОВЛЕНИЕ: черновик проекта подвергся большой критике, поэтому я разработал " Требования, которые следует учитывать при обработке ошибок Go 2", с меню возможностей для возможного решения.
Ответ 7
Большинство в промышленности соблюдают стандартные правила, упомянутые в документации golang Обработка ошибок и переход.
И это также помогает генерировать doc для проекта.
Ответ 8
Ниже я расскажу, как уменьшить обработку ошибок для Go, пример для получения параметров HTTP URL:
(Шаблон дизайна, полученный из https://blog.golang.org/errors-are-values)
type HTTPAdapter struct {
Error *common.AppError
}
func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
requestUUID := uuid.Parse(mux.Vars(r)[param])
if requestUUID == nil {
adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
possibleError, http.StatusBadRequest)
}
return requestUUID
}
вызов его для нескольких возможных параметров будет таким:
adapter := &httphelper.HTTPAdapter{}
viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
if adapter.Error != nil {
return nil, adapter.Error
}
Это не серебряная пуля, недостатком является то, что если у вас было несколько ошибок, вы можете получить только последнюю ошибку.
Но в этом случае это относительно повторяющийся и низкий риск, поэтому я могу просто получить последнюю возможную ошибку.
Ответ 9
Вы можете очистить код обработки ошибок для подобных ошибок (поскольку ошибки - это значения, которые вы должны здесь быть осторожны) и написать функцию, которую вы вызываете с переданной ошибкой для обработки ошибки. Вам не придется писать "if err!= Nil {}" каждый раз. Опять же, это приведет только к очистке кода, но я не думаю, что это идиоматический способ делать вещи.
Опять же, только потому, что вы можете не означает, что вы должны.
Ответ 10
goerr позволяет обрабатывать ошибки с функциями
package main
import "github.com/goerr/goerr"
import "fmt"
func ok(err error) {
if err != nil {
goerr.Return(err)
// returns the error from do_somethingN() to main()
// sequence() is terminated
}
}
func sequence() error {
ok(do_something1())
ok(do_something2())
ok(do_something3())
return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
fmt.Println("DOING 3")
return nil
}
func main() {
err_do_something := goerr.OR1(sequence)
// handle errors
fmt.Println(err_do_something)
}
Ответ 11
Если вам нужен точный контроль ошибок, это может быть не решение, но для меня большая часть времени любая ошибка - это шоу-стоп.
Итак, вместо этого я использую функции.
func Err(err error) {
if err!=nil {
fmt.Println("Oops", err)
os.Exit(1)
}
}
fi, err := os.Open("mmm.txt")
Err(err)