Apakah abstraksi harus mengurangi pembacaan kode?

19

Seorang pengembang yang baik yang bekerja dengan saya memberi tahu saya baru-baru ini tentang beberapa kesulitan yang ia miliki dalam mengimplementasikan fitur dalam beberapa kode yang kami warisi; katanya masalahnya adalah bahwa kode itu sulit untuk diikuti. Dari sana, saya melihat lebih dalam ke dalam produk dan menyadari betapa sulitnya untuk melihat jalur kode.

Ini menggunakan banyak antarmuka dan lapisan abstrak, sehingga mencoba memahami di mana segala sesuatu dimulai dan berakhir cukup sulit. Itu membuat saya berpikir tentang waktu saya melihat proyek-proyek sebelumnya (sebelum saya begitu menyadari prinsip-prinsip kode bersih) dan merasa sangat sulit untuk berkeliling dalam proyek, terutama karena alat navigasi kode saya akan selalu mendaratkan saya di sebuah antarmuka. Dibutuhkan banyak usaha ekstra untuk menemukan implementasi konkret atau di mana ada sesuatu yang ditransfer dalam beberapa arsitektur tipe plugin.

Saya tahu beberapa pengembang benar-benar menolak wadah injeksi ketergantungan karena alasan ini. Ini membingungkan jalur perangkat lunak sehingga kesulitan navigasi kode secara eksponensial meningkat.

Pertanyaan saya adalah: ketika suatu kerangka kerja atau pola memperkenalkan begitu banyak overhead seperti ini, apakah itu layak? Apakah ini merupakan gejala dari pola implementasi yang buruk?

Saya kira pengembang harus melihat ke gambaran yang lebih besar tentang apa yang dibawa oleh abstraksi ke proyek untuk membantu mereka melewati frustrasi. Namun biasanya, sulit untuk membuat mereka melihat gambaran besar itu. Saya tahu saya gagal menjual kebutuhan IOC dan DI dengan TDD. Bagi para pengembang itu, penggunaan alat-alat itu hanya terlalu mudah dibaca kode.

Martin Blore
sumber

Jawaban:

17

Ini benar-benar lebih dari komentar panjang pada jawaban @kevin cline.

Meskipun bahasa-bahasa itu sendiri tidak selalu menyebabkan atau mencegah hal ini, saya pikir ada sesuatu yang menurutnya terkait dengan bahasa (atau setidaknya komunitas bahasa) sampai taraf tertentu. Secara khusus, meskipun Anda dapat mengalami semacam masalah yang sama dalam bahasa yang berbeda, sering kali akan mengambil bentuk yang agak berbeda dalam bahasa yang berbeda.

Sebagai contoh, ketika Anda mengalami ini dalam C ++, kemungkinan itu adalah hasil yang kurang dari terlalu banyak abstraksi, dan lebih banyak hasil dari terlalu banyak kepintaran. Sebagai contoh, programmer telah menyembunyikan transformasi penting yang sedang terjadi (yang tidak dapat Anda temukan) di iterator khusus, jadi apa yang tampak seperti menyalin data dari satu tempat ke tempat lain benar-benar memiliki sejumlah efek samping yang tidak ada hubungannya dengan lakukan dengan menyalin data. Hanya untuk membuat hal-hal menarik, ini disisipkan dengan output yang dibuat sebagai efek samping dari menciptakan objek sementara dalam proses pengecoran satu jenis objek ke yang lain.

Sebaliknya, ketika Anda menabraknya di Jawa, Anda jauh lebih mungkin melihat beberapa varian "dunia halo perusahaan" yang terkenal, di mana alih-alih satu kelas sepele yang melakukan sesuatu yang sederhana, Anda mendapatkan kelas dasar abstrak dan kelas turunan konkret yang mengimplementasikan antarmuka X, dan dibuat oleh kelas pabrik dalam kerangka kerja DI, dll. 10 baris kode yang melakukan pekerjaan nyata dikuburkan di bawah 5.000 baris infrastruktur.

Beberapa dari itu tergantung pada lingkungan setidaknya sebanyak bahasa - bekerja secara langsung dengan lingkungan windowing seperti X11 dan MS Windows terkenal karena mengubah program "halo dunia" yang sepele menjadi 300+ baris sampah yang hampir tidak dapat diuraikan. Seiring waktu, kami telah mengembangkan berbagai toolkit untuk melindungi kami dari hal itu - tetapi 1) toolkit itu cukup non-sepele sendiri, dan 2) hasil akhirnya masih tidak hanya lebih besar dan lebih kompleks, tetapi juga biasanya kurang fleksibel daripada setara mode teks (misalnya, meskipun hanya mencetak beberapa teks, mengalihkannya ke file jarang dimungkinkan / didukung).

Untuk menjawab (setidaknya sebagian dari) pertanyaan awal: setidaknya ketika saya melihatnya, itu bukan masalah buruknya implementasi pola daripada hanya menerapkan pola yang tidak sesuai dengan tugas yang ada - sebagian besar sering mencoba menerapkan beberapa pola yang mungkin berguna dalam program yang tidak dapat dihindari besar dan rumit, tetapi ketika diterapkan pada masalah yang lebih kecil akhirnya membuatnya menjadi besar dan kompleks juga, meskipun dalam kasus ini ukuran dan kompleksitasnya benar-benar dapat dihindari .

Jerry Coffin
sumber
7

Saya menemukan bahwa ini sering disebabkan oleh tidak mengambil pendekatan YAGNI . Segala sesuatu melalui antarmuka, meskipun hanya ada satu implementasi konkret dan tidak ada rencana saat ini untuk memperkenalkan yang lain, adalah contoh utama untuk menambah kompleksitas yang Anda Tidak Perlu. Mungkin bid'ah tetapi saya merasakan hal yang sama tentang banyak penggunaan injeksi ketergantungan.

Carson63000
sumber
+1 untuk menyebutkan YAGNI dan abstraksi dengan titik referensi tunggal. Peran utama membuat abstraksi adalah memfaktorkan poin bersama dari banyak hal. Jika abstraksi hanya direferensikan dari satu titik, kita tidak dapat berbicara tentang memfaktorkan hal-hal umum, abstraksi seperti ini hanya berkontribusi pada masalah yoyo. Saya akan memperpanjang ini karena ini berlaku untuk semua jenis abstraksi: fungsi, generik, makro, apa pun ...
Calmarius
3

Yah, abstraksi tidak cukup dan kode Anda sulit dimengerti karena Anda tidak dapat mengisolasi bagian mana yang melakukan apa.

Terlalu banyak abstraksi dan Anda melihat abstraksi tetapi bukan kode itu sendiri, dan kemudian membuat sulit untuk mengikuti utas eksekusi nyata.

Untuk mencapai abstraksi yang baik, seseorang harus KISS: lihat jawaban saya untuk pertanyaan ini untuk mengetahui apa yang harus diikuti untuk menghindari masalah semacam itu .

Saya pikir menghindari hierarki dan penamaan yang mendalam adalah hal paling penting untuk dilihat pada kasus yang Anda gambarkan. Jika abstraksi diberi nama baik, Anda tidak perlu terlalu dalam, hanya ke tingkat abstraksi di mana Anda perlu memahami apa yang terjadi. Penamaan memungkinkan Anda mengidentifikasi di mana tingkat abstraksi ini.

Masalahnya muncul dalam kode tingkat rendah, ketika Anda benar-benar membutuhkan semua proses untuk dipahami. Kemudian, enkapsulasi melalui modul yang terisolasi jelas adalah satu-satunya bantuan.

Klaim
sumber
3
Yah, abstraksi tidak cukup dan kode Anda sulit dimengerti karena Anda tidak dapat mengisolasi bagian mana yang melakukan apa. Itu enkapsulasi, bukan abstraksi. Anda dapat mengisolasi bagian-bagian dalam kelas beton tanpa banyak abstraksi.
Pernyataan
Kelas bukan satu-satunya abstraksi yang kami gunakan: fungsi, modul / pustaka, layanan, dll. Di kelas Anda, Anda biasanya mengabstraksikan setiap fungsionalitas di belakang fungsi / metode, yang dapat memanggil metode lain yang mengabstraksikan satu sama lain ke fungsi lainnya.
Klaim
1
@Statement: Enkapsulasi data tentu saja merupakan abstraksi.
Ed S.
Hirarki ruang nama benar-benar bagus.
JAB
2

Bagi saya itu masalah kopling dan terkait dengan rincian desain. Bahkan bentuk kopling yang paling longgar pun memperkenalkan ketergantungan dari satu hal ke hal lainnya. Jika itu dilakukan untuk ratusan hingga ribuan objek, bahkan jika semuanya relatif sederhana, patuhi SRP, dan bahkan jika semua dependensi mengalir menuju abstraksi yang stabil, yang menghasilkan basis kode yang sangat sulit untuk dipikirkan sebagai keseluruhan yang saling terkait.

Ada hal-hal praktis yang membantu Anda mengukur kompleksitas basis kode, tidak sering dibahas dalam SE teoritis, seperti seberapa jauh ke dalam tumpukan panggilan yang bisa Anda dapatkan sebelum Anda mencapai akhir, dan seberapa dalam Anda harus pergi sebelum Anda bisa, dengan sangat percaya diri, pahami semua kemungkinan efek samping yang dapat terjadi pada tingkat setumpuk panggilan termasuk jika terjadi pengecualian.

Dan saya telah menemukan, hanya dalam pengalaman saya, bahwa sistem yang lebih datar dengan tumpukan panggilan dangkal cenderung jauh lebih mudah untuk dipikirkan. Contoh ekstrem adalah sistem entitas-komponen di mana komponen hanyalah data mentah. Hanya sistem yang memiliki fungsionaltiy, dan dalam proses penerapan dan penggunaan ECS, saya menemukan itu sistem yang paling mudah, sejauh ini, untuk alasan ketika basis kode kompleks yang menjangkau ratusan ribu baris kode pada dasarnya mendidih ke beberapa lusin sistem yang mengandung semua fungsionalitas.

Terlalu Banyak Hal Memberikan Fungsi

Alternatif sebelumnya ketika saya bekerja di basis kode sebelumnya adalah sistem dengan ratusan hingga ribuan objek yang sangat kecil dibandingkan dengan beberapa lusin sistem besar dengan beberapa objek yang digunakan hanya untuk mengirimkan pesan dari satu objek ke objek lainnya ( Messageobjek, misalnya, yang memiliki antarmuka publik sendiri). Pada dasarnya itulah yang Anda dapatkan secara analog ketika Anda mengembalikan ECS kembali ke titik di mana komponen memiliki fungsi dan setiap kombinasi unik dari komponen dalam suatu entitas menghasilkan jenis objeknya sendiri. Dan itu akan cenderung menghasilkan fungsi yang lebih kecil, lebih sederhana yang diwarisi dan disediakan oleh kombinasi objek tanpa akhir yang memodelkan ide-ide kecil ( Particleobjek vs.Physics System, mis.). Namun, itu juga cenderung menghasilkan grafik kompleks antar-dependensi yang membuatnya sulit untuk berpikir tentang apa yang terjadi dari tingkat luas, hanya karena ada begitu banyak hal dalam basis kode yang benar-benar dapat melakukan sesuatu dan karena itu dapat melakukan sesuatu yang salah - - tipe yang bukan tipe "data", tetapi tipe "objek" dengan fungsionalitas terkait. Jenis yang berfungsi sebagai data murni tanpa fungsi yang terkait tidak mungkin salah karena mereka tidak dapat melakukan apa pun sendiri.

Antarmuka murni tidak banyak membantu masalah kelengkapan ini karena bahkan jika itu membuat "kompilasi-waktu dependensi" tidak terlalu rumit dan memberikan ruang bernapas lebih besar untuk perubahan dan perluasan, itu tidak membuat "dependensi runtime" dan interaksi pun menjadi lebih rumit. Objek klien masih berakhir menjalankan fungsi pada objek akun konkret bahkan jika mereka dipanggil IAccount. Polimorfisme dan antarmuka abstrak memiliki kegunaannya, tetapi mereka tidak memisahkan hal-hal dengan cara yang benar-benar membantu Anda beralasan tentang semua efek samping yang dapat berlangsung pada titik tertentu. Untuk mencapai jenis decoupling yang efektif ini, Anda memerlukan basis kode yang memiliki lebih sedikit hal yang mengandung fungsionalitas.

Lebih Banyak Data, Lebih Sedikit Fungsi

Jadi saya telah menemukan pendekatan ECS, bahkan jika Anda tidak menerapkannya sepenuhnya, akan sangat membantu, karena ternyata apa yang akan menjadi ratusan objek menjadi hanya data mentah dengan sistem besar, yang dirancang lebih kasar, yang menyediakan semua Kegunaan. Ini memaksimalkan jumlah tipe "data" dan meminimalkan jumlah tipe "objek", dan karena itu benar-benar meminimalkan jumlah tempat di sistem Anda yang benar-benar bisa salah. Hasil akhirnya adalah sistem yang sangat "datar" tanpa grafik ketergantungan yang kompleks, hanya sistem untuk komponen, tidak pernah sebaliknya, dan tidak pernah komponen ke komponen lain. Ini pada dasarnya jauh lebih banyak data mentah dan jauh lebih sedikit abstraksi yang memiliki efek memusatkan dan meratakan fungsi basis kode ke bidang utama, abstraksi kunci.

30 hal yang lebih sederhana tidak selalu lebih mudah untuk dipikirkan dari 1 hal yang lebih kompleks, jika 30 hal yang lebih sederhana itu saling terkait sementara hal yang kompleks itu berdiri sendiri. Jadi saran saya sebenarnya adalah untuk mentransfer kompleksitas dari interaksi antara objek dan lebih ke arah objek bulkier yang tidak harus berinteraksi dengan apa pun untuk mencapai decoupling massa, ke seluruh "sistem" (bukan monolit dan objek dewa, ingatkan Anda, dan bukan kelas dengan 200 metode, tetapi sesuatu yang levelnya jauh lebih tinggi daripada Messageatau Particlemeskipun memiliki antarmuka minimalis). Dan mendukung tipe data lama yang lebih sederhana. Semakin Anda bergantung pada itu, semakin sedikit kopling yang Anda dapatkan. Bahkan jika itu bertentangan dengan beberapa ide SE, saya telah menemukan itu sangat membantu.


sumber
0

Pertanyaan saya adalah, ketika suatu kerangka kerja atau pola memperkenalkan begitu banyak overhead seperti ini, apakah itu layak? Apakah ini merupakan gejala dari pola implementasi yang buruk?

Mungkin itu adalah gejala memilih bahasa pemrograman yang salah.

kevin cline
sumber
1
Saya tidak mengerti bagaimana ini ada hubungannya dengan bahasa pilihan. Abstraksi adalah konsep mandiri bahasa tingkat tinggi.
Ed S.
@ Ed: Beberapa abstraksi lebih mudah direalisasikan dalam beberapa bahasa daripada yang lain.
kevin cline
Ya, tetapi itu tidak berarti Anda tidak dapat menulis abstraksi yang dipelihara dengan sempurna dan mudah dipahami dalam bahasa-bahasa tersebut. Maksud saya adalah bahwa jawaban Anda tidak menjawab pertanyaan atau membantu OP dengan cara apa pun.
Ed S.
0

Pemahaman yang buruk tentang pola desain cenderung menjadi penyebab utama masalah ini. Salah satu yang terburuk yang pernah saya lihat untuk ini yo-yo'ing dan memantul dari antarmuka ke antarmuka tanpa data konkret di antaranya adalah perpanjangan untuk Oracle's Grid Control.
Jujur terlihat seperti seseorang telah memiliki metode pabrik abstrak dan orgasme pola dekorator di seluruh kode Java saya. Dan itu membuatku merasa hampa dan sendirian.

Jeff Langemeier
sumber
-1

Saya juga akan mengingatkan untuk tidak menggunakan fitur-fitur IDE yang membuatnya mudah untuk melakukan hal-hal abstrak.

Christopher Mahan
sumber