Teknik dan alat penelusuran kesalahan untuk membantu Anda menulis kode yang lebih baik

Memperbaiki bug dan kesalahan dalam kode Anda dapat menjadi tugas yang memakan waktu dan terkadang membuat frustrasi. Dibutuhkan waktu untuk mempelajari cara men-debug secara efektif. IDE yang kuat seperti Visual Studio dapat membuat pekerjaan Anda jauh lebih mudah. IDE dapat membantu Anda memperbaiki kesalahan dan men-debug kode Anda dengan lebih cepat, dan membantu Anda menulis kode yang lebih baik dengan lebih sedikit bug. Artikel ini menyediakan tampilan holistik dari proses "perbaikan bug", sehingga Anda dapat mengetahui kapan harus menggunakan penganalisis kode, kapan menggunakan debugger, cara memperbaiki pengecualian, dan cara membuat kode untuk niat. Jika Anda sudah tahu bahwa Anda perlu menggunakan debugger, lihat Pertama-tama lihat debugger.

Dalam artikel ini, Anda mempelajari cara bekerja dengan IDE untuk membuat sesi pengkodian Anda lebih produktif. Kami membahas beberapa tugas, seperti:

  • Siapkan kode Anda untuk penelusuran kesalahan dengan menggunakan penganalisis kode IDE

  • Cara memperbaiki pengecualian (kesalahan run-time)

  • Cara meminimalkan bug dengan mengodekan niat (menggunakan penegasan)

  • Waktu menggunakan debugger

Untuk menunjukkan tugas-tugas ini, kami menunjukkan beberapa jenis kesalahan dan bug paling umum yang mungkin Anda temui saat mencoba men-debug aplikasi Anda. Meskipun kode sampel adalah C#, informasi konseptual biasanya berlaku untuk C++, Visual Basic, JavaScript, dan bahasa lain yang didukung oleh Visual Studio (kecuali jika disebutkan). Cuplikan layar berada di C#.

Membuat aplikasi sampel dengan beberapa bug dan kesalahan di dalamnya

Kode berikut memiliki beberapa bug yang dapat Anda perbaiki menggunakan IDE Visual Studio. Aplikasi ini adalah aplikasi sederhana yang mensimulasikan mendapatkan data JSON dari beberapa operasi, mendeserialisasi data ke objek, dan memperbarui daftar sederhana dengan data baru.

Untuk membuat aplikasi, Anda harus menginstal Visual Studio dan beban kerja pengembangan desktop .NET terinstal.

  • Jika Anda belum menginstal Visual Studio, buka halaman pengunduhan Visual Studio untuk menginstalnya secara gratis.

  • Jika Anda perlu menginstal beban kerja tetapi sudah memiliki Visual Studio, pilih Alat>Dapatkan Alat dan Fitur. Alat Penginstal Visual Studio diluncurkan. Pilih beban kerja Pengembangan desktop .NET, lalu pilih Ubah.

Ikuti langkah-langkah berikut untuk membuat aplikasi:

  1. Buka Visual Studio. Di jendela mulai, pilih Buat proyek baru.

  2. Di kotak pencarian, masukkan konsol lalu salah satu opsi Aplikasi Konsol untuk .NET.

  3. Pilih Selanjutnya.

  4. Masukkan nama proyek seperti Console_Parse_JSON, lalu pilih Berikutnya atau Buat, sebagaimana berlaku.

    Pilih kerangka kerja target yang direkomendasikan atau .NET 8, lalu pilih Buat.

    Jika Anda tidak melihat templat proyek Aplikasi Konsol untuk .NET, buka Alat>Dapatkan Alat dan Fitur, yang membuka Alat Penginstal Visual Studio. Pilih beban kerja Pengembangan desktop .NET, lalu pilih Ubah.

    Visual Studio membuat proyek konsol, yang muncul di Penjelajah Solusi di panel kanan.

Saat proyek siap, ganti kode default dalam file Program.cs proyek dengan kode sampel berikut:

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

Menemukan garis berlekuk merah dan hijau!

Sebelum Anda mencoba memulai aplikasi sampel dan menjalankan debugger, periksa kode di editor kode untuk garis berlekuk merah dan hijau. Ini mewakili kesalahan dan peringatan yang diidentifikasi oleh penganalisis kode IDE. Garis berlekuk merah adalah kesalahan waktu kompilasi, yang harus Anda perbaiki sebelum Anda dapat menjalankan kode. Garis berlekuk hijau adalah peringatan. Meskipun Anda sering dapat menjalankan aplikasi tanpa memperbaiki peringatan, aplikasi tersebut dapat menjadi sumber bug dan Anda sering menghemat waktu dan masalah dengan menyelidikinya. Peringatan dan kesalahan ini juga muncul di jendela Daftar Kesalahan, jika Anda lebih suka tampilan daftar.

Di aplikasi sampel, Anda akan melihat beberapa squiggle merah yang perlu Anda perbaiki, dan yang hijau yang perlu Anda selidiki. Berikut adalah kesalahan pertama.

Kesalahan yang ditampilkan sebagai berlekuk merah

Untuk memperbaiki kesalahan ini, Anda dapat melihat fitur lain dari IDE, yang diwakili oleh ikon bola lampu.

Periksa bola lampu!

Garis berlekuk merah pertama mewakili kesalahan waktu kompilasi. Arahkan kursor ke atasnya dan Anda melihat pesan The name `Encoding` does not exist in the current context.

Perhatikan bahwa kesalahan ini menunjukkan ikon bola lampu di kiri bawah. Bersama dengan ikon ikon obengobeng , ikon ikon bola lampu bola lampu mewakili Tindakan Cepat yang dapat membantu Anda memperbaiki atau merefaktor kode sebaris. Bola lampu mewakili masalah yang harus Anda perbaiki. Obeng adalah untuk masalah yang mungkin Anda pilih untuk diperbaiki. Gunakan perbaikan yang disarankan pertama untuk mengatasi kesalahan ini dengan mengeklik menggunakan System.Text di sebelah kiri.

Gunakan bola lampu untuk memperbaiki kode

Saat Anda memilih item ini, Visual Studio menambahkan using System.Text pernyataan di bagian atas file Program.cs , dan squiggle merah menghilang. (Saat Anda tidak yakin tentang perubahan yang diterapkan oleh perbaikan yang disarankan, pilih Pratinjau tautan perubahan di sebelah kanan sebelum menerapkan perbaikan.)

Kesalahan sebelumnya adalah kesalahan umum yang biasanya Anda perbaiki dengan menambahkan pernyataan using baru ke kode Anda. Ada beberapa kesalahan umum dan serupa dengan yang satu ini seperti The type or namespace "Name" cannot be found. Jenis kesalahan ini mungkin menunjukkan referensi rakitan yang hilang (klik kanan proyek, pilih Tambahkan>Referensi), nama yang salah eja, atau pustaka yang hilang yang perlu Anda tambahkan (untuk C#, klik kanan proyek dan pilih Kelola Paket NuGet).

Memperbaiki kesalahan dan peringatan yang tersisa

Ada beberapa garis berlekuk lagi untuk dilihat dalam kode ini. Di sini, Anda akan melihat kesalahan konversi jenis umum. Saat mengarahkan kuarter ke atas squiggle, Anda melihat bahwa kode mencoba mengonversi string ke int, yang tidak didukung kecuali Anda menambahkan kode eksplisit untuk melakukan konversi.

Kesalahan konversi jenis

Karena penganalisis kode tidak dapat menebak niat Anda, tidak ada bola lampu untuk membantu Anda saat ini. Untuk memperbaiki kesalahan ini, Anda perlu mengetahui niat kode. Dalam contoh ini, tidak terlalu sulit untuk melihat bahwa points harus menjadi nilai numerik (bilangan bulat), karena Anda mencoba menambahkan points ke totalpoints.

Untuk memperbaiki kesalahan ini, ubah anggota points dari kelas User dari ini:

[DataMember]
internal string points;

ke berikut ini:

[DataMember]
internal int points;

Garis berlekuk merah di editor kode hilang.

Selanjutnya, arahkan kursor ke atas garis berlekuk hijau dalam deklarasi anggota data points. Penganalisis kode memberi tahu Anda bahwa variabel tidak pernah diberi nilai.

Pesan peringatan untuk variabel yang tidak ditetapkan

Biasanya, ini mewakili masalah yang perlu diperbaiki. Namun, di aplikasi sampel, Anda sebenarnya menyimpan data dalam variabel points selama proses deserialisasi, lalu menambahkan nilai tersebut ke anggota data totalpoints. Dalam contoh ini, Anda mengetahui niat kode dan dapat mengabaikan peringatan dengan aman. Namun, jika Anda ingin menghilangkan peringatan tersebut, Anda dapat mengganti kode berikut:

item.totalpoints = users[i].points;

dengan ini:

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

Garis berlekuk hijau hilang.

Memperbaiki pengecualian

Ketika Anda telah memperbaiki semua berlekuk merah dan diselesaikan--atau setidaknya diselidiki--semua berlekuk hijau, Anda siap untuk memulai debugger dan menjalankan aplikasi.

Tekan F5 (Debug > Mulai Debugging) atau tombol Mulai Penelusuran Kesalahan. Mulai Penelusuran Kesalahan di toolbar Debug.

Pada titik ini, aplikasi sampel menampilkan pengecualian SerializationException (kesalahan runtime). Artinya, aplikasi tersedak pada data yang coba diserialisasikan. Karena Anda memulai aplikasi dalam mode debug (debugger terlampir), Pembantu Pengecualian debugger membawa Anda langsung ke kode yang menampilkan pengecualian dan memberi Anda pesan kesalahan yang bermanfaat.

SerializationException terjadi

Pesan kesalahan menginstruksikan Anda bahwa nilai 4o tidak dapat diurai sebagai bilangan bulat. Jadi, dalam contoh ini, Anda tahu datanya buruk: 4o harus 40. Namun, jika Anda tidak mengontrol data dalam skenario nyata (katakanlah Anda mendapatkannya dari layanan web), apa yang Anda lakukan tentang hal itu? Bagaimana Anda memperbaikinya?

Ketika Anda mencapai pengecualian, Anda perlu mengajukan (dan menjawab) beberapa pertanyaan:

  • Apakah pengecualian ini hanya bug yang dapat Anda perbaiki? Atau,

  • Apakah pengecualian ini sesuatu yang mungkin ditemui pengguna Anda?

Jika itu pertanyaan yang pertama, perbaiki bug. (Dalam aplikasi sampel, maka Anda perlu memperbaiki data yang buruk.) Jika yang terakhir, Anda mungkin perlu menangani pengecualian dalam kode Anda menggunakan try/catch blok (kita melihat kemungkinan strategi lain di bagian berikutnya). Di aplikasi sampel, ganti kode berikut:

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

dengan kode ini:

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
}

Blok try/catch memiliki beberapa biaya performa, jadi Anda hanya ingin menggunakannya ketika Anda benar-benar membutuhkannya, yaitu, di mana (a) mereka mungkin terjadi dalam versi rilis aplikasi, dan di mana (b) dokumentasi untuk metode menunjukkan bahwa Anda harus memeriksa pengecualian (dengan asumsi dokumentasinya selesai!). Dalam banyak kasus, Anda dapat menangani pengecualian dengan tepat dan pengguna tidak perlu mengetahuinya.

Berikut adalah beberapa tips penting untuk penanganan pengecualian:

  • Hindari menggunakan blok tangkapan kosong, seperti catch (Exception) {}, yang tidak mengambil tindakan yang sesuai untuk mengekspos atau menangani kesalahan. Blok tangkapan kosong atau noninformatif dapat menyembunyikan pengecualian dan dapat membuat kode Anda lebih sulit untuk di-debug alih-alih lebih mudah.

  • Gunakan blok try/catch di sekitar fungsi tertentu yang menampilkan pengecualian (ReadObject, di aplikasi sampel). Jika Anda menggunakannya di sekitar potongan kode yang lebih besar, Anda akhirnya menyembunyikan lokasi kesalahan. Misalnya, jangan gunakan blok try/catch di sekitar panggilan ke fungsi induk ReadToObject, yang ditunjukkan di sini, atau Anda tidak akan tahu persis di mana pengecualian terjadi.

    // Don't do this
    try
    {
        User[] users = ReadToObject(data);
    }
    catch (SerializationException)
    {
    }
    
  • Untuk fungsi yang tidak dikenal yang Anda sertakan dalam aplikasi, terutama fungsi yang berinteraksi dengan data eksternal (seperti permintaan web), periksa dokumentasi untuk melihat pengecualian apa yang mungkin dilemparkan fungsi. Ini bisa menjadi informasi penting untuk penanganan kesalahan yang tepat dan untuk penelusuran kesalahan aplikasi Anda.

Untuk aplikasi sampel, perbaiki SerializationException dalam metode GetJsonData dengan mengubah 4o ke 40.

Tip

Jika Anda memiliki Copilot, Anda bisa mendapatkan bantuan AI saat menelusuri kesalahan pengecualian. Cukup cari tombol TanyaKan SalinanCuplikan layar tombol Minta Salinan.. Untuk informasi selengkapnya, lihat Debug dengan Copilot.

Mengklarifikasi niat kode Anda menggunakan penegasan

Pilih tombol Mulai UlangMulai ulang Aplikasi di Toolbar Debug (Ctrl + Shift + F5). Ini menghidupkan ulang aplikasi dalam langkah yang lebih sedikit. Anda melihat output berikut di jendela konsol.

Nilai null dalam output

Anda dapat melihat sesuatu dalam output ini tidak benar. Nama dan nilai nama belakang untuk rekaman ketiga kosong!

Ini adalah saat yang tepat untuk berbicara tentang praktik pengodean yang bermanfaat, sering kali kurang dimanfaatkan, yaitu menggunakan pernyataan assert dalam fungsi Anda. Dengan menambahkan kode berikut, Anda menyertakan pemeriksaan runtime untuk memastikan bahwa firstname dan lastname bukan null. Ganti metode UpdateRecords dengan kode berikut:

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

dengan ini:

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

Dengan menambahkan pernyataan assert seperti ini ke fungsi Anda selama proses pengembangan, Anda dapat membantu menentukan niat kode Anda. Dalam contoh sebelumnya, kami menentukan item berikut:

  • String yang valid diperlukan untuk nama depan
  • String yang valid diperlukan untuk nama belakang

Dengan menentukan niat dengan cara ini, Anda menerapkan persyaratan Anda. Ini adalah metode sederhana dan berguna yang dapat Anda gunakan untuk memunculkan bug selama pengembangan. (Pernyataan assert juga digunakan sebagai elemen utama dalam pengujian unit.)

Pilih tombol Mulai UlangMulai ulang Aplikasi di Toolbar Debug (Ctrl + Shift + F5).

Catatan

Kode assert hanya aktif dalam build Debug.

Saat Anda menghidupkan ulang, debugger berhenti sementara pada pernyataan assert, karena ekspresi users[i].firstname != null mengevaluasi ke false alih-alih true.

Pernyataan diselesaikan ke false

Kesalahan assert ini memberi tahu Anda bahwa ada masalah yang perlu Anda selidiki. assert dapat mencakup banyak skenario di mana Anda tidak selalu melihat pengecualian. Dalam contoh ini, pengguna tidak melihat pengecualian, dan null nilai ditambahkan seperti firstname dalam daftar catatan Anda. Kondisi ini dapat menyebabkan masalah di kemudian hari (seperti yang Anda lihat di output konsol) dan mungkin lebih sulit untuk di-debug.

Catatan

Dalam skenario di mana Anda memanggil metode pada nilai null, menghasilkan NullReferenceException. Anda biasanya ingin menghindari penggunaan blok try/catch untuk pengecualian umum, yaitu pengecualian yang tidak terkait dengan fungsi pustaka tertentu. Objek apa pun dapat menampilkan NullReferenceException. Periksa dokumentasi untuk fungsi pustaka jika Anda tidak yakin.

Selama proses penelusuran kesalahan, ada baiknya untuk menyimpan pernyataan assert tertentu sampai Anda tahu bahwa Anda perlu menggantinya dengan perbaikan kode aktual. Katakanlah Anda memutuskan bahwa pengguna mungkin mengalami pengecualian dalam build rilis aplikasi. Dalam hal ini, Anda harus memfaktorkan ulang kode untuk memastikan bahwa aplikasi Anda tidak menampilkan pengecualian fatal atau mengakibatkan beberapa kesalahan lainnya. Jadi, untuk memperbaiki kode ini, ganti kode berikut:

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

dengan kode ini:

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

Dengan menggunakan kode ini, Anda memenuhi persyaratan kode dan memastikan bahwa rekaman dengan firstname nilai atau lastnamenull tidak ditambahkan ke data.

Dalam contoh ini, kami menambahkan dua pernyataan assert di dalam perulangan. Biasanya, saat menggunakan assert, yang terbaik adalah menambahkan pernyataan assert di titik masuk (awal) fungsi atau metode. Saat ini Anda sedang melihat UpdateRecords metode di aplikasi sampel. Dalam metode ini, Anda tahu bahwa Anda berada dalam masalah jika salah satu argumen metode adalah null, jadi periksa keduanya dengan pernyataan assert di titik masuk fungsi.

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

Untuk pernyataan sebelumnya, niat Anda adalah memuat data yang ada (db) dan mengambil data baru (users) sebelum memperbarui apa pun.

Anda dapat menggunakan assert dengan segala jenis ekspresi yang memutuskan ke true atau false. Jadi, misalnya, Anda dapat menambahkan pernyataan assert seperti ini.

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

Kode sebelumnya berguna jika Anda ingin menentukan niat berikut: nilai titik baru yang lebih besar dari nol (0) diperlukan untuk memperbarui catatan pengguna.

Memeriksa kode Anda di debugger

Oke, sekarang setelah Anda memperbaiki segala sesuatu yang penting yang salah dengan aplikasi sampel, Anda dapat beralih ke hal-hal penting lainnya!

Kami menunjukkan Pembantu Pengecualian debugger kepada Anda, tetapi debugger adalah alat yang jauh lebih kuat yang juga memungkinkan Anda melakukan hal-hal lain seperti menelusuri kode Anda dan memeriksa variabelnya. Kemampuan yang lebih kuat ini berguna dalam banyak skenario, terutama skenario berikut:

  • Anda mencoba mengisolasi bug runtime dalam kode Anda, tetapi tidak dapat melakukannya menggunakan metode dan alat yang sebelumnya dibahas.

  • Anda ingin memvalidasi kode Anda, yaitu, menontonnya saat berjalan untuk memastikannya berperilaku dengan cara yang Anda harapkan dan melakukan apa yang Anda inginkan.

    Sangat instruktif untuk menonton kode Anda saat berjalan. Anda dapat mempelajari selengkapnya tentang kode Anda dengan cara ini dan sering kali dapat mengidentifikasi bug sebelum mereka menunjukkan gejala yang jelas.

Untuk mempelajari cara menggunakan fitur penting debugger, lihat Penelusuran kesalahan untuk pemula absolut.

Memperbaiki masalah performa

Jenis bug lainnya termasuk kode yang tidak efisien yang menyebabkan aplikasi Anda berjalan lambat atau menggunakan terlalu banyak memori. Umumnya, mengoptimalkan performa adalah sesuatu yang Anda lakukan nanti dalam pengembangan aplikasi Anda. Namun, Anda dapat mengalami masalah performa lebih awal (misalnya, Anda melihat bahwa beberapa bagian aplikasi Berjalan lambat), dan Anda mungkin perlu menguji aplikasi dengan alat pembuatan profil sejak dini. Untuk informasi selengkapnya tentang alat pembuatan profil seperti alat Penggunaan CPU dan Penganalisis Memori, lihat Pertama-tama lihat alat pembuatan profil.

Dalam artikel ini, Anda telah mempelajari cara menghindari dan memperbaiki banyak bug umum dalam kode Anda dan kapan harus menggunakan debugger. Selanjutnya, pelajari selengkapnya tentang menggunakan debugger Visual Studio untuk memperbaiki bug.