WIL - 2025.08 Week 5

|

이번 주는 특별히 큰 진전을 이룬 건 없는 것 같다.
계획했던 CS50 강의는 손도 못 댔고, 타입스크립트 문제 풀이를 한두 개 정도 진행했을 뿐이다.
그나마 『패턴으로 익히고 설계로 완성하는 리액트』 책의 마지막 파트만 남겨두었고, 추가 공부를 위해 책 세 권을 새로 구매한 정도가 이번 주의 성과였다.

그 외에는 메인 개발 작업보다는 각종 에러 처리가 더 기억에 남는다.

회사에서 NestJS로 개발을 진행하면서 class-validator가 Body pipe 단계에서 길이 검증을 제대로 수행하지 못하는 문제를 발견했다.
원래라면 “길이가 올바르지 않다”는 식의 에러 메시지를 띄워야 했는데, 실제로는 DB의 길이 제약 오류가 발생하면서 민감한 DB 정보까지 노출되는 상황이었다. 이는 보안상 큰 문제가 될 수 있기 때문에 원인을 추적했는데, 범인은 TypeScript의 Omit이었다.

Omit은 타입스크립트의 타입 유틸리티일 뿐, 컴파일 시점에서만 작동하여 타입을 변경할 뿐이다.
런타임 시 실제 클래스에서 프로퍼티를 제거하지 않기 때문에 class-validator에서는 해당 필드가 여전히 존재하는 것으로 간주되고, 결국 유효성 검증이 동작하지 않은 것이다. 덕분에 OmitType과 같은 NestJS 전용 유틸리티를 사용해야 한다는 교훈을 얻었다.

또한 일렉트론으로 배포한 앱에서 사용자 CS 문의가 들어왔다. 코드를 살펴보니, 과거에 비공개 레포에서 일렉트론 자동 업데이트 및 배포가 정상적으로 되지 않는 문제가 있어 공개 레포로 전환했던 적이 있었다.
이 과정에서 깃허브 검색에 최대한 노출되지 않게 하려고 레포 이름을 다르게 설정해 두었는데, 그 부분이 원인이 되어 버그가 발생한 듯했다. 이렇게 작은 결정이 예상치 못한 방식으로 스노우볼처럼 굴러올 줄은 전혀 상상하지 못했다.

이번 주는 계획대로 흘러가진 않았지만, 에러와 문제 해결 과정에서 더 큰 배움을 얻었다.
결국 개발은 문제 없는 길을 찾는 게 아니라, 문제를 마주하고 풀어가는 과정임을 다시금 깨달았다.

TypeScript에서 {}는 null/undefined를 제외한 모든 값을 뜻한다.

|

TypeScript에서 {}는 조금 애매한 타입인데,
null과 undefined를 제외한 모든 값을 뜻한다.

let a: {} = 123; // ✅ number 가능
let b: {} = 'hello'; // ✅ string 가능
let c: {} = { x: 1 }; // ✅ object 가능
let d: {} = []; // ✅ array 가능

let e: {} = null; // ❌ 오류
let f: {} = undefined; // ❌ 오류

BFE.dev문제에서 NonNullable<T>를 직접 구현하는 풀이가 있었는데,
정답은 아래와같았다.

type MyNonNullable<T> = T & {};

유니언 타입에서 & {}를 교차시키면,
null과 undefined만 걸러지고 나머지는 그대로 유지된다.

type Ex = string | null | undefined;

type Result = Ex & {};
// = (string & {}) | (null & {}) | (undefined & {})
// = string | never | never
// = string

즉 T가 유니언일 때, 각각을 {}와 교차시켜 계산하여 null/undefined를 걸러낸다.
(유니언은 반복문의 효과를 가져옴)

WIL - 2025.08 Week 4

|

이번 주는 회사에 출근하지 않고 재택으로 일했다. 개발팀장님이 출근할 때는 맞춰야 하지만, 크롤링 작업은 집에서 집중하는 것이 더 효율적이라고 판단했다.

크롤링 자동화를 위해 Electron, BrightData, Playwright로 개발을 시도했으나 실패했다. 시간 압박 속에서 조급하게 접근한 것이 원인이었던 것 같다. 다행히 동료의 코드로 수동 크롤링을 이어갈 수 있었다. 실패 과정에서 얻은 것도 있었다. 페이지 로딩 문제는 크롬 디버그 모드와 user-agent 설정으로 해결할 수 있다는 점을 알게 되었다. 다만 셀렉터 변동성이 많아 완전 자동화까지는 아직 어렵다고 느꼈다.

학습 측면에서는 패턴으로 익히고 설계로 완성하는 리액트를 읽기 시작했다. TDD의 레드-그린-리팩터 주기를 배우고, ACL(오류 방지 계층) 개념도 접했다. 회사에서 사용하던 services 구조가 바로 ACL의 일종임을 깨달았다. 데이터 변환을 한 곳에서만 처리해 뷰를 단순하게 유지한다는 점이 인상적이었다.

또, CS50 강의를 듣기 시작했다. 전구의 on/off를 1과 0으로 표현하는 이진수 개념을 배우고, 각 자리수가 2의 거듭제곱을 나타낸다는 것도 이해했다. 강의는 아직 초반이지만 강사의 열정 덕분에 흥미롭게 보고 있다.

추가로 BFE.dev에서 타입스크립트 문제를 하나씩 다시 풀고 있다.

한 주를 몰아 적다 보니 글이 길어지고 일기처럼 흘러간다. 하지만 지금은 배운 것을 기록하는 습관을 만드는 것이 더 중요하다고 생각한다. 언젠가는 짧고 단정한 기록으로 다듬어낼 수 있을 거라 믿는다.

WIL(Weekly I Learned)를 쓰게 되다.

|

하루는 금방 지나가고, 매일 무언가를 배우지만 금세 흘러가 버린다.
예전에 TIL을 썼을 땐 그 순간의 기록이 쌓이는 게 즐거웠다. 하지만 시간이 지나면서 어느새 숙제처럼 느껴지고, 결국 깃 잔디만 채우는 일로 변해버렸다. 기록의 의미가 흐려진 순간이었다.

요즘은 재택을 주로 하면서 출퇴근에 쓰던 시간을 아끼고 있다. 하지만 돌이켜보면 그 시간조차 온전히 잠으로만 흘려보내고 있었다. 편안하긴 했지만, 어딘가 아쉽다는 생각이 들었다.
그래서 다시 기록을 시작해 보기로 했다. 다만 이번에는 하루가 아니라 한 주 단위로.

억지로 모든 걸 남기려 하지 않을 거다. 그냥 내키는 대로 적고 싶은 만큼만 적을 거다. 살아보니 그게 나한테는 더 오래가는 방식이었다.

Weekly I Learned에서는 이번 주에 새로 알게 된 개념과 배운 내용, 회사에서의 업무 경험, 그리고 그 밖의 배움을 얻은 순간들을 함께 기록해둘 거다.
기록은 언젠가 되돌아보았을 때 내 발자취가 되어줄 거라 믿는다.

타입스크립트 분배 조건부 타입

|

BFE.dev 사이트의 타입스크립트 챌린지를 풀면서 새롭게 알게 된 개념들을 정리합니다.


TypeScript 조건부 타입에서 중요한 규칙 중 하나는
조건부 타입이 유니언 타입에 대해 자동으로 분배(distribute) 된다는 점이다.

📌 기본 형태

T extends U ? X : Y

여기서 T가 유니언 타입 (A | B | C)이라면,
조건부 타입은 각 멤버별로 나눠서 평가된다.

즉:

(A | B | C) extends U ? X : Y

는 다음처럼 분배된다.

(A extends U ? X : Y) |
(B extends U ? X : Y) |
(C extends U ? X : Y)

📌 분배를 막는 방법

분배가 항상 원하는 동작은 아닐것이다.
만약 T 전체가 E에 속하는지를 한 번에 체크하고 싶다면,
[T]처럼 튜플로 감싸주면 분배가 일어나지 않는다.

type NoDistribute<T, E> = [T] extends [E] ? never : T

✅ 정리

  • 조건부 타입에서 T가 유니언이면 멤버별로 분배된다. (반복문처럼 각 멤버를 순회하면서 조건을 적용한다)

  • 이를 분배 조건부 타입(Distributive Conditional Types) 이라고 한다.

  • 분배를 원하지 않을 때는 [T] extends [U]로 튜플 감싸기를 활용한다.

  • Exclude, Extract, NonNullable 등 여러 유틸리티 타입이 이 규칙을 기반으로 한다.