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.
22. 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)
26. 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()
}
27. 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()
}
28. 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
}
29. 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
}
30. 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
}
31. Custom Errors : Os
func baa(f *file) error {
…
n, err := f.WriteAt(x, 3)
if _, ok := err.(*PathError) {
…
} else {
log.Fatalf(err)
}
}
32. Custom Errors : Os
…
if serr != nil {
if serr, ok := serr.(*PathError); ok &&
serr.Err == syscall.ENOTDIR {
return nil
}
return serr
…
33. Custom Errors : Os
…
if serr != nil {
if serr, ok := serr.(*PathError); ok &&
serr.Err == syscall.ENOTDIR {
return nil
}
return serr
…
35. 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
42. Too Many Methods
•A lot of people from OO
backgrounds overuse
methods
•Natural draw to define
everything via structs and
methods
43. 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
44. What Is A Method?
•Defines the behavior of a type
•A function that operates
against a value
•Should use state
•Logically connected
45. Functions Can Be Used
With Interfaces
•Methods, by definition, are
bound to a specific type
•Functions can accept
interfaces as input
46.
47. 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)
}
}
...
}
48. 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)
}
}
...
}
50. 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)
51. 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
52. 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
53. Afero File
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
}
54. type Time struct {
sec int64
nsec uintptr
loc *Location
}
func (t Time) IsZero() bool {
return t.sec == 0 && t.nsec == 0
}
Time
56. 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
64. 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
65. 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