7 Common mistakes in Go and when to avoid them

Post on 02-Jul-2015

16.782 views 5 download

description

I've spent the past two years developing some of the most popular libraries and applications written in Go. I've also made a lot of mistakes along the way. Recognizing that "The only real mistake is the one from which we learn nothing. -John Powell", I would like to share with you the mistakes that I have made over my journey with Go and how you can avoid them.

Transcript of 7 Common mistakes in Go and when to avoid them

@Spf13

Author of Hugo, Cobra, Afero, Viper & more

–Mel Brooks

“As  long  as  the  world  is  turning  and  spinning,  we're  gonna  be  dizzy  and  we're  gonna  make  

mistakes.”

–Al Franken

“Appreciate  your  mistakes  for  what  they  are:  precious  life  lessons  that  can  only  be  learned  

the  hard  way.  “

Interfaces•One of Go’s most powerful

features •Permits extensibility •Defined by methods •Adherance is only satisfied by

behavior

func StripHTML(s string) string { output := ""

// Shortcut strings with no tags in them if !strings.ContainsAny(s, "<>") { output = s } else { s = strings.Replace(s, "\n", " ", -1) s = strings.Replace(s, "</p>", "\n", -1) s = strings.Replace(s, "<br>", "\n", -1) s = strings.Replace(s, "<br />", "\n", -1)

Func Restricted To String

func StripHTML(r Reader) string { buf := new(bytes.Buffer) buf.ReadFrom(r) by := buf.Bytes() if bytes.ContainsAny(s, []byte("<>")) { by = bytes.Replace(by, []byte("\n"), " ", -1) by = bytes.Replace(by, []byte("</p>"), "\n", -1) by = bytes.Replace(by, []byte("<br>"), "\n", -1) by = bytes.Replace(by, []byte("<br />"), "\n", -1)

Any Read() Works

type Source struct { Frontmatter []byte content []byte }

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

func (s *Source) Content() (Reader) { return bytes.NewReader(content) }

Example : Read

type Source struct { Frontmatter []byte content []byte }

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

func (s *Source) Content() (Reader) { return bytes.NewReader(content) }

Example : Read

type Source struct { Frontmatter []byte content []byte }

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

func (s *Source) Content() (Reader) { return bytes.NewReader(content) }

Example : Read

Custom Errors•Can provide context to

guarantee consistent feedback •Provide a type which can be

different from the error value •Can provide dynamic values

(based on internal error state)

func NewPage(name string) (p *Page, err error) {

if len(name) == 0 {

return nil, errors.New("Zero length page name")

}

Standard Error

var ErrNoName = errors.New("Zero length page name")func NewPage(name string) (*Page, error) {

if len(name) == 0 {

return nil, ErrNoName

}

Exported Error Var

var ErrNoName = errors.New("Zero length page name") func Foo(name string) (error) {

err := NewPage("bar")

if err == ErrNoName {

newPage("default")

} else {

log.FatalF(err)

}

}

Exported Error Var

// Portable analogs of some common system call errors. var ErrInvalid = errors.New("invalid argument") var ErrPermission = errors.New("permission denied")

// PathError records an error and // the operation and file path that caused it. type PathError struct { Op string Path string Err error }

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

Custom Errors : Os

// Portable analogs of some common system call errors. var ErrInvalid = errors.New("invalid argument") var ErrPermission = errors.New("permission denied")

// PathError records an error and // the operation and file path that caused it. type PathError struct { Op string Path string Err error }

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

Custom Errors : Os

func (f *File) WriteAt(b []byte, off int64) (n int, err error) { if f == nil { return 0, ErrInvalid } for len(b) > 0 { m, e := f.pwrite(b, off) if e != nil { err = &PathError{"write", f.name, e} break } n += m b = b[m:] off += int64(m) } return }

Custom Errors : Os

func (f *File) WriteAt(b []byte, off int64) (n int, err error) { if f == nil { return 0, ErrInvalid } for len(b) > 0 { m, e := f.pwrite(b, off) if e != nil { err = &PathError{"write", f.name, e} break } n += m b = b[m:] off += int64(m) } return }

Custom Errors : Os

func (f *File) WriteAt(b []byte, off int64) (n int, err error) { if f == nil { return 0, ErrInvalid } for len(b) > 0 { m, e := f.pwrite(b, off) if e != nil { err = &PathError{"write", f.name, e} break } n += m b = b[m:] off += int64(m) } return }

Custom Errors : Os

func baa(f *file) error { … n, err := f.WriteAt(x, 3) if _, ok := err.(*PathError) { … } else { log.Fatalf(err) } }

Custom Errors : Os

if serr != nil {

if serr, ok := serr.(*PathError); ok && serr.Err == syscall.ENOTDIR {

return nil

}

return serr

Custom Errors : Os

if serr != nil {

if serr, ok := serr.(*PathError); ok &&

serr.Err == syscall.ENOTDIR { return nil

}

return serr

Custom Errors : Os

Interfaces Are Composable

•Functions should only accept interfaces that require the methods they need

•Functions should not accept a broad interface when a narrow one would work

•Compose broad interfaces made from narrower ones

Too Many Methods

•A lot of people from OO backgrounds overuse methods

•Natural draw to define everything via structs and methods

What Is A Function?

•Operations performed on N1 inputs that results in N2 outputs

•The same inputs will always result in the same outputs

•Functions should not depend on state

What Is A Method?

•Defines the behavior of a type •A function that operates

against a value •Should use state •Logically connected

Functions Can Be Used With Interfaces

•Methods, by definition, are bound to a specific type

•Functions can accept interfaces as input

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

Example From Hugo

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

Example From Hugo

Pointers Vs Values

•It’s not a question of performance (generally), but one of shared access

•If you want to share the value with a function or method, then use a pointer

•If you don’t want to share it, then use a value (copy)

Pointer Receivers

•If you want to share a value with it’s method, use a pointer receiver

•Since methods commonly manage state, this is the common usage

•Not safe for concurrent access

Value Receivers•If you want the value copied

(not shared), use values •If the type is an empty struct

(stateless, just behavior)… then just use value

•Safe for concurrent access

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 }

Afero File

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)

}

Io.Reader & Io.Writer

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))

}

Stop Doing This!!

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))

}

Stop Doing This!!

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))

}

Stop Doing This!!

https://github.com/spf13/hugo/blob/master/hugolib/page.go#L582

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)

}

Instead

Consider Concurrency

•If you provide a library someone will use it concurrently

•Data structures are not safe for concurrent access

•Values aren’t safe, you need to create safe behavior around them

Making It Safe•Sync package provides behavior

to make a value safe (Atomic/Mutex)

•Channels cordinate values across go routines by permitting one go routine to access at a time

func (m *MMFs) Create(name string) (File, error) {

m.getData()[name] = MemFileCreate(name)

m.registerDirs(m.getData()[name])

return m.getData()[name], nil

}

Maps Are Not Safe

func (m *MMFS) Create(name string) (File, error) {

m.getData()[name] = MemFileCreate(name)

m.registerDirs(m.getData()[name])

return m.getData()[name], nil

}

Maps Are Not Safe

panic: runtime error: invalid memory address or nil pointer dereference

[signal 0xb code=0x1 addr=0x28 pc=0x1691a7]

goroutine 90 [running]:

runtime.panic(0x501ea0, 0x86b104)

/usr/local/Cellar/go/1.3.3/libexec/src/pkg/runtime/panic.c:279 +0xf5

github.com/spf13/afero.(*MemMapFs).registerDirs(0xc208000860, 0x0, 0x0)

/Users/spf13/gopath/src/github.com/spf13/afero/memmap.go:88 +0x27

Maps Are Not Safe

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

}

Maps Can Be Used Safely

@Spf13Author of Hugo, Cobra, Afero, Viper & more