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

완료됨

이제 새로 획득한 디버깅 지식을 활용해 보겠습니다. 작업 첫날이며, 회사의 주력 제품인 피보나치 계산기의 버그를 수정하여 .NET 디버깅 기술을 작동시키고자 합니다.

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

.NET 디버깅용 Visual Studio를 설정하려면 먼저 .NET 프로젝트가 필요합니다. Visual Studio는 새 프로젝트를 쉽게 만들 수 있는 많은 시작 템플릿을 제공합니다.

  1. Visual Studio에서 파일>새로 만들기>프로젝트를 선택합니다.

  2. 새 프로젝트 만들기 대화 상자에서 콘솔 앱을 선택하고 다음을 선택합니다.

  3. 프로젝트 이름을 DotNetDebugging으로 지정하고 저장할 위치를 선택합니다. 다른 값을 기본값으로 유지한 다음, 다음을 선택합니다.

  4. 최종 화면에서 만들기 를 선택합니다.

Visual Studio는 선택한 템플릿을 사용하여 콘솔 프로젝트를 만듭니다. 프로젝트가 로드되면 Program.cs를 선택하여 엽니다.

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

현재 프로젝트는 “Hello World” 메시지를 콘솔에 작성하기만 하면 디버그할 것이 많지는 않습니다. 대신 짧은 .NET 프로그램을 사용하여 피보나치 시퀀스의 N번째 번호를 계산해 보겠습니다.

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

0, 1, 1, 2, 3, 5, 8, 13, 21...

다음 샘플 코드에는 버그가 포함되어 있으므로 Visual Studio의 디버깅 도구를 사용하여 문제를 진단하고 해결해 보겠습니다.

  1. Program.cs의 내용을 다음 코드로 바꿉니다.
int result = Fibonacci(5);
Console.WriteLine(result);

static int Fibonacci(int n)
{
    Console.WriteLine("The output is: ");
    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;
}

참고

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

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

  2. 디버그하기 전에 업데이트된 코드가 작동하는 방식을 살펴보겠습니다. Visual Studio 명령 모음에서 녹색 시작 단추를 눌러 프로그램을 실행합니다.

  3. 디버그 콘솔 출력의 끝에서 프로그램이 콘솔에 3을 쓴 다음 코드 0으로 종료되는 것을 볼 수 있습니다. 보통 프로그램 종료 코드 0은 프로그램이 크래시 없이 실행되고 종료되었음을 나타냅니다. 그러나 충돌하는 경우와 올바른 값을 반환하는 경우 사이에는 차이가 있습니다.

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

이 경우, 피보나치 수열의 5번째 값을 계산하도록 프로그램에 요청했습니다.

0, 1, 1, 2, 3, 5, 8, 13, 21...

이 목록의 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. 2보다 작으면 n 루프가 실행되지 않습니다. 함수의 끝에 있는 문은 return 0인 경우 n 0을 반환하고, 1이 1 또는 2이면 n 1을 반환합니다. 이러한 값은 정의에 따라 피보나치 계열의 0, 첫 번째 및 두 번째 값입니다.
  3. n이 2보다 큰 경우는 더 흥미롭습니다. 이러한 경우 현재 값은 이전 두 값의 합으로 정의됩니다. 따라서 이 루프의 n1n2는 이전 두 값이며 sum은 현재 반복에 대한 값입니다. 이 논리에 따라 이전 두 값의 합계를 계산하여 그것을 sum에 설정할 때마다, n1n2 값을 업데이트합니다.

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

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

코드를 단계별로 실행하는 것은 유용하지만 번거로울 수 있습니다. 특히 반복적으로 호출되는 루프 또는 기타 코드를 사용하는 경우 루프를 단계별로 계속 실행하는 대신 루프의 첫 번째 줄에 새 중단점을 설정해도 됩니다.

중단점을 배치할 위치를 전략적으로 고려하는 것이 중요합니다. sum 값이 현재 피보나치 최댓값을 나타내므로 해당 값에 특히 관심이 있습니다. 따라서 설정된 sum 줄에 중단점을 배치해 보겠습니다.

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

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

    참고

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

  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. 18번째 줄에 다음과 같은 하나 이상의 중단점을 설정해 보겠습니다.

    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)
    {
        Console.WriteLine("The output is: ");
        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. 11번째 줄을 이전과 같이 변경하고 18번째 줄의 중단점은 남겨 둡니다.

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

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

    있잖아요! 제대로 표시된 것 같네요. 잘했어요, 피보나치, Inc.을 도와주셔서 감사합니다!

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

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

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

완료되었습니다. Visual Studio에서 .NET 디버거를 사용하여 작성하지 않은 일부 코드를 디버그했습니다.

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