Go语言主要的设计准则是:简洁、明白。
简洁是指语法和C类似,相当的简单;明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。
错误处理是学习任何编程语言都需要考虑的一个重要话题。以前学习C语言时,对于错误的处理就是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功还是失败。后来,许多编程语言会在语言层面上增加错误处理的支持,比如java的异常(exception)的概念和try-catch关键字的引入。
Go的简洁性有一点常被人说道,就是Go的25个关键字。所以Go在错误处理上没有增加关键字,而是定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。而且Go的多值返回可以让我们在返回一个常规的返回值之外,还能轻易地返回一个详细的错误描述。
上面讲的Go对于错误处理的方式,在Go的许多类库中都可以发现。这里看下下面的例子。
package main
import (
"fmt"
"strconv"
)
func main() {
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
}
strconv.Atoi是一个将字符串转为int的方法。上面的代码直接执行结果如下:
如果将代码改成:
i, err := strconv.Atoi("abc")
看下Atoi函数的定义,在strconv/atoi.go源文件下:
// Atoi is shorthand for ParseInt(s, 10, 0).
func Atoi(s string) (i int, err error) {
i64, err := ParseInt(s, 10, 0)
return int(i64), err
}
可以看出,这里就是返回一个常规的返回值之外,多返回了一个详细的错误描述。
这里的error是Go的一个内置的接口类型,我们可以在$GOROOT\src\builtin下面找到相应的定义。
type error interface {
Error() string
}
而我们在很多内部包里面用到的 error是errors包(位置$GOROOT\src\errors)里实现的私有结构errorString.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
通过上面的源码了解到,errors包还实现了一个New()方法。让我们可以通过errors.New把一个字符串转化为errorString,以得到一个满足接口error的对象。至于New()方法如何使用,将上面的例子稍微做下修改如下:
package main
import (
"errors"
"fmt"
"strconv"
)
func main() {
i, err := strconv.Atoi("abc")
if err != nil {
//使用New()显示错误信息
fmt.Println(errors.New("couldn't convert number"))
return
}
fmt.Println("Converted integer:", i)
}
通过上面的介绍我们知道error是一个interface,所以通过定义实现此接口的结构,我们就可以实现自己的错误定义。
Go接口的概念和其他变成语言差不多,不过更具灵活性。使用时不需要从接口继承或者像Java一样需要使用implements来明确指定类型和接口之间的关系。
下面代码实现了一个自定义的异常.
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
type error interface {
Error() string
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当被除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
以上只是异常的使用方式,可是对于异常的处理要贯穿到程序的整个设计和开发过程,知道了方法,还得不断培养意识才可以.
还有一点,这里引入了Go的接口的概念,一开始我也是有些不明白,因为Go的接口和像java的面向对象的里的接口含义区别还是蛮大的。留在后面去探索了。