让我们说我们想要实现以下计算:
outval / err = f3(f3(f1(inval))
其中每一个的f1
,f2
,f3
可与当时的错误失败,我们停止计算,并设置err
为Error被失败的函数返回.(当然,嵌套可以任意长)
在像C++/JAVA/C#这样的语言中,可以通过使用f1
,f2
并f3
抛出异常并将计算封装在try-catch块中来轻松完成,而在像Haskell这样的语言中,我们可以使用monads代替.
现在我正在尝试在GO中实现它,我能想到的唯一方法是显而易见的 - 否则是一个相当冗长的梯形图.如果我们无法嵌套调用,我没有问题,但在我看来,在代码中的每一行看起来很丑并且它打破了流程后添加错误检查.我想知道是否有更好的方法.
编辑:根据peterSO的评论进行编辑
下面是具体的示例和简单的实现
package main
import "fmt"
func f1(in int) (out int, err error) {
return in + 1, err
}
func f2(in int) (out int, err error) {
return in + 2, err
}
func f3(in int) (out int, err error) {
return in + 3, err
}
func calc(in int) (out int, err error) {
var temp1, temp2 int
temp1, err = f1(in)
if err != nil {
return temp1, err
}
temp2, err = f2(temp1)
if err != nil {
return temp2, err
}
return f3(temp2)
}
func main() {
inval := 0
outval, err := calc3(inval)
fmt.Println(inval, outval, err)
}
Run Code Online (Sandbox Code Playgroud)
我想说明的是,函数calc可能借助于可能失败的库函数进行一些计算,语义就是如果任何调用失败,calc会将错误传播给调用者(类似于不处理异常).在我看来,calc的代码很难看.
在所有库函数具有完全相同签名的特殊情况之间,我们可以使代码更好(我使用http://golang.org/doc/articles/wiki/#tmp_269中的想法)
func saferun(f func (int) (int, error)) func (int, error) (int, error) {
return func (in int, err error) (int, error) {
if err != nil {
return in, err
}
return f(in)
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以将calc重新定义为
func calc(in int) (out int, err error) {
return saferun(f3)(saferun(f2)(f1(in)))
}
Run Code Online (Sandbox Code Playgroud)
或者作为
func calc(in int) (out int, err error) {
sf2 := saferun(f2)
sf3 := saferun(f3)
return sf3(sf2(f1(in)))
}
Run Code Online (Sandbox Code Playgroud)
但是如果没有泛型支持,我不确定如何将这种方法用于任何一组库函数.
如果你真的想要能够做到这一点,你可以使用撰写功能.
func compose(fs ...func(Value) (OutVal, error)) func(Value) (OutVal, error) {
return func(val Value) OutVal, Error {
sVal := val
var err error
for _, f := range fs {
sval, err = f(val)
if err != nil {
// bail here and return the val
return nil, err
}
}
return sval, nil
}
}
outVal, err := compose(f1, f2)(inVal)
Run Code Online (Sandbox Code Playgroud)
大多数情况下,您可能希望比这更简单,因为其他人在遇到代码时可能很难理解您的代码.
首先,你习惯的try-catch风格的扩展版本,明显借鉴了jimt的答案和PeterSO的答案.
package main
import "fmt"
// Some dummy library functions with different signatures.
// Per idiomatic Go, they return error values if they have a problem.
func f1(in string) (out int, err error) {
return len(in), err
}
func f2(in int) (out int, err error) {
return in + 1, err
}
func f3(in int) (out float64, err error) {
return float64(in) + .5, err
}
func main() {
inval := "one"
// calc3 three is the function you want to call that does a computation
// involving f1, f2, and f3 and returns any error that crops up.
outval, err := calc3(inval)
fmt.Println("inval: ", inval)
fmt.Println("outval:", outval)
fmt.Println("err: ", err)
}
func calc3(in string) (out float64, err error) {
// Ignore the following big comment and the deferred function for a moment,
// skip to the comment on the return statement, the last line of calc3...
defer func() {
// After viewing what the fXp function do, this function can make
// sense. As a deferred function it runs whenever calc3 returns--
// whether a panic has happened or not.
//
// It first calls recover. If no panic has happened, recover returns
// nil and calc3 is allowed to return normally.
//
// Otherwise it does a type assertion (the value.(type) syntax)
// to make sure that x is of type error and to get the actual error
// value.
//
// It does a tricky thing then. The deferred function, being a
// function literal, is a closure. Specifically, it has access to
// calc3's return value "err" and can force calc3 to return an error.
// A line simply saying "err = xErr" would be enough, but we can
// do better by annotating the error (something specific from f1,
// f2, or f3) with the context in which it occurred (calc3).
// It allows calc3 to return then, with this descriptive error.
//
// If x is somehow non-nil and yet not an error value that we are
// expecting, we re-panic with this value, effectively passing it on
// to allow a higer level function to catch it.
if x := recover(); x != nil {
if xErr, ok := x.(error); ok {
err = fmt.Errorf("calc3 error: %v", xErr)
return
}
panic(x)
}
}()
// ... this is the way you want to write your code, without "breaking
// the flow."
return f3p(f2p(f1p(in))), nil
}
// So, notice that we wrote the computation in calc3 not with the original
// fX functions, but with fXp functions. These are wrappers that catch
// any error and panic, removing the error from the function signature.
// Yes, you must write a wrapper for each library function you want to call.
// It's pretty easy though:
func f1p(in string) int {
v, err := f1(in)
if err != nil {
panic(err)
}
return v
}
func f2p(in int) int {
v, err := f2(in)
if err != nil {
panic(err)
}
return v
}
func f3p(in int) float64 {
v, err := f3(in)
if err != nil {
panic(err)
}
return v
}
// Now that you've seen the wrappers that panic rather than returning errors,
// go back and look at the big comment in the deferred function in calc3.
Run Code Online (Sandbox Code Playgroud)
所以,你可能会抗议你要求更容易,而事实并非如此.整体上没有参数,但是如果库函数全部返回错误值并且您希望链接函数调用而没有错误值,则可用的解决方案是包装库函数,并且包装器非常薄且易于编写.唯一困难的部分是延迟函数,但它是一种可以学习和重用的模式,它只有几行代码.
我不想过多地推广这个解决方案,因为它不是经常使用的解决方案.这是一个有效的模式,并且确实有一些适当的用例.
正如jimt所提到的,错误处理是一个很大的主题."在Go中进行错误处理的好方法是什么?" 对于SO来说这将是一个很好的问题,除了它没有通过"整本书"critereon的问题.我可以想像的围棋错误处理这个问题的整本书.
相反,我将提供我的一般观察,如果你只是开始使用错误值而不是试图让它们消失,过了一段时间你就会开始理解这样做的优点.当你在真实世界的程序中首次编写if语句时,看起来像我们在这里使用的玩具示例中的if语句的冗长阶梯看起来仍然像一个冗长的if语句阶梯.当您确实需要处理这些错误时,您会回到代码中并突然将其视为存根,等待您使用真正的错误处理代码充实.您可以看到该做什么,因为导致错误的代码就在那里.您可以阻止用户查看模糊的低级别错误消息,而是显示有意义的内容.您作为程序员被提示做正确的事情而不是接受默认的事情.
有关更全面的答案,一个很好的资源开始是文章Error Handling and Go.如果您搜索Go-Nuts消息,那么也会对此事进行长时间的讨论.标准库中的函数相互调用很多(惊喜),因此标准库的源代码包含许多处理错误的示例.这些都是很好的例子,因为代码是由Go作者编写的,他们正在推广这种使用错误值的编程风格.
错误与例外之间的讨论是漫长而乏味的.因此,我不会进入它.
最简单的回答你的问题涉及Go的内置defer
,panic
以及recover
在讨论的功能在这个博客帖子.他们可以提供类似于例外的行为.
package main
import "fmt"
func main() {
defer func() {
// This recovers from a panic if one occurred.
if x := recover(); x != nil {
fmt.Printf("%v\n", x)
}
}()
value := f(f(f(1)))
fmt.Printf("%d\n", value)
}
func f(i int) int {
value := i*i + 1
// something goes wrong, panic instead of returning an error.
panic("ohnoes")
return value
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2198 次 |
最近记录: |