使用 Go 常犯的错误,以及如何避免它们

1. Not Accepting Interfaces

Go 语言的数据类型 可以表达状态和行为,状态即数据类型的内部数据结构,行为是数据类型拥有的方法。 接口是 Go 语言的最强大的功能之一, 它实现了类型的可扩展性.一组方法定义一个接口。数据类型只要实现了接口的所有的行为,就称它满足接口。

bytes.Buffer 是可变长度的字节缓冲区,有 Read 和 Write 方法。

错误的写法

func (page *Page) saveSourceAs(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))
}

正确的写法

func (page *Page) saveSourceAs(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)
}

2. Not Using io.Reader & io.Writer

这两个接口的好处:

两个接口的定义:

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

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

错误的写法:

 func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error {
	 v.config = make(map[string]interface{})
	 v.marshalReader(buf, v.config)
	 return nil
 }

正确的写法

func (v *Viper) ReadBufConfig(in io.Reader) error {
	 v.config = make(map[string]interface{})
	 v.marshalReader(in, v.config)
	 return nil
}

3.Requiring Broad interfaces

错误的写法

func ReadIn(f File) {
	b := []byte{}
	n, err := f.Read(b)
	...
}

正确的写法

func ReadIn(r Reader) {
	b := []byte{}
	n, err := r.Read(b)
	...
}

4. Methods Vs Functions

有面向对象开发经验的开发者容易过度滥用对象方法,用结构体和方法来定义一切。

What is A function ?

What is A Method ?

函数与方法之间的关系

正确的写法

func extracShortcodes(s string, p *Page, pt Template) (string, map[string]shortcode, error) {
	...
	for {
		switch currItem.typ {
		...
		case tError:
			err := fmt.Errorf("%s:%d: %s", p.BaseFileName(), (p.lineBNumRawContentStart()+pt.lexer.lineNum()-1), currItem)
		}
	}
}

5. Pointer Vs Values

当你需要共享一个值,就使用指针,否则使用 a value (copy) 如果你想通过方法共享一个值时,使用指针接收者,因为方法通常管理状态,但这样不是并发安全的

如果一个类型是空结构体(无状态,只有行为), 那么使用只使用值,这样是并发安全的

使用指针接收者的例子

type InMemoryFile struct {
	at int64
	name string
	data []byte
	closed bool
}

func (f *InMemoryFile) Close() error {
	atomic.StoreInt64(&f.at, 0)
	f.closed = true
	return nil
}

使用值接收者的例子

type Time struct {
	sec int64
	nsec uintptr
	loc *Location
}

func (t Time) IsZero() bool {
	return t.sec == 0 && t.nsec == 0
}

6. Thinking of errors As strings

error 是一个接口,它的行为就是返回错误信息的字符串。 公开的error变量更容易被检查。

type error interface {
	Error() string
}

错误的写法

func NewPage(name string) (p *Page, err error) {
	if len(name) == 0 {
		retrun nil ,errors.New("Zero length page name")
	}
}

正确的写法

var ErrNoName = erros.New("Zero length page name")

func NewPage(name string) (*Page, error) {
	if len(name) == 0 {
		return nil, ErrNoName
	}
}

func Foo(name string) (error) {
	err := NewPage("bar")
	if err == ErrNoName {
		newPage("default")
	} else {
		log.FatalF(err)
	}
}

通过定制化 error 接口,可以扩张已有的 error 接口,

例子

type Error struct {
	Code ErrorCode
	Message string
	Detail interface{}
}

func (e Error) Error() string {
	return fmt.Sprintf("%s: %s", strings.ToLower(strings.Replace(e.Code.String()),"_"," ", -1), e.Message)
}
type PathError struct {
	Op string
	Path string
	Err error
}
func (e *PathError) Error() string {
	return e.Op + " " + e.Path + ": " + e.Err.Error()
}

7. Maps Are Not Safe

并发写Map,程序可能会Panic,可以使用互斥锁来保护Map

func (m *MMFS) Create(name string) (File, error) {
	m.lock()
	m.getData()[name] = MemFileCreate(name)
	m.unlock()
	m.registerDirs(m.getData())[name]
	return m.getData()[name],nil
}

8. 有些值是不可比较的

9. 注意 make 和 new 的区别

type Foo map[string]string

x := make(Foo)
x["ping"] = "pong"    // ok

y := new(Foo)
(*y)["ping"] = "pong" // panic!