高质量编程与性能调优实战|Go语言基础|青训营笔记

29 阅读4分钟

这是我参与[第五届青训营]笔记创作的第三天

1、高质量编程

编写的代码能够达到正确可靠、简洁清晰的目标可称为高质量代码

简单性:

  • 消除“多余的复杂性”,以简单清晰的逻辑编写代码
  • 不理解的代码无法修复改进

可读性

  • 代码是写给人看的,而不是机器
  • 编写可维护代码的第一步是确保代码可读

生产力

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

1、编码规范

注释

公共符号始终要注释:对代码的功能要注释

  • 有一个例外,不需要注释实现接口的方法
注释应该做的
  • 注释应该解释代码作用

适合注释公共符号

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

适合注释实现过程

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

适合解释代码的外部因素

提供额外上下文

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

适合解释代码的限制条件

控制流程

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

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

错误和异常处理

简单错误
  • 简单错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
  • 优先使用errors.New来创建匿名变量来直接表示简单错误
  • 如果有格式化的需求,使用fmt.Errorf
func defaultCheckRedirect(req *Request,via []*Request) error {
    if len(via) >= 10 {
        return errors.New("stopped after 10 redirects") //在出现十次错误之后就停止
    }
    return nil
}

defer

  • defer语句会在函数返回前调用
  • 多个defer语句是后进先出
func main(){
    if true {
        defer fmt.Printf("1")
    }else {
        defer fmt.Printf("2")
    }
    defer fmt.Printf("3")
}

运行结果31

命名规范

  • 简洁
  • 缩略词全大写,但当其位于比那辆开头且不需要导出时,使用全小写
  • 变量近距离了其被使用的地方越远,则需要携带越多的上下文信息

函数名规范

  • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
  • 函数名尽量简短
  • 当foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
  • 当名为foo的包某个函数返回类型T时(T并不是Foo),可以再函数名中加入类型信息

代码格式

推荐使用gofmt自动格式化代码

2、性能优化建议:

1、性能优化建议-Benchmark

  • 性能表现需要实际数据衡量
  • Go语言提供了支持基准性能测试的benchmark工具

go test -bench=. -benchmen

func Fib(n int) int {
    if n < 2{
        return n
    }
    return Fib(n - 1) + Fib(n -2)
}
​
func BenchmarkFib10(b *testing.B){
    for n := 0; n < b.N; n ++{
        Fib(10)
    }
}

2、性能优化建议-Slice

slice 预分配内存

尽可能在使用make()初始化切片时提供容量大小

func NoPreAlloc(size int) {
    data := make([]int,0)
    for k := 0; k <size;k ++{
        data = append(data,k)
    }
}//没有分配
func PreAlloc(size int) {
    data := make([]int,0,size)
    for k := 0; k <size;k ++{
        data = append(data,k)
    }
}//有分配

3、性能优化建议-Map

map 预分配内存

  • 不断向map中添加元素的操作会触发map扩容
  • 提前分配好空间可以减少内存拷贝和Rehash的消耗
  • 建议根据实际需求提前预估好需要的空间

4、性能优化建议-字符串处理

使用strings.Builder

func Plus(n int, str string) string {
    s := ""
    for i := 0; i < n ; i ++{
        s += str
    }
    return s
}//性能最差
func StrBuilder(n int, str string) string {
    var builder strings.Builder
    for i := 0; i < n; i++{
        builder.WriteString(str)
    }
    return builder.string()
}//性能第二强
func ByteBuffer(n int,str string) string {
    buf := new(bytes.Buffer)
    for i := 0; i < n;i ++{
        buf.WriteString(str)
    }
    return buf.String()
}//性能最强

使用strings.Builder

使用 + 拼接性能最差,strings.Builder,bytes.Buffer相近,Strings.Buffer更快

5、性能优化建议-空结构体

空结构体struct{}实例不占据任何的内存空间

func EmptyStructMap(n int) {
    m := make(map[int]struct{})
    
    for i := 0;i < n; i ++{
        m[i] = false
    }
}

实现Set,可以考虑用map来代替

对于这个常见,只需要用到map来代替

即将是将map的值设置为bool类型,也会多占据1个字节空间

2、性能调优实战

原则:

  • 要依靠数据不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

性能分析工具pprof

1、性能分析工具pprof-功能简介

  • 分析-Profile
  • 工具-Tool
  • 展示-View
  • 采样-Sample

2、实战

func main() {
    log.SetFlags(log.Lshortfile | log.LstdFlags)
    log.SetOutput(os.Stdout)
​
    runtime.GOMAXPROCS(1)              //限制CPU使用数
    runtime.SetMutexProfileFraction(1) //开启锁调用跟踪
    runtime.SetBlockProfileRate(1)     //开启阻塞调用跟踪
​
    go func() {
        if err := http.ListenAndServe(":6060", nil); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }()