Golang编程:容易混淆的几个问题「1」

发表时间: 2022-05-14 11:57

学习一门新语言,就像是进行一场打怪升级的游戏,通关之前很沮丧,通关之后又特别喜悦。最近学习Golang,就有这种感觉。

Golang是最近比较热门的一款编程语言,它最重要的特性之一就是支持协程。协程可以理解为用户态的线程,相较于进程和线程,它更轻量、消耗的资源更少。也正是因为这个原因,Golang被广泛应用于对性能要求较高的项目中。

Golang设计简单,仅有25个关键字。虽然,这让学习和使用Golang的负担减少了很多,但对于新手而言,掉坑的风险同样也增加了不少。

下面是我在学习Golang的过程中碰到的几个一看就会一做就错的问题,总结出来与大家分享。

变量未初始化就使用

在Golang中,所有的变量都是需要进行初始化才能用的,不初始化就使用,会导致panic。

在Golang中,变量初始化的方式有显性和隐性之分。隐性初始化就是系统自动识别变量类型并进行初始化,显性初始化就是需要开发者主动使用new或make方法进行初始化。

map、指针类型的变量不能隐性初始化,只能显性初始化。如果不进行显性初始化,就直接使用,会产生panic。如下:

map没有初始化就使用会报错

指针没有初始化,也会报错

map、指针类型的变量,显性初始化可以使用new 或 make 函数。如下:

使用make函数进行初始化

使用new函数进行初始化

new函数和make函数的区别在于,new函数可以用于分配内存,它返回的指针;而make函数只能用于slice,map,chan类型,它返回的是类型。

在循环迭代器中使用协程

前面已经说过,协程是Golang很重要的能力,它常被用来处理异步耗时的任务。比如,发送邮件、订单发货等。由于多个协程是并发运行的,所以当你循环创建协程时,你要特别注意某些参数的传递,因为一不小心你可能就会触发问题。比如,下面这个案例的结果,你觉得会是什么呢?是0、1、2、……、10吗?

但是,如果你去亲自执行一下,你就会发现结果并非如此,实际可能是直接输出10,10,……,10。

为什么会这样呢?这里有两个原因,一个是因为循环变量i实际上只是一个普通的变量,每次循环时i都会被重新赋值,而并非生成新的变量;二个是因为循环比协程先执行完,导致协程开始执行时,i已经变成了最大值。

在上图中,我们在循环里加了睡眠时间以延长每次循环的时间,结果正常输出预期结果。这说明,我们上面说到的那两个原因是对的。虽然,这里通过设置睡眠时间,解决了问题,但是在实际工作中,我们并不能这样做。因为我们无法准确知道每个协程真实的执行时间,如果你设置的时间太长,又影响了系统的运行效率。所以,解决这个问题最好的方式是通过传参来解决。如下:

将循环变量i通过入参in传入到协程中。

误认为map是有序的

很多时候,map是否有序,对于我们的影响并不大,因为大多数使用场景就是存值取值而已,并不涉及对key的排序。但是有个使用场景你需要注意,那就是涉及到对json字符串进行校验的时候。比如,你有个接口,参数类型是application/json,它除了要解析参数外,可能还要对参数进行签名校验。

很多新手刚开始就是直接使用参数绑定的方式将json参数解析到map上,然后后续要取值、校验就只对map变量进行操作。如下:

他们以为json字符串解析到map变量后,key的顺序不会变化,但实际map是无序的,json字符串被解析成了map后,里面的key与原来在json字符串里的顺序是不一致的,所以后续逻辑如果要对json字符串进行签名校验,就会发现一直校验失败。如下:

像这种情况,我们要把参数解析和签名校验分开处理,校验签名时要获取最原始json串,当校验成功后才使用解析后生成的map变量来做后续处理。