00. Dart 개요, 키워드, 변수 - Dart

Shawn Kang·2022년 7월 23일
0

Dart

목록 보기
1/4
post-thumbnail

글을 열며

최근에 Flutter를 잡을 일이 많았다. 학교랑 SW마에스트로에서 맡은 프로젝트가 다 Flutter 기반이었기 때문. 그런데 Flutter를 만지다 보니까 Dart의 언어적 특성을 이해하지 못해 발생하는 오류에 막히는 경우가 잦았다. 대표적으로 Dart의 Null Safety와 비동기 처리 등...

그래서 이왕 Flutter를 본격적으로 써 보기로 한 김에, 그 기반에 해당하는 Dart 역시 제대로 파 보자는 생각을 하게 되었다. 그리고 그 첫 걸음이 이 게시글.

일단 당장 프로젝트 개발을 시작해야 하는 입장에서 한 언어를 처음부터 판다는 건 고된 일이겠지만, 그래도 중간에 뭐 하나 빼먹어서 계속 헤메는 것보다는 아예 시작부터 확실하게 배우고 가는 게 어떻게든 도움이 될 것이라고 생각한다.

그리고 새로운 것을 배우는 일은 언제나 즐거우니까.

(모든 내용은 Dart 공식 문서를 바탕으로 작성하였다.)


용어 정리

처음에는 영어 단어를 직접 번역해서 쓸까 하다가, 그렇게 되면 Dart 개발자의 의도를 해칠 수도 있을 것 같아서 웬만해선 원문 그대로 쓰기로 했다. 그럼에도 혹시나 해서 몇 가지 용어에 대해 미리 정리를 좀 하고 가려고 한다.

  • nullable: null을 포함할 수 있는

  • non-nullable: null을 포함할 수 없는

  • late initialization: 초기화 지연, 특정 리소스가 실제로 필요할 때에 초기화하는 방식이다. 왜 이 방식을 사용하는지는 이 글 뒤편에서 설명 예정.

  • expression: 런타임에 특정 값을 가지는 코드 (ex. 조건식)

  • statement: 런타임에 특정 값을 가지지 않는 코드 (ex. if-else문)

  • spread 연산자: ...variable 또는 ...?variable의 형태이며, List를 포함한 Dart의 Collection 자료형에 사용할 경우, variable에 해당하는 값들을 모두 ... 연산자가 포함된 Collection 자료형에 이어 붙인다.
    List 끝에 다른 List를 그대로 갖다 붙이는 Python의 List.extend()와 비슷한 느낌.

List<int> a = [1, 2, 3];
List<int> b = [...a, 4, 5, 6]; // 리스트 b = [1, 2, 3, 4, 5, 6]
  • Collection if: [1, 2, 3, if (condition) 4]의 형태이며, List를 포함한 Dart의 Collection 자료형에 사용할 경우, 특정 조건을 만족할 때 조건문 뒤에 붙은 변수를 Collection에 포함한다.
    위 코드를 예시로 보면, condition이 충족될 경우 4가 리스트에 포함되는 식으로 동작한다.

Expression과 statement에 대해서는 바로 아래에서 상세히 설명할 예정이므로, 설명이 부족하다고 느껴지는 분께서는 조금만 참아주시길 바란다.


Dart의 중요한 개념

이 파트에서는 Dart를 본격적으로 파기 전, Dart를 사용함에 있어 가장 중요한 개념 몇 가지를 먼저 소개한다. 언어 전반을 관통하는 정말 중요한 내용이 많으니 세심하게 볼 필요가 있는 부분이다.

  • 사용자가 변수에 할당하는 모든 것들은 '객체'이며, 모든 객체는 '클래스'의 인스턴스다. 숫자, 함수, 심지어는 함수와 null까지도 다 객체라는 말이다. 단, null을 제외한 모든 객체는 Object 클래스로부터 상속되어진다.

  • Dart는 나름 자료형에 엄격한 편이지만, 자료형을 명확히 명시할 필요는 없다. 왜냐하면 Dart에서 자체적으로 자료형을 추측할 수 있기 때문.
    (다만, 이는 관례적으로 final 또는 const에만 적용되는 듯하다. 그렇지 않은 일반적인 변수에는 C/C++처럼 자료형을 명시하는 것 같다. 나도 그렇게 하는 편이고... 아무튼 참고하기 바란다.)

  • 만약 사용자가 Null Safety를 활성화할 경우, null을 포함할 수 있다고 별도로 표시하지 않는 한 모든 변수에는 null이 할당될 수 없다. 변수를 nullable하게 만들기 위해서는 그 변수의 자료형 뒤에 물음표(?)를 붙여주면 된다. 예를 들어 int? 자료형에는 정수도, null도 할당될 수 있다.
    만약 사용자가 보기에 특졍 Expression에 절대로 null이 들어갈 일이 없는데 Dart가 이에 동의하지 않을 경우, 사용자는 느낌표(!)를 붙여 그 Expression에 절대 null이 할당될 일이 없음을 명시할 수 있다. 예를 들어, 이렇게: int x = nullableButNotNullInt!;

  • 모든 자료형이 허용되는 상황을 명시하고 싶을 경우, Object?, Object 자료형을 활용하자. 만약 자료형 확인을 컴파일 전이 아니라 런타임 이후로 미루고 싶을 경우에는 dynamic 자료형을 활용할 수 있다.

  • Dart는 제네릭 자료형을 지원한다. 정수를 저장할 수 있는 List<int>나 모든 자료형을 다 담을 수 있는 List<Object>처럼.

  • Dart는 최상위 수준 함수(main()과 같은)를 지원한다. 클래스나 객체에 종속된 함수도 선언 가능하며(static 또는 인스턴스 함수 등), 함수 안에서 함수를 선언할 수도 있다(nested 또는 지역 함수 등).

  • 위와 비슷하게, Dart는 최상위 수준 변수를 지원한다. 클래스나 객체에 종속된 변수도 선언 가능하다(static 또는 인스턴스 변수 등). 인스턴스 변수는 가끔 fields나 properties로 불리기도 한다.

  • Java와는 다르게 Dart에는 public, privateprotected 키워드가 없다. 다만 언더스코어(_)로 시작하는 변수는 그 변수가 선언된 소스 코드에 대해서 private해진다.

  • 식별자는 문자나 언더스코어(_)로 시작할 수 있다. 첫 문자 뒤에는 문자, 언더스코어(_)에 숫자까지 가능하다.

  • Dart에는 Expression(런타임에 특정 값을 가지는)과 Statement(그렇지 않는)라는 것들이 있다. 예를 들어, 특정한 값을 갖지 않는 if-else Statement(쉽게 말해 if 문)과 비교했을 때, 조건 Expression(쉽게 말해 조건식)에 해당하는 contition ? expr1 : expr2는 런타임에 expr1 또는 expr2 중 하나의 값을 갖게 된다.
    Statement는 하나 이상의 Expression을 포함할 수 있지만, Expression이 직접적으로 Statement를 포함하는 것은 불가능하다.

  • Dart tools는 두 가지 문제를 사용자에게 보고한다: '경고' 및 '오류'다. Dart는 경고에 대해서는 사용자의 코드가 동작하지 않을 수도 있다는 건 알려도, 그 코드를 실행하는 것까지는 막지 않는다. 오류는 컴파일 시, 그리고 런타임에서 발생할 수 있다. 컴파일 시 발생하는 오류는 코드의 실행을 직접적으로 막으며, 런타임에 발생하는 오류는 여러분이 익히 알고 있을 '예외'에 해당한다.

여담

세 번째 항목에 "Null Safety가 활성화되어 있을 경우..."라는 표현이 있다. 여기서 아마 "어떻게 Null Safety를 켜고 끌 수 있는 걸까?"라는 의문이 들 수도 있을 것이다. 조금 더 정확히 설명을 하면, Dart에는 원래 Null Safety가 없었지만 2.0으로 버전업되면서 생기게 되었다. 그래서 2.0 이후 Dart에서는 컴파일 전에 설정을 통해 Null Safety를 끌 수도 있다.

이걸 끌 수 있게 풀어 준 이유를 추측해보자면, 2.0 이전에 개발된 외부 라이브러리들에는 Null Safety가 적용되지 않았을 거고, 따라서 2.0 이후에는 그 라이브러리들을 사용할 수 없게 된다. 아마 이러한 부분에서 호환성과 사용자의 편의성을 확보하기 위해 풀어 준 게 아닐까 한다. (오피셜은 아님)

Null Safety를 끄려면 Dart 소스 코드를 실행하기 전, 터미널에 --no-sound-null-safety를 붙여 주면 된다고 한다. Flutter도 마찬가지다. 아래 예시처럼 말이다:

dart --no-sound-null-safety run source-code.dart
flutter run --no-sound-null-safety

Dart의 Null Safety에 대한 자세한 내용은 Understanding null safety | Dart를 참고하기 바란다. 중요한 내용이라 꼭 읽어보는 것을 추천.


키워드

Keywords | Language tour | Dart를 참고하자.

당연히 Dart에도 언어적 필요성에 의해 선점된 키워드가 존재한다. 크게 중요한 내용은 아니라 위의 링크로 대체하도록 하겠다.


변수

초기 값

nullable 변수를 초기화하지 않을 경우, 그 변수의 초기 값은 null로 설정된다.

만약 Null Safety가 활성화되어 있을 경우, 사용자는 non-nullable 변수를 선언함과 동시에 반드시 초기화를 해 주어야 한다. 지역 변수의 경우는 선언과 동시에 초기화할 필요는 없으나, 그 변수가 사용되기 전까지는 값을 할당해야 한다.

최상위 수준 변수와 클래스 변수에는 late initialization이 적용된다. 해당 변수들의 초기화 코드는 프로그램의 진입점이 아니라, 그 변수가 처음 사용되는 시점에서 실행된다.

late 키워드

Dart 2.12에서 추가된 키워드. 이 키워드는 아래와 같은 두 가지 상황에서 사용된다:

  • non-nullable 변수를 쓰고는 싶지만, 선언 이후에 초기화를 하고 싶을 때
  • 특정 변수에 late initialization을 적용하고 싶을 때

위에서 봤듯이, non-nullable 변수는 반드시 선언과 동시에 초기화를 해 주어야 한다. 이런 강제성을 풀고 조금 유연하게 사용하고 싶을 때 late 키워드를 사용하는 것. 재미있는 점은 late 변수를 사용하면서 선언과 동시에 초기화를 할 경우, 그 변수의 초기화는 변수가 처음 사용되는 시점에서 진행된다는 거다.

late int intValue = 3;

이런 late initialization는 두 가지 이유에서 사용자에게 유리하다:

  • 변수가 당장 필요하지 않을 수도 있고, 그런 변수를 코드 실행과 동시에 초기화하는 것에는 비용이 필요함.
  • 객체의 변수를 초기화하기 위해서는 그 변수에 해당하는 this에 접근해야 하기 때문.

finalconst 키워드

만약 사용자가 특정 변수의 값을 바꿀 생각이 없다면, 특정한 자료형을 지정하는 대신 final이나 const 키워드를 사용할 수 있다. 바로 아래 코드처럼:

// 일반적인 접근
const int intValue = 3;

// 더 좋은 접근
const intValue = 3;

두 키워드의 특징은 아래와 같다:

  • final 변수의 값은 최초 1회만 설정 가능
  • const 변수는 컴파일 단계 상수이며, 암시적으로 final 변수이기도 함

Dart 공식 문서는 const 변수가 클래스 수준의 변수라면, 그 변수를 static const로 선언할 것을 추천하고 있다.

또한 사용자는 어떤 const 변수를 선언 후 초기화할 때, 그 변수의 값을 나타내는 expression에는 const 키워드를 생략할 수 있다. 이게 말로만 들으면 잘 와닿지 않을 텐데, 아래 코드를 보면 바로 이해가 갈 거라고 생각한다:

// 일반적인 접근
const intList = const [1, 2, 3];

// 더 좋은 접근
const intList = [1, 2, 3];

Dart 공식 문서에서는 불필요한 const 키워드의 낭비를 방지하자는 의미에서 저렇게 코드를 작성하는 것을 추천하고 있다.

또한 특정 변수가 final 또는 const가 아닐 경우, 사용자는 그 변수의 값을 마음대로 바꿀 수 있다. 당연한 얘기를 왜 꺼내냐고? 왜냐하면 만약 그 변수의 '값'이 final 또는 const라고 해도 바꿀 수 있기 때문이다. 다시 말해 final 또는 const 키워드는 변수 따로, 변수에 할당되는 값에 따로 적용된다는 말.

// 값 재할당 가능
List<int> intList = const[1, 2, 3];
intList = [4, 5, 6]; // 오류 안 나고 재할당 실행됨

// 값 재할당 불가능
const intList = [1, 2, 3];
intList = [4, 5, 6]; // 여기서 오류 발생

또한 사용자는 자료형 확인과 캐스트(is 그리고 as), 컬렉션 if, 그리고 spread 연산자를 활용해 상수를 선언할 수도 있다. 각각의 요소에 대한 예시는 아래 코드에 적어두도록 하겠다:

// 1. 캐스트(as) 활용
const Object i = 3;
const list = [i as int]; // Dart는 list를 List<int> 자료형으로 인식함

// 2. is와 Collection if 활용
const map = {if (i is int) i: 'int'};

// 3. spread 연산자(...) 활용
const set = {if (list is List<int>) ...list};

Dart & Flutter 관련 유용한 웹 페이지

공식 문서들이다:

그 외 유용한 다른 것들:

  • DartPad: Dart에서 공식적으로 제공하는 웹 기반 개발 환경이다. 간단한 소스 코드를 적고 빠르게 테스트를 돌려봐야 할 때 유용하다.
  • pub.dev: Dart의 외부 패키지를 확인해볼 수 있는 곳이다. http 등 유용한 패키지의 Documentation과 설치 방법 등을 찾아볼 수 있다.
  • Icons class - Flutter API: Flutter에서 지원하는 모든 Icon 이미지를 한 번에 볼 수 있는 웹 페이지다. 아이콘이 정말 많고 디자인도 다 이쁘지만, 웹 페이지에 이미지가 잔뜩이라 로딩하는 데 시간이 조금 걸릴 수는 있다.

마치며

원래는 이 게시글에 자료형 내용까지 같이 적으려고 했는데, 생각보다 분량이 많아져 자료형은 다음 글로 넘기도록 하겠다.

BOJ에서 Dart를 지원하는 그 날까지 화이팅.

그리고 언제나 틀린 내용에 대한 지적은 환영입니다!

profile
i meant to be

0개의 댓글