Saya telah membaca banyak artikel baru-baru ini yang menggambarkan obsesi primitif sebagai bau kode.
Ada dua manfaat dari menghindari obsesi primitif:
Itu membuat model domain lebih eksplisit. Misalnya, saya dapat berbicara dengan analis bisnis tentang Kode Pos alih-alih string yang berisi kode pos.
Semua validasi ada di satu tempat, bukan di seluruh aplikasi.
Ada banyak artikel di luar sana yang menggambarkan ketika itu adalah kode bau. Sebagai contoh, saya dapat melihat manfaat dari menghilangkan obsesi primitif untuk kode pos seperti ini:
public class Address
{
public ZipCode ZipCode { get; set; }
}
Berikut adalah konstruktor dari Kode Pos:
public ZipCode(string value)
{
// Perform regex matching to verify XXXXX or XXXXX-XXXX format
_value = value;
}
Anda akan melanggar prinsip KERING menempatkan logika validasi di mana-mana kode pos digunakan.
Namun, bagaimana dengan objek berikut:
Tanggal Lahir: Periksa apakah tanggal yang lebih besar dari yang ditentukan dan kurang dari hari ini.
Gaji: Periksa lebih besar atau sama dengan nol.
Apakah Anda akan membuat objek DateOfBirth dan objek Gaji? Manfaatnya adalah Anda dapat membicarakannya saat mendeskripsikan model domain. Namun, apakah ini kasus overengineering karena tidak ada banyak validasi. Apakah ada aturan yang menjelaskan kapan dan kapan tidak menghilangkan obsesi primitif atau haruskah Anda selalu melakukannya jika memungkinkan?
Saya kira saya bisa membuat tipe alias bukan kelas, yang akan membantu dengan poin satu di atas.
DateOfBirth
konstruktor untuk diperiksa?Salary
danDistance
keberatan, Anda tidak dapat menggunakannya secara tidak sengaja secara bergantian. Anda bisa jika keduanya tipedouble
.Jawaban:
Yang sebaliknya adalah "pemodelan domain", atau mungkin "over engineering".
Memperkenalkan objek Gaji dapat menjadi ide bagus karena alasan berikut: angka jarang berdiri sendiri dalam model domain, mereka hampir selalu memiliki dimensi dan unit. Kami biasanya tidak memodelkan sesuatu yang berguna jika kami menambah panjang waktu atau massa, dan kami jarang mendapatkan hasil yang baik ketika kami mencampur meter dan kaki.
Adapun DateOfBirth, mungkin - ada dua masalah untuk dipertimbangkan. Pertama, membuat Date non-primitif memberi Anda tempat untuk memusatkan semua kekhawatiran aneh seputar matematika tanggal. Banyak bahasa menyediakan satu di luar kotak; DateTime , java.util.Date . Ini adalah implementasi agnostik domain dari tanggal, tetapi mereka bukan primitif.
Kedua,
DateOfBirth
sebenarnya bukan waktu kencan; di sini di AS, "tanggal lahir" adalah konstruksi budaya / fiksi hukum. Kita cenderung mengukur tanggal lahir dari tanggal lokal kelahiran seseorang; Bob, lahir di California, mungkin memiliki tanggal kelahiran "lebih awal" daripada Alice, lahir di New York, meskipun ia adalah yang lebih muda dari keduanya.Tentu tidak selalu; pada batas-batasnya, aplikasi tidak berorientasi objek . Cukup umum untuk melihat primitif digunakan untuk menggambarkan perilaku dalam tes .
sumber
java.util.Date
denganjava.time.LocalDate
Sejujurnya: itu tergantung.
Selalu ada risiko overengineering kode Anda. Seberapa luas DateOfBirth dan Gaji akan digunakan? Apakah Anda hanya akan menggunakannya dalam tiga kelas yang sangat erat, atau apakah akan digunakan di seluruh aplikasi? Apakah Anda "hanya" merangkum mereka dalam Tipe / Kelas mereka sendiri untuk menegakkan satu kendala itu, atau dapatkah Anda memikirkan lebih banyak kendala / fungsi yang sebenarnya ada di sana?
Mari kita ambil Gaji misalnya: Apakah Anda memiliki operasi dengan "Gaji" (misalnya menangani mata uang yang berbeda, atau mungkin fungsi toString ())? Pertimbangkan apa itu Gaji / lakukan ketika Anda tidak melihatnya sebagai primitif sederhana, dan ada peluang bagus untuk Gaji menjadi kelasnya sendiri.
sumber
Aturan praktis yang mungkin tergantung pada lapisan program. Untuk Domain (DDD) alias Entitas Layer (Martin, 2018), ini mungkin juga "untuk menghindari primitif untuk apa pun yang mewakili konsep domain / bisnis". Pembenarannya seperti yang dinyatakan oleh OP: model domain yang lebih ekspresif, validasi aturan bisnis, membuat konsep implisit eksplisit (Evans, 2004).
Tipe alias dapat menjadi alternatif yang ringan (Ghosh, 2017), dan dire-refoured ke kelas entitas saat diperlukan. Sebagai contoh, kita mungkin pertama mengharuskan
Salary
be>=0
, dan kemudian memutuskan untuk melarang$100.33333
dan apa pun di atas$10,000,000
(yang akan bangkrut klien). PenggunaanNonnegative
primitif untuk mewakiliSalary
dan konsep lain akan mempersulit refactoring ini.Menghindari primitif juga dapat membantu menghindari rekayasa berlebihan. Misalkan kita perlu menggabungkan Gaji dan Tanggal Lahir ke dalam struktur data: misalnya, untuk memiliki lebih sedikit parameter metode atau untuk meneruskan data antar modul. Kemudian kita bisa menggunakan tuple dengan tipe
(Salary, DateOfBirth)
. Memang, tuple dengan primitif,,(Nonnegative, Nonnegative)
tidak informatif, sedangkan beberapa kembungclass EmployeeData
akan menyembunyikan bidang yang diperlukan antara lain. Tanda tangan di katakancalcPension(d: (Salary, DateOfBirth))
lebih fokus daripada dicalcPension(d: EmployeeData)
, yang melanggar Prinsip Segregasi Antarmuka. Demikian juga, seorang spesialisclass SalaryAndDateOfBirth
tampaknya canggung dan mungkin merupakan pembunuhan berlebihan. Kemudian, kita dapat memilih untuk mendefinisikan kelas data; tupel dan tipe domain elemental mari kita menunda keputusan seperti itu.Dalam lapisan luar (misalnya GUI) mungkin masuk akal untuk "menghapus" entitas ke primitif konstituen mereka (misalnya untuk dimasukkan ke dalam DAO). Ini mencegah abstraksi domain bocor ke lapisan luar, seperti yang disarankan dalam Martin (2018).
Referensi
E. Evans, "Desain Berbasis Domain", 2004
D. Ghosh, "Pemodelan Domain Fungsional dan Reaktif", 2017
RC Martin, "Arsitektur bersih", 2018
sumber
Lebih baik menderita Obsesi Primitif atau menjadi Astronot Arsitektur ?
Kedua kasus ini bersifat patologis, dalam satu kasus Anda memiliki terlalu sedikit abstraksi, yang mengarah ke pengulangan dan dengan mudah mengira sebuah apel sebagai jeruk, dan yang lain Anda lupa untuk berhenti menggunakannya dan mulai menyelesaikan sesuatu, sehingga sulit untuk menyelesaikan apa pun. .
Seperti hampir selalu, Anda menginginkan moderasi, jalan tengah yang diharapkan baik-baik saja.
Ingatlah bahwa properti memang memiliki nama, selain tipe. Juga, menguraikan alamat menjadi bagian-bagian penyusunnya mungkin terlalu menyempit jika selalu dilakukan dengan cara yang sama. Tidak semua dunia berada di pusat kota NY.
sumber
Jika Anda memang memiliki kelas gaji, itu bisa memiliki metode seperti ApplyRaise.
Di sisi lain kelas ZipCode Anda tidak harus memiliki validasi internal untuk menghindari duplikasi validasi di mana pun Anda bisa memiliki kelas ZipCodeValidator yang dapat disuntikkan, jadi jika sistem Anda berjalan baik pada alamat US dan UK, Anda bisa menyuntikkan saja validator yang benar dan ketika Anda harus menangani alamat AUS juga Anda bisa menambahkan validator baru.
Kekhawatiran lain adalah jika Anda harus menulis data ke database melalui EntityFramework maka perlu tahu cara menangani Gaji atau Kode Pos.
Tidak ada jawaban yang jelas tentang di mana harus menarik garis batas antara bagaimana kelas cerdas seharusnya, tetapi saya akan mengatakan bahwa saya cenderung untuk memindahkan logika bisnis, seperti memvalidasi, ke kelas logika bisnis yang menjadikan kelas data sebagai data murni karena hal ini tampaknya untuk bekerja lebih baik dengan EntityFramework.
Sedangkan untuk menggunakan alias tipe, nama anggota / properti harus memberikan semua informasi yang diperlukan tentang konten, jadi saya tidak akan menggunakan alias tipe.
sumber
(Apa pertanyaannya sebenarnya)
Kapan penggunaan tipe primitif bukan bau kode?
(Menjawab)
Ketika parameter tidak memiliki aturan di dalamnya - gunakan tipe primitif.
Gunakan tipe primitif untuk suka:
Gunakan objek untuk suka:
Contoh terakhir ini memiliki aturan di dalamnya, yaitu, objek
SimpleDate
terdiri dariYear
,Month
, danDay
. Melalui penggunaan Object dalam hal ini, konsepSimpleDate
valid dapat diringkas dalam objek.sumber
Terlepas dari contoh kanonik alamat email atau kode pos yang diberikan di tempat lain dalam pertanyaan ini, Di mana saya menemukan refactoring jauh dari Primitive Obsession dapat sangat membantu adalah dengan ID entitas (lihat https://andrewlock.net/using-strongly-typed-entity -id-untuk-menghindari-primitif-obsesi-bagian-1 / untuk contoh bagaimana melakukannya di .NET).
Saya telah kehilangan hitungan berapa kali bug merangkak masuk karena metode memiliki tanda tangan seperti ini:
Mengkompilasi dengan baik, dan jika Anda tidak teliti dengan pengujian unit Anda, mungkin lulus juga. Namun, ubah ID entitas tersebut ke dalam kelas khusus domain, dan hai presto, waktu kompilasi:
Bayangkan metode itu diskalakan hingga 10 atau lebih parameter, semua
int
tipe data (apalagi bau kode Daftar Parameter Panjang ). Semakin buruk ketika Anda menggunakan sesuatu seperti AutoMapper untuk bertukar antara objek domain dan DTO, dan refactoring yang Anda lakukan tidak diambil oleh pemetaan automagic.sumber
Di sisi lain, ketika berhadapan dengan banyak negara dan sistem kode pos yang berbeda, itu berarti Anda tidak dapat memvalidasi kode pos kecuali Anda tahu negara yang dimaksud. Jadi
ZipCode
kelas Anda juga perlu menyimpan negara.Tetapi apakah Anda kemudian secara terpisah menyimpan negara sebagai bagian dari
Address
(yang mana kode pos juga bagian dari), dan bagian dari kode pos (untuk validasi)?ZipCode
kelas tetapiAddress
kelas, yang lagi-lagi akan mengandungstring ZipCode
yang berarti kita telah menjadi lingkaran penuh.Saya tidak mengerti pernyataan mendasar Anda bahwa ketika sebuah informasi memiliki tipe variabel tertentu, Anda entah bagaimana wajib menyebutkan tipe itu setiap kali Anda berbicara dengan seorang analis bisnis.
Mengapa? Mengapa Anda tidak dapat hanya berbicara tentang "kode pos" dan sepenuhnya mengabaikan jenis tertentu? Diskusi seperti apa yang Anda lakukan dengan analis bisnis Anda (bukan teknis!) Di mana jenis properti itu esensial untuk percakapan?
Dari mana saya berasal, kode pos selalu berupa angka. Jadi kita punya pilihan, kita bisa menyimpannya sebagai
int
atau sebagaistring
. Kita cenderung menggunakan string karena tidak ada harapan operasi matematika pada data, tetapi analis bisnis tidak pernah mengatakan kepada saya bahwa itu perlu string. Keputusan itu diserahkan kepada pengembang (atau bisa dibilang analis teknis, meskipun dalam pengalaman saya mereka tidak langsung berurusan dengan seluk beluk).Seorang analis bisnis tidak peduli dengan tipe data, asalkan aplikasi melakukan apa yang diharapkan untuk dilakukan.
Validasi adalah binatang yang sulit untuk ditangani, karena bergantung pada apa yang diharapkan manusia.
Pertama, saya tidak setuju dengan argumen validasi sebagai cara untuk menunjukkan mengapa obsesi primitif harus dihindari, karena saya tidak setuju bahwa (sebagai kebenaran universal) data selalu perlu divalidasi setiap saat.
Misalnya, bagaimana jika ini adalah pencarian yang lebih rumit? Alih-alih pemeriksaan format sederhana, bagaimana jika validasi Anda memerlukan menghubungi API eksternal dan menunggu tanggapan? Apakah Anda benar-benar ingin memaksa aplikasi Anda untuk memanggil API eksternal ini untuk setiap
ZipCode
objek yang Anda instantiate?Mungkin itu persyaratan bisnis yang ketat, dan tentu saja itu bisa dibenarkan. Tetapi ini bukan kebenaran universal. Akan ada banyak kasus penggunaan di mana ini lebih merupakan beban daripada solusi.
Sebagai contoh kedua, ketika memasukkan alamat Anda dalam formulir, itu biasa untuk memasukkan kode pos Anda sebelum negara Anda. Meskipun menyenangkan untuk memiliki umpan balik validasi langsung di UI, itu sebenarnya akan menjadi penghalang bagi saya (sebagai pengguna) jika aplikasi mengingatkan saya pada format kode pos yang "salah", karena sumber sebenarnya dari masalah ini adalah (misalnya) bahwa negara saya bukan negara yang dipilih secara default, dan dengan demikian validasi terjadi untuk negara yang salah.
Ini adalah pesan kesalahan yang salah, yang mengalihkan perhatian pengguna dan menyebabkan kebingungan yang tidak perlu.
Sama seperti bagaimana validasi abadi bukanlah kebenaran universal, tidak ada contoh saya. Itu kontekstual . Beberapa domain aplikasi memerlukan validasi data di atas segalanya. Domain lain tidak menempatkan validasi yang tinggi dalam daftar prioritas karena kerumitan yang dibawanya bertentangan dengan prioritas aktualnya (misalnya pengalaman pengguna, atau kemampuan untuk awalnya menyimpan data yang salah sehingga dapat diperbaiki, bukannya tidak pernah membiarkannya menjadi disimpan)
Masalah dengan validasi ini adalah bahwa mereka tidak lengkap, berlebihan atau menunjukkan masalah yang jauh lebih besar .
Memeriksa bahwa kencan lebih besar daripada pikiran berlebihan. Mindate secara harfiah berarti bahwa itu adalah tanggal sekecil mungkin. Selain itu, di mana Anda menarik garis relevansi? Apa gunanya mencegah
DateTime.MinDate
tetapi membiarkanDateTime.MinDate.AddSeconds(1)
? Anda menghargai nilai tertentu yang tidak terlalu salah dibandingkan dengan banyak nilai lainnya.Ulang tahun saya adalah 2 Januari 1978 (tidak, tapi anggap saja). Tetapi katakanlah data dalam aplikasi Anda salah, dan sebaliknya dikatakan ulang tahun saya adalah:
Semua tanggal ini salah. Tak satu pun dari mereka yang "lebih benar" daripada yang lain. Tetapi aturan validasi Anda hanya akan menangkap salah satu dari tiga contoh ini.
Anda juga telah sepenuhnya menghilangkan konteks tentang bagaimana Anda menggunakan data ini. Jika ini digunakan dalam mis. Bot pengingat ulang tahun, saya akan mengatakan validasinya tidak ada gunanya karena tidak ada konsekuensi buruk khusus untuk mengisi tanggal yang salah.
Di sisi lain, jika ini adalah data pemerintah dan Anda memerlukan tanggal lahir untuk mengotentikasi identitas seseorang (dan kegagalan untuk melakukannya mengarah pada konsekuensi buruk, misalnya menolak jaminan sosial seseorang), maka kebenaran data sangat penting dan Anda perlu sepenuhnya memvalidasi data. Validasi yang diajukan yang Anda miliki sekarang tidak memadai.
Untuk gaji, ada beberapa akal sehat bahwa itu tidak boleh negatif. Tetapi jika Anda secara realistis berharap bahwa data yang tidak masuk akal dimasukkan, saya sarankan Anda menyelidiki sumber dari data yang tidak masuk akal ini. Karena jika mereka tidak dapat dipercaya untuk memasukkan data sensis, Anda juga tidak dapat mempercayai mereka untuk memasukkan data yang benar .
Jika bukan gaji yang dihitung oleh aplikasi Anda, dan entah bagaimana itu mungkin berakhir dengan angka negatif (dan benar), maka pendekatan yang lebih baik akan dilakukan
Math.Max(myValue, 0)
untuk mengubah angka negatif menjadi 0, daripada gagal validasi. Karena jika logika Anda memutuskan bahwa hasilnya adalah angka negatif, gagal validasi berarti harus mengulang perhitungan, dan tidak ada alasan untuk berpikir bahwa itu akan muncul dengan nomor yang berbeda untuk kedua kalinya.Dan jika muncul dengan nomor yang berbeda, itu sekali lagi membuat Anda curiga bahwa perhitungannya tidak konsisten dan karenanya tidak dapat dipercaya.
Ini bukan untuk mengatakan bahwa validasi tidak berguna. Tetapi validasi yang tidak berarti itu buruk, baik karena butuh usaha tanpa benar-benar menyelesaikan masalah, dan memberi orang rasa aman yang salah.
sumber