Sterowanie funkcjami odroczenia, paniki i odzyskiwania
Teraz przyjrzyjmy się niektórym przepływom sterowania, które są unikatowe dla języka Go: defer
, panic
i recover
. Każda z tych funkcji ma kilka przypadków użycia. W tym miejscu zapoznamy się z najważniejszymi przypadkami użycia.
Defer, funkcja
W języku defer
Go instrukcja odroczy uruchomienie funkcji (w tym dowolnych parametrów), dopóki funkcja zawierająca defer
instrukcję nie zostanie zakończona. Ogólnie rzecz biorąc, odroczysz funkcję, gdy chcesz uniknąć zapominania o zadaniach, takich jak zamykanie pliku lub uruchamianie procesu oczyszczania.
Można odroczyć dowolną liczbę funkcji. Instrukcje defer
są uruchamiane w odwrotnej kolejności od ostatniego do pierwszego.
Sprawdź, jak działa ten wzorzec, uruchamiając następujący przykładowy kod:
package main
import "fmt"
func main() {
for i := 1; i <= 4; i++ {
defer fmt.Println("deferred", -i)
fmt.Println("regular", i)
}
}
Oto dane wyjściowe kodu:
regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1
W tym przykładzie zwróć uwagę, że za każdym razem, gdy fmt.Println("deferred", -i)
została odroczona, wartość elementu i
została zapisana, a wywołanie funkcji zostało dodane do kolejki. Po zakończeniu main()
regular
drukowania wartości przez funkcję uruchomiono wszystkie odroczone wywołania. Zwróć uwagę, że dane wyjściowe z odroczonych wywołań są w odwrotnej kolejności (po raz ostatni na wyjściu), ponieważ są one wyskoczone z kolejki.
Typowy przypadek defer
użycia funkcji polega na zamknięciu pliku po zakończeniu korzystania z niego. Oto przykład:
package main
import (
"io"
"os"
"fmt"
)
func main() {
newfile, error := os.Create("learnGo.txt")
if error != nil {
fmt.Println("Error: Could not create file.")
return
}
defer newfile.Close()
if _, error = io.WriteString(newfile, "Learning Go!"); error != nil {
fmt.Println("Error: Could not write to file.")
return
}
newfile.Sync()
}
Po utworzeniu lub otwarciu pliku odroczyć .Close()
funkcję, aby uniknąć zapomnienia o zamknięciu pliku po zakończeniu.
Funkcja Panic
Błędy środowiska uruchomieniowego sprawiają, że program Języka Go panikuje, na przykład próbuje uzyskać dostęp do tablicy przy użyciu indeksu poza granicami lub wyłuszczenia wskaźnika zerowego. Można również wymusić program paniki.
Wbudowana panic()
funkcja zatrzymuje normalny przepływ sterowania w programie Go. Jeśli używasz wywołania panic
, wszystkie odroczone wywołania funkcji są uruchamiane normalnie. Proces kontynuuje stos do momentu zwrócenia wszystkich funkcji. Następnie program ulega awarii z komunikatem dziennika. Komunikat zawiera wszelkie informacje o błędzie i ślad stosu, aby pomóc zdiagnozować główną przyczynę problemu.
Podczas wywoływania panic()
funkcji można dodać dowolną wartość jako argument. Zazwyczaj wysyłasz komunikat o błędzie o tym, dlaczego panikujesz.
Na przykład poniższy kod łączy panic
funkcje i defer
. Spróbuj uruchomić ten kod, aby zobaczyć, jak przepływ sterowania jest przerywany. Zwróć uwagę, że procesy czyszczenia nadal działają.
package main
import "fmt"
func highlow(high int, low int) {
if high < low {
fmt.Println("Panic!")
panic("highlow() low greater than high")
}
defer fmt.Println("Deferred: highlow(", high, ",", low, ")")
fmt.Println("Call: highlow(", high, ",", low, ")")
highlow(high, low + 1)
}
func main() {
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
Dane wyjściowe są następujące:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
panic: highlow() low greater than high
goroutine 1 [running]:
main.highlow(0x2, 0x3)
/tmp/sandbox/prog.go:13 +0x34c
main.highlow(0x2, 0x2)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x1)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x0)
/tmp/sandbox/prog.go:18 +0x298
main.main()
/tmp/sandbox/prog.go:6 +0x37
Program exited: status 2.
Oto, co się stanie po uruchomieniu kodu:
Wszystko działa normalnie. Program wyświetla wysokie i niskie wartości przekazane do
highlow()
funkcji.Gdy wartość parametru
low
jest większa niż wartośćhigh
, program ulega paniki. Zostanie wyświetlonyPanic!
komunikat. W tym momencie przepływ sterowania zostanie przerwany, a wszystkie funkcje odroczone zaczynają wyświetlaćDeferred...
komunikat.Program ulega awarii i zobaczysz pełny ślad stosu. Nie widzisz komunikatu
Program finished successfully!
.
Wywołanie panic()
funkcji zwykle jest uruchamiane, gdy nie są oczekiwane poważne błędy. Aby uniknąć awarii programu, możesz użyć innej funkcji o nazwie recover()
.
Odzyskaj funkcję
Czasami możesz uniknąć awarii programu i zamiast tego zgłosić błąd wewnętrznie. A może chcesz oczyścić bałagan przed zezwoleniem na awarię programu. Na przykład możesz chcieć zamknąć dowolne połączenie z zasobem, aby uniknąć większej liczby problemów.
Go udostępnia wbudowaną recover()
funkcję, aby umożliwić odzyskanie kontroli po paniki. Wywołujesz recover
tylko funkcję, w której wywołujesz również funkcję defer
. Jeśli wywołasz recover()
funkcję, zwraca nil
i nie ma innego efektu w normalnym uruchomieniu.
Spróbuj zmodyfikować main
funkcję w poprzednim kodzie, aby dodać wywołanie funkcji recover()
w następujący sposób:
func main() {
defer func() {
handler := recover()
if handler != nil {
fmt.Println("main(): recover", handler)
}
}()
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
Po uruchomieniu programu dane wyjściowe powinny wyglądać następująco:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
main(): recover from panic highlow() low greater than high
Program exited.
Czy widzisz różnicę w stosunku do poprzedniej wersji? Główną różnicą jest to, że nie widzisz już błędu śledzenia stosu.
main()
W funkcji odroczyć funkcję anonimową, w której jest wywoływana recover()
funkcja. Wywołanie recover()
powrotu nie powiedzie się nil
, gdy program panikuje. W tym przykładzie możesz coś zrobić, aby wyczyścić bałagan, ale w tym przykładzie wystarczy wydrukować coś.
Kombinacja panic
funkcji i recover
jest idiomatycznym sposobem, w jaki go obsługuje wyjątki. Inne języki programowania używają try/catch
bloku. Go preferuje podejście, które omówiliśmy tutaj.
Aby uzyskać więcej informacji, zapoznaj się z propozycją dodawania wbudowanej try
funkcji w języku Go.