вторник, 22 января 2019 г.

Эффективный Go: оператор defer

Оператор defer в Go планирует вызов функции (отложенной функции) для запуска непосредственно перед выполнением функцией defer возврата (return). Это необычный, но эффективный способ справиться с такими ситуациями, как ресурсы, которые должны быть освобождены независимо от того, какой путь функция принимает для возврата. Каноническими примерами являются разблокировка мьютекса или закрытие файла.

// Contents возвращает содержимое файла как строку.
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    // f.Close будет выполнена когда мы закончим.
    defer f.Close()  

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...)
        if err != nil {
            if err == io.EOF {
                break
            }
            // f будет закрыта если мы делаем возврат здесь
            return "", err  
        }
    }
    // f будет закрыта если мы делаем возврат здесь
    return string(result), nil 
}

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

Аргументы для отложенной функции (которые включают получателя, если функция является методом) оцениваются, когда defer выполняется, а не тогда, когда выполняется вызов (call). Это помогает избежать излишних забот о переменных, меняющих значения при выполнении функции, это также означает что одно место отложенного вызова может отложить исполнение несколько функций. Вот простой пример.

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

Отложенные функции выполняются в порядке LIFO (last-in-first-out - последним зашел, первым вышел), поэтому этот код вызовет 4 3 2 1 0 для печати после возврата из функции. Более правдоподобный пример - простой способ отследить выполнение функции через программу. Мы могли бы написать пару простых трассировок рутины как следующие:

func trace(s string)   { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

// Используем их:
func a() {
    trace("a")
    defer untrace("a")
    // делаем что-нибудь....
}

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

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

печатает

entering: b
in b
entering: a
in a
leaving: a
leaving: b

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


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


Комментариев нет:

Отправить комментарий