Sound null safety 발표
Sound null safety 발표
Dart 팀에게 오늘은 큰 이정표가 되는 날이에요. null safety 작업의 테크 프리뷰를 공개했기 때문이죠. null safety는 찾아내기 어려운 경우가 많은 특정 부류의 버그를 피하게 해 주고, 게다가 여러 성능 개선도 가능하게 해 줘요. 지금 조기 테크 프리뷰를 공개하니 여러분의 피드백을 기다리고 있어요.
이 글에서는 Dart 팀이 null safety를 도입할 계획을 설명하고, 우리가 말하는 sound null safety가 무엇인지, 그리고 그것이 다른 많은 언어가 취하는 접근 방식과 어떻게 다른지도 다룰게요.
본문
왜 null safety인가요?
Dart는 타입 안전(type-safe) 언어예요. 특정 타입의 변수를 얻으면 컴파일러가 그 변수가 그 타입임을 보장해 준다는 뜻이죠. 하지만 타입 안전만으로는 그 변수가 null이 아니라는 걸 보장해 주지 않아요.
null 오류는 아주 흔해요. GitHub를 검색해 보면 Dart 코드에서 null 때문에 생긴 이슈가 수천 개, 그걸 고치려는 커밋이 수만 개에 이르죠.
다음 예시 코드에서 null 가능성 문제를 찾아볼 수 있는지 한번 시도해 보세요.
void printLengths(List<File> files) {
for (var file in files) {
print(file.lengthSync());
}
}
이 함수는 null을 인자로 받으면 확실히 실패해요. 하지만 고려해야 할 두 번째 경우도 있어요.
void main() {
// Error case 1: passing a null to files.
printLengths(null);
// Error case 2: passing list of files, containing a null item.
printLengths([File('filename1'), File('filename2'), null]);
}
null safety 기능이 이 문제를 사라지게 해 줘요. null safety가 있으면 코드에 대해 더 자신 있게 추론할 수 있어요. 귀찮은 실행 시점 null 역참조 오류 더는 없고, 대신 코드를 작성하는 순간 정적 오류를 받게 되죠.
Sound null safety
Dart의 null safety는 sound(건전한)해요. 즉 위 예시에서 files 목록과 그 안의 요소들이 null일 수 없다는 걸 Dart가 100% 확신한다는 뜻이에요. Dart가 코드를 분석해서 어떤 변수가 non-nullable이라고 판단하면, 그 변수는 항상 non-nullable이에요. 디버거에서 실행 중인 코드를 들여다보면 non-nullable 성질이 실행 시점에도 유지되는 걸 확인할 수 있죠. 반면 일부 다른 구현은 unsound해서 많은 경우 여전히 실행 시점 null 검사가 필요해요. Dart는 Swift와 함께 sound null safety를 공유하지만, 그렇게 하는 프로그래밍 언어는 그리 많지 않아요.
Dart null safety의 sound함에는 또 반가운 함의가 있어요. 바로 프로그램이 더 작고 빨라질 수 있다는 거죠. Dart가 files가 절대 null이 아님을 정말 확신하니까 최적화할 수 있어요. 예를 들어 Dart AOT(ahead-of-time) 컴파일러는 변수가 null이 아니라는 걸 알면 null 검사를 추가할 필요가 없어서, 더 작고 빠른 네이티브 코드를 만들 수 있어요.
아주 고무적인 예비 결과도 확인했는데요. 전형적인 Flutter 프레임워크 렌더링 패턴을 흉내 낸 마이크로벤치마크에서 19%의 성능 향상을 보았어요.
설계 원칙
null safety의 상세 설계를 시작하기 전에 Dart 팀은 다음 세 가지 핵심 원칙을 정했어요.
- 기본적으로 non-nullable. 변수가 null일 수 있다고 명시적으로 알려 주지 않는 한, Dart는 그 변수를 non-nullable로 간주해요. 이걸 기본값으로 고른 이유는 API에서 non-null이 압도적으로 흔한 선택이었기 때문이에요.
- 점진적으로 도입 가능. 세상에는 Dart 코드가 아주 많아요. null safety로 부분별로 점진적으로 마이그레이션할 수 있어야 해요. 같은 프로젝트에 null-safe 코드와 non-null-safe 코드가 공존할 수 있어야 하죠. 마이그레이션을 돕는 도구도 제공할 거예요.
- 완전히 sound. 앞서 말했듯 Dart의 null safety는 sound해요. 프로젝트 전체와 의존성을 null safety로 마이그레이션하면 sound함의 완전한 이점을 누릴 수 있어요.
null safety로 변수 선언하기
핵심 문법은 꽤 간단해요. 다음은 서로 다른 방식으로 선언한 non-nullable 변수들이에요. 기억하세요, non-nullable이 기본이므로 이 선언들은 오늘날 것처럼 보이지만, 그 의미가 달라져요.
// In null-safe Dart, none of these can ever be null.
var i = 42;
final b = Foo();
String m = '';
Dart는 위 변수들 중 어느 것에도 null을 할당하지 못하게 해요. 1,000줄 뒤에서 i = null을 시도하면 정적 분석 오류와 빨간 물결선을 받고, 프로그램이 컴파일을 거부해요.
변수를 nullable로 만들고 싶다면 ?를 쓰면 돼요.
// These are all nullable variables.
int? j = 1; // Can be null later.
final Foo? c = getFoo(); // Maybe the function returns null.
String? n; // Is null at first. Can be null at any later time, too.
위 변수들은 오늘날 모든 변수와 똑같이 동작해요.
? 문법은 다른 곳에서도 쓸 수 있어요.
// In function parameters.
void boogie(int? count) {
// It's possible that count is null.
}
// In function return values.
Foo? getFoo() {
// Can return null instead of Foo.
}
// Also: generics, typedefs, type checks, etc.
// And any combination of the above.
하지만 다시 한번, 궁극적인 목표는 ?를 거의 쓰지 않게 되는 거예요. 대부분의 타입은 non-nullable일 테니까요.
null safety를 더 쉽게 쓰기
Dart 팀은 null safety를 가능한 한 쉽게 쓰게 하려고 노력하고 있어요. 예를 들어 if로 null 값을 확인하는 다음 코드를 보세요.
void honk(int? loudness) {
if (loudness == null) {
// No loudness specified, notify the developer
// with maximum loudness.
_playSound('error.wav', volume: 11);
return;
}
// Loudness is non-null, let's just clamp it to acceptable levels.
_playSound('honk.wav', volume: loudness.clamp(0, 11));
}
Dart가 그 if 문을 지나갈 무렵에는 loudness 변수가 null일 수 없다는 걸 알아챈다는 점에 주목하세요. 그래서 Dart는 아무 수고 없이 clamp() 메서드를 호출하게 해 줘요. 이런 편의는 flow analysis(흐름 분석) 덕분에 가능한데요, Dart 분석기가 마치 코드를 실행하는 것처럼 훑으면서 코드에 대한 추가 정보를 자동으로 알아내는 거예요.
항상 non-null 값을 할당하므로 Dart가 변수가 non-null임을 확신할 수 있는 또 다른 예를 볼게요.
int sign(int x) {
// The result is non-nullable.
int result;
if (x >= 0) {
result = 1;
} else {
result = -1;
}
// By this point, Dart knows the result cannot be null.
return result;
}
위의 할당 중 하나(예: result = -1; 줄)를 지우면 Dart는 result가 non-null임을 보장할 수 없어요. 정적 오류를 받고 코드가 컴파일되지 않죠.
flow analysis는 함수 안에서만 동작해요. 전역 변수나 클래스 필드라면 Dart는 언제 어떤 값이 할당될지 보장할 수 없어요. Dart가 애플리케이션 전체의 흐름을 모델링할 수는 없으니까요. 그래서 변수가 처음 읽히기 전에는 non-null임을 알지만 즉시 초기화할 수는 없을 때, 새 late 키워드를 쓸 수 있어요.
class Goo {
late Viscosity v;
Goo(Material m) {
v = m.computeViscosity();
}
}
v는 초기화되지 않은 상태로 시작하지만 non-null이라는 점에 주목하세요. Dart는 여러분이 non-null 값이 할당되기 전에 v를 읽으려 하지 않으리라 믿고, 코드는 오류 없이 컴파일돼요.
null safety는 하위 호환적이에요
Dart 팀은 null safety를 테크 프리뷰까지 끌어올리는 데 1년 넘게 일해 왔어요. Dart 2를 도입한 이후로 Dart 언어에 추가된 가장 큰 변화죠. 그런데도 이것은 파괴적 변경(breaking change)이 아니에요. 기존 코드가 null safety를 쓰는 코드를 호출할 수 있고, 그 반대도 가능해요. 설령 null safety가 제공된 뒤에도 그것은 여러분이 준비가 되면 도입할 수 있는 선택적 기능으로 남아요. 기존 코드는 변경 없이 계속 실행될 거예요.
최근에 Dart 핵심 라이브러리를 전면적으로 null safety를 쓰도록 마이그레이션했어요. 하위 호환성의 예로, 기존 테스트와 Dart/Flutter 테스트 환경에서 실행되는 테스트 앱에 아무런 깨짐 없이 핵심 라이브러리를 교체했죠. 새 핵심 라이브러리는 내부 Google 고객들의 운영 코드베이스에도 바로 배포해 아무 문제가 없었어요. 기능이 출시되면 우리는 모든 패키지와 앱을 null safety를 쓰도록 마이그레이션할 계획이에요. 여러분도 함께 하시길 바라요. 다만 여러분의 일정에 맞춰 패키지별, 앱별로 하실 수 있어요.
null safety 로드맵
null safety를 세 단계에 걸쳐 점진적으로 출시할 계획이에요.
- 테크 프리뷰. 오늘 출시되며, Dart의 dev 채널에서 사용할 수 있어요. 아래 "지금 바로 시도하기" 섹션에서 자세한 내용을 확인하세요. 아직은 변경될 수 있으니, 프로덕션 코드에는 아직 null safety를 쓰지 마세요. 그래도 테스트해 보고 피드백은 꼭 주세요!
- 베타 릴리스. null safety가 Dart의 beta 채널에서 제공되고, 더는 실험 플래그 뒤에 숨어 있지 않아요. 기능이 기대되는 최종 버전에 매우 가까워지죠. pub.dev 패키지나 플러그인을 관리하고 있다면 이때 마이그레이션을 시작할 수 있지만, 아직 안정 버전으로 게시해서는 안 돼요.
- 안정 릴리스. 모두가 null safety를 갖게 되고, 마이그레이션한 패키지와 플러그인을 안정 버전으로 게시하도록 권장받게 돼요. 프로덕션 앱도 마이그레이션해야 하죠.
모든 게 잘 풀린다면 올해 연말 전에 null safety를 안정 기능으로 출시할 계획이에요. 그 사이에 코드를 null safe로 만드는 데 도움이 되는 도구도 추가할 거예요.
- 기존 패키지와 앱을 업그레이드하는 여러 단계를 자동화하는 데 도움을 주는 마이그레이션 도구
- 패키지가 null safety를 지원하는지 알 수 있게 해 주는 pub.dev의 태그
- null safety를 지원하는 의존성의 최신 버전을 찾는 것을 지원하도록 확장된
pub outdated명령어
지금 바로 시도하기
지금 null safety를 가장 빠르게 시도하는 방법은 null safety가 활성화된 DartPad 버전인 nullsafety.dartpad.dev를 열어 보는 거예요. Learn with Snippets 드롭다운을 열면 새 문법과 null safety 기초를 다루는 일련의 학습 연습 문제를 찾을 수 있어요.
작은 명령줄 앱에서도 null safety를 시도해 볼 수 있어요. (아직 Flutter 같은 큰 프레임워크는 마이그레이션하지 않았어요.) 먼저 dev 채널 Dart SDK 사본을 받고, 이 샘플 Dart CLI 앱 사본(GitHub 저장소, zip 아카이브)을 받으세요. 샘플 앱의 README 파일에 null safety 실험 플래그로 앱을 실행하는 방법이 나와 있어요. 샘플의 다른 파일들은 VS Code와 Android Studio에서 디버깅을 활성화하는 실행 구성을 제공하고요.
다음 문서도 준비했고, 더 만들 계획이에요.
- Null safety 가이드
- null safety를 갖춘 핵심 라이브러리 API 참조
null safety를 Dart에 가져오게 되어 정말 기뻐요. sound null safety는 오류가 적은 코드를 작성하고 더 나은 성능을 얻는 데 도움이 되는 Dart의 독특한 기능이에요. 테크 프리뷰에서 이 기능을 직접 실험해 보시고, 이슈 트래커로 피드백을 주시길 바라요. 즐거운 코딩 되세요!
더 알아보기
- nullsafety.dartpad.dev에서 지금 바로 시도해 보기
- Null safety 가이드에서 더 자세히 배우기