Saya menulis banyak kode yang melibatkan tiga langkah dasar.
- Dapatkan data dari suatu tempat.
- Ubah data itu.
- Letakkan data itu di suatu tempat.
Saya biasanya menggunakan tiga jenis kelas - terinspirasi oleh pola desain masing-masing.
- Pabrik - untuk membangun objek dari sumber daya tertentu.
- Mediator - untuk menggunakan pabrik, melakukan transformasi, lalu menggunakan komandan.
- Komandan - untuk meletakkan data itu di tempat lain.
Kelas saya cenderung sangat kecil, seringkali metode tunggal (publik), misalnya mendapatkan data, mengubah data, melakukan pekerjaan, menyimpan data. Ini mengarah pada proliferasi kelas, tetapi secara umum bekerja dengan baik.
Di mana saya berjuang adalah ketika saya datang ke pengujian, saya akhirnya akan erat dengan tes. Sebagai contoh;
- Factory - membaca file dari disk.
- Commander - menulis file ke disk.
Saya tidak bisa menguji satu tanpa yang lain. Saya bisa menulis kode 'test' tambahan untuk melakukan baca / tulis disk, tetapi kemudian saya mengulangi sendiri.
Melihat. Net, kelas File mengambil pendekatan yang berbeda, menggabungkan tanggung jawab (dari saya) pabrik dan komandan bersama. Ini memiliki fungsi untuk Buat, Hapus, Ada, dan Baca semua di satu tempat.
Haruskah saya melihat untuk mengikuti contoh. Net dan menggabungkan - terutama ketika berhadapan dengan sumber daya eksternal - kelas saya bersama? Kode itu masih digabungkan, tetapi lebih disengaja - itu terjadi pada implementasi asli, bukan dalam tes.
Apakah masalah saya di sini bahwa saya telah menerapkan Prinsip Tanggung Jawab Tunggal agak terlalu bersemangat? Saya memiliki kelas terpisah yang bertanggung jawab untuk membaca dan menulis. Ketika saya dapat memiliki kelas gabungan yang bertanggung jawab untuk berurusan dengan sumber daya tertentu, misalnya disk sistem.
sumber
Looking at .Net, the File class takes a different approach, it combines the responsibilities (of my) factory and commander together. It has functions for Create, Delete, Exists, and Read all in one place.
- Perhatikan bahwa Anda menyatukan "tanggung jawab" dengan "hal yang harus dilakukan." Tanggung jawab lebih seperti "bidang perhatian". Tanggung jawab kelas File adalah melakukan operasi file.File
pustaka Anda dari C # adalah, untuk semua yang kita tahuFile
kelas hanya bisa menjadi fasad, menempatkan semua operasi file ke satu tempat - ke dalam kelas, tetapi bisa secara internal menggunakan kelas baca / tulis yang sama dengan Anda yang akan sebenarnya mengandung logika yang lebih rumit untuk penanganan file. Kelas semacam itu (theFile
) masih akan mematuhi SRP, karena proses benar-benar bekerja dengan sistem berkas akan diabstraksi di belakang lapisan lain - kemungkinan besar dengan antarmuka pemersatu. Bukan mengatakan itu masalahnya, tapi bisa jadi begitu. :)Jawaban:
Mengikuti prinsip Tanggung Jawab Tunggal mungkin merupakan petunjuk Anda di sini, tetapi di mana Anda memiliki nama yang berbeda.
Segregasi Tanggung Jawab Permintaan Perintah
Pergi belajar itu dan saya pikir Anda akan menemukannya mengikuti pola yang sudah dikenal dan bahwa Anda tidak sendirian dalam bertanya seberapa jauh untuk mengambil ini. Tes asam adalah jika mengikuti ini memberi Anda manfaat nyata atau jika itu hanya mantra buta yang Anda ikuti sehingga Anda tidak perlu berpikir.
Anda telah menyatakan keprihatinan tentang pengujian. Saya tidak berpikir mengikuti CQRS menghalangi penulisan kode yang dapat diuji. Anda mungkin hanya mengikuti CQRS dengan cara yang membuat kode Anda tidak dapat diuji.
Ini membantu untuk mengetahui bagaimana menggunakan polimorfisme untuk membalikkan dependensi kode sumber tanpa perlu mengubah aliran kontrol. Saya tidak begitu yakin di mana keahlian Anda pada tes menulis.
Sepatah kata hati, mengikuti kebiasaan yang Anda temukan di perpustakaan tidak optimal. Perpustakaan memiliki kebutuhan mereka sendiri dan sudah tua. Jadi, bahkan contoh terbaik hanyalah contoh terbaik dari waktu itu.
Ini bukan untuk mengatakan tidak ada contoh yang benar-benar valid yang tidak mengikuti CQRS. Mengikutinya akan selalu sedikit menyakitkan. Tidak selalu layak untuk dibayar. Tetapi jika Anda membutuhkannya, Anda akan senang menggunakannya.
Jika Anda menggunakannya perhatikan kata peringatan ini:
sumber
Anda memerlukan perspektif yang lebih luas untuk menentukan apakah kode tersebut sesuai dengan Prinsip Tanggung Jawab Tunggal. Tidak dapat dijawab hanya dengan menganalisis kode itu sendiri, Anda harus mempertimbangkan kekuatan atau aktor apa yang dapat menyebabkan persyaratan berubah di masa depan.
Katakanlah Anda menyimpan data aplikasi dalam file XML. Faktor apa yang dapat menyebabkan Anda mengubah kode yang terkait dengan membaca atau menulis? Beberapa kemungkinan:
Dalam semua kasus ini, Anda harus mengubah baik membaca dan logika menulis. Dengan kata lain, mereka bukan tanggung jawab yang terpisah.
Tetapi mari kita bayangkan skenario yang berbeda: Aplikasi Anda adalah bagian dari pipa pemrosesan data. Bunyinya beberapa file CSV yang dihasilkan oleh sistem yang terpisah, melakukan beberapa analisis dan pemrosesan, kemudian mengeluarkan file yang berbeda untuk diproses oleh sistem ketiga. Dalam hal ini membaca dan menulis adalah tanggung jawab independen dan harus dipisahkan.
Intinya: Anda secara umum tidak dapat mengatakan jika membaca dan menulis file adalah tanggung jawab yang terpisah, itu tergantung pada peran dalam aplikasi. Tetapi berdasarkan petunjuk Anda tentang pengujian, saya kira itu adalah tanggung jawab tunggal dalam kasus Anda.
sumber
Secara umum Anda memiliki ide yang tepat.
Sepertinya Anda memiliki tiga tanggung jawab. IMO "Mediator" mungkin melakukan banyak hal. Saya pikir Anda harus mulai dengan memodelkan tiga tanggung jawab Anda:
Kemudian suatu program dapat dinyatakan sebagai:
Saya rasa ini bukan masalah. Banyak IMO dari kohesif kecil, kelas yang dapat diuji lebih baik daripada kelas besar, kurang kohesif.
Setiap bagian harus dapat diuji secara independen. Dimodelkan di atas, Anda dapat mewakili membaca / menulis ke file sebagai:
Anda dapat menulis tes integrasi untuk menguji kelas-kelas ini untuk memverifikasi mereka membaca dan menulis ke sistem file. Sisa dari logika dapat ditulis sebagai transformasi. Sebagai contoh jika file adalah format JSON, Anda dapat mengubah
String
s.Kemudian Anda bisa berubah menjadi objek yang tepat:
Masing-masing dapat diuji secara independen. Anda juga dapat menguji unit
program
di atas dengan mengejekreader
,transformer
danwriter
.sumber
FileWriter
dengan membaca langsung dari sistem file daripada menggunakanFileReader
. Terserah kepada Anda apa tujuan Anda dalam ujian. Jika Anda menggunakanFileReader
, tes ini akan rusak jika salah satuFileReader
atauFileWriter
rusak - yang mungkin memerlukan lebih banyak waktu untuk debug.Jadi fokus di sini adalah pada apa yang menyatukan mereka . Apakah Anda melewati objek di antara keduanya (seperti a
File
?) Lalu itu adalah File yang mereka gabungkan, bukan satu sama lain.Dari apa yang Anda katakan, Anda telah memisahkan kelas Anda. Perangkapnya adalah bahwa Anda menguji mereka bersama karena lebih mudah atau 'masuk akal' .
Mengapa Anda memerlukan input
Commander
untuk berasal dari disk? Yang dipedulikan adalah menulis menggunakan input tertentu, maka Anda dapat memverifikasi itu menulis file dengan benar menggunakan apa yang ada dalam tes .Bagian sebenarnya yang Anda uji
Factory
adalah 'apakah ia akan membaca file ini dengan benar dan menampilkan hal yang benar'? Jadi mengejek file sebelum membacanya dalam ujian .Atau, pengujian bahwa Pabrik dan Komandan berfungsi ketika digabungkan bersama baik-baik saja - itu sejalan dengan Pengujian Integrasi dengan cukup bahagia. Pertanyaan di sini lebih merupakan masalah apakah Unit Anda dapat mengujinya secara terpisah atau tidak.
sumber
Ini adalah pendekatan prosedural yang khas, yang ditulis oleh David Parnas pada tahun 1972. Anda berkonsentrasi pada bagaimana segala sesuatunya berjalan. Anda mengambil solusi konkret dari masalah Anda sebagai pola level yang lebih tinggi, yang selalu salah.
Jika Anda mengejar pendekatan berorientasi objek, saya lebih suka berkonsentrasi pada domain Anda . Tentang apa semua ini? Apa tanggung jawab utama sistem Anda? Apa konsep utama yang hadir dalam bahasa pakar domain Anda? Jadi, pahami domain Anda, dekomposisikan, perlakukan bidang tanggung jawab tingkat tinggi sebagai modul Anda , perlakukan konsep tingkat rendah yang direpresentasikan sebagai kata benda sebagai objek Anda. Ini adalah contoh yang saya berikan pada pertanyaan terakhir, sangat relevan.
Dan ada masalah nyata dengan keterpaduan, Anda telah menyebutkannya sendiri. Jika Anda membuat beberapa modifikasi sebagai input logika dan menulis tes di atasnya, itu sama sekali tidak membuktikan bahwa fungsi Anda berfungsi, karena Anda bisa lupa untuk meneruskan data itu ke lapisan berikutnya. Lihat, lapisan-lapisan ini secara intrinsik digabungkan. Dan decoupling buatan membuat segalanya lebih buruk. Saya tahu itu sendiri: Proyek 7 tahun dengan 100 tahun pria di belakang pundak saya ditulis sepenuhnya dengan gaya ini. Lari dari itu jika Anda bisa.
Dan secara keseluruhan hal SRP. Ini semua tentang kohesi yang diterapkan pada ruang masalah Anda, yaitu domain. Itulah prinsip mendasar di balik SRP. Ini menghasilkan objek yang pintar dan menerapkan tanggung jawab mereka untuk diri mereka sendiri. Tidak ada yang mengendalikan mereka, tidak ada yang memberi mereka data. Mereka menggabungkan data dan perilaku, hanya memperlihatkan yang terakhir. Jadi objek Anda menggabungkan validasi data mentah, transformasi data (yaitu, perilaku) dan kegigihan. Itu bisa terlihat seperti berikut:
Akibatnya ada beberapa kelas kohesif yang mewakili beberapa fungsi. Perhatikan bahwa validasi biasanya pergi ke objek-nilai - setidaknya dalam pendekatan DDD .
sumber
Hati-hati dengan abstraksi yang bocor ketika bekerja dengan sistem file - Saya melihatnya terlalu sering diabaikan, dan ia memiliki gejala yang telah Anda jelaskan.
Jika kelas beroperasi pada data yang berasal dari / masuk ke file-file ini maka sistem file menjadi detail implementasi (I / O) dan harus dipisahkan darinya. Kelas-kelas ini (pabrik / komandan / mediator) seharusnya tidak mengetahui sistem file kecuali tugas mereka hanyalah menyimpan / membaca data yang disediakan. Kelas yang berhubungan dengan sistem file harus merangkum parameter konteks khusus seperti jalur (mungkin dilewatkan melalui konstruktor), sehingga antarmuka tidak mengungkapkan sifatnya (kata "File" dalam nama antarmuka adalah bau sebagian besar waktu).
sumber
Menurut pendapat saya, itu terdengar seperti Anda sudah mulai menuju jalan yang benar tetapi Anda belum mengambilnya cukup jauh. Saya pikir memecah fungsionalitas menjadi kelas yang berbeda yang melakukan satu hal dan melakukannya dengan baik adalah benar.
Untuk melangkah lebih jauh, Anda harus membuat antarmuka untuk kelas Factory, Mediator, dan Commander Anda. Kemudian Anda dapat menggunakan versi-kelas yang mengejek dari kelas-kelas tersebut ketika menulis tes unit Anda untuk implementasi konkret yang lain. Dengan mengolok-olok Anda dapat memvalidasi bahwa metode dipanggil dalam urutan yang benar dan dengan parameter yang benar dan bahwa kode yang diuji berperilaku baik dengan nilai pengembalian yang berbeda.
Anda juga bisa melihat abstrak pengambilan / penulisan data. Anda akan pergi ke sistem file sekarang tetapi mungkin ingin pergi ke database atau bahkan socket di masa depan. Kelas mediator Anda tidak perlu berubah jika sumber / tujuan data berubah.
sumber