4 Simple Tricks to Write Java Clean Code

Posted by Marta on December 18, 2020 Viewed 3592 times

Card image cap

In this article, you will learn four simple and practical tricks that will help you write java clean code that is easy to read and maintain. You can apply these five tip sequentially, and you will produce much cleaner easy to read java code. Firstly, I will explain each trick and then show you examples of how to apply each of them. Without further to do, let’s get started!

Why writing Java Clean Code?

When you are writing code, the first goal is to get the code to work; however, is your job done once you got the code working? Not really. As a professional developer, your duty is not only to get code to work. You also need to make sure that you write high-quality code.

High-Quality code sounds excellent; however, what does it imply? It means you are writing code that is easy to read and understand by other teammate developers; in other words, readability. Plus, the code does what it supposes to do in all cases and is predictable.

Why is readability so important? Code is something dynamic that changes as a business change. Therefore, other developers will have to work with it and modify it as business needs change. The easier the code is to understand, the quicker the other person can change it and adapt it to new requirements. And consequently, allow the business to evolve.

Although this seems a simple and straightforward idea, developers often overlooked this idea. As a result, the code quality will decrease to a point where a developer can take days to do a simple code modification.

1. Split into small functions

The first step for writing better clean java code is organization. Putting together pieces of code that are logically related and start separating different aspects of your logic.

One essential tool that can help you to get started is functions. Writing short functions with descriptive names is a great way to organize logic and make a long code piece more readable and approachable.

Let’s see that in action in the following example. Below you can see a piece of code for the next game.

Study the code snippets for about 3 or 5 minutes and see how much you can understand.

    @Override
    public void actionPerformed(ActionEvent e) {

        ballx = ballx + ballxSpeed;
        bally = bally + ballySpeed;
        if (ballx >= paddelx && ballx <= paddelx + 100 && bally >= 475) {
            ballySpeed = -7;
            score++;
        }

        if (bally >= 700 ) {
            if((score1+score) > bestscore) {
            	bestscore = score + score1;
            }
            score = 0;
            score1 = 0;
            bally = 30;
            bally1 = 10;

            gameOver = true;
        }
        if (bally <= 0) {
            ballySpeed = 7;
        }
        if (ballx >= 775) {
            ballxSpeed = -5;
        }
        if (ballx <= 0) {
            ballxSpeed = 5;
        }
        ballx1 = ballx1 + ballx1Speed;
        bally1 = bally1 + bally1Speed;

        if (ballx1 >= paddelx && ballx1 <= paddelx + 100 && bally1 >= 475) {
            bally1Speed = -14;
            score1++;
        }

        if (bally1 >= 700) {
            score1 = 0;
            bally1 = 10;
        }
        if (bally1 <= 0) {
            bally1Speed = 14;
        }
		if (ballx1 >= 775) {
            ballx1Speed = -10;
        }
        if (ballx1 <= 0) {
            ballx1Speed = 10;
        }
        repaint();
    }

Did you manage to understand it? Probably you did, but I bet it took you more than 3 minutes. Plus, even though you now understand it, you will forget the code’s intent in a couple of days, and you will need to analyze it again.

Additionally, think about maintainability. For instance, how difficult to modify this code? For example, adding a new paddle can turn into a complicated task.

Let’s see how to make it more readable and maintainable using functions:

    @Override
    public void actionPerformed(ActionEvent e) {

        moveRedBall(ballxSpeed, ballySpeed);

        if (redBallTouchPaddel()) {
            changeRedBallToUpDirection();
            incrementScore(1);
        }
		if (redBallReachWindowBottom()) {
            updateScore();
            resetScore();
            moveRedBallToInitialPosition();
            gameOver = true;
        }
		if (redBallReachWindowTop()) {
            changeRedBallToDownDirection();
        }
        if (redBallTouchRightSide()) {
            changeRedBallToLeftDirection();
        }
		if (redBallReachLeftSide()) {
            changeRedBallToRightDirection();
        }
        moveBlackBall(ballx1Speed,bally1Speed);
		if (blackBallTouchPaddel()) {
            changeBlackBallToUpDirection();
            incrementBackBallScore(1);
        }
		if (blackBallReachWindowBottom()) {
            resetScore();
            moveBlackBallToInitialPosition();
        }
		if (blackBallReachWindowTop()) {
            changeBlackBallToDownDirection();
        }
        if (blackBallTouchRightSide()) {
            changeBlackBallToLeftDirection();
        }
		if (blackBallReachLeftSide()) {
            changeBlackBallToRightDirection();
        }		
        repaint();
    }

And here are the small functions:

    public void updateScore() {
        if((score1+score) > bestscore) {
            bestscore = score + score1;
        }
    }
    public void moveRedBallToInitialPosition(){
        bally = 30;
    }
    public void moveBlackBallToInitialPosition(){
        bally1 = 10;
    }
    public void resetScores(){
        score = 0;
        score1 = 0;
    }
    public void incrementScore(int increment){
        score+=increment;
    }
    public void incrementBackBallScore(int increment){
        score1+=increment;
    }
    public void moveBlackBall(int xSpeed, int ySpeed){
        ballx1 = ballx1 + xSpeed;
        bally1 = bally1 + ySpeed;
    }
    public void moveRedBall(int xSpeed, int ySpeed){
        ballx = ballx + xSpeed;
        bally = bally + ySpeed;
    }
    public void changeRedBallToUpDirection(){
        ballySpeed = -7;
    }
    public void changeBlackBallToUpDirection(){
        bally1Speed = -14;
    }
    public void changeRedBallToDownDirection(){
        ballySpeed = 7;
    }
    public void changeBlackBallToDownDirection(){
        bally1Speed = 14;
    }
    public void changeRedBallToLeftDirection(){
        ballxSpeed = -5;
    }
    public void changeBlackBallToLeftDirection(){
        ballx1Speed = -10;
    }
    public void changeRedBallToRightDirection(){
        ballxSpeed = 5;
    }
    public void changeBlackBallToRightDirection(){
        ballx1Speed = 10;
    }
    public boolean redBallReachWindowBottom(){
        return bally >= 700;
    }
    public boolean blackBallReachWindowBottom(){
        return bally1 >= 700;
    }
    public boolean redBallReachWindowTop(){
        return bally <= 0;
    }
    public boolean blackBallReachWindowTop(){
        return bally1 <= 0;
    }
    public boolean blackBallReachLeftSide(){
        return ballx1 <= 0;
    }
    public boolean redBallReachLeftSide(){
        return ballx <= 0;
    }
    public boolean blackBallTouchPaddel(){
        return ballx1 >= paddlex && ballx1 <= paddlex + 100 && bally1 >= 475;
    }
    public boolean redBallTouchPaddel(){
        return ballx >= paddlex && ballx <= paddlex + 100 && bally >= 475;
    }
    public boolean blackBallTouchRightSide(){
        return ballx1 >= 775;
    }
    public boolean redBallTouchRightSide(){
        return ballx >= 775;
    }

At this point, you probably think that there is more code now than before; how is this helping? And you are right, there is more code, but this is only the first step. Extracting functions can help us spot duplication and identify the intent of the code statements, which means now we understand the code a lot better, and we can move on to the next step.

2. Organise in classes

The second step to writing better java clean code more readable and maintainable is organizing the code in classes. In this case, organizing means identifying the entities in our code and their actions and then extract them into classes.

For instance, after reading the list of new functions, you probably notice two main entities: balls and score. And the actions have to do with checking if the balls collide with other elements, moving the balls, and updating the scores. Keep in mind your classes should be small, so the intent is clear and easy to understand.

See below an example of how to move the code into classes. First, I will create a class named Ball containing all functions to do with updating or checking the ball:

public class Ball {
    public int x;
    public int y;
    public int xSpeed;
    public int ySpeed;

    public Ball(int x, int y, int xSpeed, int ySpeed){
        this.x = x;
        this.y = y;
        this.xSpeed = xSpeed;
        this.ySpeed = ySpeed;
    }
	public void moveToInitialPosition(int initialPosition){
         y = initialPosition;
    }
    public void move(){
        x = x + xSpeed;
        y = y + ySpeed;
    }
    public void changeVerticalDirection(int speed){ ySpeed = speed; }

    public void changeHorizontalDirection(int speed){ xSpeed = speed; }
    public boolean hasReachBottom(){ return y >= 700; }

    public boolean hasReachWindowTop(){ return y <= 0; }

    public boolean hasReachLeftSide(){ return x <= 0; }

    public boolean hasReachRightSide(){
        return x >= 775;
    }

	public boolean hasTouchPaddel(int paddelx)
    {
        return x >= paddelx && x <= paddelx + 100 && y >= 475;
    }
}

And another class that contains all methods that manipulate the scores:

public class ScorePanel {

    public int ballScore;
    public int ballScore1;
    public int bestScore;

    public void updateBestScore() {
        if((ballScore1+ballScore) > bestScore) {
            bestScore = ballScore1+ballScore;
        }
    }

    public void reset(){
        ballScore = 0;
        ballScore1 = 0;
    }

    public void resetSecondaryScore(){ ballScore1 = 0; }

    public void increment(int increment){ ballScore+=increment; }

    public void incrementSecondScore(int increment){ ballScore1+=increment; }

    public boolean hitSecondBallScoreLimit(){ return ballScore >= 5; }

    public int totalScore(){ return ballScore + ballScore1; }
}

And finally, let’s see how original code looks after the class reorganisation:

    // constants
    private final static int SLOW_UP_DIRECTION = -7;
    private final static int SLOW_DOWN_DIRECTION = 7;
    private final static int SLOW_LEFT_DIRECTION = -5;
    private final static int SLOW_RIGHT_DIRECTION = 5;

    private final static int FAST_UP_DIRECTION = -14;
    private final static int FAST_DOWN_DIRECTION = 14;
    private final static int FAST_LEFT_DIRECTION = -10;
    private final static int FAST_RIGHT_DIRECTION = 10;

    @Override
    public void actionPerformed(ActionEvent e) {

        redBall.move();

        if (redBall.hasTouchPaddel(paddlex)) {
            redBall.changeVerticalDirection(SLOW_UP_DIRECTION);
            scorePanel.increment(1);
        }
        if (redBall.hasReachBottom()) {
            scorePanel.updateBestScore();
            scorePanel.reset();
            redBall.moveToInitialPosition(30);
            gameOver = true;
        }
        if (redBall.hasReachWindowTop()) {
            redBall.changeVerticalDirection(SLOW_DOWN_DIRECTION);
        }

        if (redBall.hasReachRightSide()) {
            redBall.changeHorizontalDirection(SLOW_LEFT_DIRECTION);
        }
        if (redBall.hasReachLeftSide()) {
            redBall.changeHorizontalDirection(SLOW_RIGHT_DIRECTION);
        }

        blackBall.move();

        if (blackBall.hasTouchPaddel(paddlex)) {
            blackBall.changeVerticalDirection(FAST_UP_DIRECTION);
            scorePanel.incrementSecondScore(1);
        }
        if (blackBall.hasReachBottom()) {
            scorePanel.resetSecondaryScore();
            blackBall.moveToInitialPosition(10);
        }
        if (blackBall.hasReachWindowTop()) {
            blackBall.changeVerticalDirection(FAST_DOWN_DIRECTION);
        }
        if (blackBall.hasReachRightSide()) {
            blackBall.changeHorizontalDirection(FAST_LEFT_DIRECTION);

        }
        if (blackBall.hasReachLeftSide()) {
            blackBall.changeHorizontalDirection(FAST_RIGHT_DIRECTION);
        }
        repaint();
    }

The code is a lot more readable. With a quick read-through, you can understand that the code checks if the balls collide with any object and change their direction. Plus, updating the score every time the user moves the paddle and keep the first ball from reaching the bottom screen. With these simple tips, the code quality has hugely improved. Of course, you could continue improving, but this already gets the code to a decent level.

3. Create Unit Test

Another vital aspect of programming is code confidence. Once you organize the code and is looking clean and more structured, How can you increase you assurance that the code does what it should do? You could test it manually. However, that will mean you will need to do all your check and testing every time you modify the code. That can become tedious.

A more professional and practical way is automating your test using JUnit. This way, you will write code that checks the code is behaving as expected. Although writing these tests takes a bit longer than a manual test at first, you end up saving a lot of time. Every time you introduce a new modification, you can sit back and run the tests to check all is working.

Let’ see an example. Below I have included a code snippet, checking if two sentences are anagrams ( Check what is an anagram). Usually, after you write a code like this, you can analyze it and have some code confidence. However, you can’t be sure until you have proof. The proof is the test.

public class Anagrams {
public static boolean checkIfAnagrams3(String text1,String text2){

        // Remove whitespaces and uppercase
        String cleanText1 = text1.replace(" ","").toLowerCase();
        String cleanText2 = text2.replace(" ","").toLowerCase();
        if(cleanText1.length()!=cleanText2.length()) return false;

        // Sum up the value of all characters
        int sum1 = 0;
        int sum2 = 0;
        for(int i=0;i<cleanText1.length();i++){
            sum1=sum1+(int)cleanText1.charAt(i);
            sum2=sum2+(int)cleanText2.charAt(i);
        }
        return sum1==sum2;
    }
}

And now let’s create a test:

import org.junit.Assert;
import org.junit.Test;

public class AnagramsTest {

    @Test
    public void testSentencesThatAreAnagrams(){
        boolean isAnagram1 = Anagrams.checkIfAnagrams1("New York Times","monkeys write");
        Assert.assertTrue(isAnagram1);

        boolean isAnagram2 =  Anagrams.checkIfAnagrams1("McDonald's restaurants",  "Uncle Sam's standard rot");
        Assert.assertTrue(isAnagram2);

        boolean isAnagram3 =  Anagrams.checkIfAnagrams1("Rocket Boys",  "October Sky");
        Assert.assertTrue(isAnagram3);
    }

    @Test
    public void testSentencesThatAreNotAnagrams(){
        boolean isAnagram1 = Anagrams.checkIfAnagrams1("Hey there","How are you?");
        Assert.assertFalse(isAnagram1);
    }
}

After creating the test, you can execute it every time you make any change in the original code and automatically check your code still works.

4. Avoid returning Null Pointers

Up to this point, we have seen three simple ways to will help to better java code, and here is the last tip. Avoid returning null. This tip might sound trivial; however, worth mentioning since the NullPointerException is one of the most frequent exceptions raised by java programs. However, it shouldn’t be since it is easily avoidable.

When you return null in a function or method, any caller code must check for null to avoid a null pointer. Consequently, you will need to add more code to doing null checks and prevent null pointers. A null value floating within your program creates a risk since it will provoke an exception as soon as any other piece of code tries to access it.

So, What can you return instead? You should return an empty value, an special case, or throw an exception and make sure the exception is handled somewhere in the caller code. Avoiding null references will minimize the risk of NullPointer exceptions.

Let’s see an example. Consider the following code.

import java.util.HashMap;
import java.util.Map;

public class CountryFactory {

    private Map<String,Country> countries = new HashMap<>();

    public CountryFactory(){
        countries.put("uk", new Country("United Kingdom","uk","London"));
        countries.put("fr", new Country("France","fr","Paris"));
        countries.put("es", new Country("Spain","es","Madrid"));
    }

    public Country findCountry(String prefix){
        return countries.get(prefix);
    }

    public class Country{

        private String name;
        private String prefix;
        private String capital;

        private Country(String name, String prefix, String capital){
            this.name = name;
            this.prefix = prefix;
            this.capital = capital;
        }

        public String name(){return name;}
        public String capital(){return capital;}

    }

    public static void main(String[] args){
        CountryFactory countryFactory = new CountryFactory();
        Country country = countryFactory.findCountry("it");
        System.out.println("Country name: "+ country.name() + "with capital "+country.capital());

        Country country2 = countryFactory.findCountry("es");
        System.out.println("Country name: "+ country2.name() + "with capital "+country.capital());
    }

}

The above code snippet is returning a null pointer when calling the method findCountry(). As a result, the main method will encounter a null pointer and resume the execution without printing the second country.

The best practice to avoid returning null references is returning a parti case or empty. Thus I will create a Country instance named unknown and return it when the findCountry() method can’t find any matching country.

// Place this line under the countries map 
// from the previous code snippet 
private Country UNKNOWN = new Country("Unknown ","","Unknown");
 
// Replace the findCountry method
public Country findCountry(String prefix){
	Country foundCountry = countries.get(prefix);
    if(foundCountry == null){
    	return UNKNOWN;
    }
    return foundCountry;
}
 

Handling null references, as explained above, is a clean and neat way to deal with null references and minimize null pointer exceptions.

Conclusion

To summarize, we have seen four simple ways to write readable and clean java code. Reorganize your code using functions and classes, writing JUnit tests to make sure your code works as expected. And lastly, avoid returning null reference to minimize the risk of null pointer exceptions.

I hope you enjoy the article and find it helpful. Thank you so much for reading and supporting this blog!

More Interesting Articles

Apache Spark Java Tutorial: Simplest Guide to Get Started

All-in-One Guide: How to work with a CSV file in Java

How to Write a Check Anagram Program in Java

Maven Jar Plugin – How to make an executable jar file

Project-Based Programming Introduction

Steady pace book with lots of worked examples. Starting with the basics, and moving to projects, data visualisation, and web applications

100% Recommended book for Java Beginners

Unique lay-out and teaching programming style helping new concepts stick in your memory

90 Specific Ways to Write Better Python

Great guide for those who want to improve their skills when writing python code. Easy to understand. Many practical examples

Grow Your Java skills as a developer

Perfect Boook for anyone who has an alright knowledge of Java and wants to take it to the next level.

Write Code as a Professional Developer

Excellent read for anyone who already know how to program and want to learn Best Practices

Every Developer should read this

Perfect book for anyone transitioning into the mid/mid-senior developer level

Great preparation for interviews

Great book and probably the best way to practice for interview. Some really good information on how to perform an interview. Code Example in Java