how to think in go
TRANSCRIPT
How To Think In Goor, let me tell you about all the ways I screwed up
https://www.flickr.com/photos/spam/3355824586
Panics and Errors
“Oh look, panic() and rescue()! I can probably use it like lightweight exceptions!”
Misconception
! panic: unrecoverable errors ! error: recoverable errors
panic / error
sub foo { eval { might_die(); }; if ($@) { # handle $@ } important_task(); }
Perl: Exceptions
func main() { defer func() { if err := recover(); err != nil { // handle err } }() mightPanic() importantTask() // Never reached if mightPanic() panics }
A bad port
func mightPanic() { defer func() { if err := recover(); err != nil { // handle err } }() panic(“BOO!”) }
func main() { mightPanic() importantTask() }
Attempt #2
func main() { if err := tryit(mightPanic); err != nil { fmt.Printf(“Found error: %s\n”, err) } importantTask() }
func mightPanic() { panic(“BOO!”) }
Attempt #3
func tryit(code func()) (err error) { defer func() { if e := recover(); e != nil { var ok bool if err, ok = e.(error); !ok { err = fmt.Errorf("%s", e) } } }() code() return err }
Attempt #3
“Why don’t I just use plain errors to begin with?”
Go Way
func mightError() error { if errorCase { return errors.New(“error case 1”) } if err := someOtherThingMightError(); err != nil { // propagate error return err } return nil }
Use errors!
func main() { if err := mightError(); err != nil { // handle it } importantTask() }
Use errors!
! Do not dream about exceptions ! Do stick with errors
Use errors!
Concurrency
“Go’s makes concurrency so easy, we can just port our multi-process code in an instant!”
Misconception
! PIDs to identify processes ! Processes can be signaled ! Processes can notify termination
Processes
! No pid to identify goroutines ! No signals to communicate ! Goroutines don’t notify on exit
Goroutine
sub sub main { my $pid = fork(); if (! defined $pid) { die “Failed to fork: $!” } if (! $pid) { while(1) { do_stuff() } } $SIG{CHLD} = sub { my $pid = wait; print “reaped $pid\n”; };
sleep 5; kill TERM => $pid; while (kill 0 => $pid) { sleep 1 } }
Perl: Processes
func main() { // Won’t work go func() { for { doStuff() } } }
Go: A simple goroutine
func main() { exitCh := make(chan struct{}) go func() { defer func() { close(exitCh) }() // close upon termination for { doStuff() } } <-exitCh // Wait for something to happen }
Go: Detect termination
func main() { exitCh := make(chan struct{}) incomingCh := make(chan struct{}) go func() { defer func() { close(exitCh) }() // close upon termination for { select { case <-incomingCh: // Got termination request return } doStuff() } }
Go: Accept Termination Request
// Send request to terminate loop time.AfterFunc(5 * time.Second, func() { incomingCh <-struct{}{} }) <-exitCh // Wait for something to happen }
Go: Accept Termination Request
! Explicit coordination required ! No goroutine specific storage
Goroutine
! You must explicitly bail out for infinite loops in goroutines
One more thing:
! Still, goroutines are worth it ! Channels can do much more
Too much hassle?
Designing Structures
“Structs and methods will allow us to make our Object-oriented code easy to port”
Misconception
Worker::Base
Worker::Foo Worker::Foo Worker::Foo
package Worker::Base; # snip sub foo { # do stuff.. shift->something_overridable_in_child_class(); }
sub something_overridable_in_child_class { … }
Perl: Interaction Between Parent/Child
package Worker::Foo; # snip use parent qw(Worker::Base); sub something_overridable_in_child_class { … } sub work { my $self = shift; while (1) { # do stuff… $self->foo(); # Doesn’t translate very well into Go } }
Perl: Interaction Between Parent/Child
! No. Just No. ! Embedded structs + Automatic Delegation
Go: No Inheritance
type Name string func (n Name) Greet() string { return fmt.Sprintf(“Hello, my name is %s”, n) }
Go: Name
n := Name(“Daisuke Maki”) println(n.Greet()) // “Hello, my name is Daisuke Maki”
Go: Name
type Person struct { Name // Embedded struct Age uint // A regular field }
Go: Person
p := &Person{ Name: Name(“Daisuke Maki”), Age: 38 }
println(p.Greet()) // “Hello, my name is Daisuke Maki”
Go: Automatic Delegation
p.Greet() // → p.Name.Greet() // The receiver is p.Name, not p.
Go: Automatic Delegation
func (p Person) Greet() string { return fmt.Sprintf( “%s. I’m %d years old”, p.Super.Greet(), // Pseudo-code. Doesn’t work p.Age, ) }
Go: Customizing Person.Greet()
type BaseWorker struct {} func (w BaseWorker) Foo() { // This only class BaseWorker.SomethingOverridableInChildClass() w.SomethingOverridableInChildClass() }
type FooWorker struct { BaseWorker } func (w FooWorker) Work() { for { // Do interesting stuff… w.Foo() // w.BaseWorker.Foo(), receiver is never FooWorker } }
Go: Another Failed Attempt
Wrong Approach: Top Down
Abstract Base Class
Concrete Implementation
Specialized Implementation
…
…
Suggested Approach: Bottom Up
Type A
Component 1
Component 2
Component 3
Component 4
Type B Type C
! Interfaces ! Promise that a type has certain
methods
Go: Grouping Types
type Greeter interface { Greet() string }
func main() { p := &Person{ Name: “Mary Jane”, Age: 30 } n := Name(“John Doe”) greeters := []Greeters{ p, n } … }
Go: Things that can Greet()
func sayHello(g Greeter) { println(g.Greet()) }
for _, g := range greeters { sayHello(g) }
Go: Things that can Greet()
! Think in terms of ability (methods) ! But note: no “base” method
implementations
Go: Interfaces
// WRONG! No methods for interfaces func (g Greeter) SayHello() { println(g.Greet()) }
Go: No “base” methods
// OK. Functions that take interfaces work func sayHello(g Greeter) { println(g.Greet()) }
// Each type would have to make this call func (n Name) SayHello() { sayHello(n) // Name is a Greeter }
func (p Person) SayHello() { sayHello(n) // And so is Person, through delegation to p.Name }
Go: No “base” methods
! Think of abilities, not Is-A, Has-A ! Compose from smaller components
Go: Designing Models
! Don’t Use Exception: Use errors
Conclusions
! Don’t Expect Goroutines = Threads/Processes
Conclusions
! Don’t Design Using Tree-style hierarchy ! Create layers of standalone functionalities ! Compose them ! Use interfaces
Conclusions
! (If you haven’t already heard) When In Go, Do As The Gophers Do
Conclusions
Thank YouFurther reading:
! https://talks.golang.org/2014/readability.slide ! https://talks.golang.org/2014/gocon-tokyo.slide ! https://talks.golang.org/2013/bestpractices.slide ! http://blog.golang.org/errors-are-values