Лучшие практики использования и сохранения перечислений

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

Особенно:

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

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


person Community    schedule 14.04.2009    source источник


Ответы (10)


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

person Community    schedule 28.04.2009

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

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

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

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

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

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

person Community    schedule 28.04.2009
comment
Есть два сценария, которые следует поддерживать: кто-то переупорядочивает перечисления в моем файле ИЛИ кто-то выполняет некоторый рефакторинг (чтобы прояснить неправильный первоначальный выбор имени) и ломает сохраненные данные. Я думаю, что последнее более важно, и порядковый номер - это способ сохранения данных. - person Justin; 13.05.2010

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

В коде

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

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

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

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

Сохраняющиеся перечисления

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

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

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

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

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

public enum SomeEnum
{
    None = 0,
}

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

person Community    schedule 23.04.2009
comment
Я не согласен. Это будет иметь смысл только в том случае, если вы иногда оставляете переменные неинициализированными, что я считаю плохой практикой. Я часто встречал идею отсутствия значения, но считаю, что это только скрывает реальную проблему (неинициализированная переменная). - person sleske; 28.04.2009
comment
Как скрыть проблему? Это делает его явным, как int, допускающий значение NULL. Я оставляю значения неинициализированными в коде, потому что я знаю, что CLR установит их по умолчанию. Они все еще инициализированы, это просто неявно. - person Quibblesome; 28.04.2009
comment
Ну, наверное, дело в стиле. Я твердо верю в полную инициализацию всех переменных при объявлении (или, самое большее, в if-else сразу после объявления). В противном случае вы можете забыть инициализировать их, особенно если поток кода сложный. См. Также c2.com/cgi/wiki?SingleStepConstructor. - person sleske; 29.04.2009

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

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

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

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

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

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

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

person Community    schedule 22.04.2009
comment
+1 явное присвоение значений - единственный способ избежать повреждения при изменении перечисления - person ashes999; 20.12.2012

Imho, что касается кодовой части:

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

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

person Community    schedule 14.04.2009
comment
Согласитесь в принципе, однако, по крайней мере, в Java перечисление ограничено тем, что у него не может быть суперкласса (как указано выше), поэтому иногда, вероятно, лучше использовать класс typeafe enum. - person sleske; 14.04.2009

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

person Community    schedule 14.04.2009
comment
Не гарантируется, что значение int перечисления будет одинаковым с течением времени. - person Miguel Ping; 14.04.2009
comment
Кроме того, если вы используете короткую строку, производительность должна быть такой же. Char (2) занимает 2 байта, int обычно также занимает 2 или 4 байта. - person sleske; 14.04.2009
comment
@Miguel Ping: Идея состоит в том, чтобы явно присвоить идентификатор (int или char) каждому перечислению. Использование внутренне сгенерированного int перечисления действительно очень опасно. - person sleske; 14.04.2009
comment
если в БД желательно иметь значение, выходящее за рамки целочисленных значений, я бы использовал таблицу, отображающую целочисленные значения в читаемые человеком строки. Кроме того, да, целочисленные значения не должны изменяться позже. Хотя это не должно быть проблемой; единственное значение, которое должны иметь базовые целочисленные значения перечисления, состоит в том, что они отличаются от других членов перечисления. (т.е. у них не должно быть причин когда-либо меняться). если целочисленные значения имеют значение, выходящее за рамки уникальной идентификации, вероятно, следует использовать другую структуру данных. - person Dave Cousineau; 04.01.2012

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

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

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

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

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

person Community    schedule 27.04.2009

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

person Community    schedule 05.04.2013