高质量编程及代码规范
编程原则
-
简单性
消除多余的复杂性,代码逻辑简单清晰
-
可读性
编写可维护代码的第一步是代码可读
-
生产力
团队的工作效率更高
编码规范
代码格式:使用gofmt自动格式化代码
注释:注释应该做到解释代码的作用,解释代码如何做,解释代码实现的原因,解释代码什么情况下会出错
命名规范:命名应简洁明了,当变量名的详略不影响对代码的理解时,代码应从简,而命名的简化不应牺牲对代码的理解难度。函数和变量名缩略词应全大写,当出现在前段且不需导出时,可以全部小写。包名只有小写字母组成,简短并包含一定的上下文信息
控制流程:避免嵌套,保持逻辑的清晰
性能优化
测试性能工具Benchmark
借助benchmark工具测试基准性能
对于完成的fib.go文件,建立fib_test.go文件。譬如测试函数fib的性能,需要在fib_test.go文件中编写函数测试代码,函数命名规则为Benchmark<待测试函数名,首字母大写>
// fib.go
func fib(n int) int {
if n == 1 {
return 1
}
if n == 0 {
return 1
}
return fib(n-1) + fib(n-2)
}
// fib_test.go
import (
"testing"
)
func BenchmarkFib(b *testing.B) {
for i := 1; i <= b.N; i++ {
fib(5)
}
}
在终端输入如下命令
go test -bench=. -benchem
测试结果
goos: darwin
goarch: arm64
pkg: awesomeProject
BenchmarkFib-8 49861150 24.86 ns/op 0 B/op 0 allocs/op
PASS
ok awesomeProject 1.712s
第四行共有五组数据,第一组表示测试函数名,后接CPU核数,第二组表示一共执行几次,即b.N的值,第三组为每次执行花费时间,第四组表示每次执行申请多大内存,第五组表示每次执行申请几次内存
性能优化 Slice
Slice预分配内存,尽量在使用make()初始化时给定容量信息
func NoPreAlloc(size int) {
data := make([]int, 0)
for i := 0; i < size; i++ {
data = append(data, i)
}
}
func PreAlloc(size int) {
data := make([]int, 0, size)
for i := 0; i < size; i++ {
data = append(data, i)
}
}
BenchmarkNoPreAlloc-8 8790717 134.9 ns/op 504 B/op 6 allocs/op
BenchmarkPreAlloc-8 39776478 29.66 ns/op 160 B/op 1 allocs/op
可见预先给定切片容量,在进行插入元素时,执行效率更高,需求分配的内存次数更少,程序运行速率更快
Slice 原理
Slice的逻辑空间和物理空间都类似于顺序表
type Slice struct{
array unsafe.Pointer
len int
cap int
}
cap表示Slice的容量,即不扩容情况下可使用的最大空间。当需要扩容时,会重新申请一段足够的内存空间,将原有元素复制到新的空间中,此操作会改变指向那一内存空间的指针
性能优化 map
Map预分配内存,尽量在使用make()初始化时给定容量信息
func PreAllocMap(size int) {
data := make(map[int]int, size)
for i := 0; i < size; i++ {
data[i] = 1
}
}
func NoPreAllocMap(size int) {
data := make(map[int]int)
for i := 0; i < size; i++ {
data[i] = 1
}
}
BenchmarkNoPreAllocMap-8 1887325 636.3 ns/op 941 B/op 3 allocs/op
BenchmarkPreAllocMap-8 3074744 389.7 ns/op 603 B/op 1 allocs/op
不断向map中添加元素操作会出发map的扩容机制,根据性能测试结果可知,提前分配好空间可以减少内存拷贝和rehash的消耗
性能优化 字符串
func Plus(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
func StringBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
func ByteBuffer(n int, str string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(str)
}
return buf.String()
}
参照测试结果,+拼接性能最差,string.Builder和bytes.Buffer的性能较高
在Go语言中,字符串属于不可变类型,因此+每次都会重新分配内存,效率最低。后二者的底层都是[]bytes数组,不需要每次拼接都内存扩容,效果相对更好
性能优化 空结构体
空结构体实例不占用任何内存空间,在一些场景下可作为占位符使用
实现Set,可以考虑用map来实现,只需要用到map的键