Some Notes from Software Complexity: The Art of Naming - Hackernoon
First, Naming things is hard. No, Naming things is simple, but Conveying meaning may be hard, but this is a learned skill by practice. Read a bunch, the more you expand your vocabulary the more you’ll gain understanding in conveying meaning.
Corollary 1: Container name is a function of its elements
Does it have a bed? Then it’s a bedroom.
Now, the opposite is also true: based on the container name we can infer its components. If we were talking about a bedroom it would very likely that it had a bed.
Corollary 2: we can infer components based on the container name.
Corollary 3: the clarity with which a container is defined is proportional to how closely related its components are.
The problem here lies not in the amount of objects in the same room, but that completely unrelated things are being treated as if they had the same functions. At home, we put together things that have the same concern, purpose and intent and that makes organizing easier, whereas by messing with these responsibilities we cannot be sure what the architects wanted or how these objects are meant to be used. By messing, we block flow.
When components are related, it’s easier to find a good name.
When things are unrelated, it becomes increasingly difficult.
Our understanding is correlative to our perception.
Example: HTTP domain and a car
public interface WhatIsAGoodNameForThis {
/* methods for a car */
public void gas();
public void brake();
/* methods for an HTTP client */
public Response makeGetRequest(String param);
}
Example: names guiding design
class PostAlerter
def notify\_post\_users
def notify\_group\_summary
def notify\_non\_pm\_users
def create_notification
def unread_posts
def unread_count
def group_stats
end
The name PostAlerter suggests functionalities that alert someone about a post. However, unread_posts, unread_count and group_stats clearly deal with something else, making this class name not ideal for what it does. Moving those three methods to a class called PostsStatistics would make matters clearer and more predictable for newcomers.
class PostAlerter
def notify_post_users
def notify_group_summary
def notify_non_pm_users
def create_notification
end
class PostsStatistics
def unread_posts
def unread_count
def group_stats
end
Method 1: Break Apart
When to use: You cannot find a good name for a class or component, but you already have isolated concepts and want to find good names for their groupings.
It consists in two steps:
- identify the concepts we have
- break them apart
In the toilet + bed scenario, we pull apart each different thing we can identify by pushing bed to the left, toilet to the right. Ok, now we have two separate things that we can finally reason about in a natural way.
When you cannot find a good name for something, you probably have more than one thing in front of you. And, as you know by now, naming multiple things is hard. When in trouble, try to identify what pieces and actions compose what you have.
Example
We have an unnamed class containing a request
, response
, headers
, URLs
, body
, caching
and timeout
. Pulling all those apart from this main class, we’re left with the components Request
, Response
, Headers
, URLs
, ResponseBody
, Cache
, Timeout
and so on. If all we had was the name of these classes, we could fairly sure assume we were dealing with a web request. A good candidate for a web request component is HTTPClient
.
When the code is hard, don’t think about the whole first. Don’t. Think about the parts.
Method 2: Discover New Concepts
When to apply: when a class is not simple or coherent.
Discovering new concepts requires knowledge of the business domain. When software applies the same nomenclatures as the business, an ubiquitous language is established. one that allows professionals from different areas of expertise to speak the same idioms.
Example: Encapsulating components into one new concept
This marketplace ecommerce charged students with different rules in different countries with multiple payment gateways. Requirements were fairly complex. When I saw the charge code, PaymentGateway
, I was shocked at how complicated it was, with several dependencies, including: User
, UserAddress
, CreditCard
, BillingAddress
, SellerAddress
, LineItems
, Discounts
, and more. Its constructor was gigantic and this complexity made it hard to add new rules, as touching one thing broke others, as well as requiring us to change all gateway adapters.
Here I am, with these details about things I need you (the PaymentGateway) to charge for me. If this was a desk, I’d have these papers organized and I’d probably call them Invoices. So what if I created one class called Invoice, which is nothing more than the aggregation of all these other details, such that the gateway doesn’t need to know how those rules are done because Invoice will? Instead of injecting a million objects, I just hand one over to you?
The Invoice term wasn’t used anywhere. We spent a month refactoring on the side and once we were done, we were able to change the software much more swiftly.
Invoice is a good example of concept that is but an aggregation of data from many sources and the majority knows what it is. The final solution added the Invoice class which alone was injected into the gateways, serving as a facade pattern and hiding other classes.
Good naming is not just about writing beautiful words, but also writing precisely what needs to be expressed that wasn’t before.
Example: Pivoting names based on the business domain
Example: Generalization
A long ago, a CMS had database tables news
, history
, videos
, articles
, pages
and other. Most of them had the same columns, title
, summary
, text
. videos
table had extra attributes such as url
(to embed YouTube) and history
had a date
attribute so the page would show a list of historic events by year. All these tables looked like copies, with a few differences here and there, and adding new functionality required rewriting a lot of boilerplate all over again.
I collapsed all those tables into one called contents
with a foreign key pointing to a table called sections
, which had the list of sections such as news, history, videos and others. Now, one code for contents was enough. Once the forms for managing content were done, it took 1/N of the time it would normally take to implement anything because for every new section of the same type, it was already done.
Generalizing by giving it another name enables great productivity. News is a Content. Article is a Content. History is a Content. Can all of these share the same attributes? Yes. Survey? No, not a Content.
Method 3: Criteria For Grouping
When to use: when names are good but they don’t fit well with each other.
Components can be grouped by a variety of criteria, including physical nature, economic, emotional, social and, the most used in software, functional.
Example: Couch and TV stay in the same room, grouped together based on a functional criteria, as they both have the same function or purpose, to provide leisure.
In software, we tend to group components by function. List your project files and you will probably see something like controllers/
, models/
, adapters/
, templates/
and so on. However, some times these groups will not feel comfortable and that will be the perfect time to reevaluate the structure of your modules.
Leveraging Contexts
Every app has a different context, and every module within it, every class within them, down to every function. The word User alone could mean user of the system, but also perhaps a database table, or a 3rd-party service credential. lib/billing/user
differs from lib/booking/user
, but they’re still user.
Imagine that every container, such as a module, is a bucket. Within them, components are insulated from the outer world. You’re free to name those classes whatever you want. It frees the mind from having to find esoteric names for common things.
Example: Namespaces
Adwords::Ad
, Facebook::Ad
, Bing::Ad
, RemoteAdService::Ad
- Interface between those classes.
Database::Ad
- this is the ORM for the ads table.
GUI::Ad
, API::Ad
Words can mean different things depending on context, and when we leverage context, we can choose simpler words for components.