에러 처리: 예외와 assert

에러 처리: 예외와 assert

Dart 코드는 예외를 던지고(throw) 잡을 수(catch) 있어요. 예외는 예상하지 못한 일이 발생했음을 알리는 에러예요. 예외를 잡지 않으면 그 예외를 던진 [isolate]이 중단되고, 보통 그 isolate과 프로그램이 종료됩니다. 이 글에서는 예외를 던지고, 잡고, 정리하는 방법과 개발 중에 쓰는 assert 문까지 살펴볼게요.

출처: Dart 공식 문서 — Error handling

예외

자바와 달리 Dart의 예외는 전부 unchecked 예외예요. 메서드는 어떤 예외를 던질 수 있는지 선언하지 않고, 여러분이 어떤 예외든 잡아야 할 의무도 없어요.

Dart는 ExceptionError 타입과 그 외 수많은 미리 정의된 서브타입을 제공해요. 물론 자체 예외를 정의할 수도 있습니다. 게다가 Dart 프로그램은 ExceptionError 객체뿐 아니라 null이 아닌 어떤 객체든 예외로 던질 수 있어요.

Throw

예외를 던지거나 raise 하는 예시를 볼게요.

throw FormatException('Expected at least 1 section');

임의의 객체도 던질 수 있어요.

throw 'Out of llamas!';

예외를 던지는 것은 표현식이기 때문에, => 문과 표현식을 쓸 수 있는 다른 어디에서든 예외를 던질 수 있어요.

void distanceTo(Point other) => throw UnimplementedError();

Catch

예외를 잡거나 capture하면(다시 던지지 않는 한) 예외가 전파되는 것을 막습니다. 예외를 잡으면 그것을 처리할 기회가 생겨요.

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

둘 이상의 타입 예외를 던질 수 있는 코드를 다루려면 여러 catch 절을 지정할 수 있어요. 던져진 객체의 타입과 일치하는 첫 catch 절이 그 예외를 처리합니다. catch 절이 타입을 지정하지 않으면 그 절은 어떤 타입의 던져진 객체든 처리할 수 있어요.

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

위 코드에서 보듯 on이나 catch 또는 둘 다를 쓸 수 있어요. 예외 타입을 지정해야 할 때는 on을 쓰고, 예외 처리기에 예외 객체가 필요할 때는 catch를 씁니다.

catch()에는 매개변수 하나 또는 둘을 지정할 수 있어요. 첫 번째는 던져진 예외이고, 두 번째는 스택 트레이스(StackTrace 객체)예요.

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n$e');
} catch (e, s) {
  print('Exception details:\n$e');
  print('Stack trace:\n$s');
}

예외를 부분적으로 처리하면서 전파되도록 두려면 rethrow 키워드를 사용해요.

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Finally

예외가 던져지든 아니든 어떤 코드가 실행되게 하려면 finally 절을 써요. 일치하는 catch 절이 없으면 finally 절이 실행된 뒤 예외가 전파됩니다.

try {
  breedMoreLlamas();
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

finally 절은 일치하는 catch 절들 다음에 실행돼요.

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}

Assert

개발 중에는 assert(<condition>, <optionalMessage>); 문을 사용해 불리언 조건이 거짓이면 정상 실행을 방해할 수 있어요.

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

assert에 메시지를 붙이려면 assert의 두 번째 인자로 문자열을 추가해요(선택적으로 [trailing comma] 포함).

assert(
  urlString.startsWith('https'),
  'URL ($urlString) should start with "https".',
);

assert의 첫 인자는 불리언 값으로 평가되는 어떤 표현식이든 될 수 있어요. 표현식의 값이 true면 assert는 성공하고 실행은 계속됩니다. false면 assert는 실패하고 AssertionError 예외가 던져져요.

assert가 정확히 언제 동작할까요? 그것은 사용하는 도구와 프레임워크에 따라 달라져요.

  • Flutter는 [debug mode]에서 assert를 활성화한다.
  • webdev serve 같은 개발 전용 도구는 보통 기본으로 assert를 활성화한다.
  • dart run이나 dart compile js 같은 일부 도구는 --enable-asserts 커맨드라인 플래그로 assert를 지원한다.

운영 코드에서는 assert가 무시되고, assert의 인자도 평가되지 않아요.

더 알아보기