Program CS sekolah saya menghindari penyebutan pemrograman berorientasi objek, jadi saya telah melakukan beberapa bacaan sendiri untuk menambahnya - khususnya, Konstruksi Perangkat Lunak Berorientasi Objek oleh Bertrand Meyer.
Meyer berulang kali menekankan bahwa kelas harus menyembunyikan sebanyak mungkin informasi tentang implementasi mereka, yang masuk akal. Secara khusus, ia berargumen berulang kali bahwa atribut (yaitu, statis, sifat-sifat non-komputer kelas) dan rutinitas (sifat kelas yang sesuai dengan panggilan fungsi / prosedur) harus dapat dibedakan satu sama lain.
Sebagai contoh, jika sebuah kelas Person
memiliki atribut age
, ia menegaskan bahwa seharusnya tidak mungkin untuk mengatakan, dari notasi, apakah Person.age
berkorespondensi secara internal dengan sesuatu seperti return current_year - self.birth_date
atau hanya return self.age
, di mana self.age
telah didefinisikan sebagai atribut konstan. Ini masuk akal bagi saya. Namun, ia kemudian mengklaim sebagai berikut:
Dokumentasi klien standar untuk suatu kelas, yang dikenal sebagai bentuk pendek dari kelas, akan dirancang agar tidak mengungkapkan apakah fitur yang diberikan adalah atribut atau fungsi (dalam kasus yang bisa jadi salah satu).
yaitu, ia mengklaim bahwa bahkan dokumentasi untuk kelas harus menghindari menentukan apakah seorang "pengambil" melakukan perhitungan atau tidak.
Ini, saya tidak mengikuti. Bukankah dokumentasi satu-satunya tempat di mana penting untuk memberi tahu pengguna tentang perbedaan ini? Jika saya mendesain database yang penuh dengan Person
objek, bukankah penting untuk mengetahui apakah Person.age
panggilan itu mahal atau tidak , jadi saya dapat memutuskan apakah akan menerapkan semacam cache atau tidak untuknya? Apakah saya salah paham apa yang dia katakan, atau dia hanya contoh ekstrem dari filosofi desain OOP?
sumber
Jawaban:
Saya tidak berpikir poin Meyer adalah bahwa Anda tidak harus memberi tahu pengguna ketika Anda memiliki operasi yang mahal. Jika fungsi Anda akan mencapai basis data, atau membuat permintaan ke server web, dan menghabiskan beberapa jam menghitung, kode lain akan perlu tahu itu.
Tetapi pembuat kode yang menggunakan kelas Anda tidak perlu tahu apakah Anda telah menerapkan:
atau:
Karakteristik kinerja antara kedua pendekatan itu sangat minimal sehingga tidak masalah. Coder yang menggunakan kelas Anda benar-benar tidak peduli yang Anda miliki. Itu maksud meyer.
Tapi itu tidak selalu terjadi, misalnya, misalkan Anda memiliki metode ukuran pada wadah. Itu bisa diterapkan:
atau
atau bisa juga:
Perbedaan antara dua yang pertama seharusnya tidak menjadi masalah. Tetapi yang terakhir bisa memiliki konsekuensi kinerja yang serius. Itu sebabnya STL, misalnya, mengatakan bahwa
.size()
adalahO(1)
. Itu tidak mendokumentasikan dengan tepat bagaimana ukuran dihitung, tetapi itu memberi saya karakteristik kinerja.Jadi : mendokumentasikan masalah kinerja. Jangan mendokumentasikan detail implementasi. Saya tidak peduli bagaimana std :: mengurutkan barang-barang saya, selama itu melakukannya dengan benar dan efisien. Kelas Anda juga tidak boleh mendokumentasikan bagaimana penghitungannya, tetapi jika sesuatu memiliki profil kinerja yang tidak terduga, dokumentasikan hal itu.
sumber
// O(n) Traverses the entire user list.
len
gagal melakukan ini ... (Dalam setidaknya beberapa situasi, itu adalahO(n)
, seperti yang kita pelajari dalam sebuah proyek di perguruan tinggi ketika saya menyarankan menyimpan panjang alih-alih menghitung ulang setiap iterasi loop)O(n)
?Dari pandangan akademis atau puritan CS, tentu saja kegagalan untuk menjelaskan dalam dokumentasi apa pun tentang internal penerapan fitur. Itu karena pengguna kelas idealnya tidak membuat asumsi tentang implementasi internal kelas. Jika implementasinya berubah, idealnya tidak ada pengguna yang tidak akan menyadari hal itu - fitur ini membuat abstraksi dan internal harus tetap tersembunyi sepenuhnya.
Namun, sebagian besar program dunia nyata menderita "Hukum abstraksi bocor" karya Joel Spolsky , yang mengatakan
Itu berarti, hampir tidak mungkin untuk membuat abstraksi kotak hitam lengkap dari fitur kompleks. Dan gejala khas dari ini adalah masalah kinerja. Jadi untuk program dunia nyata, mungkin menjadi sangat penting panggilan mana yang mahal dan mana yang tidak, dan dokumentasi yang baik harus mencakup informasi itu (atau harus mengatakan di mana pengguna kelas diizinkan untuk membuat asumsi tentang kinerja, dan di mana tidak ).
Jadi saran saya adalah: sertakan informasi tentang panggilan mahal yang potensial jika Anda menulis dokumen untuk program dunia nyata, dan mengecualikannya untuk program yang Anda tulis hanya untuk tujuan pendidikan kursus CS Anda, mengingat setiap pertimbangan kinerja harus disimpan sengaja di luar ruang lingkup.
sumber
Anda dapat menulis apakah panggilan yang diberikan mahal atau tidak. Lebih baik, gunakan konvensi penamaan seperti
getAge
untuk akses cepat danloadAge
ataufetchAge
untuk pencarian mahal. Anda pasti ingin memberi tahu pengguna jika metode ini melakukan IO.Setiap detail yang Anda berikan dalam dokumentasi itu seperti kontrak yang harus dihormati oleh kelas. Ini harus menginformasikan tentang perilaku penting. Seringkali, Anda akan melihat indikasi kompleksitas dengan notasi O besar. Tetapi Anda biasanya ingin singkat dan to the point.
sumber
Iya nih.
Inilah sebabnya saya terkadang menggunakan
Find()
fungsi untuk menunjukkan bahwa menelepon mungkin perlu waktu. Ini lebih dari sebuah konvensi daripada yang lain. Waktu yang dibutuhkan untuk fungsi atau atribut untuk kembali ada bedanya program (meskipun mungkin kepada pengguna), meskipun di antara programer ada adalah harapan bahwa, jika dideklarasikan sebagai atribut, biaya untuk menyebutnya harus rendah.Bagaimanapun, harus ada informasi yang cukup dalam kode itu sendiri untuk menyimpulkan apakah sesuatu adalah fungsi atau atribut, jadi saya tidak benar-benar melihat kebutuhan untuk mengatakan itu dalam dokumentasi.
sumber
Get
metode di atas atribut untuk menunjukkan operasi yang lebih berat. Saya telah melihat cukup kode di mana pengembang menganggap sebuah properti hanyalah sebuah accessor dan menggunakannya beberapa kali alih-alih menyimpan nilai ke variabel lokal, dan dengan demikian mengeksekusi algoritma yang sangat kompleks lebih dari sekali. Jika tidak ada konvensi untuk tidak mengimplementasikan properti seperti itu dan dokumentasi tidak mengisyaratkan kompleksitas, maka saya berharap siapa pun yang memiliki aplikasi seperti itu selamat mencoba.get
metode yang setara dengan akses atribut dan jadi tidak mahal.Penting untuk dicatat bahwa edisi pertama buku ini ditulis pada tahun 1988, pada hari-hari awal OOP. Orang-orang ini bekerja dengan bahasa berorientasi objek yang lebih murni yang banyak digunakan saat ini. Bahasa OO kami yang paling populer saat ini - C ++, C # & Java - memiliki beberapa perbedaan yang cukup signifikan dari cara bahasa-bahasa awal, yang lebih murni OO, bekerja.
Dalam bahasa seperti C ++ & Java, Anda harus membedakan antara mengakses atribut dan pemanggilan metode. Ada dunia perbedaan antara
instance.getter_method
daninstance.getter_method()
. Yang satu benar-benar mendapatkan nilai Anda dan yang lain tidak.Ketika bekerja dengan bahasa OO yang lebih murni, dari persuasi Smalltalk atau Ruby (yang nampak bahwa bahasa Eiffel yang digunakan dalam buku ini), itu menjadi saran yang sangat valid. Bahasa-bahasa ini secara implisit akan memanggil metode untuk Anda. Tidak ada perbedaan antara
instance.attribute
daninstance.getter_method
.Saya tidak akan berkeringat saat ini atau menganggapnya terlalu dogmatis. Maksudnya bagus - Anda tidak ingin pengguna kelas Anda khawatir tentang detail implementasi yang tidak relevan - tetapi itu tidak diterjemahkan dengan baik ke sintaksis banyak bahasa modern.
sumber
Sebagai pengguna, Anda tidak perlu tahu bagaimana sesuatu diterapkan.
Jika kinerja adalah masalah, sesuatu harus dilakukan di dalam implementasi kelas, bukan di sekitarnya. Oleh karena itu, tindakan yang benar adalah memperbaiki implementasi kelas atau untuk mengajukan bug ke pengelola.
sumber
string.length
akan dihitung ulang setiap kali itu berubah.Setiap dokumentasi yang berorientasi pada programmer yang gagal memberi tahu programmer tentang biaya kerumitan rutinitas / metode adalah salah.
Kami mencari untuk menghasilkan metode bebas efek samping.
Jika eksekusi suatu metode telah menjalankan kompleksitas waktu dan / atau kompleksitas memori selain dari itu
O(1)
, dalam lingkungan yang dibatasi memori atau waktu, itu dapat dianggap memiliki efek samping .The prinsip paling mengejutkan dilanggar jika metode melakukan sesuatu yang sama sekali tak terduga - dalam hal ini, memonopoli memori atau membuang-buang waktu CPU.
sumber
Saya pikir Anda memahaminya dengan benar, tetapi saya juga berpikir Anda memiliki poin yang bagus. jika
Person.age
diimplementasikan dengan perhitungan yang mahal, maka saya pikir saya juga ingin melihatnya dalam dokumentasi. Itu bisa membuat perbedaan antara menyebutnya berulang kali (jika itu operasi yang murah) atau memanggilnya sekali dan menyimpan nilai (jika mahal). Saya tidak tahu pasti, tetapi saya pikir dalam hal ini Meyer mungkin setuju bahwa peringatan dalam dokumentasi harus dimasukkan.Cara lain untuk menangani ini mungkin dengan memperkenalkan atribut baru yang namanya menyiratkan bahwa perhitungan yang panjang mungkin terjadi (seperti
Person.ageCalculatedFromDB
) dan kemudianPerson.age
mengembalikan nilai yang di-cache di dalam kelas, tetapi ini mungkin tidak selalu sesuai, dan tampaknya terlalu rumit hal, menurut saya.sumber
age
aPerson
, Anda harus memanggil metode untuk mendapatkannya. Jika penelepon mulai melakukan hal-hal yang terlalu pintar untuk menghindari harus melakukan perhitungan, mereka berisiko memiliki implementasi mereka tidak bekerja dengan benar karena mereka melewati batas ulang tahun. Implementasi yang mahal di kelas akan bermanifestasi sebagai masalah kinerja yang dapat di-root oleh profil dan perbaikan seperti caching dapat dilakukan di dalam kelas, di mana semua penelepon akan melihat manfaat (dan hasil yang benar).Person
kelas, tapi saya pikir pertanyaannya dimaksudkan sebagai lebih umum danPerson.age
itu hanya sebuah contoh. Mungkin ada beberapa kasus di mana akan lebih masuk akal bagi penelepon untuk memilih - mungkin callee memiliki dua algoritma yang berbeda untuk menghitung nilai yang sama: satu cepat tetapi tidak akurat, satu jauh lebih lambat tetapi lebih akurat (rendering 3D datang ke pikiran sebagai satu tempat di mana itu mungkin terjadi), dan dokumentasi harus menyebutkan ini.Dokumentasi untuk kelas berorientasi objek sering melibatkan pertukaran antara memberikan pengelola kelas fleksibilitas untuk mengubah desainnya, versus memungkinkan konsumen kelas untuk memanfaatkan sepenuhnya potensinya. Jika kelas berubah akan memiliki sejumlah properti yang akan memiliki tertentu yang sebenarnya hubungan satu sama lain (misalnya
Left
,Right
, danWidth
properti dari integer-coordinate grid-aligned rectangle), orang mungkin mendesain kelas untuk menyimpan kombinasi dua properti dan menghitung yang ketiga, atau orang mungkin mendesainnya untuk menyimpan ketiganya. Jika tidak ada antarmuka yang memperjelas properti mana yang disimpan, programmer kelas mungkin dapat mengubah desain jika hal itu terbukti bermanfaat karena beberapa alasan. Sebaliknya, jika misalnya dua properti diekspos sebagaifinal
bidang dan yang ketiga tidak, maka versi kelas yang akan datang harus selalu menggunakan dua properti yang sama sebagai "basis".Jika properti tidak memiliki hubungan yang tepat (misalnya karena mereka
float
ataudouble
bukan daripadaint
), maka mungkin perlu untuk mendokumentasikan properti mana yang "mendefinisikan" nilai suatu kelas. Sebagai contoh, meskipunLeft
plusWidth
seharusnya samaRight
, matematika floating-point sering tidak tepat. Sebagai contoh, anggap suatuRectangle
yang menggunakan tipeFloat
acceptLeft
danWidth
sebagai parameter konstruktor dikonstruksi denganLeft
diberikan sebagai1234567f
danWidth
sebagai1.1f
.float
Representasi terbaik dari jumlah tersebut adalah 1234568,125 [yang dapat ditampilkan sebagai 1234568,13]; yang lebih kecil berikutnyafloat
adalah 1234568.0. Jika kelas benar-benar menyimpanLeft
danWidth
, itu dapat melaporkan nilai lebar seperti yang ditentukan. Namun, jika konstruktor dihitungRight
berdasarkan pass-inLeft
danWidth
, dan kemudian dihitungWidth
berdasarkanLeft
danRight
, itu akan melaporkan lebar1.25f
daripada sebagai pass-in1.1f
.Dengan kelas yang bisa berubah, hal-hal bisa menjadi lebih menarik, karena perubahan ke salah satu nilai yang saling terkait akan menyiratkan perubahan ke setidaknya satu yang lain, tetapi mungkin tidak selalu jelas yang mana. Dalam beberapa kasus, mungkin yang terbaik untuk menghindari metode yang "set" satu properti seperti itu, melainkan juga harus metode untuk misalnya
SetLeftAndWidth
atauSetLeftAndRight
, atau make jelas apa sifat yang sedang ditetapkan dan yang berubah (misalnyaMoveRightEdgeToSetWidth
,ChangeWidthToSetLeftEdge
atauMoveShapeToSetRightEdge
) .Kadang-kadang mungkin berguna untuk memiliki kelas yang melacak nilai properti mana yang telah ditentukan dan yang telah dihitung dari yang lain. Misalnya, kelas "momen dalam waktu" mungkin menyertakan waktu absolut, waktu lokal, dan zona waktu offset. Seperti banyak jenis seperti itu, diberikan dua potong informasi, satu dapat menghitung yang ketiga. Tahu yang manasepotong informasi dihitung, namun, kadang-kadang mungkin penting. Sebagai contoh, misalkan suatu peristiwa dicatat terjadi pada "17:00 UTC, zona waktu -5, waktu setempat 12:00 siang", dan satu kemudian menemukan bahwa zona waktu seharusnya -6. Jika ada yang tahu bahwa UTC direkam dari server, catatan tersebut harus dikoreksi menjadi "18:00 UTC, zona waktu -6, waktu setempat 12:00 siang"; Jika seseorang memasukkan waktu lokal di luar jam maka harus "17:00 UTC, zona waktu -6, waktu setempat 11:00". Tanpa mengetahui apakah waktu global atau lokal harus dianggap "lebih dapat dipercaya", bagaimanapun, tidak mungkin untuk mengetahui koreksi mana yang harus diterapkan. Namun, jika catatan melacak waktu yang ditentukan, perubahan zona waktu dapat meninggalkan yang satu itu sementara mengubah yang lain.
sumber
Semua aturan ini tentang cara menyembunyikan informasi di kelas sangat masuk akal dengan asumsi perlu melindungi seseorang dari pengguna kelas yang akan membuat kesalahan dengan membuat ketergantungan pada implementasi internal.
Tidak apa-apa untuk membangun perlindungan seperti itu, jika kelas memiliki penonton seperti itu. Tetapi ketika pengguna menulis panggilan ke fungsi di kelas Anda, mereka mempercayai Anda dengan rekening bank waktu eksekusi mereka.
Inilah hal yang sering saya lihat:
Objek memiliki bit "dimodifikasi" yang mengatakan jika mereka, dalam beberapa hal, ketinggalan zaman. Cukup sederhana, tetapi kemudian mereka memiliki objek bawahan, jadi mudah untuk membiarkan "dimodifikasi" menjadi fungsi yang menjumlahkan semua objek bawahan. Kemudian jika ada beberapa lapisan objek bawahan (kadang-kadang berbagi objek yang sama lebih dari sekali) sederhana "Dapatkan" dari properti "dimodifikasi" akhirnya dapat mengambil sebagian kecil dari waktu eksekusi yang sehat.
Ketika suatu objek diubah dengan cara tertentu, diasumsikan bahwa objek lain yang tersebar di sekitar perangkat lunak perlu "diberitahu". Ini dapat terjadi pada beberapa lapisan struktur data, jendela, dll. Yang ditulis oleh programmer yang berbeda, dan kadang-kadang berulang dalam rekursi tak terbatas yang perlu dijaga. Bahkan jika semua penulis dari penangan notifikasi tersebut cukup berhati-hati untuk tidak membuang waktu, seluruh interaksi gabungan dapat berakhir dengan menggunakan sebagian kecil waktu eksekusi yang tidak terduga dan menyakitkan, dan asumsi bahwa itu hanya "perlu" dibuat dengan riang.
SO, saya suka melihat kelas-kelas yang menyajikan antarmuka abstrak bersih yang bagus untuk dunia luar, tetapi saya ingin memiliki beberapa gagasan tentang bagaimana mereka bekerja, jika hanya untuk memahami pekerjaan apa yang menyelamatkan saya. Tetapi di luar itu, saya cenderung merasa bahwa "lebih sedikit lebih banyak". Orang-orang begitu terpikat pada struktur data sehingga mereka berpikir lebih banyak lebih baik, dan ketika saya melakukan tuning kinerja alasan besar universal untuk masalah kinerja adalah kepatuhan terhadap struktur data yang membengkak membangun cara orang diajarkan.
Jadi pergilah.
sumber
Menambahkan detail implementasi seperti "hitung atau tidak" atau "info kinerja" membuatnya lebih sulit untuk menjaga kode dan dokumen tetap sinkron .
Contoh:
Jika Anda memiliki metode "mahal-kinerja" apakah Anda ingin mendokumentasikan "mahal" juga untuk semua kelas yang menggunakan metode ini? bagaimana jika Anda mengubah implementasinya menjadi tidak mahal lagi. Apakah Anda ingin memperbarui info ini ke semua konsumen juga?
Tentu saja baik bagi pengelola kode untuk mendapatkan semua info penting dari dokumentasi kode, tetapi saya tidak suka dokumentasi yang mengklaim sesuatu yang tidak valid lagi (tidak sinkron dengan kode)
sumber
Ketika jawaban yang diterima sampai pada kesimpulan:
dan kode yang didokumentasikan sendiri dianggap lebih baik daripada dokumentasi yang mengikuti bahwa nama metode harus menyatakan hasil kinerja yang tidak biasa.
Jadi tetap
Person.age
untukreturn current_year - self.birth_date
tetapi jika metode ini menggunakan loop untuk menghitung usia (ya):Person.calculateAge()
sumber