英文:
Golang idiomatic error handling
问题
我已经为独立和嵌入式使用(例如与CustomerRepository一起)创建了一个“base”存储库结构,以避免一直检查错误,并为Gorp(数据库工具包)创建了一个抽象层,并创建了一个更符合我的喜好的API。
我在这个基本结构中检查错误,如果发现错误就会引发panic,因为在我看来,如果存在错误,则表示开发错误,代码可能会引发panic,因为验证等应该在数据到达存储库之前进行。
我在这里找到了一个问题https://stackoverflow.com/questions/16963298/golang-error-handling-techniques,但它没有涵盖像我所做的那样将错误封装在基本结构中并引发panic。
我所做的是否符合Go的惯用方式?
package repositories
import (
"github.com/coopernurse/gorp"
)
type Repository struct {
Gorp gorp.SqlExecutor
}
func (r *Repository) GetById(i interface{}, id int) interface{} {
obj, err := r.Gorp.Get(i, id)
if err != nil {
panic(err)
}
return obj
}
func (r *Repository) Get(holder interface{}, query string, args ...interface{}) interface{} {
if err := Gorp.SelectOne(holder, query, args); err != nil {
panic(err)
}
}
func (r *Repository) Select(i interface{}, query string, args ...interface{}) {
if _, err := Gorp.Select(holder, query, args); err != nil {
panic(err)
}
}
func (r *Repository) Insert(list ...interface{}) {
if err := r.Gorp.Insert(list...); err != nil {
panic(err)
}
}
func (r *Repository) Update(list ...interface{}) int64 {
count, err := r.Gorp.Update(list...)
if err != nil {
panic(err)
}
return count
}
func (r *Repository) Delete(list ...interface{}) int64 {
count, err := r.Gorp.Delete(list...)
if err != nil {
panic(err)
}
return count
}
这样做符合Go的惯用方式吗?
英文:
I have created a "base" repository struct for standalone and embedded use (e.g with CustomerRepository) to avoid having to check errors all the time, and to create an abstraction for Gorp (the database toolkit), and to create an API slightly more to my liking.
I check for errors in this base struct and panic if one is found, as if one does exist in my opinion it then indicates a dev error and the code may as well panic, seeing as validation etc. should happen before data gets to the Repository.
I found this question https://stackoverflow.com/questions/16963298/golang-error-handling-techniques, but it doesn't cover wrapping errors up in a base struct like I have done and just panicking.
Is what I have done idiomatic Go?
package repositories
import (
"github.com/coopernurse/gorp"
)
type Repository struct {
Gorp gorp.SqlExecutor
}
func (r *Repository) GetById(i interface{}, id int) interface{} {
obj, err := r.Gorp.Get(i, id)
if err != nil {
panic(err)
}
return obj
}
func (r *Repository) Get(holder interface{}, query string, args ...interface{}) interface{} {
if err := Gorp.SelectOne(holder, query, args); err != nil {
panic(err)
}
}
func (r *Repository) Select(i interface{}, query string, args ...interface{}) {
if _, err := Gorp.Select(holder, query, args); err != nil {
panic(err)
}
}
func (r *Repository) Insert(list ...interface{}) {
if err := r.Gorp.Insert(list...); err != nil {
panic(err)
}
}
func (r *Repository) Update(list ...interface{}) int64 {
count, err := r.Gorp.Update(list...)
if err != nil {
panic(err)
}
return count
}
func (r *Repository) Delete(list ...interface{}) int64 {
count, err := r.Gorp.Delete(list...)
if err != nil {
panic(err)
}
return count
}
答案1
得分: 13
不要慌张,这不是Go的方式。相反,可以像这样做:
func (r *Repository) GetById(i interface{}, id int) (interface{}, error) {
obj, err := r.Gorp.Get(i, id)
if err != nil {
return nil, err
}
return obj, nil
}
然后在调用者中处理错误。根据你上面的评论,我看到你在一个Martini处理程序中使用这些函数,所以你可以像这样做:
func MyHandler(parameters) (int, string) {
obj, err := repository.GetById(something, id)
if err == repository.ErrNotFound {
return http.StatusNotFound, fmt.Sprintf("could not find by id: %d", id)
}
if err != nil {
return http.StatusInternalError, err.Error()
}
return http.StatusOK, fmt.Printf("obj: %v", obj)
}
这更符合Go的方式。确保r.Gorp.Get
返回你在包内声明的具体错误。
var ErrNotFound = errors.New("not found")
根据你的代码逻辑,创建尽可能多的错误变量。
英文:
Don't panic, this isn't the Go way. Instead, do something like this --
func (r *Repository) GetById(i interface{}, id int) (interface{}, error) {
obj, err := r.Gorp.Get(i, id)
if err != nil {
return nil, err
}
return obj, nil
}
Then just handle the error in your caller. I see from your comments above that you are using these functions inside a Martini handler, so you would do something like this --
func MyHandler(parameters) (int, string) {
obj, err := repository.GetById(something, id)
if err == repository.ErrNotFound {
return http.StatusNotFound, fmt.Sprintf("could not find by id: %d", id)
}
if err != nil {
return http.StatusInternalError, err.Error()
}
return http.StatusOk, fmt.Printf("obj: %v", obj)
}
This is more like the Go way. Make sure that r.Gorp.Get does return specific errors that you declare inside your package.
var ErrNotFound = errors.New("not found")
Create as many as make sense to your code.
答案2
得分: 10
惯用的方式是将错误与相关的类型值一起返回,即:
func (list ...interface{}) (v int46, err error) {}
然后在调用这些函数时检查 err != nil。
最终使用 panic() 会导致类似异常的错误处理和更多的样板代码(如果你认为错误是可恢复的)。
在Go中,惯用的错误处理方式虽然冗长,但比模拟异常要简洁一些(这不是“Go方式”)。
英文:
The idiomatic way would be to return the error with the associated type value, i.e.
func (list ...interface{}) (v int46, err error) {}
... and subsequently check err != nil where these functions are called.
Ultimately using panic() will result in Exception-like error handling and more boiler-plate code (if you deem the error to be recoverable).
Idiomatic error handling is verbose in Go, but less so than emulating exceptions (which is fundamentally not the "Go way").
答案3
得分: 0
在GitHub上,你会看到大多数代码,甚至是Go自己的库,都使用错误作为返回值,但这种方法往往会使你的代码变得臃肿和复杂。
在使用Go几年后,我找到了一种更好的方法来处理这个问题。
在你的代码中添加以下辅助函数:
func panicIfError(err error) {
if err != nil {
panic(err)
}
}
func getNiceError(panicError any) error {
stack := string(debug.Stack())
index := strings.LastIndex(stack, "panic")
if index != -1 {
stack = stack[index:]
index = strings.Index(stack, "\n")
if index != -1 {
stack = stack[index+1:]
}
}
return fmt.Errorf("%v\n%v", panicError, stack)
}
func recoverError(recoveredError *error) {
panicError := recover()
if panicError == nil {
recoveredError = nil
} else {
*recoveredError = getNiceError(panicError)
}
}
在你的代码中,只在中心位置返回错误,比如应用程序的HTTP服务器主入口来处理请求。其他所有函数不应返回错误,而是使用panic:
func f3() {
_, err := os.ReadFile("myfile.txt")
panicIfError(err)
}
在你想要捕获错误并处理的中心位置,创建一个返回错误的函数:
func centralHandlingFunc() (recoveredError error) {
defer recoverError(&recoveredError)
callToUnsafeFunc()
return recoveredError
}
因此,99%的代码都使用简单的panicIfError来处理错误。
这样你的代码会更简单、更易读。
不仅如此,在出现错误时,你还可以获得完整的错误堆栈跟踪,这极大地简化了错误调查。
我写了一篇关于这个方法的文章,你可以在这里阅读更多关于已知错误处理方法以及这个特定方法的详细信息:https://runkiss.blogspot.com/2022/09/go-error-handling.html
英文:
While most code you will see in GitHub, and even in Go's own libraries is using the error as return value, this method tend to inflate and complex your code.
After several years of using Go, I found a better way to do this.
Add the following helper functions in your code:
func panicIfError(err error) {
if err != nil {
panic(err)
}
}
func getNiceError(panicError any) error {
stack := string(debug.Stack())
index := strings.LastIndex(stack, "panic")
if index != -1 {
stack = stack[index:]
index = strings.Index(stack, "\n")
if index != -1 {
stack = stack[index+1:]
}
}
return fmt.Errorf("%v\n%v", panicError, stack)
}
func recoverError(recoveredError *error) {
panicError := recover()
if panicError == nil {
recoveredError = nil
} else {
*recoveredError = getNiceError(panicError)
}
}
In your code, return errors only in central locations, such as the application HTTP server main entry for handling requests. All other functions should not return errors, but instead panic:
func f3() {
_, err := os.ReadFile("myfile.txt")
panicIfError(err)
}
In the central location, where you want to catch the errors and handle it, create a function that does return error:
func centralHandlingFunc() (recoveredError error) {
defer recoverError(&recoveredError)
callToUnsafeFunc()
return recoveredError
}
So 99% of the code uses the simple panicIfError to handle errors.
This way your code is simpler, and more readble.
Not only that, you also get the full error stack trace in case of errors, which greatly simplifier errors investigation.
I have created a post about this, you can read more details about the known error handling methods, and about this specific method: https://runkiss.blogspot.com/2022/09/go-error-handling.html
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论