# Глава 9. Паника и восстановление
## Паника
Компилятор языка Go проверяет все ошибки, которые может проверить, на этапе компиляции нашей программы. Однако существуют ошибки, которые могут быть выявлены только на этапе времени выполнения. Например, выход за границы массива или разыменование нулевого указателя. В этих случаях, говоря в терминах языка Go, возникает *паника (panic)*.
Тогда выполнение пользовательской программы прекращается, выполняются все отложенные функции (`defer`) и происходит выход с печатью сообщения о том, в каком месте и по какой причине возникла паника. Технически при возникновении паники программа останавливается и раскручивается стек вызовов.
Панику нужно вызвать самостоятельно, когда происходит некоторая недопустимая ситуация. Например, деление на нуль или достижение одного из условий, до которого программа не должна была дойти:
```go {.example_for_playground}
package main
import "fmt"
func hello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
grade := "bookkeeper"
if grade == "junior" || grade == "middle" || grade == "senior" {
hello(grade)
} else {
panic(fmt.Sprintf("no such grade: %s", grade))
}
}
```
В результате получим сообщение следующего вида:
```
panic: no such grade: bookkeeper
goroutine 1 [running]:
main.main()
main.go:15 +0x2ed
```
**Паника вызывается тогда и только тогда, когда возникает ошибка в логике программы**. Так, если бы `grade` задавался с клавиатуры пользователем и не соответствовал одному из заранее заданных значений, то это ошибка ввода. В этом случае паника недопустима. Такая ошибка должна быть обработана другими средствами. Например, достаточно вывести ошибку на экран и попросить ввести `grade` повторно. Однако, если `grade` задан изначально программистом в коде, и по логике работы программы другое его значение невозможно, то смело вызывайте панику. В надежных программах паника не возникает никогда.
В Go существует следующее соглашение. Функции, которые вызывают панику в случае ошибки, начинаются с префикса `Must`. Например, для компиляции регулярного выражение существует функция `regexp.Compile`. В случае, когда что-то идет не так, функция возвращает ошибку. В паре с этой функцией существует `regexp.MustCompile`. Эта функция делает то же самое, но паникует в случае ошибки. {.task_text}
Функция `strings.Index(s string, substr string) int` ищет подстроку `substr` в строке `s` и возвращает первое вхождение. Если такого вхождения нет, то она возвращает `-1`. Реализуйте функцию-обертку `MustIndex`, которая делает тоже самое, но в случае, когда вхождения нет, паникует. {.task_text}
```go {.task_source #golang_chapter_0090_task_0010}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(MustIndex("banana", "nan"))
}
// ваш код здесь
```
Функция `MustIndex` будет иметь ту же сигнатуру, что и `strings.Index`. {.task_hint}
```go {.task_answer}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(MustIndex("banana", "nan"))
}
func MustIndex(s string, substr string) int {
idx := strings.Index(s, substr)
if idx == -1 {
panic("no substr in the str")
}
return idx
}
```
## Восстановление
Иногда после паники требуется восстановление. Например, если веб-сервер завершается с паникой, он может сперва завершить все открытые соединения с клиентами. Для этого используют ключевое слово `recover`:
```go {.example_for_playground}
package main
import (
"fmt"
"time"
)
func startServer() {
defer func() {
if p := recover(); p != nil {
fmt.Printf("Internal error: %s. Closing all connections...", p)
}
}()
time.Sleep(1 * time.Millisecond) // server is working
panic("not enough free disk space")
}
func main() {
startServer()
}
```
Вызов `panic`/`recover` в Go чем-то похож на `try`/`catch` в других языках. Однако `panic`/`recover` не стоит использовать для рутинной обработки ошибок. Полное восстановление после паники не рекомендуется, потому что состояние переменных пакета может оказаться непредсказуемым.
Что выведет следующий код? {.task_text}
```go {.example_for_playground}
package main
import "fmt"
func main() {
defer func() {
if p := recover(); p != nil {
fmt.Print("E")
}
}()
defer fmt.Print("A")
defer fmt.Print("B")
fmt.Print("C")
panic("D")
}
```
```consoleoutput {.task_source #golang_chapter_0090_task_0020}
```
Отложенные функции после вызова паники выполняются в порядке, обратном тому, в котором они были отложены. Сообщение, с которым произошла паника, возвращается функцией `recover` в `p`. {.task_hint}
```go {.task_answer}
CBAE
```
## Резюме
1. Иногда в результате неправильной логики программного кода возникают ситуации, когда дальнейшее продолжение работы невозможно. В таких случаях используют ключевое слово `panic`.
2. В случае, когда необходимо произвести дополнительные действия во время паники, используют ключевое слово `recover`.
3. Паника и восстановление имеют нечто общее с обработкой исключений в других языках. Однако после паники необходимо все же завершать работу приложения. Восстановление может привести к тому, что критическая ошибка окажется попросту незамеченной.
Следующие главы находятся в разработке
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!