1. 프로그래밍 오류 종류
프로그램에서 오류가 발생하면 시스템 레벨에서 프로그램에 문제를 야기하여 원치 않는 버그를 일으키거나, 심각하면 실행 중인 프로그램을 강제로 종료시키도 한다.
프로그램 오류의 원인으로는 정말 다양한 상황이 있을 수 있다. 내부적인 요인으로는 프로그램 설계 로직에 구멍이 있어서 그럴 수도 있고, 외부적인 요인으로는 프로그램 자체 문제가 아닌 하드웨어에서 문제가 생겨 프로그램에 오류가 발생할 수 도 있다.
프로그래밍에서는 이러한 오류를 발생 시점에 따라 다음과 같이 3가지로 나눈다.
1. 컴파일 에러(compile-time error) : 컴파일시에 발생하는 에러
2. 논리적 에러(logical error) : 실행은 되지만 의도와 다르게 동작하는 것
3. 런타임 에러(runtime error) : 실행 시에 발생하는 에러
컴파일 에러 (Compillation Error)
컴파일 에러는 컴파일 단계에서 오류가 발견되면 컴파일러가 에러 메시지 출력해 주는 것을 말한다.
컴파일 에러 발생의 대표적인 원인으로 문법 구문 오류(syntax error)를 들 수 있다.
예를 들어 코딩을 할 때 맞춤법, 문장부호(;), 선언되지 않은 변수 사용을 하면 아래와 같이 빨간 줄로 잘못되었다고 컴파일 에러를 일으킨다.
참고로 컴파일 에러는 소스 코드를 javac.exe로 컴파일하는 과정에서 컴파일러가 전반적인 코드를 체크해서 에러 메시지를 보여주는 형태이지만, IDE에서는 일정 주기로 계속 자동으로 컴파일을 해주기 때문에 바로바로 문제를 알 수 있는 것이다.
하지만 컴파일 에러는 그렇게 심각하게 볼 오류 종류는 아닌데 컴파일 에러가 있다는 것은, 곧 컴파일이 안된다는 의미이며, 이는 프로그램이 만들어지지 않아 프로그램 실행 자체가 불가하기 때문이다. 따라서 개발자는 차후에 일어날 에러를 컴파일러가 미리 알려준다고 생각하며 코드를 수정하면 될 것이다.
논리 에러 (Logic Error)
논리적 에러는 이른바 '버그'라고 생각하면 된다. 프로그램이 실행하고 작동하는 데는 아무런 문제가 없는 오류이지만, 결과가 예상과 달라 사용자가 의도한 작업을 수행하지 못하게 되어 서비스 이용에 지장이 생길 수 있다.
예를 들어 재고량이 음수가 나오면 안 되는데 음수가 나와버리는 경우, 게임 캐릭터가 피가 0이어도 죽지 않는 경우를 들 수 있다. 논리적 오류는 컴퓨터 입장에서는 프로그램이 멀쩡히 돌아가는 것이니 에러 메시지를 알려주지 않는다. 따라서 개발자는 프로그램의 전반적인 코드와 알고리즘을 체크 필요가 있다.
런타임 에러 (Runtime Error)
컴파일 에러를 꼼꼼하게 잡아 컴파일에는 문제가 없더라도, 프로그램 실행 중에 에러가 발생해서 잘못된 결과를 얻거나, 혹은 외부적인 요인으로 기계적 결함으로 프로그램이 비정상적으로 종료될 수 있다. 이것이 우리가 집중적으로 파헤쳐 봐야 할 실행 오류(런타임 에러)이다. 대체로 개발 시 설계 미숙(논리적)으로 발생하는 에러가 대부분이며, 런타임 에러 발생 시 프로그래머가 역추적해서 원인 확인해야 한다. 따라서 이러한 잠재적인 런타임 에러를 방지하기 위해서는 프로그램의 실행 도중 발생할 수 있는 경우의 수를 고려하여 이에 대한 대비를 철저히 해야 한다.
오류(error)와 예외(exception)
자바 프로그래밍에서는 실행 시(runtime) 발생할 수 있는 오류를 '에러(error)'와 '예외(exception)' 두 가지로 구분하였다.
1) 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
2) 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
에러는 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고 예측이 불가능하다. 즉, 에러는 JVM 실행에 문제가 생긴 것이므로 개발자가 대처할 방법이 없다.
반면 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 오류를 말한다. 즉, 알고리즘 오류로 예외가 계속 발생한다고 해도 에러처럼 프로그램이 종료될 경우는 적기 때문이다. 그렇다고 예외(Exception)가 단어의 어감처럼 가볍게 볼 녀석은 아니다. 예외에 대한 오류 처리를 제대로 하지 않으면 전혀 예상하지 못한 오류 발생으로 프로그램에 작지 않은 문제를 야기하기 때문이다. 대부분의 예외(Exception)는 개발자가 구현한 로직에서 발생한 실수나 사용자의 영향에 의해 발생한다.
그래서 예외는 에러와 달리 문제가 발생하더라도 이에 대한 대응 코드를 미리 작성해 놓음으로써 어느 정도 프로그램의 비정상적인 종료 혹은 동작을 막을 수 있다. 이 예외에 대한 대응 코드가 자바의 예외 처리 문법(try - catch)이다. 따라서 개발자는 예외 처리를 통해 언제나 예외 상황을 처리하여 프로그램이 종료되는 일이 없도록 코드의 흐름을 바꿀 필요가 있다.
cf.) 예외 처리(exception handling)
예외 처리란 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 행위를 말한다.
프로그램 실행도중에 발생하는 에러는 어쩔 수 없지만, 예외는 프로그래머의 오류 예상에 따라 충분히 포괄적으로 방지할 수 있기 때문이다.
따라서 예외 처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스러운 비정상 종료를 막고 정상적인 실행상태를 유지하는 것이다.
2. 자바의 예외(Exception) 클래스
2-1. 예외 클래스의 계층 구조
자바에서는 오류를 에러(Error)와 예외(Exception)로 나누었고 이들을 클래스로 구현하여 처리하도록 하였다.
우리에게 익숙한 IllegalArgumentException을 비롯해 NullPointerException과 IOException도 모두 클래스이다.
JVM은 프로그램을 실행하는 도중에 예외가 발생하면 해당 예외 클래스로 객체를 생성하고서 예외 처리 코드에서 예외 객체를 이용할 수 있도록 해준다. getMessage 나 printStackTrace 메서드 역시 예외 객체의 메서드를 가져와 오류를 출력하는 것이다.
자바의 오류 클래스 계층 구조를 살펴보면 다음과 같이 구성되어 있다.
Error 클래스는 위에서 언급한 바와 같이 외부적인 요인으로 인해 발생하는 오류이기 때문에 개발자가 대처할 수는 없다. 따라서 우리가 중점적으로 봐야 할 클래스는 바로 Exception 클래스이다.
위의 예외 클래스 계층 구조는 복잡하게 보이지만 다음과 같이 간단하게 설명할 수 있다.
자바에서 다루는 모든 예외 오류는 Exception 클래스에서 처리한다. 그리고 위의 Exception 클래스 트리 구조를 보면 Unchecked와 Checked로 구분됨을 볼 수 있는데, 이것이 컴파일 에러와 런타임 에러를 따로 클래스로 구분했기 때문이다.
즉, Exception 클래스는 다시 RuntimeException(런타임 에러를 다룸)과 그 외의 자식 클래스 그룹(위 그림에서 Other Exception, 컴파일 에러를 다룸)으로 나뉘게 된다.
Exception 및 하위 클래스 : 사용자의 실수와 같은 외적인 요인에 의해 발생하는 컴파일 시 발생하는 예외
- 존재하지 않는 파일의 이름을 입력 (FileNotFoundException)
- 실수로 클래스의 이름을 잘못 기재 (ClassNotFoundException)
- 입력한 데이터 형식이 잘못된 경우 (DataFormatException)
RuntimeException 클래스 : 프로그래머의 실수로 발생하는 예외
- 배열의 범위를 벗어남 (IndexOutOfBoundsException)
- 값이 null인 참조 변수의 멤버를 호출 (NullPointerException)
- 클래스 간의 형 변환을 잘못함 (ClassCastException)
- 정수를 0으로 나누는 산술 오류 (ArithmeticException)
참고로 Throwable 클래스는 다음과 같이 설명할 수 있다.
오류와 예외 모두 자바의 최상위 클래스인 Object를 상속받는다. 그리고 그 사이에는 Throwable 클래스와 상속관계가 있는데, Throwable 클래스의 역할은 오류나 예외에 대한 메시지를 담는 것이다. 대표적으로 getMessage와 printStackTrace 메서드가 바로 이 클래스에 속해 있다. 당연히 Throwable을 상속받은 Error와 Exception 클래스에서도 위 두 메서드를 사용할 수 있게 된다.
2-2. 런타임 예외 클래스 종류
위 그림과 같이 파란 부분으로 표시된 실행(runtime) 예외 클래스에 대해 다음과 같이 간단히 설명한다.
Exception | 설명 |
ArrayIndexOutOfBoundsException | 배열의 범위를 넘어선 인덱스를 참조할 때 발생하는 에러 |
ArithmeticException | 정수를 0으로 나눌 때 발생하는 에러 |
NullPointException | null 객체에 접근해서 method를 호출하는 경우 발생하는 에러 |
NumberFormatException | - 정수가 아닌 문자열을 정수로 변환할 때 예외 발생 - 숫자타입으로 변경할 수 없는 문자를 치환시키려고 하면 발생하는 대표적인 에러 |
ClassCastException | - 타입 변환은 상위 클래스와 하위 클래스간의 상속 관계거나 구현 클래스와 인터페이스간 일 때만 가능 - 상속, 구현 관계 아니면 클래스는 다른 클래스로 타입을 변환할 수 없는데, 이 규칙을 무시하고 억지로 타입을 변환시킬경우 발생하는 에러 |
InputMismatchException | 의도치 않는 입력 오류 시 발생하는 예외 |
2-3. 컴파일 예외 클래스 종류
위 그림과 같이 초록 부분으로 표시된 예외 클래스에 대해 다음과 같이 간단히 설명한다.
Exception | 설명 |
IOException | - 컴퓨터 프로그램이 실행될 때 언제 어떤 문제가 발생할지 모르는 일이기 때문에, 컴퓨터와 상호소통 하는 I/O(입력과 출력)에 관해서는 발생할 수 있는 예외에 대해서 까다롭게 규정하고 있음 - 그래서 입력과 출력을 다루는 메서드에 예외처리(IOException)가 없다면 컴파일 에러가 발생하게 됨 |
FileNotFoundException | 파일에 접근하려고 하는데 파일을 찾지 못했을 때 발생하는 에러 |
3. Checked Exception / Unchecked Exception
자바의 예외(Exception)는 컴파일 에러와 런타임 에러로 구분된다는 것을 위 내용을 통해 알 수 있었다.
그런데 또다시 예외 종류로서 Checked Exception과 Unchecked Exception으로 나뉜다.
Checked Exception은 컴파일 예외클래스들을 가리키는 것이고, Unchecked Exception은 런타임 예외클래스들을 가리키는 것으로 보면 된다.
또다시 Checked / Unchecked Exception으로 재분류 한 이유는 코드적 관점에서 예외 처리 동작을 필수 지정 유무에 따라 나뉘기 때문이다.
구분 | Checked Exception | Unchecked Exception |
처리 여부 | 반드시 예외를 처리해야 함 | 명시적인 처리를 안해도 됨 |
확인 시점 | 컴파일 단계 | 런타임 단계 |
예외 종류 | RuntimeException을 제외한, Exception 클래스와 그를 상속받는 하위 예외 - IOException - FileNotFoundException - SQLException |
RuntimeException 과 그 하위 예외 - NullPointerException - IllegalArgumentException - IndexOutOfBoundException - SystemException |
3-1. 코드에서 명시적 예외 처리 유무
Checked / Unchecked Exception의 가장 핵심적인 차이는 '반드시 예외 처리를 해야 하는가?'이다.
Checked Exception은 체크하는 시점이 컴파일 단계이기 때문에, 별도의 예외 처리를 하지 않는다면 컴파일 자체가 되지 않는다. 따라서 Checked Exception이 발생할 가능성이 있는 메서드라면 반드시 로직을 try - catch로 감싸거나 throws로 던져서 처리해야 한다.
반면에 Unchecked Exception의 경우는 명시적인 예외 처리를 하지 않아도 된다. Unchecked Exception도 예외이긴 하지만, 개발자의 충분한 주의로 미리 회피할 수 있는 경우가 대부분이라 그나마 상대적으로 미약한 예외로 처리되어 자바 컴파일러는 별도의 예외 처리를 하지 않도록 설계되어 있기 때문이다. 따라서 에러를 일부러 일으키는 코드가 있더라도 try - catch 처리하지 않더라도 컴파일도 되고 실행까지 가능하다.
3-2. Checked를 Unchecked 예외로 변환하기
앞서 checked exception은 반드시 try - catch 문으로 감싸야 된다고 했었다. 하지만 코드마다 일일이 예외처리하는 것도 귀찮고 오히려 가독성을 해친다고 생각할 경우, 그냥 unchecked exception으로 변환시켜 컴파일에게 예외처리를 강제하지 않게 할 수 있다.
예를 들어 checked exception의 종류의 예외를 포함한 코드를 작성하면 컴파일러가 예외 처리(try - catch)를 강제한다. 가장 대표적인 예로 FileWriter 클래스를 이용해 파일을 불러오는 코드를 작성하면 반드시 try - catch로 감싸주어야 컴파일이 된다.
이런 식으로 설계한 이유는, 처음 자바 언어를 개발했을 때 프로그래밍 경험이 적은 사람도 보다 견고한 프로그램을 작성할 수 있도록 유도하기 위해서인데, 실제로 별것 아닌 예외도 checked exception으로 등록한 것이 꽤 많다.
자바 프로그래밍 언어가 처음 개발되던 1990년대와 지금의 컴퓨터 환경은 많이 달라졌기 때문에, 실제로 런타임 예외로 처리해도 될 것들이 아직도 checked exception으로 등록되어 강제적으로 try - catch 문을 사용해야 하는 불편함이 있고, 또한 로직상 Runtime Exception으로 할 수밖에 없는 경우가 있기 때문에, 추가된 기법이라고 생각하면 된다.
따라서 연결된 예외(chained exception)를 이용해, checked 예외를 unchecked 예외로 바꾸면 예외처리가 선택적이 되므로 억지로 거추장스러운 예외처리를 하지 않아도 된다.
class MyCheckedException extends Exception { ... } // checked excpetion
public class Main {
public static void main(String[] args) {
install();
}
public static void install() {
throw new RuntimeException(new IOException("설치할 공간이 부족합니다."));
// Checked 예외인 IOException을 Unchecked 예외인 RuntimeException으로 감싸 Unchecked 예외로 변신 시킨다
}
}
'DEV > JAVA' 카테고리의 다른 글
[JAVA] 올바르지 않은 난수 취약점(Math.random) 해결 방법 (0) | 2024.11.25 |
---|---|
[JAVA] 엑셀 파일 생성과 다운로드 예제 (feat. Apache Poi Excel Create) (0) | 2024.04.01 |
[JPA] JPA의 정의와 Spring Data JPA와의 차이점 (0) | 2024.03.22 |
[JAVA] try-with-resources 사용하기(try-catch-finally 와 비교) (2) | 2024.01.18 |
[JAVA] 두 날짜 사이 값 구하기 (2) | 2023.11.27 |
댓글