高质量编程简介及编码规范 | 青训营

88 阅读4分钟

高质量编程概念

高质量编程指的是编写出正确可靠、简洁清晰的代码。其需要达成以下条件:各种边界条件考虑晚辈;能够处理异常情况,保证稳定性;易读易维护。由Go语言开发者 Dave Cheney 提出的编程原则分为以下几点:

  1. 简单性:消除多余的复杂性,要以简单清晰的逻辑编写代码,不理解的代码无法修复改进。
  2. 可读性:代码是写给人看的,而不是机器,要编写可维护代码,首先要确保代码可读。
  3. 生产力:团队整体工作效率十分重要

编码规范

为了编写高质量的Go代码,需要遵从以下编码规范:

1.代码格式

编写Go代码推荐使用gofmt自动格式化代码。gofmt是Go语言官方提供的工具,能够自动格式化 Go 代码为官方统一风格。常见的 IDE 都能很方便的完成 gofmt 工具的配置,例如 GoLand IDE 默认启用了在编辑结束后自动对 Go 文件格式化的功能;Go 语言还为我们提供了 goimports 工具,实际等同gofmt加上依赖包管理,可以实现自动增删依赖的包引用。

2.注释

注释的作用是帮助开发者和使用者更好地理解代码。注释应该做的有:

  1. 解释代码作用。
  2. 解释代码如何做的: 适合注释实现过程。
  3. 解释代码实现的原因: 适合解释代码的外部因素,提供额外上下文。
  4. 解释代码什么情况下会出错: 适合解释代码的限制条件。 5.公共符号始终要注释。

3.命名规范

1.简洁胜于冗长。 2.缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写 例如使用 ServeHTTP 而不是 ServeHttp,使用 XMLHTTPRequest 或者 xmlHTTPRequest 3.变量在定义时距离其被使用的地方越远,则需要携带越多的上下文信息 4.全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义

4.控制流程

1.避免嵌套,保持正常流程清晰 在下面的示例中,如果两个分支中都包含 return 语句,则可以去除冗余的 else:

// Bad
if foo {
    return x
} else {
    return nil
}

// Good
if foo {
    return x
}
return nil

2.尽量保持正常代码路径为最小缩进,优先处理错误情况和特殊情况,尽早返回或继续循环来减少嵌套,示例代码如下:

// Bad
func OneFunc() error {
    err := doSomething()
    if err == nil {
        err := doAnotherThing()
        if err == nil {
            return nil // normal case
        }
        return err
    }
    return err
}

// Good
func OneFunc() error {
    err := doSomething()
    if err != nil {
        return err
    }
    err = doAnotherThing()
    if err != nil {
        return err
    }
    return nil // Normal case
}

5.错误和异常处理

1.简单错误:简单错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误;优先使用 errors.New 来创建匿名变量直接表示该错误;如果有格式化需求,使用 fmt.Errorf,示例代码如下:

func defaultCheckRedirect(req *Request, via []*Request) error {
    if len(via) >= 10 {
        return errors.New("Stopped after 10 redirects")
    }
    return nil
}

2.错误的 Wrap 和 Unwrap:错误的 Wrap 实际上提供了一个错误嵌套另一个错误的能力,从而生成了一个错误的跟踪链。在 fmt.Errorf 中使用 %w 关键字来将一个错误关联到错误链中,示例代码如下:

result, err := doSomething()
if err != nil {
    return fmt.Errorf("error when do something: %w", err)
}

3.错误判定:判定一个错误是否为特定错误,使用 errors.Is,不同于使用 ==, 使用该方法可以判断错误链上的所有错误中是否含有特定错误,示例代码如下:

go复制代码data, err := loadDataFromFile("PATH")
if errors.Is(err, fs.ErrNotExist) {
    // File not found at filesystem
    data.NewFile()
}

4.在错误链上获取特定种类的错误,使用 errors.As,具体代码如下:

data, err := loadDataFromFile("PATH")
if err != nil {
    var pathError *fs.PathError
    if errors.As(err, &pathError) {
        fmt.Println("Failed to load file at path:", pathError.Path)
    } else {
        return nil
    }
}

5.panic 的使用:不建议在业务代码中使用 panic;调用函数若不包含 recover 会造成程序崩溃;若问题可以被屏蔽或解决,建议使用 error;当程序在启动节点发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic。

6.recover 的使用:recover 只能在被 defer 的函数中使用;嵌套无法生效;只在当前 goroutine 生效 defer 的语句是先进后出;recover 的一个重要作用是在 panic 后记录重要信息。