Saya pernah mendengar bahwa disarankan untuk memvalidasi argumen metode publik:
- Haruskah seseorang memeriksa nol jika dia tidak mengharapkan nol?
- Haruskah metode memvalidasi parameternya?
- MSDN - CA1062: Validasi argumen metode publik (Saya memiliki latar belakang .NET tetapi pertanyaannya tidak spesifik C #)
Motivasi bisa dimengerti. Jika modul akan digunakan dengan cara yang salah, kami ingin membuang pengecualian segera alih-alih perilaku yang tidak terduga.
Yang menggangguku, adalah bahwa argumen yang salah bukanlah satu-satunya kesalahan yang dapat dibuat saat menggunakan modul. Inilah beberapa skenario kesalahan di mana kita perlu menambahkan logika pemeriksaan jika kita mengikuti rekomendasi dan tidak ingin eskalasi kesalahan:
- Panggilan masuk - argumen yang tidak terduga
- Panggilan masuk - modul dalam kondisi yang salah
- Panggilan eksternal - hasil yang tidak terduga dikembalikan
- Panggilan eksternal - efek samping yang tidak terduga (masuk dua kali ke modul panggilan, melanggar keadaan dependensi lainnya)
Saya sudah mencoba memperhitungkan semua kondisi ini dan menulis modul sederhana dengan satu metode (maaf, bukan-C # guys):
public sealed class Room
{
private readonly IDoorFactory _doorFactory;
private bool _entered;
private IDoor _door;
public Room(IDoorFactory doorFactory)
{
if (doorFactory == null)
throw new ArgumentNullException("doorFactory");
_doorFactory = doorFactory;
}
public void Open()
{
if (_door != null)
throw new InvalidOperationException("Room is already opened");
if (_entered)
throw new InvalidOperationException("Double entry is not allowed");
_entered = true;
_door = _doorFactory.Create();
if (_door == null)
throw new IncompatibleDependencyException("doorFactory");
_door.Open();
_entered = false;
}
}
Sekarang Aman =)
Cukup menyeramkan. Tapi bayangkan betapa menyeramkannya dalam modul nyata dengan lusinan metode, keadaan kompleks dan banyak panggilan eksternal (hai, pecinta injeksi ketergantungan!). Perhatikan bahwa jika Anda memanggil modul yang perilakunya dapat diganti (kelas tidak disegel dalam C #) maka Anda membuat panggilan eksternal dan konsekuensinya tidak dapat diprediksi pada ruang lingkup pemanggil.
Kesimpulannya, apa jalan yang benar dan mengapa? Jika Anda dapat memilih dari opsi di bawah ini, tolong jawab pertanyaan tambahan.
Periksa seluruh penggunaan modul. Apakah kita perlu tes unit? Apakah ada contoh kode seperti itu? Haruskah ketergantungan injeksi dibatasi dalam penggunaan (karena akan menyebabkan lebih banyak memeriksa logika)? Apakah tidak praktis untuk memindahkan cek ke waktu debug (tidak termasuk dalam rilis)?
Periksa hanya argumen. Dari pengalaman saya, pemeriksaan argumen - terutama pemeriksaan nol - adalah pemeriksaan yang paling tidak efektif, karena kesalahan argumen jarang mengarah pada kesalahan kompleks dan eskalasi kesalahan. Sebagian besar waktu Anda akan mendapatkan NullReferenceException
di baris berikutnya. Jadi mengapa cek argumen begitu istimewa?
Jangan periksa penggunaan modul. Pendapat ini sangat tidak populer, dapatkah Anda menjelaskan mengapa?
Jawaban:
TL; DR: Validasi perubahan negara, andalkan [validitas dari] kondisi saat ini.
Di bawah ini saya hanya mempertimbangkan verifikasi yang diaktifkan rilis. Penegasan aktif hanya Debug adalah bentuk dokumentasi, yang berguna dengan caranya sendiri dan di luar ruang lingkup untuk pertanyaan ini.
Pertimbangkan prinsip-prinsip berikut:
Definisi
Keadaan yang bisa berubah
Masalah
Dalam bahasa imperatif, gejala kesalahan dan penyebabnya dapat dipisahkan oleh jam-jam angkat berat. Korupsi negara dapat menyembunyikan dirinya dan bermutasi untuk menghasilkan kegagalan yang tidak dapat dijelaskan, karena inspeksi negara saat ini tidak dapat mengungkapkan proses korupsi penuh dan, karenanya, asal mula kesalahan.
Larutan
Setiap perubahan negara harus dibuat dan diverifikasi dengan cermat. Salah satu cara untuk berurusan dengan keadaan yang bisa berubah adalah menjaganya agar tetap minimum. Ini dicapai dengan:
Saat memperluas status komponen, pertimbangkan untuk melakukannya dengan membiarkan kompiler memaksakan imutabilitas data baru. Juga, tegakkan setiap kendala runtime yang masuk akal, membatasi status potensial yang dihasilkan ke set terkecil yang terdefinisi dengan baik.
Contoh
Kohesi pengulangan dan tanggung jawab
Masalah
Memeriksa prasyarat dan kondisi operasi pasca-mengarah pada duplikasi kode verifikasi di kedua klien dan komponen. Memvalidasi permintaan komponen sering kali memaksa klien untuk mengambil sebagian tanggung jawab komponen.
Larutan
Andalkan komponen untuk melakukan verifikasi keadaan bila memungkinkan. Komponen harus menyediakan API yang tidak memerlukan verifikasi penggunaan khusus (verifikasi argumen atau penegakan urutan operasi, misalnya) untuk menjaga status komponen agar terdefinisi dengan baik. Mereka berkewajiban untuk memverifikasi argumen permohonan API sebagaimana diperlukan, melaporkan kegagalan dengan cara yang diperlukan, dan berusaha untuk mencegah korupsi negara mereka.
Klien harus mengandalkan komponen untuk memverifikasi penggunaan API mereka. Tidak hanya pengulangan yang dihindari, klien tidak lagi bergantung pada detail implementasi komponen tambahan. Pertimbangkan kerangka kerja sebagai komponen. Hanya tulis kode verifikasi khusus ketika invarian komponen tidak cukup ketat atau untuk merangkum pengecualian komponen sebagai detail implementasi.
Jika suatu operasi tidak mengubah keadaan dan tidak dicakup oleh verifikasi perubahan keadaan, verifikasi setiap argumen pada tingkat sedalam mungkin.
Contoh
Menjawab
Ketika prinsip yang dijelaskan diterapkan pada contoh yang dipermasalahkan, kita mendapatkan:
Ringkasan
Status Klien terdiri dari nilai bidangnya sendiri dan bagian dari status komponen yang tidak dicakup oleh invariannya sendiri. Verifikasi hanya boleh dilakukan sebelum keadaan aktual klien berubah.
sumber
Kelas bertanggung jawab atas negaranya sendiri. Jadi validasi sejauh itu membuat atau menempatkan hal-hal dalam keadaan yang dapat diterima.
Tidak, jangan melemparkan pengecualian, alih-alih berikan perilaku yang dapat diprediksi. Akibat wajar dari tanggung jawab negara adalah menjadikan kelas / aplikasi sebagai toleran dan praktis. Misalnya, meneruskan
null
keaCollection.Add()
? Hanya saja, jangan tambahkan dan teruskan. Anda mendapatkannull
input untuk membuat objek? Buat objek nol atau objek default. Di atas,door
sudahopen
? Jadi apa, teruskan.DoorFactory
argumen adalah nol? Buat yang baru. Ketika saya membuat,enum
saya selalu memilikiUndefined
anggota. Saya menggunakan liberalDictionary
danenums
mendefinisikan hal-hal secara eksplisit; dan ini sangat membantu dalam memberikan perilaku yang dapat diprediksi.Ya meskipun saya berjalan melalui bayangan lembah parameter saya tidak akan takut argumen. Untuk sebelumnya saya juga menggunakan parameter default dan opsional sebanyak mungkin.
Semua hal di atas memungkinkan pemrosesan internal untuk terus berjalan. Dalam aplikasi tertentu saya memiliki banyak metode di beberapa kelas dengan hanya satu tempat di mana pengecualian dilemparkan. Bahkan kemudian, itu bukan karena argumen nol atau bahwa saya tidak dapat melanjutkan memprosesnya karena kode akhirnya menciptakan objek "non-fungsional" / "null".
sunting
mengutip komentar saya secara keseluruhan. Saya pikir desain tidak hanya "menyerah" ketika bertemu 'nol'. Terutama menggunakan objek komposit.
akhiri edit
sumber
encapsulation
&single responsibility
. Hampir tidak adanull
pengecekan setelah lapisan pertama yang berinteraksi dengan klien. Kode <strike> toleran </strike> kuat. Kelas dirancang dengan status default dan berfungsi tanpa ditulis seolah-olah kode yang berinteraksi adalah bug-ridden, runk junk. Orang tua komposit tidak harus menjangkau lapisan anak-anak untuk mengevaluasi validitas (dan implikasinya, periksanull
semua sudut dan celah). Orangtua tahu apa arti status default anak