Apakah menggunakan "baru" di konstruktor selalu buruk?

37

Saya telah membaca bahwa menggunakan "baru" dalam konstruktor (untuk objek lain selain yang bernilai sederhana) adalah praktik yang buruk karena membuat pengujian unit tidak mungkin (karena itu kolaborator tersebut perlu dibuat juga dan tidak dapat diejek). Karena saya tidak benar-benar berpengalaman dalam pengujian unit, saya mencoba mengumpulkan beberapa aturan yang akan saya pelajari terlebih dahulu. Juga, apakah ini aturan yang secara umum valid, terlepas dari bahasa yang digunakan?

Ezoela Vacca
sumber
9
Itu tidak membuat pengujian menjadi tidak mungkin. Itu membuat pemeliharaan dan pengujian kode Anda lebih sulit. Telah membaca lem baru misalnya.
David Arno
38
"selalu" selalu salah. :) Ada praktik terbaik, tetapi ada banyak sekali pengecualian.
Paul
63
Bahasa apa ini? newberarti berbagai hal dalam berbagai bahasa.
user2357112 mendukung Monica
13
Pertanyaan ini sedikit tergantung pada bahasanya, bukan? Bahkan tidak semua bahasa memiliki newkata kunci. Bahasa apa yang Anda tanyakan?
Bryan Oakley
7
Aturan konyol. Gagal menggunakan injeksi ketergantungan ketika Anda seharusnya tidak banyak berhubungan dengan kata kunci "baru". Alih-alih mengatakan bahwa "menggunakan baru adalah masalah jika Anda menggunakannya untuk memutus inversi dependensi", katakan saja "jangan putus inversi dependensi".
Matt Timmermans

Jawaban:

36

Selalu ada pengecualian, dan saya mengambil masalah dengan 'selalu' dalam judul, tapi ya, pedoman ini secara umum valid, dan juga berlaku di luar konstruktor juga.

Menggunakan baru dalam konstruktor melanggar D dalam SOLID (prinsip inversi ketergantungan). Itu membuat kode Anda sulit untuk diuji karena pengujian unit adalah tentang isolasi; sulit untuk mengisolasi kelas jika memiliki referensi konkret.

Ini bukan hanya tentang pengujian unit. Bagaimana jika saya ingin mengarahkan repositori ke dua database berbeda sekaligus? Kemampuan untuk lulus dalam konteks saya sendiri memungkinkan saya untuk instantiate dua repositori berbeda yang menunjuk ke lokasi yang berbeda.

Tidak menggunakan yang baru di konstruktor membuat kode Anda lebih fleksibel. Ini juga berlaku untuk bahasa yang dapat menggunakan konstruksi selain newuntuk inisialisasi objek.

Namun, jelas, Anda perlu menggunakan penilaian yang baik. Ada banyak waktu ketika itu newboleh digunakan , atau di mana lebih baik tidak melakukannya, tetapi Anda tidak akan memiliki konsekuensi negatif. Di beberapa titik di suatu tempat, newharus dipanggil. Berhati-hatilah untuk memanggil newdi dalam kelas yang bergantung pada banyak kelas lainnya.

Melakukan sesuatu seperti menginisialisasi koleksi pribadi kosong di konstruktor Anda baik-baik saja, dan menyuntikkannya akan tidak masuk akal.

Semakin banyak referensi yang dimiliki kelas, semakin berhati-hati Anda untuk tidak menelepon newdari dalamnya.

TheCatWhisperer
sumber
12
Saya benar-benar tidak melihat nilai apa pun dalam aturan ini. Ada aturan dan pedoman tentang cara menghindari kopling ketat dalam kode. Aturan ini tampaknya berurusan dengan masalah yang sama persis, kecuali bahwa itu terlalu ketat sementara tidak memberi kami nilai tambahan. Jadi apa gunanya? Mengapa membuat objek header dummy untuk implementasi LinkedList saya alih-alih menangani semua kasus khusus yang berasal dari head = nullcara apa pun meningkatkan kode? Mengapa meninggalkan koleksi termasuk nol dan membuatnya sesuai permintaan lebih baik daripada melakukannya di konstruktor?
Voo
22
Anda tidak mengerti intinya. Ya, modul kegigihan adalah sesuatu yang mutlak tidak harus bergantung pada modul level yang lebih tinggi. Tetapi "jangan pernah menggunakan yang baru" tidak mengikuti dari "beberapa hal harus disuntikkan dan tidak secara langsung digabungkan". Jika Anda mengambil salinan Agile Software Development yang tepercaya, Anda akan melihat bahwa Martin pada umumnya berbicara tentang modul, bukan kelas tunggal: "Modul tingkat tinggi berurusan dengan kebijakan tingkat tinggi aplikasi. Kebijakan ini umumnya tidak terlalu peduli dengan detail yang diterapkan mereka."
Voo
11
Jadi intinya adalah Anda harus menghindari ketergantungan antar modul - terutama antar modul dengan level abstraksi yang berbeda. Tetapi sebuah modul bisa lebih dari satu kelas dan tidak ada gunanya membuat antarmuka antara kode yang dipasangkan dengan erat dari lapisan abstraksi yang sama. Dalam aplikasi yang dirancang dengan baik yang mengikuti prinsip-prinsip SOLID tidak setiap kelas harus mengimplementasikan antarmuka dan tidak semua konstruktor harus selalu hanya mengambil antarmuka sebagai parameter.
Voo
18
Saya downvoted karena banyak sup kata kunci dan tidak banyak diskusi tentang pertimbangan praktis. Ada sesuatu tentang repo ke dua DB, tapi jujur, itu bukan contoh dunia nyata.
jpmc26
5
@ jpmc26, BJoBnh Tanpa masuk ke apakah saya memaafkan jawaban ini atau tidak, intinya diungkapkan dengan sangat jelas. Jika Anda berpikir "prinsip inversi ketergantungan", "referensi konkret" atau "inisialisasi objek" hanyalah kata kunci, maka sebenarnya Anda tidak tahu apa artinya. Itu bukan salah jawaban.
R. Schmitz
50

Sementara saya lebih suka menggunakan konstruktor untuk hanya menginisialisasi instance baru daripada membuat beberapa objek lain, objek helper ok, dan Anda harus menggunakan penilaian Anda apakah ada sesuatu seperti helper internal atau tidak.

Jika kelas mewakili koleksi, maka itu mungkin memiliki array penolong internal atau daftar atau hashset. Itu akan digunakan newuntuk membuat pembantu ini dan itu akan dianggap cukup normal. Kelas tidak menawarkan injeksi untuk menggunakan pembantu internal yang berbeda dan tidak punya alasan untuk melakukannya. Dalam hal ini, Anda ingin menguji metode publik objek, yang mungkin pergi ke mengumpulkan, menghapus, dan mengganti elemen dalam koleksi.


Dalam beberapa hal, konstruk kelas bahasa pemrograman adalah mekanisme untuk membuat abstraksi tingkat yang lebih tinggi, dan kami membuat abstraksi semacam itu untuk menjembatani kesenjangan antara domain masalah dan primitif bahasa pemrograman. Namun, mekanisme kelas hanyalah alat; itu bervariasi berdasarkan bahasa pemrograman, dan, beberapa abstraksi domain, dalam beberapa bahasa, hanya memerlukan beberapa objek pada tingkat bahasa pemrograman.

Singkatnya, Anda harus menggunakan penilaian apakah abstraksi hanya memerlukan satu atau lebih objek internal / helper, sementara masih dilihat oleh penelepon sebagai abstraksi tunggal, atau, apakah objek lain akan lebih baik terkena penelepon untuk membuat untuk kontrol dependensi, yang akan disarankan, misalnya, ketika pemanggil melihat objek-objek lain dalam menggunakan kelas.

Erik Eidt
sumber
4
+1. Ini benar sekali. Anda harus mengidentifikasi apa perilaku kelas yang dimaksudkan, dan yang menentukan detail implementasi mana yang perlu diekspos dan mana yang tidak. Ada semacam permainan intuisi halus yang harus Anda mainkan dengan menentukan apa "tanggung jawab" kelas itu juga.
jpmc26
27

Tidak semua kolaborator cukup menarik untuk diuji unit secara terpisah, Anda dapat (secara tidak langsung) mengujinya melalui kelas hosting / instantiating. Ini mungkin tidak sejalan dengan gagasan beberapa orang tentang perlunya menguji setiap kelas, setiap metode publik, dll. Terutama ketika melakukan tes sesudahnya. Saat menggunakan TDD, Anda dapat menolak 'kolaborator' ini mengekstraksi kelas yang sudah sepenuhnya diuji dari proses pengujian pertama Anda.

Joppe
sumber
14
Not all collaborators are interesting enough to unit-test separatelyakhir cerita :-), Kasus ini mungkin dan tidak ada yang berani menyebutkan. @Joppe saya mendorong Anda untuk sedikit menguraikan jawabannya. Misalnya, Anda bisa menambahkan beberapa contoh kelas yang hanya detail implementasi (tidak cocok untuk penggantian) dan bagaimana mereka dapat diekstraksi yang terakhir jika kami anggap perlu.
Laiv
@Laiv model domain biasanya konkret, non-abstrak, Anda tidak perlu menyuntikkan objek bersarang di sana. Objek nilai sederhana / objek kompleks yang tidak memiliki logika juga merupakan kandidat.
Joppe
4
+1, tentu saja. Jika suatu bahasa telah diatur sehingga Anda harus menelepon new File()untuk melakukan sesuatu yang berhubungan dengan file, tidak ada gunanya melarang panggilan itu. Apa yang akan Anda lakukan, tulis tes regresi terhadap Filemodul stdlib ? Tidak mungkin. Di sisi lain, memanggil newpada diri diciptakan kelas lebih meragukan.
Kilian Foth
7
@KilianFoth: Unit keberuntungan mencoba sesuatu yang secara langsung memanggil new File().
Phoshi
1
Itu dugaan . Kita dapat menemukan kasus di mana itu tidak masuk akal, kasus di mana itu tidak berguna dan kasus di mana itu masuk akal dan itu berguna. Ini masalah kebutuhan dan preferensi.
Laiv
13

Karena saya tidak benar-benar berpengalaman dalam pengujian unit, saya mencoba mengumpulkan beberapa aturan yang akan saya pelajari terlebih dahulu.

Hati-hati mempelajari "aturan" untuk masalah yang belum pernah Anda temui. Jika Anda menemukan beberapa "aturan" atau "praktik terbaik", saya sarankan mencari contoh mainan sederhana di mana aturan ini "seharusnya" digunakan, dan mencoba menyelesaikan masalah itu sendiri , mengabaikan apa yang dikatakan "aturan".

Dalam hal ini, Anda bisa mencoba membuat 2 atau 3 kelas sederhana dan beberapa perilaku yang harus mereka terapkan. Laksanakan kelas dengan cara apa pun yang terasa alami dan tulis unit test untuk setiap perilaku. Buatlah daftar masalah yang Anda temui, misalnya jika Anda memulai dengan hal-hal yang bekerja satu arah, maka harus kembali dan mengubahnya nanti; jika Anda bingung tentang bagaimana hal-hal yang seharusnya cocok bersama; jika Anda merasa terganggu menulis boilerplate; dll.

Kemudian cobalah memecahkan masalah yang sama dengan mengikuti "aturan". Sekali lagi, buat daftar masalah yang Anda temui. Bandingkan daftar tersebut, dan pikirkan tentang situasi mana yang lebih baik ketika mengikuti aturan, dan yang mungkin tidak.


Mengenai pertanyaan Anda yang sebenarnya, saya cenderung memilih pendekatan port dan adapter , di mana kami membuat perbedaan antara "logika inti" dan "layanan" (ini mirip dengan membedakan antara fungsi murni dan prosedur yang efektif).

Inti logika adalah semua tentang menghitung hal-hal "di dalam" aplikasi, berdasarkan pada domain masalah. Ini mungkin berisi kelas seperti User, Document, Order, Invoice, dll baik-baik saja untuk memiliki kelas inti panggilan newuntuk kelas inti lainnya, karena mereka "internal" rincian pelaksanaan. Misalnya, membuat Ordermungkin juga membuat Invoicedan Documentmerinci apa yang dipesan. Tidak perlu mengejek ini selama tes, karena ini adalah hal-hal aktual yang ingin kita uji!

Port dan adapter adalah bagaimana logika inti berinteraksi dengan dunia luar. Di sinilah hal-hal seperti Database, ConfigFile, EmailSender, dll hidup. Ini adalah hal-hal yang membuat pengujian sulit, sehingga disarankan untuk membuat ini di luar logika inti, dan meneruskannya sesuai kebutuhan (baik dengan injeksi ketergantungan, atau sebagai argumen metode, dll.).

Dengan cara ini, logika inti (yang merupakan bagian khusus aplikasi, di mana logika bisnis yang penting tinggal, dan tunduk pada churn yang paling banyak) dapat diuji sendiri, tanpa harus peduli dengan database, file, email, dll. Kami hanya dapat mengirimkan beberapa nilai contoh dan memeriksa apakah kami mendapatkan nilai output yang benar.

Port dan adaptor dapat diuji secara terpisah, menggunakan tiruan untuk database, sistem file, dll. Tanpa harus peduli dengan logika bisnis. Kami hanya bisa memberikan beberapa nilai contoh dan memastikan semuanya disimpan / dibaca / dikirim / dll. secara tepat.

Warbo
sumber
6

Izinkan saya untuk menjawab pertanyaan, mengumpulkan apa yang saya anggap sebagai poin kunci di sini. Saya akan mengutip beberapa pengguna untuk singkatnya.

Selalu ada pengecualian, tetapi ya, aturan ini umumnya berlaku, dan juga berlaku di luar konstruktor.

Menggunakan baru dalam konstruktor melanggar D dalam SOLID (dependensi inversi utama). Itu membuat kode Anda sulit untuk diuji karena pengujian unit adalah tentang isolasi; sulit untuk mengisolasi kelas jika memiliki referensi konkret.

-TheCatWhisperer-

Ya, menggunakan newkonstruktor di dalam sering menyebabkan cacat desain (misalnya kopling ketat) yang membuat desain kami kaku. Sulit untuk diuji ya, tetapi bukan tidak mungkin. Properti yang dimainkan di sini adalah ketahanan (toleransi terhadap perubahan) 1 .

Namun demikian, kutipan di atas tidak selalu benar. Dalam beberapa kasus, mungkin ada kelas yang dimaksudkan untuk digabungkan secara ketat . David Arno telah mengomentari pasangan.

Tentu saja ada pengecualian di mana kelas adalah objek nilai yang tidak dapat diubah , detail implementasi , dll. Di mana mereka seharusnya digabungkan secara ketat .

-David Arno-

Persis. Beberapa kelas (misalnya kelas dalam) bisa jadi hanya detail implementasi dari kelas utama. Ini dimaksudkan untuk diuji bersama dengan kelas utama, dan mereka tidak perlu diganti atau diperpanjang.

Selain itu, jika pemujaan SOLID kita membuat kita mengekstrak kelas - kelas ini , kita bisa melanggar prinsip lain yang baik. Yang disebut Hukum Demeter . Di sisi lain, yang menurut saya sangat penting dari sudut pandang desain.

Jadi kemungkinan jawabannya, seperti biasa, tergantung . Menggunakan newkonstruktor di dalam bisa menjadi praktik yang buruk. Namun tidak selalu secara sistematis.

Jadi, kita perlu mengevaluasi apakah kelas merupakan detail implementasi (sebagian besar kasus tidak) dari kelas utama. Jika ya, biarkan saja. Jika tidak, pertimbangkan teknik seperti Composition Root atau Dependency Injection oleh IoC Containers .


1: tujuan utama SOLID bukan untuk membuat kode kita lebih dapat diuji. Itu untuk membuat kode kita lebih toleran terhadap perubahan. Lebih fleksibel dan konsekuensinya, lebih mudah untuk diuji

Catatan: David Arno, TheWhisperCat, saya harap Anda tidak keberatan saya mengutip Anda.

Laiv
sumber
3

Sebagai contoh sederhana, pertimbangkan kodesemu berikut

class Foo {
  private:
     class Bar {}
     class Baz inherits Bar {}
     Bar myBar
  public:
     Foo(bool useBaz) { if (useBaz) myBar = new Baz else myBar = new Bar; }
}

Karena newini adalah detail implementasi murni dari Foo, dan keduanya Foo::Bardan Foo::Bazmerupakan bagian dari Foo, ketika pengujian unit Footidak ada gunanya mengejek bagian Foo. Anda hanya mengejek bagian luar Foo saat unit-testing Foo.

MSalters
sumber
-3

Ya, menggunakan 'baru' di kelas root aplikasi Anda adalah bau kode. Ini berarti Anda mengunci kelas agar menggunakan implementasi spesifik, dan tidak akan dapat menggantikan yang lain. Selalu memilih untuk menyuntikkan ketergantungan ke konstruktor. Dengan begitu, Anda tidak hanya akan dapat dengan mudah menyuntikkan dependensi yang diejek selama pengujian, tetapi juga akan membuat aplikasi Anda jauh lebih fleksibel dengan memungkinkan Anda untuk dengan cepat mengganti implementasi yang berbeda jika diperlukan.

EDIT: Untuk downvoters - inilah tautan ke buku pengembangan perangkat lunak yang menandai 'baru' sebagai kemungkinan bau kode: https://books.google.com/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 # v = onepage & q = new% 20keyword% 20code% 20smell & f = false

Eternal21
sumber
15
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.Mengapa ini menjadi masalah? Tidak semua ketergantungan dalam pohon ketergantungan harus terbuka untuk penggantian
Laiv
4
@ Paul: Memiliki implementasi standar berarti Anda mengambil referensi yang terikat erat ke kelas konkret yang ditentukan sebagai default. Itu tidak membuatnya disebut "bau kode," sekalipun.
Robert Harvey
8
@EzoelaVacca: Saya akan berhati-hati menggunakan kata-kata "kode bau" dalam konteks apa pun. Ini seperti mengatakan "republik" atau "demokrat;" apa arti kata-kata itu? Segera setelah Anda memberikan label seperti itu, Anda berhenti memikirkan masalah yang sebenarnya, dan pembelajaran berhenti.
Robert Harvey
4
"Lebih fleksibel" tidak secara otomatis "lebih baik".
whatsisname
4
@EzoelaVacca: Menggunakan newkata kunci bukanlah praktik yang buruk, dan tidak pernah ada. Begitulah cara Anda menggunakan alat yang penting. Anda tidak menggunakan palu godam, misalnya, di mana bola palu bola akan cukup.
Robert Harvey