How to refactor Java main method code

22nd March 2019 0 By Alin Bistrian

There are many reasons why we may end up with a lot of code into the main method.

 

Few reasons can be, students have a school assignment and they put all their code in the main method, or simply copy an example from the internet that has all the code inside main.

 

While I was learning I was very often confronted with similar situations where I got an example from the internet and, either I had to extract the functionality that I needed or I had to refactor so I can integrate the entire example with my code.

 

In either situation, I found it difficult to refactor the code so I can properly use it in my project.

 

That is why I have decided to start a blog post series where I will present the refactoring methods I have found and developed in the past years.

 

This methods still help me a lot in my daily programming activities and I would like to share them with you as well.

 

Moving the code outside of the main method

To start with I have picked up a code example from the internet where all the code is in the main method:


public class Calculator {

    public static void main(String[] args) {
        // TODO code application logic here

        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i < 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            String comment = String.valueOf(output);
            int grade = Integer.parseInt(output);

            if(comment.equals("QUIT")){
                break;
            }else if (!comment.equals("QUIT") && grade < 0){
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
                continue;
            }

            total += grade;
            averageMarks = total/5;
        }

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }
}

What is this program suppose to do?

It will take five grades from the user input and will calculate the average and the total “grade.”

 

It needs to validate the user input, and if the user inputs an invalid grade, it should ask the user to enter again. The program will terminate if the user enters “QUIT“.

 

The first problem we have here is that all the code is in the main method which is not good because in Java we should use the main method just to start our program.

 

The first task is to take all the code out of main and put it into a temporary method, let’s say “calculateGrade()” and call this method from “main“.

 

Here is how our code looks like now:

 

public class Calculator {

    public static void main(String[] args) {
        GradeCalculator calc = new GradeCalculator();
        calc.calculateGrade();
    }

    public void calculateGrade() {
        // TODO code application logic here

        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i &lt; 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            String comment = String.valueOf(output);
            int grade = Integer.parseInt(output);

            if(comment.equals("QUIT")){
                break;
            }else if (!comment.equals("QUIT") && grade < 0){
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
                continue;
            }

            total += grade;
            averageMarks = total/5;
        }

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }
}

 

Before we continue, I want to clarify something. This post is about code refactoring, and it does not evaluate whether the requirements are correct.

 

Things we want our program to do

Now we can look at the method “calculateGrade()” and identify code that can be moved to a different method but before we do that let us write a list of thing that we expect our software will do:

 

  1. When we launch the program, it displays the App Description: “This app is use to calculate average and total of the grade”
  2.  Read input from the user for five times and validate it every time by doing the following checks:
    • Check that if the value is “QUIT“, print the message “You choose to quite! Goodbye!” and terminate the program.
    • Check that the inputted value is a numeric string like “3“, “5.50” and ask the user to input again if the value is not numeric like “three“, “five“. When asking the user to input again display the message: “Enter a valid number or QUIT“.
    • Check that the inputted grade is greater than (zero) and ask the user to input again if the value is less than 0 (zero). When requesting the user to input again display the message: “Enter a valid number or QUIT“.
  3. Display the result if all the user input is valid.

 

With this list, we have broken down the big task of refactoring in smaller ones.

 

From now on I will list the things we want to refactor then I will display the modified code after which we will discuss the changes.

 

 

Display App Description

Now let us start with the first item on our list and a few other small changes as follows:

  • We removed the “// TODO code application logic here” comment because it is no longer necessary.
  • We have moved the statements that print the app description into a different method.

 


public class Calculator {

    public static void main(String[] args) {
        GradeCalculator calc = new GradeCalculator();
        calc.calculateGrade();
    }

    public void calculateGrade() {
        displayAppDescription();

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i &lt; 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            String comment = String.valueOf(output);
            int grade = Integer.parseInt(output);

            if(comment.equals("QUIT")){
                break;
            }else if (!comment.equals("QUIT") && grade < 0){
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
                continue;
            }

            total += grade;
            averageMarks = total/5;
        }

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

    public void displayAppDescription() {
        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");
    }
}

 

Before we continue, let us look at the code and check if there are any redundancies that we can remove.

 

We can observe that line “19” is redundant:  “String comment = String.valueOf(output);” because we already have the inputted value into “output” string variable on line “18” hence we will remove it and whenever the code expects “comment” we will use the “output” variable which holds the value user has entered.

 

We so that the average marks are calculated every time user inputs a grade, but it is not used only until the user inputs all the grades. That is why we can move this calculation outside of the “for” loop. From “31” to “34

 

In the following code we can see the changes we have talked about:

 


public class Calculator {

    public static void main(String[] args) {
        GradeCalculator calc = new GradeCalculator();
        calc.gradeCalculator();
    }

    public void gradeCalculator() {
        displayAppDescription();

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i &lt; 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            //String comment = String.valueOf(output); <----REMOVED
            int grade = Integer.parseInt(output);

            if(output.equals("QUIT")){     //<----Replaced comment with output
                break;
            }else if (!output.equals("QUIT") && grade < 0){  //<----Replaced comment with output
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
                continue;
            }

            total += grade;
            //averageMarks = total/5;   <----- Statement moved outside for loop
        }
 
        averageMarks = total/5;     //<----- Outside the for loop

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

    public void displayAppDescription() {
        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");
    }
}

 

 

And this is how our code looks like after we made the changes and cleaned it up:

 


public class GradeCalculator {

    public static void main(String[] args) {
        GradeCalculator calc = new GradeCalculator();
        calc.calculateGrade();
    }

    public void calculateGrade() {
        displayAppDescription();

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i &lt; 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            int grade = Integer.parseInt(output);

            if(output.equals("QUIT")){
                break;
            }else if (!output.equals("QUIT") && grade < 0){
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
                continue;
            }

            total += grade;
        }

        averageMarks = total/5;

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

    public void displayAppDescription() {
        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");
    }
}

 

The code looks better, but there is still a big problem that we did not even add to our list, probably because we did not see it when we created the list. The problem is related to NumberFormatExeption which is not handled in the code above. For now, we will focus on something else and we will discuss it later when it will make more sense.

 

User Input Validation

Now moving on to the second point on our list, and that is to read and validate the user input for five times.

 

Looking at the code, we see that all the reading and the validation happens inside the “for” loop:

 


for(int i = 0; i &lt; 5; i++){
    System.out.println("Enter the marks: ");
    String output = input.nextLine();
    int grade = Integer.parseInt(output);

    if(output.equals("QUIT")){
        break;
    } else if (!output.equals("QUIT") && grade < 0){
        System.out.println("Enter a valid number or QUIT");
        i = i - 1;
        continue;
    }

    total += grade;
}

 

Things that happen inside this loop:

  • A message is printed which asks the user to enter the grade marks.
  • At “3” the code reads the user input and store it in the “output” string variable.
  • Between lines “6” and “12“, the user input is validated.

 

Since we have identified the part of the code where the user input is validated, we can move it in a different method which we can name it as “validateInput()

 


public class GradeCalculator {

    public static void main(String[] args) {
        GradeCalculator calc = new GradeCalculator();
        calc.calculateGrade();
    }

    public void calculateGrade() {
        displayAppDescription();

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i &lt; 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            
            

            total += grade;
        }

        averageMarks = total/5;

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

    public void validateInput(String output) {
        int grade = Integer.parseInt(output);

        if(output.equals("QUIT")){
            break;
        }else if (!output.equals("QUIT") && grade < 0){
            System.out.println("Enter a valid number or QUIT");
            i = i - 1;
            continue;
        }
    }

    public void displayAppDescription() {
        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");
    }

}

 

We have moved the code that validates user input into a new method without making any changes. Of course, this will not even compile but that is ok for now all we care about is that all this code is isolated inside a method where all the validation will take place.

 

This is a good thing because in the future if we need to change the validation logic, we know exactly where to go. We will make changes to it a little bit later.

 

First let us go back to our for loop, where we know that if the user enters “QUIT” the program should terminate but in this code there is no message printed to the user telling the user goodbye at least which is not polite 🙂 so I added a message that will be displayed to the user informing him that he chooses to quit the program.

 

We want to check if the user wants to quit before we do any other input validation.

 

To do that we will add an if/else statement in the for loop so every time there is some input from the user we first check if that input is “QUIT” and if it is then we will print the goodbye message and terminate the program.

 

Else we will validate the input further by calling the “validateInput()” method and passing the user input which was stored in the “output” string variable.

 

Here is how these changes look like:

 


//.....

    for(int i = 0; i &lt; 5; i++){
        System.out.println("Enter the marks: ");
        String output = input.nextLine();
            
        if(output.equals("QUIT")) {
            System.out.println("You choose to quite! Goodbye!");
            System.exit(0);
        } else {
            validateInput(output);
        }

        total += grade;
    }    

//.....

 

Now we know what will happen if the user inputs “QUIT“, what we want to do next is to validate the input if it is different from “QUIT“. To do that we need to look into “validateInput()” method.

 

The way I see it is very simple if the input is valid, return true else return false. Which will bring up another question, how does a valid input look like?

 

Because it is a grade calculator program, I would say the user input to be valid needs to be a numeric string and its value should be greater than 0 (zero).

  • Numeric string example: “3”, “5.50” – Valid
  • Non numeric string example: “three”, “five” – Invalid
  • Negative Number: “-4.00” – Invalid

 

So we need to validate that “output” variable that is passed to the method can be converted to a number (it can be converted if it is a numeric string like in the above example).

 

Before we implement the validation logic we need to clean up the code a little bit and make some changes to the method return type. We need to change it to return a boolean, true if the input is valid or false if the input is invalid.

 

Here is the list of changes we want to make:

  • Remove the for loop related code:
    • break;
    • i = i -1;
  • Remove code that checks for “QUIT” because we already handled that value in “calculateGrade()” method.
  • Change the method return type from “void” to “boolean
  • Declare a boolean variable and return it from the method

 

This is the “validateInput()” method before these changes:

 


    public void validateInput(String output) {
        int grade = Integer.parseInt(output);

        if(output.equals("QUIT")){
            break;
        }else if (!output.equals("QUIT") && grade < 0){
            System.out.println("Enter a valid number or QUIT");
            i = i - 1;
            continue;
        }
    }

 

And this is how the method looks like after the changes:

 


    public boolean validateInput(String output) {
        boolean inputValid = false;

        int grade = Integer.parseInt(output);
		
		if (grade < 0){
            System.out.println("Enter a valid number or QUIT");
        }

        return inputValid;
    }

 

Now we can focus on validating user input but before we do that I would like to make a change to this line:

 


int grade = Integer.parseInt(output);

 

This line is trying to parse the user input into an integer like 3 or 5, but this is a grade calculator program, so it is very probable that the user will enter a number with some decimal places like 4.50

 

If the user does that, then the above line of code will throw a NumberFormatException, and the program will terminate abnormally. We can handle the exception but before we want to enhance the program to accept input with decimal places.

 

To do that we need to change the above line as follows:

 


double grade = Double.parseDouble(output);

 

Now our program can calculate the grade even if the user enters a double number like 4.50 which leaves us with two more validations to do.

 

  • Validate that the user inputs a numeric string
  • In case the string is a numeric string validate that the number is greater than 0 (zero)

 

The code is already validating the condition where the user inputs a number that is less than 0 (zero) but is printing a message instead of setting the value “inputValid” to false. So the first thing is to remove the print statement and set the value of “inputValid to false.

 

Since we know that if the number is less than 0 (zero), the input is invalid and we need to set the “inputValid” to false and if the number is greater then 0 (zero) set it to true.

 

To do that we need to add an else statement to the if that we already have. And inside that else statement we set the “inputValid” true because if we get here, then the input is, well valid.

 

This is how the code looks like after all these changes:

 


    public boolean validateInput(String output) {
        boolean inputValid = false;

        double grade = Double.parseDouble(output);
		
		if (grade < 0){
            inputValid = false;
        } else {
        	inputValid = true;
        }

        return inputValid;
    }

 

In the code above there is a code redundancy on line 4:

 

 
double grade = Double.parseDouble(output); 

 

And we can move this line inside the  if condition like this:

 

 

    public boolean validateInput(String output) {
        boolean inputValid = false;
		
		if (Double.parseDouble(output) < 0){
            inputValid = false;
        } else {
        	inputValid = true;
        }

        return inputValid;
    }

 

The code above looks better, but there are still two problems with this method:

 

  1. We removed the code that will inform the user that input is invalid and he/she should enter again
  2. If the user inputs a non-numeric string like “five” when the “Double.parseDouble(output)” statement is evaluated a NumberFormatException is thrown.

 

Adding the missing print statement:

 


    public boolean validateInput(String output) {
        boolean inputValid = false;
		
		if (Double.parseDouble(output) < 0){
			System.out.println("Enter a valid number or QUIT");
            inputValid = false;
        } else {
        	inputValid = true;
        }

        return inputValid;
    }

 

In the code above if the user inputs a negative number the message “Enter a valid number or QUIT” is printed and “inputValid” variable is set to false indicating that the input was invalid.

 

Handle the NumberFormatException with a try/catch block

 

To handle NumberFormatException exception, we will surround the if/else with a try/catch block like in the following code:

 


    public boolean validateInput(String output) {
        boolean inputValid = false;
		
        try {
            if (Double.parseDouble(output) < 0){
                System.out.println("Enter a valid number or QUIT");
                inputValid = false;
            } else {
                inputValid = true;
            }
        } catch (NumberFormatException e) {
            System.out.println("Enter a valid number or QUIT");
            inputValid = false;
        }

        return inputValid;
    }

 

As you can see when we caught the exception we printed the message “Enter a valid number or QUIT” and we set the “inputValue” to false. Why? Because if the user inputs “five” that is an invalid input and we should inform the user to try again.

 

Printing this message here we have introduced a code duplication. The message that is printed to the user appears inside the if statement and inside the catch statement which is not good and we need to remove the duplication.

 

Removing code duplication and refactor the for loop

The way to remove that duplication is by moving this print statement outside the “validateInput()” method and put it inside the “for” loop in the “calculateGrade()” method and print it if the user input is invalid.

 

To do that we had to introduce an “if/else” statement inside the “for” loop like in the following code:

 


    public void calculateGrade() {
        displayAppDescription();

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i < 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            
            if(output.equals("QUIT")) {
                System.out.println("You choose to quite! Goodbye!");
                System.exit(0);
            } else {
                validateInput(output);
            }

            total += grade;
        }

        averageMarks = total/5;

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

There are a few other things we need to address.

 

Remove “System.out.println(“Enter a valid number or QUIT”);” statement from “validateInput()” method.

 

We can do this because we have moved this statement to the “calculateGrade()” method. And here is how this method looks like now:

 


    public boolean validateInput(String output) {
        boolean inputValid = false;
		
        try {
            if (Double.parseDouble(output) < 0){
                inputValid = false;
            } else {
                inputValid = true;
            }
        } catch (NumberFormatException e) {
            inputValid = false;
        }

        return inputValid;
    }

 

Going back to “calculateGrade()” method.

 

First, we want to change the “averageMarks” and total variable type from int to double because the user may enter a double 5.50 and the int variable type will not be able to store it.

 

The second thing we can see is that we no longer have a “grade” variable and we can change it with “Double.parseDouble(output)”  statement.

 

The reason why we can do this is that we already know that the input is valid since the “validateInput(output)” method returned true. Otherwise, the execution would not have entered this “else if” block.

 

Before we look at the new code, we need to add to it one more missing part.

 

In the else statement we know that the user input is invalid and we print the message to the user.

 

Then we want the user to input again for the same “for” loop iteration. To do that we need to add the following code “i = i – 1;” in case the user input is invalid, and the code execution reaches else block. This code was present but we removed it when we moved the validation logic into a new method and now we need to add it back.

 

Here is how the “calculateGrade()” method looks like after these changes:

 


    public void calculateGrade() {
        displayAppDescription();

        int averageMarks = 0;
        int total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i < 5; i++){
            System.out.println("Enter the marks: ");
            String output = input.nextLine();
            
            if(output.equals("QUIT")) {
                System.out.println("You choose to quite! Goodbye!");
                System.exit(0);
            } else if(validateInput(output)) {
                total += grade;
            } else {
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
            }
        }

        averageMarks = total/5;

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

 

Before we look at the entire refactored code, I would like to make another change.

 

For readability purpose, I would like to change the name of the variable that holds the user input from “String output….” to “String input….

 

Here is how our refactored code looks like:

 


public class Calculator {

    public static void main(String[] args) {
        GradeCalculator calc = new GradeCalculator();
        calc.calculateGrade();
    }

    
    public void calculateGrade() {
        displayAppDescription();

        double averageMarks = 0;
        double total = 0;

        Scanner input = new Scanner(System.in);

        for(int i = 0; i < 5; i++){
            System.out.println("Enter the marks: ");
            String userInput = input.nextLine();
            
            if(userInput.equals("QUIT")) {
                System.out.println("You choose to quite! Goodbye!");
                System.exit(0);
            } else if(validateInput(userInput)) {
                total += Double.parseDouble(output);
            } else {
                System.out.println("Enter a valid number or QUIT");
                i = i - 1;
            }
        }

        averageMarks = total/5;

        System.out.println("Average marks is: " +averageMarks);
        System.out.println("Total marks is: " +total);
    }

    public boolean validateInput(String userInput) {
        boolean inputValid = false;
        
        try {
            if (Double.parseDouble(userInput) < 0){
                inputValid = false;
            } else {
                inputValid = true;
            }
        } catch (NumberFormatException e) {
            inputValid = false;
        }

        return inputValid;
    }

    public void displayAppDescription() {
        System.out.println("***********************************************************");
        System.out.println("This app is use to calculate average and total of the grade");
        System.out.println("***********************************************************");
    }
}

 

Conclusion

Of course, there is still more refactoring that we can do, but it is enough for one post. Before we do any more refactoring we need to see at least a few benefits from the refactoring we did.

 

For example, if we want to change the app description message we know where to look to make that change. Because we have moved that part into a method named “displayAppDescription()“.

 

If we need to change the input validation logic we can do that inside the “validateInput()” method which saves us from reading through code that is not related to the input validation.

 

Code that is concerned with input validation will be in “validateInput()” method, code that is concerned with app description will be in “displayAppDescription()” method.

This post is a preview of how the separation of concerns looks like in Object Oriented Programming. At least the way I see it.

 

In the next post, we will do more refactoring, and we will introduce a few new functionalities for this program.

 

Thank you very much for reading this article I hope you enjoyed it.

 

I will publish the next article on refactoring very soon.

 

Please let me know if there is any part that is not clear or if you see any mistake. I will be very happy to clarify or correct anything that may be wrong in this post.