go.talks: bestpractices: adding compelling example for error handling.
R=adg, r, dan.kortschak, nightlyone
CC=golang-dev
https://golang.org/cl/12636043
diff --git a/2013/bestpractices.slide b/2013/bestpractices.slide
index 3ee24bc..dec2ea5 100644
--- a/2013/bestpractices.slide
+++ b/2013/bestpractices.slide
@@ -24,13 +24,13 @@
* Some code
-.code bestpractices/shortercode1.go /GOPHER/,/DUMP/
+.code bestpractices/shortercode1.go /type Gopher/,/^}/
-.code bestpractices/shortercode1.go /DUMP/,/MAIN/
+.code bestpractices/shortercode1.go /WriteTo/,/^}/
* Avoid nesting by handling errors first
-.code bestpractices/shortercode2.go /DUMP/,/MAIN/
+.code bestpractices/shortercode2.go /WriteTo/,/^}/
Less nesting means less cognitive load on the reader
@@ -38,19 +38,37 @@
Deploy one-off utility types for simpler code
-.code bestpractices/shortercode3.go /BINWRITER/,/DUMP/
+.code bestpractices/shortercode3.go /binWriter/,/^}/
-.code bestpractices/shortercode3.go /DUMP/,/MAIN/
+.code bestpractices/shortercode3.go /Write writes/,/^}/
+
+* Avoid repetition when possible
+
+Using `binWriter`
+
+.code bestpractices/shortercode3.go /WriteTo/,/^}/
* Type switch to handle special cases
-.code bestpractices/shortercode4.go /WRITE/,/DUMP/
+.code bestpractices/shortercode4.go /func .* Write/,/^}/
-.code bestpractices/shortercode4.go /DUMP/,/MAIN/
+.code bestpractices/shortercode4.go /WriteTo/,/^}/
* Type switch with short variable declaration
-.code bestpractices/shortercode5.go /WRITE/,/DUMP/
+.code bestpractices/shortercode5.go /func .* Write/,/^}/
+
+* Writing everything or nothing
+
+.code bestpractices/shortercode6.go /binWriter/,/^}/
+
+.code bestpractices/shortercode6.go /Write writes/,/^}/
+
+* Writing everything or nothing
+
+.code bestpractices/shortercode6.go /Flush/,/^}/
+
+.code bestpractices/shortercode6.go /func .* WriteTo/,/^}/
* Function adapters
@@ -154,19 +172,19 @@
Let's use the Gopher type from before
-.code bestpractices/shortercode1.go /GOPHER/,/DUMP/
+.code bestpractices/shortercode1.go /type Gopher/,/^}/
We could define this method
-.code bestpractices/shortercode1.go /DumpToFile/,/DumpToFile/
+.code bestpractices/shortercode1.go /WriteToFile/
But using a concrete type makes this code difficult to test, so we use an interface.
-.code bestpractices/shortercode1.go /DumpToReadWriter/,/DumpToReadWriter/
+.code bestpractices/shortercode1.go /WriteToReadWriter/
And, since we're using an interface, we should ask only for the methods we need.
-.code bestpractices/shortercode1.go /DumpToWriter/,/DumpToWriter/
+.code bestpractices/shortercode1.go /WriteToWriter/
* Keep independent packages independent
diff --git a/2013/bestpractices/shortercode1.go b/2013/bestpractices/shortercode1.go
index 51dffb3..cfcbc74 100644
--- a/2013/bestpractices/shortercode1.go
+++ b/2013/bestpractices/shortercode1.go
@@ -1,61 +1,61 @@
-// +build ignore,OMIT
+// +build OMIT
package main
import (
"encoding/binary"
- "image/color"
"io"
"log"
"os"
)
-// GOPHER OMIT
type Gopher struct {
Name string
- Age int32
- FurColor color.Color
+ AgeYears int
}
-// DUMP OMIT
-func (g *Gopher) DumpBinary(w io.Writer) error {
- err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
+// Example of bad code, missing early return. OMIT
+func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
+ err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
if err == nil {
- _, err := w.Write([]byte(g.Name))
+ size += 4
+ var n int
+ n, err = w.Write([]byte(g.Name))
+ size += int64(n)
if err == nil {
- err := binary.Write(w, binary.LittleEndian, g.Age)
+ err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
if err == nil {
- return binary.Write(w, binary.LittleEndian, g.FurColor)
+ size += 4
}
- return err
+ return
}
- return err
+ return
}
- return err
+ return
}
-// MAIN OMIT
func main() {
- w := os.Stdout
g := &Gopher{
Name: "Gophertiti",
- Age: 3383,
- FurColor: color.RGBA{B: 255},
+ AgeYears: 3382,
}
- if err := g.DumpBinary(w); err != nil {
- log.Fatal("DumpBinary: %v", err)
+ if _, err := g.WriteTo(os.Stdout); err != nil {
+ log.Printf("DumpBinary: %v\n", err)
}
}
-func (g *Gopher) DumpToFile(f *os.File) error {
- return nil
+// Example of bad API, it's better to use an interface.
+func (g *Gopher) WriteToFile(f *os.File) (int64, error) {
+ return 0, nil
}
-func (g *Gopher) DumpToReadWriter(rw io.ReadWriter) error {
- return nil
+// Example of bad API, it's better to use a narrower interface.
+func (g *Gopher) WriteToReadWriter(rw io.ReadWriter) (int64, error) {
+ return 0, nil
}
-func (g *Gopher) DumpToWriter(f io.Writer) error {
- return nil
+// Example of better API.
+func (g *Gopher) WriteToWriter(f io.Writer) (int64, error) {
+ return 0, nil
}
diff --git a/2013/bestpractices/shortercode2.go b/2013/bestpractices/shortercode2.go
index 082457c..4971dbe 100644
--- a/2013/bestpractices/shortercode2.go
+++ b/2013/bestpractices/shortercode2.go
@@ -1,49 +1,44 @@
-// +build ignore,OMIT
+// +build OMIT
package main
import (
"encoding/binary"
- "image/color"
"io"
"log"
"os"
)
-// GOPHER OMIT
type Gopher struct {
Name string
- Age int32
- FurColor color.Color
+ AgeYears int
}
-// DUMP OMIT
-func (g *Gopher) DumpBinary(w io.Writer) error {
- err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
+func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
+ err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
if err != nil {
- return err
+ return
}
- _, err = w.Write([]byte(g.Name))
+ size += 4
+ n, err := w.Write([]byte(g.Name))
+ size += int64(n)
if err != nil {
- return err
+ return
}
- err = binary.Write(w, binary.LittleEndian, g.Age)
- if err != nil {
- return err
+ err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
+ if err == nil {
+ size += 4
}
- return binary.Write(w, binary.LittleEndian, g.FurColor)
+ return
}
-// MAIN OMIT
func main() {
- w := os.Stdout
g := &Gopher{
Name: "Gophertiti",
- Age: 3383,
- FurColor: color.RGBA{B: 255},
+ AgeYears: 3382,
}
- if err := g.DumpBinary(w); err != nil {
- log.Fatal("DumpBinary: %v", err)
+ if _, err := g.WriteTo(os.Stdout); err != nil {
+ log.Printf("DumpBinary: %v\n", err)
}
}
diff --git a/2013/bestpractices/shortercode3.go b/2013/bestpractices/shortercode3.go
index 47d10a7..47eca96 100644
--- a/2013/bestpractices/shortercode3.go
+++ b/2013/bestpractices/shortercode3.go
@@ -1,57 +1,50 @@
-// +build ignore,OMIT
+// +build OMIT
package main
import (
"encoding/binary"
- "image/color"
"io"
"log"
"os"
)
-// GOPHER OMIT
type Gopher struct {
Name string
- Age int32
- FurColor color.Color
+ AgeYears int
}
-// BINWRITER OMIT
type binWriter struct {
- w io.Writer
- err error
+ w io.Writer
+ size int64
+ err error
}
-// WRITE OMIT
-// Write writes a value into its writer using little endian.
+// Write writes a value to the provided writer in little endian form.
func (w *binWriter) Write(v interface{}) {
if w.err != nil {
return
}
- w.err = binary.Write(w.w, binary.LittleEndian, v)
+ if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
+ w.size += int64(binary.Size(v))
+ }
}
-// DUMP OMIT
-func (g *Gopher) DumpBinary(w io.Writer) error {
+func (g *Gopher) WriteTo(w io.Writer) (int64, 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
+ bw.Write(int64(g.AgeYears))
+ return bw.size, bw.err
}
-// MAIN OMIT
func main() {
- w := os.Stdout
g := &Gopher{
Name: "Gophertiti",
- Age: 3383,
- FurColor: color.RGBA{B: 255},
+ AgeYears: 3382,
}
- if err := g.DumpBinary(w); err != nil {
- log.Fatal("DumpBinary: %v", err)
+ if _, err := g.WriteTo(os.Stdout); err != nil {
+ log.Printf("DumpBinary: %v\n", err)
}
}
diff --git a/2013/bestpractices/shortercode4.go b/2013/bestpractices/shortercode4.go
index fd7f6cc..a026463 100644
--- a/2013/bestpractices/shortercode4.go
+++ b/2013/bestpractices/shortercode4.go
@@ -1,62 +1,59 @@
-// +build ignore,OMIT
+// +build OMIT
package main
import (
"encoding/binary"
- "image/color"
"io"
"log"
"os"
)
-// GOPHER OMIT
type Gopher struct {
Name string
- Age int32
- FurColor color.Color
+ AgeYears int
}
type binWriter struct {
- w io.Writer
- err error
+ w io.Writer
+ size int64
+ err error
}
-// WRITE OMIT
-// Write writes a value into its writer using little endian.
+// Write writes a value to the provided writer in little endian form.
func (w *binWriter) Write(v interface{}) {
if w.err != nil {
return
}
- switch v.(type) {
+ switch v.(type) { // HL
case string:
s := v.(string)
w.Write(int32(len(s)))
w.Write([]byte(s))
+ case int:
+ i := v.(int)
+ w.Write(int64(i))
default:
- w.err = binary.Write(w.w, binary.LittleEndian, v)
+ if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
+ w.size += int64(binary.Size(v))
+ }
}
}
-// DUMP OMIT
-func (g *Gopher) DumpBinary(w io.Writer) error {
+func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
bw := &binWriter{w: w}
- bw.Write(g.Name)
- bw.Write(g.Age)
- bw.Write(g.FurColor)
- return bw.err
+ bw.Write(g.Name) // HL
+ bw.Write(g.AgeYears)
+ return bw.size, bw.err
}
-// MAIN OMIT
func main() {
- w := os.Stdout
g := &Gopher{
Name: "Gophertiti",
- Age: 3383,
- FurColor: color.RGBA{B: 255},
+ AgeYears: 3382,
}
- if err := g.DumpBinary(w); err != nil {
- log.Fatal("DumpBinary: %v", err)
+ if _, err := g.WriteTo(os.Stdout); err != nil {
+ log.Printf("DumpBinary: %v\n", err)
}
}
diff --git a/2013/bestpractices/shortercode5.go b/2013/bestpractices/shortercode5.go
index 39a280d..e9234bf 100644
--- a/2013/bestpractices/shortercode5.go
+++ b/2013/bestpractices/shortercode5.go
@@ -1,61 +1,57 @@
-// +build ignore,OMIT
+// +build OMIT
package main
import (
"encoding/binary"
- "image/color"
"io"
"log"
"os"
)
-// GOPHER OMIT
type Gopher struct {
Name string
- Age int32
- FurColor color.Color
+ AgeYears int
}
type binWriter struct {
- w io.Writer
- err error
+ w io.Writer
+ size int64
+ err error
}
-// WRITE OMIT
-// Write write the given value into the writer using little endian.
+// Write writes a value to the provided writer in little endian form.
func (w *binWriter) Write(v interface{}) {
if w.err != nil {
return
}
- switch v := v.(type) {
+ switch x := v.(type) { // HL
case string:
- w.Write(int32(len(v)))
- w.Write([]byte(v))
+ w.Write(int32(len(x)))
+ w.Write([]byte(x))
+ case int:
+ w.Write(int64(x))
default:
- w.err = binary.Write(w.w, binary.LittleEndian, v)
+ if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
+ w.size += int64(binary.Size(v))
+ }
}
}
-// DUMP OMIT
-func (g *Gopher) DumpBinary(w io.Writer) error {
+func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
bw := &binWriter{w: w}
bw.Write(g.Name)
- bw.Write(g.Age)
- bw.Write(g.FurColor)
- return bw.err
+ bw.Write(g.AgeYears)
+ return bw.size, bw.err
}
-// MAIN OMIT
func main() {
- w := os.Stdout
g := &Gopher{
Name: "Gophertiti",
- Age: 3383,
- FurColor: color.RGBA{B: 255},
+ AgeYears: 3382,
}
- if err := g.DumpBinary(w); err != nil {
- log.Fatal("DumpBinary: %v", err)
+ if _, err := g.WriteTo(os.Stdout); err != nil {
+ log.Printf("DumpBinary: %v\n", err)
}
}
diff --git a/2013/bestpractices/shortercode6.go b/2013/bestpractices/shortercode6.go
new file mode 100644
index 0000000..0c6a79c
--- /dev/null
+++ b/2013/bestpractices/shortercode6.go
@@ -0,0 +1,66 @@
+// +build OMIT
+
+package main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "io"
+ "log"
+ "os"
+)
+
+type Gopher struct {
+ Name string
+ AgeYears int
+}
+
+type binWriter struct {
+ w io.Writer
+ buf bytes.Buffer // HL
+ err error
+}
+
+// Write writes a value to the provided writer in little endian form.
+func (w *binWriter) Write(v interface{}) {
+ if w.err != nil {
+ return
+ }
+ switch x := v.(type) {
+ case string:
+ w.Write(int32(len(x)))
+ w.Write([]byte(x))
+ case int:
+ w.Write(int64(x))
+ default:
+ w.err = binary.Write(&w.buf, binary.LittleEndian, v) // HL
+ }
+}
+
+// Flush writes any pending values into the writer if no error has occurred.
+// If an error has occurred, earlier or with a write by Flush, the error is
+// returned.
+func (w *binWriter) Flush() (int64, error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+ return w.buf.WriteTo(w.w)
+}
+
+func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
+ bw := &binWriter{w: w}
+ bw.Write(g.Name)
+ bw.Write(g.AgeYears)
+ return bw.Flush() // HL
+}
+
+func main() {
+ g := &Gopher{
+ Name: "Gophertiti",
+ AgeYears: 3382,
+ }
+
+ if _, err := g.WriteTo(os.Stdout); err != nil {
+ log.Printf("DumpBinary: %v\n", err)
+ }
+}