从客户端的角度来看,对于有效的无效输入抛出哪个异常

Posted

技术标签:

【中文标题】从客户端的角度来看,对于有效的无效输入抛出哪个异常【英文标题】:Which exception to throw for invalid input which is valid from client perspective 【发布时间】:2013-06-05 15:22:12 【问题描述】:

我正在编写代码来查找和交叉 2 行。 当线的斜率相等时,它们不相交。但另一方面,具有相等斜率的输入是完全有效的。

public static Point calculateIntersection(Line line1, Line line2) 

    if (line1 == null || line2 == null) 
        throw new NullPointerException(" some message ");
    

    if (line1.getConstant() == line2.getConstant()) 
        return new Point(0, line1.getConstant());
    

    if (line1.getSlope() == line2.getSlope()) 
        throw new IllegalArgumentException("slopes are same, the lines do not intersect.");
    

    int x = (line2.getConstant() - line1.getConstant()) / (line1.getSlope() - line2.getSlope());
    int y = line1.getSlope() * x + line1.getConstant();

    return new Point(x, y);

问题是抛出非法参数异常是正确的做法吗? 由于输入是有效的,它并不能完全说服我。

自定义异常是正确的做法吗? 听起来是个不错的选择,但额外的意见会有所帮助。

谢谢

【问题讨论】:

这真的是个例外吗?我完全赞成使用异常而不是可为空的返回值,但在这种情况下,它会表达一个预期的返回值。 输入不匹配异常,数字格式异常或者在最一般的情况下,只需添加非法参数异常。 【参考方案1】:

问题是抛出非法参数异常是正确的做法吗?

这取决于您希望/需要如何“框定”这种情况;即它是错误、用户输入错误还是程序应该能够处理的东西?

如果两条线不相交的情况无疑是一个“错误”,那么IllegalArgumentException 就可以了。这就是异常的目的。 (注意这是一个unchecked异常,所以期望它不会被捕获/恢复。)

如果您希望程序能够自行恢复,那么自定义异常是最好的主意。这样,您可以减少代码被(比如说)一个库方法抛出(比如说)IllegalArgumentException ... 而不是意味着“两条线相交”之外的东西而混淆的可能性。

如果您希望将这种情况作为输入验证的一部分报告给最终用户,那么一般的“验证错误”异常可能比特定的自定义异常更合适。但是,此方法看起来并非旨在(仅)用于用户输入验证。


在某些情况下,根本不抛出异常可能会更好,但是(IMO)这不是这些情况之一。替代方法是返回null 或返回一个Point 值,意味着“没有这样的点”给调用代码。替代方案的问题是:

如果您返回null,应用程序必须处理null 的情况......否则就会出现NPE。 没有自然的Point 实例可以用来表示“不是一个点”。

这并不是说您无法使这些替代方案发挥作用。只是在这种情况下,可能要做更多的工作,而可能不会有切实的回报。

【讨论】:

【参考方案2】:

这几乎肯定不应该引发异常,因为使用任意两个Line 值调用这样的方法非常有意义。您已经正确处理了 null 值。

您还非常合理地在一种定义不明确的输入情况下定义了您的类的行为,即两条重合的“常量”(水平)行,您在该行上返回x=0 处的点。对于其他定义不明确的输入,您应该类似地选择返回值:重合垂直线、既不水平也不垂直的重合线以及不重合的平行线。

在我看来,最后一种情况(不重合的平行线)最自然的结果是null,这反映了没有交点的事实。

然后由客户端决定空交集是否保证异常、错误消息或其他。例如。提示用户输入要相交的线的交互式外壳可能会打印一条错误消息并要求用户再试一次。一些更复杂的计算,例如线性优化器试图为其搜索定义边界,如果产生平行线的约束相互矛盾,则可能希望抛出 IllegalArgumentException

当然,所有这些情况下的返回值(重合线或非重合平行线)都应准确记录在方法的 javadoc 中。

【讨论】:

【参考方案3】:

我会说你做了正确的事:你尽早发现了这种情况。要么就是这样,要么人们会抱怨“你的程序有问题,看看这个输入数据,除以 0”。

鉴于在 99+% 的情况下不会出现此类错误,这是一种异常情况,不允许声明已检查的异常,因此未检查的异常看起来确实是正确的选择。

现在,至于IllegalArgumentException 是否是“好人”,它至少是描述这种情况的最接近的例外......你可以,如果你觉得你有一个更好的名字,总是创建你自己的一个继承@ 987654322@.

如果,另一方面,这种情况并不少见,那么也许应该审查达到该功能的逻辑,以免一开始就遇到这种情况。

【讨论】:

【参考方案4】:

正如@Andy-Lowry 和@KamikazeCZ 所说,这不应该是一个例外。

这个方法不应该关心客户端是否期望线总是相交;它应该只关心找到两条线的交点——这本来就可能不会发生。

如果调用者返回一个表明没有交集的结果,那么该代码可以决定它是否是无效输入,因为最终用户已得到适当的警告,或者他们可以处理的事情(可能通过重新提示),或者抛出一个自定义异常。

那么,回到这个方法应该返回什么?某种标记值,与 indexOf 在集合库中返回 -1 的方式相同。返回null 是一个合理的哨兵。在 Java 8 中,您可以返回一个 Optional<Point>,以帮助提醒调用者可能没有正确的 Point

你还有一个额外的问题:有人要求一条线与它自己的交点是什么? (在数学上,两条线的交点是 0 点、1 点或无限多点。)您可能需要能够返回 两个 标记值,这在 Java 中涉及更多。这次这种方法可以摆脱这种情况,通过说“在多个答案的情况下,这个方法可能会返回其中任何一个”,或者(我可能会做的)“......返回最接近原点的点” .

顺便说一句,这种想法很大程度上源于单元测试的心态:首先定义正确答案应该是针对各种极端情况输入,然后再启动代码并让自己承诺某种返回类型等。

最后:在使用== 比较getSlope() 的结果时,请注意浮点舍入错误。这可能是在这里做的最好的事情,但它仍然存在问题。但是,您假设(或舍入)与ints 的交点的方式表明您的问题可能存在非常特殊的约束/假设。

【讨论】:

【参考方案5】:

Keep in mind that a more generic version of this question was closed as primarily opinion-based.

如果是我,我会返回null。在大多数情况下,您不应将异常处理用作流控制的一种形式。返回 null 将有助于避免使用您的方法的任何代码。

相关讨论:

Dont Use Exceptions For Flow Control

Should a retrieval method return 'null' or throw an exception when it can't produce the return value? [closed]

Are exceptions as control flow considered a serious antipattern? If so, Why?

Arguments for or against using Try/Catch as logical operators [closed]

【讨论】:

【参考方案6】:

应使用异常来捕获程序流中的错误(“内部发生的情况”),而不是用于输入验证。我根本不会抛出异常。 想想看,这不是“异常”的意思,用户输入两条斜率相等的线是完全正常的。

【讨论】:

看下一行:要么是那个,要么是除以0 当用户进入两个相同的斜率时,你当然不会继续除以零,而是警告他并停止。抛出异常不是一个好主意,因为它......嗯......不是一个例外。这是完全正常的。这就是 if 语句的用途。 哦,是吗?用户期望一个结果。以及相交的结果。 用户期望一个结果,或者,如果你不能给出一个结果,他理解的错误信息。抛出异常:1) 不对应于 2) 可能不那么可读 3) 包含一些开销,这在这种情况下还不错,但在其他一些情况下可能会出现

以上是关于从客户端的角度来看,对于有效的无效输入抛出哪个异常的主要内容,如果未能解决你的问题,请参考以下文章

如果输入参数在Django模板中无效,如何抛出异常

从客户端的角度来看移动端IM即时通讯的消息可靠性和送达机制

如何从 MFP 8 Java 适配器向客户端抛出用户异常?

从服务器抛出的自定义错误未在客户端的 HttpErrorResponse 中返回

OleDbCommand 在有效的命令文本上抛出异常

从路由返回了无效的 JSON。也许抛出了异常?