과제리뷰

[계산기] 피드백 이후 개선

hahahabbb 2024. 10. 29. 17:14

1. if 문 / try-catch 문 차이 완벽 이해

1) 의문 사항 :  try-catch가 아닌 if 문으로도 해결할 수 있는 예외일 경우 if문으로 해도 괜찮은지

2) 설명 : 

 (1) if 문

-  예상이 가능한 조건을 처리

- EX) 나눗셈을 진행할 때 분모가 0이면 안되는 경우 (당연하게도 예상 가능한 예외임)

 

  (2) try-catch 문

-  예상하지 못하거나 예외를 미리 체크할 수 없는 상황

- EX) 우리가 사용자에게 입력을 받을 때 발생할 수 있는 입출력 오류 같은 경우에는 미리 체크할 수 없기 때문에 try-catch로 처리

 

  (3) 표준 예외 처리

  • InputMismatchException : 정수로 입력해야 하는데 문자를 입력한 경우 예외 발생
  • ArithmeticException : 정수를 0으로 나눌 경우 예외 발생
  • NullPointerException : null 값을 허용하지 않는 메서드에 null을 건낼 때 발생시킬 수 있는 예외
  • ArrayIndexOutOfBoundException : 배열의 범위를 벗어나서 접근한 경우 예외 발생
  • FileNotFoundException : 파일을 찾을 수 없을 경우 예외 발생
  • IllegalArgumentException : 호출자가 인수로 부적절한 값을 넘길 때 던지는 예외. EX) 나이 입력에 음수가 할당되는 경우
  • IllegalStateException : 호출된 메서드를 수행하기에 적절하지 않을 때. EX) 체스 게임을 진행하는데 체스판이 생성되지 않은 경우
  • ConcurrentModificationException : 단일 스레드 환경에서 적합한 동작을, 멀티 스레드에서 동작하려고 할 때 발생하는 예외.
  • (etc)

  (4) 적용  분모가 0인 나눗셈 처리 

//수정 전
	try {
                //Calculator 클래스의 연산 메서드로 연산 수행
                result = calc.calculate(op, firstReal, secondReal);

            }
            catch (BadDivideException e){    // second가 0이고 연산자가 / 일 경우 에러메세지를 출력
                System.out.println(e.getMessage());
            }

 

//수정 후
	if (op == '/' & secondReal == 0) {    // 분모가 0인 경우 처음부터 재입력
                System.out.println("나눗셈 연산에서 분모에 0이 입력될 수 없습니다. 처음부터 다시 입력해주세요.");
                continue;
            }

 

- 정수 간의 연산일 경우, ArithmeticException 표준 예외 활용 가능

// 정수 간 연산일 경우
		try {
                    res = OperatorType.DIVIDE.operate(firstReal, secondReal);
                }
                catch (ArithmeticException ae){
                    System.out.println("나눗셈 연산에서 분모에 0이 입력될 수 없습니다. 처음부터 다시 입력해주세요.");
                }

 

(5) 적용2 - 입력 연산자 예외 처리

    /* symbol과 일치시키기 */
    public static OperatorType fromOperator(char operator) {
        for (OperatorType type : OperatorType.values()) {
            if (type.symbol == operator) {
                return type;
            }
        }
        throw new IllegalArgumentException("해당하는 연산자가 없습니다." + operator);
    }

 

2. null 안전하게 다루기 (null-safe) 참조

1) null을 참조하게 되면 NPE(Null Point Exception)라는 런타임 오류 발생

2) null-safe 방법

 (1) if 문 처리

- 직접 if문에서 예외처리

void nullSafe(Box box) {

    Message msg = box.getMessage();
    if (msg == null) {
      msg = new Message();
    }
    if (Objects.isNull(msg)) {
      msg = new Message();
    }
}

 

 (2) 적용  종료 시 yes 입력하면 while 반복문 종료

- sc.nextLine()이 null을 반환할 가능성은 낮지만, 만약 null이 반환되면 equals("yes") 부분에서 NullPointerException이 발생할 수 있음

- null 체크를 추가하거나 "yes".equals() 방식을 사용해 null을 안전하게 처리

//수정 전
	   //종료 여부 묻기
            System.out.println("종료하시겠습니까?(yes/no)");
            if (sc.nextLine().equals("yes")) break;
//수정 후
            //종료 여부 묻기
            System.out.println("종료하시겠습니까?(yes/no)");
            String exitCheck = sc.nextLine();
            if ("yes".equals(exitCheck)) break;

 

3. enum  참조

1) 특징

- 안정성 => Enum 의 생성자 같은 경우 제어자가 private 으로 강제되기 때문에 컴파일 시점 이후에 다른 값이 추가되거나 바뀔 수가 없음

- 표현력 상승

- enum으로 관리되는 데이터가 빈번하게 변경(추가/제거) 되지 않아야 함.

 

2) 사용 이유

- 객체의 책임을 분리하기 위함. 객체가 상태와 행동 데이터를 가지고 있어야 함.

=> 그렇지 않으면, 데이터베이스에 의존하게 됨

 

3) enum 사용 예시

public enum Weeks {
	요소들;

	private한 필드들;
    
	생성자(필드 초기화) {}

	getter, setter 메서드 {}
}

EX)

public enum Weeks {
  MONDAY("mon", 10),
  TUESDAY("tue", 20),
  WEDNESDAY("wed", 30),
  THURSDAY("thu", 40),
  FRIDAY("fri", 50), 
  STURDAY("sat", 60),
  SUNDAY("sun", 70);
  
  private final String name;
  private final int value;
  
  private Weeks(String name, int value) {
  	this.name = name;
    this.value = value;
  }
  
  public String getName() {
  	return this.name;
  }
  
  public int getValue() {
  	return this.value;
  }
}

 

4) 적용 - 사칙연산

//OperatorType.java

public enum OperatorType {
    ADD('+'),
    SUBTRACT('-'),
    MULTIPLY('*'),
    DIVIDE('/');

    private final char symbol;

    OperatorType(char symbol) {
        this.symbol = symbol;
    }
    /* symbol과 일치시키기 */
    public static OperatorType fromOperator(char operator) {
        for (OperatorType type : OperatorType.values()) {
            if (type.symbol == operator) {
                return type;
            }
        }
        throw new IllegalArgumentException("해당하는 연산자가 없습니다." + operator);
    }
}
// ArithmeticCalculator.java


OperatorType type = OperatorType.fromOperator(op);

switch (type) {
            case ADD:
                res = firstReal + secondReal;
                break;
            case SUBTRACT:
                res = firstReal - secondReal;
                break;
            case MULTIPLY:
                res = firstReal * secondReal;
                break;
            case DIVIDE:
                res = firstReal / secondReal;
                break;
        };

 

4. 캡슐화

1) 정리 : 필드와 메서드를 즉 상태와 행동을 묶고, 외부에는 드러나지 않게 하는 것

2) 장점 : 

- 코드의 중복을 제거

- 데이터 처리하는 방식을 외부에서 확인 불가

3) 사용 예시

public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var(discountedPrice);
}

이 함수에서는 discount 행위가 직접 드러나며 2가지 작업을 하고 있다. goods를 discount하는 행위를 goods 클래스 내부에서 수행하고 foo함수에서는 이를 적용하도록 구현하는 것이 베스트

class Goods {
    int price = 10000;
    ...
    public int getDiscountedPrice() {
        return price * 0.9;
    }
}

public void foo(Goods goods) {
    double discountedPrice = goods.getDiscountedPrice();
    var(discountedPrice);
}

 

5. 컬렉션 변경

1) 요구 사항 : 저장된 연산 결과 중 Scanner로 입력받은 값보다 큰 결과값들 출력 => 기존에는 순회를 구현하지 않아 Queue로 진행했으나 순회까지 할 경우 ArrayList가 적합하다고 판단하여 순회 시 Queue를 ArrayList로 변경함.

2) 적용

/* Queue를 ArrayList로 변경 후 순회 메서드 적용 과정 */
    private ArrayList<Double> resArray;	//추가

    public List<Double>getResultList(){
        resArray = new ArrayList<>(resQueue);
        return resArray;
    }
    public void setResultList(Double resArray){
        this.resArray.add(resArray);
    }

    public List<Double> getGoeList1(Double targetNum){
        getResultList();
        return resArray.stream()
                .filter(result -> result >= targetNum)
                .toList();
    }

 

6. 람다 & 스트림

1)

2) 적용 - 결과값 리스트 순회하며 입력값보다 큰 값 return

    public List<Double> getGoeList1(Double targetNum){
        getResultList();
        return resArray.stream()
                .filter(result -> result >= targetNum)
                .toList();
    }