É uma boa prática ter um valor especial "ALL" em uma enumeração

11

Estou desenvolvendo um novo serviço em um ambiente de microsserviços. Este é um serviço REST. Para simplificar, digamos que o caminho seja: / historyBooks

E o método POST para esse caminho cria um novo livro de história.

Vamos supor que um livro de história cubra uma ou mais épocas da história.

Por uma questão de brevidade, vamos supor que temos apenas as seguintes épocas da história humana:

  • Antigo
  • Pós Clássico
  • Moderno

No meu código, eu gostaria de representá-los em um enum.

O corpo do método (a carga útil) está no formato JSON e deve incluir um nome de campo eras. Este campo é uma lista de eravalores que este livro aborda.

O corpo pode parecer com:

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

Nesse serviço específico, a lógica de negócios é:
Se nenhuma Eras foi fornecida na entrada, este livro é considerado para cobrir todas as Eras.

Na revisão da API, foi feita uma sugestão:
inclua outro valor ALL, para a enum das eras, para indicar explicitamente que todas as eras estão cobertas.

Eu acho que tem alguns prós e contras.

Prós:

Entrada explícita

Contras:

Se dois itens da lista forem fornecidos, diga ALLe Ancient- o que será retirado do aplicativo? Eu acho que isso ALLdeve substituir os outros valores, mas essa é uma nova lógica de negócios.

Se eu executar uma consulta, para livros que cobrem épocas específicas, como eu representaria um livro que cubra todas as épocas? Se ALLtambém for usado para a saída (usando a mesma lógica), é responsabilidade do consumidor interpretar ALLcomo ["Ancient", "Post Classical", "Modern"].

Minha pergunta

Eu acho que ter o novo ALLcausa mais confusão do que não tê-lo.

O que você acha? Você adicionaria esse ALLvalor ou manteria sua API sem ele?

Ron Klein
fonte
7
O que acontece se você decidir adicionar a era atômica? Os livros que cobrem as eras "todas" recebem magicamente novos conteúdos? Você começa a mentir sobre o conteúdo dos livros? Você passa por e atualiza tudo? Isso pode não ser trivial se alguns livros já tiverem conteúdo sobre a era atômica.
precisa saber é o seguinte

Respostas:

13

Depende se as suas Eras disponíveis estão disponíveis para o aplicativo de chamada. Presumivelmente, eles são para que o usuário possa selecionar em que está interessado. Se for esse o caso, é um problema de front-end fornecer uma opção "TODOS". Se fosse eu, enviaria uma lista de todas as épocas, em vez de uma opção "todas". Caso contrário, você precisará de uma opção "ALL" (ALL) e correrá o risco de que o front end recupere as coisas de uma época que não entenderá.

Como você aponta, ALL com outras opções significa que você pode obter solicitações conflitantes. Uma consideração adicional é que você pode aprovar "ALL" e, em seguida, quaisquer outras enumerações se tornam exclusões em vez de inclusões, por exemplo, "ALL", "Ancient" significa "Tudo, exceto os Antigos". Isso faz algum tipo de sentido, mas obviamente a interface do usuário deve refletir o que pode ser muito complexo.

TL; DR; Principalmente "ALL" é uma interface de usuário atraente e você pode obter a mesma coisa no nível de serviço sem ambiguidade, por isso não faça isso.

LoztInSpace
fonte
6

Se nenhuma Eras foi fornecida na entrada, este livro é considerado para cobrir todas as Eras.

Isso e também ter um caso especial de 'ALL' é um IMHO ruim - suas APIs devem ser explícitas sempre que possível. Ter casos especiais como 'nada significa realmente tudo' significa que qualquer pessoa que consome sua API também precisa conhecer todos os seus casos especiais ou, como no caso 'ALL', levar a solicitações em que a intenção e / ou resultado são ambíguos.

Casos especiais também costumam levar a códigos mais complexos, tanto em termos de validação se a entrada do usuário é válida ou não, quanto em lidar com a solicitação corretamente.

GoatInTheMachine
fonte
1

Para variáveis ​​categóricas, eu evitaria colocar um curinga "todos" no mesmo nível.

Parece que você tem um critério de pesquisa em dois níveis para o período:

  1. booleano, is_selective?
  2. conjunto, disjunção de categorias específicas

O chamador pode especificar (falso, ignorado) ou (verdadeiro, {'antigo', 'moderno'}).

Ou você pode optar por interpretar o conjunto vazio como o curinga, que indica Não seletivo.

Para o seu caso específico, parece que você tem uma variável contínua, o ano, discretizada para alguns valores conhecidos, e o que você realmente deseja é fazer aritmética de intervalo. Portanto, suas consultas aceitariam um intervalo de anos (início, fim) e as enumerações poderiam fornecer esses atributos de maneira conveniente.

J_H
fonte
1

tl; dr
Para sua pergunta real - referente às estruturas de dados locais na implementação - eu concordo com a sua conclusão: Evite um valor especial de todas as épocas, pois ele adiciona complexidade e oportunidades para cometer erros. No entanto, especialmente a interface do usuário do usuário final e talvez os dados de carga útil serializados possam ser histórias diferentes.

Estruturas de dados locais

Vejamos isso de uma perspectiva do sistema de tipos. Basicamente, uma enum é um tipo que define um conjunto disjuntivo de categorias específicas (enumeradores), como já apontado na resposta por @JH . Uma variável do tipo enum contém exatamente uma dessas categorias. Se você deseja representar uma coleção de valores do enumerador, precisará de um segundo tipo:

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

Um allenumerador é proibido porque combina esses dois tipos. Não é uma categoria única, mas uma coleção de todas as categorias possíveis.

Em um nível estritamente prático, lidar com qualquer tipo de valor especial complica a implementação - e, portanto, aumenta a probabilidade de erros - porque você precisa especificá-lo em qualquer lugar ou descompactá-lo para corresponder ao seu significado real. Ah, e que tal uma coleção explícita de todos os enumeradores possíveis. Quando e onde isso deve ser reduzido ao allvalor especial ? Deveria ser colapsado?

Minha arquitetura preferida é não ter nenhuma representação de todas as épocas no código. Isso inclui o allenumerador, mas também uma coleção vazia que significa todas as eras . É exatamente o mesmo caso especial, apenas com um disfarce diferente.

Se dois itens da lista forem fornecidos, diga ALL e Ancient - o que será retirado do aplicativo? [...]

Especificaria na especificação da API que o marcador todas as eras sempre deve aparecer por si só, porque ter algo em cima disso não faz sentido. Então eu rejeitaria isso como uma violação do contrato. Mas é um bom exemplo de como todas as épocas complicam a implementação e a lógica de negócios.

O principal problema é que você é forçado a especificar regras para a conversão entre o valor especial e seu significado subjacente. E então você e todos os outros usuários da API precisam implementar essas regras corretamente. O cínico em mim me diz que não é uma questão de saber se alguém vai entender errado, mas simplesmente quando .

Dados serializados (JSON)

Originalmente, eu tinha uma seção inteira aqui sobre modificação de consultas somente leitura e funções diferentes para a "eras"lista. Mas no final eu apaguei porque o KISS. As regras para enum / coleção não são irracionais para os dados JSON, portanto, manter as coisas consistentes é a solução mais simples.

UI para o usuário final

Suponho que haverá uma transformação de dados de qualquer maneira entre a interface do usuário e as estruturas de dados subjacentes, provavelmente porque você tem algum tipo de arquitetura MVC-ish. Portanto, use o que for mais conveniente e intuitivo para o usuário. Em sua resposta, @Markus deu um ótimo exemplo com as diferentes opções de seleção para os dias da semana.

besc
fonte
1

Eu acho que ter o novo ALL causa mais confusão do que não tê-lo.

Sim.

Se você pensa de uma perspectiva de coleção: você tem uma coleção inteira de algo. E toda vez que você deseja apenas um subconjunto dessa coleção, é necessário fornecer algum tipo de condição de filtro , quando um elemento é considerado um elemento desse subconjunto. E ALLé o caso, quando nenhum filtro é aplicado, não "a condição do filtro é ALL" .

Se nenhuma Eras foi fornecida na entrada, este livro é considerado para cobrir todas as Eras.

Eu viraria de cabeça para baixo: este livro não cobre nenhuma época específica .

Isso também é consistente da perspectiva da consulta:

Se nenhuma era for selecionada, todos os livros serão retornados (incluindo este com um campo de era vazio); e quando uma era é selecionada, apenas os livros com a era selecionada são retornados - recuperar todos os livros de uma era especial mais geral seria inesperado.

O único ponto em que eu acrescentaria a ALLcategoria - é para a conveniência do usuário, porque pode ser mais irritante ter "Nada" selecionado em vez de "Tudo".

Thomas Junk
fonte
0

Eu recentemente implementei um agendador básico e, como o @LoztInSpace já mencionou, a maior parte é o açúcar da interface do usuário.

A interface do usuário precisa ser absolutamente clara quanto ao que o usuário alcançará ao selecionar uma das opções.

No meu caso, tenho dias da semana para selecionar. Além de "Todos", adicionei "Dias úteis" e "Fim de semana" como opções. A seleção de cada opção a incluirá em um uso posterior e você precisará desmarcar manualmente todos os dias que não desejar.

Tecnicamente, isso significa que, se o usuário selecionar "Dias da semana", a interface do usuário terá cinco caixas de seleção marcadas e a enum usará o valor "Dias úteis". Se uma das caixas de seleção estiver desmarcada, o valor "Dias úteis" será substituído por um mapa de bits dos quatro dias restantes.

Não use uma parte das opções para incluir tudo e ao mesmo tempo excluir algo para evitar conflitos e verifique se a interface do usuário faz sentido para o usuário.

Eu acho que uma boa orientação é que o uso da opção "Tudo" faz sentido se o usuário puder entender facilmente o conceito que "Tudo" inclui (todos os dias da semana) ou se não é importante (pesquise todas as categorias na amazon)

Markus
fonte
0

Eu gosto da ideia de carregar explicitamente um ALL enum, mas prefiro defini-los como sinalizadores

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

Usando uma biblioteca como flagon ou tocando manualmente com operações bit a bit quando todos os valores são selecionados, você usa ALL.

Lembre-se de que os valores da enum devem sempre ser o dobro do seu antecessor. E que, para verificação de integridade, você nunca deve remover uma enumeração, embora possa desativá-la, ajustando seu código para sempre contar as desativadas como parte de TODAS.

Embora eu tenha um sinalizador NONE e deixe o cliente decidir o que fazer quando NONE for usado, caso a empresa mude mais tarde.

Se essa implementação for adotada, em vez de passar os valores, você passaria a soma dos valores selecionados.

MVCDS
fonte