新手问题 分享3个Go编程的小知识

joy · 2019年05月16日 · 258 次阅读

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

各种 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 的区别》 https://sanyuesha.com/2017/07/26/go-make-and-new/ 2.《Using named return variables to capture panics in Go》 https://www.calhoun.io/using-named-return-variables-to-capture-panics-in-go/

  1. https://dave.cheney.net/2019/01/29/you-shouldnt-name-your-variables-after-their-types-for-the-same-reason-you-wouldnt-name-your-pets-dog-or-cat

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

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册