设为首页 加入收藏

TOP

golang1.13中重要的新特新(二)
2019-09-30 16:41:33 】 浏览:126
Tags:golang1.13 重要 新特
VATE。比如将其设为none时所有的包都会从proxy进行获取。

从这些变化来看go团队始终在寻找一种能细粒度控制的统一的包管理解决方案,虽然目前和npm、pypi还有巨大的差距,但仍不失为成功道路上的坚实一步。

标准库的新功能

每次新版本发布都会给标准库带来大把的新功能新特性,这次也不例外。

本节会介绍一个小的新功能,以及一个重要的新变化。

判断变量是否为0值

golang中任何类型的0值都有明确的定义,然而遗憾的是不同的类型0值不同,特别是那些自定义类型,如果你要判断一个变量是否0值那么将会写出复杂繁琐而且扩展困难的代码。

因此reflect中新增了这一功能简化了操作:

package main

import (
        "fmt"
        "reflect"
)

func main() {
        a := 0
        b := 1
        c := ""
        d := "a"
        fmt.Println(reflect.ValueOf(a).IsZero()) // true
        fmt.Println(reflect.ValueOf(b).IsZero()) // false
        fmt.Println(reflect.ValueOf(c).IsZero()) // true
        fmt.Println(reflect.ValueOf(d).IsZero()) // false
}

当然,反射一劳永逸的代价是更高的性能消耗,所以具体取舍还要参照实际环境。

错误处理的革新

其实算不上革新,只是对现有做法的小修小补。golang团队始终有觉得error既然是值那就一定得体现value的equal操作的怪癖,所以整体上还是很怪。

首先要介绍错误链(error chains)的概念。

在1.13中,我们可以给error实现一个Unwrap的方法,从而实现对error的包装,比如:

type PermError {
        os.SyscallError
        Pid uint
        Uid uint
}

func (err *PermError) String() string {
        return fmt.Sprintf("permission error:\npid:%v\nuid:\ninfo:%v", err.Pid, err.Uid, err.SyscallError)
}

func (err *PermError) Error() string {
        return err.String()
}

// 重点在这里
func (err *PermError) Unwrap() error {
        return err.SyscallError
}

假设我们包装了一个基于SyscallError的权限错误,包括了所有因为权限问题而触发的error。StringError方法都是常规的自定义错误中会实现的方法,我们重点看Unwrap方法。

Unwrap字面意思就是去包装,也就是我们把包装好的上一层错误重新分离出来并返回。os.SyscallError也实现了Unwrap,于是你可以继续向上追溯直达最原始的没有实现Unwrap的那个error为止。我们称从PermError开始到最顶层的error为一条错误链。

如果我们用→指向Unwrap返回的对象,会形成下面的结构:

PermError → os.SyscallError → error

还可以出现更复杂的结构:
A → Err1 ___________
|
V
B → Err2 → Err3 → error

这样无疑提升了错误的表达力,如果不想自己单独定义一个错误类型,只想附加某些信息,可以依赖fmt.Errorf

newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr)
sysErr == newErr.(interface {Unwrap() error}).Unwrap()

fmt.Errorf新的占位符%w只能在一个格式化字符串中出现一次,他会把error的信息填充进去,然后返回一个实现了Unwrap的新error,它返回传入的那个error。另外提案里的Wrapper接口目前还没有实现,但是标准库用了我在上面的做法暂时实现了Wrapper的功能。

因为错误链的存在,我们不能在简单的用等于号基于判断基于值的error了,但好处是我们现在还可以判断基于类型的error。

为了能继续让error表现自己的值语义,errors包里增加了Is和As以及辅助它们的Unwrap函数。

Unwrap

errors.Unwrap会调用传入参数的Unwrap方法,As和Is使用它来追溯整个错误链。

像上一小节的代码就可以简化成这样:

newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr)
sysErr == errors.Unwrap(newErr).Unwrap()

Is

我们提到等于号的比较很多时候已经不管用了,有的时侯一个error只是对另一个的包装,当这个error产生时另一个也已经发生了,这时候我们只需要比较处于上层的error值即可,这时候你就需要errors.Is帮忙了:

newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr)
errors.Is(newErr, sysErr)
errors.Is(newErr, os.ErrExists)

你永远也不知道程序会被怎样扩展,也不知道error之间的关系未来会怎样变化,因此总是用Is代替==是不会犯错的。

不过凡事总有例外,例如io.EOF就不需要使用Is去比较,因为它程序意义上算不上是error,而且一般也不会有人包装它。

As

除了传统的基于值的判断,对某个类型的错误进行处理也是一个常见需求。例如前文的A,B都来自error,假设我们现在要处理所有基于这个error的错误,常见的办法是switch进行比较或者依赖于基类的多态能力。

显而易见的是switch判断的做法会导致大量重复的代码,而且扩展困难;而在golang里没有继承只有组合,所以有运行时多态能力的只有interface,这时候我们只能借助错误链让errors.As帮忙了:

// 注意As的第二个参数只能是你需要判断的类型的指针,不可以直接传一个nil进去
var p1 *os.SyscallError
var p2 *os.PathError
errors.As(newErr, &p1)
errors.As(newErr, &p2)

如果p1和p2的类型在newErr所在的错误链上,就会返回true,实现了一个很简陋的多态效果。As总是用于替代if _, ok := err.(type); ok这样的代码。

当然,上面的函数一方面让你少写了很多代码,另一方面又严重依赖反射,特别是错误链很长的时候需要反复追溯多次,所以这里有两条忠告:

  1. 不要过渡包装,没什么是加间接层解决不了的,但是中间层太多不
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇GoLang 开山篇 下一篇[系列] go-gin-api 路由中间件 - ..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目