go高质量开发规范与性能调优|青训营笔记

146 阅读6分钟

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

一、高质量编程

何为高质量编程:高质量是指代码质量正确且可靠,简洁清晰能达到需求的代码的编写。

  1. 简单性
  • 消除“多余的复杂性”,以简单清晰的逻辑编写代码(不理解的代码无法修复改进)
  1. 可读性
  • 在后续的维护以及迭代中,代码的修改优化是不可避免的,良好的可读性会使迭代更加轻松

1.注释

Good code has lots of comments,bad code requires lots of comments ———Dave Thomas and Andrew Hunt 翻译:好代码有很多注释,坏代码需要很多注释

1.注释应该解释代码作用

 // sort the given Array
 func sort(array int[]) err{
     ...
 }

2.注释应该解释代码如何实现

   func sort(array int[]) err{
       // use bubble_sort to sort the array
       ...
   }

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

// I parseTimeZone parses a time zone string and returns its length. Time zones
// are human-generated and unpredictable. We can't do precise error checking.
// On the other hand. for a correct parse there must be a time zone at the
// beginning of the string, so it's almost always true that there's one
// there. We look at the beginning of the string for a run of upper-case letters.
// If there are more than 5, it's an error.
// If there are 4 or 5 and the last is a T, it's a time zone.
// If there are 3, it's a time zone.
// Otherwise, other than special cases,
// it's not a time zone.
// GMT is special because it can have an hour offset.
func parseTimeZone(value string) (length int, ok bool)

2.各类名字规范

编码规范(是一种规范不是语法规定,不这样写代码也能跑,但是可阅读性和复用性大大降低)

  • 1.变量名命名规范

在函数形参的变量名的定义中,尽量做到见名知义,不使用无意义的变量名定义 例如sort(x int[],z int) 通过函数名我们能清楚的知道这个函数是一个排序函数,因此在形参的定义中,我们最好见名知义,定义成x,可能在后续书写函数实现的过程中,忘记x的意义,又或者在他人使用该函数的时候根本不知道如何调用传入参数。因此修改成sor(array int[],length int)。修改成这样,最后可阅读性大大提升

  • 2.函数名命名规范

  • 对于函数名定义,函数名不用携带报名的的上下文信息(包名和函数名总是成对出现)

  • 函数名尽量简短(方便后续使用者调用)

  • 当包名与包名内的某个函数的返回值额类型同名时,可以省略类型信息不导致歧义

foo包中一个函数返回值类型为Foo func save(f Foo) (bool,error); 这里见名知义就知道save的是Foo类型的数据

  • 当包名和中某个函数的返回值类型是其他不同名的任意类型,可以在函数名中加入类型信息

foo包中一个函数返回值为T 则函数名可以 func getT(code string,size int) T ; 这里通过名字知道返回值是得到一个T类型的数据

  • 3.包名命名规范

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

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

  • 不要与标准库同名

总结:核心目标是降低阅读理解代码的成本,重点考虑上下文信息,设计简洁清晰的名称(简洁和清晰不冲突)

Good naming is like a good joke,if you have to explain it,it's not funny

3.控制流程

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

二、高性能

1.性能测试 - Benchmark

IMG_03021AE68659-1.jpeg

  • 第一个黄框中BenchmarkFil10-8 :代表了 8核cpu的信息
  • 第二个黄框中 标志一共执行了1855870 次
  • 第三个黄框中 表示了每次执行花费了 602.5ns
  • 第四个黄框中 表示每次申请了多大的内存
  • 最后一个代表每次执行申请了几次内存

通过Benchmark分析优化点:

1.在slice的使用中,虽然go中切片支持动态内存,但是如果不指定,底层需要耗费更多的时间来进行分配扩容等操作。因此尽量定义时,先预分配内存大小。同理map也可以通过预分配来优化性能

2.字符串处理,在字符串频繁拼接使用的时候,最好不要直接对string 使用 '+' 来拼接,而是使用string.Buffer来进行追加*实际原理是由于其底层使用byte[]数组,拼接涉及到的是动态扩容,而string类型通过'+'拼接会开辟一块两个大小之和的新的内存空间。*如果想要更高效,则使用string.Builder

3.atomic包的使用

  • 锁的实现是通过操作系统来实现的,属于系统
  • atomic操作是通过硬件实现,效率高于锁

二、性能优化实战(通过pprof分析性能)

通过该命令配合pprof的工具来进行查询电脑的cpu等各种指标子情况 截屏2022-05-11 21.12.59.png 截屏2022-05-11 21.36.53.png 在其中执行top命令,通过结果可以看到不同函数的cpu占用命令。 其中需要注意的是 flat 和 cum

截屏2022-05-11 21.14.40.png flat:函数本身的cpu的消耗; cum:函数本身和调用该函数的消耗

通过list命令加上参数可以查看其中占用cpu时间最长的代码行

截屏2022-05-11 21.18.42.png 从list中可以看出占用cpu时间最多的是24行的循环,显而易见错误出现在那里,因此将该语句注释(当然实际项目开发情况,可能是某些语句的效率问题或者等等,不直接注释掉,而是进行优化,此时观察其cpu占有率发现,几乎正常

IMG_1C9DA55DB6B7-1.jpeg

同理通过观察pprof的view图解决内存占用过多问题 ,将以下代码注释掉

func (m *Mouse) Steal() {
   log.Println(m.Name(), "steal")
   //max := constant.Gi
   //for len(m.buffer) * constant.Mi < max {
   // m.buffer = append(m.buffer, [constant.Mi]byte{})
   //}
}

pprof采样过程和原理:

1.CPU

  • 采样对象:函数调用和它们占用的时间
  • 采样率:100次/秒,固定值
  • 采样时间:从手动启动到手动结束 IMG_E49DB6A7EBD6-1.jpeg

2.Heap

  • 采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
  • 采样率:每分配512kb记录一次,可在运行开头修改
  • 指标:alloc_space,alloc_objects,inuse_space,inuse_objects

3.Goroutine

  • 协程:记录所有用户发起且在运行中的goroutine(即入口飞runtime开头的)runtim.main的调用栈信息
  • ThreadCreate:记录程序创建的所有系统线程的信息

IMG_D72A50106395-1.jpeg

性能调优的原则:依靠数据而不是采择,优化主要瓶颈,且优化不能影响正确性