Convenience Methods: Don’t Be Scared Of Commitment
The term “convenience method” could mean a lot of things, so I’ll start by describing what I intend it to mean in this situation:
If a method has a large number of parameters, a convenience method performs the same set of operations as the aforementioned method without requiring its caller to explicitly provide all of the necessary arguments. That is to say, a convenience method assumes a certain amount of context to do its job.
What would this look like in a practical example? Let’s say we have a (rather verbose) method from a class that keeps track of purchases made:
public PurchaseRecord addPurchase(AccountType wallet, String itemName, double dollarValue, Date timestamp)
This would let you specify the name of the item you bought, how much it cost, which wallet it came out from, and when you bought it. This is great! It lets you specify granularly what you intend to convey.
However, one thing we see all too often as developers, are patterns in our usages. If we’re building a system that frequently logs the same kind of transactions, we could make our developers lives easier. We do this by reducing verbosity:
public PurchaseRecord addCoffeePurchaseNow(AccountType wallet)
By reading the method name we can tell that there’s a certain expectation:
itemName is most likely to be the name of a standard coffee beverage, the price is assumed, and we’re buying it now. Because we demand our coffee. All we have to worry about is what wallet this spending money is coming from.
You might be reading this and thinking, “yeah, this is common stuff” — but I’ve run into developers that are scared to do this.
Here are a few reasons I’ve heard why, and why they might not be as big of a concern as you think:
This makes our methods specific, and they won’t be generic enough.
Developers sometimes fall into the trap of thinking generic means context-free. This would be a dangerous case to make, and I’d even go as far as to say it would be false equivalence.
Yes, it’s true: as software engineers, one of our most basic, most important duties is to make sure the working parts of our codebase are generic. They should be able to adapt to ever-changing business requirements, and mindfully decoupled so they can be used transformatively across different parts of our codebase.
However, that is not the same thing as denying our code of context.
Actually, a large point of creating objects in an object oriented programming paradigm is that every object is full of context! A list is a data structure that houses a collection of objects that are of the same type; a controller operates on models and interprets interactions. Each class has a certain responsibility, and it should be able to perform to that responsibility without getting handheld by its instance caller.
If we had that
addPurchase method used freely by any object that needs to buy coffee, it would quickly become a litter of multi-line invocations that will be difficult to wade through. By encapsulating our context, we lessen the cognitive load on our method caller — we outline the implied arguments in the method’s name, instead of forcing the caller to define it manually. This additionally provides an opportunity for us to encapsulate to a single source for change, if for example our coffee guy jacked the prices up by a dollar.
Under the hood, our mechanisms are still generic, but we’ve given it some context. If the caller needs to explicitly specify what they bought, they still have the option to. But if they’re just buying an average cup of joe, they can just buy and be quickly on their way.
Adding one convenience method invites for more, and it would clutter the API of our housing class.
Yes! Let it! Object oriented programming is all about abstraction, and providing convenient APIs to our callers means we’ve achieved a good level of it. Why should the outside worry about things they don’t have to? They don’t have to tell me
Date.now() every time, because I can look at the clock too.
The over-simplification aside, if your class looks too cluttered with methods outlining all the possible cases, it likely means that you should further abstract. Do you need a new layer on top? Do you need to break it down by an overarching context, like available vendors? Development is often about how well you can identify the usage patterns. From the most granular, low-level work to the simplest — this ability to identify patterns is the cornerstone behind good abstraction. If you find yourself frustrated at the hoops you have to jump through to get a simple reoccurring thing done, that’s probably an open invitation for you to abstract more.
Remember: assign each of your classes one responsibility, and ensure that it adheres to it well.
What if the business requirements change? Now there’s a ton of methods to teardown.
This is the same mindset that inspires over-engineering: there are many things that could happen in the future, so we should prepare for them. Whether this is expressed through action or inaction is unimportant: it is over-engineering.
Business requirements always change. That’s just the reality of it all. If it’s not features, it’ll be analytics. If it’s not analytics, it’ll be UI changes. If it’s not UI, it’ll be upkeep to bring in that shiny new SDK that brings support for the next generation devices. They. Always. Change.
Coding is now, by and large, an iterative craft. I’ve heard analogies drawn to working with clay: you scrape, you add, then you scrape. If it adds a benefit now, and if it looks like it will gain you a benefit for a foreseeable future, add them! You literally get a benefit out of it. If it becomes a hinderance in the future, the time to remove it will come.
Rome wasn’t built in a day, and nobody wakes up one morning thinking they should refactor their whole codebase. If you’ve taken your measures and are attentive about changes as they come, it will be akin to ripping off a bandaid — not amputating a leg.
Don’t be scared of commitment. Identify what contexts make sense in your product, and apply it liberally because why not? It’s in the name: convenience methods. It should make your life a little more convenient. Code that is easier for humans to write is a more human code, so long as the human expectations match the actual workings of the machine interpretation that lies below.