在编写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语言之异常处理的更多相关文章

  1. Go语言中异常处理painc()和recover()的用法

    Go语言中异常处理painc()和recover()的用法 1.Painc用法是:用于抛出错误.Recover()用法是:将Recover()写在defer中,并且在可能发生panic的地方之前,先调 ...

  2. 第六十三课、C语言的异常处理

    http://www.cnblogs.com/gui-lin/p/6379101.html 一.异常处理 1.异常的概念 (1).程序在运行过程中可能产生异常 (2).异常(Exception)和Bu ...

  3. c#基础语言编程-异常处理

    异常的定义 异常就是程序中的运行时错误,当出现异常时,系统会捕获这个错误并抛出一个异常.若程序没有提供处理该异常的代码,系统会挂起这个程序. 常见异常的类型 System.Exception 最泛化的 ...

  4. [R]R语言里的异常处理与错误控制

    之前一直只是在写小程序脚本工具,几乎不会对异常和错误进行控制和处理. 随着脚本结构和逻辑更复杂,脚本输出结果的准确性验证困难,同时已发布脚本的维护也变得困难.所以也开始考虑引入异常处理和测试工具的事情 ...

  5. 【C++】异常简述(一):C语言中的异常处理机制

    人的一生会遇到很多大起大落,尤其是程序员. 程序员写好的程序,论其消亡形式无非三种:无疾而终.自杀.他杀. 当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要 ...

  6. C语言中的异常处理

    一 前言: 异常处理,对于做面向对象开发的开发者来说是再熟悉不过了,例如在C#中有 try { ... } catch( Exception e){...} finally{ ..... } 在C++ ...

  7. C++异常处理

    引言 异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题.而传统错误处理技术,检查到一个局部无法处理的问题时: 1.终止程序(例如atol,atoi ...

  8. java异常处理机制

    本文从Java异常最基本的概念.语法开始讲述了Java异常处理的基本知识,分析了Java异常体系结构,对比Spring的异常处理框 架,阐述了异常处理的基本原则.并且作者提出了自己处理一个大型应用系统 ...

  9. C语言里面捕获错误机制

    在C语言中异常处理一般有这么几种方式: 1.使用标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于<stdlib.h>头文件中. 2.使用asser ...

随机推荐

  1. 标签static

    静态文本控件的功能比较简单,可作为显示字符串,图标,位图用.创建一个窗口可以使用成员函数: BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, ...

  2. 05 - 替换vtkDataObject中的Update方法 VTK 6.0 迁移

    VTK6 引入了许多不兼容的变.其中之一是删除vtkDataObject中所有有关管道的方法.下面讨论update方法并提供迁移现有代码的建议. Update() vtkDataObject::Upd ...

  3. 自己封装的HttpRequest,个人觉的比较HttpHelper好用

    新年开篇,忙归忙,还是要写点什么,不然越来越懒,分享我写的HttpTooler public delegate void RequestCompleted(object sender, string ...

  4. DBUtils框架

    一.O-R Mapping 简介    一]概念:可以理解为对象和数据库的映射.    二]常用O-R Mapping映射工具        1)Hibernate(全自动框架)        2)l ...

  5. log4jdbc

    log4jdbc http://www.blogjava.net/badqiu/archive/2010/08/20/329464.html http://blog.csdn.net/sfdev/ar ...

  6. 2015第28周六SVN和Git

    svn作为一个优秀源码版本的管理工具,可以适合绝大多数项目.但是因为它的采用中心化管理,不可避免的存在本地代码的备份和版本管理问题.也就是说对于尚未或暂无法提交到Subversion服务器的本地代码来 ...

  7. HDOJ 1236 排名(练耐心题)

    Problem Description 今天的上机考试虽然有实时的Ranklist,但上面的排名只是根据完成的题数排序,没有考虑 每题的分值,所以并不是最后的排名.给定录取分数线,请你写程序找出最后通 ...

  8. Android豆瓣图书查询Demo

    原文出自:方杰| http://fangjie.info/?p=26 转载请注明出处 首先先看一下Demo预览效果吧,主要也就是两个Activity.涉及到的技术有zxing开源项目的使用,网络协议豆 ...

  9. AddForce给物体添加刚体效果并且脚本增加一个力(按空格实现)

    using UnityEngine; using System.Collections; public class CubeAddForce : MonoBehaviour { float hor,v ...

  10. linux权限及ntfs文件系统权限的知识

    关于ntfs权限的问题 文件的权限: [-dcbps][u:rwx][g:rwx][a:rwx] 当中: r=4, w=2, x=1,  u=owner, g=group, a=all user   ...