Go 项目

Go 项目是一个偏工程化的概念,包含 Go 应用

b051da025c897996473df44693ea4ecc

Go 应用

2392d94feb95d3d64d765abe7d6e5e69

代码结构

按层拆分

最大的问题:相同功能可能在不同层被使用,而这些功能又分散在不同的层中,容易造成循环引用

ed0c3dfyy52ac82539cb602eec9f0146

1
2
3
4
5
6
7
8
9
10
├── controllers
│ ├── billing
│ ├── order
│ └── user
├── models
│ ├── billing.go
│ ├── order.go
│ └── user.go
└── views
└── layouts

按功能拆分

Go 项目最常用的拆分方法

  1. 不同模块,功能单一,可以实现高内聚低耦合的设计哲学
  2. 所有功能只实现一遍,引用逻辑清晰,大大减少循环引用的概率

0d65eb1363bf8055e209bc24d1d99ca5

1
2
3
4
5
pkg
├── billing
├── order
│ └── order.go
└── user

代码规范

编码规范

  1. Uber Go 语言编码规范
  2. 静态代码检查工具:golangci-lint

最佳实践

  1. Effective Go
  2. Go Code Review Comments
  3. Style guideline for Go packages

代码质量

编写可测试的代码

可测试:如果函数的所有代码均能在单元测试环境按预期被执行

0cef423ec1a4f06f6f4715bd0b9f4497

ListPosts 函数不可测试,因为依赖于一个 gRPC 连接

1
2
3
4
5
6
7
8
9
10
11
12
13
package post

import "google.golang.org/grpc"

type Post struct {
Name string
Address string
}

func ListPosts(client *grpc.ClientConn) ([]*Post, error) {
// use client to do something
return nil, nil
}

可测试的代码,将函数入参改为 Service 接口类型,只需要传入一个实现了 Service 接口的实例,函数便可执行成功

main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

type Post struct {
Name string
Address string
}

type Service interface {
ListPosts() ([]*Post, error)
}

func ListPosts(service Service) ([]*Post, error) {
return service.ListPosts()
}
main_test.go
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
package main

import "testing"

type fakeService struct {
}

// Duck Typing
func (s *fakeService) ListPosts() ([]*Post, error) {
posts := make([]*Post, 0)
posts = append(posts, &Post{
Name: "colin",
Address: "Shenzhen",
})
posts = append(posts, &Post{
Name: "alex",
Address: "Beijing",
})
return posts, nil
}

func TestListPosts(t *testing.T) {
if _, err := ListPosts(&fakeService{}); err != nil {
t.Fatal("list posts failed")
}
}
1
2
3
=== RUN   TestListPosts
--- PASS: TestListPosts (0.00s)
PASS
Mock 工具 描述
golang/mock 官方,最常用,与 testing 集成很好,实现了基于 interface 的 Mock 功能
go-sqlmock Mock 数据库连接
httpmock Mock HTTP请求
bouk/monkey 能够通过替换函数指针的方式来修改任意函数的实现,最终解决方案

高单元测试覆盖率

  1. 使用 gotests 或者 GoLand 自动生成单元测试代码
  2. 定期检查单元测试的覆盖率
1
2
3
4
5
6
7
8
9
10
11
12
13
$ gotests -all -w -i main.go 
Generated TestListPosts

$ go test -race -cover -coverprofile=./coverage.out -timeout=10m -short -v ./...
=== RUN TestListPosts
--- PASS: TestListPosts (0.00s)
PASS
coverage: 0.0% of statements
ok github.com/zhongmingmao/utest 0.021s coverage: 0.0% of statements

$ go tool cover -func ./coverage.out
github.com/zhongmingmao/utest/main.go:12: ListPosts 0.0%
total: (statements) 0.0%

Code Review

  1. GitHub / GitLab

编程哲学

面向接口编程

  1. Go 接口是方法的集合
  2. Duck Typing:任何类型只要实现了接口中的方法集,则实现了该接口
  3. 接口的作用:为不同层级的模块提供一个定义好的中间层,可以对上下游进行解耦
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
package main

import "fmt"

type Bird interface {
Fly()
Type() string
}

type Canary struct {
Name string
}

func (receiver *Canary) Fly() {
fmt.Printf("Canary[%v] is Flying\n", receiver.Name)
}

func (receiver *Canary) Type() string {
return fmt.Sprintf("Canary[%v]", receiver.Name)
}

type Crow struct {
Name string
}

func (receiver *Crow) Fly() {
fmt.Printf("Crow[%v] is Flying\n", receiver.Name)
}

func (receiver *Crow) Type() string {
return fmt.Sprintf("Crow[%v]", receiver.Name)
}

func LetItFly(bird Bird) {
fmt.Printf("Let %s Fly\n", (bird).Type())
bird.Fly()
}

func main() {
LetItFly(&Canary{"A"})
LetItFly(&Crow{"B"})
// Let Canary[A] Fly
// Canary[A] is Flying
// Let Crow[B] Fly
// Crow[B] is Flying
}

面向对象编程

  1. 必要时(接近于自然的思考方式),Go 应用也应该采用 OOP
  2. Go 不支持 OOP,可以通过语言级的特性来实现类似的效果
    • 结构体:类、抽象、封装
    • 结构体变量:实例
    • 继承通过组合来实现
      • 组合:结构体的嵌套
    • 接口:多态

27c84757b1f4626e84535d994ca70eb8

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
package main

import "fmt"

type Bird struct {
Type string
}

func (receiver *Bird) Class() string {
return receiver.Type
}

type Birds interface {
Class() string
Name() string
}

type Canary struct {
Bird // 通过匿名字段实现继承:Bird 为 Canary 的父类,Canary 继承了 Bird 的属性和方法
name string
}

func (receiver *Canary) Name() string {
return receiver.name
}

type Crow struct {
Bird
name string
}

func (receiver *Crow) Name() string {
return receiver.name
}

func NewCanary(name string) *Canary {
return &Canary{
Bird: Bird{Type: "Canary"},
name: name,
}
}

func NewCrow(name string) *Crow {
return &Crow{
Bird: Bird{Type: "Crow"},
name: name,
}
}

// BirdInfo 通过接口类型作为入参来实现多态
func BirdInfo(birds Birds) {
fmt.Printf("I'm %s, I belong to %s bird class!\n", birds.Name(), birds.Class())
}

func main() {
BirdInfo(NewCanary("A"))
BirdInfo(NewCrow("B"))
// I'm A, I belong to Canary bird class!
// I'm B, I belong to Crow bird class!
}

软件设计方法

设计模式

针对一些特定场景总结出来的最佳实现方式,比较具体,相对简单

1440f4bbcda682c8f5e7a599c8c51f9c

SOLID 原则

更侧重于设计原则,是设计代码时的指导方针

19b697bbbe31450d6cc8f222491d3e3b

项目管理

高效的开发流程(敏捷模型) + Makefile 管理项目 + 项目管理自动化(自动生成代码、借助工具、对接 CICD 等)

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
build              Build source code for host platform.
build.multiarch Build source code for multiple platforms. See option PLATFORMS.
image Build docker images for host arch.
image.multiarch Build docker images for multiple platforms. See option PLATFORMS.
push Build docker images for host arch and push images to registry.
push.multiarch Build docker images for multiple platforms and push images to registry.
deploy Deploy updated components to development env.
clean Remove all files that are created by building.
lint Check syntax and styling of go sources.
test Run unit test.
cover Run unit test and get test coverage.
release Release iam
format Gofmt (reformat) package sources (exclude vendor dir if existed).
verify-copyright Verify the boilerplate headers for all files.
add-copyright Ensures source code files have copyright license headers.
gen Generate all necessary files, such as error code files.
ca Generate CA files for all iam components.
install Install iam system with all its components.
swagger Generate swagger document.
serve-swagger Serve swagger spec and docs.
dependencies Install necessary dependencies.
tools install dependent tools.
check-updates Check outdated dependencies of the go projects.
help Show this help info.

借助工具

90ca527c2863fe642f9ab3d5b90fe980