I found that there’s a slight aversion to creating new types in the codebases I work in. I saw it during my early days while I was working in Java projects, and I see it today in the occasional Go project. Function bodies with lots of local variables, functions that take a large number of arguments or returning a large number of results, extensions to existing types rather than making new ones. It’s a strange phenomenon.

I can’t explain why this is. Maybe it’s a fear of feeling like you’re tampering with the “grand design” of the codebase. This is plausible as it was the feeling I had as a junior dev. Afraid to create new classes in Java thinking that I’m introducing a new concept to the project that others had to deal with going forward. _I can add all the verbs I want, but who am _ I to introduce a new noun?

This is obviously a ridiculous notion when you think about it for more than a few seconds. If you come up with a concept or a series of values that naturally go together, so much so that you’re carrying them together as a series of arguments through multiple function calls, it’s probably in your interested to make a type for it. That’s what the type system is for: a means of grouping similar bits of information into an easy-to-use whole.

This makes total sense for the application models: the entities to which you’re software’s reason for being hinges on. But I’ve found it useful to make types for the lesser bits of information: requests from handlers passed through to the service layer, for instance. Just now, I’m working on some code that deals with creating subscriptions. I need to carry the office ID, customer ID, price ID, the subscription quantity, the tax settings, and the subscription metadata from the API handler all the way through to the Stripe client. This is less than what the subscription model deals with, but it’s still a pain to carry these six bits of information separately through the unmarshalling logic, the validation logic, and then through to the server.

So what did I do? I made a “CreateSubscriptionRequest” struct, a new type. Yes, it’s not going to be reusable, but who cares? It makes the code and my life simpler. And honestly, I think the whole “object-orientated approach” to software design really screwed up our thinking here. There was this feeling in the zeitgeist that types and classes are sacred, and that to create a new one is a privilege bestowed only to the leads, architects, and anyone else that had write access to the UML diagrams. Each type was to be an artefact of design, probably because of how much baggage came from defining a new one: they had to be in a separate file, must have seven different constructors, and the fields must be mediated through the use of getters and setters. And if you need something similar to what you’re working on, you didn’t “copy-and-paste” like some animal; you inherited or composed what was there. Given all this, it’s probably understandable that creating new types felt like a decision with a significant bit of “weight”; and who are you, mere lowly junior developer, to make such a decision to create a type just to make it easier to handle data from your handler?

I think the culture around C and Go have got it right. Need to carry a few things for a single function? Create a new type. Don’t worry that it’s used only for a single function. Don’t worry that it only contains a subset of fields of the model you’re operating on.1

Now obviously it’s possible to go too far, and start having way too many types than is necessary. Don’t forget that a new type is a bit more cognitive load, as the person maintaining you application will now need to unpack and reference your type when they need to work on it. Just stick with what you need, and make it clear what the purpose of the type is. “CreateSubscriptionRequest” makes it plan that this type only deals with the areas of a code that creates subscriptions, and will probably only make sense through those code paths.

But take it from someone that’s had do deal with codes passing through and returning several values of strings, ints, and bools through a series of function calls: a single struct value is much easier to work with. All it takes is the courage for someone to say “yes, that should be a type.”

Don’t be afraid for that someone to be you.


  1. In fact, that might actually better than using the model type and adding “this field is ignored, that field must be zero, etc. etc.” in the function docs. ↩︎