A code smell is a chunk of code or general coding pattern that looks like it might indicate a deeper issue in the overall structure and design of a codebase.

Think of a code smell as any sign that suggests a section of code should be refactored. It's not that the code is buggy or non-functional -- often times, smelly code runs just fine -- but smelly code is often hard to maintain and extend, which can lead to technical issues (especially on larger projects).

In this article, we'll highlight 10 of the most common code smells, what to look for, and how to deodorize them. If you're a new programmer, avoid these and your code will be noticeably better!

1. Tight Coupling

The Problem

Tight coupling is when two objects are so dependent on one another's data and/or functions that modifying one requires modifying the other. When two objects are too tightly coupled, making changes to code can be a nightmare and you're more likely to introduce bugs with every change.

For example:

        class Worker {
  Bike bike = new Bike();
  public void commute() {
    bike.drive();
  }
}

In this case, Worker and Bike are tightly coupled. What if one day you wanted to drive a Car instead of a Bike for your commute? You'd have to go into the Worker class and replace all Bike-related code with Car-related code. It's messy and prone to errors.

The Solution

You can loosen coupling by adding a layer of abstraction. In this case, the Worker class doesn't just want to drive Bikes, but also Cars, and maybe Trucks, possibly even Scooters. These are all Vehicles, aren't they? So create a Vehicle interface, which allows you to insert and replace different Vehicle types as desired:

        class Worker {
  Vehicle vehicle;
  public void changeVehicle(Vehicle v) {
    vehicle = v;
  }
  public void commute() {
    vehicle.drive();
  }
}

interface Vehicle {
  void drive();
}

class Bike implements Vehicle {
  public void drive() {
  }
}

class Car implements Vehicle {
  public void drive() {
  }
}

2. God Objects

The Problem

A God object is massive class/module that contains too many variables and functions. It "knows too much" and "does too much," which is problematic for two reasons. First, other classes/modules become overly reliant on this one for data (tight coupling). Second, the overall structure of the program becomes muddy as everything gets crammed into the same place.

The Solution

Take a God object, separate its data and functions according to what problems they exist to solve, then turn those groupings into objects. If you have a God object, it may be better off as a composition of many smaller objects.

For example, suppose you have a monstrous User class:

        class User {
  public String username;
  public String password;
  public String address;
  public String zipcode;
  public int age;
  ...
  public String getUsername() {
    return username;
  }
  public void setUsername(String u) {
    username = u;
  }
}

You could convert it into a composition of the following:

        class User {
  Credentials credentials;
  Profile profile;
  ...
}

class Credentials {
  public String username;
  public String password;
  ...
  public String getUsername() {
    return username;
  }
  public void setUsername(String u) {
    username = u;
  }
}

The next time you need to modify login procedures, you don't have to crawl through a massive User class because the Credentials class is more manageable!

3. Long Functions

The Problem

A long function is exactly what it sounds like: a function that has grown too long. While there isn't a specific number for how many lines of code is "too long" for a function, it's one of those things where you know it when you see it. It's pretty much a tighter-scope version of the God object problem -- a long function has too many responsibilities.

The Solution

Long functions should be broken into many sub-functions, where each sub-function is designed to handle a single task or problem. Ideally, the original long function will turn into a list of sub-function calls, making the code cleaner and easier to read.

4. Excessive Parameters

The Problem

A function (or class constructor) that requires too many parameters is problematic for two reasons. First, it makes code less readable and makes it harder to test. But second, and more importantly, it may indicate that the purpose of the function is too ambiguous and is trying to handle too many responsibilities.

The Solution

While "too many" is subjective for a parameters list, we recommend being wary of any function that has more than 3 parameters. Sure, sometimes it makes sense to have a single function with 5 or even 6 parameters, but only if there's a really good reason for it.

Most of the time, there isn't one and the code would be better off breaking that function into two or more different functions. Unlike the "Long Functions" code smell, this one can't be solved just by replacing code with sub-functions -- the function itself needs to be divided and broken into separate functions covering separate responsibilities.

5. Poorly Named Identifiers

The Problem

One- or two-letter variable names. Nondescript function names. Overly-adorned class names. Marking variable names with their type (e.g. b_isCounted for a boolean variable). And worst of all, mixing different naming schemes throughout a single codebase. All of these result in hard-to-read, hard-to-understand, and hard-to-maintain code.

The Solution

Picking good names for variables, functions, and classes is a hard-learned skill. If you're joining an existing project, comb through it and see how existing identifiers are named. If there's a style guide, memorize it and adhere to it. For new projects, consider forming your own style guide and stick to it.

In general, variable names should be short but descriptive. Function names should typically have at least one verb and it should be immediately obvious what the function does just from its name, but avoid cramming in too many words. Same goes for class names.

6. Magic Numbers

The Problem

You're browsing through some code that (hopefully) someone else wrote and you spot some hardcoded numbers. Maybe they're part of an if-statement, or maybe part of some arcane calculations that don't seem to make sense. You need to modify the function, but you just can't make sense of what the numbers mean. Cue head scratching.

The Solution

When writing code, these so-called "magic numbers" should be avoided at all costs. Hardcoded numbers make sense at the time they're written, but they can quickly lose all meaning -- especially when someone else tries to maintain your code.

One solution is to leave comments that explain the number, but the better option is to convert magic numbers into constant variables (for calculations) or enumerations (for conditional statements and switch statements). By giving magic numbers a name, code becomes infinitely more readable at a glance and less prone to buggy changes.

7. Deep Nesting

The Problem

There are two main ways to end up with deeply nested code: loops and conditional statements. Deeply nested code isn't always bad, but can be problematic because it can be tough to parse (especially if variables aren't named well) and even tougher to modify.

The Solution

If you find yourself writing a double, triple, or even quadruple for-loop, then your code may be trying to reach too far outside of itself to find data. Instead, provide a way for the data to be requested through a function call on whatever object or module contains the data.

On the other hand, deeply-nested conditional statements are often a sign that you're trying to handle too much logic in a single function or class. In fact, deep nesting and long functions tend to go hand in hand. If your code has massive switch statements or nested if-then-else statements, you may want to implement a State Machine or Strategy pattern instead.

Deep nesting is particularly prevalent among inexperienced game programmers!

8. Unhandled Exceptions

The Problem

Exceptions are powerful but easily abused. Lazy programmers who incorrectly use throw-catch statements can make debugging exponentially harder, if not impossible. For example, ignoring or burying caught exceptions.

The Solution

Instead of ignoring or burying caught exceptions, at least print out the exception's stack trace so debuggers have something to work with. Allowing your program to fail silently is a recipe for future headaches, guaranteed! Also, prefer to catch specific exceptions over general exceptions. Learn more in our article on how to handle exceptions the right way.

9. Duplicate Code

The Problem

You perform the same exact logic in multiple unrelated areas of your program. Later, you realize you need to modify that logic, but don't remember all the places where you implemented it. You end up changing it in only 5 out of 8 places, resulting in buggy and inconsistent behaviors.

The Solution

Duplicate code is a prime candidate for being turned into a function. For example, let's say you're developing a chat application and you write this:

        String queryUsername = getSomeUsername();
boolean isUserOnline = false;

for (String username : onlineUsers) {
  if (username.equals(queryUsername)) {
    isUserOnline = true;
  }
}

if (isUserOnline) {
  ...
}

Somewhere else in the code, you realize you need to perform the same "is this user online?" check. Instead of copy-pasting the loop, you can pull it out into a function:

        public boolean isUserOnline(String queryUsername) {
  for (String username : onlineUsers) {
    if (username.equals(queryUsername)) {
      return true;
    }
  }
  return false;
}

Now anywhere in your code, you can use the isUserOnline() check. If you ever need to modify this logic, you can tweak the method and it'll apply everywhere it's called.

10. Lack of Comments

The Problem

The code has absolutely no comments anywhere. No documentation blocks for functions, no usage overviews for classes, no explanations of algorithms, etc. One might argue that well-written code doesn't need comments, but the truth is that even the best-written code still takes more mental energy to understand than English.

The Solution

The goal of an easy-to-maintain codebase should be code that's written well enough that it doesn't need comments, but still has them. And when writing comments, aim for comments that explain why a snippet of code exists instead of explaining what it's doing. Comments are good for the soul and sanity. Don't neglect them.

How to Write Code That Doesn't Smell

As obvious as it might seem, most code smells arise from a misunderstanding or neglect of good programming principles and patterns. For example, a solid adherence to the DRY principle eliminates most code duplication, while mastery of the Single Responsibility principle makes it nearly impossible to create monstrous God objects.

We also recommend reading our article on how to write cleaner code, which looks at a more practical side of programming. If you can't read your own code and understand it at a glance, how will anybody else? Clean code is odorless code.

What do you struggle with most when it comes to programming? Share with us down in the comments below!

Image Credit: SIphotography/Depositphotos