일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- fastify
- AX210
- react-dom
- swiper
- 무선랜카드 교체
- Grafana
- 우테코
- 유한 상태 머신
- browserslist
- react
- 데모데이
- web component
- Docker
- docker-compose
- RTL8852BE
- custom element
- 우아한테크코스
- scroll-snap
- 터치 스와이프
- Prometheus
- 우아한테크코스 레벨3
- HTML
- CSS
- webpack
- github Actions
- live share
- Shadow DOM
- typescript
- javascript
- GitHub Pages
- Today
- Total
IT일상
프론트엔드에서 트러블 슈팅하기 본문
리뉴얼 된 블로그로 보기: https://solo5star.dev/posts/56/
트러블 슈팅(Trouble Shooting)
트러블 슈팅이란, 어떠한 문제가 발생했을 때 각종 수법을 활용하여 원인을 찾아내는 것을 뜻합니다. 즉 문제 해결의 일종이라 할 수 있는데요, 우아한테크코스 프론트엔드 과정을 진행하며 있었던 기억에 남는 트러블 슈팅 사례 중 하나를 소개해보고자 합니다.
이 사례는 다른 사람의 문제를 해결해주는 입장이었기 때문에, 문제의 상황에 대한 이해도가 전혀 없는 상태에서 문제를 파악하고 해결해나가는 좀 더 순수한 트러블 슈팅 과정을 보실 수 있을 겁니다!
문제 상황
즐겁게 우테코 미션을 하던 중... 크루 한 분이 저를 찾아와서 도움을 요청했습니다.
"인풋 박스에 값을 입력하고 버튼을 누르면 콘솔에서 오류가 발생해요. 어떻게 해결할 수 있을까요?"
Uncaught TypeError: Cannot read properties of null (reading 'value')
at n.value
콘솔에 나타나는 에러의 내용입니다. 프론트 개발을 하다보면 자주 마주할 정도로 흔한 에러인데요, 코드의 평가 결과가 null이 되었고, 그 null에 value라는 프로퍼티로 접근을 시도하면 발생하는 에러입니다.
<!-- index.html의 일부 -->
<div id="purchase-amount-inputs">
<input type="text" placeholder="금액" />
<input type="submit" value="구입" />
</div>
handleSubmitPurchaseAmount(event) {
event.preventDefault();
// 아래 라인에서 null.value 로 평가됨 (대체 왜 null로 평가되는거지??)
const purchaseAmount = $('#purchase-amount-form input[type=text]').value;
}
근데 소스코드를 보면 논리적으로 아무런 문제가 없습니다. #purchase-amount-form 이라는 DOM은 index.html에 존재하고 있었고, 그 아래에 input[type=text]에 해당되는 DOM 또한 존재했기 때문입니다. $('#purchase-amount-form input[type=text]') 코드가 null로 평가될 이유가 없는데, 대체 왜 그런 것일까요?
게다가 개발 중일땐(npm run start) 문제가 발생하지 않았는데 배포하였을 때(npm run build + GitHub Pages) 해당 문제가 발생했다고 합니다.
여기까지가 크루가 고통스러워하며(?) 겪은 문제에 대한 상황 설명입니다.
문제를 해결하기 위해 상황 정보 수집하기
크루는 VSCode로 개발을 하며, npm run start 명령을 입력하여 개발 서버를 띄우고 코드를 작성한 후 npm run build 명령을 입력하여 빌드하고 GitHub Pages에 배포합니다.
동일한 코드를 개발 서버를 통해 띄우고 실행하면 문제가 없는데 빌드하고 배포하면 문제가 발생한다는 거죠. 즉 코드에는 문제가 없습니다. 코드가 빌드되고 배포되는 과정에 문제 해결의 단서가 있다고 추측할 수 있습니다.
실제로 문제가 발생하였는지 확인하기 위해 빌드된 결과물을 확인해볼 수 있습니다.
크롬의 DevTools를 사용하면 에러를 쉽게 추적할 수 있습니다. 포착되지 않은 예외에서 일시중지를 체크하고 에러를 발생시키면 코드 실행이 일시중지됩니다. 역시나 null로 평가되고 있네요.
여기서, DOM 선택자에서 어떤 부분까지 되고 어떤 부분은 안되는지 자세히 살펴볼 수 있습니다.
#purchase-amount-form 까지는 잘 나오는데, input[type=text]가 나오지 않네요. 분명 input DOM은 존재하고 있고 입력도 잘 되었는데 존재하지 않다니 어떻게 된 걸까요?
개발자 도구(DevTools)에서 요소 탭을 통해 확인해보니 input이 분명 있긴 한데 type=text가 없네요. 분명 코드 상에서 적어주었는데도 불구하고 없습니다.
여기까지, 어떤 상황에서 문제가 발생하는지 정확히 식별할 수 있었습니다. 이렇게 문제 상황을 명확히 함으로서 원인을 추측하고 문제 발생 지점을 좁혀나갈 수 있습니다.
문제 발생 지점 좁히기
코드를 작성하고 배포하기까지 숨겨진 과정이 정말(정~~말) 많습니다. 그 수 많은 과정 중 어떤 지점이 원인이고 문제를 발생시켰는지 찾아나가는 과정이 정말로 중요합니다.
콘솔에 빨갛게 나타난 에러는 (대체로) 많은 정보를 제공해주지 않습니다. 물론 가끔씩 해결 방안을 제안해주는 친절한 라이브러리(예: React)도 있습니다만 그것에 너무 많은 것을 기대해서는 안됩니다. 에러를 단순히 에러가 발생했다는 정도로 인식한다면 트러블 슈팅에 많은 어려움을 겪을 것입니다.
사례를 통해 이야기하겠지만 코드를 작성하는 과정과 배포되어있는 상태의 중간 과정을 파헤치게 됩니다. 만약 이 중간 과정에 대해 자세히 알지 못한다면 트러블 슈팅에 있어 이 과정이 고통스러울 수 있습니다.
앞서 문제 상황을 식별할 수 있었고 해당 (type=text가 사라진)결과를 실제로 확인까지 해볼 수 있었습니다. 즉 빌드 및 배포 과정 어딘가에서 type=text가 사라졌다고 추측할 수 있습니다. 누가 범인일까요? npm run build? GitHub Pages?
문제 발생 지점을 좀 더 좁히기 위해 npm run build를 실행해보고 빌드 결과를 확인해볼 수 있습니다. 만약 빌드 결과에서 type=text가 존재한다면 GitHub Pages의 문제일 것이고, 빌드 결과에서 사라졌다면 npm run build가 범인이겠죠?
npm run build를 실행하면 dist 폴더에 빌드 결과가 출력됩니다. GitHub Pages에 배포한다면 dist 폴더의 파일들을 그대로 배포하게 됩니다.
dist 폴더에 index.html 파일이 생성되었네요. 이는 npm run build의 출력입니다. minify도 되어있어 엔터나 공백이 모두 사라진 것을 확인할 수 있습니다. VSCode 기준으로 Alt + Shift + F 단축키를 입력하면 사람이 보기 편하게 포맷팅할 수 있습니다.
아니... 정말로 type=text가 사라져 있네요. npm run build가 범인이었습니다. 가끔이지만 개발자가 코드를 잘 작성해도 개발자 이외의 요인에서 문제를 일으키는 경우도 있습니다.
결국 npm run build의 문제였네요. 이렇게 문제 상황을 정의하고 좁혀 나감으로서 에러 해결에 한 발자국 다가갈 수 있었습니다.
어두운 것을 밝게
프론트엔드 앱을 빌드하고 배포하는 방법은 간단합니다. npm run build를 입력하고 출력된 dist 폴더를 GitHub Pages에 올리면 끝입니다. 그런데... npm run build라는 명령어 한 줄에는 굉장히 많은 동작이 포함되어 있습니다.
npm run build의 문제를 해결하기 위해 npm run build의 어두운(추상화되어있는) 면을 구체화해보려고 합니다.
npm run build를 실행하면 webpack이 호출되어 dist 폴더가 출력됩니다. webpack은 프론트엔드 리소스(js, css, html)을 번들링하는데 사용하는 도구입니다.
webpack의 기본 동작은 입력(entry)으로 js 파일을 넣으면 출력으로 번들링 된(하나로 묶인) js 파일이 나오게 되어 있습니다. 아무런 추가 설정 없이 webpack을 호출하여 빌드한다면 여러 개의 js 파일이 하나의 js 파일로 번들링되는 것 이외에 다른 영향을 주진 않을 겁니다.
흔히 언급되는 트랜스파일링이나 css 같은 것들은 webpack 추가 설정을 통해 이루어집니다. 따라서 index.html에 영향을 준 원인에는 설정에 문제가 있다고 추측할 수 있습니다.
webpack 설정 파일입니다. 입력으로 step2-index.js 가 주어지면 출력으로 step2-bundle.js 가 나옵니다.
webpack은 기본적으로 js 외엔 관여하지 않습니다. css와 html 파일을 번들링하려면 module.rules 및 plugins 설정이 필요합니다.
index.html에서 type=text를 사라지게 한 범인은 바로 여기있다고 할 수 있습니다.
그렇다면 index.html에 영향을 준 loader 혹은 plugin을 찾으면 되는데, HtmlWebpackPlugin이라는 수상한 냄새를 잔뜩 풍기는 녀석이 있네요.
HtmlWebpackPlugin에서 index.html를 전처리하는 과정에서 type=text를 제거하였다, 라고 가정해볼 수 있습니다. 이 플러그인의 공식 문서를 보고 정말 그렇게 동작하도록 되어있는지 찾아보고 결론을 내릴 수 있을 것 같습니다.
공식 문서를 보면서 type=text를 없앨 만한 옵션이 있는지 찾아보았는데, HtmlWebpackPlugin의 minification 문단을 보니 의심스러운 옵션을 찾을 수 있었습니다! type=text 는 충분히 불필요한 옵션이라고 간주될 수 있을 것 같습니다. input DOM은 type attribute가 없으면 기본적으로 type=text로 동작하니까요.
문제 해결
바로 검증해보기 위해 removeRedundantAttributes 옵션을 추가하고 빌드해보았습니다. 옵션이 예상한대로 동작한다면, type=text는 빌드 결과에서 남아있을 겁니다.
확인해보니 빌드 결과에서 type=text가 남아있었고 문제를 해결할 수 있었습니다! 🥳
정리하자면, webpack 빌드 과정에서 HtmlWebpackPlugin 플러그인이 index.html를 minify하는 과정에서 type=text를 redundant attribute로 간주하여 제거하였고 이것이 결과적으로 에러를 발생시켰다고 정리할 수 있었습니다.
오류 없이 정상적으로 잘 동작하네요! 성공적으로 트러블 슈팅을 완료하였네요.
다시 한 번 정리해보는 트러블 슈팅 방법
사례를 통해 트러블 슈팅 과정에 대해 설명해보았는데, 어떤가요? 다시 한 번 정리하자면, 이렇습니다.
- 문제 정의
- 문제와 관련된 상황 정보 수집
- 문제 발생 지점 좁히기
- 가정과 검증을 반복하며 문제 해결
저는 개발자 인생을 살아오면서 정말 많은 분들에게 다양한 질문들을 받았었는데, 대부분의 분들이 그냥 잘 안된다고 하면서 오류를 보여줍니다. 그럴 때 제가 먼저 던져보는 질문은,
무엇이 문제인가요?
어떤 결과가 되기를 원했나요?
지금은 어떤 상황이죠?
질문을 받는 입장에서 단순히 해당 부분의 코드만 보고 해결하는 것은 (대체로) 불가능합니다. 왜냐면 문제의 발생은 개발자가 작성한 코드에서만 일어나는 것이 아니기 때문입니다. 의외로 환경적인 부분의 원인으로 인해 발생하는 경우도 많습니다.
따라서 문제를 해결할 때 관련된 상황(context)을 숙지하는 것이 중요합니다. 특히 질문을 받는 입장에서, 코드와 환경에 대한 이해도가 질문자의 이해도 만큼(혹은 그보다 더) 갖춰져야 하기 때문에, 질문자와 꾸준히 소통하면서 정보를 수집하기도 합니다.
단순히 에러가 발생했다는 것만으로는 문제를 정의할 수 없습니다. 어쩌면 질문자가 겪는 문제가 에러에 있는 것이 아닐 수도 있기 때문입니다. (좀 더 근본적인 부분에서 문제일 수도 있습니다) 따라서 어떤 결과를 원하는 지도 물어봅니다. 어쩌면 문제를 회피할 수 있는 방법이 있을 수도 있기 때문입니다.
위와 같이 질문자-답변자 상황이 아닌, 혼자서 트러블 슈팅을 한다면 이러한 단계는 보통 생략할 겁니다. 그렇지만 트러블 슈팅에 있어서 빠질 수 없는 단계라고 생각합니다. 만약 트러블 슈팅이 while(true)에 빠져있다면, 문제 정의 단계부터 천천히 해보는 것을 권해드리고 싶네요. 어쩌면 문제 해결이 답이 아닐 수도 있습니다. 문제를 지혜롭게 회피할 수 있는 방법도 있을 거에요.
트러블 슈팅은 마치 PS(Problem Solving)와 비슷합니다. 그래서 그런지 저는 다른 사람의 오류를 트러블 슈팅하는 것이 즐겁습니다. 특히 문제에 대한 컨텍스트가 없기 때문에, 정말 순수하게 처음부터 문제를 해결해볼 수 있다는 점이 재미있습니다.
추상화를 아는 것과 모르는 것의 차이
최근 웹을 개발하는 것은 정말 쉬워졌습니다. 개발을 쉽게 만들어 주는 도구들이 정말 많기 때문입니다. 이러한 도구들은 복잡한 일들을 단순화시켜주기 때문에 추상화가 잘 되어있다고 할 수 있습니다. 추상화가 잘 되어있는 도구는 그 사용법만 잘 알면 생산성이 크게 올라갑니다.
그렇지만 이 글에서 사례를 통해 알아보았듯이 문제가 발생하였을 때 추상화된 과정을 알지 못한다면 트러블 슈팅이 어렵고 고통스러울 것입니다. 위의 그림은 그 중간 과정을 잘 모르더라도 웹 페이지를 쉽게 만들 수 있다는 점에서 추상화가 잘 되어있다고 할 수 있습니다. 그렇지만 중간 과정에서 발생하는 에러까지 추상화가 잘 되어있는 것은 아닙니다. 따라서 도구를 사용하며 발생하는 문제를 추적하고 해결해야 하는 과정이 필요하다면 이러한 중간 과정을 공부하는 것은 필수적입니다. 웹 개발자가 네트워크를 잘 알아야 하는 이유도 이와 같은 맥락입니다.
만약 추상화 된 과정을 위의 그림과 같이 구체화할 수 있다면 트러블 슈팅에 있어 발휘할 수 있는 힘은 남다를 겁니다. 이것이 제가 생각하는, 개발자가 추상화를 잘 알아야 하는 이유입니다.
'프론트엔드' 카테고리의 다른 글
React에서 터치 스와이프 구현하기 2 (유한 상태 머신으로 리팩토링) (5) | 2023.09.30 |
---|---|
React에서 터치 스와이프 구현하기 (0) | 2023.09.24 |
React와 Reactive는 어떻게 다를까요? (0) | 2023.08.28 |
크로스 브라우저 지원을 위한 browserslist + eslint + stylelint 셋업 (2) | 2023.08.23 |
React SSR (서버 사이드 렌더링) 얕게 시작해보기 (React.hydrateRoot) (4) | 2023.05.05 |