Are hotdogs sandwiches?
Yes. And the reason is that protein inside of a partially-to-fully open piece of bread constitutes a sandwich. Okay, that’s out of the way.
There is an ongoing, raging debate at Caviar about sandwiches, hot dogs, and all manner of food. As you can image, as a food company we encounter all kinds of fun, quirky takes on food and as a result we get ourselves into a ye olde semantic debates about what food is and is not.
This, of course, is a silly way to blow off steam and enjoy hanging out with each other. But, this kind of semantic debate is important in a healthy, functioning team. Naming is hard. It is one of the hardest things to get right, and it is important because it increases the efficacy of communication between team members, which ultimately results in how fast you can ship new software.
Naming is Hard
Many of us started our professional careers in college, writing basic code in Java or Python, and writing our code a bit like this:
r = 0
for x in arr:
r += x
After writing this code, we’d get passing grades because the code worked as asked. But, after we write more and more code, we learn that categorization and dealing with groups of functionality makes it easier for us to understand. We are then introduced to Object Oriented programming, or the concept of using modules for organization, at least. We’re then introduced to some kind of framework, be it an MVC framework like Rails, the iOS SDK, or some other framework. You’re introduced to these high level patterns like Models, Views, and Controllers. And then our code starts to splay out into these objects.
And, we feel comfortable in these worlds. Models are where business logic goes. Views are where view logic goes. Controllers deal with user interaction. And so it goes, until we realize that we have 1000 line models and tables with 100+ columns. Now what?
It is a common story, and it is one that Caviar and many other teams at Square deal with every day. One of the tools that you use to walk yourself out of this world is naming. But, it is easy to say that you should name your objects properly and hard to do it. Let’s use an example.
The User object. If you use Rails, you likely use Devise, and if you use Devise, you likely have a User object. Right off the bat, we’re in bad naming territory. What is a “User”? They are someone that “uses” something else. In the Caviar domain, that could mean: someone ordering food, someone working at Caviar, one of our couriers, an engineer, an API reader, a guest, a high value customer, someone creating a support case, etc. All of these are valid interpretations of User. When you have so many things that CAN be a user, you dilute that value of what a User is.
The consequences of this are that whenever we talk about a User, we need to qualify it. This is a Courier User; this is a Guest User; this is a User placing an Order. This is inefficient and confusing, leading to things lost in communication and ultimately errors down the road.
How to Name Good
Naming is important. How do we name things well, though? There are a few guidelines that I use to help steer me in the right direction. They don’t always lead to the right answer, but the rules have served as a good starting point.
Avoid common names
In software development, there are some names that we use all over the place that accumulate meaning. A good example is the User object above. By naming it User, there is an implied amount of meaning applied directly to it. By avoiding these names, you can challenge yourself to think about what you are really describing. I took a quick poll from a bunch of virtual friends at the Sheep and Cheese mailing list, along with polling some Square engineers, and here is a list that we came up with:
- Functions that start with “get”
- o, obj, v, val
These common names have a natural “gravity” to them, pulling code to them as they grow bigger and bigger. Avoid using these names if you can and you solve a whole class of issues.
Use domain language
One source of naming inspiration should come from the domain that you work in. Some of these are immediately obvious: at Caviar we call representations of couriers Couriers in our codebase, instead of something else like Drivers or DeliveryUsers. This inspiration can come in some non-obvious ways, though. When we started working on printing for restaurants, we started calling these Receipts. But, we quickly realized that restaurants called them tickets. So, we renamed Receipts to Tickets to match up with restaurants, and create a stronger common language.
Using common language with your domain and stakeholders decreases the amount of context switching that each of you have to do. This means that you can spend more time solving problems and less time arguing semantics.
Lean on pattern languages
We’ve been watching a bunch of Ruby Tapas lately and Avdi talks a bunch about pattern languages:
“A pattern language is a set of complementary thought-forms that helped some people get their minds around a problem. ” — Avdi Grimm, Patterns are for People
Avdi is right. The software industry is rife with people who have encountered the same naming struggles you’re working through right now. Lucky for us, some of those people took the time to write those naming conversations down into books or articles. Utilize those resources — especially when it comes to describing what a class technically does!
David Heinemeier Hansson, when building Rails, had a copy of Pattern of Enterprise Architecture next to him, helping to guide him through the tough naming decisions that would eventually become ActiveRecord, ActionController, ActionView, etc. Other pattern languages to consider:
- Gang of Four
- Design Patterns in Ruby (If you are a Rubyist, use this instead of the GoF book)
- Refactoring to Patterns
- Implementation Patterns
- Working Effectively with Legacy Code (talks about key patterns used to deal with legacy code)
- Using the pattern language defined in the frameworks you use. Rails, Spring, Elixir, React, etc. will all have pattern languages embedded inside of them. Reuse that pattern language, it is part of the framework you are using!
- Search for inspiration in popular open source projects that you may not be using. It is perfectly fine to talk about using the Swift Optional type in a Ruby codebase as a source of inspiration.
This is a small rule, but it makes code much easier to read. In any given context, attempt to remove duplication. This is best explained through a series of examples:
attr_reader :restaurant_special_notes, :restaurant_prep_time
def initialize(restaurant_special_notes:, restaurant_prep_time:)
@restaurant_special_notes = restaurant_special_notes
@restaurant_prep_time = restaurant_prep_time
In this example, the word “restaurant” appears 9 times — way too many. In this class, we already know everything is related to a restaurant. So, we can reduce duplication by removing those names from the instance variables.
attr_reader :special_notes, :prep_time
def initialize(special_notes:, prep_time:)
@special_notes = special_notes
@prep_time = prep_time
And, to my eyes, this is cleaner and easier to read.
Pithy is better
There is a persistent criticism of the Java programming language about the names of objects that tend to surface in applications written in Java. It’s a bit overblown, but fair. Long, complicated names are more difficult to read. Additional words or characters can slow reading down. Descriptive names are good, but complicated names can get in the way of reading.
Acronyms are the worst kind of jargon. They are opaque, lack description, and often are presented with no context. People use them because highly technical terms can be cumbersome to say over and over again. However, we are writing code to be read over and over by multiple types of people. So, we should optimize for clarity and understanding, instead of ease of writing.
Give it a night’s sleep, then move on
Sometimes you’ll have to put in a name you don’t love. Brainstorm with your pair or team to come up with a better name, but give it a time limit. If you haven’t come up with a good name after 5 minutes, go for a walk, grab some coffee, and see what happens.
Good, Awkward Names
Sometimes, names are awkward. These names, at first glance, can seem to be less clear because of the awkwardness of the name. However, we need to place the names in context and ask if they are doing their job: conveying appropriate levels of meaning to the reader.
Sometimes, awkward names are like that because the code is awkward. It isn’t straightforward and represents a complex concept. Having names that accurately portray that can be difficult to come up with. An example from our codebase is a repository of courier jobs where we need to fetch jobs that are in a “ready” state, but have not been assigned or acted on in any way.
We racked our brains for a bit on this one, and we came up with an awkward name: auspicious_jobs_for_fulfillment(fulfillment). Under normal conditions, the word auspicious would seem to be out of place and awkward. But, it accurately represents the concept and signals to the code reader that there is something interesting about this logic than what you might be expecting, so dig in more and make sure you understand the implications of using this method.
An alternative name could have been something like “new_jobs_for_fulfillment” or “ready_jobs_for_fulfillment” but those would only capture a subset of the meaning, and potentially lead future developers astray. New Jobs, Ready Jobs, and Confirmed Jobs all have a specific meaning, but they all mean “Haven’t been assigned to a courier yet” too, and so the umbrella term of auspicious, though awkward, accurately captures the meaning.
Good Names Mean Higher Productivity
All of these guidelines and suggestions add up to one thing: a faster team. When your team is able to effectively communicate high level concepts with ease, they can solve problems quicker, not have to reset and make sure everyone is on the same page, can read and understand code faster, and identify issues in a shorter amount of time.
Building good naming into your engineering practice can make a huge impact on you and your team. It takes five minutes to get right; on those occasions it takes longer, it’s because the concept is that much more difficult to describe and deserves the attention. Nailing the name in the beginning will pay dividends later.
Looping back to where to started: how would we approach this differently? What would the impact be? Well, we’d first recognize that when we created a User, it was meant to be a “Diner” (customers who order food on Caviar). If we had named the object Diner at the start, then we’d run into some interesting conversations.
When we introduced roles and admin privileges, it would have been odd to attach those to a Diner object. But, since we had the User object, we didn’t feel awkward about it. So onto User those features went. When couriers needed a login, we didn’t feel weird about having Courier credentials and information on the User object, since Couriers are clearly Users. So, on to that object those features went. And so on.
Instead, by naming User -> Diner from the start, we’d have avoided a bunch of conflating and complection of our code, created more isolatable concepts, and set us up for better architecture in the long run. We’ve since spent some time deconstructing the User object, but we still have a way to go. If you can avoid these mistakes in the beginning, you can avoid a bunch of work down the road to disentangle your concepts and names.
HotDog is-a Sandwich
Yeah, it’s awkward. But it’s correct. At least for this context, at this time, with this team. We should continue to talk about it and make sure the naming works for us as time goes on. It is possible (though, let’s be honest, unlikely) that hot dogs will not be sandwiches in the future. That might be because we choose some different definition for a sandwich.
Naming is hard. These conversations are never complete. But they’re so important, and teams can use them to keep improving productivity. If you’re having highly efficient conversations where everyone understands complex concepts with a small number of words, you can accelerate how fast you reach a solution.