• Go踩坑记录分享


    废话不多说直接上使用错误使用示例

    示例1

    有时我们为了避免切片底层数据扩展带来的开销,会提前指定make的cap大小

    1. func Example1() {
    2. persons := make([]*Person, 1000)
    3. for i := 0; i < 10000; i++ {
    4. persons = append(persons, &Person{
    5. Name: "example1",
    6. Age: i,
    7. })
    8. }
    9. }
    10. // 正确方法:声明lens为0,就可以使用append将item逐一加到切片中
    11. type Person struct {
    12. Name string
    13. Age int
    14. }
    15. func Example1() {
    16. persons := make([]*Person, 0, 1000)
    17. for i := 0; i < 1000; i++ {
    18. persons = append(persons, &Person{
    19. Name: "example1",
    20. Age: i,
    21. })
    22. }
    23. }
    24. // 正确方法2:使用切片逐个赋值的方式
    25. type Person struct {
    26. Name string
    27. Age int
    28. }
    29. func Example1() {
    30. persons := make([]*Person, 1000)
    31. for i := 0; i < 1000; i++ {
    32. persons[i] = &Person{
    33. Name: "example1",
    34. Age: i,
    35. }
    36. }
    37. }

    错误分析

    虽然我们指定了make的cap大小,但是我们并没有指定make的length字段的大小,这个时候length字段的大小等于cap的大小,参照下面切片用法

    补充:切片正确初始化、创建方法

    使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:

    1. // 创建一个整型切片
    2. // 其长度和容量都是 5 个元素
    3. slice := make([]int, 5)

    此时只指定了切片的长度,那么切片的容量和长度相等。也可以分别指定长度和容量:

    1. // 创建一个整型切片
    2. // 其长度为 3 个元素,容量为 5 个元素
    3. slice := make([]int, 3, 5)

    分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
    访问所有的数组元素。 

    示例2

    1. 向nil map中添加元素导致panic

    1. func Example2() {
    2. var m1 map[string]string
    3. m1["a"] = "b"
    4. }
    5. // 解决方案:给map初始化

    2. 函数返回值为map时,在使用前也应该初始化,否则会panic

    1. func Example3() (m1 map[string]string) {
    2. m1["a"] = "b"
    3. return m1
    4. }
    5. // 解决方案:给map初始化

    3. map有嵌套,嵌套的map也要初始化,否则会panic

    1. func Example2() {
    2. m1 := make(map[string]map[int]int)
    3. m1["go"][0] = 0
    4. }
    5. // 正确方法
    6. // 先初始化,再使用
    7. func Example6() {
    8. m1 := make(map[string]map[int]int)
    9. m1["go"] = make(map[int]int)
    10. m1["go"][0] = 0
    11. }

    示例3

    对于带for循环的select,break并不能打破for循环,会永远循环下去

    1. func Example3(ctx context.Context) {
    2. for {
    3. select {
    4. case <-ctx.Done():
    5. break
    6. default:
    7. ...
    8. }
    9. }
    10. }
    11. 正确方法1:采用break + 标签
    12. func Example3(ctx context.Context) {
    13. //LOOP 是标签
    14. LOOP:
    15. for {
    16. select {
    17. case <-ctx.Done():
    18. break LOOP
    19. default:
    20. ...
    21. }
    22. }
    23. }
    24. 正确方法2:采用goto + 标签
    25. func Example3(ctx context.Context) {
    26. for {
    27. select {
    28. case <-ctx.Done():
    29. goto LOOP
    30. default:
    31. ...
    32. }
    33. }
    34. //END 是标签
    35. END:
    36. }

    示例4

    当panic发生后,虽然有recover,但是并没有将error信息传递到上层,可能会导致后续逻辑异常

    1. func Example4(ctx context.Context) {
    2. err := DoExample4(ctx)
    3. if err != nil {
    4. return
    5. }
    6. ...
    7. }
    8. func DoExample4() error {
    9. defer func(ctx context.Context) {
    10. if err := recover(); err != nil {
    11. log.ErrorContextf(ctx, "panic: %v", err)
    12. }
    13. }()
    14. panic("DoExample4")
    15. }
    16. // 正确做法:及时处理返回异常信息
    17. func Example4(ctx context.Context) {
    18. err := DoExample4(ctx)
    19. if err != nil {
    20. return
    21. }
    22. ...
    23. }
    24. func DoExample4() (err error) {
    25. defer func() {
    26. if panicErr := recover(); panicErr != nil {
    27. err = fmt.Errorf("panic %v", panicErr)
    28. }
    29. }()
    30. panic("DoExample4")
    31. }

    示例5

    Go协程启动的函数,没有recover,painc可能导致整个程序panic

    1. func Example5() {
    2. go func() {
    3. doSomething()
    4. }()
    5. }
    6. func doSomething() {
    7. println("this is a good day!")
    8. panic("but it panic")
    9. }
    10. // 正确方法:Go协程启动的函数,一定要有recover方法
    11. func Example5() {
    12. go func() {
    13. defer func() {
    14. // 捕获错误并做相应的处理
    15. if err := recover(); err != nil {
    16. println("the program is err = %s", err)
    17. }
    18. }()
    19. doSomething()
    20. }()
    21. }
    22. func doSomething() {
    23. println("this is a good day!")
    24. panic("but it panic")
    25. }

    示例6

    Go协程中,采用闭包的形式使用请求传入的context,可能会导致context canceled错误

    1. func Example6(ctx context.Context) {
    2. go func() {
    3. defer func() {
    4. if err := recover(); err != nil {
    5. ...
    6. }
    7. }()
    8. doSomething(ctx)
    9. }()
    10. }
    11. func doSomething(ctx context.Context) {
    12. ...
    13. }
    14. // 解决方法:我们可以克隆一个context,例如使用trpc包中提供多个CloneContext方法
    15. func Example6(ctx context.Context) {
    16. go func(ctx context.Context) {
    17. defer func() {
    18. if err := recover(); err != nil {
    19. ...
    20. }
    21. }()
    22. doSomething(ctx)
    23. }(trpc.CloneContext(ctx))
    24. }
    25. func doSomething(ctx context.Context) {
    26. ...
    27. }

    示例7

    使用gorm查询时,用Take, First, Last、Find函数查询数据库时,当传入函数的参结果集的变量只能为Struct类型或Slice类型,当传入变量为Struc类型时,如果检索出来的数据为0条,会抛出ErrRecordNotFound错误,当传入变量为Slice类型时,任何条件下均不会抛出ErrRecordNotFound错误

    具体分析:GORM之ErrRecordNotFound采坑记录 - 掘金

    1. // 查不到数据返回ErrRecordNotFound
    2. func Example7(db *gorm.DB) error {
    3. person := model.Person{}
    4. return db.Find(&person, -1).Error
    5. }
    6. // 查不到数据返回nil
    7. func Example7(db *gorm.DB) error {
    8. person := []model.Person{}
    9. return db.Find(&person, -1).Error
    10. }

    示例8

    当gorm更新使用struct时,会有零值更新问题,就是对于零值字段,会忽略更新

    1. type Person struct {
    2. Name string
    3. Age int
    4. }
    5. // 更新数据库时忽略结构体中为零值的字段
    6. db.Model(&person).Updates(Person{Name: "test", Age: 0})
    7. // 解决方法:使用map可以不忽略零值字段
    8. // 当然也可以在定义数据时避免使用零值字段
    9. type Person struct {
    10. Name string
    11. Age int
    12. }
    13. db.Model(&person).Updates(map[string]interface{}{“Name": "test", "Age": 0})

    示例9

    闭包使用for…range声明的变量,导致非预期结果

    本质原因是for…range中声明的变量其实只有一个地址,每次循环都是赋值给同一个地址

    1. func Example9() {
    2. s := []string{"a", "b", "c"}
    3. for _, v := range s {
    4. go func() {
    5. fmt.Println(v)
    6. }()
    7. }
    8. select {}
    9. }
    10. // 正确方法1:通过复制变量,避免地址操作带来的影响
    11. func Example9() {
    12. s := []string{"a", "b", "c"}
    13. for _, v := range s {
    14. v1 := v
    15. go func() {
    16. fmt.Println(v1)
    17. }()
    18. }
    19. select {}
    20. }
    21. // 正确方法2:通过函数传参深拷贝来解决,地址操作带来的影响
    22. func Example9() {
    23. s := []string{"a", "b", "c"}
    24. for _, v := range s {
    25. v1 := v
    26. go func() {
    27. fmt.Println(v1)
    28. }()
    29. }
    30. select {}
    31. }

    示例10

    map并发读写,会直接panic掉

    1. func Example10() {
    2. m := make(map[int]int)
    3. go func() {
    4. for i := 0; i < 10000; i++ {
    5. m[i] = i
    6. }
    7. }()
    8. go func() {
    9. for i := 0; i < 10000; i++ {
    10. fmt.Println(m[i])
    11. }
    12. }()
    13. time.Sleep(time.Second * 5) // fatal error: concurrent map read and map write
    14. }
    15. 解决方法:
    16. 加锁
    17. sync.Map
    18. 改成协程内局部变量

    示例11

    slice并发写不会出现panic,能正常跑,但数据通常不是预期结果

    1. func Example11() {
    2. var arr []int
    3. for i := 0; i < 1000; i++ {
    4. go func(v int) {
    5. arr = append(arr, v)
    6. }(i)
    7. }
    8. time.Sleep(time.Second)
    9. for i, v := range arr {
    10. fmt.Println(i, ": ", v) // 数量,非预期的1000个
    11. }
    12. }
    13. 解决方法:加锁控制并发写入的并发覆盖问题
    14. func Example11() {
    15. lock := sync.Mutex{}
    16. var arr []int
    17. for i := 0; i < 1000; i++ {
    18. go func(v int) {
    19. lock.Lock()
    20. arr = append(arr, v)
    21. lock.Unlock()
    22. }(i)
    23. }
    24. time.Sleep(time.Second)
    25. for i, v := range arr {
    26. fmt.Println(i, ": ", v) // 数量预1000个
    27. }
    28. }

  • 相关阅读:
    ClickHouse备份方案
    电动两轮车驶入“年轻化”新赛道,新老品牌谁能率先突围?
    PyTorch搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)
    为vscode开发一款svn右键菜单扩展
    一文揭秘JavaScript中你不知道的async与await实现原理与细节
    粒子特效-Xffect
    云原生 · Kubernetes - k8s集群搭建(kubeadm)(持续收录报错中)
    java计算机毕业设计基于springboo+vue的准妈妈孕期育儿婴幼儿交流平台
    AGI概念与实现
    手机强制移除ppt密码,ppt权限密码多少?
  • 原文地址:https://blog.csdn.net/qq_34261446/article/details/126344072