DAY 04 | Dart 언어의 function과 class를 살펴보자

그 전에, null safety 먼저 살펴 보자

Java와 같은 전통적인 언어는 모든 자료형이 기본적으로 nullable이다. null 이 절대 들어가지 말아야 할 녀석도 nullable로 존재하다가 잠깐의 실수로 NullPointerException을 띄우기도 한다. Garbage Collection과 더불어 내가 Java를 꺼려하는 데 한몫한 것들 중 하나다. null의 존재는 null safety를 제공하는 언어까지만 타협하기로 했다. 그렇게 가장 대중적인 언어 Java를 거부하니 취업을 못 하지....

Dart는 null safety를 지원한다. 모든 type은 기본적으로 null 을 허용하지 않고, ? 를 붙여 nullable-type으로 만들어야 null 을 넣을 수 있게 된다. 타입 안정성을 유지한 채 null 을 사용하는 거다.

Dart 문법은 아니지만 union type을 사용하여 나타내 보면,

TYPE? = TYPE | null;
int? = int | null;

null 을 쓰는 게 좋냐 안 쓰는 게 좋냐 하는 건 개발자마다 입장이 갈린다더라. 난 null 사용을 지양하는 편이다. 애초에 null 이 존재하지 않는 언어를 좋아하니까. null 을 제공하는 언어들은 nullable에 대한 판단을 런타임에 수행하지만 null 을 아예 없애버린 Rust에서는 Option<T> 열거형을 통해 컴파일 시점에 명시적인 상태 처리를 해버린다는 게 내가 Rust를 좋아하는 이유 중 하나다. Rust가 좀 더 대중화되었으면 좋겠다(?). 아무래도 Dart/Flutter를 공부하고 있으면서도 역시 내가 만나본 언어들 중 내 추구미에 가장 맞아떨어지는 건 Rust란 말이지. 절차지향, 객체지향, 함수형 패러다임을 모두 적절히 차용한 Rust로 오세ㅇ…ㅛ…가 아니라, 난 Dart/Flutter 해야디…ㅎ

nullable variable 뒤에 ! 를 붙이면 일시적으로 non-nullable로 취급할 수 있다. int a;int? b; 에 대해 a = b!; 하는 식이다. 이러한 강제 형변환은 아무 때나 쓰지 말고 반드시 필요한 곳에서만 사용하자.

null safety와 관련하여 알아둘 연산자는 ???. 가 있다. null-aware operator ?? 를 사용하면 nullable variable에 null 이 들어 있을 때 사용할 기본값을 지정할 수 있다. conditional access operator ?. 를 사용하면 nullable object의 property나 method에 접근할 때 안전성을 확보할 수 있다.

NULLABLE_VALUE ?? DEFAULT_VALUE

NULLABLE_VALUEnull 이 아니라면 그 값을 그대로 사용하고, NULLABLE_VALUEnull 이라면 DEFAULT_VALUE 를 사용한다.

NULLABLE_OBJECT?.PROPERTY_OR_METHOD()

NULLABLE_OBJECTnull 이 아닌 경우에만 PROPERTY_OR_METHOD() 를 사용하고 NULLABLE_OBJECTnull 인 경우에는 그 뒤를 무시하고 null 을 반환한다.

NULLABLE_OBJECT!.PROPERTY_OR_METHOD()

NULLABLE_OBJECT 를 여기에서만 non-nullable 로 취급하여 null 이 아님을 강제한다.

가장 작은 모듈화, function

물론 function을 모듈화를 위해서만 쓰는 건 아니지만, function을 통해 모듈화를 할 수 있다. '함수'라고 번역되곤 하지만 여기선 function이라고 하겠다.

function은 기본적으로 statement의 연속이다. 외부 값을 parameter(매개변수)로 받아 사용한다. 함수를 호출하는 곳에서 argument(인자)로 전달한 값이 parameter로 들어간다. variable에 값을 대입하듯이 argument가 parameter에 대입된 채 function의 코드블록 내에 있는 statement가 실행되는 것이다. parameter는 function과 함께 선언되어 그 scope를 벗어나면 사라지는 local variable이다.

function은 정의만 되어 있으면 아무 의미 없고 호출을 하여 사용해야 한다. function뿐만 아니라 모든 코드는 메모리 영역에 올라가야 의미 있다.

Dart의 function에서 parameter를 받는 방식은 크게 세 가지가 있다.

positional parameter

전통적인 방식의 parameter로, 모든 positional parameter는 순서가 정해져 있고 필수적으로 전달해야 한다. nullable인 경우에도 비워두지 못하고 null 이라도 전달한다. 모든 argument는 전달한 순서대로 parameter에 들어간다.

void doSomething(String task, int? count) {
  // TASK
}

doSomething('eat', 3);
doSomething('sleep', null); // 안 쓸 거여도 전달은 해야 한다

optional positional parameter

기본적으로는 positional parameter인데 optional이 붙었다. 이 녀석은 argument로 전달하지 않아도 된다. 단, argument를 전달하지 않았을 경우에 null 이 들어갈지 default 값을 설정할지 정해야 한다. 대입 연산자를 통해 default 값을 설정할 수 있다. [] 로 묶어서 optional을 명시할 수 있으며, positional parameter와 함께 사용할 경우 반드시 positional parameter를 먼저 작성한 후 그 뒤에 optional positional parameter를 사용해야 한다.

optional인 parameter가 여러 개일 때 뒷 부분은 완전히 비워두어도 되지만, 중간에 빈 칸이 들어갈 경우에는 무엇을 비워 놓으려고 하는 것인지 명확하지 않기 때문에 null 을 전달해야 한다.

void doSomething(String task, [int? count, String status='안함']) {
  // TASK
}

doSomething('sleep');
doSomething('eat', 3); // == doSomething('eat', 3, '안함')
doSomething('study', 2, '하다 말았음');
doSomething('play game', null, '중간을 비울 거면 null을 넣어야 하지');

named parameter

named parameter는 Map 을 닮았다. {} 로 묶어서 사용하며 key: value 형태로 argument를 전달한다. parameter 이름을 key로 하여 순서와 상관 없이 사용할 수 있다. 기본적으로 required인 positional parameter와 달리 named parameter는 기본적으로 optional이기에 필수적인 값에 대해서는 required 를 붙여 주어야 한다. 선택적인 값에 대해서는 optional positional parameter와 마찬가지로 null 이 들어갈지 default 값을 설정할지 정해야 한다.

순서만 맞춰 주면 positional parameter와 optional positional parameter를 같이 쓸 수 있듯이 positional parameter와 named parameter도 positional parameter를 먼저 작성하면 같이 쓸 수 있다. 다만, Python 등의 언어와는 달리 optional positional parameter와 named parameter를 혼용하는 것은 지원하지 않는다. 사실 그것을 지원한다고 해도 섞여 있으면 헷갈리기만 하니 섞어 쓸 바에야 named parameter로 통일하도록 하자.

void doSomething({required String task, int? count, String status='안함'}) {
  // TASK
}

doSomething(task: 'sleep');
// == doSomething(task: 'sleep', count: null, status: '안함');
doSomething(status: '순서 상관 없음', task: 'play game');
// == doSomething(task: 'play game', count: null, status: '순서 상관 없음');

이름이 없는 함수도 있다! 언제 쓸까요, 라는 말에 무심코 "콜백 함수..."라고 반응해 버렸다. Button을 클릭하면 작동시킬 코드를 정의할 때 onclick 에 콜백 함수로 정의해 넣는 식이다. onclick 에 대입할 함수에는 굳이 이름을 안 붙여도 되니까 말이다. 여러 버튼에 같은 함수를 전달할 거면 Function 변수로 사용해도 되긴 한다. 물론 아직 class를 안 배웠기에(?) 이건 당장 살펴볼 영역은 아니긴 하다.

class를 살짝 살펴보자면,

function에 정의와 호출이 있다면 class도 정의와 생성이 있다. 정의만으로는 아무것도 하지 않고 생성을 해서 메모리 영역에 올려야 비로소 실제 사용할 수 있는 instance(혹은 object라고도 할 수 있다)가 된다.

object와 instance는 크게 다르지 않지만, 굳이 구분하자면 object는 좀 더 개념적인 영역이고 instance는 실제 메모리 공간을 차지하고 있는 녀석이다. Dart/Flutter 진영에서는 이들을 구분하지 않으므로 크게 신경쓰지 않아도 되지만. 우리는 object라고 하겠다. 모든 자료형의 시조 supar class Object 와 혼동하지 않게 주의하자.

class는 variable과 function을 합쳐 놓은 형태의 구조로 정의한다. class name, properties, constructor, method으로 구성된다. class name은 UpperCamelCase(PascalCase)로 작성한다. Dart의 constructor는 class 이름과 동일하며 생략 가능하며, 생략 시 내장 constructor가 사용된다.

class의 variable은 property라고 부르고 class의 function은 method라고 부른다. 좀 더 object 관점에서의 이름이다. method는 function과 똑같이 생겼지만 statement의 모듈화를 목적으로 한 function과 달리 property 제어를 목적으로 하고 있다.

class에 대한 건 다음 시간에 좀 더 본격적으로 살펴볼 예정이다!

그리고...

Dart에서는 class를 이렇게 사용하는군, 하는 걸 가볍게 몇 문제만 실습해 보았다. 아무래도 Rust의 struct & impl 와는 많이 다르겠지.

그리고 이제 프로젝트를 위한 아이디어를 떠올려야 한다. 취업 준비를 위한 자기소개서 및 이력서를 작성해 볼 필요도 있고. 실습 시간은 공식적인 실습보다는 프로젝트 준비와 취업 준비에 좀 더 집중해야 한다. 질문도 많이 하라고 하시는데... 개념은 아는데 그 개념의 이름은 모르는 경우가 많아서 뭘 어떻게 질문해야 할지는 잘 모르겠다. 개념의 이름을 알 경우에는 검색해서 설명을 읽어보면 대체로 이해되던데. 쉽지 않다.

어디서 아이디어 같은 게 툭, 하고 튀어나왔으면 좋겠다. 내가 안 쓰고 싶은 앱은 동기 부여가 안 되는데 내가 쓰고 싶은 앱도 떠오르지 않는다. 내가... 뭘 좋아하지? 이런 거 있었으면 좋겠다, 하는 것도 잘 안 떠오르고. 역시 늘 아이디어 떠올리는 게 가장 어렵다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글