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

259 阅读4分钟

本节课涉及到高质量编程中的以下几点:

  1. 什么是高质量编程?
  2. 高质量编程编码规范
  3. 性能优化建议

高质量编程

概念

编程的基本目标是完成对应的功能,但这远远称不上高质量编程。在编程时,不仅要达到对应的目标,还要达到可靠、简洁、清晰、低复杂性、可扩展、风格统一的目标。

Go语言开发者 Dave Cheney 关于高质量编程的观点:

  • 简单性:要以简单清晰的逻辑编写代码,不理解的代码无法修复改进
  • 可读性:代码是写给人看的,要编写可维护代码,首先要确保代码可读
  • 生产力:团队整体工作效率十分重要

编码规范

像Google等很多大公司都推出了自己的编码规范。比较著名的如阿里巴巴的阿里巴巴Java编码规范和Google的Effective Go等等。在这里我们抽取一些公共的规范来介绍。

  1. 公共符号始终要注释

注释应该做的:

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

重点:无论长度或复杂度,对外公开的任何符号都要进行注释。

代码是最好的注释,而注释的目的是提供代码之外的帮助信息。对于公共库函数,必须要做到事无巨细地介绍库中的每一处细节。所以对于程序中向外公开的任何符号都要进行注释。但是,对接口的实现函数不需要注释,因为在此处注释不能提供任何额外信息。


// 此函数根据输入的路径打开一个文件
// 如果成功找到并打开文件,将返回 File结构体和nil
// 如果在打开过程中发生错误,将返回nil和错误描述
// 具体错误描述请参考 error.go
func OpenFile(path string) (File, error)

func (f *File) Delete() error
  1. 代码格式

编写程序时要始终编写具有通用格式的代码,在这个方面,go语言为我们提供了 gofmt 工具,能够自动格式化 Go 代码为官方统一风格。常见的 IDE 都能很方便的完成 gofmt 工具的配置,例如 GoLand IDE 默认启用了在编辑结束后自动对 Go 文件格式化的功能。

Go 语言还为我们提供了 goimports 工具,作用在于自动增删依赖的包引用,将依赖包按字母序排序分类,并具有 gofmt 的全部功能。

  1. 命名规范

变量

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

例如,在常见的 for 循环中,上边的索引变量名 index 显然比下方的 i 冗长:


// Bad
for index := 0; index < len(s); index++ {
    // Do something
}

// Good
for index := 0; index < len(s); index++ {
    // Do something
}

在上面的代码中,index 这一变量名没有增加对程序的理解,反而导致程序变得冗长了。

此外,在对外提供的函数的参数或全局变量中,最好令变量名带有更多的信息:

// Good
func (c *Client) send(req *Request, deadline time.Time)

// Bad
func (c *Client) send(req *Request, t time.Time)

t 这个名字可以指任意时间,不如 deadline 更加精确和有意义。

函数

  • 函数名不携带包名的信息,因为包名和函数名总是成对出现的
  • 函数名应当尽量简短
  • 当名为 foo 的包的某个函数返回类型和包名无关系时,可以在函数名中加入返回类型信息

例如,在下面的两个函数中,明显第一个函数更好。

package http

// Good
func Serve(l net.Listener, handler Handler) error

// Bad
func ServeHTTP(l net.Listener, handler Handler) error

包名

  • 只由小写字母组成,不包含大写字母和下划线等
  • 简短并包含一定的上下文信息
  • 不要与已有的库(例如标准库)同名
  • 不使用常用变量名,包名要有特点
  • 使用单数
  • 谨慎使用缩写

总体来说,命名规范的核心是降低阅读理解代码的成本,重点考虑上下文信息,设计简洁清晰的名称。

  1. 控制流程

避免嵌套,保持正常流程清晰

在下面的示例中,如果两个分支中都包含 return 语句,则可以去除冗余的 else


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

// Good
if foo {
    return x
}
return nil

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

// 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
}

小结:

  • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
  • 正常流程代码沿着屏幕向下移动
  • 提升代码可维护性和可读性
  • 故障问题大多出现在复杂的条件语句和循环语句中
  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
}

错误的 Wrap 和 Unwrap:

  • 错误的 Wrap 实际上提供了一个错误嵌套另一个错误的能力,从而生成了一个错误的跟踪链
  • fmt.Errorf 中使用 %w 关键字来将一个错误关联到错误链中
result, err := doSomething()
if err != nil {
    return fmt.Errorf("error when do something: %w", err)
}

错误判定

  • 判定一个错误是否为特定错误,使用 errors.Is
  • 不同于使用 ==, 使用该方法可以判断错误链上的所有错误中是否含有特定错误
data, err := loadDataFromFile("PATH")
if errors.Is(err, fs.ErrNotExist) {
    // File not found at filesystem
    data.NewFile()
}
  • 在错误链上获取特定种类的错误,使用 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
    }
}

panic 的使用

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

recover 的使用

  • recover 只能在被 defer 的函数中使用
  • 嵌套无法生效
  • 只在当前 goroutine 生效
  • defer 的语句是先进后出

recover 的一个重要作用是在 panic 后记录重要信息