DEV Community

Yegor Voronianskii
Yegor Voronianskii

Posted on

Stop writing code in main method

Greetings to everyone who reads this post! Due to my work, I often run into novice programmers and look at their code. Sometimes it is beautiful, sometimes not, but I understand that a person learns, and the further he masters the best practices and technologies, the better his code becomes. But I would still like to pay some attention to such a simple task that almost all novice programmers face - writing a calculator

Alt Text

Here's what novice programmers write:

import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        int sum = 0;
        int number1 = 0, number2 = 0;
        boolean correct = true;
        char operation1;
        Scanner sc = new Scanner(System.in);
        System.out.println("Enter first number:");
        while (!sc.hasNextInt()) {
            System.out.println("Sorry, it is not a digit. Try again, please");
            sc.next();
        }
        number1 = sc.nextInt();
        System.out.println("Thanks! You entered number: " + number1);
        System.out.println("Enter a number:");
        while (!sc.hasNextInt()) {
            System.out.println("It is not a digit. Try again");
            sc.next();
        }
            number2 = sc.nextInt();
        System.out.println("Thanks! It is a digit " + number2);
        do {
            correct = true;
            System.out.println("What would you like to do? + - / *");
            operation1 = sc.next().charAt(0);
            switch (operation1) {
                case '+':
                    sum = number1 + number2;
                    System.out.println("Sum of this two equals to " + sum);
                    break;
                case '-':
                    sum = number1 - number2;
                    System.out.println("The difference between equals to: " + sum);
                    break;
                case '/':
                    if (number2 != 0) {
                        sum = number1 / number2;
                        System.out.println("Divied equals to: " + sum);
                    } else {
                        System.out.println("Divided by zero is not allowed.");
                        correct = false;
                    }
                    break;
                case '*':
                    sum = number1 * number2;
                    System.out.println("Multiplication of this two: " + sum);
                    break;
                default:
                    System.out.println("Unknow operation");
                    correct = false;
                    break;
            }
            } while (correct != true);
        }
    }
Enter fullscreen mode Exit fullscreen mode

What problems do you see in this code? I see the following problems

  1. Everything is written in the main method, and certainly represents more than 10 lines
  2. There are no tests for this code, that is, there is no confidence that it will work correctly
  3. Structural programming style - the switch statement encourages this
  4. Using the Scanner, but it can be forgiven because it has convenient methods for getting numbers right away.

How would you fix this code? I would fix the given code as follows

  1. Would create an Operation interface for all operations on numbers
public interface Operation {
    int execute(int a, int b);
}
Enter fullscreen mode Exit fullscreen mode

And accordingly I would implement this interface in the following classes

public class Addition implements Operation {

    @Override
    public int execute(int a, int b) {
        return a + b;
    }
}

public class Deduction implements Operation {
    @Override
    public int execute(int a, int b) {
        return a - b;
    }
}

public class Division implements Operation {
    @Override
    public int execute(int a, int b) {
        return a / b;
    }
}

public class Multiplication implements Operation {
    @Override
    public int execute(int a, int b) {
        return a * b;
    }
}
Enter fullscreen mode Exit fullscreen mode

I would do the same with input and output methods, but before we start writing them, let's add a very useful library - Apache Commons

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

The interface for the operation with input-output of information will look like this

public interface IO {

    int getNumber(String message, String errorMessage);

    Operation getOperation(String message);

    void printResult(String result);
}
Enter fullscreen mode Exit fullscreen mode

And this is how its implementation will look like

public class ConsoleIO implements IO {

    private final BufferedReader bufferedReader;
    private final List<String> allowedOperation;
    private Map<String, Operation> operationMap;

    public ConsoleIO(final List<String> allowedOperation) {
        this.bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        this.allowedOperation = allowedOperation;
        fullFillServiceMap();
    }

    @Override
    public int getNumber(String message, String errorMessage) {
        if (StringUtils.isNotBlank(message)) {
            System.out.println(message);
        }
        boolean validNumber = false;
        int numberResult = 0;
        while (!validNumber) {
            try {
                String line = bufferedReader.readLine();
                if (StringUtils.isNumeric(line)) {
                    numberResult = Integer.parseInt(line);
                    validNumber = true;
                }
            } catch (IOException e) {
                System.out.println(errorMessage);
                throw new RuntimeException(e);
            }
        }

        return numberResult;
    }

    @Override
    public Operation getOperation(String operationKey) {
        if (this.allowedOperation == null || this.allowedOperation.isEmpty()) {
            throw new IllegalStateException("Не заданы разрешенные операции");
        }
        boolean validOperation = false;
        while (!validOperation) {
            if (this.allowedOperation.contains(operationKey)) {
                validOperation = true;
            }
        }
        return operationMap.get(operationKey);
    }

    @Override
    public void printResult(String result) {
        System.out.println(result);
    }

    private void fullFillServiceMap() {
        this.operationMap = new HashMap<>();
        this.operationMap.put("+", new Addition());
        this.operationMap.put("-", new Deduction());
        this.operationMap.put("*", new Multiplication());
        this.operationMap.put("/", new Division());
    }
}
Enter fullscreen mode Exit fullscreen mode

The only thing left is to write the calculator itself, that is, the operation handler

public class Calculator {

    private IO io;
    private Map<Integer, String> idOperationKeys;

    public Calculator(IO io) {
        this.io = io;
        fillMap();
    }

    public void launch() {
        boolean working = true;
        while (working) {
            int number = io.getNumber("1.Addition\n2.Substraction\n3.Multiplication\n4.Divison\n5.Exit", "Not valid number");
            if (number == 5) {
                working = false;
            } else {
              int result = 0;
                try {
                    result = io.getOperation(idOperationKeys.get(number))
                            .execute(
                                    io.getNumber("Enter first number", "Not valid number"),
                                    io.getNumber("Enter second number", "Not valid number")
                            );
                    io.printResult("The result of you operation equals to " + result);
                } catch (RuntimeException runtimeException) {
                    io.printResult(runtimeException.getMessage());
                }
            }
        }
    }

    private void fillMap() {
        idOperationKeys = new HashMap<>();
        idOperationKeys.put(1, "+");
        idOperationKeys.put(2, "-");
        idOperationKeys.put(3, "*");
        idOperationKeys.put(4, "/");
    }

}
Enter fullscreen mode Exit fullscreen mode

Well, in order for our program to finally work, we will edit the main method

public class App {

    public static void main(String[] args) {
        new Calculator(
                new ConsoleIO(
                        Arrays.asList("+", "-", "*", "/")
                )
        ).launch();
    }
}
Enter fullscreen mode Exit fullscreen mode

My implementation does not claim to be the best solution, however I see several advantages in it:

  1. This code is easier to write tests
  2. The code is open to add new functionality
  3. No dependencies on any implementation - we use abstraction for input and output of information

That's all for me, thanks for your attention!

If you liked the article, share with your friends and like

Subscribe and leave your comments

Oldest comments (0)