Baru-baru ini saya mendapat masalah dengan keterbacaan kode saya.
Saya memiliki fungsi yang melakukan operasi dan mengembalikan sebuah string yang mewakili ID operasi ini untuk referensi di masa mendatang (sedikit seperti OpenFile di Windows yang mengembalikan pegangan). Pengguna akan menggunakan ID ini nanti untuk memulai operasi dan memantau penyelesaiannya.
ID harus berupa string acak karena masalah interoperabilitas. Ini menciptakan metode yang memiliki tanda tangan yang sangat tidak jelas seperti:
public string CreateNewThing()
Ini membuat maksud tipe pengembalian tidak jelas. Saya pikir untuk membungkus string ini dalam tipe lain yang membuat maknanya lebih jelas seperti ini:
public OperationIdentifier CreateNewThing()
Jenis ini hanya akan berisi string dan digunakan kapan pun string ini digunakan.
Sudah jelas bahwa keuntungan dari cara operasi ini adalah keamanan tipe dan niat yang lebih jelas, tetapi juga menciptakan lebih banyak kode dan kode yang tidak terlalu idiomatis. Di satu sisi saya suka keamanan tambahan, tetapi juga menciptakan banyak kekacauan.
Apakah Anda pikir itu praktik yang baik untuk membungkus tipe sederhana di kelas untuk alasan keamanan?
sumber
Jawaban:
Primitif, seperti
string
atauint
, tidak memiliki arti dalam domain bisnis. Konsekuensi langsung dari ini adalah bahwa Anda mungkin keliru menggunakan URL saat ID produk diharapkan, atau menggunakan kuantitas saat mengharapkan harga .Ini juga mengapa tantangan Object Calisthenics menampilkan pembungkus primitif sebagai salah satu aturannya:
Dokumen yang sama menjelaskan bahwa ada manfaat tambahan:
Memang, ketika primitif digunakan, biasanya sangat sulit untuk melacak lokasi yang tepat dari kode yang terkait dengan tipe-tipe itu, seringkali mengarah pada duplikasi kode yang parah . Jika ada
Price: Money
kelas, itu wajar untuk menemukan rentang memeriksa di dalam. Sebaliknya, jikaint
(lebih buruk, adouble
) digunakan untuk menyimpan harga produk, siapa yang harus memvalidasi kisaran? Produk? Rabat? Kereta?Akhirnya, manfaat ketiga yang tidak disebutkan dalam dokumen adalah kemampuan untuk mengubah jenis yang relatif mudah. Jika hari ini saya
ProductId
memilikishort
tipe yang mendasarinya dan kemudian saya harus menggunakannyaint
, kemungkinan kode untuk berubah tidak akan menjangkau seluruh basis kode.Kekurangannya - dan argumen yang sama berlaku untuk setiap aturan latihan Object Calisthenics - adalah bahwa jika dengan cepat menjadi terlalu berlebihan untuk membuat kelas untuk semuanya . Jika
Product
berisiProductPrice
yang mewarisi dariPositivePrice
mana mewarisi dariPrice
yang pada gilirannya mewarisi dariMoney
, ini bukan arsitektur bersih, melainkan kekacauan lengkap di mana untuk menemukan satu hal, pengelola harus membuka beberapa file lusin setiap kali.Hal lain yang perlu dipertimbangkan adalah biaya (dalam hal garis kode) untuk membuat kelas tambahan. Jika pembungkusnya tidak berubah (sebagaimana mestinya, biasanya), itu berarti, jika kami mengambil C #, Anda harus memiliki, di dalam pembungkus setidaknya:
ToString()
,Equals
danGetHashCode
override (juga banyak LOC).dan akhirnya, jika relevan:
==
dan!=
operator,Seratus LOC untuk pembungkus sederhana membuatnya cukup mahal, yang juga mengapa Anda mungkin benar-benar yakin akan keuntungan jangka panjang dari pembungkus tersebut. Gagasan ruang lingkup yang dijelaskan oleh Thomas Junk sangat relevan di sini. Menulis seratus LOC untuk mewakili yang
ProductId
digunakan di seluruh basis kode Anda terlihat cukup berguna. Menulis kelas sebesar ini untuk sepotong kode yang membuat tiga baris dalam satu metode jauh lebih dipertanyakan.Kesimpulan:
Apakah membungkus primitif di kelas yang memiliki makna dalam domain bisnis aplikasi ketika (1) membantu mengurangi kesalahan, (2) mengurangi risiko duplikasi kode atau (3) membantu mengubah jenis yang mendasarinya nanti.
Jangan membungkus secara otomatis setiap primitif yang Anda temukan dalam kode Anda: ada banyak kasus di mana menggunakan
string
atauint
baik-baik saja.Dalam praktiknya, dalam
public string CreateNewThing()
, mengembalikan instanceThingId
kelas alih-alihstring
mungkin membantu, tetapi Anda juga dapat:Mengembalikan turunan
Id<string>
kelas, yaitu objek bertipe generik yang menunjukkan bahwa tipe yang mendasarinya adalah string. Anda mendapatkan manfaat keterbacaan, tanpa kekurangan karena harus mempertahankan banyak jenis.Kembalikan instance
Thing
kelas. Jika pengguna hanya membutuhkan ID, ini dapat dengan mudah dilakukan dengan:sumber
Saya akan menggunakan ruang lingkup sebagai aturan praktis: Semakin sempit ruang lingkup menghasilkan dan mengkonsumsi seperti
values
itu, semakin kecil kemungkinan Anda harus membuat objek yang mewakili nilai ini.Katakanlah Anda memiliki kodesemu berikut
maka ruang lingkupnya sangat terbatas dan saya akan melihat tidak masuk akal untuk menjadikannya tipe. Tetapi katakan, Anda menghasilkan nilai ini dalam satu layer dan meneruskannya ke layer lain (atau bahkan objek lain), maka akan masuk akal untuk membuat tipe untuk itu.
sumber
doFancyOtherstuff()
menjadi subrutin, Anda mungkin berpikirjob.do(id)
referensi tersebut tidak cukup lokal untuk disimpan sebagai string sederhana.Sistem tipe statis adalah tentang mencegah penggunaan data yang salah.
Ada beberapa contoh jelas dari jenis yang melakukan ini:
Ada contoh yang lebih halus
Kami mungkin tergoda untuk menggunakan
double
harga dan panjang, atau menggunakanstring
untuk nama dan URL. Tetapi melakukan itu merongrong sistem tipe kami yang mengagumkan, dan memungkinkan penyalahgunaan ini untuk lulus pemeriksaan statis bahasa.Membuat pound-detik bingung dengan Newton-detik dapat memiliki hasil yang buruk saat runtime .
Ini khususnya masalah dengan string. Mereka sering menjadi "tipe data universal".
Kami biasanya digunakan untuk antarmuka teks dengan komputer, dan kami sering memperluas antarmuka manusia (UI) ini ke antarmuka pemrograman (API). Kami menganggap 34.25 sebagai karakter 34.25 . Kami menganggap tanggal sebagai karakter 05-03-2015 . Kami menganggap UUID sebagai karakter 75e945ee-f1e9-11e4-b9b2-1697f925ec7b .
Tetapi model mental ini membahayakan abstraksi API.
Demikian pula, representasi tekstual tidak boleh memainkan peran apa pun dalam mendesain tipe dan API. Waspadalah
string
! (dan tipe "primitif" yang terlalu umum lainnya)Jenis berkomunikasi "operasi apa yang masuk akal."
Sebagai contoh, saya pernah bekerja pada klien ke HTTP REST API. REST, dilakukan dengan benar, menggunakan entitas hypermedia, yang memiliki hyperlink yang menunjuk ke entitas terkait. Di klien ini, tidak hanya entitas yang diketik (mis. Pengguna, Akun, Berlangganan), tautan ke entitas itu juga diketik (UserLink, AccountLink, SubscriptionLink). Tautan-tautan itu sedikit lebih dari sekadar pembungkus
Uri
, tetapi jenis-jenis yang terpisah membuatnya tidak mungkin untuk mencoba menggunakan AkunLink untuk menjemput seorang Pengguna. Seandainya semuanya menjadiUri
s - atau bahkan lebih buruk,string
- kesalahan ini hanya akan ditemukan saat runtime.Demikian juga, dalam situasi Anda, Anda memiliki data yang hanya digunakan untuk satu tujuan: untuk mengidentifikasi suatu
Operation
. Seharusnya tidak digunakan untuk hal lain, dan kita seharusnya tidak mencoba mengidentifikasiOperation
dengan string acak yang kami buat. Membuat kelas terpisah menambah keterbacaan dan keamanan kode Anda.Tentu saja, semua hal baik bisa digunakan berlebihan. Mempertimbangkan
berapa banyak kejelasan yang ditambahkan ke kode Anda
seberapa sering digunakan
Jika "tipe" data (dalam arti abstrak) sering digunakan, untuk tujuan yang berbeda, dan antara antarmuka kode, itu adalah kandidat yang sangat baik untuk menjadi kelas yang terpisah, dengan mengorbankan verbositas.
sumber
05-03-2015
artinya bila diartikan sebagai kencan ?Terkadang.
Ini adalah salah satu kasus di mana Anda perlu mempertimbangkan masalah yang mungkin timbul dari menggunakan
string
bukan yang lebih konkretOperationIdentifier
. Apa keparahan mereka? Apa kemungkinan mereka?Maka Anda perlu mempertimbangkan biaya menggunakan jenis lainnya. Seberapa menyakitkan untuk digunakan? Berapa banyak pekerjaan yang harus dilakukan?
Dalam beberapa kasus, Anda akan menghemat waktu dan usaha dengan memiliki jenis beton yang bagus untuk diatasi. Pada orang lain, itu tidak akan sepadan dengan masalahnya.
Secara umum, saya pikir hal semacam ini harus dilakukan lebih dari sekarang. Jika Anda memiliki entitas yang berarti sesuatu di domain Anda, sebaiknya gunakan itu sebagai jenisnya sendiri, karena entitas itu lebih cenderung berubah / tumbuh bersama bisnis.
sumber
Saya umumnya setuju bahwa berkali-kali Anda harus membuat tipe untuk primitif dan string, tetapi karena jawaban di atas merekomendasikan membuat tipe dalam kebanyakan kasus, saya akan mencantumkan beberapa alasan mengapa / kapan tidak:
sumber
short
(misalnya) memiliki biaya yang sama dibungkus atau dibuka. (?)Tidak, Anda tidak boleh mendefinisikan tipe (kelas) untuk "semuanya".
Tetapi, seperti yang dinyatakan oleh jawaban lain, sering kali berguna untuk melakukannya. Anda harus mengembangkan - secara sadar, jika mungkin - perasaan atau perasaan terlalu banyak gesekan karena tidak adanya tipe atau kelas yang sesuai, saat Anda menulis, menguji, dan memelihara kode Anda. Bagi saya, timbulnya terlalu banyak gesekan adalah ketika saya ingin mengkonsolidasikan beberapa nilai primitif sebagai nilai tunggal atau ketika saya perlu memvalidasi nilai-nilai (yaitu menentukan mana dari semua nilai yang mungkin dari tipe primitif yang sesuai dengan nilai valid dari 'tipe tersirat').
Saya telah menemukan pertimbangan seperti apa yang Anda ajukan dalam pertanyaan Anda untuk bertanggung jawab atas terlalu banyak desain dalam kode saya. Saya telah mengembangkan kebiasaan sengaja menghindari menulis kode lebih dari yang diperlukan. Semoga Anda menulis tes yang baik (otomatis) untuk kode Anda - jika ya, maka Anda dapat dengan mudah memperbaiki kode Anda dan menambahkan jenis atau kelas jika hal itu memberikan manfaat bersih untuk pengembangan dan pemeliharaan kode Anda yang sedang berlangsung .
Jawaban Telastyn dan jawaban Thomas Junk keduanya memberikan poin yang sangat baik tentang konteks dan penggunaan kode yang relevan. Jika Anda menggunakan nilai di dalam satu blok kode (mis. Metode, putaran,
using
blok) maka boleh saja menggunakan tipe primitif. Bahkan lebih baik menggunakan tipe primitif jika Anda menggunakan sekumpulan nilai berulang kali dan di banyak tempat lain. Tetapi semakin sering dan luas Anda menggunakan satu set nilai, dan semakin dekat bahwa set nilai sesuai dengan nilai-nilai yang diwakili oleh tipe primitif, semakin Anda harus mempertimbangkan merangkum nilai-nilai dalam kelas atau tipe.sumber
Di permukaan sepertinya semua yang perlu Anda lakukan adalah mengidentifikasi operasi.
Selain itu Anda mengatakan apa yang seharusnya dilakukan oleh suatu operasi:
Anda mengatakannya dengan cara seolah-olah ini "hanya bagaimana identifikasi digunakan", tetapi saya akan mengatakan ini adalah properti yang menggambarkan operasi. Kedengarannya seperti definisi tipe bagi saya, bahkan ada pola yang disebut "pola perintah" yang sangat cocok. .
Ia mengatakan
Saya pikir ini sangat mirip dibandingkan dengan apa yang ingin Anda lakukan dengan operasi Anda. (Bandingkan frasa yang saya buat dengan tebal dalam kedua kutipan) Alih-alih mengembalikan string, kembalikan pengidentifikasi untuk
Operation
dalam arti abstrak, sebuah pointer ke objek kelas itu di oop misalnya.Mengenai komentar
Tidak, itu tidak akan terjadi. Ingatlah bahwa polanya sangat abstrak , sangat abstrak sehingga faktanya agak meta. Artinya, mereka sering mengabstraksi pemrograman itu sendiri dan bukan konsep dunia nyata. Pola perintah adalah abstraksi dari panggilan fungsi. (Atau metode) Seolah-olah seseorang menekan tombol pause tepat setelah melewati nilai-nilai parameter dan tepat sebelum eksekusi, akan dilanjutkan nanti.
Berikut ini adalah mempertimbangkan oop, tetapi motivasi di baliknya harus berlaku untuk paradigma apa pun. Saya suka memberikan beberapa alasan mengapa menempatkan logika ke dalam perintah dapat dianggap sebagai hal yang buruk.
Untuk rekap: pola perintah memungkinkan Anda untuk membungkus fungsionalitas menjadi objek, yang akan dieksekusi nanti. Demi modularitas (fungsionalitas ada terlepas dari dieksekusi melalui perintah atau tidak), testabilitas (fungsionalitas harus dapat diuji tanpa perintah) dan semua kata-kata buzz lainnya yang pada dasarnya mengekspresikan permintaan untuk menulis kode yang baik, Anda tidak akan menempatkan logika aktual ke dalam perintah.
Karena polanya abstrak, mungkin sulit untuk menghasilkan metafora dunia nyata yang baik. Berikut ini upaya:
"Hei, nenek, bisakah kamu menekan tombol rekam di TV jam 12 jadi aku tidak ketinggalan simpsons di saluran 1?"
Nenek saya tidak tahu apa yang terjadi secara teknis ketika dia menekan tombol rekam itu. Logikanya ada di tempat lain (di TV). Dan itu bagus. Fungsionalitas dienkapsulasi dan informasi disembunyikan dari perintah, ini adalah pengguna API, belum tentu merupakan bagian dari logika ... oh ya ampun aku mengoceh kata-kata buzz lagi, saya lebih baik menyelesaikan edit ini sekarang.
sumber
Ide dibalik pembungkus tipe primitif,
Jelas akan sangat sulit dan tidak praktis untuk melakukannya di mana-mana, tetapi penting untuk membungkus jenis-jenis yang diperlukan,
Misalnya jika Anda memiliki Order kelas,
Properti penting untuk mencari Pesanan sebagian besar adalah OrderId dan InvoiceNumber. Dan Jumlah dan CurrencyCode terkait erat, di mana jika seseorang mengubah CurrencyCode tanpa mengubah Jumlah Pesanan tidak lagi dapat dianggap sah.
Jadi, hanya membungkus OrderId, InvoiceNumber dan memperkenalkan komposit untuk mata uang masuk akal dalam skenario ini dan mungkin membungkus deskripsi tidak masuk akal. Jadi hasil yang disukai bisa terlihat seperti,
Jadi tidak ada alasan untuk membungkus semuanya, tetapi hal-hal yang benar-benar penting.
sumber
Pendapat tidak populer:
Anda biasanya tidak boleh mendefinisikan tipe baru!
Mendefinisikan tipe baru untuk membungkus kelas primitif atau dasar kadang-kadang disebut mendefinisikan tipe pseudo. IBM menggambarkan ini sebagai praktik buruk di sini (mereka berfokus secara khusus pada penyalahgunaan obat generik dalam kasus ini).
Jenis pseudo membuat fungsi pustaka umum tidak berguna.
Fungsi Matematika Java dapat bekerja dengan semua bilangan primitif. Tetapi jika Anda mendefinisikan kelas baru Persentase (membungkus ganda yang dapat berada dalam kisaran 0 ~ 1) semua fungsi ini tidak berguna dan perlu dibungkus oleh kelas yang (lebih buruk) perlu tahu tentang representasi internal dari kelas Persentase .
Jenis pseudo menjadi viral
Saat membuat banyak pustaka, Anda akan sering menemukan bahwa pseudotip ini menjadi viral. Jika Anda menggunakan kelas Persentase yang disebutkan di atas dalam satu perpustakaan, Anda harus mengonversinya di batas perpustakaan (kehilangan semua keamanan / makna / logika / alasan lain yang Anda miliki untuk membuat tipe itu) atau Anda harus membuat kelas-kelas ini dapat diakses ke perpustakaan lain juga. Menginfeksi perpustakaan baru dengan tipe-tipe Anda di mana double sederhana mungkin sudah cukup.
Buang pesan
Selama jenis yang Anda bungkus tidak membutuhkan banyak logika bisnis, saya akan menyarankan untuk tidak membungkusnya dalam kelas semu. Anda hanya boleh membungkus kelas jika ada kendala bisnis yang serius. Dalam kasus lain, penamaan variabel Anda harus benar dalam menyampaikan makna.
Sebuah contoh:
A
uint
dapat dengan sempurna merepresentasikan UserId, kita dapat terus menggunakan operator bawaan Java untukuint
s (seperti ==) dan kita tidak memerlukan logika bisnis untuk melindungi 'keadaan internal' dari id pengguna.sumber
Seperti semua tips, mengetahui kapan menerapkan aturan adalah keterampilan. Jika Anda membuat tipe Anda sendiri dalam bahasa yang digerakkan oleh tipe, Anda mendapatkan pemeriksaan tipe. Jadi secara umum itu akan menjadi rencana yang bagus.
NamingConvention adalah kunci keterbacaan. Gabungan keduanya bisa menyampaikan niat dengan jelas.
Tapi /// masih berguna.
Jadi ya, saya akan mengatakan membuat banyak tipe sendiri ketika masa hidup mereka di luar batas kelas. Juga pertimbangkan penggunaan keduanya
Struct
danClass
, tidak selalu Kelas.sumber
///
artinya