The Builder Pattern in Go

First, the builder pattern in Java

Over my years developing large applications in Java I’ve become a huge fan of the builder pattern as described in Effective Java. The pattern is so important that is item 2 in the book. The point of the pattern is that if a constructor takes many parameters (4+ is a good rule of thumb), then the constructors become unreadable. Especially if you have many parameters that are optional, or many parameters with the same type.

For example, one of the constructors for a JodaTime DateTime takes 7 ints. If you just invoke the constructor you will end up with something like:

DateTime dt = new DateTime(2016, 5, 30, 13, 30, 21, 500);

You might be able to guess what some of those parameters are, but do you know what they all mean? Tools like an IDE can help with readability but they typically aren’t available when doing a code review using a web based tool.

One solution is to get everyone to include in line comments that with the parameter name in these situations.

DateTime dt = new DateTime(
    2016, // year
    5,    // monthOfYear
    30,   // dayOfMonth
    13,   // hourOfDay
    30,   // minuteOfHour
    21,   // secondOfMinute
    500   // millisOfSecond
);

The code becomes much more verbose, but at least the reviewer has some context. Another solution is to use a fluent or java bean like API.

DateTime dt = new DateTime()
    .withYear(2016)
    .withMonthOfYear(5)
    .withDayOfMonth(30)
    .withHourOfDay(13)
    .withMinuteOfHour(30)
    .withSecondOfMinute(21)
    .withMillisOfSecond(500);

This is an improvement in my opinion. It is clear what each parameter is for, and if you are missing a parameter then you just don’t include it. The downside to this is that with each call you are modifying state of the object which means there can be times when the object is in an invalid state.

I’ve actually seen a bug where this happened in the production code. The method was setting a user’s birthday and looked like:

DateTime dt = new DateTime()
    .setDayOfMonth(30)
    .setMonthOfYear(9)
    .setYear(1980);

This worked great for almost year, and then it became February… You see, the default constructor uses the current date and time for the initial values. When the code changed the day to 30 the object was invalid because February does not have 30 days, so the method threw an IllegalArgumentException.

The solution to this would be to make DateTime immutable (see item 15 in Effective Java) and construct it with a builder. This could look something like:

DateTime dt = new DateTime.Builder()
    .setYear(2016)
    .setMonthOfYear(5)
    .setDayOfMonth(30)
    .setHourOfDay(13)
    .setMinuteOfHour(30)
    .setSecondOfMinute(21)
    .setMillisOfSecond(500)
    .build();

Now which ever order you call the set methods in, validation of the DateTime won’t happen until build() is called, and the bug we had would have been prevented.

The downside to liberal use of the builder pattern is that you have to write lots of builders and that can be annoying and time consuming. The good news is that are tools like AutoValue that make writing builders much easier.

Now, the pattern in Go

All of this was in my mind when I started writing go code. I’d create a package with non-exported members and, as suggested in Effective Go, I’d add an exported function to create a new instance.

package id3

type ID3Tag struct {
  title   string
  artist  string
  album   string
  genre   string
  year    int
  track   int
}

func New(title, artist, album, genre string, year, track int) *ID3Tag {
  return &ID3Tag{title, artist, album, genre, year, track}
}

But just like in Java, invoking this function can lead to confusion, especially if any of the parameters are optional; is the first parameter the album name or the track name?

t := id3.New("Bleed American", "Jimmy Eat World", "", "", 2001, 1)

So I went back to my bag of tricks and decided that I should use the builder pattern. I did a search and found a few other people had come to the same conclusion, including a pretty cool library to make writing builders easier. But when I went to actually use this code it never felt like idiomatic go to me.

Then it hit me, go already has something like a builder as part of the language! I created a second struct with exported members. Then I pass the second struct as a parameter to the function that constructs the instance. The function has all of the data needed to do validation and construction. Because go has such great syntax for composite literals you get the readability for free.

package id3

// The builder has all exported fields and no methods
type Builder struct {
  Title  string
  Artist string
  Album  string
  Genre  string
  Year   int
  Track  int
}

// None of the fields are exported, which makes the struct immutable
// unless your provide functions to mutate them
type ID3Tag struct {
  title  string
  artist string
  album  string
  genre  string
  year   int
  track  int
}

func New(b *Builder) (*ID3Tag, error) {
  // Do any necessary validation
  if b.Year < 0 {
    return nil, errors.New("No music was recorded before year 0")
  }

  return &ID3Tag{
    title:  b.Title,
    artist: b.Artist,
    album:  b.Album,
    genre:  b.Genre,
    year:   b.Year,
    track:  b.Track,
  }, nil
}

And then when you want to create a new instance

t, err := id3.New(&id3.Builder{
  Title:  "Bleed American",
  Artist: "Jimmy Eat World",
  Year:   2001,
  Track:  1,
})

I’m not the only one using this pattern either (though I haven’t seen anyone else describe it is a builder pattern). If you look at the render library, it has a constructor that takes an Options struct which contains all the necessary parameters. Render also uses a neat trick of having the New function take a variable number of Options arguments. If no Options are provided then is uses the defaults, otherwise it takes the first Options and ignores the rest. This allows the same function to be used with or without parameters, which isn’t usually possible in go, since it doesn’t support function overloading.

I hope that you will agree with me that this is a nice way to get the benefits of the builder pattern in Go in an idiomatic way. It also has the advantage of not requiring you to write much code or rely on reflection like the library I linked above. If you have other ideas about how to implement builders in Go I’d love to hear about them.

CC By SA Cover photo