Методы обработки ошибок 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)