Why is it good programming practice to use meaningful identifier names for variables and constants?

This article covers tips and tricks on what is, why and how to write clean code.

I started coding some years ago, diving into a lot of different languages eager to learn more. At some point, I felt that I was just learning to switch between languages and not really improving my skills as a coder.

I decided to learn how to be a better coder and a friend recommended me to read this great book “Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin.

Reading this book really enriched my knowledge on how to write better and cleaner code and I vividly recommend it to everyone willing to improve.

This article sums up some basic topics that I like to remember and I would like to share with you guys, but if you actually want to dig deeper you should really read the book.

“You know you are working on clean code when each routine you read turns out to be pretty much what you expected. You can call it beautiful code when the code also makes it look like the language was made for the problem.”

— Ward Cunningham

This statement pretty much nailed it. To write clean code, you need to understand what clean code is. This is very debatable and vague because it depends on an individual perspective when it comes to specifics, but… “we are authors” and people read our code, that isn’t debatable. So, every time we write a line of code, we should remember to write it for the readers and these wise words align with this premise.

The ratio of time spent reading vs writing is well over 10:1, to write code we are constantly reading it. So even if the code is harder to write we should invest in readable code vs easier code to write.

While reading the article you’ll feel that it’s impossible to follow this strictly, and you’re right. You can’t possibly do clean code every time. But we should always give our best when writing code. Also, consider that, clean code can also be bad code. Code is good when it adds value to the business, the most perfect and clean code that doesn’t add value, is also, bad code.

Now let’s get our hands dirty.

Meaningful names

How meaningful ?

The name of a variable, function, or class, should answer all the big questions. It should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent.

Use intention revealing names

int d; // elapsed time in days

vs

int elapsedTimeInDays;

Beware of using names which vary in small ways

Read XYZControllerForEfficientHandlingOfStrings and now XYZControllerForEfficientStorageOfStrings.

How long it took to spot the difference between both? Also, if you’re in a nice IDE with search and autocompletion and stuff, this rule improves the way you search.

Use pronounceable names

class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
}

vs

class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
}

Use searchable names

If a variable or constant might be seen or used in multiple places in a body of code, it is imperative to give it a search-friendly name.

for [int j = 0; j < 34; j++] {
s += [t[j] * 4] / 5;
}

vs

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for [int = 0; j < NUMBER_OF_TASKS; j++] {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = [realdays / WORK_DAYS_PER_WEEK];
sum += realTaskWeeks;
}

Sum above is not really useful but at least is searchable. The intentionally named code makes the function longer, but check how easier it will be to find WORKS_DAYS_PER_WEEK than to find all the places where 5 was used.

Class names

Classes and objects should have noun or noun phrase names like Customer, WikiPage, Account, and AddressParser. Avoid words like Manager, Processor, Data, or Info in the name of a class. A class name should not be a verb.

Method Names

Methods should have verb or verb phrase names like postPayment, deletePage, or save.

When constructors are overloaded, use static factory methods with names that describe the arguments.

Complex fulcrumPoint = new Complex[23.0]

vs

Complex fulcrumPoint = Complex.FromRealNumber[23.0];

Pick one word per concept

This one is super important. When starting to have a really big code base, you and your team might start failing to be consistent in the concepts. That might end up having a fetch, retrieve, and get as equivalent methods of different classes.

Imagine you use fetch to call an api that populates your object and get to just return the value of some property. What if you start confusing the two ? You’ll need to always check what is the behaviour of that specific method if you don’t stick with the concept.

Pick one word for one abstract concept and stick with it.

Functions

public static String renderPageWithSetupAndTeardowns[
PageData pageData, boolean isSuite
] throws Exception {
boolean isTestPage = pageData.hasAttribute["Test"];
if [isTestPage] {
WikiPage testPage = pageData.getWikiPage[];
StringBuffer newPageContent = new StringBuffer[];
includeSetupPages[testPage, newPageContent, isSuite];
newPageContent.append[pageData.getContent[]];
includeTeardownPages[testPage, newPageContent, isSuite];
pageData.setContent[newPageContent.toString[]];
}

return pageData.getHtml[];
}

Is this function short ?

“The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that”

Refactored version

public static String renderPageWithSetupAndTeardowns[
PageData pageData, boolean isSuite
] throws Exception {
boolean isTestPage = pageData.hasAttribute["Test"];
if [isTestPage[pageData]] {
includeSetupAndTeardownPages[pageData, isSuite];
}

return pageData.getHtml[];
}

Do one thing

This implies that the blocks within if statements, else statements, while statements, and so on should be one line long. Probably that line should be a function call. Not only does this keep the enclosing function small, but it also adds documentary value because the function called within the block can have a nicely descriptive name.

This also implies that functions should not be large enough to hold nested structures. Therefore, the indent level of a function should not be greater than one or two. This, of course, makes the functions easier to read and understand.

But how do you know if the function is doing one thing or three? You need to check the level of abstraction below the stated name of the function. Here’s the trick, describe the function with a brief TO paragraph.

TO RenderPageWithSetupsAndTeardowns, we check to see whether the page is a test page and if so, we include the setups and teardowns. In either case we render the page in HTML.

If a function does only those steps that are one level below the stated name of the function, then the function is doing one thing. After all, the reason we write functions is to decompose a larger concept [in other words, the name of the function] into a set of steps at the next level of abstraction.

The stepdown rule

This rule implies that we read code from top to bottom. We want to read code and have different level of abstractions close. We want to read it as a set of TO paragraphs each of which is describing the current level of abstraction and referencing subsequent TO paragraphs at the next level down.

To include the setups and teardowns, we include setups, then we include the test page content, and then we include the teardowns.

To include the setups, we include the suite setup if this is a suite, then we include the regular setup.

To include the suite setup, we search the parent hierarchy for the “SuiteSetUp” page and add an include statement with the path of that page.

To search the parent. . .

So in code the order should be something like this

includeSetupAndTeardownPages[];
includeSetup[];
includeSuiteSetup[]
searchParent[];
...
includeRegularSetup[];

Switch statements

We can never get rid of them, they are ugly, and make functions big. But we can make sure that each switch statement is buried in a low-level class and is never repeated. We do this, of course, with polymorphism.

public Money calculatePay[Employee e]
throws InvalidEmployeeType {
switch [e.type] {
case COMMISSIONED:
return calculateCommissionedPay[e];
case HOURLY:
return calculateHourlyPay[e];
default:
throw new InvalidEmployeeType[e.type];
}
}

There are several problems with this function. First, it’s large, and when new employee types are added, it will grow. Second, it very clearly does more than one thing. Third, it violates the Single Responsibility Principle [SRP] because there is more than one reason for it to change. Fourth, it violates the Open Closed Principle [OCP] because it must change whenever new types are added.

The solution for this is to abstract it and hide it using an ABSTRACT FACTORY.

public abstract class Employee {
public abstract boolean isPayDay[];
public abstract Money calculatePay[];
public abstract void deliverPay[Money pay];
}
-----------
public interface EmployeeFactory {
public Employee makeEmployee[EmployeeRecord r] throws InvalidEmployeeType;
-----------
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee[EmployeeRecord r] throws InvalidEmployeeType {
switch [r.type] {
case COMMISSIONED:
return new CommissionedEmployee[r];
case HOURLY:
return new HourlyEmployee[r];
default:
throw new InvalidEmployeeType[r.type];
}
}
}

Function arguments

The ideal number of arguments for a function is zero [niladic]. Next comes one [monadic], followed closely by two [dyadic]. Three arguments [triadic] should be avoided where possible. More than three [polyadic] requires very special justification — and then shouldn’t be used anyway.

Some common use cases below

Monadic example

There are two very common reasons to pass a single argument into a function. You may be asking a question about that argument, as in boolean fileExists[“MyFile”].

Dyadic example

A function with two arguments is harder to understand than a monadic function. For example, writeField[name] is easier to understand than writeField[output-Stream, name]. Though the meaning of both is clear, the first glides past the eye, easily depositing its meaning. The second requires a short pause until we learn to ignore the first parameter. And that, of course, eventually results in problems because we should never ignore any part of code. The parts we ignore are where the bugs will hide.

There are times, of course, where two arguments are appropriate. For example, Point p = new Point[0,0]; is perfectly reasonable. Cartesian points naturally take two arguments. Indeed, we’d be very surprised to see new Point[0]. However, the two arguments in this case are ordered components of a single value!Whereas output-Stream and name have neither a natural cohesion, nor a natural ordering.

Even obvious dyadic functions like assertEquals[expected, actual] are problematic. How many times have you put the actual where the expected arguments have no natural ordering. The expected,actual ordering is a convention that requires practice to learn.

Argument objects

When a function seems to need more than two or three arguments, it is likely that some of those arguments can be wrapped into a class of their own. Consider, for example, the difference between the two following declarations:

Circle makeCircle[double x, double y, double radius];

vs

Circle makeCircle[Point center, double radius];

Have no side effects

Side effects are lies. Your function promises to do one thing, but it also does other hidden things. Sometimes it will make unexpected changes to the variables of its own class or do unexpected behaviour. Sometimes it will make them to the parameters passed into the function or to system glo- bals. In either case they are devious and damaging mistruths that often result in strange temporal couplings and order dependencies, or hide some business logic you are missing.

Command/Query separation

Functions should either do something or answer something, but not both.

We end this article here, but there’s so much more. In the next article we’ll see some topics that help us join the dots and how to organize our classes.

Please note that all examples are from the book, consider this some personal topics I like to keep in mind.

Why is it good coding practice to give variables meaningful names?

Best Practices For Naming Objects Keep object names short: this makes them easier to read when scanning through code. Use meaningful names: A meaningful or expressive variable name will be eaiser for someone to understand.

Why is it important to understand the importance of constants and variables in programming?

A program can contain many variables and constants, so it is important to give them sensible names that try to describe the item of data that they hold. The key difference between a variable and a constant is: The value stored in a variable can/may change during the running of the program.

How are meaningful identifiers useful?

Answer. Meaningful identifiers means the names of variables, programs, file names, picture names function and procedure names , class names and object names. These names are all given in such a way that one understands the program easily.

Chủ Đề