본문 바로가기

Dart & Flutter/Dart

Null safety in Dart/Flutter

오늘 포스팅 할 내용을 Flutter 를 사용하면서 알게된 Dart언어의 Null safety 문법에 대해서 알아본다.

 

Index

  • Null 이란?
  • Null safety 란? 
  • Question mark 사용방법 
  • Exclamation mark 사용방법

Null 이란?

  • Null 은 값이 없음'을 나타내는 영단어! 하지만 헷갈리면 안된다. 값이 없다라는게 0이 아니라 그냥 존재하지도 않는 것이다.
  • 아래의 이미지를 보면 화장실에 두루마리 휴지가 없다 하지만 왼쪽은 휴지심이 있고 휴지가 떨어져서 0이 됐지만 오른쪽은 휴지심지 조차 안보인다.
  • 왼쪽상태 = 0 / 오른쪽 상태 = Null 
  • 오늘의 주제에 맞게 Null을 지뢰라고 생각해 보자. 지뢰는 분명 너무나 위험하다. 하지만 보이지 않는 곳에 존재한다. 그리고 우리가 예측하지 못 한 장소(서비스 되는 앱/웹)와 시간에 폭발해 버릴 수 있다. 

0 과 null 

이미지 출처 : 나무위키

Null safety 란? 

  • 단어 뜻 그대로 Null(지뢰)에 대해서 안전하다 라고 이야기 한다.  개발을 조금이라도 접해본 사람은 Runtime에 Null pointer Exception 을 만나본 경험이 있을거라 생각한다.  개발이나 테스트 중 운 좋게 이런 에러를 만나서 운영에 베포하기 전에 고치면 좋지만 Null pointer Exceipton 은 기존 대부분의 언어에서는 컴파일 타임에 잡아내지 못하기 때문에 잠재적인 위험성을 갖게 된다.
  • Dart에서는 이런 지뢰같은 Null을 우리의 애플리케이션에 심는 걸 엄격하게 막고있다. 아니! 기본적으로 우리가 만드는 앱에 지뢰는 단 하나도 존재할 수 없다.
  • Null safety 덕분에 더욱 안정적인 서비스를 만들 수 있다.

 

Question mark ( Nullable )

  • Dart는 기본적으로 변수 선언에 대해서 null을 허용하지 않는다. 만약 변수를 선언하고 초기화 하지 않았거나 직접적으로 null 을 넣는다면 컴파일 에러가 난다. 하지만 가끔 널을 허용해야 되는 예외적인 상황이 있을 수 있다. 그럴 때 변수타입 마지막에 "?" 를 붙여주면 해당 변수는 null을 허용하도록 우리가 예외적으로 선언하는 것이다. 아래 총 4가지의 예제가 있다. 첫번째 예제는 Dart 언어 2.0 이전에 Null Safety가 적용되기 전 코드부터 하나하나 알아가 보자.

1. Null safety 적용 전 (dart 2.12미만버전) 기존코드

Non - Null safety (정상)

1-1.  (3번라인) name 변수를 선언하고 초기화 하지 않았다. 

1-2.  (4번라인) name 변수를 print() 메소드로 콘솔에 출력한다.

1-3.  Console에 에러없이 null이 콘솔에 출력된다.

 

2. Null safety 적용 후 (dart 2.12이상버전) 기존코드 에러

Null safety (컴파일에러)

2-1.  (3번라인) name 변수를 선언하고 초기화 하지 않았다. 

2-2.  (4번라인) name 변수를 print() 메소드로 콘솔에 출력한다.

2-3.   Console에 아무것도 출력되지 않는다.

 

Null safety 적용 후 예제의 에러 를 보자. ( The Non-nullable local variable 'name' must be assigned before it can be used. ) 변수를 사용하기 전에 할당해야 한다. 그렇지 않으면 컴파일 타임에 에러를 내기 때문에 개발자는 null의 위험성이 어디에 있는지 사전에 인지할 수 있다.

 

 

3. Null safety 적용 후 (dart 2.12이상버전) 선언과 초기화의 분리 예제

Null safety (정상)

3-1. (3번라인) name 변수를 선언하고 초기화 하지 않았다. 

3-2. (4번라인) name 변수에 "debaeloper" 라는 문자열을 넣고있다.

3-3. (5번라인) name 변수를 print() 메소드로 콘솔에 출력한다.

3-4. Console에 에러없이 "debaeloper"가 콘솔에 출력된다.

 

이번에는 초기화를 다음 라인에 넣고 실행한 결과이다.  변수가 사용되기 전에 초기화를 했기 때문에 에러가 나지않고 name변수의 값을 정상으로 출력하는 걸 확인할 수 있다. Non nullable 이기 때문에 name변수를 사용하기 전에 꼭 초기화 해야한다.

 

4. Null safety 적용 후 (dart 2.12이상버전) Null 허용하기 

Null safety (정상)

4-1.  (3번라인) name 변수를 선언하고 초기화 하지 않았다. 

4-2.  (4번라인) name 변수를 print() 메소드로 콘솔에 출력한다.

4-3.  Console에 에러없이 null이 콘솔에 출력된다.

 

3번 라인에 String을 선언할 때 "?" 를 넣어주면 해당 변수를 Nullable 하게 만들게 된다. 쉽게 말해서 우리가 원래 Null safety 전에 모든 변수는 기본이 Nullable 이였지만 2.0 이상부터는 Nullable 변수를 만들려면 꼭 "?" 를 선헌해야 된다.

 

Exclamation mark ( absolutely not null.)

"!" (Exclamation mark) : 절대 null이 아니야!! 걱정하지마!! 

뭔가 어리둥절 하다 Null safety하면 당연히 null이 아니지 뭔 개떡같은 소리인가... 싶기도 하지만 조금전 우리는 Null을 허용하는 방법을 배웠다.  위에 언급한 "?" 마크는 널을 허용 한다는 뜻이다. 그렇기 때문에 "!" 널이 허용되는 변수에 대해서 개발자가 "이 부분에서는 널이 아니니까 걱정하지마~! " 라고 하는 이야기와 같다고 생각하면 좋을 것 같다.

 

1. Nullable변수를 Non-Nullable 변수에 값을 넣는 예제

아래 예제를 살펴볼 예정인데 코드에 보면 1. 정상 / 2. 에러 / 3. 에러  이렇게 세 가지가 있다. "!" 를 언제,왜 사용을 하는지 세가지로 Dart문서에서 알려주는 예제를 가져와서 정리해 봤다. 

Exclamation mark 가 필요한 예제

  • 1. 정상 - 1번은  변수 a(Non Nullable)에  변수 couldBeNullButIsnt(Nullable)를 넣었는데 에러가 나지 않는다. 이유는 Nullable 변수에 해당하더라도 이미 초기화를 진행했기 때문에 해당 변수에 값이 있다는걸 인지하고 있어서 에러가 나지 않는다.
  • 2. 에러 - 2번은  변수 listThatCouldHoldNulls(Nullable) 값중 첫번째(index == 0) 값을 변수 b(Non Nullable)에 넣고있다 우리가 눈으로 보기에는 첫번째 값 2가 들어 있는게 보이지만 Dart에서는 List<int?> 로 Null을 허용하게 만들어 놓은 참조변수는 값이 있더라도 넣을 수 없다는 컴파일 에러가 난다.
  • 3. 에러 - 3번은 화살표 함수 couldReturnNullButDoesnt() 를 호출해서 -3을 return받는다. 그리고 그 값에 절대값을 씌우는 abs()를 호출하는데 이곳에서 에러가 난다. 이유는 화살표함수의 return 타입이 int? 로 Null을 허용하고 있기 때문에 abs()함수를 호출할 수 없다는 컴파일 오류가 난다.

위 예제를 보고 우리는 두 가지를 알 수 있다. 

  • 첫번째 - 참조변수가 아닌 Primetive 타입의 변수는 Nullable타입의 변수라 하더라도 사용하기 전에 값을 넣어주면 다른 Non-Nullable 변수에 값을 대입할 수 이다. 
  • 두번째 - 참조변수나 함수or메서드에 대해서는 return 타입으로 확인할 수 있기 때문에 return 되는 값이 100% null이 아니더라도 Nullable로 선언된 함수or메서드는 Non-Nullable변수에 값을 대입할 수 없다.

 

2. Nullable변수를 Non-Nullable 변수에 에러없이 값을 넣는 예제

 

위 예제에서 2, 3번의 컴파일 에러는 (소스가 절대 변하지 않고 외부에서 변경하지 않는다면...) null이 아닌 실제값을 항상 넣을 수 있음을 알 수 있다.  여기서 우리는 "!"를 써서 Dart에게 확실하게 null이 아니라고 알려주면 컴파일 단계에서 Null체크를 하지않고 무시한다.

 

" Dart야 걱정마 Nullable 이지만 이곳에서 사용할 때는 100% null이 아니고 값이 있어!! 내가 증명할게! 그러니까 컴파일 에러내지마!!"  라고 알려주면 Dart는 안심하고 컴파일 단계에서 null체크 없이 코드를 실행한다. (하지만 혹시라도 null이 들어오면 에러가 발생한다... 그건 개발자 책임... )

 

이제 드디어 "!"  사용 코드를 보자

위 예제를 보면 에러가 났던 2, 3번의 위치에 에러가 없다는 걸 확인할 수 있다.  Nullable 값 뒤쪽으로 "!"를 넣어주면 된다.

 

내생각

"?""!"를 최대한 사용하지 말고 개발을 진행 하는게 지뢰찾기 게임을 줄일 수 있는 방법이다.