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()))})
- GO备忘录
- GO备忘录
- Go语言备忘录:反射的原理与使用详解
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 备忘录
- 初探swift语言的学习笔记九(OC与Swift混编)
- 一个人的战斗
- ftp文件收发代码
- 使用Navicat For MySql 将mysql中的数据导出
- svn: E000017: Can't create directory '/home/cmcc/.svn/tmp': File exists
- GO备忘录
- [刷题]Majority Number II
- android:layout_gravity
- OpenGL和Mesa的关系
- Eclipse 搭建PhoneGap 开发环境
- Android自定义控件的好处
- android cts测试失败项以及原因
- 生产者/消费者问题的多种Java实现方式
- java:instanceof