Haruskah kita mendefinisikan tipe untuk semuanya?

141

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?

Ziv
sumber
4
Anda mungkin tertarik membaca tentang Jenis Tiny. Saya bermain dengannya sebentar dan tidak akan merekomendasikannya tetapi ini adalah pendekatan yang menyenangkan untuk dipikirkan. darrenhobbs.com/2007/04/11/tiny-types
Jeroen Vannevel
40
Setelah Anda menggunakan sesuatu seperti Haskell, Anda akan melihat seberapa banyak penggunaan tipe membantu. Jenis membantu memastikan program yang benar.
Nate Symer
3
Bahkan bahasa yang dinamis seperti Erlang mendapat manfaat dari sistem tipe, itulah sebabnya mengapa ia memiliki sistem typpec dan alat pengetekan pengetikan yang akrab bagi Haskellers meskipun merupakan bahasa yang dinamis. Bahasa seperti C / C ++ di mana jenis sebenarnya dari apa pun adalah bilangan bulat yang kami sepakati dengan kompiler untuk memperlakukan dengan cara tertentu, atau Java di mana Anda hampir semacam memiliki jenis jika Anda memutuskan untuk berpura-pura kelas adalah jenis yang hadir baik masalah kelebihan muatan semantik di bahasa (apakah ini "kelas" atau "tipe"?) atau ketik ambiguitas (apakah ini "URL", "String" atau "UPC"?). Pada akhirnya, gunakan alat apa pun yang Anda bisa untuk membuat ambigu.
zxq9
7
Saya tidak yakin dari mana ide itu berasal dari C ++ yang memiliki jenis overhead. Dengan C ++ 11, tidak ada pembuat kompiler yang melihat masalah dalam memberikan setiap lambda tipe uniknya sendiri. Tapi TBH Anda tidak dapat benar-benar berharap untuk menemukan banyak wawasan dalam komentar tentang "sistem tipe C / C ++" seolah keduanya berbagi sistem tipe.
MSalters
2
@ JDB - tidak, tidak. Bahkan tidak mirip.
Davor Ždralo

Jawaban:

152

Primitif, seperti stringatau int, 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:

Aturan 3: Bungkus semua primitif dan String

Dalam bahasa Jawa, int adalah objek primitif, bukan objek nyata, sehingga mematuhi aturan yang berbeda dari objek. Ini digunakan dengan sintaks yang tidak berorientasi objek. Lebih penting lagi, int sendiri hanya skalar, jadi tidak ada artinya. Ketika suatu metode mengambil int sebagai parameter, nama metode perlu melakukan semua pekerjaan mengekspresikan maksud. Jika metode yang sama menggunakan Hour sebagai parameter, akan lebih mudah untuk melihat apa yang terjadi.

Dokumen yang sama menjelaskan bahwa ada manfaat tambahan:

Benda kecil seperti Hour atau Uang juga memberi kita tempat yang jelas untuk menempatkan perilaku yang seharusnya berserakan di kelas lain .

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: Moneykelas, itu wajar untuk menemukan rentang memeriksa di dalam. Sebaliknya, jika int(lebih buruk, a double) 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 ProductIdmemiliki shorttipe yang mendasarinya dan kemudian saya harus menggunakannya int, 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 Productberisi ProductPriceyang mewarisi dari PositivePricemana mewarisi dari Priceyang pada gilirannya mewarisi dari Money, 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:

  • Pembuat properti,
  • Bidang dukungannya,
  • Konstruktor yang memberikan nilai ke bidang dukungan,
  • Sebuah kebiasaan ToString(),
  • Komentar dokumentasi XML (yang menghasilkan banyak baris),
  • A Equalsdan GetHashCodeoverride (juga banyak LOC).

dan akhirnya, jika relevan:

  • Sebuah DebuggerDisplay atribut,
  • Override ==dan !=operator,
  • Akhirnya kelebihan dari operator konversi implisit untuk secara mulus mengkonversi ke dan dari tipe enkapsulasi,
  • Kontrak kode (termasuk invarian, yang merupakan metode yang agak panjang, dengan tiga atributnya),
  • Beberapa konverter yang akan digunakan selama serialisasi XML, serialisasi JSON atau menyimpan / memuat nilai ke / dari database.

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 ProductIddigunakan 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 stringatau intbaik-baik saja.

Dalam praktiknya, dalam public string CreateNewThing(), mengembalikan instance ThingIdkelas alih-alih stringmungkin 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 Thingkelas. Jika pengguna hanya membutuhkan ID, ini dapat dengan mudah dilakukan dengan:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    
Arseni Mourzenko
sumber
33
Saya pikir kekuatan untuk secara bebas mengubah tipe yang mendasarinya penting di sini. Ini string hari ini, tapi mungkin GUID atau besok panjang. Membuat pembungkus tipe menyembunyikan informasi yang menyebabkan perubahan lebih sedikit di jalan. ++ Jawaban yang bagus di sini.
RubberDuck
4
Dalam hal uang, pastikan mata uangnya termasuk dalam objek Uang, atau seluruh program Anda menggunakan yang sama (yang kemudian harus didokumentasikan).
Paŭlo Ebermann
5
@ Bllfl: walaupun ada kasus yang valid di mana pewarisan yang mendalam diperlukan, saya biasanya melihat pewarisan seperti itu dalam kode yang dirancang terlalu tertulis tanpa memperhatikan keterbacaan, yang dihasilkan dari sikap bayar-oleh-LOC kompulsif. Demikian karakter negatif dari bagian jawaban saya ini.
Arseni Mourzenko
3
@ ArTs: Itulah argumen "mari kita kutuk alat itu karena beberapa orang menggunakannya dengan cara yang salah".
Blrfl
6
@MainMa Contoh di mana makna mungkin terhindar dari kesalahan yang mahal: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar
60

Saya akan menggunakan ruang lingkup sebagai aturan praktis: Semakin sempit ruang lingkup menghasilkan dan mengkonsumsi seperti valuesitu, semakin kecil kemungkinan Anda harus membuat objek yang mewakili nilai ini.

Katakanlah Anda memiliki kodesemu berikut

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

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.

Thomas Junk
sumber
14
Poin yang sangat bagus. Jenis eksplisit adalah alat penting untuk mengkomunikasikan maksud , yang sangat penting di seluruh objek dan batas layanan.
Avner Shahar-Kashtan
+1 walaupun jika Anda belum refactored semua kode doFancyOtherstuff()menjadi subrutin, Anda mungkin berpikir job.do(id)referensi tersebut tidak cukup lokal untuk disimpan sebagai string sederhana.
Mark Hurd
Ini sepenuhnya benar! Ini terjadi ketika Anda memiliki metode pribadi di mana Anda tahu bahwa dunia luar tidak akan pernah menggunakan metode itu. Setelah itu, Anda bisa berpikir sedikit lebih ketika kelas dilindungi dan setelah itu ketika kelas publik tetapi bukan bagian dari api publik. Cakupan ruang lingkup dan banyak seni untuk menempatkan garis pada posisi yang baik antara banyak jenis primitif dan banyak kustom.
Samuel
34

Sistem tipe statis adalah tentang mencegah penggunaan data yang salah.

Ada beberapa contoh jelas dari jenis yang melakukan ini:

  • Anda tidak bisa mendapatkan bulan UUID
  • Anda tidak dapat melipatgandakan dua string.

Ada contoh yang lebih halus

  • Anda tidak dapat membayar untuk sesuatu menggunakan panjang meja
  • Anda tidak dapat membuat permintaan HTTP menggunakan nama seseorang sebagai URL.

Kami mungkin tergoda untuk menggunakan doubleharga dan panjang, atau menggunakan stringuntuk 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.

Kata-kata atau bahasanya, sebagaimana ditulis atau diucapkan, tampaknya tidak memainkan peran apa pun dalam mekanisme pemikiran saya.

Albert Einstein

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 menjadi Uris - 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 mengidentifikasi Operationdengan string acak yang kami buat. Membuat kelas terpisah menambah keterbacaan dan keamanan kode Anda.


Tentu saja, semua hal baik bisa digunakan berlebihan. Mempertimbangkan

  1. berapa banyak kejelasan yang ditambahkan ke kode Anda

  2. 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.

Paul Draper
sumber
21
"string sering menjadi tipe data 'universal'" - itulah asal mula "bahasa yang diketik secara ketat" moniker.
MSalters
@Malters, ha ha, sangat bagus.
Paul Draper
4
Dan tentu saja, apa 05-03-2015 artinya bila diartikan sebagai kencan ?
CVn
10

Apakah Anda pikir itu praktik yang baik untuk membungkus tipe sederhana di kelas untuk alasan keamanan?

Terkadang.

Ini adalah salah satu kasus di mana Anda perlu mempertimbangkan masalah yang mungkin timbul dari menggunakan stringbukan yang lebih konkret OperationIdentifier. 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.

Telastyn
sumber
10

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:

  • Performa. Saya harus merujuk ke bahasa aktual di sini. Dalam C #, jika ID klien pendek dan Anda membungkusnya dalam kelas - Anda baru saja membuat banyak overhead: memori, karena sekarang 8 byte pada sistem 64 bit dan kecepatan karena sekarang ini dialokasikan pada heap.
  • Saat Anda membuat asumsi tentang tipe. Jika ID klien pendek dan Anda memiliki beberapa logika yang mengemasnya - Anda biasanya sudah membuat asumsi tentang jenisnya. Di semua tempat itu Anda sekarang harus menghancurkan abstraksi. Jika itu satu tempat, itu bukan masalah besar, jika semua tempat, Anda mungkin menemukan bahwa separuh waktu Anda menggunakan primitif.
  • Karena tidak semua bahasa memiliki typedef. Dan untuk bahasa yang tidak dan kode yang sudah ditulis, membuat perubahan seperti itu bisa menjadi tugas besar yang bahkan mungkin memperkenalkan bug (tetapi semua orang memiliki suite uji dengan cakupan yang baik, kan?).
  • Dalam beberapa kasus ini mengurangi keterbacaan. Bagaimana saya mencetak ini? Bagaimana saya memvalidasi ini? Haruskah saya memeriksa nol? Apakah semua pertanyaan itu mengharuskan Anda menelusuri definisi tipe.
ytoledano
sumber
3
Tentunya jika jenis pembungkus adalah struct daripada kelas, maka short(misalnya) memiliki biaya yang sama dibungkus atau dibuka. (?)
MathematicalOrchid
1
Apakah itu bukan desain yang baik untuk suatu tipe untuk merangkum validasinya sendiri? Atau untuk membuat tipe pembantu untuk memvalidasinya?
Nick Udell
1
Pengepakan atau munging yang tidak perlu adalah anti-pola. Informasi harus mengalir secara transparan melalui kode aplikasi, kecuali jika transformasi secara eksplisit & benar-benar diperlukan . Jenisnya baik karena mereka biasanya mengganti sejumlah kecil kode yang baik, terletak di satu tempat, untuk sejumlah besar implementasi yang buruk tersebar di seluruh sistem.
Thomas W
7

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, usingblok) 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.

Kenny Evitt
sumber
5

Di permukaan sepertinya semua yang perlu Anda lakukan adalah mengidentifikasi operasi.

Selain itu Anda mengatakan apa yang seharusnya dilakukan oleh suatu operasi:

untuk memulai operasi [dan] untuk memantau penyelesaiannya

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

pola perintah [...] digunakan untuk merangkum semua informasi yang diperlukan untuk melakukan suatu tindakan atau memicu suatu peristiwa di lain waktu .

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 Operationdalam arti abstrak, sebuah pointer ke objek kelas itu di oop misalnya.

Mengenai komentar

Ini akan menjadi wtf-y untuk memanggil kelas ini perintah dan kemudian tidak memiliki logika di dalamnya.

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.

  1. Jika Anda memiliki semua logika dalam perintah, itu akan membengkak dan berantakan. Anda akan berpikir "oh baiklah, semua fungsi ini harus di kelas yang terpisah." Anda akan refactor logika dari perintah.
  2. Jika Anda memiliki semua logika dalam perintah, akan sulit untuk menguji dan menghalangi saat pengujian. "Sial, mengapa saya harus menggunakan perintah ini omong kosong? Saya hanya ingin menguji apakah fungsi ini memuntahkan 1 atau tidak, saya tidak ingin memanggilnya nanti, saya ingin mengujinya sekarang!"
  3. Jika Anda memiliki semua logika dalam perintah, tidak masuk akal untuk melaporkan kembali setelah selesai. Jika Anda berpikir tentang fungsi yang akan dieksekusi secara sinkron secara default, tidak masuk akal untuk diberitahu ketika selesai dijalankan. Mungkin Anda memulai utas lain karena operasi Anda sebenarnya untuk membuat avatar ke format bioskop (mungkin perlu utas tambahan pada raspberry pi), mungkin Anda harus mengambil beberapa data dari server, ... apa pun itu, jika ada alasan untuk melaporkan kembali ketika selesai, bisa jadi karena ada beberapa ketidaksinkronan (apakah itu sebuah kata?) sedang terjadi. Saya pikir menjalankan utas atau menghubungi server adalah logika yang seharusnya tidak ada dalam perintah Anda. Ini agak argumen meta dan mungkin kontroversial. Jika Anda pikir itu tidak masuk akal, tolong beri tahu saya di komentar.

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.

batal
sumber
Anda benar, saya belum memikirkannya seperti itu. Tetapi pola perintah tidak 100% cocok karena logika operasi dienkapsulasi di kelas lain dan saya tidak bisa memasukkannya ke dalam objek yang merupakan perintah. Ini akan menjadi wtf-y untuk memanggil kelas ini perintah dan kemudian tidak memiliki logika di dalamnya.
Ziv
Ini sangat masuk akal. Pertimbangkan untuk mengembalikan Operasi, bukan OperationIdentifier. Ini mirip dengan C # TPL tempat Anda mengembalikan Tugas atau Tugas <T>. @ Ziv: Anda mengatakan "logika operasi dienkapsulasi di kelas lain." Tetapi apakah itu benar-benar berarti Anda juga tidak dapat menyediakannya dari sini?
Moby Disk
@ Ziv saya mohon berbeda. Lihatlah alasan saya menambahkan jawaban saya dan lihat apakah itu masuk akal bagi Anda. =)
null
5

Ide dibalik pembungkus tipe primitif,

  • Untuk menetapkan bahasa khusus domain
  • Batasi kesalahan yang dilakukan oleh pengguna dengan memberikan nilai yang salah

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,

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

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,

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

Jadi tidak ada alasan untuk membungkus semuanya, tetapi hal-hal yang benar-benar penting.

Pelican Terbang Rendah
sumber
4

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 uintdapat dengan sempurna merepresentasikan UserId, kita dapat terus menggunakan operator bawaan Java untuk uints (seperti ==) dan kita tidak memerlukan logika bisnis untuk melindungi 'keadaan internal' dari id pengguna.

Roy T.
sumber
Setuju. semua filosofi pemrograman baik-baik saja, tetapi dalam praktiknya Anda harus membuatnya bekerja dengan baik
Ewan
Jika Anda pernah melihat iklan pinjaman kepada orang-orang termiskin di Inggris, Anda akan menemukan bahwa Persentase untuk membungkus ganda di kisaran
0..1
1
Di C / C ++ / Objective C Anda bisa menggunakan typedef, yang memberi Anda hanya nama baru untuk tipe primitif yang ada. Di sisi lain, kode Anda harus berfungsi apa pun jenis dasarnya, misalnya jika saya mengubah OrderId dari uint32_t menjadi uint64_t (karena saya perlu memproses lebih dari 4 miliar pesanan).
gnasher729
Saya tidak akan menurunkan suara Anda untuk keberanian Anda, tetapi pseudotip dan tipe yang ditentukan dalam jawaban teratas berbeda. Itu adalah tipe yang spesifik untuk bisnis. Psuedotypes adalah tipe yang masih generik.
0fnt
@ gnasher729: C ++ 11 juga memiliki literal yang ditentukan pengguna yang membuat proses pembungkus menjadi sangat manis bagi pengguna: Jarak d = 36.0_mi + 42.0_km; . msdn.microsoft.com/en-us/library/dn919277.aspx
damix911
3

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 Structdan Class, tidak selalu Kelas.

phil soady
sumber
2
Apa ///artinya
Gabe
1
/// adalah komentar dokumentasi dalam C # yang memungkinkan intellisense untuk menunjukkan dokumentasi tanda tangan pada titik metode panggilan. Pertimbangkan cara terbaik untuk mendokumentasikan kode. Dapat ditanami oleh alat dan menawarkan jangkauan perilaku kecerdasan. msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
phil soady