Skip to content

Clean Code

The quality of the software is not ideal in all projects and therefore efforts are made in many areas to improve it. The use of software engineering is intended to improve the code in all its aspects. However, one will keep saying “Why should I write good code at all? - it doesn't interest anyone anyway ”. Experience: A large part of the programmers write bad source code without worrying much about the consequences, following the motto As long as it is running, it's okay!

What is good code anyway? Many developers have probably asked themselves this question and will continue to ask it in the future. Books like “Clean Code” by Robert C. Martin contain many rules that code should obey.

What is clean code?

  • Clean code is easy to read.
  • It can also be understood by other developers.
  • Classes and methods are developed towards the fulfillment of a task and are not contaminated by secondary tasks.
  • The dependencies on other code are limited to a minimum.
  • Clean code is easy to test.
  • There are no duplications.
  • There are no surprises in the code.

Good code motivates

  • Everyone involved is proud of their work.
  • Programming is more fun.
  • Good code has fewer bugs.
  • It's easier to test.

For the employee in the project this means:

Good code reduces unpleasant work.

In the next you will see some best practices and recommendation how to write good code and prevent bad code.

Tips on writing clean code

Be consistent with formatting

It is not important whether you use spaces or tabs for indentation, as long as you stick with one or the other. There's nothing worse than code that is difficult to read because it is poorly formatted.

Be consistent with naming conventions

Again, consistency is more important than the specific style you use. Do you name your variables my_counter, myCounter or MyCounter? It is no matter; just pick a style and be consistent. (Of course, only a beginner would use the same style for every type of variable; let's hope you're smart enough to use one style for local variables, another for class names, etc.)

Recover or fail gracefully

Robust programs should report an error message and attempt to continue. Failing that, they should halt gracefully. These type of error handling are not good: * Lazy: you obviously copied and pasted it from a book or web site that explains how to connect to a database. * Ungraceful: failure to connect to the database is not a sufficient reason to exit the entire application right then and there. * Inconsiderate: what if another module needs to do some clean-up before the application exits? * Unhelpful: of what real use is the message Unable to connect to DB? A better message might be "9-Sept-2020 16:54:22 pm: connectDatabase() failed on line "456" of module "foo" at least if you're talking to a programmer. To the end user, even that "improved" message is mindless doublespeak.

Provide meaningful error messages

Expanding on the previous point, you should provide a user-friendly error message while simultaneously logging a programmer-friendly message with enough information that they can investigate the cause of the error.

Exception catching

Always know why you are catching an exception. if you catch an exception you need to have a good reason. Reporting an expected error condition to the user in a more friendly way is a good reason. Handling an expected, but unusual, condition is another. But very often people just swallow the exceptions with an empty catch block (this is a real nono).

Descriptive variable names

Use descriptive variable names. This is a rule that can be difficult to follow but it should be named in general variables to make clear what a variable contains. This is often instinctively apparent, and in these situations, short names such as i for loop indexes are perfectly Fine for files. But try to make it meaningful when you use a longer name.

Cut-and-paste code

One easy solution suggested very frequently as a way to enhance the code of people is to disable the paste function on each developer computer. For example if you have a snippet of 6 lines of code that do one thing, and you want a different set of variables to do it again, don't copy and paste the code. Have a function (or method, if you're stuck with Java) instead.

There are several explanations for that. One is sheer brevity. Another is that it makes it much easier to read your code, since you would replace 12 lines of code with just 2. Obviously this will be at the cost of an additional function/method, but because these are standing alone and separated from the rest of the code, their readability effect is very limited. But the biggest explanation is that you make life a lot easier for yourself by doing this. And if this is a one-time script, you will always be modifying the code around it. This is simpler when split up into pieces, such as functions/methods.

Communication Variables

One of the most difficult things to pick up when reading code is to understand the heat flow of values through the code variables. (This is one reason why functional programming is popular.) This has a range of consequences about how to write the code: * You should try to assign variables as close as possible to where they are being used. * If a variable is only used inside a block (if statement, while loop, or whatever), then it really should be declared in there. * You should really work hard to keep your functions/methods short.

Return values

Don't preserve return values you don't use. This makes it easier to track the data flow. For example consider the follwoing code:

boolean present = myCollection.remove(object);

However, you could write it like this:

' myCollection.remove(object);'

You can safely assume that this will be known to any reader of your code, and therefore using the first form is a signal that "I care about this return value and I will use it for something." If you don't, the reader will be confused, as they assume they now need to figure out what you're going to do with this variable.

Leaving it out, of course, also saves space so there is really no reason not to drop these variables.

Delete needless code

Finding code that's commented out is quite common but still hanging around. Mostly this is bad because it unnecessarily blocks the code. People tend to do this because they want the chance of getting the code back, either because they're writing an alternative they 're not sure about, or because they don't want to delete it (and that seems very common). But there is no particular need to keep such code around in almost all cases. You can use version control, which ensures that you can still find some deleted code once more.

As this code is no longer compiled or executed, there is also no real difference between commenting it out or deleting it. You have made the move, already. The difference is that now it's more possible that this technology will shift slowly out of sync with the technology around it, so it won't work anymore by the time you realize that you want it again. That actually makes the code unsafe, because it tempts you to use code without knowing what it is doing. (If you really understood that, even if it was deleted, you could retyp it quite quickly.)

Commenting on code while you're working on it is perfect, of course. I do so a lot. I never search in code, however, which is commented out, and whenever I find such code I delete it, without asking or telling anyone. They should not have commented this out if they wanted the code. And that'll be in the history of the version, anyway.

Don't comment too much

Even well-intentioned commenting on code can quickly create new problems.

It is not helpful if above each line of code there is a comment that describes again in prose what the following code does. Not only does this bloat the code visually, it forces other developers to read and understand the comments in addition to the code - a time-consuming process. If the code no longer fits the comments due to refactoring, confusion is inevitable. We then ask ourselves, for example, whether the code is incorrect because the comment expresses something different. Or is the comment itself wrong?

We can avoid these irritations from the outset by structuring the code so clearly that no comments are necessary for explanation. This can be achieved, for example, by means of meaningful class, variable and method names. Good code tells what it does by itself.

Avoid unnecessary code

Another problem is the use of redundant code. What do we mean by that? The following example shows several of these cases.

public CustomerServiceImpl() {  
  super();
}
public boolean containsCustomer(final Customer argCustomer) {
  if (customers.contains(argCustomer) == true) {
    return true;  
  } else {
    return false;  
  }
}

The parameterless constructor: This only calls super() without performing any further initializations. In this case, the declaration of the constructor is completely superfluous, since according to the Java Language Specification such a constructor is generated automatically anyway. Explicit checking for the Boolean values true or false in if statements is also unnecessary. With a bit of refactoring, we'll find that the entire containsCustomer method can be reduced to a single statement: return customers.contains (argCustomer). It is always best when you can delete code without changing its behavior. The advantage is obvious: We no longer have to read any code that is automatically generated in the background by the compiler and can concentrate on the essential processes.

No surprises

The written code should not bring any surprises of any kind. One such surprise is hidden in the follwoing example, where, the customers collection is displayed on the console, sorted by the customer's last name.

public void printSortedByLastName () {
  SortedSet sortedCustomers = new TreeSet (new LastNameComparator ());
  sortedCustomers.addAll (customers);
  for (Customer customer: sortedCustomers) {
    System.out.println (customer);
  }
}

One question: where does sorting actually take place? Java connoisseurs will know that this is done internally in the TreeSet implementation when addAll () is called - using the LastNameComparator. What is important is the effects of using a TreeSet: Since a set does not allow duplicates by definition, only some of the customers would be displayed on the console. If you had e.g. three customers with the last name "Smith", only one of them would be displayed - even if all three customers had different first names. Undoubtedly a surprise that might only have been noticed in live operation, if customer data records are missing. It would make more sense to have the sorting carried out explicitly using Collections.sort () (analogous to Listing 1) in order to avoid such unpleasant surprises and at the same time improve the readability of the code.

Code Smells Within Classes

a code smell is any characteristic in the source code of a program that possibly indicates a deeper problem. Determining what is and is not a code smell is subjective, and varies by language, developer, and development methodology.

The term was popularised by Kent Beck on WardsWiki in the late 1990s. Usage of the term increased after it was featured in the 1999 book Refactoring: Improving the Design of Existing Code by Martin Fowler. It is also a term used by agile programmers.

In the next you will see some of the Code Smells Within Classes cases, following by Code Smells Between Classes cases.

Comments

There's a fine line between comments that illuminate and comments that obscure. Are the comments necessary? Do they explain "why" and not "what"? Can you refactor the code so the comments aren't required? And remember, you're writing comments for people, not machines.

Long Method

All other things being equal, a shorter method is easier to read, easier to understand, and easier to troubleshoot. Refactor long methods into smaller methods if you can.

Long Parameter List

The more parameters a method has, the more complex it is. Limit the number of parameters you need in a given method, or use an object to combine the parameters.

Duplicated code

Duplicated code is the bane of software development. Stamp out duplication whenever possible. You should always be on the lookout for more subtle cases of near-duplication, too. Don't Repeat Yourself!

Conditional Complexity

Watch out for large conditional logic blocks, particularly blocks that tend to grow larger or change significantly over time. Consider alternative object-oriented approaches such as decorator, strategy, or state.

Combinatorial Explosion

You have lots of code that does almost the same thing.. but with tiny variations in data or behavior. This can be difficult to refactor-- perhaps using generics or an interpreter?

Large Class

Large classes, like long methods, are difficult to read, understand, and troubleshoot. Does the class contain too many responsibilities? Can the large class be restructured or broken into smaller classes?

Type Embedded in Name

Avoid placing types in method names; it's not only redundant, but it forces you to change the name if the type changes.

Uncommunicative Name

Does the name of the method succinctly describe what that method does? Could you read the method's name to another developer and have them explain to you what it does? If not, rename it or rewrite it.

Inconsistent Names

Pick a set of standard terminology and stick to it throughout your methods. For example, if you have Open(), you should probably have Close().

Dead Code

Ruthlessly delete code that isn't being used. That's why we have source control systems!

Speculative Generality

Write code to solve today's problems, and worry about tomorrow's problems when they actually materialize. Everyone loses in the "what if.." school of design. You (Probably) Aren't Gonna Need It.

Oddball Solution

There should only be one way of solving the same problem in your code. If you find an oddball solution, it could be a case of poorly duplicated code-- or it could be an argument for the adapter model, if you really need multiple solutions to the same problem.

Temporary Field

Watch out for objects that contain a lot of optional or unnecessary fields. If you're passing an object as a parameter to a method, make sure that you're using all of it and not cherry-picking single fields.

Code Smells Between Classes

Alternative Classes with Different Interfaces

If two classes are similar on the inside, but different on the outside, perhaps they can be modified to share a common interface.

Primitive Obsession

Don't use a gaggle of primitive data type variables as a poor man's substitute for a class. If your data type is sufficiently complex, write a class to represent it.

Data Class

Avoid classes that passively store data. Classes should contain data and methods to operate on that data, too.

Data Clumps

If you always see the same data hanging around together, maybe it belongs together. Consider rolling the related data up into a larger class.

Refused Bequest

If you inherit from a class, but never use any of the inherited functionality, should you really be using inheritance?

Inappropriate Intimacy

Watch out for classes that spend too much time together, or classes that interface in inappropriate ways. Classes should know as little as possible about each other.

Indecent Exposure

Beware of classes that unnecessarily expose their internals. Aggressively refactor classes to minimize their public surface. You should have a compelling reason for every item you make public. If you don't, hide it.

Feature Envy

Methods that make extensive use of another class may belong in another class. Consider moving this method to the class it is so envious of.

Lazy Class

Classes should pull their weight. Every additional class increases the complexity of a project. If you have a class that isn't doing enough to pay for itself, can it be collapsed or combined into another class?

Message Chains

Watch out for long sequences of method calls or temporary variables to get routine data. Intermediaries are dependencies in disguise.

Middle Man

If a class is delegating all its work, why does it exist? Cut out the middleman. Beware classes that are merely wrappers over other classes or existing functionality in the framework.

Divergent Change

If, over time, you make changes to a class that touch completely different parts of the class, it may contain too much unrelated functionality. Consider isolating the parts that changed in another class.

Shotgun Surgery

If a change in one class requires cascading changes in several related classes, consider refactoring so that the changes are limited to a single class.

Parallel Inheritance Hierarchies

Every time you make a subclass of one class, you must also make a subclass of another. Consider folding the hierarchy into a single class.

Incomplete Library Class

We need a method that's missing from the library, but we're unwilling or unable to change the library to include the method. The method ends up tacked on to some other class. If you can't modify the library, consider isolating the method.

Solution Sprawl

If it takes five classes to do anything useful, you might have solution sprawl. Consider simplifying and consolidating your design.

Execise 1:

public boolean max(int a, int b) {
 if(a > b) {
  return true;
 } else if (a == b) {
  return false;
 } else {
  return false;
 }
}
Execise 2:
public void employeeMethod(Employee employee) {
 // Some actions
 double yearlySalary = employee.getYearlySalary();
 double awards = employee.getAwards();
 double monthlySalary = getMonthlySalary(yearlySalary, awards);
 // Continue processing
}
public double getMonthlySalary(double yearlySalary, double awards) {
 return (yearlySalary + awards)/12;
}
Execise 3:
public void calcQuadraticEq(double a, double b, double c) {
 double D = b * b - 4 * a * c;
 if (D > 0) {
  double x1, x2;
  x1 = (-b - Math.sqrt(D)) / (2 * a);
  x2 = (-b + Math.sqrt(D)) / (2 * a);
  System.out.println("x1 = " + x1 + ", x2 = " + x2);
 } else if (D == 0) {
  double x;
  x = -b / (2 * a);
  System.out.println("x = " + x);
 } else {
  System.out.println("Equation has no roots");
 }
}
Execise 4:
class Human {
 private String name;
 private String age;
 private String country;
 private String city;
 private String street;
 private String house;
 private String quarter;

public String getFullAddress() {
 StringBuilder result = new StringBuilder();
   return result.append(country)
    .append(", ").append(city)
    .append(", ").append(street)
    .append(", ").append(house)
    .append(" ").append(quarter).toString();
 }
}