golang pkg Tips & Tracks
Golang
- Goals
The efficientcy of a statically-typed compiled language with the ease of programming of a dynamic language.
Safety: type-safe and memory-safe
Good support for concurrency and communication.
Efficient, latency-free garbage collection.
High-speed compilation.
Design principles
Keep concept orthogonal(正交).
Keep the grammar regular and simple.
Reduce typing. Let the language work things out.
Reduce typing. Keep the type system clear.The Big Picture
Fundamentals:
- Clean, concise syntax.
- Lightweight type system.
- No implicit conversions: keep things explicit.
- Untyped unsized constants:
- Strict separation of interface and implementation.
Run-time:
- Garbage collection.
- Strings, maps, communication channels
- Concurrency.
Package model:
Explicit dependencies to enable faster builds.
Go Identifiers
Go 项目组织
Go应用
编写高质量的Go应用
- 代码结构
- 目录结构
- 按功能拆分模块
- 层次拆分 - MVC
- 功能拆分 - 实现高内聚低耦合的设计哲学
- 代码规范 - UBer_Go_Guid
- 代码质量
- 编程哲学
- 面向接口编程
- 代码扩展性更强
- 解耦上下游的实现
- 提高代码的可测性
- 面向“对象”编程
- 类、抽象、封装通过结构体来实现
- 实例通过结构体变量来实现
- 继承通过组合来实现,一个结构体嵌到另一个结构体,称作组合
- 多态通过结构来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69package main
import "fmt"
// 基类: Bird
type Bird struct {
Type string
}
// 鸟来的类别
func (bird *Bird) Class() string {
return bird.Type
}
// 定义鸟类
type Birds interface {
Name() string
Class() string
}
// 鸟类: 金丝雀
type Canary struct {
Bird
name string
}
func (c *Canary) Name() string {
return c.name
}
// 鸟类: 乌鸦
type Crow struct {
Bird
name string
}
func (c *Crow) Name() string {
return c.name
}
func NewCrow(name string) *Crow {
return &Crow{
Bird: Bird{
Type: "Crow",
},
name: name,
}
}
func NewCanary(name string) *Canary {
return &Canary{
Bird: Bird{
Type: "Canary",
},
name: name,
}
}
func BirdInfo(birds Birds) {
fmt.Printf("I'm %s, I belong to %s bird class!\n", birds.Name(), birds.Class())
}
func main() {
canary := NewCanary("CanaryA")
crow := NewCrow("CrowA")
BirdInfo(canary)
BirdInfo(crow)
}
- 面向接口编程
- 软件设计方法
- 设计模式 Design Pattern
针对特定场景总结出来的最佳实现方式,特点是解决场景比较具体,实施起来比较简单
- 创建型模式 Creational Patterns
提供一种在创建对象的同时隐藏创建逻辑的方式,不使用new运算符直接实例化对象
- 单例模式 Singleton Pattern
单例模式使全局只有一个实例,并且负责创建自己的对象,比较适合全局共享一个实例,且只需要被初始化一次的场景. [数据库实例、全局配置、全局任务池]
- 饿汉模式: 全局实例在包被加载时创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package singleton
type singleton struct{}
var ins *singleton
func init() {
//导入时 初始化静态实例
ins = &singleton{}
}
func GetInsOr() *singleton {
// 返回创建好的初始化实例
return ins
} - 懒汉模式: 全局实例在包被使用时创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package singleton
import "sync"
type singleton struct {
Name string
}
var ins *singleton
var once sync.Once
func GetInsOr() *singleton2 {
// Do is intended for initialization that must be run exactly once
once.Do(func() {
ins2 = &singleton{"Chyi"}
})
return ins
}
- 饿汉模式: 全局实例在包被加载时创建
- 简单工厂模式
传入参数并返回一个结构体的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package factory
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) Greet() {
fmt.Printf("Hi! My name is %s", p.Name)
}
// 简单工厂模式确保我们创建的实例具有需要的参数,进而保证实例按照预期执行
// 返回非指针的实例,可以确保实例属性避免属性被意外/任意修改
func NewPerson(name string, age int) Person {
return Person{
Name: name,
Age: age,
}
} - 抽象工厂模式
与简单工厂模式区别是返回的是接口而不是结构体, 在不公开内部实现的情况下,让调用者使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package abstract_factory
import "fmt"
type Person interface {
Greet()
}
type person struct {
name string
age int
}
func (p person) Greet() {
fmt.Printf("Hi!, my name is %s", p.name)
}
// Here, NewPerson returns an interface, and not the person struct iteself
func NewPerson(name string, age int) Person {
return person{
name: name,
age: age,
}
} - 工厂方法模式
依赖工厂接口,通过实现工厂接口来创建多种工厂,将对象创建从一个对象负责所有集体类的实例化,编程由一群子类负责对具体类的实例化,从而将过程解耦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package method_factory
type Person struct {
name string
age int
}
func NewPersonFactory(age int) func(name string) Person {
return func(name string) Person {
return Person{
name: name,
age: age,
}
}
} - 建造者模式
- 原型模式
- 单例模式 Singleton Pattern
- 结构型模式 Structural Patterns
- 访问者模式
- 模版模式 Template Pattern
将一个类中能够公共使用的方法放置在抽象类中实现,将不能公共使用的方法作为抽象方法,强制子类实现,这样就做到一个类作为模版,让开发者填充需要填充的地方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58package template
import "fmt"
type Cooker interface {
fire()
cooke()
outfire()
}
// 类似一个抽象类
type CookMenu struct{}
func (CookMenu) fire() {
fmt.Println("开火")
}
// 做菜 交给具体的子类实现
func (CookMenu) cooke() {
}
func (CookMenu) outfire() {
fmt.Println("关火")
}
// 封装具体步骤
func doCook(cook Cooker) {
cook.fire()
cook.cooke()
cook.outfire()
}
type XiHongShi struct {
CookMenu
}
func (*XiHongShi) cooke() {
fmt.Println("做西红柿")
}
type ChaoJiDan struct {
CookMenu
}
func (ChaoJiDan) cooke() {
fmt.Println("做炒鸡蛋")
}
func TestTemplate(t *testing.T) {
// 做西红柿
xihongshi := &XiHongShi{}
doCook(xihongshi)
// 做炒鸡蛋
chaojidan := &ChaoJiDan{}
doCook(chaojidan)
} - 策略模式 Strategy Pattern
采用不同策略的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44package strategy
// 策略模式
// 定义一个策略类
type IStrategy interface {
do(int, int) int
}
// 策略实现: 加
type add struct{}
func (*add) do(a, b int) int {
return a + b
}
// 策略实现: 减
type reduce struct{}
func (*reduce) do(a, b int) int {
return a - b
}
// 具体策略的执行者
type Operator struct {
strategy IStrategy
}
// 设置策略
func (operator *Operator) setStrategy(strategy IStrategy) {
operator.strategy = strategy
}
// 调用策略中的方法
func (operator *Operator) calculate(a, b int) int {
return operator.strategy.do(a, b)
}
func TestStrategy(t *testing.T) {
operator := Operator{}
operator.setStrategy(&add{})
result := operator.calculate(1, 2)
fmt.Println("add:", result)
} - 状态模式
- 观察者模式
- 备忘录模式
- 中介者模式
- 迭代器模式
- 解释器模式
- 命令模式
- 责任链模式
- 行为型模式 Behavioral Patterns
关注对象之间的通信
- 适配器模式
- 桥接模式
- 组合模式
- 装饰模式
- 外观模式
- 享元模式
- 代理模式 Proxy Pattern
可以为另一个对象提供一个替身或者占位符,以控制对这个对象的访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37package proxy
import "fmt"
type Seller interface {
sell(name string)
}
// 火车站
type Station struct {
stock int // 库存
}
func (station *Station) sell(name string) {
if station.stock > 0 {
station.stock--
fmt.Printf("代理点中: %s买了一张票,剩余: %d \n", name, station.stock)
} else {
fmt.Println("票已售空")
}
}
// 火车代理点
type StationProxy struct {
station *Station // 持有火车站对象
}
func (proxy *StationProxy) sell(name string) {
if proxy.station.stock > 0 {
proxy.station.stock--
fmt.Printf("代理点中: %s买了一张票,剩余: %d \n", name, proxy.station.stock)
} else {
fmt.Println("票已售空")
}
} - 选项模式 Options Pattern
结构体参数很多,期望创建一个携带默认值的结构体变量,并选择性修改其中一些参数的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68package options
import "time"
type Connection struct {
addr string
cache bool
timeout time.Duration
}
const (
defaultTimeout = 10
defaultCaching = false
)
type options struct {
timeout time.Duration
caching bool
}
// Option override behavior of Connect
type Option interface {
apply(*options)
}
type optionFunc func(*options)
func (f optionFunc) apply(o *options) {
f(o)
}
func WithTimeout(t time.Duration) Option {
return optionFunc(func(o *options) {
o.timeout = t
})
}
func WithCaching(cache bool) Option {
return optionFunc(func(o *options) {
o.caching = cache
})
}
// NewConnect create a connection.
func NewConnect(addr string, opts ...Option) (*Connection, error) {
options := options{
timeout: defaultTimeout,
caching: defaultCaching,
}
for _, o := range opts {
o.apply(&options)
}
return &Connection{
addr: addr,
cache: options.caching,
timeout: options.timeout,
}, nil
}
func TestOptions(t *testing.T) {
connect, err := NewConnect("127.0.0.1", WithCaching(true), WithTimeout(5))
if err != nil {
log.Fatal(err)
}
fmt.Println(connect)
}
- 创建型模式 Creational Patterns
- SOLID原则
侧重设计原则,设计代码指导方针
- SRP: 单一功能原则, 一个类或者模块只负责完成一个指责
- OCP: 开闭原则,软件实体应该对扩展开放,对修改关闭
- LSP: 里氏替换原则, 如果S是T的子类型,则类型T的对象可以替换为类型S的对象,而不会破坏程序
- DIP: 依赖倒置原则,依赖于抽象而不是一个实例,其本质是要面向接口编程,不要面向实现编程
- ISP: 接口分离原则,客户端程序不应该依赖它不需要的方法
- 设计模式 Design Pattern
- 代码结构
项目管理
项目文档
Go 工具集
工具名 | 功能 |
---|---|
golines | 格式化长行 |
goimports | 导入包管理,自动增删依赖包,按照字母排序 |
mockgen | 接口Mock工具 |
gotests | 根据Go代码自动生成单元测试模版 |
go-junit-report | go test输出转换为junit xml |
richgo | 用文本装饰丰富go test的输出 |
golangci-lint | 静态代码检查工具 |
rts | response to struct 根据服务器的响应生成Go结构体 |
protoc-go-inject-tag | 通过protoc工具生成pb.go文件中注入自定义标签 |
db2struct | 将数据库表一键转换为go struct,支持自定义Tag和多种命名格式配置 |
gsemver | 根据git commit规范自动生成语义化版本 |
git-chglog | 根据git commit 自动生成CHANGELOG |
github-release | 命令行工具,创建、修改github release |
go-mod-outdated | 检查依赖包是否有更新 |
depth | 通过分析导入的库,将某个包的依赖关系用树状结构显示出来 |
go-callvis | 可视化显示Go调用关系 |
cfssl | CouldFlare的PKI的TLS工具集 |
addllicense | 通过扫描指定文件的文件,确保源码文件有版权头 |
gothanks | 自动在github上Star项目的依赖包所在的github repository |
swagger | 自动生成Go Swagger文档 |
Go Guide
Golang Frequently Asked Questions (FAQ)
Map access is unsafe only when updates are occurring.
How can I gurantee my type satisfies an interface?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package main
import "fmt"
type I interface {
Say(string)
}
type T struct {
}
func (t *T) Say(s string) {
fmt.Println(s)
}
// 用于触发编译期的接口的合理性检查机制
// 如果T没有实现I,会在编译期报错
// 赋值右边是断言类型的零值,对于指针类型、切片、map是nil,对于结构体类型是空结构
// Ask the compiler to check that the type T implements the interface I by
var _ I = (*T)(nil) // Verify that *T implements I.
func main() {
t := T{}
t.Say("Say Hello")
}Should I define methods on values or pointers?
1
2
3
4
5
6
7func (s *MyStruct) pointerMethod() {} // method on pointer
func (s MyStruct) valueMethod() {} // method on value
> When defining a method on a type, the receiver behaves exactly as if it were an argument to the method. Whether to define the receiver as a value or as a pointer is the same question, then, as whether a function argument should be a value or poiner.
1. First, and most important, does the method need to modify the receiver?
2. Second is the consideration of efficiency. If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.What operations are atomic? What about mutexes?
Do not communicate by sharing memory. Instead, share memory by communicating.
How can I control the number of CPUs?
1
The runtime can allocate more threads than the value of GOMAXPROCS to service multiple outstanding I/O requests. GOMAXPROCS only affects how many goroutines can actually execute at once; arbitrarily more may be blocked in system calls.
Who do I write a unit test?
1
2
3
4
51. Create a new file ending in _test.go in the same directory as your package sources. Inside that file, import "testing" and write functions of the form
func TestFoo(t *testing.T) {
…
}What compiler technology is used to build the compilers?
The default compiler, gc, was originally written in C, but since the Go 1.5 release the compiler has been a Go program.
Why do garbage collection? Won’t it be too expensive?
1
The current implementation is a mark-and-sweep collector.
User Go语言编码规范
指导原则:
指向interface的指针
- 使用指针传递才能通过接口方法修改基础数据
Interface 合理性验证
- var _ http.Handler = (*Handler)(nil) // 触发编译期的接口合理性检查机制,如果Handler没有实现Http.Handler 会报错
- 赋值右边是断言类型的零值,指针类型、切片和映射都是nil,对于结构类型是空结构
接收器receiver与接口
- 使用值接收器的方法即可以通过值调用,也可以通过指针调用
- 带指针接收器的方法只能通过指针调用
1
2
31. 一个类型可以有值接收器方法集和指针接收器方法集
一个值接收器方法集是指针接收器方法集的子集
2. 值对象只可以使用值接收器方法集,指针对象可以使用值接收器方法集+指针接收器方法集
零值 Mutex
零值sync.Mutex, sync.RWMutx是有效的,指向Mutex的指针基本是不必要的
1
1. 使用结构体指针,mutex应该作为结构体的非指针字段,即使该结构体不被导出,不要将mutex嵌入到结构体中
边界处拷贝Slices和Maps
1
21. 接收Slices 和 Maps: 当map或slice作为函数参数传入时,用户可以对其进行修改
2. 注意用户对报漏内部状态的map或slice的修改使用defer释放资源
Channel的size要么是1,要么是无缓冲的
枚举从1开始:
使用time处理时间
1
2
3
41. time.Time表示瞬时时间
2. time.Duration表示时间段
3. Time.Add: 某个时刻比前一个时刻晚
4. Time.AddDate: 下一个日历日错误类型
1
2
3
41. errors.New: 对于简单静态字符串的错误
2. fmt.Errorf: 对于格式化的错误字符串
3. 实现Error()方法的自定义类型
4. errors.Wrap: Wrapped errors错误包装 Error Wrapping
处理类型断言失败
追加时优先执行切片容量
性能
- 将原语转换为字符串或从字符串转换时,strconv速度比fmt快
- 指定容器容量,以便为容器预先分配内存,将在添加元素时最小化后续分配 – 容量提示
- 指定切片容量,编译器为其提供make()的slice的容量分配足够的内存
规范
包名
1
2
31. 全部小写,没有大写或下划线
2. 不使用复数
3. 不要用 common, util, shared, lib导入别名
除非导入之间有直接冲突,否则避免导入别名
减少嵌套
代码通过尽可能先处理错误情况/特殊情况今早返回或继续循环减少嵌套,减少嵌套多个级别的代码的代码
顶层变量声明
Golang Tips & Tracks
- 负载均衡算法对比
负载均衡最重要的就是均衡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package main
import (
"fmt"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func normal_shuffle(slice []int) {
for i := 0; i < len(slice); i++ {
a := rand.Intn(len(slice))
b := rand.Intn(len(slice))
slice[a], slice[b] = slice[b], slice[a]
}
}
func shuffle(indexes []int) {
for i := len(indexes); i > 0; i-- {
lastIdx := i - 1
idx := rand.Intn(i)
indexes[lastIdx], indexes[idx] = indexes[idx], indexes[lastIdx]
}
}
func main() {
var cnt1 = map[int]int{}
for i := 0; i < 1000000; i++ {
var sl = []int{0, 1, 2, 3, 4, 5, 6}
normal_shuffle(sl)
cnt1[sl[0]]++
}
var cnt2 = map[int]int{}
for i := 0; i < 1000000; i++ {
var sl = []int{0, 1, 2, 3, 4, 5, 6}
shuffle(sl)
cnt2[sl[0]]++
}
fmt.Println("normal shuffle: ", cnt1)
fmt.Println("fisher yates : ", cnt2)
}
chyi/go-awesome-prj/shuffle via 🐹 v1.17 took 38s
➜ go run main.go
normal shuffle: map[0:224558 1:128887 2:129201 3:129493 4:129487 5:129202 6:129172]
fisher yates : map[0:142974 1:143116 2:142768 3:142660 4:142930 5:142505 6:143047]