kontrak / pernyataan kode: apa dengan cek rangkap?

10

Saya penggemar tulisan, kontrak, atau jenis cek apa pun yang tersedia dalam bahasa yang saya gunakan. Satu hal yang sedikit mengganggu saya adalah bahwa saya tidak yakin apa praktik umum untuk menangani cek duplikat.

Contoh situasi: Saya pertama-tama menulis fungsi berikut

void DoSomething( object obj )
{
  Contract.Requires<ArgumentNullException>( obj != null );
  //code using obj
}

kemudian beberapa jam kemudian saya menulis fungsi lain yang memanggil yang pertama. Karena semuanya masih segar dalam memori, saya memutuskan untuk tidak menduplikasi kontrak, karena saya tahu bahwa kita DoSomethingakan mengecek objek nol:

void DoSomethingElse( object obj )
{
  //no Requires here: DoSomething will do that already
  DoSomething( obj );
  //code using obj
}

Masalah yang jelas: DoSomethingElsesekarang tergantung pada DoSomethinguntuk memverifikasi bahwa obj bukan nol. Jadi sebaiknya jangan DoSomethingpernah memutuskan untuk tidak memeriksa lagi, atau jika saya memutuskan untuk menggunakan fungsi lain, obj mungkin tidak diperiksa lagi. Yang membuat saya menulis implementasi ini:

void DoSomethingElse( object obj )
{
  Contract.Requires<ArgumentNullException>( obj != null );
  DoSomething( obj );
  //code using obj
}

Selalu aman, jangan khawatir, kecuali bahwa jika situasinya tumbuh, objek yang sama mungkin diperiksa beberapa kali dan ini merupakan bentuk duplikasi dan kita semua tahu itu tidak begitu baik.

Apa praktik paling umum untuk situasi seperti ini?

stijn
sumber
3
ArgumentBullException? Itu yang baru :)
CVn
lol @ keterampilan mengetik saya ... Saya akan mengeditnya.
stijn

Jawaban:

13

Secara pribadi saya akan memeriksa nol dalam fungsi apa pun yang akan gagal jika mendapat nol, dan tidak dalam fungsi apa pun yang tidak.

Jadi, dalam contoh Anda di atas, jika doSomethingElse () tidak perlu dereference obj maka saya tidak akan memeriksa obj untuk null di sana.

Jika DoSomething () tidak keberatan dereference maka harus memeriksa nol.

Jika kedua fungsi dereferensi maka keduanya harus memeriksa. Jadi jika DoSomethingElse dereferences obj maka harus memeriksa nol, tetapi DoSomething juga masih harus memeriksa nol karena dapat dipanggil dari jalur lain.

Dengan cara ini Anda dapat membiarkan kode cukup bersih dan masih menjamin bahwa cek berada di tempat yang benar.

Luke Graham
sumber
1
Saya sangat setuju. Prasyarat masing-masing metode harus berdiri sendiri. Bayangkan Anda menulis ulang DoSomething()sedemikian rupa sehingga prasyarat tidak lagi diperlukan (tidak mungkin dalam kasus khusus ini, tetapi mungkin terjadi dalam situasi yang berbeda), dan menghapus pemeriksaan prakondisi. Sekarang beberapa metode yang tampaknya sama sekali tidak berhubungan rusak karena prasyarat yang hilang. Saya akan mengambil sedikit duplikasi kode untuk kejelasan kegagalan aneh seperti itu dari keinginan untuk menyimpan beberapa baris kode, kapan saja.
CVn
2

Bagus! Saya melihat Anda mengetahui tentang Kontrak Kode untuk .NET. Kontrak Kode berjalan lebih jauh dari pernyataan rata-rata Anda, di mana pemeriksa statis adalah contoh terbaik. Ini mungkin tidak tersedia untuk Anda jika Anda belum menginstal Visual Studio Premium atau lebih tinggi, tetapi penting untuk memahami maksud di baliknya jika Anda akan menggunakan Kontrak Kode.

Ketika Anda menerapkan kontrak ke suatu fungsi, itu secara harfiah adalah kontrak . Fungsi itu menjamin untuk berperilaku sesuai dengan kontrak, dan dijamin hanya akan digunakan sebagaimana didefinisikan oleh kontrak.

Dalam contoh yang Anda berikan, DoSomethingElse()fungsi tidak sesuai dengan kontrak seperti yang ditentukan oleh DoSomething(), karena nol dapat dilewati, dan pemeriksa statis akan menunjukkan masalah ini. Cara untuk menyelesaikan ini adalah dengan menambahkan kontrak yang sama ke DoSomethingElse().

Sekarang, ini berarti akan ada duplikasi, tetapi duplikasi ini diperlukan saat Anda memilih untuk mengekspos fungsionalitas di dua fungsi. Fungsi-fungsi ini, meskipun bersifat pribadi, dapat dipanggil dari tempat yang berbeda di kelas Anda juga, jadi satu-satunya cara untuk menjamin bahwa dari panggilan mana pun argumen tidak akan pernah batal adalah dengan menduplikasi kontrak.

Ini seharusnya membuat Anda mempertimbangkan kembali mengapa Anda membagi perilaku menjadi dua fungsi. Saya selalu berpendapat ( bertentangan dengan kepercayaan populer ) bahwa Anda tidak boleh membagi fungsi yang hanya dipanggil dari satu tempat . Dengan mengekspos enkapsulasi dengan menerapkan kontrak, ini menjadi lebih jelas. Sepertinya saya menemukan argumen tambahan untuk tujuan saya! Terima kasih! :)

Steven Jeuris
sumber
mengenai paragraf terakhir Anda: dalam kode aktual kedua fungsi adalah anggota dari dua kelas yang berbeda, itulah sebabnya mereka terpecah. Terlepas dari itu, saya berada dalam situasi berikut berkali-kali: menulis fungsi yang panjang, memutuskan untuk tidak membaginya. Kemudian cari tahu bahwa beberapa logika diduplikasi di tempat lain, jadi pisahkan saja. Atau setahun kemudian membacanya lagi dan menemukannya tidak dapat dibaca, jadi bagi-bagi saja. Atau ketika debugging: fungsi split = kurang menggedor tombol F10. Ada lebih banyak alasan, jadi secara pribadi saya lebih suka berpisah, meskipun itu berarti kadang-kadang terlalu ekstrim.
stijn
(1) "Nanti cari tahu bahwa beberapa logika digandakan di tempat lain" . Itulah mengapa saya merasa lebih penting untuk selalu "berkembang ke arah API" daripada sekadar membagi fungsi. Pikirkan terus-menerus tentang penggunaan kembali, tidak hanya di dalam kelas saat ini. (2) "Atau setahun kemudian membacanya lagi dan menemukannya tidak dapat dibaca" Karena fungsi memiliki nama ini lebih baik? Anda bahkan lebih mudah dibaca jika Anda menggunakan apa yang oleh komentator di blog saya gambarkan sebagai "paragraf kode". (3) "fungsi split = kurang menggedor tombol F10" ... Saya tidak mengerti mengapa.
Steven Jeuris
(1) setuju (2) keterbacaan adalah preferensi pribadi sehingga itu tidak benar-benar sesuatu yang sedang dibahas untuk saya .. (3) melalui fungsi 20 baris memerlukan menekan F10 20 kali. Melewati fungsi yang memiliki 10 baris tersebut dalam fungsi split mengharuskan saya untuk memilih hanya harus menekan F10 11 kali. Ya, dalam kasus pertama saya dapat meletakkan breakpoints atau pilih 'jumpt to cursor' tapi itu masih lebih merupakan upaya daripada kasus kedua.
stijn
@stijn: (2) setuju; p (3) terima kasih telah menjelaskan!
Steven Jeuris