Saya menghadapi masalah dengan apa yang saya rasa terlalu banyak abstraksi dalam basis kode (atau setidaknya menghadapinya). Sebagian besar metode dalam basis kode telah diabstraksi untuk mengambil dalam induk tertinggi A dalam basis kode, tetapi anak B dari orangtua ini memiliki atribut baru yang memengaruhi logika beberapa metode tersebut. Masalahnya adalah bahwa atribut tersebut tidak dapat diperiksa dalam metode tersebut karena input diabstraksi menjadi A, dan A tentu saja tidak memiliki atribut ini. Jika saya mencoba membuat metode baru untuk menangani B secara berbeda, itu dipanggil untuk duplikasi kode. Saran dari pimpinan teknologi saya adalah membuat metode bersama yang menggunakan parameter boolean, tetapi masalah dengan ini adalah bahwa beberapa orang melihat ini sebagai "aliran kontrol tersembunyi," di mana metode bersama memiliki logika yang mungkin tidak terlihat oleh pengembang masa depan , dan juga metode bersama ini akan tumbuh terlalu rumit / berbelit-belit sekali jika atribut masa depan perlu ditambahkan, bahkan jika dipecah menjadi metode bersama yang lebih kecil. Ini juga meningkatkan sambungan, mengurangi kekompakan, dan melanggar prinsip tanggung jawab tunggal, yang ditunjukkan oleh seseorang di tim saya.
Pada dasarnya, banyak abstraksi dalam basis kode ini membantu mengurangi duplikasi kode, tetapi itu membuat metode perluasan / perubahan lebih sulit ketika mereka dibuat untuk mengambil abstraksi tertinggi. Apa yang harus saya lakukan dalam situasi seperti ini? Saya berada di pusat untuk disalahkan, meskipun semua orang tidak bisa menyetujui apa yang mereka anggap baik, jadi pada akhirnya itu menyakitkan saya.
Jawaban:
Tidak semua duplikasi kode dibuat sama.
Katakanlah Anda memiliki metode yang mengambil dua parameter dan menambahkannya bersama-sama disebut
total()
. Katakanlah Anda memiliki satu lagi yang dipanggiladd()
. Implementasinya terlihat sangat identik. Haruskah mereka digabung menjadi satu metode? TIDAK!!!Prinsip Jangan-Ulangi-Sendiri atau KERING bukan tentang pengulangan kode. Ini tentang menyebarkan keputusan, ide, sehingga jika Anda pernah mengubah ide Anda, Anda harus menulis ulang di mana-mana Anda menyebarkan ide itu. Blegh. Itu buruk. Jangan lakukan itu. Alih-alih gunakan KERING untuk membantu Anda membuat keputusan di satu tempat .
Tapi KERING bisa rusak menjadi kebiasaan memindai kode mencari implementasi yang serupa yang tampaknya seperti copy dan paste dari tempat lain. Ini adalah bentuk KERING mati otak. Sial, Anda bisa melakukan ini dengan alat analisis statis. Itu tidak membantu karena mengabaikan titik KERING yang membuat kode fleksibel.
Jika persyaratan total saya berubah, saya mungkin harus mengubah
total
implementasi saya . Itu tidak berarti saya perlu mengubahadd
implementasi saya . Jika beberapa goober menghancurkan mereka bersama menjadi satu metode saya sekarang dalam sedikit rasa sakit yang tidak perlu.Berapa banyak rasa sakit? Tentunya saya hanya bisa menyalin kode dan membuat metode baru ketika saya membutuhkannya. Jadi bukan masalah besar kan? Malarky! Jika tidak ada yang lain Anda membayar saya nama baik! Nama baik sulit didapat dan tidak merespons dengan baik ketika Anda mengutak-atik maknanya. Nama baik, yang memperjelas maksud, lebih penting daripada risiko Anda menyalin bug yang, sejujurnya, lebih mudah diperbaiki ketika metode Anda memiliki nama yang tepat.
Jadi saran saya adalah untuk berhenti membiarkan reaksi brengsek lutut ke kode serupa mengikat basis kode Anda di simpul. Saya tidak mengatakan Anda bebas untuk mengabaikan fakta bahwa ada metode dan bukannya copy dan paste mau tak mau. Tidak, masing-masing metode harus memiliki nama baik yang mendukung satu gagasan tentang itu. Jika implementasinya cocok dengan implementasi beberapa ide bagus lainnya, sekarang, hari ini, siapa yang peduli?
Di sisi lain, jika Anda memiliki
sum()
metode yang memiliki implementasi yang identik atau bahkan berbeda daritotal()
, namun semakin lama kebutuhan total Anda berubah, Anda harus berubahsum()
maka ada kemungkinan bahwa ini adalah ide yang sama dengan dua nama berbeda. Tidak hanya kode akan lebih fleksibel jika mereka digabungkan, itu akan kurang membingungkan untuk digunakan.Adapun parameter Boolean, ya itu bau kode jahat. Tidak hanya aliran kontrol itu masalah, lebih buruk itu menunjukkan bahwa Anda memotong abstraksi pada titik yang buruk. Abstraksi seharusnya membuat hal-hal lebih sederhana untuk digunakan, tidak lebih rumit. Melewati bools ke metode untuk mengontrol perilakunya seperti menciptakan bahasa rahasia yang memutuskan metode mana yang benar-benar Anda panggil. Ow! Jangan lakukan itu padaku. Beri setiap metode namanya masing-masing kecuali Anda memiliki polimorfisme yang jujur .
Sekarang, Anda tampak lelah dengan abstraksi. Itu terlalu buruk karena abstraksi adalah hal yang luar biasa ketika dilakukan dengan baik. Anda sering menggunakannya tanpa memikirkannya. Setiap kali Anda mengendarai mobil tanpa harus memahami sistem rack and pinion, setiap kali Anda menggunakan perintah cetak tanpa memikirkan interupsi OS, dan setiap kali Anda menyikat gigi tanpa memikirkan setiap bulu individu.
Tidak, masalah yang tampaknya Anda hadapi adalah abstraksi yang buruk. Abstraksi dibuat untuk melayani tujuan yang berbeda dari kebutuhan Anda. Anda perlu antarmuka sederhana menjadi objek kompleks yang memungkinkan Anda meminta agar kebutuhan Anda dipenuhi tanpa harus memahami objek tersebut.
Ketika Anda menulis kode klien yang menggunakan objek lain, Anda tahu apa kebutuhan Anda dan apa yang Anda butuhkan dari objek itu. Tidak. Itu sebabnya kode klien memiliki antarmuka. Ketika Anda klien, tidak ada yang bisa memberi tahu Anda apa kebutuhan Anda selain Anda. Anda membuat antarmuka yang menunjukkan apa kebutuhan Anda dan menuntut apa pun yang diserahkan kepada Anda memenuhi kebutuhan itu.
Itu adalah abstraksi. Sebagai klien saya bahkan tidak tahu apa yang saya bicarakan. Saya hanya tahu apa yang saya butuhkan darinya. Jika itu berarti Anda harus membungkus sesuatu untuk mengubah antarmuka sebelum menyerahkannya kepada saya baik-baik saja. Saya tidak peduli. Lakukan saja apa yang perlu saya lakukan. Berhentilah membuatnya menjadi rumit.
Jika saya harus melihat ke dalam abstraksi untuk memahami bagaimana menggunakannya abstraksi telah gagal. Saya seharusnya tidak perlu tahu cara kerjanya. Itu berhasil. Berikan nama yang bagus dan jika saya melihat ke dalam, saya seharusnya tidak terkejut dengan apa yang saya temukan. Jangan membuat saya terus mencari ke dalam untuk mengingat bagaimana menggunakannya.
Ketika Anda bersikeras bahwa abstraksi bekerja dengan cara ini, jumlah level di belakangnya tidak masalah. Selama Anda tidak mencari di belakang abstraksi. Anda bersikeras bahwa abstraksi sesuai dengan kebutuhan Anda agar tidak beradaptasi dengannya. Agar ini berfungsi, ia harus mudah digunakan, memiliki nama baik, dan tidak bocor .
Itulah sikap yang menelurkan Ketergantungan Injeksi (atau hanya lewat referensi jika Anda sekolah tua seperti saya). Ia bekerja dengan baik dengan lebih memilih komposisi dan delegasi daripada warisan . Sikap itu berjalan dengan banyak nama. Yang favorit saya adalah kirim, jangan tanya .
Saya bisa menenggelamkan Anda dalam prinsip-prinsip sepanjang hari. Dan sepertinya rekan kerja Anda sudah seperti itu. Tapi ada satu hal: tidak seperti bidang teknik lainnya, perangkat lunak ini berusia kurang dari 100 tahun. Kita semua masih mencari tahu. Jadi jangan biarkan seseorang dengan banyak buku terdengar mengintimidasi belajar menggertak Anda menjadi menulis sulit untuk membaca kode. Dengarkan mereka tetapi bersikeras mereka masuk akal. Jangan mengambil apa pun dengan iman. Orang-orang yang mengkode dengan cara tertentu hanya karena mereka diberitahu ini-itu-cara-tanpa tahu mengapa membuat kekacauan terbesar dari semua.
sumber
Pepatah yang biasa kita semua baca di sini dan di sana adalah:
Ya, ini tidak benar! Contoh Anda menunjukkannya. Karena itu saya akan mengusulkan pernyataan yang sedikit dimodifikasi (jangan ragu untuk menggunakan kembali ;-)):
Ada dua masalah berbeda dalam kasus Anda:
Keduanya terkait:
Shape
dapat menghitungnya dengansurface()
cara khusus.Jika Anda abstrak beberapa operasi di mana ada pola perilaku umum yang umum, Anda memiliki dua pilihan:
Selain itu, pendekatan ini dapat menghasilkan efek kopling abstrak di tingkat desain. Setiap kali Anda ingin menambahkan semacam perilaku khusus baru, Anda harus mengabstraksikannya, mengubah induk abstrak, dan memperbarui semua kelas lainnya. Itu bukan jenis perubahan propagasi yang diinginkan seseorang. Dan itu tidak benar-benar dalam semangat abstraksi tidak tergantung pada spesialisasi (setidaknya dalam desain).
Saya tidak tahu desain Anda dan tidak bisa membantu lagi. Mungkin itu benar-benar masalah yang sangat kompleks dan abstrak dan tidak ada cara yang lebih baik. Tapi apa kemungkinannya? Gejala-gejala overgeneralisasi ada di sini. Mungkin sudah waktunya untuk melihatnya lagi, dan mempertimbangkan komposisi daripada generalisasi ?
sumber
Setiap kali saya melihat metode di mana perilaku mengaktifkan jenis parameternya, saya segera mempertimbangkan terlebih dahulu apakah metode itu benar-benar milik pada parameter metode. Misalnya, alih-alih memiliki metode seperti:
Saya akan melakukan ini:
Kami memindahkan perilaku ke tempat yang tahu kapan harus menggunakannya. Kami membuat abstraksi nyata di mana Anda tidak perlu tahu jenis atau detail implementasi. Untuk situasi Anda, mungkin lebih masuk akal untuk memindahkan metode ini dari kelas asli (yang akan saya panggil
O
) untuk mengetikA
dan menimpanya dalam jenisB
. Jika metode ini dipanggildoIt
pada beberapa objek, pindahdoIt
keA
dan timpa dengan perilaku yang berbeda diB
. Jika ada bit data dari tempatdoIt
awalnya disebut, atau jika metode ini digunakan di tempat yang cukup, Anda dapat meninggalkan metode asli dan mendelegasikan:Kita bisa menyelam lebih dalam lagi. Mari kita lihat saran untuk menggunakan parameter boolean dan melihat apa yang bisa kita pelajari tentang cara rekan kerja Anda berpikir. Usulannya adalah melakukan:
Ini terlihat sangat mengerikan seperti yang
instanceof
saya gunakan pada contoh pertama saya, kecuali bahwa kita mengeksternalkan cek itu. Ini berarti bahwa kita harus menyebutnya dengan salah satu dari dua cara:atau:
Pertama-tama, titik panggilan tidak tahu jenisnya
A
. Karena itu, haruskah kita melewati boolean sepanjang jalan? Apakah itu benar-benar pola yang kita inginkan di seluruh basis kode? Apa yang terjadi jika ada tipe ketiga yang perlu kita pertanggungjawabkan? Jika ini adalah bagaimana metode ini dipanggil, kita harus memindahkannya ke tipe dan membiarkan sistem memilih implementasi untuk kita secara polimorfis.Dengan cara kedua, kita harus sudah tahu jenis
a
di titik panggilan. Biasanya itu berarti kita membuat instance di sana, atau mengambil instance dari tipe itu sebagai parameter. Membuat metodeO
yang dibutuhkan diB
sini akan berhasil. Kompiler akan tahu metode mana yang harus dipilih. Ketika kita mengemudi melalui perubahan seperti ini, duplikasi lebih baik daripada menciptakan abstraksi yang salah , setidaknya sampai kita mengetahui ke mana kita benar-benar pergi. Tentu saja, saya menyarankan agar kita tidak benar-benar melakukan apa pun yang telah kita ubah ke titik ini.Kita perlu melihat lebih dekat hubungan antara
A
danB
. Secara umum, kita diberitahu bahwa kita harus memilih komposisi daripada warisan . Ini tidak benar dalam setiap kasus, tetapi itu benar dalam jumlah kasus yang mengejutkan begitu kita menggali.B
Warisan dariA
, artinya kita percayaB
adalahA
.B
harus digunakan sepertiA
, kecuali bahwa itu bekerja sedikit berbeda. Tetapi apa perbedaan itu? Bisakah kita memberi perbedaan nama yang lebih konkret? BukankahB
ini sebuahA
, tetapi benar-benarA
memilikiX
yang bisaA'
atauB'
? Akan seperti apa kode kita jika kita melakukan itu?Jika kami memindahkan metode ke
A
seperti yang disarankan sebelumnya, kami dapat menyuntikkan instanceX
keA
, dan mendelegasikan metode itu keX
:Kita dapat menerapkan
A'
danB'
, serta menyingkirkanB
. Kami telah memperbaiki kode dengan memberikan nama pada konsep yang mungkin lebih implisit, dan memungkinkan kami untuk mengatur perilaku itu saat runtime alih-alih waktu kompilasi.A
sebenarnya menjadi kurang abstrak juga. Alih-alih hubungan warisan yang diperpanjang, itu memanggil metode pada objek yang didelegasikan. Objek itu abstrak, tetapi lebih fokus hanya pada perbedaan implementasi.Ada satu hal terakhir yang harus dilihat. Mari kembali ke proposal rekan kerja Anda. Jika pada semua situs panggilan kita secara eksplisit mengetahui jenis yang
A
kita miliki, maka kita harus membuat panggilan seperti:Kami berasumsi sebelumnya ketika menulis yang
A
memiliki salahX
satuA'
atauB'
. Tetapi mungkin bahkan anggapan ini tidak benar. Apakah ini satu-satunya tempat di mana perbedaan antaraA
danB
hal-hal? Jika ya, maka mungkin kita bisa mengambil pendekatan yang sedikit berbeda. Kami masih memilikiX
yang salahA'
atauB'
, tetapi bukan milikA
. HanyaO.doIt
peduli tentang itu, jadi mari kita serahkan saja keO.doIt
:Sekarang situs panggilan kami terlihat seperti:
Sekali lagi,
B
menghilang, dan abstraksi bergerak ke yang lebih fokusX
. Namun, kaliA
ini bahkan lebih sederhana dengan mengetahui lebih sedikit. Itu bahkan kurang abstrak.Penting untuk mengurangi duplikasi dalam basis kode, tetapi kita harus mempertimbangkan mengapa duplikasi terjadi di tempat pertama. Duplikasi bisa menjadi tanda abstraksi yang lebih dalam yang berusaha keluar.
sumber
Abstraksi oleh Waris bisa menjadi sangat jelek. Hirarki kelas paralel dengan pabrik-pabrik khas. Refactoring bisa menjadi sakit kepala. Dan juga perkembangan selanjutnya, tempat di mana Anda berada.
Ada alternatif: titik ekstensi , abstraksi yang ketat, dan kustomisasi berjenjang. Katakanlah satu kustomisasi pelanggan pemerintah, berdasarkan kustomisasi itu untuk kota tertentu.
Peringatan: Sayangnya ini bekerja paling baik ketika semua (atau sebagian besar) kelas dibuat memanjang. Tidak ada pilihan untuk Anda, mungkin dalam jumlah kecil.
Ekstensibilitas ini berfungsi dengan memiliki kelas basis objek yang dapat diperpanjang menampung ekstensi:
Secara internal ada pemetaan malas objek ke objek diperluas oleh kelas ekstensi.
Untuk kelas dan komponen GUI, ekstensibilitasnya sama, sebagian dengan warisan. Menambahkan tombol dan semacamnya.
Dalam kasus Anda, validasi harus melihat apakah diperpanjang dan memvalidasi dirinya terhadap ekstensi. Memperkenalkan poin ekstensi hanya untuk satu kasus, tambahkan kode yang tidak bisa dimengerti, tidak bagus.
Jadi tidak ada solusi selain berusaha bekerja dalam konteks saat ini.
sumber
'kontrol aliran tersembunyi' terdengar terlalu mudah bagi saya.
Setiap konstruk atau elemen yang diambil di luar konteks mungkin memiliki karakteristik itu.
Abstraksi itu baik. Saya marahi mereka dengan dua pedoman:
Lebih baik tidak abstrak terlalu cepat. Tunggu lebih banyak contoh pola sebelum abstracing. 'Lebih' tentu saja subyektif dan spesifik untuk situasi yang sulit.
Hindari terlalu banyak level abstraksi hanya karena abstraksi itu baik. Seorang programmer harus menjaga level-level itu di kepala mereka untuk kode baru atau yang diubah ketika mereka menyelami basis kode dan pergi 12 level dalam. Keinginan untuk kode yang disarikan dengan baik dapat menyebabkan begitu banyak level sehingga sulit bagi banyak orang untuk mengikuti. Ini juga mengarah pada basis kode 'ninja maintained only'.
Dalam kedua kasus 'lebih banyak dan' terlalu banyak 'bukan angka tetap. Tergantung. Itu yang membuatnya sulit.
Saya juga suka artikel ini dari Sandi Metz
https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction
duplikasi jauh lebih murah daripada abstraksi yang salah
dan
lebih memilih duplikasi daripada abstraksi yang salah
sumber