개인 과제가 하나 주어졌다.
요구사항은 아래와 같다.
Scanner
클래스를 사용하여 사용자 입력을 처리합니다.Calculator
클래스 생성App
클래스의 main
메서드에서 Calculator
클래스의 연산 결과를 저장하고 있는 컬렉션 필드에Calculator
클래스에 저장된 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를App
클래스의 main
메서드에 삭제 메서드가 활용될 수 있도록 수정Enum
타입을 활용하여 연산자 타입에 대한 정보를 관리하고, 사칙연산 계산기 ArithmeticCalculator
클래스에 활용double
타입의 값을 전달 받아도 연산이 수행되도록 만들기Scanner
로 입력받은 값보다 큰 결과값들을 출력Lambda
& Stream
을 활용하여 구현오늘은 Level2를 구현하며 코드를 점진적으로 클린 코드로 개선한 부분에 대해 작성해볼 예정이다.
먼저 정상적인 기능 작동에만 포커싱을 두고 만든 기존 코드는 아래와 같았다.
public class App {
public static void main(String[] args) {
Calculator calc = new Calculator();
Scanner sc = new Scanner(System.in);
while (true) {
int num1, num2;
char operator;
// 필터링 구간
while (true) {
System.out.print("첫 번째 숫자를 입력하세요: ");
if (sc.hasNextInt()) {
num1 = sc.nextInt();
if (num1 >= 0) {
break;
}
System.out.println("양수(0 포함)를 입력하세요.");
} else {
System.out.println("숫자만 입력할 수 있습니다.");
sc.next();
}
}
while (true) {
System.out.print("사칙연산 기호를 입력하세요: ");
operator = sc.next().charAt(0);
if (operator == '+' || operator == '-' || operator == '*' || operator == '/') {
break;
}
System.out.println("사칙연산 기호가 아닙니다. 다시 입력해주세요.");
}
while (true) {
System.out.print("두 번째 숫자를 입력하세요: ");
if (sc.hasNextInt()) {
num2 = sc.nextInt();
if (num2 >= 0) {
break;
}
System.out.println("양수(0 포함)를 입력하세요.");
} else {
System.out.println("숫자만 입력할 수 있습니다.");
sc.next();
}
}
// 계산 구간
int result = calc.calculate(num1, num2, operator);
System.out.println("결과: " + result);
// 종료 구간
System.out.println("더 계산 하시겠습니까? (exit 입력시 종료)");
if (sc.next().equals("exit")) {
System.out.println("저장된 연산 결과: " + calc.getResults());
break;
}
}
sc.close();
}
}
public class Calculator {
private List<Integer> results = new ArrayList<Integer>();
public int calculate(int num1, int num2, char operator) {
int result = 0;
// 계산 구간
if (operator == '+') {
result = num1 + num2;
} else if (operator == '-') {
result = num1 - num2;
} else if (operator == '*') {
result = num1 * num2;
} else if (operator == '/') {
if (num2 == 0) {
System.out.println("나눗셈 연산에서 분모(두 번째 정수)에 0이 입력될 수 없습니다.");
} else {
result = num1 / num2;
}
}
results.add(result);
return result;
}
public List<Integer> getResults() {
return results;
}
public void setResults(List<Integer> results) {
this.results = results;
}
public void removeResult() {
results.remove(0);
}
}
코드를 개선하고 나니 이 코드의 문제점이 너무 많다는 게 실감된다. 😅
제일 크게 개선한 부분 몇 가지만 소개하자면 다음과 같다.
public class Filter {
Scanner sc = new Scanner(System.in);
public int checkedNum() {
while (true) {
if (sc.hasNextInt()) {
return sc.nextInt();
} else {
System.out.println("숫자만 입력할 수 있습니다.");
sc.next();
}
}
}
public int checkedPositiveNum() {
while (true) {
int num = checkedNum();
if (num >= 0) {
return num;
}
System.out.println("양수(0 포함)를 입력하세요.");
}
}
}
public int addNum() {
// 필터링 구간
System.out.print("첫 번째 숫자를 입력하세요: ");
return filter.checkedPositiveNum();
}
public int addNum(char operator) {
System.out.print("두 번째 숫자를 입력하세요: ");
int num = filter.checkedPositiveNum();
if (operator == '/' && num == 0) {
System.out.println("나눗셈 연산에서 분모(두 번째 정수)에 0이 입력될 수 없습니다.");
return addNum(operator);
}
return num;
}
if
문을 switch
문으로 대체 // 계산 구간
switch (operator) {
case '+':
result = firstNum + secondNum;
break;
case '-':
result = firstNum - secondNum;
break;
case '*':
result = firstNum * secondNum;
break;
case '/':
result = firstNum / secondNum;
break;
}
System.exit(0)
메서드를 사용하여 main
메서드 종료) switch (sc.next()) {
case "1":
System.out.println("저장된 연산 결과: " + calc.getResults());
continue;
case "2":
calc.removeResult();
System.out.println("저장된 연산 결과: " + calc.getResults());
continue;
case "3":
flag = true;
subFlag = false;
break;
case "4":
System.out.println("종료합니다.");
System.exit(0);
default:
System.out.println("잘못된 입력입니다. 다시 시도해주세요.");
}
(⭐+if문보다 switch문을 권장하는 이유)
1. 가독성 및 코드 구조
→ 값에 따라 어떤 분기가 발생하는지 한눈에 파악하기 쉽게 해준다.
2. 성능
→ 컴파일러가 여러 번의 조건 검사를 순차적으로 수행해야하는 if-else
에 비해
switch
문은 해시 테이블이나 검색 트리로 변환하여 성능을 높일 수 있다.
3. 새로운 케이스 추가 용이
→ if-else
체인은 각 조건이 별도로 작성되므로 추가할 때마다 비교 연산이 추가되어야 한다.
4. 에러 방지
→ default
케이스를 사용하여 데드 코드를 방지할 수 있으며, 특정 변수의 값에 따라 정확하게 분기하므로 논리적인 오류를 줄이는 데 도움이 된다.
flag
로 수정 & do-while
문으로 변경package level2;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Input input = new Input();
Calculator calc = new Calculator();
Menu menu = new Menu(calc);
int firstNum, secondNum;
char operator;
boolean flag = false;
boolean subFlag = true;
do {
// 입력 구간
firstNum = input.addNum();
operator = input.addOperator();
secondNum = input.addNum(operator);
// 출력 구간 (계산)
int result = calc.calculate(firstNum, secondNum, operator);
System.out.println("결과: " + result);
// 종료 구간 (메뉴 출력)
flag = menu.display(subFlag);
} while (flag);
sc.close();
}
}
정리가 끝난 main
메서드 부분이다. 훨씬 깔끔하다.
모든 기능을 분리한 전체 구조는 아래와 같다.
App - main method Calculate - calculate method Filter - filter method Input - input method Menu - display method
main
메서드에서 계산을 위해 생성한 calc
객체와 display
메서드에서 저장된 연산 결과를 보기 위해 생성한 calc
객체가 서로 다른 객체여서, Menu
클래스에서 results
리스트의 값이 불러와지지 않았다.
고민을 하다 처음엔 static
으로 해결을 했으나, 튜터님께 여쭤본 결과 다른 방법을 찾아보라고 하셨다.
객체를 전달하는 방법을 고민한 끝에, main
메서드에서 생성한 calc
객체를 menu
객체의 생성자로 넘겨 해결할 수 있었다.
같은 클래스의 객체를 두 개 생성하면 서로 다른 객체라는 사실을 알고 있었음에도 구현하는 과정에서 놓쳤다는 점에서, 더 깊이 공부할 필요성을 느꼈다. 또한 튜터님께 코드리뷰를 받으며 클린 코드가 무엇인지 조금은 이해할 수 있었고, 매우 유익한 경험이었다.