函数式编程
与其他编程语言的差异
1 可以有多个返回值 
2 所有参数都是值传递:slice,map,channel会有传引用的错觉 
3 函数可以作为变量的值 
4 函数可以作为参数和返回值
可变参数
...传过来的参数,例如求和:
func TestSum(t *testing.T) {
	sumFunc := func(args ...int) int {
		var ret int
		for _, op := range args {
			ret += op
		}
		return ret
	}
	t.Logf("the sum is: %v\n", sumFunc(1, 2, 3, 4, 5))
}
defer函数
1 panic后defer会继续执行 
2 os.Exit()会直接退出,defer不会执行
示例:装饰者模式实现计时功能
func getTimeSpent(inner func(op int) int) func(op int) int {
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent: ", time.Since(start).Seconds())
		return ret
	}
}
func runSlow(op int) int {
	time.Sleep(time.Second * 3)
	fmt.Println("runSlow run...")
	return op * op
}
func runFast(op int) int {
	time.Sleep(time.Second * 1)
	fmt.Println("runFast run...")
	return op * op * op
}
func TestTimer(t *testing.T) {
	tsSf := getTimeSpent(runSlow)
	tsFs := getTimeSpent(runFast)
	t.Log("exec....")
	t.Logf("runSlow ret is : %v\n", tsSf(10))
	t.Logf("runFast ret is : %v\n", tsFs(10))
}
以上请求的输出是:
=== RUN   TestTimer
    main_test.go:61: exec....
runSlow run...
time spent:  3.0143102
    main_test.go:63: runSlow ret is : 100
runFast run...
time spent:  1.0120686
    main_test.go:64: runFast ret is : 1000
--- PASS: TestTimer (4.03s)
PASS
Process finished with the exit code 0
defer
defer执行顺序是栈 
defer中函数如果包含的参数也是函数,会直接使用函数的值作为参数执行,而不是稍后执行
面向对象编程
结构体
定义
// 例如定义结构体:学生
type Student Struct {
     Age  int
     Name string
}
初始化
e  := Student{18, "Tom"} // 直接根据结构初始化,不建议
e1 := Student{Age: 18, Name: "Tom"}
e2 := new(Student) //这里返回的是指针/引用,相当于e := &Student{}
e2.Age = 18
e2.Nane = "Tom"
方法
定义
// 在实例对应方法被调用时,实例成员会进行值复制
func (e Employee) String() string {
     fmt.Printf("Name: %s", e.Name)
}
// 通常为避免内存拷贝,使用第二种调用方式
func (e *Employee) String() string {
     fmt.Printf("Name: %s", e.Name)
}
接口
与其他编程语言的差别: 
1 接口为非入侵性,实现不依赖于接口定义 
2 所以接口的定义可以包含在接口使用者包内
type Phone interface {
	Call() string
}
type AndroidPhone struct {
}
type Iphone struct {
}
func (nokia AndroidPhone) Call() string {
	return "nokia is calling..."
}
func (iphone7 Iphone) Call() string {
	return "iphone7 is calling..."
}
func TestNewPhone(t *testing.T) {
	var phone Phone
	phone = new(AndroidPhone)
	t.Logf("1: android: %s\n", phone.Call()) // android: nokia is calling...
	phone = new(Iphone)
	t.Logf("2: iphone: %s\n", phone.Call()) // iphone: iphone7 is calling...
}
确保程序实现接口
上一个示例中可以采用如下方法确保实现了接口:
var _ Phone = &AndroidPhone{}
自定义类型
1 type IntconvFunc func(n int) int 
2 type MyPoint int 
例如之前的获取时间差代码,可以改写为:
var GetInner func(op int) int
func getTimeSpent(inner GetInner) GetInner {
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent: ", time.Since(start).Seconds())
		return ret
	}
}
匿名类型嵌入
1 注意不是继承
go多态实现
依旧以上文为例,实现类似多态的功能:
type Phone interface {
	Photo() string
}
type AndroidPhone struct {
}
type Iphone struct {
}
func (nokia AndroidPhone) Photo() string {
	return "nokia is photograph..."
}
func (iphone7 Iphone) Photo() string {
	return "iphone7 is photograph..."
}
func photoUseDiffPhone(inter Phone) {
	fmt.Printf("interface : %T, res: %s\n", inter, inter.Photo())
}
func TestDiffInterface(t *testing.T) {
	android := &AndroidPhone{}
	iphone := &Iphone{}
	photoUseDiffPhone(android)
	photoUseDiffPhone(iphone)
}
空接口与断言
1 空接口可以表示任何类型 
2 通过断言将空接口转化为指定类型
空接口示例
设置一个类型为空接口interface{},初始化或赋值是可以给定任意类型
type NilInterfaceTest struct {
	Name        string
	Age         int8
	PhoneNumber interface{}
}
func TestNilInterface(t *testing.T) {
	a := NilInterfaceTest{Name: "Tom", Age: 18, PhoneNumber: 13333333333}
	b := NilInterfaceTest{Name: "Jerry", Age: 17, PhoneNumber: "011-73313"}
	t.Logf("nil interface as int: %v\n", a)    // nil interface as int: {Tom 18 13333333333}
	t.Logf("nil interface as string: %v\n", b) // nil interface as string: {Jerry 17 011-73313}
}
断言示例
断言判断接口类型
注意,断言是用来判断接口的,类似a := 10这种,无法使用断言
// 断言
v, ok := p.(int) // ok为true是表示成功
// 具体使用
func TestAssertData(t *testing.T) {
	var a interface{}
	var b interface{}
	a = 10
	b = "test"
	aAssert, ok := a.(int)
	t.Logf("assert a %v, ret %v\n", aAssert, ok)
	bAssert, ok := b.(int)
	t.Logf("assert b %v, ret %v\n", bAssert, ok)
}
最佳实践
使用switch p.(type)判断空接口的类型:
func getType(p interface{}, t *testing.T) {
	switch p.(type) {
	case string:
		t.Log("the type is string...")
	case int:
		t.Log("the type is int...")
	default:
		t.Log("the type is other...")
	}
}
func TestAssertType(t *testing.T) {
	var p interface{}
	var q interface{}
	p = 12
	q = "test"
	getType(p, t) // the type is int...
	getType(q, t) // the type is string...
}
go接口最佳实践
保持接口最小,较大的接口定义,可以组合较小的接口
错误处理
error
内部自己定义的错误,要想程序稳健,尽量不要错过处理任何一个error
与其他编程语言的差异
1 没有异常机制 
2 error类型实现了error接口 
3 可以通过error.New来快速创建错误实例
最佳实践
定义不同的错误常量,以便于判断错误类型,函数最后一个返回值最好设置为nil,方便接收方判断
func divisionData(divisor int32, dividend int32) (float32, error) {
	var DividendZeroError error = errors.New("dividend cannot be 0")
	if dividend == 0 {
		return 0, DividendZeroError
	}
	return float32(divisor) / float32(dividend), nil
}
func TestErrorMessage(t *testing.T) {
	var a int32 = 3
	var b int32 = 6
	ret, err := divisionData(a, b)
	if err != nil {
		t.Logf("device error: %v\n", err)
	}
	t.Logf("device ret: %v\n", ret) // device ret: 0.5
	var c int32 = 3
	var d int32 = 0
	ret, err = divisionData(c, d)
	if err != nil {
		t.Logf("device error: %v\n", err) // device error: Dividend cannot be 0
	}
	t.Logf("device ret: %v\n", ret) // device ret: 0
}
panic的处理
1 不可恢复的错误 
2 退出前会调用defer指定的函数
panic与os.Exit()的区别
1 os.Exit()退出时不会调用defer指定的函数 
2 os.Exit()退出时不输出当前调用栈信息
recover
Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
recover可能会形成僵尸服务进程,导致health check失效
模块化和包
package
1 基本复用模块单元,以首字母大写表明可被包外部访问 
2 代码的package可以和所在目录不一致 
3 同一个目录里的package名称要保持一致
init
可以有多个init,按照顺序执行 
package引入的init先执行,按照顺序执行 
在main执行前,所有init都会被执行
go语言进阶
并发编程模式
MPG
machine,一个 machine 对应一个内核线程,相当于内核线程在 Golang 进程中的映射
processor,一个 prcessor 表示执行 Go 代码片段的所必需的上下文环境,可以理解为用户代码逻辑的处理器
goroutine,是对 Golang 中代码片段的封装,其实是一种轻量级的用户线程。
注意事项
调度顺序不固定,如果不传值,那么在循环中执行可能只能拿到相同的值
func TestGoroutine(t *testing.T) {
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println(i) // 随机,有调用时间
		}(i)
		go func() {
			fmt.Println(i) // 循环次数较小,机器较好的情况下,基本是10
		}()
	}
}