Use cadeia vazia, nula ou remova propriedade vazia na solicitação / resposta da API

25

Ao transferir objetos por meio de uma API, como no formato JSON sem esquema, qual é a maneira ideal de retornar uma propriedade de cadeia inexistente? Eu sei que existem diferentes maneiras de fazer isso, como nos exemplos nos links listados abaixo.

Tenho certeza de que usei null no passado, mas não tenho um bom motivo para fazê-lo. Parece simples usar nulo ao lidar com o banco de dados. Mas o banco de dados parece um detalhe de implementação que não deve preocupar a parte do outro lado da API. Por exemplo, eles provavelmente usam um armazenamento de dados sem esquema que armazena apenas propriedades com valores (não nulos).

Do ponto de vista do código, restringir as funções de string para trabalhar apenas com um tipo, ou seja, string(não nulo), facilita a prova; evitar nulo também é uma razão para ter um Optionobjeto. Portanto, se o código que produz solicitação / resposta não usar nulo, meu palpite é que o código do outro lado da API não será forçado a usar nulo também.

Eu gosto bastante da ideia de usar uma string vazia como uma maneira fácil de evitar o uso de null. Um argumento que ouvi por usar nulo e contra a cadeia vazia é que a cadeia vazia significa que a propriedade existe. Embora eu entenda a diferença, também me pergunto se é apenas o detalhe da implementação e se o uso de cadeias nulas ou vazias faz alguma diferença na vida real. Também me pergunto se uma string vazia é análoga a uma matriz vazia.

Então, qual é a melhor maneira de fazer isso que atenda a essas preocupações? Depende do formato do objeto que está sendo transferido (esquema / esquema)?

imel96
fonte
2
Observe também que o Oracle trata seqüências de caracteres vazias e nulas da mesma maneira. E aí está: em um questionário em papel, como você poderia distinguir entre nenhuma resposta dada e uma resposta que consiste em uma string vazia?
Bernhard Hiller
Se você usa herança, é fácil dizer. if( value === null) { use parent value; } No entanto, se você definir o valor filho, mesmo que seja uma sequência vazia (por exemplo, substitua o valor pai padrão por branco), como você "herdará" novamente o valor? Para mim, defini-lo como nulo significaria "desmarcar esse valor para que possamos usar o valor pai".
Frank Forte
Como "Remover propriedade vazia" também é por que "evitar" a null(é verdade que nullé evitado como tal), o questionador significa "Retornar não nulo" [Objeto] (ou seja: String vazia, Matriz vazia, etc.) quando eles escreva "evitar".
cellepo 7/09

Respostas:

18

TLDR; Remover propriedades nulas

A primeira coisa a ter em mente é que os aplicativos em suas bordas não são orientados a objetos (nem funcionais se estiver programando nesse paradigma). O JSON que você recebe não é um objeto e não deve ser tratado como tal. São apenas dados estruturados que podem (ou não) converter em um objeto. Em geral, nenhum JSON de entrada deve ser confiável como um objeto de negócios até que seja validado como tal. Apenas o fato de ter desserializado não o torna válido. Como o JSON também possui primitivas limitadas em comparação com os idiomas de back-end, geralmente vale a pena criar um DTO alinhado ao JSON para os dados recebidos. Em seguida, use o DTO para construir um objeto de negócios (ou tentativa de erro) para executar a operação da API.

Quando você vê o JSON apenas como um formato de transmissão, faz mais sentido omitir propriedades que não estão definidas. É menos para enviar através do fio. Se o seu idioma de back-end não usar nulos por padrão, você provavelmente poderá configurar seu desserializador para dar um erro. Por exemplo, minha instalação comum para Newtonsoft.Json converte propriedades nulas / ausentes optionsomente para / de tipos F # e, caso contrário, ocorrerá um erro. Isso fornece uma representação natural de quais campos são opcionais (aqueles com optiontipo).

Como sempre, as generalizações apenas levam você até aqui. Provavelmente, há casos em que uma propriedade padrão ou nula se encaixa melhor. Mas a chave é não olhar para estruturas de dados na borda do seu sistema como objetos de negócios. Os objetos de negócios devem ter garantias de negócios (por exemplo, citar pelo menos três caracteres) quando criados com êxito. Mas as estruturas de dados retiradas do fio não têm garantias reais.

Kasey Speakman
fonte
3
Embora a maioria dos serializadores modernos possua campos opcionais, a omissão de nulos na resposta nem sempre é uma boa ideia, pois pode introduzir complexidade adicional para lidar com campos nulos. Assim, é realmente caso-dependente , dependendo de como sua biblioteca de serialização alças nullables, e se ou não o (potencial) complexidade adicional de lidar com esses nullables é realmente vale a pena salvar alguns bytes por pedido. Você deve trabalhar duro para analisar seus casos de negócios.
Chris Cirefice
@ ChrisCirefice Sim, acredito que o último parágrafo cobre isso. Há casos em que será melhor empregar estratégias diferentes.
Kasey Speakman
Concordo que o JSON é usado apenas como formato de transmissão, não está passando objetos através de fios como CORBA. Também concordo que as propriedades podem ser adicionadas e removidas; representações podem mudar, controles podem mudar, especialmente na web.
Imel96
15

Atualização: editei a resposta um pouco, porque isso pode causar confusão.


Indo com uma string vazia é um não definitivo. String vazia ainda é um valor, está apenas vazia. Nenhum valor deve ser indicado usando uma construção que não representa nada null,.

Do ponto de vista do desenvolvedor da API, existem apenas dois tipos de propriedades:

  • necessário (estes DEVEM ter um valor de seu tipo específico e NÃO DEVEM estar vazios),
  • opcional (estes podem conter um valor de seu tipo específico, mas também podem conter null.

Isso deixa bastante claro que quando uma propriedade é obrigatória, ou seja. necessário, nunca pode ser null.

Por outro lado, se uma propriedade opcional de um objeto não for definida e deixada em branco, prefiro mantê-los na resposta de qualquer maneira com o nullvalor Pela minha experiência, torna mais fácil para os clientes da API implementar a análise, pois eles não precisam verificar se uma propriedade realmente existe ou não, porque ela está sempre lá, e eles podem simplesmente converter a resposta em seu DTO personalizado, tratando nullvalores como opcional.

Incluir / remover dinamicamente campos das forças de resposta, incluindo condições adicionais nos clientes.


De qualquer maneira, da maneira que você escolher, mantenha-o consistente e bem documentado. Dessa forma, realmente não importa o que você usa para sua API, desde que o comportamento seja previsível.

Andy
fonte
Sim, cadeia vazia é um valor que eu uso nullpara referências. Misturar valores com referências é uma das minhas preocupações. Como você diferencia os campos opcionais dos campos nullde sequência não opcional que têm nullvalor? Re-analisando, não é o fato de testar a existência de propriedades tornar o analisador mais frágil?
Imel96 13/03/19
2
@ imel96 Os campos não opcionais NUNCA podem ser nulos. Se algo não é opcional, DEVE sempre conter um valor (de seu tipo específico).
Andy
3
Este. Como consumidor frequente de APIs, odeio quando tenho que lidar com estruturas "dinâmicas" voltando para mim, mesmo que seja na forma de um campo opcional sendo omitido. (também concordamos que há uma grande diferença entre um ZLS e um Nulo). Eu aceitaria alegremente valores nulos o dia todo. Como autor da API, um dos meus objetivos é tornar o consumo do cliente o mais simples possível, e isso significa sempre ter estruturas de dados esperadas.
jleach
@DavidPacker, então, se bem entendi, você usa nullpara indicar que o valor é opcional. Portanto, quando você define um objeto que possui uma propriedade de sequência não opcional e o consumidor não possui essa propriedade, ele deve enviar uma sequência vazia para essa propriedade. Isso esta certo?
imel96
2
@GregoryNisbet Não faça isso, por favor. Isso não faz sentido.
Andy
3

null O uso depende do aplicativo / idioma

Por fim, a escolha de usar ou não nullcomo um valor válido de aplicativo é amplamente determinada por seu aplicativo e linguagem de programação / interface / borda.

Em um nível fundamental, recomendo tentar usar tipos distintos se houver classes distintas de valores. nullpode ser uma opção se sua interface permitir e houver apenas duas classes de uma propriedade que você está tentando representar. Omitir uma propriedade pode ser uma opção se sua interface ou formato permitir. Um novo tipo agregado (classe, objeto, tipo de mensagem) pode ser outra opção.

Para o seu exemplo de string, se isso estiver na linguagem de programação, eu me faria algumas perguntas.

  1. Planejo adicionar futuros tipos de valores? Nesse caso, Optionprovavelmente será melhor para o seu design de interface.
  2. Quando preciso validar as ligações dos consumidores? Estatisticamente? Dinamicamente? Antes? Depois de? Em absoluto? Se sua linguagem de programação suportar, use os benefícios da digitação estática, pois evita a quantidade de código que você precisa criar para validação. Optionprovavelmente preenche esse caso melhor se sua sequência não for anulável. No entanto, você provavelmente terá que verificar a entrada do usuário para obter um nullvalor de string de qualquer maneira, portanto, provavelmente eu voltaria para a primeira linha de questionamento: quantos tipos de valores quero / quero representar.
  3. É nullindicativo de um erro do programador na minha linguagem de programação? Infelizmente, nullgeralmente é o valor padrão para referências ou ponteiros não inicializados (ou não explicitamente inicializados) em alguns idiomas. O nullvalor é aceitável como o valor padrão? É seguro como um valor padrão? Às vezes nullé indicativo de valores desalocados. Devo fornecer aos consumidores da minha interface uma indicação desses possíveis problemas de gerenciamento de memória ou inicialização no programa deles? Qual é o modo de falha de uma chamada em face desses problemas? O chamador está no mesmo processo ou thread que o meu para que esses erros sejam um alto risco para o meu aplicativo?

Dependendo das suas respostas a essas perguntas, você provavelmente poderá saber se nullé ou não adequado para sua interface.

Exemplo 1

  1. Sua aplicação é essencial para a segurança
  2. Você está usando algum tipo de inicialização de heap na inicialização e nullé um possível valor de sequência passado após falha ao alocar espaço para uma sequência.
  3. Existe um potencial em que essa string atinge sua interface

Resposta: nullprovavelmente não é apropriado

Justificação: nullneste caso, é realmente usado para indicar dois tipos diferentes de valores. O primeiro pode ser um valor padrão que o usuário da sua interface pode querer definir. Infelizmente, o segundo valor é um sinalizador para indicar que seu sistema não está funcionando corretamente. Nesses casos, você provavelmente deseja falhar o mais seguro possível (o que isso significa para o seu sistema).

Exemplo 2

  1. Você está usando uma estrutura C que possui um char *membro.
  2. Seu sistema não usa alocação de heap e você está usando a verificação MISRA.
  3. Sua interface aceita essa estrutura como um ponteiro e verifica se a estrutura não aponta para NULL
  4. O valor padrão e seguro do char *membro para sua API pode ser indicado por um único valor deNULL
  5. Após a inicialização da estrutura do usuário, você gostaria de fornecer ao usuário a possibilidade de não inicializar explicitamente o char *membro.

Resposta: NULLpode ser apropriado

Fundamentação da petição: Há uma pequena chance de que sua estrutura passe na NULLverificação, mas não foi inicializada. No entanto, sua API pode não ser capaz de explicar isso, a menos que você tenha algum tipo de soma de verificação no valor da estrutura e / ou verificação de intervalo do endereço da estrutura. Os linters MISRA-C podem ajudar os usuários da sua API sinalizando o uso de estruturas antes da inicialização. No entanto, quanto ao char *membro, se o ponteiro para estrutura apontar para uma estrutura inicializada, NULLé o valor padrão de um membro não especificado em um inicializador de estrutura. Portanto, NULLpode servir como um valor padrão seguro para o char *membro struct em seu aplicativo.

Se estiver em uma interface de serialização, eu me perguntaria as seguintes perguntas sobre se deve ou não usar nulo em uma string.

  1. É nullindicativo de um possível erro do lado do cliente? Para JSON em JavaScript, esse provavelmente é um não, pois nullnão é necessariamente usado como uma indicação de falha na alocação. Em JavaScript, é usado como uma indicação explícita da ausência do objeto em uma referência a ser configurada problemática. No entanto, existem analisadores e serializadores não-javascript que mapeiam JSON nullpara o nulltipo nativo . Se for esse o caso, isso implica na discussão sobre se o nulluso nativo é ou não adequado para sua combinação específica de idioma, analisador e serializador.
  2. A ausência explícita de um valor de propriedade afeta mais do que um único valor de propriedade? Às vezes, a nullindica realmente que você tem um novo tipo de mensagem inteiramente. Pode ser mais limpo para seus consumidores do formato de serialização especificar apenas um tipo de mensagem completamente diferente. Isso garante que a validação e a lógica do aplicativo possam ter uma separação limpa entre as duas distinções de mensagens que sua interface da web fornece.

Conselho Geral

nullnão pode ser um valor em uma borda ou interface que não o suporte. Se você estiver usando algo de natureza extremamente flexível na digitação de valores de propriedades (por exemplo, JSON), tente inserir alguma forma de esquema ou validação no software de borda do consumidor (por exemplo, JSON Schema ), se puder. Se for uma API da linguagem de programação, valide a entrada do usuário estaticamente, se possível (por meio de digitação) ou o mais alto possível em tempo de execução (também conhecido como prática de programação defensiva nas interfaces voltadas ao consumidor). O mais importante é documentar ou definir a borda, para que não haja dúvidas sobre:

  • Que tipo de valor uma determinada propriedade aceita
  • Quais faixas de valor são válidas para uma determinada propriedade.
  • Como um tipo agregado deve ser estruturado. Quais propriedades devem / devem / podem estar presentes em um tipo agregado?
  • Se for algum tipo de contêiner, quantos itens podem ou devem conter e quais são os tipos de valores que o contêiner contém?
  • Em que ordem, se houver, são devolvidas propriedades ou instâncias de um tipo de contêiner ou agregados?
  • Que efeitos colaterais existem na definição de valores específicos e quais são os efeitos colaterais da leitura desses valores?
retroespalhado
fonte
1

Aqui minha análise pessoal dessas questões. Não é apoiado por nenhum livro, jornal, estudo ou qualquer outra coisa, apenas minha experiência pessoal.

Cadeias vazias como null

Este é um não-vai para mim. Não misture a semântica da sequência vazia com a não definida. Em muitos casos, eles podem ser perfeitamente intercambiáveis, mas você pode encontrar casos em que não definido e definido mas vazio significam algo diferente.

Um tipo de exemplo estúpido: digamos que exista um atributo que armazene uma chave estrangeira, e esse atributo não esteja definido ou seja null, isso significaria que não há relação definida, enquanto uma cadeia vazia ""pode ser entendida como uma relação definida e o atributo O ID do registro estrangeiro é essa sequência vazia.

Não definido vs null

Este não é um tópico em preto ou branco. Existem prós e contras em ambas as abordagens.

A favor da definição explícita de nullvalores, existem os seguintes profissionais:

  • As mensagens são mais auto-descritivas, pois você conhece todas as chaves apenas olhando para qualquer mensagem
  • Relacionado ao ponto anterior, é mais fácil codificar e detectar erros no consumidor dos dados: é mais fácil detectar erros se você buscar as chaves erradas (talvez erros de ortografia, talvez a API tenha sido alterada etc.).

A favor de assumir que uma chave inexistente é igual à semântica de null:

  • Algumas mudanças são mais fáceis de se adaptar. Por exemplo, se uma nova versão do esquema da mensagem incluir uma nova chave, você poderá codificar o consumidor das informações para trabalhar com essa chave futura, mesmo quando o produtor da mensagem ainda não tiver sido atualizado e ainda não tiver fornecido essas informações.
  • As mensagens podem ser menos detalhadas ou mais curtas

Caso a API seja de alguma forma estável e você a documente completamente, acho perfeitamente correto afirmar que uma chave inexistente é igual ao significado de null. Mas se for mais bagunçado e caótico (como costuma acontecer), acho que você pode evitar dores de cabeça se definir explicitamente todo valor em cada mensagem. Ou seja, em caso de dúvida, tenho a tendência de seguir a abordagem detalhada.

Tudo isso dito, o mais importante: exponha suas intenções claramente e seja consistente. Não faça uma coisa aqui e a outra ali. Software previsível é melhor software.

bgusach
fonte
O exemplo para usar cadeias vazias é o que quero dizer com detalhes de implementação, ou seja, assumindo que a API é usada para expor as linhas do banco de dados. Fará alguma diferença se não houver banco de dados envolvido e apenas para transferir representações de objetos?
imel96
Não precisa ser um detalhe de implementação. Meu exemplo fala sobre PKs relacionadas ao banco de dados, mas o que tentei explicar é que uma string vazia não é nula / nada / null. Outro exemplo: em um jogo, há um objeto de personagem e ele tem um atributo "parceiro". Um nullparceiro significa claramente que não há parceiro, mas ""pode ser entendido como existe um parceiro cujo nome é "".
bgusach
Eu estou bem com a referência de parceiro nulo significa que não há parceiro e também referência não é uma string. Mas o nome do parceiro é uma string, mesmo que você permita nulo como nome do parceiro, você não pegaria esse nulo e o substituiria por um string vazio em algum momento?
imel96
Se não houver parceiro, eu não alteraria a nullstring for vazia. Talvez renderizando-o em um formulário, mas nunca no modelo de dados.
bgusach
Eu não quis dizer nenhum parceiro, parceiro seria um objeto. É do parceiro nameque eu estava falando, você permitiria que o nome do parceiro fosse nulo?
precisa saber é
1

Eu forneceria uma string vazia em uma situação em que uma string esteja presente, e por acaso seja uma string vazia. Eu forneceria null em uma situação em que desejo dizer explicitamente "não, esses dados não estão lá". E omita a chave para dizer "não há dados lá, não se preocupe".

Você julga qual dessas situações pode acontecer. Faz sentido para o seu aplicativo ter cadeias vazias? Deseja distinguir entre dizer explicitamente "sem dados" usando nulo e implicitamente sem ter valor? Você só deve ter as duas possibilidades (nula e sem chave presente) se ambas precisarem ser distinguidas pelo cliente.

Agora, lembre-se de que se trata de transmitir dados. O que o receptor faz com os dados é da sua conta e eles farão o que for mais conveniente para eles. O receptor deve ser capaz de lidar com qualquer coisa que você jogue com ele (possivelmente rejeitando os dados) sem bater.

Se não houver outras considerações, transmitirei o que for mais conveniente para o remetente e o documentarei . Prefiro não enviar valores ausentes, pois isso provavelmente melhora a velocidade da codificação, transmissão e análise do JSON.

gnasher729
fonte
Eu gosto do seu argumento em "se precisar ser distinguido pelo cliente".
imel96
0

Embora eu não possa dizer o que é melhor , quase certamente não é um simples detalhe de implementação , ele altera a estrutura de como você pode interagir com essa variável.

Se algo puder ser nulo, você sempre deve tratá-lo como se fosse nulo em algum momento , para ter sempre dois fluxos de trabalho , um para nulo e outro para uma sequência válida. Um fluxo de trabalho dividido não é necessariamente uma coisa ruim, pois há bastante manipulação de erros e usos de casos especiais que você pode utilizar, mas ofusca seu código.

Se você sempre interagir com a string da mesma maneira , provavelmente será mais fácil para a funcionalidade ficar na sua cabeça .

Assim como em qualquer pergunta "o que é melhor", fico com a resposta: depende . Se você deseja dividir seu fluxo de trabalho e capturar mais explicitamente quando algo não estiver definido, use null. Se você preferir que o programa continue fazendo o mesmo, use uma string vazia. O importante é que você seja consistente , escolha um retorno comum e continue com ele.

Considerando que você está criando uma API, eu recomendaria manter uma string vazia, pois há menos para o usuário compensar, porque, como usuário de uma API, não conhecerei todos os motivos pelos quais sua API poderia me dar um valor nulo, a menos que você ' está muito bem documentado, o que alguns usuários não lêem de qualquer maneira.

Erdrik Ironrose
fonte
Ter "fluxo de trabalho dividido" é ruim. Digamos que do lado do produtor tudo esteja limpo, os métodos do tipo string retornam apenas strings, nunca nulos. Se a API usar nulo, em algum momento o produtor precisará criar isso nullpara estar em conformidade com a API. Então o consumidor precisa lidar nulltambém. Mas acho que entendi o que você está dizendo, apenas decida e defina a API com autoridade, certo? Isso significa que não há nada de errado com nenhum deles?
imel96
Sim, tudo o que você fizer na sua API afetará a maneira como o usuário precisará estruturar o código. Portanto, considerando seu design em termos do usuário da API, você poderá definir qual é o melhor. Em última análise, é a sua API. Apenas seja consistente. Somente você pode decidir os prós e os contras da abordagem.
Erdrik Ironrose
0

Documento!

TL; DR>

Faça como achar melhor - às vezes o contexto em que é usado é importante. Exemplo, Vincular variáveis ​​a um Oracle SQL: String vazia é interpretado como um NULL.

Simplesmente eu diria - Certifique-se de documentar cada cenário mencionado

  • NULO
  • Em branco (vazio)
  • Em falta (removido)

Seu código pode agir de diferentes maneiras - documentar como o código reage a ele:

  • Fail (Exception etc), talvez até falhe na validação (possivelmente uma exceção verificada) vs não pode lidar com a situação corretamente (NullPointerException).
  • Forneça padrões razoáveis
  • O código se comporta de maneira diferente

Além disso, cabe a você se comportar de forma consistente e, possivelmente, adotar algumas de suas próprias práticas recomendadas. Documente esse comportamento consistente. Exemplos:

  • Trate nulo e faltando o mesmo
  • Trate uma string vazia exatamente como tal. Somente no caso de uma ligação SQL, pode ser considerado um espaço em branco. Verifique se o seu SQL se comporta de maneira consistente e esperada.
YoYo
fonte
O problema é que, sem abordar as preocupações, as divergências aconteciam com bastante frequência. Considere no ambiente de equipe que a decisão precisa ser da equipe, muitas vezes significa que haveria uma discussão. Quando você tem várias equipes, todas as equipes têm direito a suas próprias decisões. Vi APIs que só posso supor que são implementadas por equipes diferentes que não concordam entre si. Se alguém pode concordar com uma coisa, a documentação é trivial.
imel96
0

tl; dr - se você o usar: seja consistente com o que significa.

Se você incluísse null, o que isso significaria? Existe um universo de coisas que poderia significar. Um valor simplesmente não é suficiente para representar um valor ausente ou desconhecido (e essas são apenas duas das inúmeras possibilidades: por exemplo, Faltando - foi medido, mas ainda não o sabemos. Desconhecido - não tentamos medir isto.)

Em um exemplo que me deparei recentemente, um campo pode estar vazio porque não foi relatado para proteger a privacidade de alguém, mas era conhecido no lado do remetente, não era do lado do remetente, mas era do repórter original ou desconhecido para ambos. E tudo isso importava para o receptor. Portanto, geralmente um valor não é suficiente.

Com uma suposição de mundo aberto (você simplesmente não sabe sobre as coisas não declaradas), você simplesmente deixaria de fora e poderia ser qualquer coisa. Com a suposição de mundo fechado (as coisas não declaradas são falsas, por exemplo, no SQL), é melhor você esclarecer o que nullsignifica e ser o mais consistente possível com essa definição ...

Grimaldi
fonte