다트 3 마이그레이션 가이드

다트 3 마이그레이션 가이드

기존 다트 코드를 다트 3과 호환되도록 마이그레이션하는 방법이에요.

출처: Dart 3 migration guide

본문

다트 3은 다트에 새로운 핵심 기능을 도입하는 주요 릴리스예요: 레코드(records), 패턴(patterns), 그리고 **클래스 수정자(class modifiers)**가 그것이에요.

이 새로운 기능들과 함께, 다트 3은 기존 코드를 깨뜨릴 수 있는 여러 변경 사항을 담고 있어요.

이 가이드는 다트 3으로 업그레이드한 뒤 마주칠 수 있는 마이그레이션 문제를 해결하는 데 도움을 줄 거예요.

서론

버전 미지정 변경 vs 버전 지정 변경

아래 나열된 잠재적 호환성 파괴 변경은 두 범주 중 하나에 속해요.

  • 버전 미지정 변경(Unversioned changes): 이 변경은 다트 3.0 SDK 이상으로 업그레이드한 후의 모든 다트 코드에 영향을 줘요. 이 변경을 "끄는" 방법은 없어요.
  • 버전 지정 변경(Versioned changes): 이 변경은 패키지 또는 앱의 언어 버전이 >= 다트 3.0으로 설정된 경우에만 적용돼요. 언어 버전은 pubspec.yaml 파일의 sdk 하한 제약(lower-constraint)에서 파생돼요. 다음과 같은 SDK 제약은 다트 3 버전 지정 변경을 적용하지 않아요.
environment:
  sdk: '>=2.14.0 <3.0.0'

하지만 다음과 같은 SDK 제약은 적용해요.

environment:
  sdk: '>=3.0.0 <4.0.0'

새로운 다트 3 기능을 사용하려면 언어 버전을 3.0으로 업데이트해야 해요. 그러면 동시에 다트 3 버전 지정 변경도 얻게 돼요.

다트 3 하위 호환성

다트 2.12 이상에서 널 세이프티를 사용한 많은 패키지와 앱은 다트 3과 하위 호환될 가능성이 높아요. 이는 SDK 제약의 하한이 2.12.0 이상인 모든 패키지에서 가능해요.

다트의 pub 도구는 상한이 3.0.0 미만 버전으로 제한되어 있어도 해석(resolution)을 허용해요. 예를 들어, 다음 제약을 가진 패키지는 다트 3.x SDK로 해석될 수 있어요. 하한 제약이 2.12 이상이면 pub이 상한 제약 <3.0.0<4.0.0으로 재해석하기 때문이에요.

environment:
  sdk: '>=2.14.0 <3.0.0'           # This is interpreted as '>=2.14.0 <4.0.0'

이 덕분에 개발자들은 이미 2.12 널 세이프티를 지원하는 패키지와 함께 다트 3의 사운드 널 세이프티를, 코드가 다른 다트 3 변경의 영향을 받지 않는 한 두 번째 마이그레이션 없이 사용할 수 있어요.

영향 테스트

소스 코드가 다트 3 변경의 영향을 받는지 파악하려면 다음 단계를 따라요.

$ dart --version    # Make sure this reports 3.0.0 or higher.
$ dart pub get      # This should resolve without issues.
$ dart analyze      # This should pass without errors.

pub get 단계가 실패하면, 의존성을 업그레이드해서 더 최신 버전이 다트 3을 지원하는지 확인해 보세요.

$ dart pub upgrade
$ dart analyze      # This should pass without errors.

또는 필요하다면 메이저 버전 업그레이드까지 포함해 보세요.

$ dart pub upgrade --major-versions
$ dart analyze      # This should pass without errors.

다트 3 언어 변경 사항

100% 사운드 널 세이프티

다트 2.12는 2년여 전에 널 세이프티를 도입했어요. 다트 2.12에서 사용자는 pubspec 설정으로 널 세이프티를 활성화해야 했어요. 다트 3에서 널 세이프티는 내장되어 있어서 끌 수 없어요.

범위(Scope) — 이 변경은 모든 다트 3 코드에 적용되는 버전 미지정 변경이에요.

증상(Symptom) — 널 세이프티를 지원하지 않고 개발된 패키지는 pub get으로 의존성을 해석할 때 문제를 일으켜요.

$ dart pub get

Because pkg1 doesn't support null safety, version solving failed.
The lower bound of "sdk: '>=2.9.0 <3.0.0'" must be 2.12.0 or higher to enable null safety.

2.12 미만의 언어 버전을 선택하는 언어 버전 주석으로 널 세이프티를 거부한 라이브러리는 분석 또는 컴파일 오류를 일으켜요.

$ dart analyze .
Analyzing ....                         0.6s

  error • lib/pkg1.dart:1:1 • The language version must be >=2.12.0.
  Try removing the language version override and migrating the code.
  • illegal_language_version_override
$ dart run bin/my_app.dart
../pkg1/lib/pkg1.dart:1:1: Error: Library doesn't support null safety.
// @dart=2.9
^^^^^^^^^^^^

마이그레이션(Migration) — 다트 3으로의 마이그레이션을 시작하기 전에, 앱 또는 패키지가 100% 마이그레이션되어 널 세이프티를 활성화했는지 확인해요. 이를 위해서는 다트 3 SDK가 아니라 다트 2.19 SDK가 필요해요. 앱이나 패키지를 먼저 널 세이프티를 지원하도록 마이그레이션하는 방법을 배우려면 널 세이프티 마이그레이션 가이드를 확인해 보세요.

기본값에 대한 콜론 문법

역사적인 이유로 명명된 선택 매개변수(named optional parameters)는 기본값을 : 또는 =로 지정할 수 있었어요. 다트 3에서는 = 문법만 허용돼요.

범위 — 이 변경은 언어 버전 3.0 이상에만 적용되는 버전 지정 변경이에요.

증상 — 다트 분석이 다음과 같은 오류를 만들어 내요.

line 2 • Using a colon as a separator before a default value is no longer supported.

마이그레이션 — 콜론 사용에서:

int someInt({int x: 0}) => x;

등호 사용으로 바꿔요.

int someInt({int x = 0}) => x;

이 마이그레이션은 수동으로 하거나, dart fix로 자동화할 수 있어요.

$ dart fix --apply --code=obsolete_colon_for_default_value

mixin

다트 3 이전에는, 선언된 생성자가 없고 Object 외의 상위 클래스가 없다면 어떤 클래스든 mixin으로 쓸 수 있었어요.

다트 3에서, 언어 버전 3.0 이상의 라이브러리에 선언된 클래스는 mixin으로 표시되지 않는 한 mixin으로 쓸 수 없어요. 이 제한은 해당 클래스를 mixin으로 사용하려는 어떤 라이브러리의 코드에도 적용돼요—후자 라이브러리의 언어 버전과는 무관해요.

범위 — 이 변경은 언어 버전 3.0 이상에만 적용되는 버전 지정 변경이에요.

증상 — 다음과 같은 분석 오류가 발생해요.

Mixin can only be applied to class.

분석기는 mixin 클래스도 mixin도 아닌 클래스가 with 절에 사용될 때 이 진단을 만들어 내요.

마이그레이션 — 클래스가 mixin으로 사용되도록 의도됐는지 판단해요.

클래스가 인터페이스를 정의한다면, implements를 사용하는 것을 고려해 보세요.

switch

다트 3.0은 switch 케이스를 상수 표현식 대신 패턴으로 해석해요.

범위 — 이 변경은 언어 버전 3.0 이상에만 적용되는 버전 지정 변경이에요.

증상 — switch 케이스에서 발견되는 대부분의 상수 표현식은 같은 의미를 가진 유효한 패턴이에요(명명된 상수, 리터럴 등). 이런 것들은 똑같이 동작하고 증상이 생기지 않아요.

유효한 패턴이 아닌 몇몇 상수 표현식은 invalid_case_patterns 린트를 촉발해요.

마이그레이션 — 케이스 패턴 앞에 const를 붙이면 원래 동작으로 되돌릴 수 있어요. 그러면 패턴으로 해석되지 않아요.

case const [1, 2]:
case const {'k': 'v'}:
case const {1, 2}:
case const Point(1, 2):

이 호환성 파괴 변경에 대한 빠른 수정은 dart fix를 사용하거나 IDE에서 실행할 수 있어요.

continue

다트 3은 continue 문이 루프(for, do, while 문)나 switch 멤버가 아닌 레이블을 대상으로 하면 컴파일 타임 오류를 보고해요.

범위 — 이 변경은 언어 버전 3.0 이상에만 적용되는 버전 지정 변경이에요.

증상 — 다음과 같은 오류를 보게 돼요.

The label used in a 'continue' statement must be defined on either a loop or a switch member.

마이그레이션 — 동작 변경이 괜찮다면, continuefor, do 또는 while 문에 붙어 있어야 하는 유효한 레이블 문을 대상으로 하도록 바꿔요.

동작을 유지하고 싶다면 continue 문을 break 문으로 바꿔요. 이전 다트 버전에서 루프나 switch 멤버를 대상으로 하지 않는 continue 문은 break처럼 동작했어요.

다트 3 핵심 라이브러리 변경 사항

제거된 API

호환성 파괴 변경 #49529: 핵심 라이브러리가 수년간 폐기(deprecated)된 API를 제거하도록 정리됐어요. 다음 API는 다트 핵심 라이브러리에서 더 이상 존재하지 않아요.

범위 — 이 변경은 모든 다트 3 코드에 적용되는 버전 미지정 변경이에요.

dart:core

  • 폐기된 List 생성자를 제거했어요(널 세이프티가 아니었기 때문). 리스트 리터럴(빈 리스트는 [], 빈 타입 리스트는 <int>[] 등)이나 List.filled를 사용해요. 이는 널 세이프티 코드가 아닌 경우에만 영향을 줘요—널 세이프티 코드는 이미 이 생성자를 쓸 수 없었거든요.
  • int.parse, double.parse, num.parse의 폐기된 onError 인자를 제거했어요. 대신 tryParse 메서드를 사용해요.
  • 폐기된 proxyProvisional 애너테이션을 제거했어요. 원래 proxy 애너테이션은 다트 2에서 효과가 없었고, Provisional 타입과 provisional 상수는 다트 2.0 개발 과정에서만 내부적으로 사용됐어요.
  • 폐기된 Deprecated.expires 게터를 제거했어요. 대신 Deprecated.message를 사용해요.
  • 폐기된 CastError 오류를 제거했어요. 대신 TypeError를 사용해요.
  • 폐기된 FallThroughError 오류를 제거했어요. 이 오류를 던지던 종류의 fall-through는 다트 2.0에서 컴파일 타임 오류가 됐어요.
  • 폐기된 NullThrownError 오류를 제거했어요. 이 오류는 널 세이프티 코드에서 절대 던져지지 않아요.
  • 폐기된 AbstractClassInstantiationError 오류를 제거했어요. 추상 클래스의 생성자를 호출하는 것은 다트 2.0에서 컴파일 타임 오류가 됐어요.
  • 폐기된 CyclicInitializationError를 제거했어요. 순환 의존은 더 이상 널 세이프티 코드에서 런타임에 감지되지 않아요. 그런 코드는 대신 StackOverflowError 같은 다른 방식으로 실패할 거예요.
  • 폐기된 NoSuchMethodError 기본 생성자를 제거했어요. 대신 NoSuchMethodError.withInvocation 명명 생성자를 사용해요.
  • 폐기된 BidirectionalIterator 클래스를 제거했어요. 기존의 양방향 이터레이터는 여전히 동작해요. 다만 뒤로 이동하는 특정 이름에 고정시키는 공유 상위 타입이 없을 뿐이에요.

dart:async

  • 폐기된 DeferredLibrary 클래스를 제거했어요. 대신 deferred as 임포트 문법을 사용해요.

dart:developer

  • 폐기된 MAX_USER_TAGS 상수를 제거했어요. 대신 maxUserTags를 사용해요.
  • 다트 2.0 이후로 깨져 있던 폐기된 Metrics, Metric, Counter, Gauge 클래스를 제거했어요.

dart:html

  • 이전에 발표한 대로, DocumentHtmlDocument의 폐기된 registerElementregisterElement2 메서드를 제거했어요. 자세한 내용은 #49536을 참조해요.

dart:math

  • Random 인터페이스는 이제 구현(implement)만 할 수 있고 확장(extend)할 수 없어요.

dart:io

  • vm_service:11.0.0에서 도입된 새로운 String id를 수용하도록 NetworkProfiling을 업데이트했어요.

증상 — 다트 분석(예: IDE 안에서, 또는 dart analyze/flutter analyze에서)이 다음과 같은 오류로 실패해요.

error line 2 • Undefined class 'CyclicInitializationError'.

마이그레이션 — 이 API들의 사용을 수동으로 마이그레이션해요.

extends & implements

다트 3은 클래스의 능력을 제한할 수 있는 새로운 클래스 수정자를 지원해요. 이것들은 핵심 라이브러리의 여러 클래스에 적용됐어요.

범위 — 이 변경은 언어 버전 3.0 이상에만 적용되는 버전 지정 변경이에요.

dart:async — 다음 선언은 구현(implement)만 할 수 있고 확장(extend)할 수 없어요.

  • StreamConsumer
  • StreamIterator
  • StreamTransformer
  • MultiStreamController

이 선언들 중 어떤 것도 상속할 구현을 담고 있지 않아요. 인터페이스로만 쓰이도록 interface로 표시된 거예요.

dart:coreFunction 타입은 더 이상 구현, 확장, 또는 믹스인할 수 없어요. 다트 2.0 이후로 implements Function을 쓰는 것이 하위 호환을 위해 허용됐지만, 아무 효과도 없었어요. 다트 3.0에서 Function 타입은 final이라 서브타입을 만들 수 없어서, 코드가 잘못해서 동작한다고 가정하는 것을 막아 줘요.

다음 선언은 구현만 할 수 있고 확장할 수 없어요.

  • Comparable
  • Exception
  • Iterator
  • Pattern
  • Match
  • RegExp
  • RegExpMatch
  • StackTrace
  • StringSink

이 선언들 중 어떤 것도 상속할 구현을 담고 있지 않아요. 인터페이스로만 쓰이도록 interface로 표시된 거예요.

다음 선언은 더 이상 구현하거나 확장할 수 없어요.

  • MapEntry
  • OutOfMemoryError
  • StackOverflowError
  • Expando
  • WeakReference
  • Finalizer

MapEntry 값 클래스는 이후 최적화를 가능하게 하려고 제한되어 있어요. 나머지 클래스들은 플랫폼과 밀접하게 결합되어 있어서 서브클래싱이나 구현 대상으로 의도되지 않았어요.

dart:collection — 다음 인터페이스는 더 이상 확장할 수 없고 구현만 할 수 있어요.

  • Queue

다음 구현 클래스는 더 이상 구현할 수 없어요.

  • LinkedList
  • LinkedListEntry

다음 구현 클래스는 더 이상 구현하거나 확장할 수 없어요.

  • HasNextIterator (또한 폐기됨.)
  • HashMap
  • LinkedHashMap
  • HashSet
  • LinkedHashSet
  • DoubleLinkedQueue
  • ListQueue
  • SplayTreeMap
  • SplayTreeSet

다트 3 도구 변경 사항

제거된 도구

역사적으로 다트 팀은 코드 포매팅(dartfmt), 코드 분석(dartanalyzer) 등을 위한 여러 작은 개발자 도구를 제공해 왔어요. 다트 2.10(2020년 10월)에서 우리는 새롭고 통합된 다트 개발자 도구인 dart 도구를 도입했어요.

범위 — 이 변경은 모든 다트 3 코드에 적용되는 버전 미지정 변경이에요.

증상 — 다트 3에서 이 작은 도구들은 존재하지 않고, 새롭게 통합된 dart 도구로 대체됐어요.

마이그레이션dart 도구에서 사용 가능한 새 하위 명령을 사용해요.

과거 도구 dart 대체 폐기 시점 중단 시점
stagehand dart create 2.14 2.14*
dartfmt dart format 2.14 2.15
dart2native dart compile exe 2.14 2.15
dart2js dart compile js 2.17 2.18
dartdevc webdev 2.17 2.18
dartanalyzer dart analyze 2.16 2.18
dartdoc dart doc 2.16 2.17
pub dart pub 2.15 2.17

널 세이프티 마이그레이션 도구

다트 3은 널 세이프티가 없는 코드를 지원하지 않으므로, 다음 널 세이프티 마이그레이션 명령이 제거됐어요.

  • dart migrate
  • dart pub upgrade --null-safety
  • dart pub outdated --mode=null-safety

범위 — 이 변경은 모든 다트 3 코드에 적용되는 버전 미지정 변경이에요.

증상 — 이 명령들은 실패할 거예요.

마이그레이션 — 널 세이프티로 마이그레이션하려면 다트 2.19를 사용해요.

분석기 구성

더 엄격한 검사를 활성화하기 위한 분석기 구성 옵션이 변경됐어요.

범위 — 이 변경은 모든 다트 3 코드에 적용되는 버전 미지정 변경이에요.

증상 — 이전 구성 옵션은 다음과 같은 경고와 함께 실패해요.

The option 'implicit-casts' is no longer supported.
Try using the new 'strict-casts' option.

마이그레이션 — 분석기 구성의 이 부분을:

analyzer:
  strong-mode:
    implicit-casts: false
    implicit-dynamic: false

다음으로 바꿔요.

analyzer:
  language:
    strict-casts: true
    strict-raw-types: true

기타 도구 변경 사항

  • 폐기된 Observatory가 기본적으로 숨겨졌어요. DevTools를 사용하는 것을 권장해요.
  • dart format fix 명령이 dart fix로 대체됐어요 (#1153).
  • 다트 웹 컴파일러를 위해 SDK에 번들된 스냅샷 파일이 정리됐어요 (#50700).
  • 일부 코드에서 dart format의 출력이 조금 바뀌었어요.
  • Windows에서 pub-cache의 기존 위치에 대한 하위 호환을 종료했어요. 다트 3 이전에는 %APPDATA%\Pub\Cache가 pub-cache의 폴백 위치였어요. 다트 3부터 기본 pub-cache는 %LOCALAPPDATA%\Pub\Cache에 위치해요. 전역으로 활성화한 패키지를 PATH에 추가했다면, PATH를 %LOCALAPPDATA%\Pub\Cache\bin을 포함하도록 업데이트하는 것을 고려해 보세요.

범위 — 이 변경은 모든 다트 3 코드에 적용되는 버전 미지정 변경이에요.

더 알아보기