일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- navigator
- 메이지유신
- overlay
- 이미테이션게임
- column
- 상대성이론
- 고사성어
- 한자
- 사자성어
- 제약
- 동아시아
- 정규표현식
- 앨런튜링
- 부호화
- 영화
- GridView
- 경황
- jetpack compose
- 조지레이코프
- 육서
- 튜링기계
- 근대화
- NATO
- IntrinsicWidth
- mixin
- 역사
- constraint
- swiftUI
- DART
- Flutter
- Today
- Total
인문주의 사피엔스
Flutter/Dart에서 Null Safety를 사용하는 이유 본문
null 안정성(safety)의 의미
문제의 발생
bool isEmpty(String string) => string.length == 0;
void main() {
String value;
isEmpty(value);
}
null 안정성(safety)이 적용되지 않은 이 간단한 코드는 null 안정성을 적용하지 않은 환경에서 문제 없이 컴파일됩니다. 그러나 코드를 실행하면 isEmpty 함수 내부의 string.length에서 null 참조 오류가 발생합니다.
불가능한 해결책
오류가 발생한 이유는 String value; 구문에서 value가 초기화되지 않았기 때문입니다. null 참조 오류를 막는 방법은 하나 뿐입니다. value를 참조하는 모든 곳에 null 여부를 확인하는 코드를 추가하는 것입니다. 하지만 그런 해결 방식은 비효율적입니다. 비효율을 넘어 코드의 양이 많아질수록 불가능에 가까워집니다.
근본적인 해결책
그래서 나온 해결책이 데이터 타입을 null을 허용하는 것과 허용하지 않는 것으로 분리하는 것입니다. 위의 소스코드에 null 안정성을 적용하면 코딩 단계에서부터 문법 오류를 발생시키기 때문에 아예 실행조차 할 수 없게 됩니다. null 안정성을 적용하면 null 값을 허용하는 타입과 허용하지 않는 타입이 서로 다른 타입으로 취급되기 때문입니다. 그래서 null 안정성은 실행 단계의 null 참조 오류를 코딩 단계의 문법 오류로 바꾸는 효과를 낳습니다. 결과적으로 null 참조 오류의 가능성을 상당폭으로 낮추게 됩니다.
Dart 2.12 문법
null 안정성은 Flutter 2.0 및 Dart 2.12부터 지원되기 시작했습니다. Dart 2.12에서 null 안정성의 적용을 위해 사용하는 문법 요소는 네 가지로 ?, !, required, late입니다.
데이터 타입에 null 허용 여부 표시하기
null 안정성이 적용되면 데이터 타입은 기본적으로 null이 허용되지 않습니다. 변수는 항상 값을 가집니다. 아래 코드에서 nonNullValue의 데이터 타입은 int입니다. int는 null을 허용하지 않습니다. 따라서 int로 정의된 nonNullValue는 반드시 값을 가지도록 초기화되어야 합니다. 초기화되지 않으면 문법 오류가 발생됩니다.
만약 null을 사용하고 싶다면 데이터 타입 뒤에 ?를 붙여야 합니다. nullableValue의 데이터 타입은 int?입니다. 따라서 반드시 값을 가질 필요는 없습니다. 초기화하지 않아도 문법 오류가 발생되지 않습니다.
class MyClass {
int nonNullValue; // syntax error
int? nullableValue;
}
null을 허용할지 말지의 여부는 전적으로 선택사항입니다. 따라서 별 신경을 쓰지 않고 null을 허용하기 시작한다면 결과적으로 null 안정성을 사용하지 않을 때와 차이가 없는 상황이 될 수 있습니다. 그러므로 null의 사용이 필수불가결한 경우가 아니라면 ?를 붙이지 않는다는 원칙을 세우고 따르는 것이 좋습니다.
변수 초기화
변수를 초기화하는 방법은 두 가지가 있습니다.
class MyClass {
int nonNullValue = 0;
late int nonNullValueLate;
}
nonNullValue처럼 초기화하는 것이 보통의 경우입니다.
경우에 따라 nonNullValueLate처럼 late를 붙여 초기화를 나중으로 미룰 수도 있습니다. 미루어진 초기화는 nonNullValueLate가 처음으로 참조되는 시점에 이루어집니다. 만약 처음으로 참조될 때 초기화가 되지 않으면 null 참조 오류가 발생됩니다. 오류의 가능성에도 불구하고 late로 초기화를 지연시키는 이유는 두 가지 경우로 설명될 수 있습니다. 하나는 변수를 선언하는 시점에는 초기값을 알 수 없지만 initState 함수 등을 통해 초기화가 반드시 보장되는 경우입니다. 또 다른 경우는 다음 코드에서 확인할 수 있습니다.
late String temperature = _readThermometer();
이 코드에서 temperature는 보기와 달리 프로그램이 시작될 때 초기화되지 않습니다. 프로그램이 실행된 후에 temperature가 처음으로 참조되는 시점이 되면 _readThermometer 함수가 호출됩니다. 만약 프로그램이 종료될 때까지 알고리즘 상 temperature가 참조되지 않는다면 _readThermometer 함수가 호출될 일도 없어집니다. 결과적으로 _readThermometer 함수를 호출함으로써 발생되는 시간과 메모리 등의 비용을 절약하게 됩니다.
required
함수의 지명 변수(named parameter)의 경우에는 초기화 대신 required를 사용할 수 있습니다. required가 붙은 지명 변수는 그 함수가 호출될 때 반드시 값이 입력되도록 강제됩니다.
void myFunction({
int& nullableValue,
int nonNullValueInit = 0,
required int nonNullValue,
})
{
}
myFunction을 호출할 때 nullableValue와 nonNullValueInit은 생략될 수 있습니다. 그러나 nonNullValue를 생략하면 컴파일러가 문법 오류를 발생시킵니다.
변수의 null 여부 확인하기
null이 허용된 변수의 값이 null인지 아닌지의 여부를 프로그램 실행중에 확인하는 방법은 다양합니다.
void myFunction({bool? nullableValue, Function()? callback, MyClass? myClass})
{
if (nullableValue!) {}
if (nullableValue ?? false) {}
callback?.call();
myClass?.member = 100;
}
알고리즘상 절대 null이 될 수 없다는 확신이 있다면 !를 사용할 수 있습니다. 또는 ??를 사용하여 null인 경우의 대체 값을 입력할 수도 있습니다. ?를 사용하여 null인 콜백 변수나 클래스 변수가 참조되지 않도록 방지할 수도 있습니다.
null 안정성 설정하기
Dart 2.12가 되기 전에 만든 Flutter 프로젝트에 null 안정성을 적용하려면 Dart 버전을 업그레이드하고 의존하는 외부 패키지(dependency)의 null 안정성 적용 여부를 확인해야 합니다. 우선 터미널에서 Dart 버전을 확인해서 2.12 이상으로 업데이트합니다.
$ dart --version
프로젝트가 외부 패키지(dependency)를 사용한다면 패키지의 null 안정성 여부를 확인합니다.
$ dart pub outdated --mode=null-safety
다음은 위 명령을 실행한 결과의 예입니다.
Dart 명령 도구에 관한 세부 내용은 문서를 참조합니다.
의존하는 외부 패키지(dependency) 업그레이드 하기
불가피한 경우가 아니라면 모든 패키지의 null 안정성을 확보해야 합니다. 이를 견고한 null 안정성(sound null safety)이라고 합니다. 견고한 null 안정성을 위해서는 위의 명령 실행 결과에서 모든 패키지의 상태가 최소한 Resolvable가 되어야 합니다.
기본적으로 null 안정성이 적용된 패키지와 아닌 패키지를 함께 사용할 수는 있습니다. 이를 불량한 null 안정성(unsound null safety)이라고 합니다. 불량한 null 안정성의 경우 당연히 가장 의존성이 낮은 패키지부터 시작해 단계별로 업그레이드해야 합니다.
어떤 경우이든 계속 진행하기로 결정했다면 외부 패키지들을 업그레이드합니다. 여기부터 프로젝트의 내용과 결과가 직간접적으로 변경되기 시작합니다.
$ dart pub upgrade --null-safety
$ dart pub get
Dart 2.12 적용하기
null 안정성을 적용한다는 것은 Dart 버전을 2.12로 변경하는 것입니다. pubspec.yaml 파일의 Dart 버전을 2.12 이상으로 설정하면 null 안정성이 적용됩니다.
environment:
sdk: ">=2.12.0 <3.0.0"
pubspec.yaml 파일을 직접 변경하면 수작업을 시작한다는 의미입니다. 나중에 설명할 변환 도구를 이용하면 pubspec.yaml 파일의 변경이 최종 결과가 됩니다. 어떤 경우든 Dart 버전을 2.12로 변경하면 기존 코드가 바로 null 안정성의 적용을 받아 null 허용 여부가 불분명한 코드에 문법 오류가 발생합니다. 따라서 Dart 2.12의 문법에 맞게 소스코드를 수정해야 합니다.
소스코드를 직접 수정했다면 코드 분석 명령을 실행해서 결과를 확인할 수 있습니다.
$ dart analyze
변환 도구 이용하기
Dart 2.12가 되기 전에 작성된 코드를 변환하기 위해 전용 도구를 이용할 수도 있습니다.
도구 실행하기
명령으로 도구를 실행합니다. (작업이 끝나고 명령을 종료하려면 Ctrl+C를 누릅니다)
$ dart migrate
위 명령은 웹브라우저와 통신하면서 변환 작업을 수행하는 로컬 호스트를 작동시키고 로컬 호스트의 주소를 출력합니다.
View the migration suggestions by visiting:
http://127.0.0.1:60278/Users/you/project/mypkg.console-simple?authToken=Xfz0jvpyeMI%3D
주소를 클릭하면 웹브라우저에 변환 도구가 표시됩니다. 변환 도구는 크롬 브라우저에 최적화되어 있기 때문에 다른 브라우저를 사용한다면 주소를 복사해서 크롬 브라우저에서 실행하는 것이 좋습니다.
도구 사용하기
앞에서 나온 문법 설명을 참고하면 도구의 사용은 직관적이고 쉽습니다. 'RETURN FROM SOURCES' 버튼을 누르면 브라우저 외부에서 수정된 소스코드를 다시 불러옵니다. 'APPLY MIGRATION' 버튼을 누르면 모든 소스코드와 pubspec.yaml 파일이 최종적으로 수정됩니다.
'Proposed Edits'에 제안된 수정 내용이 적당하지 않다면 'Edit Details'에 표시되는 버튼을 눌러 힌트 주석을 표시합니다. 힌트 주석은 'APPLY MIGRATION' 버튼을 누를 때 최종적으로 반영되기 때문에 버튼을 누르기 전까지는 기존의 프로젝트의 빌드에 영향을 주지 않습니다. 힌트 주석은 ?, !, late, required를 포함하는 직관적인 내용입니다.
'프로그래밍 > Flutter' 카테고리의 다른 글
Flutter에서 Overlay 클래스 사용하기 (0) | 2022.03.15 |
---|---|
Flutter의 제약(constraint) 이해하기 (0) | 2022.03.05 |
Flutter에서 AlertDialog에 GridView 포함하기 (0) | 2022.02.21 |
Flutter에서 IntrinsicWidth가 필요한 경우 (0) | 2022.02.17 |
Flutter 애니메이션의 이해와 구현 (0) | 2021.11.22 |