Які недоліки у відображенні інтегральних ідентифікаторів для перерахунків?


16

Я думав над створенням власних типів для таких ідентифікаторів:

public enum CustomerId : int { /* intentionally empty */ }
public enum OrderId : int { }
public enum ProductId : int { }

Моя основна мотивація цього - запобігати помилку, коли ви випадково передаєте orderItemId функції, яка очікувала orderItemDetailId.

Здається, що ентузіасти безперебійно працюють із усім, що я хотів би використовувати в типовому веб-додатку .NET:

  • Маршрутизація MVC працює чудово
  • Серіалізація JSON працює чудово
  • Кожна ОРМ, яку я думаю, працює чудово

Тож зараз мені цікаво: "чому я не повинен цього робити?" Це єдині недоліки, про які я можу придумати:

  • Це може заплутати інших розробників
  • Це вводить невідповідність у вашій системі, якщо у вас є нецілісні ідентифікатори.
  • Може знадобитися додаткове лиття, наприклад (CustomerId)42. Але я не думаю, що це буде проблемою, оскільки маршрутизація ORM та MVC зазвичай передає вам значення типу enum безпосередньо.

Отже, моє запитання: що я пропускаю? Це, мабуть, погана ідея, але чому?



3
Це називається "мікротипізація", ви можете гуляти навколо (наприклад, це для Java, деталі різні, але мотивація однакова)
qbd

Я набрав дві часткові відповіді, а потім видалив їх, бо зрозумів, що думаю про це неправильно. Я згоден з вами, що "Це, мабуть, погана ідея, але чому?"
користувач2023861

Відповіді:


7

Чудова ідея створити тип для кожного виду ідентифікаторів, головним чином, із зазначених вами причин. Я б цього не зробив, реалізуючи тип як enum, оскільки створення правильної структури або класу дає вам більше варіантів:

  • Ви можете додати неявну конверсію до int. Якщо ви подумаєте про це, то інший напрям, int -> CustomerId, той проблемний, який заслуговує явного підтвердження від споживача.
  • Ви можете додати ділові правила, які визначають, які ідентифікатори є прийнятними, а які - ні. Йдеться не лише про слабку перевірку того, чи є позитивний int: іноді список усіх можливих ідентифікаторів зберігається в базі даних, але змінюється лише під час розгортання. Тоді ви можете легко перевірити, чи вказаний ідентифікатор існує проти кешованого результату відповідного запиту. Таким чином, ви можете гарантувати, що ваша програма швидко вийде з ладу за наявності недійсних даних.
  • Методи на ідентифікаторі можуть допомогти вам зробити дорогі перевірки існування БД або отримати відповідний об'єкт / домен.

Про реалізацію цього можна прочитати у статті Функціональний C #: Первісна одержимість .


1
я б сказав, що ви майже напевно, тому не хочете загортати кожну інту в клас, а в структуру
jk.

Добре зауваження, я трохи оновив відповідь.
Лукаш Ланськ

3

Це розумний злом, але все-таки хак в тому, що зловживає функцією для чогось, що не є цільовим призначенням. У хаків є витрата на ремонтопридатність, тому недостатньо, що ви не можете знайти жодних інших недоліків, а також потрібно мати значні переваги порівняно з більш ідіоматичним способом вирішення тієї ж проблеми.

Ідіоматичним способом було б створення типу обгортки або структури з одного поля. Це забезпечить вам безпеку одного типу більш чітко та ідіоматично. Хоча ваша пропозиція є розумною, я не бачу жодних суттєвих переваг у використанні типів обгортки.


1
Основна перевага enum полягає в тому, що він "просто працює" так, як вам потрібно з MVC, серіалізацією, ORM і т. Д. Я створив власні типи (структури та класи) в минулому, і ви закінчите писати більше коду, ніж ви повинні мати для того, щоб ці рамки / бібліотеки працювали з вашими користувацькими типами.
default.kramer

Серіалізація повинна працювати прозоро в будь-якому випадку, але, наприклад, ORM вам потрібно налаштувати якесь явне перетворення. Але це ціна сильно набраної мови.
ЖакБ

2

З мого POV ідея гарна. Намір надати різні типи непов'язаним класам ідентифікаторів і в іншому випадку виразити семантику за допомогою типів і дозволити компілятору перевірити це, є хорошим.

Я не знаю, як це працює в .NET-продуктивності, наприклад, значення перерахувань обов'язково поля. Код OTOH, який працює з базою даних, швидше за все, пов'язаний з I / O, і ідентифікатори в коробці не будуть вузьким місцем.

У досконалому світі ідентифікатори були б незмінними і порівнянними лише за рівність; Фактичні байти - це деталь реалізації. Непов’язані ідентифікатори мали б несумісні типи, тому ви не могли помилково використовувати один для іншого. Ваше рішення практично відповідає рахунку.


-1

Ви не можете цього зробити, перерахунки повинні бути заздалегідь визначені. Тому, якщо ви не готові заздалегідь визначити весь спектр можливих значень ідентифікаторів клієнтів, ваш тип буде марним.

Якщо ви робите, ви б не вирішили своєї основної мети запобігти випадковому призначенню ідентифікатора типу A ідентифікатору типу B Тож перерахунки не корисні для вашої мети.

Це викликає питання, хоча якщо було б корисно створити типи для ідентифікатора клієнта, ідентифікатора замовлення та ідентифікатора продукту, походячи з Int32 (або Guid). Я можу придумати причини, через які це було б неправильно.

Мета ідентифікації - ідентифікація. Для цього вже ідеальні інтегральні типи, вони не отримують більше ідентифікації, спускаючись з них. Не потрібна і не потрібна спеціалізація, ваш світ не буде представлений краще, маючи різні типи для ідентифікаторів. Тож з точки зору ОО це було б погано.

Якщо ви пропонуєте, що ви хочете більше, ніж безпека типу, ви хочете, щоб забезпечити цінність безпеки, і це виходить за межі області моделювання, що є операційною проблемою.


1
Ні, перерахунки не повинні бути визначені в .NET. І справа в тому, що кастинг навряд чи трапляється, наприклад, в public ActionResult GetCustomer(CustomerId custId)жодному ролі не потрібно - рамка MVC надасть значення.
default.kramer

2
Я не згоден. Для мене це має ідеальний сенс з точки зору ОО. Int32 не має семантики, це чисто "технічний" тип. Але в мене є потреба в абстрактному ідентифікаторному типі, який служить призначенню об'єктів і який може бути реалізований як Int32 (але може бути довгим чи будь-яким, насправді не має значення). У нас є конкретні спеціалізації, такі як ProductId, які походять від Identifier, які ідентифікують конкретний екземпляр ProductId. Немає жодного логічного сенсу присвоювати значення OrderItemId ProductId (це явно помилка), тому чудово, що система типу може захистити нас від цього.
qbd

1
Я не знав, що C # був таким гнучким щодо використання переписок. Це, звичайно, бентежить мене як розробника, що звик до того, що («)» перерахунки - це перерахування можливих значень. Вони, як правило, задають діапазон іменованих ідентифікаторів. Тож тепер цей тип "зловживається" в тому сенсі, що немає діапазону і немає імен, просто залишився тип. І, мабуть, і тип не такий безпечний. Інша річ: приклад, очевидно, додаток до бази даних, і в якийсь момент ваш «перелік» буде перенесений на цілісне поле типу, в який момент ви б безпечно втратили свій передбачуваний тип.
Мартін Мейт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.