前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go中没有try/catch,该如何处理错误?

Go中没有try/catch,该如何处理错误?

作者头像
Go学堂
发布2023-01-31 15:13:43
5240
发布2023-01-31 15:13:43
举报
文章被收录于专栏:Go工具箱

在Go语言中,没有像其他语言那样提供try/catch方法来处理错误。然而,Go中是将错误作为函数返回值来返回给调用者的。下面详细讲解Go语言的错误处理方法。

在Go中,当程序遇到错误时,不像其他语言那样会终止运行。而是将错误作为是一个普通的值从函数中返回,让调用者根据函数的返回值来进行处理。由源码可知,error是Go中一个内建的数据类型,默认值是nil。一般作为函数返回值列表的最后一个返回,由调用者检查是否是nil。类似这样:

代码语言:javascript
复制
val, err := myFunction(args...)
if err != nil {
  //处理错误
}else {
  //运行正常的代码
}

让我们来看下error在源码中的定义:

代码语言:javascript
复制
type error interface {
  Error() string
}

原来,error实际上就是一个interface类型,并定义了一个返回字符串的Error方法。即所有实现了Error方法的类型都可以作为错误类型。我们自定义一个错误类型:

代码语言:javascript
复制
package main
import "fmt"

type MyError struct{}

func (myErr *MyError) Error() string {
  return "Something unexpected happed!"
}

func main() {
  myErr := &MyError()
  
  fmt.Println(myErr)
}

以上代码中,我们定义了一个MyError结构体,实现了Error方法,这样MyError就成了一个错误类型。

但是,这样每次我们都需要创建一个结构体,并且实现Error方法,是不是有点复杂?Go的开发者们为了避免复杂的创建, 在errors包中提供了New函数来快速创建错误类型。如下:

代码语言:javascript
复制
package main

import (
  "fmt"
  "errors"
)

func main() {
  //这里通过调用New函数快速创建了一个myErr类型
  myErr := errors.New("Someting unexpected happend!")
  
  fmt.Println(myErr)
}

再来看下errors包中New方法的源码定义,很简单,就是定义了一个errorString结构体,实现了Error方法。整个包的代码如下:

代码语言:javascript
复制
package errors

func New(text string) error {
  return &errorString{text}
}

type errorString struct {
  s string
}

func (e *errorString) Error() string {
  return e.s
}

error信息的实际应用

在真实的项目中,我们不仅仅只需要一个字符串信息,而是需要更多的信息来帮助我们程序发生了什么。

下面以HTTP请求返回错误(状态码非200)为例来来讲解。当我们处理HTTP请求时,需要知道HTTP的状态码是什么以及如何处理。如下:

代码语言:javascript
复制
type ErrorCodeHandle struct {
  StatusCode int
  Method string
  Handler func(context.Context)
}

func (err *ErrorCodeHandle) Error() string {
  return fmt.Sprintf("Something went wrong with the method-%v request. Server returned StatusCode-%v.", err.Method, err.StatusCode)
}

func GetUserEmail(userId int) (string, error) {
  
  //请求失败
  return "", &ErrorCodeHandle{404, "GET"}
}

func main() {
  if email, err := GetUserEmail(1); err != nil {
    fmt.Println(err)
    
    //这里使用了类型断言,因为凡是实现了Error方法的struct都属于error类型
    if errVal := err.(ErrorCodeHandle); errVal.Status == 404 {
      fmt.Println("Not Found")
      err.Handle(context.Background())
    }else {
      //没有错误,函数调用成功
      fmt.Println("User email is:", email)
    }
  }
}

让我们来解析下上述代码:

  • 我们自定义了ErrorCodeHandle类型,并实现了Error方法
  • GetUserEmail模拟返回404错误
  • 在main函数中,调用GetUserEmail函数,并对err进行了类型断言,判断是否是ErrorCodeHandle类型,以便进一步获取该结构体中的属性

当函数返回的错误属于不同的错误类型时,可以使用switch.. case语句进行判断。如下:

代码语言:javascript
复制
type NetworkErr struct {}
  
  func (e *NetworkError) Error() string {
    return "A network connection was aborted"
  }
  
  type FileSaveFailedError struct {}
  
  func (e *FileSaveFailedError) Error() string {
    return "The request file could not be saved"
  }
  
  func saveFileToRemote() error {
    result := 2 //模拟保存操作的结果
    if result == 1 {
      return &NetworkError{}
    }else if result == 2 {
      return &FileSaveFailedError{}
    }else {
      return nil
    }
  }
  
  func main() {
    //检查错误类型:err.(type)
    switch err := saveFileToRemote(); err.(type) {
      case nil:
        fmt.Println("File successfully saved")
      case *NetworkError:
        fmt.Println("Network Error:", err)
      case *FileSaveFailedError:
        fmt.Println("File save Error:", err)
    }
  }

好了,现在来总结一下:

  • error是一个接口类型,默认值是nil,
  • 一般作为函数的最后一个返回值返回,由调用者处理错误
  • 在调用者中判断错误的时候,需要用类型断言判断error的类型,再做后续处理。因为凡是实现了该接口中Error方法的类型都可以作为自定义的错误类型。
  • 在实现了error接口的数据类型中,可以自定义上下文信息,以帮助调用者获取更多的信息
  • 因为是数据类型,所以可以自定义方法来获取想要的错误信息,而非直接调用类型属性

一些建议

1. 对错误进行处理

有一种方式可以忽略错误,就是用下划线接收返回值。

代码语言:javascript
复制
val, _ := someFunctionWhichCanReturnAnError()

像上面代码就忽略了错误。即使没有获取错误或者错误不重要,这将对后续代码导致级联的影响。所以,强烈建议在可能的情况下都要处理错误。

2. 不要单纯将错误返回,添加上下文信息

代码语言:javascript
复制
func someFunction() error {
  val, err := someFunctionWhichCanReturnAnError()
  
  if err != nil {
    return err
  }
  //处理其他逻辑
}

以上代码中,在遇到错误时就是简单的把错误返回了,这导致调用者不知道该错误来源于哪里。因此,较好的方式是将该错误进一步封装,添加更多的上下文信息。例如可以使用errors包中的Wrap方法来给错误增加上说明。

3. 避免重复处理错误

当处理日志的时候,可能会把日志记录到日志文件汇总。以下代码就是重复记录了2次日志。

代码语言:javascript
复制
func someFunction() error {
  if err != nil {
    //记录err日志
    return err
  }
}


func someOtherFunction() error {
  val, err := someFunction()
  if err != nil {
    //记录err日志
    return err
  }
}

因此,最好的做法是在最开始的调用者那里记录日志

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go学堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档