Dart 튜토리얼 1편: 언어 철학과 실행 모델
요약
핵심 요지
문서가 설명하는 범위
- Dart가 클라이언트 앱 개발에 집중한 이유와 설계 철학
- 개발과 배포 단계에서 각각 다른 컴파일 방식을 사용하는 이유
null안전성이runtime오류를 컴파일 단계로 이동시키는 방법
읽는 시간: 16분 | 난이도: 초급
참고 자료
- Dart overview - 언어 설계 철학과 플랫폼 전략
- Introduction to Dart - 언어 핵심 개념과 타입 시스템
- Sound null safety - null 안전성 원칙과 실용적 효과
문제 상황
웹과 모바일 앱 개발에서 null 값 접근 오류는 가장 흔한 runtime 오류입니다.
이 오류는 코드를 실행해야만 발견되므로 디버깅이 어렵습니다.
개발자가 변수에 값이 있다고 생각했지만 실제로는 null인 경우, 앱이 실행 중에 갑자기 멈춥니다.
사용자는 에러 메시지를 보게 되고, 개발자는 어느 시점에 null이 할당되었는지 역추적해야 합니다.
기존 방식의 한계
// JavaScript 예시function getFirstName(user) { return user.name.first; // user나 user.name이 null이면 runtime 오류}문제는 다음과 같습니다.
- 실행해봐야만 오류를 발견할 수 있다.
null체크를 빼먹으면 오류가 숨어있다가 사용자 환경에서 터진다.- 방어 코드를 일일이 작성해야 해서 코드가 길어진다.
해결 방법
Dart는 클라이언트 앱 개발에 집중하면서 생산성과 안전성을 모두 잡기 위해 세 가지 핵심 설계를 채택했습니다.
단계 1: 타입 추론으로 생산성 높이기
Why
NOTE타입을 매번 직접 적기 시작하면, 코드가 빠르게 길어지고 수정 비용도 커집니다.
그래서 “안전성은 유지하면서, 반복 입력은 줄이는 방식”이 필요합니다.// 매번 타입을 직접 적기 시작하면 코드가 금방 길어진다.String name = 'Voyager I';int year = 1977;double distance = 3.7;
What
NOTE
type inference는 변수의 초기값을 보고 타입을 결정합니다.
How
TIPDart는
type-safe7 언어지만 타입을 일일이 적지 않아도 됩니다.
type inference가 작동해서 변수의 초기값을 보고 타입을 자동으로 결정합니다.
이렇게 하면 코드는 짧아지지만 타입 검사는 그대로 작동합니다.var name = 'Voyager I'; // String으로 추론됨var year = 1977; // int로 추론됨var distance = 3.7; // double로 추론됨이 코드에서
name은String,year는int,distance는double로 고정됩니다.
즉, 이후에는 각 변수에 “그 타입이 아닌 값”을 넣으려고 하면 컴파일 단계에서 막힙니다.
Watch out
WARNING타입이 추론된 변수에 다른 타입 값을 다시 넣을 수 없습니다.
var year = 1977;// year = '1977'; // 컴파일 오류: year는 int로 추론되었다.
결론: 타입을 명시하지 않아도 컴파일러가 타입 오류를 잡아냅니다.
단계 2: JIT와 AOT로 개발·배포 속도 모두 잡기
Why
NOTE개발 중에는 “빨리 고치고 바로 확인”하는 반복 속도가 중요합니다.
반대로 배포 후에는 “빠르게 시작하고 일정한 성능으로 동작”하는 것이 중요합니다.Terminal window # 개발 중: 빠르게 실행해서 확인dart run# 배포용: 미리 컴파일해서 실행 파일 생성dart compile exe
What
NOTE
JIT는 실행하면서 필요한 코드를 즉시 컴파일하는 방식이고,AOT는 배포 전에 미리 컴파일하는 방식입니다.
How
TIPDart는 개발 단계와 배포 단계에 각각 다른 컴파일 방식을 사용합니다.
개발 단계:
JIT컴파일
코드를 수정하면 즉시 반영되는hot reload8가 가능합니다.
Dart VM9이 변경된 부분만 빠르게 재컴파일하므로 수정→테스트 사이클이 1초 안에 완료됩니다.
앱 상태를 유지한 채로 UI나 로직을 바로 확인할 수 있어 생산성이 크게 올라갑니다.배포 단계:
AOT컴파일
앱을 미리 기계어로 컴파일해서 배포합니다.
실행 시작이 빠르고 성능이 일정하며 메모리 사용도 최적화됩니다.
웹 플랫폼에서는JavaScript10나WebAssembly11로 컴파일할 수 있습니다.void main() {print('Hello, Dart');}// 개발: dart run으로 JIT 실행// 배포: dart compile exe로 AOT 컴파일
Watch out
WARNING이 선택은 “코드로 전환하는 기능”이 아니라, 실행/빌드 도구가 결정하는 실행 방식입니다.
즉, 같은 코드라도 개발 실행과 배포 빌드에서 동작 방식(컴파일 시점)이 달라질 수 있습니다.
결론: 개발 속도와 배포 성능을 동시에 확보합니다.
단계 3: sound null safety로 오류를 컴파일 단계로 이동
Why
NOTE
null은 “값이 없음”을 뜻하지만, 체크를 빼먹는 순간 런타임 오류로 이어지기 쉽습니다.
그래서 “어디가null이 될 수 있는지”를 타입으로 드러내는 규칙이 필요합니다.String? name = null;// print(name.length); // 컴파일 오류: name이 null일 수 있다.
What
NOTE
sound null safety는non-nullable12로 선언한 값이runtime에서도null이 될 수 없게 보장합니다.
How
TIPDart는
sound null safety를 적용해서null관련 오류를runtime에서 컴파일 단계로 이동시킵니다.기본은
non-nullable
타입 뒤에?를 붙이지 않으면null이 될 수 없습니다.
컴파일러가 이 규칙을 강제하므로 실행 전에null할당을 잡아냅니다.
nullable13은 명시적으로 표현
null이 필요한 변수에만?를 붙여서 의도를 명확히 합니다.
nullable변수에 접근할 때는null체크를 먼저 하도록 컴파일러가 요구합니다.String title = 'Dart'; // null 불가String? subtitle = null; // null 허용// title.length는 항상 안전// subtitle.length는 컴파일 오류 → subtitle?.length로 수정 필요
Watch out
WARNING
nullable값은 “사용하기 전에” 반드시 처리 흐름을 정해야 합니다.
가장 단순한 방법은if로null여부를 확인한 뒤 사용하는 것입니다.String? subtitle = null;if (subtitle != null) {print(subtitle.length);}왜 sound인가
컴파일러가non-nullable이라고 판단한 변수는runtime에서도 절대null이 될 수 없습니다.
디버거로 확인해도null이 들어있지 않습니다.
이것이 “sound”의 의미입니다.설계 의도는 다음과 같습니다.
runtime오류를 컴파일 단계로 이동시켜 배포 전에 문제를 발견한다.null체크가 필요한 곳과 불필요한 곳을 타입만 봐도 알 수 있다.- 방어 코드를 줄여서 핵심 로직에 집중할 수 있다.
Footnotes
-
null(널): 값이 없음을 나타내는 특수한 값이다. ↩
-
runtime(런타임): 코드가 실제로 실행되는 시점과 환경을 뜻한다. ↩
-
type inference(타입 추론): 변수의 초기값을 보고 컴파일러가 타입을 자동으로 결정하는 기능이다. ↩
-
JIT(Just-In-Time 컴파일): 코드를 실행하면서 필요한 부분을 즉시 기계어로 변환하는 방식이다. ↩
-
AOT(Ahead-Of-Time 컴파일): 배포 전에 코드를 미리 기계어로 변환하는 방식이다. ↩
-
sound null safety(완전한 널 안전): non-nullable로 선언된 변수가 runtime에서도 절대 null이 될 수 없음을 보장하는 시스템이다. ↩
-
type-safe(타입 안전): 변수에 선언된 타입과 다른 값이 들어가는 것을 컴파일러가 막는 원칙이다. ↩
-
hot reload(핫 리로드): 앱을 재시작하지 않고 코드 변경사항을 즉시 반영하는 기능이다. ↩
-
Dart VM(다트 가상 머신): Dart 코드를 실행하고 JIT 컴파일을 담당하는 실행 환경이다. ↩
-
JavaScript(자바스크립트): 웹 브라우저에서 실행되는 프로그래밍 언어다. ↩
-
WebAssembly(웹어셈블리): 웹에서 고성능 실행을 위한 바이너리 포맷이다. ↩
-
non-nullable(널 비허용): null 값을 가질 수 없는 타입이다. ↩
-
nullable(널 허용): null 값을 가질 수 있는 타입으로
?를 붙여 표시한다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!