Go语言中的惯用写法(Happy Path)

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

Idiomatic Go Happy Path

问题

假设我们有一个返回某个值和错误的函数。处理错误和值声明的首选方式是什么?

func example_a(data interface{}) (interface{}, error) {
    var err error
    var bytes []byte
    if bytes, err = json.Marshal(data); err != nil {
        return nil, err
    }
    // ... 
    return use(bytes), nil
}

func example_b(data interface{}) (interface{}, error) {
    if bytes, err := json.Marshal(data); err != nil {
        return nil, err
    } else {
        // ... 
        return use(bytes), nil
    }
}

func example_c(data interface{}) (result interface{}, err error) {
    var bytes []byte
    if bytes, err = json.Marshal(data); err != nil {
        return
    }
    // ... 
    return use(bytes), nil
}

func example_d(data interface{}) (interface{}, error) {
    bytes, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }
    // ... 
    return use(bytes), nil
}

func example_dream(data interface{}) (interface{}, error) {
    if bytes, err := json.Marshal(data); err != nil {
        return nil, err
    }
    // ... 
    return use(bytes), nil
}

示例A很清晰,但是增加了两行额外的代码。此外,我发现在这种特殊情况下为什么要使用var不太清楚,同时:=也不总是合适。然后你想在后面的代码中重用err声明,我不太喜欢将声明和赋值分开。

示例B使用了if-declare-test语言特性,我猜这是被鼓励的,但同时你被迫嵌套函数继续执行,违反了happy-path原则,这也是被鼓励的。

示例C使用了命名参数返回特性,介于A和B之间。最大的问题是,如果你的代码库使用了B和C两种风格,那么很容易混淆:==,这可能会导致各种问题。

示例D(从建议中添加)对我来说与C有相同的使用问题,因为不可避免地会遇到以下情况:

func example_d(a, b interface{}) (interface{}, error) {
    bytes, err := json.Marshal(a)
    if err != nil {
        return nil, err
    }

    bytes, err := json.Marshal(b) //编译错误
    if err != nil {
        return nil, err
    }

    // ... 
    return use(bytes), nil
}

所以根据先前的声明,我必须修改我的代码以使用:==,这使得难以查看和重构。

示例Dream是我对Go的一种直观期望-没有嵌套,没有太多冗长的代码,可以快速退出并重用变量。显然它不能编译。

通常use()会内联并重复模式多次,增加了嵌套或分割声明的问题。

那么处理这种多返回和声明的最典型方式是什么?有我所忽略的模式吗?

英文:

Suppose we have a function that returns some value and an error. What's the preferred way of handling the error and value declarations?

func example_a(data interface{}) (interface{}, error) {
	var err error
	var bytes []byte
	if bytes, err = json.Marshal(data); err != nil {
		return nil, err
	}
    // ... 
	return use(bytes), nil
}

func example_b(data interface{}) (interface{}, error) {
	if bytes, err := json.Marshal(data); err != nil {
		return nil, err
	} else {
        // ... 
		return use(bytes), nil
	}
}

func example_c(data interface{}) (result interface{}, err error) {
	var bytes []byte
	if bytes, err = json.Marshal(data); err != nil {
		return
	}
    // ... 
	return use(bytes), nil
}

func example_d(data interface{}) (interface{}, error) {
    bytes, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }
    // ... 
    return use(bytes), nil
}

func example_dream(data interface{}) (interface{}, error) {
	if bytes, err ≡ json.Marshal(data); err != nil {
		return nil, err
	}
    // ... 
	return use(bytes), nil
}

Example A is clear, but it adds 2 extra lines. Moreover, I find that it's unclear why in this particular case we should use var, and at the same time := is not always appropriate. Then you want to reuse the err declaration somewhere down the line, and I'm not a big fan of splitting declaration and assignment.

Example B is using the if-declare-test language feature, which I surmise is encouraged, but at the same time you are forced to nest function continuation violating the happy-path principle, which too is encouraged.

Example C uses the named parameter return feature, which is something between A and B. Biggest problem here, is that if your code base is using styles B and C, then it's easy to mistake := and =, which can cause all kinds of issues.

Example D (added from suggestions) has for me the same kind of usage problem as C, because inevitably I run into the following:

func example_d(a, b interface{}) (interface{}, error) {
    bytes, err := json.Marshal(a)
    if err != nil {
        return nil, err
    }

    bytes, err := json.Marshal(b) //Compilation ERROR
    if err != nil {
        return nil, err
    }

    // ... 
    return use(bytes), nil
}

So depending on previous declarations I have to modify my code to either use := or =, which makes it harder to see and refactor.

Example Dream is what I kind of intuitively would have expected from GO - no nesting, and quick exit without too much verbosity and variable reuse. Obviously it doesn't compile.

Usually use() is inlined and repeats the pattern several times, compounding the nesting or split declaration issue.

So what's the most idiomatic way of handling such multiple returns and declarations? Is there a pattern I'm missing?

答案1

得分: 7

如果你查看很多 Go 代码,你会发现以下情况是常见的:

func example(data interface{}) (interface{}, error) {
    bytes, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }
    // ... 
    return use(bytes), nil
}

在适当的情况下,声明和测试 if 结构是很好的,但在这里通常不合适。

英文:

If you look at lots of Go code you will find the following to be the usual case:

func example(data interface{}) (interface{}, error) {
    bytes, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }
    // ... 
    return use(bytes), nil
}

The declare and test if construct is nice in its place, but it is not generally apropriate here.

huangapple
  • 本文由 发表于 2017年9月3日 23:10:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/46024818.html
匿名

发表评论

匿名网友

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

确定