Saya bertanya-tanya apakah ada pro dan kontra terhadap gaya ini:
private void LoadMaterial(string name)
{
if (_Materials.ContainsKey(name))
{
throw new ArgumentException("The material named " + name + " has already been loaded.");
}
_Materials.Add(
name,
Resources.Load(string.Format("Materials/{0}", name)) as Material
);
}
Metode itu, untuk masing-masing name
, harus dijalankan hanya sekali. _Materials.Add()
akan mengeluarkan pengecualian jika akan dipanggil beberapa kali untuk hal yang sama name
. Apakah penjaga saya, sebagai akibatnya, sepenuhnya berlebihan, atau ada beberapa manfaat yang kurang jelas?
Itu C #, Unity, kalau ada yang tertarik.
exceptions
async
sumber
sumber
_Materials.Add
melempar pengecualian?string.Format
rangkaian string lebih untuk membangun pesan pengecualian. 2) hanya gunakanas
jika Anda berharap para pemain gagal dan periksa hasilnyanull
. Jika Anda berharap untuk selalu mendapatkanMaterial
, gunakan gips seperti(Material)Resources.Load(...)
. Pengecualian yang jelas lebih mudah untuk di-debug daripada pengecualian referensi nol yang terjadi kemudian.LoadMaterialIfNotLoaded
atauReloadMaterial
(nama ini dapat menggunakan perbaikan) berguna.Jawaban:
Manfaatnya adalah pengecualian "khusus" Anda memiliki pesan kesalahan yang bermakna bagi siapa saja yang memanggil fungsi ini tanpa mengetahui bagaimana penerapannya (yang di masa depan mungkin Anda!).
Memang, dalam hal ini mereka mungkin bisa menebak apa arti "standar", tetapi Anda masih memperjelas bahwa mereka melanggar kontrak Anda, daripada menemukan bug aneh dalam kode Anda.
sumber
_Materials.Add
bukan kesalahan yang ingin Anda kirimkan ke pemanggil, saya pikir metode relabelling kesalahan ini tidak efisien. Untuk setiap sukses panggilanLoadMaterial
(operasi normal) Anda akan melakukan tes kewarasan yang sama dua kali , diLoadMaterial
dan kemudian lagi pada_Materials.Add
. Jika lebih banyak lapisan dibungkus, masing-masing menggunakan gaya yang sama, Anda bahkan dapat memiliki lebih banyak kali tes yang sama._Materials.Add
tanpa syarat, kemudian menangkap kesalahan potensial dan di pawang melemparkan yang berbeda. Pekerjaan tambahan sekarang hanya dilakukan dalam kasus-kasus yang keliru, jalur eksekusi yang luar biasa di mana Anda tidak benar-benar khawatir tentang efisiensi sama sekali.Saya setuju dengan jawaban Ixrec . Namun, Anda mungkin ingin mempertimbangkan alternatif ketiga: menjadikan fungsi idempoten. Dengan kata lain, kembali lebih awal daripada melempar
ArgumentException
. Ini sering lebih disukai jika Anda akan dipaksa untuk memeriksa apakah sudah dimuat sebelum meneleponLoadMaterial
setiap waktu. Semakin sedikit prasyarat yang Anda miliki, semakin sedikit beban kognitif pada programmer.Melempar pengecualian akan lebih baik jika itu benar-benar kesalahan programmer, di mana itu harus jelas dan diketahui pada waktu kompilasi jika materi sudah dimuat, tanpa harus memeriksa saat runtime sebelum memanggil.
sumber
LoadMaterial
memiliki kendala sebagai berikut: "Setelah memanggilnya materi selalu dimuat dan jumlah materi yang dimuat tidak berkurang". Melontarkan pengecualian untuk kendala ketiga "Materi tidak dapat ditambahkan dua kali" tampaknya konyol dan berlawanan dengan intuisi saya. Membuat fungsi idempoten mengurangi ketergantungan kode pada urutan eksekusi dan status aplikasi. Sepertinya ini kemenangan ganda bagi saya.Pertanyaan mendasar yang harus Anda tanyakan adalah: Apa yang Anda inginkan dari antarmuka untuk fungsi Anda? Secara khusus, apakah kondisi tersebut
_Materials.ContainsKey(name)
merupakan prasyarat dari fungsi tersebut?Jika ini bukan prasyarat, fungsi harus memberikan hasil yang terdefinisi dengan baik untuk semua kemungkinan katup
name
. Dalam hal ini, pengecualian yang dilemparkan jikaname
bukan merupakan_Materials
bagian dari antarmuka fungsi. Itu berarti perlu menjadi bagian dari dokumentasi antarmuka dan jika Anda pernah memutuskan untuk mengubah pengecualian itu di masa depan, itu akan menjadi perubahan antarmuka yang melanggar .Pertanyaan yang lebih menarik adalah apa yang terjadi jika itu merupakan prasyarat. Dalam hal ini prasyarat itu sendiri menjadi bagian dari antarmuka fungsi, tetapi cara suatu fungsi berperilaku ketika kondisi ini dilanggar belum tentu merupakan bagian dari antarmuka.
Dalam hal ini, pendekatan yang Anda posting, memeriksa pelanggaran prekondisi dan melaporkan kesalahan, adalah apa yang dikenal sebagai pemrograman defensif . Pemrograman defensif adalah baik karena memberi tahu pengguna lebih awal ketika mereka melakukan kesalahan dan memanggil fungsi dengan argumen palsu. Itu buruk karena secara signifikan meningkatkan beban pemeliharaan, karena kode pengguna mungkin bergantung bahwa fungsi menangani pelanggaran prasyarat dengan cara tertentu. Secara khusus, jika Anda menemukan bahwa pemeriksaan runtime pernah menjadi hambatan kinerja di masa mendatang (tidak mungkin untuk kasus sederhana seperti itu, tetapi sangat umum untuk prasyarat yang lebih kompleks), Anda mungkin tidak lagi dapat menghapusnya.
Ternyata kerugian itu bisa sangat signifikan, yang memberikan jenis pemrograman reputasi buruk di kalangan tertentu. Namun, tujuan awal masih valid: Kami ingin pengguna fungsi kami memperhatikan sejak awal ketika mereka melakukan kesalahan.
Oleh karena itu banyak pengembang saat ini mengusulkan pendekatan yang sedikit berbeda untuk masalah seperti ini. Alih-alih melempar pengecualian, mereka menggunakan mekanisme seperti-tegas untuk memeriksa prasyarat. Artinya, prasyarat dapat diperiksa dalam debug build untuk membantu pengguna dalam menangkap kesalahan sejak awal, tetapi mereka bukan bagian dari antarmuka fungsi. Perbedaannya mungkin tampak halus pada pandangan pertama, tetapi bisa membuat perbedaan besar dalam praktik.
Secara teknis, memanggil fungsi dengan prasyarat yang dilanggar adalah perilaku yang tidak terdefinisi. Tetapi implementasinya mungkin memutuskan untuk mendeteksi kasus-kasus itu dan segera memberi tahu pengguna jika itu terjadi. Sayangnya, pengecualian bukan alat yang baik untuk mengimplementasikan ini, karena kode pengguna dapat bereaksi terhadapnya dan dengan demikian mulai bergantung pada keberadaan mereka.
Untuk penjelasan terperinci tentang masalah dengan pendekatan defensif klasik, serta kemungkinan implementasi untuk pemeriksaan prasyarat gaya tegas, lihat ceramah John Lakos ' Programming Defensive Done Right from CppCon 2014 ( Slides , Video ).
sumber
Sudah ada beberapa jawaban di sini, tetapi inilah jawaban yang memperhitungkan Unity3D (jawabannya sangat spesifik untuk Unity3D, saya akan melakukan sebagian besar ini sangat berbeda dalam sebagian besar konteks):
Secara umum, Unity3D tidak menggunakan pengecualian secara tradisional. Jika Anda melemparkan pengecualian di Unity3D, itu tidak seperti di aplikasi .NET rata-rata Anda, yaitu tidak akan menghentikan program, t sebagian besar Anda dapat mengkonfigurasi editor untuk berhenti. Itu hanya akan login. Ini dapat dengan mudah membuat game dalam keadaan tidak valid dan membuat efek kaskade yang membuat kesalahan sulit dilacak. Jadi saya akan mengatakan dalam kasus Unity, membiarkan
Add
melemparkan pengecualian adalah opsi yang sangat tidak diinginkan.Tetapi pada pemeriksaan kecepatan pengecualian bukanlah kasus optimasi prematur dalam beberapa kasus karena cara kerja Mono di Unity pada beberapa platform. Faktanya, Unity3D di iOS mendukung beberapa optimasi skrip tingkat lanjut, dan pengecualian yang dinonaktifkan * adalah efek samping dari salah satunya. Ini benar-benar sesuatu yang perlu dipertimbangkan karena optimasi itu terbukti sangat berharga bagi banyak pengguna, menunjukkan kasus yang realistis untuk mempertimbangkan membatasi penggunaan pengecualian di Unity3D. (* pengecualian terkelola dari mesin, bukan kode Anda)
Saya akan mengatakan bahwa di Unity Anda mungkin ingin mengambil pendekatan yang lebih khusus. Ironisnya, jawaban yang sangat rendah pada saat penulisan ini , menunjukkan satu cara saya dapat menerapkan sesuatu seperti ini secara khusus dalam konteks Unity3D (di tempat lain sesuatu seperti ini benar-benar tidak dapat diterima, dan bahkan dalam Unity itu cukup tidak elegan).
Pendekatan lain yang saya pertimbangkan sebenarnya tidak menunjukkan kesalahan sejauh pemanggil yang bersangkutan, melainkan menggunakan
Debug.LogXX
fungsi. Dengan begitu Anda mendapatkan perilaku yang sama seperti melempar pengecualian yang tidak ditangani (karena cara Unity3D menangani mereka) tanpa risiko menempatkan sesuatu dalam keadaan aneh di telepon. Juga pertimbangkan apakah ini benar-benar kesalahan (Sedang mencoba memuat materi yang sama dua kali berarti kesalahan dalam kasus Anda? Atau mungkin ini kasusDebug.LogWarning
yang lebih dapat diterapkan).Dan sehubungan dengan penggunaan hal-hal seperti
Debug.LogXX
fungsi alih-alih pengecualian, Anda masih harus mempertimbangkan apa yang terjadi ketika pengecualian akan dilemparkan dari sesuatu yang mengembalikan nilai (seperti GetMaterial). Saya cenderung untuk mendekati ini dengan melewatkan nol bersamaan dengan mencatat kesalahan ( sekali lagi, hanya di Unity ). Saya kemudian menggunakan cek nol di MonoBehaviors saya untuk memastikan setiap dependensi seperti materi bukan nilai nol, dan menonaktifkan MonoBehavior jika itu. Contoh untuk perilaku sederhana yang memerlukan beberapa dependensi adalah sesuatu seperti ini:SceneData.GetValue<>
mirip dengan contoh Anda dalam hal ini memanggil fungsi pada kamus yang melempar pengecualian. Tapi alih-alih melempar pengecualian yang digunakannyaDebug.LogError
yang memberikan jejak stack seperti pengecualian normal dan mengembalikan null. Pemeriksaan yang mengikuti * akan menonaktifkan perilaku alih-alih membiarkannya terus ada dalam keadaan tidak valid.* cek terlihat seperti itu karena pembantu kecil yang saya gunakan yang mencetak pesan yang diformat ketika menonaktifkan objek game **. Cek nol sederhana dengan
if
kerja di sini (** cek pembantu hanya dikompilasi di build Debug (seperti yang ditegaskan). Menggunakan lambdas dan ekspresi seperti itu di Unity dapat mengurangi kinerja)sumber
Saya suka dua jawaban utama, tetapi ingin menyarankan agar nama fungsi Anda dapat ditingkatkan. Saya sudah terbiasa dengan Java, jadi YMMV, tetapi metode "add" seharusnya, IMO, tidak membuang Pengecualian jika item sudah ada di sana. Seharusnya menambahkan item lagi , atau, jika tujuannya adalah Set, jangan lakukan apa pun. Karena itu bukan perilaku Materials.Add, yang harus diganti nama TryPut atau AddOnce atau AddOrThrow atau serupa.
Demikian juga, LoadMaterial Anda harus diganti nama menjadi LoadIfAbsent atau Put atau TryLoad atau LoadOrThrow (tergantung pada apakah Anda menggunakan jawaban # 1 atau # 2) atau semacamnya.
Ikuti konvensi penamaan C # Unity apa pun itu.
Ini akan sangat berguna jika Anda memiliki fungsi AddFoo dan LoadBar lain di mana diperbolehkan untuk memuat hal yang sama dua kali. Tanpa nama yang jelas pengembang akan frustrasi.
sumber
Semua jawaban menambah ide berharga, saya ingin menggabungkannya:
Putuskan semantik yang dimaksudkan dan yang diharapkan dari
LoadMaterial()
operasi. Setidaknya ada opsi berikut:Prasyarat pada
name
∉ Memuat Bahan : →Ketika prasyarat dilanggar, efeknya
LoadMaterial()
tidak ditentukan (seperti dijawab oleh ComicSansMS ). Hal ini memungkinkan sebagian besar kebebasan dalam implementasi dan perubahan di masa depanLoadMaterial()
. Atau,efek pemanggilan
LoadMaterial(name)
denganname
∈ LoadedMaterial ditentukan; antara:spesifikasi menyatakan bahwa hasilnya idempoten (seperti dalam jawaban oleh Karl Bielefeldt ),
Ketika telah memutuskan semantik, Anda harus memilih implementasi. Opsi dan pertimbangan berikut saat diusulkan:
Lempar pengecualian khusus (seperti yang disarankan oleh Ixrec ) →
Untuk menghindari biaya berulang kali memeriksa
name
∉ Material yang Dimuat, Anda dapat mengikuti saran Marc van Leeuwen :Biarkan Dictionay. Tambahkan pengecualian →
Meskipun, sebagian besar pemilih lebih setuju dengan Ixrec
Alasan tambahan untuk memilih implementasi ini adalah:
ArgumentException
pengecualian, danTetapi jika kedua alasan ini penting, Anda juga dapat menurunkan pengecualian khusus dari
ArgumentException
dan menggunakan yang asli sebagai pengecualian berantai.Jadikan
LoadMaterial()
idempotent sebagai anwser oleh Karl Bielefeldt dan terunggul paling sering (75 kali).Opsi implementasi untuk perilaku ini:
Periksa dengan Dictionary.ContainsKey ()
Selalu panggil Kamus.Tambahkan () tangkap yang
ArgumentException
dilemparnya ketika kunci untuk memasukkan sudah ada dan abaikan pengecualian itu. Dokumentasikan bahwa mengabaikan pengecualian adalah apa yang ingin Anda lakukan dan mengapa.LoadMaterials()
(hampir) selalu dipanggil satu kali untuk masing-masingname
, ini menghindari biaya berulang kali memeriksaname
∉ LoadedMaterials cf. Marc van Leeuwen . Namun,LoadedMaterials()
sering disebut berkali-kali untuk hal yang samaname
, ini menimbulkan biaya (mahal) untuk melemparArgumentException
dan menumpuk.Saya berpikir bahwa ada
TryAdd
analog- metoda untuk TryGet () yang akan memungkinkan Anda untuk menghindari pengecualian mahal melempar dan menumpuk gulungan panggilan gagal ke Dictionary.Add.Tapi
TryAdd
metode ini tampaknya tidak ada.sumber
Kode melempar pengecualian berlebihan.
Jika Anda menelepon:
dengan kunci yang sama ia akan melempar
Pesannya adalah: "Item dengan kunci yang sama telah ditambahkan."
The
ContainsKey
cek berlebihan karena pada dasarnya perilaku yang sama dicapai tanpa itu. Satu-satunya item yang berbeda adalah pesan aktual dalam pengecualian. Jika benar ada manfaat dari memiliki pesan kesalahan khusus, maka kode penjaga memiliki beberapa kelebihan.Kalau tidak, saya mungkin akan menolak penjaga dalam kasus ini.
sumber
Saya pikir ini lebih merupakan pertanyaan tentang desain API daripada pertanyaan konvensi pengkodean.
Apa hasil yang diharapkan (kontrak) dari panggilan:
Jika penelepon dapat mengharapkan / dijamin bahwa setelah memanggil metode ini, material "kayu" dimuat, maka saya tidak melihat alasan untuk melempar pengecualian ketika materi itu sudah dimuat.
Jika terjadi kesalahan saat memuat materi, misalnya, koneksi database tidak dapat dibuka, atau tidak ada material "kayu" di repositori, maka sudah benar untuk melemparkan pengecualian untuk memberi tahu penelepon masalah itu.
sumber
Mengapa tidak mengubah metode sehingga mengembalikan 'benar' jika materi ditambahkan dan 'salah' jika tidak?
sumber