Three-day Thinking in Go (Day 2)

9 minute read

用户定义类型

类型组合

用户可以利用组合(composition)来从已有的类型中定义新的类型

 1type (
 2  car struct {
 3    year int
 4    maker string
 5  }
 6
 7  ev struct {
 8    car car
 9    chargeRate float32
10  }
11)
12
13model3 := ev {
14  chargeRate, 10.0
15  car: car{
16    year: 2017,
17    maker: "Tesla",
18  }
19}
20// can visit
21model3.car.maker
22model3.chargeRate

匿名类型组合

相当于把nested的类型展开了

 1type (
 2  car struct {
 3    year int
 4    maker string
 5  }
 6
 7  ev struct {
 8    car
 9    chargeRate float32
10  }
11)
12
13model3 := ev {
14  chargeRate, 10.0
15  car: car{ // 默认用type的名字
16    year: 2017,
17    maker: "Tesla",
18  }
19}
20// can visit
21model3.maker
22model3.chargeRat

并行类型

  • 用户可以把一个类型定义为一个已有的类型
  • 这个类型是全新的(并行的类型),并不是别名,
  • 和原始type使用的时候可能需要type promotion
  • 类型上的方法不会继承

匿名结构体

有时候,有一些用完即抛的操纵,比如un-marshall json,或者test case的输入, 可以考虑创建匿名结构体

1func main() {
2  model3 := struct {
3    year int
4    maker string
5  } {
6    year: 2017,
7    maker: "Tesla",
8  }
9}

方法

  • 类型方法是一种特殊的function,定义的是type的行为
  • 用来区分类型方法还是函数是, 函数接收器(function receiver), 有接收器的是方法, 没有接收器的为函数
  • 可以用在primitive类型也可以用户定义的类型
  • pass by value (这个value可以是指针)
  • 用 dot 来访问 type上的方法
  • 对于 mutable 和 immutable 的区分是在函数接收器的传递上
    • 指针方法,指针作为接收器,
    • 值方法,传值作为接收器,
 1// 值方法 value semantic
 2package main
 3
 4import "fmt"
 5import "time"
 6
 7type car struct {
 8	year  int
 9	maker string
10}
11
12func (c car) isNew() bool {
13	return c.year == time.Now().Year()
14}
15
16}
17func (c car) setYear() {
18	c.year = time.Now().Year()
19}
20
21func main() {
22	myCar := car{
23		year:  2019,
24		maker: "Tesla",
25	}
26	fmt.Printf("%#v\n", myCar) // => main.car{year:2019, maker:"Tesla"}
27	myCar.setYear()
28	fmt.Printf("%#v\n", myCar) // => main.car{year:2019, maker:"Tesla"}
29}
1// mutation的方法需要使用 指针方法 Value Semantic
2func (c *car) setYear() {
3	c.year = time.Now().Year()
4}

接口

An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

  • 接口声明行为
  • 接口名字一般使用er/or结尾的表动作的名词
  • 多态
  • 接口不是类型的一部分
  • 接口一个双字数据结构
    • 一个指针指向type相关的信息结构体 iTable(type info + method set)
    • 一个指针指向数据
  • 不需要显式的声明某一个类型实现了某一个接口
  • 通过方法集弄清楚一个类型是否实现了某接口
  • 接口可以组合composite
  • 尽可能让接口小 (The bigger the interface, the weaker the abstraction - Rob Pike)
  • 小心接口污染
 1package main
 2
 3import "fmt"
 4
 5type painter interface {
 6	paint(color string)
 7}
 8
 9type table struct {
10	brand, color string
11}
12
13func (t table) paint(color string) {
14	t.color = color
15}
16
17type chair struct {
18	brand, color string
19}
20
21func (c *chair) paint(color string) {
22	c.color = color
23}
24
25func paintIt(p painter, color string) {
26	p.paint(color)
27}
28
29func main() {
30	tasks := []painter{
31		table{brand: "s", color: "black"},
32    chair{brand: "c", color: "red"},  // => cannot use chair literal (type chair) as type painter in slice literal: chair does not implement painter (paint method has pointer receiver)
33    &chair{brand: "c", color: "red"}, // => 用空interface
34	}
35
36	for i := 0; i < len(tasks); i++ {
37		paintIt(tasks[i], "white")
38		fmt.Println(tasks[i])
39	}
40}
41

接口方法集和(Method Set)规则

  • method set 记录了对于一个type支持的方法
  • 对于一个类型T,method set为其receiver为T类型的方法
  • 对于类型*T, method set则包括了receiver为T和*T类型的方法
接收器caller Tcaller *T
includedincluded
指针noincluded

 1package main
 2
 3import (
 4	"fmt"
 5	"reflect"
 6)
 7
 8type Painter interface {
 9	Paint()
10	RemovePaint()
11	Color()
12}
13
14type Tom struct{}
15
16func (t Tom) Color()        {}
17func (t *Tom) Paint()       {}
18func (t *Tom) RemovePaint() {}
19
20func main() {
21	var t Tom
22	elemType := reflect.TypeOf(&t).Elem() // Elem returns the value that the interface v contains or that the pointer v points to
23	n := elemType.NumMethod()
24	for i := 0; i < n; i++ {
25		fmt.Println(elemType.Method(i).Name)
26	} // =>  Color
27
28	var pt *Tom
29	elemType = reflect.TypeOf(&pt).Elem()
30	n = elemType.NumMethod()
31	for i := 0; i < n; i++ {
32		fmt.Println(elemType.Method(i).Name)
33	} // => Color, Paint, RemovePaint
34}
35

利用reflection可以根据call实现了同一个interface方法的不同类型

 1package main
 2
 3import "fmt"
 4
 5type painter interface {
 6	paint(color string)
 7}
 8
 9type table struct {
10	brand, color string
11}
12
13func (t table) paint(color string) {
14	t.color = color
15}
16
17type chair struct {
18	brand, color string
19}
20
21type tv struct {
22	brand, color string
23}
24
25func (c *chair) paint(color string) {
26	c.color = color
27}
28
29func paintIt(o interface{}, color string) {
30	if p, ok := o.(painter); ok {
31		p.paint(color)
32		return
33	}
34	fmt.Println("no way to paint this one")
35}
36
37func main() {
38	tasks := []interface{}{
39		&table{brand: "table", color: "black"},
40		&chair{brand: "chair", color: "red"},
41		&tv{brand: "tv", color: "while"},
42	}
43
44	for i := 0; i < len(tasks); i++ {
45		fmt.Println("paint", tasks[i], "to white color")
46		paintIt(tasks[i], "white")
47		fmt.Println("after paint", tasks[i])
48	}
49}
50
51// paint &{table black} to white color
52// after paint &{table black}
53// paint &{chair red} to white color
54// after paint &{chair white}
55// paint &{tv while} to white color
56// no way to paint this one
57// after paint &{tv while}

Testing

  • 测试是go的一等公民,所以自带了一个testing的框架
  • test file 必须以 _test.go 结尾
  • test 方法命名必须以 Test 开头
  • test code 和 code 在同一个文件夹
  • table testing 是 idiomatic, 标准库很多test是这么写的
  • 自带test coverage tool
  • 可以方便的测试http
1go test    # 在当前folder跑test
2go test -v # 在当前folder跑test 并打印测试信息
3go test .../. # 在当前文件夹以及子文件夹中跑test
4go test --run hello # 跑含有hello的tests
5go test -coverprofile=cov.out # 生成测试覆盖结果
6go tool cover -html=cov.out # 在网页中测试覆盖结果

Assert/BDD/TDD/Mocking

1go get github.com/stretchr/testify

BDD: Ginkgo/goblin Mock: moq

Table-driven Test

 1func sayNumber(i int) int {
 2	return i*2
 3}
 4
 5func TestSayNumber(t *testing.T) {
 6	testcases := []map[string]struct{
 7		value, expect int
 8	} {
 9		"test_one": {1, 10}
10		"test_two": {2, 20}
11	}
12
13	for _, u := range testcases {
14		actual, expected := sayNumber(u.value), u.expected
15		if actual != expected {
16			t.Fatalf("Use case %v expected %v but got %v", u.value, expected, actual)
17		}
18	} // => Failed  Use case 1 exptected 10 but got 2
19}

MicroBenchmarks

  • go自带有性能测试的支持
  • Benchmark tests 也放在 _test.go 的文件中
  • Benchmark 为test的前缀
  • Test用的是 testing.T 而Benchmark用的是testing.B
  • 测试跑的次数b.N 会自动被调整 确保测量是可靠的
  • 默认的测试时间是1s 但是可以通过-benchtime调整
  • 可以用-benchmeme来检测内存的分配
  • 跑test并不会跑Benchmark
  • assign 返回值 避免 inlining
 1var Output string
 2
 3func BenchmarkGreet(b *testing.B) {
 4	// test setup
 5
 6	// reset timer if needed
 7	b.ResetTimer()
 8
 9	var output string
10	for i := 0; i < b.N; i++ {
11		// assign return avoid inlining
12		output = greeter.Greet("hi")
13	}
14	Output = output
15}
16// go test -bench Greet
17// go test -bench Greet --benchmem
18// go test -bench Greet --benchtime 5s

GO入门系列

  1. 系列1
  2. 系列2
  3. 系列3

wechat-qrcode