Apakah OK untuk fungsi untuk mengubah parameter

17

Kami memiliki lapisan data yang membungkus Linq To SQL. Dalam datalayer ini kita memiliki metode ini (disederhanakan)

int InsertReport(Report report)
{
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
    return report.ID; 
}

Saat mengirim perubahan, ID laporan diperbarui dengan nilai dalam basis data yang kemudian kami kembalikan.

Dari sisi panggilan sepertinya ini (disederhanakan)

var report = new Report();
DataLayer.InsertReport(report);
// Do something with report.ID

Melihat kode, ID telah diatur di dalam fungsi InsertReport sebagai semacam efek samping, dan kemudian kita mengabaikan nilai kembali.

Pertanyaan saya adalah, haruskah saya mengandalkan efek samping dan melakukan sesuatu seperti ini sebagai gantinya.

void InsertReport(Report report)
{
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
}

atau haruskah kita mencegahnya

int InsertReport(Report report)
{
    var newReport = report.Clone();
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport.ID; 
}

bahkan mungkin

Report InsertReport(Report report)
{
    var newReport = report.Clone();
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport; 
}

Pertanyaan ini muncul ketika kami membuat unit test dan menemukan bahwa tidak benar-benar jelas bahwa properti ID parameter laporan akan diperbarui dan bahwa untuk mengejek perilaku efek samping terasa salah, kode akan berbau jika Anda mau.

John Petrak
sumber
2
Untuk itulah dokumentasi API.

Jawaban:

16

Ya, tidak apa-apa, dan cukup umum. Ini bisa menjadi tidak jelas, seperti yang Anda temukan.

Secara umum, saya cenderung memiliki metode tipe kegigihan untuk mengembalikan instance objek yang diperbarui. Itu adalah:

Report InsertReport(Report report)
{        
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
    return report; 
}

Ya, Anda mengembalikan objek yang sama dengan yang Anda lewati, tetapi itu membuat API lebih jelas. Tidak perlu untuk mengkloning - jika ada sesuatu yang akan menyebabkan kebingungan jika, seperti dalam kode asli Anda, penelepon terus menggunakan objek yang mereka lewati.

Pilihan lain adalah menggunakan DTO

Report InsertReport(ReportDTO dto)
{
    var newReport = Report.Create(dto);
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport; 
}

Dengan begitu API sangat jelas, dan penelepon tidak dapat secara tidak sengaja mencoba dan menggunakan objek yang diteruskan / dimodifikasi. Bergantung pada apa yang dilakukan kode Anda, itu bisa sedikit menyusahkan.

Ben Hughes
sumber
Saya tidak akan mengembalikan objek jika Ira tidak diperlukan. Itu terlihat seperti pemrosesan ekstra dan penggunaan memori (berlebih). Aku pergi kekosongan atau pengecualian, jika tidak perlu. Selain itu, saya memilih sampel DTO Anda.
Independen
Semua jawaban ini meringkas diskusi kita sendiri. Ini tampaknya menjadi pertanyaan tanpa jawaban nyata. Pernyataan Anda "Ya, Anda mengembalikan objek yang sama dengan yang Anda lewati, tetapi itu membuat API lebih jelas." cukup banyak menjawab pertanyaan kami.
John Petrak
4

IMO ini adalah contoh langka di mana efek samping dari perubahan diinginkan - karena entitas Laporan Anda MEMILIKI ID, itu sudah bisa dianggap memiliki kekhawatiran DTO, dan bisa dibilang ada kewajiban ORM untuk memastikan bahwa dalam memori Laporan entitas disimpan selaras dengan objek representasi basis data.

StuartLC
sumber
6
+1 - setelah memasukkan entitas dalam DB, Anda akan mengharapkannya untuk memiliki ID. Akan lebih mengejutkan jika entitas kembali tanpa itu.
MattDavey
2

Masalahnya adalah bahwa tanpa dokumentasi, tidak jelas sama sekali apa yang dilakukan metode ini dan terutama mengapa mengembalikan integer.

Solusi termudah adalah menggunakan nama yang berbeda untuk metode Anda. Sesuatu seperti:

int GenerateIdAndInsert(Report report)

Namun, ini tidak cukup ambigu: jika, seperti dalam C #, instance dari reportobjek dilewatkan dengan referensi, akan sulit untuk mengetahui apakah reportobjek asli dimodifikasi, atau jika metode mengkloningnya dan memodifikasi hanya klon. Jika Anda memilih untuk memodifikasi objek asli, akan lebih baik untuk memberi nama metode:

void ChangeIdAndInsert(Report report)

Solusi yang lebih rumit (dan mungkin kurang optimal) adalah sangat memperbaiki kode. Bagaimana dengan:

using (var transaction = new TransactionScope())
{
    var id = this.Data.GenerateReportId(); // We need to find an available ID...
    this.Data.AddReportWithId(id, report); // ... and use this ID to insert a report.
    transaction.Complete();
}
Arseni Mourzenko
sumber
2

Biasanya programmer berharap bahwa hanya metode instance objek yang dapat mengubah kondisinya. Dengan kata lain, saya tidak terkejut jika report.insert()mengubah id laporan, dan mudah untuk memeriksa. Tidaklah mudah untuk bertanya-tanya untuk setiap metode di seluruh aplikasi apakah itu mengubah id laporan atau tidak.

Saya juga berpendapat bahwa mungkin IDseharusnya tidak menjadi milik Reportsama sekali. Karena tidak berisi id yang valid untuk waktu yang lama, Anda benar-benar memiliki dua objek berbeda sebelum dan setelah penyisipan, dengan perilaku yang berbeda. Objek "sebelum" dapat dimasukkan, tetapi tidak dapat diambil, diperbarui, atau dihapus. Objek "setelah" adalah kebalikannya. Satu memiliki id dan yang lainnya tidak. Cara mereka ditampilkan mungkin berbeda. Daftar mereka muncul mungkin berbeda. Izin pengguna terkait mungkin berbeda. Keduanya adalah "laporan" dalam arti bahasa Inggris, tetapi keduanya sangat berbeda.

Di sisi lain, kode Anda mungkin cukup sederhana sehingga satu objek akan cukup, tetapi itu sesuatu yang perlu dipertimbangkan jika kode Anda dibumbui if (validId) {...} else {...}.

Karl Bielefeldt
sumber
0

Tidak, itu tidak baik! Sangat cocok untuk prosedur untuk memodifikasi parameter hanya dalam bahasa prosedural, di mana tidak ada cara lain; dalam bahasa OOP panggilan metode perubahan pada objek, dalam hal ini pada laporan (seperti report.generateNewId ()).

Dalam hal ini metode Anda melakukan 2 hal, maka hancurkan SRP: ia memasukkan catatan dalam db dan menghasilkan id baru. Penelepon tidak dapat mengetahui bahwa metode Anda juga menghasilkan id baru karena hanya disebut insertRecord ().

m3th0dman
sumber
3
Ermm ... bagaimana jika db.Reports.InsertOnSubmit(report)memanggil metode perubahan pada objek?
Stephen C
Tidak apa-apa ... itu harus dihindari, tetapi dalam hal ini LINQ ke SQL sedang melakukan modifikasi parameter sehingga tidak seperti OP dapat menghindari itu tanpa efek klon melompat hoop (yang merupakan pelanggaran SRP sendiri).
Telastyn
@StephenC Saya mengatakan bahwa Anda harus memanggil metode itu pada objek laporan; dalam hal ini tidak ada artinya dalam melewatkan objek yang sama sebagai parameter.
m3th0dman
@ Telastyn saya berbicara dalam kasus umum; praktik yang baik tidak dapat dihormati 100%. Dalam kasus khususnya tidak ada yang bisa menyimpulkan apa cara terbaik dari 5 baris kode ...
m3th0dman