Go语言快速上手(三) | 青训营笔记

162 阅读1分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记.

 几项最佳实践

1.关于init()函数

Golang中init()函数是个特殊的函数,它的执行晚于变量的初始化而早于main()函数

  • 同一个包中的同一个文件中,可以有多个init()函数,按照顺序执行。
  • 而不同包的init函数按照包导入的依赖关系决定执行顺序。

乱使用init()函数,会使得代码的可阅读性和可理解性变差。建议不使用init(),或者克制使用。

2.关于切片追加

当使用切片追加时,总是将append的结果保存在相同切片中。如果需要新切片,先copy再追加

例如下述代码,初学者很难判断出来x,y,z都是0124

func main() {
	x := []int{}
	x = append(x, 0)
	x = append(x, 1)
	x = append(x, 2)
	y := append(x, 3)
	z := append(x, 4)
	fmt.Println(x, y, z)
}

3.关于切片构建

一般切片的构建,使用make()函数来完成,如果预知切片的大小,最好直接指定容量,否则一开始写个容量0,后面疯狂append,每次append都要新开空间然后复制,性能极差;所以一开就开够。

注意一个小陷阱:使用make构造切片时,如果只写一个参数的话,默认这个数既是len,也是capacity,所以感觉使用make指定好了容量,但是其实一append还是整个开空间复制,所以用make一定要写中间的0,不要偷懒。

当不知道cap时,要赋值一个适当的cap,既不能太大(占用内存空间),也不能太小(不断拷贝和调用gc)。

4.关于指针

Go中有两种指针:

1.限制型指针,类似 var ptr *int,这种指针只能寻址,但不能通过加减地址来任意读写内存;

2.无限制指针,unsafe.pointer,这种指针类似于C语言中的void*,可以突破Go类型系统的限制,任意读写内存。

对于第一种指针的用法:

  • Go的参数传递是值传递的,需要有结构体内部进行修改,使用指针。
  • 注意,如果不是修改的情况,建议不要使用指针,好处是避免结构体内的field被任意或者不小心修改。
  • 当结构体或者容器数据很大时,可以考虑用指针来提高性能
  • 但是不那么大的话,尽量用值。因为传指针Go要进行逃逸分析,所以传递指针的性能并不一定比传值性能更好,因为要进行逃逸分析。

5.关于恐慌(Panic)

Go中,当有运行时异常时,会报恐慌;也可以用panic()函数来直接触发恐慌。

发生panic后,后面的代码将停止运行,当前函数或方法会直接return。

Go中,延迟(defer)函数可以在函数return前执行,一般会将panic的recover逻辑放在defer函数中。

如果启子协程,如果想防止因为子协程中发生panic而导致整个程序终止,建议在启动子协程的开始增加恢复panic的延迟函数;延迟函数就是前有defer修饰的函数,是在主函数return前一定会执行的方法,所以一旦panic了就会立即执行defer函数,可以在不知道哪里可能会panic时用,并且一旦起协程就写defer也是一个好的编程习惯。

func main() {
	go some()
	go func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println("panic is recovered")
			}
		}()
		fmt.Println("Do something!")
		panic("this is a panic")
	}()
	time.Sleep(2 * time.Second)
	fmt.Println("end")
}