One of the most important things to pay attention to when writing software is how we name our symbols. Both data and behavior should be named in a way that represents the essence of what we're trying to do.
Naming not only affects readability and understandability, but it also has a dramatic impact on how we design and abstract a problem. Driving from the original intent by the user all the way down into the names of classes and methods helps us maintain consistency in our thinking and build better software.
Names also reflect code quality. A good name describes the single responsibility a method or variable has. If we can't name something based on its purpose, it often indicates that either we're unclear on its function or it's doing too much.
Naming Classes and Methods
The two general things that we can name in software are classes and methods; think of them as nouns and verbs. Classes are generally stated as nouns and their names should reflect their intent, purpose, or reason for existing. The single responsibility principle tells us that a class should have only one reason to exist. The name of the class should reflect that reason.
Naming also offers metaphors that help us model a solution to a problem. A class represents either a tangible object (e.g., an insurance policy) or a nonphysical thing(e.g., a relationship) that we can name. But the class is not the thing itself; it represents the thing only in the way it behaves. Class name should accurately represent the capabilities a class has, not a more general item it may not fully represent. Misnaming classes create subtle misunderstandings that can result in incorrect design.
Classes can contain methods, which are behaviors that the class performs or, more accurately, behaviors that objects, once instantiated from classes, perform. Because methods are behaviors, they’re generally expressed as verbs. Method names should be stated in the active voice and clearly say what they do. Some methods return specific pieces of information, such as other objects, or answers to questions, like “true” or “false.” Use names that reflect the method’s behavior.
For example, if you wanted to have a behavior determine whether a customer exists within a particular collection of customers, then you could have a method called isCustomer(name) that would return “true” or “false,” depending on whether there was a customer with that particular name in that collection. I often use a name with the word "is" as a prefix on methods that return true or false, especially if it is called from a conditional statement.
When we name a method, we always want to name it from the perspective of the one calling it to do something. This makes it far easier for the user of methods to get their desired results and properly design a solution to a problem. The caller of a method has the conceptual perspective; they are interested in getting something done. The method being called has the specification perspective; they are interested in exposing a service through delegating to other services.
When we first think about defining a method, we want to think about what it should do and how we would like to call it. But we must think about it from the perspective of the outside looking in. A method has a signature, which includes its name and a list of parameters that it takes, as well as a value it returns. Parameters and return values are either primitive data types—such as integers, Boolean values, etc.—or other objects.
Your compiler has the ability to type-check that method signatures are accurate. For example, the Java compiler will verify if you pass a parameter that you say is going to be an integer but really pass in a character, and will give you a warning if a method is being passed incorrect types of information.
But while compilers can help verify that a program is syntactically correct, they cannot check semantics—whether your metaphors are used accurately. It is the programmer's responsibility to make sure that the code makes sense and does what it's supposed to do. Just because a program compiles does not mean it does what you meant for it to do!
We want to name things in ways that make their purpose clear. We don't want our code to be difficult for others to understand, so it is good to name things in an accurate, consistent manner. Naming methods for what they do and from the caller’s perspective makes for a more clearly defined behavior.
The Importance of Refactoring
But designs must change as needs change, and when that happens, it also means that the names of our symbols may need to change as well. As our designs evolve during agile sprints and iterations, the names that we use for classes and methods will also need to change to reflect their changing purpose.
Rename is my most used refactoring technique because I change the name of my methods all the time when I’m coding. As I get clearer on exactly what my methods need to do, I continue to reflect that by changing my method names to be more accurate. The names that we use are our primary means of communicating our design in the code, so when my design changes, so do many of my names.
I often find opportunities for better names when I refactor my code, too. This is one of the reasons I strongly advocate refactoring code. There are some things we can see better in hindsight, and the purpose of our classes, methods, and variables is one of them. For my internal code that only I call, I’ll typically take the opportunity to refactor and improve a name as soon as I see the need. Many IDEs like Eclipse, Visual Studio, etc., offer several automated refactoring options, including rename. When you refactor to rename a method, for example, all the calls to that method from everywhere in that program will also be renamed. This is an enormous time-saver.
Refactoring also represents the opportunity to restructure our code and make it clearer. Long methods should be reviewed critically, and opportunities to extract and name bits of functionality should be taken. We should always prefer wrapping a behavior in a well-named method rather than keeping that behavior in a larger method with a block comment around it. Long methods can hide smaller methods and even entire classes.
As “Uncle Bob” Martin says regarding long methods, “Extract until you can extract no more.” In other words, pull smaller methods out of longer methods as much as possible. You may discover missing entities from your domain model. I’ll extract any behavior from a long method that I can give a name, even if it is only one or two lines long, as it helps describe what the code is doing more clearly.
Doing the above refactoring means we eventually end up with lots of little methods that wrap bits of functionality. For those methods not exposed to the outside world, we can make these methods private because they are probably only going to be called from the one place they were extracted from. This helps keep the external view of our code clean and uncomplicated. As most just-in-time compilers optimize these indirect calls away, there’s no performance hit when structuring code in small methods.
Code is just bits and bytes. The meaning in code is the meaning we give it, and the way we represent that meaning is with our words. We want to make sure the meaning of our code is conveyed clearly so that it’s straightforward to work with in the future. Use names that clearly communicate your intentions, and refactor those names when your intentions change.