절대 초보자를 위한 디버그

소프트웨어 개발자로서 작성한 코드가 예상한 대로 항상 작동하는 것은 아닙니다. 때로는 완전히 다른 일을합니다! 예기치 않은 일이 발생하면 다음 작업은 이유를 파악하는 것이며, 몇 시간 동안 코드를 계속 응시하려는 유혹을 받을 수도 있지만 디버깅 도구 또는 디버거를 사용하는 것이 더 쉽고 효율적입니다.

불행히도 디버거는 코드의 모든 문제 또는 "버그"를 마술처럼 드러낼 수 있는 것이 아닙니다. 디버깅 Visual Studio와 같은 디버깅 도구에서 코드를 단계별로 실행하여 프로그래밍 실수를 저지른 정확한 지점을 찾는 것을 의미합니다. 그런 다음 코드 및 디버깅 도구에서 수정해야 하는 사항을 이해하면 프로그램을 계속 실행할 수 있도록 임시 변경을 수행할 수 있습니다.

디버거를 효과적으로 사용하는 것은 학습하는 데 시간과 연습이 필요하지만 궁극적으로는 모든 소프트웨어 개발자에게 기본적인 작업입니다. 이 문서에서는 디버깅의 핵심 원칙을 소개하고 시작하기 위한 팁을 제공합니다.

자신에게 올바른 질문을 하여 문제를 명확히 합니다.

문제를 해결하기 전에 문제를 명확히 하는 데 도움이 됩니다. 코드에서 이미 문제가 발생할 것으로 예상합니다. 그렇지 않으면 디버그하는 방법을 알아내려고 여기 있지 않을 것입니다. 따라서 디버깅을 시작하기 전에 해결하려는 문제를 식별했는지 확인합니다.

  • 코드가 무엇을 할 것으로 예상했나요?

  • 대신 무슨 일이 일어났는가?

    앱을 실행하는 동안 오류(예외)가 발생하면 좋은 일이 될 수 있습니다. 예외는 코드를 실행할 때 발생하는 예기치 않은 이벤트이며, 일반적으로 어떤 종류의 오류입니다. 디버깅 도구는 예외가 발생한 코드의 정확한 위치로 이동하고 가능한 수정 사항을 조사하는 데 도움이 될 수 있습니다.

    다른 일이 발생하면 문제의 증상은 무엇입니까? 코드에서 이 문제가 발생한 위치를 이미 의심하시나요? 예를 들어 코드에 일부 텍스트가 표시되지만 텍스트가 잘못된 경우 데이터가 잘못되었거나 표시 텍스트를 설정하는 코드에 일종의 버그가 있음을 알 수 있습니다. 디버거에서 코드를 단계별로 실행하면 변수에 대한 모든 변경 내용을 검사하여 잘못된 값이 할당되는 시기와 방법을 정확하게 검색할 수 있습니다.

가설 검토

버그 또는 오류를 조사하기 전에 특정 결과를 예상한 가정을 생각해 보세요. 숨겨져 있거나 잘못된 가정은 심지어 디버거에서 문제의 원인을 보고 있을 때조차도 문제를 식별하는 데 방해가 될 수 있습니다. 가능한 가정 목록이 길어질 수 있습니다. 다음은 가정에 도전하도록 스스로에게 물어보는 몇 가지 질문입니다.

  • 올바른 API(즉, 올바른 개체, 함수, 메서드 또는 속성)를 사용하고 있나요? 사용 중인 API는 사용자가 생각하는 작업을 수행하지 않을 수 있습니다. (디버거에서 API 호출을 검사한 후 수정하려면 올바른 API를 식별하는 데 도움이 되도록 설명서로 이동해야 할 수 있습니다.)

  • API를 올바르게 사용하고 있나요? 올바른 API를 사용했지만 올바른 방법으로 사용하지 않았을 수도 있습니다.

  • 코드에 오타가 포함되어 있나요? 일부 오타(예: 변수 이름의 단순한 맞춤법 오류)는 특히 변수를 사용하기 전에 선언할 필요가 없는 언어로 작업하는 경우 확인하기 어려울 수 있습니다.

  • 코드를 변경했는데, 그 변경이 보이는 문제와 관련이 없다고 생각했나요?

  • 개체 또는 변수에 실제로 발생한 것과 다른 특정 값(또는 특정 형식의 값)이 포함될 것으로 예상했나요?

  • 코드의 의도를 알고 있나요? 다른 사람의 코드를 디버그하는 것이 더 어려운 경우가 많습니다. 코드가 아닌 경우 코드를 효과적으로 디버그하기 전에 코드가 수행하는 작업을 정확하게 학습하는 데 시간을 할애해야 할 수 있습니다.

    코드를 작성할 때는 작게 시작하고 작동하는 코드로 시작합니다. (좋은 샘플 코드는 여기에 유용합니다.) 경우에 따라 달성하려는 핵심 작업을 보여 주는 작은 코드 조각으로 시작하여 크거나 복잡한 코드 집합을 수정하는 것이 더 쉽습니다. 그런 다음 코드를 증분 방식으로 수정하거나 추가하여 각 지점에서 오류를 테스트할 수 있습니다.

가정에 의문을 제기하면 코드에서 문제를 찾는 데 걸리는 시간을 줄일 수 있습니다. 문제를 해결하는 데 걸리는 시간을 줄일 수도 있습니다.

디버깅 모드에서 코드를 단계별로 실행하여 문제가 발생한 위치를 찾습니다.

일반적으로 앱을 실행하면 코드가 실행된 후에만 오류 및 잘못된 결과가 표시됩니다. 프로그램도 이유를 알리지 않고 예기치 않게 종료될 수 있습니다.

디버깅 모드라고도 하는 디버거 내에서 앱을 실행하면 디버거는 프로그램이 실행될 때 발생하는 모든 작업을 적극적으로 모니터링합니다. 또한 언제든지 앱을 일시 중지하여 상태를 검사한 다음 코드 줄을 한 줄씩 단계별로 실행하여 발생하는 모든 세부 정보를 볼 수 있습니다.

Visual Studio에서는 F5(또는 디버그>디버깅 시작 메뉴 명령 또는 디버깅 시작 단추를 보여 주는 디버깅 시작 단추아이콘을 사용하여 디버깅 모드로 전환합니다. 디버그 도구 모음의). 예외가 발생하는 경우 Visual Studio의 예외 도우미는 예외가 발생한 정확한 지점으로 이동하고 다른 유용한 정보를 제공합니다. 코드에서 예외를 처리하는 방법에 대한 자세한 내용은디버깅 기술 및 도구를 참조하세요.

예외가 발생하지 않은 경우 코드에서 문제를 찾을 위치를 잘 알 수 있을 것입니다. 이 단계에서는 디버거와 중단점을 사용하여 보다 신중하게 코드를 검사할 수 있는 기회를 얻을 수 있습니다. 중단점은 신뢰할 수 있는 디버깅의 가장 기본적이고 필수적인 기능입니다. 중단점은 코드가 실행되는 시퀀스인 변수 값 또는 메모리 동작을 살펴볼 수 있도록 Visual Studio에서 실행 중인 코드를 일시 중지해야 하는 위치를 나타냅니다.

Visual Studio에서 코드 줄 옆의 왼쪽 여백을 클릭하여 중단점을 빠르게 설정할 수 있습니다. 또는 커서를 줄에 놓고 F9누릅니다.

이러한 개념을 설명하기 위해 이미 몇 가지 버그가 있는 몇 가지 예제 코드를 살펴보겠습니다. C#을 사용하고 있지만 디버깅 기능은 Visual Basic, C++, JavaScript, Python 및 기타 지원되는 언어에 적용됩니다. Visual Basic에 대한 샘플 코드도 제공되지만 스크린샷은 C#에 있습니다.

샘플 앱 만들기(일부 버그 포함)

다음으로, 몇 가지 버그가 있는 애플리케이션을 만듭니다.

  1. Visual Studio가 설치되어 있고 .NET 데스크톱 개발 워크로드가 설치되어 있어야 합니다.

    Visual Studio를 아직 설치하지 않은 경우 Visual Studio 다운로드 페이지로 이동하여 무료로 설치합니다.

    워크로드를 설치해야 하지만 Visual Studio가 이미 있는 경우 도구를>도구 및 기능 가져오기선택합니다. Visual Studio 설치 관리자가 시작됩니다. .NET 데스크톱 개발 워크로드를 선택한 다음 수정선택합니다.

  2. Visual Studio를 엽니다.

    시작 창에서 새 프로젝트 만들기을 선택합니다. 검색 상자에 콘솔을 입력하고, 언어로 C# 또는 Visual Basic을 선택한 다음, .NET용 콘솔 앱을 선택합니다. 다음 선택합니다. 프로젝트 이름으로 ConsoleApp_FirstApp 을 입력하고 다음을 선택합니다.

    다른 프로젝트 이름을 사용하는 경우 예제 코드를 복사할 때 프로젝트 이름과 일치하도록 네임스페이스 값을 수정해야 합니다.

    권장 대상 프레임워크나 .NET 8을 선택한 다음, 만들기을 선택하세요.

    .NET용 콘솔 앱 프로젝트 템플릿이 표시되지 않으면, 도구>기능 및 도구 가져가기메뉴로 이동하여 Visual Studio 설치 관리자를 엽니다. .NET 데스크톱 개발 워크로드를 선택한 다음 수정선택합니다.

    Visual Studio는 콘솔 프로젝트를 생성하며, 이 프로젝트는 오른쪽 창의 솔루션 탐색기에 표시됩니다.

  3. Program.cs(또는 Program.vb)에서 모든 기본 코드를 다음 코드로 바꿉 있습니다. (C# 또는 Visual Basic 중에서 올바른 언어 탭을 먼저 선택합니다.)

    using System;
    using System.Collections.Generic;
    
    namespace ConsoleApp_FirstApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Welcome to Galaxy News!");
                IterateThroughList();
                Console.ReadKey();
            }
    
            private static void IterateThroughList()
            {
                var theGalaxies = new List<Galaxy>
            {
                new Galaxy() { Name="Tadpole", MegaLightYears=400, GalaxyType=new GType('S')},
                new Galaxy() { Name="Pinwheel", MegaLightYears=25, GalaxyType=new GType('S')},
                new Galaxy() { Name="Cartwheel", MegaLightYears=500, GalaxyType=new GType('L')},
                new Galaxy() { Name="Small Magellanic Cloud", MegaLightYears=.2, GalaxyType=new GType('I')},
                new Galaxy() { Name="Andromeda", MegaLightYears=3, GalaxyType=new GType('S')},
                new Galaxy() { Name="Maffei 1", MegaLightYears=11, GalaxyType=new GType('E')}
            };
    
                foreach (Galaxy theGalaxy in theGalaxies)
                {
                    Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears + ",  " + theGalaxy.GalaxyType);
                }
    
                // Expected Output:
                //  Tadpole  400,  Spiral
                //  Pinwheel  25,  Spiral
                //  Cartwheel, 500,  Lenticular
                //  Small Magellanic Cloud .2,  Irregular
                //  Andromeda  3,  Spiral
                //  Maffei 1,  11,  Elliptical
            }
        }
    
        public class Galaxy
        {
            public string Name { get; set; }
    
            public double MegaLightYears { get; set; }
            public object GalaxyType { get; set; }
    
        }
    
        public class GType
        {
            public GType(char type)
            {
                switch(type)
                {
                    case 'S':
                        MyGType = Type.Spiral;
                        break;
                    case 'E':
                        MyGType = Type.Elliptical;
                        break;
                    case 'l':
                        MyGType = Type.Irregular;
                        break;
                    case 'L':
                        MyGType = Type.Lenticular;
                        break;
                    default:
                        break;
                }
            }
            public object MyGType { get; set; }
            private enum Type { Spiral, Elliptical, Irregular, Lenticular}
        }
    }
    

    이 코드의 목적은 은하계 이름, 은하계와의 거리 및 은하계 유형을 모두 목록에 표시하는 것입니다. 디버그하려면 코드의 의도를 이해하는 것이 중요합니다. 다음은 출력에 표시할 목록의 한 줄에 대한 형식입니다.

    은하 이름, 거리, 은하 유형.

앱 실행

F5 또는 디버깅 시작 단추가 표시된 디버깅 시작 단추 아이콘을 누릅니다. 코드 편집기 위에 있는 디버그 도구 모음에 있습니다.

앱이 시작되고 디버거에서 예외가 전혀 발견되지 않습니다. 그러나 콘솔 창에 표시되는 출력은 예상과 다릅니다. 예상 출력은 다음과 같습니다.

Tadpole  400,  Spiral
Pinwheel  25,  Spiral
Cartwheel, 500,  Lenticular
Small Magellanic Cloud .2,  Irregular
Andromeda  3,  Spiral
Maffei 1,  Elliptical

하지만 이 출력이 대신 표시됩니다.

Tadpole  400,  ConsoleApp_FirstApp.GType
Pinwheel  25,  ConsoleApp_FirstApp.GType
Cartwheel, 500,  ConsoleApp_FirstApp.GType
Small Magellanic Cloud .2,  ConsoleApp_FirstApp.GType
Andromeda  3,  ConsoleApp_FirstApp.GType
Maffei 1, 11,  ConsoleApp_FirstApp.GType

출력과 코드를 살펴보면 GType 은하 유형을 저장하는 클래스의 이름이라는 것을 알고 있습니다. 클래스 이름이 아닌 실제 은하 유형(예: "나선형")을 표시하려고 합니다.

앱 디버그

  1. 앱이 계속 실행 중인 상태에서 중단점을 삽입합니다.

    foreach 루프에서 Console.WriteLine 메서드 옆을 마우스 오른쪽 단추로 클릭하여 상황에 맞는 메뉴가 표시되면, 플라이아웃 메뉴에서 중단점>삽입 중단점을 선택합니다.

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears + ",  " + theGalaxy.GalaxyType);
    }
    

    중단점을 설정하면 왼쪽 여백에 빨간색 점이 나타납니다.

    출력에 문제가 표시되면 디버거에서 출력을 설정하는 이전 코드를 확인하여 디버깅을 시작합니다.

  2. 디버그 도구 모음에서 RestartApp 단추를 표시하는 다시 시작아이콘을 선택합니다. 디버그 도구 모음의 단추(Ctrl + Shift + F5).

    설정한 중단점에서 앱이 일시 중지됩니다. 노란색 강조 표시는 디버거가 일시 중지된 위치를 나타냅니다(노란색 코드 줄이 아직 실행되지 않음).

  3. 오른쪽의 GalaxyType 변수를 마우스로 가리킨 다음, 스패너 아이콘 왼쪽에서 theGalaxy.GalaxyType을 확장합니다. GalaxyType에는 속성 MyGType이 포함되어 있으며 속성 값은 Spiral로 설정되어 있습니다.

    코드 줄이 노란색으로 표시되고 Galaxy GalaxyType 속성 아래에 메뉴가 열려 있는 Visual Studio 디버거의 스크린샷

    "나선형"은 콘솔에 예상대로 출력되길 기대한 올바른 값입니다. 따라서 앱을 실행하는 동안 이 코드의 값에 액세스할 수 있는 것이 좋습니다. 이 시나리오에서는 잘못된 API를 사용합니다. 디버거에서 코드를 실행하는 동안 이 문제를 해결할 수 있는지 살펴보겠습니다.

  4. 동일한 코드에서 디버깅하는 동안, 커서를 theGalaxy.GalaxyType의 끝에 놓고 그것을 theGalaxy.GalaxyType.MyGType로 변경하세요. 편집할 수 있지만 코드 편집기는 오류(빨간색 물결선)를 표시합니다. (Visual Basic에서는 오류가 표시되지 않으며 이 코드 섹션이 작동합니다.)

  5. F11(Debug>Step Into 또는 디버그 도구 모음의 Step Into 버튼)을 눌러 현재 코드 줄을 실행합니다.

    F11 디버거를 한 문장씩 진행하며 코드를 실행합니다. F10 (스텝 오버)는 유사한 명령이며 디버거를 사용하는 방법을 학습할 때 두 명령 모두 유용합니다.

    디버거를 진행하려고 하면 편집을 컴파일할 수 없음을 나타내는 핫 다시 로드 대화 상자가 나타납니다.

    코드 줄이 빨간색으로 강조 표시되고 편집 옵션이 선택된 메시지 상자가 있는 Visual Studio 디버거 스크린샷

    편집하며 계속하기 대화 상자가 나타나 편집을 컴파일할 수 없음을 나타냅니다.

    코드 줄이 빨간색으로 강조 표시되고 편집 옵션이 선택된 메시지 상자가 있는 Visual Studio 디버거 스크린샷

    메모

    Visual Basic 예제 코드를 디버깅하려면, 디버그 도구 모음에서 앱 다시 시작 버튼이 있는 다시 시작아이콘을 클릭하라는 지시가 있을 때까지 다음 몇 단계를 건너뛰십시오. 버튼입니다.

  6. 핫 리로드 메시지 상자에서 편집 또는 편집하고 계속하기를 선택하세요. 이제 오류 목록 창에 오류 메시지가 표시됩니다. 이 오류는 'object'MyGType대한 정의가 포함되어 있지 않음을 나타냅니다.

    빨간색으로 강조 표시된 코드 줄과 두 개의 오류가 나열된 오류 목록 창이 있는 Visual Studio 디버거 스크린샷

    각 은하계를 GType 형식의 개체(MyGType 속성 포함)로 설정하더라도 디버거는 theGalaxy 개체를 GType형식의 개체로 인식하지 않습니다. 무슨 일이죠? 은하 유형을 설정하는 코드를 살펴보려고 합니다. 이렇게 하면 GType 클래스에 확실히 MyGType속성이 있지만 뭔가 옳지 않은 것을 알 수 있습니다. object 대한 오류 메시지는 단서로 밝혀졌습니다. 언어 인터프리터에 대해 형식은 GType형식의 개체 대신 object 형식의 개체로 나타납니다.

  7. galaxy 형식 설정과 관련된 코드를 살펴보면 GalaxyType 속성이 Galaxy 클래스에서 GType대신 object로 지정되어 있는 것을 발견합니다.

    public object GalaxyType { get; set; }
    
  8. 앞의 코드를 다음과 같이 변경합니다.

    public GType GalaxyType { get; set; }
    
  9. 디버그 도구 모음에서 앱 다시 시작 단추가 표시된 다시 시작아이콘을 선택합니다. 디버그 도구 모음의 단추(Ctrl + Shift + F5)) 코드를 다시 컴파일하고 다시 시작합니다.

    이제 디버거가 Console.WriteLine에서 일시 중지되면, theGalaxy.GalaxyType.MyGType위에 마우스를 가져다 대어 값을 제대로 설정되었는지 확인할 수 있습니다.

  10. 왼쪽 여백에서 중단점 표시를 클릭하여 중단점을 제거합니다(또는 오른쪽 클릭하고 중단점>삭제 중단점선택), 그 다음 F5를 눌러 계속 진행합니다.

    앱이 실행되고 출력이 표시됩니다. 좋아 보인다, 그런데 한 가지를 알아챈다. 당신은 작은 마젤란 구름 은하가 콘솔 출력에서 불규칙한 은하로 나타날 것으로 예상했지만, 전혀 은하 유형이 표시되지 않습니다.

    Tadpole  400,  Spiral
    Pinwheel  25,  Spiral
    Cartwheel, 500,  Lenticular
    Small Magellanic Cloud .2,
    Andromeda  3,  Spiral
    Maffei 1,  Elliptical
    
  11. switch 문 앞에 이 코드 줄에 중단점을 설정합니다(Visual Basic의 Select 문 앞에).

    public GType(char type)
    

    이 코드는 은하계 형식이 설정된 위치이므로 좀 더 자세히 살펴보겠습니다.

  12. 디버그 도구 모음에서 '앱 다시 시작' 단추가 표시된 '다시 시작'아이콘을 선택하세요. 그리고 디버그 도구 모음의 단추(Ctrl + Shift + F5)를 눌러 다시 시작하세요.

    디버거는 중단점을 설정하는 코드 줄에서 일시 중지됩니다.

  13. 마우스를 type 변수 위에 올려놓으세요. 문자 코드에 따라 S 값이 표시됩니다. 불규칙한 은하 유형임을 알고 있기 때문에 I값에 관심이 있습니다.

  14. F5 누르고 type 변수 위로 마우스를 다시 가리킵니다. type 변수에 I 값이 표시될 때까지 이 단계를 반복합니다.

    노란색 코드 줄과 형식 변수 값이 73인 창이 있는 Visual Studio 디버거 스크린샷 I

  15. 이제 F11(디버그>한 단계씩)를 누릅니다.

  16. switch 문에서 'I' 값이 나타날 때까지 F11을 누릅니다 (Visual Basic의 경우Select 문). 여기서는 오타로 인해 발생하는 명확한 문제가 표시됩니다. 코드가 MyGType을 불규칙 은하 유형으로 설정하는 위치로 진행할 것으로 예상했지만, 디버거가 이 코드를 완전히 건너뛰고 Visual Basic의Else 구문에서 switch 구문의 default 섹션에서 일시 중지합니다.

    오타 오류를 보여 주는 스크린샷

    코드를 보면 case 'l' 문에 오타가 표시됩니다. "case 'I'으로 되어야 합니다."

  17. 코드에서 case 'l'을 선택하고 case 'I'으로 바꿉니다.

  18. 중단점을 제거한 다음 다시 시작 단추를 선택하여 앱을 다시 시작합니다.

    이제 버그가 수정되었으며 예상 출력이 표시됩니다.

    아무 키나 눌러 앱을 마칩니다.

요약

문제가 표시되면 F10F11 같은 디버거 및 단계 명령을 사용하여 문제가 있는 코드 영역을 찾습니다.

메모

문제가 발생하는 코드 영역을 식별하기 어려운 경우 문제가 발생하기 전에 실행되는 코드에서 중단점을 설정한 다음 문제 매니페스트가 표시될 때까지 단계 명령을 사용합니다. 추적점 사용하여 출력 창에 메시지를 기록할 수도 있습니다. 기록된 메시지를 확인하고 아직 기록되지 않은 메시지를 알아차리면 종종 코드 영역을 문제와 격리할 수 있습니다. 범위를 좁히려면 이 프로세스를 여러 번 반복해야 할 수 있습니다.

문제가 있는 코드 영역을 찾으면 디버거를 사용하여 조사합니다. 문제의 원인을 찾으려면 디버거에서 앱을 실행하는 동안 문제 코드를 검사합니다.

  • 변수 검사하고 변수에 포함해야 하는 값의 형식이 포함되어 있는지 확인합니다. 잘못된 값을 찾은 경우 잘못된 값이 설정된 위치를 확인합니다(값이 설정된 위치를 찾으려면 디버거를 다시 시작하거나 호출 스택확인하거나 둘 다 확인해야 할 수 있습니다).

  • 애플리케이션이 예상한 코드를 실행하고 있는지 확인합니다. 예를 들어 샘플 애플리케이션에서는 switch 문의 코드가 은하 유형을 불규칙한 형식으로 설정해야 했지만 앱은 오타로 인해 코드를 건너뛰었습니다.

디버거를 사용하여 버그를 찾을 수 있습니다. 디버깅 도구는 코드의 의도를 알고 있는 경우에만 버그를 당신을 위해 찾을 수 있습니다. 개발자가 해당 의도를 표현하는 경우에만 도구에서 코드의 의도를 알 수 있습니다. 단위 테스트 를 작성하는 것이 바로 그 방법입니다.

다음 단계

이 문서에서는 몇 가지 일반적인 디버깅 개념을 알아보았습니다. 다음으로 디버거에 대해 자세히 알아보기 시작할 수 있습니다.

먼저 디버거 살펴보기