次の方法で共有


より優れたコードを記述するのに役立つデバッグ手法とツール

コード内のバグやエラーの修正には時間がかかり、場合によってはイライラする作業になる場合があります。 効果的にデバッグする方法については、時間がかかります。 Visual Studio のような強力な IDE を使用すると、ジョブが大幅に簡単になります。 IDE は、エラーの修正やコードのデバッグを迅速に行うのに役立ち、バグを減らしながらより優れたコードを記述するのに役立ちます。 この記事では、"バグ修正" プロセスの全体像を示します。これにより、コード アナライザーを使用するタイミング、デバッガーを使用するタイミング、例外を修正する方法、意図のコーディング方法を把握できます。 デバッガーを使用する必要があることがわかっている場合は、まずデバッガーを 確認するを参照してください。

この記事では、IDE を使用してコーディング セッションの生産性を高める方法について説明します。 次のようないくつかのタスクについて取り上げられます。

  • IDE のコード アナライザーを使用してデバッグ用のコードを準備する

  • 例外を修正する方法 (実行時エラー)

  • 意図をコーディングしてバグを最小限に抑える方法 (assert を使用)

  • デバッガーを使用するタイミング

これらのタスクを示すために、アプリのデバッグ時に発生する可能性があるエラーとバグの最も一般的な種類をいくつか示します。 サンプル コードは C# ですが、概念情報は通常、Visual Studio でサポートされている C++、Visual Basic、JavaScript、およびその他の言語 (記載されている場合を除く) に適用できます。 スクリーンショットは C# にあります。

いくつかのバグとエラーを含むサンプル アプリを作成する

次のコードには、Visual Studio IDE を使用して修正できるバグがいくつかあります。 このアプリケーションは、何らかの操作から JSON データを取得し、データをオブジェクトに逆シリアル化し、単純なリストを新しいデータで更新することをシミュレートする単純なアプリです。

アプリを作成するには、Visual Studio がインストールされ、 .NET デスクトップ開発 ワークロードがインストールされている必要があります。

  • Visual Studio をまだインストールしていない場合は、Visual Studio のダウンロード ページに移動して無料でインストールします。

  • ワークロードをインストールする必要があるが、Visual Studio が既にある場合は、[ ツール]>[ツールと機能の取得] を選択します。 Visual Studio インストーラーが起動します。 .NET デスクトップ開発ワークロードを選択し、[変更] を選択します。

アプリケーションを作成するには、次の手順に従います。

  1. Visual Studio を開きます。 [スタート ウィンドウ] で、 [新しいプロジェクトの作成] を選択します。

  2. 検索ボックスに「 console 」と入力し、.NET の コンソール アプリ オプションの 1 つを入力します。

  3. [次へ] を選択します。

  4. Console_Parse_JSONなどのプロジェクト名を入力し、必要に応じて [次へ] または [作成] を選択します。

    推奨されるターゲット フレームワークまたは .NET 8 を選択し、次に 作成を選択します。

    .NET 用コンソール アプリ のプロジェクト テンプレートが表示されない場合は、[ツール] >[ツールと機能] に移動し、Visual Studio インストーラーを開きます。 .NET デスクトップ開発ワークロードを選択し、[変更] を選択します。

    Visual Studio によってコンソール プロジェクトが作成され、右側のウィンドウのソリューション エクスプローラーに表示されます。

プロジェクトの準備ができたら、プロジェクトの Program.cs ファイル内の既定のコードを次のサンプル コードに置き換えます。

using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;

namespace Console_Parse_JSON
{
    class Program
    {
        static void Main(string[] args)
        {
            var localDB = LoadRecords();
            string data = GetJsonData();

            User[] users = ReadToObject(data);

            UpdateRecords(localDB, users);

            for (int i = 0; i < users.Length; i++)
            {
                List<User> result = localDB.FindAll(delegate (User u) {
                    return u.lastname == users[i].lastname;
                    });
                foreach (var item in result)
                {
                    Console.WriteLine($"Matching Record, got name={item.firstname}, lastname={item.lastname}, age={item.totalpoints}");
                }
            }

            Console.ReadKey();
        }

        // Deserialize a JSON stream to a User object.
        public static User[] ReadToObject(string json)
        {
            User deserializedUser = new User();
            User[] users = { };
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json));
            DataContractJsonSerializer ser = new DataContractJsonSerializer(users.GetType());

            users = ser.ReadObject(ms) as User[];

            ms.Close();
            return users;
        }

        // Simulated operation that returns JSON data.
        public static string GetJsonData()
        {
            string str = "[{ \"points\":4o,\"firstname\":\"Fred\",\"lastname\":\"Smith\"},{\"lastName\":\"Jackson\"}]";
            return str;
        }

        public static List<User> LoadRecords()
        {
            var db = new List<User> { };
            User user1 = new User();
            user1.firstname = "Joe";
            user1.lastname = "Smith";
            user1.totalpoints = 41;

            db.Add(user1);

            User user2 = new User();
            user2.firstname = "Pete";
            user2.lastname = "Peterson";
            user2.totalpoints = 30;

            db.Add(user2);

            return db;
        }
        public static void UpdateRecords(List<User> db, User[] users)
        {
            bool existingUser = false;

            for (int i = 0; i < users.Length; i++)
            {
                foreach (var item in db)
                {
                    if (item.lastname == users[i].lastname && item.firstname == users[i].firstname)
                    {
                        existingUser = true;
                        item.totalpoints += users[i].points;

                    }
                }
                if (existingUser == false)
                {
                    User user = new User();
                    user.firstname = users[i].firstname;
                    user.lastname = users[i].lastname;
                    user.totalpoints = users[i].points;

                    db.Add(user);
                }
            }
        }
    }

    [DataContract]
    internal class User
    {
        [DataMember]
        internal string firstname;

        [DataMember]
        internal string lastname;

        [DataMember]
        // internal double points;
        internal string points;

        [DataMember]
        internal int totalpoints;
    }
}

赤と緑の波線を見つけてください!

サンプル アプリを起動してデバッガーを実行する前に、コード エディターのコードで赤と緑の波線を確認します。 これらは、IDE のコード アナライザーによって識別されるエラーと警告を表します。 赤い波線はコンパイル時エラーを意味し、コードを実行する前に修正する必要があります。 緑色の波線は警告です。 多くの場合、警告を修正せずにアプリを実行できますが、バグの原因になる可能性があり、調査することで時間と手間を省くことがよくあります。 これらの警告とエラーは、リスト ビューを使用する場合は、[ エラー一覧 ] ウィンドウにも表示されます。

サンプル アプリには、修正する必要がある複数の赤い波線と、調査する必要がある緑色の波線が表示されます。 最初のエラーを次に示します。

エラーは赤い波線として表示されます

このエラーを解決するには、電球アイコンで表される IDE の別の機能を確認します。

電球を確認してください。

最初の赤い波線は、コンパイル時エラーを表します。 その上にマウス ポインターを置くと、メッセージ The name `Encoding` does not exist in the current contextが表示されます。

このエラーの左下に電球アイコンが表示されていることに注意してください。 ドライバー アイコン ドライバー アイコンと共に、電球アイコン 電球アイコン は、コードをインラインで修正またはリファクタリングするのに役立つクイック アクションを表します。 電球は、修正 する必要 がある問題を表します。 スクリュードライバーは、あなたが修理または解決を選択できる可能性のある問題用です。 最初に提案された修正プログラムを使用して、左側の System.Text をクリックしてこのエラーを解決します。

電球を使用してコードを修正する

この項目を選択すると、using System.Text先頭に ステートメントが追加され、赤い波線が消えます。 (修正候補によって適用された変更が不明な場合は、修正プログラムを適用する前に右側の [変更のプレビュー ] リンクを選択してください)。

上記のエラーは、通常、コードに新しい using ステートメントを追加して修正する一般的なエラーです。 The type or namespace "Name" cannot be found.このようなエラーには、いくつかの一般的なエラーがあります。たとえば、このようなエラーは、アセンブリ参照が見つからないことを示している可能性があります (プロジェクトを右クリックし、[追加]、[追加>参照]、スペルミスの名前、または追加する必要があるライブラリがありません (C# の場合は、プロジェクトを右クリックし、[NuGet パッケージの管理] を選択します)。

残りのエラーと警告を修正する

このコードには、さらにいくつか注目すべき波線があります。 ここでは、一般的な型変換エラーが表示されます。 マウスを波線に合わせると、コードが文字列をintに変換しようとしているのがわかりますが、明示的な変換コードを追加しない限り、その変換はサポートされていません。

型変換エラー

コード アナライザーは意図を推測できないため、今回役立つ電球はありません。 このエラーを修正するには、コードの意図を知っている必要があります。 この例では、pointspointsを追加しようとしているため、totalpointsが数値 (整数) 値である必要があることを確認するのは難しくありません。

このエラーを修正するには、points クラスの User メンバーを次の内容から変更します。

[DataMember]
internal string points;

次のようにします。

[DataMember]
internal int points;

コードエディタには赤い下線が消えます。

次に、 points データ メンバーの宣言で緑色の波線にカーソルを合わせます。 コード アナライザーは、変数に値が割り当てられないことを示します。

割り当てられていない変数の警告メッセージ

通常、これは修正が必要な問題を表します。 ただし、サンプル アプリでは、実際には逆シリアル化プロセス中に points 変数にデータを格納し、その値を totalpoints データ メンバーに追加しています。 この例では、コードの意図を把握しており、警告を無視しても問題ありません。 ただし、警告を排除する場合は、次のコードを置き換えることができます。

item.totalpoints = users[i].points;

これと:

item.points = users[i].points;
item.totalpoints += users[i].points;

緑の波線が消え去ります。

例外を修正する

赤い波線をすべて修正し、緑色の波線をすべて解決するか、少なくとも調査を行ったら、デバッガーを起動してアプリを実行する準備が整います。

F5 キーを押すか、デバッグ ツールバーの [>] ボタンをクリックします (デバッグデバッグの開始)。

この時点で、サンプル アプリは SerializationException 例外 (ランタイム エラー) をスローします。 つまり、アプリはシリアル化しようとしているデータに詰まっています。 デバッグ モード (デバッガーがアタッチされている) でアプリを起動したため、デバッガーの例外ヘルパーによって、例外をスローしたコードが表示され、役に立つエラー メッセージが表示されます。

SerializationException が発生する

エラー メッセージは、 4o 値を整数として解析できないことを示します。 したがって、この例では、データが不適切であることがわかります。 4o40する必要があります。 ただし、実際のシナリオでデータを制御できない場合 (たとえば、Web サービスからデータを取得するとします)、それについて何をしますか? これをどのように修正しますか?

例外が発生した場合は、いくつかの質問をする (および回答する) 必要があります。

  • この例外は、修正できるバグにすぎませんか? または

  • あなたのユーザーがこの例外に遭遇する可能性はありますか?

前者の場合は、バグを修正します。 (サンプル アプリでは、不適切なデータを修正する必要があります)。後者の場合は、 try/catch ブロックを使用してコード内の例外を処理する必要がある場合があります (次のセクションで考えられるその他の戦略を確認します)。 サンプル アプリで、次のコードを置き換えます。

users = ser.ReadObject(ms) as User[];

次のコードを使用します。

try
{
    users = ser.ReadObject(ms) as User[];
}
catch (SerializationException)
{
    Console.WriteLine("Give user some info or instructions, if necessary");
    // Take appropriate action for your app
}

try/catch ブロックにはパフォーマンス コストがあるため、実際に必要な場合にのみ使用する必要があります。つまり、(a) アプリのリリース バージョンで発生する可能性があり、(b) メソッドのドキュメントは例外を確認する必要があることを示します (ドキュメントが完成したと仮定します)。 多くの場合、例外を適切に処理でき、ユーザーは例外について知る必要はありません。

例外処理に関する重要なヒントを次に示します。

  • エラーを公開または処理するための適切なアクションを実行しない、 catch (Exception) {}などの空の catch ブロックは使用しないでください。 空または内容のない catch ブロックを使用すると、例外が隠され、コードのデバッグがより困難になる可能性があります。

  • 例外をスローする特定の関数を囲む try/catch ブロックを使用します (サンプル アプリではReadObject)。 より大きなコードチャンクの周りでそれを使用すると、エラーの場所を隠す結果になります。 たとえば、次に示すように、親関数try/catchの呼び出しに関するReadToObject ブロックを使用しないでください。または、例外が発生した場所が正確にわかりません。

    // Don't do this
    try
    {
        User[] users = ReadToObject(data);
    }
    catch (SerializationException)
    {
    }
    
  • アプリに含める未知の関数、特に外部データを操作する関数 (Web 要求など) については、ドキュメントを調べて、関数がスローする可能性のある例外を確認してください。 これは、適切なエラー処理とアプリのデバッグに関する重要な情報です。

サンプル アプリの場合は、SerializationExceptionGetJsonData に変更して、4o メソッドの40を修正します。

ヒント

Copilotをお持ちの場合は、例外をデバッグする際に AI の支援を受けることができます。 [Copilot に質問する] ボタンの スクリーンショットを探してください。 ボタン。 詳細については、「Copilotを使用したデバッグ」を参照してください。

assert を使用してコードの意図を明確にする

デバッグ ツールバー内で [RestartRestart App] ボタンを選択し、Ctrl + Shift + F5 を押します。 これにより、少ない手順でアプリが再起動されます。 コンソール ウィンドウに次の出力が表示されます。

出力の Null 値

この出力では何か正しくない箇所が見受けられます。 3 番目のレコードの 名前 の値は空白です。

これは、関数で assert ステートメントを使用する、多くの場合、十分に活用されていない、役に立つコーディング方法について説明するのに適したタイミングです。 次のコードを追加することで、ランタイム チェックを含め、 firstnamelastnamenullされていないことを確認します。 UpdateRecords メソッドで次のコードを置き換えます。

if (existingUser == false)
{
    User user = new User();
    user.firstname = users[i].firstname;
    user.lastname = users[i].lastname;

これと:

// Also, add a using statement for System.Diagnostics at the start of the file.
Debug.Assert(users[i].firstname != null);
Debug.Assert(users[i].lastname != null);
if (existingUser == false)
{
    User user = new User();
    user.firstname = users[i].firstname;
    user.lastname = users[i].lastname;

開発プロセス中にこのような assert ステートメントを関数に追加することで、コードの意図を指定するのに役立ちます。 前の例では、次の項目を指定します。

  • 名の前に有効な文字列が必要です
  • 姓には有効な文字列が必要です

この方法で意図を指定することで、要件を適用します。 これは、開発中にバグを表示するために使用できる簡単で便利な方法です。 (assert ステートメントは、単体テストのメイン要素としても使用されます)。

デバッグ ツールバー内で [RestartRestart App] ボタンを選択し、Ctrl + Shift + F5 を押します。

assert コードは、デバッグ ビルドでのみアクティブです。

再起動すると、式assertusers[i].firstname != nullではなくfalseに評価されるため、デバッガーはtrueステートメントで一時停止します。

Assert が false に解決される

assert エラーは、調査する必要がある問題があることを示します。 assert は、必ずしも例外が表示されない多くのシナリオに対応できます。 この例では、ユーザーに例外は表示されません。レコードの一覧にnullとしてfirstname値が追加されます。 この状態により、後で問題が発生する可能性があり (コンソール出力に表示される場合など)、デバッグが困難になる可能性があります。

null値に対してメソッドを呼び出すシナリオでは、NullReferenceException結果が返されます。 通常は、一般的な例外 、つまり特定のライブラリ関数に関連付けられていない例外に対して、 try/catch ブロックを使用しないようにする必要があります。 任意のオブジェクトが NullReferenceExceptionをスローできます。 不明な場合は、ライブラリ関数のドキュメントを確認してください。

デバッグ プロセス中は、実際のコード修正プログラムに置き換える必要があることがわかっているまで、特定の assert ステートメントを保持することをお勧めします。 たとえば、アプリのリリース ビルドでユーザーが例外を検出する可能性があるとします。 その場合は、アプリが致命的な例外を発生させたり、他のエラーが発生したりしないようにコードをリファクタリングする必要があります。 そのため、このコードを修正するには、次のコードを置き換えます。

if (existingUser == false)
{
    User user = new User();

次のコードを使用します。

if (existingUser == false && users[i].firstname != null && users[i].lastname != null)
{
    User user = new User();

このコードを使用すると、コード要件を満たし、firstnamelastnameまたはnull値を持つレコードがデータに追加されないようにします。

この例では、ループ内に 2 つの assert ステートメントを追加しました。 通常、 assertを使用する場合は、関数またはメソッドのエントリ ポイント (先頭) に assert ステートメントを追加することをお勧めします。 現在、サンプル アプリの UpdateRecords メソッドを確認しています。 このメソッドでは、いずれかのメソッド引数が nullされている場合に問題が発生していることがわかっているので、関数のエントリ ポイントで assert ステートメントで両方を確認します。

public static void UpdateRecords(List<User> db, User[] users)
{
    Debug.Assert(db != null);
    Debug.Assert(users != null);

上記のステートメントでは、既存のデータ (db) を読み込み、何かを更新する前に新しいデータ (users) を取得することが目的です。

assertは、trueまたはfalseに解決される任意の種類の式で使用できます。 そのため、たとえば、次のような assert ステートメントを追加できます。

Debug.Assert(users[0].points > 0);

上記のコードは、次の意図を指定する場合に便利です。ユーザーのレコードを更新するには、0 より大きい新しいポイント値が必要です。

デバッガーでコードを検査する

これで、サンプル アプリで間違っている重要なものをすべて修正したので、他の重要な要素に移動できます。

デバッガーの例外ヘルパーについて説明しましたが、デバッガーははるかに強力なツールであり、コードのステップ実行や変数の検査などの他の操作も可能です。 これらのより強力な機能は、多くのシナリオ、特に次のシナリオで役立ちます。

  • コード内のランタイム バグを分離しようとしていますが、前述のメソッドとツールを使用して実行することはできません。

  • コードを検証する必要があります。つまり、コードが実行されている間に見て、期待した動作と目的に合った動作をしていることを確認します。

    実行中にコードを見ることは有益です。 この方法でコードの詳細を学習し、明らかな症状が現れる前にバグを特定することがよくあります。

デバッガーの基本的な機能を使用する方法については、「 初心者向けのデバッグ」を参照してください。

パフォーマンスの問題を修正する

別の種類のバグには、アプリの実行速度が遅くなったり、メモリが多すぎたりする非効率的なコードが含まれます。 一般に、パフォーマンスの最適化は、後でアプリ開発で行います。 ただし、パフォーマンスの問題が早期に発生する可能性があります (たとえば、アプリの一部の実行速度が低下していることがわかります)。また、早い段階でプロファイリング ツールを使用してアプリをテストする必要がある場合があります。 CPU 使用率ツールやメモリ アナライザーなどのプロファイリング ツールの詳細については、「プロファイリング ツールを最初に確認する」を参照してください。

この記事では、コード内の多くの一般的なバグを回避して修正する方法と、デバッガーを使用するタイミングについて説明しました。 次に、Visual Studio デバッガーを使用してバグを修正する方法について説明します。