Sterowanie funkcjami odroczenia, paniki i odzyskiwania

Ukończone

Teraz przyjrzyjmy się niektórym przepływom sterowania, które są unikatowe dla języka Go: defer, panici 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:

  1. Wszystko działa normalnie. Program wyświetla wysokie i niskie wartości przekazane do highlow() funkcji.

  2. Gdy wartość parametru low jest większa niż wartość high, program ulega paniki. Zostanie wyświetlony Panic! komunikat. W tym momencie przepływ sterowania zostanie przerwany, a wszystkie funkcje odroczone zaczynają wyświetlać Deferred... komunikat.

  3. 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.