Effective Go

  命名规范:

  •  function 的命名有讲究, 首字母是否大写代表了 该函数的可见性, 大写表示可见,小写表示包外不可见
  • package name 一般简洁简短即可单词小写,因为import时候可以重命名,所以不用担心命名碰撞
  • package name 一般也是source dir的名字,例如 the package in src/encoding/base64 is imported as "encoding/base64" but has name base64, not encoding_base64 and not encodingBase64.
  • package name 引入code中时候,引入包名,使用类似于bufio.Reader 包名+函数名方式更好,更清晰。
  • 接口名字, 只有一个方法的接口, 命名通常选择 用方法的名字+er后缀来 完成命名 或者和 struct 构造器差不多的命名
  • 接口不用下划线结合方式命名,用MixCapital 方式,也就是首字母大写方式

 

 

 flow control:

  • case 可以选择多个值
  • example:
    func shouldEscape(c byte) bool { switch c { case ' ', '?', '&', '=', '#', '+', '%': return true } return false }

        defer 是LIFO的结构,defer 函数会在函数出栈的时候执行,但是defer 参数的执行会立马执行,就能以一种较优雅方式来实现函数tracking

例子:

    

package main

import "fmt"

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

output:  entering: b
in b
entering: a
in a
leaving: a
leaving: b

 

 

new 和 make关键字

  • new 直接返回struct 对应指针,并且没有初始化内存结构,被称为zero value,对应的有些 struct 如果能不初始化内存就能使用,就会是一个比较方便的状态。如 sync.Mutex,bytes.Buffer
  • new(struct) 返回的数据,当struct 没有额外变量的时候,new 函数返回的数据和 &struct{} 是相同的
  • 至于go 没有构造函数这个问题:
  • Golang中没有设计构造函数. 取而代之的, 设计Golang的大师希望你用普通函数去实现构造的任务.

 

  • make ,只能用于 map,slice,channel的初始化,初始化的时候,都会赋予默认的内存空间,不会==nil,这个 和new struct,struct里面的变量 没有赋予内存空间 ==nil 不同,至于make返回的是不是指针这个问题
  • effective go里面是这样解释的,  it returns an initialized (not zeroed) value of type T (not *T). 但是这不意味着,这些结构在传参的时候,会进行值拷贝,因为以map为例 ,map本身其实是一个指针type,传参也不会拷贝map里面的值,导致性能消耗。
  • slice 内部其实也是由指针 定义的数据结构,然后传参也是传递指针参数,    type slice struct { array unsafe.Pointer len int cap int }
  • channel 也是由指针传递 值的拷贝

 

 

slice 和 array 

array 是值,赋值和函数传参都会导致拷贝值,而想要不经常拷贝值,请用slice

array 的size 是其中的一部分属性, The size of an array is part of its type. The types [10]int and [20]int are distinct.

 

map

获取 map 中不存在的值,返回的是默认值,不是nil, 例如 int 的默认值是0,struct 的默认值就是 {} 空的 struct

go风格中获取是否存在map中的key,  可以通过  

 

_, present := timeZone[tz] ,第二个参数为是否存在,前面的为值
类型转换的参数传参  .(type) 返回第二个参数也为辅助判断是否ok的参数,error也是在返回的第二个参数,由此可得,go风格中,辅助判断是否获取结果成功的参数一般为第二个位置,第一个位置一般是想要的结果,通过判断第二个参数得出第一个结果是否成功拿到

关于默认值和nil

go中默认值和nil 是不一样的,某些类型,如int 的默认值为0,可以想象到的基本类型的默认值 为基本默认值,如bool,float64,string等,而struct的默认值都不为nil,为{} ,默认值为nil的 类型,有slice,map,channel,interface,point等,编译器会提示不能和nil相等的都不会为nil

 

print

关于一个比较trick的地方,string的方法,如何输出内容,比较容易出错的地方

 

 

Method, value vs points

关于 是 value绑定method,还是pointer 绑定method 有两种不同的表现

  1. go是值传递,value绑定method的话,意味值,你在这个method改变value的值,函数调用处的value是改变不到值的,因为改变的value的拷贝,而pointer却能改变值
  2. 而且这两种绑定方式,一般会让函数接口有不同的设计,因为value你在绑定函数里面改变值没有用,所以一般会返回新的value值,而pointer即不用,pointer可以遵照go的常见库的设计,可以返回其他信息和err信息,直接上代码
  3.  

    type ByteSlice []byte

    func (slice ByteSlice) Append(data []byte) []byte {
        // Body exactly the same as the Append function defined above.
    }
    This still requires the method to return the updated slice. We can eliminate that clumsiness by redefining the method to take a pointer to a ByteSlice as its receiver, so the method can overwrite the caller's slice.

    func (p *ByteSlice) Append(data []byte) {
        slice := *p
        // Body as above, without the return.
        *p = slice
    }

    用指针绑定struct 的函数,也更方便成实现既有接口的函数,例如很多自带接口,都会返回 err信息以及其他,所以实现这类接口,一般用的都是 pointer,举个例子:Writer 接口

    func (p *ByteSlice) Write(data []byte) (n int, err error) {
        slice := *p
        // Again as above.
        *p = slice
        return len(data), nil
    }
    then the type *ByteSlice satisfies the standard interface io.Writer, which is handy. For instance, we can print into one.

        var b ByteSlice
        fmt.Fprintf(&b, "This hour has %d days\n", 7)

     

  4. 还有再解释下,绑定在value的函数和绑定到pointer的函数表现的区别,指针绑定的函数, value绑定的函数不能改变原 value的值,而指针绑定的函数能改变原value的值

interface and method

  1. go 里面的接口,经常是方法比较少的,这样方便实现 和组合
  2. xx.(type) 能获取该 value的type, 指定转化为某种 type 可以使用   tmpVa,ok:=xxx.(XXtype) 来实现, 不同的interface, struct 在声明的时候已经注明了 type ,例如  type A struct{}  , type B interface{}, 当ok为
  3. false的时候,tmpVa返回的是默认值,  type 是自定义一个自己的type的关键字,也可以type 函数为新类型,type OneFunc func() , type A int ,type Float float64等等都是合理的,通过自己声明的type,可以绑定一些其他的方法,  interfaces are just sets of methods, which can be defined for (almost) any type.

      4. 即使是函数type 也能实现接口,这个在http 包里面有所体现,看起来有点奇怪但是却实用

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers.  If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
    f(w, req)
}


// Argument server.
func ArgServer(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, os.Args)
}
ArgServer now has same signature as HandlerFunc, so it can be converted to that type to access its methods

http.Handle("/args", http.HandlerFunc(ArgServer))

所以,这个的封装,可以用在http 包下的server函数
func (mux *ServeMux) Handle(pattern string, handler Handler)


 

   函数type也可以实现接口,这个比较巧妙的做法

 

接口内嵌,或者说继承概念,可以通过,

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
    Reader
    Writer
}
struct 实现组合接口时候,可以考虑组合实现,
type ReadWriter struct {
    reader *Reader
    writer *Writer
}
当然需要先初始化里面的内部组件值.

 

concurrency     

go 里面的闭包的概念,和其他语言类似,函数里面调用了其他子函数,子函数能利用的信息是父函数传入的相关信息

func Announce(message string, delay time.Duration) { go func() { time.Sleep(delay) fmt.Println(message) }() // Note the parentheses - must call the function. }


关于 for range 里面的,for 循环里面的variable 是共享变量, 有时候要注意其值的拷贝

举一例子:

func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func(req *Request) {
            process(req)
            <-sem
        }(req)
    }
}
Compare this version with the previous to see the difference in how the closure is declared and run. Another solution is just to create a new variable with the same name, as in this example:

func Serve(queue chan *Request) {
    for req := range queue {
        req := req // Create new instance of req for the goroutine.
        sem <- 1
        go func() {
            process(req)
            <-sem
        }()
    }
}

 


channel

  1.  select 用在channel 接受值上面,好处是可以加 default 或者是超时选项,普通的channel接收值表达式,只能阻塞等待
  2.  

    tmpFun := func(aChan chan int, comment string) {
            fmt.Println(comment)
            select {
            case a1 := <-aChan:
                fmt.Println(a1)
            case <-time.After(10 * time.Second):
                fmt.Println("time out")
            //default:
            //    fmt.Println("default value")
            }


    相比 a1:=<- aChan 只能阻塞多了几个选项

     

error 

error 接口方法, Error() string ,如何保持应该有的信息,和原来的error信息,可以使用组合的方式保持原有error信息, for example: 

 

// PathError records an error and the operation and
// file path that caused it.
type PathError struct {
    Op string    // "open", "unlink", etc.
    Path string  // The associated file.
    Err error    // Returned by the system call.
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

 

panic and recover

panic 表示的是没有预料到的异常,可以通过 直接在defer 函数里面 调用 recover() 函数来停止这种异常的持续向上抛出,通过这种方式,不影响整体程序的运行.

panic的处理,可以通过 type 方式来只处理,自己有意直接抛出的panic,然后其他的panic继续向上抛出,直至有人捕获或者直接退出

// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
    return string(e)
}

// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
    panic(Error(err))
}

// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
    regexp = new(Regexp)
    // doParse will panic if there is a parse error.
    defer func() {
        if e := recover(); e != nil {
            regexp = nil    // Clear return value.
            err = e.(Error) // Will re-panic if not a parse error.
        }
    }()
    return regexp.doParse(str), nil
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值