Small changes, Big wins: How little tweaks make your code easy on the eye
When we think of clean code, our brain jumps to SOLID principles, short classes, no comments, and good design patterns. While all of those apply, they are useless without the simplest thing of all: good naming.
I can spend 2 days writing a service that respects all SOLID principles, unit-testable, easy to change, but understood by nobody by me. Worse, even I wouldn’t be able to understand it in 6 months.
How is that possible?
It’s the reason you are reading this article: Bad naming.
As developers, we spend more time reading code than writing it. That’s a fact. The clarity of our codebase is critical to our efficiency. At the heart of this clarity, we can find naming.
Selecting the right names for variables, functions, and classes can mean the difference between code that’s a pleasure to read and code that feels like punishment.
This article will showcase some examples that I’ve seen in my career and will suggest some alternative naming. Also, to keep the article “G-rated” I will not include my thoughts while trying to figure out the true meaning of some names.
1. Variables: Clarity over Brevity
There was a time when space was such a big issue that long variable names were a problem. Fortunately, it is not the case anymore. Just take a look at these variables and see if you can guess what they hold without looking at the suggestion:
Bad
int d = 10;
string n = "John";
double h = 180.5;
int nr = 100;
Good
int days = 10;
string name = "John";
double height = 180.5;
int employeeCount = 100;
It might look like a silly example but I can’t say how many times I saw functions which were having input parameters named with a single or two letters. Unless you are deliberately trying to be as cryptic as possible, please use full nouns.
Remember, code is read more often than written, so prioritize readability over the convenience of typing fewer characters.
Note: be careful to not overdo it. I once saw a variable named like this:
double balanceOfAccountAfterPaymentButBeforeConfirmation = 500.5;
Although it describes in detail what it holds, look at this:
double minimumAccountBalanceToHaveInCheckingAccount = 1.0;
double balanceOfAccountAfterPaymentRequestIsProcessed = GetAccountBalance() - GetPaymentRequest();
if (balanceOfAccountAfterPaymentRequestIsProcessed > minimumAccountBalanceToHaveInCheckingAccount)
{
// do some operations
}
A normal “if” instruction becomes unnecessarily hard to understand. Imagine classes written like this. I would simply get lost.
I would write it like this:
double minimumBalance = 1.0;
double postPaymentBalance = GetAccountBalance() - GetPaymentRequest();
if (postPaymentBalance > minimumBalance)
{
// do some operations
}
2. Functions: Say What You Do
Whenever I create a new function I try to follow a few rules:
- no abbreviations
- no more than one verb in a function name
- no bogus words, unless they are well-understood business domain names. For example, in the context of a pharma app, blind means something different than in the context of a poker app
Bad
1.
void s(string message) {...}
// yes, I saw this in a class named Email.
2.
void UpdateDB() {...}
// without extra context, seeing this method I would
// expect a parameter in the definition so it updates something.
3.
double Calculate(Point a, Point b) {...}
// what it calculates for those points?
4.
int GetUserIdByEmailAddress(string email) {...}
// function is clear, but is it really necessary to specify in the name
// something that can be understood from the parameter name?
5.
string GetDirectionsAndSaveStatistics(Point source, Point destination, int userId) {...}
// something seems wrong, but not sure what
Good
1.
void SendMessage(string message) {...} // clear intent
2.
void SaveChangesInDB() {...}
// now it is clear we want to commit changes to DB
3.
double CalculateDistance(Point a, Point b) {...}
// clear as day from the name what the function will do.
// NOTE: here the input parameters are one letter long because
// naming points would be pointless (pun intended)
4.
int GetUserId(string email) {...}
// simple
5.
string GetDirections(Point source, Point destination) {...}
void SaveStatistics(int userId) {...}
// sometimes you don't have a choice but to break a function in 2.
// try to keep functions focused on doing one thing: a query or a command
3. Boolean Variables: Ask a question
Booleans are special. They can hold only 2 values. They are basically a yes/no but in a mathematical form.
For this reason, it is best to name booleans to sounds like a yes/no question.
Bad
1.
bool flag = DateTime.Now.Hour > store.OpeningHour
&& DateTime.Now.Hour < store.ClosingHour;
// I can infer from the logic
2.
bool visibility = circle.radius == 0;
// reduced visibility? increased? invisible?
3.
bool isNotEnabled = formElement.Enable == false;
// is a yes/no question, but is it easy to follow?
4.
bool isOneServiceActive = Service.Any(service => service.isOnline);
// the worst one of all, the name is a lie.
// I might think that only ONE service is active based on the name
// when the value looks to see if at least one service is active.
Good
1.
bool isOpen;
// so much nicer and simpler to use in "if" statements
2.
bool isVisible;
// clear and concise
3.
bool isEnabled;
// yes, I just removed the not.
// In my junior days, I once named something IsDisabled. While it might sound
// good, it is a negation in a fancier form.
// If isDisabled == false => the element is enabled
4.
bool isAnyServiceAvailable;
// rephrased to a more natural language.
// also replaces Active for Available as it is more natural for a service to
// be available, rather than active
4. Classes and Objects: Use Nouns, Avoid Generic Terms
Classes and objects should be named after what they represent — usually nouns. Avoid generic and vague terms as they are often a sign of an ill-defined class responsibility.
Generic terms:
- Manager: by far the most abused word. It might have had some meaning a long time ago, but now I would avoid it entirely. It tells you nothing
class SessionManager{}
// replace with
class SessionTracker{}
class MemoryManager {}
// replace with
class MemoryAllocator {}
class MemoryPool {}
- Helper: like FileHelper, RepositoryHelper, ConnectionHelper. These classes tend to attract all methods for which developers were too lazy to think of a proper place
- Utils: Helper classes but with a different name. The problem with naming isn’t the word itself. Replacing RepositoryHelper with RepositoryUtils doesn’t solve anything. The problem isn’t the name, but what the class represents: a catch-all. The need to use “utils” or “helper” is just a red flag that maybe you should rethink your code, not that you should look for a synonym.
- Processor: this is just a fancy way of saying “service”. Instead of using “processor”, try to explain what it processes.
1.
class FileProceesor {}
// replace with
class FileReader{} // or
class FileWriter{}
2.
class ImageProcessor{}
// replace with
class ImageEditor{} // or
class ImageArchiver{}
3.
class JobProcessor{}
// replace with
class JobExecuter {} // or
class TaskRunner {}
Instead, try to give your classes more specific, meaningful names that clearly express their purpose and functionality.
5. Avoid Disinformation
Maybe the worst one of all is when a variable/function looks well named but it refers to something different.
In my experience, this is mostly seen in the function header variable names. No, I don’t think that the developers were malicious. I think they didn’t care or thought that it might have an impact.
Why do I say this? Most times developers start writing a function, then some change comes in, they update the function to meet the new requirement, they say to just do a test and then they’ll come back to do the refactoring.
In my experience, very few do come back for the refactoring. So unless the mistake is caught during code review, it has every chance to remain like that.
Bad
1.
public void ProcessData(List<UserId> numbers) {...}
// 'numbers' is misleading if the list contains Ids
2.
public User GetUser(string user)
{
var user = Database.GetUserByName(user);
if(user == null)
Database.CreateUser(user);
return user;
}
// this one is nasty, if you don't look in the implementation you don't
// realize that it performs mutations in db
3.
public void Delete(int userId) {...}
// I could assume everything is ok here, but what if I will tell you that the
// user will not be deleted, but marked inactive
Good
1.
public void ProcessData(List<int> userIds) {...}
// now we know what the function expects from definition
2.
public void CreateUser(string username) {...}
public User GetUser(string username) {...}
// split in 2 functions: one for update, one for delete
3.
public void MarkAsInactive(int userId) {...}
// a simple rename will remove any confusion
If you would take away one thing from this point is to avoid having “getter” functions that perform actions. When we run a “select” statement in the database, we don’t worry that it might send an email, drop something from a db or alter some data in any way.
“Getter” functions should be able to be executed 1 million times and return the same thing. So please, never perform updates in methods that sound like will only retrieve something.
Key Takeaways
- Clarity over brevity: Names should be descriptive and specific, even if it means they’re a bit longer.
- Use standard conventions: Follow the naming conventions of your programming language.
- Avoid disinformation: Don’t use names that might mislead the reader.
- Reflect the data type or entity: A name should reflect the data type (for variables) or the entity it represents (for classes).
- Avoid generic terms in class names: Use specific names that clearly express a class’s purpose and responsibility.
- Avoid negative naming: avoid naming things with no, not, never
Remember, the art of naming is not about adhering to rules but about making code easier to read and understand. If you are unsure on how to name something, ask for help. We don’t always have the best idea, but discussing it with someone is almost guaranteed to lead you to a better name.
And if you feel like fighting on your own, use tools like ChatGPT and GoogleBard. Explain your problem and ask for better naming. They might not offer the best response, but they will give you some ideas to explore.
In the words of Phil Karlton: “There are only two hard things in computer science: cache invalidation and naming things.” But with conscious effort, the latter becomes an easier task with each line of code you write.
Happy coding!