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()))
})
版权声明:本文为博主原创文章,未经博主允许不得转载。