В языке Go нет исключений в том виде, к которому мы привыкли в С/С++, Java, Python и т.п. языках. Здесь вообще не принято доводить ситуацию до настолько критической, когда происходит экстренное «выбрасывание» куда-то далеко наверх по дереву вызовов. Путь Go — проверка входящих данных и различных условий. Если можем работать — работаем. Если не можем — возвращаем наружу объект соответствующей ошибки.
И всё же в Go имеется механизм для обработки исключительных ситуаций. Самый простой пример такой ситуации — деление на 0. И сейчас мы разберём, как действует механизм «исключений» в Go.
Возбудить исключение можно в любом месте программы вызвав функцию panic() и передав ей какие-либо данные (например, строку описания проблемы). Конечно же, паника может запускаться и вне вашего кода. Тот же пример с делением на 0 — панику запустит Go. Или если вы используете regexp.MustCompile() на невалидном паттерне — панику запустит пакет регулярок.
Справляются с паникой при помощи функции recover(), которая должна выполняться в одном из блоков defer. Связано это с тем, что любая паника приводит к завершению текущей функции, перед которым выполняются блоки defer.
Рассмотрим три варианта примера с делением на 0.
Ужасный
1 2 3 4 5 6 7 8 |
func div(x, y int) int { return x / y } func main () { x, y := 234, 0 z := div(x, y) } |
Этот пример ужасен тем, что когда y=0 происходит паника, которая никак не обрабатывается и программа падает.
Неправильный
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func div(x, y int) (res int) { defer func() { if e := recover(); e != nil { res = 0 // а если нам нужна ошибка, то создаем её при помощи fmt.Errorf("%v", e) } }() return x / y } func main () { x, y := 234, 0 z := div(x, y) } |
Неправильность этого примера в том, что: а) мы просто подавили панику и б) даже не записали в лог факт её возникновения. Программа не упадёт, но и не обязательно будет работать правильно.
Правильный
1 2 3 4 5 6 7 8 9 10 11 12 |
func div(x, y int) (int, error) { if y == 0 { return 0, errors.New("Division by zero!") } return x / y, nil } func main () { x, y := 234, 0 z, err := div(x, y) } |
А вот здесь мы делаем именно так, как и надо делать. Проверили возможность работы. Если у=0, выдали ошибку, которую можем проверить снаружи. Ну и в main мы уже должны проверить, что ошибок не было и затем либо использовать результат, либо как-то обрабатывать ошибку.