fender
20k
2015-02-13 12:27:00
31
26757

초보 개발자를 위한 디버깅 방법 소개


들어가는 말


바로 아래 에 이어서 초보 개발자를 위한 올바른 디버깅 방법에 대해 이야기해볼까 합니다.

개인적으로 안타깝게 생각하는 부분은, 수 많은 정부 지원 학원에서 초급 개발자를 양산하면서도 개발자로서 가장 중요하고 기본이 될만한 소양들을 전혀 가르치지 않는다는 점입니다.

아마도 수박 겉핥기로 문법과 객체지향 개념 정도 가르치고 바로 스프링이나 마이바티스 같은 프레임워크를 응용하는 내용으로 넘어가는 것 같은데, 이런 과정을 거쳐 실무를 맡게 되는 개발자들이 문제가 발생했을 때 효율적으로 해결할 수 있기를 바라는 것은 어렵습니다.

개발자로 일하다 보면 디버깅을 위해 소비하는 시간은 생각 보다 많습니다. 그리고 디버깅의 접근 방식에 따라 단 30초면 원인을 좁힐 수 있는 문제를 쓸데없는 검색과 요행을 바라면서 이것 저것 고쳐보는 식의 대응으로 몇 시간씩 낭비하는 경우가 흔합니다.

그래서 저는 입문 단계의 개발자라면 프레임워크 사용법 등을 익히기 전에 반드시 올바른 디버깅 습관을 기르도록 노력할 것을 권해드리고 싶습니다.

본론으로 들어가서, 제가 생각하는 올바른 디버깅의 절차는 아래와 같습니다:

(재현 가능성 확보) -> 단서 수집 -> 단서 분석 -> 가설 수립 -> 가설 검증

이 때, 검증 과정은 전 단계에서 수립한 가설을 모두 소진하거나 문제가 해결될 때까지 반복하며, 전자의 경우 필요한 경우 전 단계로 돌아가서 잘못된 전제나 빠뜨린 가설이 있는지 확인해야합니다.

각 단계에 대한 자세한 설명은 아래와 같습니다.

바람직한 디버깅 절차


1. 재현 가능성 확보 (복잡한 이슈의 경우)


어쩌다가 한 번씩 발생하는 문제 만큼 원인을 찾기 어려운 문제도 드물 것입니다.

이 단계에서 가장 중요한 것은, 우선 최대한 곁가지를 덜어내고 오류의 핵심적 현상만을 분리하고, 가능하면 최소한의 오버헤드로 현상을 반복 재현할 수 있도록 하는 것입니다.

보통 디버깅 과정에서는 여러 차례 현상을 재현해보게되고, 해당 과정에 시간을 많이 소비할 수록 효율은 급격하게 떨어집니다.

단순한 오류의 경우라면 이 과정을 생략할 수 있지만, 복잡한 오류일수록 이 과정에 공을 들이는 것이 좋습니다. 어떤 경우에는 해당 오류를 재현하는 테스트 케이스를 작성하거나 임시 페이지 등을 만드는 것이 필요하기도 합니다.

2. 단서의 확보


디버깅을 시작하기 위해선 무엇보다 문제의 원인에 접근할 수 있도록 돕는 단서를 최대한 상세하게 많이 확보하는 것이 필요합니다.

이 단계에서 중요한 것은 내용을 빠뜨리지 않고 수집하는 것입니다.

실무를 하다보면, 탐캣 로그는 확인했지만 브라우저 콘솔은 보지 않았다던지, 아니면 초보들이 자주하는 실수대로 try-catch로 예외를 잡아서 스택트레이스를 보존하지 않고 커스텀 메시지만 뿌린다던지, 혹은 로그 레벨을 변경하지 않는 식으로 중요한 단서를 누락해서 디버깅에 애를 먹는 경우를 정말 흔하게 볼 수 있습니다.

올바른 방법으로 단서를 수집하는 것도 매우 중요합니다. 예컨대 메모리 에러가 발생가거나 성능 문제를 겪는다면 로그를 찍는 것 보다는 프로파일 데이터를 확보하는 것이 최우선이 되어야 합니다.

3. 단서의 분석


이 단계는 앞서 수집한 단서를 분석해서 현상의 일차적인 원인을 찾는 단계이며, 가장 흔한 과정은 스택 트레이스의 내용을 파악하는 작업입니다.

어쩌면 이 부분이 초보 개발자 입장에서 가장 쉽게 익힐 수 있으면서도 가장 중요한 내용이 될 것 같습니다.

스택 트레이스를 읽기 위해서는 무엇이 핵심 예외이고 무엇이 파생되는 예외인지 구분하는 것이 중요합니다. 보통 서버측 자바 프로그램은 복잡한 계층 위에서 구동되기 때문에 예외가 몇 겹으로 포장(wrap)되어 있는 경우가 흔합니다.

이 경우 로그에 스택트레이스가 제법 길게 기록되어 초보 개발자들이 지레 겁을 먹는 경우도 있는데, 'Caused By' 문구나 순서를 근거로 따라가면 실제로 중요한 내용은 단 한 줄인 경우가 많습니다.

두 번째로, 스택 트레이스 자체를 읽을 수 있는 능력이 필요합니다. 예외에 대한 스택 트레이스는 말 그대로 오류가 난 시점의 '호출 스택의 기록'입니다.

이를 관련 소스 코드를 띄워 놓고 순서대로 따라가면서 대입해보는 연습을 하는 것이 중요합니다. 보통 스택트레이스의 첫 번째 줄 이하의 스택은 예외에 대한 어떤 '맥락'으로 이해할 수 있습니다. 당연한 이야기지만 맥락을 먼저 이해하면 본론 역시 이해하기가 그만큼 수월해집니다.

이 단계에서 가장 중요한 것은 예외가 발생한 정확한 위치와 예외의 성격을 확인 하는 것입니다. 최소한 자신의 소스 코드 몇 번째 줄에서 어떤 예외가 발생했는지 확인 후, 해당 예외의 내용을 파악하는 것이 기본입니다.

내용을 파악하는 데는 우선 메시지를 읽어보고, 필요한 경우 해당 예외 클래스의 정의를 API 문서를 통해 확인하는 것이 좋습니다.

초보 개발자들이 NullPointerException을 단지 '널값이 들어가면 나는 오류'라고만 이해해서 헤매거나 NoClassDefError/NoSuchMethodError/ClassNotFoundException 등과 같이 내용만 알면 10초면 원인을 알 수 있는 단순한 오류로 몇 시간씩 고생하는 건 정말 흔하게 볼 수 있는 일입니다.

최소한 한 번 본 예외는 정확하게 무엇을 의미하는 지 API를 찾아보고 이해하는 습관이 무엇보다 중요합니다.

4. 가설의 수립


단순한 문제의 경우 이미 전 단계에서 해답이 나오는 것이 일반적이지만 때때로 직접적인 원인은 알 수 있지만 보다 근본적인 문제를 알 수 없는 경우도 있습니다.

예컨대 이 변수가 널 값이 되어 NullPointerException이 나는 것은 알겠는데 애초에 왜 널 값이 들어가는 지 모르겠다는 식의 경우인데, 이 때는 보다 체계적인 접근이 필요합니다.

제가 추천하는 방법은 전 단계에서 확인한 직접적인 현상을 일으킬 수 있는 가능성에 대한 모든 경우의 수를 정리하는 것입니다.

위의 예를 활용하면, 해당 변수가 널 값이 될 수 있는 논리적인 모든 경우의 수 - 즉, 조건에 따라 할당 문을 건너 뛸 수 있는 가능성, 예외가 발생해서 할당이 이루어지지 않았을 가능성 등등을 빠짐없이 찾아서 가능성이 높은 순으로 정렬하는 것입니다.

5. 가설의 검증


일단 가설을 수립했다면 각 가설을 검증해야 합니다. 코드를 자세히 살펴보면 해당 가설의 성립을 활용할 수 있는 중단점을 설정할 수 있는 포인트나 반증할 수 있는 조건들이 보입니다.

단순하게 말하자면, 만일 검증하려는 가설이 앞서 예에서 '이 조건문이 성립하면 해당 변수가 널값일 수 있는데, 정말 조건문이 성립했을까?'에 대한 답을 찾는 과정입니다.

단순한 경우라면 중단점으로 조건문 실행을 감시하거나 로그를 추가해볼 수도 있고, 아니면 해당 조건문이 성립했다면 반드시 설정되는 다른 변수라던지 화면상의 변화 등을 통해 반증해볼 수도 있습니다.

많은 개발자들이 디버깅을 위해 중단점과 로그를 사용하지만 이를 무작위로 적용하는 경우와 이렇게 경우의 수를 좁히기 위해 체계적으로 접근하는 과정에서 사용하는 것은 효율성에서 정말 큰 차이를 보입니다.

따라서, 그런 식으로 테스트를 할 때엔 반드시 내가 지금 어떤 가설을 검증하기 위해 작업을 하고 있는지 잊어버리지 않도록 노력하는 것이 중요합니다.

6. 반복


정상적인 경우라면 이전 단계를 통해 근본 원인을 찾을 수 있어야 하지만, 때로는 상정한 모든 경우의 수를 소진했음에도 여전히 문제를 해결할 수 없는 경우가 있습니다.

이 때는 전 단계로 돌아가서 빠뜨린 경우의 수가 있는지, 혹은 그러한 가설들을 추출하기 위한 전제 조건에 오류가 없는지 (예컨대 이 조건문이 성립하면 해당 변수가 널이라고 생각했는데 그렇지 않은 경우) 등을 꼼꼼하게 확인해보아야 합니다.

마치며


꽤나 장황하고 복잡하게 들릴 수도 있지만, 개인적으로는 이러한 접근이 디버깅을 가장 효율적으로 할 수 있는 최상의 방벙이라고 생각하고, 초보 개발자라면 특히 시간을 써서 익숙해지도록 연습해야 한다고 봅니다.

이러한 '분석-가설-검증'의 절차가 익숙해지면 이전에는 검색으로 몇 시간을 고생하던 문제들도 나중에는 잠깐 눈으로 훑어보는 것 만으로 바로 정확한 원인을 찾아 해결하는 단계까지 발전할 수 있습니다.

그런 단계로 발전하기 위해 절대로 피해야할 것은 뚜렷한 이유 없이 무작위로 코드를 고쳐보고 이전 상태로 돌려보거나 단지 검색만을 위해 트레이스를 활용하는 등의 습관입니다.

물론 최악의 경우 검색으로 힌트를 얻거나 일종의 '소거법'으로 하나하나 연관이 없는 부분을 제거하는 방식으로 문제를 찾아야 하는 때도 있습니다.

하지만 이는 어디까지나 예외적인 경우고, 일반적인 문제는 항상 체계적으로 접근해서 원인을 찾는 습관을 갖는 것이 중요합니다.

아무리 SI 환경에서 개발자의 실력이 크게 중요하지 않다고 하지만, 누군가 하루를 소비할 문제를 십 분만에 풀 수 있다면 그 만큼 야근을 덜 할 수 있고 스트레스도 적게 일을 할 수 있습니다.

디버깅을 할 때 어떤 접근 방법을 택하는 지는 실제로 과정없이 십 분과 하루만큼의 극단적인 효율성 차이를 나타낼 만큼 중요한 문제입니다.

그리고 그런 시간 절약, 혹은 낭비가 십 년 쯤 쌓이면 결국 그 차이가 십 년후 자신의 커리어가 닿을 수 있는 한계를 결정짓는 것이라고 봅니다.
45
58
  • 댓글 31

  • 피델리데
    1k
    2015-02-13 12:19:38
    정말 좋은 내용입니다.

    본문에도 말씀하셨지만 구글 검색으로 답을 찾는건 참 빠르고 좋기도 합니다만
    이런 식의 논리적인 사고를 방해하는 경우도 있다는 생각을 합니다.
    1번을 하기 이전에 로그 가지고 그냥 구글 검색 혹은 질문으로 빠져버리면
    2번 3번 으로 당연히 이어질 필요가 없으니까요
    답이 인터넷 어딘가에 있다고 가정하니 아예 논리적인 생각 자체를 포기하는거죠
    그것도 그거고 일반적인 웹사이트의 CRUD 페이지를 찍어내는 속된 말로
    장표 작업에서는 이런 논리적인 사고의 디버깅을 할 일이 거의 없는것도
    문제라면 문제겠죠

    이 생각하고 고민하는 과정이 얼마나 즐거운지 많은 개발자가 알았으면 좋겠습니다.
  • orakiller
    376
    2015-02-13 13:37:34
    정말 좋은 글 잘 읽었습니다.
  • 응애~
    264
    2015-02-13 13:51:17
    좋은 내용 감사합니다!
  • KAKABI
    236
    2015-02-13 14:21:16
    좋은 내용이네요
    감사합니다.^^
  • 새우드릴
    522
    2015-02-13 15:01:53
    잘 적어주셨네요.

    "나는 포기하지 않고 열심히 노력을 다했지만 능력부족이었다."고 하는거면 할말이 없지만
    확실히 원인 파악만 잘 되도 자신이 뭘 해야 할지 알텐데
    그냥 오류 주루룩 뜨는거 보고 아 몰라 복잡해 하고 포기하는 건 안될 일이죠.

    오류에 대처하는 경험을 쌓아서 문제 해결능력을 키워나가는게
    초급들이 좋아하는 '많이 배우는' 게 아닐까 생각합니다.
  • lettuceonly
    1k
    2015-02-13 15:28:31
    좋은내용감사합니다 fender님 많은 도움이 ?습니다
  • nobody_knows
    852
    2015-02-13 15:43:04
    이전 게시판(code:lifeqna)에서 옮겨 왔습니다.
  • 문어신
    199
    2015-02-13 17:11:43
    읽다가 무릎을 탁! 쳤다는..
    좋은 글 감사합니다.
  • ICanFly
    252
    2015-02-16 09:49:30
    좋은글이네요 ^^
  • 허리펴
    1k
    2015-02-16 09:59:51

    지금도 초보지만 

    초보시절 생각나네요 ㅋㅋ

    하지만 아직도 모르는 에러 투성이 ㅠㅠ

  • 연구원
    196
    2015-02-16 11:21:57

    안녕하세요. 초보자를 위한 강좌를 올리셨네요 ^^;

    전에 올리신글에 "아 그냥 초보자 무시하는 또한명의 개발자구나" 라고 생각하고 , 무례한 댓글을 달아서 정말 죄송합니다.

    fender 님은 진정한 대인 입니다.

  • yongbam
    190
    2015-02-16 12:59:43
    좋은 내용이네요 :D
  • howlongwilliloveyou
    644
    2015-02-16 14:18:27

    대단합니다

  • 가꼬이이
    6
    2015-02-16 18:39:01

    좋은 글 감사합니다. 언제쯤 초보를 벗어 날수 있을지.  흐

  • 그날이올까
    29
    2015-02-20 16:52:01

    좋은 내용 감사합니다.

    저도 가끔 써먹는 소거법 써먹는데요, 역시 단순무식하게 찾기에 소거법 보다 좋은 것은 없는거 같습니다.

  • 골드만삭스
    1k
    2015-02-24 17:42:22

    펜더님 감사합니다.

  • 베가스
    343
    2015-02-25 09:46:50

    좋은 글 감사합니다.

  • 북삼촌사람
    1k
    2015-02-26 10:13:54

    좋은 글 감사합니다.

  • vingorius
    201
    2015-02-27 04:47:01

    내용도 내용이지만, 글을 참 잘 쓰십니다. 감사합니다.

  • ddakker
    1k
    2015-03-03 20:38:58

    좋은 정보 감사합니다.

  • 칸나
    1k
    2015-03-05 14:59:12

    개인적으로는 unit test 시나리오를 꾸준히 추가하는게 가장 간단하고 안정성이 보장된 디버깅이 아닐까 싶습니다.

  • codingkids
    0
    2015-03-15 17:47:59

    좋은 내용이네요. 저도 저런 단계를 수립하고 디버깅을 하는건 아니지만 디버깅을 하다보니 저런 형태의 방법으로 하게되는 것 같네요. 정리가 잘되서 이해하기가 더 쉽습니다.

  • tongjorim
    8
    2015-03-23 21:25:27

     좋은글 잘 읽었습니다.

    단서의 분석에서 다시한번 뜨끔한 1인이였습니다;;

  • nhj12311
    205
    2016-01-14 23:38:56

    재현 가능성 확보 중요한 말이네요

    대게 프로그램 입력을 그대로 재현하면 되긴하는데...

  • 훈훈
    313
    2016-09-03 15:26:20

    좋은 글 감사합니다. 아직 배우는 입장이지만 잊버려서는 안되겠군요


  • 초코쪼꼬
    6k
    2017-11-01 16:37:44

    이렇게 좋은글을 이제서야 보게 되네요. 감사합니다.

  • 복족스
    89
    2017-12-13 15:29:34
    맞는 말씀이네요.. 또 배우고 갑니다. 감사합니다!
  • 아플라
    617
    2019-05-09 10:55:45

    좋은글 감사합니다.

    힘들어도 포기하지않고 스택을 분석해봐야겠네요.

  • yrrehc
    68
    2019-06-09 19:52:10

    좋은 글 감사합니다.! 

    단서의 분석에서 띠용했네요.!

    몰라서 질문하려고 했는데 제가 조금 더 찾아보고 해야겠어요.


  • 파란색개발자
    2
    2019-12-26 11:16:46
    감사합니다. 가슴에 새겨야 할 정도로 중요한 개발자의 자세이네요.
  • 거꾸러해도장팀장
    55
    2020-09-04 17:35:29

    오! 좋은글은 추천!

  • 로그인을 하시면 댓글을 등록할 수 있습니다.