#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
может быть не лучшей стратегией, поскольку она отбрасывает исходную ошибку. В качестве альтернативы вы можете обернуть исходную ошибку ошибкой txerr = 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 {
?