분기

분기 (Branches)

코드의 흐름을 조건에 따라 갈라지게 만들고 싶을 때가 있어요. Dart에서는 if 문, if-case 문, switch 문과 식을 써서 분기를 만들 수 있어요. 이 페이지에서는 이 세 가지를 하나씩 살펴볼게요.

출처: Dart 공식 문서

본문

분기를 다루는 것 외에도 Dart에서는 반복(loops)과 예외(exception)로도 흐름 제어를 할 수 있어요. 하지만 이 페이지의 핵심은 분기예요.

if

Dart의 if 문은 선택적으로 else 절을 가질 수 있어요. if 뒤 괄호 안의 조건은 반드시 불리언(boolean)으로 평가되는 표현식이어야 해요.

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

if를 표현식 문맥에서 쓰는 방법은 조건 표현식(Conditional expressions) 문서에서 다뤄요.

if-case

Dart의 if 문은 case 절 뒤에 패턴을 붙여 쓸 수 있어요.

if (pair case [int x, int y]) return Point(x, y);

패턴이 값과 일치하면, 패턴이 정의한 변수들이 유효 범위 안에 들어온 상태로 해당 분기가 실행돼요. 위 예시에서 리스트 패턴 [int x, int y]가 값 pair와 일치하므로, 패턴이 정의한 x, y 변수를 가진 채로 return Point(x, y) 분기가 실행됩니다.

일치하지 않으면, 있다면 else 분기로 진행해요.

if (pair case [int x, int y]) {
  print('Was coordinate array $x,$y');
} else {
  throw FormatException('Invalid coordinates.');
}

if-case 문은 하나의 패턴에 대해 매칭하고 구조 분해(destructure)하는 방법을 제공해요. 값을 여러 패턴에 대해 검사하려면 switch를 사용하세요. if 문 안의 case 절은 언어 버전이 최소 3.0이어야 해요.

switch 문 (Switch statements)

switch 문은 값 표현식을 일련의 case들과 비교해서 평가해요. 각 case 절은 값이 일치해야 하는 패턴이에요. case에는 어떤 종류의 패턴이든 쓸 수 있어요.

값이 어떤 case의 패턴과 일치하면 그 case 본문이 실행돼요. 비어 있지 않은 case 절은 완료 후 switch의 끝으로 점프해요. break 문이 필요하지 않다는 뜻이에요. 비어 있지 않은 case 절을 끝내는 다른 유효한 방법으로는 continue, throw, return 문이 있어요.

어떤 case도 일치하지 않을 때 코드를 실행하려면 default 또는 와일드카드 _ 절을 써요.

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
  case 'PENDING':
    executePending();
  case 'APPROVED':
    executeApproved();
  case 'DENIED':
    executeDenied();
  case 'OPEN':
    executeOpen();
  default:
    executeUnknown();
}

빈 case는 다음 case로 통과(fall through)해서, case들이 본문을 공유할 수 있게 해줘요. 통과하지 않는 빈 case에는 break를 본문으로 쓰세요. 순차적이지 않은 통과가 필요하다면 continue 문과 레이블(label)을 쓸 수 있어요.

switch (command) {
  case 'OPEN':
    executeOpen();
    continue newCase; // Continues executing at the newCase label.

  case 'DENIED': // Empty case falls through.
  case 'CLOSED':
    executeClosed(); // Runs for both DENIED and CLOSED,

  newCase:
  case 'PENDING':
    executeNowClosed(); // Runs for both OPEN and PENDING.
}

논리 OR 패턴(logical-or pattern)을 쓰면 case들이 본문이나 가드를 공유하게 할 수 있어요. 패턴과 case 절에 대해 더 자세히 알고 싶다면 패턴 문서의 Switch statements and expressions 부분을 확인하세요.

switch 식 (Switch expressions)

switch 은 일치하는 case의 본문 표현식을 기반으로 값을 생성해요. switch 식은 Dart가 식을 허용하는 모든 곳에서 쓸 수 있어요. 다만 표현식 문(expression statement)의 시작 에서는 예외예요. 예를 들면:

var x = switch (y) { ... };

print(switch (x) { ... });

return switch (x) { ... };

표현식 문의 시작에 switch를 쓰고 싶다면 switch 문을 사용하세요.

switch 식을 쓰면 다음과 같은 switch 을:

// Where slash, star, comma, semicolon, etc., are constant variables...
switch (charCode) {
  case slash || star || plus || minus: // Logical-or pattern
    token = operator(charCode);
  case comma || semicolon: // Logical-or pattern
    token = punctuation(charCode);
  case >= digit0 && <= digit9: // Relational and logical-and patterns
    token = number();
  default:
    throw FormatException('Invalid');
}

이렇게 으로 다시 쓸 수 있어요.

token = switch (charCode) {
  slash || star || plus || minus => operator(charCode),
  comma || semicolon => punctuation(charCode),
  >= digit0 && <= digit9 => number(),
  _ => throw FormatException('Invalid'),
};

switch 식의 문법은 switch 문과 달라요.

  • case는 case 키워드로 시작하지 않아요.
  • case 본문은 일련의 문 대신 단일 표현식이에요.
  • 각 case는 본문을 가져야 해요. 빈 case에 대한 암묵적 통과는 없어요.
  • case 패턴은 본문과 : 대신 =>로 구분해요.
  • case들은 ,로 구분돼요(선택적 trailing , 허용).
  • 기본 case는 default_를 모두 허용하는 대신 _만 쓸 수 있어요.

switch 식은 언어 버전이 최소 3.0이어야 해요.

완전성 검사 (Exhaustiveness checking)

완전성 검사는 값이 switch에 들어왔는데 어떤 case와도 일치하지 않을 가능성이 있을 때 컴파일 타임 에러를 보고하는 기능이에요.

// Non-exhaustive switch on bool?, missing case to match null possibility:
switch (nullableBool) {
  case true:
    print('yes');
  case false:
    print('no');
}

nullableBool은 null이 될 수 있는데 그 경우를 다루는 case가 없어서 에러가 나요. 기본 case(default 또는 _)는 switch를 통과할 수 있는 모든 가능한 값을 덮어요. 그래서 어떤 타입이든 switch를 완전하게 만들어 주죠.

enum과 sealed 타입은 switch에 특히 유용한데, 기본 case가 없어도 가능한 값들을 모두 알고 완전히 열거할 수 있기 때문이에요. 클래스에 sealed 수정자를 쓰면 그 클래스의 하위 타입을 switch할 때 완전성 검사를 켤 수 있어요.

sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.length);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => math.pi * r * r,
};

누군가 Shape의 새 하위 클래스를 추가하면 이 switch 식은 불완전해져요. 완전성 검사가 빠진 하위 타입을 알려주죠. 이렇게 해서 Dart를 함수형 대수 데이터 타입(algebraic datatype) 스타일로 쓸 수 있어요.

가드 절 (Guard clause)

case 절 뒤에 선택적 가드 절을 설정하려면 when 키워드를 써요. 가드 절은 if case, 그리고 switch 문과 식 모두에서 사용할 수 있어요.

// Switch statement:
switch (something) {
  case somePattern when some || boolean || expression:
    //             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
    body;
}

// Switch expression:
var value = switch (something) {
  somePattern when some || boolean || expression => body,
  //               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
}

// If-case statement:
if (something case somePattern when some || boolean || expression) {
  //                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
  body;
}

가드는 매칭 후에 임의의 불리언 표현식을 평가해요. 이렇게 해서 case 본문을 실행할지 말지 추가 제약을 걸 수 있죠. 가드 절이 false로 평가되면 전체 switch를 빠져나가는 대신 다음 case로 진행돼요.

더 알아보기