Является ли хорошей практикой иметь специальное значение «ALL» в перечислении


11

Я разрабатываю новый сервис в среде микросервисов. Это REST сервис. Для простоты предположим, что путь: / historyBooks

И метод POST для этого пути создает новую книгу истории.

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

Для краткости предположим, что у нас есть только следующие эпохи человеческой истории:

  • древний
  • Пост Классический
  • Современный

В моем коде я хотел бы представить их в enum.

Тело метода (полезная нагрузка) имеет формат JSON и должно содержать имя поля eras. Это поле представляет собой список eraзначений, которые охватывает эта книга.

Тело может выглядеть так:

{
  "name": "From the cave to Einstein - a brief history review",
  "author": "Foo Bar",
  "eras": ["Ancient", "Post Classical", "Modern"]
}

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

В обзоре API было предложено:
включить другое значение, ALLдля enrasum, чтобы явно указать, что все эры покрыты.

Я думаю, что у него есть свои плюсы и минусы.

Плюсы:

Явный ввод

Минусы:

Если в списке указаны два пункта, скажем ALLи Ancient- что будет взято из приложения? Я думаю, это ALLдолжно переопределить другие значения, но это новая бизнес-логика.

Если я выполню запрос для книг, которые охватывают определенные эпохи, как бы я представлял книги, которые охватывают все эпохи? Если ALLтакже используются для вывода ( с использованием той же логики), то это ответственность потребителя интерпретировать , ALLкак ["Ancient", "Post Classical", "Modern"].

Мой вопрос

Я думаю, что наличие нового ALLвызывает больше путаницы, чем отсутствие его вообще.

Что вы думаете? Вы бы добавили это ALLзначение или оставили бы свой API без него?


7
Что произойдет, если вы решите добавить атомную эру? Книги, которые охватывают "все" эпохи, волшебным образом получают новый контент? Вы начинаете лгать о содержании книг? Вы проходите и обновляете все? Это может быть нетривиальным, если в некоторых книгах уже есть содержание об атомной эре.
8bittree

Ответы:


13

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

Как вы указали, ALL с другими опциями означает, что вы можете получать конфликтующие запросы. Еще одно соображение заключается в том, что вы можете передать «ВСЕ», тогда любые другие перечисления станут исключениями, а не включениями, например «ВСЕ», «Древний» означает «Все, кроме Древних». Это имеет некоторый смысл, но, очевидно, пользовательский интерфейс должен отражать то, что может быть слишком сложным.

TL; DR; В основном, «ВСЕ» - это тонкость пользовательского интерфейса, и вы можете достичь того же на уровне обслуживания без двусмысленности, поэтому не делайте этого.


6

Если во входных данных не указано ни одной эпохи, считается, что эта книга охватывает все эпохи.

Это, а также наличие особого случая «ВСЕ», являются плохими ИМХО - ваши API должны быть по возможности явными. Наличие особых случаев, таких как «на самом деле ничего не значит все», означает, что любой, кто использует ваш API, также должен знать все ваши особые случаи или, как и в случае «ВСЕ», приводить к запросам, где намерение и / или результат неоднозначны.

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


1

Для категориальных переменных я бы не стал использовать подстановочный знак «все» на одном уровне.

Похоже, у вас есть двухуровневый критерий поиска для периода времени:

  1. логическое, is_selective?
  2. набор, дизъюнкция определенных категорий

Вызывающий может указать (false, игнорируется) или (true, {'Ancient', 'Modern'}).

Или вы можете интерпретировать пустой набор как подстановочный знак, он обозначает «Не селективный».

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


1

tl; dr
Что касается вашего фактического вопроса - касающегося локальных структур данных в реализации - я согласен с вашим выводом: избегайте особой ценности всех эпох, потому что это в основном добавляет сложности и возможности совершать ошибки. Однако, особенно пользовательский интерфейс конечного пользователя и, возможно, сериализованные данные полезной нагрузки могут быть разными историями.

Локальные структуры данных

Давайте посмотрим на это с точки зрения системы типов. По сути, enum - это тип, который определяет несвязанный набор определенных категорий (перечислителей), как уже указывалось в ответе @JH . Переменная типа enum содержит ровно одну из этих категорий. Если вы хотите представить коллекцию значений перечислителя, вам нужен второй тип:

// C++-inspired pseudo-code
enum Era { ancient, post_classical, modern };
using Eras = Collection<Era>;

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

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

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

Если в списке указаны два элемента, скажем, ВСЕ и Древний - что будет взято из приложения? [...]

Я бы указал в спецификации API, что маркер all eras всегда должен появляться сам по себе, потому что иметь что-то поверх него не имеет смысла. Тогда я бы отклонил это как нарушение контракта. Но это хороший пример того, как все эпохи усложняют как реализацию, так и бизнес-логику.

Основная проблема заключается в том, что вы вынуждены указать правила для преобразования между специальным значением и его основным значением. И тогда вы и все остальные, использующие API, должны правильно реализовать эти правила. Циник во мне говорит мне, что вопрос не в том , ошибется ли кто-то, а просто когда .

Сериализованные данные (JSON)

Первоначально у меня был целый раздел о модификациях и запросах только для чтения и различных ролях в "eras"списке. Но в конце концов я удалил его, потому что поцелуй. Правила для enum / collection не являются необоснованными для данных JSON, поэтому поддерживать согласованность - самое простое решение.

Пользовательский интерфейс для конечного пользователя

Я предполагаю, что в любом случае произойдет преобразование данных между пользовательским интерфейсом и базовыми структурами данных, скорее всего потому, что у вас есть какая-то архитектура MVC. Итак, используйте все, что является наиболее удобным и интуитивно понятным для пользователя. В своем ответе @Markus привел отличный пример с различными вариантами выбора для дней недели.


1

Я думаю, что наличие нового ALL вызывает больше путаницы, чем отсутствие его вообще.

Да.

Если вы думаете с точки зрения коллекции: у вас есть целая коллекция чего-то. И каждый раз, когда вам нужно только подмножество этой коллекции, вы должны предоставить какое-то условие фильтра , когда элемент считается элементом этого подмножества. И ALLэто тот случай, когда фильтр не применяется, а не «условие фильтра есть ALL» .

Если во входных данных не указано ни одной эпохи, считается, что эта книга охватывает все эпохи.

Я бы перевернул это с ног на голову: эта книга не охватывает конкретную эпоху.

Это также согласуется с точки зрения запроса:

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

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


0

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

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

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

Технически это означает, что если пользователь выбирает «Будние дни», пользовательский интерфейс будет иметь пять флажков и перечисление будет использовать значение «Рабочие дни». Если один из флажков снят, значение «Рабочие дни» заменяется битовой картой или оставшимися значениями оставшихся четырех дней.

Не используйте одну часть параметров, чтобы включить все и в то же время исключить что-либо, чтобы избежать конфликтов и убедиться, что пользовательский интерфейс имеет смысл для пользователя.

Я думаю, что хорошая ориентация заключается в том, что использование опции «Все» имеет смысл, если пользователь может легко понять концепцию, что включает в себя «Все» (все дни недели), или если это довольно неважно (поиск по всем категориям на Amazon)


0

Мне нравится идея явного переноса перечисления ALL, но я бы предпочел установить их как флаги

const enum = {
    ALL: { value: 0 },
    ANCIENT: { name: 'Ancient', value: 1 },
    POST_CLASSICAL: { name: 'Post Classical', value: 2 },
    MODERN: { name: 'Modern', value: 4 }
}

Используя библиотеку, такую ​​как flagon, или вручную играя с побитовыми операциями, когда все значения выбраны, вместо этого вы используете ALL.

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

Хотя вместо этого я бы выбрал флаг NONE и позволил бы клиенту решать, что делать, когда используется NONE, если бизнес изменится позже.

Если взять эту реализацию, вместо передачи enuns, вы передадите сумму выбранных значений.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.