Способ не записывать long defer с использованием последнего значения ошибки?

#go #deferred

#Вперед #отложенный

Вопрос:

У меня есть этот код, который работает.

 func (r *repoPG) WithTransaction(txFunc func() error) (err error) {
    tx := db.NewTx()

    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
            tx.Rollback()
        } else if err != nil {
            tx.Rollback()
        } else {
            tx.Commit()
        }
    }()

    err = txFunc()
    return
}
  

Я хочу, чтобы каждый раз приходилось писать так долго defer , поэтому я пытаюсь написать функцию, подобную этой:

 func TxDefer(tx, err) {
  if r := recover(); r != nil {
    err = fmt.Errorf("panic: %v", r)
    tx.Rollback()
  } else if err != nil {
    tx.Rollback()
  } else {
    tx.Commit()
  }
}
  

используя его как:

 func (r *repoPG) WithTransaction(txFunc func() error) (err error) {
    tx := db.NewTx()

    defer TxDefer(tx, err)

    err = txFunc()
    return
}
  

Но это ужасно неверно, потому err что это всегда оригинал, а не результат txFunc() , верно?

Как я могу это исправить?

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

1. Зачем вообще использовать defer, если это все усложняет? Также я уверен tx.Commit , что etc возвращает свои собственные error . При использовании defer любая из этих ошибок очистки не фиксируется.

2. При каких обстоятельствах этот код будет паниковать?

3. В некоторых случаях методы длинные и полны коварных вещей. Defer — это то, для чего он нужен, верно? Есть ли какие-либо противопоказания к использованию *err ?

4. Учитывая, насколько мало WithTransaction тело, я думаю, что не имеет особого смысла извлекать отложенный финализатор в отдельную именованную функцию. Если вы оставите его закрытым, вам не нужно перепрыгивать через обручи, чтобы установить доступ на запись err . Также вы можете просто вызвать return txFunc() , он также присвоит именованный результат.

5. Как упоминалось выше, tx.Commit() и tx.Rollback() может также приводить к ошибкам, замена err может быть не лучшей стратегией, поскольку она отбрасывает исходную ошибку. В качестве альтернативы вы можете обернуть исходную ошибку ошибкой tx err = fmt.Errorf("failed to rollback tx, %s: %w", rollbackErr, err) .

Ответ №1:

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

Откат и фиксация ошибок возврата. Эти ошибки должны быть возвращены вызывающей стороне.

 func TxDefer(tx Transaction, perr *error) {
  if r := recover(); r != nil {
    *perr = fmt.Errorf("panic: %v", r)
    tx.Rollback()
  } else if *perr != nil {
    err = tx.Rollback()
    if err != nil {
       // replace original error with rollback error
       *perr = err
    }
  } else {
    *perr = tx.Commit()
  }
}
  

Используйте это так:

 func (r *repoPG) WithTransaction(txFunc func() error) (err error) {
    tx := db.NewTx()
    defer TxDefer(tx, amp;err)
    err = txFunc()
    return
}
  

В приведенном выше коде выражение *perr вычисляется до текущего значения err in WithTransaction . Значение err в вопросе — это значение err на момент отсрочки.

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

1. Является ли эта строка } else if *err != nil { такой же, как эта: } else if err != nil { ?