DEV Community

Yegor Voronyansky

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

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);
}
}
``````

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);
}
``````

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;
}
}
``````

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>
``````

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);
}
``````

And this is how its implementation will look like

``````public class ConsoleIO implements IO {

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

public ConsoleIO(final List<String> allowedOperation) {
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 {
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 Deduction());
this.operationMap.put("*", new Multiplication());
this.operationMap.put("/", new Division());
}
}
``````

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, "/");
}

}
``````

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();
}
}
``````

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