Методы обработки ошибок Go [закрыто]

108

Я только начинаю работать с Go. В моем коде этого становится много:

   if err != nil {
      //handle err
   }

или это

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

Есть ли какие-нибудь хорошие идиомы / стратегии / лучшие практики для проверки и обработки ошибок в Go?

ИЗМЕНИТЬ, чтобы уточнить: я не кричу и не предлагаю команде Go придумать что-то получше. Я спрашиваю, правильно ли я делаю или я пропустил какую-то технику, которую придумало сообщество. Спасибо всем.

gmoore
источник
4
Нет, на самом деле нет. Это часто обсуждаемая тема, и она разумная. Также было много предложений по эволюции. Кажется, команда ответила, что это не должно быть проблемой в хорошо написанном коде.
Denys Séguret 06
Обратите внимание, что этот связанный вопрос на самом деле не такой, как этот. Ответы слишком конкретны.
Denys Séguret 06
У этого раздражения также есть объяснение: это затрудняет быстрое написание программы, но также затрудняет создание ошибок путем простого повторного генерирования ошибок.
Denys Séguret 06
Вы можете найти Эндрю Герранда и Брэда Фитцпатрика, которые написали начало HTTP / 2-клиента в Go более или менее похожим образом youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Ответы:

61

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

zzzz
источник
12
«Авторы Go пишут обработку ошибок таким образом». Звучит неплохо.
gmoore 06
«Некоторые наверняка не согласятся» : я не уверен, что кто-то скажет, что это не лучшая практика, доступная сегодня. Некоторые просят синтаксический сахар или другие изменения, но сегодня я не думаю, что какой-либо серьезный программист будет проверять ошибки в противном случае.
Denys Séguret 06
@dystroy: Окей, некоторые говорят « это нормально », другие называют это «ошибки обрабатываются в возвращаемых значениях. Стиль 70-х». и так далее ;-)
zzzz
2
@jnml Обработка ошибок таким образом , это вопрос дизайна языка, который является весьма спорной темой. К счастью, есть десятки языков на выбор.
fuz 06
4
Что меня просто убивает, так это то, что один и тот же шаблон используется для каждого вызова функции. Это делает код довольно шумным в некоторых местах и ​​просто кричит о синтаксическом сахаре, чтобы упростить код без потери какой-либо информации, что, по сути, является определением краткости (что, я считаю, является более важным атрибутом, чем многословие, но это, возможно, спорный точка). Принцип здравый, но синтаксис оставляет желать лучшего ИМХО. Однако жаловаться запрещено, так что я просто выпью свой kool-aid сейчас ;-)
Томас
30

Через шесть месяцев после того, как этот вопрос был задан, Роб Пайк написал в блоге сообщение под названием « Ошибки - это ценности» .

Там он утверждает, что вам не нужно программировать способом, представленным OP, и упоминает несколько мест в стандартной библиотеке, где они используют другой шаблон.

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

...

Используйте язык, чтобы упростить обработку ошибок.

Но помните: что бы вы ни делали, всегда проверяйте свои ошибки!

Это хорошее чтение.

Майкл Дирдеафф
источник
Спасибо! Проверим это.
gmoore 06
Статья потрясающая, она в основном представляет объект, который может находиться в состоянии сбоя, и если это так, он просто игнорирует все, что вы с ним делаете, и остается в состоянии сбоя. Мне кажется, это почти монада.
Waterlink
@Waterlink Ваше утверждение бессмысленно. Все, что имеет состояние, почти монада, если немного прищуриться. Думаю, сравнение его с en.wikipedia.org/wiki/Null_Object_pattern более полезно.
user7610
@ user7610, спасибо за отзыв. Могу только согласиться.
Waterlink
3
Пайк: «Но помните: что бы вы ни делали, всегда проверяйте свои ошибки!» - это так 80-е. Ошибки могут произойти где угодно, перестаньте обременять программистов и ради Пита используйте исключения.
Славомир
22

Я согласен с ответом 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

Вторая форма называется использованием оператора инициализации if .

Итак, что касается лучших практик, насколько мне известно (за исключением использования пакета «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, поэтому вы захотите использовать его в своих собственных проектах или если ваша команда согласится использовать его.

метакьюла
источник
3
То, что вы можете это сделать, не означает, что это хорошая идея. Это похоже на шаблон « Цепочка ответственности» , за исключением того, что его труднее читать (мнение). Я бы сказал, что это не идиоматический Go. Хотя интересно.
Стивен Сорока
2

«Стратегия» обработки ошибок в golang и других языках состоит в том, чтобы постоянно распространять ошибки вверх по стеку вызовов, пока вы не достигнете в стеке вызовов достаточно высокого уровня для обработки этой ошибки. Если вы попытались обработать эту ошибку слишком рано, то, скорее всего, в конечном итоге код повторится. Если вы справитесь с этим слишком поздно, вы что-то сломаете в своем коде. Golang делает этот процесс очень простым, поскольку он очень четко показывает, обрабатываете ли вы ошибку в данном месте или распространяете ее.

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

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

химаншу оджа
источник
2

Боги Go опубликовали «черновой вариант» обработки ошибок в Go 2. Он направлен на изменение идиомы ошибок:

Обзор и дизайн

Им нужна обратная связь от пользователей!

Обратная связь вики

Вкратце это выглядит так:

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

ОБНОВЛЕНИЕ. Эскизный проект получил много критики, поэтому я составил « Требования, которые следует учитывать при обработке ошибок Go 2» с меню возможностей для возможного решения.

Лиам
источник
1

Большинство в отрасли следуют стандартным правилам, упомянутым в документации golang. Обработка ошибок и Go . И это также помогает создавать документы для проекта.

псчилаканти
источник
По сути, это ответ только по ссылке. Я бы посоветовал вам добавить что-то в ответ, чтобы, если ссылка станет недействительной, ваш ответ все равно пригодился бы.
Neo
спасибо за ценный комментарий.
pschilakanti
0

Ниже я расскажу о сокращении обработки ошибок для Go, образец предназначен для получения параметров URL-адреса HTTP:

(Шаблон проектирования взят из 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
    }

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

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

mel3kings
источник
-1

Вы можете очистить свой код обработки ошибок для подобных ошибок (поскольку ошибки - это значения, с которыми вы должны быть осторожны здесь) и написать функцию, которую вы вызываете с переданной ошибкой для обработки ошибки. Тогда вам не придется каждый раз писать «if err! = Nil {}». Опять же, это приведет только к очистке кода, но я не думаю, что это идиоматический способ делать что-то.

Опять же, то, что вы можете , не означает, что вы должны .

20клиг
источник
-1

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)
}
Анлхорд Смитсон
источник
Ик. Усложнять / скрывать логику обработки ошибок, как это, не очень хорошая идея, ИМО. Результирующий код (который требует предварительной обработки исходного кода с помощью goerr) труднее читать / рассуждать, чем идиоматический код Go.
Dave C
-4

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

Поэтому вместо этого я использую функции.

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)
Гон
источник
Такие сообщения должны отправляться, stderrа не stdout, поэтому просто используйте log.Fatal(err)или log.Fatalln("some message:", err). Поскольку почти ничто иное, как mainпринятие такого решения о завершении всей программы (т.е. возвращать ошибки из функций / методов, не прерывать), в редких случаях это то, что вы хотите сделать, это чище и лучше сделать это явно (т.е. if err := someFunc(); err != nil { log.Fatal(err) }), а не с помощью «вспомогательной» функции, которая не понимает, что она делает (имя «Err» нехорошее, оно не указывает на то, что может завершить программу).
Dave C
Узнал обновку! Спасибо, @DaveC
Gon