Dart 튜토리얼 6편: 에러 처리 전략(try/catch/throw/assert)

요약#

핵심 요지#

  • 문제 정의: 오류를 잡지 않으면 exception1이 전파되어 프로그램이 예기치 않게 종료될 수 있다.
  • 핵심 주장: Dart는 try2/catch3/on4/finally5throw6로 예외를 명확히 처리하도록 설계됐다.
  • 주요 근거: Exception7Error8는 성격이 다르며, 상황에 맞게 처리 전략을 달리한다.
  • 실무 기준: 복구 가능한 상황은 Exception으로 보고 catch에서 처리하고, 개발 중 가정 검증은 assert9로 잡는다.

문서가 설명하는 범위#

  • try/catch/on/finally 문법과 사용 흐름
  • throw, rethrow10로 예외를 발생·전파하는 방법
  • assert로 개발 중 가정을 검증하는 방식

읽는 시간: 15분 | 난이도: 초급


참고 자료#


문제 상황#

프로그램은 외부 입력과 환경에 영향을 받습니다.
파일이 없거나, 네트워크가 끊기거나, 예상과 다른 데이터가 들어올 수 있습니다.
이때 실패를 “없던 일”로 만들 수는 없으니, 실패를 어떻게 다룰지 규칙이 필요합니다.

Dart는 “예외(exception)“를 통해 오류 상황을 표현합니다.
예외를 잡지 않으면 호출 스택을 타고 위로 전파되고, 마지막까지 처리되지 않으면 실행이 멈출 수 있습니다.


해결 방법#

입문자가 바로 쓸 수 있는 에러 처리 전략을 4단계로 정리합니다.

단계 1: try/catch로 실패를 잡고 계속 진행하기#

Why#

NOTE

입력/환경은 항상 예측대로 움직이지 않습니다.
그래서 “실패할 수 있는 구간”을 표시하고, 실패했을 때의 흐름을 코드로 정해둬야 합니다.

void main() {
// 실패 가능: 문자열이 숫자가 아닐 수 있다.
int.parse('not a number');
}

What#

NOTE

try/catch는 실패가 발생할 수 있는 코드를 감싸고, 실패를 한 곳에서 받아 처리하는 문법입니다.

How#

TIP

try 블록은 “실패할 수 있는 코드”를 감싸는 공간입니다.
catchtry 안에서 발생한 exception을 받아 처리합니다.
catch는 예외 객체와 함께 StackTrace11도 받을 수 있습니다.

void main() {
try {
final value = int.parse('not a number');
print(value);
} catch (e, s) {
print('파싱 실패: $e');
print('스택 트레이스: $s');
}
}

Watch out#

WARNING

catch에서 예외를 “조용히” 무시하면, 문제를 늦게 발견할 수 있습니다.
최소한 실패가 있었다는 사실은 로그로 남기는 편이 안전합니다.

try {
int.parse('not a number');
} catch (e) {
print('실패를 무시하지 말고 기록합니다: $e');
}

결론: 실패를 잡아 로그를 남기고, 프로그램 전체가 즉시 종료되는 것을 막을 수 있습니다.


단계 2: on으로 “특정 타입”만 골라서 처리하기#

Why#

NOTE

예외를 한 덩어리로 처리하면, “예상한 실패”와 “예상하지 못한 실패”가 섞입니다.
그래서 먼저 “예상한 실패 타입”을 분리해 두는 편이 읽기 쉽습니다.

// 숫자 파싱에서는 FormatException 같은 예외를 예상할 수 있다.

What#

NOTE

on은 특정 예외 타입만 골라서 처리하는 문법입니다.

How#

TIP

on을 사용하면 “특정 예외 타입”만 골라서 처리할 수 있습니다.
이 방식은 “어떤 실패를 예상했고, 어디서 처리할지”를 더 분명하게 만듭니다.

void main() {
try {
final value = int.parse('not a number');
print(value);
} on FormatException catch (e) {
print('형식이 맞지 않습니다: $e');
} catch (e) {
print('그 외 예외: $e');
}
}

Watch out#

WARNING

on으로 잡지 못하는 예외도 있을 수 있습니다.
그래서 마지막에 일반 catch를 두고, 예상 밖 실패를 한 곳으로 모으는 편이 안전합니다.

결론: 예상한 실패는 구체적으로 처리하고, 그 외 예외는 한 곳에서 모아 처리할 수 있습니다.


단계 3: finally로 성공·실패와 상관없이 마무리하기#

Why#

NOTE

실패가 났을 때 가장 위험한 상황은 “정리 작업이 빠져서 상태가 꼬이는 것”입니다.
그래서 성공/실패와 무관하게 반드시 실행해야 하는 코드는 별도로 분리합니다.

// 성공/실패와 무관하게 실행돼야 하는 정리 로직이 있다.

What#

NOTE

finally는 예외 발생 여부와 상관없이 항상 실행되는 블록입니다.

How#

TIP

finallytry가 성공하든 실패하든 항상 실행됩니다.
즉, 자원을 정리하거나 상태를 원래대로 돌려놓는 작업을 여기에 둡니다.

void main() {
try {
print('작업 시작');
throw Exception('문제 발생');
} catch (e) {
print('예외 처리: $e');
} finally {
print('항상 실행되는 정리 작업');
}
}

Watch out#

WARNING

finally는 “항상 실행”되므로, 여기에서 예외가 나면 원래 예외를 가릴 수 있습니다.
따라서 finally에는 정리 작업만 넣고, 복잡한 로직은 피하는 편이 안전합니다.

결론: 실패가 나더라도 정리 작업이 빠지지 않아 “중간에 멈춘 상태”를 줄일 수 있습니다.


단계 4: throw/rethrow로 실패를 표현하고 전파하기#

Why#

NOTE

실패를 잡는 것만큼 중요한 것이 “실패를 올바르게 표현하는 것”입니다.
이 단계가 흐릿하면, 어디서 실패가 시작됐는지 추적하기 어렵습니다.

void saveUser(String? name) {
if (name == null) throw ArgumentError('name must not be null');
}

What#

NOTE

throw는 예외를 발생시키고, rethrow는 잡은 예외를 스택 정보를 유지한 채 다시 던집니다.

How#

TIP

throw로 예외를 발생시키고, 필요하면 위로 전파할 수 있습니다.
또한 rethrowcatch에서 받은 예외를 “다시 던지는” 용도입니다.
이때 스택 정보가 유지된다는 점이 중요합니다.

void saveUser(String? name) {
if (name == null) {
throw ArgumentError('name must not be null');
}
// 저장 로직...
}
void main() {
try {
saveUser(null);
} catch (e) {
print('저장 실패: $e');
}
}

catch에서 로그를 남기고, 처리는 상위 계층에 맡기고 싶다면 rethrow를 사용합니다.

void runJob() {
try {
doJob();
} catch (e) {
print('여기서 기록만 하고 위로 올립니다: $e');
rethrow;
}
}

Watch out#

WARNING

로그만 남기고 처리 책임을 위로 올리려면, 새 예외를 만들기보다 rethrow가 더 자연스럽습니다.
그래야 “어디서 처음 실패했는지”가 호출 경로에 남습니다.

결론: 실패를 “표현”(throw)과 “처리”(catch)를 분리해, 책임을 단계별로 나눌 수 있습니다.


개발 중에는 assert로 “가정”을 검증하기#

assert는 개발 중 조건을 검증하는 도구입니다.
조건이 false이면 AssertionError12를 던지며, 도구/환경에 따라 활성화 여부가 달라질 수 있습니다.
또한 프로덕션에서는 assert가 무시되고, 인자 평가도 하지 않을 수 있습니다.

void main() {
final text = 'Dart';
assert(text.isNotEmpty);
}

결론: “이 시점에는 반드시 성립해야 하는 조건”을 코드로 남겨, 개발 단계에서 더 빨리 실패를 발견할 수 있습니다.

Footnotes#

  1. exception(예외): 프로그램 실행 중 발생한 오류 상황을 표현하는 객체다.

  2. try(트라이): 예외가 발생할 수 있는 코드를 감싸는 블록이다.

  3. catch(캐치): 발생한 예외를 받아 처리하는 블록이다.

  4. on(온): 특정 예외 타입만 골라 처리하는 문법이다.

  5. finally(파이널리): 예외 발생 여부와 상관없이 항상 실행되는 블록이다.

  6. throw(스로우): 예외를 발생시키는 키워드다.

  7. Exception(예외 타입): 일반적으로 복구 가능한 오류 상황을 나타내는 예외 유형이다.

  8. Error(에러 타입): 보통 프로그램의 버그나 잘못된 사용처럼 즉시 수정이 필요한 상황을 나타내는 유형이다.

  9. assert(어설트): 개발 중 조건을 검증하고, 실패 시 예외를 발생시키는 문이다.

  10. rethrow(리스로우): catch에서 잡은 예외를 스택 정보를 유지한 채 다시 던지는 키워드다.

  11. StackTrace(스택 트레이스): 예외가 발생하기까지의 호출 경로를 담는 정보다.

  12. AssertionError(어설션 에러): assert 조건이 실패할 때 발생하는 예외다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Dart 튜토리얼 6편: 에러 처리 전략(try/catch/throw/assert)
https://moodturnpost.net/posts/dart/dart-error-handling-strategy/
작성자
Moodturn
게시일
2026-01-04
Moodturn

목차