Home   QGraph

The Name Fail

How misnaming a class hurt us royally.

Importance of meaningful names

Naming things properly is important. Since human brain can hold limited information at one time, it is important to minimize the difference between what an entity is called and its functionality. A popular mind game has the words GREEN, RED, and YELLOW, written in red, yellow and green respectively, and the challenge is to speak out the colors (and not the words) as fast as you can.

Choosing meaningful names is not that hard

Choosing meaningful name is actually not that hard. Of course number of items will be denoted by nItems, and total height will be denoted by totalHeight. A function to compute the sum will be called computeSum() and a function to check for alerts will be called as checkForAlerts.

Keeping the names meaningful is hard

What is hard is that the names continue to be meaningful. It is hard for the names to keep up with change in the functionality of the underlying entity. Let's say you have a function which sends messages. You begin by call it sendMessages(). Do you update the name when the function starts to update some stats? Do you update the name when the function modifies the message a little before sending to the recipients? How about the scenario when the function validates the message before sending it and decides not to send it if validation fails?

Functionality overload creeps in in small bits, with each bit being innocuous enough to not warrant a change in the name of the variable (which may involve changes across mulitple files). This results in gradual accmulation of technical debt.

How a misnamed class led to an embarrassing bug

At QGraph, we have a concept of "campaign", which can of several types: "regular campaign", "trigger campaign" and "inapp campaign". (You do not need to know the details what the campaigns are). They were implemented in the classes RegularCampaign, TriggerCamapign and InappCamapaign. Each of these three classes inherits from a base class called Campaign. Of these, the regular campaign needed a "progress updater", while the others did not.

For the sake of uniformity, we decided that all campaign would use a progress updater, which will be passed to as an argument in the constructor of campaign classes and through there to the constructor of class Campaign. However, regular campaign would use an instance of class ProgressUpdater, while trigger and inapp campaigns would use an instance of class NullProgressUpdater. NullProgressUpdater would have the same functions exposed as the regular ProgressUpdater, but would do nothing. That way, the functionality of updating progress could reside in the base class Campaign and all individual campaign classes could continue to inherit from Campaign.

This design of having two updaters ProgressUpdater and NullProgressUpdater served us very well. We were also able use NullProgressUpdater for writing test cases for various campaigns.

As time passed, more functionalities were added to ProgressUpdater. It so turned out that ProgressUpdater was the only class in whole code flow that was updating the database, and thus developers started to use ProgressUpdater for updating the database with various statistics and metadata. All this seemed logical too: we were happy that there was a single gateway to the databse, and all the updates anyway seemed related, if only tangentially, to "progress".

It so happened that once there was a progress updater related requirement which was required by all campaigns, not just regular campaigns. And here the developer did what he should not have done: he added that functionality to both ProgressUpdater and NullProgressUpdater. That was the easiest thing to do, though not the correct thing.

Why is that bad? This is bad because now the name "NullProgressUpdater" is no longer consistent with the fact that this progress updater is actually doing some work.

We could have introduced another progress updater, say MinimalProgressUpdater, which would have been used by campaigns other than RegularCampaign i.e. TriggerCampaign and InappCampaign. NullCampaign

After a few more months, there was another requirement which fit well in putting the code in ProgressUpdater. However, the requirement was so unrelated to "progress update" that we finally decided to change the name "progress updater" to more general "stats updater". Thus ProgressUpdater was to be renamed to StatsUpdater and NullProgressUpdater was to be renamed to NullStatsUpdater. However, the developer, rather than renaming the file null_progress_udpater.py to null_stats_updater.py decided to copy stats_updater.py to null_stats_updater.py and then modify all the functions to no-op. He assumed that since it is null stats updater, all the function calls should be dummy.

Thus, NullProgressUpdater went back to being true to its name. This resulted in a bug in our code which was uncovered only around after a month when a client complained of obviously incorrect results. Only then we then introduced the new stats updater, called TriggerStatsUpdater.

It requires discipline and good judgement to decide when to make the extra effort of refactoring while implementing an added functionality. We lacked the good judgement in above case.

No silver bullet

Unfortunately, just like car accidents, there is no way to avoid the bad judgement calls. The best we can do is to adopt best practices and be disciplined. And just as we expect car accidents to go down with self driving cards, coding utopia will be achieved when computers start writing code.