废话不多说直接上使用错误使用示例
有时我们为了避免切片底层数据扩展带来的开销,会提前指定make的cap大小
- func Example1() {
- persons := make([]*Person, 1000)
- for i := 0; i < 10000; i++ {
- persons = append(persons, &Person{
- Name: "example1",
- Age: i,
- })
- }
- }
-
- // 正确方法:声明lens为0,就可以使用append将item逐一加到切片中
- type Person struct {
- Name string
- Age int
- }
-
- func Example1() {
- persons := make([]*Person, 0, 1000)
-
- for i := 0; i < 1000; i++ {
- persons = append(persons, &Person{
- Name: "example1",
- Age: i,
- })
- }
- }
-
- // 正确方法2:使用切片逐个赋值的方式
- type Person struct {
- Name string
- Age int
- }
-
- func Example1() {
- persons := make([]*Person, 1000)
-
- for i := 0; i < 1000; i++ {
- persons[i] = &Person{
- Name: "example1",
- Age: i,
- }
- }
- }
虽然我们指定了make的cap大小,但是我们并没有指定make的length字段的大小,这个时候length字段的大小等于cap的大小,参照下面切片用法
使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:
- // 创建一个整型切片
- // 其长度和容量都是 5 个元素
- slice := make([]int, 5)
此时只指定了切片的长度,那么切片的容量和长度相等。也可以分别指定长度和容量:
- // 创建一个整型切片
- // 其长度为 3 个元素,容量为 5 个元素
- slice := make([]int, 3, 5)
分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。
1. 向nil map中添加元素导致panic
- func Example2() {
- var m1 map[string]string
-
- m1["a"] = "b"
- …
- }
-
- // 解决方案:给map初始化
2. 函数返回值为map时,在使用前也应该初始化,否则会panic
- func Example3() (m1 map[string]string) {
- m1["a"] = "b"
- return m1
- }
- // 解决方案:给map初始化
3. map有嵌套,嵌套的map也要初始化,否则会panic
- func Example2() {
- m1 := make(map[string]map[int]int)
- m1["go"][0] = 0
- }
-
- // 正确方法
- // 先初始化,再使用
- func Example6() {
- m1 := make(map[string]map[int]int)
- m1["go"] = make(map[int]int)
- m1["go"][0] = 0
- }
对于带for循环的select,break并不能打破for循环,会永远循环下去
- func Example3(ctx context.Context) {
- for {
- select {
- case <-ctx.Done():
- break
- default:
- ...
- }
- }
- }
-
- 正确方法1:采用break + 标签
- func Example3(ctx context.Context) {
- //LOOP 是标签
- LOOP:
- for {
- select {
- case <-ctx.Done():
- break LOOP
- default:
- ...
- }
- }
- }
-
- 正确方法2:采用goto + 标签
- func Example3(ctx context.Context) {
- for {
- select {
- case <-ctx.Done():
- goto LOOP
- default:
- ...
- }
- }
- //END 是标签
- END:
- }
当panic发生后,虽然有recover,但是并没有将error信息传递到上层,可能会导致后续逻辑异常
- func Example4(ctx context.Context) {
- err := DoExample4(ctx)
- if err != nil {
- return
- }
- ...
- }
-
- func DoExample4() error {
- defer func(ctx context.Context) {
- if err := recover(); err != nil {
- log.ErrorContextf(ctx, "panic: %v", err)
- }
- }()
-
- panic("DoExample4")
- }
-
- // 正确做法:及时处理返回异常信息
- func Example4(ctx context.Context) {
- err := DoExample4(ctx)
- if err != nil {
- return
- }
- ...
- }
-
- func DoExample4() (err error) {
- defer func() {
- if panicErr := recover(); panicErr != nil {
- err = fmt.Errorf("panic %v", panicErr)
- }
- }()
-
- panic("DoExample4")
- }
-
-
Go协程启动的函数,没有recover,painc可能导致整个程序panic
- func Example5() {
- go func() {
- doSomething()
- }()
- }
-
- func doSomething() {
- println("this is a good day!")
- panic("but it panic")
- }
-
- // 正确方法:Go协程启动的函数,一定要有recover方法
- func Example5() {
- go func() {
- defer func() {
- // 捕获错误并做相应的处理
- if err := recover(); err != nil {
- println("the program is err = %s", err)
- }
- }()
- doSomething()
- }()
- }
-
- func doSomething() {
- println("this is a good day!")
- panic("but it panic")
- }
Go协程中,采用闭包的形式使用请求传入的context,可能会导致context canceled错误
- func Example6(ctx context.Context) {
- go func() {
- defer func() {
- if err := recover(); err != nil {
- ...
- }
- }()
-
- doSomething(ctx)
- }()
- }
-
- func doSomething(ctx context.Context) {
- ...
- }
-
-
- // 解决方法:我们可以克隆一个context,例如使用trpc包中提供多个CloneContext方法
- func Example6(ctx context.Context) {
- go func(ctx context.Context) {
- defer func() {
- if err := recover(); err != nil {
- ...
- }
- }()
-
- doSomething(ctx)
- }(trpc.CloneContext(ctx))
- }
-
- func doSomething(ctx context.Context) {
- ...
- }
使用gorm查询时,用Take, First, Last、Find函数查询数据库时,当传入函数的参结果集的变量只能为Struct类型或Slice类型,当传入变量为Struc类型时,如果检索出来的数据为0条,会抛出ErrRecordNotFound错误,当传入变量为Slice类型时,任何条件下均不会抛出ErrRecordNotFound错误
具体分析:GORM之ErrRecordNotFound采坑记录 - 掘金
- // 查不到数据返回ErrRecordNotFound
- func Example7(db *gorm.DB) error {
- person := model.Person{}
- return db.Find(&person, -1).Error
- }
-
-
- // 查不到数据返回nil
- func Example7(db *gorm.DB) error {
- person := []model.Person{}
- return db.Find(&person, -1).Error
- }
当gorm更新使用struct时,会有零值更新问题,就是对于零值字段,会忽略更新
- type Person struct {
- Name string
- Age int
- }
-
- // 更新数据库时忽略结构体中为零值的字段
- db.Model(&person).Updates(Person{Name: "test", Age: 0})
-
-
- // 解决方法:使用map可以不忽略零值字段
- // 当然也可以在定义数据时避免使用零值字段
- type Person struct {
- Name string
- Age int
- }
-
-
- db.Model(&person).Updates(map[string]interface{}{“Name": "test", "Age": 0})
闭包使用for…range声明的变量,导致非预期结果
本质原因是for…range中声明的变量其实只有一个地址,每次循环都是赋值给同一个地址
- func Example9() {
- s := []string{"a", "b", "c"}
- for _, v := range s {
- go func() {
- fmt.Println(v)
- }()
- }
- select {}
- }
-
- // 正确方法1:通过复制变量,避免地址操作带来的影响
- func Example9() {
- s := []string{"a", "b", "c"}
- for _, v := range s {
- v1 := v
- go func() {
- fmt.Println(v1)
- }()
- }
- select {}
- }
-
-
- // 正确方法2:通过函数传参深拷贝来解决,地址操作带来的影响
- func Example9() {
- s := []string{"a", "b", "c"}
- for _, v := range s {
- v1 := v
- go func() {
- fmt.Println(v1)
- }()
- }
- select {}
- }
-
map并发读写,会直接panic掉
- func Example10() {
- m := make(map[int]int)
- go func() {
- for i := 0; i < 10000; i++ {
- m[i] = i
- }
- }()
- go func() {
- for i := 0; i < 10000; i++ {
- fmt.Println(m[i])
- }
- }()
- time.Sleep(time.Second * 5) // fatal error: concurrent map read and map write
- }
-
-
- 解决方法:
- 加锁
- sync.Map
- 改成协程内局部变量
slice并发写不会出现panic,能正常跑,但数据通常不是预期结果
- func Example11() {
- var arr []int
- for i := 0; i < 1000; i++ {
- go func(v int) {
- arr = append(arr, v)
- }(i)
- }
- time.Sleep(time.Second)
- for i, v := range arr {
- fmt.Println(i, ": ", v) // 数量,非预期的1000个
- }
- }
-
-
- 解决方法:加锁控制并发写入的并发覆盖问题
- func Example11() {
- lock := sync.Mutex{}
- var arr []int
- for i := 0; i < 1000; i++ {
- go func(v int) {
- lock.Lock()
- arr = append(arr, v)
- lock.Unlock()
- }(i)
- }
- time.Sleep(time.Second)
- for i, v := range arr {
- fmt.Println(i, ": ", v) // 数量预1000个
- }
- }