개발자로 살면서 빼놓을 수 없는 작업 중 하나가 디버깅이 아닐까 싶습니다.


아무리 머리속으로 잘 계산하고 꼼꼼하게 로직을 작성하더라도 버그는 발생하기 마련이죠.


실수에 의해서, 설계미스로 인해서, 스펙을 잘못 이해해서, 등등의 이유로 의도하지 않은 결과는 언제나 나옵니다.



이런 버그들을 잘 잡기 위해서는 무엇을 해야할까요



일단 우리가 갈 목적지는 정해져있습니다. 잘못된 코드가 있는 위치를 파악하고, 그 부분을 수정하는 것입니다.


하지만 버그에 대해서 우리가 접하는 것은 OK 버튼을 눌렀는데 아무런 반응이 없다는 등의 "현상"입니다.


"현상" 혹은 "증상"을 가지고 버그를 찾아내기 위해서는 일단 몇가지 단서들이 필요합니다.


1. 증상이 발생하는 경로


이 경로는 복잡한 로직일수록 알기가 어렵습니다. 사용자가 느끼는 증상이 발생한 순간과 버그가 발생한 순간이 꼭 동일하다고 볼 수는 없기 때문이죠. 특히 buffer overflow로 인한 메모리 오류 문제는 오염이 된 순간에는 어떠한 "증상"도 발생하지 않을 수 있습니다. 그 순간에는 오염된 부분에 접근하지 않기 때문이죠. 하지만, 좀 더 시간이 지나서 그 오염된 영역에 접근하는 순간 사용자는 기대하는 동작이 아닌, 어떤 "증상"을 경험하게 됩니다. 재미있는 것은 이때 마지막으로 사용자가 한 동작과 "증상"의 재현과는 아무런 관련이 없을 수도 있다는 점입니다.


그렇기 때문에 증상이 발생하는 경로는 다양할 수도 있습니다. 우리는 이 경로들을 모두 모아서 분석하여 최단 경로이면서 확실한 경로를 찾아내야합니다.


2. 증상이 발생하는 확률


버그는 경우에 따라서는 확률적으로 발생할 수 있습니다. 멀티쓰레드의 타이밍 문제, 네트워크 프로그램에서의 버그, 등등이 특히 그렇습니다. 혹은 버그는 100% 확률로 발생하지만, 재현 경로를 정확하게 알지 못해서 어떤 경우에는 발생하고, 어떤 경우에는 그렇지 발생하지 않는 것으로 보이는 상황도 있습니다.


반대로 100%라고 생각하는 버그도 정확한 근거가 없다면 조심스럽게 접근을 해야합니다. 100% 재현일 것이라고 생각하여 수정 후 테스트에서 증상이 나오지 않는다고 해서 '버그가 수정되었다' 라고 판단하더라도 실제로는 100% 재현이 아니었고 그 테스트에서만 우연히 발생하지 않았을 수도 있습니다.


3. 증상이 발생하는 그룹 / 발생하지 않는 그룹


재현경로와 확률을 찾아냈으면, 이제는 가능하다면 발생하는 그룹과 발생하지 않는 그룹을 찾아보는 것이 수정하는데 큰 도움이 됩니다.  


예를 들어 1.0.1 버전에서는 발생하지 않는데 2.0.0 버전에서는 발생하는 버그라면 1.0.1 버전 릴리즈 이후 2.0.0까지의 수정사항 중에 버그를 발생시키는 원인이 있을 것입니다. 혹은 rev A 보드에서는 매우 정상적이지만, rev B에서는 문제가 발생한다면 h/w의 문제 혹은 h/w 변경사항을 s/w에서 적용을 제대로 못한 것이 원인일 수 있습니다.




재현 경로와 재현 확률, 그리고 대조군을 모두 알아냈다면 버그를 수정하는 것은 그리 어렵지 않습니다. 재현 경로와 재현 확률은 버그를 수정하는 데도 도움이 되지만, 사실 더 중요한 것은 버그 수정 후 확인 절차입니다. 재현 경로와 재현 확률을 모른다면 버그를 수정했다 할지라도, 그 버그가 정말 수정이 되었는지 확인하는데 큰 어려움이 있습니다. 항상 버그를 처음에 대면할때는 인사하듯이 "어떻게 하여 그 증상이 나타났습니까? 기억나는 부분까지만이라도 알려주세요", "그 증상은 어느정도 확률로 발생하는가요?" 등의 질문을 하는 버릇을 길러보세요~