디버깅은 개발 과정에서 어려운 도전이지만, 특히 초보 개발자에게는 큰 난관일 수 있습니다. 이는 개발 숙련도가 덜 된 것과 필요한 지식이 부족하기 때문입니다. 이로 인해 간단한 해결 방법으로도 처리 가능한 오류에 대해 막막함을 느낄 수 있습니다.
개발 방법론이나 기술에 관한 정보는 검색을 통해 쉽게 얻을 수 있지만, 디버깅에 관한 자료는 그렇게 많지 않습니다. 이 때문에 초보 개발자들에게 디버깅은 더욱 어려운 일이 될 수 있습니다.
때때로 오류 메시지가 명확히 나타나거나, 검색 결과가 즉시 도움이 되는 경우도 있지만, 그렇지 않을 때에는 심지어 경험 많은 개발자들도 어려움을 겪을 수 있습니다.
또한, '하이젠버그 현상' 같은 경우에는 디버깅이 더욱 복잡해질 수 있습니다. 오류를 확인하는 과정이 오류에 영향을 주는 경우 등인데요. 디버깅 코드 자체가 오류에 영향을 주어 오류 발생 확률을 크게 줄이는 경우가 있습니다.
먼저, 프로그래밍에서 가장 흔하게 발생하는 오류 유형과 이를 어떻게 피할 수 있는지에 대한 설명입니다.
구문 오류(Syntax Errors)는 프로그래밍 언어의 문법 규칙을 위반할 때 발생합니다. 이러한 오류는 코드 작성 시점에 발견되며, 많은 텍스트 에디터나 통합 개발 환경(IDE)은 작성 시점에 구문 오류에 대해 경고하는 기능을 제공합니다. 문법이 익숙하지 않은 경우에는 ChatGPT와 같은 AI에게 오류 메시지와 함께 오류난 구문을 올려서 질문하면 상당히 정확하게 수정해 줍니다.
논리 오류(Logic Errors)는 프로그램이 기술적으로 정확하게 작동하지만, 결과가 기대한 것이 아닐 때 발생합니다. 논리 오류를 피하기 위해서는 코드를 작고 관리하기 쉬운 조각으로 나누고, 다음 단계로 넘어가기 전에 각 조각을 별도로 테스트하는 것이 좋습니다.
런타임 오류(Runtime Errors)는 프로그램이 실행되는 동안 발생합니다. 예를 들어, 범위를 벗어난 배열의 인덱스에 접근하려고 시도하거나 0으로 나누는 등의 오류가 있습니다. 이러한 오류는 작성된 코드에서 즉시 알아차리기 어려울 수 있어 디버깅이 특히 어렵습니다.
런타임 오류를 피하기 위해서는 코드를 철저히 테스트하고, 예상치 못한 조건을 처리하기 위해 try-catch 블록과 같은 오류 처리 기법을 사용할 수 있습니다. 그렇다고 해서 try-catch로 에러를 무시하는 용도로 사용해서는 안됩니다. 런타임 오류는 즉각적으로 발견되지 않을 수 있기 때문에 자동으로 리포트하는 등의 모니터링이 필요합니다.
산술 오류(Arithmetic Errors)는 논리 오류의 한 유형이지만 수학에 관련된 것입니다. 이러한 오류를 방지하기 위해서는 절대로 사용하지 않을 것 같은 입력값들을 의도적으로 찾아서 테스트를 할 필요가 있습니다.
자원 오류(Resource Errors)는 시스템이 필요한 자원을 사용할 수 없을 때 발생합니다. 예를 들어 데이터베이스 서버의 데이터 저장이 하드 디스크 용량을 모두 사용하는 경우입니다. 이러한 오류를 방지하려면, 시스템이 필요한 모든 자원이 사용 가능한지 지속적으로 모니터링해야 합니다.
인터페이스 오류(Interface Errors)는두 시스템이 서로 통신하는 방식에 문제가 있을 때 발생합니다. 이러한 오류를 방지하려면, 시스템이 통신하는 모든 인터페이스에 대해 명확한 문서를 작성하고, 이러한 인터페이스를 테스트해야 합니다.
동시성 오류(Concurrency Errors)는 여러 스레드나 프로세스가 동시에 같은 자원에 접근하려고 할 때 발생합니다. 이러한 오류를 방지하기 위해 동기화 메커니즘을 적절히 사용할 줄 알아야 합니다. 이와 관련해서는 아래 두 강의를 추천드립니다.
http://10bun.tv/beginner/episode-2 http://10bun.tv/beginner/episode-3비동기 코드에서 확률적으로 발생하는 오류 역시 해결이 쉽지 않습니다. 이처럼 복잡하고 어려운 오류 상황에 직면했을 때, 몇 가지 간단한 절차를 따르는 것이 큰 도움이 될 수 있습니다.
첫 번째로, 오류를 재현하는 방법을 찾아내는 것이 중요합니다. 어떤 조건이나 절차에 의해 오류가 발생하는지 파악하는 것이 필요합니다.
두 번째로, 오류의 발생 위치를 찾는 것입니다. 초보 개발자의 경우 전체 코드를 완벽히 이해하지 못한 상태에서 오류를 마주하게 될 수 있습니다. 복잡한 오류는 코드의 전반적인 논리적 흐름에 의해 발생하는 경우가 많으므로, 오류가 발생하는 범위를 좁혀나가는 것이 도움이 됩니다.
저는 신입 개발자들에게 다른 것은 몰라도 오류의 위치는 반드시 찾으라고 주문합니다. 간혹 검색을 하면서 해결책을 고민만 하다가 저를 찾아오는 신입 개발자들이 있었는데요. 오류를 진단할 아무런 단서도 정리가 되지 않아서 제가 처음부터 다시 고민해야 하는 상황들이 종종 있었습니다. 선배 개발자들에게 도움을 요청할 때에는, 단순히 '안된다'는 말보다는 재현 방법과 오류 위치를 미리 파악하고 물어볼 때 더욱 적극적인 도움을 받을 수 있습니다.
제목이 살짝 자극적인 것 같지만, 제가 초보 개발자들에게 주고 싶은 디버깅 팁은 이렇게 단순합니다. 검색이나 오류 메시지로 바로 해결할 수 없는 문제의 경우, 우선 재현 방법과 오류 위치를 찾는 것부터 시작해보세요.
어떤 상황에서는 오류의 위치를 찾아내는 것이 매우 어렵습니다. 이럴 때 저는 코드의 일부를 주석 처리하며 오류가 발생하는 위치를 찾아내거나, 각 영역에 로그를 남겨 원하는 동작이 이루어지지 않는 위치를 파악합니다.
오류의 재현 방법과 위치를 파악했다면, 이제 본격적으로 오류를 해결해야 합니다.
이 단계에서는 마치 탐정이 된 듯, 단서를 찾아내는 작업을 시작합니다. 개발 툴의 디버깅 기능을 활용해 코드를 단계 별로 실행하며, 코드의 진행과 변수 값들을 확인합니다. 로그를 통해 코드가 예상대로 작동하는지 확인하고, 의심되는 코드 일부를 수정해 반응을 확인합니다. 데이터 입력값을 바꾸며 결과를 확인하고, 개발 툴에서 제공하는 프로파일 데이터도 모아 봅니다.
이렇게 단서를 모아가며 가설을 세웁니다. 오류가 계속해서 해결되지 않는다면, "설마?"하는 생각이 드는 아이디어라도 테스트해보는 것이 중요합니다. 다양한 시도를 하는 과정에서 간접적으로 해결책을 발견하는 경우도 있기 때문입니다.
떠오른 가설에 대해 테스트를 진행하고, 결과에 따라 다시 가설을 세우는 과정을 반복합니다.
오류 해결 뿐만 아니라 방지와 재발을 막는 것 역시 중요합니다. 이를 자동화 할 수 있다면 이상적입니다.
가장 간단한 방법은 자동으로 로그와 지표를 수집하는 것입니다. 이에 대해선 와탭의 서비스들이 도움을 드릴 수 있을텐데요. 고객의 코드 수정 없이 자동으로 성능과 오류 등의 데이터를 수집하고 분석하여 쉽게 이해할 수 있는 리포트를 제공하고 있습니다.
여력이 있다면 TDD(Test-Driven Development)를 도입하는 것을 고려해 볼 수 있습니다. 하지만, TDD는 적용하는데 어려움을 겪는 경우가 많습니다. 이런 상황에서는 TDD 대신 오류가 발견될 때마다 테스트 코드를 작성하는 것도 큰 도움이 됩니다.
E2E(End-to-End) 테스트를 고려해 보는 것도 추천합니다. E2E는 사용자 환경에서 발생 가능한 시나리오를 자동으로 테스트하고 오류를 알려주는 도구입니다. 자세한 사항은 Cypress, TestCafe, Selenium 등의 제품을 참조하시면 됩니다.
지금까지 디버깅에 대한 간단한 요령을 다루어 보았습니다. 디버깅은 실제 경험을 통해 배우는 것이 중요하므로, 시간과 시행착오를 필요로 합니다. 이 글만으로는 디버깅 기량이 획기적으로 향상되지 않을 수 있지만, 이 글이 제시한 절차가 초보 개발자들의 디버깅 가이드가 되어, 어려움을 겪고 있을 때 도움이 될 수 있기를 바랍니다.
감사합니다.