青训营课程 Lesson3 自用笔记 | 青训营笔记

87 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记

1.高质量编程

高质量:正确可靠、简洁清晰

  • 各种边界条件是否考虑完备?
  • 异常情况处理,稳定性保证,减少别人的代码对自己的代码的影响
  • 易读易维护,自己和别人都能看懂代码

编程原则:简单性、可读性、生产力

编码规范:代码格式、注释、命名规范、控制流程、错误和异常处理

  • 代码格式:推荐使用gofmt自动格式化代码,goimports也是一个很好的工具,相当于gofmt+依赖管理

  • 注释:

    • 解释代码的作用(适合注释公共符号,对函数的作用做注释等)
    • 代码是如何做的(注释实现过程)
    • 代码实现的原因(为什么这么做?提供额外上下文,解释代码的外部因素)
    • 代码什么情况会出错(解释代码的限制条件)
  • 命名规范:

    • 对变量(variable)的命名:简洁;大小写问题;变量距离其被使用的地方越远,则需要在名字里携带越多的上下文信息
    • 对函数(function)的命名:不必携带包名的上下文信息,因为函数名和包名总成对出现;尽量简短;函数返回类型与函数的包名一致时,可以省略类型信息,不一致时,可以加入类型信息
    • 对包(package)的命名:只用小写字母;简短,并包含一定上下文信息;不与标准库同名;;不与常用变量重名;使用单数而非复数;谨慎的使用缩写
  • 控制流程

    • 避免嵌套(例如,如果if-else两个分支中都包含return语句,则可以去除冗余的else,就if{return},外面写上原本写在else里的return)
if foo{
    return x
}else{
    return nil
}

修改为

if foo{
    return x
}
return nil
    • 尽量保持正常代码路径为最小缩进,(能拆出来就拆出来,不要一层套一层,像>符号一样越往中间缩进越多)就是说把错误情况和特殊情况放在前面,尽早返回或继续循环来减少嵌套(像上面那样只有if没有else的分支,要能看懂)
  • 错误和异常处理

    • 简单错误:仅出现一次,且在其他地方不需要捕获的错误,用errors.New("")来自定义错误内容就行,如果有格式化的需求,用fmt.Errorf
    • 错误的包装(Wrap)和解包(Unwrap):错误嵌套,错误跟踪链?
    • 错误判定:errors.Is,与==不同的是,该方法可以判定错误链上的所有错误是否含有特定的错误;errors.As,在错误链上获取特定种类的错误
    • panic:当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic,不建议在业务代码中使用panic
    • recover:在当前协程(goroutine)的被defer的函数中生效(如果写了多个defer,那它们的执行顺序是后进先出的);如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈(err = fmt.Errorf("gifts panic: %v\n%s", e, debug.Stack()
    • error尽可能提供简明的上下文信息链,方便定位问题

性能优化建议

  • Benchmark:Go语言提供的支持基准性能测试的工具(go test -bench=. -benchmem 输出结果的含义在PPT上可见)
  • slice:预分配内存,尽可能在使用make()初始化切片时提供容量信息
    • 切片本质是一个数组片段的描述
    • 切片操作并不复制切片指向的元素
    • 创建一个新的切片会复用原来切片的底层数组
    • 把大切片切成小切片,原来的大切片占用的内存不会被释放,推荐使用copy来替代re-slice来解决这个问题
  • map:预分配内存,跟slice一样,创建时分配好内存可以提高性能
    • 不预分配内存的话,不断添加元素会触发map的扩容
    • 提前分配好空间可以减少内存拷贝和Rehash的消耗
  • 字符串处理:使用strings.Builder
    • 字符串在Go中是不可变类型,占用内存大小是固定的,每次使用+操作都会重新分配内存
    • strings.Builder和bytes.Buffer底层都是[]byte数组,内部维护了一个内存扩容策略,不需要每次拼接都重新分配内存
    • strings.Builder又比bytes.Buffer要快一点,因为bytes.Buffer转化为字符串时重新申请了一块空间,而strings.Builder是直接将[]byte转成了字符串
    • 使用Grow方法(Builder和Buffer都有这个方法),在字符串拼接中预分配内存,可以让性能再次提高
  • 空结构体:使用空结构体可以节省内存(第二次课的TopicDAO和PostDAO?)
  • atomic包:多线程时使用,比加锁的时间性能好很多,可以用于保护一个变量(sync.Mutex应该用来保护一段逻辑)(atomic.Value)

2.性能调优实战

性能调优原则

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

性能分析工具pprof

  • pprof是用于可视化和分析性能分析数据的工具
  • http://localhost:6060/debug/pprof/
  • 排查CPU问题- go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
    • top命令:flat-->当前函数的执行耗时;flat%-->占CPU的百分比;sum%-->上面每一行flat%总和;cum-->当前函数本身加上其他调用函数的总耗时;sum%-->cum占CPU总时间的比例(没有调用其他函数时,flat==cum;函数中只有其他函数的调用时,flat==0)
    • 通过top找到了tiger....Eat是占CPU最多的函数,然后通过list Eat,根据指定的正则表达式查找代码行
    • web命令,可以生成一个调用关系图,调用关系可视化
  • 排查堆内存问题go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"这边要装个graphviz才能在网页上看到图,blog.csdn.net/lanchunhui/… 安装教程在此,先不装了,等真的要用到再装吧
  • 排查协程问题go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
  • 排查锁问题go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
  • 排查阻塞问题go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

pprof的采样过程和原理

  • CPU
  • Heap 堆内存
  • 协程Goroutine 线程创建ThreadCreate
  • 阻塞Block 锁Mutex

性能调优案例

  • 业务服务优化

图1.png

  • 基础库优化
  • Go语言优化

图2.png (笔记感觉看学员手册里的都行了)