此前有一篇 Golang开发环境中的技巧,偏向于 go tools 等等的记录,今后还会继续收录,因为那些碎片记忆起来有负担,不如收归一个确定的地点,省得寻找不易。

但今天这篇虽然 link 差别不大,关注的重点却不在于 go tools 方面或者 ide 方向,而是纯粹的代码编写上的技巧。

我认为是的,你随意就好。

In Coding

函数调用时,不要 inline 另一个调用

在业务逻辑编码过程中,避免使用嵌套连环函数调用。

也就是说,下面这样的代码不是好的:

1
2
3
if err = user.Verify(formUserID(), formUsername(), formPassword()); err != nil {
  // ...
}

怎么才是好的?将函数调用分离开,每个函数调用的参数都是简单的,可就地计算的,如同这样:

1
2
3
4
uid, usr, pwd := formUserID(), formUsername(), formPassword()
if err = user.Verify(uid, usr, pwd); err != nil {
  // ...
}

为什么?

因为这样改写之后,我们可以在 line 2 打上断点,而且很容易了解 usr 值,然后很容易就地单步进入到 Verify 函数体之中。

而采用前一写法时,在 line 1 上断点,不但不能知道 usr 和 pwd 究竟是什么值,而且单步之后也不知道飞到哪儿去了,完全无法直接进入到 Verify 函数体中。

有做过单步跟踪的,自然而然就会理解到我所说的意思。

error 检测语句的优化

我没有打算提供另一个 checkerr(err) 函数写法。

实际上,我曾经在以前( Golang Testing 概览 - 基本篇 )提到过,很多很多 err != nil 语句可以改写为:

1
2
3
4
5
6
7
8
9
10
11
12
func A(filePath string) (err error) {
  var f *os.File
  if f, err = os.Open(filePath); err == nil {
    // open ok
    if f.IsRegular() {
      // go further ...
      return callForF(f)
    }
  }
  //
  return // has error
}

也即改写为深入嵌套的 err == nil 测试,在嵌套 if 的中央,用一个子函数调用(例如 callForF(f) )逃出。

这样做的用意在于:

  1. 约束函数代码到仅有两个出口点,这将会非常有利于调试(容易做断点,但难以确定错误退出的分支——除非参考 err 的具体值来判断原因)
  2. 比较容易做 coverage,很容易就能覆盖掉这个函数的一切分支

当然我已经提到过了,请勿滥用,因为它并不总是最佳选择。

错误容器

此外,在 Golang errors 编程模型 - Part II - 错误容器 中,我也提到过 error container 的概念。借助于 hedzr/errors.v2 可以不断地将若干 error 缓存到一个 container 中,然后收束为一个单一的 error 对象向上层返回。这个单一个 error 对象将会罗列出全部发生过的 errors。

最简短的示意性代码片段为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "gopkg.in/hedzr/errors.v2"

func checker() (err error) {
  var ec = errors.NewContainer("Checking for users ...\n")
  for _, u := range listAllUsers() {
    ec.Attach(checkUserIntegrities())
  }
  return ec.Error()
}


func checkUserIntegrities(u *User) (err error) {
  if ... {
    err = errors.New("forbidden")
  }
  return
}

在 line 5 你可以看见,我们不停地向容器 ec 中添加(Attach)错误对象,如果 checkUserIntegrities 返回的是没有错误(err == nil)也没有关系,因为 nil 会被 Attach 所忽略。所以我们现在的到了一个最简练的错误容器及其追加方法,而且使用起来也非常容易。

而且,收束的动作也很轻巧:用 ec.Error() 就可以获得一个 error 对象,它收容了曾经在 checkUserIntegrities 中检测到的所有 errors。

小结

这种收集,也只能慢慢来了。没有太多心情系统地整理,也没有这样的时间块。

🔚