error
У Go обробка помилок не відбувається через винятки.
Натомість помилки - це звичайні значення типів, які реалізують вбудований інтерфейс error.
Інтерфейс error дуже мінімальний.
Він містить лише один метод Error(), який повертає повідомлення про помилку як рядок тексту (англ. string).
type error interface {
Error() string
}
Щоразу, коли ми визначаємо функцію, під час виконання якої може статися помилка, яку потрібно донести до того, хто викликав функцію, треба включити error до типів, які функція повертає.
Якщо функція має кілька повернених значень, за домовленістю error завжди стоїть останнім.
func DoSomething() (int, error) {
// ...
}
Не обовʼязково щоразу самостійно реалізовувати інтерфейс error.
Щоб створити просту помилку, можна використати функцію errors.New() зі стандартного пакета errors.
Єдине, що потрібно передати, - це повідомлення про помилку як рядок тексту, а errors.New() подбає про створення значення, яке містить наше повідомлення й реалізує інтерфейс error.
Якщо функція повертає помилку, гарна практика - повертати нульове значення для решти значень, які вона повертає:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Не варто припускати, що всі функції повертають нульові значення для решти значень, якщо помилка є. Найкраще вважати, що користуватися жодним з інших повернених значень небезпечно, якщо сталася помилка. Єдині винятки - це випадки, коли документація чітко зазначає, що інші повернені значення мають сенс у разі помилки.
Якщо таку просту помилку потрібно використати в кількох місцях, краще оголосити для неї змінну, а не викликати errors.New безпосередньо в коді.
За домовленістю назва такої змінної має починатися з Err або err (залежно від того, експортована вона чи ні).
Такі змінні для помилок часто називають вартовими помилками.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
Повернімо nil як помилку, щоб показати, що під час виконання функції помилок не було:
func Foo() (int, error) {
return 10, nil
}
Якщо ми викликаємо функцію, яка повертає помилку, зазвичай зберігаємо значення помилки у змінній з назвою err.
Перш ніж використати справжній результат функції, потрібно перевірити, що помилки не було.
Щоб не вкладати «щасливий шлях» нашого коду, випадки з помилками слід обробляти першими.
Ми можемо використати == і !=, щоб порівняти помилку з nil, і знаємо, що сталася помилка, коли err не дорівнює nil.
func processUserFile() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
Найчастіше помилку повертають далі вгору стеком викликів, як показано в прикладі вище. Інший спосіб обробити помилку - записати її в журнал і продовжити з якоюсь іншою операцією. Гарна практика - або повернути помилку, або записати її в журнал, і ніколи не робити й того, й іншого.
Оскільки більшість функцій у Go повертають помилку як одне з повернених значень, ми всюди в коді Go будемо бачити та використовувати конструкцію if err != nil.
Якщо потрібно, щоб помилка містила більше інформації, ніж просто рядок тексту з повідомленням про помилку, можна створити власний тип помилки.
Як уже згадувалося, усе, що реалізує інтерфейс error (тобто має метод Error() string), може бути помилкою в Go.
Зазвичай для створення власного типу помилки використовують структуру.
За домовленістю назви власних типів помилок мають закінчуватися на Error.
Крім того, метод Error() string найкраще визначати з приймачем-вказівником; про причини цього читайте в цьому коментарі на Stack Overflow.
Зауважмо, що тоді потрібно повертати вказівник на власну помилку, інакше вона не вважатиметься error, бо значення без вказівника не надає методу Error() string.
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}
Нарешті настав день, на який ми так довго чекали, і тепер ми - щасливі власники прекрасної ферми в Альпах.
Ми й досі не любимо прокидатися надто рано вранці, щоб погодувати своїх корів.
Оскільки ми - чудові інженери, ми створюємо дозатор корму FEED-M-ALL.
Останнє, що потрібно, аби завершити наш проєкт, - це фрагмент коду, який обчислює кількість корму, яку має отримати кожна корова. Важливо, щоб кожна корова отримала однакову кількість. Потрібно уникати конфліктів. Корови дуже вразливі.
На щастя, нам не потрібно самостійно виводити всі формули для обчислення кількості корму. Ми використовуємо якусь загадкову зовнішню бібліотеку, яку знайшли в інтернеті. За задумом, вона має зробити корів найщасливішими. Бібліотека надає тип, що реалізує такий інтерфейс. На це ми будемо покладатися у коді, який пишемо самі.
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
Працюючи над своїм кодом, ми будемо вдосконалювати обробку помилок, щоб зробити її надійнішою та легшою для налагодження згодом, коли будемо користуватися нею в щоденному житті на фермі.
Перш за все, ми зосереджуємося на написанні коду, потрібного для обчислення кількості корму на одну корову.
Реалізуйте функцію DivideFood, яка приймає як аргументи FodderCalculator і кількість корів (ціле число).
Для цього завдання ми припускаємо, що передана кількість корів завжди більша за нуль.
Функція має повернути кількість корму на одну корову як float64 або помилку, якщо вона сталася.
Щоб виконати обчислення, спершу потрібно отримати загальну кількість корму для всіх корів.
Це робиться викликом методу FodderAmount, якому передають кількість корів.
Крім того, потрібен множник, на який цю кількість треба помножити.
Цей множник ми отримуємо, викликавши метод FatteningFactor.
Маючи ці два значення та кількість корів, ми тепер можемо обчислити кількість корму на одну корову (як float64).
Саме це й має повертати функція DivideFood.
Якщо один із викликаних методів повертає помилку, виконання має зупинитися, і цю помилку слід повернути (як є) з функції DivideFood.
// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>
// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"
Працюючи над першим завданням вище, ми зрозуміли, що зовнішня бібліотека, яку ми використовуємо, не така якісна, як здавалося. Наприклад, вона не вміє належно обробляти некоректні вхідні дані. Ми хочемо обійти це обмеження, додавши перевірку вхідного значення у власному коді.
Напишіть функцію ValidateInputAndDivideFood з тією ж сигнатурою, що й DivideFood вище.
DivideFood і повернути результати цього виклику."invalid number of cows".ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>
ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"
Перевірка кількості корів перед передаванням була вдалим кроком, але нас не зовсім влаштовує неконкретне повідомлення про помилку.
Ми вирішуємо зробити краще й створити власний тип помилки під назвою InvalidCowsError.
Власна помилка має містити кількість корів (int) і власне повідомлення (string), а метод Error має серіалізувати дані в такому форматі:
{number of cows} cows are invalid: {custom message}
Озброївшись власною помилкою, реалізуйте функцію ValidateNumberOfCows, яка приймає кількість корів як ціле число й повертає помилку (або nil).
InvalidCowsError з власним повідомленням "there are no negative cows".InvalidCowsError з власним повідомленням "no cows don't need food".nil, що означає, що перевірка була успішною.err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
Після нелегкої роботи з налаштування цієї функції перевірки ми помічаємо, що вже вечір, і залишаємо робоче місце, щоб помилуватися заходом сонця над горами. Ми відкладаємо додавання нової функції перевірки до нашого коду на інший день.
Зареєструйтеся на Exercism, щоб вивчати й опановувати Go, а також 34 концепції166 вправ та справжнє наставництво від людей, і все це безкоштовно.