server接收dtu透传代码_Gopher2020大会干货总结:代码技巧篇

92aaeee96249df535dee0f1c5c35fd55.png

Gopher2020大会已经结束了几天,圈内大牛的分享可谓干货满满,分享内容涉及到诸多的业务、框架、理念等,本文系会后粗略整理,主要是将一些干货内容总结、分类;本文内容不涉及业务、框架以及设计理念,整理重点在于Go的代码技巧;这些技巧并非准则,而是由一个个小tips组成,如有疏漏或错误的地方,烦请看官指正。

Functional Options

这个主题左耳朵耗子(陈皓)和毛剑都有讲到过。毛剑老师对此的总结非常精炼:从代码层面可以明确区分接口的必要参数和可选参数。以陈皓老师的代码为例:

type Server struct {
    Addr string
    Port int
    Protocol string
    Timeout time.Duration
    MaxConns int
    TLS *tls.Config
}

func NewServer(addr string, port int) (*Server, error) {}
func NewTLSServer(addr string, port int, tls *tls.Config) (*Server, error) {}
func NewServerWithTimeout(addr string, port int, timeout time.Duration) (*Server, error) {}
func NewTLSServerWithMaxAndTimeout(addr string, port int, maxconns int, timeout time.Duration, tls *tls.Config)(*Server, error) {}

对于Server而言,必要参数为AddrPort,其他均为可选参数,为此需要为众多可选参数写出各种可能组合的接口,代码冗余会相当严重,一旦后期扩展更多的可选参数,那么接口的数量将会是灾难,同时也无法明确区分哪些是可选参数,在使用Functional Options后,接口可以简化为一个。

type Option func (*Server)
func Protocol(proto string) Option {
    return func(s *Server) {
        s.Protocol = proto
    }
}

func Timeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.Timeout = timeout
    }
}

func MaxConns(maxconns int) Option {
    return func(s *Server) {
        s.MaxConns = maxconns
    }
}

func TLS(tls *tls.Config) Option {
    return func(s *Server) {
        s.TLS = tls
    }
}

func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {}

简化之后接收数量骤减为一个,同时明确了只有AddrPort是必要参数,其他均为可选参数。“代码即文档”这句话在Functional Options得到最好的体现。

Error Handling

同样也是陈皓老师带来的一种代码简化技巧:

func Parse(reader io.Reader) (*Point, error) {
    var point Point
    if err := binary.Read(reader, binary.BigEndian, &point.Longitude); err != nil {
        return nil, error
    }

    if err := binary.Read(reader, binary.BigEndian, &point.Latitude); err != nil {
        return nil, error
    }

    if err := binary.Read(reader, binary.BigEndian, &point.Distance); err != nil {
        return nil, error
    }

    if err := binary.Read(reader, binary.BigEndian, &point.EleationLoss); err != nil {
        return nil, error
    }

    return &point, nil
}

想必很多同学都有以上代码的经历,error的处理冗长累赘,读写相当累人。以上代码可以优化成:

func Parse(read io.Reader) (*Point, error) {
    var point Point
    var err error
    read := func (data interface{}) {
        if err != nil {
            return
        }

        err = binary.Read(read, binary.BigEndian, data)
    }

    read(&point.Longitude)
    read(&point.Latitude)
    read(&point.Distance)
    read(&point.EleationLoss)

    if err != nil {
        return nil ,err
    }

    return &point, nil
}

这种错误处理办法我曾在gouxp中也使用过。

Deep Comparison

在各种复杂业务需求里面,总会遇到各式对象之间需要比较是否相等,Go中没有C++的操作符重载,那么如何实现对象之间的比较呢?答案是Deep Comparison,本质是反射对象,逐一比较对象类型的所有字段。

type data struct {
    num     int
    checks  [10]func() bool
    doit    func() bool
    m       map[string]string
    bytes   []byte
}

func main() {
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:", reflect.DeepEqual(v1, v2))

    m1 := map[string]string{"one": "a", "two": "b"}
    m2 := map[string]string{"two": "b", "one": "a"}
    fmt.Println("m1 == m2:", reflect.DeepEqual(m1, m2))

    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    fmt.Println("s1 == s2:", reflect.DeepEqual(s1, s2))
}

当然,使用reflect.DeepEqual也是需要谨慎考虑的,毕竟反射很慢

Interface Partterns

严格来说,这个issue是我根据陈皓老师的内容得出的一点启发,同时也是我在gouxp中的关于接口的小技巧实践。

type Conn interface {
}

func (conn *Conn) Close(err error) {}

Conn是一个TCP连接的抽象,一条网络连接在Client和Server端都会存在一个Conn对象,如果Client Conn和Server Conn的关闭逻辑有区别,那该如何实现Close函数呢?是独立写ClientClose、ServerClose还是将Conn进一步抽象为两端分别使用的interface?

在C++里面,使用多态重写即可解决,那么在Go中呢?

type ConnCloser interface {
    close(err error)
}

type Conn interface {
    ConnCloser
}

func (conn *Conn) Close(err error) {
    conn.close(err)
}

客户端和服务端各自实现ConnCloser即可实现逻辑分离。

Avoid string to byte conversion

众所周知,字符串到byte的直接转换是很昂贵的,所以代码中要尽量避免string与byte之间的直接转换。writer.Write(byteData)要明显好于writer.Write([]byte("HelloWorld")),有兴趣的同学可以跑个Benchmark看看效果。

Use StringBuilder and StringBuffer

字符串的拼接与内存块直接写入的性能区别相差非常大,但凡涉及到字符串的拼接,请使用StringBuilderStringBuffer

Avoid Link Node When Use sync.Pool

有这样的代码:

type message struct {
    childMsg []*message
}

var msgPool = sync.Pool{New: func() interface{} {
    return &message{make([]*message, 0, 8)}
}}

func NewMsg(parent *message) *message {
    ret := msgPool.Get().(*messa(ge)
    if parent != nil && len(parent.childMsg) < 8 {
        parent.childMsg = appen(parent.childMsg, ret)
    }

    return ret
}

func RecycleMessage(msg *message) {
    msg.childMsg = msg.childMsg[:0]
    msgPool.Put(msg)
}

从上面可以看出,相当混乱的引用关系导致sync.Pool无法成功回收进而导致内存泄漏,因此在sync.Pool的使用上要注意可能存在父子关系或其他复杂引用关系的对象

Channel Pipeline

假设有这样的需求:对一组数值逐个进行平方运算,然后再逐个求和,该如何实现?仔细分析需求,有两个计算阶段是先平方再求和,那么所需数据如何传递呢?函数参数吗?如何平方或求和相当耗时应该怎么处理呢?

从Go的官方博客上可以找到关于Channel Pipeline的概念:

  • Receive values from upstream via inbound channels
  • Perform some function on that data, usually producing new values
  • Send values downstream via outbound channels

本质上就是阶段和数据的拆分,使用chan作为数据中轴;这样每个阶段有独立的实现逻辑互不影响,各个阶段可以并发实现,输入与输出均为chan,典型的生产者消费者场景。

func echo(nums []int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()

    return out
}

func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()

    return out
}

func sum(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        var sum int
        for n := range in {
            sum += n
        }
        out <- sum
        close(out)
    }()

    return out
}
func main() {
    var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    for n := range sum(sq(echo(nums))) {
        fmt.Println(n)
    }
}

延伸阅读

  • Go Concurrency Patterns: Pipelines and cancellation
  • Effective Go
  • Uber Go Style
  • 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs
  • Go Advice
  • Practical Go Benchmarks
  • Benchmarks of Go serialization methods
  • Debugging performance issues in Go programs
  • Go code refactoring: the 23x performance hunt
  • Golang Error Handling lesson
  • Errors are values
  • Why Go haven’t Map/Reduce?
  • Advanced Go Concurrency Patterns
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值