Показаны сообщения с ярлыком встроенная функция panic в Go. Показать все сообщения
Показаны сообщения с ярлыком встроенная функция panic в Go. Показать все сообщения

воскресенье, 22 ноября 2020 г.

Go style guides: не используйте panic

Код, запущенный в продуктовой среде, должен избегать паники. Паника - главный источник каскадных сбоев. В случае возникновения ошибки функция должна вернуть ошибку и позволить вызывающему абоненту решить, как ее обработать.

Неудачный вариант:

func run(args []string) {
    if len(args) == 0 {
        panic("требуется аргумент")
    }
    // ...
}

func main() {
    run(os.Args[1:])
}

Более удачный вариант:

func run(args []string) error {
    if len(args) == 0 {
        return errors.New("требуется аргумент")
    }
    // ...
    return nil
}

func main() {
    if err := run(os.Args[1:]); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Panic/recover не является стратегией обработки ошибок. Программа должна паниковать только тогда, когда происходит что-то безвозвратное, например, нулевое разыменование (nil dereference, попытка получить значение по nil адресу, например в ситуации когда переменная с типом указателя структуры содержит nil и происходит попытка разыменования указателя). Исключением является инициализация программы: плохие вещи при запуске программы, которые должны прервать выполнение программы, могут вызвать panic.

var _statusTemplate = template.Must(
     template.New("name").Parse("_statusHTML"))

Даже в тестах предпочтительнее паники t.Fatal или t.FailNow, чтобы тест был отмечен как неудачный.

Неудачный вариант:

// func TestFoo(t *testing.T)

f, err := ioutil.TempFile("", "test")
if err != nil {
    panic("не удалось настроить тест")
}

Более удачный вариант:

// func TestFoo(t *testing.T)

f, err := ioutil.TempFile("", "test")
if err != nil {
    t.Fatal("не удалось настроить тест")
}


Читайте также:


воскресенье, 3 мая 2020 г.

Паника, следы стека и способы восстановления в Golang

Паника - исключение в Go

Паника (panic) похожа на исключения (exceptions) C++ и Java, но предназначена только для ошибок времени выполнения, таких как следование по нулевому указателю или попытка индексировать массив за пределами границ. Для обозначения таких событий, как конец файла (EOF), программы Go используют встроенный тип error.

Паника останавливает нормальное выполнение goroutine:

  • Когда программа паникует, она сразу начинает раскручивать стек вызовов.
  • Это продолжается до тех пор, пока программа не выйдет из строя и не напечатает трассировку стека,
  • или пока не будет вызвана встроенная функция recover.

Паника вызвана либо ошибкой во время выполнения, либо явным вызовом встроенной функции panic.

Следы стека

Трассировка стека - отчет обо всех активных кадрах стека - обычно выводится на консоль при возникновении паники. Следы стека могут быть очень полезны для отладки:

  • вы не только видите, где произошла ошибка,
  • но и как программа прибыла в это место.

Интерпретация трассировки стека

Вот пример трассировки стека:

goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
    /usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
    ../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
    ../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
    /usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
    /usr/local/go/src/testing/testing.go:789 +0x2de

Это можно прочитать снизу вверх:

  • testing.(*T).Run вызвал testing.tRunner,
  • который вызвал bit.TestMax,
  • который вызвал bit.(*Set).Max,
  • который вызвал panic,
  • который вызвал testing.tRunner.func1.

Строки с отступом показывают исходный файл и номер строки, по которой была вызвана функция. Шестнадцатеричные числа относятся к значениям параметров, включая значения указателей и внутренних структур данных.

Распечатать и записать трассировку стека

Чтобы напечатать трассировку стека для текущей программы, используйте debug.PrintStack из пакета runtime/debug.

Вы также можете программно проверить текущую трассировку стека, вызвав runtime.Stack.

Уровень деталей

Переменная GOTRACEBACK управляет количеством выходных данных, генерируемых при сбое программы Go.

  • GOTRACEBACK=none пропускает трассировки стека goroutine полностью.
  • GOTRACEBACK=single (по умолчанию) печатает трассировку стека для текущей go-процедуры (goroutine), исключая функции, внутренние для системы времени выполнения. Ошибка выводит трассировки стека для всех go-процедур, если текущая go-процедура не существует или ошибка является внутренней по отношению к среде выполнения.
  • GOTRACEBACK=all добавляет трассировки стека для всех пользовательских go-процедур.
  • GOTRACEBACK=system, как all, но добавляет кадры стека для функций времени выполнения (run-time) и показывает go-процедуры, созданные внутри во время выполнения.

Recover и поимка паники

Встроенная функция recover может быть использована для восстановления контроля над паникующей goroutine и возобновления нормального выполнения.

  • Вызов recover останавливает раскручивание и возвращает аргумент, переданный в panic.
  • Если goroutine не паникует, recover возвращает nil.

Поскольку единственный код, который выполняется при разматывании, находится внутри отложенных (defer) функций, recover полезна только внутри таких функций.

Пример обработчика паники

func main() {
    n := foo()
    fmt.Println("main received", n)
}

func foo() int {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    m := 1
    panic("foo: fail")
    m = 2
    return m
}

Вывод:

foo: fail
main received 0

Поскольку паника возникла до того, как foo вернула значение, n по-прежнему имеет начальное нулевое значение.

Вернуть значение

Чтобы вернуть значение во время паники, вы должны использовать именованное возвращаемое значение.

func main() {
    n := foo()
    fmt.Println("main received", n)
}

func foo() (m int) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
            m = 2
        }
    }()
    m = 1
    panic("foo: fail")
    m = 3
    return m
}

Вывод:

foo: fail
main received 2

Тест паники (утилитная функция)

В этом примере мы используем отражение, чтобы проверить, имеют ли список переменных интерфейса типы, соответствующие параметрам данной функции. Если это так, мы вызываем функцию с этими параметрами, чтобы проверить, есть ли паника.

// Panics сообщает, паникует ли функция f с параметрами p.
func Panics(f interface{}, p ...interface{}) bool {
    fv := reflect.ValueOf(f)
    ft := reflect.TypeOf(f)
    if ft.NumIn() != len(p) {
        panic("неверное количество аргументов")
    }
    pv := make([]reflect.Value, len(p))
    for i, v := range p {
        if reflect.TypeOf(v) != ft.In(i) {
            panic("неверный тип аргумента")
        }
        pv[i] = reflect.ValueOf(v)
    }
    return call(fv, pv)
}

func call(fv reflect.Value, pv []reflect.Value) (b bool) {
    defer func() {
        if err := recover(); err != nil {
            b = true
        }
    }()
    fv.Call(pv)
    return
}


Читайте также:


суббота, 22 июня 2019 г.

Спецификация Go: обработка паники

Две встроенные функции, panic и recover, помогают в составлении отчетов и обработке паники во время выполнения (run-time panics) и программно определенных ошибочных условий.

func panic(interface{})
func recover() interface{}

При выполнении функции F явный вызов panic или паники во время выполнения (run-time panic) завершает выполнение F. Любые функции, отложенные (deferred) с помощью F, затем выполняются как обычно. Затем выполняются все отложенные функции, выполняемые вызывающей стороной F, и так далее, вплоть до любой отложенной функции верхнего уровня в выполняющейся процедуре. В этот момент программа завершается и сообщается об ошибке, включая значение аргумента для паники. Эта последовательность завершения называется паникой.

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

Функция recover позволяет программе управлять поведением паникующей программы. Предположим, что функция G откладывает функцию D, которая вызывает recover, и паника возникает в функции той же программы, в которой выполняется G. Когда выполнение отложенных функций достигает D, возвращаемым значением вызова D для recover будет значение, переданное вызову паники. Если D возвращается нормально, без начала новой паники, последовательность паники останавливается. В этом случае состояние функций, вызываемых между G и вызовом паники, отбрасывается, и нормальное выполнение возобновляется. Любые функции, отложенные G перед D, затем запускаются, и выполнение G прекращается, возвращаясь к своему вызывающему.

Возвращаемое значение recover равно nil, если выполняется любое из следующих условий:

  • аргумент паники был nil;
  • goroutine не паникует;
  • recover не была вызвана напрямую отложенной функцией.

Функция protect в приведенном ниже примере вызывает аргумент функции g и защищает вызывающих от паники во время выполнения, вызванной g.

func protect(g func()) {
    defer func() {
        // Println выполняется нормально, 
        // даже если есть паника
        log.Println("done")  
        if x := recover(); x != nil {
            log.Printf("run time panic: %v", x)
        }
    }()
    log.Println("start")
    g()
}


Читайте также: