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.Exitlog.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 队列里的函数调用,这增加了跳过重要清理任务的风险。

powered by Gitbook该文章修订时间: 2024-08-04 07:27:05

results matching ""

    No results matching ""