Melewati objek ke metode yang mengubah objek, apakah itu pola umum (anti-)?

17

Saya membaca tentang bau kode umum dalam buku Refactoring Martin Fowler . Dalam konteks itu, saya bertanya-tanya tentang pola yang saya lihat di basis kode, dan apakah orang dapat secara objektif menganggapnya sebagai anti-pola.

Pola adalah satu tempat objek dilewatkan sebagai argumen ke satu atau lebih metode, yang semuanya mengubah keadaan objek, tetapi tidak ada yang mengembalikan objek. Jadi ia mengandalkan sifat referensi pass by (dalam hal ini) C # /. NET.

var something = new Thing();
// ...
Foo(something);
int result = Bar(something, 42);
Baz(something);

Saya menemukan bahwa (terutama ketika metode tidak dinamai dengan tepat) saya perlu melihat ke metode tersebut untuk memahami jika keadaan objek telah berubah. Itu membuat pemahaman kode lebih kompleks, karena saya perlu melacak beberapa level dari panggilan-tumpukan.

Saya ingin mengusulkan untuk meningkatkan kode tersebut untuk mengembalikan objek lain (dikloning) dengan status baru, atau apa pun yang diperlukan untuk mengubah objek di situs panggilan.

var something1 =  new Thing();
// ...

// Let's return a new instance of Thing
var something2 = Foo(something1);

// Let's use out param to 'return' other info about the operation
int result;
var something3 = Bar(something2, out result);

// If necessary, let's capture and make explicit complex changes
var changes = Baz(something3)
something3.Apply(changes);

Bagi saya sepertinya pola pertama dipilih berdasarkan asumsi

  • bahwa itu kurang berfungsi, atau membutuhkan lebih sedikit baris kode
  • bahwa itu memungkinkan kita untuk mengubah objek, dan mengembalikan beberapa informasi lainnya
  • bahwa itu lebih efisien karena kita memiliki lebih sedikit contoh Thing.

Saya mengilustrasikan suatu alternatif, tetapi untuk mengusulkannya, orang perlu memiliki argumen yang menentang solusi asli. Apa, jika ada, argumen yang dapat dibuat untuk membuat kasus bahwa solusi asli adalah anti-pola?

Dan apa, jika ada, yang salah dengan solusi alternatif saya?

Michiel van Oosterhout
sumber
1
Ini adalah efek samping
Dave Hillier
1
@DaveHillier Terima kasih, saya kenal dengan istilah ini, tetapi belum membuat koneksi.
Michiel van Oosterhout

Jawaban:

9

Ya, solusi asli adalah anti-pola karena alasan yang Anda jelaskan: membuat sulit untuk berpikir tentang apa yang sedang terjadi, objek tidak bertanggung jawab atas keadaan / implementasinya sendiri (melanggar enkapsulasi). Saya juga akan menambahkan bahwa semua perubahan negara adalah kontrak implisit dari metode ini, membuat metode itu rapuh dalam menghadapi perubahan persyaratan.

Yang mengatakan, solusi Anda memiliki beberapa kelemahan sendiri, yang paling jelas adalah bahwa objek kloning tidak hebat. Ini bisa lambat untuk benda besar. Ini dapat menyebabkan kesalahan di mana bagian lain dari kode berpegang pada referensi lama (yang kemungkinan terjadi dalam basis kode yang Anda uraikan). Membuat objek-objek tersebut secara eksplisit tidak dapat diselesaikan menyelesaikan setidaknya beberapa masalah ini, tetapi merupakan perubahan yang lebih drastis.

Kecuali benda-benda itu kecil dan agak sementara (yang menjadikannya kandidat yang baik untuk kekekalan), saya akan cenderung untuk memindahkan lebih banyak transisi negara ke objek itu sendiri. Itu memungkinkan Anda menyembunyikan detail implementasi transisi ini dan menetapkan persyaratan yang lebih kuat di sekitar siapa / apa / di mana transisi negara tersebut terjadi.

Telastyn
sumber
1
Misalnya, jika saya memiliki objek "File", saya tidak akan mencoba untuk memindahkan metode perubahan keadaan apa pun ke objek itu - yang akan melanggar SRP. Itu tetap valid bahkan ketika Anda memiliki kelas sendiri, bukan kelas perpustakaan seperti "File" - menempatkan setiap logika transisi status ke kelas objek tidak benar-benar masuk akal.
Doc Brown
@Tetastyn Saya tahu ini jawaban lama, tapi saya kesulitan memvisualisasikan saran Anda di paragraf terakhir secara konkret. Bisakah Anda menguraikan atau memberi contoh?
AaronLS
@ AaronLS - Alih-alih Bar(something)(dan memodifikasi keadaan something), buatlah Baranggota somethingtipe. something.Bar(42)lebih cenderung bermutasi something, sementara juga memungkinkan Anda untuk menggunakan alat OO (negara pribadi, antarmuka, dll) untuk melindungi somethingnegara
Telastyn
14

ketika metode tidak dinamai dengan tepat

Sebenarnya, itu adalah bau kode asli. Jika Anda memiliki objek yang dapat diubah, ia menyediakan metode untuk mengubah kondisinya. Jika Anda memiliki panggilan ke metode semacam itu yang tertanam dalam tugas dari beberapa pernyataan lagi, tidak apa-apa untuk meringkas tugas itu ke metode sendiri - yang membuat Anda dengan tepat situasi yang dijelaskan. Tetapi jika Anda tidak memiliki nama metode seperti Foodan Bar, tetapi metode yang menjelaskan bahwa mereka mengubah objek, saya tidak melihat masalah di sini. Pikirkan

void AddMessageToLog(Logger logger, string msg)
{
    //...
}

atau

void StripInvalidCharsFromName(Person p)
{
// ...
}

atau

void AddValueToRepo(Repository repo,int val)
{
// ...
}

atau

void TransferMoneyBetweenAccounts(Account source, Account destination, decimal amount)
{
// ...
}

atau sesuatu seperti itu - Saya tidak melihat alasan di sini untuk mengembalikan objek yang dikloning untuk metode tersebut, dan juga tidak ada alasan untuk melihat penerapannya untuk memahami bahwa mereka akan mengubah keadaan objek yang dilewati.

Jika Anda tidak ingin efek samping, buat objek Anda tidak berubah, itu akan memberlakukan metode seperti yang di atas untuk mengembalikan objek yang diubah (dikloning) tanpa mengubah yang asli.

Doc Brown
sumber
Anda benar, mengganti nama metode refactoring dapat memperbaiki situasi dengan membuat efek samping menjadi jelas. Ini bisa menjadi sulit, jika modifikasi sedemikian rupa sehingga nama metode singkat tidak mungkin.
Michiel van Oosterhout
2
@michielvoo: jika penamaan metode ringkas tampaknya tidak memungkinkan, metode Anda mengelompokkan hal-hal yang salah bersama-sama alih-alih membangun abstraksi fungsional untuk tugas yang dilakukannya (dan itu benar dengan atau tanpa efek samping).
Doc Brown
4

Ya, lihat http://codebetter.com/matthewpodwysocki/2008/04/30/side-effecting-functions-are-code-smells/ untuk salah satu dari banyak contoh orang yang menunjukkan bahwa efek samping yang tidak diharapkan adalah buruk.

Secara umum prinsip dasarnya adalah bahwa perangkat lunak dibangun berlapis-lapis, dan setiap lapisan harus menyajikan abstraksi paling bersih untuk yang berikutnya. Dan abstraksi yang bersih adalah abstraksi di mana Anda harus menyimpan sesedikit mungkin dalam pikiran untuk menggunakannya. Itu disebut modularitas, dan berlaku untuk semuanya, mulai dari fungsi tunggal hingga protokol jaringan.

btilly
sumber
Saya akan mencirikan apa yang digambarkan OP sebagai "efek samping yang diharapkan." Misalnya, delegasi yang dapat Anda lewati ke mesin jenis yang beroperasi pada setiap elemen dalam daftar. Pada dasarnya itulah yang ForEach<T>terjadi.
Robert Harvey
@RobertHarvey Keluhan tentang metode yang tidak disebutkan namanya dengan benar, dan karena harus membaca kode untuk mengetahui efek sampingnya, membuat mereka pasti tidak mengharapkan efek samping.
btilly
Aku akan memberimu itu. Tetapi akibatnya adalah bahwa metode yang didokumentasikan dengan benar bernama dengan efek situs yang diharapkan mungkin bukan anti-pola.
Robert Harvey
@RobertHarvey, saya setuju. Kuncinya adalah bahwa efek samping yang signifikan sangat penting untuk diketahui, dan perlu didokumentasikan dengan hati-hati (lebih disukai atas nama metode).
btilly
Saya akan mengatakan itu adalah campuran dari efek samping yang tidak terduga dan tidak jelas. Terima kasih untuk tautannya.
Michiel van Oosterhout
3

Pertama-tama, ini tidak tergantung pada "pass by reference nature of" itu tergantung pada objek yang menjadi tipe referensi yang bisa berubah. Dalam bahasa non-fungsional itu hampir selalu akan terjadi.

Kedua, apakah ini masalah atau tidak, tergantung pada objek dan seberapa erat perubahan dalam prosedur yang berbeda diikat bersama - jika Anda gagal membuat perubahan di Foo dan yang menyebabkan Bar mogok, maka itu merupakan masalah. Tidak perlu bau kode, tapi ini masalah dengan Foo atau Bar atau Sesuatu (mungkin Bar karena harus memeriksa inputnya, tetapi bisa juga Sesuatu yang dimasukkan ke dalam kondisi tidak valid yang harus dicegah).

Saya tidak akan mengatakan bahwa itu naik ke tingkat anti-pola, melainkan sesuatu yang harus diperhatikan.

jmoreno
sumber
2

Saya berpendapat ada sedikit perbedaan antara A.Do(Something)memodifikasi somethingdan something.Do()memodifikasi something. Dalam kedua kasus, itu harus jelas dari nama metode yang dipanggil yang somethingakan dimodifikasi. Jika tidak jelas dari nama metode, terlepas dari apakah somethingitu parameter this,, atau bagian dari lingkungan, itu tidak boleh dimodifikasi.

svidgen
sumber
1

Saya pikir tidak apa-apa untuk mengubah keadaan objek dalam beberapa skenario. Misalnya, saya memiliki daftar pengguna dan saya ingin menerapkan filter berbeda ke daftar sebelum mengembalikannya ke klien.

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();

var excludeAdminUsersFilter = new ExcludeAdminUsersFilter();
var filterByAnotherCriteria = new AnotherCriteriaFilter();

excludeAdminUsersFilter.Apply(users);
filterByAnotherCriteria.Apply(users); 

Dan ya, Anda bisa membuatnya cantik dengan memindahkan pemfilteran ke metode lain, sehingga Anda akan berakhir dengan sesuatu di baris:

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();
Filter(users);

Di mana Filter(users)akan menjalankan filter di atas.

Saya tidak ingat di mana tepatnya saya menemukan ini sebelumnya, tapi saya pikir itu disebut sebagai pipa penyaringan.

CodeART
sumber
0

Saya tidak yakin apakah solusi baru yang diusulkan (menyalin objek) adalah sebuah pola. Masalahnya, seperti yang telah Anda tunjukkan, adalah tata nama fungsi yang buruk.

Katakanlah saya menulis operasi matematika yang kompleks sebagai fungsi f () . Saya mendokumentasikan bahwa f () adalah fungsi yang memetakan NXNke N, dan algoritma di belakangnya. Jika fungsi tersebut dinamai secara tidak tepat, dan tidak didokumentasikan, dan tidak memiliki kasus pengujian yang menyertainya, dan Anda harus memahami kode tersebut, dalam hal ini kode tersebut tidak berguna.

Tentang solusi Anda, beberapa pengamatan:

  • Aplikasi dirancang dari berbagai aspek: ketika suatu objek hanya digunakan untuk menyimpan nilai-nilai, atau melewati batas-batas komponen, adalah bijaksana untuk mengubah bagian dalam objek secara eksternal daripada mengisinya dengan perincian tentang cara mengubah.
  • Objek kloning menyebabkan kebutuhan memori membengkak, dan dalam banyak kasus mengarah pada keberadaan objek yang setara di negara yang tidak kompatibel ( Xmenjadi Ysetelah f(), tetapi Xsebenarnya Y,) dan mungkin inkonsistensi temporal.

Masalah yang Anda coba atasi adalah masalah yang valid; Namun, bahkan dengan rekayasa ulang yang sangat besar dilakukan, masalahnya diatasi, tidak diselesaikan.

CMR
sumber
2
Ini akan menjadi jawaban yang lebih baik jika Anda menghubungkan pengamatan Anda dengan pertanyaan OP. Seperti, itu lebih merupakan komentar daripada jawaban.
Robert Harvey
1
@RobertHarvey +1, pengamatan yang baik, saya setuju, akan mengeditnya.
CMR