[ 后端与 Go | 青训营笔记 ]

83 阅读5分钟

[ 后端与 Go | 青训营笔记 ]

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

课内:Go 高质量编程

高质量编程的定义和原则,分享了代码格式、注释、命名规范、控制流程、错误和异常处理五方面的常见编码规范

重点

  • 高质量编程简介及编码规范;
  • 性能优化指南;
  • 性能优化分析工具。

细节

高质量编程

简介

编写的代码正确可靠、简洁清晰

  • 各种边界条件;
  • 异常情况处理,稳定性保证;
  • 易读易维护。

原则

  • 简单性:逻辑清晰,消除多余的复杂性;
  • 可读性:编写可维护代码的第一步是确保代码可读;
  • 生产力:团队整体的工作效率,风格统一。
编码规范
  • 代码格式
  • 注释
  • 命名规范
  • 控制流程
  • 错误和异常处理

代码格式

  • gofmt 自动格式化代码

注释 - 公共符号始终要注释

  • 包中声明的每个公共的符号:变量、常量、函数和结构都需要注释;
  • 任何不明显不简短的功能必须注释;
  • 库中的任何函数都必须注释。

注释应该做到的:解释作用、制作过程、实现原因(提供额外的上下文)、说明什么情况下会出错。

命名规范

  • variable
  1. 简洁胜于冗长;

  2. 缩略词全大写,但位于开头且不需要导出时使用全小写;

    • 如 ServeHTTP 而不是 ServeHttp;
    • 如 XMLHTTPRequest 或 xmlHTTPRequest;
  3. 变量距离被使用位置越远,越需要携带更多的上下文信息。

image-20230117150509257

image-20230117150521078

  • function
  1. 函数名与包名成对出现;
  2. 函数名要简短;
  3. 在函数名中加入返回类型。
  • package
  1. 只由小写字母组成,不包括下划线;
  2. 简短,包含一定上下文信息;
  3. 不要与标准库同名;
  4. 不使用采用变量名作为包名;
  5. 使用单数形式;
  6. 谨慎使用缩写。

控制流程

  • 避免嵌套,保证正常流程清晰;

image-20230117151444661

  • 尽量保证正常代码路径为最小缩进;
  1. 优先处理错误、特殊情况,尽早返回或继续来减少嵌套;

image-20230117151619206

image-20230117151716640

  • 小结
  1. 处理逻辑走直线,避免复杂嵌套分支;
  2. 正常代码流程沿屏幕向下移动;
  3. 提升可维护性和可读性;
  4. 故障大多出现在复杂的条件和循环语句中。

错误与异常处理

  • 简单错误:仅出现一次的错误,且其他地方不需要捕捉该错误;
  1. 直接 errors.New 来创建匿名变量表示错误;
  2. 使用 fmt.Errof 来满足格式化需求.

image-20230117152820893

  • 错误的 Wrap 和 Unwrap
  1. 错误的 Wrap 实际上提供了一个 error 嵌套另一个 error 的能力,从而生成一个error 的跟踪链;
  2. 在 fmt.Errorf 中使用 %w 关键字将一个错误关联至错误链中。

image-20230117153657463

  • 错误判定
  1. 判断一个错误是否为特定错误,使用 errors.Is;
  2. 可以判断错误链在是否存在特定的错误;

image-20230117154045358

  1. 获取特定错误,使用 errors.As;

image-20230117154155152

image-20230117154237682

image-20230117154315481

性能优化建议

简介

  • 性能优化的前提是正确可靠、简洁清晰;
  • 性能优化是综合评估,时间效率与空间效率可能矛盾;
  • 根据语言特性提出建议。

如何使用

  • Go 语言提供了基准性能测试的 benchmark 工具
 $ go test -bench=. -benchmem

image-20230117155217902image-20230117155228631

slice 预内存分配

  • 尽可能在使用 make() 初始化切片的时候提供容量信息;
 func PreAlloc(size int) {
     data := make([]int ,size)
     for i := 1;i <= size;i++ {
         data = append()
     }
 }

image-20230117155901229

  • 每次 slice 申请内存都会复用原有数组;
  • 如果要重复使用切片的一部分,最好新建一个切片,使用 copy 复制所需内容。避免大内存未释放。
 // 在使用返回的切片时,依然会使用到原有切片
 func GetLastSlice(origin []int) []int {
     return origin[len(origin) - 2 :] 
 }
 ​
 // 创建新切片,不会使用到原有切片
 func PreGetLateSlice(origin []int) []int {
     var re = make([]int ,2)
     copy(re ,origin[len])
 }

image-20230117171718307

map 预分配内存

  • 与 slice 同理,减少内存拷贝和 Rehash 的消耗。

使用 strings.Builder

  • 字符串操作使用 strings.Builder 效率更高;
 func Add(n int ,s string) string {
     var Sa = := ""
     for i := 1;i <= n;i++ {
         sa += s
     }
     return sa
 }
 ​
 func PreAdd(n int ,s string) string {
     var builder strings.Builder
     for i := 1;i <= n;i++ {
         builder.WriteString(s)
     }
     return builder.String()
 }
  • Go 中 string 的大小是固定的,每次使用 + 号,都需要再次申请一个更大的 string 来容纳相加的双方;
  • string.Builder 不需要额外申请空间。

image-20230117173131331

使用空结构体

  • 空结构体 struct {} 实例不占用内存空间;

image-20230117173549846

 func EmptyStruct(n int) {
     var m = make(map[int]struct{}) // 空结构体
     for i := 1;i <= n;i++ {
         m[i] = struct{}{}
     }
 }
  • 如上可以实现用 map 实现 set,只需要 key ,不需要 val;

atomic 包

image-20230117173917447

image-20230117173930748

性能调优

简介

性能调优原则

  • 依靠数据而不是猜测;
  • 定位最大瓶颈而不是细枝末节;
  • 不要过早优化;
  • 不要过度优化。
pprof工具使用

image-20230117174450166

  • 只有在程序运行的时候才能进入 debug;

image-20230117175641486

image-20230117175827991

程序正常运行时的 CPU 占用;

image-20230117180136026

image-20230117180246853

总结

了解到编程原则,了解部分工具了解,对于代码优化有一定的了解。