Controle com funções de adiamento, pânico e recuperação

Concluído

Agora vamos ver alguns fluxos de controle que são exclusivos do Go: defer, panic, e recover. Cada uma dessas funções tem vários casos de uso. Vamos explorar os casos de uso mais importantes aqui.

Função Defer

Em Go, uma defer instrução adia a execução de uma função (incluindo quaisquer parâmetros) até que a função que contém a defer instrução termine. Geralmente, você adia uma função quando deseja evitar esquecer tarefas como fechar um arquivo ou executar um processo de limpeza.

Você pode adiar quantas funções quiser. As defer instruções são executadas em ordem inversa, do último para o primeiro.

Confira como esse padrão funciona executando o seguinte código de exemplo:

package main

import "fmt"

func main() {
    for i := 1; i <= 4; i++ {
        defer fmt.Println("deferred", -i)
        fmt.Println("regular", i)
    }
}

Aqui está a saída do código:

regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1

Neste exemplo, observe que toda vez fmt.Println("deferred", -i) que foi adiado, o valor para i foi armazenado e a chamada de função foi adicionada a uma fila. Depois que a função terminou de main() imprimir os regular valores, todas as chamadas adiadas foram executadas. Observe que a saída das chamadas adiadas está na ordem inversa (última entrada, primeira saída), pois elas são retiradas da fila.

Um caso de uso típico para a defer função é fechar um arquivo depois de terminar de usá-lo. Eis um exemplo:

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()
}

Depois de criar ou abrir um arquivo, você adia a .Close() função para evitar esquecer de fechar o arquivo depois de terminar.

Função de pânico

Erros de tempo de execução fazem um programa Go entrar em pânico, como tentar acessar uma matriz usando um índice fora dos limites ou desreferenciando um ponteiro nulo. Você também pode forçar um programa a entrar em pânico.

A função interna panic() para o fluxo normal de controle em um programa Go. Quando você usa uma panic chamada, todas as chamadas de função adiadas são executadas normalmente. O processo continua até a pilha até que todas as funções retornem. Em seguida, o programa falha com uma mensagem de log. A mensagem inclui todas as informações de erro e um rastreamento de pilha para ajudá-lo a diagnosticar a causa raiz do problema.

Quando você chama a panic() função, você pode adicionar qualquer valor como um argumento. Normalmente, você envia uma mensagem de erro sobre por que está em pânico.

Por exemplo, o código a seguir combina as panic funções e defer . Tente executar este código para ver como o fluxo de controle é interrompido. Observe que os processos de limpeza ainda estão em execução.

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!")
}

Eis o resultado:

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.

Veja o que acontece quando o código é executado:

  1. Tudo corre normalmente. O programa imprime os valores altos e baixos passados para a highlow() função.

  2. Quando o valor de low é maior do que o valor de high, o programa entra em pânico. Você vê a Panic! mensagem. Neste ponto, o fluxo de controle é interrompido e todas as funções adiadas começam a imprimir a Deferred... mensagem.

  3. O programa falha e você vê o rastreamento de pilha completa. Você não vê a Program finished successfully! mensagem.

Uma chamada para a função geralmente é executada panic() quando erros graves não são esperados. Para evitar uma falha de programa, você pode usar outra função chamada recover().

Função de recuperação

Às vezes, você pode querer evitar uma falha de programa e, em vez disso, relatar o erro internamente. Ou talvez você queira limpar a bagunça antes de deixar o programa falhar. Por exemplo, você pode querer fechar qualquer conexão com um recurso para evitar mais problemas.

Go fornece a função integrada recover() para permitir que você recupere o controle após um pânico. Você só chama recover uma função em que também está chamando defer. Se você chamar a recover() função, ela retornará nil e não terá outro efeito na execução normal.

Tente modificar a main função no código anterior para adicionar uma chamada à recover() função, desta forma:

func main() {
    defer func() {
	handler := recover()
        if handler != nil {
            fmt.Println("main(): recover", handler)
        }
    }()

    highlow(2, 0)
    fmt.Println("Program finished successfully!")
}

Quando você executa o programa, a saída deve ter esta aparência:

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.

Vê a diferença em relação à versão anterior? A principal diferença é que você não vê mais o erro de rastreamento de pilha.

main() Na função, você adia uma função anônima onde você chama a recover() função. Uma chamada para recover() não retornar nil quando o programa está em pânico. Você pode fazer algo aqui para limpar a bagunça, mas neste exemplo, você simplesmente imprime algo.

A combinação das panic funções e recover é a maneira idiomática como Go lida com exceções. Outras linguagens de programação usam o try/catch bloco. Go prefere a abordagem que você explorou aqui.

Para obter mais informações, confira a proposta de adicionar uma função integrada try em Go.