文档章节

Go笔记-错误处理和defer

漂泊尘埃
 漂泊尘埃
发布于 2017/02/27 17:05
字数 2053
阅读 8
收藏 0
点赞 0
评论 0

error

error类型的声明可在builtin包中查看:

type error interface {
    Error() string
}

如果直接使用这个,我们需要声明一个结构体然后实现Error()方法,常用的是errors包中的New():

// 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
}

Go中error类型的nil值和nil

问题描述

func retErr() error {
	var r *MyError = nil
	if err() {
		r = &MyError{}
	}
	return r
}
func main() {
	err := retErr()
	if err != nil {
		fmt.Println("error:", err)
	} else {
		fmt.Println("no error.")
	}
}

这里retErr()返回的err并不是nil.

在底层,interface作为两个成员实现:一个类型和一个值。该值被称为接口的动态值, 它是一个任意的具体值,而该接口的类型则为该值的类型。

只有在内部值和类型都未设置时(nil, nil),一个接口的值才为 nil。特别是,一个 nil 接口将总是拥有一个 nil 类型。若我们在一个接口值中存储一个 int 类型的指针,则内部类型将为 int,无论该指针的值是什么:(*int, nil)。 因此,这样的接口值会是非 nil 的,即使在该指针的内部为 nil。

所以,上面的err是一个有效值(非nil),值为 nil。

处理方式一

retErr()在返回值时,如果要返回nil,就直接return nil。

处理方式二

main()在判断时这样:

if err.(*MyError) != nil

返回错误信息

import (
	"errors"
	"fmt"
)

func main() {
	ret, err := error_test(false)
	if err != nil {
		fmt.Println("err is ", err, "ret is ", ret)
	} else {
		fmt.Println("ret is ", ret, "err is ", err)
	}
}

func error_test(flag bool) (ret string, err error) {
	if flag {
		err = errors.New("error test")
		return
	} else {
		return "success", nil
	}
}

若传递true,结果为err is error test ret is

若传递false,结果为ret is success err is <nil>

自定义错误类型

Go语言引入了一个关于错误处理的标准模式,即error接口,该接口的定义如下:

type error interface{ 
	Error() string
} 

下面红色部分代码参考src\pkg\os\error.go

import (
	"errors"
	"fmt"
)

func main() {
	err := my_error()
	if err != nil {
		fmt.Println("err is ", err)
	} else {
		fmt.Println("err is nil")
	}
}
func my_error() error {
	_, err := error_test(true)
	if err != nil {
		return &PathError{"test", "path", err}
	} else {
		return nil
	}
}

func error_test(flag bool) (ret string, err error) {
	if flag {
		err = errors.New("error test")
		return
	} else {
		return "success", nil
	}
}

type PathError struct {
	Op   string
	Path string
	Err  error
}

func (e *PathError) Error() string {
	return e.Op + " " + e.Path + ": " + e.Err.Error()
}

defer 延迟函数

它的作用是:延迟执行,在声明时不会立即执行,而是在函数return后时按照后进先出的原则依次执行每一个defer。这样带来的好处是,能确保我们定义的函数能百分之百能够被执行到,这样就能做很多我们想做的事,如释放资源,清理数据,记录日志等

func CopyFile(dst, src string) (w int64, err error) {
	srcFile, err := os.Open(src)
	if err != nil {
		return
	}
	defer srcFile.Close()
	dstFile, err := os.Create(dstName)
	if err != nil {
		return
	}
	defer dstFile.Close()
	return io.Copy(dstFile, srcFile)
}

即使其中的Copy()函数抛出异常,Go仍然会保证dstFile和srcFile会被正常关闭。

如果觉得一句话干不完清理的工作,也可以使用在defer后加一个匿名函数的做法:

defer func() {
	// 做你复杂的清理工作
}()

defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行


下面说明一下defer执行的顺序

func deferFunc() int {
	index := 0

	fc := func() {

		fmt.Println(index, "匿名函数1")
		index++

		defer func() { 
			fmt.Println(index, "匿名函数1-1")
			index++
		}()
	}

	defer func() { 
		fmt.Println(index, "匿名函数2")
		index++
	}()

	defer fc()

	return func() int {
		fmt.Println(index, "匿名函数3")
		index++
		return index
	}()
}

func main() {
	ret := deferFunc()
	fmt.Println("main() :", ret)
}

执行结果:

0 匿名函数3
1 匿名函数1
2 匿名函数1-1
3 匿名函数2
main() : 1
  1. defer是在执行完return后执行
  2. return的结果并没有随着index递增
  3. 按照先进后出的顺序执行

返回语句之后的defer不会执行

func main() {
	i := true

	defer func() {
		fmt.Println("f1")
	}()

	defer func() {
		fmt.Println("f2")
	}()

	if i {
		return
	}

	defer func() {
		fmt.Println("f3")
	}()
}

输出: f2 f1,没有f3


被延期执行的函数,它的参数(包括接收者,如果函数是一个方法)是在defer执行的时候被求值的,而不是在调用执行的时候。这样除了不用担心变量随着函数的执行值会改变,这还意味着单个被延期执行的调用点可以延期多个函数执行。这里有一个简单的例子

for i := 0; i < 3; i++ {
	defer fmt.Print(i, " ")
}

输出:2 1 0


验证defer的函数的参数是在defer定义的地方被求值的

func enter(s string) string {
	fmt.Println("entering ", s)
	return s
}
func leave(s string) {
	fmt.Println("leaving ", s)
}
func a() {
	defer leave(enter("a"))
	fmt.Println("in a...")
	b()
}
func b() {
	defer leave(enter("b"))
	fmt.Println("in b...")
}

main()中调用a()

输出:

entering  a
in a...
entering  b
in b...
leaving  b
leaving  a

被延迟的函数是leave,它的参数是enter的返回值,所以在定义defer的地方先求了enter的返回值,上面defer的结果等同于下面的语句:

enter(“a”)
defer leave(“a”)

defer中改变返回值

	f := func() (ret int) {
		defer func() {
			ret++
		}()
		return 1
	}
	v := f()
	fmt.Println(v)

输出2.

panic和recover

func panic(interface{}) 
func recover() interface{}

当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程

panic类似于抛出异常,recover类似于捕获异常

假如执行:

func() {
	panic("panic text.")
}()

程序将抛出错误然后退出,但如果这样:

defer func() {
	if r := recover(); r != nil {
		fmt.Println("recover text: ", r)
	}
}()

func() {
	panic("panic text.")
}()

fmt.Println("panic之后的语句")

程序执行的结果为:

recover text:  panic text.

panic()之后,程序就退出函数了,之后的语句不会执行了,除了defer

如果没有panic,上面定义的defer也会在函数退出时执行。

看一下 json 包中的一段代码

func (d *decodeState) unmarshal(v interface{}) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r) // 这里
			}
			err = r.(error)
		}
	}()

	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return &InvalidUnmarshalError{reflect.TypeOf(v)}
	}

	d.scan.reset()
	// We decode rv not rv.Elem because the Unmarshaler interface
	// test must be applied at the top level of the value.
	d.value(rv)
	return d.savedError
}

这个实现中,我们可以获知几个用法:

  • 在恢复时,通过函数的命名返回值可以返回错误信息
  • 通过类型断言可以判断是运行时错误还是调用了 panic 函数
  • 在恢复时,可以继续调用 panic

错误处理的冗余

func init() {
    http.HandleFunc("/view", viewRecord)
}

func viewRecord(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    if err := viewTemplate.Execute(w, record); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

每次发生错误都需要调用http.Error(w, err.Error(), 500),还可能有更复杂多处理逻辑。

通过复用检测函数来减少类似的代码

type appHandler func(http.ResponseWriter, *http.Request) error

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

这里的appHandler和上面的viewRecord实现了相同的接口,用appHandler来包装viewRecord。

注册时这样:

func init() {
    http.Handle("/view", appHandler(viewRecord))
}

然后viewRecord就可以这样写:

func viewRecord(w http.ResponseWriter, r *http.Request) error {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        return err
    }
    return viewTemplate.Execute(w, record)
}

其实是将错误处理的共有代码封装到了appHandler函数中。

提供更友好的错误提示

自定义错误类型

type appError struct {
    Error   error
    Message string
    Code    int
}

这样我们的自定义路由器可以改成如下方式:

type appHandler func(http.ResponseWriter, *http.Request) *appError

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if e := fn(w, r); e != nil { // e is *appError, not os.Error.
        c := appengine.NewContext(r)
        c.Errorf("%v", e.Error)
        http.Error(w, e.Message, e.Code)
    }
}

这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式:

func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        return &appError{err, "Record not found", 404}
    }
    if err := viewTemplate.Execute(w, record); err != nil {
        return &appError{err, "Can't display record", 500}
    }
    return nil
}

© 著作权归作者所有

共有 人打赏支持
漂泊尘埃

漂泊尘埃

粉丝 5
博文 35
码字总数 70992
作品 0
朝阳
Golang 学习笔记(07)—— 错误及异常处理

本文为转载,原文:Golang 学习笔记(07)—— 错误及异常处理 Golang 基础知识 错误指的是可能出现问题的地方出现了问题,比如打开一个文件时失败,这种情况在人们的意料之中 ;而异常指的是...

ChainZhang ⋅ 2017/12/28 ⋅ 0

go学习笔记——函数

函数 定义 可以给返回值命名,就像函数的输入参数一样。返回值被命名之后,它们的值在函数开始的时候被自动初始化为空。在函数中执行不带任何参数的return语句时,会返回对应的返回值变量的值...

Bluven ⋅ 2014/03/19 ⋅ 0

GO语言-Defer初解

GO语言-Defer初解 学习笔记 defer:调用一个被 defer 的函数时在函数刚要返回之前延迟执行,当函数无论怎样返回,某资源必须释放时,可用这种与众不同、但有效的处理方式。传统的例子包括解锁...

liaojie ⋅ 2012/09/08 ⋅ 0

《从零开始学Swift》学习笔记(Day 3)——Swift 2.0之后增加的关键字

Swift 2.0学习笔记(Day 3)——Swift 2.0之后增加的关键字 原创文章,欢迎转载。转载请注明:关东升的博客 看了之前的学习笔记知道了什么是关键字,现在提示各位在Swift 2.0之后增加defer、...

智捷课堂 ⋅ 2015/08/20 ⋅ 0

golang入门学习笔记(四)

作者: 一字马胡 转载标志 【2017-11-25】 更新日志 日期 更新内容 备注 2017-11-25 新建文章 go语言入门学习笔记(四) golang入门学习笔记系列 golang入门学习笔记(一) golang入门学习笔...

一字马胡 ⋅ 2017/11/25 ⋅ 0

《高性能javascript》 笔记

第一部分:关于script 当把js脚本通过script标签放在head中的时候,早期浏览器在遇到script的时候会阻止浏览器加载和渲染html。知道javascript脚本被下载并执行完,且这些javascript是依次下载和...

modernizr ⋅ 2014/04/03 ⋅ 1

[新手学Go]Go语言的defer

defer:主要是用来处理错误。例如某个程序在运行时出现异常而下面还有事物需要处理,传统的语言遇到错误会立即终止程序活动。而go则引入了完美的容错机制——defer来处理异常错误 譬如: pack...

廖君 ⋅ 2013/10/27 ⋅ 0

Go语言中的资源管理和错误处理

主要讲解Go语言中的资源管理和错误处理 由于在资源管理时往往会出现错误,所以往往将资源管理和出错处理一起考虑 资源管理主要知识点: 初步认识defer,defer可以在return之前调用,即使函数...

Oo若离oO ⋅ 05/21 ⋅ 0

go语言文件汇总

归并排序及go语言实现 堆排序算法及go语言实现 Go语言基础学习(一)变量 【Leetcode】:Counting Bits问题 in Go语言 基于go语言的心跳响应 【Leetcode】:Single Number III问题 in Go语言 ...

d_watson ⋅ 2016/04/15 ⋅ 2

Go的异常处理 defer, panic, recover

Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,...

blacklovebear ⋅ 2014/02/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 44分钟前 ⋅ 0

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 今天 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 今天 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 今天 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部