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?
sumber
Jawaban:
Saya tidak akan membahas masalah desain - hanya pertanyaan apakah akan melakukan sesuatu dengan "benar" di API non-publik.
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.
sumber
Saya biasanya mengikuti beberapa aturan sederhana:
IllegalArgumentException
.).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
Zone
tidak seharusnya digunakan dan / atau diakses oleh orang luar, jadikan paket kelas ini privat (dan mungkinfinal
), 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.sumber
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
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.)
sumber
CardDescriptor
yang 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.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.
sumber
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.
sumber