这篇文章的内容来自Docker的Steve Francia大神的分享,视频链接:https://www.youtube.com/watch?v=29LLRKIL_TI&t=357s
下面我尝试将自己的理解记录并分享,由于英文水平有限,如果文中有错误,望不吝指正。
部分人最严重的错误是把错误当成恶魔,认为错误是不可饶恕的。事实却是当我们尝试使用一些新的事物时,出现错误是必然的。视频中提道,大师和初学者的区别就是大师比初学者尝试错误的次数要多得多:
Do you want to know the difference between a master and a beginner ?
The master has failed more times than the beginner has tried.
下面进入正文:
作者拿hugo中的一段代码作例子:
func (page *Page) saveScourceAs(path string) {
b := new(bytes.Buffer)
b.Write(page.Source.Content)
page.saveSource(b.Bytes(),path)
}
func (page *Page) saveSource(by []byte, inpath string){
WriteToDisk(inpath,bytes.NewReader(by))
}
大家可以看出代码中的问题吗,传入saveSource方法的字节切片是从buffer中取出的,saveSource方法中又将其转换为一个reader。这些都是不必要的转换。导致这中问题的原因就是saveSource方法的参数定义得太具体了,如果尝试像下面这样写,将要读取的字节切片抽象为reader,会更加高效:
func (page *Page) saveScourceAs(path string) {
b := new(bytes.Buffer)
b.Write(page.Source.Content)
page.saveSource(b,path)
}
func (page *Page) saveSource(b io.Reader, inpath string){
WriteToDisk(inpath,b)
}
先说说io.Reader和io.Writer的特点:
大量的库都会经常使用io.Reader和io.Writer,io.Reader和io.Writer的定义如下:
type Reader inteface{
Read(p []byte) (n int,err error)
}
type Writer inteface{
Write(p []byte) (n int,err error)
}
当我们要设置输出的时候,使用io.Writer就很恰当了:
//code from cobra
func (c *Command) SetOutput(o io.Writer){
c.output = o
}
还有下面的错误示范(并非功能性错误,而是规范的问题):
//code from viper
func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error{
...
}
更好的做法是将输入定义为一个reader:
func (v *Viper) ReadConfig(in io.Reader) error{
...
}
先看看接口类型作为函数参数的准则:
作为参数的接口太宽泛的一个例子是:
func ReadIn(f File){
b := []byte{}
n,err:=f.Read(b)
}
其实ReadIn函数只是使用接口的read方法,所以File接口太过宽泛。下面的实现更为合理:
func ReadIn(r Reader){
b:=[]byte{}
n,err := r.Read(b)
}
这一节不太好理解,我自己没有理解得很清楚。下面的内容是我按照字面意思尽量去理解的笔记,写得并不好,有错误的话劳烦大家指正。
常见的错误是过多使用方法:
为了避免这个错误,我们需要了解函数和方法的定义:
函数的定义:
方法的定义:
函数和方法还有以下区别:
指针和值的使用可以参照以下准则:
是否使用指针接收器(receiver)?
是否使用值接收器?
下面是指针接收器的例子。因为我们要修改接收器的状态,我们希望接收器是共享的,所以使用指针接收器:
func (f *InMemoryFile) Close() error{
...
f.closed=true // modify state
...
}
下面是值接收器的例子。因为该接收器不需要被共享,所以使用值传递。另外也有个童鞋回答是因为"Time is ticking",我不知道该怎么理解这个原因。
func (t Time) IsZero() bool{
return t.sec==0 && t.nsec=0
}
不少人取得一个error之后,为了判断是哪种error而对error的Error()方法返回的值进行字符串比对,这种做法并不太好,最好的做法是将error定义为公开的变量,判断error值是否相等,如:
var ErrNoName = errors.New("Zero length page name")
func Foo(name string)(error){
err := NewPage(name)
if err == ErrNoName{
newPage("default")
}
}
此外,我们还可以自定义error,好处有以下几点:
例如docker中的代码,Error中的Code和Detail是错误的上下文环境,且Detail是一个动态值,可以根据错误设定其值:
type Error struct{
Code ErrorCode
Message string
Detail interface{}
}
func (e Error) Error() string{
return fmt.Sprintf(...)
}
还有Go语言的OS包中定义的错误类型:
type PathError struct{
Op string
Path string
Err error
}
func (e *PathError) Error() string{
return ...
}
在遇上自定义error时,你可以这么处理:
func baa(f *file) error{
...
n,err := f.WriteAt(x,3)
if _,ok := err.(*PathError) {
...
} else {
...
}
}
或者这么处理:
if serr != nil{
if serr, ok := serr.(*PathError); ok && serr.Err == syscall.ENOTDIR{
return nil
}
return serr
}
什么时候该考虑并发:
如何保证线程安全?
为什么保留非线程安全的特性?
Go语言中的Map是非线程安全的,有以下几点原因:
最后再重申一下,最严重的错误就是不犯错误。犯错是一个学习和探索的过程。如果你没有经历错误,你可能在制造一个更深远更大的错误。