Dart null 안전성 베타 발표
Dart null 안전성 베타 발표 (Announcing Dart null safety beta)
사운드하고 더 안전한 상태로 패키지를 마이그레이션하는 여정의 시작
오늘 Dart와 Flutter에서 **사운드 null 안전성(sound null safety)**이 베타로 제공된다는 소식을 전해요. null 안전성은 우리의 최신 주요 생산성 기능으로, 종종 찾아내기 어려운 버그인 null 오류를 피하도록 도와줘요. 왜 null 안전성에 기대하는지 빠르게 요약해서 보고 싶다면 이 새 영상을 확인해 보세요.
null 안전성이 베타로 승격되면서, 이제 pub.dev에 있는 수천 개 패키지의 커뮤니티 마이그레이션을 시작할 때가 됐어요. 우리는 Dart 핵심 라이브러리, Flutter 프레임워크, 그리고 40개가 넘는 Dart와 Flutter 패키지를 마이그레이션했어요. 이와 함께 커뮤니티도 패키지를 마이그레이션하면서 null 안전성을 받아들이길 바라고 있어요.
베타와 함께, 우리는 null 안전성 기능의 안정 버전을 배포하기 전의 마지막 단계에 들어서고 있어요. 이 기능을 사용해 보고, 개선할 부분이나 더 이해하기 쉬운 UI 메시지, 더 명확해질 문서가 있다면 알려주시길 바래요. 여러분의 피드백을 정말 기대하고 있어요.
본문
Null 안전성에 옵트인하기
null 안전성 마이그레이션을 논의하기 전에, (null 안전성 원칙에 명시된 대로) null 안전성 도입을 언제 시작할지는 여러분이 결정한다는 점을 다시 말하는 게 중요해요. 앱과 패키지는 최소 Dart SDK 제약이 적어도 Dart 2.12 프리릴리스일 때만 null 안전성으로 실행돼요.
environment:
sdk: ">=2.12.0-0 <3.0.0"
이걸 직접 체험하려면, 아래와 같은 코드를 담은 작은 null-safe hello 앱을 만들어 보세요(예: dart create 사용). 그러면 SDK 제약을 바꾸고 dart pub get을 실행하기 전과 후로 앱을 각각 실행해서, 프로그램 동작이 어떻게 바뀌는지 확인할 수 있어요. (dart --version에서 2.12를 보고하는 SDK를 사용해야 해요.)
***bin/hello.dart:***
...
void main() {
var hello = 'Hello Dart developers';
if (someCondition) {
hello = null;
}
print(hello);
}
SDK 제약을 바꾸기 전:
$ dart run
null
SDK 제약을 바꾸고(dart pub get 실행 후):
$ dart run
bin/hello.dart:6:13: Error: Null can't be assigned to a variable of type 'String' because 'String' is not nullable.
hello = null;
^
Null 안전성으로 마이그레이션하기
패키지(또는 단순한 앱)를 null 안전성으로 마이그레이션하려면 다섯 단계를 따르면 돼요. 모두 dart.dev의 마이그레이션 가이드에 자세히 문서화되어 있어요.
1단계: 의존성이 준비됐는지 확인하기
코드는 순서대로 마이그레이션하는 걸 강력히 권장해요. 의존성 그래프의 말단(leaf)을 먼저 마이그레이션하는 거죠. 예를 들어 C가 B에 의존하고 B가 A에 의존한다면, 먼저 A를 null 안전성으로, 그다음 B, 그다음 C를 마이그레이션해요. 이 순서는 A, B, C가 라이브러리든, 패키지든, 앱이든 동일하게 적용돼요.
왜 순서가 중요할까요? 의존성이 마이그레이션되기 전에 코드를 어느 정도 마이그레이션해도 진전을 이룰 수는 있지만, 의존성이 마이그레이션 과정에서 API를 바꾸면 두 번째 마이그레이션을 해야 할 위험이 있어요. 일부 의존성이 null 안전하지 않다면, pub.dev에서 각 패키지에 나열된 연락처 정보를 통해 패키지 게시자에게 연락해 보는 걸 고려해 보세요.
의존성이 준비됐는지 검증하기
앱이나 패키지가 마이그레이션을 시작할 준비가 됐는지 검증하려면 null-safety 모드에서 dart pub outdated를 사용하면 돼요. 아래 예시는 path, process, pedantic을 Resolvable 열에 나열된 프리릴리스 버전으로 업그레이드하면 이 앱이 마이그레이션할 준비가 된다는 걸 보여줘요.
null 안전성 지원이 사소한(minor) 새 버전에서 가능하다면 Upgradable 열에서 볼 수 있어요. 종종 null 안전성 지원은 주요(major) 새 버전에서 가능해져서, 그 경우 outdated 출력의 Resolvable에 버전이 나열된 걸 보게 돼요. 그 버전들로 업그레이드하려면 pubspec.yaml 파일을 편집해서 그 major 버전을 허용하면 돼요. 예를 들어 process: ^3.0.13을 process: ^4.0.0-nullsafety로 바꿀 수 있어요.
null 안전성을 지원하는 패키지는 pub.dev에서도 찾을 수 있어요. 패키지 페이지의 새 Null safety 태그(예: collection 1.15)와 새 고급 검색의 null safety 검색 옵션을 쓰면 돼요.
2단계: 마이그레이션 도구로 마이그레이션하기
의존성이 준비됐다면 마이그레이션 도구인 dart migrate로 앱이나 패키지를 마이그레이션하면 돼요.
마이그레이션 도구는 대화형이라, 도구가 추론한 nullability 속성을 검토할 수 있어요. 어느 결론에 동의하지 않으면 nullability 힌트를 추가해서 그 추론을 바꿀 수 있어요. 마이그레이션 힌트 몇 개를 추가하는 것만으로 마이그레이션 품질에 큰 영향이 있을 수 있어요.
소수의 Dart 패키지 작성자가 null 안전성의 초기 프리뷰 빌드로 마이그레이션을 시험해 봤고, 그 피드백은 고무적이었어요. 마이그레이션 가이드에 마이그레이션 도구를 사용하는 추가 팁이 있어요.
3단계: 마이그레이션한 코드를 정적으로 분석하기
IDE나 명령줄에서 pub get으로 패키지를 업데이트하세요. 그런 다음 IDE나 명령줄로 Dart 코드에 대한 정적 분석을 수행해요.
$ dart pub get
$ dart analyze
또는 Flutter 코드에서는:
$ flutter pub get
$ flutter analyze
4단계: 테스트가 통과하는지 확인하기
테스트를 실행해서 통과하는지 확인하세요. 패키지 코드를 더 이상 null을 허용하지 않도록 바꿨다면, null 값을 기대하는 테스트를 업데이트해야 할 수 있어요.
5단계: null-safe 패키지 게시하기
마이그레이션이 완료되고 테스트가 통과하면 패키지를 프리릴리스로 게시할 수 있어요. 모범 사례를 간단히 요약하면 다음과 같아요.
- 버전 번호를 다음 major 버전으로 올려요 (예: 2.3.x에서 3.0.0으로). 이 모범 사례는 패키지 사용자들이 null 안전성을 직접 쓸 준비가 되기 전에 업그레이드하지 않도록 보장하고, API를 리팩터링해서 null 안전성을 최대한 활용할 자유를 줘요.
- pub.dev에서 패키지를 프리릴리스 버전으로 버전을 매기고 게시해요. (예:
3.0.0-nullsafety.0을 쓰지,3.0.0을 쓰지 마세요.)
마이그레이션과 버전 관리의 전체 세부 사항은 마이그레이션 가이드를 참고하세요.
사운드 null 안전성의 이점
Dart와 Flutter에서 null 안전성 기술 프리뷰에 대한 이전 블로그 글들은 여러 예시를 통해 null 안전성의 이점을 다뤘어요. 이제 null 안전성이 완성에 가까워지면서, 그 이점의 실제 사례를 몇 가지 보고 있어요.
더 안전한 코드
아주 최근에 Flutter master 채널에서 버그를 발견했는데, 어떤 머신 구성에서 여러 flutter 도구 명령이 null 오류로 충돌했어요: The method '>=' was called on null. 근본 원인은 Android Studio 4.1 감지 지원을 추가한 최근 풀 리퀘스트였어요. 그 PR이 아래와 같은 코드를 추가했죠.
final int major = version?.major;
final int minor = version?.minor;
if (globals.platform.isMacOS) {
/// plugin path of Android Studio changed after version 4.1.
if (major >= 4 && minor >= 1) {
...
오류를 찾을 수 있나요? version이 null일 수 있기 때문에 major와 minor도 모두 null일 수 있어요. 이 버그는 여기에서 따로 놓고 보면 찾기 쉬워 보이지만, 실제로는 Flutter 저장소처럼 엄격한 코드 리뷰 프로세스를 써도 이런 코드는 늘 빠져나가요. null 안전성을 쓰면 정적 분석이 이 문제를 즉시 잡아줘요.
그건 아주 단순한 오류였어요. Google 내부에서 코드에 null 안전성을 일찍 사용하면서, 훨씬 더 복잡한 오류들이 잡히고 null 안전성을 통해 해결되는 것을 봤어요. 몇 가지 예시를 들어볼게요.
- 한 내부 팀은 null 안전성이 결코 null일 수 없다는 걸 아는 코드에서 null 값을 확인하느라 자주 시간을 보내는 자신들을 발견했어요. 이 문제는 protobuf를 쓰는 코드에서 가장 자주 나타났는데, 선택 필드(optional field)가 설정되지 않으면 null이 아니라 기본 값을 반환하기 때문이에요. 그래서 기본 값과 null 값을 혼동하면서 잘못 기본 조건을 확인하게 됐죠.
- Google Pay 팀은 Flutter 코드에서
State객체를 Widget 문맥 밖에서 접근하려다 실패하는 버그를 찾아냈어요. null 안전성 이전에는 그런 접근이 null을 반환해 오류를 가렸지만, null 안전성에서는 사운드 분석이 그 속성이 절대 null이 될 수 없다고 판단해 분석 오류를 냈어요. - Flutter 팀은
Window.render()의scene매개변수에 null이 전달되면 Flutter 엔진이 잠재적으로 충돌할 수 있는 버그를 찾아냈어요. null 안전성 마이그레이션 중에Scene을 non-nullable로 표시하는 힌트를 추가했고, null이 전달되면 일어날 수 있는 잠재적 앱 충돌을 쉽게 막을 수 있었어요.
컴파일 중 사운드 null 안전성 활용하기
Dart null 안전성의 soundness에는 또 하나 반가운 함의가 있어요. Dart 컴파일러가 nullability 정보를 활용할 수 있다는 거죠. 이걸 통해 프로그램이 더 작고 빨라질 가능성이 있어요. 아직 sound null 안전성으로 완전히 마이그레이션된 실제 앱이 많지는 않지만(지금 막 앱들이 의존하는 패키지의 생태계 마이그레이션을 시작했으니까요), 핵심 프레임워크에서 매우 고무적인 결과를 보고 있어요.
최근에 hello_world 샘플을 다시 컴파일해서 앱 크기에 null 안전성이 미치는 영향을 측정해 봤어요. 이것은 단순히 "hello world"만 표시하는 최소한의 예시예요. 컴파일된 코드의 전체 크기를 비교했더니, sound null 안전성으로 다시 컴파일한 것만으로 (장치에 설치된) 압축되지 않은 코드 크기가 3.5% 줄었어요. 이 앱이 코드 10줄뿐임에도 가능했던 이유는 포함된 모든 라이브러리의 코드 크기가 줄었기 때문이에요. 예를 들어 Flutter 프레임워크 자체(package:flutter)는 3.9% 줄었어요.
코드 속도에 관해서는, sound 타입 시스템을 강제하는 게 잠재적으로 오버헤드를 더할 수 있어요. 하지만 null 검사가 줄어드는 것도 코드를 더 빠르게 만들 잠재력이 있어요. 벤치마크에 대한 우리의 초기 분석은 성능이 이전 릴리스와 비슷하고, 새로운 추가 타입 정보가 미래에 새로운 종류의 성능 개선을 할 가능성을 만들어 준다는 걸 보여줘요. 성능 작업에 대해서는 향후 블로그 글에서 더 많이 쓸 계획이에요.
어떤 경우에는 null 안전성이 성능 향상으로 이어지는 걸 이미 봤어요. 보통 null 안전성으로의 마이그레이션이 코드 로직의 결함을 드러낼 때 그랬어요. 예를 들어 Flutter 웹 텍스트 레이아웃 캐시에서 이슈를 발견했어요. 이 캐시는 nullable 키를 사용했고, null일 때 TextAlign.start를 쓰는 로직이 있었어요. 이 로직이 캐시에 결함을 일으켜, 요소들이 여전히 기본 값을 가지고 있는데도 바뀐 것처럼 보이게 만들었어요. 그 결과 잦은 캐시 미스가 발생했죠. non-nullable textAlign getter를 추가하면서 캐싱 결함을 고치는 데 도움이 됐고, 텍스트가 캐시된 경우 텍스트 렌더링 성능이 14배 증가했어요.
오늘 시작하세요!
null 안전성을 담은 Dart와 Flutter의 베타 버전이 오늘 준비됐어요. Flutter로 개발한다면 flutter channel beta 다음에 flutter upgrade로 베타로 전환할 수 있어요. 그렇지 않다면 Dart SDK 아카이브에서 독립형 Dart SDK를 받을 수 있어요.
패키지를 개발한다면 마이그레이션 가이드를 읽고 마이그레이션을 계획하시길 권해요. 이슈나 제안이 있으면 알려주세요.
앱 개발자라면 이 기능이 stable 채널에 오를 때까지 마이그레이션을 미루는 게 더 나을 수도 있어요. 우리는 베타에서 나온 피드백을 빠르게 처리하고 남은 이슈를 고칠 계획이에요. null 안전성이 안정 릴리스에 언제 들어갈지 구체적인 일정을 말하긴 어렵지만, 내년 초로 생각하고 있어요.
Dart를 더 견고한 언어로, Flutter를 더 강한 프레임워크로 만드는 작업에 대한 여러분의 지원과 피드백에 감사드려요!
더 알아보기
- Null 안전성 마이그레이션 가이드 — 패키지와 앱을 null safe로 옮기는 전체 절차
- Null 안전성 원칙 — null 안전성 도입 시기와 기본 원칙