能写出这几个 Go 程序 BUG,自己都得脸红

2023-10-15 03:40
文章标签 go 程序 几个 bug 写出 脸红

本文主要是介绍能写出这几个 Go 程序 BUG,自己都得脸红,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家好,我是被网管整脸红的煎鱼。

在用 Go 编程时,总会遇到各种奇奇怪怪的错误,国内外已经有许多小伙伴总结过(参考链接见参考),感觉都能凑一桌了。

之前一直想写,想着五一假期肝一肝。今天给大家分享 Go 里常见的编码错误(一)。

希望对大家有所帮助。

Go 常见错误

1. nil Map

问题

在程序中声明(定义)了一个 map,然后直接写入数据。如下代码:

func main() {var m map[string]stringm["煎鱼"] = "进脑子了"
}

输出结果:

panic: assignment to entry in nil map

会直接抛出一个 panic。

解决方法

解决方法其实就是要声明并初始化,Go 里标准写法是调用 make 函数就可以了。如下代码:

func main() {m := make(map[string]string)m["煎鱼"] = "下班了"
}

这个问题在初学 Go 时是最容易踩到的错误。

2. 空指针的引用

问题

我们在 Go 经常会利用结构体去声明一系列的方法,他看起来向面向对象中的 ”类“,在业务代码中非常常见。

如下代码:

type Point struct {X, Y float64
}func (p *Point) Abs() float64 {return math.Sqrt(p.X*p.X + p.Y*p.Y)
}func main() {var p *Pointfmt.Println(p.Abs())
}

这段程序能够正常运行吗?正常计算和输出?

输出结果:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a3143]goroutine 1 [running]:
main.(*Point).Abs(...)/Users/eddycjy/awesomeProject/main.go:13
main.main()/Users/eddycj/awesomeProject/main.go:18 +0x23

直接就恐慌了,由于空指针的引用。

解决方法

如果变量 p 是一个指针,则必须要进行初始化才可以进行调用。如下代码:

func main() {var p *Point = new(Point)fmt.Println(p.Abs())
}

又或是用值对象的方法来解决:

func main() {var p Point // has zero value Point{X:0, Y:0}fmt.Println(p.Abs())
}

3. 使用对循环迭代器变量的引用

问题

在 Go 中,循环迭代器变量是一个单一的变量,在每个循环迭代中取不同的值。这如果使用不当,可能会导致非预期的行为。

如下代码:

func main() {var out []*intfor i := 0; i < 3; i++ {out = append(out, &i)}fmt.Println("Values:", *out[0], *out[1], *out[2])fmt.Println("Addresses:", out[0], out[1], out[2])
}

输出结果是什么。大胆猜想值是 1,2,3,地址都是不一样的。对吗?

输出结果:

Values: 3 3 3
Addresses: 0x40e020 0x40e020 0x40e020

值都是 3,地址都是同一个指向。

解决方法

其中一种解决方法是将循环变量复制到一个新变量中:

for i := 0; i < 3; i++ {i := i // Copy i into a new variable.out = append(out, &i)}

输出结果:

Values: 0 1 2
Addresses: 0x40e020 0x40e024 0x40e028

原因是:在每次迭代中,我们将 i 的地址追加到 out 切片中,但由于它是同一个变量,我们实际上追加的是相同的地址,该地址最终包含分配给 i 的最后一个值。

所以只需要拷贝一份,让两者脱离关联就可以了。

4. 在循环迭代器变量上使用 goroutine

问题

在 Go 中进行循环时,我们经常会使用 goroutine 来并发处理数据。最经典的就是会结合闭包来编写业务逻辑。

如下代码:

values := []int{1, 2, 3, 4, 5}
for _, val := range values {go func() {fmt.Println(val)}()
}time.Sleep(time.Second)

但在实际的运行中,上述 for 循环可能无法达到您的预期,你想的可能是顺序输出切片中的值。

输出的结果是:

5
5
4
5
5

你可能会看到每次迭代打印的最后一个元素,甚至你会发现,每次输出的结果还不一样...

如果去掉休眠代码,会发现 goroutine 可能根本不会开始执行,程序就结束了。

解决方法

这其实就是闭包使用上的一个常见问题,编写该闭包循环的正确方法是:

values := []int{1, 2, 3, 4, 5}for _, val := range values {go func(val int) {fmt.Println(val)}(val)}

通过将 val 作为参数添加到闭包中,在每次循环时,变量 val 都会被存储在 goroutine 的堆栈中,以确保最终 goroutine 执行时值是对的。

当然,这里还有一个隐性问题。大家总会以为是按顺序输出 1, 2, 3, 4, 5。其实不然,因为 goroutine 的执行是具有随机性的,没法确保顺序。

注:经常会变形出现在许多 Go 的面试题当中,一旦复杂起来就容易让人迷惑。

5. 数组不会被改变

问题

切片和数字是我们在 Go 程序中应用最广泛的数据类型,但他常常会有一些奇奇怪怪的问题。

如下代码:

func Foo(a [2]int) {a[0] = 8
}func main() {a := [2]int{1, 2}Foo(a)       fmt.Println(a) 
}

输出结是什么。是 [8 2],对吗?

输出结果:

[1 2]

这是为什么,函数里修改了个寂寞?

解决方法

实际上在 Go 中,所有的函数传递都是值传递。也就是将数组传递给函数时,会复制该数组。如果真的是需要传进函数内修改,可以改用切片。

如下代码:

func Foo(a []int) {if len(a) > 0 {a[0] = 8}
}func main() {a := []int{1, 2}Foo(a)         fmt.Println(a)
}

输出结果:

[8 2]

原因是:切片不会存储任何的数据,他的底层 data 会指向一个底层数组。因此在修改切片的元素时,会修改其底层数组的相应元素,共享同一个底层数组的其他切片会一并修改。

你以为这就万事大吉,解决了?并不。当切片扩容时,Go 底层会重新申请新的更大空间,存在与原有切片分离的场景。

因此还是要及时将变更的值返回出来,在主流程上统一处理元数据会更好。

总结

在今天这篇文章中,我们开始了 Go 常见编码错误的第一节,共涉及 5 个案例:

  1. nil Map。

  2. 空指针的引用。

  3. 使用对循环迭代器变量的引用。

  4. 在循环迭代器变量上使用 goroutine。

  5. 数组不会被改变。

这些案例非常常见,在单一代码上看会比较容易发觉。但一旦混合到应用程序中,在繁杂代码里就比较难看出来。

祝大家吸完后少踩坑,少出 BUG。

参考

  • golang/go/wiki/CommonMistakes

  • 24 Common Mistakes in Go (gotchas) And How To Avoid Them

关注煎鱼,获取业内第一手消息和知识 👇

f41ae9828d62a72809a6c61ea5703882.png

煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路。日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

这篇关于能写出这几个 Go 程序 BUG,自己都得脸红的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/215115

相关文章

每天认识几个maven依赖(ActiveMQ+activemq-jaxb+activesoap+activespace+adarwin)

八、ActiveMQ 1、是什么? ActiveMQ 是一个开源的消息中间件(Message Broker),由 Apache 软件基金会开发和维护。它实现了 Java 消息服务(Java Message Service, JMS)规范,并支持多种消息传递协议,包括 AMQP、MQTT 和 OpenWire 等。 2、有什么用? 可靠性:ActiveMQ 提供了消息持久性和事务支持,确保消

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

uva 10061 How many zero's and how many digits ?(不同进制阶乘末尾几个0)+poj 1401

题意是求在base进制下的 n!的结果有几位数,末尾有几个0。 想起刚开始的时候做的一道10进制下的n阶乘末尾有几个零,以及之前有做过的一道n阶乘的位数。 当时都是在10进制下的。 10进制下的做法是: 1. n阶位数:直接 lg(n!)就是得数的位数。 2. n阶末尾0的个数:由于2 * 5 将会在得数中以0的形式存在,所以计算2或者计算5,由于因子中出现5必然出现2,所以直接一

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

go基础知识归纳总结

无缓冲的 channel 和有缓冲的 channel 的区别? 在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。 无缓冲的 channel 行为:无缓冲的 channel 是一种同步的通信方式,发送和接收必须同时发生。如果一个 goroutine 试图通过无缓冲 channel

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

这些心智程序你安装了吗?

原文题目:《为什么聪明人也会做蠢事(四)》 心智程序 大脑有两个特征导致人类不够理性,一个是处理信息方面的缺陷,一个是心智程序出了问题。前者可以称为“认知吝啬鬼”,前几篇文章已经讨论了。本期主要讲心智程序这个方面。 心智程序这一概念由哈佛大学认知科学家大卫•帕金斯提出,指个体可以从记忆中提取出的规则、知识、程序和策略,以辅助我们决策判断和解决问题。如果把人脑比喻成计算机,那心智程序就是人脑的