Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Penanganan pengecualian yang tepat sangat penting untuk keandalan aplikasi. Anda dapat dengan sengaja menangani pengecualian yang diharapkan untuk mencegah aplikasi mengalami kegagalan. Namun, aplikasi yang mengalami crash lebih andal dan dapat didiagnosis daripada aplikasi dengan perilaku yang tidak terdefinisi.
Artikel ini menjelaskan praktik terbaik untuk menangani dan membuat pengecualian.
Menangani pengecualian
Praktik terbaik berikut ini menyangkut cara Anda menangani pengecualian:
- Gunakan blok coba/tangkap/akhirnya untuk memulihkan dari kesalahan atau melepaskan sumber daya
- Menangani kondisi umum untuk menghindari pengecualian
- Menangkap pembatalan dan pengecualian asinkron
- Kelas desain sehingga pengecualian dapat dihindari
- Memulihkan status ketika metode tidak selesai karena pengecualian
- Menangkap dan melempar kembali pengecualian dengan benar
Gunakan blok try/catch/finally untuk memulihkan dari error atau melepaskan sumber daya
Untuk kode yang berpotensi menghasilkan pengecualian, dan kapan aplikasi Anda dapat pulih dari pengecualian tersebut, gunakan blok try/catch di sekitar kode. Dalam blok catch, selalu atur pengecualian dari yang paling khusus ke yang paling umum. (Semua pengecualian berasal dari kelas Exception. Pengecualian yang lebih turunan tidak ditangani oleh klausul catch yang didahului oleh klausa catch untuk kelas pengecualian dasar.) Saat kode Anda tidak dapat pulih dari pengecualian, jangan menangkap pengecualian tersebut. Aktifkan metode lebih jauh ke tumpukan panggilan untuk memulihkan jika memungkinkan.
Bersihkan sumber daya yang dialokasikan melalui pernyataan using atau blok finally. Lebih suka pernyataan using untuk membersihkan sumber daya secara otomatis saat pengecualian dilemparkan. Gunakan blok finally untuk membersihkan sumber daya yang tidak menerapkan IDisposable. Kode dalam klausa finally hampir selalu dijalankan bahkan ketika pengecualian dilemparkan.
Menangani kondisi umum untuk menghindari pengecualian
Untuk kondisi yang kemungkinan akan terjadi tetapi mungkin memicu pengecualian, pertimbangkan untuk menanganinya dengan cara yang menghindari pengecualian. Misalnya, jika Anda mencoba menutup koneksi yang sudah ditutup, Anda mendapatkan InvalidOperationException. Anda dapat menghindarinya dengan menggunakan pernyataan if untuk memeriksa status koneksi sebelum mencoba menutupnya.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Jika Anda tidak memeriksa status koneksi sebelum menutupnya, Anda dapat menangkap pengecualian InvalidOperationException.
try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}
Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try
Pendekatan untuk memilih tergantung pada seberapa sering Anda mengharapkan peristiwa terjadi.
Gunakan penanganan pengecualian jika peristiwa tidak sering terjadi, yaitu, jika peristiwa benar-benar luar biasa dan menunjukkan kesalahan, seperti akhir file yang tidak terduga. Saat Anda menggunakan penanganan pengecualian, lebih sedikit kode yang dijalankan dalam kondisi normal.
Periksa kondisi kesalahan dalam kode jika peristiwa terjadi secara rutin dan dapat dianggap sebagai bagian dari eksekusi normal. Saat Anda memeriksa kondisi kesalahan umum, lebih sedikit kode yang dijalankan karena Anda menghindari pengecualian.
Nota
Pemeriksaan di muka menghilangkan pengecualian sebagian besar waktu. Namun, mungkin ada kondisi balapan di mana kondisi yang dijaga berubah antara pemeriksaan dan operasi, dan dalam hal ini, Anda masih dapat dikenakan pengecualian.
Panggil metode Try* agar terhindar dari pengecualian
Jika beban performa pengecualian terlalu tinggi, beberapa metode pustaka .NET menyediakan bentuk alternatif untuk penanganan kesalahan. Misalnya, Int32.Parse melempar OverflowException jika nilai yang akan diurai terlalu besar untuk diwakili oleh Int32. Namun, Int32.TryParse tidak melemparkan pengecualian ini. Sebaliknya, ia mengembalikan Boolean dan memiliki parameter out yang berisi bilangan bulat valid yang diurai setelah berhasil.
Dictionary<TKey,TValue>.TryGetValue memiliki perilaku serupa untuk mencoba mendapatkan nilai dari kamus.
Menangkap pembatalan dan pengecualian asinkron
Lebih baik menangkap OperationCanceledException daripada TaskCanceledException, yang berasal dari OperationCanceledException, ketika Anda memanggil metode asinkron. Banyak metode asinkron melempar pengecualian OperationCanceledException jika pembatalan diminta. Pengecualian ini memungkinkan eksekusi dihentikan secara efisien dan tumpukan panggilan diurai setelah permintaan pembatalan terdeteksi.
Metode asinkron menyimpan pengecualian yang dilemparkan selama eksekusi dalam tugas yang mereka kembalikan. Jika pengecualian disimpan ke dalam tugas yang dikembalikan, pengecualian tersebut akan dilemparkan saat tugas ditunggu. Pengecualian penggunaan, seperti ArgumentException, masih dilemparkan secara sinkron. Untuk informasi selengkapnya, lihat pengecualian asinkron .
Rancang kelas sedemikian rupa sehingga pengecualian dapat dihindari
Kelas dapat menyediakan metode atau properti yang memungkinkan Anda menghindari panggilan yang akan memicu pengecualian. Misalnya, kelas FileStream menyediakan metode yang membantu menentukan apakah akhir file telah tercapai. Anda dapat memanggil metode ini untuk menghindari pengecualian yang dilemparkan jika Anda membaca melewati akhir file. Contoh berikut menunjukkan cara membaca ke akhir file tanpa memicu pengecualian:
class FileRead
{
public static void ReadAll(FileStream fileToRead)
{
ArgumentNullException.ThrowIfNull(fileToRead);
int b;
// Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin);
// Read each byte to the end of the file.
for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If
Dim b As Integer
' Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin)
' Read each byte to the end of the file.
For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class
Cara lain untuk menghindari pengecualian adalah mengembalikan null (atau default) untuk kasus kesalahan yang paling umum alih-alih melemparkan pengecualian. Kasus kesalahan umum dapat dianggap sebagai aliran kontrol normal. Dengan mengembalikan null (atau default) dalam kasus ini, Anda meminimalkan dampak performa ke aplikasi.
Untuk jenis nilai, pertimbangkan apakah akan menggunakan Nullable<T> atau default sebagai indikator kesalahan untuk aplikasi Anda. Dengan menggunakan Nullable<Guid>, default menjadi null alih-alih Guid.Empty. Terkadang, menambahkan Nullable<T> dapat membuatnya lebih jelas ketika nilai ada atau tidak ada. Di lain waktu, menambahkan Nullable<T> dapat membuat kasus tambahan untuk memeriksa yang tidak diperlukan dan hanya berfungsi untuk membuat sumber kesalahan potensial.
Memulihkan status ketika metode tidak selesai karena pengecualian
Penelepon harus dapat mengasumsikan bahwa tidak ada efek samping ketika pengecualian terjadi dari sebuah metode. Misalnya, jika Anda memiliki kode yang mentransfer uang dengan menarik dari satu rekening dan menyetorkan di rekening lain, dan terjadi pengecualian saat melakukan penyetoran, Anda tidak ingin penarikan tetap terjadi.
public void TransferFunds(Account from, Account to, decimal amount)
{
from.Withdrawal(amount);
// If the deposit fails, the withdrawal shouldn't remain in effect.
to.Deposit(amount);
}
Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
from.Withdrawal(amount)
' If the deposit fails, the withdrawal shouldn't remain in effect.
[to].Deposit(amount)
End Sub
Metode sebelumnya tidak secara langsung melemparkan pengecualian apa pun. Namun, Anda harus menulis metode sehingga penarikan dibalik jika operasi setoran gagal.
Salah satu cara untuk menangani situasi ini adalah dengan menangkap pengecualian yang dilemparkan oleh transaksi deposit dan membatalkan penarikan.
private static void TransferFunds(Account from, Account to, decimal amount)
{
string withdrawalTrxID = from.Withdrawal(amount);
try
{
to.Deposit(amount);
}
catch
{
from.RollbackTransaction(withdrawalTrxID);
throw;
}
}
Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
Dim withdrawalTrxID As String = from.Withdrawal(amount)
Try
[to].Deposit(amount)
Catch
from.RollbackTransaction(withdrawalTrxID)
Throw
End Try
End Sub
Contoh ini menggambarkan penggunaan throw untuk melempar ulang pengecualian asli, sehingga memudahkan pemanggil untuk memahami penyebab sebenarnya masalah tanpa harus memeriksa properti InnerException. Alternatifnya adalah melemparkan pengecualian baru dan menyertakan pengecualian asli sebagai pengecualian dalam.
catch (Exception ex)
{
from.RollbackTransaction(withdrawalTrxID);
throw new TransferFundsException("Withdrawal failed.", innerException: ex)
{
From = from,
To = to,
Amount = amount
};
}
Catch ex As Exception
from.RollbackTransaction(withdrawalTrxID)
Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
{
.From = from,
.[To] = [to],
.Amount = amount
}
End Try
Menangkap dan melempar ulang pengecualian dengan benar
Setelah pengecualian dilemparkan, bagian dari informasi yang dikandungnya adalah jejak tumpukan. Pelacakan tumpukan adalah daftar hierarki panggilan metode yang dimulai dengan metode yang melempar pengecualian dan berakhir dengan metode yang menangkap pengecualian. Jika Anda melempar ulang pengecualian dengan menentukan pengecualian dalam pernyataan kode throw, misalnya throw e, pelacakan tumpukan dimulai ulang pada metode saat ini dan daftar panggilan metode antara metode asli yang melemparkan pengecualian dan metode saat ini hilang. Untuk menyimpan informasi pelacakan tumpukan asli dengan pengecualian, ada dua opsi yang tergantung di mana Anda melempar ulang pengecualian dari:
- Jika Anda melempar ulang pengecualian dari dalam handler (blok
catch) yang menangkap instans pengecualian, gunakan pernyataanthrowtanpa menyebutkan pengecualian. Aturan analisis kode CA2200 membantu Anda menemukan tempat dalam kode di mana Anda mungkin secara tidak sengaja kehilangan informasi pelacakan tumpukan. - Jika Anda melempar ulang pengecualian dari suatu tempat selain handler (blok
catch), gunakan ExceptionDispatchInfo.Capture(Exception) untuk menangkap pengecualian pada handler dan ExceptionDispatchInfo.Throw() saat Anda ingin melemparnya kembali. Anda dapat menggunakan properti ExceptionDispatchInfo.SourceException untuk memeriksa pengecualian yang ditangkap.
Contoh berikut menunjukkan bagaimana kelas ExceptionDispatchInfo dapat digunakan, dan seperti apa outputnya.
ExceptionDispatchInfo? edi = null;
try
{
var txt = File.ReadAllText(@"C:\temp\file.txt");
}
catch (FileNotFoundException e)
{
edi = ExceptionDispatchInfo.Capture(e);
}
// ...
Console.WriteLine("I was here.");
if (edi is not null)
edi.Throw();
Jika file dalam kode contoh tidak ada, output berikut diproduksi:
I was here.
Unhandled exception. System.IO.FileNotFoundException: Could not find file 'C:\temp\file.txt'.
File name: 'C:\temp\file.txt'
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
at System.IO.File.ReadAllText(String path, Encoding encoding)
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 12
--- End of stack trace from previous location ---
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 24
Memunculkan pengecualian
Praktik terbaik berikut menyangkut bagaimana Anda melemparkan pengecualian:
- Gunakan jenis pengecualian yang telah ditentukan sebelumnya
- Gunakan metode penyusun pengecualian
- Menyertakan pesan string yang dilokalkan
- Gunakan tata bahasa yang tepat
- Tempatkan pernyataan 'throw' dengan baik
- Jangan ajukan pengecualian dalam klausul akhirnya
- Jangan ajukan pengecualian dari tempat tak terduga
- Melempar pengecualian validasi argumen secara sinkron
Gunakan jenis pengecualian yang telah ditentukan sebelumnya
Perkenalkan kelas pengecualian baru hanya ketika kelas yang telah ditentukan sebelumnya tidak berlaku. Misalnya:
- Jika kumpulan properti atau panggilan metode tidak sesuai mengingat status objek saat ini, berikan pengecualian InvalidOperationException.
- Jika parameter yang tidak valid diteruskan, berikan pengecualian ArgumentException atau salah satu kelas yang telah ditentukan sebelumnya yang berasal dari ArgumentException.
Nota
Meskipun yang terbaik adalah menggunakan jenis pengecualian yang telah ditentukan sebelumnya jika memungkinkan, Anda tidak boleh menaikkan beberapa jenis pengecualian yang dicadangkan, seperti AccessViolationException, IndexOutOfRangeException, NullReferenceException dan StackOverflowException. Untuk informasi lebih lanjut, lihat CA2201: Jangan memunculkan jenis pengecualian yang sudah ditentukan.
Menggunakan metode penyusun pengecualian
Adalah umum bagi kelas untuk melemparkan pengecualian yang sama dari berbagai tempat dalam implementasinya. Untuk menghindari kode yang berlebihan, buat metode pembantu yang membuat pengecualian dan mengembalikannya. Misalnya:
class FileReader
{
private readonly string _fileName;
public FileReader(string path)
{
_fileName = path;
}
public byte[] Read(int bytes)
{
byte[] results = FileUtils.ReadFromFile(_fileName, bytes) ?? throw NewFileIOException();
return results;
}
static FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";
return new FileReaderException(description);
}
}
Class FileReader
Private fileName As String
Public Sub New(path As String)
fileName = path
End Sub
Public Function Read(bytes As Integer) As Byte()
Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function
Function NewFileIOException() As FileReaderException
Dim description As String = "My NewFileIOException Description"
Return New FileReaderException(description)
End Function
End Class
Beberapa jenis pengecualian .NET utama memiliki metode pembantu throw statis yang mengalokasikan dan melemparkan pengecualian. Anda harus memanggil metode ini alih-alih membangun dan melemparkan jenis pengecualian yang sesuai:
- ArgumentNullException.ThrowIfNull
- ArgumentException.ThrowIfNullOrEmpty(String, String)
- ArgumentException.ThrowIfNullOrWhiteSpace(String, String)
- ArgumentOutOfRangeException.ThrowIfZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfNegative<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNotEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<T>(T, T, String)
- ObjectDisposedException.ThrowIf
Petunjuk
Aturan analisis kode berikut dapat membantu Anda menemukan tempat dalam kode Anda di mana Anda dapat memanfaatkan pembantu throw statis ini: CA1510, CA1511, CA1512, dan CA1513.
Jika Anda menerapkan metode asinkron, panggil CancellationToken.ThrowIfCancellationRequested() alih-alih memeriksa apakah pembatalan diminta lalu membuat dan melemparkan OperationCanceledException. Untuk informasi selengkapnya, lihat CA2250.
Menyertakan pesan string yang dilokalkan
Pesan kesalahan yang dilihat pengguna berasal dari properti Exception.Message pengecualian yang dilemparkan, dan bukan dari nama kelas pengecualian. Biasanya, Anda menetapkan nilai ke properti Exception.Message dengan meneruskan string pesan ke argumen message konstruktor Pengecualian .
Untuk aplikasi yang dilokalkan, Anda harus menyediakan string pesan yang dilokalkan untuk setiap pengecualian yang dapat dilemparkan aplikasi Anda. Anda menggunakan file sumber daya untuk menyediakan pesan kesalahan yang dilokalkan. Untuk informasi tentang melokalisasi aplikasi dan mengambil string yang dilokalkan, lihat artikel berikut ini:
- Cara: Membuat pengecualian yang ditentukan pengguna dengan pesan pengecualian yang dilokalkan
- Sumber Daya di Aplikasi .NET
- System.Resources.ResourceManager
Gunakan tata bahasa yang tepat
Tulis kalimat yang jelas dan sertakan tanda baca akhir. Setiap kalimat dalam string yang ditetapkan ke properti Exception.Message harus berakhir dengan tanda titik. Misalnya, "Tabel log telah meluap." menggunakan tata bahasa serta tanda baca yang tepat.
Tempatkan pernyataan 'throw' dengan tepat
Tempatkan pernyataan throw di mana penelusuran tumpukan sangat membantu. Stack trace dimulai pada pernyataan di mana pengecualian dilemparkan dan berakhir pada pernyataan catch yang menangkap pengecualian.
Jangan memunculkan pengecualian dalam klausul akhirnya
Jangan ajukan pengecualian dalam klausul finally. Untuk informasi selengkapnya, lihat aturan analisis kode CA2219.
Jangan memunculkan pengecualian dari tempat yang tidak terduga
Beberapa metode, seperti metode Equals, GetHashCode, dan ToString, konstruktor statis, dan operator kesetaraan, tidak boleh melempar pengecualian. Untuk informasi selengkapnya, lihat aturan analisis kode CA1065.
Melempar pengecualian validasi argumen secara sinkron
Dalam metode pengembalian tugas, Anda harus memvalidasi argumen dan melemparkan pengecualian yang sesuai, seperti ArgumentException dan ArgumentNullException, sebelum memasukkan bagian asinkron dari metode . Pengecualian yang dilemparkan di bagian asinkron dari metode disimpan dalam tugas yang dikembalikan dan tidak muncul sampai, misalnya, tugas ditunggu. Untuk informasi selengkapnya, lihat Pengecualian dalam metode pengembalian tugas.
Jenis pengecualian kustom
Praktik terbaik berikut menyangkut jenis pengecualian kustom:
-
Akhiri nama kelas pengecualian dengan
Exception - Menyertakan tiga konstruktor
- Menyediakan lebih banyak properti sesuai kebutuhan
Akhiri nama kelas pengecualian dengan Exception
Ketika pengecualian kustom diperlukan, beri nama dengan tepat dan dapatkan dari kelas Exception. Misalnya:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Sertakan tiga konstruktor
Gunakan setidaknya tiga konstruktor umum saat membuat kelas pengecualian Anda sendiri: konstruktor tanpa parameter, konstruktor yang mengambil pesan string, dan konstruktor yang mengambil pesan string dan pengecualian dalam.
- Exception(), yang menggunakan nilai default.
- Exception(String), yang menerima pesan string.
- Exception(String, Exception), yang menerima pesan berbentuk string dan pengecualian internal.
Misalnya, lihat Cara: Membuat pengecualian yang ditentukan pengguna.
Menyediakan lebih banyak properti sesuai kebutuhan
Berikan lebih banyak properti untuk pengecualian (selain string pesan kustom) hanya ketika ada skenario terprogram di mana informasi tambahan berguna. Misalnya, FileNotFoundException menyediakan properti FileName.