我刚刚开始使用 Go。我的代码开始有很多这样的:
if err != nil {
//handle err
}
或这个
if err := rows.Scan(&some_column); err != nil {
//handle err
}
在 Go 中检查和处理错误是否有一些好的习语/策略/最佳实践?
编辑澄清:我不是在抱怨或建议 Go 团队想出更好的东西。我在问我是否做得对,或者我错过了社区提出的一些技术。谢谢大家。
我刚刚开始使用 Go。我的代码开始有很多这样的:
if err != nil {
//handle err
}
或这个
if err := rows.Scan(&some_column); err != nil {
//handle err
}
在 Go 中检查和处理错误是否有一些好的习语/策略/最佳实践?
编辑澄清:我不是在抱怨或建议 Go 团队想出更好的东西。我在问我是否做得对,或者我错过了社区提出的一些技术。谢谢大家。
您的代码是惯用的,我认为这是可用的最佳实践。有些人肯定会不同意,但我认为这是在 Golang 标准库中随处可见的风格。换句话说,Go 作者以这种方式编写错误处理。
在被问到这个问题六个月后,Rob Pike 写了一篇题为Errors are Values的博文。
在那里,他认为您不需要按照 OP 提供的方式进行编程,并提到了标准库中使用不同模式的几个地方。
当然,涉及错误值的常见语句是测试它是否为 nil,但是还有无数其他事情可以用错误值做,并且应用其中一些其他事情可以使您的程序更好,消除很多样板如果使用死记硬背的 if 语句检查每个错误,就会出现这种情况。
...
使用该语言来简化您的错误处理。
但请记住:无论您做什么,都要检查您的错误!
这是一个很好的阅读。
我同意 jnml 的回答,即它们都是惯用代码,并添加以下内容:
你的第一个例子:
if err != nil {
//handle err
}
在处理多个返回值时更为惯用。例如:
val, err := someFunc()
if err != nil {
//handle err
}
//do stuff with val
您的第二个示例仅在处理err
值时是很好的简写。这适用于函数仅返回 的情况error
,或者如果您故意忽略返回的值而不是error
. 例如,这有时与Reader
andWriter
函数一起使用,这些函数返回int
写入的字节数(有时是不必要的信息)和error
:
if _, err := f.Read(file); err != nil {
//handle err
}
//do stuff with f
第二种形式称为使用if 初始化语句。
因此,关于最佳实践,据我所知(除了在需要时使用“errors”包来创建新错误)您已经涵盖了几乎所有您需要知道的关于 Go 中的错误的知识!
编辑:如果你发现你真的不能没有例外,你可以用defer
, panic
&recover
模仿它们。
我创建了一个库,用于通过 Go 函数队列简化错误处理和管道。
你可以在这里找到它:https ://github.com/go-on/queue
它有一个紧凑和冗长的句法变体。以下是简短语法的示例:
import "github.com/go-on/queue/q"
func SaveUser(w http.ResponseWriter, rq *http.Request) {
u := &User{}
err := q.Q(
ioutil.ReadAll, rq.Body, // read json (returns json and error)
)(
// q.V pipes the json from the previous function call
json.Unmarshal, q.V, u, // unmarshal json from above (returns error)
)(
u.Validate, // validate the user (returns error)
)(
u.Save, // save the user (returns error)
)(
ok, w, // send the "ok" message (returns no error)
).Run()
if err != nil {
switch err {
case *json.SyntaxError:
...
}
}
}
请注意,有一点性能开销,因为它使用反射。
此外,这不是惯用的 go 代码,因此您将希望在自己的项目中使用它,或者如果您的团队同意使用它。
在 golang 和其他语言中处理错误的“策略”是不断地将错误传播到调用堆栈,直到你在调用堆栈中足够高来处理该错误。如果您过早尝试处理该错误,那么您可能最终会重复代码。如果您处理得太晚,那么您将破坏代码中的某些内容。Golang 让这个过程变得超级简单,因为它让你非常清楚你是在给定位置处理错误还是将其传播出去。
如果你要忽略这个错误,一个简单的 _ 将非常清楚地揭示这个事实。如果您正在处理它,那么您正在处理的错误的确切情况是清楚的,因为您将在 if 语句中检查它。
就像上面人们所说的,错误实际上只是一个正常值。这样对待它。
Go 大神发布了 Go 2 中错误处理的“设计草案”。它旨在改变错误习语:
他们希望得到用户的反馈!
简而言之,它看起来像:
func f() error {
handle err { fmt.Println(err); return err }
check mayFail()
check canFail()
}
更新:设计草案受到了很多批评,因此我起草了 Go 2 错误处理需要考虑的需求,并提供了最终解决方案的可能性菜单。
大多数业内人士都遵循 golang 文档错误处理和 Go中提到的标准规则。它还有助于为项目生成文档。
下面是我对减少 Go 错误处理的看法,示例用于获取 HTTP URL 参数时:
(设计模式来源于https://blog.golang.org/errors-are-values)
type HTTPAdapter struct {
Error *common.AppError
}
func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
requestUUID := uuid.Parse(mux.Vars(r)[param])
if requestUUID == nil {
adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
possibleError, http.StatusBadRequest)
}
return requestUUID
}
为多个可能的参数调用它如下:
adapter := &httphelper.HTTPAdapter{}
viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
if adapter.Error != nil {
return nil, adapter.Error
}
这不是灵丹妙药,缺点是如果您有多个错误,您只能得到最后一个错误。
但在这种情况下,它相对重复且风险较低,因此我只能得到最后一个可能的错误。
您可以为类似的错误清理错误处理代码(因为错误是您在此处必须小心的值)并编写一个函数,您可以使用传入的错误来调用该函数来处理错误。你不必每次都写“if err!=nil {}”。同样,这只会导致清理代码,但我认为这不是惯用的做事方式。
同样,仅仅因为你可以并不意味着你应该。
goerr允许使用函数处理错误
package main
import "github.com/goerr/goerr"
import "fmt"
func ok(err error) {
if err != nil {
goerr.Return(err)
// returns the error from do_somethingN() to main()
// sequence() is terminated
}
}
func sequence() error {
ok(do_something1())
ok(do_something2())
ok(do_something3())
return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
fmt.Println("DOING 3")
return nil
}
func main() {
err_do_something := goerr.OR1(sequence)
// handle errors
fmt.Println(err_do_something)
}
如果你想精确控制错误,这可能不是解决方案,但对我来说,大多数时候,任何错误都是一个阻碍。
所以,我改用函数。
func Err(err error) {
if err!=nil {
fmt.Println("Oops", err)
os.Exit(1)
}
}
fi, err := os.Open("mmm.txt")
Err(err)