Seberapa perlunya mengikuti praktik pemrograman defensif untuk kode yang tidak akan pernah tersedia untuk umum?

45

Saya sedang menulis implementasi Java dari permainan kartu, jadi saya membuat tipe khusus Collection yang saya sebut Zone. Semua metode modifikasi Koleksi Java tidak didukung, tetapi ada metode di Zone API move(Zone, Card), yang memindahkan Kartu dari Zona yang diberikan ke dirinya sendiri (dilakukan dengan teknik paket-pribadi). Dengan cara ini, saya dapat memastikan bahwa tidak ada kartu yang dikeluarkan dari zona dan hilang begitu saja; mereka hanya bisa dipindahkan ke zona lain.

Pertanyaan saya adalah, seberapa perlukah pengkodean defensif semacam ini? Ini "benar," dan rasanya seperti praktik yang tepat, tetapi bukan seperti Zone API yang akan menjadi bagian dari beberapa perpustakaan umum. Ini hanya untuk saya, jadi sepertinya saya melindungi kode saya dari diri saya ketika saya mungkin bisa lebih efisien dengan hanya menggunakan Koleksi standar.

Seberapa jauh saya harus mengambil ide Zona ini? Adakah yang bisa memberi saya nasihat tentang seberapa banyak saya harus berpikir tentang menjaga kontrak dalam kelas yang saya tulis, terutama untuk yang tidak benar-benar tersedia untuk umum?

pemecah kode
sumber
4
= ~ s / perlu / direkomendasikan / gi
GrandmasterB
2
Tipe data harus benar oleh konstruksi, atau apa yang Anda bangun? Mereka harus dienkapsulasi sedemikian rupa sehingga, bisa berubah atau tidak, mereka hanya bisa berada di negara yang valid. Hanya jika tidak mungkin untuk menegakkan ini secara statis (atau sulit secara tidak masuk akal) Anda dapat meningkatkan kesalahan runtime.
Jon Purdy
1
Jangan pernah bilang tidak akan pernah. Kecuali jika kode Anda tidak pernah digunakan, Anda tidak akan pernah tahu pasti di mana kode Anda akan berakhir. ;)
Izkata
1
Komentar @codebreaker GrandmasterB adalah ekspresi ganti. Artinya: ganti "perlu" dengan "disarankan".
Ricardo Souza
1
Kode Tanpa Kode # 116 Kepercayaan Tidak Ada yang sepertinya cocok di sini.

Jawaban:

72

Saya tidak akan membahas masalah desain - hanya pertanyaan apakah akan melakukan sesuatu dengan "benar" di API non-publik.

itu hanya untuk saya, jadi sepertinya saya melindungi kode saya sendiri

Itulah intinya. Mungkin ada coders di luar sana yang ingat nuansa setiap kelas dan metode yang pernah mereka tulis dan tidak pernah salah memanggil mereka dengan kontrak yang salah. Saya bukan salah satu dari mereka. Saya sering lupa bagaimana kode yang saya tulis seharusnya berfungsi dalam beberapa jam setelah penulisan itu. Setelah Anda berpikir Anda telah melakukannya dengan benar sekali, pikiran Anda akan cenderung beralih ke masalah yang sedang Anda kerjakan sekarang .

Anda memiliki alat untuk melawannya. Alat-alat ini termasuk (tanpa urutan tertentu) konvensi, tes unit dan tes otomatis lainnya, pemeriksaan prekondisi, dan dokumentasi. Saya sendiri telah menemukan unit test sangat berharga karena keduanya memaksa Anda untuk berpikir tentang bagaimana kontrak Anda akan digunakan dan memberikan dokumentasi nanti tentang bagaimana antarmuka dirancang.

Michael K.
sumber
Senang mendengarnya. Di masa lalu saya hanya terbiasa memprogram seefisien mungkin, jadi saya terkadang kesulitan membiasakan diri dengan ide-ide seperti ini. Saya senang saya pergi ke arah yang benar.
codebreaker
15
"Efisien" dapat berarti banyak hal berbeda! Dalam pengalaman saya, pemula (bukan saya katakan Anda adalah satu) sering mengabaikan seberapa efisien mereka akan dapat mendukung program. Kode biasanya menghabiskan jauh lebih lama pada fase dukungan dari siklus hidup produknya daripada pada fase "menulis kode baru", jadi saya pikir itu adalah efisiensi yang harus dipertimbangkan dengan hati-hati.
Charlie Kilian
2
Saya pasti setuju. Kembali di perguruan tinggi, saya tidak pernah memikirkan hal itu.
codebreaker
25

Saya biasanya mengikuti beberapa aturan sederhana:

  • Cobalah untuk selalu memprogram dengan kontrak .
  • Jika suatu metode tersedia untuk umum atau menerima masukan dari dunia luar , lakukan beberapa tindakan defensif (mis IllegalArgumentException.).
  • Untuk segala sesuatu yang hanya dapat diakses secara internal, gunakan pernyataan (misalnya assert input != null).

Jika seorang klien benar-benar terlibat, mereka akan selalu menemukan cara untuk membuat kode Anda keliru. Setidaknya mereka selalu bisa melakukannya melalui refleksi. Tapi itulah keindahan desain berdasarkan kontrak . Anda tidak menyetujui penggunaan kode semacam itu, jadi Anda tidak dapat menjamin bahwa kode itu akan berfungsi dalam skenario seperti itu.

Mengenai kasus spesifik Anda, jika Zonetidak seharusnya digunakan dan / atau diakses oleh orang luar, jadikan paket kelas ini privat (dan mungkin final), atau lebih baik, gunakan koleksi yang sudah disediakan oleh Java untuk Anda. Mereka sudah diuji, dan Anda tidak perlu menemukan kembali rodanya. Perhatikan bahwa ini tidak menghalangi Anda untuk menggunakan pernyataan di seluruh kode Anda untuk memastikan semuanya berfungsi seperti yang diharapkan.

afsantos
sumber
1
+1 untuk menyebutkan Desain berdasarkan Kontrak. Jika Anda tidak dapat sepenuhnya melarang perilaku (dan itu sulit dilakukan), setidaknya Anda menjelaskan bahwa tidak ada jaminan perilaku buruk. Saya juga suka melempar IllegalStateException atau UnsupportedOperationException.
user949300
@ user949300 Tentu. Saya suka percaya bahwa pengecualian seperti itu diperkenalkan dengan tujuan yang bermakna. Menghormati kontrak tampaknya cocok dengan peran itu.
afsantos
16

Pemrograman defensif adalah hal yang sangat bagus.
Sampai mulai menghalangi penulisan kode. Maka itu bukan hal yang baik.


Berbicara sedikit lebih pragmatis ...

Sepertinya Anda berada di ujung mengambil sesuatu terlalu jauh. Tantangannya (dan jawaban untuk pertanyaan Anda) terletak pada memahami apa aturan bisnis atau persyaratan program.

Menggunakan API permainan kartu Anda sebagai contoh, ada beberapa lingkungan di mana segala sesuatu yang dapat dilakukan untuk mencegah kecurangan sangat penting. Sejumlah besar uang nyata mungkin terlibat, jadi masuk akal untuk menempatkan sejumlah besar cek untuk memastikan bahwa kecurangan tidak dapat terjadi.

Di sisi lain, Anda harus tetap memperhatikan prinsip-prinsip SOLID, terutama tanggung jawab tunggal. Meminta kelas penampung untuk mengaudit secara efektif ke mana arah kartu-kartu mungkin sedikit banyak. Mungkin lebih baik untuk memiliki lapisan audit / pengontrol antara wadah kartu dan fungsi yang menerima permintaan pemindahan.


Terkait dengan masalah tersebut, Anda perlu memahami komponen API Anda yang terbuka untuk umum (dan karenanya rentan) versus apa yang bersifat pribadi dan kurang terbuka. Saya bukan penganjur total "lapisan luar yang keras dengan bagian dalam yang lunak", tetapi hasil terbaik dari upaya Anda adalah mengeraskan bagian luar API Anda.

Saya tidak berpikir pengguna akhir perpustakaan yang dimaksudkan adalah kritis terhadap tekad tentang seberapa banyak pemrograman defensif yang Anda lakukan. Bahkan dengan modul yang saya tulis untuk saya gunakan sendiri, saya masih melakukan pemeriksaan untuk memastikan bahwa di masa depan saya tidak melakukan kesalahan yang tidak disengaja dalam menelepon perpustakaan.


sumber
2
+1 untuk "Sampai ia mulai menghalangi penulisan kode." Khusus untuk proyek pribadi jangka pendek, pengkodean secara defensif bisa memakan waktu lebih lama daripada nilainya.
Corey
2
Setuju, meskipun saya ingin menambahkan bahwa adalah hal yang baik untuk / dapat / memprogram secara defensif, tetapi juga penting untuk dapat memprogram dengan cara prototyping. Kemampuan untuk melakukan keduanya akan memungkinkan Anda untuk memilih tindakan yang paling tepat yang jauh lebih baik daripada banyak programmer yang saya tahu yang hanya dapat memprogram (semacam) defensif.
David Mulder
13

Pengkodean defensif bukan hanya ide yang baik untuk kode publik. Ini adalah ide bagus untuk kode apa pun yang tidak langsung dibuang. Tentu, Anda tahu bagaimana seharusnya dipanggil sekarang , tetapi Anda tidak tahu seberapa baik Anda akan mengingat ini enam bulan dari sekarang ketika Anda kembali ke proyek.

Sintaksis dasar Java memberi Anda banyak pertahanan yang dibakar dibandingkan dengan tingkat yang lebih rendah atau bahasa yang ditafsirkan seperti C atau Javascript masing-masing. Dengan asumsi bahwa Anda memberi nama metode Anda dengan jelas dan tidak memiliki "urutan metode" eksternal, Anda mungkin bisa lolos dengan hanya menetapkan argumen sebagai tipe data yang benar dan termasuk perilaku yang masuk akal jika data yang diketik dengan benar masih bisa tidak valid.

(Di samping itu, jika Kartu selalu harus berada di zona itu, saya pikir Anda mendapatkan yang terbaik dengan membuat semua kartu yang dimainkan dirujuk oleh koleksi global ke objek Game Anda, dan menjadikan Zone sebagai properti dari setiap kartu. Tapi karena saya tidak tahu apa yang Zona Anda lakukan selain memegang kartu, sulit untuk mengetahui apakah itu tepat.)

DougM
sumber
1
Saya menganggap zona tersebut sebagai properti kartu, tetapi karena Kartu saya berfungsi lebih baik sebagai objek yang tidak berubah, saya memutuskan cara ini adalah yang terbaik. Terima kasih atas sarannya.
codebreaker
3
@codebreaker satu hal yang dapat membantu dalam kasus itu adalah merangkum kartu ke objek lain. Ace of Spades adalah apa adanya. Lokasi tidak menentukan identitasnya, dan kartu mungkin harus tidak dapat diubah. Mungkin memiliki Zone berisi kartu: mungkin memiliki CardDescriptoryang berisi kartu, lokasinya, status menghadap ke atas / bawah, atau bahkan rotasi untuk game yang peduli tentang itu. Semua itu adalah properti yang bisa berubah yang tidak mengubah identitas kartu.
1

Pertama buat kelas yang menyimpan daftar Zona sehingga Anda tidak kehilangan Zona atau kartu di dalamnya. Anda kemudian dapat memeriksa apakah transfer ada dalam ZoneList Anda. Kelas ini mungkin akan menjadi semacam singleton, karena Anda hanya perlu satu instance, tetapi Anda mungkin ingin set Zona nanti, jadi biarkan opsi Anda terbuka.

Kedua, jangan minta Zone atau ZoneList mengimplementasikan Koleksi atau apa pun kecuali Anda mengharapkannya. Yaitu, jika Zone atau ZoneList akan diteruskan ke sesuatu yang mengharapkan sebuah Koleksi, maka terapkanlah. Anda dapat menonaktifkan banyak metode dengan meminta mereka melemparkan pengecualian (UnimplementedException, atau sesuatu seperti itu) atau dengan meminta mereka tidak melakukan apa-apa. (Berpikir sangat keras sebelum menggunakan opsi kedua. Jika Anda melakukannya karena mudah, Anda akan menemukan Anda kehilangan bug yang mungkin Anda tangkap sejak awal.)

Ada pertanyaan nyata tentang apa yang "benar". Tetapi begitu Anda mengetahui apa itu, Anda ingin melakukan hal-hal seperti itu. Dalam dua tahun Anda akan melupakan semua ini, dan jika Anda mencoba menggunakan kodenya maka Anda akan benar-benar kesal pada orang yang menulisnya dengan cara yang berlawanan dengan intuisi dan tidak menjelaskan apa pun.

RalphChapin
sumber
2
Jawaban Anda terlalu banyak berfokus pada masalah yang dihadapi, bukan pada pertanyaan yang lebih luas yang ditanyakan OP tentang pemrograman defensif secara umum.
Saya benar-benar melewati Zona ke metode yang mengambil Koleksi, jadi implementasinya diperlukan. Semacam registri zona dalam gim adalah ide yang menarik.
codebreaker
@ GlenH7: Saya menemukan bekerja dengan contoh-contoh spesifik sering membantu lebih dari sekadar teori abstrak. OP memberikan yang agak menarik, jadi saya setuju.
RalphChapin
1

Pengkodean defensif dalam desain API umumnya adalah tentang memvalidasi input dan dengan hati-hati memilih mekanisme penanganan kesalahan yang tepat. Hal-hal yang disebutkan jawaban lain juga perlu diperhatikan.

Ini sebenarnya bukan tentang apa contoh Anda. Anda membatasi permukaan API Anda, untuk alasan yang sangat spesifik. Seperti GlenH7 menyebutkan, ketika set kartu akan digunakan dalam permainan yang sebenarnya, dengan dek ('bekas' dan 'tidak terpakai'), sebuah meja dan tangan misalnya, Anda pasti ingin meletakkan cek di tempat untuk memastikan masing-masing kartu dari set hadir sekali dan hanya sekali.

Bahwa Anda merancang ini dengan "zona", adalah pilihan yang sewenang-wenang. Bergantung pada implementasinya (sebuah zona hanya bisa berupa tangan, setumpuk atau tabel dalam contoh di atas) mungkin sangat baik desainnya.

Namun, bahwa penerapan suara seperti tipe turunan dari lebih Collection<Card>set -seperti kartu, dengan API kurang membatasi. Misalnya ketika Anda ingin membuat kalkulator nilai tangan, atau AI, Anda tentu ingin bebas memilih yang mana dan berapa banyak setiap kartu yang Anda pilih.

Jadi, bagus untuk mengekspos API seketat itu, jika satu-satunya tujuan API itu adalah memastikan setiap kartu selalu berada dalam zona.

CodeCaster
sumber