我有一个函数,它将打开一堆资源并将它们捆绑到一个返回结构体中。像这样:
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)()
// and so on
但是由于我不太清楚的原因,这看起来很笨拙。有没有更好的办法?
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
}
这个问题的最终解决方案有点主观。你只需要清理资源可能会泄漏,像打开的连接或启动了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()
}
...
}
可以使用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
...
可以使用队列清理方法:
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()})
...
这相当简单,如果有必要(取决于ExpensiveResource的类型),可以在destroy()方法中添加零值检查。
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
}