使用 Go 常犯的错误,以及如何避免它们
10 Oct 20171. 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
这两个接口的好处:
- Simple & flexible interfaces,for many operations around input and output
- Provides access to a huge wealth of functionality
- Keeps operations extensible
两个接口的定义:
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
- 函数应该只接受足它需要的方法的接口
- 函数不应该接受 a broad interface when a narrow one would work
- Compose broad interfaces made from narrower ones
错误的写法
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 ?
- 对输入N1进行操作后输出N2
- 同样的输入总是产生同样的输出
- 函数不应该依赖状态
What is A Method ?
- 定义一个类型的行为
- 一个类型值操作的函数
- 应该使用状态
- Logicallly connected
函数与方法之间的关系
- 函数能够接受接口作为输入,也能和接口一起使用
- 方法和一个具体的类型绑定在一起
正确的写法
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 接口,
- 提供上下文
- 提供一个和 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. 有些值是不可比较的
- map、slice ,function 是不可以互相比较的,所以也不能作为 map 的 key 的类型
- struct 当且仅当 它的所有字段可以比较时,才可以比较。
- channel 当且仅当 channel 的类型是相同时,才可以比较。
- interface 值可以比较,当它们的动态类型是一样的,动态值是相等的时,以及两个 interface 都为 nil 时,两个interface 值 相等
- 引用类型(map,slice,channel,function,pointer)和 interface 都可以和 nil 做比较
9. 注意 make 和 new 的区别
- make 只能分配:slice、map、channel 类型的对象的内存并初始化
- new 返回的值是一个指向该类型零值的指针
type Foo map[string]string
x := make(Foo)
x["ping"] = "pong" // ok
y := new(Foo)
(*y)["ping"] = "pong" // panic!