全民制作人们大家好,我是练习时长两年半的个人练习生Barry Yan,喜欢唱、跳、Coding、羽毛球,Music!
今天给大家带来的这一档文章呢,主要是总结一下自己Coding过程中遇到的问题以及平时读一些博客的所得,因为做gopher也有了一段时间了,相比Java,有些问题的出现想要利用搜索引擎排查出来可能不是那么的迅速,所以在这里以文章的形式总结出来也方便各位初出茅庐的gopher们能够顺利的解决所遇到的问题,并能够习得一些小技巧。
为什么叫《Go语言开发小技巧&易错点100例》呢,说实话我也不知道能不能写到100例,只能说作为自己的一个小目标吧,先赚它一个亿,哈哈哈,只有目标才能促使自己不断Coding,不断发现和总结问题,相信到最后肯定要多于100个的,今天就先来9个!
先罗列一下吧(技巧类用【技】表示,易错点用【易】表示):
(1)return返回值屏蔽【技】
(2)context继承【易】
(3)禁止main退出【技】
(4)map遍历次序【易】
(5)main函数提前退出【易】
(6)包循环依赖【易】
(7)fallthrough关键字【技】
(8)简式变量声明(i:=1)仅能在函数内部使用【易】
(9)interface断言【易】
正文:
1 return返回值屏蔽【技】
返回值屏蔽的概念就是直接return也能返回函数的返回值,但是需要将返回值进行赋值操作,比如我们定义一个函数:func method(parm string) string
,返回值为string类型,实现函数时就会要求我们必须要return一个string类型的变量,但是如下的代码示例中直接一个return,并且也能正常执行
func Hello(name string) (str string) {
str = "Hello World"
if name != "" {
return "Hello " name
}
return
}
func main() {
fmt.Println(Hello(""))
fmt.Println(Hello("zs"))
}
运行结果:
代码语言:txt复制Hello World
Hello zs
这就是返回值屏蔽的效果,但是要想实现,就必须也要像定义参数一样去定义返回值,如func method(parm string) res string
,就可以直接进行return了。
2 context继承【易】
众所周知,在Go开发中context包是一个很常用并且重要的包,
代码语言:go复制func Handler(ctx context.Context) {
fmt.Println(ctx.Value("name"))
fmt.Println(ctx.Deadline())
}
func Controller(ctx context.Context) {
fmt.Println(ctx.Value("name"))
fmt.Println(ctx.Deadline())
ctx = context.WithValue(ctx, "name", "ls")
ctx, _ = context.WithTimeout(ctx, time.Second*10)
Handler(ctx)
}
func main() {
ctx := context.WithValue(context.Background(), "name", "zs")
ctx, _ = context.WithTimeout(ctx, time.Second*5)
Controller(ctx)
}
运行结果:
代码语言:shell复制zs
2022-10-15 14:38:46.0456413 0800 CST m= 5.005663601 true
ls
2022-10-15 14:38:46.0456413 0800 CST m= 5.005663601 true
context的部分规则如下:
- WithCancel:基于父级 context,创建一个可以取消的新 context。
- WithDeadline:基于父级 context,创建一个具有截止时间(Deadline)的新 context。
- WithTimeout:基于父级 context,创建一个具有超时时间(Timeout)的新 context。
- Background:创建一个空的 context,一般常用于作为根的父级 context。
- TODO:创建一个空的 context,一般用于未确定时的声明使用。
- WithValue:基于某个 context 创建并存储对应的上下文信息。
一般会有父级 context
和子级 context
的区别,我们要保证在程序的行为中上下文对于多个 goroutine
同时使用是安全的。并且存在父子级别关系,父级 context
关闭或超时,可以继而影响到子级 context
的程序。
3 禁止main退出【技】
方式一:
代码语言:go复制func main() {
defer func() {for {}}()
// TODO
}
方式二:
代码语言:go复制func main() {
defer func() { select {} }()
// TODO
}
方式三:
代码语言:go复制func main() {
// TODO
select {}
}
4 map遍历次序【易】
Go语言中map的遍历次序是无序的哈
代码语言:go复制func main() {
m := make(map[string]string)
m["A"] = "a"
m["B"] = "b"
m["C"] = "c"
m["D"] = "d"
m["E"] = "e"
for i := range m {
fmt.Println(i)
}
}
运行结果:
代码语言:shell复制C
D
E
A
B
5 main函数提前退出【易】
你是否遇见过这种情况:
代码语言:go复制func main() {
go func() {
fmt.Println("Hello goruntine")
}()
fmt.Println("Hello main")
}
运行结果:
代码语言:shell复制第一次运行:
Hello main
第n次运行:
Hello main
Hello goruntine
第n 1次运行:
Hello goruntine
Hello main
为什么会导致这样的结果呢?
答案就是多线程,并且他们的线程并不是互斥的。
解决方式:
不专业的方式
代码语言:go复制func main() {
go func() {
fmt.Println("Hello goruntine")
}()
fmt.Println("Hello main")
time.Sleep(time.Second * 5)
}
专业的方式
代码语言:go复制func main() {
group := sync.WaitGroup{}
group.Add(1)
go func() {
defer group.Done()
fmt.Println("Hello goruntine")
}()
fmt.Println("Hello main")
group.Wait()
}
6 包循环依赖错误【易】
先说明下场景:
我们在dao层的文件中定义结构体和其相关的dao层方法,但是在调用方法时(如插入数据的方法)会使用utils包中的工具方法对参数进行检查,而utils中的工具方法需要引用dao层的结构体才能够检查,因此出现了dao依赖utils包,utils包依赖dao包的情况,就导致了循环依赖的异常。
dao包文件:
代码语言:go复制package dao
import (
"fmt"
"other/article/utils"
)
type User struct {
Name string
}
func InsertUser() {
utils.CheckUser()
fmt.Println("InsertUser")
}
utils包文件:
代码语言:go复制package utils
import (
"fmt"
"other/article/dao"
)
func CheckUser() {
user := dao.User{Name: "zs"}
fmt.Println("CheckUser", user)
}
main文件:
代码语言:go复制package main
import (
"other/article/dao"
)
func main() {
dao.InsertUser()
}
运行结果:
代码语言:shell复制package command-line-arguments
imports other/article/dao
imports other/article/utils
imports other/article/dao: import cycle not allowed
解决方式:
将dao层的结构体移到一个新包中,并且dao和utils都引用这个新包。
这个错误也告诉我们一个道理,就是代码要注意划分层次,低内聚,才能更好的增加代码的可读性。
7 fallthrough关键字【技】
Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。
代码语言:go复制func main() {
i := 10
switch i {
case 1:
fmt.Println(1)
case 5:
fmt.Println(5)
fallthrough
case 10:
fmt.Println(10)
fallthrough
case 20:
fmt.Println(20)
default:
fmt.Println("default")
}
}
运行结果:
代码语言:shell复制10
20
8 简式变量声明仅能在函数内部使用【易】
什么是简式变量声明呢,我们知道Go声明变量有两种方式
代码语言:go复制// 第一种
var i int
i = 10
// 第二种 (简式变量声明)
i := 10
而第二种变量声明就不可以在方法外使用
9 interface断言【易】
代码语言:go复制func main() {
var value interface{}
value = "hello"
str := value.(string)
fmt.Println(str)
value = 100
i := value.(int32)
fmt.Println(i)
}
运行结果:
代码语言:shell复制hello
panic: interface conversion: interface {} is int, not int32
......
解决方式:
在断言之前先做一个类型判断
代码语言:go复制func main() {
var value interface{}
value = 100
switch value.(type) {
case int32:
fmt.Println(value.(int32))
case string:
fmt.Println(value.(string))
case int:
fmt.Println(value.(int))
}
}
当然GitHub有更好的方式可以将interface类型转化成我们需要的类型,比如cast插件。
参考:
https://chai2010.cn/advanced-go-programming-book/appendix/appendix-a-trap.html
https://errorsingo.com/
https://www.kancloud.cn/gopher_go/go/848998
https://blog.csdn.net/Guzarish/article/details/119627758
我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表