我正在编写一个需要一些输入的方法。现在,我认为以防御的方式编写它(即检查输入是否正确)是一个很好的做法。
基本上,输入应该是可以的,因为只有一个地方(可靠的)使用这种方法。
所以我的问题是,
- 这里的防御编程合理吗
- 如果是,它应该抛出一个已检查或未检查的异常吗
以惯用的方式在调用的每一层中使用相同的输入检查通常是一种糟糕的做法:重复自己的操作,对它的更改可能会很快变得一团糟
所以我认为,如果您的方法被设计为可由多个客户端类使用的API,那么它应该进行此检查,即使它可能是多余的。否则就保持干燥。
请注意,确保API一致性的最佳方法是通过单元测试覆盖方法
通过这种方式,您只在其功能需求需要时才提供检查,而且您不会想得太多。
如果是,它应该抛出一个已检查或未检查的异常吗?
使用已检查或未检查的异常用法是非常有争议的
理论上,当需要客户端处理时,检查异常是很好的。但在实践中,如果客户端不想处理它,或者如果方法在lambda体内使用,它可能会变成噪音。
想想,如果没有进行检查,并且输入了一些无效的输入,系统或应用程序的其余部分会发生什么。
您是否有一个安全的默认值可供回退和使用?这是所有情况的正确值吗?是否需要通知您的代码/API的调用方/客户端?因此,基于此,您甚至可以只记录一个警告,并可以继续使用默认值。但在大多数情况下,情况并非如此。
[..]只有一个地方(可靠的)使用这种方法。
截至今天。在未来,它可能会改变。因此,IMO,您可以检查输入是否有效。
它应该抛出一个已检查或未检查的异常吗?
这再次将我们带回到代码/API的调用方或客户端。你想让他们期望发生并处理它吗?如果他们能够处理异常并恢复,那么这是有意义的(注意:这意味着所有调用方在调用API/代码时都需要有一个try..catch
块)。否则,如果应该传递一个有效值,如果没有,则会出现未检查的异常(在合同/Javadoc中明确这一点)
让我们看看Java库本身中的一个例子。
File
类中的许多方法都会抛出已检查的异常。
如果输入无效,Integer.valueOf
抛出一个NumberFormatException
(这是一个未检查的表达式)。
一个好的做法是,任何公共API都必须验证其输入。
此外,还可以看看Java:已检查与未检查异常的解释在决定之前
基本上,输入应该是可以的,因为只有一个地方(可靠的)使用这种方法的。
你用这种方式推理你的函数真是太棒了。你刚刚为你的职能定义了一个合同。它表示,此函数需要一个有效的输入。在这种情况下,通过在检测到无效输入时抛出异常来使用防御性编程是一种非常好的做法。此异常将极大地简化对函数调用方的检测,这些调用方违反了约定并使用无效输入调用了函数。
如果你没有抛出一个专用的异常,那么(如果你幸运的话)你的函数可能会抛出一个通用的技术异常,例如NullPointerException
或ArrayIndexOutOfBoundsException
。这样的stacktrace仍然表明,在调用函数时发生了错误。然而,你需要分析,"发生了什么"。在最坏的情况下,您的函数根本不会抛出任何异常,并且"以某种方式"处理无效输入。你的程序以后可能会在看似不相关的位置中断,或者在更糟糕的情况下,它不会中断,并向用户呈现错误的结果。
这里的防御编程是否合理?
是的,肯定是。
如果是,它应该抛出一个已检查或未检查的异常吗?
检查异常与未检查异常仍然是一场持续的争论。您可以在StackOverflow上找到这个问题的许多答案。Scala语言只有未检查的异常。我个人也更喜欢它们。
如果您的函数也期望无效输入,该怎么办?
通常,这些都是接收人类输入的函数。例如,用户注册表格需要密码,密码不能太短,并且至少包含一个数字。在这种情况下,"failure"是预期的,应该以某种方式编码在函数的返回值中,这样函数的调用方就被迫检查两种返回值——Success和failure。这样的返回值可以使用代数数据类型进行建模。Java不允许对ADT进行自然建模,我们习惯了通过抛出异常来对Failure进行建模。也许在这种情况下,检查异常仍然有意义。
编辑::其他答案详细介绍了"何时"验证以及何时不验证。总结一下:公共方法——是的;不要跨层重复相同的验证。
我没有考虑到你的整个问题,所以我会改变我的答案。
是否应该检查输入?
使用公共方法-始终。原因是最佳实践总是调用接口而不是实现。因此,当你有一个接口时,你希望任何使用你的接口实现的人,如果他们传递了无效的参数,都能得到正确的反馈。
是否应该抛出异常?
是的!正如Bob叔叔所说,与其忘记检查返回值以检查错误,不如忘记捕获异常。换言之,例外情况更能防傻,因为它们不会"悄无声息"地失败。