Di Java, C # dan banyak bahasa lain yang diketik dengan sangat kuat, kami digunakan untuk menulis kode seperti ini:
public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }
Beberapa bahasa yang diperiksa secara dinamis tidak memberikan kata kunci untuk mengekspresikan tingkat "privasi" dari anggota kelas tertentu dan hanya mengandalkan konvensi pengkodean. Python misalnya awalan anggota pribadi dengan garis bawah:
_m(self): pass
Dapat dikatakan bahwa menyediakan kata kunci tersebut dalam bahasa yang dicentang secara dinamis akan menambah sedikit penggunaan karena hanya diperiksa pada saat runtime.
Namun, saya juga tidak dapat menemukan alasan yang baik untuk memberikan kata kunci ini dalam bahasa yang diperiksa secara statis. Saya menemukan persyaratan untuk mengisi kode saya dengan kata kunci yang agak bertele-tele seperti protected
mengganggu dan mengganggu. Sejauh ini, saya belum berada dalam situasi di mana kesalahan kompiler yang disebabkan oleh kata kunci ini akan menyelamatkan saya dari bug. Sebaliknya, saya berada dalam situasi di mana posisi yang salah protected
mencegah saya menggunakan perpustakaan.
Dengan mengingat hal ini, pertanyaan saya adalah:
Apakah informasi yang disembunyikan lebih dari sekadar konvensi antara programmer yang digunakan untuk mendefinisikan apa yang merupakan bagian dari antarmuka resmi suatu kelas?
Bisakah itu digunakan untuk mengamankan negara rahasia kelas dari serangan? Bisakah refleksi mengalahkan mekanisme ini? Apa yang membuatnya berharga bagi kompiler untuk menegakkan penyembunyian informasi?
sumber
Jawaban:
Saya sedang belajar untuk sertifikasi Java dan sejumlah besar menyangkut itu, bagaimana mengelola pengubah akses. Dan itu masuk akal dan harus digunakan dengan benar.
Saya telah bekerja dengan python dan, selama perjalanan belajar saya, saya pernah mendengar bahwa dengan python, konvensi itu ada karena orang-orang, yang bekerja dengannya, harus tahu apa artinya dan bagaimana menerapkannya. Yang mengatakan, di
_m(self): pass
garis bawah akan mengingatkan saya untuk tidak main-main dengan bidang itu. Tetapi apakah semua orang akan mengikuti konvensi itu? Saya bekerja dengan javascript dan saya harus mengatakan, mereka tidak . Kadang-kadang saya perlu memeriksa masalah dan alasannya adalah orang itu melakukan sesuatu yang tidak seharusnya dia lakukan ...baca diskusi ini tentang garis bawah python terkemuka
Dari apa yang saya katakan, Ya.
Ya itu dapat digunakan untuk mengamankan negara rahasia kelas dan itu seharusnya tidak hanya digunakan untuk itu tetapi untuk mencegah pengguna dari mengacaukan keadaan objek untuk mengubah perilaku mereka menjadi sesuatu yang mereka pikir seharusnya menjadi cara objek seharusnya memiliki sedang bekerja. Anda, sebagai pengembang, harus merencanakan dan memikirkannya dan mendesain kelas Anda dengan cara yang masuk akal, sehingga perilakunya tidak akan dirusak. Dan kompiler, sebagai teman yang baik, akan membantu Anda menjaga kode seperti yang Anda rancang dengan menerapkan kebijakan akses.
Diedit Ya, refleksi bisa, periksa komentar
Pertanyaan terakhir adalah pertanyaan yang menarik dan saya bersedia membaca jawaban mengenai hal itu.
sumber
Specifier akses "pribadi" bukan tentang kesalahan kompiler yang dihasilkannya saat pertama kali Anda melihatnya. Pada kenyataannya ini tentang mencegah Anda mengakses sesuatu yang masih dapat berubah ketika implementasi kelas yang menahan anggota pribadi berubah.
Dengan kata lain, tidak mengizinkan Anda untuk menggunakannya saat masih berfungsi mencegah Anda dari menggunakannya secara tidak sengaja saat tidak lagi berfungsi.
Seperti yang dikatakan Delnan di bawah ini, konvensi awalan tidak menganjurkan penggunaan anggota secara tidak sengaja yang dapat berubah selama konvensi diikuti dan dipahami dengan benar. Untuk pengguna jahat (atau bodoh), ia tidak melakukan apa pun untuk menghentikan mereka mengakses anggota tersebut dengan semua konsekuensi yang mungkin terjadi. Dalam bahasa dengan dukungan bawaan untuk penentu akses, hal ini tidak terjadi dalam ketidaktahuan (kesalahan kompiler), dan menonjol seperti jempol ketika berbahaya (konstruksi aneh untuk sampai ke anggota pribadi).
Specifier akses "terlindungi" adalah cerita yang berbeda - jangan anggap ini hanya "tidak terlalu umum" atau "sangat mirip pribadi". "Dilindungi" berarti Anda mungkin ingin menggunakan fungsionalitas itu ketika Anda berasal dari kelas yang berisi anggota yang dilindungi. Anggota yang dilindungi adalah bagian dari "antarmuka ekstensi" yang akan Anda gunakan untuk menambahkan fungsionalitas di atas kelas yang ada tanpa mengubah kelas yang ada itu sendiri.
Jadi, rekap pendek:
sumber
Jika Anda menulis kode yang akan dikonsumsi oleh orang lain, maka penyembunyian informasi dapat memberikan antarmuka yang lebih mudah untuk dijalankan. "Orang lain" bisa jadi pengembang lain di tim Anda, pengembang yang mengonsumsi API yang Anda tulis secara komersial, atau bahkan diri Anda di masa depan yang "tidak bisa mengingat cara kerja dang". Cara ini lebih mudah untuk bekerja dengan kelas yang hanya memiliki 4 metode yang tersedia daripada yang memiliki 40 metode.
sumber
_member
atauprivate member
di kelas saya dapat diabaikan, selama programmer lain mengerti bahwa ia hanya harus melihatpublic
atau anggota yang tidak diawali.Saya menyarankan agar untuk latar belakang, Anda mulai dengan membaca tentang invarian kelas .
Sebuah invarian adalah, untuk membuat cerita panjang pendek, sebuah asumsi tentang keadaan kelas yang seharusnya tetap benar sepanjang masa kelas.
Mari kita gunakan contoh C # yang sangat sederhana:
Apa yang terjadi di sini?
addresses
diinisialisasi pada konstruksi kelas.readonly
, jadi tidak ada dari dalam bisa menyentuhnya setelah konstruksi (ini tidak selalu benar / diperlukan, tapi itu berguna di sini).Send
metode ini dapat membuat asumsi yangaddresses
tidak akan pernah terjadinull
. Itu tidak harus melakukan pemeriksaan itu karena tidak ada cara bahwa nilainya dapat diubah.Jika kelas lain diizinkan untuk menulis ke
addresses
lapangan (yaitu jika itupublic
), maka asumsi ini tidak lagi berlaku. Setiap metode lain di dalam kelas yang bergantung pada bidang itu harus mulai melakukan pemeriksaan nol eksplisit , atau berisiko menabrak program.Jadi ya, ini lebih dari sekadar "konvensi"; semua pengubah akses pada anggota kelas secara kolektif membentuk seperangkat asumsi tentang kapan dan bagaimana keadaan itu dapat diubah. Asumsi-asumsi tersebut kemudian dimasukkan ke dalam anggota dan kelas yang tergantung dan saling tergantung sehingga programmer tidak perlu berpikir tentang seluruh keadaan program pada saat yang sama. Kemampuan untuk membuat asumsi adalah elemen penting dalam mengelola kompleksitas dalam perangkat lunak.
Untuk pertanyaan-pertanyaan lain ini:
Iya dan tidak. Kode keamanan, seperti kebanyakan kode, akan bergantung pada invarian tertentu. Pengubah akses tentu saja bermanfaat sebagai penunjuk arah bagi penelepon tepercaya yang tidak seharusnya dipusingkan olehnya. Kode berbahaya tidak akan peduli, tetapi kode jahat tidak harus melalui kompiler juga.
Tentu saja bisa. Tetapi refleksi membutuhkan kode panggilan untuk memiliki tingkat privilege / trust. Jika Anda menjalankan kode jahat dengan kepercayaan penuh dan / atau hak administratif, maka Anda telah kehilangan pertempuran itu.
Compiler sudah tidak menegakkan itu. Begitu juga runtime di .NET, Java, dan lingkungan lain seperti itu - opcode yang digunakan untuk memanggil metode tidak akan berhasil jika metode ini pribadi. Satu-satunya cara di sekitar pembatasan itu memerlukan kode tepercaya / tinggi, dan kode tinggi selalu bisa langsung menulis ke memori program. Ini diberlakukan sebanyak yang bisa ditegakkan tanpa memerlukan sistem operasi kustom.
sumber
_
menyatakan kontrak, tetapi tidak memberlakukannya. Itu mungkin membuatnya sulit untuk mengabaikan kontrak, tetapi tidak membuat sulit untuk melanggar kontrak, terutama jika dibandingkan dengan metode yang membosankan dan sering membatasi seperti Refleksi. Sebuah konvensi mengatakan, "Anda tidak boleh merusak ini". Pengubah akses berkata, "Anda tidak dapat memecahkan ini". Saya tidak mencoba mengatakan bahwa satu pendekatan lebih baik daripada yang lain - keduanya baik-baik saja tergantung pada filosofi Anda, tetapi mereka tetap pendekatan yang sangat berbeda.private
/protected
access seperti kondom. Anda tidak harus menggunakan satu, tetapi jika Anda tidak akan melakukannya, maka Anda sebaiknya yakin tentang waktu Anda dan pasangan Anda. Itu tidak akan mencegah tindakan jahat, dan bahkan mungkin tidak bekerja setiap saat, tetapi itu pasti akan menurunkan risiko dari kecerobohan, dan melakukannya sejauh lebih efektif daripada mengatakan "harap berhati-hati". Oh, dan jika Anda memilikinya, tetapi jangan menggunakannya, maka jangan mengharapkan simpati dari saya.Menyembunyikan informasi jauh lebih dari sekadar konvensi; mencoba untuk mengelilinginya sebenarnya dapat merusak fungsi kelas dalam banyak kasus. Sebagai contoh, itu adalah praktik yang cukup umum untuk menyimpan nilai dalam
private
variabel, mengeksposnya menggunakanprotected
ataupublic
properti pada waktu yang sama, dan dalam pengambil, periksa nol dan lakukan inisialisasi yang diperlukan (yaitu, pemuatan malas). Atau menyimpan sesuatu dalamprivate
variabel, memaparkannya menggunakan properti, dan dalam setter, periksa untuk melihat apakah nilainya berubah dan memecatPropertyChanging
/PropertyChanged
peristiwa. Tetapi tanpa melihat implementasi internal, Anda tidak akan pernah tahu semua yang terjadi di balik layar.sumber
Penyembunyian informasi berevolusi dari filosofi desain top-down. Python telah disebut bahasa bottom-up .
Penyembunyian informasi diberlakukan dengan baik di tingkat kelas di Java, C ++ dan C #, jadi itu bukan benar-benar konvensi di tingkat ini. Sangat mudah untuk membuat kelas menjadi "kotak hitam", dengan antarmuka publik dan detail (pribadi) yang tersembunyi.
Seperti yang Anda tunjukkan, dalam Python terserah programmer untuk mengikuti konvensi tidak menggunakan apa yang dimaksudkan untuk disembunyikan, karena semuanya terlihat.
Bahkan dengan Java, C ++ atau C #, di beberapa titik menyembunyikan informasi menjadi sebuah konvensi. Tidak ada kontrol akses pada level abstraksi tertinggi yang terlibat dalam arsitektur perangkat lunak yang lebih kompleks. Misalnya, di Jawa Anda dapat menemukan penggunaan ".internal." nama paket. Ini murni konvensi penamaan karena tidak mudah untuk menyembunyikan informasi semacam ini melalui aksesibilitas paket saja.
Satu bahasa yang berusaha mendefinisikan akses secara formal adalah Eiffel. Artikel ini menunjukkan beberapa kelemahan lain dalam menyembunyikan informasi seperti Java.
Latar belakang: Penyembunyian informasi diusulkan pada tahun 1971 oleh David Parnas . Dia menunjukkan dalam artikel itu bahwa penggunaan informasi tentang modul-modul lain dapat "secara merusak meningkatkan konektivitas struktur sistem." Menurut ide ini, kurangnya penyembunyian informasi dapat menyebabkan sistem yang sangat erat dan sulit untuk dipelihara. Dia melanjutkan dengan:
sumber
Ruby dan PHP memilikinya dan menerapkannya pada saat runtime.
Titik penyembunyian informasi sebenarnya adalah informasi yang ditampilkan. Dengan "menyembunyikan" detail internal, tujuannya menjadi jelas dari sudut pandang luar. Ada bahasa, yang merangkul ini. Di Jawa, akses default ke paket internal, di haXe ke protected. Anda secara eksplisit mendeklarasikannya kepada publik untuk mengeksposnya.
Inti dari semua ini adalah untuk membuat kelas Anda mudah digunakan dengan hanya memperlihatkan antarmuka yang sangat koheren. Anda ingin sisanya dilindungi, sehingga tidak ada orang pintar datang dan mengacaukan keadaan internal Anda untuk menipu kelas Anda untuk melakukan apa yang dia inginkan.
Juga, ketika pengubah akses diberlakukan pada saat runtime, Anda dapat menggunakannya untuk menegakkan tingkat keamanan tertentu, tetapi saya tidak berpikir ini adalah solusi yang sangat bagus.
sumber
pydoc
menghapus_prefixedMembers
dari antarmuka. Antarmuka yang berantakan tidak ada hubungannya dengan pilihan kata kunci yang diperiksa oleh kompiler.Akses pengubah jelas dapat melakukan sesuatu yang konvensi tidak bisa.
Misalnya, di Jawa Anda tidak dapat mengakses anggota / bidang pribadi kecuali jika Anda menggunakan refleksi.
Oleh karena itu jika saya menulis antarmuka untuk plugin dan dengan benar menolak hak untuk memodifikasi bidang pribadi melalui refleksi (dan untuk mengatur manajer keamanan :)), saya dapat mengirim beberapa objek ke fungsi yang dilaksanakan oleh siapa pun dan tahu bahwa ia tidak dapat mengakses bidang privasinya.
Tentu saja ada beberapa bug keamanan yang akan memungkinkannya untuk mengatasi hal ini, tetapi ini tidak penting secara filosofis (tetapi dalam praktiknya memang demikian).
Jika pengguna menjalankan antarmuka saya di lingkungannya, ia memiliki kontrol dan dengan demikian dapat menghindari pengubah akses.
sumber
Ini tidak banyak untuk menyelamatkan penulis aplikasi dari bug untuk membiarkan penulis perpustakaan memutuskan bagian mana dari implementasi mereka berkomitmen untuk mempertahankan.
Jika saya punya perpustakaan
Saya mungkin ingin dapat menggantikan
foo
implementasi dan mungkin berubahfooHelper
secara radikal. Jika sekelompok orang telah memutuskan untuk menggunakanfooHelper
terlepas dari semua peringatan dalam dokumentasi saya maka saya mungkin tidak dapat melakukannya.private
memungkinkan penulis perpustakaan memecah perpustakaan menjadi metode ukuran yang dapat dikelola (danprivate
kelas pembantu) tanpa takut bahwa mereka akan dipaksa untuk mempertahankan detail internal tersebut selama bertahun-tahun.Di samping catatan, di Jawa
private
tidak dipaksakan oleh kompiler, tetapi oleh verifier bytecode Java .Di Jawa, tidak hanya refleksi yang dapat menimpa mekanisme ini. Ada dua macam
private
di Jawa. Jenisprivate
yang mencegah satu kelas luar dari mengaksesprivate
anggota kelas luar lain yang diperiksa oleh bytecode verifier, tetapi jugaprivate
s yang digunakan oleh kelas dalam melalui metode accessor sintetis paket-swasta seperti dalamKarena kelas
B
(benar-benar bernamaC$B
) menggunakani
, kompiler menciptakan metodeB
pengakses sintetis yang memungkinkan untuk mengaksesC.i
dengan cara yang melewati verifikasi bytecode. Sayangnya, karenaClassLoader
memungkinkan Anda untuk membuat kelas dari abyte[]
, itu cukup sederhana untuk mendapatkan di privat yangC
telah terpapar ke kelas batin dengan membuat kelas baruC
di paket yang mungkin jikaC
tabung belum disegel.private
Penegakan yang tepat membutuhkan koordinasi antara classloader, bytecode verifier, dan kebijakan keamanan yang dapat mencegah akses reflektif ke privat.Iya nih. "Secure decomposition" adalah mungkin ketika programmer dapat berkolaborasi sementara masing-masing menjaga properti keamanan modul mereka - saya tidak harus mempercayai pembuat modul kode lain untuk tidak melanggar properti keamanan modul saya.
Bahasa Kemampuan Obyek seperti Joe-E menggunakan penyembunyian informasi dan cara lain untuk memungkinkan penguraian yang aman:
Makalah yang ditautkan dari halaman itu memberikan contoh bagaimana
private
penegakan memungkinkan dekomposisi yang aman.sumber
Menyembunyikan informasi adalah salah satu perhatian utama dari desain perangkat lunak yang baik. Lihatlah semua makalah Dave Parnas mulai akhir 70-an. Pada dasarnya, jika Anda tidak dapat menjamin bahwa keadaan internal modul Anda konsisten, Anda tidak dapat menjamin apa pun tentang perilakunya. Dan satu-satunya cara Anda dapat menjamin keadaan internalnya adalah menjaganya tetap pribadi dan hanya mengizinkannya diubah dengan cara yang Anda berikan.
sumber
Dengan menjadikan perlindungan sebagai bagian dari bahasa, Anda mendapatkan sesuatu: kepastian yang masuk akal.
Jika saya membuat variabel menjadi pribadi, saya memiliki kepastian yang masuk akal bahwa itu akan disentuh hanya oleh kode di dalam kelas itu atau secara eksplisit menyatakan teman-teman dari kelas itu. Cakupan kode yang dapat secara wajar menyentuh nilai tersebut terbatas dan didefinisikan secara eksplisit.
Sekarang adakah cara untuk menyiasati perlindungan sintaksis? Benar; kebanyakan bahasa memilikinya. Di C ++, Anda selalu bisa menampilkan kelas ke beberapa tipe lain dan menyodok bitnya. Di Java dan C #, Anda dapat mencerminkan diri Anda ke dalamnya. Dan seterusnya.
Namun, melakukan ini sulit . Sudah jelas bahwa Anda melakukan sesuatu yang seharusnya tidak Anda lakukan. Anda tidak dapat melakukannya secara tidak sengaja (di luar penulisan liar di C ++). Anda harus rela berpikir, "Saya akan menyentuh sesuatu yang saya diberitahu untuk tidak oleh kompiler saya." Anda harus rela melakukan sesuatu yang tidak masuk akal .
Tanpa perlindungan sintaksis, seorang programmer dapat secara tidak sengaja mengacaukan segalanya. Anda harus mengajarkan konvensi kepada pengguna, dan mereka harus mengikuti konvensi itu setiap waktu . Jika tidak, dunia menjadi sangat tidak aman.
Tanpa perlindungan sintaksis, tanggung jawab ada pada orang yang salah: banyak orang menggunakan kelas. Mereka harus mengikuti konvensi atau kejahatan yang tidak ditentukan akan terjadi. Gosok itu: kejahatan yang tidak ditentukan dapat terjadi.
Tidak ada yang lebih buruk daripada API di mana, jika Anda melakukan hal yang salah, semuanya mungkin akan tetap bekerja. Itu memberikan jaminan palsu kepada pengguna bahwa mereka telah melakukan hal yang benar, bahwa semuanya baik-baik saja, dll.
Dan bagaimana dengan pengguna baru ke bahasa itu? Mereka tidak hanya harus belajar dan mengikuti sintaks yang sebenarnya (dipaksakan oleh kompiler), mereka sekarang harus mengikuti konvensi ini (ditegakkan oleh rekan-rekan mereka yang terbaik). Dan jika tidak, maka tidak ada hal buruk yang akan terjadi. Apa yang terjadi jika seorang programmer tidak mengerti mengapa konvensi itu ada? Apa yang terjadi ketika dia mengatakan "mengacaukannya" dan hanya menusuk kemaluanmu? Dan apa yang dia pikirkan jika semuanya terus bekerja?
Dia pikir konvensi itu bodoh. Dan dia tidak akan mengikutinya lagi. Dan dia akan memberitahu semua temannya untuk tidak repot-repot juga.
sumber