태그 보관물: 디버깅

문제 영역을 나누는 식으로 디버깅하는 법


디버깅이 재미있는 이유는 그것이 미스터리와 함께 시작하기 때문입니다. 즉, 프로그램이 어떤 일을 할 거라 생각하지만 다른 일을 하는 것이죠. 그런데 항상 그렇게 간단하지만은 않습니다(때때로 현장에서 일어나는 문제와 비교했을 때 제가 사례로 제시할 수 있는 것들은 부자연스러울 겁니다). 디버깅할 때는 창의성과 독창성이 필요합니다. 디버깅에서 가장 핵심적인 것을 하나 꼽자면 그것은 바로 미스터리에 대해 분할 정복(divide and conquer) 기법을 사용하는 것입니다.

예를 들어, 10가지 일을 순차적으로 하는 프로그램을 만들었다고 해봅시다. 프로그램을 실행하자 프로그램이 충돌하네요. 프로그램이 충돌하게끔 만들지는 않았기 때문에 이제 미스터리가 하나 생긴 셈입니다. 출력 결과를 보니 순서대로 처음 7개의 작업은 성공적으로 실행됐습니다. 마지막 세 개는 출력 결과에 보이지 않으므로 이제 미스터리는 “#8, #9, #10 중 하나에서 충돌했다”로 더 작아집니다.

어느 작업이 충돌했는지 확인하는 실험을 고안할 수 있겠습니까? 물론 그럴 것입니다. 디버거를 사용하거나 printline 문(또는 사용 중인 언어에서 그에 해당하는 것이라면 무엇이든)을 #8과 #9 다음에 추가하면 됩니다. 프로그램을 다시 실행하면 “프로그램이 #9에서 충돌함”과 같이 미스터리가 더 작아질 것입니다. 언제라도 미스터리가 정확히 무엇인지 염두에 두고 있으면 초점을 유지하는 데 도움이 되죠. 어떤 문제를 해결하기 여러 사람들이 함께 머리를 맞대고 있다 보면 가장 중요한 미스터리가 무엇인지 잊어버리기 쉽습니다.

분할 정복(divide and conquer)을 디버깅 기법으로 사용할 때의 핵심은 그것을 알고리즘 설계에 사용하는 경우와 동일합니다. 즉, 미스터리를 가운데로 잘 나누기만 하면 그것을 너무 자주 나누지 않아도 될 것이고 디버깅을 더 신속하게 할 수 있을 겁니다. 그런데 미스터리의 가운데란 무엇일까요? 바로 이 부분에서 진정한 창의성과 실험이 빛을 발합니다.

초보자에게는 모든 발생 가능한 오류가 소스코드의 모든 줄에서 일어날 것처럼 보입니다. 초보자에게는 실행된 코드 영역이나 자료 구조, 메모리 관리, 외부 코드와의 상호작용, 위험성 있는 코드, 단순한 코드와 같이 나중에 프로그램의 다른 측면을 볼 수 있게 해줄 안목이 없습니다. 반면 숙련된 프로그래머의 경우에는 이러한 다른 차원을 통해 무언가 잘못될 가능성이 있는 모든 것들에 대해 완벽하진 않지만 아주 유용한 멘탈 모델이 만들어집니다. 그러한 멘탈 모델을 갖추고 있으면 미스터리의 한가운데를 효과적으로 찾는 데 도움이 되죠.

무언가 잘못될 가능성이 있는 모든 영역을 똑같이 나누고 나면 어느 부분에서 오류가 발생했는지 판단해야 합니다. ‘어떤 라인에서 프로그램이 충돌했을까?’처럼 미스터리가 간단한 경우에는 ‘오류가 발생한 줄이 지금 실행 중인 프로그램의 가운데에서 실행된 것으로 판단하는 이 줄보다 앞에서 실행됐을까, 아니면 그다음에 실행됐을까?’라고 자문해볼 수 있습니다. 대개 오류가 단 한 줄이나 단 하나의 블록에 존재한다는 사실을 알게 되는 것처럼 운이 좋은 경우는 드뭅니다. 대신 ‘그래프 내에 잘못된 노드를 가리키는 포인터가 있든지 해당 그래프 내에서 변수를 합산하는 알고리즘이 동작하지 않는군’과 같은 경우가 더 많을 것입니다. 이 경우 더 작은 부분으로 나눈 미스터리에서 어느 부분을 제거할 수 있는지 판단하기 위해 그래프 내의 포인터가 모두 올바른지 검사하는 자그마한 프로그램을 작성해야 할지도 모릅니다.

다음 항목: 오류를 제거하는 법

디버깅하는 법을 배우세요

얼마전에 알게 된 How to be a Programmer라는 책에서 첫 번째 개인 기술인 ‘디버깅하는 법을 배우세요(Learn to Debug)’를 번역했습니다. 여유가 되는대로 조금씩 번역해서 기고하려고 합니다. 오역이나 잘못된 부분에 대해서는 이곳으로 알려주세요.


디버깅은 프로그래머가 되기 위한 초석입니다. “디버그(debug)”라는 동사의 첫 번째 의미는 오류를 제거하는 것이지만 더 중요한 의미는 프로그램을 조사하면서 프로그램이 실행되는 과정을 들여다 본다는 것입니다. 디버깅을 하지 못하는 프로그래머는 사실상 장님과도 같습니다.

이상주의자들은 설계, 분석, 복잡도 이론 등과 같은 것이 디버깅보다 더 근본적인 것이라고 생각하지만 그들은 실제로 프로그래밍 업무를 하는 부류는 아닙니다. 실제 업무를 수행하는 프로그래머들이 있는 곳은 이상세계가 아닙니다. 아무리 완벽한 사람이라도 대형 소프트웨어 회사나 GNU 같은 단체, 동료들이 작성한 코드에 둘러싸여 있고, 또한 그러한 코드와 상호작용해야만 합니다. 이러한 코드는 대부분 완벽하지 않고 문서화도 완벽하게 돼 있지 않습니다. 이러한 코드가 실행되는 과정에 대한 가시성을 확보할 수 없다면 사소한 문제로도 난관에 부딪히게 될 것입니다. 이러한 가시성은 오로지 실험, 즉 디버깅을 통해서만 확보할 수 있을 때가 많습니다.

디버깅은 프로그램의 실행에 관한 것이며 프로그램 자체에 관한 것이 아닙니다. 대형 소프트웨어 회사에서 구입한 소프트웨어라면 대개 프로그램의 내부를 살펴볼 수 없습니다. 그럼에도 코드가 문서와 맞지 않는 부분이 있다거나(프로그램이 시스템과 충돌하는 것이 가장 흔하고도 심각한 예입니다) 문서화되지 않은 부분이 분명 있을 것입니다. 더 흔히 볼 수 있는 경우는 오류가 발생해서 작성한 코드를 조사했는데 어째서 오류가 생겼는지 실마리를 잡을 수 없는 경우입니다. 이것은 결국 어떤 가정이 올바르지 않거나 예상치 못한 특정 조건이 발생했음을 의미합니다. 간혹 소스코드를 뚫어져라 쳐다보면 해결되는 경우도 있습니다. 그렇지 않은 경우에는 프로그램을 디버깅해야만 합니다.

프로그램 실행 과정에 대한 가시성을 확보하려면 코드를 실행하고 코드에 관한 사항을 관찰할 수 있어야만 합니다. 이것은 화면에 표시되거나 두 이벤트 사이에 지연시간이 발생하는 것으로 눈으로 확인할 수 있는 경우가 있습니다. 다른 많은 경우에는 코드 내 특정 변수의 상태나 실제로 실행 중인 코드 라인, 특정 단정문(assertion)에 복잡한 자료구조가 담겨 있는지 여부처럼 원래는 눈으로 보기 위해 만들어지지 않은 것들이 있습니다. 이러한 숨겨진 것들을 드러내야만 합니다.

실행 중인 프로그램 ‘내부 상황’을 살펴보는 방법은 다음과 같이 나뉩니다.

  • 디버깅 도구 사용
  • 코드 줄 출력: 정보를 출력하는 줄을 추가하는 식으로 프로그램을 임시로 수정하는 방법
  • 로깅: 로그의 형태로 프로그램 실행을 언제나 확인할 수 있는 방법

디버깅 도구는 그것이 안정적이고 사용 가능할 때는 아주 좋은 방법이지만 코드 줄 출력과 로깅은 훨씬 더 중요합니다. 디버깅 도구는 언어 개발보다 뒤떨어질 때가 많아서 사용하지 못하는 경우도 있습니다. 또한 디버깅 도구는 프로그램이 실행되는 방식을 미묘하기 바꿀 수도 있기 때문에 유용하지 못한 경우도 있습니다. 마지막으로 대형 자료 구조에 대한 단정문을 검사하는 것과 같은 디버깅 방법도 있는데, 그러려면 코드를 작성해서 프로그램이 실행되는 방식을 변경해야 합니다. 디버깅 도구가 안정적일 경우 사용법을 알아두는 것이 좋지만 다른 두 가지 방법을 활용할 수 있는 것도 매우 중요합니다.

초보자 중에는 코드 수정이 필요한 경우 디버깅하기를 두려워하는 분들이 있습니다. 이것은 탐색적 수술과 다소 비슷하기 때문에 이해할 만한 상황입니다. 하지만 코드의 특정 부분을 건드려서 건너뛰게 하는 법을 배워야 합니다. 그것을 실험하는 법을 배우고, 임시로 그렇게 하더라도 상황이 더 악화되지는 않는다는 점을 알아야 합니다. 이를 두려워한다면 멘토를 찾으십시오. 이 두려움을 느끼기 시작하는 과정에서 우리는 수많은 훌륭한 프로그래머를 잃고 있습니다.

다음 항목: 문제 영역을 나누는 식으로 디버깅하는 법