Рубрики
Без рубрики

Прекратите писать код в основном методе

В этой статье я попытаюсь объяснить, почему вам следует прекратить писать код внутри метода main. С пометкой java, программирование, программное обеспечение, архитектура.

Приветствую всех, кто читает этот пост! Благодаря своей работе я часто сталкиваюсь с начинающими программистами и смотрю на их код. Иногда это красиво, иногда нет, но я понимаю, что человек учится, и чем дальше он осваивает лучшие практики и технологии, тем лучше становится его код. Но я все же хотел бы уделить некоторое внимание такой простой задаче, с которой сталкиваются практически все начинающие программисты, – написанию калькулятора

Вот что пишут начинающие программисты:

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

Какие проблемы вы видите в этом коде? Я вижу следующие проблемы

  1. Все написано в основном методе и, безусловно, представляет собой более 10 строк
  2. Для этого кода нет тестов, то есть нет уверенности в том, что он будет работать правильно
  3. Структурный стиль программирования – оператор switch поощряет это
  4. С помощью сканера, но это можно простить, потому что у него есть удобные методы для получения номеров сразу.

Как бы вы исправили этот код? Я бы исправил данный код следующим образом

  1. Создал бы интерфейс операций для всех операций с числами
public interface Operation {
    int execute(int a, int b);
}

И соответственно я бы реализовал этот интерфейс в следующих классах

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

Я бы сделал то же самое с методами ввода и вывода, но прежде чем мы начнем их писать, давайте добавим очень полезную библиотеку – Apache Commons


    org.apache.commons
    commons-lang3
    3.11

Интерфейс для работы с вводом-выводом информации будет выглядеть следующим образом

public interface IO {

    int getNumber(String message, String errorMessage);

    Operation getOperation(String message);

    void printResult(String result);
}

И вот как будет выглядеть его реализация

public class ConsoleIO implements IO {

    private final BufferedReader bufferedReader;
    private final List allowedOperation;
    private Map operationMap;

    public ConsoleIO(final List 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());
    }
}

Единственное, что осталось, – это написать сам калькулятор, то есть обработчик операций

public class Calculator {

    private IO io;
    private Map 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, "/");
    }

}

Что ж, для того чтобы наша программа наконец заработала, мы отредактируем основной метод

public class App {

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

Моя реализация не претендует на звание лучшего решения, однако я вижу в ней несколько преимуществ:

  1. Этот код проще для написания тестов
  2. Код открыт для добавления новых функций
  3. Никаких зависимостей от какой-либо реализации – мы используем абстракцию для ввода и вывода информации

Это все для меня, спасибо за ваше внимание!

Если вам понравилась статья, поделитесь с друзьями и поставьте лайк

Подписывайтесь и оставляйте свои комментарии

Оригинал: “https://dev.to/vrnsky/stop-writing-code-in-main-method-2bff”