JAVA 계산기 만들기 트러블슈팅

SJ.CHO·2024년 9월 5일

Git

1. 코드 및 Git 설정 정립 반대

  1. 문제발생 : 기초 코드뼈대를 짜고 Git 레파지토리를 먼저 설정하고 프로젝트를 구조를 Git에 맞추면서 src 루트폴더가 안보이는문제발생

  2. 원인 : Git 은 폴더의 구조를 보는 것이 아닌 '해당경로에 파일의 변경성' 만을 보기에 아무 파일이 없던 src폴더를 인식하지못함.

  3. 문제해결 : 인텔리J 상의 폴더구조로 들어가서 src를 루트폴더로 적용하며 문제해결.

  • Git은 꼭 기초뼈대를 만들고 연결하자...
  • Git 구조에 코드를 맞추는게 아닌 코드에 Git을 맞추기.

참조 :
https://yjkim-dev.tistory.com/49
https://whyprogrammer.tistory.com/568

2. push 된 commit 메세지 변경

  1. 문제발생 : 문자열이 꼬인건지 한글입력이 문제가된건지 커밋컨벤션을 어긴 커밋메세지가 push 됨.

  2. 문제해결 : git rebase 기능을 이용하여 커밋메세지를 수정 및 재 push.

git rebase HEAD~1 -i // 바로직전 커밋메세지 수정을 위한 에디터 모드 진입

  • 에디터 상의 pick 구문을 지우고 rewoed로 교체한다.
    (커밋메세지는 수정해선 안됌!)
  • 이후 esc -> :wq! -> enter 순 으로 입력한다.
commit message 수정 후, esc -> :wq! -> enter
  • 에디터상의 화면에서 커밋메세지를 수정하는 터미널화면이 나오고 수정 후 동일한 방법으로 빠져나온다.
git push origin 경로 --force
  • --force 명령어를 통해 원격저장소와 로컬저장소의 내용을 일치시키기위해 원격 저장소의 변경사항을 '강제로' 덮어씌운다.
  • force 명령어는 원격저장소의 commit 을 유실시킬수 있기때문에 굉장히 조심해서 사용해야함. 공유프로젝트의 경우 다른사람의 작업과 원격저장소의 불일치가 일어날수 있기에 사용을 지양.
  • commit 전에 항상 확인하는 습관 들이기.

참조 :
https://velog.io/@dev_bomdong/Git-%EC%9D%B4%EB%AF%B8-push%ED%95%9C-commit-message-%EC%88%98%EC%A0%95%ED%95%98%EA%B8%B0
https://mizzo-dev.tistory.com/entry/git-commit-edit

3. 브런치생성오류

  1. 문제발생 : 개발용 dev 브랜치를 생성하고 하위로 dev/lv1 등으로 브랜치를 생성시도했으나 생성오류발생.

  2. 원인 : 브랜치는 폴더 개념이 아닌 분기이기 때문에 dev/...같은 하위 분기는 생성할수 없음.

  3. 문제해결 : dev 개발브랜치를 하나만 두고 lv별 패키지를 생성, 별도 관리하기로 변경.

Lv.1

1. do while() 문 무한반복

do {
     try {
     // 스캐너를 통한 숫자 1,2 입력 후 변수저장.
      		System.out.print("첫 번째 숫자를 입력하세요 : ");
            firstNum = sc.nextInt();
            System.out.print("두 번째 숫자를 입력하세요 : ");
            secondNum = sc.nextInt();
         } catch (InputMismatchException e) {
            System.out.println("숫자를 입력해주세요.");
            continue;
            }

  1. 문제 발생 : 숫자 입력중 정해진 변수타입외 문자 예외를 처리시 예외를 처리하고 continue문을 만나 반복문으로 돌아가 입력을 받지않고 무한루프가 생기는 문제가 발생.

  2. 가설 : do 구문과 continue 키워드가 문제가 발생하는게 아닐까? 코드를 일반적인 While()반복문으로 교체했지만 결과는 동일.

  3. 원인 : Scanner 객체의 문제로 값을 입력버퍼에 저장하였지만 인터럽트가 발생하여 입력버퍼가 지워지기전에 반복문이 재생, 오입력이 계속 발생.

  4. 문제 해결 : sc.nextLine() 구문을 통해 예외 발생시 처리 후 버퍼를 초기화시켜주면서 해결.

catch (InputMismatchException e) {
     System.out.println("숫자 혹은 정수를 입력해주세요.");
     sc.nextLine();
}

2. 예외처리 구문 최적화

        try {
            // 스캐너를 통한 숫자 1,2 입력 후 변수저장.
            System.out.print("첫 번째 숫자를 입력하세요 : ");
            firstNum = sc.nextInt();
            System.out.print("두 번째 숫자를 입력하세요 : ");
            secondNum = sc.nextInt();
        } catch (InputMismatchException e) {
            System.out.println("숫자를 입력해주세요.");
            System.exit(0);
        }
        // ...
         try {
            // 연산자의 따른 연산 진행 제어문
            switch (operator) {
                case '+':
                    result = firstNum + secondNum;
                    System.out.println(firstNum + " " + operator + " " + secondNum + " = " + result);
                    break;
            // 사칙연산 계산로직 ...
            } catch (ArithmeticException e) {
            System.out.println("0으로는 나눌 수 없습니다.");
        }
  1. 문제 발생 : 코드작성 컨벤션 중 하나로 try - catch 문을 예외발생 구역마다 설정으로 인한 가독성 저하.

  2. 원인 : 예외가 발생하는 두구역을 별도의 try - catch가 담당하도록 코드를 작성.

  3. 문제해결 : try구문 영역을 늘리고 다중 catch문을 이용하여 예외를 한번에 잡으며 가독성 문제 해결.

  4. 생각해볼점 :

    1. 메서드방식으로 사용시 예외를 직접처리할것인지, throws을 통해 떠넘길지.
    1. try-catch 가 아닌 if를 통한 유효성검사로 예외가 생기기전에 핸들링을 진행할지.
try {
       // 사칙연산 계산로직...
       
         // 0 으로 나누기 연산시 예외발생 처리
      } catch (ArithmeticException e) {
         	System.out.println("0으로는 나눌 수 없습니다.");
            
        // 숫자 실수 및 문자 입력시 예외발생 처리
      } catch (InputMismatchException e) {
            System.out.println("숫자 혹은 정수를 입력해주세요.");
            sc.nextLine();
    }

참조 :
https://application-s.tistory.com/entry/JAVA-%EC%9E%85%EB%A0%A5-%EB%B2%84%ED%8D%BC-%EB%B9%84%EC%9A%B0%EA%B8%B0-Scanner-%EB%B2%84%ED%8D%BC-%EB%B9%84%EC%9A%B0%EA%B8%B0

Lv.2

1. 규정외 연산자 입력시 연산오류 발생.

  1. 문제발생 : LV2 과정으로 코드를 재구성하면서 규정된 사칙연산자외에 연산자가들어올시 연산을 시도하려는 상황 발생.

  2. 원인 : 코드를 구성하면서 연산자구별 제어문에 그외 문자일경우 상황이 빠져있는것을 확인.

  3. 문제해결 : default 구문으로 콘솔출력을 뺄 순있지만 커스텀예외클래스를 만들어 throw 를 통한 예외객체 발생방식으로 해결

//연산자 외 문자 입력시 예외 발생 유도
 default:
   throw new OperatorInputException("+,-,*,% 연산자만 입력해주세요");
public class OperatorInputException extends RuntimeException {
    OperatorInputException() {
        super();
    }

    OperatorInputException(String message) {
        super(message);
    }
}
catch (OperatorInputException e) {
     System.out.println(e.getMessage());
 }

2. main() 메소드 최적화

  • 위 코드는 예시를 위한 Lv1 메인 코드입니다.
  1. 문제발생 : Main 클래스의 main() 메소드내에서 변수명이나 구동,제어흐름이 공개되는게 좋아보이지 않음.
  2. 문제해결 : 별도의 ServiceManager 클래스를 생성. 해당 클래스에 제어를 대신 진행. main 메소드는 실행만 진행.
public class App {
    public static void main(String[] args) {
        // 서비스 매니저 객체생성 및 프로그램 실행 
        ServiceManager sm = new ServiceManager();
        sm.run();
    }
}

3. 기록메뉴 메뉴선택 정규화.

  1. 문제발생 : 연산에대한 저장값 접근에 대하여 생각보다 많은 메뉴로 인한 문자가 아닌 숫자로 입력을 받고 검증작업 필요성이 발생.
  2. 원인 : 현재 사용하는 예외도 많고 try-catch 문이 아닌 if를 통한 예외 핸들링방법에 대한 욕심.
  3. 문제해결 : 입력받는 인덱스 변수를 Stirng 형태로 변환, .matches([정규식]) 형태로 숫자의 입력으로만 들어갈 수 있도록 작성. 해당 인덱스로 지정값 출력, 삭제 메소드가 존재하기에 다시 int 형태로 변환후 매개변수를 통해 전송.
 if (!selIndex.matches("[0-9]+")) {
  	System.out.println("0 ~ 9 사이에서만 입력이 가능합니다.");
  }
case "3":
        System.out.println("알고싶은 순번의 번호를 입력해주세요.");
        selIndex = sc.nextLine();
        System.out.println("지정 순번의 결과는 = " + calc.getResult(Integer.parseInt(selIndex)) + " 입니다.");
        break;

참조 : https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A0%95%EA%B7%9C%EC%8B%9DRegular-Expression-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC

Lv.3

1. Eunm Class No enum constant 오류

  operatorType = OperatorType.valueOf(operator);
enum OperatorType {
    ADD("+"),
    SUB("-"),
    MULT("*"),
    DIV("/"),
    REM("%");
    private final String operator;

    // 생성자를 통한 String 매칭
    OperatorType(String operator) {
        this.operator = operator;
    }

    public String getOperator() {
        return operator;
    }
}
  1. 문제발생 : 해당 enum Class 를 활용해 필드값으로 사칙연산 기호를 주어 enum 매칭된 객체를 반환받기 위해 .valueOf(operator) 메소드를 사용.
  2. 가설 : .valueOf(operator) 메소드는 필드값으로 찾아오는게 아니라 eunm class 객체의 이름으로 찾아오는게 아닐까
  3. 원인 : 가설에 대한 내용이 맞았고 필드값을 통해 Eunm 객체를 찾고싶다면 따로 순회를돌아 매칭된 객체를 찾아와야했음.
  4. 문제해결 : 프로그램이 실행될때 Static 키워드를 통한 Map 을 활용하여 필드값과 열거객체를 매칭해 저장.
    이후 입력된 String값을 가지고 Map에서 열거객체를 찾아 리턴 시간복잡도는 O(1) (키값을 가지고 조회하기에)
enum OperatorType {
    ADD("+"),
    SUB("-"),
    MULT("*"),
    DIV("/"),
    REM("%");

    // 프로그램이 실행되면 Map을 캐싱해 찾고자 하는 키값과 필드값을 매칭
    private static final Map<String, OperatorType> OPERATOR_MAP =
            Collections.unmodifiableMap(Stream.of(values()).collect(Collectors.toMap(OperatorType::getOperator, Function.identity())));
    private final String operator;

    // 생성자를 통한 String 매칭
    OperatorType(String operator) {
        this.operator = operator;
    }

    // 입력된 연산자를 통해 Map 안에서 열거 객체 리턴
    public static OperatorType find(String operator) {
        if (OPERATOR_MAP.containsKey(operator)) {
            return OPERATOR_MAP.get(operator);
        }
        //연산자 외 문자 입력시 예외 발생 유도
        throw new IllegalArgumentException("맞지 않는 연산자 입력 : " + operator);
    }

    public String getOperator() {
        return operator;
    }
}

참조 :
https://itellyhood.tistory.com/70
https://velog.io/@ljinsk3/Enum%EC%9D%98-%EC%9A%94%EC%86%8C%EB%A5%BC-%EC%A1%B0%ED%9A%8C%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EB%B9%84%EA%B5%90-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C-vs-HashMap

2. 제네릭 타입 피연산자화 오류

  1. 문제 발생 : 제네릭 주입을 위해 Number class의 하위클래스들 객체만이 Type의 들어올수 있게 지정하고 해당 변수들로 연산을 진행하였으나, Type T는 피연산자가 됄수 없다는 오류발생.

  2. 가설 : Type T가 어떤형태의 Type인지 선언되있지않기에 확실한 연산을 진행하지 못하는가?

  3. 원인 : 해당 Type이 Number의 하위 객체인것은 알지만 정확한 Type에 대한 명시가 필요.

  4. 문제해결 : 들어온 매개변수에 Number.doubleValue() 메소드를 통해 double 화 시키고 연산을 진행해 문제해결

  • p.s : 내가 생각한 제네릭의 개념(타입 분간 등) 과 활용한 제네릭의 개념이 맞나 생각하여 튜터님께 물어본 결과 위에 클래스에서 Number Class에 대하여 상속을 받아 일단 숫자가 들어온다는거 자체가 제네릭으로써의 활용이 충분하다 답변해주심.
switch (operatorType) {
            case ADD:
                result = firstNum.doubleValue() + secondNum.doubleValue();
                // 연산 과정 및 결과 저장 메서드 호출.
                saveCalculationProcess(firstNum, secondNum, result, operator);
                break;

3. 제네릭 타입 스캐너 입력 타입변수 지정

  1. 문제발생 : 제네릭 타입을 통해 실수형 입력이 가능해지며 스캐너를 통한 next.int()만이 아닌 double형으로 입력이 필요해짐.
  2. 문제해결 : 먼저 문자를 입력받아 실수형태인지 확인 후 알맞은 타입변수에 집어넣은뒤 매개변수를 통해 전송하여 문제 해결.
 System.out.print("첫 번째 숫자를 입력하세요 : ");
                number = sc.nextLine();
                // 입력받은 숫자가 정수인지 실수인지 검사.
                if (number.contains(".")) {
                    firstNumDouble = Double.parseDouble(number);
                    fisrtdoublecnt++;
                } else {
                    firstNumInt = Integer.parseInt(number);
                    firstintcnt++;
                }
if (fisrtdoublecnt != 0 && seconddoublecnt != 0) {
                    if (calc.negativeIntegerChecker(firstNumDouble, secondNumDouble)) {
                        System.out.println("양수만 입력해주세요.");
                        continue;
                    }
                    System.out.println(firstNumDouble + " " + operator + " " + secondNumDouble + " = " + calc.calculate(firstNumDouble, secondNumDouble, operator));
                }
profile
70살까지 개발하고싶은 개발자

0개의 댓글