在条件下推迟资源清理的常见习语是什么?

huangapple go评论82阅读模式
英文:

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.abundle.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
}

huangapple
  • 本文由 发表于 2021年10月10日 02:22:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/69509482.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定