Место enum в современном изменчивом мире
Есть у нас, допустим, компьютерная игра с разнополыми персонажами. Пол персонажа будет храниться в поле gender. Можно сделать это поле целым числом или строкой, но хороший программист стремится сделать некорректные состояния объектов невыразимыми, и поэтому, скорее всего, заведёт для пола enum. Теперь сделать персонажа с неправильным полом просто невозможно!
А ещё в игре есть база данных, в которой хранятся все игроки, и туда тоже нужно записывать, какого они пола. Было бы неплохо сделать так, чтобы персонажа с некорректным полом в базу данных добавить было нельзя, потому что с ней будет работать не только код, в котором для полов ничего, кроме enum, не предусмотрено, но ещё и живые люди. А живые люди, как известно, так и норовят вписать в базу данных то, что туда вписывать не надо.
К счастью, специально для таких случаев во многих СУБД также есть возможность делать колонки типа enum, тем самым ограничивая диапазон значений, которые могут иметь данные в такой колонке. Чем и воспользуется опытный разработчик, который не желает, чтобы в его проекте что-то было не по фэншую.
На клиенте программист тоже не дурак и тоже знает, что некорректные состояния должны быть невыразимы, и что для этого пол персонажа должен быть enum.
Идеально!
Однако если присмотреться, то можно обнаружить один небольшой изъян, который в итоге приведёт к проблемам. Поле, в котором хранится пол, называется не sex, а gender, а значит, к нему уже подбирается гейм-дизайнер. Рано или поздно он узнает, что гендеров, в отличии от полов, существенно больше двух, и захочет добавить гендер GENERIC_TEEN. Тут-то всем и поплохеет.
Клиента надо постоянно обновлять
Первыми под удар попадут ребята, которые писали клиентский код, потому что некорректные значения параметра гендер начнут приходить с сервера по каждому чиху. А когда с сервера придёт персонаж с гендером о существовании которого клиент не знает, то игра, как и хотел программист на клиенте, просто не запустится. Теперь при появлении новых гендеров необходимо релизить новые версии клиентов и запускать принудительные обновления, которые мы все так сильно любим.
На сервере enum — добро, потому что если там обнаружено какое-то неизвестное значение, то это явно ошибка, которую надо найти и исправить, а вот на клиенте появление неизвестного значения — штатная ситуация, которая будет регулярно случаться на устаревших версиях клиента, и поэтому там enum — зло, которое надо искоренить и уничтожить как можно скорее.
Новый код не сможет работать со старой базой
Кроме того, согласно рекомендациям лучших собаководов, новый код должен уметь работать со старой схемой данных, а тут получается, что вся система может развалиться только из-за того, что в код добавили новый гендер.
Поэтому, в базе данных enum нам мешает, и в данном случае надо хранить в поле строки, или инты, или что угодно, во что при необходимости можно записать значение, не предусмотренное заранее.
И кто-то скажет: ладно, ладно, на клиенте enum вредит, и в базе данных он больше мешает, чем помогает, но в серверном коде-то enum приносит большую пользу. Он же не будет предлагать убрать enum из кода на сервере? Будет!
Старый код не сможет работать с новой базой
Нынче программисту рекомендуется не только писать код так, чтобы он нормально работал со старой базой данных, но и развивать схему данных так, чтобы немного устаревший код тоже мог нормально с ней работать.
А если на сервере для гендера использовать enum, то всё опять развалится, только уже после того, как выяснится, что с новым кодом что-то не то и надо немедленно откатить всё до предыдущей версии, а новые гендеры в базу данных уже попали.
И поэтому enum из серверного кода мерным солдатским шагом идут куда-то в направлении ближайшего лесного массива, чтобы никогда оттуда не вернуться, ибо всё, что может развалить систему в неподходящий момент — зло, а злу не место в нашем уютном коде.
Так может, забить на enum и всегда использовать строки?
Короче, как сказал бы Винни-Пух – это какие-то неправильные енамы, и это они провоцируют программиста писать неправильный код! В enum надо помещать действительно незыблемые и фундаментальные вещи, которые не меняются годами, а не гендеры, которые добавляются и исчезают каждый релиз! Каждой задаче – свой инструмент, и enum – это не тот инструмент, который нужно использовать для хранения значений, возможный спектр которых постоянно расширяется.
Если переменная может принимать только какие-то заранее известные значения и всё, что не входит в эти значения, должно приводить к ошибке, то использовать enum – хорошая идея, но если это не так, если непредусмотренные значения регулярно будут попадать в переменную, то использовать enum равно выстрелить себе в ногу, и лучше этого не делать, если вы, конечно не мазохист.
Эта конкретная ситуация интересна тем, что ничто не предвещало добавления в enum новых значений, не говоря уже о том, что новые значения будут появляться регулярно. Откуда нам вообще было знать, что полов будет больше двух?
Поэтому, согласно принципу KISS, enum на первом этапе – хороший и правильный выбор. Опытный программист сделает enum и зафиксирует, что при появлении дополнительного значения нужно немедленно запустить мероприятия, необходимые для того, чтобы enum из кода исчез.
К сожалению, практика показывает, что на самом деле добавлением нового гендера будет заниматься кто-то другой, и он, недолго думая, добавит в enum новое значение, a на этом успокоится. Так что если есть какие-то сомнения, лучше следовать простому правилу: хранится где-то, кроме кода – значит, не enum, и на этом точка.
Получается, enum вообще не должно быть в коде, связанном с гендерами?
Несмотря на то, что я сейчас наговорил, enum в коде всё равно могут быть очень полезными, даже если в них находятся значения, которые попадают в базу данных. Не надо только использовать их для того, чтобы хранить значения, которые вытаскиваются из БД, надо сравнивать то, что пришло из БД с желаемым значением. Например, когда программист хочет написать, что если гендер персонажа – GENERIC_TEEN, то ему нельзя класть в инвентарь спиртные напитки, можно завести enum Gender с методом value, возвращающим строку, и написать код который будет проверять, равенство поля gender у персонажа и Gender.GENERIC_TEEN.value() и это будет хорошо, потому что не даст программисту сделать ошибку в значении статуса.
Код должен эволюционировать
Решения, хорошо служившие нам в какой-то момент, могут стать неудобными по прошествии времени. И нужно следить за тем, чтобы код отражал текущее состояние проекта, а не был набором костылей, с помощью которых решение, созданное на коленке пару лет назад, натягивается на “здесь и сейчас”. В противном случае он будет неисчерпаемым источником факапов и лулзов для всех, кто хоть как-то связан с проектом, будь то разработчик, тестировщик или непосредственный потребитель.