一. 如何发现这个问题?
我是在浏览 Twitter
的时候, 发现博主 David Crawshaw 分享了一段代码,
推文地址: twitter.com/davidcrawsh…
原博文代码如下, 点击在 play 里运行:
package main
import "fmt"
func main() {
if a := 1; false {
} else if b := 2; false {
} else if c := 3; false {
} else {
fmt.Println(a, b, c) // 结果: 1 2 3
}
}
所以, 就扩展一下如下这种方式:
package main
import "fmt"
func main() {
if a := 1; false {
aa := 11
} else if b := 2; false {
bb := 22
} else if c := 3; false {
cc := 33
} else {
fmt.Println(a, b, c)
fmt.Println(aa, bb, cc) // 结果: undefined aa bb cc
}
}
再尝试如下这种方式:
package main
import "fmt"
func main() {
if x := 10; x < 9 {
fmt.Println(x)
} else {
fmt.Println("not x")
}
fmt.Println(x) // 结果: other if-else.go:11:14: undefined: x
}
脑洞再大点:
package main
import "fmt"
func main() {
if a := 1; false {
fmt.Println(a, b, c) // undefined: b, c
} else if b := 2; false {
fmt.Println(a, b, c) // undefined: c
} else if c := 3; false {
fmt.Println(a, b, c)
} else {
fmt.Println(a, b, c)
}
}
合理的猜测:
package main
import "fmt"
func main() {
if a := 1; false {
} else if b := 2; false {
} else if c := 3; false {
} else {
if a := 11; true {
fmt.Println(a, b, c) // 11 2 3
}
fmt.Println(a, b, c) // 1 2 3
}
}
所以可以得出一个初步的结论:
只有 if/else-if 条件表达式里的变量声明作用域才会向下到达最后 else
内部, 显而易见的不能向上作用, 作用域范围仅限于本级, 不影响正常的变量作用范围以及屏蔽作用.
二. 这么做有什么好处和坏处?
A. 好处:
- 如下面
Chris Hines
评论列出的代码 , 在处理 sql 任务的时候, 对返回值和错误做处理, 逻辑上看, 比较流畅.
if result, err := db.Exec(updateSql, ...); err != nil {
return err
} else if count, err := result.RowsAffected(); err != nil {
return err
} else if count != 1 {
return ErrNotUpdated
}
- 另一个评论的代码, emmmmm, 怎么说呢? 这么看代码确实比较简洁优美一些, 不过对于不了解这个特性的人来说, 可能有坑, 增加阅读难度.
if got, err := f(); err != nil {
// 巴拉巴拉巴拉
} else if want := ...; got != want {
t.Errorf(..., got, want)
}
好在我们可以加注释. 改成如下, 在可读性上会好点:
// NOTE: golang 特性, got 的作用域贯穿整个 if-else...
if got, err := f(); err != nil {
// 巴拉巴拉巴拉
} else if want := ...; got != want {
t.Errorf(..., got, want)
}
- 语言的设计就是如此: 参考设计文档: golang.org/ref/spec#If…, 官方并没有明确的说这么做的好处. 不过确实给书写代码带来了方便.
B. 坏处:
- emmmmm, 就是对于不知道这个特性的人来说比较奇怪. 不是很理解, 不符合编程的核心原则: 代码首先是给人看的, 顺便给机器运行.