iBetter Books
수정

갈림길에서 — 조건문

코드는 위에서 아래로 흐릅니다. 그런데 현실의 문제는 항상 직선이 아닙니다. "로그인했으면 홈 화면, 아니면 로그인 화면"처럼 상황에 따라 다른 길을 가야 합니다. 이 갈림길을 만드는 도구가 바로 조건문입니다.

Dart의 조건문은 다른 언어와 크게 다르지 않습니다. 하지만 Null Safety와 연계되는 부분에서 Dart만의 방식이 드러납니다. 하나씩 살펴봅시다.

if와 else — 둘 중 하나

가장 기본적인 형태입니다. 조건이 참이면 첫 번째 블록을, 거짓이면 else 블록을 실행합니다.

void main() {
  int score = 75;

  if (score >= 60) {
    print('합격입니다.');
  } else {
    print('불합격입니다.');
  }
}

score >= 60이라는 조건이 true이므로 "합격입니다."가 출력됩니다. else 블록은 실행되지 않습니다.

블록이 한 줄이어도 중괄호를 생략하지 않는 것을 권장합니다. 나중에 코드를 추가할 때 실수를 막아줍니다.

else if — 여러 갈림길

갈림길이 셋 이상일 때는 else if를 사용합니다.

void main() {
  int score = 82;

  if (score >= 90) {
    print('A 학점');
  } else if (score >= 80) {
    print('B 학점');
  } else if (score >= 70) {
    print('C 학점');
  } else {
    print('D 학점 이하');
  }
}

조건은 위에서부터 순서대로 평가됩니다. 첫 번째로 true가 되는 블록만 실행되고, 나머지는 건너뜁니다. score가 82이므로 첫 번째 조건은 거짓, 두 번째 조건(score >= 80)이 참이 되어 "B 학점"이 출력됩니다.

삼항 연산자 — 한 줄 조건식

간단한 조건은 삼항 연산자로 더 간결하게 표현할 수 있습니다.

조건 ? 참일 때 값 : 거짓일 때 값
void main() {
  int age = 20;
  String status = age >= 18 ? '성인' : '미성년자';
  print(status); // 성인
}

삼항 연산자는 값을 반환합니다. 변수에 바로 할당하거나 출력에 바로 사용할 수 있어서 편리합니다. 단, 중첩해서 쓰면 읽기 어려워지므로 한 단계 이상 중첩하지 않는 것이 좋습니다.

void main() {
  int temperature = 28;

  // 삼항 연산자를 문자열 안에서 사용
  print('현재 날씨는 ${temperature >= 30 ? "더움" : "적당함"}입니다.');
}

조건문과 Null Safety

PART 02에서 ?로 nullable 변수를 선언했습니다. null이 있을 수 있는 값은 사용하기 전에 반드시 확인해야 합니다. if 문이 그 역할을 합니다.

void main() {
  String? username = '다트학생'; // nullable String

  if (username != null) {
    // 이 블록 안에서 username은 String으로 취급됩니다
    print('안녕하세요, ${username.toUpperCase()}님!');
  } else {
    print('손님, 어서 오세요.');
  }
}

if (username != null) 블록 안에서 Dart 컴파일러는 username이 절대 null이 아님을 알고, String처럼 안전하게 사용할 수 있게 해줍니다. 이를 스마트 캐스트(smart cast) 또는 타입 좁히기(type narrowing) 라고 합니다.

null인 경우 기본값을 쓰고 싶다면 PART 02에서 배운 ?? 연산자를 활용할 수도 있습니다.

void main() {
  String? nickname;
  String displayName = nickname ?? '익명';
  print('$displayName 님이 입장했습니다.');
}

is 연산자 — 타입 확인

PART 02에서 is 연산자를 잠깐 만났습니다. if와 함께 쓰면 타입에 따라 다른 동작을 할 수 있습니다.

void main() {
  Object value = '안녕하세요';

  if (value is String) {
    // 이 블록 안에서 value는 String으로 취급됩니다
    print('문자열 길이: ${value.length}');
  } else if (value is int) {
    print('정수: ${value.isEven ? "짝수" : "홀수"}');
  }
}

is 이후에도 타입이 좁혀집니다. value is String 블록 안에서는 value.length처럼 String의 메서드를 바로 쓸 수 있습니다.

assert — 개발 중 검증

assert는 개발 중에만 동작하는 특별한 도구입니다. 조건이 거짓이면 즉시 오류를 발생시켜 "이 상황은 절대 일어나면 안 된다"고 선언하는 것입니다.

void setAge(int age) {
  assert(age >= 0, '나이는 음수가 될 수 없습니다. 입력값: $age');
  print('나이가 $age로 설정되었습니다.');
}

void main() {
  setAge(25);  // 정상
  setAge(-1);  // 개발 모드에서 AssertionError 발생
}

assert(조건, 메시지) 형태입니다. 조건이 true이면 아무 일도 일어나지 않습니다. false이면 AssertionError가 발생합니다.

중요한 점은 assert릴리스 빌드에서 완전히 제거된다는 것입니다. 배포된 앱에서는 실행되지 않습니다. 따라서 사용자 입력 검증이 아닌, 개발자가 실수하지 않도록 돕는 용도로 씁니다.

전체 예제 — 성적 처리기

배운 내용을 하나로 묶어 보겠습니다.

void main() {
  int? rawScore = 88;

  // null 확인
  if (rawScore == null) {
    print('점수가 입력되지 않았습니다.');
    return;
  }

  // assert로 유효 범위 검증 (개발 중)
  assert(rawScore >= 0 && rawScore <= 100, '점수는 0~100 사이여야 합니다.');

  // 등급 판정
  String grade;
  if (rawScore >= 90) {
    grade = 'A';
  } else if (rawScore >= 80) {
    grade = 'B';
  } else if (rawScore >= 70) {
    grade = 'C';
  } else {
    grade = 'D';
  }

  // 삼항 연산자로 합격 여부
  String result = rawScore >= 60 ? '합격' : '불합격';

  print('점수: $rawScore점, 등급: $grade, 결과: $result');
}

실행하면 다음 결과가 나옵니다.

점수: 88점, 등급: B, 결과: 합격

다음 챕터에서는 같은 작업을 여러 번 반복하는 방법을 알아봅니다. 반복문을 배우면 100명의 성적을 처리하는 코드도 단 몇 줄로 쓸 수 있게 됩니다.