# try-catch-finally 문법

Dart 언어에서 예외 처리는 프로그램이 실행 중에 발생할 수 있는 오류나 예외 상황을 처리하기 위해 사용된다. 예외는 예상치 못한 상황을 처리할 수 있는 메커니즘을 제공한다. 예외가 발생했을 때, 프로그램이 바로 종료되는 대신 예외를 처리할 수 있는 코드를 작성함으로써 프로그램의 안정성을 높일 수 있다.

### try 블록

`try` 블록은 예외가 발생할 수 있는 코드를 감싸는 데 사용된다. `try` 블록 내에서 발생한 예외는 `catch` 또는 `finally` 블록에서 처리할 수 있다. `try` 블록이 완료되면, `catch` 또는 `finally` 블록이 실행된다.

```dart
try {
  // 예외가 발생할 가능성이 있는 코드
}
```

### catch 블록

`catch` 블록은 `try` 블록에서 발생한 예외를 처리한다. 예외가 발생하면, 프로그램의 흐름이 `try` 블록을 중단하고 즉시 `catch` 블록으로 이동한다. 예외 객체를 사용하여 어떤 종류의 예외가 발생했는지 확인할 수 있다. Dart에서는 `catch` 블록에 예외 객체와 스택 추적 정보를 받을 수 있다.

```dart
catch (e) {
  // 예외 처리 코드
}
```

또한, 예외 객체와 스택 추적 정보를 모두 받을 수도 있다.

```dart
catch (e, s) {
  // 예외 처리 코드
  print('예외: $e');
  print('스택 추적: $s');
}
```

### finally 블록

`finally` 블록은 예외가 발생하든 발생하지 않든 항상 실행되는 코드이다. 예외가 발생하더라도 반드시 실행되어야 할 코드가 있을 때 유용하다. 예를 들어, 파일을 열었다면, 파일을 닫는 작업을 반드시 해야 하는데, 이때 `finally` 블록에서 해당 작업을 처리할 수 있다.

```dart
finally {
  // 예외 발생 여부와 상관없이 항상 실행되는 코드
}
```

이러한 `finally` 블록은 자원 해제 작업을 포함한 후처리 작업에 주로 사용된다.

### 전체 예시

다음은 `try`, `catch`, `finally` 블록을 모두 사용하는 예시이다:

```dart
void main() {
  try {
    int result = 12 ~/ 0;
    print(result);
  } catch (e) {
    print('예외 발생: $e');
  } finally {
    print('이 코드는 항상 실행된다.');
  }
}
```

이 코드에서 `12 ~/ 0`은 정수 나눗셈이므로, 0으로 나누는 예외가 발생하여 `catch` 블록으로 이동한다. `catch` 블록에서 예외를 출력한 후 `finally` 블록이 실행된다.

#### 예외 흐름 다이어그램

`try-catch-finally`의 동작 흐름을 다이어그램으로 나타내면 다음과 같다:

{% @mermaid/diagram content="graph TD;
A\[try 블록 실행] --> B{예외 발생 여부};
B -- 예외 발생 --> C\[catch 블록 실행];
B -- 예외 없음 --> D\[finally 블록 실행];
C --> D;
D --> E\[프로그램 계속 실행];" %}

#### 여러 개의 `catch` 블록

Dart에서는 `catch` 블록을 여러 개 사용할 수 있으며, 각 블록은 다른 예외 유형을 처리하도록 지정할 수 있다. 이를 통해 더 세부적으로 예외를 처리할 수 있다. Dart의 모든 예외는 `Exception` 클래스 또는 그 하위 클래스의 인스턴스로 처리된다.

```dart
try {
  // 예외 발생 가능 코드
} on FormatException {
  // 특정 예외 유형 처리 (FormatException)
} catch (e) {
  // 다른 모든 예외 처리
}
```

위 코드는 `FormatException` 예외가 발생하면 해당 예외를 처리하는 `on` 블록이 실행되고, 그 외의 다른 예외는 `catch` 블록에서 처리된다.

`on` 키워드를 사용하면 예외 유형을 명확하게 지정할 수 있다. 반면, `catch`는 모든 예외를 처리하는데 적합한다.

#### `rethrow`를 통한 예외 재발생

때로는 `catch` 블록에서 예외를 처리한 후, 그 예외를 다시 던져 다른 상위 코드에서 추가로 처리해야 할 때가 있다. 이때 `rethrow` 키워드를 사용하여 예외를 재발생시킬 수 있다.

```dart
try {
  // 예외 발생 가능 코드
} catch (e) {
  print('예외 처리 중: $e');
  rethrow; // 예외를 다시 던짐
}
```

이 코드는 예외를 한 번 처리한 후, 그 예외를 상위 호출 스택으로 다시 던져 상위 코드에서 추가 처리가 가능하도록 한다.

#### 비동기 함수에서의 예외 처리

비동기 함수에서도 동일한 방식으로 예외 처리를 할 수 있지만, `Future`와 함께 사용될 경우에는 `await`와 `try-catch`를 결합해야 한다. 비동기 함수에서 예외가 발생할 경우, 해당 예외는 `Future` 객체에 전달되기 때문에 `try-catch` 블록에서 이를 처리해야 한다.

```dart
Future<void> asyncFunction() async {
  try {
    await someAsyncTask();
  } catch (e) {
    print('비동기 작업 중 예외 발생: $e');
  } finally {
    print('비동기 작업 종료');
  }
}
```

비동기 함수에서 발생한 예외도 일반적인 `catch` 블록에서 처리할 수 있으며, `finally` 블록은 여전히 작업 완료 후에 실행된다.

#### 예외 처리의 장점

예외 처리는 프로그램의 오류를 우아하게 처리할 수 있도록 하며, 프로그램이 예외로 인해 중단되지 않고 계속 실행되도록 도와준다. 예외 처리를 사용하지 않을 경우, 프로그램은 예기치 않은 오류로 인해 강제 종료될 수 있지만, `try-catch-finally` 구조를 사용하면 예외 상황을 처리하면서도 프로그램의 흐름을 제어할 수 있다.

다음으로 Dart의 예외 처리에서 유용하게 사용되는 사용자 정의 예외와 예외의 다양한 활용 방법에 대해 다룰 것이다.

#### 사용자 정의 예외

Dart에서는 기본적으로 제공되는 예외 외에도 개발자가 직접 예외를 정의할 수 있다. 이를 통해 특정 상황에서 발생할 수 있는 예외를 세분화하고, 더 명확하게 예외를 처리할 수 있다.

사용자 정의 예외는 `Exception` 클래스를 상속받아 정의할 수 있다. `Exception` 클래스는 Dart에서 예외를 처리하는 데 사용되는 기본 클래스이다. 사용자 정의 예외 클래스는 주로 예외 메시지나 추가적인 예외 정보를 담는 필드를 포함할 수 있다.

```dart
class CustomException implements Exception {
  final String message;

  CustomException(this.message);

  @override
  String toString() => 'CustomException: $message';
}
```

위의 예제는 `CustomException`이라는 사용자 정의 예외를 정의하는 코드이다. `toString()` 메소드를 재정의하여 예외가 발생했을 때 출력될 메시지를 지정할 수 있다.

사용자 정의 예외를 사용하여 예외를 발생시키는 예는 다음과 같다.

```dart
void validateInput(int input) {
  if (input < 0) {
    throw CustomException('입력 값은 음수가 될 수 없다.');
  }
}
```

`throw` 키워드를 사용하여 예외를 발생시킬 수 있으며, 이 예외는 `try-catch` 블록으로 처리된다.

```dart
void main() {
  try {
    validateInput(-1);
  } catch (e) {
    print(e);
  }
}
```

이 코드는 `validateInput(-1)`을 호출하여 예외를 발생시키고, `catch` 블록에서 해당 예외를 처리한다. 출력 결과는 다음과 같이 나타난다.

```
CustomException: 입력 값은 음수가 될 수 없다.
```

이처럼 사용자 정의 예외는 특정 도메인이나 비즈니스 로직에 맞는 예외 처리를 가능하게 하여, 코드의 가독성을 높이고, 문제를 보다 쉽게 디버깅할 수 있다.

#### 예외 객체의 추가 정보 포함

때로는 예외 객체에 추가적인 정보를 포함시켜야 할 때가 있다. Dart에서는 이를 위해 예외 객체에 필요한 데이터를 전달할 수 있다. 예를 들어, 오류 코드나 관련 데이터를 함께 제공함으로써 예외 상황을 더 구체적으로 설명할 수 있다.

```dart
class DetailedException implements Exception {
  final String message;
  final int errorCode;

  DetailedException(this.message, this.errorCode);

  @override
  String toString() => 'Error $errorCode:$message';
}
```

이 예제에서는 `errorCode`라는 필드를 추가하여 예외의 구체적인 오류 코드를 함께 전달할 수 있다. 이를 통해 예외 상황을 더욱 구체적으로 파악할 수 있다.

```dart
void main() {
  try {
    throw DetailedException('파일을 찾을 수 없다.', 404);
  } catch (e) {
    print(e);
  }
}
```

이 코드는 다음과 같은 결과를 출력한다:

```
Error 404: 파일을 찾을 수 없다.
```

이와 같은 방식을 사용하면 예외 처리 시 추가적인 정보를 제공할 수 있어, 문제를 디버깅하고 해결하는 데 도움이 된다.
