Choosing good names for objects and methods in code is one of the toughest things to do as a software engineer. It comes with the great and invisible reward of simplicity – second only to having less code in the first place, having code that’s easy to reason with makes extending it and debugging it in incident scenarios all the easier. Of course, the opposite is true as well – code that trips us up and isn’t easily understood can come back to bite us in times of trouble.
What’s in a name?
Though Shakespeare made the disconnect between a name and a purpose romantic in Romeo and Juliet when he wrote “what’s in a name?”, names and purposes should ideally be one and the same, if not very closely related, when we write code. Objects that exist in code might correspond to a real-world object or process, so their names should accurately reflect that.
If a class has less responsibility, it’s typically easier to name according to its purpose. This goes hand in hand with more maintainable software architecture in line with the single-responsibility principle. However, there are some things that you might want to represent in an object that may naturally need to have more responsibility, and that’s where naming gets much tougher.
In Rails, we often use Active Record models to reflect real-world objects or concepts within our domain, but these can grow to have extensive responsibility. Perhaps you’re representing a digital ticket for an event, or a billing subscription to your platform. These kinds of things are deceptively difficult to architect and name – while as concepts we can glean a lot about how we expect them to work from their names, they benefit from being broken down if we want to write code around them. Particularly with the given examples, there are opportunities to implement these differently according to requirements, and this might create distance between our expectations and what’s actually implemented.
What do we mean, for example, when we talk about a subscription? Usually it entails some kind of commitment to pay money on a regular basis to maintain access to some number of features, but with that comes a number of questions. Is there a grace period when you cancel? Are free trials provided? How regularly are payments required? If it’s monthly, how is it affected by the number of days in a given month? Is there another way to gain access to any or all of the features besides the subscription?
When you start asking these kinds of questions, you see the concept you knew by a single name breaking down into smaller fragments. Requirements come out, and the shape of the domain is clearer for it. Good naming ideally follows from that.
What makes naming so hard?
Clearer understanding of the domain is the basis for a good name, but the act of choosing a name gets harder because of the ambiguity of language itself. It’s possible for words to be interpreted in many different ways depending on their context. Thinking about prose rather than code, words can have multiple meanings, and the structure of sentences can create a lack of clarity.
Let’s say, for example, we’re about to call a class Match. Unless we’re in an isolated context with a clear and singular responsibility, that name leaves more questions than answers – is this a pairing of multiple things? If so, what? Are they paired because they resemble each other, or for some other reason? Are we even talking about that, and not some kind of sporting contest (a very different kind of match!)?
That’s all derived from your perception of what the word means, and it’s not the same for everyone. Your background might change how you interpret a word relative to others. Perception is the means by which we experience ambiguity.
As the responsibilities of an object or method grow, so too does the impact of its ambiguous name. If the purpose of the object is unclear from its name, then the name works against you – the name sets an expectation for the purpose of the object, and can condition you to think about it differently.
Coming back to this Match class, if we were in an isolated enough scenario where it was fine to call it that, we would still have to derive the meaning of this Match class from surrounding context. The name still doesn’t answer any of the questions we had before. Names should clearly indicate to the developer the role of the code in question. But choosing a good name is part of why naming is notoriously hard – how do you capture such responsibility in so few words?
How can we choose better names?
So now we know what’s at stake, what’s the secret to naming? How do we seek simplicity when the nature of language itself works against us?
Understand your domain
Don’t be afraid for names to change as your understanding of the domain does. The name you choose reflects your domain, so as your understanding of the domain improves, so too does your ability to name something well. As Eric Evans suggests in Domain Driven Design, a “ubiquitous language” transcends the code and describes the domain in such a way that it flows naturally for everyone working in it, regardless of role.
Reduce the scope of your class
Software architecture has a role to play – if a class is doing too much, then it becomes much harder to name it. The single-responsibility principle is a good thing to be mindful of when you’re designing classes or breaking them down. Going for a more specific name also helps – you could use a more generic name like Match if you tighten it down to the context of a feature that uses it, like DuplicatePhotoMatch (which you might use to group identical photos in a photo library app for review). As mentioned earlier, more abstract concepts might need to be broken down to many smaller objects to really give you the flexibility you need.
Watch your biases
When talking about the domain around a feature, it’s easy to get attached to a name even if it might not be the most appropriate one. This gets even easier if you didn’t choose it yourself – if you’re working from a specification that already defines the name, it can be difficult to combat that name if it isn’t immediately a good fit. Don’t be afraid to raise any potential naming issues with your wider team, but make sure that everyone’s on the same page.
Align your perception
Perhaps most importantly of all, talk about it! The more folks are involved in a naming decision (or revision), the more directly you’re facing into the problem. Everybody brings their own perception of the meaning of a potential name to the table, and through discussing that and suggesting more ideas, you can reach a name that feels agnostic to any one given perception. Bear in mind that this isn’t immune to bias – diversity and psychological safety within your team can help to combat the risk of that.
Spread the word
The language can influence others’ perceptions too, regardless of their role. A good name reflects the domain well enough that it’s as helpful for developers as it is for anyone else who’s close to the work. A great test of a name is to make sure that it’s useful to everyone – are developers, designers, product managers, and the business at large all describing things similarly?
By nature, good naming takes more than just one person. It’s one of the most difficult things we can do as engineers, but the time you take together to solve it will buy back incalculable hours (in both senses of the word!). Lean on your team and use your broad perception to describe your domain effectively.