Sebagian besar proyek yang saya gunakan menggunakan beberapa komponen open-source. Sebagai prinsip umum, apakah itu ide yang baik untuk selalu menghindari mengikat semua komponen kode ke perpustakaan pihak ketiga dan bukannya pergi melalui pembungkus enkapsulasi untuk menghindari rasa sakit perubahan?
Sebagai contoh, sebagian besar proyek PHP kami secara langsung menggunakan log4php sebagai kerangka logging, yaitu mereka instantiate melalui \ Logger :: getLogger (), mereka menggunakan -> info () atau -> memperingatkan () metode, dll. Di masa depan, namun, kerangka kerja logging hipotetis mungkin muncul yang lebih baik dalam beberapa hal. Seperti yang terjadi, semua proyek yang sangat dekat dengan tanda tangan metode log4php harus berubah, di puluhan tempat, agar sesuai dengan tanda tangan baru. Ini jelas akan memiliki dampak luas pada basis kode dan setiap perubahan adalah masalah potensial.
Untuk membuktikan basis kode baru dari skenario semacam ini, saya sering mempertimbangkan (dan kadang-kadang mengimplementasikan) kelas wrapper untuk merangkum fungsionalitas logging dan membuatnya lebih mudah, meskipun tidak mudah, untuk mengubah cara penebangan bekerja di masa depan dengan perubahan minimal ; kode memanggil pembungkus, pembungkus meneruskan panggilan ke kerangka kerja logging du jour .
Mengingat bahwa ada contoh yang lebih rumit dengan perpustakaan lain, apakah saya over-engineering atau ini tindakan pencegahan yang bijaksana dalam banyak kasus?
EDIT: Lebih banyak pertimbangan - menggunakan injeksi dependensi dan uji ganda secara praktis mengharuskan kita abstrak sebagian besar API ("Saya ingin memeriksa kode saya mengeksekusi dan memperbarui statusnya, tetapi tidak menulis komentar log / mengakses database nyata"). Bukankah ini penentu?
sumber
Jawaban:
Jika Anda hanya menggunakan sebagian kecil dari API pihak ketiga, masuk akal untuk menulis pembungkus - ini membantu enkapsulasi dan penyembunyian informasi, memastikan Anda tidak memaparkan API yang mungkin besar ke kode Anda sendiri. Ini juga dapat membantu memastikan bahwa setiap fungsionalitas yang tidak ingin Anda gunakan "tersembunyi".
Alasan bagus lainnya untuk pembungkus adalah jika Anda berharap untuk mengubah perpustakaan pihak ketiga. Jika ini adalah infrastruktur yang Anda tahu tidak akan berubah, jangan tulis pembungkus untuk itu.
sumber
Tanpa mengetahui fitur baru apa yang akan dimiliki oleh pencatat masa depan yang ditingkatkan ini, bagaimana Anda akan menulis bungkusnya? Pilihan paling logis adalah membuat wrapper Anda meng-instantiate semacam kelas logger, dan memiliki metode seperti
->info()
atau->warn()
. Dengan kata lain, pada dasarnya identik dengan API Anda saat ini.Daripada kode masa depan-bukti bahwa saya mungkin tidak perlu mengubah, atau yang mungkin memerlukan penulisan ulang yang tidak dapat dihindari, saya lebih memilih kode "bukti masa lalu". Artinya, pada kesempatan langka ketika saya secara signifikan mengubah komponen, itulah saat saya menulis bungkus agar kompatibel dengan kode terakhir. Namun, kode baru apa pun menggunakan API baru, dan saya refactor kode lama untuk menggunakannya setiap kali saya membuat perubahan pada file yang sama, atau sesuai jadwal. Setelah beberapa bulan, saya dapat menghapus bungkusnya, dan perubahannya bertahap dan kuat.
Dengan kata lain, pembungkus benar-benar hanya masuk akal ketika Anda sudah tahu semua API yang perlu Anda bungkus. Contoh yang bagus adalah jika aplikasi Anda saat ini perlu mendukung banyak driver database, sistem operasi, atau versi PHP yang berbeda.
sumber
Dengan membungkus perpustakaan pihak ketiga Anda menambahkan lapisan abstraksi tambahan di atasnya. Ini memiliki beberapa keunggulan:
Basis kode Anda menjadi lebih fleksibel terhadap perubahan
Jika Anda perlu mengganti pustaka dengan pustaka lain, Anda hanya perlu mengubah implementasi Anda di pembungkus Anda - di satu tempat . Anda dapat mengubah implementasi pembungkus dan tidak perlu mengubah apa pun tentang hal lain, dengan kata lain Anda memiliki sistem yang digabungkan secara longgar. Kalau tidak, Anda harus melalui seluruh basis kode Anda dan membuat modifikasi di mana-mana - yang jelas bukan yang Anda inginkan.
Anda dapat menentukan API pembungkus secara independen dari API perpustakaan
Pustaka yang berbeda dapat memiliki API yang sangat berbeda dan pada saat yang sama tidak ada pustaka yang tepat seperti yang Anda butuhkan. Bagaimana jika beberapa perpustakaan membutuhkan token untuk diteruskan bersama dengan setiap panggilan? Anda dapat melewatkan token di aplikasi Anda di mana pun Anda perlu menggunakan perpustakaan atau Anda dapat menyimpannya di tempat yang lebih terpusat, tetapi dalam hal apa pun Anda memerlukan token tersebut. Kelas wrapper Anda membuat semuanya menjadi sederhana lagi - karena Anda bisa menyimpan token di dalam kelas wrapper Anda, jangan pernah memaparkannya ke komponen apa pun di dalam aplikasi Anda dan sepenuhnya abstrak jauh dari kebutuhan untuk itu. Keuntungan besar jika Anda pernah menggunakan perpustakaan yang tidak menekankan desain API yang baik.
Pengujian unit jauh lebih sederhana
Tes unit seharusnya hanya menguji satu hal. Jika Anda ingin menguji unit kelas Anda harus mengejek dependensinya. Ini menjadi lebih penting jika kelas itu melakukan panggilan jaringan atau mengakses sumber daya lain di luar perangkat lunak Anda. Dengan membungkus pustaka pihak ketiga, mudah untuk mengejek panggilan itu dan mengembalikan data pengujian atau apa pun yang dibutuhkan pengujian unit. Jika Anda tidak memiliki lapisan abstraksi seperti itu, menjadi jauh lebih sulit untuk melakukan ini - dan sebagian besar waktu ini menghasilkan banyak kode jelek.
Anda membuat sistem yang digabungkan secara longgar
Perubahan pada pembungkus Anda tidak berpengaruh pada bagian lain dari perangkat lunak Anda - setidaknya selama Anda tidak mengubah perilaku pembungkus Anda. Dengan memperkenalkan lapisan abstraksi seperti pembungkus ini, Anda dapat menyederhanakan panggilan ke perpustakaan dan dan hampir sepenuhnya menghapus ketergantungan aplikasi Anda di perpustakaan itu. Perangkat lunak Anda hanya akan menggunakan pembungkus dan itu tidak akan membuat perbedaan bagaimana pembungkus diimplementasikan atau bagaimana ia melakukan apa yang dilakukannya.
Contoh Praktis
Mari jujur. Orang-orang dapat berdebat tentang kelebihan dan kekurangan dari hal seperti ini selama berjam-jam - itulah sebabnya saya lebih suka hanya menunjukkan contoh kepada Anda.
Katakanlah Anda memiliki beberapa jenis aplikasi Android dan Anda perlu mengunduh gambar. Ada banyak perpustakaan di luar sana yang membuat memuat dan menyimpan gambar dengan mudah, misalnya Picasso atau Universal Image Loader .
Kita sekarang dapat mendefinisikan antarmuka yang akan kita gunakan untuk membungkus perpustakaan mana pun yang akhirnya kita gunakan:
Ini adalah antarmuka yang sekarang dapat kita gunakan di seluruh aplikasi kapan pun kita perlu memuat gambar. Kami dapat membuat implementasi antarmuka ini dan menggunakan injeksi ketergantungan untuk menyuntikkan contoh implementasi di mana pun kami menggunakan
ImageService
.Katakanlah kita awalnya memutuskan untuk menggunakan Picasso. Kami sekarang dapat menulis implementasi
ImageService
yang menggunakan Picasso secara internal:Cukup lurus ke depan jika Anda bertanya kepada saya. Membungkus perpustakaan tidak harus rumit untuk menjadi berguna. Antarmuka dan implementasinya memiliki kurang dari 25 baris kode gabungan sehingga hampir tidak ada upaya untuk membuat ini, tapi kami sudah mendapatkan sesuatu dengan melakukan ini. Lihat
Context
bidang dalam implementasinya? Kerangka kerja injeksi ketergantungan pilihan Anda sudah akan siap menyuntikkan dependensi itu sebelum kami menggunakanImageService
aplikasi kami , sekarang aplikasi Anda tidak perlu peduli tentang bagaimana gambar diunduh dan dependensi apa pun yang mungkin dimiliki perpustakaan. Semua aplikasi Anda lihat adalahImageService
dan ketika itu membutuhkan gambar yang dipanggilload()
dengan url - sederhana & mudah.Namun manfaat nyata datang ketika kita mulai mengubah banyak hal. Bayangkan kita sekarang perlu mengganti Picasso dengan Universal Image Loader karena Picasso tidak mendukung beberapa fitur yang benar-benar kita butuhkan saat ini. Apakah kita sekarang harus menyisir basis kode kita dan dan dengan teliti mengganti semua panggilan ke Picasso dan kemudian menangani puluhan kesalahan kompilasi karena kita lupa beberapa panggilan Picasso? Tidak. Yang perlu kita lakukan adalah membuat implementasi baru
ImageService
dan memberi tahu kerangka kerja injeksi ketergantungan kita untuk menggunakan implementasi ini mulai sekarang:Seperti yang Anda lihat implementasi mungkin sangat berbeda, tetapi itu tidak masalah. Kami tidak perlu mengubah satu baris kode pun di aplikasi kami. Kami menggunakan pustaka yang sangat berbeda yang mungkin memiliki fitur yang sama sekali berbeda atau mungkin digunakan sangat berbeda tetapi aplikasi kami tidak peduli. Sama seperti sebelumnya aplikasi kami hanya melihat
ImageService
antarmuka denganload()
metodenya dan bagaimanapun metode ini diterapkan tidak masalah lagi.Setidaknya bagi saya ini semua sudah terdengar cukup bagus, tapi tunggu! Masih ada lagi. Bayangkan Anda sedang menulis unit test untuk kelas yang sedang Anda kerjakan dan kelas ini menggunakan
ImageService
. Tentu saja Anda tidak dapat membiarkan unit test Anda melakukan panggilan jaringan ke beberapa sumber daya yang terletak di beberapa server lain, tetapi karena Anda sekarang menggunakanImageService
Anda dapat dengan mudah membiarkanload()
kembali statis yangBitmap
digunakan untuk unit test dengan menerapkan mockedImageService
:Untuk meringkas dengan membungkus perpustakaan pihak ketiga, basis kode Anda menjadi lebih fleksibel terhadap perubahan, secara keseluruhan lebih sederhana, lebih mudah untuk diuji dan Anda mengurangi penggabungan komponen yang berbeda dalam perangkat lunak Anda - semua hal yang menjadi semakin penting saat Anda memelihara perangkat lunak.
sumber
Saya pikir membungkus perpustakaan pihak ketiga hari ini kalau-kalau ada sesuatu yang lebih baik datang besok adalah pelanggaran yang sangat boros dari YAGNI. Jika Anda berulang kali memanggil kode pihak ketiga dengan cara yang khas untuk aplikasi Anda, Anda akan (harus) mengubah panggilan itu menjadi kelas pembungkus untuk menghilangkan pengulangan. Kalau tidak, Anda sepenuhnya menggunakan API perpustakaan dan pembungkus apa pun akan terlihat seperti perpustakaan itu sendiri.
Sekarang anggaplah perpustakaan baru muncul dengan kinerja superior atau apa pun. Dalam kasus pertama, Anda hanya menulis ulang pembungkus untuk API baru. Tidak masalah.
Dalam kasus kedua, Anda membuat pembungkus mengadaptasi antarmuka lama untuk mendorong perpustakaan baru. Sedikit lebih banyak pekerjaan, tetapi tidak ada masalah, dan tidak ada lebih banyak pekerjaan daripada yang akan Anda lakukan jika Anda telah menulis bungkus sebelumnya.
sumber
Alasan dasar untuk menulis pembungkus di sekitar perpustakaan pihak ketiga adalah agar Anda bisa menukar perpustakaan pihak ketiga itu tanpa mengubah kode yang menggunakannya. Anda tidak dapat menghindari penggabungan ke sesuatu, jadi argumennya adalah bahwa lebih baik memasangkan ke API yang telah Anda tulis.
Apakah ini sepadan dengan usaha adalah cerita yang berbeda. Debat itu kemungkinan akan berlanjut untuk waktu yang lama.
Untuk proyek-proyek kecil, di mana kemungkinan perubahan seperti itu diperlukan rendah, itu mungkin upaya yang tidak perlu. Untuk proyek yang lebih besar, fleksibilitas itu mungkin lebih besar daripada upaya ekstra untuk membungkus perpustakaan. Namun, sulit untuk mengetahui apakah itu yang terjadi sebelumnya.
Cara lain untuk melihatnya adalah prinsip dasar mengabstraksi apa yang mungkin berubah. Jadi, jika perpustakaan pihak ketiga sudah mapan dan tidak mungkin diubah, mungkin tidak apa-apa untuk membungkusnya. Namun, jika perpustakaan pihak ketiga relatif baru, ada kemungkinan lebih besar bahwa perpustakaan itu perlu diganti. Yang mengatakan, pengembangan perpustakaan yang mapan telah ditinggalkan banyak kali. Jadi, ini bukan pertanyaan yang mudah dijawab.
sumber
Selain apa yang sudah dikatakan @Oded , saya hanya ingin menambahkan jawaban ini untuk tujuan khusus pencatatan.
Saya selalu memiliki antarmuka untuk logging, tetapi saya belum pernah mengganti
log4foo
kerangka kerja.Hanya perlu setengah jam untuk menyediakan antarmuka dan menulis bungkusnya, jadi saya kira Anda tidak membuang waktu terlalu banyak jika ternyata tidak perlu.
Ini adalah kasus khusus YAGNI. Meskipun saya tidak membutuhkannya, tidak butuh banyak waktu dan saya merasa lebih aman. Jika hari pertukaran logger benar-benar datang, saya akan senang saya berinvestasi setengah jam karena itu akan menyelamatkan saya lebih dari satu hari bertukar panggilan dalam proyek dunia nyata. Dan saya tidak pernah menulis atau melihat unit test untuk logging (terlepas dari tes untuk implementasi logger itu sendiri), jadi harapkan cacat tanpa pembungkus.
sumber
Saya sedang berurusan dengan masalah ini pada proyek yang sedang saya kerjakan. Tetapi dalam kasus saya perpustakaan adalah untuk grafis dan dengan demikian saya dapat membatasi itu digunakan untuk sejumlah kecil kelas yang berurusan dengan grafik, dibandingkan menaburkannya di seluruh proyek. Dengan demikian, cukup mudah untuk beralih API nanti jika perlu; dalam kasus seorang penebang masalah menjadi jauh lebih rumit.
Jadi saya akan mengatakan keputusan itu banyak berkaitan dengan apa yang sebenarnya dilakukan perpustakaan pihak ketiga dan seberapa banyak rasa sakit yang terkait dengan mengubahnya. Jika mengubah semua panggilan API akan mudah, maka itu mungkin tidak layak dilakukan. Namun jika mengubah perpustakaan nanti akan sangat sulit maka saya mungkin akan membungkusnya sekarang.
Di luar itu, jawaban lain telah mencakup pertanyaan utama dengan sangat baik sehingga saya hanya ingin fokus pada tambahan terakhir itu, tentang injeksi ketergantungan dan benda tiruan. Tentu saja ini tergantung pada bagaimana tepatnya kerangka logging Anda bekerja, tetapi dalam kebanyakan kasus tidak ada yang tidak memerlukan pembungkus (meskipun mungkin akan mendapat manfaat dari satu). Buat saja API untuk objek tiruan Anda persis sama dengan pustaka pihak ke-3 dan kemudian Anda dapat dengan mudah bertukar di objek tiruan untuk pengujian.
Faktor utama di sini adalah apakah perpustakaan pihak ke-3 bahkan diimplementasikan melalui injeksi dependensi (atau pelacak layanan atau beberapa pola yang longgar seperti itu). Jika fungsi pustaka diakses melalui metode statis atau singleton atau sesuatu, maka Anda harus membungkusnya dalam objek yang dapat Anda gunakan dengan injeksi dependensi.
sumber
Saya sangat di kamp pembungkus dan tidak bisa mengganti perpustakaan pihak ketiga dengan prioritas terbesar (meskipun itu adalah bonus). Alasan utama saya bahwa nikmat pembungkus sederhana
Dan ini memanifestasikan dirinya, biasanya, dalam bentuk satu kapal duplikasi kode, seperti pengembang menulis 8 baris kode hanya untuk membuat
QButton
dan gaya itu cara itu harus mencari aplikasi, hanya untuk desainer yang ingin tidak hanya melihat tetapi juga fungsi tombol untuk berubah sepenuhnya untuk seluruh perangkat lunak yang pada akhirnya mengharuskan kembali dan menulis ulang ribuan baris kode, atau menemukan bahwa memodernisasi pipa rendering memerlukan penulisan ulang epik karena basis kode menaburkan ad-hoc tingkat rendah yang diperbaiki pipeline kode OpenGL di semua tempat alih-alih memusatkan desain penyaji waktu nyata dan meninggalkan penggunaan OGL hanya untuk implementasinya.Desain ini tidak disesuaikan dengan kebutuhan desain khusus kami. Mereka cenderung menawarkan superset besar-besaran dari apa yang sebenarnya dibutuhkan (dan apa yang bukan bagian dari desain sama pentingnya, jika tidak lebih dari apa yang ada), dan antarmuka mereka tidak dirancang untuk secara khusus melayani kebutuhan kita dalam "tingkat tinggi" pikir = satu permintaan "semacam cara yang menghalangi kita dari semua kontrol desain pusat jika kita menggunakannya secara langsung. Jika pengembang akhirnya menulis kode tingkat jauh lebih rendah dari yang seharusnya diperlukan untuk mengekspresikan apa yang mereka butuhkan, mereka kadang-kadang bisa berakhir dengan membungkusnya sendiri dengan cara ad-hoc yang membuatnya jadi Anda berakhir dengan lusinan yang ditulis dengan tergesa-gesa dan kasar- pembungkus yang dirancang dan didokumentasikan, bukan pembungkus yang dirancang dengan baik dan terdokumentasi dengan baik.
Tentu saja saya akan menerapkan pengecualian yang kuat untuk perpustakaan di mana pembungkusnya adalah terjemahan satu-ke-satu dari apa yang ditawarkan API pihak ketiga. Dalam hal ini mungkin tidak ada desain tingkat lebih tinggi untuk dicari yang lebih langsung mengungkapkan persyaratan bisnis dan desain (seperti itu mungkin terjadi untuk sesuatu yang lebih menyerupai perpustakaan "utilitas"). Tetapi jika ada desain yang jauh lebih tersesuaikan tersedia yang jauh lebih langsung mengekspresikan kebutuhan kita, maka saya sangat berada di kamp pembungkus, sama seperti saya sangat mendukung penggunaan fungsi tingkat yang lebih tinggi dan menggunakannya kembali dengan menguraikan kode perakitan seluruh tempat.
Anehnya saya telah berbenturan dengan pengembang dengan cara-cara di mana mereka tampak sangat tidak percaya dan pesimis terhadap kemampuan kita merancang, katakanlah, fungsi untuk membuat tombol dan mengembalikannya sehingga mereka lebih suka menulis 8 baris kode tingkat lebih rendah yang berfokus pada mikroskopis rincian pembuatan tombol (yang akhirnya perlu diubah berulang kali di masa mendatang) selama mendesain dan menggunakan fungsi tersebut. Saya bahkan tidak melihat tujuan dari kita mencoba merancang apa pun di tempat pertama jika kita tidak bisa mempercayai diri kita untuk merancang pembungkus semacam ini dengan cara yang masuk akal.
Dengan kata lain saya melihat perpustakaan pihak ketiga sebagai cara untuk berpotensi menghemat waktu yang sangat besar dalam implementasi, bukan sebagai pengganti untuk merancang sistem.
sumber
Gagasan saya tentang Perpustakaan Pihak ketiga:
Ada beberapa diskusi baru-baru ini di komunitas iOS tentang pro dan kontra (OK, sebagian besar kontra) menggunakan dependensi pihak ketiga. Banyak argumen yang saya lihat agak umum - mengelompokkan semua perpustakaan pihak ketiga ke dalam satu keranjang. Seperti kebanyakan hal, tidak sesederhana itu. Jadi, mari kita fokus pada satu kasus saja
Haruskah kita menghindari menggunakan perpustakaan UI pihak ketiga?
Alasan untuk mempertimbangkan perpustakaan pihak ketiga:
Tampaknya ada dua alasan utama pengembang mempertimbangkan untuk menggunakan perpustakaan pihak ketiga:
Sebagian besar perpustakaan UI ( tidak semua! ) Cenderung masuk ke dalam kategori kedua. Benda ini bukan ilmu roket, tetapi butuh waktu untuk membangunnya dengan benar.
Ada cukup banyak dua jenis kontrol / tampilan:
UICollectionView
dariUIKit
.UIPickerView
. Sebagian besar perpustakaan pihak ketiga cenderung masuk dalam kategori kedua. Terlebih lagi, mereka sering diekstraksi dari basis kode yang sudah ada yang dioptimalkan.Asumsi awal tidak diketahui
Banyak pengembang melakukan review kode dari kode internal mereka tetapi mungkin mengambil kualitas kode sumber pihak ketiga begitu saja. Layak untuk menghabiskan sedikit waktu hanya menjelajahi kode perpustakaan. Anda mungkin pada akhirnya terkejut melihat beberapa bendera merah, misalnya swizzling digunakan di tempat yang tidak diperlukan.
Anda tidak bisa menyembunyikannya
Karena cara UIKit dirancang, Anda kemungkinan besar tidak akan dapat menyembunyikan perpustakaan UI pihak ketiga, misalnya di belakang adaptor. Perpustakaan akan terjalin dengan kode UI Anda menjadi de-facto proyek Anda.
Biaya waktu masa depan
UIKit berubah dengan setiap rilis iOS. Segalanya akan pecah. Ketergantungan pihak ketiga Anda tidak akan bebas perawatan seperti yang Anda harapkan.
Kesimpulan:
Dari pengalaman pribadi saya, sebagian besar penggunaan kode UI pihak ketiga bermula untuk bertukar fleksibilitas yang lebih kecil untuk mendapatkan waktu tertentu.
Kami menggunakan kode yang sudah jadi untuk mengirimkan rilis kami saat ini lebih cepat. Cepat atau lambat, kami mencapai batas perpustakaan dan berdiri di depan keputusan yang sulit: apa yang harus dilakukan selanjutnya?
sumber
Menggunakan perpustakaan secara langsung lebih ramah bagi tim pengembang. Ketika pengembang baru bergabung, ia mungkin sepenuhnya berpengalaman dengan semua kerangka kerja yang digunakan namun tidak akan dapat berkontribusi secara produktif sebelum mempelajari API yang dikembangkan di rumah Anda. Ketika pengembang yang lebih muda mencoba untuk maju dalam grup Anda, ia akan dipaksa untuk mempelajari API spesifik Anda yang tidak ada di tempat lain, alih-alih memperoleh kompetensi generik yang lebih berguna. Jika seseorang mengetahui fitur atau kemungkinan yang berguna dari API asli, mungkin tidak dapat menjangkau lapisan yang ditulis oleh seseorang yang tidak menyadarinya. Jika seseorang akan mendapatkan tugas pemrograman sambil mencari pekerjaan, mungkin tidak dapat mendemonstrasikan hal-hal dasar yang ia gunakan berkali-kali, hanya karena selama ini ia mengakses fungsionalitas yang dibutuhkan melalui pembungkus Anda.
Saya pikir masalah ini mungkin lebih penting daripada kemungkinan jauh menggunakan perpustakaan yang sama sekali berbeda nanti. Satu-satunya kasus saya akan menggunakan pembungkus adalah ketika migrasi ke implementasi lain pasti direncanakan atau API yang dibungkus tidak cukup beku dan terus berubah.
sumber