Kritik dan kerugian injeksi ketergantungan

118

Ketergantungan injeksi (DI) adalah pola yang terkenal dan modis. Sebagian besar insinyur mengetahui kelebihannya, seperti:

  • Membuat isolasi dalam pengujian unit mungkin / mudah
  • Mendefinisikan dependensi kelas secara eksplisit
  • Memfasilitasi desain yang baik ( prinsip tanggung jawab tunggal (SRP) misalnya)
  • Mengaktifkan implementasi switching dengan cepat ( DbLoggeralih-alih ConsoleLoggermisalnya)

Saya rasa ada konsensus luas industri bahwa DI adalah pola yang baik dan bermanfaat. Tidak terlalu banyak kritik saat ini. Kerugian yang disebutkan dalam komunitas biasanya kecil. Beberapa dari mereka:

  • Peningkatan jumlah kelas
  • Pembuatan antarmuka yang tidak perlu

Saat ini kami membahas desain arsitektur dengan kolega saya. Dia cukup konservatif, tetapi berpikiran terbuka. Dia suka mempertanyakan hal-hal, yang saya anggap baik, karena banyak orang di TI hanya menyalin tren terbaru, mengulangi kelebihannya dan secara umum tidak berpikir terlalu banyak - jangan menganalisis terlalu dalam.

Hal-hal yang ingin saya tanyakan adalah:

  • Haruskah kita menggunakan injeksi ketergantungan ketika kita hanya memiliki satu implementasi?
  • Haruskah kita melarang membuat objek baru kecuali bahasa / kerangka kerja?
  • Apakah menyuntikkan satu ide buruk implementasi (katakanlah kita hanya memiliki satu implementasi sehingga kita tidak ingin membuat antarmuka "kosong") jika kita tidak berencana untuk menguji unit kelas tertentu?
Landeeyo
sumber
33
Apakah Anda benar-benar bertanya tentang injeksi ketergantungan sebagai suatu pola, atau apakah Anda bertanya tentang penggunaan kerangka kerja DI? Ini adalah hal yang sangat berbeda, Anda harus mengklarifikasi bagian mana dari masalah yang Anda minati, atau secara eksplisit bertanya tentang keduanya.
Frax
10
@Frax tentang pola, bukan kerangka kerja
Landeeyo
10
Anda membingungkan inversi ketergantungan dengan injeksi ketergantungan. Yang pertama adalah prinsip desain. Yang terakhir adalah teknik (biasanya diimplementasikan dengan alat yang ada) untuk membangun hierarki objek.
jpmc26
3
Saya sering menulis tes menggunakan database nyata dan tidak ada benda tiruan sama sekali. Berfungsi sangat baik dalam banyak kasus. Dan kemudian Anda tidak perlu antarmuka sebagian besar waktu. Jika Anda memiliki UserServicekelas itu hanya merupakan tempat untuk logika. Itu akan menyuntikkan koneksi database dan tes berjalan di dalam transaksi yang dibatalkan. Banyak yang menyebut praktik buruk ini, tetapi saya menemukan bahwa ini bekerja dengan sangat baik. Tidak perlu memutarbalikkan kode Anda hanya untuk pengujian dan Anda mendapatkan kekuatan pencarian bug dari tes integrasi.
usr
3
DI hampir selalu baik. Yang buruk dengan itu adalah banyak orang berpikir mereka tahu DI tetapi semua yang mereka tahu adalah bagaimana menggunakan beberapa kerangka kerja aneh tanpa bahkan yakin apa yang mereka lakukan. DI saat ini banyak menderita dari Pemrograman Kargo Cargo.
T. Sar

Jawaban:

160

Pertama, saya ingin memisahkan pendekatan desain dari konsep kerangka kerja. Injeksi ketergantungan pada level paling sederhana dan paling mendasar hanyalah:

Objek induk menyediakan semua dependensi yang diperlukan untuk objek anak.

Itu dia. Perhatikan, bahwa tidak ada yang memerlukan antarmuka, kerangka kerja, gaya injeksi apa pun, dll. Agar adil, saya pertama kali mempelajari tentang pola ini 20 tahun yang lalu. Itu bukan hal baru.

Karena lebih dari 2 orang memiliki kebingungan atas istilah orang tua dan anak, dalam konteks injeksi ketergantungan:

  • The orangtua adalah objek yang instantiates dan mengkonfigurasi objek anak menggunakan
  • The anak adalah komponen yang dirancang untuk secara pasif dipakai. Yaitu itu dirancang untuk menggunakan dependensi apa pun yang disediakan oleh orang tua, dan tidak instantiate dependensi itu sendiri.

Ketergantungan injeksi adalah pola untuk komposisi objek .

Mengapa antarmuka?

Antarmuka adalah kontrak. Mereka ada untuk membatasi seberapa erat dua objek dapat digabungkan. Tidak setiap ketergantungan memerlukan antarmuka, tetapi mereka membantu dengan menulis kode modular.

Saat Anda menambahkan dalam konsep pengujian unit, Anda mungkin memiliki dua implementasi konseptual untuk antarmuka tertentu: objek nyata yang ingin Anda gunakan dalam aplikasi Anda, dan objek tiruan atau stubbed yang Anda gunakan untuk menguji kode yang bergantung pada objek. Itu saja bisa menjadi pembenaran yang cukup untuk antarmuka.

Mengapa kerangka kerja

Inisialisasi menginisialisasi dan menyediakan dependensi ke objek anak dapat menakutkan ketika ada banyak dari mereka. Kerangka memberikan manfaat berikut:

  • Ketergantungan otomatis untuk komponen
  • Mengkonfigurasi komponen dengan pengaturan
  • Mengotomatiskan kode pelat ketel agar Anda tidak harus melihatnya ditulis di beberapa lokasi.

Mereka juga memiliki kelemahan berikut:

  • Objek induk adalah "wadah", dan bukan apa pun dalam kode Anda
  • Itu membuat pengujian lebih rumit jika Anda tidak dapat memberikan dependensi secara langsung dalam kode pengujian Anda
  • Ini dapat memperlambat inisialisasi karena menyelesaikan semua dependensi menggunakan refleksi dan banyak trik lainnya
  • Debugging Runtime bisa lebih sulit, terutama jika wadah menyuntikkan proxy antara antarmuka dan komponen aktual yang mengimplementasikan antarmuka (pemrograman berorientasi aspek yang dibangun ke Spring muncul dalam pikiran). Kontainer adalah kotak hitam, dan mereka tidak selalu dibangun dengan konsep apa pun yang memfasilitasi proses debugging.

Semua yang mengatakan, ada trade-off. Untuk proyek kecil di mana tidak ada banyak bagian yang bergerak, dan ada sedikit alasan untuk menggunakan kerangka kerja DI. Namun, untuk proyek yang lebih rumit di mana ada komponen tertentu yang sudah dibuat untuk Anda, kerangka kerjanya dapat dibenarkan.

Bagaimana dengan [artikel acak di Internet]?

Bagaimana dengan itu? Sering kali orang bisa terlalu bersemangat dan menambahkan banyak batasan dan mencaci maki Anda jika Anda tidak melakukan hal-hal dengan "satu cara yang benar". Tidak ada satu cara yang benar. Lihat apakah Anda dapat mengekstraksi sesuatu yang bermanfaat dari artikel dan mengabaikan hal-hal yang tidak Anda setujui.

Singkatnya, pikirkan sendiri dan cobalah.

Bekerja dengan "kepala lama"

Belajar sebanyak mungkin. Apa yang akan Anda temukan dengan banyak pengembang yang memasuki usia 70-an adalah bahwa mereka telah belajar untuk tidak bersikap dogmatis tentang banyak hal. Mereka memiliki metode yang telah mereka gunakan selama beberapa dekade yang menghasilkan hasil yang benar.

Saya memiliki hak istimewa untuk bekerja dengan beberapa di antaranya, dan mereka dapat memberikan umpan balik yang sangat jujur ​​dan masuk akal. Dan di mana mereka melihat nilai, mereka menambahkan alat-alat itu ke daftar lagu mereka.

Berin Loritsch
sumber
6
@ CarlLeth, saya telah bekerja dengan sejumlah kerangka kerja dari varian Spring hingga .net. Spring akan membiarkan Anda menyuntikkan implementasi langsung ke bidang pribadi menggunakan beberapa sihir hitam refleksi / classloader. Satu-satunya cara untuk menguji komponen yang dibangun seperti itu adalah dengan menggunakan wadah. Spring memang memiliki pelari JUnit untuk mengonfigurasi lingkungan pengujian, tetapi lebih rumit daripada mengatur semuanya sendiri. Jadi ya, saya hanya memberi contoh praktis .
Berin Loritsch
17
Ada satu lagi kelemahan yang saya temukan sebagai rintangan dengan DI melalui kerangka ketika saya memakai topi pemecah masalah / pemelihara saya: tindakan seram di kejauhan yang mereka berikan membuat debugging offline lebih sulit. Dalam kasus terburuk, saya harus menjalankan kode untuk melihat bagaimana dependensi diinisialisasi dan diteruskan. Anda menyebutkan ini dalam konteks "pengujian", tetapi sebenarnya jauh lebih buruk jika Anda baru mulai melihat sumbernya, tidak pernah keberatan mencoba menjalankannya (yang mungkin melibatkan satu ton pengaturan). Menghalangi kemampuan saya untuk mengetahui kode apa yang dilakukan dengan hanya meliriknya adalah hal yang buruk.
Jeroen Mostert
1
Antarmuka bukan kontrak, mereka hanya API. Kontrak menyiratkan semantik. Jawaban ini menggunakan terminologi khusus bahasa dan konvensi khusus Java / C #.
Frank Hileman
2
@BerinLoritsch Poin utama dari jawaban Anda sendiri adalah bahwa prinsip DI! = Kerangka kerja DI yang diberikan. Fakta bahwa Spring dapat melakukan hal-hal yang mengerikan dan tidak termaafkan adalah kerugian Spring, bukan kerangka kerja DI pada umumnya. Kerangka DI yang baik membantu Anda mengikuti prinsip DI tanpa trik jahat.
Carl Leth
1
@ CarlLeth: semua kerangka kerja DI dirancang untuk menghapus atau mengotomatisasi beberapa hal yang tidak ingin dijelaskan oleh pemrogram, mereka hanya bervariasi dalam caranya. Sepengetahuan saya, mereka semua menghilangkan kemampuan untuk mengetahui bagaimana (atau jika ) kelas A dan B berinteraksi dengan hanya melihat A dan B - setidaknya, Anda juga perlu melihat pengaturan DI / konfigurasi / konvensi. Bukan masalah bagi programmer (sebenarnya itu yang mereka inginkan), tetapi potensi masalah bagi pengelola / debugger (mungkin programmer yang sama, nanti). Ini adalah trade off yang Anda buat bahkan jika kerangka DI Anda "sempurna".
Jeroen Mostert
88

Injeksi ketergantungan, seperti kebanyakan pola, merupakan solusi untuk masalah . Jadi mulailah dengan menanyakan apakah Anda bahkan memiliki masalah sejak awal. Jika tidak, maka menggunakan pola yang paling mungkin akan membuat kode lebih buruk .

Pertimbangkan terlebih dahulu jika Anda dapat mengurangi atau menghilangkan ketergantungan. Semua hal lain dianggap sama, kami ingin setiap komponen dalam sistem memiliki dependensi sesedikit mungkin. Dan jika ketergantungannya hilang, pertanyaan tentang menyuntikkan atau tidak menjadi diperdebatkan!

Pertimbangkan sebuah modul yang mengunduh beberapa data dari layanan eksternal, mem-parsingnya, dan melakukan beberapa analisis kompleks, dan menulis untuk menghasilkan sebuah file.

Sekarang, jika ketergantungan pada layanan eksternal hardcoded, maka akan sangat sulit untuk menguji unit pemrosesan internal modul ini. Jadi Anda mungkin memutuskan untuk menyuntikkan layanan eksternal dan sistem file sebagai dependensi antarmuka, yang akan memungkinkan Anda untuk menyuntikkan ejekan sebagai gantinya yang pada gilirannya membuat unit-pengujian logika internal menjadi mungkin.

Tetapi solusi yang jauh lebih baik adalah dengan memisahkan analisis dari input / output. Jika analisis diekstraksi ke modul tanpa efek samping, akan lebih mudah untuk diuji. Perhatikan bahwa mengejek adalah bau kode - tidak selalu dapat dihindari, tetapi secara umum, lebih baik jika Anda dapat menguji tanpa mengandalkan mengejek. Jadi dengan menghilangkan dependensi, Anda menghindari masalah yang seharusnya diringankan DI. Perhatikan bahwa desain seperti itu juga mematuhi SRP jauh lebih baik.

Saya ingin menekankan bahwa DI tidak selalu memfasilitasi SRP atau prinsip desain bagus lainnya seperti pemisahan masalah, kohesi tinggi / kopling rendah dan sebagainya. Mungkin juga memiliki efek sebaliknya. Pertimbangkan kelas A yang menggunakan kelas B lain secara internal. B hanya digunakan oleh A dan karena itu sepenuhnya dienkapsulasi dan dapat dianggap sebagai detail implementasi. Jika Anda mengubah ini untuk menyuntikkan B ke konstruktor A, maka Anda telah mengekspos detail implementasi ini dan sekarang pengetahuan tentang ketergantungan ini dan tentang bagaimana menginisialisasi B, masa pakai B dan seterusnya harus ada beberapa tempat lain dalam sistem secara terpisah. dari A. Jadi Anda memiliki keseluruhan arsitektur yang lebih buruk dengan masalah yang bocor.

Di sisi lain ada beberapa kasus di mana DI benar-benar bermanfaat. Misalnya untuk layanan global dengan efek samping seperti logger.

Masalahnya adalah ketika pola dan arsitektur menjadi tujuan dalam diri mereka sendiri daripada alat. Hanya bertanya, "Haruskah kita menggunakan DI?" adalah jenis menempatkan kereta di depan kuda. Anda harus bertanya: "Apakah kita punya masalah?" dan "Apa solusi terbaik untuk masalah ini?"

Sebagian dari pertanyaan Anda adalah: "Haruskah kita membuat antarmuka yang berlebihan untuk memenuhi tuntutan pola?" Anda mungkin sudah menyadari jawaban untuk ini - sama sekali tidak ! Siapa pun yang memberi tahu Anda sebaliknya mencoba menjual sesuatu kepada Anda - kemungkinan besar jam konsultasi mahal. Antarmuka hanya memiliki nilai jika mewakili abstraksi. Antarmuka yang hanya meniru permukaan kelas tunggal disebut "antarmuka header" dan ini adalah antipattern yang dikenal.

JacquesB
sumber
15
Saya sangat setuju! Perhatikan juga bahwa mengejek sesuatu demi hal itu berarti kita tidak benar-benar menguji implementasi yang sebenarnya. Jika Akegunaan Bdalam produksi, tetapi hanya diuji terhadap MockB, pengujian kami tidak memberi tahu kami apakah itu akan berfungsi dalam produksi. Ketika komponen murni (tanpa efek samping) dari model domain menyuntikkan dan mengejek satu sama lain, hasilnya adalah pemborosan besar waktu semua orang, basis kode yang membengkak dan rapuh, dan kepercayaan yang rendah pada sistem yang dihasilkan. Mengejek batas sistem, bukan di antara potongan acak dari sistem yang sama.
Warbo
17
@CarlLeth Mengapa menurut Anda DI membuat kode "dapat diuji dan dipelihara", dan kode tanpa lebih sedikit? JacquesB benar bahwa efek samping adalah hal yang membahayakan kemampuan uji / pemeliharaan. Jika kode tidak memiliki efek samping, kami tidak peduli apa / di mana / kapan / bagaimana ia memanggil kode lain; kita bisa tetap sederhana dan langsung. Jika kode memiliki efek samping, kami harus peduli. DI dapat menarik efek samping dari fungsi dan memasukkannya ke dalam parameter, membuat fungsi-fungsi itu lebih dapat diuji tetapi program lebih kompleks. Terkadang itu tidak dapat dihindari (mis. Akses DB). Jika kode tidak memiliki efek samping, DI hanyalah kompleksitas yang tidak berguna.
Warbo
13
@ CarlLeth: DI adalah salah satu solusi untuk masalah membuat kode dapat diuji secara terpisah jika memiliki dependensi yang melarangnya. Tapi itu tidak mengurangi kompleksitas keseluruhan, juga tidak membuat kode lebih mudah dibaca, yang berarti tidak perlu meningkatkan rawatan. Namun, jika semua dependensi tersebut dapat dihilangkan dengan pemisahan kekhawatiran yang lebih baik, ini dengan sempurna "membatalkan" manfaat DI, karena itu membatalkan kebutuhan DI. Ini sering merupakan solusi yang lebih baik untuk membuat kode lebih dapat diuji dan dipelihara pada saat yang sama.
Doc Brown
5
@Warbo Ini adalah asli dan masih mungkin satu-satunya penggunaan sah mengejek. Bahkan pada batas sistem, jarang diperlukan. Orang-orang benar-benar membuang banyak waktu untuk membuat dan memperbarui tes yang hampir tidak berharga.
Frank Hileman
6
@CarlLeth: ok, sekarang saya melihat dari mana kesalahpahaman itu berasal. Anda berbicara tentang inversi ketergantungan . Tapi, pertanyaannya, jawaban ini dan komentar saya adalah tentang DI = injeksi depency .
Doc Brown
36

Dalam pengalaman saya, ada sejumlah kelemahan injeksi ketergantungan.

Pertama, menggunakan DI tidak menyederhanakan pengujian otomatis sebanyak yang diiklankan. Unit menguji sebuah kelas dengan implementasi tiruan dari sebuah antarmuka memungkinkan Anda memvalidasi bagaimana kelas itu akan berinteraksi dengan antarmuka. Yaitu, ini memungkinkan Anda menguji unit bagaimana kelas yang diuji menggunakan kontrak yang disediakan oleh antarmuka. Namun, ini memberikan jaminan yang jauh lebih besar bahwa input dari kelas yang diuji ke antarmuka adalah seperti yang diharapkan. Ini memberikan jaminan yang agak buruk bahwa kelas yang diuji merespons seperti yang diharapkan untuk keluar dari antarmuka karena hampir secara universal mengejek keluaran, yang sendiri tunduk pada bug, penyederhanaan yang berlebihan, dan sebagainya. Singkatnya, itu TIDAK membiarkan Anda memvalidasi bahwa kelas akan berperilaku seperti yang diharapkan dengan implementasi nyata dari antarmuka.

Kedua, DI membuatnya lebih sulit untuk dinavigasi melalui kode. Ketika mencoba menavigasi ke definisi kelas yang digunakan sebagai input ke fungsi, antarmuka dapat berupa apa saja mulai dari gangguan kecil (mis. Di mana ada satu implementasi) hingga tenggang waktu utama (mis. Ketika antarmuka yang terlalu umum seperti IDisposable digunakan) ketika mencoba menemukan implementasi aktual yang digunakan. Ini dapat mengubah latihan sederhana seperti "Saya perlu memperbaiki pengecualian referensi nol dalam kode yang terjadi tepat setelah pernyataan logging ini dicetak" menjadi upaya sehari penuh.

Ketiga, penggunaan DI dan framework adalah pedang bermata dua. Ini dapat sangat mengurangi jumlah kode pelat-ketel yang dibutuhkan untuk operasi umum. Namun, hal ini dilakukan dengan mengorbankan kebutuhan pengetahuan terperinci tentang kerangka kerja DI tertentu untuk memahami bagaimana operasi umum ini sebenarnya disatukan. Memahami bagaimana dependensi dimuat ke dalam kerangka kerja dan menambahkan dependensi baru ke dalam kerangka kerja untuk menyuntikkan dapat memerlukan membaca cukup banyak bahan latar belakang dan mengikuti beberapa tutorial dasar tentang kerangka kerja. Ini dapat mengubah beberapa tugas sederhana menjadi yang agak memakan waktu.

Eric
sumber
Saya juga akan menambahkan bahwa semakin banyak Anda menginjeksi, semakin lama waktu startup Anda. Kebanyakan kerangka kerja DI membuat semua instance tunggal injeksi pada saat startup, terlepas dari di mana mereka digunakan.
Rodney P. Barbati
7
Jika Anda ingin menguji kelas dengan implementasi nyata (bukan tiruan), Anda dapat menulis tes fungsional - tes yang mirip dengan tes unit, tetapi tidak menggunakan tiruan.
BЈовић
2
Saya pikir paragraf kedua Anda perlu lebih bernuansa: DI itu sendiri tidak menyulitkan (er) menelusuri kode. Paling sederhana, DI hanyalah konsekuensi dari mengikuti SOLID. Yang meningkatkan kompleksitas adalah penggunaan tipuan dan kerangka DI yang tidak perlu . Selain itu, jawaban ini mengenai paku di kepala.
Konrad Rudolph
4
Ketergantungan injeksi, di luar kasus di mana itu benar-benar diperlukan, juga merupakan tanda peringatan bahwa kode berlebihan lainnya mungkin ada dalam kelimpahan besar. Para eksekutif sering terkejut mengetahui bahwa pengembang menambah kompleksitas demi kompleksitas.
Frank Hileman
1
+1. Ini adalah jawaban nyata untuk pertanyaan yang diajukan, dan seharusnya menjadi jawaban yang diterima.
Mason Wheeler
13

Saya mengikuti saran Mark Seemann dari "Dependency injection in .NET" - untuk menduga.

DI harus digunakan ketika Anda memiliki 'ketergantungan yang tidak stabil', misalnya ada peluang yang masuk akal untuk berubah.

Jadi jika Anda berpikir Anda mungkin memiliki lebih dari satu implementasi di masa depan atau implementasinya mungkin berubah, gunakan DI. Kalau tidak, newtidak apa-apa.

rampok
sumber
5
Catatan dia juga memberikan saran berbeda untuk OO dan bahasa fungsional blog.ploeh.dk/2017/01/27/…
jk.
1
Poin bagus. Jika kita membuat antarmuka untuk setiap ketergantungan secara default, maka itu entah bagaimana melawan YAGNI.
Landeeyo
4
Bisakah Anda memberikan referensi ke "injeksi Ketergantungan dalam. NET" ?
Peter Mortensen
1
Jika Anda menguji unit, maka sangat mungkin ketergantungan Anda tidak stabil.
Jaquez
11
Untungnya, pengembang selalu dapat memprediksi masa depan dengan akurasi sempurna.
Frank Hileman
5

Hewan kesayangan saya yang terbesar tentang DI sudah disebutkan dalam beberapa jawaban secara sepintas, tetapi saya akan sedikit mengembangkannya di sini. DI (seperti yang kebanyakan dilakukan hari ini, dengan wadah dll) benar-benar, benar-benar menyakitkan keterbacaan kode. Dan keterbacaan kode bisa dibilang alasan di balik sebagian besar inovasi pemrograman saat ini. Seperti kata seseorang - menulis kode itu mudah. Membaca kode itu sulit. Tetapi ini juga sangat penting, kecuali jika Anda menulis semacam utilitas kecil sekali pakai sekali pakai.

Masalah dengan DI dalam hal ini adalah bahwa itu buram. Wadahnya adalah kotak hitam. Objek hanya muncul dari suatu tempat dan Anda tidak tahu - siapa yang membangunnya dan kapan? Apa yang diteruskan ke konstruktor? Dengan siapa saya berbagi contoh ini? Siapa tahu...

Dan ketika Anda bekerja terutama dengan antarmuka, semua fitur "pergi ke definisi" dari IDE Anda terbakar. Sangat sulit untuk melacak aliran program tanpa menjalankannya dan hanya melangkah untuk melihat hanya implementasi WHICH dari antarmuka yang digunakan di tempat khusus ini. Dan kadang-kadang ada beberapa kendala teknis yang mencegah Anda melangkah. Dan bahkan jika Anda bisa, jika melibatkan melalui usus memutar dari wadah DI, seluruh urusan dengan cepat menjadi latihan frustrasi.

Untuk bekerja secara efisien dengan sepotong kode yang menggunakan DI, Anda harus terbiasa dengannya dan sudah tahu apa yang terjadi.

Vilx-
sumber
3

Mengaktifkan implementasi switching dengan cepat (DbLogger, bukan ConsoleLogger misalnya)

Meskipun DI secara umum jelas merupakan hal yang baik, saya sarankan untuk tidak menggunakannya secara membabi buta untuk semuanya. Misalnya, saya tidak pernah menyuntikkan penebang. Salah satu keunggulan DI adalah membuat dependensi eksplisit dan jelas. Tidak ada gunanya mendaftar ILoggersebagai ketergantungan dari hampir setiap kelas - hanya berantakan. Adalah tanggung jawab pencatat untuk memberikan fleksibilitas yang Anda butuhkan. Semua penebang saya adalah anggota final statis, saya dapat mempertimbangkan menyuntikkan penebang ketika saya membutuhkan yang tidak statis.

Peningkatan jumlah kelas

Ini adalah kerugian dari kerangka DI yang diberikan atau kerangka kerja yang mengejek, bukan dari DI itu sendiri. Di sebagian besar tempat, kelas saya bergantung pada kelas beton, yang berarti tidak diperlukan pelat ketel. Guice (framework Java DI) secara default mengikat sebuah kelas untuk dirinya sendiri dan saya hanya perlu mengesampingkan pengikatan dalam tes (atau mentransfernya secara manual sebagai gantinya).

Pembuatan antarmuka yang tidak perlu

Saya hanya membuat antarmuka saat dibutuhkan (yang agak jarang). Ini berarti bahwa kadang-kadang, saya harus mengganti semua kemunculan kelas dengan sebuah antarmuka, tetapi IDE dapat melakukan ini untuk saya.

Haruskah kita menggunakan injeksi ketergantungan ketika kita hanya memiliki satu implementasi?

Ya, tapi hindari boilerplate yang ditambahkan .

Haruskah kita melarang membuat objek baru kecuali bahasa / kerangka kerja?

Tidak. Akan ada banyak kelas nilai (tidak berubah) dan data (bisa berubah), di mana instance baru saja dibuat dan diedarkan dan di mana tidak ada gunanya menyuntikkan mereka - karena mereka tidak pernah disimpan di objek lain (atau hanya di lainnya benda seperti itu).

Untuk mereka, Anda mungkin perlu menyuntikkan pabrik, tetapi sebagian besar waktu itu tidak masuk akal (bayangkan misalnya, @Value class NamedUrl {private final String name; private final URL url;}Anda benar-benar tidak memerlukan pabrik di sini dan tidak ada yang disuntikkan).

Apakah menyuntikkan satu ide buruk implementasi (katakanlah kita hanya memiliki satu implementasi sehingga kita tidak ingin membuat antarmuka "kosong") jika kita tidak berencana untuk menguji unit kelas tertentu?

IMHO tidak apa-apa asalkan tidak menyebabkan kode mengasapi. Jangan menyuntikkan dependensi tetapi jangan membuat antarmuka (dan tidak ada XML konfigurasi gila!) Karena Anda dapat melakukannya nanti tanpa kerumitan.

Sebenarnya, dalam proyek saya saat ini, ada empat kelas (dari ratusan), yang saya putuskan untuk dikecualikan dari DI karena mereka kelas sederhana yang digunakan di terlalu banyak tempat, termasuk objek data.


Kerugian lain dari kebanyakan kerangka kerja DI adalah overhead runtime. Ini dapat dipindahkan ke waktu kompilasi (untuk Java, ada Belati , tidak tahu tentang bahasa lain).

Namun kelemahan lain adalah keajaiban terjadi di mana-mana, yang dapat disetel ke bawah (misalnya, saya menonaktifkan pembuatan proxy saat menggunakan Guice).

maaartinus
sumber
-4

Saya harus mengatakan bahwa menurut pendapat saya, seluruh konsep Dependency Injection terlalu dibesar-besarkan.

DI adalah hari modern yang setara dengan nilai-nilai global. Hal-hal yang Anda suntikkan adalah lajang global dan objek kode murni, jika tidak, Anda tidak dapat menyuntikkannya. Sebagian besar penggunaan DI dipaksakan pada Anda untuk menggunakan perpustakaan yang diberikan (JPA, Data Musim Semi, dll). Untuk sebagian besar, DI menyediakan lingkungan yang sempurna untuk memelihara dan mengolah spageti.

Dalam semua kejujuran, cara termudah untuk menguji kelas adalah untuk memastikan bahwa semua dependensi dibuat dalam metode yang dapat diganti. Kemudian buat kelas Uji yang berasal dari kelas aktual dan timpa metode itu.

Kemudian Anda instantiate kelas Test dan menguji semua metodenya. Ini tidak akan jelas bagi sebagian dari Anda - metode yang Anda uji adalah yang dimiliki kelas yang sedang diuji. Dan semua tes metode ini terjadi dalam file kelas tunggal - kelas pengujian unit yang terkait dengan kelas yang diuji. Tidak ada overhead di sini - ini adalah cara pengujian unit bekerja.

Dalam kode, konsep ini terlihat seperti ini ...

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

Hasilnya persis sama dengan menggunakan DI - yaitu, ClassUnderTest dikonfigurasikan untuk pengujian.

Satu-satunya perbedaan adalah bahwa kode ini benar-benar ringkas, benar-benar dienkapsulasi, lebih mudah dikodekan, lebih mudah dipahami, lebih cepat, menggunakan lebih sedikit memori, tidak memerlukan konfigurasi alternatif, tidak memerlukan kerangka kerja apa pun, tidak akan pernah menjadi penyebab halaman 4 (WTF!) Tumpukan jejak yang mencakup persis ZERO (0) kelas yang Anda tulis, dan benar-benar jelas bagi siapa pun yang memiliki pengetahuan OO sekecil apa pun, dari pemula hingga Guru (Anda akan berpikir, tetapi akan keliru).

Yang sedang berkata, tentu saja kita tidak bisa menggunakannya - itu terlalu jelas dan tidak cukup trendi.

Namun, pada akhirnya, kekhawatiran terbesar saya dengan DI adalah bahwa dari proyek-proyek yang saya lihat gagal total, semuanya adalah basis kode besar di mana DI adalah lem yang menyatukan semuanya. DI bukan arsitektur - itu benar-benar hanya relevan dalam beberapa situasi, yang sebagian besar dipaksakan pada Anda untuk menggunakan perpustakaan lain (JPA, Data Musim Semi, dll). Sebagian besar, dalam basis kode yang dirancang dengan baik, sebagian besar penggunaan DI akan terjadi pada tingkat di bawah di mana kegiatan pengembangan harian Anda berlangsung.

Rodney P. Barbati
sumber
6
Anda telah menggambarkan bukan yang setara, tetapi kebalikan dari injeksi ketergantungan. Dalam model Anda, setiap objek perlu mengetahui implementasi konkret dari semua dependensinya, tetapi menggunakan DI yang menjadi tanggung jawab komponen "utama" - untuk merekatkan implementasi yang sesuai bersama-sama. Ingatlah bahwa DI bergandengan tangan dengan DI lainnya - inversi ketergantungan, di mana Anda tidak ingin komponen tingkat tinggi memiliki ketergantungan yang keras pada komponen tingkat rendah.
Logan Pickup
1
itu rapi ketika Anda hanya memiliki satu tingkat warisan kelas dan satu tingkat ketergantungan. Tentunya itu akan berubah menjadi neraka di bumi saat ia mengembang?
Ewan
4
jika Anda menggunakan antarmuka dan memindahkan initializeDependencies () ke konstruktor yang sama tetapi lebih rapi. Langkah selanjutnya, menambahkan parameter konstruksi berarti Anda dapat menghapus semua TestClasses Anda.
Ewan
5
Ada banyak yang salah dengan ini. Seperti yang dikatakan orang lain, contoh 'setara DI' Anda sama sekali bukan injeksi ketergantungan, ini adalah antitesis, dan menunjukkan kurangnya pemahaman konsep dan memperkenalkan potensi jebakan lainnya juga: objek yang diinisialisasi sebagian adalah bau kode Sebagai Ewan menyarankan, Pindahkan inisialisasi ke konstruktor, dan sampaikan melalui parameter konstruktor. Maka Anda memiliki DI ...
Mr.Mindor
3
Menambahkan ke @ Mr.Mindor: ada "kopling berurutan" anti-pola yang lebih umum yang tidak hanya berlaku untuk inisialisasi. Jika metode suatu objek (atau, lebih umum, panggilan API) harus dijalankan dalam urutan tertentu, misalnya barhanya dapat dipanggil setelah foo, maka itu adalah API yang buruk. Ia mengklaim menyediakan fungsionalitas ( bar), tetapi kami tidak dapat menggunakannya (karena foomungkin tidak dipanggil). Jika Anda ingin tetap dengan initializeDependenciespola (anti?) Anda, Anda setidaknya harus membuatnya pribadi / dilindungi dan menyebutnya secara otomatis dari konstruktor, sehingga API tulus.
Warbo
-6

Pertanyaan Anda sebenarnya adalah "Apakah pengujian unit buruk?"

99% dari kelas alternatif Anda untuk disuntikkan akan menjadi tiruan yang memungkinkan pengujian unit.

Jika Anda melakukan pengujian unit tanpa DI, Anda memiliki masalah tentang cara mendapatkan kelas untuk menggunakan data mocked atau layanan mocked. Katakanlah 'bagian dari logika' karena Anda mungkin tidak memisahkannya menjadi layanan.

Ada beberapa cara alternatif untuk melakukan ini, tetapi DI adalah yang bagus dan fleksibel. Setelah Anda menggunakannya untuk pengujian, Anda sebenarnya terpaksa menggunakannya di mana-mana, karena Anda memerlukan beberapa kode, bahkan itu disebut 'poor man's DI' yang membuat tipe konkret.

Sulit membayangkan kerugian yang begitu buruk sehingga keuntungan dari pengujian unit kewalahan.

Ewan
sumber
13
Saya tidak setuju dengan pernyataan Anda bahwa DI adalah tentang pengujian unit. Memfasilitasi pengujian unit hanyalah salah satu manfaat dari DI, dan bisa dibilang bukan yang paling penting.
Robert Harvey
5
Saya tidak setuju dengan premis Anda bahwa unit testing dan DI sangat dekat. Dengan menggunakan tiruan / rintisan, kita membuat test suite berbohong kepada kita sedikit lebih: sistem yang diuji semakin jauh dari sistem nyata. Itu buruk secara objektif. Kadang-kadang itu lebih besar daripada terbalik: panggilan FS yang diejek tidak perlu dibersihkan; permintaan HTTP yang dipermainkan cepat, deterministik, dan bekerja offline; dll. Sebaliknya, setiap kali kita menggunakan hard-coded newdi dalam metode, kita tahu bahwa kode yang sama berjalan dalam produksi berjalan selama pengujian.
Warbo
8
Tidak, ini bukan "apakah pengujian unit buruk?", Itu "apakah mengejek (a) benar-benar perlu, dan (b) sepadan dengan peningkatan kompleksitas yang terjadi?" Itu pertanyaan yang sangat berbeda. Pengujian unit tidak buruk (secara harfiah tidak ada yang memperdebatkan ini), dan itu pasti layak secara umum. Tetapi tidak semua pengujian unit memerlukan ejekan, dan ejekan memang membawa biaya besar sehingga harus, setidaknya, digunakan secara bijaksana.
Konrad Rudolph
6
@ Ewan Setelah komentar terakhir Anda, saya rasa kami tidak setuju. Saya mengatakan bahwa sebagian besar unit test tidak memerlukan DI [frameworks] , karena kebanyakan unit test tidak perlu mengejek. Bahkan, saya bahkan akan menggunakan ini sebagai heuristik untuk kualitas kode: jika sebagian besar kode Anda tidak dapat diuji unit tanpa DI / benda tiruan maka Anda telah menulis kode buruk yang terlalu tepat digabungkan. Sebagian besar kode harus dipisahkan secara terpisah, tanggung jawab tunggal, dan tujuan umum, dan dapat diuji secara terpisah.
Konrad Rudolph
5
@Wan Link Anda memberikan definisi yang baik untuk pengujian unit. Dengan definisi itu, Ordercontoh saya adalah unit test: ini menguji metode ( totalmetode Order). Anda mengeluh karena memanggil kode dari 2 kelas, respons saya begitu apa ? Kami tidak menguji "2 kelas sekaligus", kami sedang menguji totalmetode. Kita seharusnya tidak peduli bagaimana metode melakukan tugasnya: itu adalah detail implementasi; mengujinya menyebabkan kerapuhan, kopling ketat, dll. Kami hanya peduli dengan perilaku metode (nilai balik dan efek samping), bukan kelas / modul / register CPU / dll. itu digunakan dalam proses.
Warbo