7 common mistakes in go and when to avoid them

72
7 Common Mistakes In Go and when to avoid them

Upload: steven-francia

Post on 02-Jul-2015

16.782 views

Category:

Software


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

Page 2: 7 Common mistakes in Go and when to avoid them

@Spf13

Author of Hugo, Cobra, Afero, Viper & more

Page 3: 7 Common mistakes in Go and when to avoid them

–Mel Brooks

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

mistakes.”

Page 4: 7 Common mistakes in Go and when to avoid them

–Al Franken

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

the  hard  way.  “

Page 9: 7 Common mistakes in Go and when to avoid them

Interfaces•One of Go’s most powerful

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

behavior

Page 10: 7 Common mistakes in Go and when to avoid them
Page 12: 7 Common mistakes in Go and when to avoid them

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

Page 14: 7 Common mistakes in Go and when to avoid them

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

Page 15: 7 Common mistakes in Go and when to avoid them

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

Page 16: 7 Common mistakes in Go and when to avoid them

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

Page 17: 7 Common mistakes in Go and when to avoid them

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

Page 22: 7 Common mistakes in Go and when to avoid them

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)

Page 23: 7 Common mistakes in Go and when to avoid them

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

if len(name) == 0 {

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

}

Standard Error

Page 24: 7 Common mistakes in Go and when to avoid them

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

if len(name) == 0 {

return nil, ErrNoName

}

Exported Error Var

Page 25: 7 Common mistakes in Go and when to avoid them

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

Page 26: 7 Common mistakes in Go and when to avoid them

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

Page 27: 7 Common mistakes in Go and when to avoid them

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

Page 28: 7 Common mistakes in Go and when to avoid them

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

Page 29: 7 Common mistakes in Go and when to avoid them

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

Page 30: 7 Common mistakes in Go and when to avoid them

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

Page 31: 7 Common mistakes in Go and when to avoid them

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

Custom Errors : Os

Page 32: 7 Common mistakes in Go and when to avoid them

if serr != nil {

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

return nil

}

return serr

Custom Errors : Os

Page 33: 7 Common mistakes in Go and when to avoid them

if serr != nil {

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

serr.Err == syscall.ENOTDIR { return nil

}

return serr

Custom Errors : Os

Page 35: 7 Common mistakes in Go and when to avoid them

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

Page 36: 7 Common mistakes in Go and when to avoid them
Page 42: 7 Common mistakes in Go and when to avoid them

Too Many Methods

•A lot of people from OO backgrounds overuse methods

•Natural draw to define everything via structs and methods

Page 43: 7 Common mistakes in Go and when to avoid them

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

Page 44: 7 Common mistakes in Go and when to avoid them

What Is A Method?

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

against a value •Should use state •Logically connected

Page 45: 7 Common mistakes in Go and when to avoid them

Functions Can Be Used With Interfaces

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

•Functions can accept interfaces as input

Page 46: 7 Common mistakes in Go and when to avoid them
Page 47: 7 Common mistakes in Go and when to avoid them

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

Page 48: 7 Common mistakes in Go and when to avoid them

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

Page 50: 7 Common mistakes in Go and when to avoid them

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)

Page 51: 7 Common mistakes in Go and when to avoid them

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

Page 52: 7 Common mistakes in Go and when to avoid them

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

Page 53: 7 Common mistakes in Go and when to avoid them

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

Page 56: 7 Common mistakes in Go and when to avoid them

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

Page 57: 7 Common mistakes in Go and when to avoid them

type Reader interface {

Read(p []byte) (n int, err error)

}

type Writer interface {

Write(p []byte) (n int, err error)

}

Io.Reader & Io.Writer

Page 58: 7 Common mistakes in Go and when to avoid them
Page 59: 7 Common mistakes in Go and when to avoid them

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

Page 60: 7 Common mistakes in Go and when to avoid them

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

Page 61: 7 Common mistakes in Go and when to avoid them

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

Page 62: 7 Common mistakes in Go and when to avoid them

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

Page 64: 7 Common mistakes in Go and when to avoid them

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

Page 65: 7 Common mistakes in Go and when to avoid 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

Page 66: 7 Common mistakes in Go and when to avoid them
Page 67: 7 Common mistakes in Go and when to avoid them

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

Page 68: 7 Common mistakes in Go and when to avoid them

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

Page 69: 7 Common mistakes in Go and when to avoid them

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

Page 70: 7 Common mistakes in Go and when to avoid them

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

Page 72: 7 Common mistakes in Go and when to avoid them

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