Интерфейс error
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 52 из 107.
В Go ошибки обрабатываются через встроенный интерфейс, называемый error. Этот интерфейс удивительно прост — он требует всего один метод:
type error interface {
Error() string
}Любой тип, реализующий метод Error(), возвращающий строку, автоматически удовлетворяет интерфейсу error. Это работа системы интерфейсов Go — явного объявления не требуется.
Самый распространенный способ создания ошибок — использование пакета errors:
import "errors"
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}Функции в Go обычно возвращают ошибку в качестве последнего возвращаемого значения. Если ошибка не возникла, возвращается nil. Затем вызывающая сторона проверяет, не является ли ошибка отличной от nil, прежде чем продолжить:
result, err := Divide(10, 0)
if err != nil {
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result)Вы также можете использовать fmt.Errorf для создания отформатированных сообщений об ошибках:
func GetUser(id int) (string, error) {
if id <= 0 {
return "", fmt.Errorf("invalid user id: %d", id)
}
return "Alice", nil
}Этот паттерн возврата ошибок вместо генерации исключений является фундаментальным для Go. Это делает обработку ошибок явной и заставляет вас задумываться о том, что происходит, когда что-то идет не так — ключевой аспект написания надежного объектно-ориентированного кода.
Задание
ЛегкоДавайте создадим систему банковских счетов, которая демонстрирует паттерн обработки ошибок в Go. Вы создадите простой счет, который проверяет операции и возвращает информативные ошибки, когда что-то идет не так.
Вы организуете свой код в двух файлах:
account.go: Создайте структуруBankAccountс экспортируемым полемOwner(string) и неэкспортируемым полемbalance(float64). Реализуйте следующее:NewBankAccount(owner string, initialDeposit float64) (*BankAccount, error)— конструктор, который возвращает ошибку с сообщением"initial deposit must be positive", если начальный депозит равен нулю или отрицателен. В противном случае создайте и верните счет.Deposit(amount float64) error— добавляет деньги на баланс. Возвращает ошибку с сообщением"deposit amount must be positive", если сумма равна нулю или отрицательна.Withdraw(amount float64) error— снимает деньги с баланса. Возвращает ошибку с сообщением"withdrawal amount must be positive", если сумма равна нулю или отрицательна, или"insufficient funds", если баланс меньше суммы снятия.Balance() float64— возвращает текущий баланс.
main.go: Считайте данные счета и суммы транзакций из входных данных. Попытайтесь создать счет и выполнить операции. После каждой операции выводите либо сообщение об успехе, либо сообщение об ошибке.
Будут предоставлены следующие входные данные:
- Строка 1: Имя владельца (Owner name)
- Строка 2: Сумма начального депозита
- Строка 3: Сумма депозита
- Строка 4: Сумма снятия
Для каждой операции выведите результат:
- После создания счета:
Account created for [Owner] with balance: $[balance]илиError: [error message] - После депозита:
Deposited successfully. New balance: $[balance]илиError: [error message] - После снятия:
Withdrew successfully. New balance: $[balance]илиError: [error message]
Форматируйте все значения баланса с двумя знаками после запятой. Если создание счета не удалось, пропустите остальные операции.
Например, для входных данных Alice, 100, 50 и 200 ваш вывод должен быть таким:
Account created for Alice with balance: $100.00
Deposited successfully. New balance: $150.00
Error: insufficient fundsА для Bob, -50, 25 и 10 ваш вывод должен быть таким:
Error: initial deposit must be positiveШпаргалка
Go обрабатывает ошибки через встроенный интерфейс error, который требует наличия одного метода:
type error interface {
Error() string
}Создавайте ошибки с помощью пакета errors:
import "errors"
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}Функции возвращают ошибки в качестве последнего возвращаемого значения. Возвращайте nil, если ошибка не возникла:
result, err := Divide(10, 0)
if err != nil {
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result)Используйте fmt.Errorf для форматированных сообщений об ошибках:
func GetUser(id int) (string, error) {
if id <= 0 {
return "", fmt.Errorf("invalid user id: %d", id)
}
return "Alice", nil
}Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Чтение имени владельца
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Чтение начального депозита
initialDepositStr, _ := reader.ReadString('\n')
initialDeposit, _ := strconv.ParseFloat(strings.TrimSpace(initialDepositStr), 64)
// Чтение суммы депозита
depositStr, _ := reader.ReadString('\n')
depositAmount, _ := strconv.ParseFloat(strings.TrimSpace(depositStr), 64)
// Чтение суммы снятия
withdrawStr, _ := reader.ReadString('\n')
withdrawAmount, _ := strconv.ParseFloat(strings.TrimSpace(withdrawStr), 64)
// TODO: Создайте новый банковский счет, используя NewBankAccount
// Если возникла ошибка, выведите "Error: [error message]" и выполните return
// В случае успеха выведите "Account created for [Owner] with balance: $[balance]"
// TODO: Попробуйте внести сумму депозита
// Если возникла ошибка, выведите "Error: [error message]"
// В случае успеха выведите "Deposited successfully. New balance: $[balance]"
// TODO: Попробуйте снять сумму для снятия
// Если возникла ошибка, выведите "Error: [error message]"
// В случае успеха выведите "Withdrew successfully. New balance: $[balance]"
// Не забудьте отформатировать значения баланса с двумя знаками после запятой, используя fmt.Printf("%.2f", balance)
_ = ownerName
_ = initialDeposit
_ = depositAmount
_ = withdrawAmount
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool