参数验证最佳实践

时间:2011-06-28 16:52:33

标签: c# validation parameter-passing

想象一下,您的应用程序是某种前端,适用于您的所有业务逻辑。这个前端有很多依赖它的DLL,这些DLL中的方法可以在前端单次执行给定方法时重复调用。如果您的应用程序的用户不直接访问这些DLL,您应该......

1)冒险(小)性能命中并验证每种方法中的参数,即使您最终可以验证相同参数约5次;或

2)风险意外行为并假设,当您验证输入参数时,传入和传出内部代码的所有其他可能参数都是有效的(例如,既不为null也不为空)?

修改:举一个例子,假设你有一个正则表达式RegexA和一个方法

internal bool Matches(string expression)
{
    return RegexA.IsMatch(expression);
}

IsMatch会在null参数上抛出异常,但不会在空字符串上抛出异常。如果您事先知道空字符串永远不会与该正则表达式匹配,那么您之前是否应该使用if (String.IsNullOrEmpty(expression)),即使知道它可能在IsMatch框架方法中被验证为无效?在这种情况下,您显然正在重复验证,但重复它还是冒风险更好?

5 个答案:

答案 0 :(得分:11)

除非参数的验证费用昂贵,否则我会选择#1。快速失败行为使您可以在很短的时间内捕获错误,这将比在每个方法开始时编写一些保护语句节省更多的时间。

您可能感兴趣的一项技术是.NET的代码契约,它允许您创建准编译时检查,以确保没有人在没有确保输入与预期模式匹配的情况下调用方法。

我个人尝试使用代码合同,发现我的需求有一点点开销。但是,我很欣赏语法,所以我创建了一个类来帮助这些保护语句,但这只能在运行时使用。它的工作原理如下:

public void ChangeUserName(int userId, string name)
{
    Require.ThatArgument(userId > 0);
    Require.ThatArgument(!string.IsNullOrWhitespace(name,
        () => "Usernames must be non-empty strings");
    var user = GetUser(userId);
    Require.That(user != null, 
        () => new UserDoesNotExistException("No user exists with ID " + userId));
    user.Name = name;
    ...
}

对于这些检查有很大帮助的最后一项技术是Resharper的Annotations。例如,请考虑以下方法:

[CanBeNull]
public User GetUser(int userId)
{
    var user =  ... // Get the user from the db
    return user;
}

通过告诉Resharper该方法可能返回一个空值,如果您在尝试访问user之前未对user.Name进行空检查,它将会发出警告。另一个注释可用于告诉Resharper Require.That(user != null)构成空检查。您也可以像这样重写您的方法:

[NotNull]
public User GetUser(int userId)
{
    Require.ThatArgument(userId > 0);
    var user =  ... // Get the user from the db
    Require.That(user != null)
    return user;
}

通过将此方法标记为NotNull,Resharper可以自动告诉您user != null将始终解析为true,因此您无需检查它。您可以使用各种有趣的东西来简化验证。

答案 1 :(得分:8)

通常参数检查非常便宜,即使被调用了数千次。 例如,如果值为null,则字符串或Collection为emtpy,数字在给定范围内。

但要注意检查可能昂贵,所以请三思而行:在大字符串上评估正则表达式,检查文件是否存在,检查集合中的所有元素符合一定的标准。

我也建议只检查公开或受保护的方法。 请注意,所有未经检查参数的公共方法都是潜在风险

编辑/另一个想法: 如果方法不使用参数但只是将传递给另一个方法,那么您也可以省略检查。只有实际使用这些参数的方法才能进行检查。

这是因为如果参数的要求发生变化,您需要在多个地方更改验证,冒着不一致的风险。

答案 2 :(得分:5)

作为图书馆的作者,您不能假设消费者已经对输入进行了适当的验证,因此您作为图书馆作者希望在使用它们之前确保参数有效。

作为库的使用者,如果您知道哪些输入会导致库失败,为什么要将这些输入传递给该库?验证它们,以便您可以提示您的用户提供更好的输入或以其他方式取消您所处的任何过程。

在我看来,您可能是图书馆和消费者的作者这一事实并不是特别相关,因为这种关系可能会发生很大变化。

答案 3 :(得分:1)

非常有趣的话题:))

通常,您应该实现低于用户界面的“验证外观”,并且通常由用户界面和外部服务访问的最低级别。

你可以检查null并在UI中验证输入只是为了避免无用的往返服务器,客户端验证是一个很好的做法,你仍然不能相信调用者只传递你有效的值。

答案 4 :(得分:1)

您可能会得到不同意见,但在我看来......最好在两个层中进行验证。在前面和业务逻辑(你称之为dll)