
오전에는 class의 constructor에 대한 걸 다시 짚고 넘어가다가, 코딩과는 살짝 무관한, 하지만 SeSAC과 유관한 이야기를 했다. 아무튼 우리는 취업(혹은 창업)을 해야 하니, 어떻게 준비할 것인가. 이력서 포맷은 크게 중요한 건 아니니까 일단 준비해 보고 너무 아니다 싶으면 적당한 포맷을 추천해 주신다고 하시길래 처음부터 적당한 포맷을 추천해 주실 수 있는지 여쭤봤다. 기존에 만들어서 개선해 온 게 있는 사람은 그걸 쓰면 되지만, 이제 막 시작하는 사람 입장에서는 엄한 것보다는 적당한 것을 쓰는 게 나을 테니까.
사진이나 포맷 같은 건 엄청 중요한 건 아니지만 우리는 0.1점의 차이도 무시하지 않겠다는 태도로 준비해야 한다고. 스펙이 그걸 압도할 수준이 되면 괜찮지만 우리는 집요하게 다듬어야 한다고 하시는데 무슨 말인지는 알 것 같다.
아무튼 틈틈이 이력서/포트폴리오/자기소개서를 준비해 보자. Flutter랑은 무관하지만 요즘 한창 준비 중인 니트컴퍼니 전시의 방명록 시각화 웹 페이지도 포트폴리오에 넣고 해야지. (전시장 입구에서 사용자가 /register 등록 화면에 접속하여 등록하면 전시장 내부에 있는 / 전광판에 그 사람의 정보가 뜨고, 비밀번호 입력해서 들어가는 /admin 대시보드에서 시간대별 방문자 수를 확인할 수 있는 바이브코딩 프로젝트다. 어제 오후에 스탭 회의에서 말 나와서 어제 밤에 만들고 오늘 아침에 배포해 봤다. 그리고 오늘 점심에 스탭 채팅방에 피드백 달라고 링크 공유했는데 아직 보완 의견은 없다.)
분명 "어떤 앱 만들고 싶은지 주말동안 생각해 오자" 한지 얼마 안 된 것 같은데 벌써 또 "자기소개서에 어필하고 싶은 장점 다섯 가지 주말동안... 벌써 주말이야?????" 하고 있다.
지난 시간에는 constructor만 살펴 보았다. property에 대해 좀 더 알아보자.
모든 property는 기본적으로 private으로 작성하는 게 객체지향의 원칙이다. encapsulation 관점에서 그게 맞다. 하지만 Dart는 public을 기본으로 한다. 모든 property에 getter와 setter를 설정할 거면 그냥 public으로 하는 게 낫다는 입장이다.
다음과 같은 property에 대해 getter/setter를 사용하면
class User { // 다른 property 생략 int _age; // constructor 및 method도 생략 }getter로는 이렇게 그냥 가져가면서
int get age => _age;setter로 값을 설정할 땐 유효성 검사를 할 수 있다.
set age(int age) { (age > 0) ? _age = age : print('Invalid age'); }
Dart의 getter/setter는 method가 아닌 property다. 다음과 같이 property 접근하듯 사용한다. 정의할 땐 method와 유사하게 생겼지만 사용하는 입장에서 property처럼 사용하기에 property로 취급한다.
User anon = User(); user.age = 30; print(user.age);
코드가 실행되다가 error 상황을 마주치면 crash되며 중단된다. 이를 막을 수 있는 가장 일반적인 방법은 error handling이다. error 상황을 마주치면 적절한 조치를 취하는 거다.
성공/실패 경우를 모두 처리하지 않으면 컴파일 자체가 되지 않아, error 처리를 누락하는 실수를 원천 차단하는 Rust에 익숙한 사람은 잘 마주치지 않는 로직이다. 복구 가능한 error는 Result<T, E> 로 작성하고 그렇지 않은 error는 panic! 으로 작성하여 error를 처리하면 흐름 제어를 보다 명확하게 할 수 있다. 하지만 Dart를 비롯한 대부분의 언어들은 try-catch 를 통해 error를 처리한다.
try { // 작업을 시도한다 TASK1; TASK2; TASK_WITH_ERROR; // 여기서 error 발생! TASK3; // 이건 실행되지 않는다 } on FormatException { // 숫자 써야 하는데 문자 쓴다거나 할 때 발생한 error 처리 } on IntegerDivisonByZeroException { // 0으로 나누는 나눗셈 시도 시 발생한 error 처리 } catch (e) { // 앞에서 errorType으로 필터링되지 않은 error 처리 } finally { // error 발생 여부와 별개로 항상 실행되는 영역 }
on 뒤에 올 수 있는 error type은 다양한데 모든 걸 외우고 있진 않아도 된다. 필요할 때 "이런 error에 대한 error type은 뭐라고 부르지?" 하고 찾아가며 작성해도 충분하다.
내부적으로 error 발생이 구현되어 있는 코드에 대해서는 위와 같이 구현하면 되고, 그 외 특정 상황에서 error handling을 하고자 한다면 throw Exception('임의의 error message'); 하여 적절한 error message를 띄울 수 있다. Excpetion class를 상속받아 catch (e) {} 에서 이에 대한 처리를 구현할 수도 있다.
그냥 작성한 코드는 synchronous하게 실행된다. 순차적으로 실행된다는 것이다. 하지만 오래 걸리는 작업을 마냥 기다리는 건 비효율적인 일이다. 때로는 asynchronous하게 실행하여 병렬적인 작업을 수행할 필요가 있다.
병렬? 비동기?
순차적으로 실행하지 않는 것은 concurrent와 parallel이 있다.
concurrent는 한 녀석이 여러 작업을 번갈아가며 수행하는 것이고 parallel은 여러 녀석이 각자의 작업을 수행하여 전체로 봤을 때 여러 작업이 동시에 실행되는 것이다.
concurrent하면서도 parallel한 경우에는 여러 녀석이 여러 녀석을 번갈아가며 수행하는 것이다.
Dart에서는 function에 async 키워드를 붙여 asynchronous function으로 만들 수 있다.
Future<TYPE> FUNCTION_NAME(TYPE PARAMETER) async { TASK; }
asynchronous와 syncrhonous가 섞여 있다면 synchronous가 먼저 실행된다고 볼 수 있다. 모든 명령은 최종적으로 하나의 thread로 이루어진 main isolate에서 실행되는데, synchronous는 바로 main isolate로 들어가지만 asynchronous는 event loop의 스케줄링을 거쳐 main isolate에 들어가기 때문이다.
asynchronous function의 return type은 Future<Generic> 이다. return 값이 없어도 void 보다는 Future<void> 로 작성하는 게 좋다. 오류가 나지는 않아도 코드의 명확성과 유지보수성 관점에서 중요하다.
Dart에서 asyncrhonous를 담는 type은 Future 와 Stream 이 있는데, Future 는 한 번의 asynchronous 요청에 대한 응답의 느낌이고, Stream 은 터널을 열어놓고 데이터를 계속 asynchronous로 주고받을 수 있는 느낌이다. Stream 은 async* 키워드와 yield 키워드를 통해 사용할 수 있는데 여기서는 길게 얘기하지 않겠다. 나중에 Flutter에서 다룰 때 좀 더 자세히 살펴보자.
Future 는 세 가지 상태를 갖는다. 처음에는 Uncompleted 상태로 존재하다가 asynchronous 처리가 완료되면 Completed 상태로 변경된다. Completed 상태는 Completed with a value 상태와 Completed with an error 상태로 나뉜다.
- Uncompleted (아직 asynchronous 작업이 끝나지 않음)
- Completed
- Completed with a value (asynchronous 작업이 정상 종료됨)
- Completed with an error (asynchronous 작업 중 오류 발생)
Completed 상태의 Future 는 asynchronous 작업의 결과값 또는 error 데이터를 가지고 있다.
Completed with a value 상태의 Future instance는 then() method를 통해 사용할 수 있다. 중간에 error가 발생하여 Completed with an error 상태로 전달된 Future instance는 then() method를 무시하고, catchError() 를 통해 처리할 수 있다. 반대로 Completed with a value 상태로 전달되어 then() method가 실행된 경우 catchError() method는 무시된다.
error handling에서 error 발생 여부와 상관 없이 finally 코드 블록이 실행되듯이, Completed 상태의 Future 는 error 발생 여부와 상관 없이 whenCompleted() method를 사용하여 공통 작업을 수행할 수 있다.
futureInstance .then((value) { TASK; }) .catchError((error) { ERROR_HANDLING; }) .whenCompleted(() { DO_ALWAYS; });
concurrency는 intermediate 수준의 내용이기도 하고, 개념은 알고 있지만 내가 많이 안 다뤄본 부분이기도 해서 실습을 진행했다. 개념을 이해하는 데 시간이 걸리는 영역이라 intermediate 수준인 거지 개념을 이해하고 있으면 코드를 작성하는 건 어렵지 않았다.
이력서/자기소개서/포트폴리오에 대한 가이드(정답은 아니고 제안? 참고사항?)를 받았으니 주말에 좀 준비해 봐야겠다. 내일은 청년정책박람회에서 하는 특강 두 개 선정되어서 거기 다녀올 거고 일요일쯤 여유가 되려나. 계속 미루면 어느 순간 한참 지나 있으니 이번 주말에는 조금이라도 건드려 보도록 하자!