프로그램에서 오류가 발생할 경우 시스템 레벨에서 프로그램에 문제를 야기하여, 원치않는 버그를 일으키거나, 심할경우 실행 중인 프로그램을 강제로 종료하기도 한다.
내부적 요인으로는 프로그램 설계 로직에 문제가 생겨서 그럴 수 있고, 외부적인 요인으로는 프로그램 자체 문제가 아닌 하드웨어에서 문제가 생겨 오류가 발생할 수 있다.
프로그래밍 언어에서는 오류를 발생 시점에 따라 크게 3가지로 나눈다.
컴파일 단계에서 오류를 발견하면 컴파일러가 에러 메세지를 출력해주는 것을 말한다. 컴파일 에러 발생의 대표 원인으로 문법 구문 오류(syntax error)를 들 수 있다.
예를들어 코딩을 할 때 맞춤법, 또는 문장부호(;), 선언되지 않은 변수와 같은 것들을 사용하면 컴파일 단계에서 에러를 일으킨다.
컴파일 에러는 심각하게 볼 오류는 아니다. 컴파일 단계에서 오류가 났다는 것은 프로그램 실행 자체가 안됐기 때문에 개발자가 찾아서 수정을 하면 되며, IDE에서는 일정 주기로 계속 자동으로 컴파일을 해주기 때문에 문제르 바로바로 알 수 있다.
컴파일 에러가 발생하지 않았더라도, 프로그램 실행 중 에러가 발생하여 잘못된 결과를 얻거나, 외부적인 요인으로 기계적 결함으로 프로그램이 비정상적으로 종료가 될 수 있다.
대체로 개발 단계에서 설계 미숙(논리적)으로 발생하는 경우가 많으며, 런타임 에러(실행 오류)가 발생하게 되면 개발자가 역추적하여 원인을 찾아야 한다. 이러한 잠재적인 런타임 에러를 방지하기 위해 프로그램 실행도중 발생할 수 있는 경우의 수를 고려하여 이에 대한 대비를 한다.
논리적 에러로 이른바 '버그'라고 불린다고 볼 수 있다.
프로그램이 실행하고 진행하는데 아무런 문제가 없는 오류이지만, 결과가 예쌍과 달라서 사용자가 의도한 작업을 수행하지 못하게 되어 서비스 이용에 지장이 생길 수 있다.
예를 들어서 음수가 나오면 안되는 상황에서 음수의 결과값이 나오는 경우 같은 것들을 들 수 있다.
논리적 오류의 경우 프로그램 자체는 문제없이 돌아가기 때문에 별다른 에러 메세지를 알리지 않는다. 따라서 개발자는 프로그램 전반의 코드와 알고리즘을 체크해야 한다.
자바 프로그래밍에는 실행시(Runtime)발생할 수 있는 오류를 '에러(Error)'와 '예외(Exception)' 두가지로 구분한다.
프로그램 코드에 의해서 수습될 수 없는 심각한 오류
에러는 컴파일시 문법적인 오류와 런타임시 Null Point 참조와 같은 오류로 프로세스에 심각한 문제를 야기시켜 프로세스를 종료시킬 수 있다.
Error는 메모리 부족(Out of Memory Error) 이나 스택 오버플로우 (Stack Over flow Error)와 같이 발생하면 복구할 수 없는 심각한 오류이며, 예측 불가능하다. 즉, 에러는 JVM 실행에 문제가 생긴 것으로 개발자가 대처할 방법이 없다.
JVM에 설정된 메모리의 한계를 벗어난 상황일 때 발생. Heap 사이즈가 부족하거나, 너무 많은 class를 로드할때, 가용 가능한 swap이 없을때, 큰 메모리의 native메소드가 호출될 때 등이 있다. 이를 해결하기위해 dump 파일분석, jvm 옵션 수정 등이 있습니다.
Stack 영역의 메모리가 지정된 범위를 넘어갈 때 발생한다. Stack 메모리는 보통 지역 변수가 저장되는 영역이다. 함수에서 지역 변수를 선언하면 지역 변수는 Stack 메모리에 할당되고, 함수를 빠져나오면 Stack에서 메모리가 해제된다.
만약 한 함수에서 너무 큰 지역 변수를 선언하거나 함수를 재귀적으로 무한정 호출하게 되면 stack overflow가 발생할 수 있다.
Stack Overflow가 발생하면 컴파일러 옵션에서 stack 영역의 크기를 늘리거나 또는 함수에서 사용하는 지역 변수의 크기를 줄이거나 아니면 지역 변수를 전역 변수로 바꾸어 해결할 수 있다.
프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
알고리즘 오류로 Exception 예외가 계속 발생한다 해도 Error처럼 프로그램이 죽거나 그럴 경우는 적다.
대부분의 예외(Exception)는 개발자가 구현한 로직에서 발생한 실수나 사용자의 영향에 의해 발생한다. 그래서 예외는 에러와 달리 문제가 발생해도 이에 대한 대응 코드를 미리 작성해놓으면 어느정도 프로그램의 비정상적인 종료 또는 동작을 방지할 수 있다.
Java의 경우 예외처리 문법 (Try-Catch)이 있다.
따라서 개발자는 예외처리(Exception Handling)을 통해 예외 상황을 처리하여 프로그램이 종료되지 않도록 할 필요가 있다.
프로그램 실행 시 발생할 수 있는 예외의 발생에 대한 코드를 작성하는 행위를 말한다. 프로그램 실행 중 발생하는 에러는 어쩔 수 없지만, 예외의 경우 개발자의 실력에 따라 충분히 포괄적으로 방지가 가능하다. 예외처리의 목적은 예외의 발생으로 인한 실행중인 프로그램의 비정상적인 종료를 막고, 정상적인 실행상태를 유지하는데 의미가 있다.
자바에서는 오류를 Error와 Exception으로 나눴고, 이들을 클래스로 구현하여 처리한다.
IllegalArgumentException을 비롯해 NullPointerException과 IOException도 모두 클래스이다.
JVM은 프로그램을 실행하는 도중 예외가 발생하면, 해당 예외 클래스로 객체를 생성하고 예외 처리 코드에서 예외 객체를 이용할 수 있도록 한다.
Error와 Exception 모두 자바의 최상위 클래스인 Object 클래스를 상속받는다. 그리고 그 사이에 Throwable 클래스와 상속 관계가 있으며, Throwable 클래스의 역할은 오류나 예외에 대한 메세지를 담는 것이다.
대표적으로 getMessage()와 printStackTrace()메서드가 이 클래스에 속해있다. Error와 Exception 클래스에서도 위 두 메서드를 마찬가지로 사용 가능하다.
Error 클래스의 경우 외부적인 요인으로 인해 발생하는 오류이기 때문에 대처가 불가능하다. 따라서 눈여겨 볼 클래스는 Exception 클래스이다.
자바에서 다루는 모든 예외 오류는 Exception 클래스에서 처리한다. 그리고 아래의 Exception 클래스 트리 구조를 보았을 때 파란색은 컴파일 에러, 붉은색은 런타임에러를 보여준다.
사용자의 실수와 같은 외적인 요인에 의해 발생되는 컴파일할 때 발생하는 예외
Checked Exception으로 예외처리가 필수며, 처리하지 않을 경우 컴파일이 실행되지 않는다. JVM 외부와 통신(네트워크, 파일 시스템 등)할 때 주로 쓰인다. RuntimeException 이외의 모든 예외가 이곳에 해당한다.
푸른 색인 Checked Exception은 컴파일 예외 클래스들을 카리키는 것이며, 붉은 색인 Unchecked Exception은 런타임 예외 클래스들을 가리킨다.
Checked / Unchecked Exception으로 재분류한 이유는 코드적 관점에서 예외 처리 동작을 필수 지정 유무에 따라 나뉘기 때문
명시적 예외 처리가 필요한 경우(Checked Exception)의 경우 체크하는 시점이 컴파일 하는 단계이기 때문에,별도로 예외처리를 하지 않으면 컴파일 자체가 안된다.
따라서 Checked Exception이 발생 가능한 메서드면 반드시 로직을 Try-Catch 구문으로 감싸거나 throws로 던져서 처리해야 한다.
반면 명시적 예외 처리가 불필요한 경우(Unchecked Exception)의 경우 예외 처리를 하지 않아도 된다. 개발자의 충분한 주의로 미리 피할 수 있는 경우가 대부분이여서 자바 컴파일러는 별도의 예외처리를 하지 않도록 설계되어 있기 때문이다. 따라서 Try-catch 구문으로 감싸지 않아도 컴파일도 되고 실행이 가능하다.
예시
public static void install () {
throw new RuntimeException(new IOException("설치 불가능"));
}
이처럼 try-catch 구문이 필수인 Checked 예외인 IOException을 Unchecked 예외인 RuntimeException으로 감싸 컴파일 단계에서 예외처리를 강제하는 것을 피할 수 있다.
예외 처리 회피와 비슷하게 메서드 밖으로 예외를 던지지만, 무작정 던지는 것이 아니라 적절한 예외로 필터링 해서 던지는 방법
조금 더 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경하여 throws하는 것이라 볼 수 있다.
예외처리를 상위 클래스로 단순하게 포장(wrap)하는 방법도 일컫는다.
예외를 아무 로직 없이 catch만 하는 것은 바람직하지 x
catch에 단순히 throw만 하는 것도 바람직하지 x
Exception의 추적성과 유지보수성을 높이기 위햐 e.toString() 또는 e.getMessage()로 마지막 예외 메세지만 남기는 것 보다, 전체 Exception Stack을 다 넘기는 편이 좋다.
대표적인 slf4j 라이브러리의 log.error() 역시 e.printStackTrace() 처럼 Exception의 stack도 남긴다.
e.printStackTrace() 대신 LoggingFramework(slf4j, commons logging, log4j, logback)를 활용할 것
Logging Framework를 이용하면 로그 파일을 쪼갤 수 있으며, 여러 서버의 log를 한곳에 모아서 보는 System도 활용이 가능하다.
로직 중에 예외가 발생할지 모르는 부분에 try-catch 구문으로 감싼다.
try에는 위험한 로직이 포함되며, catch에는 예외가 발생할 경우 수행될 로직이 들어간다.
try중이라도 예외가 발생할 경우 다음의 코드들은 실행되지 않으며, catch 구문으로 넘어간다.
catch 구문은 else-if 처럼 여러개 쓸 수 있다.
finally는 가장 마지막에 실행하고 싶은 로직이 들어가며, 대표적으로 .close()와 같은 것이 있다.
예외 처리를 현재의 메서드가 직접 처리하지 않고, 호출한 곳에다 예외의 발생 여부를 통보한다. 호출한 메서드는 이걸 또 Throw 할 것인지 직접 처리할 건지 정해야 한다. (return보다 강력함)
예외는 개발자가 작성한 로직에 의해 발생된다. 추가적으로 예외에 대한 책임을 전가할 수도 있으며, throw와 throws이다.
throw는 예외를 강제로 발생시킨 후, 상위 블럭이나 catch문으로 예외를 던진다.
main에서 exceptionTest 메서드를 호출하고, 여기서 throw를 통해 Exception을 강제로 발생하고 있다.
이 때문에 catch 블럭으로 처리가 위임되며, 여기서 예외를 처리하고 있다.
throws는 예외가 발생하면 상위 메서드로 예외를 던진다.
일반적으로 throws를 사용하면 try, catch 구문이 생성되지 않는데, 이는 throws 구문에 의해 예외 처리를 호출부로 위임하기 때문이다.
즉 throw를 통해 예외를 발생시키고 throws는 이 예외를 밖으로 던지고 있다.
둘을 함께 사용하면 예외처리를 catch문에서도 하고, 호출부로 예외를 던진다.
throw e를 통해 예외를 발생시키면 throws에 의해 상위 메서드로 예외를 던진다. 예외처리를 예외 발생지와 호출부에서 하고 싶으면 이 방법을 쓸 수 있다.