[인프콘 2024] 디버깅 마인드셋: 디버깅의 고통을 절반으로 줄여주는 고수들의 행동패턴 따라하기
소개

안녕하세요. 저는 현재 XL8이라는 실리콘밸리 소재의 스타트업에서 프론트엔드 팀 리드로 일하고 있는 배휘동입니다.
XL8은 인간이 번역하고 검수한 영상 자막 데이터를 전 세계에서 가장 많이 가지고 있어요. 이걸 이용해서 굉장히 뛰어난 성능의 번역 엔진을 만들었죠. 넷플릭스 같은 OTT 컨텐츠의 다국어 자막을 납품하는 회사들이 주 고객입니다.
이 엔진을 이용한 제품이 회사 내에 여러 가지 있는데, 저는 영상 컨텐츠의 자막을 번역하고 편집하는 MediaCAT이라는 SaaS를 맡아서 개발하고 있어요.
발표 계기
디버깅이라는 주제에 대해서는 제 커리어의 비교적 초기부터 관심을 가졌습니다. 블로그에 디버깅에 관련된 글도 몇 편 썼었고요.
- 디버그를 디버깅하기: 단계적 디버깅 프로세스 템플릿
- [디버깅 사례 공유] 제 컴퓨터에서는 됐었는데요...
- [디버깅 사례 공유] 잘못된 IndexedDB 사용으로 인한 무한 Suspense 문제 해결
- 사라지는 HTML 엘리먼트를 개발자 도구에서 검사하는 방법
원래는 이렇게 삽질했던 경험을 늘어놓으면서 정리하는 정도에 만족했는데요.

작년에 우연히, 토스의 Head of FE이자 이번 인프콘에도 참여하신 박서진님과 페어 디버깅을 하면서 신선한 충격을 받았습니다.
함께 풀었던 문제는 제가 회사에서 몇시간동안 씨름하던, material-ui의 드롭다운과 관련된 버그였는데요. 문제를 설명하고 나서 바로 소스코드로 넘어가려던 저를 서진님이 막으시고는 브라우저 콘솔 창을 떠나지 않고 유심히, 소리내서 에러 메시지를 읽으시더군요. '오 신기하네요' 같은 추임새를 넣으시면서요.
그리고 저랑 대화하면서 크롬 디버거의 기능들을 신들린 듯이 사용하시더니, 제가 몇시간 앓던 문제가 20분도 안 돼서 손쉽게 풀렸습니다.
조금 과장하자면 마법 같았습니다. 무척 신기한 경험이었어서, 이것저것 캐물은 다음 회사로 돌아와 팀원들에게 신나게 공유했었죠.

그런데 최근에 저도 이런 마법을 부린 적 있었습니다(관련 글: 어떻게 그 판단을 할 수 있었을까). 회사 동료분과 페어 디버깅을 하던 때였는데요.
동료분은 목록 페이지에서 특정 액션을 한 결과물이, 개별 아이템 페이지에서도 이렇게 반영되어야 하는데 반영이 안 되는 문제가 가끔씩 나타난다고 하셨어요. 재현 조건은 아직 찾지 못한 상태였고요.
저는 이 이야기를 듣자마자 캐시 갱신이 안 된 문제일 것 같았고, 직관적으로 개별 아이템 페이지에 먼저 갔다가 목록으로 돌아가서 동작을 수행했을 때를 확인해보자고 제안했습니다. 그리고 그게 맞더군요. 그렇게 페어 디버깅 세션은 5분도 안 되어 끝났습니다.
왜 디버깅 역량을 키우기 어려운가
이런 마법같은 순간들이, 개발자들이 디버깅 역량을 효과적으로 키우기 어려운 이유를 말해주기도 합니다.
사실 우리 개발자들은 평소에 자주 버그를 맞닥뜨리고, 직접 해결하거나 남들이 해결하는 걸 지켜보기도 하죠. 하지만 고수들의 디버깅을 관찰하더라도, 대개 문제 자체와 해결된 결과물만 보이지 그 안에 어떤 인지적 과정이 있었는지는 알기 어렵습니다.

제가 직관적으로 했던 제안에서, 대체 이런 판단과 의사결정을 어떻게 할 수 있었는지는 숨겨져 있었던 것처럼요.
돌이켜보면, 당시 제 머릿속에서는 이런 일들이 벌어졌습니다.
- 신호 인식: 문제가 발생할 때가 있고 아닐 때가 있다? 특정 동작 이후에 정보 갱신이 안 되는 문제다?
- 과거 경험과의 유사성: 동작 이후 정보 갱신 문제는 대부분 인메모리 캐시가 갱신되지 않은 게 원인이던데.
- 전제조건: 캐시 갱신이 안 되려면 먼저 캐시가 메모리에 들어가야 하고, 그러려면 그 쿼리를 먼저 호출해야 한다.
- 사전지식: 목록 페이지와 개별 아이템 페이지에서 호출하는 쿼리가 다르다는 걸 알고 있었다.
- 과거 경험과의 유사성: 어떤 페이지에 먼저 들어갔느냐에 따라 문제의 재현 여부가 달라지는 경험을 한 적이 있다.
그래서 "개별 아이템 페이지에 먼저 들어가서 특정 쿼리에 대한 캐싱이 된 상태에서, 목록 페이지의 동작을 수행 후 개별 아이템 페이지의 쿼리 캐시가 갱신되지 않은 것 아닐까?" 라는 결론을 낼 수 있던 거였죠.

제가 조금 전 셀프 분석했던 방법은 인지심리학계의 거장 개리 클라인이 만들어서1989년 논문에서 발표한 Critical Decision Method, 줄여서 CDM이라고 부르는 기법입니다.
이 CDM은 인지작업 분석(Cognitive Task Analysis, CTA)이라는 기법의 한 종류인데요. 이러한 분석 방법을 잘 활용해서 전문가들을 인터뷰하면 그들의 머릿속에 숨은 암묵지, 무의식적으로 인식하는 패턴, 저도 모르게 하는 습관 등을 효과적으로 뽑아낼 수 있습니다.
인지작업 분석으로 디버깅 고수들을 인터뷰하다
저는 이 인지작업 분석을 이용해 디버깅 전문가들의 머릿속을 파헤쳐보고 싶다는 생각으로, 친분이 있는 디버깅 고수들을 찾아가 인터뷰를 했습니다. 분야는 프론트엔드, 백엔드, 리서치 등 다양했고요.

한편, 저는 디버깅이라는 게 단순히 소프트웨어에만 국한된 게 아니라고 생각했어요. 그래서 다른 분야의 문제해결 전문가들도 몇 분 인터뷰를 해봤습니다. 문제에 접근하고 해결하는 방식에 공통점을 발견할 수 있을 거라고 기대했거든요.
그리고 ChatGPT에게도 인터뷰를 해봤어요. 사람과 할 때는 인터뷰 시간이 부족해 물어보지 못했던 것들을 잔뜩 물어볼 수 있었고, 기존의 인터뷰를 검증해볼 수도 있었죠. 절대 지치지 않는 인터뷰는 정말 매력적이더군요.
인터뷰의 초반부는 언제나 이런 식으로 시작했습니다. 우선 인터뷰이에게 디버깅할 때 하는 행동들의 종류나 순서를 그려달라고 요청합니다. 인지작업 분석에서는 Task Diagram Mapping이라고 부르는 기법입니다.

그러면 위와 같이 몇 가지 행동을 그려줄텐데, 여기서 디버깅을 잘 하는 사람과 못 하는 사람의 차이가 큰 게 무엇인지 골라달라고 했죠.
그러면 더 중요한 게(위 이미지에서 빨간색 체크) 튀어나오는데요. 이 다음 실제 최근의 디버깅 사례를 듣고, 추가적인 탐침을 하면서, 본인이 말한대로 실제로 행동했는지 교차검증을 했습니다.
이러한 인터뷰 과정에서 확신하게 된 건, 디버깅은 마법이 아니라 마술에 가깝다는 점이었습니다.

마법은 그냥 신비하고, 이해하기 어려운 무언가죠. 반면 마술은 트릭을 알고 손기술을 익힌다면 누구나 배울 수 있는 기술입니다. 여기서 트릭은 고수들의 인지적 작업이 담긴 행동패턴이고, 손기술을 익히는 건 그 행동패턴을 따라하며 훈련하고 체화하는 걸로 볼 수 있겠네요.
그런데 재미있게도, 인터뷰이 본인도 스스로의 트릭과 손기술에 대해 명확히 인지하고, 정리되어있지 않은 경우가 대부분이었어요.
실제로 인터뷰 과정에서 인터뷰이들이 이런 말씀들을 하셨고요.
"와 제가 이렇게 했었군요. 몰랐어요."
"이렇게까지 파고들어본 적이 없었는데 정리가 잘 되었습니다."
"저희 팀원 분들에게 이거 그대로 보여주면 되겠어요."
"이거 하고 나니 제가 자주 하는 다른 기법들도 떠오르더라고요."
즉 고수들도 본인이 어떻게 하는지 잘 모르고, 그러니까 잘 가르쳐주지도 못했던 거죠. 그렇다면 이런 디버깅 고수들의 패턴을 정리해서 주니어들에게 가르치면 큰 효과가 있으리라는 확신이 생겼습니다. 실제로 저 자신의 디버깅 능력도, 일련의 인터뷰 이후로 훨씬 좋아진 걸 체감했기도 했고요.
그러면 지금부터 고수의 디버깅은 무엇이 다른지, 그들이 어떤 생각과 행동을 하는지 제가 정리한 패턴을 보여드리겠습니다.
일반적인 디버깅과 고수의 디버깅
디버깅이라는 행위를 크게 3단계로 보면 이렇습니다. 문제의 원인을 찾고, 해결하고, 사후 처리를 하는 거죠.

그런데 이렇게 보면 이 3단계의 비중이 똑같아 보이지만, 실제로 우리가 디버깅을 할 때 이 3가지에 얼마나 시간과 노력을 쏟는지를 기준으로 생각해보면,

때로는 원인만 하루종일 파악하다가 아무것도 못할 때도 있고
때로는 원인을 대충 보고, 파악했다고 생각한 뒤 바로 해결에 뛰어드는 경우도 많습니다.
문제 해결 과정을 정리해서 팀에 공유하거나, 문제가 애초에 일어나지 않게 환경을 개선하는 등의 사후 처리도 없거나, 비중이 적죠.
반면, 디버깅 고수들은 대략 이런 패턴을 보이더군요.

시간과 노력을 원인 파악에 훨씬 더 쏟는 거죠. 물론 원인을 알아내고 나서도 문제를 해결하기 어려울 때 어떻게 하는가, 해결 후 무엇을 하는가 처럼 뒷부분에도 굉장히 중요한 게 많았지만, 고수들의 전문성의 핵심은 무엇보다도 문제의 근본 원인을 파악하는 데에 있다는 걸 알았습니다.
그러면 원인을 분석할 때 고수들은 뭘 하길래 훨씬 더 빠르고 효과적으로 디버깅을 하는 걸까요?
이쯤에서 새삼스럽지만, 디버깅이라는 게 뭔지 나름의 정의를 내려볼게요.
저는 디버깅이란 "의도대로 동작하지 않는 무언가를 정상화하는 행위"라고 봅니다. 그리고 무언가를 정상화하려면, 먼저 정상적인 상태가 무엇인지 명확히 정의내려야겠죠.

즉 '정상적인 환경에서는, 어떤 조건에서 어떤 순서로 어떤 일들이 벌어져야 하는가'를 정의하는 겁니다. 이게 있어야 무엇이 맞고 무엇이 틀린지를 알 수 있고, 틀린 걸 맞게 고칠 수 있습니다. 조금 과장하자면 에러 메시지를 읽고, 깃 로그를 뒤지고, 현상을 관찰하는 모든 행동이 이걸 정의하기 위한 거라고 말할 수 있겠네요.
그리고 다시, 이건 '심적 표상'을 만드는 과정이라고도 볼 수 있습니다. 심적 표상이라는 단어가 생소한 분들도 있으실 텐데요. 심적 표상, 영어로 mental representation은 우리 마음 속에 존재하는 일종의 지식 구조를 뜻합니다. 안데르스 에릭손이라는 전문성 연구의 최고 권위자에 따르면, 이 심적 표상의 양과 질이 전문가와 비전문가의 퍼포먼스 차이를 만드는 핵심 요인입니다.

심적 표상을 아주 단순하게 표현하면, 패턴 인식과 의사결정을 위해 머릿속에 존재하는, 일종의 디시전 트리라고 할 수 있습니다.
디버깅 고수들의 머릿속에는 정상적인 컴포넌트라면 이런 조건에서 이렇게 동작해야 한다, 같은 다양하면서도 해상도 높은 디시전 트리가 존재했던 거죠. 그래서 이런 트리에 맞는 패턴을 직관적으로 알아차리고, 그에 맞는 행동을 할 수 있었던 겁니다.

일반적인 디버깅은 대개 약간의 경험치만 얻고 끝납니다. 나중에 복기하기도 어렵고, 애초에 대부분 금방 휘발됩니다.
하지만 디버깅 고수들은 기존의 심적 표상을 이용해 문제를 풀고, 디버깅이 끝나면 경험이 누적되어 심적 표상의 양과 질이 더 좋아집니다. 머릿속 디시전 트리가 업데이트되면서, 그 패턴의 문제에 대해서 좀 더 똑똑해지는 거죠.
이런 게 꾸준히 반복되면, 나중에는 문제를 쓱 보기만 해도 '아 이건 요거 때문이에요' 라는 말을 할 수 있게 됩니다. 머릿속에서 패턴 매칭이 바로 되어버렸으니까요.
그럼 이러한 디버깅 고수의 심적 표상을 우리는 어떻게 쌓을 수 있을까요?
문제 원인 파악을 위한 5단계 가이드 - 서론
조금 전 디버깅 고수들은 원인 파악에 많은 시간을 쏟는다고 말씀드렸죠.
저는 고수들의 여러 인터뷰에서 발견된 반복 패턴과 저의 개발 및 디버깅 경험을 버무려서, 고수의 실제 행동을 따라할 수 있는 5단계 가이드를 만들었습니다.

이는 하나의 문제에 대한 원인 파악 가이드이기도 하지만, 해당 문제의 도메인과 패턴에 대한 심적 표상을 쌓는 훈련법이기도 합니다. 저는 개발자들이 각 단계에 내포된 의미를 이해하며, 의식적으로 훈련한다면 디버깅뿐만 아니라 문제 해결 역량 자체가 전반적으로 향상되리라고 믿습니다.
각 단계를 자세히 설명하기 전에 몇 가지 언급할 만한 게 있어요.
개발을 잘 하는 사람들은 신규 기능을 만들기 전에, 기능을 추가하기 쉽게 기존 구조를 리팩토링한다는 말이 있죠. 이와 비슷하게, 대부분의 전문가들은 반복되는 작업에 대한 퍼포먼스를 높이기 위해 작업에 들어가기 전에 하는 행동들이 있습니다. 마찬가지로, 사후에 하는 행동들도 있고요.

우선 본격적으로 원인 파악을 시작하기 전에는 작업을 위한 계획을 세우는 게 유리합니다. 예를 들어 내가 이 원인 파악을 위해 이런저런 일들을 할텐데, 전체적으로는 얼마나 시간을 쓸지 미리 정해두는 식이죠.
그 시간이 지나면 하던 일을 멈추고 회고하면서, 그때까지 파악한 정보나 확인한 가설 등을 문서화합니다. 그 다음은 산책을 하며 뇌를 다시 일깨운 뒤 다시 시작하거나, 주변에 알리면서 도움을 청할 수 있겠죠.

그리고 원인 파악 후에는 또다시 여러가지 판단과 의사결정, 행동이 들어갑니다. 해결 방법을 설계하고, 해결에 들어가는 비용과 효용을 예상하고, 그에 따라 우선순위를 결정하고, 문제를 실제로 해결할 담당자를 결정하고, 관련자들에게 상황을 공유한다거나 같은 것들이죠.
이런 사후 작업에도 당연히 많은 전문성이 함축되어 있어요. 하지만 파레토의 법칙처럼, 80%의 문제는 원인만 잘 파악해도 이후는 술술 풀립니다. 그러니 오늘은 원인 파악에만 집중해보겠습니다. 저는 이게 디버깅 역량 향상에 가장 ROI가 높은 방법이라고 보거든요.

또, 편의상 5단계 라고는 말했지만 실제로 고수들의 행동을 보면 이 단계들을 순차적으로 따르진 않습니다. 단계를 엄청 왔다갔다해요. 예를 들어 정상 동작을 정의한 다음, 최소 재현 환경을 구축해서 관찰하다 보니 정상 동작에 대한 정의가 더 명확해져서 고치고, 다시 나아가는 식이죠.
이런 점들을 염두에 두시고, 이제 본격적으로 각 단계에 대한 설명을 해보겠습니다.
문제 원인 파악을 위한 5단계 가이드 - 본론
1. 문제 정의
원인 파악의 첫번째 단계는 문제 정의입니다. 지금 어떤 문제를 풀려고 하는지 한두 문장으로 적어보는 거죠. 원인 파악이라는 여정에서 이정표를 만든다고 보시면 됩니다.

이 이정표는 사전에 작업 계획을 설계하면서 함께 만들 때도 많아요. 이렇게 이정표를 만들어두면, 사전에 정했던 시간이 될 때마다 중간중간 빠져나와서 돌이켜볼 때도 유용하고, 다른 사람에게 문제에 대해 설명하고 공유할 때도 유용합니다.
이러한 이정표 없이 작업에 들어간다면, 쉬운 문제는 괜찮겠지만 복잡한 문제를 풀 때는 당장 중요하지 않은 서브태스크에 빠져들어 시간을 허비할 위험이 있습니다. 이런 걸 야크 셰이빙이라고도 하죠. "X를 하려면 Y를 해야 하는데, Y를 하려면 Z가 필요해서 어느새 Z만 주구장창 하고 있는" 거요. 이럴 때는 정신차리고 다시 X로 돌아가든, 아니면 문제를 재정의해서 Z를 제대로 풀든 해야 합니다.
이런 야크 셰이빙을 피하는 걸 이 이정표가 도와준다는 이야기입니다.

제가 몇 달 전에 실제로 겪었던 문제를 예시로 들어볼게요. 돌이켜보면 시작부터 꽤 운이 좋았던 사례였죠.
저희 서비스에서 제공하는 자막 에디터에서 특정 단축키가 동작하지 않는다는 유저 리포트가 이메일로 들어왔습니다. 확인해보니 해당 고객의 환경은 Windows OS였어요. 저희의 평소 개발 및 테스트 환경은 MacOS였고요. 당연히, MacOS에서는 없었던 문제였고요.
리포트를 받은 PM에게 다행히 윈도우즈 노트북도 있어서, PM이 본인 컴퓨터에서 몇 번의 시도 끝에 문제를 재현했습니다. 이 시점에 PM이 저에게 같이 문제를 보자고 하셨어요.
저와 콜을 하면서 문제가 좀 더 좁혀졌습니다. 단축키들은 디폴트 설정이 있고 커스터마이즈가 가능했는데, 윈도우즈에서 CTRL+DIGIT으로 기본 설정된 단축키들만 동작하지 않더군요. 커스터마이즈를 한 뒤에는 동작을 했고요. 이게 제가 풀어야 할 문제였어요.
2. 정상 동작 정의
두번째는 올바른 동작을 정의하는 단계입니다.

정상적인 환경에서는 어떤 조건에서, 어떤 순서로, 어떤 일들이 벌어져야 하는지 적어보는 거죠. 이게 앞서도 언급했듯 5단계중 가장 중요한 단계라고 봐도 무방해요. 이걸 정의하는 과정이 곧 심적 표상을 만드는 핵심이라고 할 수 있거든요.
먼저 현재 어떤 일들이 벌어지고 있는지 유심히 관찰하고, 관찰한 정보와 더불어 내가 이미 알고 있는 정보들도 적어봅니다. 예를 들어 에러 메시지가 존재한다면 그 에러 메시지를 아주 자세히 읽는 거죠. 메시지 자체, 어디서 발생했나, 스택트레이스는 어떤가 같은 것들이요.
그다음은 올바른 동작을 테스트코드처럼 적어봅니다. given, when, then 같은 템플릿을 따르면 편하죠.
만약 올바른 동작을 정의하는 게 어렵게 느껴진다면 아직 문제를 제대로 풀 준비가 안 되었다는 신호입니다. 좀 더 똑똑해지기 위한 추가 정보를 여러 소스에서 수집해야 해요. 다른 사람에게 도움을 요청할 수도 있고, 깃에 저장된 히스토리를 뒤져볼 수도 있고, 공식문서를 다시 읽어볼 수도 있고, 구글링이나 GPT를 사용할 수도 있겠죠.

제 사례에서는, 저는 이미 PM분과 함께 현상을 관찰하며 많은 정보를 확보해둔 상태였기에 그것들을 다시 제 언어로 적는 것부터 시작했어요.
위 이미지의 내용이 실제로 당시 제가 디버깅하면서 적었던 내용들입니다. 이 중에서 "다른 단축키로 덮어쓰면 동작한다"가 특히 미스테리한 부분이었고, 여기에 집중해서 살펴봐야겠다는 생각이 들더군요.

다음은 Given, when, then 포맷에 맞춰서 테스트코드처럼 정상 동작을 적어봤어요. 스플릿 단축키를 눌렀을 때 벌어진 3가지 현상에 대해 적었죠.
3. 최소 재현 환경 구축하며 관찰
정상 동작을 정의했으면, 그 다음 단계는 문제가 발생하는 최소 재현 환경을 직접 구축하며 관찰하는 것입니다.

아직 재현이 안 됐으면 재현을 위한 정보 수집 및 시도를 다방면으로 합니다. 그중에서도 언제부터 발생했는가? 가 아주 중요한 정보예요. 이건 버그의 영향 범위를 파악하기 위해서도 필요합니다. 에러 모니터링 도구나 깃 커밋 메시지, 슬랙 같은 걸 검색하는 거죠.
만약 세션 리플레이 같은 도구로 기록된 게 있다면, 문제가 발생했던 사용자의 환경과 동작, 예를 들어 어떤 세팅을 해둔 상태에서, 문제 동작 이전에 어떤 동작을 했었는지 같은 것들도 최대한 따라해봅니다.
그리고 문제를 더 작게 격리하는 건, 재현에 성공했든 아직 못했든 간에 아주 좋은 습관입니다.
- 문제가 특정 조건에서만 발생한다면 그 조건이 항상 참이 되도록 변경할 수도 있고요.
- 문제 상황에서 정상이 될 때까지 하나씩 컴포넌트나 코드 조각을 주석처리할 수도 있고,
- 아예 빈 프로젝트에서 시작해서 비정상이 될 때까지 나아갈 수도 있겠죠.
이렇게 하면서 정상 환경과 무엇을 다르게 하면 어떻게 되는지에 대한 패턴을 관찰합니다. 격리를 잘 할수록 변경에 영향을 받는 요소가 줄어들기 때문에 가설 검증과 관찰이 수월해져요.
만약 내 환경에서 재현을 끝끝내 실패했다면, 정상 동작 정의 단계와 마찬가지로 추가 정보를 수집해야 합니다. 의심가는 부분에 로그를 자세히 심거나, 문제를 리포트한 사용자를 직접 만나는 것도 좋아요. (관련 글: 내가 사용자 문제 디버깅에 사용하는 네 가지 도구)

저도 윈도우즈 노트북은 다행히 있었는데, 아쉽게도 그 노트북에 개발 환경은 세팅되지 않은 상태였어요. 그래도 좀 더 자세한 로그가 쌓이는 스테이징 환경에서 기본 단축키로 CTRL+1 로 스플릿을 해보니 동작하지 않는다는 건 빠르게 확인했습니다. 별다른 조건 없이 100% 재현이 되니까, 아까도 말했듯 아주 운이 좋은 상황이었죠.
더 격리를 해볼까도 생각해봤지만, 재현 경로가 너무 단순했기 때문에 추가 격리를 하진 않았습니다. 당장은 이걸로도 충분하다고 판단했어요.
그리고 패턴 관찰을 시작했죠. 2단계에서 생각했던 것처럼, 단축키를 재지정하면 왜 동작하는가? 에 집중해보기로 했어요.
이전에 단축키 관련 작업을 하면서, 단축키를 덮어쓰면 로컬스토리지에 해당 단축키의 정보가 이런 식으로 저장된다는 걸 알고 있었어요. 정상 환경인 제 맥에서는 덮어쓰니 예상대로 로컬스토리지가 업데이트되더군요.
shortcutOverrides: [
{
"feature": "MenuItem.Items.Subtitle.SplitSubtitleMenuItem",
"darwin": {
"primary": ["Control","Digit1"]
},
"nonDarwin": {}
}
]그런데 윈도우즈에서는 단축키를 덮어쓰니까, 로컬스토리지의 shortcutOverrides 배열에 아이템이 두 개 생기는 거 아니겠어요?
shortcutOverrides: [
{
"feature": "MenuItem.Items.Subtitle.SplitSubtitleMenuItem",
"darwin": {},
"nonDarwin": {
"primary": ["Control","Digit1"]
}
},
{
"feature": "MenuItem.Items.Subtitle.ClicheFunctionMenuItem",
...
}
]처음에는 알아채지 못했는데 몇 번 반복하다 보니 눈에 띄었고, 그 순간 진부하지만 유레카를 외쳤습니다. 이녀석이 문제의 원인이겠다는 느낌이 확 왔죠.
그러면서, 2단계에서 정의했던 '정상 동작'이 에 제 머릿속에서 업데이트되었습니다.

기존에는 좌측과 같은 정의만 있었다면, 우측처럼 추가로 정상 동작을 정의할 수 있게 된거죠.
즉, 심적 표상이 갱신되었습니다. 그러면서 다음에 비슷한 문제가 생기면 훨씬 더 빠르게 원인을 알아채고, 해결할 수 있겠다는 자신감도 생겼어요.
이렇듯 디버깅의 각 단계를 순차적으로, 완벽하게 밟아나가는 게 아니라 조금 엉성한 상태에서 앞뒤로 왔다갔다를 엄청 하게 됩니다. 실제로는 초단위로 왔다갔다 하겠지만, 이 가이드를 따라하면서는 의식적으로 천천히 해보는 것도 좋다고 봅니다. 어쨌든 이 과정에서 좋은 심적 표상이 계발되기만 하면 돼요.
4. 차이를 발생시키는 다양한 원인 탐색
이제 재현하며 관찰한 정보를 토대로, 정상 환경과 문제 환경의 차이를 발생시키는 원인이 무엇일지 탐색합니다.

이 때는 추상적이든 구체적이든 떠오르는대로 적어보는데요. 단계의 제목에도 써있듯, '다양한'이 중요합니다. 바로 검증에 들어가지 않고 자유롭게 지도를 펼치는 거죠. 이는 원인이 복합적일 수도 있기도 하고, 옵션이 많아야 가설 설정과 검증 싸이클을 빠르게 돌 수 있기 때문이기도 합니다.
물론 도메인 경험이 많을수록 첫번째 옵션이 진실일 가능성이 높습니다만, 그렇지 않은 주니어 개발자 입장에서는 이렇게 옵션을 다양하게 적어보는 게 아주 좋은 훈련이 됩니다. 3개 정도는 옵션을 적어보는 거죠.
다른 단계와 마찬가지로 본인이 다양한 옵션을 적기 어렵다면 추가 정보를 수집합니다. 구글링을 하거나, GPT와 이야기를 나눌 수도 있고, 다른 동료를 불러서 상황 설명을 하거나 산책하는 것만으로도 메타인지가 올라가서 새로운 옵션이 떠오를 수도 있어요.

제 사례로 돌아와서, 저는 덮어쓸 때 맥과 윈도우즈의 동작 차이가 어디서 오는지를 생각해봤습니다.
우선 맥과 윈도우즈에서 디폴트 단축키가 Darwin과 NonDarwin이라는 두 개의 세트로 지정되어있다는 건 알고 있었어요. 그렇다면 모종의 실수로 인해, 윈도우즈에서만 두 동작이 동일한 기본 단축키를 가지도록 되었던 것 아닐까, 하는 옵션이 맨 처음 뇌리를 스쳤죠.
그리고 바로 검증에 들어갔어요. 이거 말고 당장 다른 게 떠오르지 않기도 했고, 처음에 1시간으로 정했던 디버깅 시간이 다 되어가고 있기도 했고요. 이게 아니라면 다음 옵션을 생각해보려고 했죠.
5. 가설 설정 및 검증
이제 원인 파악의 마지막, 가설 설정 및 검증 단계입니다.

우선, 가장 그럴듯한 옵션을 골라서 검증 가능한 가설의 형태로 바꿉니다. 무엇을 바꿀지, 어떻게 바꿀지, 그 때 무엇을 관찰할지 같은 정보가 포함되도록요. 예를 들어 A가 B로 되어있는 게 C 현상의 원인이라면, B를 B'로 변경했을 때 C가 C'로 바뀌어야 한다 처럼요.
그리고 실제로 작은 변경을 가하면서, 가설대로 현상이 변하는지 관찰합니다. 이렇게 해보면서 바로 문제가 해결되는 경우도 꽤 많은데요. 만약 문제가 완전히 해결되는 게 아니라 다른 문제로 전이됐다면, 야크 셰이빙을 하는 대신 1단계부터 새로 시작하는 게 대개 더 유리합니다.
가설이 틀렸으면 새로운 정보를 입수한 것이니 (좋아하면서) 정리해두고, 원인 파악이 될 때까지 다음 가설로 넘어가서 반복합니다. 이렇게 하면서 정상 동작, 심적 표상이 또 업데이트될 수도 있겠죠.
만약 끝내 원인 파악이 안 됐다면 어떻게 할까요. 이 때가 제일 위험한데, 이 상태에서 집착하다가 시간을 하루종일 쓰게 될 가능성이 높습니다. 진정하고, 다른 단계와 마찬가지로 원인 파악을 위한 추가 정보를 여러 소스에서 수집합니다.

저는 위와 같이 가설을 세우고, 검증을 해봤습니다. 다행히 가설이 맞다는 게 바로 확인이 됐어요.
그래서 문제의 원인은 뭐였냐면요. 우선 코드상으로는 이게 사건의 전말이었습니다.
- 에디터는 단축키에 대한 콜백이 2개 있으면 뒤의 것이 앞의 것을 덮어씀
ClicheFunction은 코드 순서상 뒤에 등록된 콜백이라서,CTRL+1을 하면ClicheFunction1이 동작ClicheFunction은 사용자가 지정해둔 텍스트를 바로 붙여넣어주는 기능. 디폴트로는 텍스트가 비어있어서, 단축키 눌러도 아무 동작도 안 하는 것처럼 보임
그러면 이런 코딩 실수가 왜 벌어졌는가? 도 중요하겠죠.
- 이전에 디폴트 단축키를 정리하고 변경하는 과정에서 생겼던 실수
ClicheFunction은 다른 기능과 달리 숫자키 전체에 대해 동적으로 기능 및 기본 단축키를 추가해주고 있었음. 그래서 텍스트 서치로 겹치는 단축키를 찾지 못함- 당시 윈도우즈에서의 테스트를 소홀히 했고, 단축키가 겹쳤다는 걸 인지하지 못함
다음에는 비슷한 버그가 덜 생기게 하기 위해, 이 때 버그를 고치면서 다른 겹치는 단축키가 정말 없는지 전수조사했고 다행히 더 없다는 걸 확인했습니다.
그리고 당시에 윈도우즈에서 테스트를 소홀히 했던 건, 단축키 관련 동작을 구현하거나 수정할 때는 윈도우즈에서도 테스트와 QA를 꼼꼼하게 하는 것으로 프로세스를 재정비했어요.
문제 원인 파악을 위한 5단계 가이드 - 리캡

지금까지, 문제 원인 파악을 위한 다섯 단계 가이드를 소개드렸습니다. 물론 아까도 말씀드렸듯 깔끔하게 순서대로 가진 않았죠. 제 간단한 사례에서도 1, 2, 3, 2, 4, 5로 2단계가 두 번 등장했었네요.
그리고 원인 파악 이후의 액션도 중요하다고 말씀드렸는데요. 실제로 저의 에디터 단축키 문제도 해결이 그리 쉽진 않았어요. 문제가 되는 코드는 몇 줄 안 됐지만, 기존에 단축키를 사용하던 사용자들에게 어떻게 의사소통해야 할지, 그래서 기본 단축키를 어떻게 할지, 앞으로는 어떻게 할지 등 고려할 게 생각보다 많았거든요.
이 뒷부분에 대해서는, 기회가 된다면 다른 블로그 글이나 발표에서 다뤄보고 싶네요.
디버깅 고수들의 도구
이걸로 본론은 끝났습니다만, 사실 디버깅 전문가 인터뷰에서 얻은 건 이 가이드가 끝이 아니었습니다. 그중에서도 고수들의 도구와 습관에 대해서 간단히 이야기해볼게요.

명필은 붓을 가리지 않는다는 말이 있죠. 저도 이번 발표하면서 처음 알게 됐는데, 이게 한자로는 '능서불택필'이라고 하더군요.
그런데 전문가들은, 실제로는 붓을 엄청 가립니다. 더 정확히 말하면, 전문가들은 도구를 찾아내고, 선택하고, 사용하는 데에도 전문가라고 할 수 있죠.
디버깅 고수들의 인지적 도구: 디버깅 마인드셋
그리고 디버깅 고수들에게 가장 중요한 도구는 이 디버깅 마인드셋 그 자체였어요.

여러 인터뷰에서도, 주니어들은 정상 동작 정의 자체를 할 생각을 안 한다거나, 재현 환경을 격리해야 한다는 인식 자체가 없거나, 가설 기반으로 검증하며 탄탄하게 나아가야 한다는 인식 자체가 없다는 이야기를 많이 들었습니다.
그러면 인지적 도구가 아닌, 우리가 보통 '도구'라고 할 때 생각하는 그런 실용적 도구는 없었을까요?
디버깅 고수들의 실용적 도구: 상황에 맞는 디버거
당연히 있었습니다. 디버깅 고수들은, 환경과 상황에 맞는 디버거를 능숙하게 사용하는 것도 아주 중요한 역량으로 뽑았어요. (관련 글: 내가 사용자 문제 디버깅에 사용하는 네 가지 도구)

예를 들어 프론트엔드 개발자라면 브라우저의 자바스크립트 디버거를 잘 활용하면 디버깅 속도가 몇 배로 빨라지는데, 콘솔 로그만 주구장창 찍고 있는 거죠. 이건 경력 쌓인 시니어라고 크게 달라지는 것 같진 않습니다. 솔직히 저도 박서진님과 페어 디버깅을 하기 전까지는 디버거를 아주 기초적으로만 사용하고 있었거든요.
좀 과장하자면, 크롬 디버거를 적극적으로 사용하면서 제 디버깅 인생이 달라졌다고 봐도 됩니다. 여러분도 아직 디버거를 많이 안 쓰고 계시다면 구글의 교육 영상을 꼭 시청해보시길 바랍니다.
꼭 프론트엔드가 아니라 다른 환경에서도, 그리고 새로운 언어나 프레임워크를 배울 때도, 내가 올바르게 가고 있는지 아주 작은 단위로 볼 수 있게 해주는 디버거를 먼저 세팅해두면 학습이 훨씬 수월해집니다.
디버깅 고수들의 습관
이번에는 인터뷰에서 들었던, 디버깅 고수들의 습관을 몇 개 말해볼게요.
디버깅이라는 반복되는 작업을 수월하게 해주는 작지만 소중한 습관입니다. 특정한 작업을 건너뛰기보다는, 같은 작업을 더 빠르고 효과적으로 만들어주는 루틴이라고 보시면 되는데요.
첫번째 고수의 습관: TDD(Toilet Driven Development)
첫번째 고수의 습관은 화장실 기반 개발이었습니다.

이분은 혼자 개발할 때 엄청 물을 많이 마십니다. 아주 큰 물병을 가지고 다니면서 습관적으로 물을 마시는데, 그러다보니 화장실에 자주 가세요. 평균 20분에 한번씩은 간다고 하시더군요. 화장실 갈 때는 휴대폰도 안 가져가고요.
이렇게 강제로 생기는 휴식시간에 자연스럽게 지난 20분에 대한 회고를 하고, 결과적으로 좋은 돌파구를 화장실에서 많이 떠올린다고 하셨습니다.
두번째 고수의 습관: DDD(PR Description Driven Development)
두번째 고수의 습관은 커밋 메시지나 PR에 남기는 설명에 기반한 개발이었습니다.

이분은 다른 동료들보다 깃에서 원하는 내용을 찾아내는 데 아주 특출난 분이었는데요. 이게 대체 어떻게 가능한가 파헤쳐보니, 애초에 깃에 커밋을 할 때나 PR 디스크립션을 작성할 때부터 다르게 하시더군요.
개발할 때 의미 단위로 커밋을 쪼개고, 나중에 검색하기 쉬운 커밋 메시지와 PR 제목, 내용을 쓰려는 의식적 노력을 아주 많이 하셨습니다. 이렇게 하면 당연히 개발하는 당시에도 뇌에 기억이 잘 들어갈 것이고, 나중에 검색하거나 기억에서 꺼낼 때에도 훨씬 수월해지겠죠.
커밋은 나중에 찾아보기 위한 것이다. PR은 읽는 사람을 위한 것이다. 어찌보면 당연한 말인데, 커밋할 당시에는 간과하기 쉬운 습관이기도 합니다.
세번째 고수의 습관: IDD(Issue Driven Development)
마지막으로, 세번째 고수의 습관은 이슈 기반 개발이었어요.

이분의 회사는 웹상에 자료가 많지 않은 프레임워크를 메인으로 사용하고 있었는데, 그러다보니 검색을 통해 문제를 해결하기가 쉽지 않았어요. 그래서 오픈소스 라이브러리의 메인테이너들과 디스코드를 통해 직접 의사소통하거나, 깃헙에 이슈를 올려야 할 일이 많았다고 해요.
근데 오픈소스에 이슈를 등록할 때 웬만하면 요구하는 게 있죠. 최소 재현 코드를 함께 올려달라. 이런 일이 반복되니 최소 재현 코드를 만드는 게 아예 습관이 되었다고 하시더군요.
그리고 모든 문제를 스스로 해결하려고 하는 게 아니라, 이슈만 만들어서 올려두고 현재 문제에는 workaround를 적용해서 다음 작업으로 나아가는 일도 많다고 하셨습니다.
맺으며
디버깅 고수들이 부리는 마법은, 사실 누구나 배울 수 있는 기술입니다. 디버깅 고수들이 어떤 생각을 하며 어떤 행동을 하는지 알고, 그들의 행동을 따라하며 내걸로 만들어 연습하여, 좋은 심적 표상을 쌓아나가면 누구나 디버깅 역량을 효과적으로 늘릴 수 있습니다.

실제로 NextStep에서 열었던, '이펙티브 디버깅'이라는 이름으로 이 디버깅 마인드셋을 실습하는 워크숍을 통해 많은 개발자 분들이 본인의 디버깅 역량을 향상시킬 수 있겠다, 이렇게 써먹을 수 있겠다는 자신감을 얻으시기도 했습니다.
물론 본인이 원래 하던 방식이 있을테니 한번에 바꾸긴 어려울 겁니다. 그렇지만 고수들의 패턴에서 조금씩 가져다가 점진적으로 개선하면 됩니다. 그리고 주변의 고수들이 디버깅하거나 개발할 때 어떤 신호를 인식하고, 어떤 생각을 하고, 어떤 행동을 하는지 적극적으로 관찰해보세요.
우리 모두 더 나은 개발자, 더 나은 문제해결사가 되길 기원합니다.

Member discussion