Mengapa Kode Bersih menyarankan menghindari variabel yang dilindungi?

168

Kode Bersih menyarankan untuk menghindari variabel yang dilindungi di bagian "Jarak Vertikal" pada bab "Memformat":

Konsep-konsep yang berkaitan erat harus dijaga secara vertikal dekat satu sama lain. Jelas aturan ini tidak berfungsi untuk konsep yang termasuk dalam file terpisah. Tapi kemudian konsep yang berkaitan erat tidak boleh dipisahkan menjadi file yang berbeda kecuali Anda memiliki alasan yang sangat bagus. Memang, ini adalah salah satu alasan mengapa variabel yang dilindungi harus dihindari .

Apa alasannya?

Matsemann
sumber
7
menunjukkan contoh di mana Anda pikir Anda perlu itu
nyamuk
63
Saya tidak akan menganggap banyak di dalam buku itu sebagai Injil.
Rig
40
@Rig Anda berbatasan dengan penistaan ​​di bagian ini dengan komentar seperti itu.
Ryathal
40
Saya setuju dengan itu. Saya sudah membaca buku itu dan bisa memiliki pendapat sendiri tentang itu.
Rig
10
Saya telah membaca buku itu dan meskipun saya setuju dengan sebagian besar dari apa yang ada di sana, saya juga tidak akan mengatakan saya menganggapnya sebagai Injil. Saya pikir setiap situasi berbeda .. dan bisa mengikuti panduan yang disajikan dalam buku ini cukup sulit untuk banyak proyek.
Simon Whitehead

Jawaban:

277

Variabel yang dilindungi harus dihindari karena:

  1. Mereka cenderung mengarah ke masalah YAGNI . Kecuali Anda memiliki kelas turunan yang benar-benar melakukan hal-hal dengan anggota yang dilindungi, jadikan ini pribadi.
  2. Mereka cenderung mengarah ke masalah LSP . Variabel yang dilindungi umumnya memiliki beberapa invarian intrinsik yang dikaitkan dengan mereka (atau kalau tidak mereka akan menjadi publik). Pewaris kemudian perlu mempertahankan properti-properti itu, yang bisa dikacaukan atau dilanggar orang.
  3. Mereka cenderung melanggar OCP . Jika kelas dasar membuat terlalu banyak asumsi tentang anggota yang dilindungi, atau pewaris terlalu fleksibel dengan perilaku kelas, itu dapat menyebabkan perilaku kelas dasar yang dimodifikasi oleh ekstensi itu.
  4. Mereka cenderung mengarah pada warisan untuk ekstensi daripada komposisi. Hal ini cenderung mengarah pada penggabungan yang lebih ketat, lebih banyak pelanggaran terhadap SRP , pengujian yang lebih sulit, dan banyak hal lain yang termasuk dalam diskusi 'komposisi pendukung atas warisan'.

Tapi seperti yang Anda lihat, semua ini 'cenderung'. Terkadang anggota yang dilindungi adalah solusi yang paling elegan. Dan fungsi yang dilindungi cenderung memiliki lebih sedikit masalah ini. Tetapi ada beberapa hal yang menyebabkan mereka diperlakukan dengan hati-hati. Dengan apa pun yang membutuhkan perawatan semacam itu, orang akan membuat kesalahan dan dalam dunia pemrograman yang berarti bug dan masalah desain.

Telastyn
sumber
Terima kasih banyak alasannya. Namun, buku itu menyebutkannya secara khusus dalam konteks pemformatan dan jarak vertikal, itu bagian yang saya ingin tahu.
Matsemann
2
Tampaknya Paman Bob mengacu pada jarak vertikal ketika seseorang merujuk pada anggota yang dilindungi di antara kelas, dan bukan di kelas yang sama.
Robert Harvey
5
@ Matemann - tentu. Jika saya mengingat buku itu dengan akurat, bagian itu berfokus pada keterbacaan dan kemampuan menemukan kode. Jika variabel dan fungsi berfungsi pada sebuah konsep, mereka harus ditutup dalam kode. Anggota yang dilindungi akan digunakan oleh tipe turunan, yang tentu tidak bisa dekat dengan anggota yang dilindungi karena mereka berada dalam file yang sama sekali berbeda.
Telastyn
2
@ steve314 Saya tidak bisa membayangkan skenario di mana kelas dasar dan semua pewarisnya hanya akan hidup dalam satu file. Tanpa contoh, saya cenderung menganggapnya sebagai penyalahgunaan warisan atau abstraksi yang buruk.
Telastyn
3
YAGNI = Anda Tidak Akan Membutuhkannya LSP = Prinsip Pergantian Liskov OCP = Prinsip Buka / Tutup SRP = Prinsip Tanggung Jawab Tunggal
Bryan Glazer
54

Itu alasan yang sama persis Anda menghindari global, hanya pada skala yang lebih kecil. Itu karena sulit untuk menemukan di mana-mana variabel sedang dibaca, atau lebih buruk, ditulis, dan sulit untuk membuat penggunaan konsisten. Jika penggunaannya terbatas, seperti ditulis di satu tempat yang jelas di kelas turunan dan dibaca di satu tempat yang jelas di kelas dasar, maka variabel yang dilindungi dapat membuat kode lebih jelas dan ringkas. Jika Anda tergoda untuk membaca dan menulis variabel mau tak mau di seluruh banyak file, lebih baik untuk merangkumnya.

Karl Bielefeldt
sumber
26

Saya belum membaca buku itu, tetapi saya bisa mengerti apa yang dimaksud Paman Bob.

Jika Anda memakai protectedsesuatu, itu artinya suatu kelas dapat mewarisinya. Tetapi variabel anggota seharusnya milik kelas di mana mereka terkandung; ini adalah bagian dari enkapsulasi dasar. Memakai protectedvariabel anggota memecah enkapsulasi karena sekarang kelas turunan memiliki akses ke detail implementasi dari kelas dasar. Ini masalah yang sama yang terjadi ketika Anda membuat variabel publicdi kelas biasa.

Untuk memperbaiki masalah, Anda dapat merangkum variabel dalam properti yang dilindungi, seperti:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Ini memungkinkan nameuntuk secara aman diatur dari kelas turunan menggunakan argumen konstruktor, tanpa mengekspos detail implementasi dari kelas dasar.

Robert Harvey
sumber
Anda membuat properti publik dengan setter yang dilindungi. Bidang yang dilindungi harus diselesaikan dengan properti yang dilindungi dengan setter pribadi.
Andy
2
+1 sekarang. Setter bisa dilindungi juga, saya kira, tetapi id id basis dapat menegakkan jika nilai baru valid atau tidak.
Andy
Hanya untuk menawarkan sudut pandang yang berlawanan - saya membuat semua variabel saya dilindungi di kelas dasar. Jika mereka hanya dapat diakses melalui antarmuka publik maka itu seharusnya bukan kelas turunan, seharusnya hanya berisi objek baseclass. Tapi saya juga menghindari hierarki lebih dari 2 kelas
Martin Beckett
2
Ada perbedaan besar antara protecteddan publicanggota. Jika BaseTypemengekspos anggota publik, itu menyiratkan bahwa semua jenis turunan harus memiliki anggota yang sama bekerja dengan cara yang sama, karena sebuah DerivedTypeinstance dapat diteruskan ke kode yang menerima BaseTypereferensi dan berharap untuk menggunakan anggota itu. Sebaliknya, jika BaseTypemengekspos protectedanggota, maka DerivedTypemungkin berharap untuk mengakses anggota itu base, tetapi basetidak bisa lain dari a BaseType.
supercat
18

Argumen Paman Bob terutama salah satu dari jarak: jika Anda memiliki konsep yang penting untuk suatu kelas, bundel konsep tersebut bersama-sama dengan kelas dalam file itu. Tidak dipisahkan di dua file pada disk.

Variabel anggota yang dilindungi tersebar di dua tempat, dan, agaknya terlihat seperti sulap. Anda referensi variabel ini, namun tidak didefinisikan di sini ... di mana adalah itu didefinisikan? Dan dengan demikian perburuan dimulai. Lebih baik hindari protectedsama sekali, argumennya.

Sekarang saya tidak percaya bahwa aturan harus dipatuhi surat sepanjang waktu (seperti: Anda tidak akan menggunakan protected). Lihatlah semangat apa yang dia dapat ... bundel hal-hal terkait bersama menjadi satu file - gunakan teknik pemrograman dan fitur untuk melakukan itu. Saya akan merekomendasikan agar Anda tidak menganalisis secara berlebihan dan terperangkap dalam detail tentang ini.

J. Polfer
sumber
9

Beberapa jawaban yang sangat bagus sudah ada di sini. Tapi satu hal yang perlu ditambahkan:

Dalam sebagian besar bahasa OO modern itu adalah kebiasaan yang baik (dalam Java AFAIK diperlukan) untuk menempatkan setiap kelas ke file sendiri (tolong jangan mengerti tentang C ++ di mana Anda sering memiliki dua file).

Jadi hampir tidak mungkin untuk menjaga fungsi-fungsi dalam kelas turunan dengan mengakses variabel yang dilindungi dari kelas dasar "secara vertikal dekat" dalam kode sumber dengan definisi variabel. Tetapi idealnya mereka harus disimpan di sana, karena variabel yang dilindungi dimaksudkan untuk penggunaan internal, yang membuat fungsi acessing mereka sering "konsep yang terkait erat".

Doc Brown
sumber
1
Tidak di Swift. Menariknya, Swift tidak memiliki "dilindungi" tetapi ia memiliki "fileprivate" yang menggantikan "teman" yang dilindungi "dan C ++.
gnasher729
@ gnasher729 - tentu saja, Swift memiliki banyak Smalltalk di warisannya. Smalltalk tidak memiliki apa pun selain variabel pribadi dan metode publik. Dan privat dalam Smalltalk berarti privat: dua objek bahkan dari kelas yang sama tidak dapat mengakses variabel satu sama lain.
Jules
4

Gagasan dasarnya adalah bahwa "bidang" (variabel tingkat instance) yang dinyatakan sebagai terproteksi mungkin lebih terlihat daripada seharusnya, dan lebih sedikit "terlindungi" daripada yang mungkin Anda inginkan. Tidak ada pengubah akses di C / C ++ / Java / C # yang setara dengan "hanya dapat diakses oleh kelas anak dalam rakitan yang sama", sehingga memberi Anda kemampuan untuk menentukan anak-anak Anda sendiri yang dapat mengakses bidang di rakitan Anda, tetapi tidak mengizinkan anak-anak yang dibuat di majelis lain akses yang sama; C # memiliki pengubah internal dan terlindungi, tetapi menggabungkan mereka membuat akses "internal atau dilindungi", bukan "internal dan dilindungi". Jadi, bidang yang dilindungi dapat diakses oleh anak mana pun, baik Anda menulisnya atau orang lain melakukannya. Dengan demikian dilindungi adalah pintu terbuka bagi seorang peretas.

Juga, bidang menurut definisi mereka tidak memiliki validasi yang melekat dalam mengubahnya. di C # Anda dapat membuat satu hanya baca, yang membuat tipe nilai secara efektif konstan dan tipe referensi tidak dapat diinisialisasi ulang (tapi masih sangat bisa berubah), tapi hanya itu saja. Dengan demikian, bahkan dilindungi, anak-anak Anda (yang tidak dapat Anda percayai) memiliki akses ke bidang ini dan dapat mengaturnya untuk sesuatu yang tidak valid, membuat keadaan objek tidak konsisten (sesuatu yang harus dihindari).

Cara yang diterima untuk bekerja dengan bidang adalah menjadikannya pribadi dan mengaksesnya dengan properti, dan / atau metode pengambil dan penyetel. Jika semua konsumen kelas membutuhkan nilainya, jadikan pengambil (setidaknya) publik. Jika hanya anak-anak yang membutuhkannya, buat rajin rajin rajin

Pendekatan lain yang menjawab pertanyaan adalah bertanya pada diri sendiri; mengapa kode dalam metode anak memerlukan kemampuan untuk mengubah data keadaan saya secara langsung? Apa yang dikatakan tentang kode itu? Itulah argumen "jarak vertikal" di wajahnya. Jika ada kode pada seorang anak yang harus secara langsung mengubah status induk, maka mungkin kode itu harus menjadi milik orangtua di tempat pertama?

KeithS
sumber
4

Ini diskusi yang menarik.

Sejujurnya, alat yang baik dapat mengurangi banyak masalah "vertikal" ini. Bahkan, menurut pendapat saya gagasan tentang "file" sebenarnya adalah sesuatu yang menghambat pengembangan perangkat lunak - sesuatu yang sedang dikerjakan proyek Light Table (http://www.chris-granger.com/2012/04/12/light- tabel --- a-new-ide-concept /).

Keluarkan file dari gambar, dan cakupan yang dilindungi menjadi jauh lebih menarik. Konsep yang dilindungi menjadi paling jelas dalam bahasa seperti SmallTalk - di mana setiap warisan hanya memperluas konsep asli kelas. Itu harus melakukan segalanya, dan memiliki segalanya, yang dilakukan oleh kelas induknya.

Menurut pendapat saya, hierarki kelas harus sedangkal mungkin, dengan sebagian besar ekstensi berasal dari komposisi. Dalam model seperti itu, saya tidak melihat alasan mengapa variabel pribadi tidak dapat dilindungi. Jika kelas diperluas harus mewakili ekstensi termasuk semua perilaku kelas induk (yang saya percaya seharusnya, dan karena itu percaya metode pribadi secara inheren buruk), mengapa kelas diperluas tidak mewakili penyimpanan data juga?

Dengan kata lain - dalam hal apa pun di mana saya merasa variabel pribadi akan lebih cocok daripada yang dilindungi, pewarisan bukanlah solusi untuk masalah di tempat pertama.

Spronkey
sumber
0

Seperti dikatakan di sana, ini hanya salah satu alasan, yaitu, kode yang terhubung secara logis harus ditempatkan di dalam entitas yang terhubung secara fisik (file, paket dan lainnya). Pada skala yang lebih kecil ini sama dengan alasan Anda tidak menempatkan kelas yang membuat DB query di dalam paket dengan kelas yang menampilkan UI.

Namun alasan utama bahwa variabel yang dilindungi tidak direkomendasikan adalah karena mereka cenderung memecah enkapsulasi. Variabel dan metode harus memiliki visibilitas sebatas mungkin; untuk referensi lebih lanjut lihat Joshua Bloch's Java Efektif , Butir 13 - "Minimalkan aksesibilitas kelas dan anggota".

Namun, Anda tidak boleh menggunakan semua iklan litteram ini, hanya sebagai guildline; jika variabel yang dilindungi sangat buruk mereka tidak akan ditempatkan di dalam bahasa di tempat pertama. Tempat yang masuk akal untuk bidang yang dilindungi karena berada di dalam kelas dasar dari kerangka pengujian (kelas uji integrasi memperpanjangnya).

m3th0dman
sumber
1
Jangan berasumsi bahwa karena bahasa memungkinkan, itu boleh dilakukan. Saya tidak yakin benar-benar ada alasan dewa untuk mengizinkan bidang yang dilindungi; kami sebenarnya melarang mereka di majikan saya.
Andy
2
@Andy Anda belum membaca apa yang saya katakan; Saya mengatakan bahwa bidang yang dilindungi tidak direkomendasikan dan alasan untuk ini. Jelas ada skenario di mana variabel yang dilindungi diperlukan; Anda mungkin menginginkan nilai akhir konstan hanya dalam subkelas.
m3th0dman
Anda mungkin ingin menulis ulang beberapa pos Anda saat itu, karena Anda tampaknya menggunakan variabel dan bidang secara bergantian dan membuat eksplisit bahwa Anda akan mempertimbangkan hal-hal seperti const const yang dilindungi sebagai pengecualian.
Andy
3
@Andy Cukup jelas bahwa karena saya merujuk ke variabel yang dilindungi, saya tidak berbicara tentang variabel lokal atau parameter tetapi ke bidang. Saya juga menyebutkan skenario di mana variabel yang dilindungi tidak konstan berguna; jika salah bagi perusahaan untuk menegakkan cara programmer menulis kode.
m3th0dman
1
Jika itu jelas saya tidak akan bingung dan menurunkan Anda. Aturan dibuat oleh pengembang senior kami, seperti keputusan kami untuk menjalankan StyleCop, FxCop dan memerlukan pengujian unit dan ulasan kode.
Andy
0

Saya menemukan menggunakan variabel yang dilindungi untuk tes JUnit dapat bermanfaat. Jika Anda menjadikannya pribadi maka Anda tidak dapat mengaksesnya selama pengujian tanpa refleksi. Ini bisa bermanfaat jika Anda menguji proses yang rumit melalui banyak perubahan status.

Anda bisa menjadikannya pribadi, dengan metode getter yang dilindungi, tetapi jika variabel hanya akan digunakan secara eksternal selama tes JUnit, saya tidak yakin itu solusi yang lebih baik.

Nick
sumber
-1

Ya, saya setuju itu agak aneh mengingat bahwa (seingat saya) buku ini juga membahas semua variabel non-pribadi sebagai sesuatu yang harus dihindari.

Saya pikir masalah dengan pengubah yang dilindungi adalah bahwa tidak hanya anggota yang terpapar ke subkelas tetapi juga dapat dilihat oleh seluruh paket. Saya pikir inilah aspek ganda yang Paman Bob ambil pengecualian di sini. Tentu saja, seseorang tidak dapat selalu memiliki metode yang dilindungi, dan dengan demikian kualifikasi variabel yang dilindungi.

Saya pikir pendekatan yang lebih menarik adalah membuat semuanya publik, yang akan memaksa Anda untuk memiliki kelas kecil, yang pada gilirannya menjadikan seluruh metrik jarak vertikal agak diperdebatkan.

CurtainDog
sumber