演習 -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" というメッセージを出力するものなので、デバッグすることはあまりありません。 代わりに、フィボナッチ数列の N番目の値を計算する短い .NET プログラムを使用します。

フィボナッチ数列は、0 と 1 から始まる一連の数値であり、後続の他のすべての数値は前の 2 つの合計になります。 このシーケンスは次のように続きます。

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;
    }
    

    Note

    このコードにはエラーが含まれています。このモジュールで後ほどそれをデバッグします。 そのバグが修正されるまで、ミッション クリティカルなフィボナッチ アプリケーションではそれを使用しないことをお勧めします。

  3. Windows と Linux の場合は、Ctrl + S キーを押してファイルを保存します。 Mac の場合は、Cmd + S キーを押します。

  4. デバッグする前に、更新されたコードがどのように動作するかを見てみましょう。 ターミナルで次のコマンドを入力して、プログラムを実行します。

    dotnet run
    

    プログラムの出力が変更されたターミナル ウィンドウ。

  5. 結果 3 がターミナル出力に示されます。 このフィボナッチ数列のグラフを参照すると、かっこ内の各値に 0 から始まるシーケンス位置が表示され、結果は 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 パラメーターに対して表示されている値は何ですか。
  • 関数の実行が開始する時点での、ローカル変数 n1n2sum の値は何ですか。
  1. 次に、[ステップ オーバー] デバッガー コントロールを使用して、for ループに進みます。

    [ステップ オーバー] ボタンのスクリーンショット

  2. for ループ内の最初にある次のような行に達するまで進みます。

    sum = n1 + n2;
    

Note

for(...) {} 行を移動するには、コマンドに複数のステップが必要であることにお気付きかもしれません。 このような状況になるのは、この行に複数の "ステートメント" があるためです。 ステップ実行すると、コード内の次のステートメントに進みます。 通常、1 行につき 1 つのステートメントがあります。 そうでない場合、次の行に進むには複数のステップが必要です。

コードについて考える

デバッグの重要な部分は、停止して、コードの部分 (関数とループなどのブロックの両方) で何が実行されようとしているかを、情報を基にして推測することです。 確実でなくても構いません。それは、デバッグ プロセスの一部です。 ただし、デバッグ プロセスに積極的に関わることは、バグをより迅速に見つけるのに役立ちます。

詳しく調べる前に、フィボナッチ数列とは、0 と 1 から始まる一連の数値であり、後続の他のすべての数値は前の 2 つの値の合計になる、ということに注意してください。

つまり、次のようになります。

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 ステートメントからは、n が0 の場合は 0 が返され、n が 1 または 2 の場合は 1 が返されます。 これらは、定義上、フィボナッチ数列の 0 番目、1 番目、2 番目の値です。
  3. さらに興味深いのは、n が 2 以上大きい場合です。 そのような場合、現在の値は、前の 2 つの値の合計として定義されます。 このループの場合、n1n2 が前の 2 つの値で、sum が現在の反復の値です。 そのため、前の 2 つの値の合計が明らかになり、それが sum に設定されるたびに、n1n2 の値が更新されます。

その先まで考え過ぎる必要はありません。 少しはデバッガーに頼ることができます。 ただし、コードが期待どおりに動作しているかどうかを確認し、そうでない場合はより多くの情報を得ることは役に立ちます。

ブレークポイントを使用してバグを見つける

コードのステップ実行は役に立ちますが、ループや、繰り返し呼び出されるその他のコードを操作する場合は特に、面倒な場合があります。 ループを何回もステップ実行するのではなく、ループの最初の行に新しいブレークポイントを設定できます。

これを行うときは、ブレークポイントを設定する場所を戦略的に考えることが重要です。 現在の最大のフィボナッチ数を表しているため、sum の値に特に関心があります。 そのため、sum が設定された "" の行にブレークポイントを設定してみましょう。

  1. 13 行目に 2 つ目のブレークポイントを追加します。

    2 つ目のブレークポイントの設定を示すスクリーンショット。

    Note

    コードがまだ実行中の場合は、1、2 行ステップ実行してみることで、より効率的な行にブレークポイントを簡単に更新できます。

  2. ループに適切なブレークポイントを設定できたので、[続行] デバッガー コントロールを使用して、ブレークポイントにヒットするまで進めます。 ローカル変数を調べると、次の行のように表示されます。

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

    これらの行はすべて正しいように見えます。 ループの最初の実行の場合、前の 2 つの値の sum は 1 になります。 1 行ずつステップ実行するのではなく、ブレークポイントを利用して、次のループまで移動することができます。

  3. [続行] を選択し、ブレークポイントに次にヒットするまでプログラム フローを続けます。それはループの次のパスになります。

    Note

    [続行] を使用するとき、バグを飛び越えてしまうことをあまり気にしないでください。 多くの場合、コードを複数回デバッグして問題を検出することをお勧めします。 多くの場合、ステップ実行時に慎重になりすぎるよりも、複数回実行する方が短時間で済みます。

    今回は、次の値が表示されます。

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

    これについて考えてみましょう。 これらの値はまだ意味があるでしょうか。 そのように見えます。 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 行目にブレークポイントをもう 1 つ設定してみましょう。次のようになります。

    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
    

    そうはなりませんでした。 具体的には、Fibonacci(5) を期待したのですが、Fibonacci(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 に組み込まれているログ機能とトレース機能を使用して、記述するコードをデバッグしやすくする方法について学習します。