Go语言之异常处理
在编写Go语言代码的时候,我们应该习惯使用error类型值来表明非正常的状态。作为惯用法,在Go语言标准库代码包中的很多函数和方法也会以返回error类型值来表明错误状态及其详细信息。
error是一个预定义标识符,它代表了一个Go语言內建的接口类型。这个接口的类型声明如下:
type error interface{
Error() string
}
其中的Error方法声明的意义就在于为方法调用方提供当前错误状态的详细信息。任何数据类型只要实现了这个可以返回string类型值的Error方法就可以成为一个error接口类型的实现。不过在通常情况下,我们并不需要自己去编写一个error的实现类型。Go语言的标准库代码包errors为我们提供了一个用于创建errors类型值的函数New。该方法的声明如下:
func New(text string) error {
return &errorString{text}
}
errors.New函数接受一个string类型的参数值并可以返回一个error类型值。这个error类型值的动态类型就是errors.errorString类型。New函数的唯一参数被用于初始化那个errors.errorString类型的值。从代表这个实现类型的名称上可以看出,该类型是一个包级私有的类型。它只是errors包的内部实现的一部分,而非公开的API。errors.errorString类型及其方法的声明如下:
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
传递给errors.New函数的参数值就是当我们调用它的Error方法的时候返回的那个结果值。
我们可以使用代码包fmt中的打印函数打印出error类型值所代表的错误的详细信息,就像这样:
var err error = errors.New("A normal error.")
这些打印函数在发现打印的内容是一个error类型值的时候都会调用该值的Error方法并将结果值作为该值的字符串表示形式。因此,我们传递给errors.New的参数值即是其返回的error类型值的字符串表示形式。
另一个可以生成error类型值的方法是调用fmt包中的Errorf函数。调用它的代码类似于:
err2 := fmt.Errorf("%s\n", "A normal error.")
与fmt.Printf函数相同,fmt.Errorf函数可以根据格式说明符和后续参数生成一个字符串类型值。但与fmt.Printf函数不同的是,fmt.Errorf函数并不会在标准输出上打印这个生成的字符串类型值,而是用它来初始化一个error类型值并作为该函数的结果值返回给调用方。在fmt.Errorf函数的内部,创建和初始化error类型值的操作正是通过调用errors.New函数来完成。
在大多数情况下,errors.New函数和fmt.Errorf函数足以满足我们创建error类型值的要求。但是,接口类型error使得我们拥有了很大的扩展空间。我们可以根据需要定义自己的error类型。例如,我们可以使用额外的字段和方法让程序使用方能够获取更多的错误信息。例如,结构体类型os.PathError是一个error接口类型的实现类型。它的声明中包含了3个字段,这使得我们能够从它的Error方法的结果值当中获取到更多的信息。os.PathError类型及其方法的声明如下:
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
} func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
从os.PathError类型的声明上我们可以获知,它的3个字段都是公开的。因此,在任何位置上我们都可以直接通过选择符访问到它们。但是,在通常情况下,函数或方法中的相关结果声明的类型应该是error类型,而不应该是某一个error类型的实现类型。这也是为了遵循面向接口编程的原则。在这种情况下,我们常常需要先判定获取到的error类型值的动态类型,再依此来进行必要的类型转换和后续操作。例如:
file, err3 := os.Open("/etc/profile")
if err3 != nil {
if pe, ok := err3.(*os.PathError); ok {
fmt.Printf("Path Error: %s (op=%s, path=%s)\n", pe.Err, pe.Op, pe.Path)
} else {
fmt.Printf("Unknown Error: %s\n", err3)
}
}
我们通过类型断言表达式和if语句来对os.Open函数返回的error类型值进行处理。这与把error类型值作为结果值来表达函数执行的错误状态的做法一样,也属于Go语言中的异常处理的惯用法之一。
如果os.Open函数在执行过程中没有发生任何错误,那么我们就可以对变量file所代表的文件的内容进行读取了。相关代码如下:
r := bufio.NewReader(file)
var buf bytes.Buffer for {
byteArray, _, err4 := r.ReadLine()
if err4 != nil {
if err4 == io.EOF {
break
} else {
fmt.Printf("Read Error: %s\n", err4)
break
}
} else {
buf.Write(byteArray)
}
}
io.EOF变量正是由errors.New函数的结果值来初始化的。EOF是文件结束符(End Of File)的缩写。对于文件读取操作来说,它意味着读取器已经读到了文件的末尾。因此,严格来说,EOF并不应该算作一个真正的错误,而仅仅属于一种“错误信号”。
变量r代表了一个读取器。它的ReadLine方法返回3个结果值。第三个结果值的类型就是error类型的。当读取器读到file所代表的文件的末尾时,ReadLine方法会直接将变量io.EOF的值作为它的第三个结果值返回。如果判断的结果为true,那么我们就可以直接终止那个用于连续读取文件内容的for语句的执行。否则,我们就应该意识到在读取文件内容的过程中有真正的错误发生了,并采取相应的措施。
注意,只有当两个error类型的变量的值确实为同一个值的时候,使用比较操作符==进行判断时才会得到true。从另一个角度看,我们可以预先声明一些error类型的变量,并把它们作为特殊的“错误信号”来使用。任何需要返回同一类“错误信号”的函数或方法都可以直接把这类预先声明的值拿来使用。这样我们就可以很便捷的使用==来识别这些“错误信号”并进行相应的操作了。
不过,需要注意的是,这类变量的值必须是不可变的。也就是说,它们的实际类型的声明中不应该包含任何公开的字段,并且附属于这些类型的方法也不应该包含对其字段进行赋值的语句。例如,我们前面提到的os.PathError类型就不适合作为这类变量的值的动态类型,否则很可能会造成不可预知的后果。
这种通过预先声明error类型的变量为程序使用方提供便利的做法在Go语言标准库代码包中非常常见。
关于实现error接口类型的另一个技巧是,我们还可以通过把error接口类型嵌入到新的接口类型中对它进行扩展。例如,标准库代码包net中的Error接口类型,其声明如下:
// An Error represents a network error.
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
一些在net包中声明的函数会返回动态类型为net.Error的error类型值。在使用方,对这种error类型值的动态类型的判定方法与前面提及的基本一致。
如果变量err的动态类型是net.Error,那么就我们可以根据它的Temporary方法的结果值来判断当前的错误状态是否临时的:
if netErr, ok := err.(net.Error); ok && netErr.Temporary() { }
如果是临时的,那么就可以间隔一段时间之后再进行对之前的操作进行重试,否则就记录错误状态的信息并退出。假如我们没有对这个error类型值进行类型断言,也就无法获取到当前错误状态的那个额外属性,更无法决定是否应该进行重试操作了。这种对error类型的无缝扩展方式所带来的益处是显而易见的。
在Go语言中,对错误的正确处理是非常重要的。语言本身的设计和标准库代码中展示的惯用法鼓励我们对发生的错误进行显式的检查。虽然这会使Go语言代码看起来稍显冗长,但是我们可以使用一些技巧来简化它们。这些技巧大都与通用的编程最佳实践大同小异,或者已经或将要包含在我们所讲的内容(自定义错误类型、单一职责函数等)中,所以这并不是问题。况且,这一点点代价比传统的try-catch方式带来的弊端要小得多。
Go语言之异常处理的更多相关文章
- Go语言中异常处理painc()和recover()的用法
Go语言中异常处理painc()和recover()的用法 1.Painc用法是:用于抛出错误.Recover()用法是:将Recover()写在defer中,并且在可能发生panic的地方之前,先调 ...
- 第六十三课、C语言的异常处理
http://www.cnblogs.com/gui-lin/p/6379101.html 一.异常处理 1.异常的概念 (1).程序在运行过程中可能产生异常 (2).异常(Exception)和Bu ...
- c#基础语言编程-异常处理
异常的定义 异常就是程序中的运行时错误,当出现异常时,系统会捕获这个错误并抛出一个异常.若程序没有提供处理该异常的代码,系统会挂起这个程序. 常见异常的类型 System.Exception 最泛化的 ...
- [R]R语言里的异常处理与错误控制
之前一直只是在写小程序脚本工具,几乎不会对异常和错误进行控制和处理. 随着脚本结构和逻辑更复杂,脚本输出结果的准确性验证困难,同时已发布脚本的维护也变得困难.所以也开始考虑引入异常处理和测试工具的事情 ...
- 【C++】异常简述(一):C语言中的异常处理机制
人的一生会遇到很多大起大落,尤其是程序员. 程序员写好的程序,论其消亡形式无非三种:无疾而终.自杀.他杀. 当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要 ...
- C语言中的异常处理
一 前言: 异常处理,对于做面向对象开发的开发者来说是再熟悉不过了,例如在C#中有 try { ... } catch( Exception e){...} finally{ ..... } 在C++ ...
- C++异常处理
引言 异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题.而传统错误处理技术,检查到一个局部无法处理的问题时: 1.终止程序(例如atol,atoi ...
- java异常处理机制
本文从Java异常最基本的概念.语法开始讲述了Java异常处理的基本知识,分析了Java异常体系结构,对比Spring的异常处理框 架,阐述了异常处理的基本原则.并且作者提出了自己处理一个大型应用系统 ...
- C语言里面捕获错误机制
在C语言中异常处理一般有这么几种方式: 1.使用标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于<stdlib.h>头文件中. 2.使用asser ...
随机推荐
- C#中out的一种用法
1.当希望方法返回多个值时,声明out 方法很有用. 这样使方法可以有选择地返回值. using System; using System.Collections.Generic; using Sys ...
- oracle数据泵之解决方案(用户)导入导出。
看到网上有这样的介绍而且很多,但觉得都是大神才能一下子看的懂.自己总结下菜鸟能看懂的. 1.导出. 首先第一步: 操作系统—开始—运行输入“cmd”进入dos界面输入“sqlplus/nolog”按回 ...
- 用法总结:NSArray,NSSet,NSDictionary-备用
Foundation framework中用于收集cocoa对象(NSObject对象)的三种集合分别是: NSArray 用于对象有序集合(数组)NSSet 用于对象无序集合 (集合)NS ...
- LIBSVM之一
libSVM简单的介绍 libSVM是台湾林智仁(Chih-Jen Lin) 教授2001年开发的一套支持向量机库,这套库运算速度挺快,可以很方便的对数据做分类或回归.由于libSVM程序小,运用灵活 ...
- Entity Framework with MySQL 学习笔记一(拦截)
参考 : http://msdn.microsoft.com/en-us/data/dn469464.aspx EF 允许我们在发送SQL请求和返回数据时做一些拦截的动作 比如可以自定义写 log , ...
- poj 3335 Rotating Scoreboard
http://poj.org/problem?id=3335 #include <cstdio> #include <cstring> #include <algorit ...
- AD怎样画 board shape
先用随便那一层的线,画出你想要的边框的形状,圆角可通过shift+空格来切换出来选中你刚刚话的形状(要是闭合面),design-board sharp-define from selected obj ...
- C++代码覆盖率工具Coverage Validator
市面上的C++代码覆盖率工具大都收费,Coverage Validator也不例外.Coverage Validator应该少有人听过,我也是在stackoverflow里听别人介绍的.所以下载了试用 ...
- 【转】用串口登录Beaglebone Black、用usb共享电脑网络、内核模块的本地编译
原文网址:http://bbs.eeworld.com.cn/thread-431507-1-1.html 串口连接BBB使用usb线可以连接BBB和电脑,用ssh就可以登录BBB来进行操作.但有时候 ...
- delphi7调用webservice Java 传入参数为空
在delphi7中,new-webservices-wsdl importer中输入wsdl地址,会自动生成wsdl单元代码.在调用时,传入参数到服务器端时为空了. 网上说缺少 InvRegistry ...