分享3个Go编程的小知识

前阵子在网上看到一些关于Go比较不错的小知识点,下面总结下分享给大家。

new和make的区别

new和make都是Go中用来创建对象用的,new这个关键字在很多编程语言里都有,比如在C++和Java里,都可以用new来创建对象。

在Go中,new的作用同样是用来创建对象,比如new(T)将会为T创建对象,并同时将这个对象赋一个零值,然后返回T对象的指针*T,下面我们演示用3种不同的办法创建bytes.Buffer的对象,并返回它的指针。

// 声明一个变量,然后取它的地址并赋值给指针p
var buf bytes.Buffer
p := &buf

// 使用复合声明的方式一步完成
p := &bytes.Buffer{}

// 使用new也是一步完成
p := new(bytes.Buffer)
复制代码

上面3种创建对象的方法都是等价的。

make在日常的编程中也很常用,但是它的使用范围仅限于在slice,map,channel中。使用make也是创建对象,比如make(T),但它返回的是对象的值T,回顾下make的用法。

// 创建一个长度为0,容量为8的slice
sl := make([]string, 0, 8)

// 创建一个阻塞的channel
ch := make(chan int)

// 创建一个map
m := make(map[string]string)
复制代码

上面使用make创建的对象返回的都是对应的值类型。

总结下new和make的区别:

1. new返回的是T的指针,make返回的是T的值。

2. make仅能用于创建slice,map,channel。

变量名不要带有类型

对于变量命名,Go大师Dave Cheney举了个很有趣比喻:你给变量命名就像给你家的宠物取名一样,名字上不要带上“xx狗”,”xx猫“,因为大家都能知道它是狗还是猫。

所以你的变量名应该是描述变量的内容,而不是描述变量的类型,看看以下的写法。

var usersMap map[string]*User
复制代码

usersMap这个变量名看起来还不错,描述的是*User的map映射类型。但是Go是一门静态语言,我们给一个map取变量名的时候是不需要像动态语言一样,因为怕赋值错误的类型而给它加上类型的,因此这个Map后缀是多余的。

我们再以这种方式来命名几个变量。

var (
   companiesMap map[string]*Company
   productsMap  map[string]*Products
)
复制代码

现在,我们已经命名了3个map类型的变量:usersMap,companiesMap,productsMap
,其中它们对应的value值都是不同的struct类型。当我们将*User赋值productsMap的时候,这时候编译器是会报错的,不像动态语言那样只能在运行时才会报错。

这种情况下,加上Map后缀并没有更好的描述这个变量,反而还只是一个多余的后缀。所以不建议在变量名中带有类型。

同样的在方法命名上也类似。

type Config struct {
    //
}

func (c *Config) WriteConfig(w io.Writer) {
    //
}

// would be better
func (c *Config) Write(w io.Writer) {
    //
}
复制代码

上面代码中,因为WriteConfig是*Config的方法,所以Config后缀也是多余的。

另外,包名最好别占用类型的名字,就像context包里的Context类型,当我们引入这个context包的时候,只能用类似ctx这种变量名,而不是context。

// 这样命名就很奇怪,而且十分不好看
func WriteLog(context context.Context, message string)

// 只能以这样的形式取名
func WriteLog(ctx context.Context, message string)
复制代码

总结

Go的变量命名宗旨是简洁明了,变量名应该独立于它的类型。

使用命名的返回值捕获panic

想象下你写的代码中,使用到的一个函数会panic可能会panic,而且你还改不了那个函数,像这样:

func pressButton() {
    fmt.Println("I'm Mr. Meeseeks, look at me!!")
    // other stuff then happens, but if Jerry asks to
    // remove 2 strokes from his golf game...
    panic("It's gettin' weird!")
}
复制代码

虽然这个函数会panic,但你还是不得不使用它,当它panic的时候我们可以捕获这个错误,就像这样:

func doStuff() error {
    var err error
    // If there is a panic we need to recover in a deferred func
    defer func() {
        if r := recover(); r != nil {
            err = errors.New("the meeseeks went crazy!")
        }
    }()

    pressButton()
    return err
}
复制代码

当pressButton发生panic的时候,我们会以为将会返回一个error,但结果是返回一个nil。是因为pressButton发生panic了就直接返回了,并不会走到return err。

想修复这个问题很简单,只需给error起一个变量名就好了。

func doStuff() (err error) {
    // If there is a panic we need to recover in a deferred func
    defer func() {
        if r := recover(); r != nil {
	    err = errors.New("the meeseeks went crazy!")
	}
    }()

    pressButton()
    return err
}
复制代码

参考文献

1.《理解 Go make 和 new 的区别》
sanyuesha.com/2017/07/26/…

2.《Using named return variables to capture panics in Go》
www.calhoun.io/using-named…

3.dave.cheney.net/2019/01/29/…

感谢阅读,欢迎大家指正,留言分享交流~

来源:微信公众号《Go后端干货》

各种Go,后端技术,面试题分享,欢迎关注