Est-ce une bonne pratique d'avoir une valeur spéciale «TOUS» dans une énumération

11

Je développe un nouveau service dans un environnement de micro-services. Il s'agit d'un service REST. Pour simplifier, disons que le chemin est: / historyBooks

Et la méthode POST pour ce chemin crée un nouveau livre d'histoire.

Supposons qu'un livre d'histoire couvre une ou plusieurs époques de l'histoire.

Par souci de concision, supposons que nous n'avons que les époques suivantes de l'histoire humaine:

  • Ancien
  • Post Classique
  • Moderne

Dans mon code, je voudrais les représenter dans un enum.

Le corps de la méthode (la charge utile) est au format JSON et doit inclure un nom de champ eras. Ce champ est une liste de eravaleurs que ce livre couvre.

Le corps peut ressembler à:

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

Dans ce service spécifique, la logique métier est la suivante:
si aucune ère n'est fournie dans l'entrée, ce livre est considéré comme couvrant toutes les époques.

Lors de l'examen de l'API, une suggestion a été faite:
inclure une autre valeur, ALLpour l'énumération des époques, pour indiquer explicitement que toutes les époques sont couvertes.

Je pense qu'il a des avantages et des inconvénients.

Avantages:

Entrée explicite

Les inconvénients:

Si deux éléments de la liste sont fournis, dites ALLet Ancient- que retiendra-t-on de la demande? Je suppose que cela ALLdevrait remplacer les autres valeurs, mais c'est une nouvelle logique métier.

Si je lance une requête, pour les livres qui couvrent des époques spécifiques, comment représenter un livre qui couvre toutes les époques? Si ALLest également utilisé pour la sortie (en utilisant la même logique), il est de la responsabilité du consommateur d'interpréter ALLcomme ["Ancient", "Post Classical", "Modern"].

Ma question

Je pense qu'avoir le nouveau ALLcause plus de confusion que ne pas l'avoir du tout.

Qu'est-ce que tu penses? Souhaitez-vous ajouter cette ALLvaleur ou conserver votre API sans elle?

Ron Klein
la source
7
Que se passe-t-il si vous décidez d'ajouter l'ère atomique? Est-ce que les livres qui couvrent "toutes" les époques ont comme par magie un nouveau contenu? Commencez-vous à mentir sur le contenu des livres? Allez-vous tout mettre à jour? Cela pourrait ne pas être trivial si certains livres ont en fait déjà du contenu sur l'ère atomique.
8bittree

Réponses:

13

Dépend si vos époques disponibles sont disponibles pour l'application appelante. Vraisemblablement, ils le sont afin que l'utilisateur puisse sélectionner ce qui l'intéresse. Si tel est le cas, c'est un problème frontal de fournir une option "TOUS". Si c'était moi, cela lui ferait envoyer une liste de toutes les époques plutôt qu'une option "tout". Sinon, vous avez besoin d'une option "ALL" et courez le risque que le front-end récupère les choses d'une époque qu'il ne comprendra pas.

Comme vous le faites remarquer, TOUT avec d'autres options signifie que vous pouvez obtenir des demandes conflictuelles. Une autre considération est que vous pouvez passer "TOUS" puis toutes les autres énumérations deviennent des exclusions plutôt que des inclusions, par exemple "TOUS", "Anciens" signifie "Tout sauf les Anciens". Cela a un certain sens, mais il est évident que l'interface utilisateur doit refléter ce qui peut être trop complexe.

TL; DR; La plupart du temps, "TOUS" est une interface agréable et vous pouvez obtenir la même chose au niveau du service sans ambiguïté, alors ne le faites pas.

LoztInSpace
la source
6

Si aucune époque n'est indiquée dans l'entrée, ce livre est considéré comme couvrant toutes les époques.

Ceci, et ayant également un cas spécial 'ALL', sont mauvais à mon humble avis - vos API doivent être explicites dans la mesure du possible. Le fait d'avoir des cas spéciaux comme `` rien ne signifie vraiment tout '' signifie que toute personne qui consomme votre API doit également connaître tous vos cas spéciaux ou, comme dans le cas `` TOUS '', conduire à des demandes dont l'intention et / ou le résultat sont ambigus.

Des cas particuliers conduisent également souvent à un code plus complexe à la fois en termes de validation de la validité ou non de l'entrée utilisateur et de traitement correct de la demande.

GoatInTheMachine
la source
1

Pour les variables catégorielles, j'éviterais de mettre un caractère générique "tous" au même niveau.

Il semble que vous ayez un critère de recherche à deux niveaux pour la période:

  1. booléen, is_selective?
  2. ensemble, disjonction de catégories spécifiques

L'appelant peut spécifier (faux, ignoré) ou (vrai, {'ancien', 'moderne'}).

Ou vous pouvez choisir d'interpréter l'ensemble vide comme le caractère générique, cela signifie Non sélectif.

Pour votre cas spécifique, il semble que vous ayez une variable continue, l'année, qui est discrétisée en quelques valeurs bien connues, et ce que vous voulez vraiment, c'est faire de l'arithmétique d'intervalle. Vos requêtes accepteraient donc une plage d'années (début, fin) et les énumérations pourraient commodément fournir de tels attributs.

J_H
la source
1

tl; dr
Pour votre question réelle - concernant les structures de données locales dans la mise en œuvre - je suis d'accord avec votre conclusion: évitez une valeur spéciale de toutes les époques , car elle ajoute surtout de la complexité et des possibilités de faire des erreurs. Cependant, en particulier l'interface utilisateur utilisateur final et peut-être les données de charge utile sérialisées peuvent être des histoires différentes.

Structures de données locales

Regardons cela du point de vue du système de type. Fondamentalement, une énumération est un type qui définit un ensemble disjonctif de catégories spécifiques (énumérateurs), comme cela a déjà été souligné dans la réponse de @JH . Une variable de type enum contient exactement l'une de ces catégories. Si vous souhaitez représenter une collection de valeurs d'énumérateur, vous avez besoin d'un deuxième type:

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

Un allénumérateur est un no-go car il écrase ces deux types ensemble. Ce n'est pas une seule catégorie, mais une collection de toutes les catégories possibles.

À un niveau strictement pratique, tout type de valeur spéciale complique la mise en œuvre - et augmente ainsi la probabilité d'erreurs - parce que vous devez le caser partout partout ou le déballer pour qu'il corresponde à sa signification réelle. Oh, et que dire d'une collection explicite de tous les énumérateurs possibles. Quand et où cela devrait-il être réduit à la allvaleur spéciale ? Doit-il s'effondrer du tout?

Mon architecture préférée est de ne pas avoir de représentation de toutes les époques dans le code. Cela inclut l' allénumérateur, mais aussi une collection vide signifiant toutes les époques . C'est exactement le même cas spécial, juste sous un déguisement différent.

Si deux éléments de la liste sont fournis, dites TOUS et Anciens - que retiendra-t-on de l'application? [...]

Je préciserais dans la spécification API que le marqueur toutes les époques doit toujours apparaître seul, car avoir quelque chose au-dessus n'a pas de sens. Ensuite, je rejetterais cela comme une violation de contrat. Mais c'est un bel exemple de la façon dont toutes les époques compliquent à la fois la mise en œuvre et la logique métier.

Le problème principal est que vous êtes obligé de spécifier des règles de conversion entre la valeur spéciale et sa signification sous-jacente. Et puis vous et tous les autres utilisateurs de l'API devez implémenter ces règles correctement. Le cynique en moi me dit qu'il ne s'agit pas de savoir si quelqu'un va se tromper, mais simplement quand .

Données sérialisées (JSON)

À l'origine, j'avais une section entière ici sur la modification des requêtes en lecture seule et les différents rôles de la "eras"liste. Mais à la fin, je l'ai supprimé parce que KISS. Les règles d'énumération / collecte ne sont pas déraisonnables pour les données JSON, donc garder les choses cohérentes est la solution la plus simple.

UI pour l'utilisateur final

Je suppose qu'il y aura de toute façon une transformation des données entre l'interface utilisateur et les structures de données sous-jacentes, probablement parce que vous avez une sorte d'architecture MVC-ish. Alors, utilisez ce qui est le plus pratique et intuitif pour l'utilisateur. Dans sa réponse, @Markus a donné un excellent exemple avec les différentes options de sélection pour les jours de la semaine.

besc
la source
1

Je pense qu'avoir le nouveau ALL cause plus de confusion que de ne pas l'avoir du tout.

Oui.

Si vous pensez du point de vue de la collection: vous avez toute une collection de quelque chose. Et chaque fois que vous ne voulez qu'un sous-ensemble de cette collection, vous devez fournir une sorte de condition de filtre , lorsqu'un élément est considéré comme un élément de ce sous-ensemble. Et ALLc'est le cas, quand aucun filtre n'est appliqué, pas "la condition du filtre est ALL" .

Si aucune époque n'est indiquée dans l'entrée, ce livre est considéré comme couvrant toutes les époques.

Je voudrais le renverser: ce livre ne couvre aucune époque spécifique .

Cela est également cohérent du point de vue des requêtes:

Si aucune ère n'est sélectionnée, tous les livres sont retournés (y compris avec un champ d'ère vide); et quand une ère est sélectionnée, seuls les livres avec l'ère sélectionnée sont retournés - récupérer tous les livres d'une ère spéciale plus les livres généraux serait en quelque sorte inattendu.

Le seul point où j'ajouterais la ALLcatégorie - est pour la commodité de l'utilisateur, car il peut être plus irritant de sélectionner "Rien" au lieu de "Tout".

Thomas Junk
la source
0

J'ai récemment implémenté un planificateur de base et comme @LoztInSpace l'a déjà mentionné, la majeure partie est du sucre d'interface utilisateur.

L'interface utilisateur doit être absolument claire quant à ce que l'utilisateur réalisera lors de la sélection de l'une des options.

Dans mon cas, je dois sélectionner en semaine. En plus de "Tous", j'ai ajouté "Jours ouvrables" et "Week-end" comme options. La sélection de chaque option l'inclura dans une utilisation ultérieure et vous devrez désélectionner manuellement les jours que vous ne souhaitez pas inclure.

Techniquement, cela signifie que si l'utilisateur sélectionne "Jours de semaine", l'interface utilisateur aura cinq cases cochées et l'énumération utilisera la valeur "Jours ouvrables". Si l'une des cases à cocher n'est pas cochée, la valeur "Jours ouvrables" est remplacée par une mappe de bits or-ed des quatre jours restants.

N'utilisez pas une partie des options pour tout inclure et en même temps pour exclure quelque chose afin d'éviter les conflits et assurez-vous que l'interface utilisateur a du sens pour l'utilisateur.

Je pense qu'une bonne orientation est que l'utilisation de l'option "Tout" a du sens si l'utilisateur peut facilement comprendre le concept de ce que "Tout" comprend (tous les jours de la semaine) ou s'il est plutôt sans importance (recherchez toutes les catégories sur Amazon)

Markus
la source
0

J'aime l'idée de porter explicitement une énumération ALL, mais je préfère les définir comme indicateurs

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

En utilisant une bibliothèque comme flagon ou en jouant manuellement avec des opérations au niveau du bit lorsque toutes les valeurs sont sélectionnées, vous utilisez plutôt ALL.

Gardez à l'esprit que les valeurs de l'énumération doivent toujours être le double de leur prédécesseur. Et que pour la vérification de l'intégrité, vous ne devez jamais supprimer une énumération, bien que vous puissiez la désactiver, en ajustant votre code pour toujours compter les désactivées dans le cadre de TOUS.

Bien que j'aurais un indicateur NONE à la place et que le client décide quoi faire lorsque NONE est utilisé, au cas où l'entreprise changerait plus tard.

Si cette implémentation est prise, au lieu de passer les enuns, vous passeriez la somme des valeurs sélectionnées à la place.

MVCDS
la source