代码合同可以代替参数验证吗?

时间:2013-10-18 17:40:47

标签: .net code-contracts

另一个人asked a similar question,但答案对我来说并不清楚。

我们的项目中有很多参数验证代码。它们通常仅在公共方法中进行空参数检查:

if (param == null)
{
  throw new ArgumentNullException("param");
}

这些检查不是规范的一部分。它们的主要用途是在出现编程错误时获得更好的堆栈转储。如果没有进行这样一个简单的检查,你可以在一个随机点的最后十层深处,在一个其他库的中间位置NullReferenceException。这使得整个回溯到根本原因的过程更长。验证检查在语义上是清晰的,允许您更早地发现问题。

由于我们做了很多,我们一直在寻找更短的方法来进行此类检查。如:

ArgumentHelper.ThrowIfNull(param, "param");

或类似的语法。我们基本上要避免开销。 if-throw语法使代码不必要地长。

当我尝试使用这种语法时,我注意到我正在收集类似断言和代码契约的类似语法。代码契约非常有吸引力,因为静态分析的可能性以及在开发周期的早期捕获错误。

但是据我所知,代码合同并非旨在解决“输入验证”问题或任何类型的验证问题。 Because the exceptions raised by it are not meant to be caught。它们不是为了使调试更容易,因为默认行为是从发布版本中的此类检查中剥离代码。 The rewriter just seems like a hack around it and it's not very performant either

我从“静态验证”和“参数验证”的所有理解中都有不同的目标,因此它们无法互换。然而,使用Code.Contracts为我的序言if-then-throw开头听起来很自然,我想知道我是否有错。

我的问题是:使用代码合同进行参数验证是否有效?是否有一个明确的问题我可以询问任何验证代码并得出如下答案:“是的,你可以使用代码合同来检查这个”和“你不能使用代码合同来检查”吗?我如何区分?

我提出“如果我删除合同声明,该功能是否会继续正常工作?”。这是正确的方法吗?

2 个答案:

答案 0 :(得分:4)

嗯。我写的答案并不清楚: - /。我会用不同的方式来表达它。

我说你的方法是正确的,但是有一个复杂的问题。从用户输入验证的角度来看这可能会有所帮助。拿这个代码:

public void SetUserName(string userInput)
{
    if(String.IsNullOrWhitespace(userInput))
        throw new ValidationException("...");       
}

用户完全有可能忘记输入新的用户名;空字符串是此方法可能接收的预期输入之一,它旨在处理它们。

现在假设我们添加了一个处理用户输入的方法:

public string DoSomethingToUserName(string userInput)
{
    return userInput.ToLower();
}

此方法假定userInput不为null;正如你所说,如果没有真实,它将无法运作。如果传入空值 ,则有人编程错误。所以我们可以说这是方法接口的一部分,并让静态验证器警告我们是否有人傻逼:

public string DoSomethingToUserName(string userInput)
{
    Contract.Requires(userInput != null);

    return userInput.ToLower();
}

如果在运行时发生这种情况,这意味着不仅有人犯了编程错误,而且静态验证程序无法检测到问题,无论是由于某些Reflection使用或类似问题,还是因为它存在错误。因此,至少在公共场合合同上打开合同重写总是一个好主意,以防万一。

因此,我们使用契约来检查参数,只有在没有它们的情况下方法不起作用的情况下;它们是该方法成功运行的先决条件。如果先决条件失败,那么有人会蠢蠢欲动,我们不想抓住异常,因为它揭示了一个错误。

当DoSomethingToUserName实际上没有操纵用户名时,就会出现复杂情况:

public string DoSomethingToUserName(string userInput)
{
    return DoFirstThingToUserName(userInput);
}

private string DoFirstThingToUserName(string userInput)
{
    Contract.Requires(userInput != null);

    return userInput.ToLower();
}

现在,如果用户名为null,DoFirstThingToUserName将失败; DoSomethingToUserName不关心它是否存在。但是,没有人能够通过查看公共接口告诉userInput必须预先验证。所以我们需要向外界宣布合同:

public string DoSomethingToUserName(string userInput)
{
    Contract.Requires(userInput != null);

    return DoFirstThingToUserName(userInput);
}

private string DoFirstThingToUserName(string userInput)
{
    Contract.Requires(userInput != null);

    return userInput.ToLower();
}

答案 1 :(得分:1)

快速回答是否定的,您不能用代码契约替换输入验证。主要区别在于输入验证不应该对进入的无效数据抛出异常,因为它不是特殊情况。另一方面,合同违规总是表示系统中的错误,因此应该抛出异常甚至粉碎你的应用程序(遵循失败原则)。

查看我的博文:Code contracts vs input validation