Интерфейс error
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 52 из 107.
В Go обработка errors выполняется через встроенный interface с именем error. Этот interface необычайно прост. Он требует всего один метод:
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", если initial deposit равен нулю или отрицателен. В противном случае создай и верни счёт.Deposit(amount float64) error— добавляет деньги к balance. Возвращай ошибку с сообщением"deposit amount must be positive", если amount равен нулю или отрицателен.Withdraw(amount float64) error— снимает деньги с balance. Возвращай ошибку с сообщением"withdrawal amount must be positive", если amount равен нулю или отрицателен, или"insufficient funds", если balance меньше суммы снятия.Balance() float64— возвращает текущее значение balance
main.go: Считай данные счёта и суммы операций из входных данных. Попробуй создать счёт и выполнить операции. После каждой операции выведи либо сообщение об успешном выполнении, либо сообщение об ошибке.
Будут предоставлены следующие входные данные:
- Line 1: имя владельца
- Line 2: сумма initial deposit
- Line 3: сумма Deposit
- Line 4: сумма снятия
Для каждой операции выведи результат:
- После создания счёта:
Account created for [Owner] with balance: $[balance]илиError: [error message] - После Deposit:
Deposited successfully. New balance: $[balance]илиError: [error message] - После снятия:
Withdrew successfully. New balance: $[balance]илиError: [error message]
Форматируй все значения balance с двумя десятичными знаками. Если создание счёта завершится ошибкой, пропусти остальные операции.
Например, если заданы 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Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать имя владельца
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Read initial deposit
initialDepositStr, _ := reader.ReadString('\n')
initialDeposit, _ := strconv.ParseFloat(strings.TrimSpace(initialDepositStr), 64)
// Read deposit amount
depositStr, _ := reader.ReadString('\n')
depositAmount, _ := strconv.ParseFloat(strings.TrimSpace(depositStr), 64)
// Read withdrawal amount
withdrawStr, _ := reader.ReadString('\n')
withdrawAmount, _ := strconv.ParseFloat(strings.TrimSpace(withdrawStr), 64)
// TODO: Создать новый банковский счёт с помощью NewBankAccount
// Если возникает ошибка, вывести "Error: [error message]" и вернуться
// Если успешно, вывести "Account created for [Owner] with balance: $[balance]"
// TODO: Попытаться внести сумму депозита
// Если возникает ошибка, вывести "Error: [error message]"
// If successful, print "Deposited successfully. New balance: $[balance]"
// TODO: Попытаться снять сумму снятия
// Если возникает ошибка, вывести "Error: [error message]"
// If successful, print "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Потренируйтесь самостоятельно: Онлайн-компилятор Go