当参数不符合您的预期时,您是否应始终检查参数并在.NET中抛出异常?例如。空对象或空字符串?
我开始这样做,但后来认为如果在每一种方法上完成,我的代码会很多。我应该检查私有和公共方法的参数吗?
我最终抛出了很多ArgumentNullException(“name”)异常,即使处理异常的代码不能以编程方式执行任何不同的操作,因为不能保证“name”将来不会改变。
我认为这个信息在查看充满异常信息的日志时非常有用吗?
最好的做法是始终“平易近人”。
答案 0 :(得分:12)
我的两分钱:所有公共方法都应该始终检查传入的参数的有效性。这通常称为“按合同编程”,这是一种快速捕获错误并避免通过私有函数传播它们的好方法。许多人认为(包括我自己)不应该直接进行单元测试。抛出异常,如果你的函数或程序无法纠正错误本身,它应该抛出异常,因为它已被抛入无效状态。
答案 1 :(得分:6)
对于公共方法,然后是:绝对有必要检查你的论点;对于内部/私人电话,Eric Lippert可能会将其归类为“笨蛋”(here);他的建议是不要抓住他们......修改代码!
为避免代码膨胀,您可能需要考虑AOP,例如postsharp。为了说明,Jon Skeet有一个postharp属性,可以进行空参数检查,here。然后(引用他的例子),你可以简单地归因于方法:
[NullArgumentAspect("text")]
public static IEnumerable<char> GetAspectEnhancedEnumerable(string text)
{ /* text is automatically checked for null, and an ArgumentNullException thrown */ }
这里另一个方便的技巧可能是扩展方法;扩展方法有一个奇怪的功能,你可以在空实例上调用它们...所以你可以做以下(使用泛型而不是“对象”是这样你不会意外地通过装箱值类型调用它) :
static void ThrowIfNull<T>(this T value, string name) where T : class
{
if (value == null) throw new ArgumentNullException(name);
}
// ...
stream.ThrowIfNull("stream");
你可以用超出范围等做同样的事情。
答案 2 :(得分:5)
没有什么比追逐未设置为对象实例的'对象引用'更糟糕的了。如果您的代码足够复杂,则很难知道什么是失败的 - 特别是在生产系统中,特别是如果它处于罕见的边界条件下。显式异常在解决这些问题方面有很长的路要走。这是一种痛苦,但如果发生 的事情,那么你不会后悔的事情之一。
答案 3 :(得分:3)
这取决于您的班级/方法的消费者。如果它都是内部的,我会说它并不重要。如果您有未知/第三方消费者,那么您需要进行大量检查。
答案 4 :(得分:1)
我对这种情况的理念是通知并继续(适用时)。 伪代码将是:
if value == not_valid then
#if DEBUG
log failure
value = a_safe_default_value
#elsif RELASE
throw
#endif
end
通过这种方式,您可以在开发过程中轻松进行迭代,并让用户测试您的应用程序,而不会让他们感到沮丧。
答案 5 :(得分:1)
我采取的方法是检查参数并在公开可见成员上抛出异常 - 任何公开可见的我都是指在汇编边界之外(因此任何public
,protected
或protected internal
方法一个public
类。这是因为你(通常)设计一个程序集作为一个自治单元,所以程序集范围内的任何东西都应遵循如何调用其他任何东西的规则。
对于任何非公开可见的成员(即internal
或private
成员或类),我使用Debug.Assert
来执行检查。这样,如果程序集中的任何调用者违反了您在开发/测试时立即发现的合同,但在最终部署的解决方案中没有性能开销,因为这些语句在RELEASE版本中被删除。
答案 6 :(得分:1)
我的大部分经验都是相对受限制的系统,这种类型的代码膨胀是不可取的。所以我自己的直觉是使用仅调试断言,或者完全不使用它。您希望在测试给出错误值的调用者时会发生任何可能的无效输入,因此只要您在调试模式和释放模式下进行测试,您就会看到诊断信息。否则,您将在最终发生崩溃时调试崩溃。
如果代码大小和性能无关紧要(在几乎所有代码中,简单的null或范围检查都不会影响性能),那么在发布模式下保留在代码中的断言越多,就越有可能诊断故障而无需在测试模式下重新创建错误。这可以节省大量时间。特别是,如果您的产品是库,那么很大一部分“错误”报告是由于客户错误造成的,因此没有任何预发布测试可以防止它们在野外发生。您越早向客户证明他们的代码是错误的,他们就能越早修复它,您就可以回过头来找到自己的错误。
但是,在C / C ++中,我发现检查空指针的具体情况只是一个小帮助。如果有人通过指针,那么完整的有效性条件不是“不能为空”。它需要指向当前进程可读(可能也可写)到某个大小的内存,并且包含正确类型的对象,可能在所有可能状态的某个子集中。它不需要被释放,不会被其他地方的缓冲区溢出所破坏,可能不会被另一个线程同时修改,等等。你不会在方法入口处测试所有这些,所以你仍然可以错过无效参数。任何导致你或其他程序员认为“这个指针不是空因此必须有效”的东西,因为你只测试了有效性条件的一小部分,这是误导。
如果你完全通过指针,那么你已经在你需要信任调用者不会给你垃圾的领域。拒绝一个特定的垃圾实例仍然让你相信打电话者不会给你任何他们可以召唤的无法检测到的无数垃圾。如果您发现空指针是来自您的特定调用者的常见垃圾,那么通过各种方法测试它们,因为它节省了诊断系统中其他地方的错误的时间。这取决于评估是否在调用者的代码中找到带有“向我传递空指针”的症状的错误值得膨胀自己的代码(可能是二进制大小,当然还有源代码):如果这样的错误很少,那么你'可能会浪费时间并对他们进行房地产检查。
当然,在某些语言中,您不会通过指针传递,并且调用者只有有限的机会来破坏内存,因此垃圾的范围较小。但是在Java中,传递错误的对象仍然是比传递错误的null更常见的编程错误。在任何情况下,如果将它们留给运行时发现,并且查看堆栈跟踪,则通常很容易诊断Null。因此,即使在那里,空检查的价值也非常有限。在C ++和C#中,您可以使用pass-by-reference,其中禁止使用null。
您可能测试的任何其他特定无效输入和任何语言都是如此。完整的状态前和状态后测试(如果可能的话)当然是另一回事,因为如果你能测试整个通话合约那么你就会更加稳固。如果您可以使用编织或其他任何方式断言合同而不添加函数本身的源代码,那就更好了。
答案 7 :(得分:0)
嗯,这取决于。如果您的代码无论如何都要获取null,然后抛出异常,那么确保您有合理的清理代码可能更有意义。如果否则可能无法检测到,或者清理可能会长时间运行,或者可能存在进程外调用(例如数据库),那么最好不要错误地更改世界,然后将其更改回来。
答案 8 :(得分:0)
考虑到最好在可以处理异常的地方捕获异常这一事实,我将异常放置在应用程序的最高层。如果可以处理异常;然后处理它。
这意味着低级的东西通常对于传递的参数较少的错误处理。这样可以避免杂乱无章并尊重性能。
这通常意味着具有较高层的接口的方法将进行更多的参数检查。