Apakah mungkin untuk menerapkan KERING tanpa meningkatkan kopling?

14

Asumsikan kita memiliki modul perangkat lunak A yang mengimplementasikan fungsi F. Modul B lainnya mengimplementasikan fungsi yang sama dengan F '.

Ada beberapa cara untuk menghilangkan kode duplikat:

  1. Biarkan A menggunakan F 'dari B.
  2. Biarkan B menggunakan F dari A.
  3. Masukkan F ke dalam modul C-nya sendiri dan biarkan A dan B menggunakannya.

Semua opsi ini menghasilkan dependensi tambahan antar modul. Mereka menerapkan prinsip KERING dengan biaya meningkatkan sambungan.

Sejauh yang saya bisa lihat, kopling selalu meningkat atau disewa pindah ke tingkat yang lebih tinggi saat menerapkan KERING. Tampaknya ada konflik antara dua prinsip paling mendasar dari desain perangkat lunak.

(Sebenarnya saya tidak merasa heran bahwa ada konflik seperti itu. Ini mungkin yang membuat desain perangkat lunak yang baik sangat sulit. Saya menemukan mengejutkan bahwa konflik ini biasanya tidak dibahas dalam teks pengantar.)

Sunting (untuk klarifikasi): Saya berasumsi bahwa kesetaraan F dan F 'bukan hanya kebetulan. Jika F harus dimodifikasi, F 'kemungkinan harus dimodifikasi dengan cara yang sama.

Frank Puffer
sumber
2
... Saya pikir KERING bisa menjadi strategi yang sangat berguna, tetapi pertanyaan ini menggambarkan ketidakefisienan dengan KERING. Beberapa (misalnya, penggemar OOP) mungkin berpendapat bahwa Anda harus menyalin / menempel F ke B hanya demi mempertahankan otonomi konseptual A dan B tetapi saya belum menemukan skenario di mana saya akan melakukan itu. Saya pikir kode copy / paste hanya tentang opsi terburuk, saya tidak tahan dengan "kehilangan memori jangka pendek" di mana saya hanya yakin bahwa saya sudah menulis metode / fungsi untuk melakukan sesuatu; memperbaiki bug dalam satu fungsi dan lupa memperbarui yang lain bisa menjadi masalah besar lainnya.
jrh
3
Ada banyak sekali prinsip OO yang saling bertentangan. Dalam kebanyakan kasus, Anda harus menemukan pertukaran yang wajar. Tapi IMHO prinsip KERING adalah yang paling berharga. Seperti yang ditulis @jrh: menerapkan perilaku yang sama di banyak tempat adalah mimpi buruk pemeliharaan yang harus dihindari dengan biaya berapa pun. Mengetahui bahwa Anda lupa memperbarui salah satu salinan yang berlebihan dalam produksi dapat menurunkan bisnis Anda.
Timothy Truckle
2
@TimothyTruckle: Kita seharusnya tidak menyebutnya prinsip OO karena mereka berlaku untuk paradigma pemrograman lain juga. Dan ya, KERING berharga tetapi juga berbahaya jika berlebihan. Bukan hanya karena cenderung menciptakan dependensi dan kompleksitas. Ini juga sering diterapkan pada duplikat yang disebabkan oleh kebetulan yang memiliki alasan berbeda untuk berubah.
Frank Puffer
1
... juga terkadang ketika saya mengalami skenario ini, saya dapat memecah F menjadi bagian-bagian yang dapat digunakan untuk apa yang dibutuhkan A dan apa yang dibutuhkan B.
jrh
1
Coupling pada dasarnya bukanlah hal yang buruk, dan seringkali diperlukan untuk mengurangi kesalahan dan meningkatkan produktivitas. Jika Anda menggunakan fungsi parseInt dari pustaka standar bahasa Anda di dalam fungsi Anda, Anda akan menggabungkan fungsi Anda ke pustaka standar. Saya belum pernah melihat program yang tidak melakukan ini selama bertahun-tahun. Kuncinya adalah untuk tidak membuat sambungan yang tidak perlu. Paling sering, antarmuka digunakan untuk menghindari / menghapus sambungan semacam itu. Misalnya, fungsi saya dapat menerima implementasi parseInt sebagai argumen. Namun, ini tidak selalu diperlukan, juga tidak selalu bijaksana.
Joshua Jones

Jawaban:

14

Semua opsi ini menghasilkan dependensi tambahan antar modul. Mereka menerapkan prinsip KERING dengan biaya meningkatkan sambungan.

Kenapa ya mereka lakukan. Tetapi mereka mengurangi kopling antara garis. Apa yang Anda dapatkan adalah kekuatan untuk mengubah kopling. Kopling hadir dalam berbagai bentuk. Mengekstrak kode meningkatkan tipuan dan abstraksi. Peningkatan itu bisa baik atau buruk. Hal nomor satu yang menentukan yang Anda dapatkan adalah nama yang Anda gunakan untuk itu. Jika melihat nama itu membuat saya terkejut ketika saya melihat ke dalam, maka Anda belum melakukan bantuan.

Juga, jangan ikuti KERING dalam ruang hampa. Jika Anda membunuh duplikat Anda bertanggung jawab untuk memprediksi bahwa kedua penggunaan kode itu akan berubah bersama. Jika mereka cenderung berubah secara independen, Anda telah menyebabkan kebingungan dan kerja ekstra untuk sedikit manfaat. Tapi nama yang sangat bagus bisa membuat itu lebih enak. Jika yang dapat Anda pikirkan adalah nama yang buruk maka tolong, berhenti saja sekarang.

Kopling akan selalu ada kecuali sistem Anda sangat terisolasi sehingga tidak ada yang akan tahu jika itu berfungsi. Jadi coupling refactoring adalah permainan memilih racun Anda. Mengikuti KERING dapat memberikan hasil dengan meminimalkan pemasangan yang dibuat dengan mengungkapkan keputusan desain yang sama berulang-ulang di banyak tempat hingga sangat sulit untuk diubah. Tetapi KERING dapat membuatnya mustahil untuk memahami kode Anda. Cara terbaik untuk menyelamatkan situasi itu adalah menemukan nama yang sangat bagus. Jika Anda tidak dapat memikirkan nama yang baik saya harap Anda terampil menghindari nama yang tidak berarti

candied_orange
sumber
Hanya untuk memastikan saya memahami maksud Anda dengan benar, izinkan saya mengatakannya sedikit berbeda: Jika Anda menetapkan nama yang baik untuk kode yang diekstrak, kode yang diekstraksi tidak lagi relevan untuk memahami perangkat lunak karena nama mengatakan semuanya (idealnya). Kopling masih ada pada level teknis tetapi tidak pada level kognitif. Karena itu relatif tidak berbahaya, bukan?
Frank Puffer
1
Nevermind, salahku
Basilevs
@ FrankPuffer lebih baik?
candied_orange
3

Ada cara untuk memutus dependensi eksplisit. Yang populer adalah menyuntikkan dependensi dalam runtime. Dengan cara ini, Anda mendapatkan KERING, menghapus sambungan dengan biaya keamanan statis. Ini sangat populer saat ini, bahwa orang-orang bahkan tidak mengerti, itu adalah tradeoff. Misalnya, wadah aplikasi secara rutin menyediakan manajemen ketergantungan perangkat lunak yang sangat menyulitkan dengan menyembunyikan kompleksitas. Bahkan injeksi konstruktor tua biasa gagal untuk menjamin beberapa kontrak karena kurangnya jenis sistem.

Untuk menjawab judul - ya, itu mungkin, tetapi bersiaplah untuk konsekuensi dari pengiriman runtime.

  • Tentukan antarmuka F A dalam A, menyediakan fungsionalitas F
  • Tentukan antarmuka F B di B
  • Masukkan F ke dalam C
  • Buat modul D untuk mengelola semua dependensi (tergantung pada A, B dan C)
  • Adaptasi F ke F A dan F B dalam D
  • Suntikkan (lewati) pembungkus ke A dan B

Dengan cara ini, satu-satunya jenis dependensi yang Anda miliki adalah D tergantung pada masing-masing modul lainnya.

Atau daftarkan C dalam wadah aplikasi dengan injeksi dependensi bawaan dan nikmati keberuntungan autowiring yang perlahan-lahan meningkatkan loop dan deadlock classloading runtime.

Basilevs
sumber
1
+1, tetapi dengan peringatan eksplisit bahwa ini biasanya merupakan tradeoff yang buruk. Keamanan statis itu ada untuk melindungi Anda, dan menghindarinya dengan banyak aksi seram di kejauhan hanya meminta bug yang sulit dilacak lebih jauh ke depan ketika kompleksitas proyek Anda sedikit berkembang ...
Mason Wheeler
1
Bisakah DI benar-benar dikatakan memecah ketergantungan? Bahkan secara formal, Anda memerlukan antarmuka dengan tanda tangan F untuk mengimplementasikannya. Tapi tetap saja, jika modul A akhirnya menggunakan F dari C itu didepannya di atasnya, terlepas dari C yang disuntikkan pada saat runtime atau langsung terhubung. Saya tidak memutuskan ketergantungan, bug hanya menunda kegagalan jika dependenc tidak disediakan
max630
@ max630 menggantikan ketergantungan implementasi dengan yang kontraktual, yang lebih lemah.
Basilev
@ max630 Anda benar. DI tidak bisa dikatakan memecah ketergantungan. Faktanya, DI adalah metode untuk memperkenalkan dependensi, dan benar-benar ortogonal untuk pertanyaan yang diajukan sehubungan dengan penggandengan. Perubahan dalam F (atau antarmuka yang mengenkapsulasi) masih akan membutuhkan perubahan pada A dan B.
slide sisi-raja
1

Saya tidak yakin jawaban tanpa konteks lebih lanjut masuk akal.

Apakah Asudah tergantung Batau sebaliknya? - dalam hal ini kita mungkin memiliki pilihan rumah yang jelas F.

Apakah Adan Bsudah berbagi dependensi umum yang mungkin menjadi rumah yang baik F?

Seberapa besar / kompleksnya F? Apa lagi Ftergantung pada?

Apakah modul Adan Bdigunakan dalam proyek yang sama?

Akankah Adan Bakhirnya berbagi beberapa ketergantungan bersama?

Bahasa / modul sistem apa yang digunakan: Seberapa menyakitkan modul baru, dalam rasa sakit programmer, dalam overhead kinerja? Sebagai contoh, jika Anda menulis dalam C / C ++ dengan sistem modul menjadi COM, yang menyebabkan rasa sakit pada kode sumber, memerlukan tooling alternatif, memiliki implikasi pada debugging, dan memiliki implikasi kinerja (untuk permintaan antar-modul), saya mungkin mengambil jeda yang serius.

Di sisi lain, jika Anda berbicara tentang Java atau C # DLL yang menggabungkan agak mulus dalam lingkungan eksekusi tunggal, itu masalah lain.


Fungsi adalah abstraksi, dan mendukung KERING.

Namun, abstraksi yang baik harus lengkap - abstraksi yang tidak lengkap sangat mungkin menyebabkan klien yang mengkonsumsi (programmer) membuat kekurangan dengan menggunakan pengetahuan tentang implementasi yang mendasarinya: ini menghasilkan kopling yang lebih ketat daripada jika abstraksi yang ditawarkan malah lebih lengkap.

Jadi, saya berpendapat untuk mencari untuk membuat abstraksi yang lebih baik untuk Adan Bbergantung pada daripada hanya memindahkan satu fungsi tunggal ke modul baru C

Saya akan mencari satu set fungsi untuk digoda menjadi abstraksi baru, yang bisa dikatakan, saya mungkin menunggu sampai basis kode lebih lanjut untuk mengidentifikasi refactoring abstraksi yang lebih lengkap / lebih lengkap untuk dilakukan daripada berdasarkan pada satu kode fungsi kirim.

Erik Eidt
sumber
2
Bukankah berbahaya bagi pasangan abstraksi dan grafik ketergantungan?
Basilev
Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.Ini mengasumsikan bahwa A akan selalu bergantung pada B (atau sebaliknya), yang merupakan asumsi yang sangat berbahaya. Fakta bahwa OP melihat F tidak secara inheren bagian dari A (atau B) menunjukkan bahwa F ada tanpa melekat pada salah satu perpustakaan. Jika F milik satu perpustakaan (misalnya metode ekstensi DbContext (F) dan perpustakaan pembungkus Entity Framework (A atau B)), maka pertanyaan OP akan diperdebatkan.
Flater
0

Di sini, jawaban yang berfokus pada semua cara Anda dapat "meminimalkan" masalah ini merugikan Anda. Dan "solusi" hanya menawarkan berbagai cara untuk membuat sambungan bukan solusi yang sesungguhnya.

Yang benar adalah bahwa Anda gagal memahami masalah yang Anda buat sendiri. Masalah dengan contoh Anda tidak ada hubungannya dengan KERING, melainkan (lebih luas) dengan desain aplikasi.

Tanyakan pada diri Anda mengapa modul A dan B terpisah jika keduanya bergantung pada fungsi F yang sama? Tentu saja Anda akan memiliki masalah dengan manajemen dependensi / abstraksi / kopling / nama-Anda-jika Anda berkomitmen untuk desain yang buruk.

Pemodelan aplikasi yang tepat dilakukan sesuai dengan perilaku. Dengan demikian, potongan A dan B yang bergantung pada F perlu diekstraksi ke dalam modul mereka sendiri. Jika ini tidak memungkinkan, maka A dan B perlu dikombinasikan. Dalam kedua kasus tersebut, A dan B tidak lagi berguna bagi sistem dan seharusnya tidak ada lagi.

KERING adalah prinsipal yang dapat digunakan untuk mengekspos desain yang buruk, bukan menyebabkannya. Jika Anda tidak dapat mencapai KERING ( ketika itu benar-benar berlaku - mencatat suntingan Anda) karena struktur aplikasi Anda, itu adalah tanda yang jelas bahwa struktur telah menjadi kewajiban. Inilah sebabnya mengapa “refactoring berkelanjutan” juga merupakan prinsip yang harus diikuti.

ABC dari prinsip desain lainnya (SOLID, KERING, dll) di luar sana semua digunakan untuk mengubah (termasuk refactoring) aplikasi lebih tanpa rasa sakit. Fokuslah pada hal itu , dan semua masalah lain mulai menghilang.

slide sisi raja
sumber
Apakah Anda menyarankan untuk memiliki satu modul di setiap aplikasi?
Basilev
@ Basilevs Sama sekali tidak (kecuali itu dibenarkan). Saya sarankan memiliki modul yang sepenuhnya dipisahkan sebanyak mungkin. Bagaimanapun, itulah tujuan keseluruhan modul.
slide sisi raja
Nah, pertanyaan menyiratkan modul A dan B mengandung fungsi yang tidak terkait, dan untuk sudah diekstraksi sesuai, mengapa dan bagaimana seharusnya mereka tidak ada lagi? Apa solusi Anda untuk masalah yang disebutkan?
Basilev
@ Basilevs Pertanyaan ini menyiratkan A dan B belum dimodelkan dengan benar. Kekurangan inheren inilah yang menyebabkan masalah sejak awal. Tentu saja fakta sederhana bahwa mereka memang ada bukanlah bukti bahwa mereka harus ada. Itulah poin yang saya buat di atas. Jelas, desain alternatif diperlukan untuk menghindari patah KERING. Penting untuk dipahami bahwa tujuan dari semua "prinsip desain" ini adalah untuk membuat aplikasi lebih mudah untuk diubah.
slide sisi raja
Apakah satu ton metode lain, dengan dependensi yang sama sekali berbeda dimodelkan buruk dan harus diulang, hanya karena ini satu-satunya yang tidak disengaja? Atau apakah Anda berasumsi bahwa, modul A dan B masing-masing berisi metode tunggal?
Basilev
0

Semua opsi ini menghasilkan dependensi tambahan antar modul. Mereka menerapkan prinsip KERING dengan biaya meningkatkan sambungan.

Saya memiliki pendapat yang berbeda, setidaknya untuk opsi ketiga:

Dari uraian Anda:

  • A membutuhkan F
  • B membutuhkan F
  • Baik A maupun B tidak saling membutuhkan.

Menempatkan F dalam modul C tidak meningkatkan kopling karena A dan B sudah membutuhkan fitur C.

mouviciel
sumber