写点什么

Go 语言开发小技巧 & 易错点 100 例(一)

作者:Barry Yan
  • 2022-10-15
    北京
  • 本文字数:3237 字

    阅读完需:约 1 分钟

Go语言开发小技巧&易错点100例(一)

全民制作人们大家好,我是练习时长两年半的个人练习生 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"))}
复制代码


运行结果:


Hello WorldHello zs
复制代码


这就是返回值屏蔽的效果,但是要想实现,就必须也要像定义参数一样去定义返回值,如func method(parm string) res string,就可以直接进行 return 了。

2 context 继承【易】

众所周知,在 Go 开发中 context 包是一个很常用并且重要的包,


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)}
复制代码


运行结果:


zs2022-10-15 14:38:46.0456413 +0800 CST m=+5.005663601 truels2022-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 退出【技】

方式一:


func main() {   defer func() {for {}}()   // TODO}
复制代码


方式二:


func main() {   defer func() { select {} }()   // TODO}
复制代码


方式三:


func main() {   // TODO   select {}}
复制代码

4 map 遍历次序【易】

Go 语言中 map 的遍历次序是无序的哈


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)   }}
复制代码


运行结果:


CDEAB
复制代码

5 main 函数提前退出【易】

你是否遇见过这种情况:


func main() {   go func() {      fmt.Println("Hello goruntine")   }()   fmt.Println("Hello main")}
复制代码


运行结果:


第一次运行:Hello main
第n次运行:Hello mainHello goruntine
第n+1次运行:Hello goruntineHello main
复制代码


为什么会导致这样的结果呢?


答案就是多线程,并且他们的线程并不是互斥的。


解决方式


不专业的方式


func main() {   go func() {      fmt.Println("Hello goruntine")   }()   fmt.Println("Hello main")   time.Sleep(time.Second * 5)}
复制代码


专业的方式


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 包文件:


package dao
import ( "fmt" "other/article/utils")
type User struct { Name string}
func InsertUser() { utils.CheckUser() fmt.Println("InsertUser")}
复制代码


utils 包文件:


package utils
import ( "fmt" "other/article/dao")
func CheckUser() { user := dao.User{Name: "zs"} fmt.Println("CheckUser", user)}
复制代码


main 文件:


package main
import ( "other/article/dao")
func main() { dao.InsertUser()}
复制代码


运行结果:


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 代码。


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")   }}
复制代码


运行结果:


1020
复制代码

8 简式变量声明仅能在函数内部使用【易】

什么是简式变量声明呢,我们知道 Go 声明变量有两种方式


// 第一种var i inti = 10
// 第二种 (简式变量声明)i := 10
复制代码


而第二种变量声明就不可以在方法外使用

9 interface 断言【易】

func main() {   var value interface{}   value = "hello"   str := value.(string)   fmt.Println(str)
value = 100 i := value.(int32) fmt.Println(i)}
复制代码


运行结果:


hellopanic: interface conversion: interface {} is int, not int32......
复制代码


解决方式


在断言之前先做一个类型判断


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

发布于: 刚刚阅读数: 4
用户头像

Barry Yan

关注

做兴趣使然的Hero 2021-01-14 加入

Just do it.

评论

发布
暂无评论
Go语言开发小技巧&易错点100例(一)_10月月更_Barry Yan_InfoQ写作社区