Exception

DeadWhale·2023년 2월 21일
0

JAVA

목록 보기
4/10

Exceptional event 라는 단어를 축약한 단어.
자바 튜토리얼 사이트의 설명

Definition : 정의
An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

예외는 프로그램 실행 중에 프로그램 명령의 정상적인 흐름을 방해하는 이벤트입니다.

자바의 예외는 Checked Exception , Unchecked Exception , Error 3종류로 구성 되어 있다.

Throwable

❕ `Checked Exception` , `Unchecked Exception` , `Error` 의 최상위 부모

주요 메서드

Method Signature
public String getMessage()예외의 상세한 정보를 문자열로 반환
String toString()예외에 대한 간략한 정보를 문자열로 반환
public void printStackTrace()예외의 발생 위치 + 스택 추적 정보를 출력
StackTraceElement[] getStackTrace()예외의 발생 위치 + 스택 추적 정보를 배열로 반환
public synchronized Throwable getCause()예외를 발생시킨 원인 예외를 반환

getMessage()

public class Main {
    public static void main(String[] args) {
        String str = null;
        try {
            System.out.println(str.length());
        } catch (NullPointerException e) {
            System.out.println(e.getMessage());
        }
    }
}
  • 문자열에 null을 할당 한 후 문자열의 길이를 출력하게 되면 NPE 가 발생하게 된다.
  • 예외를 catch한 후 Exception객체에서 message를 사용하면 null을 반환하게 된다.

왜 null을 반환하냐면 에러 메시지를 미리 설정하지 않았기 때문에
에러 메시지가 null인 상태이기 때문에 null 자체를 반환한다.

  • IO Exception의 예시 ( 없는 파일을 읽으려 하기 때문에 에러 발생 );
try {
    FileInputStream file = new FileInputStream("없는_파일.txt");
} catch (IOException e) {
    e.printStackTrace();
    System.out.println("IOException occurred: " + e.getMessage());
}
  • 실행결과
java.io.FileNotFoundException: nonexistent.txt (No such file or directory)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
	at Main.main(Main.java:6)
IOException occurred: 없는_파일.txt (No such file or directory)
  • 위의 예외에서 “No such file or directory” 문자열은 FileNotFoundException 에서 기본적으로 제공하는 예외 메시지이다 (IO Exception의 하위 클래스)

String toString()

  • toString은 Object의 toString을 오버라이드 한 내용이다.
public String toString() {
    String s = getClass().getName();
    String message = getLocalizedMessage();
    return (message != null) ? (s + ": " + message) : s;
}
  • 문자열 s에는 발생한 예외 클래스명을 저장한다
  • message에는 getLocalizedMessage 를 활용해 예외메세지를 저장한다 ❕ `getMessage()` 가 오버라이드 되지 않는 Throwable 하위 클래스는 `getLocalizedMessage()` 를 호출해 예외 메시지를 반환해야 안전하다. 왜 안전하지? 하위 메소드에서 `getMessage()` 를 오버라이드 하지 않은 경우에는 `getLocalizedMessage()` 에서 예외 메시지를 반환한다 이떄 반환되는 문자열이 하위 클래스에서 설정한 예외 메시지가 아닐 수도 있다.
  • 삼항연사자를 활용해getLocalizedMessage 가 없는 경우에는 예외명만 전달한다.

public void printStackTrace()

Exception in thread "main" java.lang.NullPointerException
    at com.example.MyClass.myMethod(MyClass.java:10)
    at com.example.MyClass.main(MyClass.java:5)
  • 내가 가장 많이 사용하는 메소드
  • 예외 발생 시 디버깅 용으로 사용된다.
  • stack은 호출 순서를 의미한다.

    main → method( A ) → method( B ) → method( C )
    이러한 호출의 흐림이 생기게 되면
    호출 스택에는 main , A , B , C 가 순서대로 쌓이게 된다.

    스택에서 C가 제거된 후 B가 다시 호출되고 B가 제거되면 A가 호출된다

C 스택에서 예외가 발생 시

Exception in thread "main" java.lang.RuntimeException: 예외 발생
    at com.example.C.methodC(C.java:8)
    at com.example.B.methodB(B.java:8)
    at com.example.A.methodA(A.java:8)
    at com.example.Main.main(Main.java:8)

B 스택에서 예외가 발생 시

Exception in thread "main" java.lang.RuntimeException: 예외 발생
    at com.example.B.methodB(B.java:8)
    at com.example.A.methodA(A.java:8)
    at com.example.Main.main(Main.java:8)

호출 스택은 일반적으로 쓰레드와 1대1관계를 가지지만
하나의 쓰레드에서 여러개의 호출 스택을 사용하는 경우도 있다

예를 들어
재귀호출을 사용할 떄 하나의 호출 스택에 모든 함수 호출 스택을 쌓게 되면
이때 바로 Stack Overflow가 발생하게 된다.

  • (반복문이나 호출 스택의 크기를 늘려 해결할 수 있다)

Error

개발할때 에러라는 말을 굉장히 자주 사용하지만
ThrowableError는 자바 프로그램 외의 문제를 의미한다.

시스템 레벨의 문제들이 대표적인대

  • OutOfMemoryError : 메모리 부족 오류
  • StackOverflowError : 스택 오버플로우 오류
  • InternalError : 가상 머신 내부 오류
  • NoClassDefFoundError : 클래스를 찾을 수 없는 오류
  • ThreadDeath : 쓰레드가 강제로 종료될 때 발생하는 오류드

등이 가장 대표적이다.

간단한 식별 방법으로는 출력 오류가 Error로 끈나는가 Exception으로 끈나는지를 가지고 구분해도 된다.

Error는 프로세스에 영향을 주고 , Exception은 쓰레드에 영향을 준다.

Error 클래스는 예외 처리가 존재하지 않는데
Error들의 발생 원인들은 대체적으로 복구할수 없는 수준의 에러가 대부분이기 때문이다.

위의 괄호한의 예외들은 대표적으로 코드로 해결할 수 없는 문제다.

NoClassDefFoundError 와 같은 에러는 클래스를 찾을 수 없다는 의미인대

코드를 찾지 못했는데 코드를 찾기 위해 코드를 쓰는건 어불 성설이다.

결과적으로 예외는 프로그램의 비정상 종류다.

그렇다면 개발자는 Error에 대응하기 위해 어떠한 것을 할 수 있을까.

기도

  • 날아다니는 스파게티교나 , 천주교.... 종교에 기도한다.

메모리 관리에 주의하기.

  • 메모리 누수를 생각하며 개발해야 한다.

시스템의 안정성과 신뢰성을 고려하기

  • 백업과 , 복구 방법 , 모니터링 시스템의 구축을 고려해야 한다.

로그를 기록하기

  • 로그를 기록하고 파일로 내보내서 에러 발생 시점과 원인을 파악할 수 있다.

코드 리뷰

  • 코드로 해결할 수 없는 예외지만 코드로 인해 발생하기도 한다.
  • 본인이 파악할 수 없는 예외를 다른 팀원에게 코드의 분석을 맞긴다.

Runtime Exception

unchecked Exception || checked Exception

런타임 에러는 컴파일시에는 예상하지 못하다가 , 실행 도중 발생하는 예외를 의미한다.

대표적인게 NPE( NullPointerException )

  • 서버로 아이디 값이 넘어와야 하지만 , 대소문자 , parameter등이 달라서 발생하는 문제가 대표적

ArrayIndexOutOfBoundsException

  • 인덱스의 크기가 음수 혹은 배열의 크기보다 클 경우 발생한다.
public class MyException {
    public static void main(String[] args) {
        MyException mx = new MyException();
        mx.callException();
    }

    public void callException() {
        throw new ExceptionExtendsRuntime();
    }
}

예외 클래스를 직접 정의해보았다.

ExceptionExtendsRuntime 클래스는 RuntimeException를 extends받고

ExceptionExtendsException 클래스는 Exception 를 직접 상속 시켰다.

결과는 당연하게 ExceptionExtendsException 는 예외 처리를 요구하게 된다.

이 포인트에서 들 의문점은 RuntimeException 과 내가 만든 ExceptionExtendsException
두 클래스는 모두 Exception 클래스를 상속받고 super() 를 활용해
상위 메서드를 호출하는 모양인대 왜 내가 정의한 예외는 try-catch를 요구할까?

일단 집고 넘어가야할 문제는 RuntimeException 을 내가 구현할 수 있을까라는 문제였다.

catched 와 unchecked의 차이는 오로지 컴파일 시점의 문제라고 한다.

선택된 예외는 컴파일 시에 확인되어 처리 중인지 확인합니다.
예외를 포착하거나 포함 메서드가 예외를 발생시킨다고 선언합니다.
실행 시 선택된 예외와 선택되지 않은 예외는 구분되지 않으며 JVM에 의해 동일하게 처리됩니다.
따라서 "확인"은 순전히 컴파일 타임 개념입니다.


이 분의 말씀대로 라면 단순히 컴파일 시점에만 식별하기 때문에
내가 만든 클래스를 식별할 수 없기 때문에 .단순한 Checked Exception이라고 판단하나 보다

결론은 컴파일러가 컴파일 하면서 RuntimeException만 감지 후 예외처리를 강제로 하지 않도록 구현이 되어있다 보다 , 나중에 언젠가 컴파일러를 까보면서 어떻게 감지하는지 알고 싶다.


checked Excpetion는 Try-catch를 강제하지만
Unchecked Exception은 catch를 강제하지 않는다.
그렇다면 이 둘을 구분하는 기준은 뭘까,

Unchecked Exception 는 프로그램의 오류나 버그를 나타내는 예외를 의미한다.
Checked Exception 는 몇몇 상황에서 발생시 프로그램의 중단을 발생 시킬 수 있냐의 차이라 판단했다.

외부의 리소스를 활용시 이러한 예외가 많다고 생각하는데 .
네트워크 , 파일처리 같은 예외는 컴파일시 파악할 수 없어서가 아닐까라고 생각해보았다.

마지막으로 RuntimeException은 예외 처리를 강제하지 않는 이유는 무엇일까 찾아보았는데
가장 핵심적인 내용은 예외처리가 필요한 경우가 적어서 인것 같다.

그리고 주로 프로그램의 설계와 구현의 오류와 버그를 나타내는 예외인대 이러한 경우는
대부분 예외처리로 문제를 해결할 수 없는 경우가 많아서도 있는것 같다.

예를 들어 StackOverflowError 는 호출스택의 과도한 누적으로 발생하게 하는데
예외처리를 하더라도 계속해서 생기는 문제이기 때문에 설계의 문제이다,
이런 경우에는 스택의 크기를 늘리거나 , 호출을 최적화 하는 방식으로 해결해야한다.


Rollback

검색하면서 예외 발생 시 트랜잭션 처리에 관한 내용도 계속 보였는데

보면서 뭔가 기시감이 들었다.

뭔가 이 내용에 대해 기선님에게 들어본것 같은데 하면서

다시 확인해본 결과

  • 기본적으로 예외 발생 시 트랜잭션 처리의 기준은 개발자가 판단해야한다.
  • 왜 이러한 내용이 정확한것 처럼 퍼졌는가.
    • Spring에서 RuntimeException 계열은 기본적으로 Rollback을 수행한다.
    • 하지만 이런 예외 처리는 사용자의 요구사항에 따라 유동적으로 해결할 수 있다.

하지만 자바 == Spring이 아니기 때문에 해당 표는 정확하지 않다고 판단되었다.

Spring은 왜 RuntimeException 발생 시 왜 기본적으로 rollback 되도록 하였을까의 이유는 일관성인것 같다

RuntimeException의 예외들은 로직,설계상의 문제로 데이터의 정보가 깨지거나 하는 등의 문제가 발생하는대
이러한 경우 Rollback 을 통해 원래의 데이터 상태로 복구하는 것이 중요해서 인것 같다.

ex)

  1. 사용자의 비밀번호가 변경 요청이 들어옴
  2. 프론트와 Parameter name이 달라 null 들어오는 경우가 발생
  3. 이 때 서버 개발자는 데이터를 검증해 예외를 발생시켜야 한다.
  4. 이 때 만약 예외 처리가 발생하지 않은면 사용자의 비밀번호가 null 들어가는 문제가 발생하게 된다.

더 깊게 공부하고 싶은 부분이 몇 부분이 있었다.
호출 스택 , 컴파일러가 RuntimeException과 Excetpion을 식별하는 방법,
등등 나중에 쓰레드에 대해 파고들때 호출스택을 같이 파보고 ,
컴파일 내부구조에 대해 공부할 일이 있으면 식별하는 방식도 알아보고 싶다.

0개의 댓글