© Karl Beecher 2018
Karl BeecherBad Programming Practices 101https://doi.org/10.1007/978-1-4842-3411-2_3

3. Variables

Karl Beecher1 
(1)
Berlin, Germany
 

Objectives

In this chapter, you’ll learn:
  • The chaos that poorly named variables can induce

  • How to do variable declaration in a confusing way

  • Why being lax with the scope of variables causes problems

  • How to abuse the type system of a language

  • The power of null to cause trouble

Prerequisites

Before reading this chapter, make sure you’re familiar with basic types, like integers, strings, and collections.

Introduction

Where would programmers be without variables? Without them, there would be no way to record and keep track of information as a program goes about its business.

As variables are such a fundamental feature of programming, it should delight you to know that there exist numerous easy ways to misuse them. This chapter will cover some of the most notorious and readily visible bad practices.

Use Obscure Names —Thinking Up Meaningful Labels Isn’t Worth the Effort

There are only Three hard problems in Computer Science: cache invalidation, naming things, and off-by-one errors.

— Phil Karlton (Source: The Internets)

The preceding quote, one that’s widely appreciated among programmers, should fill you with a wonderful feeling of foreboding. If software folk find it so hard to name things, getting it wrong should be easy.

All Meaningless

In most programming languages, variable names can typically reach whatever length the programmer desires. But why waste your time typing long names into the editor?

Think about it: a variable called amount has six letters in its name. If you refer to that variable twenty times throughout a program, that’s 120 keystrokes right there. However, if you shorten the name instead to a, that’s five keystrokes saved every time, adding up to a reduction of one hundred keystrokes.

Just think of what you could do with the time saved!

In fact, why risk incorporating even the merest hint of meaning in your variable names? A colleague with their wits about them might guess that a means amount. Instead, just use an arbitrary letter. You’ve got 25 others to choose from.

The beauty of this “strategy ” is that it has no natural limit. Exhausted all 26 letters of the alphabet? Just add a second character, yielding names like aa and x1.

Thumbs Down!

In this day and age, when many IDEs provide auto-completion, there’s no excuse for shortening variable names. Reviewers will complain that names should carry meaning so that readers of your code don’t have to continually look up what a variable actually refers to. Instead, with meaningful names, they can concentrate on what your code is doing.

Consider this example:

int a = 10000;
Map<String, Double> p = new HashMap<>();
for (int i = 0; i < 193; i++) {
    int q = getCountryPopulation(i);
    int b = getCountryArea(i);
    String n = getCountryName(i);
    if (q > a) {
        double r = q / b;
        p.put(n, r);
    }
}

This snippet of code calculates the population densities of countries greater than 10,000 square kilometers in size and puts those values into a map. It would be much easier to understand with clearer variable names :

final int minimumArea = 10000;
Map<String, Double> populationDensities =
        new HashMap<>();
for (int i = 0; i < 193; i++) {
    int population = getCountryPopulation(i);
    int area = getCountryArea(i);
    String name = getCountryName(i);
    if (population > minimumArea) {
        double populationDensity = population / area;
        populationDensities.put(name,
                populationDensity);
    }
}

Your reviewer might make exceptions for some names in a few circumstances. For example, index variables in short loops are usually allowed to be named something like i or n. They’ll advise you to “be guided by your common sense” or something similarly dangerous.

As a bonus practice, notice that the minimumArea variable has become a constant instead of a variable (thanks to the final keyword). You’ll be encouraged to mark as constant any values that shouldn’t change throughout the program so as to guard against their being updated accidentally.

Vowel Movements

Using one-letter variable names will likely result in your code’s being sent back fast with a message like “use more descriptive names please!” But this doesn’t mean your quest to use awful variable names ends here.

Another technique — one that allows you to both obfuscate names and get right up the reviewer’s nose at the same time—is to remove all the non-leading vowels from a name. See the wonderful effects in these examples:
  • velocity becomes vlcty

  • volume becomes vlm

  • count becomes cnt

  • price becomes prc

  • quantity becomes qntty

  • total becomes ttl

Notice how the shortened versions sort of, kind of look like the original names? They bear enough resemblance to let you argue they’re meaningful, but they’re still likely to cause the reviewer headaches from having to continually check and recheck the meaning . The best thing about this technique is it allows you to be pedantic. The reviewer wanted you to use longer names, and so you have!

Thumbs Down!

Good programmers aren’t above using shortened names themselves. Certain style guides will tell you things like:
  • If you must abbreviate, limit it to local variables used in a single context (GNU, 2016).

  • Shorten words, don’t delete letters within a word (Google, 2017a).

  • Use commonly-accepted abbreviations, like num or url, and use them consistently; don’t switch between the full name and shortened version throughout the code (Apple, 2013).

Lazy Naming

Programmers are problem solvers. If naming variables is a hard problem, then the solution is obvious: spend less effort on doing it. This way, you get lousy names and have expended barely any effort in the process. Problem solved. But which labor-saving techniques are available?

One of my favorites is to name a variable after its type. Just look how impressively useless these names are:

String string;
int number;
boolean flag;

Now, imagine the reviewer looking at the names. “Number of what?” they scream. Or, “Of course it’s a flag, it’s a Boolean, but what is it flagging ?”

Ah, warms your heart, doesn’t it?

Another form of uselessness is ambiguity in names. There’s nothing more infuriating than an integer variable called count (“A count of what?”) or a subroutine called doProcess (“Aagggghhh!”).

Treat Variable Declaration Like a Waste of Time

Statically typed languages like Java require you to declare variables before using them. The whole business of declaring and initializing variables is typically governed by clear rules in almost every coding standard and textbook.

But who’s got time for that? Here’s how to do declarations the quick and dirty way.

Be Confusing

You might not have realized how much variable declarations can be fertile ground for sowing confusion . After all, they seem so simple and innocent.

In fact, just doing something like declaring multiple variables on one line can cause confusion. Check this out:

int scoreBob, scoreJohn = 10;

After executing this statement, both Bob and John got a score of 10, right? Wrong. John got 10, because his variable was declared and initialized , but Bob’s variable was only declared. An uninitialized int in Java means that variable has a value of 0. In this case , poor old Bob ends up with no score.

Thumbs Down!

You’ll find some anal-level attention to detail in some coding standards and textbooks when it comes to variable declaration. For example, most of them don’t leave to chance the question of whether declarations should be one-per-line or not (Long, 2013). The answer, apparently, is almost always that they should.

int scoreBob = 10;
int scoreJohn = 10;

Be Contrarian

Sometimes there’s no generally agreed-upon rule on an issue. One project likes method A, another likes method B. This can cause a dilemma for a coding chaos monkey. How do you break a rule when the rule varies?

Simple: Be contrarian. Check which rule your project prefers and do it differently. Remember the earlier anti-rule: Be inconsistent.

Variable declarations are a good example of this. Standards differ on whether a variable’s initialization and declaration should be done together and close to its first use. Oracle’s Java code conventions, for example, advise that all declarations be done at the beginning of a code block and that initializations be done later (Oracle, 1999). On the other hand, Google’s Java guidelines require that declaration happens together with initialization as close as possible to the variable’s first use (Google, 2017b).

Based on my personal experience, I’d say people generally prefer the latter approach, and several prominent textbooks agree (for example, Martin, 2009; McConnell, 2004).

So, if your project prefers the latter approach too, then separate out your declarations and initializations. Try putting the declaration of the variable near the top of a routine, the initialization somewhere in the middle (preferably in among some lines of code that don’t actually reference that variable), and finally tuck the first actual use of the variable even further down.

Here’s what might go through the mind of someone reading code written like that:
  • Upon first seeing the declaration (int foo) they’ll think, “OK, this routine contains an integer called foo.”

  • Then, as they read on, they’ll get caught up in other details.

  • Later, they’ll encounter the initialization of foo. “Oh, this must be where foo is used. I’d forgotten about that.”

  • At this point, they’ll get distracted and frustrated, as no reference to foo is actually made in the immediate vicinity. They might even lose their grip on what the wider routine is really doing and get delightfully pissed off.

Maximize the Scope of Variables

The scope of a variable tells you which parts of a program have access to it. A narrow scope means the variable can only be accessed by a small part of the code. A wide scope means it can be accessed by most, if not all, of the program.

This section will help show you the wonderfully terrible consequences of giving your variables as wide a scope as possible.

Broad Scopes

For a long time now, programming literature has recommended narrowing the scope of your variables as much as possible. But why cramp your own style? You never know which parts of a program might need access to a variable in the future, so why not allow a variable to be accessible by all parts?

Consider this example, a partial view of a class that draws basic shapes:

import com.acme.drawing.Renderer;
public class Shapes {
    public String color = "white";
    public Point center;
    public int radius;
    public void drawCircle() {
        Renderer.drawCircle(center, radius, color);
    }
}

The variables color, center, and radius are fields of the Shapes class , which means they have a relatively wide scope. As soon as you create an instance of Shapes, they exist, and all methods in Shapes can access them.

Here’s some code that uses Shapes to try to draw a pair of eyes with white corneas and black pupils.

public void drawEyes() {
    Shapes shapes = new Shapes();
    // Draw left eye
    shapes.center = new Point(50, 50);
    shapes.radius = 20;
    shapes.drawCircle();
    // Draw left pupil
    shapes.color = "black";
    shapes.center = new Point(50, 50);
    shapes.radius = 10;
    shapes.drawCircle();
    // Draw right eye
    shapes.center = new Point(100, 50);
    shapes.radius = 20;
    shapes.drawCircle();
    // Draw right pupil
    shapes.color = "black";
    shapes.center = new Point(100, 50);
    shapes.radius = 10;
    shapes.drawCircle();
}
../images/455320_1_En_3_Chapter/455320_1_En_3_Fig1_HTML.gif
Figure 3-1

Big eyes

Looks good, right? Anyone looking at the code not too carefully would expect the eyes to appear as they do in Figure 3-1. However, because the code actually smuggles in a bug, the resulting image actually looks like Figure 3-2.
../images/455320_1_En_3_Chapter/455320_1_En_3_Fig2_HTML.gif
Figure 3-2

Bug eyes

Thumbs Down!

The problem with the preceding example is that, after drawing the left pupil, the color property remains black and isn’t changed back to white before the second eye is drawn.

This illustrates a central cause of headaches when using variables with excessive scope: the need to carefully manage state. By expanding the scope of variables, you force the programmer to juggle more details, increasing the chance that mistakes are made.

The Shapes example could be rewritten like this:

public class Shapes {
    public void drawCircle(Point center, int radius,
            String color) {
        Renderer.drawCircle(center, radius, color);
    }
}

Now that they’re method parameters , the scope of the three variables has been reduced to a single method. Each variable is accessible only within the drawCircle method . The method can be used like this:

Shapes shapes = new Shapes();
shapes.drawCircle(new Point(50, 50), 20, "white");
shapes.drawCircle(new Point(50, 50), 10, "black");
shapes.drawCircle(new Point(100, 50), 20, "white");
shapes.drawCircle(new Point(100, 50), 10, "black");
It’s now easier to use because you don’t have to worry about managing state when calling the method. Everything drawCircle needs in order to do its job is created at the beginning of the process and destroyed at the end of it. Some advantages of reducing the scope:
  • You need to do less work when calling drawCircle, such as setting up state.

  • You don’t have to worry about unanticipated side effects when several methods share access to the same variable.

  • You don’t need to know as much about how the Shapes class works on the inside. You can just call its methods.

Your colleagues will prefer that you create a new variable with the narrowest scope you can. You can always expand the scope later if it becomes necessary. It’s generally takes less work to expand scope than to narrow it .

Going Global

A global variable has the greatest scope of all. Such a variable is accessible by all parts of a program. Global variables have achieved a level of infamy similar to that of the goto statement (see Chapter 2), and some languages (Java included) don’t support them.

The main argument favoring global variables as a cause of pandemonium really just extends the argument seen in the previous subsection: the management of a variable’s state gets harder as it becomes accessible to more of your program. Global variables take this problem to the max. Eventually, you’ll be overwhelmed with unmanageable behavior and flurries of unanticipated side effects .

While Java doesn’t support global variables , you can pull a trick in Java that simulates all the joys of a global variable. This piece of code creates a variable called scores that’s accessible anywhere in your program:

class HighScores {
    public static int[] scores = new int[3];
}

It records the top three high scores achieved by players of a video game in the order they were achieved. The game can also output a leaderboard:

class Game {
    public void showLeaderboard() {
        // Display scores in numerical order
        LeaderBoard table = new LeaderBoard();
        table.showScores();
    }
    public void showScoreHistory() {
        // Display scores in historical order
        HistoryBoard table = new HistoryBoard();
        table.showScores();
    }
}
Let’s say three players play and score 150, 120, and 240 (in that order). The next player chooses to look at the history board (which calls the showScoreHistory method) and sees this:
  1. 1.

    150

     
  2. 2.

    120

     
  3. 3.

    240

     
Then, the player chooses to look at the leaderboard (which calls the showLeaderboard method) and, as expected, sees this:
  1. 1.

    240

     
  2. 2.

    150

     
  3. 3.

    120

     
But then the player chooses to look at the history board again, and something strange happens. They see this:
  1. 1.

    240

     
  2. 2.

    150

     
  3. 3.

    120

     

What’s gone wrong? Let’s look inside the HistoryBoard class:

class HistoryBoard {
    public void showScores() {
        for (int i = 0; i < 3; i++) {
            System.out.println(HighScores.scores[i]);
        }
    }
}

Nothing surprising there. What about the LeaderBoard class?

class LeaderBoard {
    public void showScores() {
        Arrays.sort(HighScores.scores);
        for (int i = 0; i < 3; i++) {
            System.out.println(HighScores.scores[i]);
        }
    }
}

Ah-ha! The LeaderBoard sorts the scores array before displaying it. This has the undesirable side effect of destroying the old ordering, which means the HistoryBoard no longer works as expected thereafter.

Thoroughly Abuse the Type System

Programming languages offer type systems as a means of program verification. Assigning a type to a variable is one way to verify that your program uses that variable in a valid way. The type system is your friend.

But then, who needs friends?

Turn Numbers into Secret Codes

Numbers can be abused in a few interesting ways. Sensible people expect numbers to represent some kind of quantity, but, let’s be honest, sensible people lack vision. In computing, we can abuse numbers and put them to more “imaginative uses.

For example, you could make numbers mean something other than their value, turning them from things that measure quantity into your own secret codes. Take a look at this example:

int status_code = connect_to_device();
switch (status_code) {
    case 0:
        display_info(info_messages[1]);
        break;
    case 1:
        reattempt();
        break;
    case 2:
        display_warning(warning_messages[3]);
        break;
}

The function connect_to_device attempts to connect to a hardware device and afterward returns a status code. The rest of this program then decides what to do depending on the value of the code . Since it goes without saying we’re not commenting our code, this leaves the reviewers scratching their heads as to what exactly is supposed to happen in each case.

As a bonus, this program also stores lists of messages in arrays, meaning they’re accessible by an index number. This obscures which message is actually being referred to.

Thumbs Down!

Although these days exceptions are generally recommended over error codes for reporting problems (see Chapter 7), using status codes is still acceptable in certain contexts. Even then, it’s considered helpful to make the meaning more plain to the reader.

For example, a language like Java provides enumerations. They’re still numbers “behind the scenes,” but they allow you to use labels in place of codes. The status codes could be replaced like so:

public enum DeviceStatus {
    SUCCESS = 0,
    WARNING_CONNECTION_SLOW,
    ERROR_NO_PINGBACK
}
DeviceStatus status = connect_to_device();
switch (status) {
case SUCCESS:
    display_info(info_messages[1]);
    break;
case ERROR_NO_PINGBACK:
    reattempt();
    break;
case WARNING_CONNECTION_SLOW:
    display_warning(warning_messages[3]);
    break;
}

Something similar can be done with the index numbers of the message collections. Instead of reading info_messages[1], it’s more helpful to see something like info_messages.CONNECTION_SUCCEEDED.

Strings Are Magic—They Can Pretend to Be Any Type

Compared to most other types, strings place few limits on what they can store. Integer types limit you to numbers only. Booleans limit you to only two values. But strings allow you to store a practically unlimited array of characters. Why go to the bother of learning all the restrictions of various other types when you can just put your information into strings and do whatever you want with them?

For example, in a computer game, you might need to assign compass directions to characters in the game.

if (key_pressed == "Up") {
    // Our character, Zilda, now faces north
    zilda.direction = "North";
}

This is a nicely subtle approach because it is both simple to use and simple to get wrong. For instance, when our character uses the magical Rod of Sharathgar, he must be facing north. Hence this test:

public boolean canUseRod() {
    if (zilda.direction == "north") {
        return true;
    }
    return false;
}

At first glance, this code might seem fine, but to Java “North” and “north” are different values. Thus, this if statement will fail, even if Zilda is facing the right way, and hence he’ll never be able to use his rod.

Thumbs Down!

Strings are unrestrictive because they impose little meaning onto a value. A string is just a collection of characters . The laid-back willingness of strings to store any information you want, no questions asked, means that you don’t benefit from the careful validation a type system offers.

For example, if you choose a string to represent directions, possible values include not only “north,” “south,” “east,” and “west,” but also “NoRth,” “soiuth,” “eest,” or “cuckoo.”

If a string variable is supposed to hold numeric data, it won’t complain if you accidentally assign to it “10O” instead of “100,” and you certainly won’t be able to do arithmetic with it.

Do yourself a favor. Use appropriate types that match the meaning of your data.

Mix Things Up

One advantage of collections is you can loop over their contents and apply the same operations to each item. This, you may ask, is so simple that there’s no scope for mucking it up, is there?

Oh, ye of little faith.

You can do something that on the surface sounds so simple but in actuality can lead to verbose and brittle code if you do it the right (i.e., the wrong) way: include mixed types in your collection.

Normally, your colleagues expect collections to contain items belonging to only one type, so including objects of multiple types will be an unpleasant surprise for them. You need a language that supports this, and many do these days. For example, an ArrayList in Java can contain any type of object when you declare it as an ArrayList<Object> , since all types inherit from Object.

The following code, which collects patient information into a list, demonstrates:

ArrayList<Object> patientInfos = getPatient();
String name = (String) patientInfos.get(0);
Date dob = (Date) patientInfos.get(1);
Integer weight = (Integer) patientInfos.get(2);
System.out.println("Name: " + patientInfos.get(0));
System.out.println("Date of birth: " +
        patientInfos.get(1));
System.out.println("Weight: " + patientInfos.get(2) +
        "kg");

This code expects that patient information lists only ever contain string objects in position 0, date objects in position 1, and integer objects in position 2. If an object is not of the expected type, the cast from Object fails, and a runtime error occurs. As a result, an ArrayList—something that ought to be a flexible construct—is abused and turned into a more brittle, record-style structure.

In this case, it’s fairly easy to make a mistake and put an object of the wrong type in the wrong position. As an added bonus, it’s a mistake that compilers won’t ordinarily pick up on. Objects occupying an incorrect space in the list will be revealed at runtime—with a bit of luck, after the program has shipped.

Thumbs Down!

An important skill to develop in programming is recognizing when the built-in types become insufficient for your needs and you must create your own.

The preceding example is one of many different symptoms that can indicate a program design is crying out for a new type. In this case, code that deals with several values of different types naturally clumps together. Collectively, those values describe a single entity: a patient. The example would therefore be improved by bringing those three properties together into a dedicated structure. For example, in an object-oriented program , you might create a PatientRecord type with methods like getName() and getWeight().

Null —The Harbinger of Doom

Null is marvellous. It’s something so dangerous and error-prone that even its own inventor has practically disowned it.

I call it my billion-dollar mistake. . . . My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

—Tony Hoare, 2009

While Tony Hoare may have lost his nerve as he grew older, you, on the other hand, embrace chaos. Null makes a potent weapon, so you should stick it in your arsenal.

Null Checks

Needless to say, don’t perform null checks.

CustomerAccount c = getNextCustomer();
System.out.println(c.getSurname());

This reasonable-looking code gets a CustomerAccount object and prints out the customer’s surname. However, the author was lazy (always a good approach to bad programming) and didn’t bother to check how the CustomerAccount class works. If they had, they’d have discovered that the getSurname method returns null if the surname was not assigned a value.

Therefore, this code is a NullPointerException just waiting to happen.

Seeding Disaster

When you write your own subroutines, make sure you return nulls whenever you can, especially in surprising ways.
  • When you create a variable that will eventually be returned by a subroutine, initialize it to null.

  • When a subroutine needs to return an “empty” value, return null.

  • Don’t, whatever you do, give any clues to users of the subroutine that indicate it might return null. That only increases the risk that the user will act on that knowledge to make their use of the subroutine more robust (and robust is a dirty word in this book!).

Thumbs Down!

The fight against null goes on. Not only are eagle-eyed reviewers on guard against its use in program code , but also programming languages are adapting to reduce its potential to cause damage.

Reviewers will watch out for certain problems in your code. If they’re careful enough, they’ll catch your missing null checks. They might insist on rewriting the preceding example like so:

CustomerAccount c = getNextCustomer();
if (c.getSurname() != null) {
    System.out.println(c.getSurname());
}

Of course, if you wrote the CustomerAccount class , they might go one better and make you change its behavior so that its properties are initialized to non-null empty values. For example, when an account has no surname, getSurname should return an empty string. When an account has no credit cards, getCreditCards should return an empty collection rather than null.

If a subroutine must return null, you’ll be told to make that potential very clear. This can depend on the language used. For example, Java allows you to write JavaDoc comments to describe what a method might return. Such a comment should include potential null return :

/**
 * Looks up an account by the customer's surname.
 * @return The account object or null if the account
 *     could not be located.
 */
public CustomerAccount getAccountBySurname(
        String surname) {
    // ...
}

Additionally, Java supports annotations like @NotNull that indicate things like whether a variable can be null or whether a method is allowed to return null (Oracle, 2014). Tools like compilers and IDEs can ensure that your code matches the behavior promised by those annotations and report problematic code at compile-time accordingly.

Another weapon in the fight against null is the Optional type .1 It neatly encapsulates the idea of a variable’s having the potential to contain no value (i.e., be equal to null) and forces the programmer to consider what to do if that’s the case. It makes the handling of potential missing values safer and easier than trying to remember to perform null checks.

In the following example, getGradeForStudent returns the grade assigned to a student taking an exam. However, it’s possible that the student hasn’t yet taken the exam, in which case the grade would be missing. Therefore, getGradeForStudent returns an Optional<Grade> object instead of a Grade object .

// maybeGrade may or may not contain a Grade
Optional<Grade> maybeGrade = getGradeForStudent(
        studentNumber);
// Call the Grade's toString method, or
// return "Unassigned" if the grade isn't present.
String grade = maybeGrade
        .map(Grade::toString)
        .orElse("Unassigned");
System.out.println(grade);

Since attempting to print a variable with a null value results in an error , the Optional type can first try to get the string representation of the grade (by calling grade’s toString method). If it finds the value is missing, it instead returns the value contained in the orElse method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.234.225