diff --git a/11.1.md b/11.1.md index bb8460d1..490d6548 100644 --- a/11.1.md +++ b/11.1.md @@ -1,16 +1,16 @@ # 11.1 错误处理 -Go语言设计的时候主要的特点是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐式的东西,Go在设计错误的时候也是一样。我们知道C语言里面返回错误是使用-1或者nil之类的返回信息表示错误,但是对于使用者来说这个返回值根本不知道什么意思,而Go里面当发生异常时返回一个Error类型,通过前面编写的代码,我们发现在Go语言里面有很多地方都使用了Error类型,很多函数都有这个Error返回。例如`os.Open`函数当打开文件失败时返回一个不为nil的error +Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如`os.Open`函数在打开文件失败时将返回一个不为nil的error变量 func Open(name string) (file *File, err error) -下面这个例子通过`os.Open`打开一个文件,如果出错那么会执行`log.Fatal`打印出来错误信息: +下面这个例子通过调用`os.Open`打开一个文件,如果出现错误,那么就会调用`log.Fatal`来输出错误信息: f, err := os.Open("filename.ext") if err != nil { log.Fatal(err) } -其实这样的error返回在Go语言的很多内置包里面有很多,我们这个小节将详细的介绍这些error是怎么设计的,以及在我们设计的Web应用如何更好的处理error。 +类似于`os.Open`函数,标准包中所有可能出错的API都返回了一个error变量,以方便错误处理,这个小节将将详细的介绍error类型的设计,以及在设计的Web应用中如何更好的处理error。 ## Error类型 error类型是一个接口类型,这是它的定义: @@ -28,7 +28,7 @@ error是一个内置的类型变量,我们可以在/builtin/包下面找到相 func (e *errorString) Error() string { return e.s } -你可以通过`errors.New`把一个字符串转化为errorString,然后返回error接口,其内部实现如下: +你可以通过`errors.New`把一个字符串转化为errorString,以得到一个满足接口error的对象,其内部实现如下: // New returns an error that formats as the given text. func New(text string) error { @@ -44,7 +44,7 @@ error是一个内置的类型变量,我们可以在/builtin/包下面找到相 // implementation } -我们在调用Sqrt的时候传递一个负数,然后返回一个non-nil的值,那么我们就可以根据判断调用fmt.Println打印出来错误,fmt包在处理error时会调用Error方法打印出来错误信息,请看下面调用的示例代码: +在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码: f, err := Sqrt(-1) if err != nil { @@ -52,7 +52,7 @@ error是一个内置的类型变量,我们可以在/builtin/包下面找到相 } ## 自定义Error -通过上面的介绍我们知道error是一个interface定义,那么在一些我们自定义的包里面我们可以实现这接口定义,从而实现自己的错误定义,请看下面Json包中定义的自定义错误这个例子: +通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例: type SyntaxError struct { msg string // 错误描述 @@ -71,7 +71,7 @@ offset字段在调用Error的时候不会被打印,但是我们可以通过类 return err } -上面这个例子简单的演示了如何自定义Error处理。error接口只有实现Error方法就可以实现,但是如果我们还有其他需要实现的方法呢?我们可以参考一下net包下面的错误定义: +上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法: package net @@ -81,7 +81,7 @@ offset字段在调用Error的时候不会被打印,但是我们可以通过类 Temporary() bool // Is the error temporary? } -这样我们在调用的地方可以通过类型断言判断是否是net.Error,然后根据错误的类型判断如何处理,例如下面的例子,如果一个网络发生临时性错误,那么可以sleep几秒之后重试: +在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试: if nerr, ok := err.(net.Error); ok && nerr.Temporary() { time.Sleep(1e9) @@ -92,7 +92,7 @@ offset字段在调用Error的时候不会被打印,但是我们可以通过类 } ## 错误处理 -Go语言里面错误处理是非常重要的,Go语言设计鼓励大家在发生错误时进行检测,而不是像其他语言那样抛出异常,虽然每个错误处理都写的话会显得代码很长,但是我们通过检测函数可以复用这些错误处理逻辑。 +Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。 请看下面这个例子代码: @@ -113,7 +113,7 @@ Go语言里面错误处理是非常重要的,Go语言设计鼓励大家在发 } } -上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码。 +上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。 type appHandler func(http.ResponseWriter, *http.Request) error @@ -176,10 +176,10 @@ Go语言里面错误处理是非常重要的,Go语言设计鼓励大家在发 return nil } -如上所示,这样在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个现实的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。 +如上所示,在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个显示的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。 ## 总结 -错误处理在Go语言是相当重要的,error虽然只是一个接口,但是他的变化可以很多,我们可以根据自己的需求来实现不同程度的处理,而且自定义错误处理也是相当的方便,最后介绍的错误处理机制也是给大家提供了一个如何友好的提示错误,并且如何做到更好的扩展性的一种设计,对于我们后期的运维,错误定位都起到了相当好的作用。 +在程序设计中,容错是相当重要的一部分工作,在Go中它是通过错误处理来实现的,error虽然只是一个接口,但是其变化却可以有很多,我们可以根据自己的需求来实现不同的处理,最后介绍的错误处理方案,希望能给大家在如何设计更好Web错误处理方案上带来一点思路。 ## links * [目录]() diff --git a/11.3.md b/11.3.md index 5c7c56fb..920c90a7 100644 --- a/11.3.md +++ b/11.3.md @@ -1,14 +1,46 @@ -# 11.3 Go怎么写测试用例 - -## 如何编写测试用例 - - func TestXXX(t *testing.T) { ... } - -## 如何编写压力测试 - - func BenchmarkXXX(b *testing.B) { ... } - -## links - * [目录]() - * 上一节: [使用GDB调试](<11.2.md>) +# 11.3 Go怎么写测试用例 +开发程序其中很重要的一点是测试,我们如何保证代码的质量,如何保证每个函数是可运行,运行结果是正确的,又如何保证写出来的代码性能是好的,我们知道单元测试的重点在于发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让线上的程序能够在高并发的情况下还能保持稳定。本小节将带着这一连串的问题来讲解Go语言中如何来实现单元测试和性能测试。 + +Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`命令来实现单元测试和性能测试,`testing`框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,那么接下来让我们一一来看一下怎么写。 + +## 如何编写测试用例 +由于`go test`命令只能在一个相应的目录下执行所有文件,所以我们接下来新建一个项目目录`gotest`,这样我们所有的代码和测试代码都在这个目录下。 + +接下来我们再改目录下面创建两个文件:gotest.go和gotest_test.go + +1. gotest.go:这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算: + + package gotest + + import ( + "errors" + ) + + func intdiv(a, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("除数不能为0") + } + + return a / b, nil + } + +2. gotest_test.go:这是我们的单元测试文件,但是记住下面的这些原则: + + - 文件名必须是`_test.go`结尾的,这样在执行`go test`的时候才会执行到相应的代码 + - 包名必须和被测试文件的包名一致,例如上面的包名是gotest,那么test文件的包名也必须是gotest + - 你必须import `testing`这个包 + - 所有的测试用例函数必须是`Test`开头 + - 测试用例会按照源代码中写的顺序依次执行 + - 测试函数`TestXxx()`的参数是`testing.T`,我们可以使用该类型来记录错误或者是测试状态 + - 测试格式:`func TestXxx (t *testing.T)`,`Xxx`部分可以为任意的字符串组合,但是首字母不能是小写字母[a-z],例如`Testintdiv`是错误的函数名。 + - `testing.T` + + +## 如何编写压力测试 + + func BenchmarkXXX(b *testing.B) { ... } + +## links + * [目录]() + * 上一节: [使用GDB调试](<11.2.md>) * 下一节: [小结](<11.4.md>) \ No newline at end of file