7 Common Mistakes in Go (2015)
-
Upload
steven-francia -
Category
Software
-
view
3.394 -
download
2
Transcript of 7 Common Mistakes in Go (2015)
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
7and when to avoid them
Common Mistakes
In Go
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
@Spf13Docker Chief Operator
&
Author of Hugo, Cobra, Afero, Viper
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
“Do you want to know the difference between a
master and a beginner?
The master has failed more times than the beginner has tried.”
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
–Ed Catmull
“We need to think about failure differently.
Most people think mistakes are a necessary evil. Mistakes aren't a necessary evil, they aren't evil at
all. They are an inevitable consequence of doing something new and as such should be seen
as valuable. “
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
Not Accepting Interfaces
1
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
State & Behavior•Types can express state &
behavior •State = data structure •Behavior = methods
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Interfaces•One of Go’s most powerful
features •Permits extensibility •Defined by methods •Adherence is only satisfied by
behavior
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
•Fastest static site generator
•Native Go •35+ themes •Flexible •100s of contributors •Powers GopherAcademy
gohugo.io
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
Not Using Io.Reader &
Io.Writer
2
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Cobra Cli Commander•Fast and flexible •Powers
Kubernetes & Hugo •Provides subcommands, help,
man pages, bash autocompletegithub.com/spf13/cobra
// SetOutput sets the destination for usage and error messages.
// If output is nil, os.Stderr is used.
func (c *Command) SetOutput(o io.Writer) {
c.output = o
}
Cobra Apps Enabled
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Viper•Configuration management •Supports json, yaml, toml,
defaults, flags, env vars & remote key value
•Supports nesting, cascading & aliases
github.com/spf13/viper
func (v *Viper) ReadBufConfig(buf *bytes.Buffer) error {
v.config = make(map[string]interface{})
v.marshalReader(buf, v.config)
return nil
}
Really Stop Doing This!!
func (v *Viper) ReadConfig(in io.Reader) error {
v.config = make(map[string]interface{})
v.marshalReader(in, v.config)
return nil
}
Instead
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
Requiring Broad Interfaces
3
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Afero Fs•File system abstraction •Uses standard OS interfaces •Drop in replacement for OS •Great for testing & mocking •Cross platform memory
backed filesystemgithub.com/spf13/afero
type File interface { io.Closer io.Reader io.ReaderAt io.Seeker io.Writer io.WriterAt }
Composing Interfaces
func ReadIn(f File) { b := []byte{} n, err := f.Read(b) ... }
Requiring Broad Interfaces
func ReadIn(r Reader) { b := []byte{} n, err := r.Read(b) ... }
Requiring Narrow Interfaces
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
Methods Vs Functions
4
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Too Many Methods•A lot of people from OO
backgrounds overuse methods
•Natural draw to define everything via structs and methods
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
What Is A Method?•Defines the behavior of a type •A function that operates
against a value •Should use state •Logically connected
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
Pointers Vs Values
5
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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)
func (page *Page) saveSource(b io.Reader)
func (page Page) saveSource(b io.Reader)
Pointer Vs Value Receivers
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
type Time struct {
sec int64
nsec uintptr
loc *Location
}
func (t Time) IsZero() bool {
return t.sec == 0 && t.nsec == 0
}
Time
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
Thinking Of Errors As Strings
6
type error interface {
Error() string
}
Error Is An Interface
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Standard Errors•errors.New(“error here”) is
usually sufficient •Exported Error Variables
can be easily checked
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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)
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
•Container runtime & image format
•Native Go •22k stars •1000+ contributors
docker.com
type Error struct {
Code ErrorCode
Message string
Detail interface{}
}
// Error returns a human readable representation of the error.
func (e Error) Error() string {
return fmt.Sprintf("%s: %s",
strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), e.Message)
}
Internationalization Of Errors
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Go StdLib•Standard Go
Libraries •Comprehensive and
powerful •Great examples of “good” Go
http://golang.org/pkg/
// 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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Mistake .
To Be Safe Or Not To Be
7
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
You Can’t Make Everyone Happy
You Aren’t A Jar Of Nutella
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
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
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Making It Safe•Sync package provides behavior
to make a value safe (Atomic/Mutex)
•Channels coordinate 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
}
Safe Maps With Mutex
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Keeping It Unsafe•Safety comes at a cost •Imposes behaviors on consumer •Proper API allows consumers to
add safety as needed •Consumers can use channels or
mutexes
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
}
Safe Maps With Mutex
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Maps Are Unsafe By Design
•Often safety is unnecessary •Enables consumers to
implement safety as needed •Enables consumers to
implement safety as desired
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
Biggsest Mistake;
Not Makimg Mistakes
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
–Ed Catmull
Failure is a manifestation of learning and exploration.
If you aren't experiencing failure than you are making a far worse
mistake.
You are being driven by the desire to avoid it.
fe
h
W U
w k
M
w
P n
I
v
ZA
S
U
G
k
Cw
PU
@Spf13Docker Chief of Operations
&
Author of Hugo, Cobra, Afero, Viper