参数验证最佳实践

Use*_*ser 11 c# validation parameter-passing

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

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

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

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

internal bool Matches(string expression)
{
    return RegexA.IsMatch(expression);
}
Run Code Online (Sandbox Code Playgroud)

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

Str*_*ior 12

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

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

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

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;
    ...
}
Run Code Online (Sandbox Code Playgroud)

Resharper的Annotations是一项有助于这些检查的最终技术.例如,请考虑以下方法:

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

通过告诉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;
}
Run Code Online (Sandbox Code Playgroud)

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


cod*_*nix 9

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

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

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

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

这是因为如果参数的要求发生变化,则需要在多个位置更改验证,从而存在不一致的风险.


Ant*_*ram 5

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

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

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