책의 서두에서는 객체지향을 처음 접해보는 사람들은 아래와 같은 설명을 보게 된다고 합니다.
1. "객체지향이란, 실세계를 직접적이고 직관적으로 모델링할 수 있는 패러다임" 2. "객체지향이란 현실 속에 존재하는 사물을 최대한 유사하게 모방해서 소프트웨어 내부로 옮겨오는 작업이기 때문에 그 결과물인 객체지향 소프트웨어는 실세계의 투영이다" 3. "객체"란 현실 세계에 존재하는 사물에 대한 추상화
하지만, 객체지향의 목표는 실세계를 모방하는 것이 아닌, 새로운 세계를 창조하는 것이라고 합니다.
그럼에도 불구하고 많은 사람들은 객체지향을 실세계를 모방하려고 하는 것일까?
책에서는 객체지향의 다양한 측면을 이해하고 학습하는데 매우 효과적이기 때문이라고 이야기합니다.
최근에는 객체지향적인 사고를 하려고 도전해보고, 소스 코드도 객체지향적으로 작성을 해보려고 노력하고 있습니다.
그래도 잘 이해가 되지 않고 있었다. 그런데 지금 곰곰히 생각해보면 실세계와 연결시켜서 객체지향을 이해할 수 있을 것 같다고 생각이 들었습니다.
지금부터는 커피 주문 프로세스를 가지고 객체지향 프로그래밍에 대한 개념을 이해해보려고 합니다.
협력하는 사람들
1. 손님은 캐셔에게 커피를 주문한다. 2. 캐셔는 손님의 주문을 받고 바리스타에게 주문 내역을 제공한다. 3. 바리스타는 전달받은 주문내역을 가지고 커피를 제조하고 캐셔에게 넘긴다. 4. 캐셔는 완성된 커피를 손님에게 제공한다.
위 Flow는 간단한 커피 주문 시나리오입니다. 해당 시나리오에서 손님, 캐셔, 바리스타는 각각의 객체입니다.
즉, 객체를 사람(생명체)에 비유한 것이라고 생각하면 됩니다.
또한, 커피라는 요소를 만들고 특정 인물에게 제공되는데 까지 손님, 캐셔, 바리스타가 협력(collaboration)을 하고 있습니다.
협력을 통해 거대하고 복잡한 문제를 해결할 수 있는 공동체를 형성할 수 있게 만드는 것입니다.
[역할과 책임]
손님, 캐셔, 바리스타 각 객체(인물)들은 그들 각각의 역할을 부여받습니다.
역할이란 협력 과정에서 각각의 객체들이 맡아서 해야하는 책임 또는 의무를 의미합니다.
마치, 경찰관은 범죄자를 검거할 책임이 있는 것처럼..
손님은 커피를 주문할 책임(?), 캐셔는 주문 내역을 바리스타에게 넘겨야 할 책임과 손님에게 완성된 커피를 제공해야 할 책임, 바리스타는 커피를 제조할 책임이 있는 것입니다.
대학교에서 팀 프로젝트를 하는 것처럼, 사람들이 특정 프로젝트를 마감하기 위해 협력을 하고, 협력을 위해 역할을 맡고, 그에 따른 책임을 수행하는 것입니다.
사람들이 협력을 위해 특정한 역할을 맡고 역할에 적합한 책임을 수행한다는 사실은 몇 가지 중요한 개념이 있다고 합니다.
1. 여러 사람이 동일한 역할을 수행할 수 있다 2. 역할은 대체가능성을 의미한다 3. 책임을 수행하는 방법은 자율적으로 선택할 수 있다. 4. 한 사람이 동시에 여러 역할을 수행할 수 있다.
역할, 책임, 협력
커피 주문 프로세스에서는 다음과 같이 개념들을 정리할 수 있습니다.
- 손님/캐셔/바리스타(사람): 객체
- 각 객체의 요청: 메세지
- 각 객체가 전달받은 요청을 처리하는 방법: 메서드
하나의 프로그램에서 객체들은 어떠한 기능(목적)을 구현(달성)하기 위해서 그들의 책임을 성실히 수행하고, 다른 객체들과도 협력합니다.
<객체의 역할>
- 여러 객체가 동일한 역할을 수행할 수 있다. - 역할은 대체가능성을 의미한다. - 각 객체는 책임을 수행하는 방법을 자율적으로 선택할 수 있다. - 하나의 객체가 동시에 여러 역할을 수행할 수 있다.
협력 속에 사는 객체
객체지향의 윤곽을 결정하는 것은 역할, 책임, 협력입니다.
그렇지만 위 3개 개념에 참여하는 주체는 객체입니다.
우리 사회에 인간이 빠지면 무슨 의미가 있겠는가? 라는 의미입니다.
협력이라는 객체의 특징을 기준으로, 객체는 2가지 덕목을 갖춰야 한다.
1. 객체는 충분히 협력적이어야 한다. -> 도움이 필요하면 요청하고, 도움 요청에 응답하는 열린 마음을 가져야 한다. 2. 객체는 충분히 자율적이어야 한다. -> 책임을 수행하는 방법은 책임을 지키는 선에서 자율적으로 선택한다.
계속 똑같은 말만 하는 것 같습니다만, 하나의 프로젝트를 완수한다는 목표를 위해 모든 팀원들이 협력하지만,
그 안에서 어떻게 협력할 지는 책임만 다한다면 방법은 자율적으로 선택한다.
[상태와 행동을 함께 지닌 자율적인 객체]
흔히 객체는 행동(Behavior)과 상태(State)를 함께 지닌 실체라고 정의하고 있습니다.
즉, 객체가 협력에 참여하기 위해 어떤 행동을 해야한다면 그 행동을 하는데 필요한 상태도 함께 가지고 있어야 한다는 것입니다.
(커피를 제작하는 바리스타는 커피를 제작하는 행동을 하기 위해 당연히 커피 제조법을 알고 있는 상태여야 하는겁니다)
객체가 협력에 참여하는 과정에서 자율적인 존재가 되기 위해서는 행동과 그에 따른 상태를 한 세트로 같이 지니고 있어야 한다.
객체가 자율적인 존재로 되기 위해서는 독립적인 상태를 스스로 관리함과 동시에 행동을 협력에 제공해야 한다.
[협력과 메세지]
현실 세계와 객체지향 프로그래밍 모두 협력이라는 걸 하기 위해서는 서로간의 의사소통이 필요합니다.
여러 의사소통 수단이 있는 현실과는 달리 객체지향 프로그래밍에서는 단 한가지의 수단밖에 없습니다.
해당 수단을 메세지(Message) 라고 말하고, 메세지를 송신하는 쪽을 송신자(Sender), 메세지를 수신하는 쪽을 수신자(Receiver) 라고 부릅니다.
[메서드와 자율성]
A객체가 B객체로 협력을 위해서 메세지를 보냈습니다.
B객체는 메세지를 수신하면 수신한 메세지에 대해 동작을 해야한다. 이를 메서드라고 합니다.
즉, 외부 요청을 표현하는 메세지와 요청을 처리하기 위한 구체적인 방법인 메서드를 분리하는 것은 객체의 자율성을 높이는 핵심 메커니즘이고, 이것은 캡슐화랑 관련이 있습니다.
객체지향의 본질
1. 객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고 객체를 이용해서 시스템을 분할하는 방법이다. 2. 자율적인 객체란 상태와 행위를 함께 지니며 스스로 자기 자신을 책임지는 객체를 의미한다. 3. 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다. 4. 객체는 다른 객체와 협력하기 위해 메세지를 전송하고, 메세지를 수신한 객체는 메세지를 처리하는데 적합한 메서드를 자율적으로 선택한다.
[객체를 지향해라]
필자는 평소에도, 글을 쓰는 지금도 원래는 객체지향라고 한다면 Java와 클래스만 생각했습니다.
하지만 객체지향은 단어 그대로 객체를 지향하는 것이지, 클래스를 지향하는 것이 아닙니다.
클래스의 구조와 메서드에 집중하는 것이 아니라, 객체의 역할/책임/협력에 집중해야 합니다.
훌륭한 객체지향 설계자가 되기 위해서는 코드를 담는 클래스가 아니라 메세지를 주고 받는 객체의 관점으로 사고를 전환해야 한다고 합니다.
후기
코드가 아닌 개념(이론)적인 내용만 기재가 되어있길래 생각보다 처음 이해하는데는 어려웠습니다.
하지만, 객체지향이 무엇인지에 대해서 조금은 알게 된 것 같아서 좋았습니다.
그리고 객체의 책임, 메세지, 협력 이라는 키워드에 대해서도 알게 되었습니다.
1장을 읽고는 객체지향에 대해 아주 큰 틀을 어느정도는 이해를 할 수 있던 것 같습니다.
이후 2장부터 쭉쭉 읽으면서 아 이런게 객체지향이고 실제로 프로그램을 작성해보고 싶다는 느낌을 받아보고 싶습니다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
PostgreSQL 에서는 Boolean 타입의 컬럼을 지원합니다. 그래서 True / False 외에도 다양한 Boolean 값을 활용해서 저장할 수 있습니다.
하지만, MySQL 에서는 외부적으로는 Boolean 타입을 지원하는 것처럼 보이지만, 실제로 내부 동작에서는 Boolean 타입을 지원하지 않는다고 합니다.
MySQL 에서의 Boolean -> TINYINT(1)
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20),
enabled BOOL /** 또는 BOOLEAN */
);
SHOW CREATE TABLE users;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`enabled` tinyint(1) DEFAULT NULL, /** NULLABLE */
PRIMARY KEY (`id`)
);
MySQL 에서는 BOOL 또는 BOOLEAN 컬럼 타입을 통해 테이블을 생성하면 MySQL 서버에서 실제 테이블은 TINYINT(1) 로 컬럼이 생성되는 것을 확인할 수 있습니다. 그리고, ROW INSERT 시에는 TRUE / FALSE 또는 0 / 1 로 사용합니다. (예제는 생략)
그리고 WHERE 에서 Boolean을 사용할 때 TRUE / FALSE 말고 0 또는 1로 조회할 때 2나 3과 같은 숫자로 비교할 때는 원하는 결과값을 받지 못할수도 있습니다.
Boolean 컬럼에서의 인덱스 활용
ALTER TABLE users ADD INDEX idx (enabled, name),
ALGORITHM=inplace,
LOCK=none;
위 구문은 users 테이블에 인덱스를 추가하겠다는 의미입니다. ALGORITHM과 LOCK을 설정할 수 있는 것은 몰랐는데...
ALGORITHM은 테이블 구조 변경 시 사용할 알고리즘을 지정하는 것이고, LOCK은 인덱스 생성 중 테이블 잠금 수준 지정 여부를 설정하는 것입니다. 자세한 내용은 생략하도록 하겠습니다.
이후 users에 소량의 데이터를 넣고 실행계획을 출력해보도록 해보겠습니다. (2개 ROW)
/** 2개의 ROW 생성 */
INSERT INTO users VALUES (1, 'Matt', TRUE);
INSERT INTO users VALUES (2, 'Lara', 0);
/** 실행 계획 출력 */
EXPLAIN
SELECT *
FROM users
WHERE enabled AND name = 'Matt';
/** 결과 출력 */
+----+-------------+-------+-------+-----------------+---------+------+------+----------+-------------------------+
| id | select_type | table | type | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+-------+-----------------+---------+------+------+----------+-------------------------+
| 1 | SIMPLE | users | index | ix_enabled_name | 85 | NULL | 2 | 50.00 | Using where; Using index|
+----+-------------+-------+-------+-----------------+---------+------+------+----------+-------------------------+
/** Optimizer Trace를 ON으로 변경 */
SET SESSION OPTIMIZER_TRACE="enabled=on";
/** 실제 실행되는 쿼리 확인 */
SELECT * FROM information_schema.OPTIMIZER_TRACE;
/** Optimizer Trace */
select
`users`.`id` AS `id`,
`users`.`name` AS `name`,
`users`.`enabled` AS `enabled`
from `users`
where ((0 <> `users`.`enabled`) and (`users`.`name` = 'Matt'));
실행계획을 해석해보겠습니다. 딱 봐도 알 법한 내용들은 생략하겠습니다.
select_type: SIMPLE
select_type은 각 단위 SELECT 쿼리가 어떤 타입의 쿼리인지 표시되는 컬럼.
SIMPLE은 단순 SELECT 쿼리라는 의미입니다. 쿼리가 아무리 복잡해도 실행 계획에서 select_type이 SIMPLE인 단위 쿼리는 최대 1개만 존재합니다.
type: index
type은 각 테이블의 레코드를 어떻게 읽었는지에 대한 접근 방식.
index는 인덱스를 처음부터 끝까지 읽는 인덱스 풀 스캔을 의미.
테이블 풀 스캔(type: all)보다는 빠르지만 반드시 효율적인 방법이라고는 말할 수 없습니다.
key_len
key_len은 선택된 인덱스의 길이를 의미합니다.
85가 나온 이유 (인덱스는 지금 enabled와 name을 적용해서 사용하고 있다)
enabled(boolean): 2byte
1byte 아니야?
데이터 저장 1byte + NULL 표시 1byte = 총 2byte 라고 합니다.
여기서 참고할 점! 지금 enabled 컬럼이 NULLABLE 이기 때문에 1byte가 추가된 것입니다. 만약에 enabled 컬럼이 NOT NULL 컬럼인 경우에는 1byte가 추가가 되지 않으니 참고하면 좋을 것 같습니다.
name(varchar(20)): 83byte
길이정보: 2byte
NULL 표시: 1byte
문자 데이터: 20글자 * 4byte = 80byte (utf8mb4 기준)
여기도 마찬가지! NOT NULL 컬럼인 경우에는 NULL 표시 1byte가 추가되지 않습니다.
따라서, 총 합계 85byte 입니다.
위 내용은 해당 링크를 참고해서 계산하게 되었습니다. 설명이 잘 되어있으니 참고하면 좋을 것 같습니다.
Extra
DB Optimizer가 어떻게 동작하는지에 대해 알려주는 힌트 값
Using where: 스토리지 엔진으로 부터 받은 데이터를 MySQL 엔진에서 별도의 가공을 해서 필터링 작업을 처리한 경우
Using index(커버링 인덱스)
데이터 파일을 전혀 읽지 않고 인덱스만 읽어서 쿼리를 모두 처리할 수 있는 경우
인덱스만으로 쿼리 수행이 가능할 때의 실행계획을 의미하는데, 인덱스만으로 처리되는 방법을 커버링 인덱스(Covering Index) 라고 한다.
필자가 참고한 포스팅과 결과가 다릅니다. 참고한 포스팅의 실행 계획은 type이 range며, key_len이 2였는데요.
이유가 무엇인지 살펴봤는데 ROW 수가 차이가 났기 때문이었습니다.
그래서 ROW를 약 500개 이상 넣고 다시 실행계획을 출력해보았습니다.
/** 결과 출력 */
+----+-------------+-------+-------+-----------------+---------+------+------+----------+-------------------------+
| id | select_type | table | type | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+-------+-----------------+---------+------+------+----------+-------------------------+
| 1 | SIMPLE | users | range | ix_enabled_name | 2 | NULL | 252 | 10.00 | Using where; Using index|
+----+-------------+-------+-------+-----------------+---------+------+------+----------+-------------------------+
/** Optimizer Trace를 ON으로 변경 */
SET SESSION OPTIMIZER_TRACE="enabled=on";
/** 실제 실행되는 쿼리 확인 */
SELECT * FROM information_schema.OPTIMIZER_TRACE;
/** Optimizer Trace */
select
`users`.`id` AS `id`,
`users`.`name` AS `name`,
`users`.`enabled` AS `enabled`
from `users`
where ((0 <> `users`.`enabled`) and (`users`.`name` = 'Matt'))"
type: range
range
인덱스 레인지 스캔 형태의 접근 방법. 인덱스를 하나의 값이 아닌 범위로 검색하는 경우
주로, <, >, IS NULL, BETWEEN, IN, LIKE 등의 연산자를 통해 인덱스를 검사할 때 사용
key_len
위 결과와 다르게 key_len이 2로 출력.
이는 enabled 컬럼까지만 인덱스 레인지 스캔으로 읽었다는 것을 확인할 수 있음.
이 시점에서 궁금한 점이 생겼습니다. 왜 실행 계획에서 type은 range가 출력되었고 key_len은 2로 출력이 된거지?
key_len이 2인 이유는 파악했으니 type이 range인 이유에 대해서 파악해보도록 하겠습니다.
실행계획이 range로 발생한 이유
필자가 궁금한 점은 조금의 데이터로 조회를 했을 때는 index가 출력되는데, 500개 이상의 데이터를 조회하니까 range로 출력이 된 이유에 대해서 궁금한 것입니다.
위에서 설명한대로 index 스캔 방식은 인덱스의 모든 엔트리를 처음부터 끝까지 읽는 인덱스 풀스캔 방식이고, range 스캔 방식은 인덱스에서 조건에 맞는 범위만 선택적으로 읽는 방식입니다.
index 스캔 방식은 결국에는 풀스캔 방식이기 때문에 데이터가 적을 때는 오히려 빠를 수도 있지만, 데이터가 많아지면 불필요한 인덱스 까지 모두 읽어야 된다고 생각합니다.
그렇기 때문에 옵티마이저는 테이블의 ROW 수가 2개일 때는 수가 적기 때문에 index 스캔 방식을 선택한 것이고, 테이블의 ROW 수가 500개 이상일 때는 수가 상대적으로 많이 때문에 range 스캔 방식을 선택한 것이 아닐까 라고 생각합니다.
실제로 실행된 쿼리문에 대한 간단 분석
/** 실행 계획 출력 */
SELECT *
FROM users
WHERE enabled AND name = 'Matt';
/** Optimizer Trace */
select
`users`.`id` AS `id`,
`users`.`name` AS `name`,
`users`.`enabled` AS `enabled`
from `users`
where ((0 <> `users`.`enabled`) and (`users`.`name` = 'Matt'))"
실제로 수행된 쿼리문입니다. where users.enabled <> 0 이라는 구문을 처음봤는데요. where users.enabled = TRUE로 해석된 것이 아닌 것입니다.
(실제로 users.enabled = TRUE로 설정하게 되면 더욱 효율적인 스캔 방식이 채택될 것임 - 해보니까 ref 방식이 출력됨)
옵티마이저는 enabled<>0을 NULL<enabled<0 AND 0<enabled로 변환해서 실행을 했습니다. 이는 옵티마이저가 조건절이 부정 조건으로 처리되어있는 것을 인덱스를 사용할 수 있는 구조로 변환을 해서 실행을 한 것이기 때문입니다.
이렇게 MySQL Boolean 컬럼에 대해서 잘 설명해준 블로그 글을 보면서 이해가 안되었고 궁금했던 내용을 직접 찾아가면서 공부를 해봤습니다. 필자가 참고한 포스팅의 결론은 지금 보는 포스팅에 담지 않았으니 결론이 궁금한 독자 분들은 위에서 참고링크 걸어둔 포스팅 참고하면 좋을 것 같습니다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
이번에 2번째로 연말 회고록을 작성해본다. 블로그를 열심히 한다고 말할 수는 없지만 글을 쓰려고 할 때면 어떻게 써야할 지 막막하다.
그래도 머리속으로 생각나는 대로 잘 정리해서 써보려고 한다. 사실 블로그를 처음 시작했을 땐 많은 개발자들이 블로그를 시작하니까 나도 따라해야지 라는 생각이 있었다. 그런데 이게 쓰다보니까 한번씩 생각이 날 때마다 내가 작성한 글들을 보는데 생각을 많이 할 수 있는 계기가 되는 것 같아서 한 번씩 생각날 때마다 쓰는 것 같고 이게 좋은 것 같다.
1. 회사, 그리고 6개월과 아쉬움. 그렇지만 열심히 !
취업 준비 시절에 취직에 대해 욕심이 없었다. 그냥 게으르고 의지가 많이 부족했던 시점이었던 것 같다.
그렇지만 프로젝트는 하나 제대로 끝내보고 싶어서 열심히 프로젝트에 임했었다. 그렇게 프로젝트를 하다보니 본격적으로 취업을 해봐야겠다는 생각이 들었었다.
프로젝트를 같이 하고 있던 디자이너 분이 다니고 계신 회사에서 Node.js 백엔드 개발자를 채용한다는 이야기를 해주셔서 마음을 먹고 처음으로 이력서를 제출해봤다. 다른 회사도 넣어봤어야 했는데 특별하게 넣은 곳은 없었다. 운이 좋게도 2번의 면접 끝에 2024년도 5월 경에 교육 분야 도메인 회사에 신입으로 입사하게 되었다.
합격을 하니 기분은 좋았지만, 한 편으로는 걱정이 많았던 것 같다. 다른 사람들은 이력서 최소 100번 이상은 써서 회사에 넣어보고, 코딩테스트와 면접도 열심히 준비해서 간신히 1개 회사에 붙을까 말까 하는 상황으로 알고 있었다.
그렇지만 나는 서류를 한 번 넣어봤고 한 번에 붙었다. 누군가 봤을 때 좋은거 아니야? 라고 말할 수 있지만, 요즈음에는 그 때 서류를 더 많이 넣어보고 더 다양하게 면접을 보러 다녀볼걸 이라는 생각이 많이 들었다. 회사가 불만족스럽다기 보다는 서류와 면접 준비도 하나의 좋은 경험이라고 생각하기 때문이다.
결국 나는 취업을 한 상태이고 회사 업무를 열심히 수행해야했다. 최근 8월부터 10월초 까지는 주말 없이 야근과 휴일근무를 계속 하면서 재능스스로AI수학을 문제없이 출시할 수 있었다. (물론 강제성이 조금 있었지만..ㅎㅎ) (FE분들이 더 고생 많으셨던 것 같다)
지금도 퇴근 후에 계속해서 개인 공부와 프로젝트를 진행하고 있는데 앞으로도 지금 주어진 상황에서 열심히 해야한다고 생각하기 때문에 계속 회사의 업무를, 그리고 개인 성장을 위해 더욱 노력해보려고 한다.
2. 한 번의 스터디 참여와 한 번의 스터디 운영
블로그에 몇 번 작성을 했었다. 월간CS 에서 "이펙티브 타입스크립트 발표 스터디"에 참여했었고, "모던 자바스크립트 Deep Dive 발표 스터디"를 운영했었다. 이 외에도 Docker 스터디 등 몇 개 스터디에 참여한 적이 있는데 가장 인상깊게 몰두했던 스터디였던 것 같다.
스터디에 참여하는게 나한테 개인적으로 잘 맞는다고 생각하기도 했고, 어쩌면 나처럼 게으른 사람들한테는 필수적인 요소라고 생각이 들었다. 귀찮아도 휴일에 집 밖으로 나가서 2시간 정도라도 다른 사람의 발표를 들어보며 지식을 공유하고 근황 토크도 하고.. 이런게 지금은 나에게 킥이었던 것 같다 ㅋㅋ
그렇지만 아무래도 백엔드와 관련된 스터디는 아니었다 보니.. 스터디원 중 BE는 나밖에 없었다. 그래서 조금 아쉬운 부분도 있었지만 좋은 사람들 만나서 지식 공유하고 이야기 많이 나눴던 게 나에게 좋은 경험이었던 것 같다.
이번 년도에는 성장에 키워드를 둔 만큼 읽고 싶었던 책과 강의가 있는데 그 중에 혼자하기 힘들다고 생각되는 부분들은 아마도 월간CS에서 스터디를 오픈할 수도 있을 것 같다.
3. 갑작스러운 건강 이슈
최근에 회사에서 건강검진을 받았다. 그리고 약 8만원 가량 비용을 추가로 지불하면 당화혈색소 등 혈액정밀검사를 실시해준다고 했다.
살도 많이 찌고, 아부지가 당뇨라서 가족력도 있고 아무래도 술을 조금 좋아했었다 보니 검사를 받아보고 싶었다.
검사 결과를 받았는데 생각한 것 보다는 좋지 않았다. 지금 생각나는건 이정도인 것 같다.
- 약간의 지방간 (살을 좀 빼면 없어질만한..)
- 높은 콜레스테롤 (좋은 콜레스테롤은 낮고 나쁜 콜레스테롤은 높음)
- 요산수치가 9.6으로 매우 높았음 (통풍과 연관된 수치)
- 골소공증 진단 (?)
생각한 것 보다 혈당수치는 굉장히 정상이었다. 지방간은 뭐 술을 좋아하는 편이고 과체중이다 보니.. 조금은 있었을 거라 생각했고
콜레스테롤도 중고등학교때부터 높았어서 그냥 그런가보다 했다.
그런데 요산수치랑 골소공증은 정말 깜짝놀랬다. 다행히 골소공증은 기계 문제였어서 송도가서 검사해봤는데 담당의사분이 평균 연령대보다 골밀도 수치가 좋으니 걱정하지 말라고 하셨다.
남자 정상수치는 7.0 까지라고 하는데 9.6이었다. 물론 건강검진이 있다고 하기 전 그 주에 고기랑 술을 좀 먹긴 했었다.
검사결과를 의사분이 설명해주시는데 "통증이 없었냐, 이정도면 바로 약드셔야 한다" 등 이야기를 해주셨는데 사실 좀 무서웠다.
그래서 일단 1-2개월 정도는 음식을 최대한 조심해보자는 생각이 들어서, 맥주와 순대국, 곱창을 절대 금기시했고 매일 아침에 사과와 그릭요거트를 먹기 시작했다. 그리고 고기류들도 최대한 절제하고 야채는 많이 먹었다. 부산과 군산에서 올라온 친구들을 만날 때도 고기는 최소한으로 먹고 술도 한잔먹을거 반잔씩 먹고.. 등의 나름의 노력을 했다.
그렇게 약 2개월 노력을 한 결과 다행히 요산수치가 7.2로 내려가 있었다. 조금 높긴하지만 그래도 다행이었다..
이 결과를 듣고 고삐가 풀려서 막 먹긴했지만.... 다시 정신차리고 신년에는 조심해보려고 한다.
4. 프로젝트로 인해 아주아주 바빴던 연말
최근에 10월부터 프로젝트 하나를 시작하게 되었다. 처음 시작할 때는 구현해야 할 기능의 양이 적어서 천천히 해도 괜찮겠다 생각했는데, 생각한 것 보다 기능의 양이 많아져서 퇴근하고 자기 전까지 작업을 했어야 했다. 그리고 테스트 하면서 발생한 이슈도 계속해서 대응해야 해서 시간이 많이 부족했던 것 같다.
바빠서 너무 고통스럽고 피곤했는데 지금은 이제 마무리 단계에 접하니까 뿌듯하고 내가 만든 서비스를 다른 사람들이 사용한다고 생각하니까 고생했던 날들을 보상받는 느낌이었다. 개발 역량에도 좋은 영향을 주었겠지만 책임감 이라는 덕목이 한 단계 더 레벨업한 느낌이 들었다.
하나 느낀게 있다. 프로젝트를 스터디랑 같이 병행했었는데 굉장히 빡셌다. 너무 일을 벌려둔 것 같다.
이번 년도에는 프로젝트 보다는 내가 듣고 싶고 공부하고 싶었던 분야를 먼저 공부하고, 프로젝트는 시간이 남을 때 조금씩 해보려고 한다.
"욕심부리지 말고 할 때는 하나만 열심히 하자"
5. 2025년에는 어떤 목표를 ?
여자친구는 말했다. "넌 2024년에도 거창했는데 2025년도 비슷하게 거창할 것 같아"
맞다..! 거창하고 똑같다. 내가 진짜 이루고 싶은 것들인데 2024년에는 10%도 못이룬 것 같아서 이번 년도 만큼은....!
키워드는 건강과 성장 이라고 할 수 있을 것 같다.
건강한 삶을 살아보자
라면, 고기, 술, 과자 등을 굉장히 좋아한다. 덕분에 최근에 살도 많이 쪘다. 스트레스 받으면 미친듯이 먹어내는 안좋은 습관을 가지고 있다보니.. 지킬 수 있을지는 모르겠지만 이번년도에는 진짜 70% 라도 실천해보려고 한다. 아래 내용들은 꼭 지켜보려고 노력해보려고 한다.
아침) 호박즙, 그릭요거트에 아몬드, 사과를 챙겨먹기 OR 약간의 밥과 닭가슴살 점심) 직원식당에서 일반식으로! 양은 적당히.. 저녁) 저녁 약속은 최대한 줄이고, 정말 간단하게 먹기.
주 3회 이상은 무조건 운동하기 (아침 운동하고 싶지만 무리일 거 같음)
5kg -> 10kg 체중감량
더 거창하게 하고 싶지만 이정도만이라도 지켜보려고 한다. 이 정도라도 지켜야 내가 나름 건강하게 살 것 같았다.
일기를 써보자 + 감사일기
사실 나를 아는 사람들은 이런말을 할 것이다. "너가? ㅋㅋㅋㅋㅋ" 그런데 주변에서들 많이 이야기를 했다. 한 줄이라도 일기를 써보면 생각정리가 되면서 회고할 수 있다고. 그래서 일기를 쓰기 시작했다. 3일째이다 ㅋㅋ
얼마나 갈지는 모르겠지만 매일 써보자. 그리고 매일 감사한 일을 찾아서 한 개 이상 써보기로 했다.
이를 위해 내가 사용하는 노트앱을 바꾸기로 했다. 항상 노션과 옵시디언을 쓰고 있었는데, 개인적으로 너무 불만족스러웠다.
마침 베어 라는 노트앱을 발견했는데 굉장히 마음에 들었다. 다만 유료.... 생각보다 비싸다...
이 베어 노트를 1년동안 써보려고 한다! 추천 !
급할수록 되돌아가자
최근에 개발자 이강욱 이라는 사람을 되돌아봤을 때 부족한 점이 너무 많다고 생각이 들었다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
오랜만에 블로그 포스팅을 써보는 것 같다. 조금 바빴다는 핑계로..ㅎㅎ 이제는 다시 포스팅을 조금씩 써보려고 한다.
오늘은 지금까지 애를 많이 먹었던 NestJS 프레임워크를 활용해서 Microsoft Azure OpenAI 연동 방법과 연동 후 기대에 부응하지 못했던 점을 공유드려보려고 한다. NestJS를 가지고 Azure OpenAI를 연동하는 자료는 크게 없는 것 같아서 틈새시장을 노려보겠다 '__'
1. GPT 모델을 가지고 어떤 기능을 구현하려고 했는지?
기획 유출이 될 수 있어 자세하게는 설명할 수 없지만, 민감한 사진과 프롬프트를 가지고 AI 모델에게 분석요청을 요청해서 해당 사진에 대해 분석 결과를 도출해내고, 해당 결과의 변화를 추적할 수 있는 기능을 만들고자 했다.
참고로, GPT 4모델 이상부터는 Vision 기능이 있어서 모델이 이미지를 입력받고 이에 대한 프롬프트에 응답을 할 수 있다.
대표적으로는 GPT-4o, GPT-4o-mini, GPT-4-turbo 등이 있다고 한다.
2. OpenAI (ChatGPT) vs Microsoft Azure OpenAI => Azure OpenAI
처음 입문해서 모델을 생성해야 하는 분들은 해당 링크를 참고하면 좋을 것 같다. 여기서 추가적으로 필자가 설명해보자면..
위의 블로그를 참고해서 잘 모델을 생성하면 다음과 같은 화면을 볼 수 있을 것이다.
여기서 우리는 Azure OpenAI Studio로 이동을 클릭해서 Studio로 들어가고, 배포 탭으로 들어가서 모델 배포 -> 기본 모델 배포를 클릭해보자.
그러면 모델을 선택할 수 있는 화면이 나온다. 필자는 GPT-4o 를 선택했다.
선택하고 나면 아래 화면을 볼 수 있다.
배포 유형은 Azure의 공식문서에 잘 설명이 되어있으니 참고해주시고, 필자는 표준 유형을 선택했다.
그리고 여기서 한가지 중요한점은 초반 무료 계정인 개발자분의 화면은 분당 토큰 속도 제한이 1K까지로 제한이 되어있을 것이다.
이는 무료계정이기 때문에 그렇다. 필자는 1K 이상의 토큰을 사용하기 때문에 해당 부분을 해결해야 했다.
해결하지 못하면 아래 사진과 같이 에러가 발생하는데 해당 에러를 마주쳤다면 아래 내용을 참고해서 해결해보자!
이제 설명할 내용은 100% 정확한 정보는 아니니까 참고해주길 바란다.
결론은 무료계정에서 종량제 계정으로 업그레이드를 해야한다. 계정을 업그레이드하면 필자가 올린 위 사진과 같이 1K 이상으로 설정할 수 있다. 종량제 계정으로 업그레이드를 하면 돈이 나가지 않냐? 돈 나간다. 그렇지만 우리는 처음 계정을 생성하면 200달러의 크레딧을 받기 때문에 해당 크레딧을 먼저 사용하고 나서 돈이 청구가 되니까 참고해주길 바란다.
위의 문서만 참고해도 충분히 코드를 작성할 수 있다. 해당 문서에는 API_TOKEN 방식과 Microsoft Entra ID 방식을 소개하고 있는데 API_TOKEN 모드는 보안상 프로덕션 모드에서는 크게 추천하지는 않는다고 한다. 하지만 필자는 현재 개발 모드이기 때문에 API_TOKEN방식으로도 충분할 것 같아서 API_TOKEN 방식을 선택했다.
(1) Package Install
openai에서 AzureOpenAI 생성자를 지원해주는것 같다.
npm i openai
(2) Service source code
import { AzureOpenAI } from 'openai';
@Injectable()
export class OpenAIService {
private client: AzureOpenAI;
constructor() {
this.client = new AzureOpenAI({
deployment: '<사용하려는 모델>',
apiVersion: '<OpenAI API VERSION>',
endpoint: '<Azure OpenAI Studio에 있는 endpoint 주소>',
apiKey: '<Azure OpenAI API KEY>'
});
}
public async test(): Promise<void> {
const response = await this.client.chat.completions.create({
model: 'gpt-4o-2024-05-13',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: '안녕하세요! JavaScript의 ES6에 대해서 설명해주세요.'},
// 만약 이미지를 추가로 넣어야 한다면?
// { type: 'image_url', image_url: { url: '<image url>', detail: 'auto' }}
]
}
],
temperature: 0 // 낮으면 낮을수록 모델의 응답 결정도가 높아짐.
});
console.log(response);
console.log(response.choices[0].message.content);
}
}
JavaScript의 ES6에 대해 물어봤더니 정상적으로 답변을 받았다.
5. 필자는 민감한 사진을 핸들링한다고 했다. 어떻게 해결하는지?
아마 민감한 사진이 담겨있는 사진의 URL을 위의 코드에 삽입하여 실행을 시키면 다음 에러가 발생할 것이다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
forFeature를 통해 ConfigModule에 설정 파일을 등록하는 것이다. 그러면 configService로 접근을 할 수가 있다는 것이다.
그리고 필자는 getAppConfig라는 유틸 함수를 따로 만들어서 관리를 했다.
getAppConfig 메서드과 AppConfigType
/**
* Get App Config.
* If you use useFactory in module level, you can use this function with applying
* return type.
*
* @param configService
* @returns
*/
export const getAppConfig = (configService: ConfigService): AppConfigType => {
const config = configService.get('app');
if (!config) {
throw new InternalServerErrorException('앱 설정에 문제가 있습니다.');
}
return config;
};
// AppConfigType?
type AppConfigType = ConfigType<typeof appConfig>;
만약에 저렇게 유틸함수로 빼지 않으면 Dynamic Module 등록할 때 저 로직들을 계속 작성해주어야 한다.
중복되는 부분을 제거하기 위해서 유틸 함수로 뺐다고 생각하자.
그리고 AppConfigType은 위와 같이 작성해주면 된다.
이렇게 Config를 Type-Safe하게 관리할 수 있도록 해봤다.
아무래도 TS를 사용하다 보니까 Type Checking이 안되는데에 민감한 것 같다 ㅋㅋ
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
type Merge<F, S> = Omit<F, keyof S> & S
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type Foo = {
a: number
b: string
}
type Bar = {
b: number
c: boolean
}
type cases = [
Expect<Equal<Merge<Foo, Bar>, {
a: number
b: number
c: boolean
}>>
]
S의 Key들을 F에서 제외시키고 S와 Intersection을 하면 문제가 해결될 것이라고 생각했지만 Playground에서는 에러를 발생시키고 있었다. 그래서 도저히 왜 에러가 발생하는지 이해가 되지 않았고 다른 사람이 작성한 답안을 같이 확인해봤다.
type Merge<F, S> = {
[key in keyof F | keyof S]: key extends keyof S
? S[key]
: key extends keyof F
? F[key]
: never;
};
위 코드의 Merge 타입은 정상적으로 Playground에서 동작을 하고 있다. 왜 이 Merge 타입은 통과를 하는 것인지 이해가 되지 않았다.
(물론 위 Merge 타입이 잘 작성이 되었다는 것은 이해했다. 하지만 Omit 타입을 사용한 것도 잘 작성이 되었다고 생각했다)
그래서 필자는 이 시점에서 2가지 생각이 들었다.
1. 필자가 Omit 타입을 사용해 작성한 답안이 애초에 틀린 답안인 경우
2. Type Challenge에서 제공하는 유틸 타입인 Equal 타입이 잘못된 경우
위 두 케이스를 각각 알아보도록 하자.
1. 필자가 Omit 타입을 사용해 작성한 답안이 잘못된 경우
내가 Omit 타입을 잘못 사용했나? 라는 생각이 들어 다시 한번 답안을 확인했지만 내 머리로는 특별히 로직상 문제가 없는 것 같다고 판단했다.
그리고 해당 답이 이슈 탭에서 가장 많은 따봉을 받은 답이었어서 특별히 문제가 되는 답은 아닐거라고 생각했다.
근데 나는 왜 틀리다고 나오지.. 뭔가 나랑 다르니까 22명의 사람들이 따봉을 누른게 아닐까?
그래서 어쩌면 이 사람들은 Playground에서 작업하지 않고 직접 구현한 Equal 타입을 사용하는게 아닐까라는 약간의 의심을 해봤다.
여기 지점에서 필자의 고민과 동일하게 왜 저렇게 함수 타입을 비교하는 걸까? 다른 방법은 없을까? 라고 생각할 수 있다.
필자도 100% 이해한 건 아니지만, X extends Y 와 같은 기법으로 대체해서 사용하면 문제가 되는 부분이 발생하기 때문에 조금 더 엄격하게 비교하기 위한 기법이다! 라고 정도만 이해를 하고 넘어갔다.
몇 가지 예시를 같이 들어보자.
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends number ? 1 : 2)
y = x // Should this be an error or not?
x와 y 변수에 다음과 같이 타입을 할당했다. 우리는 number 타입과 number 타입이 동일한지를 보고 싶은 것이다.
그리고 y에 x를 할당할 수 있는지를 확인했다. 이것이 우리가 원하는 Equal 로직이 아닌가?
위의 코드를 보면 Type Checker가 정상적으로 인식을 한다.
이번에는 number 타입과 string 타입을 비교해보자.
declare let x: <T>() => (T extends /*1st*/ number ? 1 : 2)
declare let y: <T>() => (T extends /*3rd*/ string ? 1 : 2)
y = x // It will occur error!
위의 코드는 이전 예시와 다르게 체커가 에러를 뱉을 것이다. 왜냐하면 x의 함수 타입과 y의 함수 타입이 불일치하기 때문이다.
위의 내용에 살을 조금 더 붙여보자. 아래 코드에서 주석으로 붙은 번호 순서대로 설명을 해보겠다.
declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends string ? 1 : 2)
const a = x<string>() // --- (1)
const b = x<number>() // --- (2)
const c = y<string>() // --- (3)
const d = y<number>() // --- (4)
y = x; --- (5)
const e = y<string>(); --- (6)
const f = y<number>(); --- (7)
1. 제네릭에 string 타입을 할당했기 때문에 string extends number가 수행될 것이다. string이 number에 할당이 가능하지 않기 때문에 2가 저장될 것이다.
2번-4번: 1번 내용을 이해했다면 충분히 해결할 수 있을 것이라고 생각한다.
5. x와 y가 동일한지 비교하기 위해 y에 x를 할당해보는 것이다. 아마 에러가 발생할 것이다. 그렇지만 일단 넘어가자.
6. y에 x를 할당했다고 가정해보자. 그러면 결국 x<string>()과 동일하다. y에 x가 정상적으로 할당되었다고 한다면 x<string>()과 y<string>()의 결과는 동일해야 한다. 하지만 x<string>()의 결과는 2, y<string>()의 결과는 1로 동일하지 않다.
7. 6번의 내용을 이해하면 이해할 수 있을 것이다.
Stack Overflow 답변 작성자님은 위와 같은 이유 때문에 extends 뒤에 오는 X와 Y가 동일해야 정상적인 결과가 반환되는 것이라고 말하고 있는 것이다.
(2) 결국 우리가 궁금한 것은 왜 { x: 1 } & { y: 2 } 그리고 {x : 1, y: 2} 를 비교했을 때 false 라는 거야?
그러면 위의 코드를 보면 source.flags는 intersection 타입이기 때문에 해당하는 값인 2097152가 할당될 것이고, target.flags는 단순 object literal이기 때문에 object 타입으로 524288이 할당될 것이다.
두 값을 비교해보면 동일하지 않기 때문에 false가 반환되는 것이고 그래서 위의 Equal 타입에서는 false가 보여지는 것이다.
(3) 그런데 아무리 생각해도 이해가 안대져?
당연하다. 이론적으로 intersection 적용한 것과 일반 object literal 타입을 비교했을 때 동일한 게 맞다.
그냥 Type Challenge에서는 이런 형태의 Equal 타입을 추구하고 있다는 것과 해당 Equal 타입은 좀 더 엄격하게? 유연하게? 타입을 비교하는 것이다 정도로만 생각해도.. 괜찮지 않을까 싶다. 더 딥하게 파고들면 굉장히 머리아프다..
(왜 엄격하게? 유연하게? 라고 표현했냐면 Intersection을 적용한 타입과 literal로 표기한 타입을 비교했을 때 false라고 하는 것을 엄격하다고 표현해야 하는지 유연하다 라고 표현해야 하는지를 잘 모르겠어서 이렇게 작성했다. StackOverflow에서는 lazy approach 라고 표기한다!)
이에 대한 추가 예시로 해당 글에 댓글로 제로초님이 unknown과 any를 비교하는 것에 대해서도 질문을 하신 것 같은데 자세한 내용은 참고하면 좋을 것 같다!
그리고 이거는 필자가 따로 해본건데 true와 boolean을 비교하는 것도 한번 해보길 바란ㄷr.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
원래는 13주로 기획했지만 중간에 커리큘럼 변동을 해서 총 12주로 마무리했다. 더 짧게 구성하고 싶었지만 빡빡한 시간 속에 하고 싶지 않았기 때문에 조금 여유롭게 구성을 한 것 같다.
(2) 온라인 OR 오프라인
필자는 오프라인 스터디를 선호하는 편이다. 나만 그러는지는 모르겠는데 온라인 스터디는 너무 집중력이 떨어진달까....
그리고 필자가 게으른 성격이다 보니 일요일에 아무것도 안하고 누워있을 것 같아서 나름의 강제성(?)을 부여한 것 같다.
이전 스터디에서는 모두 오프라인 스터디로 진행했었지만 이번 스터디 같은 경우에는 12주라는 시간동안 매주 일요일 오전 10시에 강남역을 오는 것은 쉽지 않을 것이라고 생각했다. 그래서 온라인과 오프라인을 병행하면 부담이 조금 줄어들 것 같아 병행을 선택했다.
(3) 스터디 방식
스터디는 크게 DIL 작성과 발표자료 준비로 나눌 수 있을 것 같다.
스터디 방식은 위에 있는 공고 링크보면 확인할 수 있으니 참고하면 좋을 것 같다. 간단하게만 언급해보면!
DIL 작성은 월요일부터 토요일까지 책 읽은 부분에 대해서 정리하는 거고, 발표자료 준비는 일요일까지 준비를 해오면 된다.
스터디 당일에는 랜덤으로 발표자를 선점하기 때문에 모든 스터디원이 발표자료를 준비해야 한다!
(4) 제약 조건
3개월이라는 기간동안 스터디를 운영하는 것은 리스크가 굉장히 큰 일이라고 생각한다.
루즈해지기 좋은 시간이고 중도하차를 하고 싶다는 생각이 많이 들 수 있을 기간이라고 생각했다. 그렇지만 커리큘럼을 먼저 결정했기 때문에 3개월 이라는 시간은 변경할 수 없었다.
그래서 결석, 지각에 대해 크게 제약을 두지 않았다. 원래는 보증금을 먼저 받고 지각 또는 결석, 그리고 발표자료 준비 미흡 시에는 보증금에서 깎는 등의 제약 조건을 추가하려고 했지만, 그냥 특별한 제약조건 없이 편안한 분위기에서 자유롭게 하고 싶었던 것 같다.
하지만 다음에 스터디를 운영할 땐 기간을 더 짧게 잡고 제약조건을 설정할 것 같다.
스터디를 실제로 운영해보면서 배운점
스터디를 운영하는 것은 쉽지 않구나 + 나름의 리더십?
스터디 운영이라고 해봤자 뭐 별거 있을까? 라는 생각을 가지고 있었다. 그냥 뭐 공지 올려주고 발표자료 한번씩 확인해보고..
그렇지만 결코 만만하지 않았다. 지금도 스터디 첫날 떨림이.... 기억나는 것 같다.
스터디룸 예약과 매주 스터디 공지하는 것도 계속 기억하고 있어야 하고 마음이 흔들리는 스터디원이 있으면 얘기도 같이 나눠봐야 하고 등등.. 신경 써야할게 생각보다 많았던 것 같다.
그렇지만 그 과정에서 리더십이라는 덕목을 더 많이 배웠던 것 같다. 직접 운영을 하려다 보니 어떻게 해야 스터디원들이 불편함을 가지지 않고 편안하게 스터디에 임할 수 있을까? 라는 생각을 계속 하게 되니 자연스럽게 리더십이 발휘된 것 같다는 느낌이 들었다.
실제로 필자가 일일이 발표자료 링크 올리는 파일을 생성하고, DIL 파일을 직접 생성하는 등의 작업들이 귀찮다는 생각이 들어가지고, 다른 스터디원들도 파일 생성하는게 귀찮지 않을까 싶었다. 그래서 직접 terminal에서 본인 이름을 선택하면 DIL 파일 템플릿과 발표자료 링크를 업로드 할 수 있는 템플릿 파일을 자동으로 생성해주는 스크립트를 작성해서 불편함을 줄여봤다. (소스코드는 별로...입니다)
예를 들어 발표자료 파일의 이름을 week_1 같이 작성하는데 몇 주차인지 헷갈릴 때가 분명히 있는데 필자가 작성한 스크립트로 파일을 생성하면 굳이 계산하지 않아도 되는 편리함이 있었다 :)
그리고, 평소에 남들 앞에서 먼저 말을 꺼내고 그런게 힘들었지만, 3개월 간 스터디를 주도적으로 이끌어 나가야 하다보니 책 읽을 때 어떤 부분이 어려웠는지, 관련한 자료들은 뭐가 있는지 등을 지속적으로 공유하고 스터디원들에게도 어떤 부분이 어려웠는지, 요즘 회사 및 취준 생활은 어떤지 등의 근황 토크도 같이 하니까 재미가 있었고 나도 자연스럽게 남들 앞에서 말을 꺼내는게 무섭지가 않았다.
(물론 옆에서 우리 스터디원들이 적극적으로 임해줘서 그런 것 같다)
스터디를 운영하면서 아쉬웠던 점
물론 배운점만 있는건 아니었다. 회고록을 작성하고 있는 이 시점에 생각해보면 분명히 아쉬웠던 점이 있었다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
위의 소스코드를 보면 Chainable이라는 타입을 가지는 a라는 변수에 option 메서드를 체이닝 하면서 적용하고 있다.
즉, option이라는 메서드가 Chainable을 반환을 해야 동작이 가능한 소스코드라는 점이다.
a.option('foo', 123) 이라는 코드가 Chainable 타입의 데이터를 반환해야, 반환된 Chainable 타입의 데이터에서 option 메서드를 추가로 사용할 수 있기 때문이다.
2단계: 상태 유지를 위해서는 제네릭을 사용해야 한다.
option 메서드가 Chainable을 반환하면서 체이닝이 되어야 한다. 그러기 위해서는 option 메서드가 반환하는 값이 객체에 대한 정보를 가지고 있어야 할 것이다.
1단계에서 보여지는 result1을 보면 foo라는 키에는 123의 타입인 number가 할당되어야 하고 두 번째 option 메서드가 사용되는 시점에는 1단계에서 사용된 option의 리턴값을 기억하고 있어야 한다. 이런 상황을 구현하기 위해서는 타입 레벨에서는 제네릭을 사용해야 한다.
type Chainable<T = {}> = {
option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>;
get(): any;
}
-Chainable<T = {}> : 제네릭을 통해 처음 기본 객체(빈 객체)를 선언해주어야 체이닝이 가능하다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.
TypeScript에서 지원하는 Utility Type인 Omit을 직접 구현해보는 문제였다.
해당 문제를 구현하면서 헷갈렸던 부분을 정리하려고 한다. Omit에 대한 자세한 설명을 하지는 않겠다.
(Omit<T, K> -> T에서 K들을 빼고 리턴하는 것>
풀이1)
type cases = [
Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]
interface Todo1 {
readonly title: string
description: string
completed: boolean
}
interface Expected3 {
readonly title: string
}
// 메인 풀이
type FirstMyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
-T의 Key들을 순회할 때 P라는 변수를 가지고 순회를 할건데 P가 K에 할당이 가능하다면 never를 통해 Index Signature에서 제외시키고, 할당이 불가능하다면 적용한다. 그리고 해당 경우에는 T를 P로 접근하여 value를 가져온다.
- 가장 직관적이고 무난한 풀이라고 생각이 된다.
풀이2) Exclude를 사용한 풀이 (오답)
type cases = [
Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]
interface Todo1 {
readonly title: string
description: string
completed: boolean
}
interface Expected3 {
readonly title: string
}
// 메인 풀이
type SecondMyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
};
- Omit은 T에서 K를 제외하고 반환하는 타입이기 때문에 Exclude를 사용할 수 있다는 의미이다.
-Exclude를 사용해서 T에서 K를 제외한 타입을 가져오고, 그것을 P라는 변수를 통해 순회해서 객체를 만들어준다는 의미이다.
- 하지만 해당 코드는 TypeScript Playground에서 에러를 발생시키고 있다. 이유는 무엇일까? 이후에 확인해보자.
에러가 발생하는 이유?
확인해보니 readonly를 인식하지 못해서 에러가 발생하는 것 같다.
그래서 왜 케이스를 통과를 못하는지 생각을 해보다가, keyof를 적용할 때 readonly 부분을 잡아주지 못해서 그랬다고 생각했다.
하지만 기본적으로 객체 안에서 keyof를 사용하면 readonly는 무시되지 않는 다는 것이다. 이게 무슨말인가?
interface Todo {
readonly title: string;
description: string;
}
type keyOfTodo = keyof Todo; // "title" | "description"
type Hello = {
[P in keyof Todo]: T[P]
}; // { readonly title: string; description: string; }
- Todo라는 타입에 keyof Todo를 하면 readonly가 제거된 상태로 결과가 출력된다.
- 하지만,Hello라는 타입을 보면 객체 안에서 keyof Todo를 하면 readonly가 유지된 채로 결과가 출력된다.
-이 부분을 인지하지 못해서 헷갈렸던 것 같다.
그러면 다시 위의 코드가 에러가 발생하는 이유에 대해서 생각해보자.
Exclude는 결과 타입이 객체가 아닌 유니온 타입이다.
type Fruit = "cherry" | "banana" | "strawberry" | "lemon";
type RedFruit = Exclude<Fruit, "banana" | "lemon">;
// type RedFruit = "cherry" | "strawberry" 와 같다.
방금 위에서 객체 타입에서 사용한 keyof는 readonly가 유지된다고 언급했는데, Exclude의 결과 타입이 유니온 타입이기 때문에 readonly가 유지가 되지 않는다. 그래서P in Exclude<keyof T, K> 를 하게 되면 readonly가 빠진 채로 순회가 되기 때문에 에러가 발생하고 있는 것이다.
풀이3) Exclude를 활용하면서 해결하는 방법
일단 왜 에러가 발생하는지는 파악이 되었다. 그러면 Exclude를 통해서 해결하는 방법이 있을까?
type cases = [
Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]
interface Todo1 {
readonly title: string
description: string
completed: boolean
}
interface Expected3 {
readonly title: string
}
// 메인 풀이
type ThirdMyOmit<T, K extends keyof T> = {
[P in keyof T as Exclude<P, K>]: T[P];
};
아까 봤던 코드랑 약간 다르다. 일단as 라는 키워드를 "그런데" 라고 해석해보자.
인지했으면 코드를 천천히 해석해보자.
먼저,Exclude<P, K>는 P extends K ? never : T; 라고 치환할 수 있을 것 같다.
치환한 뒤의 코드를 살펴보자.
type cases = [
Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]
interface Todo1 {
readonly title: string
description: string
completed: boolean
}
interface Expected3 {
readonly title: string
}
// 메인 풀이
type ThirdMyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : T]: T[P];
};
이렇게 보면 뭔가 조금씩 보인다.
1. keyof를 객체 내에서 사용했기 때문에 readonly 상태가 유지된다.
2. 유지되는 상태에서 P를 추출했기 때문에 P에는 readonly가 적용된 Property Key가 올 수 있다.
3. 그 P가 K에 할당될 수 있으면 빼고, 할당이 되지 않는다면 Index Signature 상태로 유지한다.
그래서 우리는 Exclude 유틸리티 타입을 사용해서 문제를 해결할 수 있었다.
최대한 다른 분들이 보기에 이해하기 쉽게 작성해봤는데 이해가 되실지는 모르겠다.
그래도 필자와 같이 해당 문제를 풀 때 이런 고민을 한적이 있다면 도움이 되었으면 좋겠다.
안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ
읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다!
감사합니다.