这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记」
一、高质量编程
何为高质量编程:高质量是指代码质量正确且可靠,简洁清晰能达到需求的代码的编写。
- 简单性
- 消除“多余的复杂性”,以简单清晰的逻辑编写代码(不理解的代码无法修复改进)
- 可读性
- 在后续的维护以及迭代中,代码的修改优化是不可避免的,良好的可读性会使迭代更加轻松
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
- 第一个黄框中
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等各种指标子情况
在其中执行top命令,通过结果可以看到不同函数的cpu占用命令。
其中需要注意的是
flat 和 cum
flat:函数本身的cpu的消耗; cum:函数本身和调用该函数的消耗
通过list命令加上参数可以查看其中占用cpu时间最长的代码行
从list中可以看出占用cpu时间最多的是24行的循环,显而易见错误出现在那里,因此将该语句注释(当然实际项目开发情况,可能是某些语句的效率问题或者等等,不直接注释掉,而是进行优化,此时观察其cpu占有率发现,几乎正常
同理通过观察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次/秒,固定值
- 采样时间:从手动启动到手动结束
2.Heap
- 采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
- 采样率:每分配512kb记录一次,可在运行开头修改
- 指标:alloc_space,alloc_objects,inuse_space,inuse_objects
3.Goroutine
- 协程:记录所有用户发起且在运行中的goroutine(即入口飞runtime开头的)runtim.main的调用栈信息
- ThreadCreate:记录程序创建的所有系统线程的信息