# 프로그래밍의 오류
- 프로그램에서 오류가 발생하면 시스템 레벨에서 프로그램에 문제를 야기하여 원치 않는 버그를 일으키거나, 심각하면 실행 중인 프로그램을 강제로 종료시키도 한다.
- 프로그래밍의 오류는 다양하지만, 크게 3가지로 나뉜다.
- 컴파일 에러(compile-time error) : 컴파일시에 발생하는 에러
- 런타임 에러(runtime error) : 실행시에 발생하는 에러
- 논리적 에러(logical error) : 실행은 되지만 의도와 다르게 동작하는것
## 논리 에러 (Logic Error)
- **버그**
- 작동하는데는 아무런 문제가 없는 오류이지만, 결과가 예상과 달라 사용자가 의도한 작업을 수행하지 못하게 되어 서비스 이용에 지장이 생길 수 있는 에러
- 논리적 오류는 컴퓨터 입장에서는 프로그램이 멀쩡히 돌아가는 것이니 에러 메시지를 알려주지 않는다.
- 따라서 개발자는 프로그램의 전반적인 코드와 알고리즘을 체크 필요가 있다
## 컴파일 에러 (Compile Error)
- 컴파일 단계에서 오류 발견하면 컴파일러가 에러 메시지 출력해주는 것을 의미
- 컴파일 에러가 있다는 것은, 곧 컴파일이 안된다는 의미이며, 이는 즉 프로그램이 만들어지지 않아 프로그램 실행 자체가 불가하다
- 사전에 문제를 파악할 수 있기 대문에 심각한 오류는 아니다
## 런타임 에러 (Runtime Error)
- 프로그램 실행 중에 에러가 발생해서 잘못된 결과를 얻거나, 혹은 외부적인 요인으로 기계적 결함으로 프로그램이 비정상적으로 종료되는 에러
- 자바에서는 런타임 에러를 두가지로 구분하였다
- 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
- 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
## 자바의 오류

- `throwable` 클래스를 상속받는 모든 클래스는 자바의 오류를 의미한다
- 자바는 오류를 크게 2가지로 구분한다
- 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
- 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
## 자바의 예외 클래스 계층 구조

JVM은 프로그램을 실행하는 도중에 오류가 발생하면 해당 오류에 맞는 클래스를 인스턴스화 하여 프로그램에게 전달한다. 이때 전달되는 클래스는 `Error` 클래스와 `Exception` 클래스이다.
### 에러(Error)
- 프로그램 코드에 의해서 **수습될 수 없는 심각한 오류**
- 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고 예측이 불가능한 상황에 발생
- `Error` 클래스는 외부적인 요인으로 인해 발생하는 오류이기 때문에 개발자가 대처 할 수는 없다.
### 예외(exception)
- 프로그램 코드에 의해서 **수습될 수 있는 다소 미약한 오류**
- 수습될 수 있는 비교적 덜 심각한 오류
- 대부분 개발자가 구현한 로직에서 발생한 실수나 사용자의 영향에 의해 발생
- 대응 코드를 미리 작성해 놓음으로써 어느정도 프로그램의 비정상적인 종료 혹은 동작을 막을 수 있다
- 자바의 예외 처리 문법(try - catch)
자바의 예외는 크게 두 가지로 나뉜다
- 체크 예외(Checked Exception)
- 런타임 예외(Runtime Exception)

### 체크 예외(Checked Exception)
- 컴파일러가 **예외 처리를 강제하는 예외**
- 컴파일러가 예외처리를 제대로 하는지 확인해준다 (하지 않을시 컴파일 오류를 발생시키며 컴파일이 되지 않는다)
- 강제적으로 예외 처리를 하도록 함으로써, 프로그램의 안정성을 높이고, 예외에 대한 적절한 처리를 유도한다
- 체크 예외는 어느 메서드가 어떤 예외를 던지는지 명확히 알 수 있다
- 주로 외부 리소스와 관련된 작업에서 발생하며, 개발자가 예외 처리를 통해 오류 상황에 대처할 수 있도록 설계되어 있다
- 예) 파일 입출력, 데이터베이스 연결 등의 경우
### 런타임 예외(Runtime Exception) / Unchecked Exception
- `RuntimeException`을 상속받는 예외들
- 컴파일러가 **예외 처리를 강제하지 않는 예외**
- 컴파일러가 따로 검사를 해주지 않는다
- 어디서 어떤 예외가 나오는지 한눈에 보이지 않는다
- 주로 프로그래머의 실수나 논리적인 오류로 인해 발생
- 예) 배열의 범위를 벗어난 인덱스에 접근하거나, `null` 참조를 사용하는 등의 경우
> **다양한 런타임 예외**
>
> 
### 체크 예외와 런타임 예외의 차이 비교
||Checked Exception|Unchecked Exception|
|---|---|---|
|처리 여부|반드시 예외를 처리해야 함|명시적인 처리를 안해도 됨|
|확인 시점|컴파일 단계|런타임 단계|
|예외 종류|RuntimeException을 제외한, Exception 클래스와 그를 상속받는 하위 예외|RuntimeException 과 그 하위 예외|
## 자바의 예외 처리 (Exception Handling)
- 예외 처리란 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것을 의미한다
- `Error`와 달리 `Exception`은 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류이다. 따라서 예외가 발생하더라도 프로그램을 종료시키지 않고, 예외에 대한 적절한 처리를 통해 프로그램을 계속 진행시킬 수 있다.
- 예외 처리의 목적은 예외의 발생으로 인한 실행중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것이다
- 자바는 예외 처리를 위해 `try-catch`문을 제공한다
### try - catch - finally 문

예외가 발생했을 때 진행 순서
1. `try` 블록의 실행이 중단됨
2. `catch` 블록 중에 발생한 예외를 처리할 수 있는지 찾음
- 위에서부터 하나씩 평가
3. 예외를 처리할 수 있는 `catch` 블록이 있다면 해당 `catch` 블록안의 코드들이 실행 후 `finally` 블록 실행. `try`블록 이후의 코드들이 실행됨
4. 예외를 처할 수 있는 `catch` 블록이 없다면 `finally`블록을 실행 후 한 단계 높은 `try` 블록으로 전달
#### `try` 문
- 예외가 발생할 수 있는 코드 블록을 감싸는 데 사용
- `try`블록 내부의 코드가 실행되다가 예외가 발생하면, 해당 예외와 일치하는 `catch` 블록으로 제어가 이동한다
#### `catch` 문
- 예외가 발생했을 때 처리할 코드 블록을 정의하는 데 사용
- `catch` 블록은 예외 유형에 따라 여러 개를 사용할 수 있다
- 각 `catch` 블록은 특정 예외 유형을 처리하도록 정의되며, 발생한 예외와 일치하는 `catch` 블록이 실행된다
``` java
try {
// 예외가 발생할 수 있는 코드
} catch (FileNotFoundException e) {
// FileNotFoundException 예외 처리
} catch (IOException e) {
// IOException 예외 처리
}
```
``` java
int a, b, c;
try {
a = 10;
b = 0;
c = a / b; // 10 나누기 0 → 산술오류 ArithmeticException
} catch (ArithmeticException e) {
c = -1; // 예외가 발생하여 이 문장이 수행된다.
}
```
#### `catch` 사용 시 알아두어야 할 점
- 예외 유형은 중복되어 사용할 수 없다
- 예외 유형과 일치하는 예외가 발생하지 않으면, `catch` 블록은 실행되지 않는다
- `catch` 블록으로 들어온 경우 다른 `catch` 블록은 실행되지 않는다
- 여러 예외 유형을 `|` 기호를 통해 하나의 `catch` 블록으로 합칠 수 있다
- 예외 유형은 상속 관계에 있는 클래스들끼리도 `catch` 블록에서 함께 처리할 수 있다
- `catch` 블록의 예외 유형이 `IOException`라면, `IOException`의 하위 클래스인 `FileNotFoundException`도 처리할 수 있다
- `catch` 문 내에서도 예외가 발생할 수 있다는 점을 인지하고 코드를 작성해야 한다
- `catch` 블록 내부에서 예외가 발생하면, 해당 예외는 `try` 블록 밖으로 전달된다
```java
try {
// 예외가 발생할 수 있는 코드
} catch (FileNotFoundException e) {
// FileNotFoundException 예외 처리
} catch (IOException e) {
// IOException 예외 처리
} catch (NullPointException | ArrayIndexOutOfBoundsExcetion e) {
// NullPointException, ArrayIndexOutOfBoundsExcetion 예외 처리
} catch (Exception e) {
// Exception 예외 처리 (위에서 처리하지 못한 예외를 처리한다)
}
```
#### `finally` 문

- 예외가 발생하던, 발생하지 않던 무조건 실행되는 블럭
- 어떤 예외가 발생하더라도 반드시 실행되어야 할 때 사용
``` java
int[] numbers = {10, 5, 0};
try {
int index = 3;
int divisor = 0;
int result = numbers[index] / divisor;
System.out.println("Result: " + result);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: 배열 인덱스가 범위를 벗어났습니다");
} catch (ArithmeticException e) {
System.out.println("Error: 0으로 나눌 수 없습니다");
} finally {
System.out.println("이 메시지는 예외에 관계없이 출력됩니다");
}
```
## 예외 던지기
- 예외 처리를 메소드 내부에서 직접 수행하지 않고, 메소드를 호출한 상위 호출자가 적절하게 처리할 수 있다. 이를 예외 던지기라고 부른다
- 예외 던지기 : 메소드 내에서 발생한 예외를 해당 메소드를 호출한 곳으로 전달하여 처리할 수 있도록 하는 기능
- `throw` 키워드를 사용하여 강제로 예외를 발생시킬 수 있다
- 이 때 `new` 생성자로 예외 클래스를 초기화하여 던져는데, 이 클래스 생성자에 입력값을 주게되면, `catch`문의 `getMessage()`메서드에서 출력할 메시지를 지정하게 된다.
- `throws` 키워드를 사용하여 상위 클래스에서 예외처리를 강제하도록 할 수 있다.
### `throw`
- 예외를 발생시키기 위해 사용
- 메소드 내부에서 사용되며, 실제로 예외를 발생시키는 데 사용된다.
- 생성된 예외 객체를 던져서 예외 처리 흐름을 시작한다.
#### `throw` 사용 예
```java
throw new 예외클래스("예외메시지");
```
``` java
public static void main(String[] args) {
try {
methodA();
} catch (RuntimeException e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
public static void methodA() {
System.out.println("methodA() is called.");
throw new RuntimeException("An error occurred in methodA.");
}
```
### `throws`
- 예외를 떠넘기기 위해 사용
- 메소드 선언부에 사용되며, 해당 메소드가 발생시킬 수 있는 예외를 명시한다.
- 메소드를 호출하는 쪽에서 이 메소드가 발생시킬 수 있는 예외에 대한 처리> 를 대비하도록 한다
#### `throws` 사용 예
``` java
메서드 선언부 throws 예외클래스 { ... }
```
``` java
public static void main(String[] args) {
try {
methodA();
} catch (IOException e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
public static void methodA() throws IOException { // IOException 예외는 checked 예외이므로, 메서드 선언부에 throws IOException을 명시해야 한다.
System.out.println("methodA() is called.");
throw new IOException("An error occurred in methodA.");
}
```
## 연결된 예외 (Chained Exception)
- 자바에서 예외 처리를 할 때, 하나의 예외가 다른 예외를 유발하거나 함께 처리해야 하는 상황을 다루기 위한 개념
- 발생한 예외를 다른 예외와 연결하여, 하나의 예외 처리 블록에서 여러 예외를 처리하는 것
- 연결된 예외를 통해 원래 발생한 예외의 원인을 추적하고, 예외 처리를 더 명확하고 효과적으로 할 수 있다
### 연결된 예외 사용 방법
- `Throwable` 클래스의 `initCause(Throwable cause)` 메소드를 사용하여 예외 객체에 원인 예외를 설정한다.
- `Throwable` 클래스의 `getCause()` 메소드를 사용하여 원인 예외를 가져온다.
``` java
try {
// 예외를 발생시키는 코드
} catch (IOException e) {
// IOException을 처리하면서 다른 예외가 발생하는 상황을 가정한다.
RuntimeException runtimeException = new RuntimeException("An error occurred while handling IOException");
runtimeException.initCause(e);
throw runtimeException;
}
```
> 연결된 예외(chained exception)를 사용하는 또 다른 이유는 checked예외를 unchecked예외로 바꿀 수 있도록 하기 위함이다.
## 커스텀 예외
- 자바에서 제공하는 예외 클래스 외에도, 개발자가 직접 만들어서 사용할 수 있는 예외 클래스를 만들 수 있다
- 자바의 예외는 결국 클래스다. 따라서 예외 클래스를 상속받아서 확장시킬 수 있다
- 커스텀 예외 클래스는 `Exception`을 상속받는 클래스를 상속받아 만들 수 있다
``` java
public final class UserNotFoundException extends RuntimeException {
public UserNotFoundException() {
super();
}
public UserNotFoundException(String message) {
super(message);
}
public UserNOtFolundException(String message, Throwable cause) {
super(message, cause);
}
}
```
``` java
public class CustomExceptionExample {
public static void main(String[] args) {
try {
validateUserInput("Invalid input");
} catch (UserNotFoundException e) {
System.out.println("Caught custom exception: " + e.getMessage());
}
}
public static void validateUserInput(String input) throws UserNotFoundException {
if ("Invalid input".equals(input)) {
throw new UserNotFoundException("The provided input is invalid.");
}
}
}
```
## Exception Handling
- 예외 처리는 예외가 발생했을 때 프로그램이 비정상적으로 종료되는 것을 막고, 정상적인 실행 상태를 유지할 수 있도록 하는 것을 의미한다
- 예외 처리는 크게 다음과 같이 구분할 수 있다
- 예외 복구
- 예외 처리 회피
- 예외 전환
### 예외 복구
- `try-catch`
- 예외가 발생했을 때 프로그램이 정상적인 상태로 돌아올 수 있도록 예외를 처리하는 방법
- 예외 상황을 해결하고 프로그램이 계속 실행될 수 있도록 코드를 작성하는 것이 포함된다
- 예외 복구는 대개 재시도 로직을 구현하거나, 대체 데이터를 사용하여 프로그램이 계속 실행될 수 있는 상태를 만드는 데 사용된다
### 예외 처리 회피
- `throws`
- 현재 발생한 예외를 처리하지 않고, 호출한 메소드나 상위 계층에 예외 처리를 위임하는 방법
- 예외를 처리하는 데 필요한 지식이나 자원이 현재 메소드에 없을 때 사용할 수 있는 방법
- 남용하면 예외 처리가 전체 시스템에서 적절히 이루어지지 않을 수 있으므로 주의가 필요하다
### 예외 전환
- `Chained Exception`
- 발생한 예외를 새로운 예외로 변경하여 처리하는 방법으로 크게 두 가지 상황에서 사용된다
1. 발생한 예외를 더 구체적인 예외로 변경하여, 호출자에게 더 유용한 정보를 제공하고자 할 때 사용
2. 발생한 예외가 시스템 계층간의 의존성을 높이게 되는 경우, 의존성을 낮추기 위해 새로운 예외로 전환한다. 이를 통해 하위 계층에서 발생한 예외가 상위 계층에 영향을 주지 않도록 할 수 있다.
- 예외 전환을 사용할 때는 원래 발생한 예외를 새로운 예외의 원인(Cause)로 설정하여, 추후 디버깅이나 로그 분석 시에 원래 예외 정보를 확인할 수 있도록 해야 한다.