Dilema
Saya telah membaca banyak buku praktik terbaik tentang praktik berorientasi objek, dan hampir setiap buku yang saya baca memiliki bagian di mana mereka mengatakan bahwa enum adalah bau kode. Saya pikir mereka telah melewatkan bagian di mana mereka menjelaskan kapan enum valid.
Karena itu, saya mencari pedoman dan / atau kasus penggunaan di mana enum BUKAN bau kode dan pada kenyataannya konstruk yang valid.
Sumber:
"PERINGATAN Sebagai patokan, enum adalah bau kode dan harus di refactored ke kelas polimorfik. [8]" Seemann, Mark, Dependency Injection di .Net, 2011, hlm. 342
[8] Martin Fowler et al., Refactoring: Meningkatkan Desain Kode yang Ada (New York: Addison-Wesley, 1999), 82.
Konteks
Penyebab dilema saya adalah API perdagangan. Mereka memberi saya aliran data Tick dengan mengirimkan melalui metode ini:
void TickPrice(TickType tickType, double value)
dimana enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }
Saya sudah mencoba membuat pembungkus di sekitar API ini karena melanggar perubahan adalah cara hidup untuk API ini. Saya ingin melacak nilai dari setiap jenis centang yang terakhir diterima pada bungkus saya dan saya telah melakukannya dengan menggunakan Kamus ticktypes:
Dictionary<TickType,double> LastValues
Bagi saya, ini tampak seperti penggunaan enum yang tepat jika mereka digunakan sebagai kunci. Tetapi saya memiliki pikiran kedua karena saya memiliki tempat di mana saya membuat keputusan berdasarkan koleksi ini dan saya tidak bisa memikirkan cara bagaimana saya bisa menghilangkan pernyataan switch, saya bisa menggunakan pabrik tetapi pabrik itu masih memiliki beralih pernyataan di suatu tempat. Tampaknya bagi saya bahwa saya hanya memindahkan barang-barang tetapi masih berbau.
Sangat mudah untuk menemukan JANGAN enum, tetapi DO, tidak mudah, dan saya akan menghargai jika orang dapat berbagi keahlian, pro dan kontra.
Pikiran kedua
Beberapa keputusan dan tindakan didasarkan pada ini TickType
dan sepertinya saya tidak bisa memikirkan cara untuk menghilangkan pernyataan enum / switch. Solusi terbersih yang dapat saya pikirkan adalah menggunakan pabrik dan mengembalikan implementasi berdasarkan TickType
. Bahkan kemudian saya masih akan memiliki pernyataan switch yang mengembalikan implementasi antarmuka.
Di bawah ini adalah salah satu kelas sampel di mana saya ragu bahwa saya mungkin menggunakan enum yang salah:
public class ExecutionSimulator
{
Dictionary<TickType, double> LastReceived;
void ProcessTick(TickType tickType, double value)
{
//Store Last Received TickType value
LastReceived[tickType] = value;
//Perform Order matching only on specific TickTypes
switch(tickType)
{
case BidPrice:
case BidSize:
MatchSellOrders();
break;
case AskPrice:
case AskSize:
MatchBuyOrders();
break;
}
}
}
enums as switch statements might be a code smell ...
Jawaban:
Enum dimaksudkan untuk kasus penggunaan ketika Anda benar-benar menyebutkan setiap nilai yang mungkin diambil oleh variabel. Pernah. Pikirkan kasus penggunaan seperti hari dalam seminggu atau bulan dalam setahun atau nilai konfigurasi register perangkat keras. Hal-hal yang keduanya sangat stabil dan diwakili oleh nilai sederhana.
Ingat, jika Anda membuat lapisan anti-korupsi, Anda tidak dapat menghindari memiliki pernyataan peralihan di suatu tempat , karena desain yang Anda bungkus, tetapi jika Anda melakukannya dengan benar, Anda dapat membatasinya di satu tempat saja dan gunakan polimorfisme di tempat lain.
sumber
Pertama, bau kode tidak berarti ada sesuatu yang salah. Itu berarti ada sesuatu yang salah.
enum
bau karena sering disalahgunakan, tetapi itu tidak berarti bahwa Anda harus menghindarinya. Anda baru saja mengetikenum
, berhenti, dan memeriksa solusi yang lebih baik.Kasus khusus yang paling sering terjadi adalah ketika enum yang berbeda berhubungan dengan tipe yang berbeda dengan perilaku yang berbeda tetapi antarmuka yang sama. Misalnya, berbicara dengan backend yang berbeda, merender halaman yang berbeda, dll. Ini jauh lebih alami diimplementasikan menggunakan kelas polimorfik.
Dalam kasus Anda, TickType tidak sesuai dengan perilaku yang berbeda. Mereka adalah berbagai jenis peristiwa atau properti yang berbeda dari keadaan saat ini. Jadi saya pikir ini adalah tempat yang ideal untuk enum.
sumber
Saat mentransmisikan data, tidak ada bau kode
IMHO, ketika mentransmisikan data menggunakan
enums
untuk menunjukkan bahwa suatu bidang dapat memiliki nilai dari serangkaian nilai yang terbatas (jarang berubah) adalah baik.Saya menganggap itu lebih baik daripada transmisi sewenang
strings
- wenang atauints
. String dapat menyebabkan masalah dengan variasi dalam pengejaan dan penggunaan huruf besar. Ints memungkinkan untuk mentransmisikan nilai di luar rentang dan memiliki sedikit semantik (misalnya saya terima3
dari layanan perdagangan Anda, apa artinya?LastPrice
"LastQuantity
? Sesuatu yang lain?Mengirimkan objek dan menggunakan hierarki kelas tidak selalu memungkinkan; misalnya wcf tidak memungkinkan pihak penerima untuk membedakan kelas mana yang telah dikirim.
Alasan lain mengapa seseorang tidak ingin mengirimkan objek kelas adalah bahwa layanan mungkin memerlukan perilaku yang sama sekali berbeda untuk objek yang ditransmisikan (seperti
LastPrice
) dari klien. Dalam hal itu mengirim kelas dan metodenya tidak diinginkan.Apakah pernyataan peralihan buruk?
IMHO, a satu
switch
pernyataan yang memanggil konstruktor berbeda tergantung padaenum
bukan bau kode. Ini belum tentu lebih baik atau lebih buruk daripada metode lain, seperti refleksi berdasarkan nama ketik; ini tergantung pada situasi aktual.Memiliki sakelar pada
enum
semua tempat adalah bau kode, oop memberikan alternatif yang sering lebih baik:sumber
int
. Pembungkus saya adalah yang menggunakanTickType
yang dicor dari yang diterimaint
. Beberapa acara menggunakan tanda tik ini dengan berbagai tanda tangan liar yang merupakan respons terhadap berbagai permintaan. Apakah sudah lazim menggunakanint
terus - menerus untuk fungsi yang berbeda? mis.TickPrice(int type, double value)
menggunakan 1,3, dan 6 untuktype
sementaraTickSize(int type, double value
menggunakan 2,4, dan 5? Apakah masuk akal untuk memisahkannya menjadi dua peristiwa?bagaimana jika Anda menggunakan jenis yang lebih kompleks:
maka Anda dapat memuat tip Anda dari refleksi atau membangunnya sendiri tetapi hal utama yang terjadi di sini adalah bahwa Anda memegang untuk Buka Tutup Prinsip SOLID
sumber
TL; DR
Untuk menjawab pertanyaan itu, saya sekarang kesulitan memikirkan waktu yang tidak tepat bau kode pada tingkat tertentu. Ada maksud tertentu yang mereka nyatakan secara efisien (ada sejumlah kemungkinan terbatas untuk nilai ini), tetapi sifatnya yang tertutup secara inheren membuat mereka secara arsitektur lebih rendah.
Maafkan saya ketika saya refactor ton kode warisan saya. / huh; ^ D
Tapi kenapa?
Ulasan yang cukup bagus mengapa demikian dari LosTechies di sini :
Kasus implementasi lain ...
Intinya adalah bahwa jika Anda memiliki perilaku yang bergantung pada nilai enum, mengapa tidak memiliki implementasi yang berbeda dari antarmuka yang sama atau kelas induk yang memastikan nilai itu ada? Dalam kasus saya, saya melihat berbagai pesan kesalahan berdasarkan kode status REST. Dari pada...
... mungkin saya harus membungkus kode status di kelas.
Maka setiap kali saya membutuhkan jenis baru IAmStatusResult, saya kode itu ...
... dan sekarang saya dapat memastikan bahwa kode sebelumnya menyadari bahwa ia memiliki ruang
IAmStatusResult
lingkup dan referensientity.ErrorKey
daripada yang lebih berbelit-belit, deadend_getErrorCode(403)
.Dan, yang lebih penting, setiap kali saya menambahkan jenis nilai pengembalian baru, tidak ada kode lain yang perlu ditambahkan untuk menanganinya . Whaddya tahu,
enum
danswitch
kemungkinan kode itu berbau.Keuntungan.
sumber
StatusResult
nilainya? Anda bisa berargumen bahwa enum berguna sebagai jalan pintas yang mudah diingat manusia dalam kasus penggunaan itu, tetapi saya mungkin masih menyebut bahwa kode berbau, karena ada alternatif yang baik yang tidak memerlukan koleksi tertutup.enum
, Anda tidak harus kode pembaruan setiap kali Anda menambah atau menghapus nilai, dan berpotensi banyak tempat, tergantung pada bagaimana Anda terstruktur kode Anda. Menggunakan polimorfisme alih-alih enum agak seperti memastikan kode Anda dalam Bentuk Normal Boyce-Codd .Apakah menggunakan enum adalah bau kode atau tidak tergantung pada konteksnya. Saya pikir Anda bisa mendapatkan beberapa ide untuk menjawab pertanyaan Anda jika Anda mempertimbangkan masalah ekspresi . Jadi, Anda memiliki koleksi berbagai jenis dan kumpulan operasi, dan Anda perlu mengatur kode Anda. Ada dua opsi sederhana:
Solusi mana yang lebih baik?
Jika, seperti ditunjukkan Karl Bielefeldt, tipe Anda sudah diperbaiki dan Anda mengharapkan sistem untuk tumbuh terutama dengan menambahkan operasi baru pada tipe ini, kemudian menggunakan enum dan memiliki pernyataan switch adalah solusi yang lebih baik: setiap kali Anda membutuhkan operasi baru Anda cukup menerapkan prosedur baru sedangkan dengan menggunakan kelas Anda harus menambahkan metode ke setiap kelas.
Di sisi lain, jika Anda berharap memiliki seperangkat operasi yang agak stabil tetapi Anda pikir Anda harus menambahkan lebih banyak tipe data dari waktu ke waktu, menggunakan solusi berorientasi objek lebih nyaman: karena tipe data baru harus diimplementasikan, Anda hanya perlu terus menambahkan kelas baru yang mengimplementasikan antarmuka yang sama sedangkan jika Anda menggunakan enum Anda harus memperbarui semua pernyataan beralih di semua prosedur menggunakan enum.
Jika Anda tidak dapat mengklasifikasikan masalah Anda di salah satu dari dua opsi di atas, Anda dapat melihat solusi yang lebih canggih (lihat misalnya lagi halaman Wikipedia dikutip di atas untuk diskusi singkat dan beberapa referensi untuk bacaan lebih lanjut).
Jadi, Anda harus mencoba memahami ke arah mana aplikasi Anda dapat berkembang, dan kemudian memilih solusi yang sesuai.
Karena buku-buku yang Anda rujuk berhubungan dengan paradigma berorientasi objek, tidak mengherankan bahwa mereka cenderung menentang penggunaan enum. Namun, solusi orientasi objek tidak selalu merupakan pilihan terbaik.
Intinya: enum tidak harus bau kode.
sumber
paint()
,show()
,close()
resize()
operasi dan widget kustom) maka pendekatan berorientasi objek lebih baik dalam arti bahwa hal itu memungkinkan untuk menambahkan jenis baru tanpa mempengaruhi terlalu banyak kode yang ada (Anda pada dasarnya menerapkan kelas baru, yang merupakan perubahan lokal).