Кращі практики використання та збереження перерахувань


77

Я бачив тут декілька запитань / дискусій щодо найкращого способу обробки та збереження значень, подібних до переліку (наприклад, збереження даних, придатних для перерахувань , як зберегти перерахування за допомогою NHibernate ), і я хотів би запитати, що таке загальний консенсус .

Зокрема:

  • Як слід обробляти ці значення в коді?
  • Як їх слід зберігати в базі даних (як текст / як число)?
  • Які компроміси різних рішень?

Примітка: Пояснення, спочатку включені до цього питання, я перемістив до відповіді.

Відповіді:


19

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


14

Початкова стаття здається мені чудовою. Проте, виходячи з коментарів, здається, деякі коментарі стосовно перечислення Java можуть пояснити деякі речі.

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

Отже, щоб уникнути цих операторів перемикання, можливо, було б доцільно помістити якийсь код та додаткові методи в клас enum. Майже ніколи не виникає необхідності створювати окремий "реальний клас, подібний до переліку".

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

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

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


1
Існує два сценарії для підтримки: хтось переупорядковує переліки у моєму файлі АБО хтось робить певний рефакторинг (для роз’яснення поганого вибору початкових імен) та злам збережених даних. Я думаю, що останнє є більш важливим, і порядковий - це шлях до збереження даних.
Джастін

7

Я намагався узагальнити своє розуміння. Не соромтеся редагувати це, якщо у вас є якісь виправлення. Отже:

У коді

У коді перерахування слід обробляти, використовуючи або рідний тип перечислення мови (принаймні, на Java та C #), або використовуючи щось на зразок "типового шаблону перечислення" . Використовувати прості константи (Integer або подібні) не рекомендується, оскільки ви втрачаєте безпеку типу (і ускладнюєте розуміння того, які значення є законними, наприклад, для методу).

Вибір між цими двома залежить від того, скільки додаткової функціональності слід приєднати до переліку:

  • Якщо ви хочете розмістити в переліченні багато функціональних можливостей (що добре, оскільки ви уникаєте перемикання () постійно на ньому), клас, як правило, є більш доречним.
  • З іншого боку, для простих enum-подібних значень перелік мови, як правило, є більш чітким.

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

Персистуючі переліки

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

  • У програмному забезпеченні кожен перелік повинен мати функції відображення для перетворення між переліченням (для використання всередині програмного забезпечення) та значенням ідентифікатора (для збереження). Деякі фреймворки (наприклад, (N) Hibernate) мають обмежену підтримку для автоматичного виконання цього. В іншому випадку вам доведеться помістити його в тип / клас переліку.
  • База даних (в ідеалі) повинна містити таблицю для кожного переліку з переліком юридичних значень. Одним стовпцем буде ідентифікатор (див. Вище), який є ПК. Додаткові стовпці можуть мати сенс, наприклад, для опису. Усі стовпці таблиці, які будуть містити значення з цього переліку, можуть використовувати цю "таблицю переліку" як FK. Це гарантує, що неправильні значення перерахування ніколи не можуть бути збережені, і дозволяє БД "стояти самостійно".

Однією з проблем цього підходу є те, що список значень юридичного переліку існує у двох місцях (код та база даних). Цього важко уникнути і тому часто вважається прийнятним, але є дві альтернативи:

  • Зберігайте лише список значень у БД, генеруйте тип перерахування під час побудови. Елегантний, але означає, що для запуску збірки потрібне з’єднання з БД, що здається проблематичним.
  • Визначте перелік значень у коді як авторитетний. Перевірте значення в БД під час виконання (зазвичай під час запуску), подайте скаргу / скасуйте на невідповідність.

6

Під час обробки коду для C # ви пропустили визначення делікування значення 0. Я майже обов'язково завжди оголошую своє перше значення як:

public enum SomeEnum
{
    None = 0,
}

Щоб служити нульовим значенням. Оскільки типом резервної копії є ціле число, а ціле число за замовчуванням дорівнює 0, тому дуже корисно в багатьох місцях знати, чи дійсно перелік був встановлений програмно чи ні.


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

3
Як це приховує проблему? Це робить це явним, як нульовий int. Я залишаю значення неініціалізованими в коді, тому що знаю, для чого CLR встановить їх за замовчуванням. Вони все ще ініціалізовані, це просто неявно.
Дивовижний

Ну, це, мабуть, питання стилю. Я твердо вірю в повну ініціалізацію всіх змінних у оголошенні (або щонайбільше у if-else безпосередньо після оголошення). В іншому випадку ви можете забути їх ініціалізувати, особливо якщо потік коду ускладнений. Див. Також c2.com/cgi/wiki?SingleStepConstructor .
sleske

5

Java або C # завжди повинні використовувати перерахування в коді. Застереження: Моє походження - C #.

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

Значення завжди повинні зберігатися в базі даних як цілісні значення, щоб захистити від рефакторингу імен перерахувань. Зберігайте документацію щодо кожного переліку у вікі та додайте коментар до поля бази даних, вказуючи на вікі-сторінку, що документує тип. Також додайте документацію XML до типу переліку, що містить посилання на запис wiki, щоб вона була доступна через Intellisense.

Якщо ви використовуєте інструмент для генерації CRUD-коду, він повинен мати можливість визначати тип переліку для стовпця, щоб згенеровані об'єкти коду завжди використовували перераховані члени.

Якщо для елемента перерахування потрібно застосувати власну логіку, у вас є кілька варіантів:

  • Якщо у вас є перерахування MyEnum, створіть статичний клас MyEnumInfo, який пропонує утилітні методи для виявлення додаткової інформації про члена перелічення за допомогою операторів перемикання або будь-якими необхідними засобами. Додавання "Інформація" до кінця імені переліку в назві класу гарантує, що вони будуть поруч один з одним у IntelliSense.
  • Прикрасьте члени перерахування атрибутами, щоб вказати додаткові параметри. Наприклад, ми розробили елемент керування EnumDropDown, який створює випадаючий список ASP.NET, заповнений значеннями перерахування, а EnumDisplayAttribute визначає добре відформатований текст відображення для використання для кожного члена.

Я не пробував цього, але з SQL Server 2005 або пізнішої версії ви могли теоретично зареєструвати код С # у базі даних, яка містила б інформацію переліку та можливість перетворення значень у перелічення для використання у поданнях або інших конструкціях, зробивши метод перекладу дані таким чином, щоб бази даних баз даних легше використовували.


+1 явно присвоєння значень - єдиний спосіб уникнути "корупції" при зміні
переліку

3

Імхо, що стосується кодової частини:

Ви завжди повинні використовувати тип 'перелічення' для своїх перелічень, в основному ви отримуєте багато халяви, якщо це робите: безпека типу, інкапсуляція та уникнення перемикань, підтримка деяких колекцій, таких як EnumSetта, EnumMapі чіткість коду.

що стосується частини персистенції, ви завжди можете зберегти рядкове представлення переліку та завантажити його назад, використовуючи метод enum.valueOf (String).


Погодьтеся в принципі, однак, принаймні в Java "enum" обмежений тим, що він не може мати суперклас (як зазначено вище), тому іноді клас "typesafe enum", ймовірно, кращий.
sleske

3

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


1
Значення int перерахування не гарантується однаковим з часом.
Мігель Пінг

Крім того, якщо ви використовуєте короткий рядок, продуктивність повинна бути однаковою. Char (2) займає 2 байти, int зазвичай також займає 2 або 4.
sleske

6
@ Мігель Пінг: Ідея полягає в явному присвоєнні ідентифікатора (int або char) кожному переліку. Використання внутрішньо згенерованого int enum дійсно дуже небезпечно.
sleske

1
якщо в БД бажано значення за цілими значеннями, я б використав таблицю, що відображає цілочисельні значення у зручні для читання рядки. також так, цілі значення не повинні змінюватися пізніше. проблема не повинна бути проблемою; єдине значення, яке повинні мати цілі значення цілого числа, це те, що вони відрізняються від інших членів переліку. (тобто: вони не повинні мати причин ніколи змінюватися). якщо цілочисельні значення мають значення, крім унікальної ідентифікації, можливо, слід використовувати іншу структуру даних.
Дейв Кусіно

3

Ну, з мого досвіду, використання перелічень для будь-чого іншого, крім передачі параметрів (як прапорів) до негайного виклику методу, в switchякийсь момент призводить до -ing.

  • Якщо ви збираєтеся використовувати перерахування по всьому коду, то у вас може вийти код, який не так просто підтримувати (сумно відомий switchвислів)
  • Розширення переліків - це біль. Ви додаєте новий елемент перерахування і в кінцевому підсумку переглядаєте весь свій код, щоб перевірити всі умови.
  • За допомогою .NET 3.5 ви можете додавати методи розширення до перерахувань, щоб вони поводились трохи більше як класи. Однак додати реальну функціональність таким чином не так просто, оскільки це все ще не клас (ви в кінцевому підсумку використовуєте switch-es у своїх методах розширення, якщо не деінде.

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

  • Щоб ваш клас поводився як перерахування, ви можете або змусити кожен похідний клас створити екземпляр як Singleton, або перевизначити Equals, щоб дозволити порівняння значень різних екземплярів.
  • Якщо ваш клас схожий на перерахування, це повинно означати, що він не повинен містити серіалізується стан - десеріалізація повинна бути можлива лише від його типу (свого роду "ідентифікатор", як ви сказали).
  • Логіка стійкості повинна обмежуватися лише базовим класом, інакше продовження вашого "переліку" було б кошмаром. Якщо ви вибрали шаблон Singleton, вам потрібно буде забезпечити належну десериалізацію в екземплярах singleton.

3

Кожного разу, коли ви знаходите себе, використовуючи "магічні числа" в коді, змінюйте їх на перелічення. Окрім економії часу (оскільки магія зникне, коли з’являться помилки ...), це заощадить ваші очі та пам’ять (значущі переліки роблять код більш читабельним та самодокументовим), оскільки вгадайте - ви, швидше за все, людина, яку потрібно підтримувати та розвивати власний код


1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.