Методи поводження з помилками [закрито]


108

Я тільки починаю з Go. Мій код починає мати багато цього:

   if err != nil {
      //handle err
   }

або це

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

Чи є якісні ідіоми / стратегії / найкращі практики для перевірки та обробки помилок у програмі Go?

EDIT, щоб уточнити: я не переймаюсь чи пропоную команді Go придумати щось краще. Я запитую, чи правильно я це роблю, чи пропустив якусь техніку, яку придумала громада. Дякую усім.


4
Ні, насправді немає. Це часто обговорювана тема, і розумна. Також було багато пропозицій щодо еволюції. Відповідь команди здається, що це не повинно бути проблемою в добре написаному коді.
Denys Séguret


Зауважте, що це пов'язане питання насправді не є таким, як це. Відповіді занадто конкретні.
Denys Séguret

Існує також обгрунтування цього роздратування: це ускладнює швидке написання програми, але також ускладнює створення помилок шляхом простого повторного введення помилок.
Denys Séguret

Ви можете знайти Ендрю Герранда та Бреда Фіцпатріка, щоб писати про початок клієнта HTTP / 2 у програмі Go більш-менш схожим способом youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Відповіді:


61

Ваш код ідіоматичний, і на мою думку, це найкраща доступна практика. Деякі з них не погоджуються напевно, але я стверджую, що це стиль, який бачать у всіх стандартних бібліотеках в Голангу . Іншими словами, автори Go пишуть таким чином обробку помилок.


12
"Автори йдуть таким чином, щоб помилки писали." Звучить добре для мене.
gmoore

"Деякі не погоджуються напевно" : Я не впевнений, що хтось скаже, що це не найкраща практика, яка існує сьогодні. Деякі запитують синтаксичний цукор чи інші зміни, але сьогодні я не думаю, що будь-який серйозний кодер перевірить помилки в іншому випадку.
Denys Séguret

@dystroy: Гаразд, одні кажуть " це сукс ", інші називають це "помилками обробляються значення повернення. Стиль 70-х". , і так далі ;-)
zzzz

2
@jnml Поводження з помилками таким чином є питанням дизайну мови, що є дуже суперечливою темою. На щастя, є кілька десятків мов на вибір.
fuz

4
Що мене просто вбиває - це те, що однаковий шаблон використовується для кожного виклику функції. Це робить код в деяких місцях досить галасливим і просто кричить на синтаксичний цукор для спрощення коду, не втрачаючи жодної інформації, що є по суті визначенням стисності (що, на мою думку, є вищим атрибутом, ніж багатослів’я, але це, мабуть, суперечливо бал). Принцип звучання, але синтаксис залишає багато бажаного ІМХО. Однак скарги заборонені, тому я зараз просто вип'ю свою колективну допомогу ;-)
Томас

30

Через шість місяців після цього запитання Роб Пайк написав повідомлення в блозі під назвою " Помилки - це цінності" .

Там він стверджує, що вам не потрібно програмувати таким чином, як представлений ОП, і згадує кілька місць у стандартній бібліотеці, де вони використовують інший малюнок.

Звичайно, загальне твердження, що включає значення помилки, - це перевірити, чи є воно нульовим, але є незліченна кількість інших речей, які можна зробити зі значенням помилки, а застосування деяких інших речей може покращити вашу програму, усуваючи велику частину котлован яка виникає, якщо кожну помилку перевіряється за допомогою оператора rote if.

...

Використовуйте мову, щоб спростити обробку помилок.

Але пам’ятайте: що б ви не робили, завжди перевіряйте свої помилки!

Це добре читати.


Дякую! Перевірте це.
gmoore

Стаття є приголомшливою, вона в основному представляє об'єкт, який може опинитися в невдалому стані, і якщо він є, він просто ігнорує все, що ви робите з ним, і залишається в невдалому стані. Мені це звучить як майже монада.
Waterlink

@Waterlink Ваше твердження безглуздо. Все, що має стан, майже монардоване, якщо трохи примружитись до нього. Порівнювати це з en.wikipedia.org/wiki/Null_Object_pattern , я думаю, корисніше.
користувач7610

@ user7610, Дякую за відгук. Я можу лише погодитися.
Уотерлінк

2
Пайк: "Але пам’ятайте: що б ви не робили, завжди перевіряйте свої помилки!" - це так 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кількість записаних байтів (іноді непотрібна інформація) та an error:

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

Друга форма називається використанням оператора if ініціалізації .

Що стосується найкращих практик, наскільки я знаю (за винятком використання пакету "помилки" для створення нових помилок, коли вони вам потрібні) ви покрили майже все, що потрібно знати про помилки 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:
           ...
       }
    }
}

Зауважте, що ефективність роботи невелика, оскільки вона використовує рефлексію.

Крім того, це не ідіоматичний код коду, тому ви хочете використовувати його у власних проектах або якщо ваша команда погодиться використовувати його.


3
Тільки тому, що ви можете це зробити, не означає, що це гарна ідея. Це виглядає як шаблон ланцюжка відповідальності , за винятком, можливо, важче читати (думка). Я б припустив, що це не "ідіоматичний". Цікаво, однак.
Стівен Сорока

2

"Стратегія" для обробки помилок у голанг та іншими мовами полягає в постійному пропонуванні помилок вверх стека викликів, поки ви не будете досить високо в стеці викликів, щоб обробляти цю помилку. Якщо ви спробували поправити цю помилку занадто рано, то, швидше за все, ви повторите код. Якщо ви впораєтесь із цим занадто пізно, то ви зламаєте щось у своєму коді. Golang робить цей процес надзвичайно простим, оскільки він дає зрозуміти, чи ви обробляєте помилку в певному місці чи поширюєте її.

Якщо ви будете ігнорувати помилку, простий _ виявить цей факт дуже чітко. Якщо ви обробляєте це, тоді точно зрозуміти, у якому випадку помилки ви обробляєте, оскільки ви будете перевіряти її в операторі if.

Як і люди, сказані вище, помилка - насправді просто нормальне значення. Це трактує це як таке.


2

Go Gods опублікували "проект дизайну" для обробки помилок у програмі Go 2. Він спрямований на зміну ідіоми помилок:

Огляд та дизайн

Вони хочуть відгуків користувачів!

Вікі для зворотного зв'язку

Якщо коротко, це виглядає так:

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

ОНОВЛЕННЯ: Проект проекту зазнав багато критики, тому я склав вимоги до розгляду помилок Go 2 з меню можливостей для можливого рішення.


1

Більшість у промисловості дотримуються стандартних правил, згаданих у документації golang Поводження з помилками та Go . І це також допомагає генерації doc для проекту.


Це по суті лише відповідь на посилання. Я б запропонував вам додати у відповідь деякий вміст, так що якщо посилання стане недійсним, ваша відповідь все-таки буде корисною.
Нео

дякую за цінний коментар.
pschilakanti

0

Нижче наводиться зниження рівня обробки помилок для 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
    }

Це не срібна куля, недолік - якщо у вас було кілька помилок, ви можете отримати лише останню помилку.

Але в цьому випадку це відносно повторюваний та низький ризик, тому я можу просто отримати останню можливу помилку.


-1

ви можете очистити код обробки помилок від подібних помилок (оскільки помилки - це значення, ви повинні бути обережними тут) і написати функцію, до якої ви викликаєте помилку, передану для усунення помилки. Вам не доведеться щоразу писати "if err! = Nil {}". Знову ж таки, це призведе лише до очищення коду, але я не думаю, що це ідіоматичний спосіб робити.

Знову ж таки, тому що ви не можете означати, що слід .


-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)
}

Ick. Ускладнення / приховування помилок обробки помилок, як це, не є гарною ідеєю ІМО. Отриманий код (який потребує попередньої обробки джерела goerr) важче читати / міркувати, ніж ідіоматичний код Go.
Дейв 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) }), а не через функцію "помічник", яка незрозуміло, що він робить (ім'я "Помилка" не добре, воно не вказує на те, що вона може припинити програму).
Дейв C

Дізналися нового! Дякую @DaveC
Gon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.