GO备忘录

winlinvip · · 3965 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

GO备忘录

GO是C之后一门难得有鲜明特色的语言,不仅仅是一些语法糖,确实解决问题的思路,想法是不太一样的。

这篇文章是我看到的,和用到的,一些常见的GO的备忘录,但是有的地方不仅仅局限于用法,而是汇总一个专题。

error

GO的error错误对象

GO只要实现了error接口,就可以是一个error对象了。

常见的是项目有全局的错误对象,就像错误码一样,返回错误对象,比较错误是否是某个错误对象。

var ErrClosedClient = errors.New("kafka: tried to use a client that was closed")

还可以将整型对象变成错误对象:

type KError int16
const (
    ErrNoError                         KError = 0
    ErrNotEnoughReplicasAfterAppend    KError = 20
)

func (err KError) Error() string {
    switch err {
    case ErrNoError:
        return "kafka server: Not an error, why are you printing me?"
    case ErrNotEnoughReplicasAfterAppend:
        return "kafka server: Messages are written to the log, but to fewer in-sync replicas than required."
    }

    return fmt.Sprintf("Unknown error, how did this happen? Error code = %d", err)
}

这样就和c里面一样,只不错这个整型现在是一个对象了。

还有一种用法,就是某个模块的错误,譬如配置的,可以用string作为错误对象:

type ConfigurationError string
func (err ConfigurationError) Error() string {
    return "kafka: invalid configuration (" + string(err) + ")"
}

这样的好处是,错误信息都是以某种格式开头的了。

还有一种自定义结构为错误,返回时构造对象,这种往往是需要在错误对象里面指定特殊的信息。
譬如json模块的错误:

type SyntaxError struct {
    msg    string // description of error
    Offset int64  // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }

这个是因为json的错误需要指出是什么位置的错误:
s.err = &SyntaxError{“unexpected end of JSON input”, s.bytes}
这样每次都需要构造一个错误对象了。

log

日志的使用:

sarama.Logger = log.New(os.Stdout, "[sarama] ", log.LstdFlags)

库默认的日志是丢弃的:

var Logger StdLogger = log.New(ioutil.Discard, "[Sarama] ", log.LstdFlags)

可以再次初始化日志为错误输出:

logger = log.New(os.Stderr, "", log.LstdFlags)

json

GO的JSON可以直接变成Struct,而且可以嵌套,改变json和字段的对应关系:

type accessLogEntry struct {
    Method string `json:"method"`
    Net struct {
        MaxOpenRequests int `json:"mor"`
    } `json:"net"`
}

这样在调用son.Marshal(access_log_entry)时,会按照这个注入信息来处理。
如果是数组,应该传指针进去,否则json.Unmarshal时将值赋值是没有用的。譬如:

type Response struct {
    Code int `json:”code”`
    Data interface{} `json:”data”`
}

里面的Data可以是个结构体:

type MyObject struct {
     Id `json:”id”`
}
res := &Response{Data:&MyObject{}}

或者是个数组:

res := &Response{Data:&[]MyObject{}}

options

用户的option,在c中有getopt函数,直接解析命令行参数。在GO中也有的:

var addr = flag.String("addr",":8080","The address to bind to")
flag.Parse()

参数是个*string,使用时可以直接用:

fmt.Println("addr is", *addr)

virtual

GO中的继承和多态
GO没有继承,可以用组合来使用某个类的数据和方法:

type CollectorConfig struct {
    core.Config
    Proxy struct {
        Enabled bool `json:"enabled"`
    } `json:"proxy"`
}

但是GO中没法使用多态,也就是Config的函数如果要在子类中调用,必须得自己实现一遍:

func (c *CollectorConfig) Validate() error {
    if err := c.Config.Validate(); err != nil {
        return err
    }
    if len(c.Kafka.Topic) == 0 {
        return errors.New("kafka.topic must not be empty")
    }
    return nil
}

并且必须显式的调用,而如果子类不实现的话,是成员里面的函数被调用,而且上下文即this也变成Config。
譬如,这个函数就必须Config和CollectorConfig都实现一遍,因为this改变了:

func (c *Config) Loads(conf string) error {
    } else if err := json.Unmarshal([]byte(s), c); err != nil {
        return err
    } else {
        return c.Validate()
    }
}
func (c *CollectorConfig) Loads(conf string) error {
    } else if err := json.Unmarshal([]byte(s), c); err != nil {
        return err
    } else {
        return c.Validate()
    }
}

虽然代码一样,但是他们调用的函数Validate都是谁有调用谁的,而并非像OO中基类函数调用时this是子类。

http multiple handlers

GO实现多级http handler,采用参数调用的方式:

func KafkaServe(producer sarama.SyncProducer, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
       producer.send(...);

        next.ServeHTTP(w, req)
    })
}

使用时,传递多个handler:

proxy := httputil.NewSingleHostReverseProxy(&url.URL{
    Scheme: "http",
    Host: fmt.Sprintf("%v:%v", c.Proxy.Host, c.Proxy.Port),
})
http.Handle("/", KafkaServe(producer, proxy))

或者使用一个临时的handler:

http.Handle("/", KafkaServe(producer, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
    w.Write([]byte(core.NewNullResponse(0).Json()))
})))

譬如,如果需要在每个响应中都插入一个Server的信息,可以定义Handler如下:

// standard http header set server field.
// for this http status handler always write the http header,
// so we invoke the next after header is wrote.
// usage:
//      http.Handler("/", StdHttpHeaderServer(fmt.Sprintf("BravoCloud/BPAQ/%v", core.CdnQosVersion), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
//      })))
// or directly use:
//      StdHttpHeaderServer(fmt.Sprintf("BravoCloud/BPAQ/%v", core.CdnQosVersion), nil).ServeHTTP(w, req)
func StdHttpHeaderServer(server string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
        w.Header().Set("Server", server)

        if next != nil {
            next.ServeHTTP(w, req)
        }
    })
}

这样在使用时就直接用这个就好了:

        http.Handle("/api/v1/cdn_fluency/summaries", core.StdHttpHeaderServer(SERVER, http.HandlerFunc(
            func(w http.ResponseWriter, req *http.Request){
                w.Write([]byte(core.NewSuccessResponse(ctx.fluency.summaries).Json()))
            },
        )))

还可以简化一点,定义一个辅助函数:

// standard http handler,
// invoke the pre before handler, ignore for nil.
// then handler handle the request, should never be nil.
func HttpHandle(pattern string, pre http.Handler, handler func(w http.ResponseWriter, req* http.Request)) {
    http.Handle(pattern, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
        if pre != nil {
            pre.ServeHTTP(w, req)
        }

        handler(w, req)
    }))
}

然后定义这个pre前置处理器:

var SERVER = fmt.Sprintf("BravoCloud/BPAQ/%v", core.CdnQosVersion)
var SSH = core.StdHttpHeaderServer(SERVER, nil)

然后在handle时就简单了:

        core.HttpHandle("/api/v1/cdn_fluency/summaries", SSH, func(w http.ResponseWriter, req *http.Request) {
            w.Write([]byte(core.NewSuccessResponse(ctx.fluency.summaries).Json()))
        })

其中SSH可以定义成几种Handler的组合。还可以定义转换函数:

// HTTP response server.
var SERVER = fmt.Sprintf("BravoCloud/BPAC/%v", core.CollectorVersion)
// use a function to convert.
func SSH(h func(w http.ResponseWriter, req *http.Request)) http.Handler {
    return core.StdHttpHeaderServer(SERVER, http.HandlerFunc(h))
}

这样在使用时也很好用:

http.Handle("/", KafkaServe(producer, c.Kafka.Topic, SSH(func(w http.ResponseWriter, req *http.Request){
    w.Write([]byte(core.NewNullResponse(0).Json()))
})))

reverse proxy

GO做代理,譬如httputil的反向代理如果需要加东西,该怎么做
其实很简单,用接口组合,譬如需要统计请求发送的字节数,那么就应该自己实现http.ResonseWriter,譬如:

proxy := httputil.NewSingleHostReverseProxy(&url.URL{
    Scheme: "http",
    Host: fmt.Sprintf("%v:%v", c.Proxy.Host, c.Proxy.Port),
})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request){
    proxy.ServeHTTP(w, req)
})

这样给真正的Handler,譬如proxy处理时,这个w可以被重写成自己的,或者其他的。

default log writer

在实现log的level系统时,默认writer是个非常好的东西,匹配的level返回默认的writer:

func (c *Config) LogTank(level string, dw io.Writer) io.Writer {
    if c.Log.Level == "trace" {
        if level == "info" {
            return ioutil.Discard
        }
        return dw
    }
}

这样就不必写长长的if else了。

panic

GO的panic,更像assert,并非处理错误而是处理不该出现的问题。
而defer可以在goroutine顶级中捕获panic,通过recover来获取。如果panic的是个error,还可以直接将error设置为返回值。

func (ctx *BravoContext) ScanContext() (err error) {
    defer func() {
        if r := recover(); r != nil {
            switch r := r.(type) {
            case error:
                err = r
            default:
                fmt.Println("unknown panic", r)
            }
        }
    } ()

其实一般如果是error,会直接返回error,但是有时候为了更简单的处理。譬如:

func (c *Config) Json() string {
    if b,err := json.Marshal(c); err != nil {
        panic(err)
    } else {
        return string(b)
    }
}

这个是直接panic(err),如果有recover就会发现是个error。
如果这个对象转换为json出错的概率为0,那么可以panic;如果概率不小,应该返回error,更规范。
也就是说,一般如果有库返回error,那么调用的地方也应该返回error,就像错误码的返回一样,除非有明确可以忽略或者处理错误码。

有时候可以将panic转换成错误,因为父函数有处理错误:

func (ctx *Context) Serve() {
    for m := range ctx.messages {
        if err := ctx.process(m); err != nil {
            core.LoggerWarn.Println(fmt.Sprintf("ignore process message failed, err is %v", err))
        }
    }
}
func (ctx *Context) process(m *sarama.ConsumerMessage) (err error) {
    defer func(){
        // we convert all panic when process the message to error,
        // do not need to terminate for a message error.
        if r := recover(); r != nil {
            switch r := r.(type) {
            case error:
                err = r
            default:
                err = errors.New(fmt.Sprintf("unknown panic: %v", r))
            }
        }
    }()
}

但在goroutine的顶级函数中,一般涉及重要的逻辑的goroutine,应该退出,让看门狗重启。

func (ctx *Context) Serve() {
    defer func() {
        // we abort system for consumer panic.
        if r := recover(); r != nil {
            core.LoggerError.Println("system error for panic", r)
            os.Exit(-1)
        }
    } ()
}

而对于一些明确可以忽略的panic,可以直接打log,然后继续处理。
如果只明确处理函数的某一部分的panic,可以用匿名函数把这部分代码包起来。

func KafkaServe(producer sarama.SyncProducer, topic string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
        func(){
            defer func(){
                // when panic for message,
                // we just log the error and continue to
                // proxy the request to backend.
                if r := recover(); r != nil {
                    core.LoggerError.Println("serve request failed, err is", r)
                }
            }()

            partition, offset, err := producer.SendMessage(&sarama.ProducerMessage);
        }()

        next.ServeHTTP(w, req)
    })
}

总之,panic是系统的异常情况,不同于错误,该如何处理需要根据具体的情况来判断。

string vs []byte

GO语言中string和byte[]可以互转。
从byte[]变成string:

if b,err := json.Marshal(c); err == nil {
    return string(b)
}

从string变成byte[]:

var s0 = "{\"nb_cpus\":100}"
if err := json.Unmarshal([]byte(s0), &c0); err != nil {
    fmt.Println("err is", err)
}

enum vs iota

GO的枚举可以指定类型:

type FileMode uint32
const (
    ModeDir FileMode = 1 << (32 - 1 - iota)
)

GO的enum枚举,就是常量:

const (
    One = 1 << iota // 1<<0
    Tow // 1<<1
    Three // 1<<2
)

return params

返回的参数有时候很方便,直接return就可以:

func xxxx()(v string, err error) {
    if v,err = parseXXX(); err != nil {
        return
    }
    return
}

有时候需要用临时变量,这时候可以覆盖参数后返回:

func xxxx() (v string, err error) {
    if xxx,err := xxx(); err != nil {
        return "",err
    }
    if v,err = parseXXX(); err != nil {
        return
    }
    return
}

date

时间相关的函数,获取当前时间:time.Now()
获取Unix时间戳,秒为单位:time.Now().Unix()
获取时间戳,毫秒为单位:time.Now().UnixNano()/int64(time.Millisecond)
之前3个小时的时间:time.Now().Add(time.Duration(-1 * 3) * time.Hour)
调整天级别的时间,前3天:time.Now().AddDate(0, 0, -3)
日期的格式化函数:time.Now().Format("2006 01 02 15 04 05.999999999 -0700 MST")

struct

可以声明局部struct,以及直接对struct赋值:

core.HttpHandle("/api/v1/cdn_fluency/filters", SSH, func(w http.ResponseWriter, req *http.Request) {
    type Filter struct {
        Opration string `json:"op"`
        Value string `json:"value"`
    }
    type Filters struct {
        Filters map[string]*Filter `json:"filters"`
    }
    filters := &Filters{
        Filters: map[string]*Filter {
            "vhost": &Filter{
                Opration: "contains",
                Value: ctx.config.Fluency.FilterVhost,
            },
            "stream": &Filter{
                Opration: "contains",
                Value: ctx.config.Fluency.FilterStream,
            },
        },
    }
    w.Write([]byte(core.NewSuccessResponse(filters).Json()))
})

版权声明:本文为博主原创文章,未经博主允许不得转载。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:winlinvip

查看原文:GO备忘录

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3965 次点击  
加入收藏 微博
1 回复  |  直到 2000-01-01 00:00:00
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传