¿Debo lanzar una excepción en caso de un valor significativo fuera del rango o manejarlo yo mismo?

42

He escrito una estructura que representa las coordenadas de latitud / longitud. Sus valores varían de -180 a 180 para las longitudes y de 90 a -90 para las latitudes.

Si un usuario de esa estructura me da un valor fuera de ese rango, tengo 2 opciones:

  1. Lanzar una excepción (arg fuera de rango)
  2. Convertir el valor a la restricción

Debido a que una coordenada de -185 tiene significado (se puede convertir fácilmente a +175, ya que esas son coordenadas polares), podría aceptarla y convertirla.

¿Es mejor lanzar una excepción para decirle al usuario que su código me ha dado un valor que no debería tener?

Editar: También sé la diferencia entre lat / lng y coordenadas, pero quería simplificar eso para una discusión más fácil: no era la idea más brillante

K. Gkinis
fuente
13
¿Debería permitirse al usuario insertar un valor fuera del rango? Si la respuesta es no, lanza una excepción. Si las reglas no son tan estrictas, realice la conversión, pero explícitamente indique en la documentación que la conversión puede ocurrir. También tenga en cuenta que en algunos idiomas el manejo de excepciones es bastante costoso.
Andy
C #, ¿importa? No admite restricciones de forma nativa si esto es lo que quieres decir.
K. Gkinis
2
Me parece bastante claro, entonces, el enfoque correcto es dejar que el usuario ingrese cualquier cosa fuera del rango y arroje una excepción cuando lo haga (si el estándar dice que el valor de abs no debe estar por encima de 180, poniendo un valor mayor que eso es una clara violación) Por cierto, C # es en realidad uno de los lenguajes donde las excepciones son bastante costosas, así que úselas realmente solo en situaciones que sean excepcionales, lo que significa que no atraparlo interrumpirá su aplicación, situaciones como esta.
Andy
2
Tiendo a alejarme de hacer suposiciones sobre lo que el usuario 'quiso decir' al pasar valores de parámetros particulares, especialmente aquellos que mi código no atiende. Suena como un caso similar.
JᴀʏMᴇᴇ
2
Las coordenadas de Web Mercator no son de -180 a 180 y -90 a 90. Eso es latitud / longitud (e incluso hay varios sistemas de coordenadas para eso). Las proyecciones de Mercator suelen ser de cientos de miles o millones y tienen unidades de "metros" (ni siquiera estrictamente eso, ya que la longitud de cada unidad cubre el aumento de la distancia real al suelo a medida que se acerca a los polos), no grados. Incluso en términos de grados, está restringido a ± 85.051129 grados porque la proyección se vuelve infinitamente ancha en los polos. (He enviado una edición corrigiendo esto, ya que no es el núcleo de la pregunta).
jpmc26

Respuestas:

51

Si el núcleo de su pregunta es esta ...

Si algún código de cliente pasa un argumento cuyo valor no es válido para lo que está modelando mi estructura de datos, ¿debería rechazar el valor o convertirlo en algo sensato?

... entonces mi respuesta general sería "rechazar", porque esto ayudará a llamar la atención sobre posibles errores en el código del cliente que realmente están causando que el valor no válido aparezca en el programa y llegue a su constructor. Llamar la atención a los errores es generalmente una propiedad deseada en la mayoría de los sistemas, al menos durante el desarrollo (a menos que sea una propiedad deseada de su sistema para confundirse en caso de errores).

La pregunta es si realmente estás enfrentando ese caso .

  • Si su estructura de datos pretende modelar coordenadas polares en general, acepte el valor porque los ángulos fuera del rango -180 y +180 no son realmente inválidos. Son perfectamente válidos y siempre tienen un equivalente que está dentro del rango de -180 y +180 (y si desea convertirlos para apuntar a ese rango, siéntase libre: el código del cliente generalmente no necesita importarle) .

  • Si su estructura de datos está modelando explícitamente las coordenadas de Web Mercator (de acuerdo con la pregunta en su forma inicial), entonces es mejor seguir las disposiciones mencionadas en la especificación (que no sé, por lo que no diré nada al respecto) . Si la especificación de lo que está modelando dice que algunos valores no son válidos, rechazarlos. Si dice que pueden interpretarse como algo sensato (y, por lo tanto, en realidad son válidos), acéptelos.

El mecanismo que utiliza para indicar si los valores fueron aceptados o no depende de las características de su idioma, su filosofía general y sus requisitos de rendimiento. Por lo tanto, podría lanzar una excepción (en el constructor) o devolver una versión anulable de su estructura (a través de un método estático que invoca a un constructor privado) o devolver un booleano y pasar su estructura a la persona que llama como un outparámetro (nuevamente a través de un método estático que invoca un constructor privado), y así sucesivamente.

Theodoros Chatzigiannakis
fuente
12
Una longitud fuera del rango -180 a +180 probablemente debería considerarse aceptable, pero la latitud fuera del rango -90 a +90 parecería absurda. Una vez que uno alcanza el Polo Norte o Sur, el viaje hacia el norte o el sur no está definido.
supercat
44
@supercat Tiendo a estar de acuerdo con usted, pero como no sé qué valores son realmente inválidos en la especificación de Web Mercator, estoy tratando de no sacar conclusiones difíciles. El OP conoce el dominio del problema mejor que yo.
Theodoros Chatzigiannakis
1
@supercat: comienza donde el meridiano llega al ecuador. viajar a lo largo del meridiano según la latitud. Si la latitud es> 90, entonces continúe por el mismo gran círculo. No hay problema. Documente y deje el resto al cliente.
Kevin Cline
44
@supercat Es peor que eso. En la proyección Web Mercator, ± 90 en realidad se proyecta a un ancho infinito. Entonces, el estándar en realidad lo corta a ± 85.051129. Además, lat / long! = Coordenadas web mercator. Las coordenadas de Mercator usan una variación de metros, no grados. (A medida que te acercas a los polos, cada "metro" en realidad corresponde a un pedazo de tierra cada vez más grande). Las coordenadas de los OP son puramente largas / largas. Web Mercator no tiene nada que ver con ellos, aparte de que podrían mostrarse en la parte superior de un mapa base de Web Mercator y alguna biblioteca espacial se proyecta bajo el capó para ellos.
jpmc26
1
Debo decir "el equivalente de ± 85.051129", ya que esa no es la coordenada real.
jpmc26
10

Depende mucho Pero debe decidir hacer algo y documentarlo .

La única cosa definitivamente incorrecta que debe hacer su código es olvidarse de considerar que la entrada del usuario podría estar fuera del rango esperado y escribir código que accidentalmente tenga algún comportamiento. Porque entonces algunas personas harán una suposición incorrecta sobre cómo se comporta su código y causará errores, mientras que otras terminarán dependiendo del comportamiento que su código tenga accidentalmente (incluso si ese comportamiento es completamente loco) y, por lo tanto, causará más errores cuando luego arregles el problema.

En este caso puedo ver argumentos de cualquier manera. Si alguien viaja +10 grados desde 175 grados, debería terminar en -175. Si siempre normaliza la entrada del usuario y trata 185 como equivalente a -175, entonces el código del cliente no puede hacer lo incorrecto cuando agrega 10 grados; siempre tiene el efecto correcto. Si trata 185 como un error, fuerza todos los casos en los que el código del cliente agrega grados relativos para poner en la lógica de normalización (o al menos recuerde llamar a su procedimiento de normalización), en realidad provocaráerrores (aunque es de esperar que sean fáciles de atrapar y que sean rápidamente eliminados). Pero si un número de longitud es ingresado por el usuario, escrito literalmente en el programa, o calculado a través de algún procedimiento destinado a estar siempre en [-180, 180), entonces un valor fuera de ese rango es muy probable que indique un error, por lo que "útilmente "convertirlo podría ocultar problemas.

Mi ideal en este caso probablemente sería definir un tipo que represente el dominio correcto. Use un tipo abstracto (no permita que el código del cliente simplemente acceda a los números sin procesar dentro de él), y proporcione una fábrica de normalización y validación (para que el cliente pueda hacer la compensación). Pero sea cual sea el valor de este tipo, 185 debe ser indistinguible de -175 cuando se ve a través de su API pública (no importa si se convierten en la construcción o si proporciona igualdad, accesos y otras operaciones que ignoran la diferencia de alguna manera) .

Ben
fuente
3

Si realmente no le importa elegir una solución, puede dejar que el usuario decida.

Dado que su estructura es un objeto de valor de solo lectura y creado por un método / constructor, puede proporcionar dos sobrecargas basadas en las opciones que tiene el usuario:

  • Lanzar una excepción (arg fuera de rango)
  • Convertir el valor a la restricción

Además, nunca permita que el usuario tenga una estructura inválida para pasar a sus otros métodos, haga lo correcto en la creación.

Editar: según los comentarios, supongo que está utilizando c #.

Filipe Borges
fuente
¡Gracias por el aporte! Lo pensaré, aunque me temo que socavaría el propósito del constructor de lanzamiento de excepciones, si uno pudiera evitarlo. ¡Aunque es interesante!
K. Gkinis
Si va a utilizar esta idea, sería mejor definir una interfaz y tener dos implementaciones que arrojen o conviertan. Sería difícil sobrecargar un constructor de una manera sensata para trabajar como lo describió.
2
@Snowman: Sí, sería difícil sobrecargar un constructor con los mismos tipos de argumentos, pero no sería difícil tener dos métodos estáticos y un constructor privado.
wchargin
1
@ K.Gkinis: El propósito del constructor de lanzamiento de excepciones no es "asegurarse de que la aplicación muera", después de todo, el cliente siempre puede catchsus excepciones. Como han dicho otros, es permitir que el cliente se restrinja si así lo desea. Realmente no estás eludiendo nada.
wchargin
Esta sugerencia complica el código sin ningún beneficio. O el método debería convertir una entrada no válida o no debería. Tener dos sobrecargas hace que el código sea más complejo sin resolver el dilema.
JacquesB
0

Depende de si la entrada es directamente de un usuario a través de alguna interfaz de usuario o del sistema.

Entrada a través de una interfaz de usuario

Es una pregunta de experiencia del usuario cómo manejar la entrada no válida. No sé sobre su caso específico, pero en general hay algunas opciones:

  • Alerte al usuario sobre el error y haga que el usuario lo repare antes de continuar (lo más común)
  • Convierte automáticamente al rango válido (si es posible), pero alerta al usuario sobre el cambio y permite que el usuario verifique antes de continuar.
  • Convierta en silencio al rango válido y continúe.

La elección depende de las expectativas de sus usuarios y de la importancia de los datos. Por ejemplo, Google corrige automáticamente la ortografía en las consultas, pero esto es de bajo riesgo porque un cambio inútil no es un problema y es fácil de solucionar (e incluso en la página de resultados se aclara que la consulta se modificó). Por otro lado, si está ingresando coordenadas para un misil nuclear, es posible que desee una validación de entrada más rígida y sin soluciones silenciosas de datos no válidos. Entonces no hay una respuesta universal.

Lo más importante, debe considerar si corregir la entrada incluso tiene un beneficio para el usuario. ¿Por qué un usuario ingresaría datos no válidos? Es fácil ver cómo alguien podría cometer un error de ortografía, pero ¿por qué alguien ingresaría una longitud de -185? Si el usuario realmente quisiera decir +175, probablemente habría escrito +175. Creo que lo más probable es que una longitud inválida sea simplemente un error de escritura, y el usuario quiso decir -85 u otra cosa. En este caso, la conversión silenciosa es mala e inútil . El enfoque más fácil de usar para su aplicación probablemente sea alertar al usuario sobre el valor no válido y hacer que el usuario lo corrija por sí mismo.

Entrada a través de una API

Si la entrada es de otro sistema o subsistema, no hay duda. Deberías lanzar una excepción. Nunca debe convertir silenciosamente la entrada no válida de otro sistema, ya que podría enmascarar errores en otras partes del sistema. Si la entrada se "corrige", debería suceder en la capa de la interfaz de usuario, no más profundamente en el sistema.

JacquesB
fuente
0

Deberías lanzar una excepción.

En el ejemplo que dio, enviando 185 y convirtiendo eso a -175, entonces podría ser útil en algunos casos proporcionar esa funcionalidad. Pero, ¿y si la persona que llama envía 1 millón? ¿Realmente quieren convertir eso? Parece más probable que sea un error. Entonces, si necesita lanzar una excepción por 1,000,000 pero no por 185, entonces debe tomar una decisión sobre un umbral arbitrario para lanzar una excepción. Ese umbral lo hará tropezar en algún momento, ya que algunas aplicaciones de llamadas envían valores alrededor del umbral.

Es mejor lanzar la excepción para valores fuera del rango.

Brendan
fuente
-1

La opción más conveniente para un desarrollador sería un soporte de error de tiempo de compilación en la plataforma, para valores fuera de rango. En este caso, el rango también debe ser parte de la firma del método, al igual que el tipo de parámetros. De la misma manera que su usuario API no puede pasar una Cadena si su firma de método está definida para tomar un número entero , el usuario no debería haber podido pasar un valor sin verificar si el valor está dentro del rango dado en la firma del método. Si no está marcado, debería recibir un error de tiempo de compilación y, por lo tanto, podría evitarse el error de tiempo de ejecución.

Pero actualmente, muy pocos compiladores / plataformas admiten este tipo de comprobación de tiempo de compilación. Entonces, es un dolor de cabeza para desarrolladores. Pero, idealmente, su método debería arrojar una excepción significativa para los valores no compatibles y documentarlo claramente.

Por cierto, realmente me encanta el modelo de error propuesto por Joe Duffy aquí .

Gulshan
fuente
¿Cómo proporcionaría errores de tiempo de compilación para la entrada del usuario?
JacquesB
@JacquesB El compilador emitirá un error si no se verifica que la entrada del usuario esté dentro del rango después de la inserción, independientemente del valor real que esté dentro del rango.
Gulshan
Pero aún debe decidir si desea rechazar la entrada o convertirla a un rango válido, por lo que esto no responde a la pregunta original.
JacquesB
@JacquesB Debo decir esto, al principio: siempre pensé que OP está desarrollando una API y la interfaz de usuario es desarrollada por otra persona, consumiendo su API. Me equivoqué sobre este punto. Acabo de leer la pregunta nuevamente y me di cuenta de esto. Todo lo que intento decir es que la validación debe hacerse en los consumidores de API y, de lo contrario, el compilador debe arrojar un error.
Gulshan
Entonces ... necesita hacer una nueva clase que contenga las entradas y valide. Cuando se construye el objeto de clase a partir de las entradas sin formato, ¿qué sucede? ¿Lanzar una excepción? ¿Pasar en silencio? Solo mueves el problema.
djechlin
-1

El valor predeterminado debe ser lanzar una excepción. También puede permitir una opción como strict=falsey hacer la coerción en función de la bandera, donde, por supuesto, strict=trueestá predeterminada. Esto es bastante común:

  • Java DateFormatadmite indulgente .
  • El analizador JSON de Gson también admite un modo indulgente .
  • Etc.
djechlin
fuente
Esto complica el código sin ningún beneficio.
JacquesB
@JacquesB lanzando una excepción por defecto, ¿o permitiendo strict=false?
djechlin
@JacquesB Agregué algunos ejemplos de API que admiten modos estrictos e indulgentes, por favor, eche un vistazo.
djechlin
-2

Para mí, la mejor práctica es nunca cambiar la entrada del usuario. El enfoque que suelo adoptar es separar la validación de la ejecución.

  • Tenga una clase simple que solo use los parámetros dados.
  • Use un decorador para proporcionar una capa de validación que pueda modificarse a voluntad sin afectar la clase de ejecución (o inyecte un validador si este enfoque es demasiado difícil).
David A
fuente
El "usuario" al que se refiere la pregunta es el desarrollador que utiliza la API, no el usuario final ...
Jay Elston