4.3. 尽早 return 而不是深度嵌套

由于 Go 语言的控制流不使用 exception,因此不需要为 trycatch 块提供顶级结构而深度缩进代码。Go 语言代码不是成功的路径越来越深地嵌套到右边,而是以一种风格编写,其中随着函数的进行,成功路径继续沿着屏幕向下移动。 我的朋友 Mat Ryer 将这种做法称为“视线”编码。[4]

这是通过使用 guard clauses 来实现的; 在进入函数时是具有断言前提条件的条件块。 这是一个来自 bytes 包的例子:

  1. func (b *Buffer) UnreadRune() error {
  2. if b.lastRead <= opInvalid {
  3. return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune")
  4. }
  5. if b.off >= int(b.lastRead) {
  6. b.off -= int(b.lastRead)
  7. }
  8. b.lastRead = opInvalid
  9. return nil
  10. }

进入 UnreadRune 后,将检查 b.lastRead 的状态,如果之前的操作不是 ReadRune,则会立即返回错误。 之后,函数的其余部分继续进行 b.lastRead 大于 opInvalid 的断言。

与没有 guard clause 的相同函数进行比较,

  1. func (b *Buffer) UnreadRune() error {
  2. if b.lastRead > opInvalid {
  3. if b.off >= int(b.lastRead) {
  4. b.off -= int(b.lastRead)
  5. }
  6. b.lastRead = opInvalid
  7. return nil
  8. }
  9. return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune")
  10. }

最常见的执行成功的情况是嵌套在第一个if条件内,成功的退出条件是 return nil,而且必须通过仔细匹配大括号来发现。 函数的最后一行是返回一个错误,并且被调用者必须追溯到匹配的左括号,以了解何时执行到此点。

对于读者和维护程序员来说,这更容易出错,因此 Go 语言更喜欢使用 guard clauses 并尽早返回错误。