728x90
프로그램 오류
프로그램이 실행중에 어떤 원인에 의해 오작동 하거나 비정상적으로 종료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다. 이를 발생 시점에 따라서 나눌 수 있다.
- 컴파일 에러 : 컴파일 시, 발생하는 에러
- 런타임 에러 : 프로그램 실행 도중, 발생하는 에러
- 논리적 에러 : 컴파일도 잘되고 실행도 잘되지만, 의도와 다르게 동작하는 것
런타임 에러
자바에서는 실행 시 발생할 수 있는 프로그램 오류를 두 가지로 구분했다.
- error (에러)
- 메모리 부족 (OutOfMemoryErrorr) 나 스택오버플로우 (StackOverFlow) 같이 일단 발생하면 복구할 수 없는 심각한 오류
- 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
- 발생하면 프로그램의 비정상적 종료를 막을 수 없다.
- exception (예외)
- 발생하더라도 수습될 수 있는 비교적 덜 심각한 오류
- 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
- 프로그램의 비정상적 종료를 막을 수 있다.
예외 클래스 계층 구조
자바에서는 실행 시 발생할 수 있는 오류 (Exception과 Error)를 클래스로 정의했다.
- Object : 예외도 객체이다. 예외의 최상위 부모도 Object 이다.
- Throwable : 최상위 예외이다.
- Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외이다.
- 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
- 상위 예외를 catch 로 잡으면 그 하위 예외까지 함께 잡는다.
- 따라서 Throwable 예외도 잡으면 안된다.
- 애플리케이션 로직은 이런 이유로 Exception 부터 필요한 예외로 생각하고 잡으면 된다.
- Error도 UnCheck Exception 이다.
- Exception : Check Exception
- 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다.
- Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 Check Exception 이다.
- 단 RuntimeException 은 예외로 한다.
- RuntimeException : UnCheck Exception, Runtime Exception
- 컴파일러가 체크하지 않는 UncheckException 이다.
- 자식 예외도 모두 UnCheck Exception 이다.
- RuntimeException 의 이름을 따라서 그 자식 예외까지 런타임 예외라고 많이 부른다.
예외에 대한 2가지 기본 규칙
- 예외는 잡아서 처리 ( try - catch )하거나 던져야 한다. ( throws )
- 예외를 잡거나 던질때, 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다.
예외를 처리하지 못하고 계속 던지면 어떻게 되는가?
- 자바 main() Thread의 경우
- 예외 로그를 출력하면서 시스템이 종료된다.
- 웹 애플리케이션의 경우
- 여러 사용자의 요청을 처리하기 때문에 하나의 예외 때문에 시스템이 종료되면 안된다.
- WAS 가 해당 예외를 받아서 처리한다.
- 주로 사용자에게 개발자가 지정한 오류 페이지를 보여준다.
Checked Exception
체크 예외는 잡아서 처리하거나, 밖으로 던지도록 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
- Exception 을 상속받아서 사용하면 Checked Exception 이 된다.
- RuntimeException 을 상속받으면 Unchecked Exception 이 된다.
- 예외가 제공하는 여러가지 기본 기능이 있는데, 그 중에 오류 메시지 보관 기능이 있다.
- Check Exception 은 예외를 잡아서 처리할 수 없을 때, 예외를 밖으로 던지는 throws 예외를 필수로 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
- 장점
- 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이다.
- 단점
- 하지만 실제로는 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에 너무 번거롭다.
- 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 한다.
- 의존관계에 따른 단점이 있다.
Unchecked Exception ( Runtime Exception )
Compiler 가 예외를 체크하지 않는다는 뜻이다.
- RuntimeException 과 그 하위 예외는 Unchecked Exception 으로 분류된다.
- 예외를 잡아서 처리하지 않아도 throws 를 선언하지 않고, 생략할 수 있다. 이 경우 자동으로 예외를 던진다.
- 장점
- 신경쓰고 싶지 않은 Unchecked 예외를 무시할 수 있다.
- 신경쓰고 싶지 않은 예외의 의존관계를 참조하지 않아도 된다.
- 단점
- Unchecked 예외는 개발자가 실수로 예외를 누락할 수 있다.
런타임 예외 구현 기술 변경시 파급 효과
- 런타임 예외를 사용하면 중간에 기술이 변경되어도 해당 예외를 사용하지 않는 컨트롤러, 서비스에서는 코드를 변경하지 않아도 된다.
- 단, 구현 기술이 변경되는 경우, 예외를 공통으로 처리하는 곳에서는 예외에 따른 다른 처리가 필요할 수 있다.
- 하지만 공통 처리하는 한 곳에서만 변경하면 되기때문에 변경의 영향 범위는 최소화 된다.
정리
- 체크 예외의 문제점 때문에 최근 라이브러리들은 대부분 런타임 예외를 기본으로 제공한다.
- 스프링도 대부분 런타임 예외를 제공한다.
- 예외를 공통으로 처리하는 부분을 앞에 만들어서 처리한다.
런타임 예외의 문서화
런타임 예외는 놓칠 수 있기 때문에 문서화가 중요하다.
- 문서에 예외를 명시한다.
- 던지는 예외가 명확하고 중요하다면, 코드에 어떤 예외를 던지는지 throws 로 명시해 개발자가 IDE 를 통해 예외를 확인하기 편리하게 한다.
예외의 스택 트레이스
log.info("예외 처리, message = {}", e.getMessage(), e);
로그를 출력할때, 마지막 파라미터에 예외를 넣어주면 로그에 스택 트레이스를 출력할 수 있다.
- 위와 같이 로그를 남겼을때, 두 번째 줄부터 예외에 대한 스택 트레이스가 추가로 출력된다.
- 로그를 남길 때 로그의 마지막 인수에 예외 객체를 전달해주면 로그가 해당 예외의 스택 트레이스를 추가로 출력해주는 것이다.
System.out 에 스택 트레이스를 출력하려면 e.printStackTrace() 를 사용하면 된다.
기존 예외 포함하기
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e); //기존 예외(e) 포함
}
}
예외 처리
- catch 는 해당 타입과 그 하위 타입을 모두 잡을 수 있다.
- 체크 예외를 처리할 수 없을때는 method() throws Exception 을 사용해서 밖으로 던질 예외를 필수로 지정해주어야 한다.
- throws 를 지정하지 않으면 컴파일 오류가 발생한다.
- throws 에 지정한 타입과 그 하위 타입 예외를 밖으로 던진다.
예외의 기본 원칙
- 기본적으로 언체크 예외를 사용한다.
- 체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용한다.
체크 예외의 문제점
- 웹 애플리케이션이라면 서블릿의 오류 페이지나, 스프링 MVC 가 제공하는 ControllerAdvice 에서 예외를 공통으로 처리한다.
- API 라면 500 에러를 사용해서 응답을 내려준다.
- 해결이 불가능한 공통 예외는 별도의 오류 로그를 남기고, 개발자가 오류를 빨리 인지할 수 있도록 메일, 알림 등을 통해서 전달받아야 한다.
즉, 크게 2가지 문제가 있다.
- 복구 불가능한 예외
- SQLException 등을 예로들면, 대부분의 서비스나 컨트롤러는 이런 문제를 해결할 수 없다.
- 따라서 이런 문제들은 일관성 있게 공통으로 처리해야 한다.
- 오류 로그를 남기고 개발자가 해당 오류를 빠르게 인지하는 것이 필요하다.
- 서블릿 필터, 스프링 인터셉터, 스프링의 ControllerAdvice 를 사용하면 공통 처리가 가능하다.
- 의존 관계에 대한 문제
- SQLException을 예로 든다.
- 서비스, 컨트롤러에서 SQLException 에 의존하게 된다.
- 향후 Repository 를 JDBC 기술이 아닌 다른 기술로 변경할때, JPAException 으로 예외가 변경된다고 가정한다.
- 이에따라 SQLException 에 의존하던 모든 서비스, 컨트롤러의 코드를 JPAException 에 의존하도록 고쳐야 한다.
- 서비스나 컨트롤러 입장에서는 본인이 처리할 수도 없는 예외를 의존해야 한다는 단점이 생긴다.
정리하면,
- 데이터베이스나 네트워크 통신처럼 시스템 레벨에서 올라온 예외들은 대부분 복구가 불가능하다.
- 이런 경우에 체크 예외를 사용하면 올라온 복구 불가능한 예외를 서비스, 컨트롤러 같은 클래스가 알고 있어야하고, 이에 따른 불필요한 의존관계 문제가 발생하게 된다.
인용
https://cocobi.tistory.com/146
728x90
'Language > Java' 카테고리의 다른 글
얕은 복사, 깊은 복사 (0) | 2023.03.26 |
---|---|
Optional 메소드 (0) | 2023.03.03 |
IllegalArgumentException, IllegalStateException (0) | 2023.02.26 |
추상클래스 (0) | 2023.02.19 |
DI(Dependent Injection) 이란? (0) | 2022.12.11 |