예외 처리, 재귀, 반복문, 좋은 예외 처리

2024. 2. 7. 00:20Backend 취업준비/Java

에러 : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류

예외 : 프로그램 코드에 의해서 발생하거나 수습될 수 있는 다소 미약한 오류 

 

예외 처리의 정의, 목적

정의 : 프로그램 실행 시 발생할수 있는 예외에 대비한 코드를 작성하는것

목적 : 프로그램의 비정상 종료를 막고, 정상적이고 의도한 실행 상태를 만드는 것

 

 

클래스 계층 구조

  • 최상위 클래스 Object, 그 하위 클래스 Throwable 클래스
  • Exception과 Error모두 Throwable 클래스의 하위 클래스 이다
  • Exception클래스는 Checked Exception, UnChecked Exception으로 구분 될 수 있다. 
  • RuntimeException역시 Exception을 상속받지만, JVM은 RuntimeException 상속 여부를 보고 실행 예외를 판단
  • 예외가 발생하면 예외 객체가 생성된다. try-catch로 예외처리를 하게 된다면, catch 블록에서 예외 객체 참조변수가 유효하다

 

 

Throwable클래스의 주요 메서드 

  • printStackTrace() : 예외 발생 당시의 호출 스택에 있었던 메서드의 정보와 예외 메세지를 화면에 출력한다
  • getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메세지를 얻는다

 

Checked Exception, UnChecked Exception

  • 컴파일러가 예외 처리를 체크하는 지 여부에 따라 둘을 나눈다.
  • Checked Exception (Exception) : 예외처리를 하지 않는다면 컴파일 조차 되지 않는다.
    • IOException : 입출력 작업 중에 발생하는 예외. 파일에서 데이터를 읽거나 쓸 때, 네트워크 통신 중 데이터를 주고받을 때 등에 발생할 수 있다
    • ClassNotFountException : 자바에서 클래스를 로드하려고 할 때 해당 클래스를 찾을 수 없는 경우 발생하는 예외.이 예외는 주로 클래스 이름을 잘못 입력하거나 클래스 파일이 존재하지 않는 경우
  • UnChecked Exception (Runtime Exception) : 예외 처리를 하지 않더라도 컴파일이 되고, 실행이 된다. 하지만 프로그램이 비정상 종료된다.
    • NullPointerException : 객체 참조가 없는상태, 객체가 없는 상태에서 객체를 사용하려 했을 때 예외가 발생.
    • NumberFormatException : 숫자로 변환될 수 없는 문자가 포함되어있을때 발생.-
    • IllegalArgumentException : 적합하지 않거나 적절하지 못한 인자를 메소드에 넘겨주었을 때 발생.
    • ClassCastException : 타입변환은 상위 클래스와 하위 클래스 간에 발생하고 구현 클래스와 인터페이스 간에도 발생한다. 이러한 관계가 아니라면 클래스는 다른 클래스로 타입 변환할 수 없다. 억지로 타입 변환을 시도할 경우 발생.
    • ArrayIndexOutOfBoundsException : 배열에서 인덱스 범위를 초과하였을때 발생.

 

예외 처리

 

  • try - catch - finally
    • try 블록 : 예외 발생 가능 코드
    • catch 블록 : 예외 처리 코드 (예외가 발생하지 않으면 실행되지 않는다)
      • 다중 catch : 
      • 멀티 catch : 
    • fianlly 블록 : 예외 발생 여부와 상관없이 항상 실행할 내용이 있을 경우에만 작성 (옵션, 생략가능) try, catch 블록에서 return문을 사용하더라도 항상 실행된다.
  • throws로 떠넘기기 
    • 예외가 발생하는 메서드 선언부에 throws 키워드를 붙이면 예외 처리를 메서드가 호출할 곳에서 하도록 한다.
    • throws를 쓰다가 조금 의문이 들었다 어짜피 예외가 발생하는 메서드를 호출할 때, try-catch로 예외처리를 하면 되는데 굳이 떠넘긴다고 표현할 필요가 있을까?
      • 컴파일러 체크 예외는 throws를 안 해주면 오류가 발생한다.
      • llegalArgumentException등 자바 표준 라이브러리의 RuntimeException으로 이미 정의되어 있는 예외 클래스나, RuntimeException으로 상속받는 사용자 정의 예외 클래스는 메서드에서 이 예외를 발생 시킬 때 별도의 throws 선언을 하지 않더라도 컴파일 오류가 발생하지 않는다. (그래도 명시적으로 적어주는 것이 좋다고 생각한다. 더 고민해보자)

 

 

사용자 입력과 예외 처리

 

  • 사용자에게 이메일을 입력 받는다고 가정하자. 만약 이메일 정규식에 부합하지 않거나, 중복 이메일이 있거나 한다면 예외를 발생 시키고 재 입력 받을 수 있다.
  • 이때 함수를 재 호출하는 재귀를 활용하거나 while문을 활용할 수 있다.
  • 각각의 방법은 장단점을 가진다.

 

재귀

 

  • 장점
    • 특별한 로직 없이 단순하게 구현할 때 유리하다
    • 코드가 간결하고 이해하기 쉽다
  • 단점
    • 재귀 함수는 기본적으로 스택 메모리를 사용하는데, 재귀의 깊이가 깊어졌을 때, stack overflow가 발생하면서 프로그램이 비정상적으로 종료 될 수 있다.
    • 언제나 안전한 프로그램을 개발해야 하는 입장에서, 충분히 에러가 발생할 수 있는 여지를 남겨놓는 것은 바람직하지 않다.
    • 또한 함수가 호출되고 종료될 때 스택 프레임을 구성하고 해제하는 과정에서 반복문보다 오버헤드가 들기 때문에 속도도 훨씬 느려지게 된다.
    • 단점을 어느 정도 상쇄 시켜줄 수 있는 꼬리 재귀 가 있다고 한다. 하지만 자바에선 불가능 하다고 한다.. 가볍게 알아 보는 것도 좋을 것 같다.

 

while 루프

 

  • 장점
    • 스택 오버 플로우에 대한 위험이 없다. 재귀보다 메모리 사용량이 더 적을 수 있다.
  • 단점
    • 코드가 다소 번잡할 수 있다. 특히 복잡한 로직을 처리할 때 while 루프 내에 많은 코드가 들어갈 수 있고, 한눈에 어떻게 작동하는지 파악이 힘들다.
    • 종료 조건을 제대로 설정하지 않으면 무한 루프에 빠질 수 있다.

결론

 

  • 성능이 중요했던 과거에는 반복문으로 구현 하는게 당연했겠지만, 하드웨어의 발전으로 소프트웨어의 자체 성능에 대한 중요도가 낮아졌기 때문에 오히려 협업이 강조되고 있는 요즘에는 코드의 가독성도 충분히 고려할 필요가 있다
  • 재귀 깊이가 예측 가능한 경우 위주로 사용하자

 

 

좋은 예외 처리

 

 

복구 가능한 오류 : 시스템 외적인 요소로 발생하는 치명적이지 않은 오류

  • 사용자의 오입력
  • 네트워크 오류
  • 서비스적으로 중요하지 않은 작업의 오류
  • 처리
    • 상시로 발생할 수 있다고 가정하고 사용자에게 문제 원인 고지
    • 너무 잦은 복구 가능한 오류는 복구 불가능한 오류 (개발자의 잘못 구현된 코드)일 수 있으니 이를 주의

복구 불가능한 오류 : 별도의 조치 없이 시스템이 자동으로 복구할 수 있는 방법이 없는 경우. 대부분 프로그램 중단

  • 메모리 부족 (Out of Memory)
    • 시스템이 필요한 메모리를 할당 받지 못할 때 발생한다.
  • 스택오버플로우 (StackOverflow)
    • 재귀 함수가 너무 깊게 호출되어 스택 메모리가 고갈 될 때 발생한다.
  • 시스템 레벨의 오류
    • 하드웨어 문제나 운영 체제의 중대한 버그로 인해 발생한다.
  • 개발자의 잘못 구현된 코드
  • 처리
    • 빠르게 개발자에게 알린다

 

null, -1, 빈 문자열 등 특수 값을 예외 대신 반환하지 않는다

 

  • 예외 상황엔 꼭 throw new exception으로 예외를 발생 시키자

 

추적 가능하도록 예외 메세지를 작성한다

 

  • 다음과 같은 형태로 예외를 생성하면 입력값에 대한 오류인것은 알겠지만, 어떻게 입력했길래 검증 로직에서 실패한 것인지 알 수가 없다.
throw new IllegalArgumentException('잘못된 입력입니다.');
  • 반면 다음과 같이 예외를 남기면, 어떤 이유로 잘못된 것인지 빠르게 파악할 수 있다.
throw new IllegalArgumentException("사용자 " + userId + "의 입력(" + inputData + ")가 잘못되었습니다.");

 

 

Layer에 맞는 예외

  • 여러 계층(layer)로 구성된 소프트웨어 아키텍처에서 각 계층에 맞게 적절한 예외를 정의하고 던지는 방법을 의미한다.
  • 이러한 방식은 오류를 더 쉽게 추적하고, 각 계층에서 발생하는 오류의 본질에 따라 적절한 처리를 할 수 있도록 한다.
  • 일반적인 3계층 웹 애플리케이션에서는 다음과 같은 계층이 있다.
    • 데이터 액세스 계층 (Data Access Layer)
    • 비즈니스 로직 계층 (Business Logic Layer)
    • 프레젠테이션 계층 (Presentation Layer)
  • 각 계층에서 발생할 수 있는 오류의 성격은 다르기 때문에, 해당 계층에 맞는 예외를 던지는 것이 유용하다

 

예외 계층 구조를 만든다

  • 수많은 사용자 정의 예외를 계층 없이 만들 지 않는다. 용도에 맞게 분류할 필요가 있으며, 기준에 맞게 분류한 Exception들은 그에 맞게 일관된 처리 방법을 적용할 수 있다.

 

 

예외의 이름에 원인과 내용을 반영한 네이밍으로 선정한다.

 

catch한 후 아무런 작업 없이 throw하는 로직을 만들지 않는다.

 

예외 상황이 아닌데, 단지 프로그램을 정상적으로 진행 시키기 위해 예외를 사용하지 않는다

  • 프로그램은 정상적으로 작동하지만, 원하는 데이터가 안 들어 온다고 해서 예외를 발생시키지 않는다.

 

출처

(도서) 이것이 자바다

자바의 정석 유튜브 강의

https://tecoble.techcourse.co.kr/post/2020-04-30-iteration_vs_recursion/

https://jojoldu.tistory.com/734