Tampaknya keamanan benang selalu / sering disebutkan sebagai manfaat utama menggunakan jenis yang tidak dapat diubah dan terutama koleksi.
Saya memiliki situasi di mana saya ingin memastikan bahwa suatu metode tidak akan mengubah kamus string (yang tidak dapat diubah dalam C #). Saya ingin membatasi hal-hal sebanyak mungkin.
Namun saya tidak yakin apakah menambahkan ketergantungan pada paket baru (Microsoft Immutable Collections) layak dilakukan. Kinerja juga bukan masalah besar.
Jadi saya kira pertanyaan saya adalah apakah koleksi yang tidak dapat diubah sangat disarankan untuk saat tidak ada persyaratan kinerja yang sulit dan tidak ada masalah keamanan utas? Pertimbangkan bahwa semantik nilai (seperti dalam contoh saya) mungkin atau mungkin tidak menjadi persyaratan juga.
ConcurrentModificationException
yang biasanya disebabkan oleh utas yang sama memutasikan koleksi di utas yang sama, di badanforeach
loop di atas koleksi yang sama.hashCode()
atauequals(Object)
mengakibatkan perubahan, ini dapat menyebabkan kesalahan saat menggunakanCollections
(misalnya, dalam suatuHashSet
objek disimpan dalam "ember" dan setelah bermutasi harus pergi ke yang lain).Jawaban:
Kekekalan menyederhanakan jumlah informasi yang Anda perlu lacak secara mental saat membaca kode nanti . Untuk variabel yang bisa berubah, dan terutama anggota kelas yang bisa berubah, sangat sulit untuk mengetahui status mereka pada baris tertentu yang Anda baca, tanpa menjalankan kode dengan debugger. Data yang tidak dapat diubah mudah untuk dipikirkan - akan selalu sama. Jika Anda ingin mengubahnya, Anda harus membuat nilai baru.
Jujur saya lebih suka membuat hal-hal yang tidak dapat diubah secara default , dan kemudian mengubahnya menjadi bisa berubah di mana terbukti bahwa mereka perlu, apakah ini berarti Anda memerlukan kinerja, atau algoritma yang Anda miliki tidak masuk akal untuk kekekalan.
sumber
val
, dan hanya pada kesempatan yang sangat jarang saya menemukan bahwa saya perlu mengubah sesuatu menjadi avar
. Banyak 'variabel' yang saya definisikan dalam bahasa apa pun hanya ada untuk menyimpan nilai yang menyimpan hasil dari beberapa perhitungan, dan tidak perlu diperbarui.Kode Anda harus menyatakan niat Anda. Jika Anda tidak ingin objek diubah setelah dibuat, buat tidak mungkin untuk dimodifikasi.
Kekekalan memiliki beberapa manfaat:
Niat penulis asli dinyatakan lebih baik.
Bagaimana Anda bisa tahu bahwa dalam kode berikut, memodifikasi nama akan menyebabkan aplikasi menghasilkan pengecualian di suatu tempat nanti?
Lebih mudah untuk memastikan bahwa objek tidak akan muncul dalam keadaan tidak valid.
Anda harus mengontrol ini di konstruktor, dan hanya di sana. Di sisi lain, jika Anda memiliki banyak setter dan metode yang memodifikasi objek, kontrol tersebut mungkin menjadi sangat sulit, terutama ketika, misalnya, dua bidang harus berubah pada saat yang sama agar objek menjadi valid.
Misalnya, objek valid jika alamatnya bukan
null
atau koordinat GPS tidaknull
, tetapi tidak valid jika alamat dan koordinat GPS ditentukan. Bisakah Anda bayangkan untuk memvalidasi ini jika alamat dan koordinat GPS memiliki setter, atau keduanya bisa berubah?Konkurensi.
Omong-omong, dalam kasus Anda, Anda tidak memerlukan paket pihak ketiga. NET Framework. Sudah termasuk
ReadOnlyDictionary<TKey, TValue>
kelas.sumber
Ada banyak alasan utas tunggal untuk menggunakan imutabilitas. Sebagai contoh
Objek A berisi Objek B.
Kode eksternal menanyakan Objek B Anda dan Anda mengembalikannya.
Sekarang Anda memiliki tiga kemungkinan situasi:
Dalam kasus ketiga kode pengguna mungkin tidak menyadari apa yang telah Anda lakukan dan dapat membuat perubahan pada objek, dan dengan demikian mengubah data internal objek Anda dengan Anda tidak memiliki kontrol atau visibilitas dari hal itu terjadi.
sumber
Ketidakmampuan juga dapat sangat menyederhanakan implementasi pemulung. Dari wiki GHC :
sumber
Memperluas apa yang diringkas oleh KChaloux dengan sangat baik ...
Idealnya, Anda memiliki dua jenis bidang, dan karenanya dua jenis kode menggunakannya. Masing-masing bidang tidak dapat diubah, dan kode tidak harus mempertimbangkan mutabilitas; atau bidang dapat diubah, dan kami perlu menulis kode yang mengambil snapshot (
int x = p.x
) atau dengan anggun menangani perubahan tersebut.Dalam pengalaman saya, sebagian besar kode berada di antara keduanya, menjadi kode optimis : itu dengan bebas merujuk data yang bisa berubah, dengan asumsi bahwa panggilan pertama ke
p.x
akan memiliki hasil yang sama dengan panggilan kedua. Dan sebagian besar waktu, ini benar, kecuali ketika ternyata tidak lagi. Ups.Jadi, sungguh, balikkan pertanyaan itu: Apa alasan saya membuat ini bisa berubah ?
Apakah Anda menulis kode defensif? Kekekalan akan menghemat beberapa penyalinan. Apakah Anda menulis kode optimis? Kekekalan akan menghindarkan Anda dari kegilaan dari serangga aneh dan mustahil itu.
sumber
Manfaat lain dari kekekalan adalah bahwa itu adalah langkah pertama dalam mengumpulkan benda-benda yang tidak dapat diubah ini menjadi kumpulan. Kemudian Anda bisa mengelolanya sehingga Anda tidak membuat banyak objek yang secara konseptual dan semantik mewakili hal yang sama. Contoh yang baik adalah String Java.
Ini adalah fenomena linguistik yang terkenal bahwa beberapa kata muncul banyak, mungkin muncul dalam konteks lain juga. Jadi, alih-alih membuat beberapa
String
objek, Anda bisa menggunakan satu objek yang tidak berubah. Tapi kemudian Anda perlu menjaga manajer kolam untuk merawat benda-benda abadi ini.Ini akan menghemat banyak memori. Ini adalah artikel yang menarik untuk dibaca juga: http://en.wikipedia.org/wiki/Zipf%27s_law
sumber
Di Jawa, C #, dan bahasa lain yang serupa, bidang tipe kelas dapat digunakan untuk mengidentifikasi objek, atau untuk merangkum nilai atau status dalam objek tersebut, tetapi bahasa tidak membuat perbedaan antara penggunaan tersebut. Misalkan objek kelas
George
memiliki bidang tipechar[] chars;
. Bidang itu dapat merangkum urutan karakter di:Array yang tidak akan pernah dimodifikasi, atau terpapar pada kode apa pun yang mungkin memodifikasinya, tetapi referensi luarnya mungkin ada.
Array yang tidak memiliki referensi luar, tetapi yang dapat dimodifikasi secara bebas oleh George.
Array yang dimiliki oleh George, tetapi yang mungkin ada pandangan luar yang diharapkan untuk mewakili keadaan George saat ini.
Selain itu, variabel dapat, alih-alih merangkum urutan karakter, merangkum tampilan langsung ke urutan karakter yang dimiliki oleh beberapa objek lain
Jika
chars
saat ini merangkum urutan karakter [angin], dan George inginchars
merangkum urutan karakter [tongkat], ada beberapa hal yang bisa dilakukan George:A. Buat array baru yang berisi karakter [tongkat] dan ubah
chars
untuk mengidentifikasi array itu daripada yang lama.B. Identifikasi entah bagaimana array karakter yang sudah ada sebelumnya yang akan selalu memegang karakter [tongkat] dan berubah
chars
untuk mengidentifikasi array itu daripada yang lama.C. Ubah karakter kedua array yang diidentifikasi oleh
chars
menjadia
.Dalam kasus 1, (A) dan (B) adalah cara yang aman untuk mencapai hasil yang diinginkan. Dalam kasus (2), (A) dan (C) aman, tetapi (B) tidak akan [itu tidak akan menyebabkan masalah langsung, tetapi karena George akan menganggap bahwa ia memiliki kepemilikan array, ia akan menganggap bahwa ia memiliki dapat mengubah array sesuka]. Dalam kasus (3), pilihan (A) dan (B) akan mematahkan pandangan luar, dan hanya pilihan (C) yang benar. Jadi, mengetahui bagaimana mengubah urutan karakter yang dienkapsulasi oleh bidang membutuhkan pengetahuan jenis bidang semantik apa itu.
Jika alih-alih menggunakan bidang tipe
char[]
, yang merangkum urutan karakter yang berpotensi bisa berubah, kode menggunakan tipeString
, yang merangkum urutan karakter yang tidak berubah, semua masalah di atas hilang. Semua bidang tipeString
merangkum urutan karakter menggunakan objek sharable yang tidak akan pernah berubah. Alhasil, jika bidang jenisString
merangkum "angin", satu-satunya cara untuk membuatnya merangkum "tongkat" adalah untuk membuatnya mengidentifikasi objek yang berbeda - yang memegang "tongkat". Dalam kasus di mana kode menyimpan satu-satunya referensi ke objek, bermutasi objek mungkin lebih efisien daripada membuat yang baru, tetapi setiap kali kelas bisa berubah, perlu untuk membedakan antara berbagai cara yang dengannya ia dapat merangkum nilai. Secara pribadi saya pikir Apps Hungaria seharusnya digunakan untuk ini (saya akan mempertimbangkan empat penggunaanchar[]
menjadi tipe yang berbeda secara semantik, meskipun sistem jenis menganggapnya identik - persis seperti situasi di mana Apps Hungaria bersinar), tetapi karena itu bukan cara termudah untuk menghindari ambiguitas seperti itu untuk merancang tipe yang tidak dapat diubah yang hanya merangkum nilai satu arah.sumber
Ada beberapa contoh yang bagus di sini, tetapi saya ingin bergabung dengan beberapa contoh pribadi di mana ketidakberdayaan membantu satu ton. Dalam kasus saya, saya mulai merancang struktur data konkuren yang tidak dapat diubah terutama dengan harapan hanya bisa menjalankan kode secara paralel dengan tumpang tindih membaca dan menulis dan tidak harus khawatir tentang kondisi balapan. Ada ceramah yang diberikan John Carmack semacam itu yang mengilhami saya untuk melakukannya di mana ia berbicara tentang gagasan semacam itu. Ini adalah struktur yang cukup mendasar dan cukup sepele untuk diterapkan seperti ini:
Tentu saja dengan beberapa lonceng dan peluit di sana seperti bisa menghilangkan elemen dalam waktu yang konstan dan meninggalkan lubang yang dapat direklamasi di belakang dan membuat balok dihilangkan jika mereka menjadi kosong dan berpotensi dibebaskan untuk contoh abadi yang diberikan. Tetapi pada dasarnya untuk memodifikasi struktur, Anda memodifikasi versi "sementara" dan secara atomis melakukan perubahan yang Anda buat untuk mendapatkan salinan abadi yang tidak menyentuh yang lama, dengan versi baru hanya membuat salinan baru dari blok yang harus dibuat unik saat menyalin dangkal dan referensi menghitung yang lain.
Namun, saya tidak menemukan itu bahwaberguna untuk keperluan multithreading. Bagaimanapun, masih ada masalah konseptual di mana, katakanlah, sistem fisika menerapkan fisika secara bersamaan ketika seorang pemain mencoba untuk memindahkan elemen di dalam dunia. Salinan data transformasi yang tidak berubah yang Anda ikuti, yang ditransformasikan oleh pemain atau yang ditransformasikan oleh sistem fisika? Jadi saya belum benar-benar menemukan solusi yang bagus dan sederhana untuk masalah konseptual dasar ini kecuali untuk memiliki struktur data yang bisa berubah yang hanya mengunci dengan cara yang lebih cerdas dan mencegah tumpang tindih membaca dan menulis ke bagian yang sama dari buffer untuk menghindari kemacetan utas. Itu sesuatu yang tampaknya John Carmack telah menemukan cara untuk menyelesaikannya dalam permainannya; setidaknya dia membicarakannya seperti dia hampir bisa melihat solusi tanpa membuka mobil cacing. Saya belum sampai sejauh dia dalam hal itu. Yang bisa saya lihat adalah pertanyaan desain tanpa akhir jika saya mencoba untuk memaralelkan semua yang ada di sekitar yang tidak berubah. Saya berharap saya bisa menghabiskan satu hari mencerna otaknya karena sebagian besar usaha saya dimulai dengan ide-ide yang dia buang.
Namun demikian, saya menemukan nilai yang sangat besar dari struktur data yang tidak dapat diubah ini di bidang lain. Saya bahkan menggunakannya sekarang untuk menyimpan gambar yang benar-benar aneh dan memang membuat akses acak memerlukan beberapa instruksi lagi (shift kanan dan bitwise
and
bersama dengan lapisan tipuan pointer), tapi saya akan membahas manfaat di bawah ini.Batalkan Sistem
Salah satu tempat paling cepat yang saya temukan untuk mendapat manfaat dari ini adalah sistem undo. Kode sistem undo dulu merupakan salah satu hal yang paling rawan kesalahan di area saya (industri FX visual), dan tidak hanya dalam produk yang saya kerjakan tetapi dalam produk pesaing (sistem undo mereka juga terkelupas) karena ada begitu banyak perbedaan jenis data yang perlu dikhawatirkan akan dibatalkan dan diulang dengan benar (sistem properti, perubahan data mesh, perubahan shader yang tidak berbasis properti seperti bertukar satu dengan yang lain, perubahan hierarki adegan seperti mengubah induk dari anak, perubahan gambar / tekstur, dll. dll.).
Jadi jumlah kode undo yang diperlukan sangat besar, sering menyaingi jumlah kode yang mengimplementasikan sistem yang sistem undo harus mencatat perubahan keadaan. Dengan bersandar pada struktur data ini, saya bisa mengembalikan sistem undo menjadi seperti ini:
Biasanya kode di atas akan sangat tidak efisien ketika data adegan Anda membentang gigabytes untuk menyalinnya secara keseluruhan. Tetapi struktur data ini hanya menyalin hal-hal dangkal yang tidak berubah, dan itu sebenarnya membuatnya cukup murah menyimpan salinan seluruh kondisi aplikasi yang tidak berubah. Jadi sekarang saya dapat mengimplementasikan sistem undo semudah kode di atas dan hanya fokus menggunakan struktur data yang tidak berubah ini untuk membuat penyalinan bagian yang tidak berubah dari status aplikasi lebih murah dan lebih murah dan lebih murah. Sejak saya mulai menggunakan struktur data ini, semua proyek pribadi saya memiliki sistem undo hanya menggunakan pola sederhana ini.
Sekarang masih ada beberapa overhead di sini. Terakhir kali saya mengukur sekitar 10 kilobyte hanya untuk menyalin seluruh kondisi aplikasi tanpa membuat perubahan apa pun (ini tidak tergantung pada kompleksitas adegan karena adegan diatur dalam hierarki, jadi jika tidak ada yang di bawah perubahan root, hanya root dangkal disalin tanpa harus turun ke anak-anak). Itu jauh dari 0 byte seperti yang diperlukan untuk sistem undo yang hanya menyimpan delta. Tetapi pada 10 kilobyte membatalkan overhead per operasi, itu masih hanya megabyte per 100 operasi pengguna. Ditambah lagi, saya masih bisa menekannya lebih jauh di masa depan jika diperlukan.
Pengecualian-Keamanan
Pengecualian keamanan dengan aplikasi yang kompleks bukanlah masalah sepele. Namun, ketika status aplikasi Anda tidak berubah dan Anda hanya menggunakan objek sementara untuk mencoba melakukan transaksi perubahan atom, maka itu pada dasarnya pengecualian-aman karena jika ada bagian dari kode yang dilemparkan, transien dibuang sebelum memberikan salinan baru yang tidak dapat diubah . Sehingga meremehkan salah satu hal tersulit yang selalu saya temukan untuk mendapatkan yang benar dalam basis kode C ++ kompleks.
Terlalu banyak orang sering hanya menggunakan sumber daya yang sesuai dengan RAII di C ++ dan berpikir itu sudah cukup untuk menjadi pengecualian-aman. Seringkali tidak, karena suatu fungsi umumnya dapat menyebabkan efek samping untuk menyatakan di luar yang lokal untuk ruang lingkupnya. Anda biasanya harus mulai berurusan dengan pelindung lingkup dan logika rollback canggih dalam kasus tersebut. Struktur data ini membuatnya jadi saya sering tidak perlu repot dengan itu karena fungsinya tidak menyebabkan efek samping. Mereka mengembalikan salinan status aplikasi yang tidak berubah dan bukannya mengubah status aplikasi.
Pengeditan Tidak Merusak
Pengeditan non-destruktif pada dasarnya adalah operasi layering / stacking / menghubungkan bersama tanpa menyentuh data pengguna asli (hanya input data dan data output tanpa menyentuh input). Biasanya sepele untuk diterapkan dengan aplikasi gambar sederhana seperti Photoshop dan mungkin tidak mendapat manfaat sebanyak itu dari struktur data ini karena banyak operasi mungkin hanya ingin mengubah setiap piksel dari keseluruhan gambar.
Namun, dengan editing mesh non-destruktif, misalnya, banyak operasi sering ingin mengubah hanya sebagian dari mesh. Satu operasi mungkin hanya ingin memindahkan beberapa simpul di sini. Yang lain mungkin hanya ingin membagi beberapa poligon di sana. Di sini struktur data yang tidak berubah membantu satu ton dalam menghindari kebutuhan untuk harus membuat seluruh salinan seluruh mesh hanya untuk mengembalikan versi baru dari mesh dengan sebagian kecil dari itu berubah.
Meminimalkan Efek Samping
Dengan struktur ini di tangan, itu juga membuatnya mudah untuk menulis fungsi yang meminimalkan efek samping tanpa menimbulkan penalti kinerja yang besar untuk itu. Saya mendapati diri saya semakin banyak menulis fungsi yang hanya mengembalikan seluruh struktur data yang tidak dapat diubah dengan nilai hari ini tanpa menimbulkan efek samping, bahkan ketika itu tampak agak boros.
Sebagai contoh, biasanya godaan untuk mengubah banyak posisi mungkin untuk menerima matriks dan daftar objek dan mengubah mereka dengan cara yang bisa berubah. Hari-hari ini saya menemukan diri saya baru saja mengembalikan daftar objek baru.
Ketika Anda memiliki lebih banyak fungsi seperti ini di sistem Anda yang tidak menimbulkan efek samping, itu pasti membuatnya lebih mudah untuk mempertimbangkan kebenarannya serta menguji kebenarannya.
Manfaat Salinan Murah
Jadi bagaimanapun, ini adalah area di mana saya menemukan yang paling banyak digunakan dari struktur data yang tidak dapat diubah (atau struktur data yang persisten). Saya juga agak terlalu bersemangat pada awalnya dan membuat pohon yang tidak bisa diubah dan daftar tertaut yang tidak berubah dan tabel hash yang tidak dapat berubah, tetapi seiring waktu saya jarang menemukan banyak kegunaan untuk itu. Saya terutama menemukan sebagian besar penggunaan array mirip chunky yang tidak bisa diubah dalam diagram di atas.
Saya juga masih memiliki banyak kode yang dapat digunakan dengan mutables (merasa ini adalah kebutuhan praktis setidaknya untuk kode tingkat rendah), tetapi status aplikasi utama adalah hierarki yang tidak dapat diubah, menelusuri dari adegan yang tidak dapat diubah ke komponen yang tidak dapat diubah di dalamnya. Beberapa komponen yang lebih murah masih disalin secara penuh, tetapi yang paling mahal seperti jerat dan gambar menggunakan struktur yang tidak dapat diubah untuk memungkinkan salinan sebagian murah itu hanya dari bagian-bagian yang perlu diubah.
sumber
Sudah banyak jawaban bagus. Ini hanyalah info tambahan yang agak spesifik untuk .NET. Saya sedang menggali posting blog .NET lama dan menemukan ringkasan manfaat dari sudut pandang pengembang Microsoft Immutable Collections:
sumber