【问题标题】:Parameter Validation Best Practices参数验证最佳实践
【发布时间】:2011-09-24 12:11:20
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 我不明白你所说的“即使你最终可以验证相同的参数大约 5 次”的意思
  • @Bumble Bee:他的意思是当你的程序执行沿着堆栈向下移动时,可能有十几个方法检查value != null。因此,对同一个值执行多次相同的逻辑,而不是根本不检查。

标签: c# validation parameter-passing


【解决方案1】:

除非参数的验证成本很高,否则我会选择#1。 Fail-fast 行为让您可以在很短的时间内捕获错误,这比在每个方法的开头编写一些保护语句所花费的时间要多得多。

您可能有兴趣帮助解决此问题的一项技术是 .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 的注释。例如,考虑以下方法:

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

通过告诉 Resharper 该方法可能返回一个空值,如果您在尝试访问 user.Name 之前没有对 user 进行空值检查,它会知道警告您。另一个注释可以告诉 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,因此您不必检查它。您可以做各种有趣的事情来简化验证。

【讨论】:

    【解决方案2】:

    非常有趣的话题:)

    一般来说,您应该实现一个低于用户界面的“验证外观”,并在用户界面和外部服务通常访问的可能最低级别。

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

          通常参数检查非常便宜,即使调用了数千次。 例如测试一个值是否为空、一个字符串或集合是否为空、一个数字是否在给定范围内。

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

          我也只建议只检查 public 或 protected 方法。 请注意,所有带有未检查参数的公共方法都是潜在风险

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

          这是因为如果参数要求发生变化,您需要在多个地方更改验证,可能会导致不一致

          【讨论】:

          • +1。关于您的编辑,通过将验证推迟到实际使用参数时,您可能会更难发现错误。如果始终提供参数但仅在某些极少数情况下使用怎么办?如果其中一个调用方法提供了无效值,我想立即知道这一点,即使这次可能不会通过该方法使用该值。应该可以合并特定参数的验证代码以防止重复和不一致,同时仍尽快(并尽可能频繁地)验证参数。
          • @StriplingWarrior 但是,如果我知道不会使用此参数,我可以传入任何值(空/空字符串)作为填充符。这是一个糟糕的设计,imo,应该有一个方法重载(如果语言允许的话)。
          猜你喜欢
          • 1970-01-01
          • 2016-09-25
          • 1970-01-01
          • 2015-10-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-09
          相关资源
          最近更新 更多