2

另一个人问了类似的问题,但我不清楚答案。

我们的项目中有很多参数验证代码。它们大多只是公共方法中的空参数检查:

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

这些检查不是规范的一部分。它们的主要用途是在发生编程错误时获得更明智的堆栈转储。如果没有完成这样一个简单的检查,您最终可能会在堆栈深处的十级中,在某个其他库中间的随机位置中使用NullReferenceException. 这使得整个回溯到根本原因的过程更长。验证检查在语义上是清晰的,可以让您更早地发现问题。

由于我们经常这样做,因此我们一直在寻找更短的方法来进行此类检查。如:

ArgumentHelper.ThrowIfNull(param, "param");

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

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

但是,据我了解,代码合同并非旨在解决“输入验证”问题或任何类型的验证问题。因为它引发的异常并不意味着被捕获。它们也不是为了使调试更容易,因为默认行为是从发布版本中的此类检查中剥离代码。重写器似乎只是围绕它进行破解,而且它的性能也不是很好

我从“静态验证”和“参数验证”中了解到的目标不同,因此它们不能互换。然而,Code.Contracts在我的序言中使用if-then-throws 一开始听起来很自然,我想知道我是不是有什么问题。

我的问题是:使用代码合同进行参数验证是否有效?是否有一个明确的问题我可以向任何验证​​代码提出并提出一个答案,例如:“是的,您可以使用代码合同来检查这个”和“不,您不能使用代码合同来检查那个”?我该如何区分?

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

4

2 回答 2

4

唔。我是那个写了不清楚的答案的人:-/。我会尝试用不同的方式来表达它。

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

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

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

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

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

此方法假定 userInput 不为空;正如你所说,如果不是真的,它就行不通。如果传入一个空值,表明有人犯了编程错误。因此,我们可以将其声明为方法接口的一部分,并让静态验证器在有人犯错时警告我们:

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

    return userInput.ToLower();
}

如果这发生在运行时,这意味着不仅有人犯了编程错误,而且静态验证器也无法检测到问题,这可能是由于某些反射使用或类似情况,或者只是因为其中存在错误。因此,以防万一,打开合同重写总是一个好主意,至少在公共表面合同上是这样。

因此,我们仅在没有它们的情况下方法无法工作的情况下才使用合约来检查参数;它们是该方法成功运行的先决条件。如果前提条件失败,那么有人犯了错误,我们不想捕获异常,因为它揭示了一个错误。

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

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

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

    return userInput.ToLower();
}

现在,如果用户名为空,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();
}
于 2013-10-21T08:05:56.917 回答
1

一个快速的答案是否定的,你不能用代码合同代替输入验证。主要区别在于输入验证不应该对传入的无效数据抛出异常,因为这不是异常情况。另一方面,违反合同总是表明您的系统中存在错误,因此应该抛出异常甚至破坏您的应用程序(遵循快速失败原则)。

查看我的博客文章:代码合同与输入验证

于 2015-02-15T04:54:37.857 回答