【GoLang】golang 最佳实践汇总_golang

 

最佳实践
1 包管理
1.1 使用包管理对Golang项目进行管理,如:godep/vendor等工具
1.2 main/init函数使用,init函数参考python
1.2.1 main->import->const->var->init
1.2.2 同一个package属于一个作用域,所以不要重复定义变量等
1.3 万能的type
1.3.1 type ages int type money float32 type months map[string]int 定义新类型
1.3.2 type testInt func(int) (bool) // 声明了一个函数类型
1.3.3 type person struct //声明一个结构体类型
1.3.4 type PeopleProtocol interface //声明一个接口类型
1.4 万能的函数
1.4.1 面向函数编程
1.4.1.1 数据作为参数传递给函数形参
1.4.2 面向对象编程
1.4.2.1 通过带有接受者函数实现,使用起来函数作为了对象的属性
1.4.2.2 golang可以自动匹配接受者的类型,不管是对象还是对象的指针
1.4.2.3 字段继承通过匿名字段实现;方法继承/重写也通过匿名字段实现
1.4.3 面向接口编程
1.4.3.1 接口定义协议,通过带接受者的函数实现接口,实现了接口的对象可以被接口引用
1.4.3.2 面向接口不是对面向对象的否定,它是面向对象编程体系中的思想精髓之一
1.4.3.3 面向接口编程的本质是面向协议编程,只要实现了该协议,不管什么对象都是可以随时替代的,可以提高软件的灵活性与可维护性opic
1.4.3.4 面向对象是为了实现代码复用,面向接口是为了实现多态、实现标准定制、灵活替代
2 交叉编译
2.1 使用交叉编译工具或者使用Golang指定目标编译
2.2 windows
2.2.1 apt-get install gcc-mingw-w64 env CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build -o ./app/app.exe ./app/main.go env CGO_ENABLED=1 GOOS=windows GOARCH=386 CC=i686-w64-mingw32-gcc go build -o ./app/app.exe ./app/main.go
3 错误处理
3.1 官方推荐
3.1.1 error也是值,并且需要总是检查error并对其处理
3.1.2 慎用或者不使用panic等手段
3.2 其他推荐
3.2.1 使用第三方errors库
3.2.2 模仿Web开发模式,子模块只管抛出异常,并在全局位置捕获异常统一处理,即使用panic、recover、defer等联合进行处理
3.2.3 defer参数值是在声明时确定,而不是在调用时才确定
4 数据类型
4.1 变量与常量
4.1.1 全局变量使用var声明,内部变量尽量使用简短方式声明/定义,需要明确指定变量类型的除外
4.1.2 常量可以指定为数值、布尔值或字符串等类型,格式:const constantName = value
4.1.3 同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明,适用于import/const/var
4.2 默认类型与默认值
4.2.1 整型、浮点型、复数默认类型分别为int/float64/complex128
4.2.2 默认值
4.2.2.1 bool-false, string-"", 整型、浮点型、复数-0
4.2.2.2 枚举使用iota进行0值重置
4.2.2.3 array需要指定长度,讨论其默认值没有意义
4.2.2.4 初始声明未初始化的slice/map默认为nil
4.2.3 初始化
4.2.3.1 声明初始化、make/new初始化、赋值初始化
4.3 值类型
4.3.1 基本类型
4.3.1.1 rune-int32, int8, int16, int32-int, int64和byte-uint8, uint8, uint16, uint32, uint64
4.3.1.2 uintptr, intptr指针类型,用于指针操作
4.3.1.3 rune是int32的别称,byte是uint8的别称
4.3.2 字符串
4.3.2.1 字符串连接使用+运算符或者使用fmt格式化返回字符串
4.3.2.2 字符串为不可变类型,如果需要改变字符串值,需要转化为[]byte/[]rune切片进行操作
4.3.2.3 字符串使用UTF-8编码存储,如果需要按照Unicode方式进行操作,需要转化为[]rune切片或者使用"unicode/utf8"包
4.3.3 数组array
4.3.3.1 定义数组必须指定长度,且数组长度定义之后不能改变
4.3.3.2 数组赋值为值的赋值,参数传递也是值传递,即实参数组赋值一份数据给形参数组
4.3.4 结构体struct
4.3.4.1 struct与array一样是值类型,作为参数会copy完整数据;slice、map、channel等传递的是数据的内存地址
4.3.4.2 struct不能使用make初始化,可以使用声明方式或者new返回指针的方式初始化
4.3.4.3 通过非匿名字段实现对象的包含与持有,通过匿名字段实现传统的类的继承关系
4.3.4.4 与面向对象语言不同的是子类struct不能赋值给父类struct,即struct可以模拟面向对象,但不是为了实现完全的面向对象
4.3.4.5 字段继承通过匿名字段实现;方法继承/重写也通过匿名字段实现
4.3.4.6 与传统面向对象不同的是,golang中子类对象不能赋值给基类对象
4.4 引用类型
4.4.1 map、channel、slice为引用类型 如果两个引用类型同时指向一个底层,那么一个改变,另一个也相应的改变 三者都可以通过for...range遍历
4.4.2 切片slice
4.4.2.1 定义slice不指定长度,使用make可以指定长度
4.4.2.2 慎用append函数改变slice的长度,可能会异常改变被引用的数组内容,这可能是我们并不期望的结果
4.4.2.3 slice底层使用struct实现,slice的cap大小由golang动态实现
4.4.2.4 slice初始化,要么引用其他数组/切片,要么使用make/new初始化,make初始化可以指定初始大小
4.4.3 哈希map
4.4.3.1 定义map不需要指定长度,通过make指定长度也没有意义,map本来就是动态的,即使指定长度没有数据就认为len为0
4.4.3.2 map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
4.4.3.3 GET MAP两个返回值, v, ok := mapa["C"], 如果不存在则ok为false
4.4.4 make与new
4.4.4.1 make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配
4.4.4.2 new返回指针, 且值为 nil; new(T)分配了零值填充的T类型的内存空间,并且返回其地址
4.4.4.3 make返回对象, 且值不为nil; make返回初始化后的(非零)值
4.4.5 channel
4.4.5.1 golang并发本质
4.4.5.1.1 PMG的原理,P对应操作系统进程--对程序的抽象,W对应操作系统线程--对寄存器的抽象, G对应goroutine--go实现的轻量级线程,也即GreenThreads用户态线程。 一句话,golang提供了用户态线程的管理机制。
4.4.5.1.2 G通过阻塞方式调用,M不会阻塞,G通过netpoller唤醒。一句话,用户态线程通过netpoller方式避免阻塞系统线程。 当然,其他可能阻塞的地方golang的runtime也会做相应的优化,比如for循环也会被runtime优化。
4.4.5.1.3 goroutine+channel的本质是对生产车间流水线的完美模拟,应用开发者只要想清楚流水线该如何设计之后就能轻易理解 goroutine/channel的精妙,gorotine相当于很多干活的人,chennel相当于很多人之前的连线,点和线之间就可以设计复杂的工作流 理解了这一点就可以将问题抽象为,这个活儿能不能多个人一起干(多个goroutine), 这多个人怎么使用(chennel)有效的组织起来形成流水线?
4.4.5.2 无缓冲的channel可用于在多个goroutine之间同步,避免了显示的加锁。需要多个goroutine存在才能跑起来,只有一个goroutine会造成死锁
4.4.5.3 有缓冲的channel是对无缓冲channel的补充,可以作为流水线缓冲队列使用,一定程度上可以减轻无缓冲channel即阻塞式channel的压力,提高流水线的整体性能
4.4.5.4 等待所有routine完成 需要考虑解决两个问题
4.4.5.4.1 使用channel同步等待、使用sync.WaitGroup等待、主线程开goroutine等待并发出done信号,单个worker作为主goroutine
4.4.5.4.2 需要安全关闭channel
4.4.5.4.2.1 单worker输出的channel可以由生产者主动关闭
4.4.5.4.2.2 多worker输出的channel可以由第三方主动关闭,但要确认不会出现PANIC
4.4.5.4.2.3 下游goroutine使用for-range等待channel关闭
4.4.5.4.2.4 worker中select监控多个channel数据流动,接收done信号
4.4.5.4.2.5 最佳实践参考
4.4.5.4.2.5.1 可以在worker函数内部自定义out channel
4.4.5.4.2.5.2 worker函数需要保证goroutine结束之后关闭out channel
4.4.5.4.2.5.3 worker函数如果有多个in channel,可以使用WaitGroup,等待所有in处理完毕之后关闭out
4.4.5.4.2.5.4 使用done channel close信号通知所有上游goroutine
4.4.5.4.2.5.5 最终消费者/主goroutine总是检查out是否被关闭
4.4.5.4.2.6 总结
4.4.5.4.2.6.1 当所有发送操作结束时,每个阶段都关闭自己的 outbound channels
4.4.5.4.2.6.2 每个阶段都会一直从 inbound channels 接收数据,直到这些 channels 被关闭,或发送者解除阻塞状态
4.5 其他
4.5.1 function
4.5.1.1 支持多返回值与变参,总是命名返回值,可以直接return
4.5.1.2 值类型(array/struct)为传值调用--参数赋值,引用类型(channel/map/slice,指针等)为传递地址调用--参数引用
4.5.1.3 值传递&引用传递
4.5.1.3.1 传值与传址本质都是copy操作,一个copy数据,一个copy地址, copy数据不能改变实参,copy地址通过指针操作可以一定程度上改变实参的值, 但肯定不是随意改变,比如append操作就不会改变原始实参
4.5.1.3.2 Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针 若函数需改变slice的长度(比如进行append操作),则仍需要取地址传递指针
4.5.1.3.3 函数参数中存在此概念,带有接受者的函数也区分值传递与引用传递,也存在此概念
4.5.1.3.4 闭包通过非参数传递的方式直接引用外层代码定义的变量,传递的是变量的地址
4.5.1.4 函数本身也可以作为类型,或者作为值传递给其他函数的形参,比如多种类型过滤器filter的实现即可参考这种模式
4.5.1.5 带有接收者的函数即可实现面向对象--方法的继承/重写 虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
4.5.1.6 struct、type自定义类型等都可以作为接受者
4.5.2 interface
4.5.2.1 接口A定义-B对象实现-C对象继承B对象属性-C也就继承了B对象的接口/可以重写-->类的继承、方法的继承、多态的实现
4.5.2.2 子类对象不能赋值给父类对象,但是接口可接受任意实现了该接口的对象,请注意这两点与传统面向对象设计语言的区别
4.5.2.3 任何对象都可以赋值给空interface,即可以存储任意类型对象,类似于C语言的 C语言的void*
4.5.2.4 像Java一样,接口也是可以通过extends继承的
4.5.2.5 反射
4.5.2.5.1 golang的反射与Java相比功能弱很多,golang不支持解析string然后执行
4.5.2.5.2 golang的反射机制只能存在于已经存在的对象/类型上面
4.5.2.5.3 如果要实现与Java一样的反射(RPC/Web框架)机制, 需要先把字符串和类的reflect.Typeof关联好,然后根据字符串找到对应的类型,用reflect.New构造对象就可以了 即需要提前有注册机制进行字符串<-->类型的关联映射
5 流程控制
5.1 if 代码块内允许声明临时变量在if-else代码块内使用,使用分号;分割多条语句
5.2 for 既可以用来循环读取数据,又可以当作while来控制逻辑,还能迭代操作,Golang中没有while操作符 在嵌套For循环中,将循环次数多的循环放在内侧,循环次数少的循环放在外侧,其性能会提高 减少循环变量的实例化,其性能也会提高
5.3 switch 默认在case之后带 break,如果需要继续遍历,可以使用fallthrough强制执行后面的case代码
5.4 break, continue, return, goto 与 C 语言实现一样