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应用

    • 代码结构
      1. 目录结构
      2. 按功能拆分模块
      • 层次拆分 - MVC
      • 功能拆分 - 实现高内聚低耦合的设计哲学
    • 代码规范 - UBer_Go_Guid
    • 代码质量
    • 编程哲学
      • 面向接口编程
        1. 代码扩展性更强
        2. 解耦上下游的实现
        3. 提高代码的可测性
      • 面向“对象”编程
        1. 类、抽象、封装通过结构体来实现
        2. 实例通过结构体变量来实现
        3. 继承通过组合来实现,一个结构体嵌到另一个结构体,称作组合
        4. 多态通过结构来实现
          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
          69
          package 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
              16
              package 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
              18
              package 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
            22
            package 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
            25
            package 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
            15
            package 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,
            }
            }
            }
          • 建造者模式
          • 原型模式
        • 结构型模式 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
            58
            package 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
            44
            package 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
            37
            package 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
            68
            package 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)
            }
      • SOLID原则

        侧重设计原则,设计代码指导方针

        • SRP: 单一功能原则, 一个类或者模块只负责完成一个指责
        • OCP: 开闭原则,软件实体应该对扩展开放,对修改关闭
        • LSP: 里氏替换原则, 如果S是T的子类型,则类型T的对象可以替换为类型S的对象,而不会破坏程序
        • DIP: 依赖倒置原则,依赖于抽象而不是一个实例,其本质是要面向接口编程,不要面向实现编程
        • ISP: 接口分离原则,客户端程序不应该依赖它不需要的方法
  • 项目管理

  • 项目文档

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
    25
    package 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
    7
    func (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
    5
    1. 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的指针

      1. 使用指针传递才能通过接口方法修改基础数据
    • Interface 合理性验证

      1. var _ http.Handler = (*Handler)(nil) // 触发编译期的接口合理性检查机制,如果Handler没有实现Http.Handler 会报错
      2. 赋值右边是断言类型的零值,指针类型、切片和映射都是nil,对于结构类型是空结构
    • 接收器receiver与接口

      1. 使用值接收器的方法即可以通过值调用,也可以通过指针调用
      2. 带指针接收器的方法只能通过指针调用
        1
        2
        3
        1. 一个类型可以有值接收器方法集和指针接收器方法集
        一个值接收器方法集是指针接收器方法集的子集
        2. 值对象只可以使用值接收器方法集,指针对象可以使用值接收器方法集+指针接收器方法集
    • 零值 Mutex

      零值sync.Mutex, sync.RWMutx是有效的,指向Mutex的指针基本是不必要的

      1
      1. 使用结构体指针,mutex应该作为结构体的非指针字段,即使该结构体不被导出,不要将mutex嵌入到结构体中
    • 边界处拷贝Slices和Maps

      1
      2
      1. 接收Slices 和 Maps: 当mapslice作为函数参数传入时,用户可以对其进行修改
      2. 注意用户对报漏内部状态的mapslice的修改
    • 使用defer释放资源

    • Channel的size要么是1,要么是无缓冲的

    • 枚举从1开始:

    • 使用time处理时间

      1
      2
      3
      4
      1. time.Time表示瞬时时间
      2. time.Duration表示时间段
      3. Time.Add: 某个时刻比前一个时刻晚
      4. Time.AddDate: 下一个日历日
    • 错误类型

      1
      2
      3
      4
      1. errors.New: 对于简单静态字符串的错误
      2. fmt.Errorf: 对于格式化的错误字符串
      3. 实现Error()方法的自定义类型
      4. errors.Wrap: Wrapped errors
    • 错误包装 Error Wrapping

    • 处理类型断言失败

    • 追加时优先执行切片容量

  • 性能

    • 将原语转换为字符串或从字符串转换时,strconv速度比fmt快
    • 指定容器容量,以便为容器预先分配内存,将在添加元素时最小化后续分配 – 容量提示
    • 指定切片容量,编译器为其提供make()的slice的容量分配足够的内存
  • 规范

    • 包名

      1
      2
      3
      1. 全部小写,没有大写或下划线
      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
    51
    package 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]