100 Go Mistakes: 1-9

So I'm pretty much looking for a place to write up notes on things I'm reading - which, right now, includes Teiva Harsanyi's "100 Go Mistakes and How to Avoid Them" (does anyone else get confused when writing title-case? Let me look up the rules quick). 

 Anyway, I'm at a point where I want to write down some of what I'm learning so I can refer back to it later, so let's go.

1 - Unintended variable shadowing


Go's short variable declaration operator (:=) can introduce unintended effects if you're making use of it within nested blocks. Here's a simple example.

The workaround is really to explicitly assign the value to the variable declared in the outer scope, either by using temporary variables when using the declaration operator, or simply assigning directly.

2 - Unnecessary nested code

This provides a fairly useful heuristic with regards to code complexity. Essentially, Harsanyi wants us to try, as far as possible, to keep the "happy path left aligned", which, according to Mar Ryer, allows readers to "quickly ... scan down one column to see the expected execution flow".

Practially, this means trying to eliminate unnecessary "else"s by rewriting if possible. I imagine this can also be used as a rationale for factoring some code out into subfunctions (although that's potentially contentious).

3 - Misusing init functions


Harsanyi points out the following issues with init functions 

  • Because they return nothing, the only error handling available to them is to panic
  • Because they're always invoked for a package, they can make testing problematic
  • They're often associated with global variables
I've used init functions in the past in the same way Cobra does, to dynamically register functionality provided in the package with some central broker.

4 - Overusing getters and setters


This is really a weird one, and perhaps one of the limitations of the "mistakes" format of the book. It's more really a suggestion to keep things simple where possible - getters and setters are not necessarily required in a lot of situations, despite the advantages they present:
  • getters and setters give you a point to "encapsulate behavior" associated with the events of getting and setting a value.
  • hide internal structure, giving "flexibility in what we expose"
  • provides a "debugging interception point"
Some suggestions - getter method should be the field's name (uppercase first letter for exposing), getting should be GetX (where X is the field name).

5 - Interface pollution


This is one of the most interesting of the first 9 items - it presents a go centric understanding of interfaces.

  • Abstractions should be discovered: We should, most of the time, begin with concrete structures and only abstract when it makes sense.
  • Abstractions should be as simple as possible: getting the granularity of the interfaces correct.
  • You should be using interfaces for:
    • Abstracting out common behavior - if multiple types share common behavior (his example is sorting algorithms) this is a good place to make a minimal interface capturing this behavior.
    • Decouping: Liskov Substitition Principle
      • Interestingly, Harsanyi goes on to point out that interfaces should actually be defined at the client code side, with us mostly preferring concrete types at the API/Provider side of the equation - something that's common with coders coming from other languages where the idiom is to define an interface and code to it (rather than discovering interfaces, as recommended).
    • Restricting behavior: Another interesting use is to define restricted types to prevent misuse of some structure (e.g. taking something that can read/write and defining an interface that only supports the read functionality in order to disable writing - this is really easy to do with golang's interfaces).
  • Interface pollution is really just the result of too many interfaces making the flow of the program difficult to follow. Focusing on concretizations generally makes things easier to follow (arguably).

6 - Interface on the producer side

This was mentioned above - avoid having the provider/producer define interfaces and let the consuming code define its own interfaces. The clients can discover their own abstractions, and the producer needn't be prescriptive about how it's used. 
"[This] relates to the concept of the Interface Segregation Principle (the I in Solid), which states that no client should be forced to depend on methods it doesn't use"(26)

7 - Returning interfaces

More of the same about interfaces - if we return interfaces, rather than concrete structs, we unnecessarily introduce restrictions on the consumers of our return values. Let them define their own types, let them discover the abtractions that make sense for them.

8 - any says nothing


The empty interface{} - any allows us to open up what we read, write, and pass around, but with the downside that we're losing all the strong typed goodness that comes with golang. It's another of the examples where we should always rather work to the concrete type rather that an abstraction.

9 - Being confused about when to use generics

This "mistake" begins with a good overview of how to use generics - the meat really is in the recommended uses of generics
  • Use for data structures. Obvious.
  • Use in functions working with slices, maps, and channels of any type
  • Factoring out behaviors instead of types
We should not use them 
  • When calling a method of the type argument (see book for example)
  • When it makes our code more complex (repeating the general takeaway from the first 35 pages).

Comments

Popular posts from this blog

JCE's Gifted and Talented Center

100 Go Mistakes 10-16