前言

这是Go常见错误系列的第12篇:Go语言中冗余的嵌套代码,俗称箭头型代码。

素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi

本文涉及的源代码全部开源在:Go常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。

常见错误

我们先看看如下的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func join(s1, s2 string, max int) (string, error) {
    if s1 == "" {
        return "", errors.New("s1 is empty")
    } else {
        if s2 == "" {
            return "", errors.New("s2 is empty")
        } else {
            concat, err := concatenate(s1, s2)
            if err != nil {
                return "", err
            } else {
                if len(concat) > max {
                    return concat[:max], nil
                } else {
                    return concat, nil
                }
            }
        }
    }
}
 
func concatenate(s1 string, s2 string) (string, error) {
    // ...
}

这段代码要做的事情很简单:

  • 把两个字符串s1和s2拼接起来,如果长度超过max,就只返回长度为max的子串。
  • 实现过程中,对s1和s2做了判空;对concatenate返回值有无error做了判断;

从功能正确性的角度来说,代码完全没有毛病。但是看着很费劲,因为嵌套了很多层。

优化版本

我们对上面的多层嵌套代码优化如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func join(s1, s2 string, max int) (string, error) {
    if s1 == "" {
        return "", errors.New("s1 is empty")
    }
    if s2 == "" {
        return "", errors.New("s2 is empty")
    }
    concat, err := concatenate(s1, s2)
    if err != nil {
        return "", err
    }
    if len(concat) > max {
        return concat[:max], nil
    }
    return concat, nil
}
 
func concatenate(s1 string, s2 string) (string, error) {
    // ...
}

这段代码实现了和刚才完全一样的功能,但是可读性好很多。

那有什么最佳实践我们可以作为参考,来规范代码实现呢?

最佳实践

通常来说,函数里的代码嵌套的层次越多,可读性就越差。可以参考如下原则来规范代码编写:

  • 当if语句里会return时,那不要继续用else了,直接把else里的内容和if放在同一个层次。

    bad case:

    1
    2
    3
    4
    5
    6
    
    if foo() {
        // ...
        return true
    } else {
        // ...
    }
    

    good case:

    1
    2
    3
    4
    5
    
    if foo() {
        // ...
        return true
    }
    // ...
    
  • 如果修改判断条件,可以减少嵌套层次,可以考虑对判断条件做调整。示例如下:

    bad case:

    1
    2
    3
    4
    5
    
    if s != "" {
        // ...
    } else {
        return errors.New("empty string")
    }
    

    good case:

    1
    2
    3
    4
    
    if s == "" {
        return errors.New("empty string")
    }
    // ...
    
  • 归纳上面2个原则,其实就是能先return的就先return,减少不必要的代码嵌套。

    感兴趣的也可以看看参考资料里"左耳朵耗子"写过的一篇文章"如何重构箭头型代码",

下一篇文章,我们会讲解下Go语言里init函数的常见错误和最佳实践。

推荐阅读

开源地址

文章和示例代码开源在GitHub: Go语言初级、中级和高级教程

公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。

个人网站:Jincheng’s Blog

知乎:无忌

福利

我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。

关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。

发送消息「进群」,和同行一起交流学习,答疑解惑。

References