Devo lançar uma exceção no caso de um valor significativo fora do intervalo ou lidar com isso sozinho?

42

Eu escrevi uma estrutura que representa coordenadas de latitude / longitude. Seus valores variam de -180 a 180 para longitudes e 90 a -90 para latitudes.

Se um usuário dessa estrutura me fornecer um valor fora desse intervalo, tenho 2 opções:

  1. Lançar uma exceção (argumento fora do intervalo)
  2. Converta o valor na restrição

Como uma coordenada de -185 tem significado (pode ser facilmente convertida em +175 por serem coordenadas polares), eu poderia aceitá-la e convertê-la.

É melhor lançar uma exceção para informar ao usuário que seu código me deu um valor que não deveria ter?

Edit: Também sei a diferença entre lat / lng e coordenadas, mas queria simplificar isso para facilitar a discussão - não era a mais brilhante das idéias

K. Gkinis
fonte
13
O usuário deve ter permissão para inserir um valor fora do intervalo? Se a resposta for não, lance uma exceção. Se as regras não forem tão rígidas, faça a conversão, mas declare explicitamente na documentação que a conversão pode ocorrer. Lembre-se também de que em alguns idiomas o tratamento de exceções é bastante caro.
Andy
C #, isso importa? Não suporta nativamente restrições se é isso que você quer dizer.
K. Gkinis
2
Parece-me bastante claro, então, a abordagem correta é permitir que o usuário insira qualquer coisa fora do intervalo e lançar uma exceção quando o fizerem (se o padrão disser que o valor abs não deve estar acima de 180, colocando um valor maior do que esse uma violação clara). A propósito, o C # é realmente um dos idiomas em que as exceções são bastante caras, portanto, use-as realmente apenas em situações excepcionais, o que significa que não capturá-lo interromperá o aplicativo, situações como esta.
Andy
2
Costumo ficar longe de fazer suposições sobre o que o usuário 'quis dizer' ao passar valores de parâmetros específicos, especialmente aqueles que meu código não atende. Parece um caso semelhante.
18416 JJM
2
As coordenadas do Web Mercator não são de -180 a 180 e -90 a 90. Isso é latitude / longitude (e existem até vários sistemas de coordenadas para isso). As projeções de Mercator estão tipicamente na casa das centenas de milhares ou milhões e têm unidades de "metros" (nem mesmo estritamente que, já que o comprimento de cada unidade cobre o aumento da distância real do solo à medida que você se aproxima dos polos), não em graus. Mesmo em termos de graus, é restrito a ± 85,051129 graus porque a projeção se torna infinitamente ampla nos pólos. (Eu enviei uma edição corrigir isso, uma vez que não é o cerne da questão.)
jpmc26

Respostas:

51

Se o núcleo da sua pergunta é este ...

Se algum código de cliente passar um argumento cujo valor é inválido para a coisa que minha estrutura de dados está modelando, devo rejeitar o valor ou convertê-lo em algo sensato?

... então minha resposta geral seria "rejeitar", porque isso ajudará a chamar a atenção para possíveis erros no código do cliente que realmente estão fazendo com que o valor inválido apareça no programa e chegue ao seu construtor. Chamar a atenção para os bugs geralmente é uma propriedade desejada na maioria dos sistemas, pelo menos durante o desenvolvimento (a menos que seja uma propriedade desejada do seu sistema atrapalhar em caso de erros).

A questão é se você está realmente enfrentando esse caso .

  • Se sua estrutura de dados se destina a modelar coordenadas polares em geral, aceite o valor porque os ângulos fora do intervalo -180 e +180 não são realmente inválidos. Eles são perfeitamente válidos e sempre têm um equivalente no intervalo de -180 e +180 (e se você deseja convertê-los para atingir esse intervalo, fique à vontade - o código do cliente geralmente não precisa se importar) .

  • Se sua estrutura de dados estiver modelando explicitamente as coordenadas do Web Mercator (de acordo com a pergunta em sua forma inicial), é melhor seguir as disposições mencionadas na especificação (que eu não sei, por isso não vou falar nada sobre isso) . Se a especificação da coisa que você está modelando disser que alguns valores são inválidos, rejeite-os. Se diz que eles podem ser interpretados como algo sensível (e, portanto, são realmente válidos), aceite-os.

O mecanismo usado para sinalizar se os valores foram aceitos ou não depende dos recursos do seu idioma, de sua filosofia geral e de seus requisitos de desempenho. Portanto, você pode lançar uma exceção (no construtor) ou retornar uma versão anulável de sua estrutura (por meio de um método estático que chama um construtor privado) ou retornar um booleano e passar sua estrutura ao chamador como outparâmetro (novamente por meio de um método estático que chama um construtor privado) e assim por diante.

Theodoros Chatzigiannakis
fonte
12
Uma longitude fora do intervalo de -180 a +180 provavelmente deve ser considerada aceitável, mas a latitude fora do intervalo de -90 a +90 parece sem sentido. Quando se alcança o Pólo Norte ou Sul, outras viagens para o norte ou para o sul ficam indefinidas.
Supercat
4
@ supercat Eu costumo concordar com você, mas como não sei quais valores são realmente inválidos na especificação do Web Mercator, estou tentando não tirar conclusões concretas. O OP conhece o domínio do problema melhor do que eu.
Theodoros Chatzigiannakis
1
@ supercat: começa onde o meridiano atinge o equador. viajar ao longo do meridiano de acordo com a latitude. Se a latitude for> 90, continue seguindo o mesmo grande círculo. Sem problemas. Documente e deixe o resto para o cliente.
Kevin Cline
4
@ supercat É pior que isso. Na projeção Web Mercator, ± 90 é realmente projetado para uma largura infinita. Portanto, o padrão realmente o interrompe em ± 85.051129. Além disso, lat / long! = Coordenadas do web mercator. As coordenadas de Mercator usam uma variação de metros, não graus. (À medida que você se aproxima dos pólos, cada "medidor" corresponde a um pedaço de terra cada vez maior.) As coordenadas dos OPs são puramente latinas / longas. O Web Mercator não tem nada a ver com eles, a não ser que eles estejam sendo exibidos no topo de um mapa base do Web Mercator e alguma biblioteca espacial esteja sendo projetada para eles.
Jpmc26
1
Eu deveria dizer "o equivalente a ± 85,051129", já que essa não é a coordenada real.
Jpmc26
10

Isso depende muito. Mas você deve decidir fazer algo e documentá-lo .

A única coisa definitivamente errada para o seu código é esquecer de considerar que a entrada do usuário pode estar fora do intervalo esperado e escrever um código que acidentalmente tenha algum comportamento. Porque então algumas pessoas fazem uma suposição incorreta sobre como seu código se comporta e isso causa bugs, enquanto outras acabam dependendo do comportamento que seu código acidentalmente tem (mesmo que esse comportamento seja completamente maluco) e, portanto, você causa mais bugs depois, quando você resolver o problema.

Nesse caso, posso ver argumentos de qualquer maneira. Se alguém viaja +10 graus a partir de 175 graus, deve terminar em -175. Se você sempre normaliza a entrada do usuário e trata 185 como equivalente a -175, o código do cliente não pode fazer a coisa errada quando adiciona 10 graus; sempre tem o efeito certo. Se você tratar 185 como um erro, forçar todos os casos em que o código do cliente estiver adicionando graus relativos para colocar na lógica de normalização (ou pelo menos lembre-se de chamar seu procedimento de normalização), você realmente causaráerros (embora esperemos que fáceis de capturar aqueles que serão rapidamente esmagados). Porém, se um número de longitude for inserido pelo usuário, gravado literalmente no programa ou calculado através de algum procedimento destinado a estar sempre em [-180, 180), é muito provável que um valor fora desse intervalo indique um erro, de modo "útil" "convertê-lo pode esconder problemas.

Meu ideal nesse caso provavelmente seria definir um tipo que represente o domínio correto. Use um tipo abstrato (não deixe o código do cliente simplesmente acessar os números brutos dentro dele) e forneça uma fábrica de normalização e validação (para que o cliente possa fazer a troca). Mas seja qual for o valor desse tipo, 185 deve ser indistinguível de -175 quando visto por meio de sua API pública (não importa se eles são convertidos em construção ou se você fornece igualdade, acessadores e outras operações que ignoram a diferença de alguma forma) .

Ben
fonte
3

Se realmente não importa para você escolher uma solução, você pode deixar o usuário decidir.

Como sua estrutura é um objeto de valor somente leitura e criada por um método / construtor, você pode fornecer duas sobrecargas com base nas opções que o usuário possui:

  • Lançar uma exceção (argumento fora do intervalo)
  • Converta o valor na restrição

Além disso, nunca permita que o usuário tenha uma estrutura inválida para passar para seus outros métodos, corrija a criação.

Editar: com base nos comentários, suponho que você esteja usando c #.

Filipe Borges
fonte
Obrigado pela contribuição! Pensarei nisso, embora eu tenha medo de que isso minaria o objetivo da exceção que lança o construtor, se alguém pudesse evitá-lo. É interessante!
21716 KK Gkinis
Se você usar essa ideia, seria melhor definir uma interface e ter duas implementações que lançam ou convertem. Seria difícil sobrecarregar um construtor de maneira sensata para trabalhar como você descreveu.
2
@ Snowman: Sim, seria difícil sobrecarregar um construtor com os mesmos tipos de argumento, mas não seria difícil ter dois métodos estáticos e um construtor privado.
Wchargin
1
@ K.Gkinis: O objetivo do construtor de lançamento de exceção não é "garantir que o aplicativo morra" - afinal, o cliente sempre pode ter catchsuas exceções. Como outros já disseram, é permitir que o cliente se restrinja, se assim o desejar. Você não está realmente contornando nada.
wchargin
Essa sugestão complica o código sem nenhum benefício. O método deve converter entrada inválida ou não. Ter duas sobrecargas torna o código mais complexo sem resolver o dilema.
JacquesB
0

Depende se a entrada é diretamente de um usuário por meio de alguma interface do usuário ou do sistema.

Entrada através de uma interface do usuário

É uma questão de experiência do usuário como lidar com entradas inválidas. Não conheço o seu caso específico, mas em geral existem algumas opções:

  • Alerte o usuário sobre o erro e faça com que ele seja corrigido antes de continuar (Mais comum)
  • Converter automaticamente no intervalo válido (se possível), mas alerte o usuário sobre a alteração e permita que ele verifique antes de prosseguir.
  • Converta silenciosamente no intervalo válido e continue.

A escolha depende das expectativas dos usuários e da importância dos dados. Por exemplo, o Google corrige automaticamente a ortografia nas consultas, mas isso é de baixo risco, porque uma alteração inútil não é um problema e é fácil de corrigir (e mesmo assim fica claro na página de resultados que a consulta foi alterada). Por outro lado, se você estiver digitando coordenadas para um míssil nuclear, poderá querer uma validação de entrada mais rígida e nenhuma correção silenciosa de dados inválidos. Portanto, não há resposta universal.

Mais importante, você deve considerar se a correção de entrada tem um benefício para o usuário. Por que um usuário insere dados inválidos? É fácil ver como alguém pode cometer um erro de ortografia, mas por que alguém digitaria uma longitude de -185? Se o usuário realmente quisesse +175, provavelmente teria digitado +175. Eu acho que é mais provável que uma longitude inválida simplesmente seja um erro de digitação, e o usuário quis dizer -85 ou outra coisa. Nesse caso, a conversão silenciosa é ruim e inútil . A abordagem mais amigável para o seu aplicativo provavelmente seria alertar o usuário sobre o valor inválido e fazer com que o usuário corrija por conta própria.

Entrada através de uma API

Se a entrada for de outro sistema ou subsistema, não há dúvida. Você deve lançar uma exceção. Você nunca deve converter entradas inválidas de outro sistema em silêncio, pois isso pode mascarar erros em outras partes do sistema. Se a entrada for "corrigida", ela deverá ocorrer na camada da interface do usuário, não mais profundamente no sistema.

JacquesB
fonte
0

Você deve lançar uma exceção.

No exemplo que você deu, enviando 185 e convertendo para -175, pode ser útil, em alguns casos, fornecer essa funcionalidade. Mas e se o chamador enviar 1 milhão? Eles realmente querem converter isso? Parece mais provável que seja um erro. Portanto, se você precisar lançar uma exceção para 1.000.000, mas não para 185, precisará tomar uma decisão sobre um limite arbitrário para lançar uma exceção. Esse limite vai atrapalhá-lo em algum momento, já que algum aplicativo de chamada está enviando valores ao redor do limite.

Melhor lançar a exceção para valores fora do intervalo.

brendan
fonte
-1

A opção mais conveniente para um desenvolvedor seria um suporte a erros de tempo de compilação na plataforma, para valores fora do intervalo. Nesse caso, o intervalo também deve fazer parte da assinatura do método, assim como o tipo dos parâmetros. Da mesma forma que o usuário da API não pode passar uma String se a assinatura do seu método estiver definida para receber um número inteiro , o usuário não deveria poder passar um valor sem verificar se o valor está dentro do intervalo fornecido na assinatura do método. Se não estiver marcado, ele deve receber um erro de tempo de compilação e, portanto, o erro de tempo de execução pode ser evitado.

Porém, atualmente, muito poucos compiladores / plataformas suportam esse tipo de verificação do tempo de compilação. Então, é uma dor de cabeça para os desenvolvedores. Mas, idealmente, seu método deve lançar uma exceção significativa para valores não suportados e documentá-lo claramente.

BTW, eu realmente amo o modelo de erro proposto por Joe Duffy aqui .

Gulshan
fonte
Como você forneceria erros de tempo de compilação para a entrada do usuário?
JacquesB
@JacquesB O compilador emitirá um erro se a entrada do usuário não estiver dentro do intervalo após a inserção, independentemente do valor real dentro do intervalo.
Gulshan
Mas você ainda precisa decidir se deseja rejeitar a entrada ou converter para um intervalo válido, para que isso não responda à pergunta original.
JacquesB
@ JacquesB Devo dizer isso, no começo - eu sempre pensei que, o OP está desenvolvendo uma API e a UI é desenvolvida por outra pessoa, consumindo sua API. Eu estava errado sobre este ponto. Acabei de ler a pergunta novamente e percebi isso. Tudo o que estou tentando dizer é que a validação deve ser feita nos consumidores da API e, se não, o compilador deve gerar um erro.
22716 Gulshan
Então ... você precisa criar uma nova classe que contenha as entradas e valide. Quando você constrói o objeto de classe a partir das entradas brutas, o que acontece? Lançar uma exceção? Passar silenciosamente? Você acabou de mudar o problema.
djechlin
-1

O padrão deve ser lançar uma exceção. Você também pode permitir uma opção como strict=falsee fazer a coerção com base na bandeira, onde strict=trueé claro que é o padrão. Isso é bastante comum:

  • Java DateFormatsuporta branda .
  • O analisador JSON do Gson também suporta um modo branda .
  • Etc.
djechlin
fonte
Isso complica o código sem nenhum benefício.
JacquesB
@JacquesB lançando exceção por padrão ou permitindo strict=false?
djechlin
@ JacquesB Adicionei alguns exemplos de APIs que oferecem suporte a modos estritos e brandos, por favor, dê uma olhada.
23416 djechlin
-2

Para mim, a melhor prática é nunca alterar a entrada do usuário. A abordagem que eu costumo adotar é separar a validação da execução.

  • Tenha uma classe simples que apenas use os parâmetros fornecidos.
  • Use um decorador para fornecer uma camada de validação que possa ser alterada à vontade sem afetar a classe de execução (ou injete um validador se essa abordagem for muito difícil).
David A
fonte
O "usuário" a questão está se referindo é o desenvolvedor usando o API, e não o usuário final ...
Jay Elston