高质量编程 | 青训营笔记

99 阅读6分钟

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

这一节课将了解以下知识:

  • 如何编写更简洁清晰的代码
  • 常用的Go语言程序优化手段
  • 熟悉Go程序性能分析工具
  • 了解工程中性能优化的原则和流程

以下是记录了前半部分课程的笔记,关于高质量编程的定义、如何编写高质量的代码。

1、高质量编程

编程首先要完成基本的功能,那什么是高质量编程?有哪些实践规范?常见的性能优化建议有哪些?

1.1 高质量编程的定义

编写的代码能够达到**正确可靠****简洁清晰**的目标可称之为高质量代码。
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护

1.2 编程原则

简单性
    消除多余的复杂性,以简单清晰的逻辑编写代码。
    不理解的代码无法进行修复改进!!!!
可读性
    代码时写给人看的,不是机器!
    编写可维护代码的第一步是确保代码可读
生产力
    团队整体工作效率非常重要
    (编码是在整个项目开发链路中的一个节点,遵循规范,避免常见缺陷的代码能够降低后续联调、测试、验证、上线等各个节点出现问题的概率,就算出现问题也能很快排查)

1.3 如何编写高质量的Go代码

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

1.3.1 代码格式

推荐使用gofmt自动格式代码
gofmt:是GO官方提供的工具,能自动格式化GO语言代码为官方统一风格
goimports:也是Go官方提供的工具=gofmt+依赖包管理;自动增删依赖的包引用,将依赖包按字母排序并分类

如下是在Goland里面配置,开启之后就可以在保存文件时自动格式化; image.png 我是直接在setting->Tools->File Watches添加了go fmt、go import,然后调用的时候goimports会报错找不到文件,然后点invoke安装即可。

image.png

在工具栏选择'Tools -> Go Tools'就能看到有gofmt和goimports工具,点击就可以应用。 image.png 例子: 自动格式化: image.png image.png

依赖包管理:

image.png

使用go imports之后的结果:

image.png

1.3.2 注释

注释的目的:
- 解释代码作用
    ·适合注释公共符号,如对外提供函数注释描述其功能和用途,只有函数功能简洁明显时才可省略。
- 解释代码如何做的
    ·对代码中复杂的,并不明显的逻辑进行说明
- 解释代码实现的原因
    ·可以解释代码的外部因素,这些因素脱离上下文后通常很难理解
- 解释代码什么情况会出错
    ·提醒使用者一些潜在的限制条件或者会无法处理的情况

小结:

  • 代码是最好的注释
  • 注释应该提供代码未表达出的上下文信息

1.3.2 命名规范

variable

简洁胜于冗长
缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写:如ServeHTTP而不是ServeHttp
变量距离其被使用的地方越远,需要携带越多的上下文信息,使用的时候能够对应到具体含义

function

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

package

只由小写字母组成
简短并包含一定的上下文信息,如schema、task
不要与标准库同名,如sync、strings

小结

  • 核心是降低阅读理解代码的成本
  • 重点考虑上下文信息,设计简洁清晰的名称

1.3.3 控制流程

避免嵌套,保持正常流程清晰
尽量保持正常代码路径为最小缩进
    ·优先处理错误情况|特殊情况,尽早返回或继续循环来减少嵌套

image.png

image.png image.png

1.3.4 错误和异常处理

简单错误:仅出现一次的错误,优先使用errors.New来创建匿名变量来直接表示
对于复杂的错误,有时候并不能简单描述,应该如何处理?
    ·错误的包装提供了一个error嵌套另一个error的能力,生成一个error的跟踪链,同时结合错误的判定方法来确认调用链中是否有关注的错误出现。这个能力的好处是每一层调用方可以补充自己对应的上下文,方便跟踪排查问题,确定问题的根本原因在哪里
    ·在fmt.Errorf中使用:%w关键字来将一个错误wrap至其错误链中

错误判定:

判断是否为特定错误用errors.Is
在错误链上获取特定种类的错误用errors.As

panic

不建议在业务中使用
调用函数不包含recover会造成程序崩溃
若问题可以被屏蔽或解决,建议使用error代替
当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic

recover

只能在被defer的函数中使用
嵌套无法生效
只在当前goroutine生效
defer的语句是后进先出(栈)
如果需要更多的上下文信息,可以在recover后在log中记录当前的调用栈

1.4 性能优化建议

性能优化的前提是满足正确可靠、简洁清晰等质量因素
性能优化是综合评估,有时候时间效率和空间效率可能对立

1.4.1 benchmark

go test -bench=. -benchmem

1.4.2 Slice

slice预分配内存,尽可能在使用make()初始化切片时提供容量信息

1.4.3 Map

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

1.4.4 字符串处理

使用string.Builder
使用+拼接性能最差,string.Builder,bytes.Buffer相近,前者更快

1.4.5 空结构体

使用空结构体节省空间
struct{}不占据任何内存,可作为各种场景下的占位符使用

1.4.6 atomic包

锁的实现通过操作系统实现,是系统调用
atomic操作时通过硬件实现,效率比锁高
sync.Mutex应该用于保护一段逻辑,不仅仅用于包含一个变量
对于非数值操作,可以使用atomic.Value,能承载一个interface{}

总结:通过学习,进一步了解了在编程中如何提高代码质量。之前写代码有点随意,不太规范,之后要将代码规范实际应用起来,可以用一些工具作为辅助,写出更简洁清晰的代码,特别是代码注释这一方面,不写注释,只要时间够久,就会不记得当初写的是个什么玩意,很难理解。