英文:
Is there a common idiom for conditionally deferring resource cleanup on error?
问题
我有一个函数,它将打开一些资源并将它们捆绑到一个返回的结构体中。就像这样:
type Bundle struct {
a,b,c ExpensiveResource
}
func NewBundle() (Bundle, error) {
var bundle Bundle
bundle.a, err = GetExpensiveResource()
if err != nil { return Bundle{}, err }
bundle.b, err = GetAnotherExpensiveResource()
if err != nil { return Bundle{}, err }
bundle.c, err = GetAThirdExpensiveResource()
if err != nil { return Bundle{}, err }
return bundle, nil
}
如果GetAThirdExpensiveResource
失败,那么bundle.a
和bundle.b
将会泄漏。有没有推荐的处理方法?我想到了一个类似于closeOnError
的函数,像这样:
func NewBundle() (Bundle, error) {
var bundle Bundle
var err error
destroyOnError:= func (r ExpensiveResource) func() {
return func () { if err != nil { r.Destroy() } }
}
bundle.a, err = GetExpensiveResource()
if err != nil { return Bundle{}, err }
defer destroyOnError(bundle.a)()
// 其他操作
但是由于某些原因,我无法准确表达,这种方法似乎有些笨拙。有没有更好的方法?
英文:
I have a function that's going to open a bunch of resources and bundle them together into a return struct. Something like this:
type Bundle struct {
a,b,c ExpensiveResource
}
func NewBundle() (Bundle, error) {
var bundle Bundle
bundle.a, err = GetExpensiveResource()
if err != nil { return Bundle{}, err }
bundle.b, err = GetAnotherExpensiveResource()
if err != nil { return Bundle{}, err }
bundle.c, err = GetAThirdExpensiveResource()
if err != nil { return Bundle{}, err }
return bundle, nil
}
If GetAThirdExpensiveResource
fails, then bundle.a
and bundle.b
leak. Is there a recommended idiom for handling this? I came up with a closeOnError
function like this:
func NewBundle() (Bundle, error) {
var bundle Bundle
var err error
destroyOnError:= func (r ExpensiveResource) func() {
return func () { if err != nil { r.Destroy() } }
}
bundle.a, err = GetExpensiveResource()
if err != nil { return Bundle{}, err }
defer destroyOnError(bundle.a)()
// and so on
But for reasons I can't quite articulate this seems clunky. Is there a better way?
答案1
得分: 2
type Bundle struct {
a, b, c ExpensiveResource
}
func (b *Bundle) destroy() {
if b.a != nil {
// 销毁 a
}
if b.b != nil {
// 销毁 b
}
if b.c != nil {
// 销毁 c
}
}
func NewBundle() (b Bundle, err error) {
defer func() {
if err != nil {
b.destroy()
}
}()
if b.a, err = GetExpensiveResource(); err != nil {
return Bundle{}, err
}
if b.b, err = GetAnotherExpensiveResource(); err != nil {
return Bundle{}, err
}
if b.c, err = GetAThirdExpensiveResource(); err != nil {
return Bundle{}, err
}
return b, nil
}
英文:
type Bundle struct {
a, b, c ExpensiveResource
}
func (b *Bundle) destroy() {
if b.a != nil {
// destroy a
}
if b.b != nil {
// destroy b
}
if b.c != nil {
// destroy c
}
}
func NewBundle() (b Bundle, err error) {
defer func() {
if err != nil {
b.destroy()
}
}()
if b.a, err = GetExpensiveResource(); err != nil {
return Bundle{}, err
}
if b.b, err = GetAnotherExpensiveResource(); err != nil {
return Bundle{}, err
}
if b.c, err = GetAThirdExpensiveResource(); err != nil {
return Bundle{}, err
}
return b, nil
}
答案2
得分: 0
这个问题的最终解决方案有一定的主观性。你只需要清理可能泄漏的资源,比如打开连接或启动goroutine的资源。
你可以使用指针来表示初始化:
bundle.rsc1, err = construct1()
if err != nil {
bundle.cleanup()
return err
}
bundle.rsc2, err = construct2()
if err != nil {
bundle.cleanup()
return err
}
其中:
func (b *Bundle) cleanup() {
if b.rsc1 != nil {
b.rsc1.Close()
}
if b.rsc2 != nil {
b.rsc2.Close()
}
...
}
你可以使用标志位:
var rsc1Initialized, rsc2Initialized ... bool
cleanup := func() {
if rsc1Initialized {
bundle.rsc1.Close()
}
if rsc2Initialized {
bundle.rsc2.Close()
}
}
bundle.rsc1, err = construct1()
if err != nil {
cleanup()
return err
}
rsc1Initialized = true
...
你可以排队清理方法:
cleaners := make([]func(), 0)
cleanup := func() {
for _, x := range cleaners {
x()
}
}
bundle.rsc1, err = construct1()
if err != nil {
cleanup()
return err
}
cleaners = append(cleaners, func() {bundle.rsc1.Close()})
...
英文:
The ultimate solution to this is somewhat subjective. You would only need cleanup for resources that might leak, like things that open connections or start goroutines.
You can use pointers to denote initialization:
bundle.rsc1, err=construct1()
if err!=nil {
bundle.cleanup()
return err
}
bundle.rsc2, err=construct2()
if err!=nil {
bundle.cleanup()
return err
}
where:
func (b *Bundle) cleanup () {
if b.rsc1!=nil {
b.rsc1.Close()
}
if b.rsc2!=nil {
b.rsc2.Close()
}
...
}
You can use flags:
var rsc1Initialied, rsc2Initialized ... bool
cleanup:=func() {
if rsc1Initialized {
bundle.rsc1.Close()
}
if rsc2Initialized {
bundle.rsc2.Close()
}
}
bundle.rsc1, err= construct1()
if err!=nil {
cleanup()
return err
}
rsc1Initialized=true
...
You can queue-up cleanup methods:
cleaners:=make([]func(), 0)
cleanup:=func() {
for _,x:=range cleaners {
x()
}
}
bundle.rsc1, err=construct1()
if err!=nil {
cleanup()
return err
}
cleaners=append(cleaners,func() {bundle.rsc1.Close()})
...
答案3
得分: 0
这是相当简单的,如果需要的话(取决于ExpensiveResource的类型),在destroy()方法中添加零值检查。
type Bundle struct {
a, b, c ExpensiveResource
}
func (bundle Bundle) destroy() {
if bundle.a != nil {
bundle.a.Destroy()
}
if bundle.b != nil {
bundle.b.Destroy()
}
if bundle.c != nil {
bundle.c.Destroy()
}
}
func NewBundle() (Bundle, error) {
var err error
var bundle Bundle
if bundle.a, err = GetExpensiveResource(); err == nil {
if bundle.b, err = GetExpensiveResource(); err == nil {
if bundle.c, err = GetExpensiveResource(); err == nil {
return bundle, nil
}
}
}
bundle.destroy()
return Bundle{}, err
}
英文:
This is fairly simple, if necessary (depending on type of ExpensiveResource) add zero-value checks in the destroy() method.
type Bundle struct {
a, b, c ExpensiveResource
}
func (bundle Bundle) destroy() {
bundle.a.Destroy()
bundle.b.Destroy()
bundle.c.Destroy()
}
func NewBundle() (Bundle, error) {
var err error
var bundle Bundle
if bundle.a, err = GetExpensiveResource(); err == nil {
if bundle.b, err = GetExpensiveResource(); err == nil {
if bundle.c, err = GetExpensiveResource(); err == nil {
return bundle, nil
}
}
}
bundle.destroy()
return Bundle{}, err
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论