Di masa lalu, saya biasanya melakukan sebagian besar manipulasi objek pada metode utama yang sedang dibuat / dimutakhirkan, tetapi saya mendapati diri saya mengambil pendekatan yang berbeda akhir-akhir ini, dan saya ingin tahu apakah ini merupakan praktik yang buruk.
Ini sebuah contoh. Katakanlah saya memiliki repositori yang menerima User
entitas, tetapi sebelum memasukkan entitas, kami memanggil beberapa metode untuk memastikan semua bidangnya disetel ke apa yang kami inginkan. Sekarang, daripada memanggil metode dan menetapkan nilai bidang dari dalam metode Sisipkan, saya memanggil serangkaian metode persiapan yang membentuk objek sebelum penyisipannya.
Metode lama:
public void InsertUser(User user) {
user.Username = GenerateUsername(user);
user.Password = GeneratePassword(user);
context.Users.Add(user);
}
Metode Baru:
public void InsertUser(User user) {
SetUsername(user);
SetPassword(user);
context.Users.Add(user);
}
private void SetUsername(User user) {
var username = "random business logic";
user.Username = username;
}
private void SetPassword(User user) {
var password = "more business logic";
user.Password = password;
}
Pada dasarnya, apakah praktik menetapkan nilai properti dari metode lain merupakan praktik buruk?
sumber
user
dilewatkan dengan referensi, kode dapat menariknya keluar dari tangan penelepon dan menggantinya dengan hanya mengatakan, katakanuser = null;
,.Jawaban:
Masalahnya di sini adalah bahwa
User
sebenarnya bisa mengandung dua hal yang berbeda:Entitas Pengguna yang lengkap, yang dapat diteruskan ke penyimpanan data Anda.
Kumpulan elemen data yang diperlukan dari pemanggil untuk memulai proses pembuatan entitas Pengguna. Sistem harus menambahkan nama pengguna dan kata sandi sebelum benar-benar Pengguna yang valid seperti pada # 1 di atas.
Ini terdiri dari nuansa tidak berdokumen untuk model objek Anda yang tidak diungkapkan dalam sistem tipe Anda sama sekali. Anda hanya perlu "mengetahuinya" sebagai pengembang. Itu tidak bagus, dan itu mengarah ke pola kode aneh seperti yang Anda temui.
Saya sarankan Anda membutuhkan dua entitas, misalnya
User
kelas danEnrollRequest
kelas. Yang terakhir dapat berisi semua yang perlu Anda ketahui untuk membuat Pengguna. Itu akan terlihat seperti kelas Pengguna Anda, tetapi tanpa nama pengguna dan kata sandi. Maka Anda bisa melakukan ini:Penelepon mulai dengan hanya informasi pendaftaran dan mendapatkan kembali pengguna yang sudah selesai setelah dimasukkan. Dengan cara ini Anda menghindari mutasi kelas apa pun dan Anda juga memiliki perbedaan jenis-aman antara pengguna yang dimasukkan dan yang tidak.
sumber
InsertUser
kemungkinan memiliki efek samping menyisipkan. Saya tidak yakin apa artinya "icky" dalam konteks ini. Sedangkan untuk menggunakan "pengguna" yang belum ditambahkan, Anda sepertinya telah melewatkan intinya. Jika belum ditambahkan, itu bukan pengguna, hanya permintaan untuk membuat pengguna. Anda bebas bekerja dengan permintaan itu, tentu saja.Efek samping tidak masalah selama tidak datang secara tak terduga. Jadi tidak ada yang salah secara umum ketika repositori memiliki metode menerima pengguna dan mengubah keadaan internal pengguna. Tapi IMHO nama metode seperti
InsertUser
tidak mengkomunikasikan hal ini dengan jelas , dan itulah yang membuatnya rentan kesalahan. Saat menggunakan repo Anda, saya akan mengharapkan panggilan sepertiuntuk mengubah status internal repositori, bukan status objek pengguna. Masalah ini ada di kedua implementasi Anda, bagaimana
InsertUser
hal ini secara internal sama sekali tidak relevan.Untuk mengatasi ini, Anda bisa
pisahkan inisialisasi dari penyisipan (sehingga pemanggil
InsertUser
kebutuhan untuk menyediakanUser
objek yang diinisialisasi penuh , atau,membangun inisialisasi ke dalam proses konstruksi objek pengguna (seperti yang disarankan oleh beberapa jawaban lain), atau
coba saja cari nama yang lebih baik untuk metode yang mengekspresikan lebih jelas apa yang dilakukannya.
Jadi memilih nama metode seperti
PrepareAndInsertUser
,OrchestrateUserInsertion
,InsertUserWithNewNameAndPassword
atau apa pun yang Anda memilih untuk membuat efek samping yang lebih jelas.Tentu saja, nama metode yang panjang menunjukkan bahwa metode tersebut mungkin melakukan "terlalu banyak" (pelanggaran SRP), tetapi kadang-kadang Anda tidak ingin atau tidak dapat dengan mudah memperbaiki ini, dan ini adalah solusi pragmatis yang paling tidak mengganggu.
sumber
Saya melihat dua opsi yang Anda pilih, dan saya harus mengatakan bahwa saya lebih memilih metode lama daripada metode baru yang diusulkan. Ada beberapa alasan untuk itu meskipun mereka pada dasarnya melakukan hal yang sama.
Dalam kedua kasus Anda mengatur
user.UserName
danuser.Password
. Saya memiliki reservasi tentang item kata sandi, tetapi reservasi tersebut tidak sesuai dengan topik yang sedang dibahas.Implikasi memodifikasi objek referensi
Metode Lama vs. Metode Baru
Metode lama membuat hal-hal lebih mudah untuk diuji:
GenerateUserName()
dapat diuji secara independen. Anda dapat menulis tes terhadap metode itu dan memastikan nama-nama tersebut dihasilkan dengan benarGenerateUserName(User user)
dan mempertahankan testability ituMetode baru menyembunyikan mutasi:
User
objeknya berubah hingga kedalaman 2 lapisUser
objek lebih mengejutkan dalam hal ituSetUserName()
tidak lebih dari menetapkan nama pengguna. Itu bukan kebenaran dalam periklanan yang menyulitkan pengembang baru untuk menemukan cara kerja di aplikasi Andasumber
Saya sepenuhnya setuju dengan jawaban John Wu. Sarannya bagus. Tapi itu sedikit merindukan pertanyaan langsung Anda.
Tidak secara inheren.
Anda tidak dapat mengambil ini terlalu jauh, karena Anda akan mengalami perilaku yang tidak terduga. Misalnya
PrintName(myPerson)
tidak boleh mengubah objek orang, karena metode ini menyiratkan itu hanya tertarik membaca nilai yang ada. Tapi itu argumen yang berbeda dari kasus Anda, karenaSetUsername(user)
sangat menyiratkan bahwa itu akan menetapkan nilai.Ini sebenarnya adalah pendekatan yang sering saya gunakan untuk tes unit / integrasi, di mana saya membuat metode khusus untuk mengubah objek untuk mengatur nilai-nilainya ke situasi tertentu yang ingin saya uji.
Sebagai contoh:
Saya secara eksplisit mengharapkan
ArrrangeContractDeletedStatus
metode untuk mengubah keadaanmyContract
objek.Manfaat utama adalah bahwa metode ini memungkinkan saya untuk menguji penghapusan kontrak dengan berbagai kontrak awal; mis. kontrak yang memiliki riwayat status panjang, atau kontrak yang tidak memiliki riwayat status sebelumnya, kontrak yang sengaja memiliki riwayat status keliru, kontrak yang tidak boleh dihapus oleh pengguna pengujian saya.
Jika saya telah bergabung
CreateEmptyContract
danArrrangeContractDeletedStatus
menjadi satu metode; Saya harus membuat beberapa varian dari metode ini untuk setiap kontrak berbeda yang ingin saya uji dalam keadaan terhapus.Dan sementara saya bisa melakukan sesuatu seperti:
Ini bisa berlebihan (karena saya toh mengubah objek), atau saya sekarang memaksakan diri untuk membuat klon
myContract
objek yang dalam; yang sangat sulit jika Anda ingin membahas setiap kasus (bayangkan jika saya ingin properti navigasi beberapa tingkat. Apakah saya perlu mengkloning semuanya? Hanya entitas tingkat atas? ... Begitu banyak pertanyaan, begitu banyak harapan tersirat )Mengubah objek adalah cara termudah untuk mendapatkan apa yang saya inginkan tanpa harus melakukan lebih banyak pekerjaan hanya untuk menghindari tidak mengubah objek sebagai masalah prinsip.
Jadi jawaban langsung untuk pertanyaan Anda adalah bahwa itu pada dasarnya bukan praktik yang buruk, selama Anda tidak mengaburkan bahwa metode ini bertanggung jawab untuk mengubah objek yang dikirimkan. Untuk situasi Anda saat ini, itu dibuat sangat jelas melalui nama metode.
sumber
Saya menemukan yang lama dan yang baru bermasalah.
Cara lama:
1)
InsertUser(User user)
Saat membaca nama metode ini muncul di IDE saya, hal pertama yang terlintas di benak saya adalah
Nama metode harus dibaca
AddUserToContext
. Setidaknya, inilah yang dilakukan metode ini pada akhirnya.2)
InsertUser(User user)
Ini jelas melanggar prinsip kejutan paling tidak. Katakanlah, saya melakukan pekerjaan saya sejauh ini dan menciptakan contoh baru
User
dan memberinyaname
dan menetapkanpassword
, saya akan terkejut:a) ini tidak hanya memasukkan pengguna ke dalam sesuatu
b) itu juga menumbangkan niat saya untuk memasukkan pengguna apa adanya; nama dan kata sandi diganti.
c) apakah ini menunjukkan, bahwa itu juga merupakan pelanggaran terhadap prinsip tanggung jawab tunggal.
Jalan baru:
1)
InsertUser(User user)
Masih melanggar SRP dan prinsip yang paling tidak mengejutkan
2)
SetUsername(User user)
Pertanyaan
Lebih baik:
SetRandomName
atau sesuatu yang mencerminkan niat.3)
SetPassword(User user)
Pertanyaan
Lebih baik:
SetRandomPassword
atau sesuatu yang mencerminkan niat.Saran:
Yang ingin saya baca adalah:
Mengenai pertanyaan awal Anda:
Tidak. Ini lebih merupakan masalah selera.
sumber