연습 - Visual Studio Code를 사용하여 디버그

완료됨

이제 새로 획득한 디버깅 지식을 활용해 보겠습니다. 이 작업의 첫 날이며, 회사의 주력 제품인 피보나치 계산기의 버그를 수정하기 위해 .NET 디버깅 기술을 발휘할 차례입니다.

디버깅을 위한 샘플 .NET 프로젝트 만들기

.NET 디버깅을 위해 Visual Studio Code를 설정하려면 먼저 .NET 프로젝트가 있어야 합니다. Visual Studio Code에는 쉽게 새 프로젝트를 만드는 통합 터미널이 포함되어 있습니다.

  1. Visual Studio Code에서 파일>폴더 열기를 선택합니다.

  2. 선택한 위치에서 DotNetDebugging이라는 새 폴더를 만듭니다. 그런 다음, 폴더 선택을 선택합니다.

  3. 주 메뉴에서 보기>터미널을 선택하여 Visual Studio Code에서 통합 터미널을 엽니다.

  4. 터미널 창에 다음 명령을 복사하고 붙여넣습니다.

    dotnet new console
    

    이 명령은 이미 작성된 간단한 “Hello World” 프로그램을 포함하여 폴더에 Program.cs 파일을 만듭니다. 또한 DotNetDebugging.csproj라는 C# 프로젝트 파일을 만듭니다.

  5. 터미널 창에 다음 명령을 복사하고 붙여넣어 “Hello World” 프로그램을 실행합니다.

    dotnet run
    

    터미널 창에 “Hello World!”가 출력으로 표시됩니다.

.NET 디버깅을 위해 Visual Studio Code 설정

  1. Program.cs를 선택하여 엽니다.

  2. Visual Studio Code에서 C# 파일을 처음으로 열면 C#에 권장되는 확장을 설치하라는 프롬프트가 표시됩니다. 이 프롬프트가 표시되면 프롬프트에서 설치 단추를 선택합니다.

    C# 확장을 설치하라는 Visual Studio Code 프롬프트의 스크린샷

  3. Visual Studio Code에서 C# 확장이 설치되며 프로젝트를 빌드하고 디버그하는 데 필요한 자산을 추가하라는 다른 프롬프트가 표시됩니다. 단추를 선택합니다.

    .NET 프로젝트를 빌드 및 디버그하는 데 필요한 자산을 추가하라는 Visual Studio Code 프롬프트의 스크린샷.

  4. 확장: C# 탭을 닫고 디버그하려는 코드에 집중해도 됩니다.

피보나치 프로그램 로직 추가

현재 프로젝트는 “Hello World” 메시지를 콘솔에 작성하기만 하면 디버그할 것이 많지는 않습니다. 대신 간단한 .NET 프로그램을 사용하여 피보나치 수열의 N번째 숫자를 계산합니다.

피보나치 수열은 숫자 0과 1로 시작하는 숫자 모음으로, 이후의 다른 모든 숫자는 이전 두 숫자의 합계가 됩니다. 수열은 다음과 같이 계속됩니다.

0, 1, 1, 2, 3, 5, 8, 13, 21...
  1. Program.cs를 선택하여 엽니다.

  2. Program.cs의 내용을 다음 코드로 바꿉니다.

    int result = Fibonacci(5);
    Console.WriteLine(result);
    
    static int Fibonacci(int n)
    {
        int n1 = 0;
        int n2 = 1;
        int sum;
    
        for (int i = 2; i < n; i++)
        {
            sum = n1 + n2;
            n1 = n2;
            n2 = sum;
        }
    
        return n == 0 ? n1 : n2;
    }
    

    참고

    이 코드에는 오류가 포함되어 있으며, 이 모듈의 뒷부분에서 해당 오류를 디버그할 예정입니다. 해당 버그가 수정될 때까지 중요 업무용 피보나치 애플리케이션에서 이 코드를 사용하지 않는 것이 좋습니다.

  3. Ctrl+S(Windows, Linux)를 선택하여 파일을 저장합니다. Mac의 경우 Cmd+S를 선택합니다.

  4. 디버깅하기 전에 업데이트된 코드가 어떻게 작동하는지 살펴보겠습니다. 터미널에서 다음 명령을 입력하여 프로그램을 실행합니다.

    dotnet run
    

    수정된 프로그램 출력을 포함한 터미널 창

  5. 결과 3이 터미널 출력에 표시됩니다. 괄호 안에 있는 각 값에 대해 0부터 시작하는 시퀀스 위치를 보여 주는 이 Fibonacci 시퀀스 차트를 참조하면 결과가 5여야 한다는 것을 알 수 있습니다. 디버거에 익숙해지고 이 프로그램을 수정할 차례입니다.

    0 (0), 1 (1), 1 (2), 2 (3), 3 (4), 5 (5), 8 (6), 13 (7), 21 (8)...
    

문제 분석

  1. 왼쪽의 실행 및 디버그 탭을 선택한 다음 디버깅 시작 단추를 선택하여 프로그램을 시작합니다. 먼저 실행 및 디버그 단추를 선택한 다음 Program.cs 파일을 선택해야 할 수도 있습니다.

    Visual Studio Code의 디버깅 시작 단추 스크린샷

    프로그램이 신속하게 완료되는 것을 볼 수 있습니다. 아직 중단점을 추가하지 않았기 때문에 정상적인 현상입니다.

  2. 디버그 콘솔이 표시되지 않으면 Windows 및 Linux의 경우 Ctrl+Shift+Y를 선택하고 Mac의 경우 Cmd+Shift+Y를 선택합니다. 끝에 다음과 같은 내용이 이어지는 몇 줄의 진단 정보가 표시됩니다.

    ...
    Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.0\System.Threading.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
    Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.0\System.Text.Encoding.Extensions.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
    3
    The program '[88820] DotNetDebugging.dll' has exited with code 0 (0x0).
    

위쪽 줄은 기본 디버깅 설정에서 "내 코드만" 옵션을 사용할 수 있음을 알려 줍니다. 즉, 디버거는 사용자 코드만 디버그하며, 이 모드를 사용하지 않도록 설정하지 않는 한 .NET의 소스 코드를 한 단계씩 실행하지 않습니다. 이 옵션을 통해 코드 디버깅에 집중할 수 있습니다.

디버그 콘솔 출력 끝에서 프로그램이 콘솔에 “3”을 쓴 다음, 코드 0과 함께 존재하는 것을 확인할 수 있습니다. 보통 프로그램 종료 코드 0은 프로그램이 크래시 없이 실행되고 종료되었음을 나타냅니다. 그러나 충돌하는 경우와 올바른 값을 반환하는 경우 사이에는 차이가 있습니다. 이 경우, 피보나치 수열의 5번째 값을 계산하도록 프로그램에 요청했습니다.

0 (0), 1 (1), 1 (2), 2 (3), 3 (4), 5 (5), 8 (6), 13 (7), 21 (8)...

이 목록의 5번째 값은 5이지만 프로그램에서는 3을 반환했습니다. 디버거를 사용하여 이 오류를 진단하고 수정해 보겠습니다.

중단점 및 한 단계씩 코드 실행 사용

  1. 1번째 줄의 왼쪽 여백을 클릭하여 int result = Fibonacci(5);에 중단점을 추가합니다.

    코드의 중단점 위치 스크린샷

  2. 다시 디버그를 시작합니다. 프로그램이 실행되기 시작합니다. 설정한 중단점으로 인해 줄 1에서 중단(실행 일시 중지)합니다. 디버거 컨트롤을 사용하여 Fibonacci() 함수를 한 단계씩 코드 실행합니다.

    한 단계씩 코드 실행 단추 스크린샷

변수 상태 확인

이제 변수 패널을 사용하여 여러 변수 값을 검사합니다.

변수 패널 스크린샷

  • n 매개 변수에 표시되는 값은 무엇인가요?
  • 함수 실행의 시작 부분에서 지역 변수 n1, n2, sum의 값은 무엇인가요?
  1. 다음으로 for 디버거 컨트롤을 사용하여 루프로 이동합니다.

    프로시저 단위 실행 단추 스크린샷

  2. 다음과 같은 줄에서 for 루프의 첫 번째 줄에 도달할 때까지 계속 진행합니다.

    sum = n1 + n2;
    

참고

for(...) {} 줄을 통과하기 위해 여러 단계에 걸쳐 명령을 실행해야 하는 것을 발견했을 수 있습니다. 이러한 상황은 이 줄에 여러 개의 문이 있기 때문에 발생합니다. 단계를 진행하면 코드의 다음 문으로 이동합니다. 일반적으로 한 줄에 하나의 문이 있습니다. 만약 그렇지 않다면 다음 줄로 이동하기 위해 여러 단계를 실행해야 합니다.

코드에 대한 생각

실행하려는 코드의 부분(루프와 같은 함수 및 블록)에 대해 알고 있는 정보를 사용하여 추측하고 이를 중지하는 것은 디버깅에서 중요한 부분입니다. 잘 몰라도 괜찮습니다. 그것도 디버깅 프로세스의 일부입니다. 그러나 디버깅 프로세스에 적극적으로 참여한다면 훨씬 더 신속하게 버그를 찾을 수 있습니다.

더 자세히 살펴보기 전에, 피보나치 수열은 숫자 0과 1로 시작하는 숫자 모음으로서, 이후의 다른 모든 숫자는 이전 두 숫자의 합계가 된다는 사실을 기억하시기 바랍니다.

이는 다음을 의미합니다.

Fibonacci(0) = 0
Fibonacci(1) = 1
Fibonacci(2) = 1 (0 + 1)
Fibonacci(3) = 2 (1 + 1)
Fibonacci(4) = 3 (1 + 2)
Fibonacci(5) = 5 (2 + 3)

정의를 이해하고 for 루프를 살펴 보면 다음을 추론할 수 있습니다.

  1. 루프는 2부터 n(찾고자 하는 피보나치 수열의 수)까지 계산합니다.
  2. n이 2보다 작은 경우 루프가 실행되지 않습니다. return이 0인 경우 함수 끝에 있는 n 문이 0을, n이 1 또는 2인 경우 1을 반환합니다. 이는 정의상 피보나치 수열의 0번째, 1번째, 2번째 값입니다.
  3. n이 2보다 큰 경우는 더 흥미롭습니다. 이러한 경우 현재 값은 이전 두 값의 합으로 정의됩니다. 따라서 이 루프의 n1n2는 이전 두 값이며 sum은 현재 반복에 대한 값입니다. 따라서 이전 두 값의 합계를 계산하고 sum으로 설정할 때마다 n1n2 값을 업데이트합니다.

그 이상으로 과도하게 생각할 필요는 없습니다. 디버거를 약간 활용할 수 있습니다. 하지만 코드가 기대한 바를 수행하는지 확인하고, 그러지 않을 경우 더 많은 정보를 얻도록 코드에 관해 생각해 볼 가치가 있습니다.

중단점을 사용하여 버그 찾기

코드를 단계별로 실행하는 것은 도움이 되지만 번거로울 수 있습니다. 특히 반복적으로 호출되는 루프나 기타 코드로 작업할 때 더욱 그렇습니다. 루프를 단계별로 계속 실행하는 대신 루프의 첫 번째 줄에 새 중단점을 설정해도 됩니다.

이 작업을 수행할 경우 중단점을 배치할 위치를 전략적으로 고려하는 것이 중요합니다. 이 값이 현재 최대 피보나치 값을 나타내므로 sum 값에 특히 관심이 있습니다. 그러므로 이 설정된 sum후에 라인에 중단점을 배치하겠습니다.

  1. 13번째 줄에 두 번째 중단점을 추가합니다.

    설정된 두 번째 중단점을 보여주는 스크린샷

    참고

    코드를 계속 실행한 다음 한 줄이나 두 줄을 단계별로 실행하면 중단점을 보다 효율적인 줄로 쉽게 업데이트할 수 있습니다.

  2. 이제 루프에 적절한 중단점을 설정했으니 디버거 컨트롤 계속을 사용하여 중단점에 도달할 때까지 진행합니다. 지역 변수를 살펴보면 다음 줄과 같이 표시됩니다.

    n [int]: 5
    n1 [int]: 0
    n2 [int]: 1
    sum [int]: 1
    i [int]: 2
    

    이러한 줄은 모두 올바른 듯합니다. 처음 루프를 실행하면 이전 두 값의 sum은 1입니다. 코드를 한 줄씩 단계별로 실행하는 대신 중단점을 활용하면 루프를 통해 다음번으로 이동할 수 있습니다.

  3. 루프를 통해 다음 단계에 있는 다음 중단점에 도달할 때까지 계속을 선택하여 프로그램 흐름을 지속합니다.

    참고

    계속을 사용할 때 버그를 건너뛰는 것을 너무 걱정하지 않아도 됩니다. 문제를 찾기 위해 코드를 여러 번 디버그하는 경우가 많습니다. 지나치게 주의하면서 단계별로 실행하는 것과는 반대로 몇 차례 실행하는 것이 더욱 빠릅니다.

    이번에는 다음 값을 보겠습니다.

    n [int]: 5
    n1 [int]: 1
    n2 [int]: 1
    sum [int]: 2
    i [int]: 3
    

    생각해 보겠습니다. 이 값은 여전히 의미가 있을까요? 그런 것 같습니다. 세 번째 피보나치 수의 경우 sum이 2와 같을 것으로 예상되며 실제로도 그러합니다.

  4. 좋습니다, 그럼 계속을 선택하여 다시 반복해 보겠습니다.

    n [int]: 5
    n1 [int]: 1
    n2 [int]: 2
    sum [int]: 3
    i [int]: 4
    

    이번에도 괜찮아 보이네요. 수열의 4번째 값은 3일 것으로 예상됩니다.

  5. 이 시점에서 코드가 모두 올바르고 버그를 발생시킬 수 있는지 궁금해지기 시작할 것입니다. 루프를 통해 마지막으로 계속 살펴보겠습니다. 계속을 한 번 더 선택합니다.

    잠시만 기다립니다. 프로그램의 실행이 완료되고 3이 출력되었습니다. 올바르지 않습니다.

    괜찮습니다. 실패한 것이 아니라 배운 것입니다. 이제 i가 4와 같아질 때까지 루프가 올바르게 실행되지만 최종 값을 계산하기 전에 종료된다는 사실을 알고 있습니다. 버그 위치에 대한 몇 가지 아이디어가 떠오르기 시작했습니다. 사용자 구분?

  6. 17번째 줄에 다음과 같이 하나 이상의 중단점을 설정해 보겠습니다.

    return n == 0 ? n1 : n2;
    

    이 중단점으로 인해 함수가 종료되기 전에 프로그램 상태를 검사할 수 있습니다. 1번째, 13번째 줄의 이전 중단점에서 예상할 수 있는 모든 사항은 이미 알아보았으므로 이 중단점들은 지워도 됩니다.

  7. 1번째, 13번째 줄의 이전 중단점을 제거합니다. 줄 번호 옆의 여백에서 클릭하거나 왼쪽 아래에 있는 중단점 창에서 1번째, 13번째 줄의 중단점을 선택 취소하면 됩니다.

    중단점 창에 나열된 중단점을 보여주는 스크린샷.

    발생할 많은 사항을 더 정확히 파악하고 오작동을 포착하도록 설계된 중단점을 설정했으므로 이제 이 버그를 잡아낼 수 있을 것입니다.

  8. 마지막으로 한번 디버거를 시작합니다.

    n [int]: 5
    n1 [int]: 2
    n2 [int]: 3
    sum [int]: 3
    

    결과가 좋지 않네요. 우리는 피보나치(5)를 요청했는데 피보나치(4)가 도출되었습니다. 이 함수는 n2를 반환하고 각 루프 반복은 sum 값을 계산하며 n2sum과 같도록 설정합니다.

    이러한 정보와 이전 버그 실행을 바탕으로 i가 5가 아닌 4일 때 루프가 종료되었다는 사실을 확인할 수 있습니다.

    for 루프의 첫 번째 줄을 조금 더 자세히 살펴보겠습니다.

    for (int i = 2; i < n; i++)
    

    좋습니다. 잠시만 기다려 주세요. 다시 말해 for 루프의 맨 위에서 in보다 작지 않으면 종료됩니다. 즉, in과 동일한 경우 루프 코드가 실행되지 않습니다. 하지만 여기에서 원했던 것은 다음과 같은 것이 아닌 i <= n까지 실행하는 것이었네요.

    for (int i = 2; i <= n; i++)
    

    변경할 경우 업데이트된 프로그램은 다음 예시와 유사하게 표시됩니다.

    int result = Fibonacci(5);
    Console.WriteLine(result);
    
    static int Fibonacci(int n)
    {
        int n1 = 0;
        int n2 = 1;
        int sum;
    
        for (int i = 2; i <= n; i++)
        {
            sum = n1 + n2;
            n1 = n2;
            n2 = sum;
        }
    
        return n == 0 ? n1 : n2;
    }
    
  9. 디버깅 세션을 아직 중지하지 않은 경우 중지합니다.

  10. 다음으로 10번째 줄을 이전과 같이 변경하고 17번째 줄의 중단점은 남겨 둡니다.

  11. 디버거를 다시 시작합니다. 이번에는 17번째 줄의 중단점에 도달하면 다음 값이 표시됩니다.

    n [int]: 5
    n1 [int]: 3
    n2 [int]: 5
    sum [int]: 5
    

    이봐요! 제대로 표시된 것 같네요. 잘하셨습니다. 이렇게 Fibonacci, Inc.의 문제를 훌륭하게 해결했습니다.

  12. 프로그램이 올바른 값을 반환하는지 확인하려면 계속을 선택합니다.

    5
    The program '[105260] DotNetDebugging.dll' has exited with code 0 (0x0).
    

    그러면 올바른 값이 반환됩니다.

잘하셨습니다! Visual Studio Code에서 .NET 디버거를 사용하여 작성하지 않은 일부 코드를 디버그했습니다.

다음 단원에서는 .NET에 기본 제공되는 로깅 및 추적 기능을 사용하여 작성하는 코드를 디버그하기 쉽게 만드는 방법을 배웁니다.