go.talks: Best Practices in Go
R=adg, campoy
CC=golang-dev
https://golang.org/cl/11407043
diff --git a/2013/bestpractices.slide b/2013/bestpractices.slide
new file mode 100644
index 0000000..3ee24bc
--- /dev/null
+++ b/2013/bestpractices.slide
@@ -0,0 +1,270 @@
+Twelve Go Best Practices
+
+Francesc Campoy Flores
+Gopher at Google
+@campoy83
+http://campoy.cat/+
+
+http://golang.org
+
+* Best practices
+
+From Wikipedia:
+
+ "A best practice is a method or technique that has consistently shown results superior
+ to those achieved with other means"
+
+Techniques to write Go code that is
+
+- simple,
+- readable,
+- maintainable.
+
+.image http://golang.org/doc/gopher/gopherbw.png 200 200
+
+* Some code
+
+.code bestpractices/shortercode1.go /GOPHER/,/DUMP/
+
+.code bestpractices/shortercode1.go /DUMP/,/MAIN/
+
+* Avoid nesting by handling errors first
+
+.code bestpractices/shortercode2.go /DUMP/,/MAIN/
+
+Less nesting means less cognitive load on the reader
+
+* Avoid repetition when possible
+
+Deploy one-off utility types for simpler code
+
+.code bestpractices/shortercode3.go /BINWRITER/,/DUMP/
+
+.code bestpractices/shortercode3.go /DUMP/,/MAIN/
+
+* Type switch to handle special cases
+
+.code bestpractices/shortercode4.go /WRITE/,/DUMP/
+
+.code bestpractices/shortercode4.go /DUMP/,/MAIN/
+
+* Type switch with short variable declaration
+
+.code bestpractices/shortercode5.go /WRITE/,/DUMP/
+
+* Function adapters
+
+.code bestpractices/httphandler.go /HANDLER1/,/HANDLER2/
+
+* Function adapters
+
+.code bestpractices/httphandler.go /HANDLER2/,/END/
+
+* Organizing your code
+
+* Important code goes first
+
+License information, build tags, package documentation.
+
+Import statements, related groups separated by blank lines.
+
+ import (
+ "fmt"
+ "io"
+ "log"
+
+ "code.google.com/p/go.net/websocket"
+ )
+
+The rest of the code starting with the most significant types, and ending
+with helper function and types.
+
+* Document your code
+
+Package name, with the associated documentation before.
+
+ // Package playground registers an HTTP handler at "/compile" that
+ // proxies requests to the golang.org playground service.
+ package playground
+
+Exported identifiers appear in `godoc`, they should be documented correctly.
+
+ // Author represents the person who wrote and/or is presenting the document.
+ type Author struct {
+ Elem []Elem
+ }
+
+ // TextElem returns the first text elements of the author details.
+ // This is used to display the author' name, job title, and company
+ // without the contact details.
+ func (p *Author) TextElem() (elems []Elem) {
+
+[[http://godoc.org/code.google.com/p/go.talks/pkg/present#Author][Generated documentation]]
+
+[[http://blog.golang.org/godoc-documenting-go-code][Gocode: documenting Go code]]
+
+* Shorter is better
+
+or at least _longer_is_not_always_better_.
+
+Try to find the *shortest*name*that*is*self*explanatory*.
+
+- Prefer `MarshalIndent` to `MarshalWithIndentation`.
+
+Don't forget that the package name will appear before the identifier you chose.
+
+- In package `encoding/json` we find the type `Encoder`, not `JSONEncoder`.
+
+- It is referred as `json.Encoder`.
+
+* Packages with multiple files
+
+Should you split a package into multiple files?
+
+- Avoid very long files
+
+The `net/http` package from the standard library contains 15734 lines in 47 files.
+
+- Separate code and tests
+
+`net/http/cookie.go` and `net/http/cookie_test.go` are both part of the `http`
+package.
+
+Test code is compiled *only* at test time.
+
+- Separated package documentation
+
+When we have more than one file in a package, it's convention to create a `doc.go`
+containing the package documentation.
+
+* Make your packages "go get"-able
+
+Some packages are potentially reusable, some others are not.
+
+A package defining some network protocol might be reused while one defining
+an executable command may not.
+
+.image bestpractices/cmd.png
+
+[[https://github.com/bradfitz/camlistore]]
+
+* APIs
+
+* Ask for what you need
+
+Let's use the Gopher type from before
+
+.code bestpractices/shortercode1.go /GOPHER/,/DUMP/
+
+We could define this method
+
+.code bestpractices/shortercode1.go /DumpToFile/,/DumpToFile/
+
+But using a concrete type makes this code difficult to test, so we use an interface.
+
+.code bestpractices/shortercode1.go /DumpToReadWriter/,/DumpToReadWriter/
+
+And, since we're using an interface, we should ask only for the methods we need.
+
+.code bestpractices/shortercode1.go /DumpToWriter/,/DumpToWriter/
+
+* Keep independent packages independent
+
+.code bestpractices/funcdraw/cmd/funcdraw.go /IMPORT/,/ENDIMPORT/
+
+.code bestpractices/funcdraw/cmd/funcdraw.go /START/,/END/
+
+* Parsing
+
+.code bestpractices/funcdraw/parser/parser.go /START/,/END/
+
+* Drawing
+
+.code bestpractices/funcdraw/drawer/dependent.go /START/,/END/
+
+Avoid dependency by using an interface.
+
+.code bestpractices/funcdraw/drawer/drawer.go /START/,/END/
+
+* Testing
+
+Using an interface instead of a concrete type makes testing easier.
+
+.code bestpractices/funcdraw/drawer/drawer_test.go ,/END/
+
+* Avoid concurrency in your API
+
+.play bestpractices/concurrency1.go /START/,/END/
+
+What if we want to use it sequentially?
+
+* Avoid concurrency in your API
+
+.play bestpractices/concurrency2.go /START/,/END/
+
+Expose synchronous APIs, calling them concurrently is easy.
+
+* Best practices for concurrency
+
+* Use goroutines to manage state
+
+Use a chan or a struct with a chan to communicate with a goroutine
+
+.code bestpractices/server.go /START/,/STOP/
+
+* Use goroutines to manage state (continued)
+
+.play bestpractices/server.go /STOP/,
+
+* Avoid goroutine leaks with buffered chans
+
+.code bestpractices/bufchan.go /SEND/,/BROADCAST/
+
+.code bestpractices/bufchan.go /MAIN/,
+
+* Avoid goroutine leaks with buffered chans (continued)
+
+.play bestpractices/bufchan.go /BROADCAST/,/MAIN/
+
+- the goroutine is blocked on the chan write
+- the goroutine holds a reference to the chan
+- the chan will never be garbage collected
+
+* Avoid goroutines leaks with buffered chans (continued)
+
+.play bestpractices/bufchanfix.go /BROADCAST/,/MAIN/
+
+- what if we can't predict the capacity of the channel?
+
+* Avoid goroutines leaks with quit chan
+
+.play bestpractices/quitchan.go /BROADCAST/,/MAIN/
+
+* Twelve best practices
+
+1. Avoid nesting by handling errors first
+2. Avoid repetition when possible
+3. Important code goes first
+4. Document your code
+5. Shorter is better
+6. Packages with multiple files
+7. Make your packages "go get"-able
+8. Ask for what you need
+9. Keep independent packages independent
+10. Avoid concurrency in your API
+11. Use goroutines to manage state
+12. Avoid goroutine leaks
+
+* Some links
+
+Resources
+
+- Go homepage [[http://golang.org]]
+- Go interactive tour [[http://tour.golang.org]]
+
+Other talks
+
+- Lexical scanning with Go [[http://www.youtube.com/watch?v=HxaD_trXwRE][video]]
+- Concurrency is not parallelism [[http://vimeo.com/49718712][video]]
+- Go concurrency patterns [[http://www.youtube.com/watch?v=f6kdp27TYZs][video]]
+- Advanced Go concurrency patterns [[http://www.youtube.com/watch?v=QDDwwePbDtw][video]]
diff --git a/2013/bestpractices/bufchan.go b/2013/bestpractices/bufchan.go
new file mode 100644
index 0000000..7f51777
--- /dev/null
+++ b/2013/bestpractices/bufchan.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "fmt"
+ "net"
+ "time"
+)
+
+// SEND OMIT
+func sendMsg(msg, addr string) error {
+ conn, err := net.Dial("tcp", addr)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ _, err = fmt.Fprint(conn, msg)
+ return err
+}
+
+// BROADCAST OMIT
+func broadcastMsg(msg string, addrs []string) error {
+ errc := make(chan error)
+ for _, addr := range addrs {
+ go func(addr string) {
+ errc <- sendMsg(msg, addr)
+ fmt.Println("done")
+ }(addr)
+ }
+
+ for _ = range addrs {
+ if err := <-errc; err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// MAIN OMIT
+func main() {
+ addr := []string{"localhost:8080", "http://google.com"}
+ err := broadcastMsg("hi", addr) // HL
+
+ time.Sleep(time.Second)
+
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println("everything went fine")
+}
diff --git a/2013/bestpractices/bufchanfix.go b/2013/bestpractices/bufchanfix.go
new file mode 100644
index 0000000..560abbd
--- /dev/null
+++ b/2013/bestpractices/bufchanfix.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "fmt"
+ "net"
+ "time"
+)
+
+// SEND OMIT
+func sendMsg(msg, addr string) error {
+ conn, err := net.Dial("tcp", addr)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ _, err = fmt.Fprint(conn, msg)
+ return err
+}
+
+// BROADCAST OMIT
+func broadcastMsg(msg string, addrs []string) error {
+ errc := make(chan error, len(addrs)) // HL
+ for _, addr := range addrs {
+ go func(addr string) {
+ errc <- sendMsg(msg, addr)
+ fmt.Println("done")
+ }(addr)
+ }
+
+ for _ = range addrs {
+ if err := <-errc; err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// MAIN OMIT
+func main() {
+ addr := []string{"localhost:8080", "http://google.com"}
+ err := broadcastMsg("hi", addr)
+
+ time.Sleep(time.Second)
+
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println("everything went fine")
+}
diff --git a/2013/bestpractices/cmd.png b/2013/bestpractices/cmd.png
new file mode 100644
index 0000000..17e70a1
--- /dev/null
+++ b/2013/bestpractices/cmd.png
Binary files differ
diff --git a/2013/bestpractices/concurrency1.go b/2013/bestpractices/concurrency1.go
new file mode 100644
index 0000000..2b8c333
--- /dev/null
+++ b/2013/bestpractices/concurrency1.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+// START OMIT
+func doConcurrently(job string, err chan error) {
+ go func() {
+ fmt.Println("doing job", job)
+ time.Sleep(1 * time.Second)
+ err <- errors.New("something went wrong!")
+ }()
+}
+
+func main() {
+ jobs := []string{"one", "two", "three"}
+
+ errc := make(chan error)
+ for _, job := range jobs {
+ doConcurrently(job, errc)
+ }
+ for _ = range jobs {
+ if err := <-errc; err != nil {
+ fmt.Println(err)
+ }
+ }
+}
+
+// END OMIT
diff --git a/2013/bestpractices/concurrency2.go b/2013/bestpractices/concurrency2.go
new file mode 100644
index 0000000..0c4f2f8
--- /dev/null
+++ b/2013/bestpractices/concurrency2.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+// START OMIT
+func do(job string) error {
+ fmt.Println("doing job", job)
+ time.Sleep(1 * time.Second)
+ return errors.New("something went wrong!")
+}
+
+func main() {
+ jobs := []string{"one", "two", "three"}
+
+ errc := make(chan error)
+ for _, job := range jobs {
+ go func(job string) {
+ errc <- do(job)
+ }(job)
+ }
+ for _ = range jobs {
+ if err := <-errc; err != nil {
+ fmt.Println(err)
+ }
+ }
+}
+
+// END OMIT
diff --git a/2013/bestpractices/funcdraw/cmd/funcdraw.go b/2013/bestpractices/funcdraw/cmd/funcdraw.go
new file mode 100644
index 0000000..92086af
--- /dev/null
+++ b/2013/bestpractices/funcdraw/cmd/funcdraw.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "flag"
+ "image/png"
+ "log"
+ "os"
+)
+
+// IMPORT OMIT
+import (
+ "code.google.com/p/go.talks/2013/bestpractices/funcdraw/drawer"
+ "code.google.com/p/go.talks/2013/bestpractices/funcdraw/parser"
+)
+
+// ENDIMPORT OMIT
+
+var (
+ width = flag.Int("width", 300, "image width")
+ height = flag.Int("height", 300, "image height")
+ xmin = flag.Float64("xmin", -10, "min value for x")
+ xmax = flag.Float64("xmax", 10, "max value for x")
+)
+
+func main() {
+ flag.Parse()
+ if flag.NArg() != 1 {
+ log.Fatal("missing expression to parse")
+ }
+
+ text := flag.Arg(0)
+ // START OMIT
+ // Parse the text into an executable function.
+ f, err := parser.Parse(text)
+ if err != nil {
+ log.Fatalf("parse %q: %v", text, err)
+ }
+
+ // Create an image plotting the function.
+ m := drawer.Draw(f, *width, *height, *xmin, *xmax)
+
+ // Encode the image into the standard output.
+ err = png.Encode(os.Stdout, m)
+ if err != nil {
+ log.Fatalf("encode image: %v", err)
+ }
+ // END OMIT
+}
diff --git a/2013/bestpractices/funcdraw/drawer/dependent.go b/2013/bestpractices/funcdraw/drawer/dependent.go
new file mode 100644
index 0000000..239c3b7
--- /dev/null
+++ b/2013/bestpractices/funcdraw/drawer/dependent.go
@@ -0,0 +1,14 @@
+package drawer
+
+// START OMIT
+import (
+ "image"
+
+ "code.google.com/p/go.talks/2013/bestpractices/funcdraw/parser"
+)
+
+// Draw draws an image showing a rendering of the passed ParsedFunc.
+func DrawParsedFunc(f parser.ParsedFunc) image.Image {
+ // END OMIT
+ return nil
+}
diff --git a/2013/bestpractices/funcdraw/drawer/drawer.go b/2013/bestpractices/funcdraw/drawer/drawer.go
new file mode 100644
index 0000000..9808615
--- /dev/null
+++ b/2013/bestpractices/funcdraw/drawer/drawer.go
@@ -0,0 +1,15 @@
+package drawer
+
+// START OMIT
+import "image"
+
+// Function represent a drawable mathematical function.
+type Function interface {
+ Eval(float64) float64
+}
+
+// Draw draws an image showing a rendering of the passed Function.
+func Draw(f Function) image.Image {
+ // END OMIT
+ return nil
+}
diff --git a/2013/bestpractices/funcdraw/drawer/drawer_test.go b/2013/bestpractices/funcdraw/drawer/drawer_test.go
new file mode 100644
index 0000000..ca16512
--- /dev/null
+++ b/2013/bestpractices/funcdraw/drawer/drawer_test.go
@@ -0,0 +1,22 @@
+package drawer
+
+import (
+ "math"
+ "testing"
+)
+
+type TestFunc func(float64) float64
+
+func (f TestFunc) Eval(x float64) float64 { return f(x) }
+
+var (
+ ident = TestFunc(func(x float64) float64 { return x })
+ sin = TestFunc(math.Sin)
+)
+
+func TestDraw_Ident(t *testing.T) {
+ m := Draw(ident)
+ // Verify obtained image.
+ // END OMIT
+ t.Error(m.ColorModel())
+}
diff --git a/2013/bestpractices/funcdraw/parser/parser.go b/2013/bestpractices/funcdraw/parser/parser.go
new file mode 100644
index 0000000..71c235e
--- /dev/null
+++ b/2013/bestpractices/funcdraw/parser/parser.go
@@ -0,0 +1,23 @@
+package parser
+
+// START OMIT
+type ParsedFunc struct {
+ text string
+ eval func(float64) float64
+}
+
+func Parse(text string) (*ParsedFunc, error) {
+ f, err := parse(text)
+ if err != nil {
+ return nil, err
+ }
+ return &ParsedFunc{text: text, eval: f}, nil
+}
+
+func (f *ParsedFunc) Eval(x float64) float64 { return f.eval(x) }
+func (f *ParsedFunc) String() string { return f.text }
+
+// END OMIT
+func parse(text string) (func(float64) float64, error) {
+ return func(x float64) float64 { return x }, nil
+}
diff --git a/2013/bestpractices/httphandler.go b/2013/bestpractices/httphandler.go
new file mode 100644
index 0000000..b9e6c39
--- /dev/null
+++ b/2013/bestpractices/httphandler.go
@@ -0,0 +1,59 @@
+package bestpractices
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func doThis() error { return nil }
+func doThat() error { return nil }
+
+// HANDLER1 OMIT
+func init() {
+ http.HandleFunc("/", handler)
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+ err := doThis()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ log.Printf("handling %q: %v", r.RequestURI, err)
+ return
+ }
+
+ err = doThat()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ log.Printf("handling %q: %v", r.RequestURI, err)
+ return
+ }
+}
+
+// HANDLER2 OMIT
+func init() {
+ http.HandleFunc("/", errorHandler(betterHandler))
+}
+
+func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ err := f(w, r)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ log.Printf("handling %q: %v", r.RequestURI, err)
+ }
+ }
+}
+
+func betterHandler(w http.ResponseWriter, r *http.Request) error {
+ if err := doThis(); err != nil {
+ return fmt.Errorf("doing this: %v", err)
+ }
+
+ if err := doThat(); err != nil {
+ return fmt.Errorf("doing that: %v", err)
+ }
+ return nil
+}
+
+// END OMIT
diff --git a/2013/bestpractices/quitchan.go b/2013/bestpractices/quitchan.go
new file mode 100644
index 0000000..3635b28
--- /dev/null
+++ b/2013/bestpractices/quitchan.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "fmt"
+ "net"
+ "time"
+)
+
+// SEND OMIT
+func sendMsg(msg, addr string) error {
+ conn, err := net.Dial("tcp", addr)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ _, err = fmt.Fprint(conn, msg)
+ return err
+}
+
+// BROADCAST OMIT
+func broadcastMsg(msg string, addrs []string) error {
+ errc := make(chan error)
+ quit := make(chan struct{})
+
+ defer close(quit)
+
+ for _, addr := range addrs {
+ go func(addr string) {
+ select {
+ case errc <- sendMsg(msg, addr):
+ fmt.Println("done")
+ case <-quit:
+ fmt.Println("quit")
+ }
+ }(addr)
+ }
+
+ for _ = range addrs {
+ if err := <-errc; err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// MAIN OMIT
+func main() {
+ addr := []string{"localhost:8080", "http://google.com"}
+ err := broadcastMsg("hi", addr) // HL
+
+ time.Sleep(time.Second)
+
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println("everything went fine")
+}
diff --git a/2013/bestpractices/server.go b/2013/bestpractices/server.go
new file mode 100644
index 0000000..b0f2612
--- /dev/null
+++ b/2013/bestpractices/server.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+// START OMIT
+type Server struct{ quit chan bool }
+
+func NewServer() *Server {
+ s := &Server{make(chan bool)}
+ go s.run()
+ return s
+}
+
+func (s *Server) run() {
+ for {
+ select {
+ case <-s.quit:
+ fmt.Println("finishing task")
+ time.Sleep(time.Second)
+ fmt.Println("task done")
+ s.quit <- true
+ return
+ case <-time.After(time.Second):
+ fmt.Println("running task")
+ }
+ }
+}
+
+// STOP OMIT
+func (s *Server) Stop() {
+ fmt.Println("server stopping")
+ s.quit <- true
+ <-s.quit
+ fmt.Println("server stopped")
+}
+
+func main() {
+ s := NewServer()
+ time.Sleep(2 * time.Second)
+ s.Stop()
+}
diff --git a/2013/bestpractices/shortercode1.go b/2013/bestpractices/shortercode1.go
new file mode 100644
index 0000000..ed0e34e
--- /dev/null
+++ b/2013/bestpractices/shortercode1.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "encoding/binary"
+ "image/color"
+ "io"
+ "log"
+ "os"
+)
+
+// GOPHER OMIT
+type Gopher struct {
+ Name string
+ Age int32
+ FurColor color.Color
+}
+
+// DUMP OMIT
+func (g *Gopher) DumpBinary(w io.Writer) error {
+ err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
+ if err == nil {
+ _, err := w.Write([]byte(g.Name))
+ if err == nil {
+ err := binary.Write(w, binary.LittleEndian, g.Age)
+ if err == nil {
+ return binary.Write(w, binary.LittleEndian, g.FurColor)
+ }
+ return err
+ }
+ return err
+ }
+ return err
+}
+
+// MAIN OMIT
+func main() {
+ w := os.Stdout
+ g := &Gopher{
+ Name: "Gophertiti",
+ Age: 3383,
+ FurColor: color.RGBA{B: 255},
+ }
+
+ if err := g.DumpBinary(w); err != nil {
+ log.Fatal("DumpBinary: %v", err)
+ }
+}
+
+func (g *Gopher) DumpToFile(f *os.File) error {
+ return nil
+}
+
+func (g *Gopher) DumpToReadWriter(rw io.ReadWriter) error {
+ return nil
+}
+
+func (g *Gopher) DumpToWriter(f io.Writer) error {
+ return nil
+}
diff --git a/2013/bestpractices/shortercode2.go b/2013/bestpractices/shortercode2.go
new file mode 100644
index 0000000..3a28e18
--- /dev/null
+++ b/2013/bestpractices/shortercode2.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+ "encoding/binary"
+ "image/color"
+ "io"
+ "log"
+ "os"
+)
+
+// GOPHER OMIT
+type Gopher struct {
+ Name string
+ Age int32
+ FurColor color.Color
+}
+
+// DUMP OMIT
+func (g *Gopher) DumpBinary(w io.Writer) error {
+ err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
+ if err != nil {
+ return err
+ }
+ _, err = w.Write([]byte(g.Name))
+ if err != nil {
+ return err
+ }
+ err = binary.Write(w, binary.LittleEndian, g.Age)
+ if err != nil {
+ return err
+ }
+ return binary.Write(w, binary.LittleEndian, g.FurColor)
+}
+
+// MAIN OMIT
+func main() {
+ w := os.Stdout
+ g := &Gopher{
+ Name: "Gophertiti",
+ Age: 3383,
+ FurColor: color.RGBA{B: 255},
+ }
+
+ if err := g.DumpBinary(w); err != nil {
+ log.Fatal("DumpBinary: %v", err)
+ }
+}
diff --git a/2013/bestpractices/shortercode3.go b/2013/bestpractices/shortercode3.go
new file mode 100644
index 0000000..0bd896f
--- /dev/null
+++ b/2013/bestpractices/shortercode3.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "encoding/binary"
+ "image/color"
+ "io"
+ "log"
+ "os"
+)
+
+// GOPHER OMIT
+type Gopher struct {
+ Name string
+ Age int32
+ FurColor color.Color
+}
+
+// BINWRITER OMIT
+type binWriter struct {
+ w io.Writer
+ err error
+}
+
+// WRITE OMIT
+// Write writes a value into its writer using little endian.
+func (w *binWriter) Write(v interface{}) {
+ if w.err != nil {
+ return
+ }
+ w.err = binary.Write(w.w, binary.LittleEndian, v)
+}
+
+// DUMP OMIT
+func (g *Gopher) DumpBinary(w io.Writer) error {
+ bw := &binWriter{w: w}
+ bw.Write(int32(len(g.Name)))
+ bw.Write([]byte(g.Name))
+ bw.Write(g.Age)
+ bw.Write(g.FurColor)
+ return bw.err
+}
+
+// MAIN OMIT
+func main() {
+ w := os.Stdout
+ g := &Gopher{
+ Name: "Gophertiti",
+ Age: 3383,
+ FurColor: color.RGBA{B: 255},
+ }
+
+ if err := g.DumpBinary(w); err != nil {
+ log.Fatal("DumpBinary: %v", err)
+ }
+}
diff --git a/2013/bestpractices/shortercode4.go b/2013/bestpractices/shortercode4.go
new file mode 100644
index 0000000..43aa6bc
--- /dev/null
+++ b/2013/bestpractices/shortercode4.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "encoding/binary"
+ "image/color"
+ "io"
+ "log"
+ "os"
+)
+
+// GOPHER OMIT
+type Gopher struct {
+ Name string
+ Age int32
+ FurColor color.Color
+}
+
+type binWriter struct {
+ w io.Writer
+ err error
+}
+
+// WRITE OMIT
+// Write writes a value into its writer using little endian.
+func (w *binWriter) Write(v interface{}) {
+ if w.err != nil {
+ return
+ }
+ switch v.(type) {
+ case string:
+ s := v.(string)
+ w.Write(int32(len(s)))
+ w.Write([]byte(s))
+ default:
+ w.err = binary.Write(w.w, binary.LittleEndian, v)
+ }
+}
+
+// DUMP OMIT
+func (g *Gopher) DumpBinary(w io.Writer) error {
+ bw := &binWriter{w: w}
+ bw.Write(g.Name)
+ bw.Write(g.Age)
+ bw.Write(g.FurColor)
+ return bw.err
+}
+
+// MAIN OMIT
+func main() {
+ w := os.Stdout
+ g := &Gopher{
+ Name: "Gophertiti",
+ Age: 3383,
+ FurColor: color.RGBA{B: 255},
+ }
+
+ if err := g.DumpBinary(w); err != nil {
+ log.Fatal("DumpBinary: %v", err)
+ }
+}
diff --git a/2013/bestpractices/shortercode5.go b/2013/bestpractices/shortercode5.go
new file mode 100644
index 0000000..e8e67c8
--- /dev/null
+++ b/2013/bestpractices/shortercode5.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "encoding/binary"
+ "image/color"
+ "io"
+ "log"
+ "os"
+)
+
+// GOPHER OMIT
+type Gopher struct {
+ Name string
+ Age int32
+ FurColor color.Color
+}
+
+type binWriter struct {
+ w io.Writer
+ err error
+}
+
+// WRITE OMIT
+// Write write the given value into the writer using little endian.
+func (w *binWriter) Write(v interface{}) {
+ if w.err != nil {
+ return
+ }
+ switch v := v.(type) {
+ case string:
+ w.Write(int32(len(v)))
+ w.Write([]byte(v))
+ default:
+ w.err = binary.Write(w.w, binary.LittleEndian, v)
+ }
+}
+
+// DUMP OMIT
+func (g *Gopher) DumpBinary(w io.Writer) error {
+ bw := &binWriter{w: w}
+ bw.Write(g.Name)
+ bw.Write(g.Age)
+ bw.Write(g.FurColor)
+ return bw.err
+}
+
+// MAIN OMIT
+func main() {
+ w := os.Stdout
+ g := &Gopher{
+ Name: "Gophertiti",
+ Age: 3383,
+ FurColor: color.RGBA{B: 255},
+ }
+
+ if err := g.DumpBinary(w); err != nil {
+ log.Fatal("DumpBinary: %v", err)
+ }
+}