Dan*_*n W 5 error-handling go go-interface
我不确定如何确切地表达这个问题,而且我看到其他人提出了类似的问题,但没有真正给出答案(这告诉我我问了错误的问题,但我不知道如何解决这个问题)。
我正在尝试学习一些基本的 Go,但在第一个障碍上就遇到了困难。
在我的测试代码中,我对一个不存在的域执行基本的 http GET 操作,以触发 DNS 警告。我发现 err.error() 返回一个字符串,因此为了断言它是否是 DNS 错误,我使用了字符串比较:
resp, err := http.Get(link)
if err != nil {
if strings.Contains(err.Error(), "no such host") == true {
return "no such host"
}
}
Run Code Online (Sandbox Code Playgroud)
这显然是很hacky,所以我做了一些谷歌搜索,看看是否有更好的方法来找出引发了什么样的错误,我发现了以下答案:
包“errors”具有 As 功能,用于解包特定的错误类型,包“net”具有 *DNSError 类型。所以:
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
...
}
Run Code Online (Sandbox Code Playgroud)
这段代码有效,但我完全不知道这个结论是如何得出的。以下是我试图理解这一点的方法,我想知道我哪里出错了。我(模糊地)理解 .Is() 和 .As() 正在做什么,但我不明白的是如何计算出什么错误“类型”来提供这些函数而无需猜测或先验知识。
我查看了 client.get() 文档,其中写着:
任何返回的错误都将是 *url.Error 类型。
更多的谷歌搜索,我发现我需要将错误转换为该类型才能使用它:
urlErr := err.(*url.Error)
Run Code Online (Sandbox Code Playgroud)
*url.Error 包含:
&url.Error{Op:"Get", URL:"http://doesnotexistkjdfhgsdfsdf.com", Err:(*net.OpError)(0xc00021a2d0)}
Run Code Online (Sandbox Code Playgroud)
所以我然后查看 url.Error 中包含的 net.OpError:
netOpError := urlErr.Err.(*net.OpError)
fmt.Printf("Net Op Error contains: %#v\n", netOpError)
---
Net Op Error contains: &net.OpError{Op:"dial", Net:"tcp", Source:net.Addr(nil), Addr:net.Addr(nil), Err:(*net.DNSError)(0xc0001a0040)}
Run Code Online (Sandbox Code Playgroud)
然后我做同样的事情并“解压”net.OpError 中包含的 net.DNSError:
dnsError := netOpError.Err.(*net.DNSError)
fmt.Printf("DNSError contains: %#v\n", dnsError)
---
DNSError contains: &net.DNSError{Err:"dial udp 169.254.169.254:53: connect: no route to host", Name:"doesnotexistkjdfhgsdfsdf.com", Server:"169.254.169.254:53", IsTimeout:false, IsTemporary:true, IsNotFound:false}
Run Code Online (Sandbox Code Playgroud)
net.DNSError 不“包含”任何其他错误,所以对我来说,这表明它是链的底部和“真正的”错误(或者至少是我想要处理的错误)。
事实是,这不是一个可行的方法,我不明白我们应该如何处理这个问题。在我发现最初的 SO 文章之前,我不知道 net.DNSError 是什么,也不知道我的错误可能是那种“类型”。
如果您不知道存在特定的错误类型,并且函数调用可能属于该类型,您怎么知道?
我对 Go 中的接口和类型的了解非常有限,我确信这在这里没有帮助,但对我来说,出现错误和知道要检查的错误类型之间似乎存在巨大的飞跃是。我希望这个问题有意义!
首先,我建议阅读这篇博文: https: //go.dev/blog/go1.13-errors
对您问题的简短回答:您是对的,要检查函数是否返回net.DNSError并访问其内部,您可以使用errors.As函数:
rsp, err := http.Get(link)
dnsErr := new(net.DNSError)
if errors.As(err, &dnsErr) {
// use dnsErr here
}
Run Code Online (Sandbox Code Playgroud)
更新:从概念上讲,假设您知道可以处理的错误类型:因此,如果您可以处理某些特定错误,则可以处理它,也可以将其包装并将其返回到上层。在使用其他语言处理错误/异常时,这也是一种常见的做法。所以我的建议是:仅处理您知道如何处理的异常。对于 HTTP 请求,通常是带有状态代码的 http 错误,DNS 错误通常返回给调用者 func。
这是包中函数示例的一些详细信息errors。
您可以使用errors.As和errrors.Is来自错误包。例如,如果您有自定义类型的错误:
type myError struct {
code int
}
Run Code Online (Sandbox Code Playgroud)
您可以通过参考检查未知错误errors.Is():
var fooErr = &myError{1}
func foo() (int, error) {...}
func main() {
_, err := foo()
fmt.Printf("is fooErr? %v\n", errors.Is(err, fooErr))
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您想实现自定义逻辑来比较您的错误(例如,code在本例中),您可以将Is方法添加到您的错误类型:
func (e *myError) Is(err error) bool {
as := new(myError)
// I'll show As method later
if !errors.As(err, &as) {
return false
}
return e.code == as.code
}
Run Code Online (Sandbox Code Playgroud)
此外,Is方法可以从包装类型中“解包”错误,例如,如果您想创建错误组合,您可以添加一个带有Unwrap方法的新结构,或者使用带有参数fmt.Errorf的方法:%w
type errWrap struct {
origin error
}
func (e *errWrap) Unwrap() error {
return e.origin
}
func (e *errWrap) Error() string {
return fmt.Sprintf("wraps error '%s'", e.origin.Error())
}
var fooErr = &myError{1}
func foo() (int, error) {
return 0, &errWrap{origin: fooErr}
}
func main() {
_, err := foo()
fmt.Printf("err == myError? %v\n", err == fooErr) // false
fmt.Printf("err is fooErr? %v\n", errors.Is(err, &myError{1})) // true
}
Run Code Online (Sandbox Code Playgroud)
或者使用fmt.Errorf:
err := fmt.Errorf("wraps: %w", fooErr)
fmt.Printf("err == myError? %v\n", err == fooErr) // false
fmt.Printf("err is fooErr? %v\n", errors.Is(err, &myError{1})) // true
Run Code Online (Sandbox Code Playgroud)
另一个重要的方法是errors.As,它的工作原理类似:您要么要检查错误的确切类型,要么对As错误实现方法,或者错误被另一个错误包裹:
err := &myError{1}
as := new(myError)
errors.As(err, &as)
fmt.Printf("code: %v\n", as.code) // code: 1
Run Code Online (Sandbox Code Playgroud)
或者用As方法:
type anotherError struct {
anotherCode int
}
func (e *anotherError) Error() string {
return fmt.Sprintf("code: %d", e.anotherCode)
}
func (e *myError) As(target interface{}) bool {
if out, ok := target.(**anotherError); ok {
(*out).anotherCode = e.code
return true
}
return false
}
func main() {
err := &myError{1}
as := new(anotherError)
errors.As(err, &as)
fmt.Printf("code: %v\n", as.anotherCode) // code: 1
}
Run Code Online (Sandbox Code Playgroud)
包装也一样:
err := fmt.Errorf("wraps: %w", &myError{1})
as := new(myError)
errors.As(err, &as)
fmt.Printf("code: %v\n", as.code) // code: 1
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6860 次 |
| 最近记录: |