高质量编程与性能调优实践 | 豆包MarsCode AI 刷题

85 阅读5分钟

编程原则

简单性

  • 消除“多余的复杂性”,以简单清晰的逻辑编写代码

  • 不理解的代码无法修复改进

可读性

  • 代码是写给人看的,而不是机器

  • 编写可维护代码的第一步是确保代码可读

生产力

  • 团队整体工作效率非常重要

编码规范 - 注释

注释应该解释代码作用

每个函数应该用注释解释它的行为、功能、作用,包括预期结果、异常等。但是,如果通过函数名、参数名,能够大概知道函数的作用时,就没有必要再编写重复描述的注释了。

注释应该解释代码如何做的

对于一个函数或代码块,应当用适当的注释解释是如何实现的。

注释应该解释代码实现的原因

对于某个目的不太明确的代码块,应当提供额外的上下文注释,以便于解释这段代码块出现的外部因素。

注释应该解释代码什么情况会出错

如果某个函数对于输入参数有一定的约束条件时,应当编写函数可能出错的情况。

公共符号是重要注释

  • 包中声明的每个公共的符号:变量、常量、函数以及结构都需要添加注释。

  • 任何既不明显也不简短的公共功能必须予以注释。

  • 无论长度或或复杂程度如何,对库中的任何函数都必须进行注释。

编码规范 - 命名规范

变量

  • 简洁胜于冗长

  • 缩略词全大写,但其位于变量开头且不需要导出时,使用全小写。例如使用 ServeHTTP 而不是 ServeHttp 。

  • 变量距离其被使用的地方越远,则需要携带更多的上下文信息。

函数

  • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的。

  • 函数名尽量简短。

  • 当名为 foo 的包某个函数返回类型为 Foo 时,可以省略类型信息。

  • 当名为 foo 的包某个函数返回类型为 T 时( T 并不是 Foo ),可以在函数名中加入类型信息。

  • 只由小写字母组成。不包含大写字母和下划线等字符。

  • 简短并包含一定的上下文信息。

  • 不要与标准库同名。例如,不要使用 sync 或者 strings

  • 不适用常用变量名作为包名。例如使用 bufio 而不是 buf

  • 使用单数而不是复数。

编程规范 - 流量控制

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

例如,如果两个分支都会 return ,则可以去除冗余的 else 。

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

// Good
if foo {
    return x
}
return nill

尽量保证正常代码路径为最小缩进

优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套。例如,用多个嵌套的 if 语句来处理正常返回情况是不好的。

// 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 {
    if err := doSomething(); err != nil {
        return err
    }
    if err := doAnotherThing(); err != nil {
        return err
    }
    return nil  // normal case
}

编程规范 - 错误和异常处理

简单错误

  • 简单错误指的是仅出现一次的错误,不会在其他地方捕获该错误。

  • 有限使用 errors.New 来创建匿名变量来直接表示简单错误。

  • 如果有格式化的需求,使用 fmt.Errorf

错误的 Wrap 和 Unwrap

  • 错误的 Wrap 实际上是提供了一个 error 嵌套另一个 error 的能力,从而生成一个 error 的跟踪链。

  • 在 fmt.Errorf 中使用 %w 关键字来实现将一个错误关联至错误链中。

错误判定

  • 使用 errors.is 来判定错误是否为特定错误。

  • 不同于使用 == , 该方法可以判定错误链上的所有错误。

  • 在错误链上获取特定种类的错误,使用 errors.As 。

性能优化建议

测试工具使用

使用指令go test -bench=. -benchmem来测试评估某个函数的性能。测试指标包括执行时间、占用内存大小等。

2024-11-06-12-17-42-image.png

性能优化建议 - Slice

  • slice 预分配内存,尽可能在使用 make() 初始化切片时提供容量信息。

  • 在已有的切片基础上创建新的切片,不会创建新的底层数组。因此,使用 copy 代替直接 re-slice , 以便于释放原 slice 。

origin := make([]int, 0, 10000)
result := origin[:3]  // 不会创建新的 slice
copy(result, origin[:3])  // 会创建新的 slice

性能优化建议 - Map

  • 同 slice 建议预分配内存,这是因为不断向 map 中添加元素的操作会触发 map 的扩容。

性能优化建议 - 字符串

  • 字符串在 Go 语言中是不可变类型,占用内存大小是固定的

  • 使用 + 每次都会重新分配内存。

  • strings.Builder , bytes.Buffer 底层都是 []byte 数组。

  • 内存扩容策略,不需要每次拼接都重新分配内存。

性能优化建议 - 空结构体

  • 空结构体本身不占用内存

  • 实现 Set , 可以考虑用 map[T]struct{} 来代替。

  • 只用到 map 的键。

  • 即使将 map 的值设置为 bool 类型,也会多占用 1 个字节空间。

总结

本次课程主要介绍了代码格式、注释、命名规范、控制流程、错误和异常处理等方面的常见编码规范,以确保代码的可读性、可维护性、可扩展性。此外,本节课还提出了有关提高程序运行效率的性能优化建议,包括 slice, map, string, empty struct 等方面的性能优化建议。同时,通过指令go test -bench=. -benchmem来测试评估程序的性能。