Troubleshooting: 무엇이 문제였는가?/본캠프 2주 차: 계산기 만들기

2단계: 절차 지향 add() vs 객체 지향 add()

writingforever162 2024. 11. 20. 00:24

[문제]

package com.project.personal.calculator2;

import java.util.*;
// [참고] 패키지 전체를 불러오고 싶을 때 사용

public class Calculator {
    ArrayList<Integer> results = new ArrayList<Integer>();

    public Calculator() {
    }

    public int calculate(int numOne, String operator, int numTwo) {
        if (operator.equals("+")) {
            return numOne + numTwo;
        } else if (operator.equals("-")) {
            return numOne - numTwo;
        } else if (operator.equals("*")) {
            return numOne * numTwo;
        } else {
            if (numTwo == 0) {
                return Integer.MIN_VALUE;
            } else {
                return numOne / numTwo;
            }
        }
    }
    // [문제] this.results.add();는 적었는데, 그 다음이 막막하다.

2단계 계산기를 만들려면 '연산 결과를 저장하는 컬렉션(Collection) 타입 필드를 가진 Calculator 클래스'를 반드시 생성해야 했다. 2주 차 강의 속 숙제를 할 때도 헷갈린 부분이었는데 역시나. 소괄호 안에 대체 무엇을 쓰고, this.results.add(); 전체를 어디에 써야 하는지도 헷갈렸다. 다행이라면 '그냥 모르겠다'가 아니라 'add() 메서드를 어디서 어떻게 써야 하는지 모르겠다'처럼 질문이 구체화되었다. 이쯤 되니 튜터님을 찾아가는 데 망설임이 사라졌다.

 

[원인]

원인은 총 두 가지로 정리할 수 있었다. 

 

첫째, 소괄호 안에 들어가는 매개변수, 일명 파라미터(Parameter) 개념이 100% 잡히지 않았다. 이는 클래스와 생성자, 객체를 여러 번 생성하면 해결될 듯했다. 오늘 방 탈출 세션에 참여했을 때 이미 겪어봤으니까.

 

둘째, 메서드가 어디서 종료되는지 인지하지 못했다. 조건식이 참이 되어 반복문이 실행되었을 때 어디서 끝나는지 몰라서 add() 위치를 정하지 못했다.

 

[해결]

이번에 발생한 문제는 절차 지향과 객체 지향으로 해결할 수 있었다.

1) 절차 지향 프로그래밍

package com.project.personal.calculator2;

import java.util.*;

public class Calculator {
    // [1] ArrayList 선언 및 생성 
    ArrayList<Integer> results = new ArrayList<Integer>();
    // [참고] ArrayList는 사이즈를 지정하는 것이 없어서 초기화할 필요가 없다. 

    public Calculator() {
    }

    public int calculate(int numOne, String operator, int numTwo) {
        // [2] 로컬 변수 선언 및 초기화
        int storage = 0;
        
        if (operator.equals("+")) {
            // [3] storage에 연산 결과 저장
            storage = numOne + numTwo;
            // [수정 전] return numOne + numTwo;
            // [수정 후] storage = numOne + numTwo;
        } else if (operator.equals("-")) {
            storage = numOne - numTwo;
            // [수정 전] return numOne - numTwo;
            // [수정 후] storage = numOne - numTwo;
        } else if (operator.equals("*")) {
            storage = numOne * numTwo;
            // [수정 전] return numOne * numTwo;
            // [수정 후] storage = numOne * numTwo;
        } else {
            if (numTwo == 0) {
                return -127;
            } else {
                storage = numOne / numTwo;
                // [수정 전] return numOne / numTwo;
                // [수정 후] storage = numOne / numTwo;
            }
        }
        // [4] add() 메서드 사용하기
        this.results.add(storage);
        // [설명] storage에 연산 결과값이 저장되었다.
        //       따라서 storage를 results에 저장해야 한다.
        // [복습] 메서드는 실행되면 끝이다. 
        //       즉, if ~ else가 전부 실행되는 게 아니다. 
        
        // [5] storage 반환 
        return storage;
    }
}

클래스에서 기껏 저장을 해놓고는 Main에 또 저장 관련 코드를 적으려고 했다. 분명 절차 지향 해결법과 객체 지향 해결법을 한꺼번에 들은 까닭이리라. 처음엔 'this.result.add(storage); 코드와 return storage; 코드 딱 두 줄만 맨 밑에 적으면 되잖아?' 속으로 감탄하며 좋은 줄 알았는데, 착각이었다. if문이 조금만 길어지면 복사한 코드를 잘못 붙여넣을 확률이 바로 수직 상승했다! 튜터님의 설명을 듣고 나서 왜 이 해결법이 절차 지향인지 확 와닿았다.

 

2) 객체 지향 프로그래밍

// [A] 클래스 Calculator에서 수정하기
package com.project.personal.calculator2;

import java.util.*;

public class Calculator {
    ArrayList<Integer> results = new ArrayList<Integer>();

    public Calculator() {
    }

    public int calculate(int numOne, String operator, int numTwo) {
        if (operator.equals("+")) {
            return numOne + numTwo;
        } else if (operator.equals("-")) {
            return numOne - numTwo;
        } else if (operator.equals("*")) {
            return numOne * numTwo;
        } else {
            if (numTwo == 0) {
                return Integer.MIN_VALUE;
            } else {
                return numOne / numTwo;
            }
        }
    }
    // [해결 1/2] 메서드 선언하기
    public void saveResults (int eachResult) {
        this.results.add(eachResult);
    }
}
// [B] Main에서 수정하기
package com.project.personal.calculator2;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        while (true) {
            System.out.print("0 이상인 첫 번째 정수를 입력해 주세요: ");
            int numFirst = sc.nextInt();

            System.out.print("사칙연산 기호(+, -, *, /) 중 하나를 입력해 주세요: ");
            String operator = sc.next();

            System.out.print("0 이상인 두 번째 정수를 입력해 주세요: ");
            int numSecond = sc.nextInt();

            Calculator test = new Calculator();

            int result = test.calculate(numFirst, operator, numSecond);

            if (result == Integer.MIN_VALUE) {
                System.out.println("나눗셈 연산에서 분모(두 번째 정수)에 0은 올 수 없습니다.");
            } else {
                // [해결 2/2] 클래스에서 선언한 메서드 호출하기
                test.saveResults(result);
                // [설명] 분모가 0일 때 결괏값은 저장할 필요가 없으므로 여기서 호출했다.
                String outcome = Integer.toString(result);
                System.out.println("연산한 결과는 " + outcome + "입니다.");
            }
            System.out.print("프로그램을 종료하시겠습니까? 종료를 원하시면 exit을 입력해 주세요: ");
            String exit = sc.next();
            if (exit.equals("exit")) {
                System.out.println("예, 알겠습니다. 프로그램을 종료합니다.");
                break;
            }
        }
    }
}

직접 메서드를 선언하고 호출하니 왜 이 방식이 객체 지향인지 알 수 있었다. 아무리 코드가 길어져도 알맞은 위치에서 메서드를 호출하면 되니까, 확장성과 유지보수 모두 고려한 방식이라고 할 수 있었다. 객체 지향적인 프로그래밍이 무엇인지 예전보다 확실히 감이 왔다.