if
- 最小化变量作用域。
if 接受初始化语句,尽可能缩小变量作用域。
// Bad
err := file.Chmod(0664)
if err != nil {
return err
}
// Good
if err := file.Chmod(0664); err != nil {
return err
}
如果需要在 if 之外使用函数调用的结果,则不应尝试缩小范围。
// Bad
if data, err := ioutil.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
// Good
data, err := ioutil.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
- if 对两个值进行判断时,被比较的值放在左边。
// Bad
if nil != err {
// error handling
}
if 0 == errorCode {
// do something
}
// Good
if err != nil {
// error handling
}
if errorCode == 0 {
// do something
}
- if 对于 bool 类型的变量,应直接进行真假判断。
var allowUserLogin bool
// Bad
if allowUserLogin == true {
// do something
}
if allowUserLogin == false {
// do something
}
// Good
if allowUserLogin {
// do something
}
if !allowUserLogin {
// do something
}
- 不必要的 else。
如果在 if 的两个分支中都设置变量,则可以将其替换为单个 if。
// Bad
var a int
if b {
a = 100
} else {
a = 10
}
// Good
a := 10
if b {
a = 100
}
又如 if else 通常可以简写为 if return。
// Bad
func Foo(bar int) {
if bar == 1 {
// ...
} else {
// ...
}
}
// Good
func Foo() {
if bar == 1 {
// ...
return
}
// ...
}
- 多个相似 if 用 switch 替换。
// Bad
func foo(key string) {
if key == pathKey {
...
}
if key == urlKey {
...
}
}
// Good
func foo(key string) {
switch key {
case pathKey:
...
case urlKey:
...
}
}
- 使用 == "" 判断字符串是否为空,这样更加直观。
// Bad
if len(str) == 0 {
...
}
// Good
if str == "" {
...
}
- 把简单的逻辑判断放前面,复杂的逻辑判断放后面。
- 不要使用双重否定。
- 判断条件较为复杂时,考虑封装成函数。
- 使用了 else if 则需要以 else 结束。
// Bad
if foo == "a" {
...
} else if foo == "b" {
...
}
// Good
if foo == "a" {
...
} else if foo == "b" {
...
} else {
// 需要有一个缺省处理逻辑
}
for
- 最小化变量作用域。
for 接受初始化语句,尽可能缩小变量作用域。
// Bad
sum := 0
i := 0
for ; i < 10; i++ {
sum += 1
}
// Good
sum := 0
for i := 0; i < 10; i++ {
sum += 1
}
- 循环变量的地址不要存储。
循环变量的地址指向的是同一个变量,我们可以通过赋值给一个同名的变量,通过变量逃逸,来达到取不同地址的目的。
// Bad
func main() {
ints := []int{1, 2, 3, 4, 5}
for _, v := range ints {
fmt.Println(&v) // 打印的是相同的地址
}
}
// Good
func main() {
ints := []int{1, 2, 3, 4, 5}
for _, v := range ints {
v := v
fmt.Println(&v) // 打印的是不同的地址
}
}
range
- 如果只需要第一项(key),就丢弃第二项(value)。
for key := range m {
if key.expired() {
delete(m, key)
}
}
- 如果只需要第二项,则把第一项置为空标识符(下划线)。
sum := 0
for _, v := range array {
sum += v
}
switch
- 必须要有 default。
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("MAC OS")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
return
- 尽早 return。
一旦有错误发生,马上返回。
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
d, err := f.Stat()
if err != nil {
return err
}
codeUsing(f, d)
goto
业务代码禁止使用 goto,其他框架或底层源码推荐尽量不用。
程序退出方式
- 使用
os.Exit
或者log.Fatal*
退出程序,而不是panic
。 - 在 main() 中退出程序且只退出一次。
仅在 main() 函数中调用os.Exit
或log.Fatal*
且只调用一次。如果有多个错误场景停止程序执行,请将该逻辑放在单独的函数并从中返回错误。 这会精简 main() 函数,并将所有关键业务逻辑放入一个单独的、可测试的函数中。
// Bad
package main
func main() {
args := os.Args[1:]
if len(args) != 1 {
log.Fatal("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 如果我们调用 log.Fatal f.Close 将不会被执行
b, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}
// ...
}
// Good
package main
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
// ...
}
当程序的多个函数具有退出能力时会存在一些问题:
(1)不明显的控制流:任何函数都可以退出程序,因此很难对控制流进行推理;
(2)难以测试:退出程序的函数也将退出调用它的测试,这使得函数很难测试,并跳过了尚未被运行的其他代码;
(3)跳过清理:当函数退出程序时,会跳过已经进入 defer 队列里的函数调用,这增加了跳过重要清理任务的风险。