개발 낙서장

[TIL] 내일배움캠프 8일차 - 예외 처리 - 본문

Java/Sparta

[TIL] 내일배움캠프 8일차 - 예외 처리 -

권승준 2024. 1. 4. 19:28

 

 

오늘의 학습 키워드📚

오류와 예외

개발을 하다 보면 오류는 수없이 맞닥뜨리게 된다. 1글자 실수로 발생하기도 하고 로직 전체에서 문제가 발생할 수도 있다.
문법에 문제가 있어 빨간 줄이 뜨기도 하고 프로그램을 돌렸더니 무한루프가 발생한다거나 범위를 벗어나는 등 수많은 종류의 오류가 있다.
그 중에서 자바에서는 오류를 에러, 예외 두 가지로 나눈다.

  • 에러 : 프로그램을 종료시키거나 종료해야만 하는 비정상적인 동작
    대표적인 예로 아예 문법이 잘못됐거나 오타가 있어 컴파일 에러가 발생한다거나 재귀 함수가 무한하게 호출돼 StackOverflow가 발생한다거나 메모리가 초과되는 OutOfMemory가 발생하는 등 여러가지 경우가 있다.
  • 예외 : 로직에 문제가 생겨 발생하는 오류이지만 개발자가 처리 가능한 오류
    대표적인 예로 Array의 범위를 벗어나 접근하면 발생하는 IndexOutOfBounds, Null에 접근하면 발생하는 NullPointerException 등이 있다.

에러는 코드 자체를 수정해야 하고 심한 경우에는 아예 갈아엎어야 할 수도 있지만 예외는 로직을 바꾸지 않더라도 해당 로직에 예외 처리를 해주는 것으로 해결할 수 있다.

예외 처리

어떤 메소드를 구현하고 사용할 때 문제가 발생한다면 해당 문제를 처리할 수 있도록 메소드를 구현해야 할 것이다.
해당 문제를 처리할 수 있도록 하는 것이 throws이다.

class OurBadException extends Exception {
    public OurBadException() {
        super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
    }
}

class OurClass {
    private final Boolean just = true;

    // 신규 문법 throws!
    public void thisMethodIsDangerous() throws OurBadException {
        if (just) {
            // 신규 문법 throw!
            throw new OurBadException();
        }
    }
}

모든 예외는 Exception이라는 클래스를 상속해야 한다.
예외가 발생하면 처리할 내용들이 담긴 OurBadException을 만들고 어떤 메소드를 사용할 때 OurBadException 예외가 발생할 가능성이 있다면 메소드 뒤에 throws 예외 이름을 붙여 사용한다.

위의 예에서는 만약 just라는 변수가 true일 경우 예외를 발생시킨다.
그래서 해당 메소드를 그냥 사용할 경우 예외가 발생하기 때문에 그냥 사용할 수는 없다.

그럼 여기서 예외를 처리해줘야 하는데 이걸 Handling이라고 한다.
처리에는 3단계 과정을 거친다.

  1. try : 예외가 발생할 수 있는 코드
  2. catch : 예외가 발생할 경우 처리할 코드
  3. finally(생략 가능) : 예외 발생 여부에 관계 없이 실행돼야 할 코드

try-catch 구문이라고도 불리는 예외 처리 과정이다.

그럼 위의 메소드를 사용하고 싶은 경우에는 다음과 같이 처리할 수 있다.

class OurClass {
    private static final Boolean just = true;

    // 신규 문법 throws!
    public static void thisMethodIsDangerous() throws OurBadException {
        if (just) {
            // 신규 문법 throw!
            throw new OurBadException();
        }
    }

    public static void main(String[] args) {
        try{
            System.out.println("예외 처리 시작");
            thisMethodIsDangerous();
            System.out.println("예외가 발생했나?");
        }
        catch (OurBadException e) {
            System.out.println(e.getMessage());
        }
        finally {
            System.out.println("예외 처리 완료");
        }
    }
}

try에 예외가 발생할 수 있는 메소드가 포함된 코드를 작성했고 catch에서는 해당 예외 메세지를 출력했고 finally에서는 예외 처리 완료라는 메세지를 출력하도록 했다.

여기서 just가 true일 경우와 false일 경우 출력이 다르게 나온다.

True False

해당 코드는 just가 true일 경우에만 예외를 발생시킨다.
true일 경우 thisMethodIsDangerous 메소드를 호출하는 부분에서 예외가 발생했으니 바로 catch문으로 넘어가 "예외가 발생했나?" 메세지는 출력이 되지 않는다.
하지만 false일 경우 예외 발생 없이 정상적으로 작동하므로 catch문은 실행되지 않기 때문에 예외 메세지는 출력되지 않는 것이다.

그래서 catch문 안에서 코드의 순서도 매우 중요하다. 에러가 발생하기 전에 반드시 실행돼야 할 코드들, 에러가 발생하면 실행되지 말아야 할 코드들의 순서를 잘 생각해서 작성해야 예외를 처리해도 프로그램이 문제없이 동작할 것이다.

그리고 catch문은 여러번 사용할 수 있다. try { ... } catch { ... } catch { ... } ... try문 안에서 3개의 메소드를 사용했는데 각기 다른 3개의 예외를 발생시킬 수 있기 때문에 모두 처리해야 한다면 각각 예외를 처리하는 catch문을 구현하면 된다.

근데 아마 대부분의 상황에서는 위에 처럼 Exception 클래스를 직접 구현하는 일은 정말 거의 없을 것이다.
이미 자바에서 수많은 예외를 구현해놨기 때문에 대부분의 상황에서는 이미 구현된 예외를 가져와 잘 처리하기만 하면 된다.

아래에는 자바의 예외 리스트 들이다.

더보기

// 출처 : https://programming.guide/java/list-of-java-exceptions.html

java.io

IOException
CharConversionException
EOFException
FileNotFoundException
InterruptedIOException
ObjectStreamException
InvalidClassException
InvalidObjectException
NotActiveException
NotSerializableException
OptionalDataException
StreamCorruptedException
WriteAbortedException
SyncFailedException
UnsupportedEncodingException
UTFDataFormatException
UncheckedIOException

java.lang

ReflectiveOperationException
ClassNotFoundException
InstantiationException
IllegalAccessException
InvocationTargetException
NoSuchFieldException
NoSuchMethodException
CloneNotSupportedException
InterruptedException

산술 예외

IndexOutOfBoundsException
ArrayIndexOutOfBoundsException
StringIndexOutOfBoundsException
ArrayStoreException
ClassCastException
EnumConstantNotPresentException
IllegalArgumentException
IllegalThreadStateException
NumberFormatException
IllegalMonitorStateException
IllegalStateException
NegativeArraySizeException
NullPointerException
SecurityException
TypeNotPresentException
UnsupportedOperationException

java.net

HttpRetryException
SocketTimeoutException
MalformedURLException
ProtocolException
SocketException
BindException
ConnectException
NoRouteToHostException
PortUnreachableException
UnknownHostException
UnknownServiceException
URISyntaxException

java.text

ParseException

java.time

DateTimeException

java.time.zone

ZoneRulesException

예외 처리 방법

1. 예외 복구하기

public String getDataFromAnotherServer(String dataPath) {
    try {
        return anotherServerClient.getData(dataPath).toString();
    } catch (GetDataException e) {
        return defaultData;
    }
}

try-catch 내에서 예외를 처리하고 원상복구 시키는 방법이다.
가장 이상적이고 기본적인 방법이지만 실제로는 이렇게 복구하기 어려운 예외들이 대부분이다.

2. 예외 처리 회피하기

public void someMethod() throws Exception { ... }

public void someIrresponsibleMethod() throws Exception {
    this.someMethod();
}

someMethod에서 발생한 예외를 다시 throws해 회피해버리는 방법이다.

3. 예외 전환하기

public void someMethod() throws IOException { ... }

public void someResponsibleMethod() throws MoreSpecificException {
    try {
        this.someMethod();
    } catch (IOException e) {
        throw new MoreSpecificException(e.getMessage());
    }
}

 

예외가 발생하면 좀 더 정확한 예외로 다시 던지는 방법이다.
언뜻 보면 예외 회피와 비슷하지만 예외 전환은 단순히 예외를 던지는 것이 아니라 분석해서 좀 더 정확한 예외로 던져주는 것이다.


오늘의 회고💬

사실 나같이 개발을 공부하는 단계에서는 예외 처리를 할 만한 상황이 거의 없다. 일부러 예외를 만들만한 상황을 유도하는 것이 아니면 웬만한 부분은 코드 수정이나 로직 수정으로 해결되는 게 많다.
하지만 실무에 들어가면 정말 수많은 예외들과 예외 처리 로직이 있을 것이다. 미리 대비해서 준비하면 무조건 도움이 될 거라고 생각한다.

 

내일의 계획📜

키오스크 만들기 개인 과제가 있기 때문에 이제 개인 과제를 진행해야 된다.
당연히 알고리즘 문제 또한 꾸준히 풀어주면서 개인 과제를 진행해야겠다.

Comments