Лучшие практики для использования и сохранения перечислений - PullRequest
74 голосов
/ 14 апреля 2009

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

В частности:

  • Как эти значения должны обрабатываться в коде?
  • Как их следует сохранить в базе данных (в виде текста / в виде числа)?
  • Каковы компромиссы различных решений?

Примечание. Я переместил объяснения, изначально включенные в этот вопрос, в ответ.

Ответы [ 10 ]

18 голосов
/ 29 апреля 2009

Я согласен со многим из того, что вы говорите. Однако я хотел бы добавить одну вещь о сохранении перечислений: я не верю, что создание перечислений во время сборки из значений БД приемлемо, но я также считаю, что проверка во время выполнения не является хорошим решением , Я бы определил третье средство: проведите модульный тест, который проверит значения перечисления в базе данных. Это предотвращает «случайное» расхождение и позволяет избежать накладных расходов при проверке перечислений в базе данных при каждом запуске кода.

12 голосов
/ 28 апреля 2009

Начальная статья выглядит хорошо для меня. Тем не менее, судя по комментариям, некоторые комментарии, касающиеся перечислений Java, могут прояснить несколько вещей.

Тип enum в Java является классом по определению, но многие программисты склонны забывать об этом, потому что они скорее связывают его с «списком допустимых значений», как и в некоторых других языках. Это больше, чем это.

Итак, чтобы избежать этих операторов switch, было бы разумно поместить некоторый код и дополнительные методы в класс enum. Почти никогда не нужно создавать отдельный «enum-подобный реальный класс».

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

Если вы хотите представить значения перечисления как целые числа в базе данных из-за скорости или по любой другой причине, это сопоставление также должно находиться в перечислении Java. По умолчанию вы получите отображение имени строки, и я доволен этим. Есть порядковый номер, связанный с каждым значением перечисления, но использование этого непосредственно в качестве отображения между кодом и базой данных не очень ярко, потому что этот порядковый номер изменится, если кто-то переупорядочит значения в исходном коде. Или добавляет дополнительные значения перечисления между существующими значениями. Или удаляет какое-то значение.

(Конечно, если кто-то изменит имя перечисления в исходном коде, сопоставление строк по умолчанию тоже станет неудачным, но это менее вероятно произойдет случайно. И при необходимости вы можете легче защититься от этого, добавив некоторые проверка во время выполнения и проверка ограничений в базе данных, как уже предлагалось здесь.)

6 голосов
/ 04 мая 2015

Я попытался обобщить мое понимание. Не стесняйтесь редактировать это, если у вас есть какие-либо исправления. Итак, вот и все:

В коде

В коде перечисления должны обрабатываться либо с использованием собственного типа перечисления языка (по крайней мере, в Java и C #), либо с использованием чего-то вроде «типизированного шаблона перечисления» . Использование простых констант (целочисленных или аналогичных) не рекомендуется, поскольку вы теряете безопасность типов (и затрудняетесь понять, какие значения являются допустимыми для ввода, например, метода).

Выбор между этими двумя зависит от того, сколько дополнительных функций нужно добавить к перечислению:

  • Если вы хотите добавить множество функций в перечисление (что хорошо, потому что вы все время избегаете его переключения), класс обычно более уместен.
  • С другой стороны, для простых значений, подобных перечислению, перечисление языка обычно более четкое.

В частности, по крайней мере в Java перечисление не может наследоваться от другого класса, поэтому, если у вас есть несколько перечислений с похожим поведением, которые вы хотели бы поместить в суперкласс, вы не можете использовать перечисления Java.

Постоянные перечисления

Чтобы сохранить перечисления, каждому значению перечисления должен быть присвоен уникальный идентификатор. Это может быть либо целое число, либо короткая строка. Предпочтительна короткая строка, так как она может быть мнемонической (для администраторов баз данных и т. Д. Легче понять необработанные данные в БД).

  • В программном обеспечении каждое перечисление должно иметь функции отображения для преобразования между перечислением (для использования внутри программного обеспечения) и значением идентификатора (для сохранения). Некоторые фреймворки (например, (N) Hibernate) имеют ограниченную поддержку для выполнения этого автоматически. В противном случае вы должны поместить его в тип / класс перечисления.
  • База данных должна (в идеале) содержать таблицу для каждого перечисления, в которой перечислены допустимые значения. Один столбец будет идентификатором (см. Выше), который является PK. Дополнительные столбцы могут иметь смысл, например, описание. Все столбцы таблицы, которые будут содержать значения из этого перечисления, могут затем использовать эту «таблицу перечислений» в качестве FK. Это гарантирует, что некорректные значения перечисления никогда не будут сохраняться, и позволяет БД «стоять самостоятельно».

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

  • Сохранять только список значений в БД, генерировать тип enum во время сборки. Элегантно, но означает, что для запуска сборки требуется соединение с БД, что кажется проблематичным.
  • Определите список значений в коде, который будет достоверным. Проверьте значения в БД во время выполнения (обычно при запуске), пожаловаться / отменить несоответствие.
6 голосов
/ 23 апреля 2009

В обработке кода для C # вы пропустили определение делакарирования значения 0. Я почти всегда объявляю свое первое значение как:

public enum SomeEnum
{
    None = 0,
}

Так, чтобы служить нулевым значением. Поскольку тип поддержки представляет собой целое число, а целое число по умолчанию равно 0, поэтому во многих местах очень полезно знать, действительно ли перечисление было установлено программно или нет.

5 голосов
/ 22 апреля 2009

Java или C # всегда должны использовать перечисления в коде. Отказ от ответственности: Мой фон C #.

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

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

Если вы используете инструмент для генерации кода CRUD, он должен иметь возможность определять тип перечисления, который будет использоваться для столбца, чтобы объекты сгенерированного кода всегда использовали перечисляемые элементы.

Если для члена перечисления необходимо применить пользовательскую логику, у вас есть несколько вариантов:

  • Если у вас есть перечисление MyEnum, создайте статический класс MyEnumInfo, который предлагает служебные методы для обнаружения дополнительной информации о члене перечисления с помощью операторов switch или любых других необходимых средств. Добавление «Info» к концу имени перечисления в имени класса гарантирует, что они будут рядом друг с другом в IntelliSense.
  • Украсить элементы перечисления атрибутами, чтобы указать дополнительные параметры. Например, мы разработали элемент управления EnumDropDown, который создает раскрывающийся список ASP.NET, заполненный значениями перечисления, а атрибут EnumDisplayAttribute задает красиво отформатированный отображаемый текст для использования для каждого элемента.

Я не пробовал этого, но в SQL Server 2005 или более поздней версии вы могли бы теоретически зарегистрировать код C # в базе данных, которая содержала бы информацию перечисления и возможность преобразовывать значения в перечисления для использования в представлениях или других конструкциях, создавая метод перевод данных более удобным для администраторов баз данных образом.

3 голосов
/ 27 апреля 2009

Каждый раз, когда вы обнаруживаете, что используете «магические числа» в коде, меняйте его на enums. Помимо экономии времени (поскольку магия исчезнет, ​​когда появятся ошибки ...), она сохранит ваши глаза и память (значимые перечисления делают код более читабельным и самодокументируемым), так как угадайте, что - вы, скорее всего, человек, который должен поддерживать и развивать ваш собственный код

3 голосов
/ 27 апреля 2009

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

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

Так что для enum-подобного объекта с чуть большей функциональностью вам нужно потратить некоторое время и создать его как класс, имея в виду несколько вещей:

  • Чтобы ваш класс вел себя как перечисление, вы можете либо принудительно создать экземпляр каждого производного класса как Singleton, либо переопределить Equals, чтобы разрешить сравнение значений различных экземпляров.
  • Если ваш класс подобен enum, это должно означать, что он не должен содержать сериализуемого состояния - десериализация должна быть возможна только из его типа (своего рода «ID», как вы сказали).
  • Логика постоянства должна быть ограничена только базовым классом, иначе расширение вашего "enum" было бы кошмаром. В случае, если вы выбрали шаблон Singleton, вам необходимо обеспечить правильную десериализацию в экземпляры Singleton.
3 голосов
/ 14 апреля 2009

Хранение текстового значения перечисления в базе данных менее предпочтительно, чем хранение целого числа, из-за необходимого дополнительного пространства и более медленного поиска. Он ценен тем, что он имеет большее значение, чем число, однако база данных предназначена для хранения, а уровень представления предназначен для того, чтобы все выглядело хорошо.

2 голосов
/ 14 апреля 2009

Имхо, как для кодовой части:

Вы должны всегда использовать тип 'enum' для своих перечислений, в основном вы получаете много халявы, если вы делаете это: Тип безопасности, инкапсуляция и избегание переключения, поддержка некоторых коллекций, таких как EnumSet и EnumMap и четкость кода.

Что касается персистентной части, вы всегда можете сохранить строковое представление перечисления и загрузить его обратно, используя метод enum.valueOf (String).

1 голос
/ 06 апреля 2013

Я знаю, что это старый форум, что если в базе данных могут быть другие вещи, интегрированные непосредственно в нее? Например. когда получающаяся БД является ЕДИНСТВЕННОЙ целью кода. Затем вы будете определять перечисления при каждой интеграции. Лучше тогда иметь их в БД. В остальном я согласен с оригинальным постом.

...