大家好,我是煎鱼。
前一次给大家分享了《你会犯这些 Go 编码谬误吗(一)?》,不晓得大家排汇的怎么样,还有再踩到相似的坑吗?
明天持续来第二弹,跟煎鱼上车。
Go 常见谬误
6. 同名变量的作用域
问题
咱们在编写程序时,因为各种长期变量,会罕用变量名 n、i、err 等。有时候会遇到一些问题,如下代码:
func main() {
n := 0
if true {
n := 1
n++
}
fmt.Println(n)
}
程序的输入后果是什么。n 是 1,还是 2?
输入后果:
0
解决办法
上述代码的 n := 1
又从新申明了一个新变量,他是同名变量,同时很要害的,他蕴含同名局部变量。
咱们的 n++
影响的是 if 区块里的变量 n,而不是内部的同名变量 n。如果要正确影响,该当批改为:
func main() {
n := 0
if true {
n = 1
n++
}
fmt.Println(n)
}
输入后果:
2
这一个案例尽管繁多提出来看并不简单,但在许多 Go 初学者刚入门时常常会在应用程序中遇到相似的问题,而后问为什么不行 …
据鱼的 7s 记忆,在全局 DB 句柄中等场景中遇到这个问题,来问煎鱼的应该有 10 次以上,是一个比拟高频的”坑“了。
7. 循环中的长期变量
问题
置信不少同学在业务代码中做过相似的事件,那就是:边循环解决业务数据,边变更值内容。
如下代码:
s := []int{1, 1, 1}
for _, n := range s {n += 1}
fmt.Println(s)
程序输入的后果是什么,i 胜利均都 +1 了吗?
不,真正的输入后果是:[1 1 1]
。
解决办法
实际上在循环中,咱们所援用的变量是 长期变量,你去批改他是没有任何意义的,批改的基本不是你的原数据的构造。
咱们须要定位到原数据,依据索引去定位批改。如下代码:
s := []int{1, 1, 1}
for i := range s {s[i] += 1
}
fmt.Println(s)
输入后果:
[2 2 2]
这很常见,一个不留神就会手抖。
8. JSON 转换和输入为空
问题
在做对外接口的数据对接和转换时,咱们常常须要对 JSON 数据进行解决。
如下代码:
type T struct {
name string
age int
}
func main() {p := T{"煎鱼", 18}
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))
}
输入后果是什么,能把姓名和年龄失常输入吗?
输入后果:
{}
你没有看错,程序的输入后果是空,没有转换到任何货色。
解决办法
起因是 JSON 的输入,只会输入公开(导出)字段,也就是首字母必须为大写。
咱们须要进行如下革新:
type T struct {
Name string
Age int
}
func main() {p := T{"煎鱼", 18}
jsonData, _ := json.Marshal(p)
...
}
输入后果:
{"Name":"煎鱼","Age":18}
又或是显式指定 JSON 的标签:
type T struct {
Name string `json:"name"`
Age int `json:"age"`
}
当初 IDE 可能很不便的间接生成 JSON 标签了,倡议大家能够习惯性补上,确保字段标准。
真的能够防止不少的对接时的拉扯和误差。
9. 认为 recover 是万能的
问题
在 Go 中,goroutine + panic + recover 是天作之合,用起来很不便。经常会有同学认为他是万能的。
如下代码:
func gohead() {go func() {panic("煎鱼上班了")
}()}
func main() {go func() {defer func() {if r := recover(); r != nil {fmt.Println(r)
}
}()
gohead()}()
time.Sleep(time.Second)
}
你认为输入后果是什么。程序被中断,还是胜利 recover 了?
输入后果:
panic: 煎鱼上班了
goroutine 17 [running]:
main.gohead.func1()
/Users/eddycjy/awesomeProject/main.go:10 +0x39
created by main.gohead
/Users/eddycjy/awesomeProject/main.go:9 +0x35
你认为 recover 是万能的?并不。
解决办法
Go1 现阶段没有万能解决办法,只能恪守 Go 的标准(Go 就是不能跨 goroutine recover,是正确的逻辑)。
Goroutine 倡议每个 goroutine 都须要有 recover 兜底,否则一旦呈现 panic 就会导致利用中断,容器会重启。
另外 Go 底层被动抛出的致命谬误 throw,是没有方法应用 recover 拦挡的,请务必留神。
注:有同学始终认为 recover 能够拦挡到 throw,特此说明。
10. nil 不是 nil
问题
咱们在做程序的逻辑解决时,常常要对接口(interface)值进行判断。
如下代码:
func Foo() error {
var err *os.PathError = nil
return err
}
func main() {err := Foo()
fmt.Println(err)
fmt.Println(err == nil)
}
你认为输入后果是什么,是 nil 和 true 吗?
输入后果:
<nil>
false
外表看起来的 nil,它并不等于 nil。
解决办法
接口值,是非凡的。只有当它的值和动静类型都为 nil 时,接口值才等于 nil。
在后面的问题代码中,实际上函数 Foo
返回的是 [nil, *os.PathError]
,咱们将其与 [nil, nil]
进行比拟,所以是 false。
如果要精确判断,要进行如下转换:
fmt.Println(err == (*os.PathError)(nil))
输入后果就会为 true。
另外就是尽量应用 error 类型,又或是防止与 interface 进行比拟,这是比拟危险的行为(有不少人不晓得这一景象)。
总结
在明天这篇文章中,咱们开始了 Go 常见编码谬误的第二节,共波及 5 个案例:
- 同名变量的作用域。
- 循环中的长期变量。
- JSON 转换和输入为空。
- 认为 recover 是万能的。
- nil 不是 nil。
这仍然是十分常见的案例,你有没有遇到过?或是有其它的新的案例呢?
欢送大家一起交换和沟通。
文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。
举荐浏览
- Go 语言入门系列:初探 Go 我的项目实战
- Go 语言编程之旅:深刻用 Go 做我的项目
- Go 语言设计哲学:理解 Go 的为什么和设计思考
- Go 语言进阶之旅:进一步深刻 Go 源码
参考
- 案例来自 Stefan Nilsson:Go gotchas
- 案例来自 24 Common Mistakes in Go (gotchas) And How To Avoid Them