Clean code race with Java examples

As software engineers, we are always in a hurry, to deliver new fancy features, to solve bugs, to finish a task on a legacy application, and move on to solving a challenging one on a new application, using the latest technologies. In a way, the life of a developer is lived on fast forward. But what’s expecting us at the end of the finish line?

Oana Alexandra Toma
ING Hubs Romania
7 min readMay 23, 2022

--

Photo by Guillaume TECHER on Unsplash
Photo by Guillaume TECHER on Unsplash

In this article I would like to bring up what are the consequences of this coding race, highlighting the principles of Clean Code that are guiding me to deliver qualitative and efficient code. But before going into details, I would like to mention that Clean Code by Robert C. Martin is the foundation of this article, being the book that impacted the way I write and review code.

Writing code is the most entertaining activity for me as a developer. I wouldn’t say the same about writing documentation, that’s why one of the principles I’m following for small units like methods and classes is:

Code should be self-explanatory.

In order to achieve this principle, I’m following a set of steps when I’m implementing a new unit of code.

When adding a new method, the recipe should be the same: be clear and concise as you are going to see in the next examples.

Spoiler Alert! Each step will be illustrated through good and bad examples.

Step 1. Choosing the right name

The name of a method is one of the most important parts, because its job is to reveal the purpose of the method, to clear expectancy.

public List<Integer> get(){                                    ❌  List<Integer> l = new ArrayList<>();
list.forEach(x-> {
if (x < 100 && x>50){
l.add(x);
}
});
return l;
}
public List<Integer> getMediumPrices(){ ✅ List<Integer> mediumPrices = new ArrayList<>();
productPrices.forEach(price-> {
if (price > SMALL_PRICE && price < BIG_PRICE ){
mediumPrices.add(price);
}
});
return mediumPrices;
}

If we have a look at the first piece of code, we are able to understand that the method is returning a list of integers, but it’s impossible to know what this list represents. Even if we look at the code inside the method, because of poor naming of variables we are quite far from being able to explain the method’s purpose.

In the second piece of code, we can figure out when to call the method, just by looking at its name, which is revealing the scope using nouns and verbs. Besides that, if we run a search through our code, the second version is easier to spot.

Another point that we should keep in mind when naming a method is having a balance regarding name length.It shouldn’t be three letters, nor three lines. I believe length should be directly proportional with the method scope.

Step 2. Choose the right number of arguments

As Uncle Bob (Robert C. Martin) likes to tell us, the perfect number of arguments for a method is zero. Why is that? Because arguments add complexity when you try to read and understand the purpose of the method. While testing a method with multiple arguments, all combinations of possible values must be taken into consideration.

There are situations when zero arguments case is not possible, as some methods need to have one argument. For example, methods that validate an object.

public boolean isAvailableProduct(Product product){
return availableProducts.contains(product);
}

Another common situation when one argument is necessary, can be with methods that are performing transformation on an object and return it.

public Product changeProductPrice(Integer price){
Product product = new Product();
product.setPrice(price);
return product;
}

Of course, there are circumstances when two arguments are necessary. The simplest case is the sum of two numbers. But, if we get to a method with three or more arguments, from my point of view we need to redesign it or have a very good explanation to keep it.

Tip:

If you ended with more than three arguments like in the example below, go through them one more time, see if any of them are repeating or can be grouped together.

public List<Product> searchByProductQuery(                     ❌
int productPrice,
String productName,
String productType,
String productQuality,
int offset,
int limit) {

}

The above method header has a lot of arguments, but the first four are related to Product search criteria, while the following two are related to pagination criteria. Now, that we know its weakness, the method header could be:

public List<Product> searchByProductQuery(                      ✅
ProductSearchCriteria product,
PaginationCriteria) {
}

By minimising the number of arguments in this case, we decrease the complexity while testing the method, as well as increase the flexibility of the implementation. Right now, it’s easier to add new search criteria for products.

For further exploration of this approach, you can check parameter object pattern.

Step 3. Keep it small

We talked about the method name, about the number of arguments, now let’s look at the body of the method.

As developers, all of us have encountered at least once a method with more than fifty lines of code. Each time it’s a hassle to read such methods, I always write down all the implementation steps, trying not to forget what the method really does.

What is the problem with big methods? Well, I believe the most dangerous fact is that they are breaking single responsibility principle. Why is this dangerous? Simply, because it will become a nightmare to extend such a method or even more, it will be complicated to maintain the code.

Let’s say we wrote a method, it has more than fifty lines, what should we do?

Refactor it!

I agree, the process of refactoring takes time, but we will appreciate that we spent one–two hours refactoring the code next time we need to extend the feature or solve a bug on that code.

When I’m starting a refactoring journey for a big method, I’m following few tips regarding clean code.

Let’s take as an example the following method.

public void updateProductPrice() {                               ❌
products.forEach(product -> {
if (!product.type.equals("Food") && !product.type.equals("Drinks")) {
if (product.price > 500) {
if (!product.name.equals("Bouquet of flowers")) {
product.price = product.price - 10;
}
}
} else {
if (product.type.equals("Food")) {
if (product.expirationDate.after(Date.from(Instant.now().plus(10, ChronoUnit.DAYS)))) {
if (product.price < 50) {
product.price = product.price + 10;
}
}
if (product.expirationDate.before(Date.from(Instant.now().plus(10, ChronoUnit.DAYS)))) {
if (product.price < 50) {
product.price = product.price - 10;
}
}
}
if (product.type.equals("Drinks")) {
if (product.expirationDate.before(Date.from(Instant.now().plus(10, ChronoUnit.DAYS)))) {
if (product.price > 200) {
product.price = product.price - 10;
}
}
}
if (product.type.equals("Bouquet of flowers")) {
if (product.expirationDate.before(Date.from(Instant.now().plus(10, ChronoUnit.DAYS)))) {
product.price = product.price - 10;
}
}
}

});
}

It’s quite a big method, processing a lot of things. The first tip when refactoring such method would be:

Break down the method

Most of the times, methods with more than fifty lines are doing more than one thing, this is noticeable just by reading aloud the code. We can break down methods into smaller methods to achieve the same goal, following the clean code principles we are talking about. This way we enforce single responsibility principle, but we also make our life easier, less effort for maintenance.

The second tip regarding refactoring this kind of methods would be:

Keep the indentation level at two.

In this case, indentation level is given by if statements, but in other cases could be also while or for. By keeping the level at two, we avoid having a lot of nested blocks which bring complexity and lead the method to do more than one thing.

How are those tips working in practice?

Coming back to our method, we can start the refactoring process taking one block of code at a time.

The main purpose of the first block of code is to lower the price by ten units for products that are not in the category of food, drinks or flowers and have a price bigger than five hundred.

A way of refactoring this first block of code could be:

if (!isPerishableProduct(product.type)) {                         ✅
updatePriceForNonPerishableProduct(product);
}

We manage to reduce five lines of code to only two lines, but also, we manage to avoid having nested blocks of IF. The two new methods implementations could look like this:


private void updatePriceForNonPerishableProduct(Product product){
if (product.price > 500) {
product.price = product.price - 10;
}
}
private boolean isPerishableProduct(String productType){
List<String> perishableProducts= Arrays.asList("Food","Drinks,Bouquet of flowers");
return perishableProducts.contains(productType);
}

If we follow the same approach for the ELSE block, the principal method will become:

public void updateProductPrice() {
products.forEach(product -> {
if (!isPerishableProduct(product.type)) {
updatePriceForNonPerishableProduct(product);
} else {
updatePriceForPerishableProduct(product)
}
});
}

We reduced the number of code lines to five, giving us a lot more possibilities to extend the functionality in the future.

But why spend time to refactor something, to name it right, to choose the right number of arguments, when we are in a race of delivering code? Why do this, if the app works without those steps as well as with them? In a few words the answer to all these questions is increased productivity. Yes, we will spend a bit more time at the beginning of the implementation, but we will reduce a lot the time spent solving bugs or extending the functionality.

As I gained experience as a developer I’ve realised that the time spent to clean is time well spent. Because in the end my code is scalable, with good performance and other colleagues can read and understand it effortlessly.

In conclusion, it’s not enough to write a functional code, we need to understand what it does even if we look at it after one year. Each small action taken for a cleaner code, (building a method with the right number of arguments, naming it properly, or keeping it in the right length) represents a step ahead.

Keeping this reflection in mind, I would like to thank you for reading the article and wish you to:

Write nice code!

If you would like to explore more about clean code I recommend you:

--

--

Oana Alexandra Toma
ING Hubs Romania

I’m an enthusiastic Java developer, fan of Spring and a world explorer.