(60)

[도서] 객체지향의 사실과 오해 - 01) 협력하는 객체들의 공동체

안녕하세요. 최근에 객체지향에 관심을 가지게 된 이강욱이라고 합니다.조영호님의 객체지향의 사실과 오해라는 책을 읽어보려고 합니다. 책의 서두에서는 객체지향을 처음 접해보는 사람들은 아래와 같은 설명을 보게 된다고 합니다.1. "객체지향이란, 실세계를 직접적이고 직관적으로 모델링할 수 있는 패러다임"2. "객체지향이란 현실 속에 존재하는 사물을 최대한 유사하게 모방해서 소프트웨어 내부로 옮겨오는 작업이기 때문에 그 결과물인 객체지향 소프트웨어는 실세계의 투영이다"3. "객체"란 현실 세계에 존재하는 사물에 대한 추상화 하지만, 객체지향의 목표는 실세계를 모방하는 것이 아닌, 새로운 세계를 창조하는 것이라고 합니다.그럼에도 불구하고 많은 사람들은 객체지향을 실세계를 모방하려고 하는 것일까?책에서는 객체지향의..

[MySQL] MySQL에서의 Boolean 컬럼에 대해서 with 실행 계획

안녕하세요. 오늘은 MySQL Boolean 타입 관련해서 팀에서 피드백을 받아서 관련 개념들을 정리해보려고 합니다.설명에 앞서, 해당 포스팅은 아래에 첨부한 블로그 포스팅들을 참고한 내용입니다.저는 포스팅 내용을 읽어보면서 잘 이해가 되지 않았던 부분들과 실제로 테스트를 해보고 내용들을 작성하려고 합니다. - https://medium.com/daangn/mysql-boolean-%EC%BB%AC%EB%9F%BC-7abd9b35c664 MySQL BOOLEAN 컬럼PostgreSQL 서버와 같은 RDBMS 서버는 네이티브하게 BOOLEAN 컬럼 타입을 지원하고 있어요. 그래서 [TRUE | FALSE] 또는 [YES | NO] 와 같은 값들을 이용해서 불리언 값을 저장할 수 있어요. 물론 Oracle…..

[회고] 2024년도 회고록과 2025년 신년계획

이번에 2번째로 연말 회고록을 작성해본다. 블로그를 열심히 한다고 말할 수는 없지만 글을 쓰려고 할 때면 어떻게 써야할 지 막막하다.그래도 머리속으로 생각나는 대로 잘 정리해서 써보려고 한다. 사실 블로그를 처음 시작했을 땐 많은 개발자들이 블로그를 시작하니까 나도 따라해야지 라는 생각이 있었다. 그런데 이게 쓰다보니까 한번씩 생각이 날 때마다 내가 작성한 글들을 보는데 생각을 많이 할 수 있는 계기가 되는 것 같아서 한 번씩 생각날 때마다 쓰는 것 같고 이게 좋은 것 같다. 1.  회사, 그리고 6개월과 아쉬움. 그렇지만 열심히 !취업 준비 시절에 취직에 대해 욕심이 없었다. 그냥 게으르고 의지가 많이 부족했던 시점이었던 것 같다.그렇지만 프로젝트는 하나 제대로 끝내보고 싶어서 열심히 프로젝트에 임했었..

[NestJS + OpenAI] Microsoft Azure OpenAI 연동 방법

오랜만에 블로그 포스팅을 써보는 것 같다. 조금 바빴다는 핑계로..ㅎㅎ 이제는 다시 포스팅을 조금씩 써보려고 한다. 오늘은 지금까지 애를 많이 먹었던 NestJS 프레임워크를 활용해서 Microsoft Azure OpenAI 연동 방법과 연동 후 기대에 부응하지 못했던 점을 공유드려보려고 한다. NestJS를 가지고 Azure OpenAI를 연동하는 자료는 크게 없는 것 같아서 틈새시장을 노려보겠다 '__'1.  GPT 모델을 가지고 어떤 기능을 구현하려고 했는지?기획 유출이 될 수 있어 자세하게는 설명할 수 없지만, 민감한 사진과 프롬프트를 가지고 AI 모델에게 분석요청을 요청해서 해당 사진에 대해 분석 결과를 도출해내고, 해당 결과의 변화를 추적할 수 있는 기능을 만들고자 했다. 참고로, GPT 4모..

[Type Challenge] LastIndexOf + 이럴 때 Equal Type을 쓰는거구나?

사전지식해당 글을 읽기 전에 Type Challenge에서 제공하는 Equal Type에 대한 이해도가 필요합니다.아래 포스팅은 제가 열심히 작성해 본 글이니 확인 한번 하시면 좋을 것 같아요! https://dev-iamkanguk.tistory.com/entry/TypeScript-Type-Challenge-Equal-Type-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0 [TypeScript + Type-Challenge] Equal Type 분석하기Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉dev..

[NestJS] Type Safe Config Service

NestJS를 사용하면 환경변수(env)는 보통 @nestjs/config의 ConfigModule을 사용할 것이다.그렇지만 해당 라이브러리에서 제공하는 ConfigService를 기본으로 사용하면 undefined도 추론되고 해당 env에 그 key가 있는지 없는지도 파악할 수 없다. 그래서 이번 포스팅에서는 어떤 환경 변수들이 있는지 추론이 되고 undefined가 추론되지 않도록 코드를 작성해보려고 한다.필자는 해당 방법이 가독성도 좋은 것 같고 관리가 생각보다 잘 되는 것 같아서 이 방법을 계속 사용해보려고 한다. 기본적으로 ConfigService를 사용해봤다고 가정해보고 포스팅 작성을 진행해보겠다.app.config.ts 작성// src/configs/app.config.tsimport { r..

[TypeScript + Type-Challenge] Equal Type 분석하기

Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉 작성해보려고 한다.아직 100% 완벽하게 이해하지 못해서 주변에 자문을 많이 구해보려고 한다 (명확한 답안을 찾지 못함) https://github.com/type-challenges/type-challenges/blob/main/questions/00599-medium-merge/README.md type-challenges/questions/00599-medium-merge/README.md at main · type-challenges/type-challengesCollec..

[회고] [월간CS] 모던 자바스크립트 Deep Dive 스터디가 끝나고..

개요https://dev-iamkanguk.tistory.com/entry/%ED%9A%8C%EA%B3%A0-%EC%9B%94%EA%B0%84CS-%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B0%9C%ED%91%9C-%EC%8A%A4%ED%84%B0%EB%94%94%EB%A5%BC-%EB%A7%88%EC%B9%98%EA%B3%A0  [회고] [월간CS] 이펙티브 타입스크립트 발표 스터디를 마치고..오랜만에 블로그를 쓰는 것 같다. 핑계일 수 있겠지만 최근에 자취를 시작하면서 정신이 너무 없고, 회사도 운이 좋게 입사하게 되어 정신이 없고... 헬스장 환불 사건.... 기타 등등..

[Type Challenge - MEDIUM] Chainable Options

https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-options/README.md type-challenges/questions/00012-medium-chainable-options/README.md at main · type-challenges/type-challengesCollection of TypeScript type challenges with online judge - type-challenges/type-challengesgithub.com 타입스크립트 스터디도 진행하면서 타입에 조금 더 자신이 생겼다고 생각했지만 이 문제를 보자마자 이해 자체를 하지 못했다.그래서 다른 분..

[Type Challenge] Omit 문제에 대한 고찰 (readonly 관련)

https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README.md type-challenges/questions/00003-medium-omit/README.md at main · type-challenges/type-challengesCollection of TypeScript type challenges with online judge - type-challenges/type-challengesgithub.com TypeScript에서 지원하는 Utility Type인 Omit을 직접 구현해보는 문제였다.해당 문제를 구현하면서 헷갈렸던 부분을 정리하려고 한다. Omit에 대한 자세한 설명을 ..

[도서] 객체지향의 사실과 오해 - 01) 협력하는 객체들의 공동체

카테고리 없음 2025. 3. 2. 03:32

안녕하세요. 최근에 객체지향에 관심을 가지게 된 이강욱이라고 합니다.

조영호님의 객체지향의 사실과 오해라는 책을 읽어보려고 합니다.

 

책의 서두에서는 객체지향을 처음 접해보는 사람들은 아래와 같은 설명을 보게 된다고 합니다.

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장부터 쭉쭉 읽으면서 아 이런게 객체지향이고 실제로 프로그램을 작성해보고 싶다는 느낌을 받아보고 싶습니다.

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[MySQL] MySQL에서의 Boolean 컬럼에 대해서 with 실행 계획

SQL/MySQL 2025. 2. 26. 17:50

안녕하세요. 오늘은 MySQL Boolean 타입 관련해서 팀에서 피드백을 받아서 관련 개념들을 정리해보려고 합니다.

설명에 앞서, 해당 포스팅은 아래에 첨부한 블로그 포스팅들을 참고한 내용입니다.

저는 포스팅 내용을 읽어보면서 잘 이해가 되지 않았던 부분들과 실제로 테스트를 해보고 내용들을 작성하려고 합니다.

 

- https://medium.com/daangn/mysql-boolean-%EC%BB%AC%EB%9F%BC-7abd9b35c664

 

MySQL BOOLEAN 컬럼

PostgreSQL 서버와 같은 RDBMS 서버는 네이티브하게 BOOLEAN 컬럼 타입을 지원하고 있어요. 그래서 [TRUE | FALSE] 또는 [YES | NO] 와 같은 값들을 이용해서 불리언 값을 저장할 수 있어요. 물론 Oracle…

medium.com

- https://zzang9ha.tistory.com/436

 

MySQL EXPLAIN 실행계획 마스터하기(feat. RealMySQL 8.0)

💯 MySQL EXPLAIN 실행계획 마스터하기(feat. RealMySQL 8.0) 실행 계획(EXPLAIN) 이란? 대부분의 DBMS는 많은 데이터를 안전하고, 빠르게 저장 및 관리하는 것이 주목적이다. 이러한 목적을 달성하기 위해 사

zzang9ha.tistory.com

 

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<>0NULL<enabled<0 AND 0<enabled로 변환해서 실행을 했습니다. 이는 옵티마이저가 조건절이 부정 조건으로 처리되어있는 것을 인덱스를 사용할 수 있는 구조로 변환을 해서 실행을 한 것이기 때문입니다.


이렇게 MySQL Boolean 컬럼에 대해서 잘 설명해준 블로그 글을 보면서 이해가 안되었고 궁금했던 내용을 직접 찾아가면서 공부를 해봤습니다. 필자가 참고한 포스팅의 결론은 지금 보는 포스팅에 담지 않았으니 결론이 궁금한 독자 분들은 위에서 참고링크 걸어둔 포스팅 참고하면 좋을 것 같습니다.

 

좋은 포스팅을 제공해주신 당근 테크 블로그의 Sunguck Lee님 감사합니다.

 

'SQL > MySQL' 카테고리의 다른 글

[MySQL] DATE_FORMAT + ORDER BY에서 발생한 이슈 (%c, %e)  (1) 2023.12.20
iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[회고] 2024년도 회고록과 2025년 신년계획

회고록 2025. 1. 2. 00:17

이번에 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으로 매우 높았음 (통풍과 연관된 수치)

- 골소공증 진단 (?)

 

생각한 것 보다 혈당수치는 굉장히 정상이었다. 지방간은 뭐 술을 좋아하는 편이고 과체중이다 보니.. 조금은 있었을 거라 생각했고

콜레스테롤도 중고등학교때부터 높았어서 그냥 그런가보다 했다.

 

그런데 요산수치랑 골소공증은 정말 깜짝놀랬다. 다행히 골소공증은 기계 문제였어서 송도가서 검사해봤는데 담당의사분이 평균 연령대보다 골밀도 수치가 좋으니 걱정하지 말라고 하셨다.

 

출처: https://blog.naver.com/naraemedical01/223093057091

 

남자 정상수치는 7.0 까지라고 하는데 9.6이었다. 물론 건강검진이 있다고 하기 전 그 주에 고기랑 술을 좀 먹긴 했었다.

검사결과를 의사분이 설명해주시는데 "통증이 없었냐, 이정도면 바로 약드셔야 한다" 등 이야기를 해주셨는데 사실 좀 무서웠다.

 

그래서 일단 1-2개월 정도는 음식을 최대한 조심해보자는 생각이 들어서, 맥주와 순대국, 곱창을 절대 금기시했고 매일 아침에 사과와 그릭요거트를 먹기 시작했다. 그리고 고기류들도 최대한 절제하고 야채는 많이 먹었다. 부산과 군산에서 올라온 친구들을 만날 때도 고기는 최소한으로 먹고 술도 한잔먹을거 반잔씩 먹고.. 등의 나름의 노력을 했다.

 

 

그렇게 약 2개월 노력을 한 결과 다행히 요산수치가 7.2로 내려가 있었다. 조금 높긴하지만 그래도 다행이었다..

이 결과를 듣고 고삐가 풀려서 막 먹긴했지만.... 다시 정신차리고 신년에는 조심해보려고 한다.

 

4.  프로젝트로 인해 아주아주 바빴던 연말

최근에 10월부터 프로젝트 하나를 시작하게 되었다. 처음 시작할 때는 구현해야 할 기능의 양이 적어서 천천히 해도 괜찮겠다 생각했는데, 생각한 것 보다 기능의 양이 많아져서 퇴근하고 자기 전까지 작업을 했어야 했다. 그리고 테스트 하면서 발생한 이슈도 계속해서 대응해야 해서 시간이 많이 부족했던 것 같다.

 

바빠서 너무 고통스럽고 피곤했는데 지금은 이제 마무리 단계에 접하니까 뿌듯하고 내가 만든 서비스를 다른 사람들이 사용한다고 생각하니까 고생했던 날들을 보상받는 느낌이었다. 개발 역량에도 좋은 영향을 주었겠지만 책임감 이라는 덕목이 한 단계 더 레벨업한 느낌이 들었다.

 

하나 느낀게 있다. 프로젝트를 스터디랑 같이 병행했었는데 굉장히 빡셌다. 너무 일을 벌려둔 것 같다.

이번 년도에는 프로젝트 보다는 내가 듣고 싶고 공부하고 싶었던 분야를 먼저 공부하고, 프로젝트는 시간이 남을 때 조금씩 해보려고 한다.

 

"욕심부리지 말고  할 때는 하나만 열심히 하자"

 

5.  2025년에는 어떤 목표를 ?

여자친구는 말했다. "넌 2024년에도 거창했는데 2025년도 비슷하게 거창할 것 같아"

맞다..! 거창하고 똑같다. 내가 진짜 이루고 싶은 것들인데 2024년에는 10%도 못이룬 것 같아서 이번 년도 만큼은....!

키워드는 건강과 성장 이라고 할 수 있을 것 같다.

 

건강한 삶을 살아보자

라면, 고기, 술, 과자 등을 굉장히 좋아한다. 덕분에 최근에 살도 많이 쪘다. 스트레스 받으면 미친듯이 먹어내는 안좋은 습관을 가지고 있다보니.. 지킬 수 있을지는 모르겠지만 이번년도에는 진짜 70% 라도 실천해보려고 한다. 아래 내용들은 꼭 지켜보려고 노력해보려고 한다.

  • 아침) 호박즙, 그릭요거트에 아몬드, 사과를 챙겨먹기 OR 약간의 밥과 닭가슴살
    점심) 직원식당에서 일반식으로! 양은 적당히..
    저녁) 저녁 약속은 최대한 줄이고, 정말 간단하게 먹기.
  • 주 3회 이상은 무조건 운동하기 (아침 운동하고 싶지만 무리일 거 같음)
  • 5kg -> 10kg 체중감량

더 거창하게 하고 싶지만 이정도만이라도 지켜보려고 한다. 이 정도라도 지켜야 내가 나름 건강하게 살 것 같았다.

 

일기를 써보자 + 감사일기

사실 나를 아는 사람들은 이런말을 할 것이다. "너가? ㅋㅋㅋㅋㅋ" 그런데 주변에서들 많이 이야기를 했다. 한 줄이라도 일기를 써보면 생각정리가 되면서 회고할 수 있다고. 그래서 일기를 쓰기 시작했다. 3일째이다 ㅋㅋ

얼마나 갈지는 모르겠지만 매일 써보자. 그리고 매일 감사한 일을 찾아서 한 개 이상 써보기로 했다.

 

이를 위해 내가 사용하는 노트앱을 바꾸기로 했다. 항상 노션과 옵시디언을 쓰고 있었는데, 개인적으로 너무 불만족스러웠다.

마침 베어 라는 노트앱을 발견했는데 굉장히 마음에 들었다. 다만 유료.... 생각보다 비싸다...

이 베어 노트를 1년동안 써보려고 한다! 추천 !

 

급할수록 되돌아가자

최근에 개발자 이강욱 이라는 사람을 되돌아봤을 때 부족한 점이 너무 많다고 생각이 들었다.

그래서 개발자로서의 기초 체력을 기르기로 결정했다.

https://www.inflearn.com/roadmaps/1641

 

향로의 인프런 대학교 CS 전공 과정 (컴퓨터구조 / 운영체제 / 알고리즘 / 자료구조 / 네트워크 /

운영체제, DBMS/RDBMS 스킬을 학습할 수 있는 로드맵을 인프런에서 만나보세요.

www.inflearn.com

 

향로님이 인프런 사이트에 올려주신 CS 전공 과정 로드맵이다. 나는 소프트웨어로 전과를 하고 졸업을 한 전공생이지만, 수업을 어려워해서 잘 따라가지 못했던 것 같아서 지금부터라도 차근차근 CS지식을 쌓아보려고 한다. 

여기서 일부 강의들은 나의 입맛대로 바꿔서 들을 것 같다. 아마도 학교 친구랑 둘이서 같이 공부를 해볼 것 같다.

 

그리고 구매했던 강의와 책을 하나하나씩 보려고 한다. 그리고 Java를 조금 공부해보려고 한다.

사실 Java를 사용해본적이 거의 없어서.. 궁금했다. 커리어 전환을 한다는 이야기가 아니라, 어떤 언어인지 궁금해서 들어보려고 한다.

 

당장 시작할 강의 리스트는 다음과 같다.

 

넓고 얕게 외워서 컴공 전공자 되기 강의 | 널널한 개발자 - 인프런

널널한 개발자 | 넓고 얕게 외워서 컴퓨터 공학 전공자가 되고 싶은 모든 비전공 초보자를 위한 강의입니다. 컴퓨터 구조, 운영체제 등 컴퓨터 공학 전공 필수과목에서 어떤 것을 배울 수 있는지

www.inflearn.com

 

코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 | 패스트캠퍼스

국내 유일 NestJS 기초부터 MSA까지 한 번에 끝내는 NestJS 백엔드 강의 | 개발자가 가장 배우고 싶다고 해서 준비한 1위 도메인, 결제&스트리밍 프로젝트와 물어볼 곳 없다고 해서 섭외한 1등 강사,

fastcampus.co.kr

해당 강의는 한번 정리하는 느낌으로 쭉 들으면 굉장히 좋을 것 같아서 큰 마음 먹고 결제했었다 ㅋㅋ

Nest 강의중에 MSA와 아키텍처, 디자인 패턴 등 까지도 설명해주는 강의가 없어서..

 

김영한의 실전 자바 - 기본편 강의 | 김영한 - 인프런

김영한 | 실무에 필요한 자바 객체 지향의 핵심 개념을 예제 코드를 통해 쉽게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문

www.inflearn.com

 

이후에도 객체지향의 사실과 오해, 리팩터링 2판, 네트워크/운영체제 등 목표한 것들이 많은데.. 너무 급하게 들으려고 하지말고 하나하나씩 곱씹는다는 느낌으로 천천히 나아가보려고 한다.


2024년도 이강욱. 고생했지만 반성하자.

2025년도에는 새로운 이강욱이 되어보자.

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[NestJS + OpenAI] Microsoft Azure OpenAI 연동 방법

Framework/NestJS 2024. 11. 26. 16:26

오랜만에 블로그 포스팅을 써보는 것 같다. 조금 바빴다는 핑계로..ㅎㅎ 이제는 다시 포스팅을 조금씩 써보려고 한다.

 

오늘은 지금까지 애를 많이 먹었던 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

- OpenAI API : https://platform.openai.com/docs/api-reference/chat/create

- Microsoft Azure OpenAI : https://learn.microsoft.com/ko-kr/azure/ai-services/openai/concepts/models?tabs=python-secure%2Cglobal-standard%2Cstandard-chat-completions#gpt-4o-and-gpt-4-turbo

 

처음 접하는 개발자 분들은 왜 이걸 고민해? 굳이 Azure 사용해야해? 라고 생각할 수도 있을 것 같다. 필자도 그랬다.

필자의 팀은 Microsoft Azure에서 제공하는 OpenAI 모델을 선택했다. "민감한 사진"을 다룬다는 점을 감안해줬으면 좋겠다. 

  • OpenAI에서 제공하는 API는 외부 서드파티 API라고 한다면 Azure는 하나의 클라우드 플랫폼 서비스이기 때문에 보다 더 보안 및 프라이버시를 중시한다.
  • Azure는 의료 데이터라고 한다면 HIPAA(Health Insurance Portability and Accountability Act)와 같은 의료 데이터 보호 규정을 준수할 수 있는 옵션을 제공한다.

결국 보안이라는 측면에서는 민감한 데이터를 다루는 우리 팀에게는 Azure가 훨씬 적합하다고 생각했기 때문에 해당 플랫폼을 선택하게 되었다.

 

3.  Microsoft Azure OpenAI에서 모델을 구축하는 방법?

https://elsboo.tistory.com/52

 

Azure OpenAI 사용법 튜토리얼

MicroSoft 의 Azure OpenAI 를 처음 접하는 입장에서 바로 따라할 수 있는 튜토리얼이 구글링으로 잘 나오지 않아 써보는 글이다. Azure OpenAI 는 OpenAI 의 chatGPT 모델을 API 나 라이브러리 형태로 제공하는

elsboo.tistory.com

처음 입문해서 모델을 생성해야 하는 분들은 해당 링크를 참고하면 좋을 것 같다. 여기서 추가적으로 필자가 설명해보자면..

위의 블로그를 참고해서 잘 모델을 생성하면 다음과 같은 화면을 볼 수 있을 것이다.

 

여기서 우리는 Azure OpenAI Studio로 이동을 클릭해서 Studio로 들어가고, 배포 탭으로 들어가서 모델 배포 -> 기본 모델 배포를 클릭해보자.

 

그러면 모델을 선택할 수 있는 화면이 나온다. 필자는 GPT-4o 를 선택했다.

선택하고 나면 아래 화면을 볼 수 있다.

 

배포 유형은 Azure의 공식문서에 잘 설명이 되어있으니 참고해주시고, 필자는 표준 유형을 선택했다.

그리고 여기서 한가지 중요한점은 초반 무료 계정인 개발자분의 화면은 분당 토큰 속도 제한이 1K까지로 제한이 되어있을 것이다.

이는 무료계정이기 때문에 그렇다. 필자는 1K 이상의 토큰을 사용하기 때문에 해당 부분을 해결해야 했다.

 

해결하지 못하면 아래 사진과 같이 에러가 발생하는데 해당 에러를 마주쳤다면 아래 내용을 참고해서 해결해보자!

 

이제 설명할 내용은 100% 정확한 정보는 아니니까 참고해주길 바란다.

 

결론은 무료계정에서 종량제 계정으로 업그레이드를 해야한다. 계정을 업그레이드하면 필자가 올린 위 사진과 같이 1K 이상으로 설정할 수 있다. 종량제 계정으로 업그레이드를 하면 돈이 나가지 않냐? 돈 나간다. 그렇지만 우리는 처음 계정을 생성하면 200달러의 크레딧을 받기 때문에 해당 크레딧을 먼저 사용하고 나서 돈이 청구가 되니까 참고해주길 바란다.

 

해당 링크들은 가격정책, 업그레이드, 사용량 관련 문서이니 참고해주길 바란다.

- https://azure.microsoft.com/ko-kr/pricing/details/cognitive-services/openai-service/

- https://learn.microsoft.com/ko-kr/azure/cost-management-billing/manage/upgrade-azure-subscription

- https://learn.microsoft.com/ko-kr/azure/ai-services/openai/quotas-limits

 

위의 내용을 따라서 GPT 모델도 배포를 했으면 왼쪽에 플레이그라운드 -> 채팅 탭으로 들어가서 코드보기를 눌러보자.

 

코드보기를 보면 API_KEY와 CURL 형식의 요청 예시가 있는데 해당 코드를 참고하면 도움이 될 것이다.

여기까지 왔으면 우리는 이제 NestJS를 활용해서 간단하게 요청-응답 구조를 구현해보자.

 

4.  NestJS를 활용한 Azure OpenAI 활용

NestJS를 어느정도 활용할줄 안다고 가정하고 포스팅 작성 하겠습니다.
그렇게 어렵지는 않습니다.

 

- https://learn.microsoft.com/ko-kr/azure/ai-services/openai/supported-languages?tabs=dotnet-secure%2Csecure%2Cpython-secure%2Ccommand&pivots=programming-language-javascript

 

Azure OpenAI Service 지원 프로그래밍 언어 - Azure AI services

Azure OpenAI에 대한 프로그래밍 언어 지원.

learn.microsoft.com

 

위의 문서만 참고해도 충분히 코드를 작성할 수 있다. 해당 문서에는 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을 위의 코드에 삽입하여 실행을 시키면 다음 에러가 발생할 것이다.

 

확인해보니 Azure 모델에서 처리하고 있는 콘텐츠 정책이 있는 것 같았다.

https://learn.microsoft.com/ko-kr/azure/ai-services/openai/concepts/content-filter?tabs=warning%2Cuser-prompt%2Cpython-new#image-content

 

Azure OpenAI Service 콘텐츠 필터링 - Azure OpenAI

Azure AI 서비스에서 Azure OpenAI의 콘텐츠 필터링 기능에 대해 알아봅니다.

learn.microsoft.com

 

동의를 받고 수집한 사진이긴 하지만 Model의 레벨에서는 자동적으로 콘텐츠를 분석하고 막는 것 같았다.

현재는 해결을 위해 노력을 하는 단계인데 공식문서를 조금 더 꼼꼼하게 읽어보니 콘텐츠 필터를 신청할 수 있다고 한다.

 

 

필자와 같은 데이터를 다루는 개발자 분이라면 해당 문서 참고해서 콘텐츠 필터 신청을 통해 정상적인 결과를 받을 수 있으면 좋겠다.


이번 블로그 포스팅이 길이가 막 그렇게 길지는 않지만 해당 내용은 약 1주일 정도 삽질을 하고 나서 정리한 글이다.

생각보다 Azure OpenAI에 대한 자료가 너무 없어서, 많이 헤맸던 것 같다. 역시나 공식문서를 더더욱 꼼꼼히 읽어봐야 할 것 같다.

나와 같이 어려움을 겪는 사람들이 해당 포스팅을 보고 더욱 빠르고 정확하게 Azure OpenAI를 연동할 수 있으면 좋겠다!

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[Type Challenge] LastIndexOf + 이럴 때 Equal Type을 쓰는거구나?

Type Challenge 2024. 11. 5. 11:45

사전지식

해당 글을 읽기 전에 Type Challenge에서 제공하는 Equal Type에 대한 이해도가 필요합니다.

아래 포스팅은 제가 열심히 작성해 본 글이니 확인 한번 하시면 좋을 것 같아요!

 

https://dev-iamkanguk.tistory.com/entry/TypeScript-Type-Challenge-Equal-Type-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0

 

[TypeScript + Type-Challenge] Equal Type 분석하기

Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉

dev-iamkanguk.tistory.com

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/05317-medium-lastindexof/README.md

 

type-challenges/questions/05317-medium-lastindexof/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

풀이

import { Equal, Expect } from "../../utils"

namespace iamkanguk_lastIndexOf {
    export type LastIndexOf<T extends unknown[], U> = T extends [...infer F, infer L] ? L extends U ? F['length'] : LastIndexOf<F, U> : -1;
}

namespace TestCase {
    // OK
    type TC1 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>;
    // OK
    type TC2 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>;
    // OK
    type TC3 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[0, 0, 0], 2>, -1>>;
    // NOPE
    type TC4 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>;
    // NOPE
    type TC5 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>;
}

 

- 문제는 그렇게 어렵지 않았다. 하나의 배열을 둘로 쪼개는데 앞에서부터 접근하는게 아니라 뒤에서부터 접근한다고 생각하면 쉬웠던 것 같다. 충분히 이해할 수 있을 것이라고 생각한다.

- 하지만 문제는 위의 코드를 보면 TC4와 TC5가 통과를 안한다는 것이다. 로직적으로 잘못된 부분이 없는데 왜 통과를 안하는 것일까?

- 결론은 L extends U 부분에서 문제가 발생할 수 있다는 것이다.

어느 부분에서 문제가 발생할 수 있는건지?

TC1부터 TC3은 literal 타입이기 때문에 extends를 해도 정확한 비교가 되기 때문에 괜찮다.

하지만 TC4와 TC5를 보면 U가 Primitive 타입(원시 타입)이다. 이는 extends로는 정확한 비교가 안되기 때문에 에러가 발생하고 있는 것이다.

 

지금부터는 위에서 구현한 로직대로 차근차근 설명해보도록 하겠다.

보시기 편하게 LastIndexOf 타입을 다시 보여드리겠습니다.

type LastIndexOf<T extends unknown[], U> =
	T extends [...infer F, infer L]
    	? L extends U
        	? F['length']
            : LastIndexOf<F, U>
        : -1;

 

예시1 - TC4)

결과값이 4가 출력되어야 한다. 제일 뒤에 있는 number 타입을 찾아야 하기 때문이다.

편의상 string은 s, number는 n으로 표현하겠다.

type TC4 = Expect<Equal<LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>;
  F L L extends U(number) Finish? LastIndex Equal with U?
1 [s, 2, n, 'a', n] 1 1 extends number -> true YES 5 NO
2 - - - -    

 

표를 보면 이해가 되실지 모르겠지만 결론은 1 extends number가 true로 추론되어서 1의 index인 5를 가져온거고 4와 5는 다르기 때문에 틀렸다고 표시되는 것이다.

 

여기서 살짝 의문이 들 수 있다. 그러면 L extends U가 아니라 U extends L을 하면 통과되는거 아냐?

맞다. number extends 1은 false이기 때문에 다음 스텝으로 넘어갈 것이고 정상적으로 4를 가져올 것이다.

 

하지만 U extends L로 바꿔도 다음 예시에서 에러가 발생한다.

예시2 - TC5)

type TC5 = Expect<Equal<LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>;

 

  F L L extends U(any) Finish? LastIndex Equal with U?
1 [s, any, 1, number, 'a', any] 1 1 extends any -> true YES 6 NO
2 - - - - - -

 

위랑 비슷한 상황이다. 1 extends any가 true로 추론되어서 1의 index인 6을 가져온 것이고 6과 5는 다르기 때문에 틀렸다고 표시된다.

U extends L로 바꾸면 어떻게 될까? 그러면 1 extends any가 아니라 any extends 1이 될 것이다. 이는 결과가 boolean이다.

 

여기서 나오는 결론은 뭐냐면 이런 상황이 발생될 때는 extends 보다 더 정확한 비교가 필요하다는 것이다.

그래서 우리는 소스코드를 다음과 같이 수정할 수 있다.

최종 소스코드

import { Expect } from "../../utils"

export type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

namespace iamkanguk_lastIndexOf {
    export type LastIndexOf<T extends unknown[], U>
    	= T extends [...infer F, infer L]
        	? Equal<U, L> extends true   // 여기!
            	? F['length']
                : LastIndexOf<F, U>
            : -1;
}

namespace TestCase {
    // OK
    type TC1 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>;
    // OK
    type TC2 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>;
    // OK
    type TC3 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[0, 0, 0], 2>, -1>>;
    // OK
    type TC4 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>;
    // OK
    type TC5 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>;
}

 

L extends U 부분을 Type Challenge에서 제공하는 Equal Type을 적용하면

더욱 정확한 비교를 통해 정답처리를 받을 수 있다.

 

이전 포스팅에서도 좋은 참고자료를 첨부했지만

아래 링크를 확인해보면 Equal Type에 대한 다른 설명도 있으니 참고하면 좋을 것 같다.

https://github.com/type-challenges/type-challenges/discussions/9100#discussioncomment-6896958

 

Could you explain how does `Equal` type implementation work? · type-challenges type-challenges · Discussion #9100

type-challenges/utils/index.d.ts Line 7 in e3cc0d3 export type Equal<X, Y> =

github.com

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[NestJS] Type Safe Config Service

Framework/NestJS 2024. 11. 3. 20:51

NestJS를 사용하면 환경변수(env)는 보통 @nestjs/config의 ConfigModule을 사용할 것이다.

그렇지만 해당 라이브러리에서 제공하는 ConfigService를 기본으로 사용하면 undefined도 추론되고 해당 env에 그 key가 있는지 없는지도 파악할 수 없다.

 

그래서 이번 포스팅에서는 어떤 환경 변수들이 있는지 추론이 되고 undefined가 추론되지 않도록 코드를 작성해보려고 한다.

필자는 해당 방법이 가독성도 좋은 것 같고 관리가 생각보다 잘 되는 것 같아서 이 방법을 계속 사용해보려고 한다.

 

기본적으로 ConfigService를 사용해봤다고 가정해보고 포스팅 작성을 진행해보겠다.

app.config.ts 작성

// src/configs/app.config.ts

import { registerAs } from '@nestjs/config';
import { getEnv } from 'src/common/utils/config.util';

export default registerAs('app', () => ({
  //==============================
  // SWAGGER CONFIGS
  //==============================
  SWAGGER: {
    USERNAME: getEnv('SWAGGER_DOCS_USERNAME'),
    PASSWORD: getEnv('SWAGGER_DOCS_PASSWORD'),
  }
}));

 

위와 같이 코드를 작성하면 섹션별로 나눌 수 있어서 어떤 환경변수들이 있는지 확인하기 굉장히 쉬워진다.

getEnv 함수는 이후에 설명하도록 하겠다. 굉장히 간단하다.

Root Module에 ConfigModule을 등록해보자.

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from './configs/app.config';
import { AuthModule } from './apis/auth/auth.module';
import { AdminModule } from './apis/admin/admin.module';
import { validationSchema } from '@configs/validation-schema';
import { UserModule } from '@apis/user/user.module';
import { CronModule } from '@libs/cron/cron.module';
import { HospitalModule } from '@apis/hospital/hospital.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: `src/configs/envs/.env.${process.env.NODE_ENV}`,
      isGlobal: true,
      load: [appConfig],
      validationSchema: validationSchema,
    })
	// ...
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

 

- load 부분에 위에서 registerAs로 선언한 config를 넣어주면 된다. 필자는 그냥 appConfig 하나로 관리해버리는데 더 나누고 싶으면 나눠도 괜찮다.

getEnv 함수 설명

/**
 * Get Environment variables by key name
 *
 * @param key
 * @returns
 */
export const getEnv = (key: string): string => {
  return process.env[key] ?? '';
};

 

진짜 별거 없다. 그냥 process.env에서 가져오도록 했고 undefined로 추론되는 것을 막기 위해 Nullish Operator를 통해 없는 경우에는 빈 문자열을 할당하도록 했다.

환경변수 사용하는 법 (Dependency Injection)

@Injectable()
export class UserService {
  constructor(
    @Inject(appConfig.KEY) private readonly config: AppConfigType,
    private readonly prisma: PrismaService,
    private readonly awsS3Service: AwsS3Service
  ) {}
  
  public async test() {
     const { ENV1, ENV2 } = this.config.AWS;
  }
}

 

위와 같이 굉장히 쉽게 사용이 가능하다. 그리고 process.env나 configService를 사용할 때는 Type Checking이 안되었을 것이다.

하지만 우리는 app.config.ts 파일에서 섹션별로 선언을 해줬기 때문에 타입이 정의되어서 Type Checking이 가능하다.

그래서 IDE 레벨에서 자동으로 추천해주기도 한다.

다른 모듈에서 Config를 import 해야하는 경우

import appConfig from '@configs/app.config';
import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CustomRedisService } from './custom-redis.service';
import { getAppConfig } from 'src/common/utils/config.util';

@Module({
  imports: [
    RedisModule.forRootAsync({
      imports: [ConfigModule.forFeature(appConfig)],
      inject: [ConfigService],
      // TODO: nestjs-redis 라이브러리 버전 체크 및 Downgrade 필요할수도?
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-expect-error
      useFactory: (configService: ConfigService): RedisModuleOptions => {
        const config = getAppConfig(configService);
        const { HOST, PORT, PASSWORD } = config.REDIS;

        return {
          config: {
            host: HOST,
            port: +PORT,
            password: PASSWORD,
          },
          readyLog: true,
          errorLog: true,
        };
      },
    }),
  ],
  providers: [CustomRedisService],
  exports: [CustomRedisService],
})
export class CustomRedisModule {}

 

해당 소스코드는 Redis Module을 작성한 코드이다.

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이 안되는데에 민감한 것 같다 ㅋㅋ

한번 다른 분들도 적용해보면 좋을 것 같다!

참고자료

- https://suyeonme.tistory.com/109

 

[Nest.js] Type-safe하게 ConfigService로 환경변수 관리하기

ConfigService를 사용하지 않는 경우 일반적으로 환경변수에 접근하는 경우. process.env로 접근하게 됩니다. 이렇게 접근하는 경우 환경변수를 사용하는데는 문제가 없지만 어떤 환경변수가 사용가능

suyeonme.tistory.com

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[TypeScript + Type-Challenge] Equal Type 분석하기

Language/TypeScript 2024. 10. 24. 10:53

Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.

필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉 작성해보려고 한다.

아직 100% 완벽하게 이해하지 못해서 주변에 자문을 많이 구해보려고 한다 (명확한 답안을 찾지 못함)

 

https://github.com/type-challenges/type-challenges/blob/main/questions/00599-medium-merge/README.md

 

type-challenges/questions/00599-medium-merge/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

해당 문제를 처음에 필자는 다음과 같이 풀었다.

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 타입을 사용하는게 아닐까라는 약간의 의심을 해봤다.

2.  Type Challenge에서 제공하는 유틸 타입인 Equal 타입이 잘못된 경우

먼저 확인해보니 Equal Type에 대한 이야기가 많은 것 같다.

아래는 TypeScript 공식 GitHub의 Issue로 올라온 내용이다.

 

https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650

 

[Feature request]type level equal operator · Issue #27024 · microsoft/TypeScript

Search Terms Type System Equal Suggestion T1 == T2 Use Cases TypeScript type system is highly functional. Type level testing is required. However, we can not easily check type equivalence. I want a...

github.com

 

Type Challenge에서 유틸성 타입으로 사용된 Equal 타입은 아래와 같다.

export type Equal<X, Y> =
    (<T>() => T extends X ? 1 : 2) extends
    (<T>() => T extends Y ? 1 : 2) ? true : false;

 

위의 타입을 어떻게 이해해야 할까? 아직 이해를 100% 하지는 못했지만..

아래 두 링크를 참고하면 이해할 수 있을 것이라고 생각된다.

 

결론만 말하면 X extends Y ? true : false 랑 비슷하지만 차이점을 두기 위한 것이라고 생각하자.

true extends boolean은 true이지만 Equal 하지는 않은 것이 사실이다. Equal 하지 않다는 것을 표현하기 위해 저 기법을 사용했다고 지금은 그렇게 생각하자 (더욱 깊은 수준에서 엄격하게 타입을 비교할 수 있다?)

 

- https://suloth.tistory.com/36

 

타입챌린지 : Equal - 번외

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. 타입챌린지에서 항상 사용하는 타입이 있다. 그것은 바로 Equal. 그래서

suloth.tistory.com

- https://kscodebase.tistory.com/643

 

Equal type 설명하기

[Feature request]type level equal operator · Issue #27024 · microsoft/TypeScript Search Terms Type System Equal Suggestion T1 == T2 Use Cases TypeScript type system is highly functional. Type level testing is required. However, we can not easily check ty

kscodebase.tistory.com

 

위의 코드가 대충 어떤 식으로 구현이 되었는지를 파악했다면 이제 저 Equal 타입이 과연 잘못된 타입일까? 를 의심해봐야 한다.

결론적으로는 잘못된 건 아니지만 100% 정확한지는 모른다는 것이다.

 

- https://stackoverflow.com/questions/68961864/how-does-the-equals-work-in-typescript/68963796#68963796

 

How does the `Equals` work in typescript?

I found an Equals utils mentioned at: https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650 export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extend...

stackoverflow.com

 

위 StackOverflow를 보면 Equal 타입에 대해 굉장히 잘 분석해준 답변이 있다. 시간 내서 천천히 해석하면서 읽어보는 것을 강추한다.

해당 내용을 보면 왜 Type Challenge에서 { x: 1 } & { y: 2 } 와 { x: 1, y: 2 } 가 왜 Equal 하지 않다는 것이 이해가 될 것이다.
(물론 이것도 100% 정답은 아닐 수 있다는 가정이다)

 

지금부터 조금 길 수도 있지만 최대한 짧게 Stack Overflow에 올라온 답변에 대해 핵심만 정리를 해보려고 한다.

리마인드 차원에서 다시 코드를 여기에 작성해보고 시작하도록 하겠다.

export type Equal<X, Y> =
    (<T>() => T extends X ? 1 : 2) extends
    (<T>() => T extends Y ? 1 : 2) ? true : false;

(1) 해당 타입은 두 개의 함수 타입을 비교해서 동등한 지 확인하는 것이다.

위 문법은 화살표 함수에 제네릭을 입힌 것이라고 보면 된다. 해당 문법이 이해가 되지 않는다면 아래 타입스크립트 핸드북을 읽어보자.

https://www.typescriptlang.org/docs/handbook/2/generics.html

 

여기 지점에서 필자의 고민과 동일하게 왜 저렇게 함수 타입을 비교하는 걸까? 다른 방법은 없을까? 라고 생각할 수 있다.

필자도 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 라는 거야?

결론만 말하자면 flag 비교로 인해 false가 발생한다는 것이다.

아래 코드는 TypeScript에서 수행하는 Type Checker의 소스 중 일부이다.

function isTypeRelatedTo(source: Type, target: Type, relation: /* ... */) {
    // ...
    if (source === target) {
        return true;
    }
    if (relation !== identityRelation) {
        // ...
    }
    else {
        if (source.flags !== target.flags) return false;
        if (source.flags & TypeFlags.Singleton) return true;
    }
    // ...
}

 

 

위의 소스코드에서 눈여겨 봐야할 점은 Type 타입이 무엇이고, flag가 의미하는게 뭐냐는 것이다.

아래 코드를 보면 이해될 것이다. TypeFlags에 Intersection 이 정의가 되어있다는 것이다.

export interface Type {
	flags: TypeFlags;   // 여기!
    symbol: Symbol;
    pattern?: DestructuringPattern;
    aliasSymbol?: Symbol;
    aliasTypeArguments?: readonly Type[];
}
    
export enum TypeFlags {
	// ...
    Object = 524288,
	Intersection = 2097152,
}

 

source에는 { x: 1 } & { y: 2 } 가 들어갔고, target에는 { x: 1, y: 2 } 가 할당되었다고 생각해보자.

그러면 위의 코드를 보면 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.

(boolean이 true | false로 치환되어 제네릭 타입에 할당되어 분배 조건부 타입이 적용된다는 건 처음알았음)

3.  마무리

이정도로 글을 마무리하려고 한다. 아직 100% 이해했다고 말은 못하지만, 이 정도로만 학습하려고 한다.

검토를 몇번 하긴 했지만 해당 글에서 틀린 부분이 몇 개 있을 것이라고 생각이 드는데...

 

관심이 있거나 Type Challenge를 평소에 푸는 개발자 분들은 필자가 위에 참조 걸어놓은 블로그들과 Stack Overflow 내용을 한 번은 읽어보면 좋을 것 같다고 생각한다.

 

언제든 따끔한 피드백은 환영입니다! 감사합니다 :)


 

필자가 올린 issue입니다. 아마 댓글이 안달릴 것 같지만 이 포스팅을 보시는 개발자 분들 중에 흥미가 있다면 한번 필자의 글을 읽어보고 답을 달아주시면 대단히 감사하겠습니다!_!

https://github.com/type-challenges/type-challenges/issues/34874

 

Difference between the Equal Type implemented in the Type Challenge Repository and the Equal Type in this issue. · Issue #34874

I have a question while solving the Merge problem of the medium level. Here is the source code I implemented the Merge type. type Merge<F, S> = Omit<F, keyof S> & S /* _____________ Test Cases ____...

github.com

 

참고자료

 

- https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650

 

[Feature request]type level equal operator · Issue #27024 · microsoft/TypeScript

Search Terms Type System Equal Suggestion T1 == T2 Use Cases TypeScript type system is highly functional. Type level testing is required. However, we can not easily check type equivalence. I want a...

github.com

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[회고] [월간CS] 모던 자바스크립트 Deep Dive 스터디가 끝나고..

회고록 2024. 10. 14. 13:40

개요

https://dev-iamkanguk.tistory.com/entry/%ED%9A%8C%EA%B3%A0-%EC%9B%94%EA%B0%84CS-%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B0%9C%ED%91%9C-%EC%8A%A4%ED%84%B0%EB%94%94%EB%A5%BC-%EB%A7%88%EC%B9%98%EA%B3%A0

 

 

[회고] [월간CS] 이펙티브 타입스크립트 발표 스터디를 마치고..

오랜만에 블로그를 쓰는 것 같다. 핑계일 수 있겠지만 최근에 자취를 시작하면서 정신이 너무 없고, 회사도 운이 좋게 입사하게 되어 정신이 없고... 헬스장 환불 사건.... 기타 등등 진짜 다양한

dev-iamkanguk.tistory.com

 

이전에 월간CS에서 이펙티브 타입스크립트 책으로 스터디를 한 적이 있었다.

위의 포스팅에서 직접 스터디를 한번 운영해보고 싶다고 언급했었는데 약 3개월이라는 시간동안 스터디를 운영해봤다.

모던 자바스크립트 Deep Dive라는 책을 1회독 하는 것을 목표로 했다. 아래는 직접 작성한 공고글이다.

 

https://inblog.ai/monthly-cs/22772

 

[24년 7월] 모던 자바스크립트 딥다이브 스터디 - 월간 CS

도서, [모던 자바스크립트 딥다이브] 발표 스터디 | 공지

inblog.ai

 

운영방식

(1) 커리큘럼

커리큘럼에 대해서 굉장히 많은 고민을 했었다. 책의 양이 방대하고 취준생 분들도 계셨지만 직장인 분들도 계셨기 때문에 적절하게 양조절을 해야했다. 그래서 다른 사람들은 어떻게 이 책을 가지고 스터디를 운영하는지 많이 찾아봤었는데 마땅하게 마음에 드는 커리큘럼이 없었다.

 

마침 ZeroCho님이 모던 자바스크립트 Deep Dive 읽는 법 이라고 유튜브 영상을 올려주셔서 참고해봤는데 생각보다 괜찮았다.

아래는 실제로 필자가 운영한 커리큘럼인데 스터디 운영에 관심있으면 해당 커리큘럼을 참고해보면 좋을 것 같다.
(추석 연휴가 껴있어서 약간의 변형이 있음. 참고 바람)

더보기

1주차: 4장 ~ 9장

2주차: 10장 ~ 15장

3주차: 16장 ~ 21장 (16장과 20장은 Optional)

4주차: 22장 ~ 23장 (22장: this / 23장: 실행 컨텍스트) (중요!)

5주차: 24장 ~ 25장 (24장: 클로저 / 25장: 클래스) (중요!)

6주차: 26장 ~ 27장 + 4주차 복습

7주차: 33장 ~ 37장 + 5주차 복습

8주차: 28장 ~ 32장 (추석연휴 - 스터디 미진행 -> Number, String과 같은 빌트인 객체 설명이라 7주차와 분량 변경)

9주차: 38장 ~ 39장

10주차: 40장 ~ 41장

11주차: 42장 ~ 45장

12주차: 46장 ~ 49장

원래는 13주로 기획했지만 중간에 커리큘럼 변동을 해서 총 12주로 마무리했다. 더 짧게 구성하고 싶었지만 빡빡한 시간 속에 하고 싶지 않았기 때문에 조금 여유롭게 구성을 한 것 같다.

(2) 온라인 OR 오프라인

필자는 오프라인 스터디를 선호하는 편이다. 나만 그러는지는 모르겠는데 온라인 스터디는 너무 집중력이 떨어진달까....

그리고 필자가 게으른 성격이다 보니 일요일에 아무것도 안하고 누워있을 것 같아서 나름의 강제성(?)을 부여한 것 같다.

 

이전 스터디에서는 모두 오프라인 스터디로 진행했었지만 이번 스터디 같은 경우에는 12주라는 시간동안 매주 일요일 오전 10시에 강남역을 오는 것은 쉽지 않을 것이라고 생각했다. 그래서 온라인과 오프라인을 병행하면 부담이 조금 줄어들 것 같아 병행을 선택했다.

(3) 스터디 방식

스터디는 크게 DIL 작성과 발표자료 준비로 나눌 수 있을 것 같다.

스터디 방식은 위에 있는 공고 링크보면 확인할 수 있으니 참고하면 좋을 것 같다. 간단하게만 언급해보면!

 

DIL 작성은 월요일부터 토요일까지 책 읽은 부분에 대해서 정리하는 거고, 발표자료 준비는 일요일까지 준비를 해오면 된다.

스터디 당일에는 랜덤으로 발표자를 선점하기 때문에 모든 스터디원이 발표자료를 준비해야 한다!

(4) 제약 조건

3개월이라는 기간동안 스터디를 운영하는 것은 리스크가 굉장히 큰 일이라고 생각한다.

루즈해지기 좋은 시간이고 중도하차를 하고 싶다는 생각이 많이 들 수 있을 기간이라고 생각했다. 그렇지만 커리큘럼을 먼저 결정했기 때문에 3개월 이라는 시간은 변경할 수 없었다.

 

그래서 결석, 지각에 대해 크게 제약을 두지 않았다. 원래는 보증금을 먼저 받고 지각 또는 결석, 그리고 발표자료 준비 미흡 시에는 보증금에서 깎는 등의 제약 조건을 추가하려고 했지만, 그냥 특별한 제약조건 없이 편안한 분위기에서 자유롭게 하고 싶었던 것 같다.

 

하지만 다음에 스터디를 운영할 땐 기간을 더 짧게 잡고 제약조건을 설정할 것 같다.

 

스터디를 실제로 운영해보면서 배운점

스터디를 운영하는 것은 쉽지 않구나 + 나름의 리더십?

스터디 운영이라고 해봤자 뭐 별거 있을까? 라는 생각을 가지고 있었다. 그냥 뭐 공지 올려주고 발표자료 한번씩 확인해보고..

그렇지만 결코 만만하지 않았다. 지금도 스터디 첫날 떨림이.... 기억나는 것 같다.

 

스터디룸 예약과 매주 스터디 공지하는 것도 계속 기억하고 있어야 하고 마음이 흔들리는 스터디원이 있으면 얘기도 같이 나눠봐야 하고 등등.. 신경 써야할게 생각보다 많았던 것 같다.

 

그렇지만 그 과정에서 리더십이라는 덕목을 더 많이 배웠던 것 같다. 직접 운영을 하려다 보니 어떻게 해야 스터디원들이 불편함을 가지지 않고 편안하게 스터디에 임할 수 있을까? 라는 생각을 계속 하게 되니 자연스럽게 리더십이 발휘된 것 같다는 느낌이 들었다.

 

실제로 필자가 일일이 발표자료 링크 올리는 파일을 생성하고, DIL 파일을 직접 생성하는 등의 작업들이 귀찮다는 생각이 들어가지고, 다른 스터디원들도 파일 생성하는게 귀찮지 않을까 싶었다. 그래서 직접 terminal에서 본인 이름을 선택하면 DIL 파일 템플릿과 발표자료 링크를 업로드 할 수 있는 템플릿 파일을 자동으로 생성해주는 스크립트를 작성해서 불편함을 줄여봤다. (소스코드는 별로...입니다)

 

예를 들어 발표자료 파일의 이름을 week_1 같이 작성하는데 몇 주차인지 헷갈릴 때가 분명히 있는데 필자가 작성한 스크립트로 파일을 생성하면 굳이 계산하지 않아도 되는 편리함이 있었다 :)

(필자는 굉장히 잘 썼지만... 다른 우리 스터디원들은 사용하셨는지는 잘 모르겠다)

 

https://github.com/monthly-cs/2024-07-modern-javascript-deep-dive/tree/main/lib

 

2024-07-modern-javascript-deep-dive/lib at main · monthly-cs/2024-07-modern-javascript-deep-dive

Contribute to monthly-cs/2024-07-modern-javascript-deep-dive development by creating an account on GitHub.

github.com

 

그리고, 평소에 남들 앞에서 먼저 말을 꺼내고 그런게 힘들었지만, 3개월 간 스터디를 주도적으로 이끌어 나가야 하다보니 책 읽을 때 어떤 부분이 어려웠는지, 관련한 자료들은 뭐가 있는지 등을 지속적으로 공유하고 스터디원들에게도 어떤 부분이 어려웠는지, 요즘 회사 및 취준 생활은 어떤지 등의 근황 토크도 같이 하니까 재미가 있었고 나도 자연스럽게 남들 앞에서 말을 꺼내는게 무섭지가 않았다.

(물론 옆에서 우리 스터디원들이 적극적으로 임해줘서 그런 것 같다)

 

스터디를 운영하면서 아쉬웠던 점

물론 배운점만 있는건 아니었다. 회고록을 작성하고 있는 이 시점에 생각해보면 분명히 아쉬웠던 점이 있었다.

이건 스터디원들에게 아쉬운 것이 아니라 나 자신한테 조금 아쉬웠던 것 같다.

(1) 발표자료를 성의있게 준비하지 못했던 것 같다.

https://github.com/monthly-cs/2024-07-modern-javascript-deep-dive/tree/main/docs/iamkanguk97

 

2024-07-modern-javascript-deep-dive/docs/iamkanguk97 at main · monthly-cs/2024-07-modern-javascript-deep-dive

Contribute to monthly-cs/2024-07-modern-javascript-deep-dive development by creating an account on GitHub.

github.com

 

필자는 발표자료를 Notion으로 준비했다. 발표자료에는 책을 읽으면서 중요했던 부분을 텍스트로 작성했고, 관련해서 참고할 만한 자료가 있다면 링크를 첨부해서 같이 보는 식으로 준비했었다.

 

하지만 이 발표자료를 보면 눈에 확 들어오지는 않았을 것 같다. 그냥 대충 국어책 읽는 듯한 느낌이 들었을 수 있었겠다 라는 생각을 이제서야 해봤다.

 

그래서 다음 스터디에 참여를 하게 된다면 Notion 보다는 PPT 같은 걸로 눈에 확 들어오고 간결한 발표자료를 준비하는게 더욱 기억에 남는 스터디가 될 수 있을 것 같다고 생각했다.

(2) 야근을 하게 되면서 작성 주기가 줄어드는 DIL ..

해당 스터디는 7월 말 부터 10월 중순까지 진행된 스터디였다. 초반에는 매일매일 퇴근을 하고 조금씩 책을 읽고 DIL을 작성했었다.

그런데 예상치 못하게 회사에서 8월부터 9월 말까지 평일 초과근무와 주말 출근이 거의 확정이 되어있는 상태였어서 약 2개월 간 휴식 없이 근무를 했던 것 같다.

 

그래서 DIL을 평일에 잘 작성하지는 못했던 것 같다. 물론 책을 한번 쭉 읽긴 했지만 DIL까지 작성할 여력은 없었던 것 같다.

하지만 핑계일 뿐..... 한다면 했었을 텐데... :(

 

DIL을 매일 작성하지는 않아도 됐지만 확실한 건 스터디는 본인이 어느정도 여유가 있을 때 해야 더욱 효과를 보는 것 같다고 생각한다.

(3) 우려했던 스터디원 탈퇴..

사실 이 이슈같은 경우에는 필자 뿐만 아니라 스터디를 운영하는 모든 분들이 가장 1순위로 우려하는 사항이라고 생각한다.

그래서 내가 많이 노력해서 탈퇴하는 스터디원이 없도록 해봐야 겠다 라는 마음가짐으로 스터디 운영을 시작했는데..

 

아쉽게도 3명의 스터디원이 탈퇴를 하게 되었다. 두 분은 개인 사정이 있으셔서 탈퇴를 하셨고, 한 분은 노쇼를 해버리셔서.. 연락을 안받으셔서 그냥 탈퇴 통보를 드렸었다.

 

어떤 단체(모임)을 운영함에 있어서 구성원이 탈퇴하는 분명히 발생할 수 있는 이슈라고 생각한다. 하지만 내가 리더십이 있는 사람이고 스터디가 배울점이 많은 스터디였다면 어쩌면 탈퇴를 하지 않았을 수도 있겠다? 라는 생각이 들었다.

 

앞으로의 스터디 계획

(1) 11월 함수형 프로그래밍 스터디 예정

이전에 유인동님의 함수형 프로그래밍 강의를 관심이 생겨서 구매해봤는데 강의를 제대로 들어본 적이 없는데 ㅠ

스터디에서 함수형 프로그래밍 이야기가 우연히 나왔고 강의를 샀는데 아직 수강 못하신 분도 계셔가지구

같이 참여했던 스터디원들끼리 함수형 프로그래밍 스터디를 11월에 짧고 굵게 한번 해보려고 해서..

한번 스터디원들이랑 같이 기획을 해보려고 한다!

(2) Real MySQL 토론 스터디

https://inblog.ai/monthly-cs/20322

 

[24년 11월] RealMySQL 토론 스터디 - 월간 CS

토론형 스터디 | 공지

inblog.ai

 

월간CS에서 처음으로 백엔드 스터디 공고를 보고 지원했다.

아직 참여자로 뽑히지는 않았지만 Real MySQL은 꼭 한번 읽어보고 싶은 책이었고, 마침 11월부터 12월까지 회사가 바쁘지 않고 여유로울 것 같아서 신청해봤다.

 

참여자로 뽑히면 발표자료도 성의있게, 매일매일 조금씩이라도 책을 꾸준하게 읽을 수 있는 습관을 키워보려고 한다.

 

 

스터디 끝나고 회식으로.. 강남역의 베트남이랑 이라는 식당 갔는데 음식도 괜찮았고 베트남 느낌나서 좋았던 것 같다!

음식 사진은 나중에... 스터디원 분들이 올려주시면 추가 첨부 할게요!

저희팀 스터디원 분들 다들 고생하셨습니다 :)

 

감사한 후기 ㅜ_ㅜ

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[Type Challenge - MEDIUM] Chainable Options

Type Challenge 2024. 10. 2. 15:44

https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-options/README.md

 

type-challenges/questions/00012-medium-chainable-options/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

타입스크립트 스터디도 진행하면서 타입에 조금 더 자신이 생겼다고 생각했지만 이 문제를 보자마자 이해 자체를 하지 못했다.

그래서 다른 분들이 어떤 생각을 가지고 풀이를 했는지를 먼저 파악하고, 직접 생각하면서 풀려고 노력을 해봤다.

시작을 어떻게 해야할지만 파악한다면 크게 어렵지는 않은 문제였던 것 같은데, 시작을 어떻게 해야할지 파악하는게 가장 어려웠던 것 같다.

 

해당 글은 다른 분들의 풀이를 참고했습니다!

https://suloth.tistory.com/52

 

타입챌린지 : 12-Chainable Options (medium)

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-o

suloth.tistory.com

 

1단계: option 메서드가 Chainable을 반환한다는 점을 파악해야 한다.

type Chainable = {
    option(key: string, value: any): Chainable;
    get(): any;
}

declare const a: Chainable;

const result1 = a
  .option('foo', 123)
  .option('bar', { value: 'Hello World' })
  .option('name', 'type-challenges')
  .get()

 

위의 소스코드를 보면 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 = {}>제네릭을 통해 처음 기본 객체(빈 객체)를 선언해주어야 체이닝이 가능하다.

- option<K extends string, V> : K는 string 타입이고, V도 선언해준다.

- (key: K extends keyof T ? never : K, value: V) : key는 K가 T의 key 값이면 중복 방지를 위해 never를 반환하고, 아니면 K를 그대로 적용한다. value는 제네릭 V를 그대로 적용해준다.

- Chainable<T & Record<K, V>> : 현재 객체의 정보를 저장하고 넘기기 위해서 제네릭을 설정해주는 것이다. T에다가 매개변수로 받은 K와 V 조합을 T에다가 적용을 시켜줘서 반환한다는 의미이다.

 

3단계: get 메서드의 리턴 타입은 무엇일까?

필자는 처음 구현할 때 ReturnType 뭐시기 해서 사용했었는데 그럴 필요가 없었다. 그냥 T 그대로 리턴하면 되는 것이다.

왜냐하면 결국에 get은 option 체이닝을 진행하고 마지막에 get 메서드를 사용하기 때문이다.

type Chainable<T = {}> = {
  option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>
  get(): T
}

 

4단계: 마지막 오류 해결

위의 코드까지 적용해보면 마지막 테스트 케이스에만 오류가 발생하는데 이유를 확인해보니까..

const result3 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 123)
  .get()

type Expected3 = {
  name: number
}

 

위와 같이 name 이라는 같은 키에서 처음에는 string을, 그다음에는 123이라는 number 타입을 적용했다.

Expected3 에는 name이라는 키에는 number 타입이 적용되었다. 즉, 마지막으로 입력된 값이 남는다는 것이다.

type Chainable<T = {}> = {
  option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<Omit<T, K> & Record<K, V>>
  get(): T
}

 

그래서 option 메서드의 반환 타입을 수정했는데 T에서 K 값을 가지고 있으면 기존 K 값을 제거해주고 새로운 K 값을 넣어주면 된다.

해당 로직을 적용하기 위해서는 Omit을 사용하면 될 것 같다.

 

 

 

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[Type Challenge] Omit 문제에 대한 고찰 (readonly 관련)

Type Challenge 2024. 9. 27. 17:19

https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README.md

 

type-challenges/questions/00003-medium-omit/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

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 유틸리티 타입을 사용해서 문제를 해결할 수 있었다.


 

최대한 다른 분들이 보기에 이해하기 쉽게 작성해봤는데 이해가 되실지는 모르겠다.

그래도 필자와 같이 해당 문제를 풀 때 이런 고민을 한적이 있다면 도움이 되었으면 좋겠다.

 

핵심은 객체 내에서의 keyof는 readonly를 유지할 수 있다는 것.

 

NestJS 오픈채팅 톡방에서 도움을 주신 Michael님 감사합니다.

 

- https://chanhuiseok.github.io/posts/ts-3/

- https://bkdragon0228.tistory.com/m/3

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

방명록