在枚举中具有特殊值“ 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审查中,提出了一个建议:为ERAS枚举
包含另一个值ALL,以明确表明已覆盖所有时代。

我认为它有一些优点和缺点。

优点:

显式输入

缺点:

如果提供了列表中的两项,请说ALLAncient-从应用程序中获取什么?我想这ALL应该覆盖其他值,但这是新的业务逻辑。

如果我运行查询,对于涵盖特定时代的图书,我将如何代表涵盖所有时代的图书?如果ALL还用于输出(使用相同的逻辑),则用户有责任将其解释ALL["Ancient", "Post Classical", "Modern"]

我的问题

我认为拥有新事物ALL比根本没有造成更多的混乱。

你怎么看?您是否要添加此ALL值或不使用它而保留API?


7
如果您决定添加原子时代会怎样?涵盖“所有”时代的书是否神奇地获得了新的内容?您是否开始对书籍的内容撒谎?您经历了所有更新吗?如果某些书确实已经包含了有关原子时代的内容,那将是不平凡的。
8bittree

Answers:


13

取决于您的可用时代是否可用于调用应用程序。大概是这样,以便用户可以选择他们感兴趣的内容。如果是这种情况,那么提供“ ALL”选项是一个前端问题。如果是我,那么它将发送所有时代的列表,而不是“所有”选项。如果不是这样,那么您需要一个“ ALL”选项并冒风险,然后前端将使事情从一个无法理解的时代恢复过来。

如您所指出的,ALL和其他选项意味着您可能会收到冲突的请求。还有一个考虑因素是,您可以传递“ ALL”,然后任何其他枚举都将成为排除对象而不是包含对象,例如“ ALL”,“ Ancient”表示“除Ancients外的所有内容”。这样做是有道理的,但显然UI必须反映出可能过于复杂的内容。

TL; DR; 通常,“ ALL”是UI的精妙之处,您可以在服务级别上毫无歧义地实现相同的功能,因此请不要这样做。


6

如果输入中未提供任何时代,则认为本书涵盖了所有时代。

这种情况,还有特殊的“ ALL”情况,是不好的恕我直言-您的API应该尽可能明确。出现特殊情况,例如“什么都不意味着一切”意味着使用您的API的任何人还必须知道您的所有特殊情况,或者与“ ALL”情况一样,导致意图和/或结果不明确的请求。

在验证用户输入是否有效以及正确处理请求方面,特殊情况也常常导致更复杂的代码。


1

对于分类变量,我避免将通配符“ all”放在同一级别。

听起来您有一个针对时间段的两级搜索条件:

  1. 布尔值,is_selective?
  2. 特定类别的集合,析取

呼叫者可以指定(false,忽略)或(true,{'ancient','modern'})。

或者,您可以选择将空集解释为通配符,它​​表示“非选择性”。

对于您的特定情况,听起来好像您有一个连续的变量,年份,离散化为几个知名的值,并且您真正想要做的是区间算术。因此,您的查询将接受(开始,结束)范围的年份,并且枚举可以方便地提供此类属性。


1

tl; dr
对于您的实际问题–有关实现中的本地数据结构–我同意您的结论:避免所有时代的特殊价值,因为这特别会增加复杂性和出错的机会。但是,尤其是最终用户UI以及序列化的有效负载数据可能是不同的故事。

本地数据结构

让我们从类型系统的角度来看这件事。基本上,枚举是一种定义特定类别(枚举器)的分离集合的类型,正如@JH答案中已经指出的那样。枚举类型的变量恰好拥有这些类别之一。如果要表示枚举器值的集合,则需要第二种类型:

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

一个all枚举是一个没有去,因为它捣烂这两种类型在一起。它不是单个类别,而是所有可能类别的集合。

在严格的实践水平上,处理任何特殊值都会使实现复杂化,从而增加了出错的可能性,因为您必须在所有位置都进行特殊处理,或者将其拆开以符合其实际含义。哦,那么所有可能的枚举数的显式集合又如何呢?应该在何时何地折叠成特殊all值?它应该完全崩溃吗?

我首选的体系结构是不包含代码中所有时代的任何表示。其中包括all枚举器,但也意味着所有时代的空集合。这是完全相同的特例,只是伪装不同。

如果提供了列表中的两项,请说ALL和Ancient-从应用程序中获取什么?[...]

我会在API规范中指定所有纪元标记必须始终单独显示,因为在其上方添加某些内容是没有意义的。然后我将其视为违反合同。但这是所有时代如何使实现和业务逻辑复杂化的一个很好的例子。

主要问题是您不得不指定在特殊值及其基本含义之间进行转换的规则。然后,您和其他所有使用API​​的人都必须正确实施这些规则。在我的愤世嫉俗的人告诉我,这不是一个问题,如果有人会弄错了,但只是

序列化数据(JSON)

最初,我在这里有一整节内容涉及修改与只读查询以及"eras"列表的不同角色。但最后我删除了它,因为KISS。枚举/收集的规则对于JSON数据而言并非不合理,因此保持事物一致是最简单的解决方案。

最终用户的用户界面

我认为UI和基础数据结构之间总会有数据转换,这很可能是因为您具有某种MVC架构。因此,请使用对用户而言最方便,最直观的方法。@Markus回答中给出了一个很好的例子,说明了一周中各天的不同选择选项。


1

我认为拥有新的ALL比根本没有拥有更多的困惑。

是。

如果您从集合的角度考虑:那么您将拥有整个东西。而且,每当您只想要该集合的一个子集时,当一个元素被视为该子集的元素时,就必须提供某种filtercondition。而ALL为的情况下,在不加过滤器,而不是“的FilterCondition中是ALL

如果输入中未提供任何时代,则认为本书涵盖了所有时代。

我将其颠倒过来:这本书没有涵盖特定时代。

从查询角度来看,这也是一致的:

如果未选择任何时代,则返回所有书籍(包括带有空时代字段的书籍);当选择了某个时代后,仅会返回具有选定时代的图书-检索某个特殊时代的所有图书以及一般图书都将是意外的。

唯一要添加ALL-类别的点是为了方便用户,因为选择“无”而不是“所有”可能更令人讨厌。


0

我最近实现了一个基本的调度程序,正如@LoztInSpace已经提到的,其中大部分是UI糖。

在选择选项之一时,用户界面必须绝对清楚,用户将会实现什么。

就我而言,我可以选择工作日。除了“全部”之外,我还添加了“工作日”和“周末”作为选项。选择每个选项将在以后的使用中将其包括在内,并且您将必须手动取消选择不想包含的任何日期。

从技术上讲,这意味着如果用户选择“工作日”,则用户界面将选中五个复选框,枚举将使用“工作日”值。如果未选中其中一个复选框,则“工作日”值将替换为剩余四天的按位映射。

不要使用选项的一部分来包含所有内容,同时不要使用某些选项来避免冲突并确保UI对用户有意义。

我认为一个很好的定位是,如果用户可以轻松地理解“全部”包括的概念(一周中的所有天),或者如果它不太重要(在亚马逊上搜索所有类别),则使用“全部”选项是有意义的


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。

请记住,枚举的值应始终为其前身的两倍。出于完整性检查的考虑,尽管可以禁用枚举,但永远不要删除它,而是将代码调整为始终将禁用的枚举计为ALL的一部分。

尽管我会改用NONE标志,让客户决定使用NONE时的处理方式,以防以后业务发生变化。

如果采用此实现,则无需传递enun,而应传递所选值的总和。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.