如果有意义的值超出范围,我应该抛出异常还是自己处理?


42

我写了一个表示纬度/经度坐标的结构。对于纬度,其值的范围是-180至180,对于纬度,其值的范围是90至-90。

如果该结构的用户给我提供的值超出该范围,则我有2个选择:

  1. 引发异常(arg超出范围)
  2. 将值转换为约束

因为坐标-185具有含义(因为极坐标可以很容易地将其转换为+175),所以我可以接受并转换它。

最好抛出一个异常来告诉用户他的代码给了我一个它不应该具有的值吗?

编辑:另外,我知道纬度/经度和坐标之间的区别,但是我想简化一下以便于讨论-这并不是最聪明的主意


13
是否应该允许用户插入超出范围的值?如果答案是否定的,请抛出异常。如果规则不那么严格,请执行转换,但在文档中明确指出转换可能发生。还应注意,在某些语言中,异常处理的成本很高。
安迪

C#,有关系吗?如果您的意思是,它本身并不支持约束。
K. Gkinis '16

2
对我来说似乎很清楚,然后,正确的方法是降低用户的输入范围,并在执行此操作时引发异常(如果标准规定abs值不得大于180,则将其设置为更大的值)明显违反)。顺便说一句,C#实际上是例外代价很高的语言之一,因此仅在例外情况下才真正使用例外,这意味着不及时捕获例外将破坏您的应用程序,例如这种情况。
安迪

2
通过传递特定的参数值,尤其是那些我的代码无法满足的参数,我倾向于不做关于用户“满意”的假设。听起来像是类似的情况。
JᴀʏMᴇᴇ

2
Web Mercator的坐标不是 -180到180,也不是-90到90。这就是纬度/经度(甚至还有几个坐标系)。墨卡托投影通常成千上万或数百万个,并具有“米”单位(甚至不严格如此,因为当您接近极点时,每个单位的长度都覆盖了增加的实际地面距离)而不是角度。即使在度数方面,它也被限制在±85.051129度,因为投影在极点处变得无限宽。(由于这不是问题的核心,因此我已提交了修改以纠正此问题。)
jpmc26 2016年

Answers:


51

如果您的问题的核心是这个...

如果某些客户端代码传递了一个参数,该参数的值对于我的数据结构正在建模的事物无效,那么我应该拒绝该值还是将其转换为明智的方法?

...然后我的一般回答是“拒绝”,因为这将有助于引起注意客户端代码中的潜在错误,这些错误实际上导致无效值出现在程序中并到达构造函数。在大多数系统中,至少在开发过程中,通常都需要引起对bug的关注(除非在发生错误的情况下,这是系统的混乱特性)。

问题是您是否真的面对这种情况

  • 如果您的数据结构通常用于建模极坐标,则可以接受该值,因为超出-180和+180范围的角度并不是真正无效的。它们是完全有效的,并且它们恰好总是具有-180到+180范围内的等效项(如果您要将它们转换为目标范围,请放心使用-客户端代码通常不需要关心) 。

  • 如果您的数据结构是对Web Mercator坐标进行显式建模(根据其初始形式的问题),那么最好遵循规范中提到的任何规定(我不知道,因此我不会赘述) 。如果您要建模的事物的规范表明某些值无效,请拒绝它们。如果说可以将它们解释为明智的事物(因此它们实际上是有效的),请接受它们。

用于发出是否接受值的信号的机制取决于语言的功能,其一般原理和性能要求。因此,您可能会抛出异常(在构造函数中)或返回结构的可为空版本(通过调用私有构造函数的静态方法),或者返回布尔值并将结构作为out参数传递给调用方(再次通过调用私有构造函数的静态方法),依此类推。


12
超出-180到+180范围的经度应该被认为是可以接受的,但是超出-90到+90范围的纬度似乎是毫无意义的。一旦到达北极或南极,就无法确定进一步向北或向南行驶。
超级猫

4
@supercat我倾向于同意您的意见,但是由于我不知道Web Mercator规范中哪些值实际上是无效的,因此我尝试不得出任何艰难的结论。OP比我更了解问题域。
Theodoros Chatzigiannakis

1
@supercat:从子午线到达赤道的地方开始。根据纬度沿着子午线旅行。如果纬度大于90,则继续沿着相同的大圆圈前进。没问题。记录下来,其余的交给客户。
凯文·克莱恩

4
@supercat比这更糟。在Web墨卡托投影中,实际上将±90投影到无限宽度。因此,该标准实际上将其截断为±85.051129。同样,纬度/经度!=网络墨卡托坐标。墨卡托坐标使用米的变化,而不是度数。(当您靠近两极时,每个“米”实际上对应着越来越大的一块地面。)OP的坐标纯粹是纬度/经度。Web Mercator与它们无关,只是它们可能显示在Web Mercator基础地图的顶部,并且某些空间库正在为它们投影。
jpmc26 2016年

1
我应该说“等于±85.051129”,因为这不是实际的坐标。
jpmc26 '02

10

这取决于很多。但是您应该决定做点事情并记录下来

代码要做的唯一绝对错误的事情是忘记考虑用户输入可能超出预期范围,并编写意外具有某些行为的代码。因为这样一来,有些人会对您的代码的行为做出错误的假设,并且会导致错误,而另一些人最终会取决于您的代码意外具有的行为(即使该行为完全是愚蠢的),因此您将导致更多的错误。当您以后解决问题时。

在这种情况下,我可以以任何一种方式看到参数。如果某人从175度移动+10度,那么他们应该以-175结尾。如果您总是规范化用户输入,因此将185等效为-175,那么当客户端代码加10度时,客户端代码就不会做错事。它始终具有正确的效果。如果您将185视为错误,则在每种情况下都强制客户端代码将相对度添加到规范化逻辑中(或至少记得调用规范化过程),这实际上会导致错误(尽管希望很容易捕获,但很快就会解决)。但是,如果用户输入经度数,直接在程序中编写经度数或通过旨在始终位于[-180,180)中的某个过程计算出经度数,则该范围之外的值很可能表示错误,因此“进行转换可能会隐藏问题。

在这种情况下,我的理想情况可能是定义一个代表正确域的类型。使用抽象类型(不要让客户代码简单地访问其中的原始数字),并提供规范化工厂和验证工厂(以便客户进行权衡)。但是无论使用哪种类型的值,通过公共API看到的185应该与-175毫无区别(无论它们是在构造上转换还是提供相等性,访问器和其他以某种方式忽略差异的操作) 。


3

如果选择一种解决方案对您来说并不重要,则可以让用户决定。

给定您的结构是只读值对象并由方法/构造函数创建,您可以根据用户具有的选项提供两个重载:

  • 引发异常(arg超出范围)
  • 将值转换为约束

另外,切勿让用户使用无效的结构来传递给您的其他方法,使其在创建时正确无误。

编辑:根据评论,我假设您正在使用c#。


感谢您的输入!我会考虑的,尽管我担心它会破坏抛出异常的构造函数的目的,如果可以避免的话。不过很有趣!
K. Gkinis '16

如果你要使用这个想法,这将是更好地定义一个接口,并有两种实现方式是投或转换。如您所描述的那样,很难以一种明智的方式使构造函数过载。

2
@Snowman:是的,要重载具有相同参数类型的构造函数很困难,但是拥有两个静态方法和一个私有构造函数并不难。
wchargin '16

1
@ K.Gkinis:引发异常的构造函数的目的不是“确保应用程序死亡”,毕竟,客户端可以随时处理catch您的异常。正如其他人所说的那样,它是允许客户根据自己的意愿进行限制。您并没有真正绕过任何事情。
wchargin '16

这个建议使代码复杂化,没有任何好处。该方法要么应该转换无效的输入,要么应该不转换。有两个重载会使代码更复杂而不解决难题。
JacquesB '16

0

这取决于输入是直接通过某个UI来自用户还是来自系统。

通过UI输入

这是一个用户体验问题,如何处理无效输入。我不知道您的具体情况,但总的来说,有几种选择:

  • 提醒用户该错误,并让用户修复该错误,然后再继续操作(最常见)
  • 自动转换为有效范围(如果可能),但提醒用户更改并允许用户在继续之前进行验证。
  • 静默转换为有效范围并继续。

选择取决于用户的期望以及数据的重要性。例如,Google会自动修复查询中的拼写错误,但是这种风险低,因为无用的更改不是问题,而且易于修复(即使这样,结果页上仍清楚地表明查询已更改)。另一方面,如果要输入核导弹的坐标,则可能需要更严格的输入验证,并且不对无效数据进行静默修复。因此,没有普遍的答案。

最重要的是,您应考虑更正输入是否对用户有利。用户为什么要输入无效数据?很容易看出有人可能会犯拼写错误,但是为什么有人会输入-185的经度?如果用户的意思是+175,则可能输入了+175。我认为无效的经度很可能只是键入错误,而用户的意思是-85或其他。在这种情况下,无声转换是不利且无济于事的。对于您的应用程序,最用户友好的方法可能是提醒用户该无效值,并让用户自己对其进行更正。

通过API输入

如果输入来自其他系统或子系统,则毫无疑问。您应该抛出异常。切勿静默转换来自另一个系统的无效输入,因为它可能掩盖系统中其他位置的错误。如果输入已“更正”,则应在UI层中进行,而不是深入系统中。


0

您应该抛出异常。

在您给出的示例中,发送185并将其转换为-175,那么在某些情况下提供该功能可能很方便。但是,如果呼叫者发送了100万怎么办?他们真的意味着要转换吗?似乎更有可能是错误。因此,如果您需要为1,000,000而不是185抛出异常,那么您必须对抛出异常的任意阈值做出决定。由于某些调用应用程序正在发送接近阈值的值,因此该阈值将使您崩溃。

最好将异常抛出值超出范围。


-1

对于开发人员而言,最方便的选择是在平台中提供编译时错误支持,以获取超出范围的值。在这种情况下,范围也应该是方法签名的一部分,就像参数的类型一样。如果您的方法签名定义为采用整数,则API用户无法传递String的方式相同,如果不检查该值是否在方法签名所给定的范围内,则用户不应传递值。如果不检查,他应该得到一个编译时错误,因此可以避免运行时错误。

但是目前,很少有编译器/平台支持这种类型的编译时间检查。因此,这是开发人员的头疼。但是理想情况下,您的方法应该针对不支持的值抛出有意义的异常,并清楚地记录下来。

顺便说一句,我真的很喜欢Joe Duffy 在这里提出的错误模型。


您将如何为用户输入提供编译时错误?
JacquesB '16

@JacquesB如果插入后未检查用户输入是否在该范围内,则无论实际值在该范围内,编译器都会发出错误。
Gulshan

但是随后您仍然必须决定是要拒绝输入还是要转换为有效范围,因此这不能回答原始问题。
JacquesB '16

@JacquesB首先,我应该这样说-我一直认为OP正在开发API,而UI是由其他人开发的,并且消耗了他的API。我错了。我只是再次阅读了这个问题并意识到了这一点。我想说的是,验证应该在API使用者处进行,否则,编译器应抛出错误。
Gulshan

所以...您需要制作一个包含输入并进行验证的新类。当您从原始输入构造类对象时,会发生什么?抛出异常?默默地过去?您只是解决问题。
djechlin '16

-1

默认值应该是引发异常。您还可以允许类似strict=false这样的选项,并根据标志进行强制转换,当然这strict=true是默认设置。这很普遍:

  • Java DateFormat支持lenient
  • Gson的JSON解析器还支持宽松模式。
  • 等等。

这使代码变得毫无价值。
JacquesB '16

@JacquesB默认抛出异常,还是允许strict=false
djechlin '16

@JacquesB我添加了一些支持严格和宽松模式的API的示例,请看一看。
djechlin '16

-2

对我来说,最佳实践是永远不要更改用户输入。我通常采用的方法是将验证与执行分开。

  • 有一个仅使用给定参数的简单类。
  • 使用装饰器提供一层可以随意更改的验证层,而不会影响执行类(或者,如果这种方法太困难,则注入一个验证器)。

问题所指的“用户”是使用API​​的开发人员,而不是最终用户...
Jay Elston 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.