One of the main reasons developers use code generators is because we, as humans, don't want to write the code ourselves. Sure, there are a bunch of second-level objectives like code reuse, getting better performance or supporting multiple languages from a single starting point, and we could write the code to do all of these things… or we could play with a cool tool that will do it for us. If you’re new to code generation, here’s a quick overview.
Simply put, code generation is building programs that produce other programs as their output. Most code generators have two main sources of input: the source code that acts as the reference and the configuration that tells it what to output. If you’ve generated SDKs from an OpenAPI specification file, created CRUD classes using a database table, or created sample forms from a database class, you’ve used code generation. In every case, we have two parts: a starting representation and the logic to execute on it. From there, everything is a configuration or a knob to tune for the specific output you want.
In this post, we’re going to explore three popular code generation tools for Go. Why Go? Because it’s approachable and plays well with a variety of code generation tools.
Go code generation tools
Stringer
Consider that you’re given an enum and you want to render it as a string. In this case, you want the string value from the constant to be the name of the constant itself. Of course, you could write that routine yourself, but as you add and remove constants, it will become annoying to keep it in sync with the method that turns it into a string. Instead, you can use Stringer to do it for you. This is the de facto code generator for any tutorial in Go, and a great place to start if you’re new to code generation.
FFJSON
When you're using marshaling and un-marshaling functions of Go's JSON, encoding and decoding routines in the standard library are done through reflection, dynamically inspecting the object at runtime to determine the JSON output. That kind of reflection can be slow, but you can speed up the process by using a tool like FFJSON that looks at the structures you want to serialize and deserialize and generates optimized routines for them ahead of time. By overriding the runtime process, you can both optimize and customize it to create exactly what you want, in the way you want it, and skip the runtime introspection.
GoMock (a.k.a. mockgen)
When you're testing code, you want to mock out all of the dependencies so you can isolate and test only the piece of code you want without worrying about the external systems — APIs, network, filesystem, etc. — beneath it. A simple way to do that is by calling all of the other services through interfaces. Then, when it comes time to test, you substitute a mock object for those interfaces so they behave in consistent, predictable ways without touching the underlying systems
Unfortunately, writing mock objects is a lot of work, especially as you change interfaces. To streamline this, you can use a tool like mockgen. Simply point mockgen at an interface, and it will generate an object that matches that interface for you. It also generates a whole bunch of methods that allow you to define what it should do when those methods are called and what to expect.
For example, if you're writing a test harness, you’d tell the mock what you expect to be called so that you can guarantee that the object and the external dependency were exercised correctly. At test completion, mockgen can match what was called and what you expected to be called.
Go testing with GoDog
And for a final generation and testing option, there’s GoDog. Unlike the other tools, GoDog doesn’t generate business logic. Instead, it generates the structure of the behavioral tests for your code. Using the Gherkin syntax of Given-When-Then to describe steps, it creates stub Go functions mapping to each step. You still have to fill in the implementation details, but the overall structure is built for you to prove. And yes, I’m also disappointed they didn’t name it “GoodDog.”
Should I check in my generated code?
Now that I’ve introduced you to a few code generation tools to get your feet wet, let’s discuss checking in generated code. A benefit of doing this is that when you download code to check it in, you clone it. You can invoke the Go tool chain to build or install the program without having to figure out the other steps to generate the code you want to work with. Another pro is that, in some cases, if you don’t check in the generated code, your program won’t compile.
Alternatively, you may not want to check in generated code because pull request reviews become challenging. Consider an instance where you're looking at incoming changes where a single change in your generation logic changed the generated code in 50 files. Realistically, you only care about that one change, not the 50 downstream changes. To address that problem, you can use Git Attributes to annotate a specific set of glob patterns as being generated and therefore ignored. Github specifically can auto-identify those files and collapse the details. This helps human reviewers focus on the pieces of code that matter.
Code generation is eminently easy
If you’ve never used a code generator before, check out the recommendations above. Stringer, GoMock, GoDog and FFJSON are some of my personal favorites, but these tools are just the tip of the iceberg. Code generation can help you solve a tremendous number of problems and make writing more code — with less lines of code — really easy. Once you’ve figured out code generation, I recommend you write some of your own tooling and never release it to the larger world. Exploring the depths, complexity, and tradeoffs is exceptionally informative, but odds are your time is better spent understanding the costs and tradeoffs of common tools and extending as you need.
Keith Casey currently works in Product and Go To Market at ngrok, helping teams build and launch their systems. Previously, he served on the Product Team at Okta, working on Identity and Authentication APIs for apps and APIs, he worked as an early Developer Evangelist at Twilio, and he worked on the Ultimate Geek Question at the Library of Congress.