GO备忘录

来源:互联网 发布:开源三网合一源码 编辑:程序博客网 时间:2024/05/01 18:29

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 int16const (    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 stringfunc (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 uint32const (    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()))})
0 0