wisePocket

[Algorithm✅️] 4칙연산을 해주는 계산기 - 혼자 만들기+ChatGPT의 피드백 받기 본문

Java & Algorithm/Algorithm Practice

[Algorithm✅️] 4칙연산을 해주는 계산기 - 혼자 만들기+ChatGPT의 피드백 받기

ohnyong 2023. 8. 1. 11:13

알고리즘 기본 문제를 객체 지향적 프로그래밍을 적용해서 계산기 형태로 만들고 풀어보려했다.

처음엔 두 수의 합을 구현하는 것을 완료했지만 뭔가 부족한 느낌이 들었고, 뭔가 프로그램처럼 만들어보고 싶었다. 분명히 예제 코드들은 인터넷에 널려있겠지만, 아무 샘플 코드도 주어지지 않은 상태에서 혼자 처음부터 모든것을 타이핑해보려 했다. 이를 통해서 기본적인 간단한 프로그램이더라도 객체 지향적으로 설계하고 만들어보는 연습을 하고 싶었다.

따라서 내가 설정한 제약 조건은

  • Main.java에서는 최대한 노출되는 코드가 없이 Class(설계도)에 정의된 객체를 생성하여 인스턴스화 시키고 객체로부터 기능을 연결하는 코드들만 보이도록 하고 싶었다.
  • 객체를 설계, 정의하는 Class는 Solution()이며 기본 생성자(Constructor)를 통해 메인에서 객체를 생성(인스턴스화) 시킬 수 있다.
  • 연산은 5종류로 Method는 다음과 같다
    • 두 수의 합 summary()
    • 두 수의 차 substract()
    • 두 수의 곱 multiplication()
    • 두 수의 몫 divisionQuiotient()
    • 두 수의 나머지 divisionRemainder()
  • 메뉴 보여주기, 메뉴 선택하기 등 프론트엔드 성격의 기능들 또한 Main.java에서 처리하는 것보다 showMenu(), chooseMenu()와 같이 Method를 통해 나타나도록 정의했다.
  • 프로그램은 사용자가 종료를 직접 선택하기 전 까지 무한히 가동되어야 한다. 
    • 원하는 연산 종류 선택 showMenu()
      • 1.더하기
      • 2.빼기
      • 3.곱하기
      • 4.나눈몫
      • 5.나눈나머지
      • 0.종료
  • 연산 종류를 선택한 이후 계산을 원하는 두개의 수를 사용자로부터 입력받는다.
  • 입력 받은 값은 Num1, Num2 변수에 저장해서 선택 되었던 연산의 Method에 Parameter로 전달한다.
  • Parameter로 두 개의 수를 전달 받은 선택된 연산 Method는 해당 연산을 진행하고 결과 값을 반환 한다.
  • 연산 결과는 입력된 값들과 연산부호, 연산 결과를 합쳐서 계산식+결과와 같은 형식으로 출력한다.
  • 프로그램은 무한히 반복되어야 하는 조건에 따라 다시 메뉴 선택으로 진입한다.

1. 할수 있는 곳 까지 일단 코딩

일단 위 제약조건을 생각하며 오픈북이 아니라 자진하여 클로즈북으로 시험을 봤다.
Main 실행부와 Solution Class를 작성해왔다.

Main.java
package Algorithm;

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        //스캐너 객체 생성(입력위해)
        Scanner sc = new Scanner(System.in);
        //계산기 객체 생성
        Solution solution = new Solution();
        //메뉴 보여주기 메서드
        solution.showMenu();
        //선택 연산확인 메서드
        solution.chooseMenu(solution.selectNum = sc.nextInt());
    }
}

Solution.java

package Algorithm;

import java.util.Scanner;

public class Solution {
    int selectNum;
    int calNum;
    int num1;
    int num2;
    int result;
    Scanner sc = new Scanner(System.in);

    //Constructor 생성
    public Solution() {
        System.out.println("생성자호출됨");
    }

    //Menu 보여주는 메서드 showMenu()
    void showMenu() {
        System.out.println("원하시는 연산을 입력해주세요.");
        System.out.println("[1]더하기");
        System.out.println("[2]빼기");
        System.out.println("[3]곱하기");
        System.out.println("[4]나눈몫");
        System.out.println("[5]나눈나머지");
    }

    //Menu 선택하는 메서드 chooseMenu()
    void chooseMenu(int selectNum) {
        for (; ; ) {
            if (selectNum == 1) {
                System.out.println("더하기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 2) {
                System.out.println("빼기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 3) {
                System.out.println("곱하기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 4) {
                System.out.println("나누기 몫 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 5) {
                System.out.println("나누기 나머지 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else {
                System.out.println("잘못된 값을 입력했습니다.\n계산기를 다시 사용하시겠습니까?\n[1]예 [2]아니오");
                int tryNum = sc.nextInt();
                if (tryNum == 1) {
                    selectNum = 1;
                    this.chooseMenu(selectNum);
                    break;
                } else {
                    System.out.println("프로그램을 종료합니다.");
                    exit();
                }
            }//--if
        }//--for
    }

    void inputNum(int selectNum) {
        calNum = selectNum;
        //Num1 입력
        System.out.println("num1을 입력해주세요");
        num1 = sc.nextInt();
        //Num2 입력
        System.out.println("num2를 입력해주세요");
        num2 = sc.nextInt();

        calculate(calNum, num1, num2);
    }

    //프로그램 종료 선택 시 Run을 종료하는 메서드 exit()
    void exit() {
        System.exit(0);
    }

    //메뉴에 따라 선택된 연산을 연결 후 반환 된 결과를 출력 하는 메서드 calculate()
    int calculate(int calNum, int num1, int num2) {
        if (calNum == 1) {
            result = summary(num1, num2);
            System.out.println("result : " + num1 + "+" + num2 + "=" + result);
        } else if (calNum == 2) {
            result = subtract(num1, num2);
            System.out.println("result : " + num1 + "-" + num2 + "=" + result);
        } else if (calNum == 3) {
            result = multiplication(num1, num2);
            System.out.println("result : " + num1 + "*" + num2 + "=" + result);
        } else if (calNum == 4) {// int == 4
            result = divisionQuiotient(num1, num2);
            System.out.println("result : " + num1 + "/" + num2 + "=" + result);
        } else if (calNum == 5) {// int == 4
            result = divisionRemainder(num1, num2);
            System.out.println("result : " + num1 + "%" + num2 + "=" + result);
        } else {
            exit();
        }
        return result;
    }

    //-----------------연산 메서드-------------------
    //두 수의 합를 구하는 메서드 summary()
    int summary(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }

    //두 수의 차를 구하는 메서드 subtract()
    int subtract(int num1, int num2) {
        result = num1 - num2;
        return result;
    }

    //두 수의 곱을 구하는 메서드 multiplication()
    int multiplication(int num1, int num2) {
        result = num1 * num2;
        return result;
    }

    //두 수의 나눈 몫을 구하는 메서드 divisionQuiotient()
    int divisionQuiotient(int num1, int num2) {
        result = num1 / num2;
        return result;
    }

    //두 수의 나눈 나머지를 구하는 메서드 divisionQuiotient()
    int divisionRemainder(int num1, int num2) {
        result = num1 % num2;
        return result;
    }
}

우선 테스트 결과 연산은 잘 진행 되지만 문제가 발생했다.
CLI로 구성된 메뉴(무한반복 for(;;)문)에서 1,2,3,4,5,0 이외의 숫자를 입력하면
잘못된 값을 입력했다는 문구와 함께 계속 또는 종료라는 GUI로 치면 팝업 같은 형식의 서브 메뉴를 나타내주었는데, 
그 부분에서 문제가 있다.

2. 문제 인식과 내 생각을 먼저 펼쳐보기

우선 내가 원하는대로 서브메뉴와 메인메뉴간의 이동이 원활하지 않은 문제가 있다.

현재까지 실행되는 로직부터 살펴보면,
프로그램 시작
-> 메뉴 등장 chooseMenu()
-> 선택 숫자 selectNum 입력에 따라 해당 연산 모드 결정
-> inputNum(selectNum)으로 selectNum을 보냄 [이 부분은 지금 글을 작성하면서 보니 필요가 없어보이는 흐름인 것 같다.]
-> inputNum(selectNum) 메서드 내에서는 받은 selectNum calNum에 할당 [selectNum이 변경되지 않도록 이것저것 하다가 이것 까지 과정을 추가했는데 좀 더 살펴봐야 겠다. 의미가 없는 전달로 보여진다.]
-> inputNum(selectNum) 메서드 내에서는 Scanner를 통해 Num1, Num2를 저장함
-> calNum,Num1,Num2를 파라미터로 calculate()라는 연산기+출력기 메서드에 전달
-> calculate(calNum,Num1,Num2) 내부에서는 calNum(==본래 selectNum==선택된연산종류)에 따라서 가장 하위 계층 메서드인 summary(), substract(), ... 등 4칙 연산을 수행하는 메서드를 선택하고 Num1,Num2를 넘겨줌
-> summary(Num1,Num2
)에서 두 수를 연산하고 결과 result를 반환함
-> 반환된 result는 calculate()메서드로 전달 되고, 출력기 역할까지 있기 때문에 결과를 출력해줌.


내가 보기엔 저 부분의 구성이 이상하다. 선택 번호를 만약 7로 잘못 입력했다고 가정하자.
해당 인스턴스에 selectNum 변수에는 7이 저장된 상태이다.
selectNum을 클래스 전역 변수로 선언했기 때문에 인스턴스가 유지 되면 소멸되지 않는다.
여기서 착각했다, static을 붙여야 전역변수이고 안붙이면 지역변수인줄로,
하지만 저것은 class instance 변수로 전역 변수 중 하나였다.
이 말은 저장된 상태가 유지된다는 말.

지역 변수는 더 좁은 스코프인 메서드내, 또는 특정 반복문, 조건문 등 안에서만 선언되고 사라지는 것이다.
그럼, 소멸 시키는 방법을 생각해야 한다. 스코프를 더 줄여서 지역 변수로 선언해야 하나보다.
그럼 조건에 해당되지 않으면 탈출 시키고, 변수가 다시 초기화 되도록 하면 7이란 숫자가 사라지고 재입력을 받을 수 있을 것 같다.

 

3. 내 생각을 정리하여 ChatGPT 선생님에게 물어보기

위 로직을 펼쳐보며 이 문제에 대한 원인을 내 수준 선까지는 파악했다.
이것에 대한 문제 해결 샘플을 찾고 맞는지를 검증해야 하는데, 나는 현재까지 이런 경우 다양한 블로그들을 검색하면서 "java 계산기 구현" 이라는 키워드로 검색 했었을 것이다. 그런데 생각보다 알고리즘 기본 문제라 객체지향으로 궂이 풀지 않은 케이스가 90%이상이라 블로그를 거의 10~20개 검색해야 1개 나올 수준이다. 너무 시간이 아깝다. 분명 스쳐 보면서 다른 것들을 배우는 것도 필요하지만 나는 당장 이 문제에 몰입되어야 하는데 방해요소가 너무 많다.

이전 업무에서 자주 활용했던, 의견을 나누었던 ChatGPT의 능력을 믿고 상담받듯이 내 문제점들을 펼쳐 보여주고, 내 문제 인식이 맞는지, 그 해답과 설명 까지도 확인 하는 과정을 진행해 봤다.

우선 ChatGPT의 능력을 확인해본다. 나와 같은 생각으로 코딩된 샘플을 만들어주는 것을 확인하였다.

하지만 나는 for문을 자주 사용하는데 while을 사용하기도 했고, if문을 자주 사용하는데 switch를 사용하기도 했다.
하지만 이 부분도 쉽게 적용시켜서 나에게 맞추어 보여준다.

이제 ChatGPT가 만든 자기의 샘플에 내가 원하는 구조로도 수정 할 수 있는지 질문을 시작했다.
부가 설명까지 정확하게 해주면서 내가 작성한 코드와 유사하게 진행 되는 모습을 확인했다.


이제 ChatGPT의 능력을 알았으니 실제로 활용해보자.
내 코드를 펼쳐 보여주고 내가 문제를 겪고 있는 부분, 플로우에 문제가 없는 부분, 문제가 발생하는 지점 등을 구어체로 표현했다. 실제로 강사, 튜터나 멘토에게 어떻게 질문해야 할까도 중요하다 생각하는데, 이 정도 긴 말을 긴 상황을 우선 인지하고 정리해야 한다고 생각한다. ChatGPT는 내가 상세하게 질문 할 수록 정확한 답변을 구해 줄 수 있는 엔진이다.

내가 예상했던 부분과 마찬가지로 selectNum의 초기화와 관련된 문제를 이야기 해줬다.
// ...
//Menu 선택하는 메서드 chooseMenu()
void chooseMenu() {
    while (true) {
        System.out.println("원하시는 연산을 입력해주세요.");
        System.out.println("[1]더하기");
        System.out.println("[2]빼기");
        System.out.println("[3]곱하기");
        System.out.println("[4]나눈몫");
        System.out.println("[5]나눈나머지");
        int selectNum = sc.nextInt();

        if (selectNum == 1 || selectNum == 2 || selectNum == 3 || selectNum == 4 || selectNum == 5) {
            if (selectNum == 1) {
                System.out.println("더하기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 2) {
                System.out.println("빼기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 3) {
                System.out.println("곱하기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 4) {
                System.out.println("나누기 몫 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 5) {
                System.out.println("나누기 나머지 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            }
        } else {
            System.out.println("잘못된 값을 입력했습니다.\n계산기를 다시 사용하시겠습니까?\n[1]예 [2]아니오");
            int tryNum = sc.nextInt();
            if (tryNum == 1) {
                continue;
            } else {
                System.out.println("프로그램을 종료합니다.");
                exit();
                break;
            }
        }
    }
}
// ...


while 문으로 표기된 부분에 대한 재수정을 요청했다. 하지만 왜 ChatGPT는 while을 계속해서 썼을까? 라는 갑자기 의문이 있었다. 그래서 그것에 대한 질문도 포함했다.
void chooseMenu() {
    for (;;) {
        System.out.println("원하시는 연산을 입력해주세요.");
        System.out.println("[1]더하기");
        System.out.println("[2]빼기");
        System.out.println("[3]곱하기");
        System.out.println("[4]나눈몫");
        System.out.println("[5]나눈나머지");
        int selectNum = sc.nextInt();

        if (selectNum == 1 || selectNum == 2 || selectNum == 3 || selectNum == 4 || selectNum == 5) {
            if (selectNum == 1) {
                System.out.println("더하기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 2) {
                System.out.println("빼기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 3) {
                System.out.println("곱하기 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 4) {
                System.out.println("나누기 몫 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            } else if (selectNum == 5) {
                System.out.println("나누기 나머지 연산을 시작합니다.");
                inputNum(selectNum);
                break;
            }
        } else {
            System.out.println("잘못된 값을 입력했습니다.\n계산기를 다시 사용하시겠습니까?\n[1]예 [2]아니오");
            int tryNum = sc.nextInt();
            if (tryNum == 1) {
                continue;
            } else {
                System.out.println("프로그램을 종료합니다.");
                exit();
                break;
            }
        }
    }
}​
for의 초기화조건증감식 부분이 (;;)로 표기되는 것도 가시적으로 좋지 않으며, break;로 중복해서 표현해야 되는 것이 유지보수 측면에서 문제였다. while(true)(false)처럼 말그대로 ~동안으로 직역해도 괜찮은 개발자간의 커뮤니케이션이 원활하게 만들기 때문이다. 그래서 두 반복문 간 대치는 되지만 상황상 while 방법이 더 자연스럽게 보여진다.

최종적인 질문을 진행했다.
나는 단순히 ChatGPT의 능력을 클로닝하는 방식의 공부를 원한 것도 아니며 질문의 의도가 아니다.
내가 원하는 대답들을 구할 수 있었다. 수정하러 간다. +@로 인풋인 Num1, Num2에 -50000이상, 50000이하의 조건을 달아주어야 한다.