본문 바로가기
TIL

라이브환경에서 발생할 수 있는 배열 접근 타입 에러를 근본적으로 막아보자!

by 치우치지않는 2025. 1. 9.

내가 프론트엔드 개발자로 있는, 솝트 메이커스 크루팀 서비스에서 런타임 타입 에러가 발생했다. 

덕분에 서비스가 잠깐이지만 먹통이 되는 일이 있었다. 

 

이런 일이 일어났을까? 

 

내가 맡은 서비스에서 장애가 나면 정말 심장이 쿵 한다..

 

홈 화면에 광고 데이터를 불러올 때 기존에 쓰고 있던 코드는, 배열의 0 번째 인덱스에 직접 접근하는 코드였다. 

meetingAds?.advertisements[0].desktopImageUrl

 

광고 데이터는 항상 있을 것이라고 생각해서 0번째 인덱스에 직접 접근하는 코드를 작성했었다. 그런데 광고를 교체하는 과정에서 advertisements 가 null 이 되어버렸고, 그에 따라 advertisements[0] 은 undefined 가 되어버렸다. 

자바스크립트는 파이썬과 달리, Out of Index 에러는 발생하지 않아, 만약 advertisements[0] 까지만 있었다면, 값이 undefined 가 될 뿐 타입스크립트에러가 발생하지 않았을 것이다. 

그러나 여기서는 undefined 가 되어버린 값에 한 번 더 접근 (.desktopImageUrl) 하고 있기에 타입 에러가 터져버린 것이다. 

 

그럼 이제 어떻게 해결하면 좋을까? 

 

1. 옵셔널 체이닝

https://github.com/sopt-makers/sopt-crew-frontend/pull/875/files

 

advertisements 필드 null 값 예외 처리 by borimong · Pull Request #875 · sopt-makers/sopt-crew-frontend

🚩 관련 이슈 close 피드 광고 (advertisements) null 대응 #874 📋 작업 내용 기능 구현 페이지 구조화 및 스타일링 📌 PR Point 무슨 이유로 어떻게 코드를 변경했는지 어떤 위험이나 우려가 발견되었는

github.com

 

에러를 마주한 당시 내가 선택했던 방법은, 옵셔널 체이닝을 이용하는 것이었다. 그리고 문제는 잘 해결되었다. 

 

그런데, 이런 문제를 사전에 원천적으로 방지할 수는 없을까? 매번 코딩할 때마다 어디에 옵셔널 체이닝을 써야하는지 내가 일일이 체크해야만 하는걸까? 하는 생각이 들었고, 방법을 찾아본 결과 아래와 같은 좋은 도구를 발견할 수 있었다!

 

2. noUncheckedIndexedAccess 옵션 활용하여, 런타임 타입 에러 가능성 원천 차단하기

바로 noUncheckedIndexedAccess 라는 타입 컨피그 옵션을 발견하였다. 

이 옵션은, 객체 또는 배열에서 index 혹은 key 로 값에 접근 시, 값의 타입이 undefined 타입일 수 있다는 걸 알려준다. 

즉 위와 같이 배열의 요소 변경으로 인해 라이브 환경에서 undefined 가 될 수 있는 모든 코드에서 컴파일 타임에 타입 에러를 발생시킨다. 

 

그래서 나는, 이 옵션을 추가하고 생긴 모든 타입 에러에 옵셔널 체이닝을 이용하여 문제를 해결하고자 했다. 그런데 과연 이게 최선일까? 옵셔널 체이닝을 사용하는 것에 대한 부작용은 없을까? 하는 생각이 들었고, 옵셔널 체이닝의 정체를 탐구해보기로 했다.

 

3. 옵셔널 체이닝의 정체

옵셔널 체이닝을 해주게 되면, 

console.log(numberOptionList[3]?.value);

와 같은 타입스크립트 코드를 트랜스파일하여 아래와 같은 자바스크립트 코드로 바꾸게 된다. 

 

console.log((_a = numberOptionList[3]) === null || _a === void 0 ? void 0 : _a.value);

 

즉 조건문을 통해, null 이거나 undefined 인 경우, undefined 를 반환하도록 해준다. 

 

가장 간편한 방법이기는 하지만, 옵셔널 체이닝을 남발하게 될 경우에는 그만큼 번들 사이즈가 증가하게 된다는 단점이 있다는 걸 발견하게 되었다. (이런 것을 syntax sugar 라고 한다.) 

 

 

4. 옵셔널 체이닝의 역할을 하는 함수 만들기

어떻게 하면 옵셔널 체이닝을 남발하지 않을까 생각하던 중,

똑같은 데이터에 접근하는데도 옵셔널 체이닝을 반복해서 쓰게 되어 번들러의 크기가 불필요하게 늘어나니,

옵셔널 체이닝의 역할을 하는 함수를 만들어 공통으로 사용하게 해준다면 문제가 해결되지 않을까? 하는 생각을 하게 되었다.

 

const newTake = (() => {
    if (take == null) {
      return numberOptionList[0];
    }

    const v = numberOptionList[Number(take)];

    if (v == null) {
      return numberOptionList[0];
    }

    return v;
  })();
 

 

** 타입스크립트에서 == (동등연산자) 로 null 을 비교하면, null과 undefined 인 경우 true 를, 그 외의 경우에는 false 를 반환하므로, 두 가지 경우를 쉽게 비교할 수 있다. 

 

이 경우 옵셔널 체이닝과 똑같은 역할을 하지만, 하나의 함수를 여러 번 재활용 가능하다. 

그러나 이 방법은 함수를 작성해야 한다는 번거로움이 생긴다.

 

 

5. ?? 연산자와 default 값 활용하기 

?? 널 병합 연산자를 사용하면, null 또는 undefined 인 경우를 처리할 수 있다.

위처럼 If 문을 쓰기 위해 함수를 만드는 것이 꽤나 번거롭기 때문에, ?? 연산자를 사용하면 더 쉽게 처리할 수 있지 않을까? 하는 생각에서 출발한 방법이다. 

 

옵셔널 체이닝, 혹은 그 역할을 하는 함수에서 내가 기대한 것은 하나이다. 

 

"어떤 값이 null 혹은 undefined 일 때, 특정 값을 반환해줘." 

 

그럼 굳이 함수를 쓰지 않고, ?? 연산자를 활용해 아래와 같이 변수를 정의해버리면 되지 않을까? 

 

const 새로운값 = undefined 일 수 있는 값 ?? default 값 

 

이렇게 작성할 경우, 불필요한 번들 크기 증가도 없어지고, 번거롭게 함수를 직접 작성할 필요도 없어진다. 

 

그래서 나는 이 방식을 채택하기로 했다! 

 

6. 결론

코드 상으로 바꾼 것은 많지 않지만, 이 방법을 생각하기까지 한 고민과 생각의 시간은 짧지 않았던 것 같다. 

팀에서 앞으로 더이상 이런 에러가 나오지 않게끔, 좋은 시스템을 만들어, 시스템으로 문제를 해결한 나의 첫 시도였기에 다른 경험보다 뜻깊은 것 같다! 

앞으로도 좋은 시스템에 대해 고민하고, 도입을 계속해서 시도해서 실수를 최대한 줄이고 안정적인 서비스를 만들어나가는 프론트엔드 개발자가 되고 싶다! 


PR 링크 : https://github.com/sopt-makers/sopt-crew-frontend/pull/970

'TIL' 카테고리의 다른 글

2024.02.06 TIL  (2) 2025.02.06
PeerDependency 에러 수정기  (3) 2024.09.09
2024.05.17 TIL  (1) 2024.05.17
2024.05.02 TIL 실패하더라도 나답게 살자.  (0) 2024.05.02
2024.05.01 TIL 나는~ 생각이~ 너무~나~ 많아~  (0) 2024.05.02

댓글