zoukankan      html  css  js  c++  java
  • C# CuttingEdge.Conditions 验证帮助类库 文档翻译

    项目主页: https://archive.codeplex.com/?p=conditions

    作者博客关于项目的文档(翻译原文): https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=38#Designed_Behavior

    前言

    CuttingEdge.Conditions 可帮助开发人员在其.NET 3.5代码库中编写前后条件验证。

    编写这些验证很容易,它提高了代码的可读性和可维护性。

    警告:

    此帖子基于CuttingEdge.Conditions 的 预览版本发布。虽然库的大多数概念和行为都是相同的,但最终版本有一些更改。

    其中最明显的是删除了 Requires() 和 Ensures{} 方法的扩展方法行为。

    请注意,不再支持以下语法:c.Requires().IsNotNull() ,建议的语法是 Condition.Requires(c).IsNotNull()。

    请在阅读本文时牢记这一点。

    编写前置条件验证可以提高代码质量。具有验证的代码更易于理解,并允许开发人员更快地发现错误,主要是在开发期间发现这些问题而不是在调试期间。

    然而,编写前置条件验证一直是编程中不太好处理的地方。编写它需要时间,我和许多开发人员(甚至是我尊重的人)都会跳过编写它们。

    跳过前置条件验证将导致代码更难以使用并且可能被滥用。它允许开发人员传递一些无效的方法参数,

    这会导致意外的程序行为以及来自调用堆栈内部的那些可怕的 NullReferenceException 等异常。它会导致更多的错误,从而花费更多的时间进行调试。

    CuttingEdge.Conditions 库试图降低编写前置条件验证的障碍并使代码更具可读性,从而产生更好的代码,更少的错误和更短的开发周期。

    要了解 CuttingEdge.Conditions 如何尝试实现这一目标,让我们首先看看我们可能每天编写的一些代码。

    这是一个前置条件验证的例子,旧的实现方式:

     1 void TheOldFashionWay(int id, IEnumerable<int> col, 
     2     DayOfWeek day)
     3 {
     4     if (id < 1)
     5     {
     6         throw new ArgumentOutOfRangeException("id", 
     7             String.Format("id should be greater " +
     8             "than 0. The actual value is {0}.", id));
     9     }
    10 
    11     if (col == null)
    12     {
    13         throw new ArgumentNullException("col",
    14             "collection should not be empty");
    15     }
    16 
    17     if (col.Count() == 0)
    18     {
    19         throw new ArgumentException(
    20             "collection should not be empty", "col");
    21     }
    22 
    23     if (day >= DayOfWeek.Monday &&
    24         day <= DayOfWeek.Friday)
    25     {
    26         throw new InvalidEnumArgumentException(
    27             String.Format("day should be between " +
    28             "Monday and Friday. The actual value " +
    29             "is {0}.", day));
    30     }
    31 
    32     // Do method work
    33 }

    对于这样一些简单的验证,这是一些非常多的代码!

    以下是 CuttingEdge.Conditions 的实现方式:

     1 //旧的调用方式
     2 void TheConditionsWay(int id, IEnumerable<int> col, 
     3     DayOfWeek day)
     4 {
     5     id.Requires("id").IsGreaterThan(0);
     6     col.Requires("col").IsNotEmpty();
     7     day.Requires("day").IsInRange(DayOfWeek.Monday, DayOfWeek.Friday);
     8 
     9     // Do method work
    10 }
    11 
    12 //新的调用方式
    13 private static void Main(string[] args)
    14 {
    15   var id = 0;
    16 
    17   Condition.Requires(id)
    18     .IsGreaterThan(20)
    19     .IsInRange(1, 999)    // ArgumentOutOfRangeException on failure
    20        .IsNotEqualTo(1000,"编号不能为 1000");   // throws ArgumentException on failure
    21 
    22   var text = "hahahahhaha";
    23 
    24   Condition.Requires(text, "字符串")
    25                 
    26     .StartsWith("h") // throws ArgumentException on failure
    27     .EndsWith("a") // throws ArgumentException on failure
    28        .Evaluate(text.Contains("abc") || text.Contains("cba")); // arg ex
    29  }

    这是完全不同的,不是吗?

    它不仅代码少得多; 它也非常易读。请注意,两种方法都有完全相同的合同; 两种方法抛出完全相同的异常!

    除了这些正常的前提条件检查,CuttingEdge.Conditions还允许您进行后置条件检查。

    与先决条件不同,违反后置条件纯粹是内部原因。

    它可以被认为是一个错误。在这种情况下抛出ArgumentException会明显混淆使用该代码的开发人员。由于存在这种差异,CuttingEdge.Conditions 对违反后置条件的代码总会抛出一个 PostconditionException 。

    以下是后置条件检查的示例:

    1 void TheConditionsWay(int id, IEnumerable<int> col, 
    2     DayOfWeek day)
    3 {
    4     id.Requires("id").IsGreaterThan(0);
    5     col.Requires("col").IsNotEmpty();
    6     day.Requires("day").IsInRange(DayOfWeek.Monday, DayOfWeek.Friday);
    7 
    8     // Do method work
    9 }

    后置条件示例显示了两个有趣的事情。

    首先,使用Ensures扩展方法来启动后置条件验证。

    其次,方法调用可以以流畅的方式链接,如 IsNotNull 和 IsOfType 方法所示。

    API

    CuttingEdge.Conditions API有许多验证方法,

    可以轻松满足98%的验证需求。目前有51种不同检查的88种扩展方法。API可分为七组:

    • 入口点方法
    • 空检查方法
    • 键入检查方法
    • 比较检查
    • 集合检查
    • 字符串检查
    • 自定义验证

    下面我将列出 CuttingEdge.Conditions 当前beta 1版本中的所有方法。

    方法的数量可能会随着时间的推移而增长,如果您认为缺少验证,请在此处或在Codeplex上发表评论。我会考虑将它们添加到库中。

    另请注意,只需将扩展方法放在您自己的项目中,您就可以使用自己的方法扩展API。

    Entry point methods 入口点方法

    入口点方法用于启动验证。可以在每个类型的每个变量上调用这些扩展方法。目前有两种方法:

    Requires (2 重载)

    Ensures (3 重载)

    Requires 可以用来写先决条件,它在验证失败时抛出 ArgumentException 或 它的一个子类。

    Ensures 可以用来写后置条件。它将抛出 PostconditionException。

    空检查方法

    如果参数需要值或不需要值,则可以使用空检查。可以对引用类型和 Nullable <T> 结构执行这些检查。

    有两种方法:

    IsNotNull(2 重载)

    IsNull(2 重载)

    键入检查方法

    有两种方法可用于类型检查:

    IsOfType

    IsNotOfType

    比较检查

    比较检查验证参数是否等于某个其他值或在给定范围内。

    它们可以在Nullable <T>结构和所有实现 IComparable 的类型上执行。目前有12种方法:

    IsEqualTo(3 重载)

    IsNotEqualTo(3 重载)

    IsGreaterThan(3 重载)

    IsNotGreaterThan(3 重载)

    IsGreaterOrEqual(3 重载)

    IsNotGreaterOrEqual(3 重载)

    IsInRange(3 重载)

    IsNotInRange(3 重载)

    IsLessThan(3 重载)

    IsNotLessThan(3 重载)

    IsLessOrEqual(3 重载)

    IsNotLessOrEqual(3 重载)

    集合检查

    集合检查可用于(至少)实现 IEnumerable 的类型。目前有18种方法:

    Contains(2 重载)

    DoesNotContain(2 重载)

    ContainsAll(2 重载)

    DoesNotContainAll(2 重载)

    ContainsAny(2 重载)

    DoesNotContainAny(2 重载)

    IsEmpty

    IsNotEmpty

    HasLength

    DoesNotHaveLength

    IsLongerThan

    IsNotLongerThan

    IsLongerOrEqual

    IsNotLongerOrEqual

    IsShorterThan

    IsNotShorterThan

    IsShorterOrEqual

    IsNotShorterOrEqual

    字符串检查

    有一组单独的方法可以验证字符串,目前有16种方法:

    Contains

    DoesNotContain

    StartsWith(2 重载)

    DoesNotStartWith(2 重载)

    EndsWith(2 重载)

    DoesNotEndWith(2 重载)

    HasLength

    DoesNotHaveLength

    IsEmpty

    IsNotEmpty

    IsNullOrEmpty

    IsNotNullOrEmpty

    IsLongerThan

    IsLongerOrEqual

    IsShorterThan

    IsShorterOrEqual

    自定义验证

    对于使用上述方法无法完成的所有检查,使用 Evaluate 方法重载是一种解决方案。

    第一个重载检查并返回一个布尔值,将在它等于false时抛出异常。

    第二个重载运行一个返回布尔值的指定 Expression。这允许开发人员定义lambda表达式。这两个重载允许字面表达您想要的任何前置或后置条件。有一种方法:

    Evaluate(2次重载)

    以下是使用Evaluate的两个示例:

    1 // Evaluate with boolean
    2 s.Requires("s").Evaluate(
    3     s.StartsWith("hello") || s.EndsWith("world"));
    4 // Evaluate using a lambda expression
    5 s.Requires("s").Evaluate((str) => 
    6     str.StartsWith("hello") || str.EndsWith("world"));

    这两个例子看起来很多,但它们实际上是完全不同的。

    第一个示例使用布尔参数,检查速度非常快。但是,它在失败时缺少一个好的异常消息。

    与 lambda 表达式的重载恰恰相反。

    它必须在每次调用时编译给定的表达式,因此不应该在代码的性能敏感部分中使用它。但是在使用时,能够抛出一个具有描述性的异常消息:

    '(str.StartsWith("hello") || str.EndsWith("world"))' should hold for s. The actual value is 'world hello'. Parameter name: s。

    CuttingEdge.Conditions API经过精心设计,

    我希望它以最直观的方式运行(您可以在这里阅读更多关于设计过程和一些背景知识)。

    下面是我做出的一些设计决策以及开发人员必须编写的代码的后果。如果这种行为很奇怪并且需要改变,请告诉我。

    空值被认为小于任何非空值。

    这与在.NET框架中验证对象的方式一致。以下代码行显示此行为:

    1 int? i = null;
    2 // Next check will pass, because null is
    3 // smaller than Int32.MinValue.
    4 i.Requires().IsLessThan(Int32.MinValue);

    包含空引用的集合参数被视为包含零元素。

    对于用户来说,这种行为可能看起来很奇怪。当检查空引用时,库的用户可能希望 API 抛出 ArgumentNullException 。

    但是在某些情况下,抛出异常将不是预期的行为,因此会导致API不一致。除此之外,选择空引用将会始终失败,将限制该 API 的有用性。

    在某些情况下,用户可能会发现空引用是有效值。即,用户可以如下定义前提条件:“ 集合应该具有少于五个元素并且可以是空引用”。

    他无法使用集合方法的 API 来表达这个前提条件。他将被迫使用 Evaluate 机制,该机制显然不太可读,并且不会抛出描述性的异常消息。

    以下示例显示了设计的行为:

     1 IEnumerable c = null;
     2 // Both HasLength and IsEmpty checks will
     3 // pass, because c equals null.
     4 c.Requires().HasLength(0);
     5 c.Requires().IsEmpty();
     6 // IsShorterThan will pass, because c is
     7 // clearly shorter than 5 characters.
     8 c.Requires().IsShorterThan(5);
     9 // When c equals null, it doesn't contain
    10 // any elements, so we'd expect
    11 // the next lines to pass.
    12 c.Requires().DoesNotContain("value");
    13 c.Requires().DoesNotContainAny(new string[] { "a", "b" });

    当空引用不是有效值时,开发人员可以使用 IsNotNull()方法。即使空引用是无效状态,也不需要这样做。以下示例显示了一些示例:

    1 // Use IsNotNull() to check for null.
    2 c.Requires().IsNotNull().IsShorterThan(5);
    3 // IsNotEmpty() will throw an ArgumentNullException
    4 // when c equals null and an ArgumentException
    5 // when c is empty but not null.
    6 c.Requires().IsNotEmpty();

    当该值列表不包含任何元素时,被检查的集合被视为包含所有指定的值。

    当指定的值列表为空时,对ContainsAll方法的调用将始终成功。

    以下示例显示了此行为:

    1 Collection<int> c =
    2     new Collection<int> { 1, 2, 3, 4, 5 };
    3 // All checks will pass.
    4 c.Requires().ContainsAll(new int[] { 1, 2 });
    5 c.Requires().ContainsAll(new int[] { 1 });
    6 c.Requires().ContainsAll(new int[0] { });
    7 c.Requires().ContainsAll(null);

    当该集合不包含任何元素时,被检查的集合被认为不包含任何指定的值。

    当指定的值列表为空时,对ContainsAny的调用将始终失败。

    以下示例显示了此行为:

    1 Collection<int> c = new Collection<int> { 1, 2, 3, 4, 5 };
    2 // Next two checks will pass.
    3 c.Requires().ContainsAny(new int[] { 1, 9 });
    4 c.Requires().ContainsAny(new int[] { 1 });
    5 // Next two checks will fail, because the
    6 // specified lists are empty.
    7 c.Requires().ContainsAny(new int[0] { });
    8 c.Requires().ContainsAny(null);

    空字符串被认为具有0个字符的长度。

    这里的理由与前面集合的描述大致相同。

    以下示例显示了此行为:

    1 string s = null;
    2 // Next check passes.
    3 s.Requires().HasLength(0);
    4 s.Requires().IsShorterThan(5);
    5 s.Requires().IsLongerThan(-1);
    6 // You should use IsEmpty() or IsNotNull()
    7 // if null is not a valid value.
    8 s.Requires().IsEmpty();
    9 s.Requires().IsNotNull().HasLength(0);

    空字符串和空字符串不被视为相等,并且有多种方法需要检查。

    以下示例显示了此行为:

    1 string s = null;
    2 // The following checks will fail.
    3 s.Requires().IsEqualTo(String.Empty);
    4 s.Requires().IsEmpty();
    5 // The following checks will pass.
    6 s.Requires().IsNullOrEmpty();
    7 s.Requires().IsNull();

    一个 null 字符串 仅仅会包含 null 字符串 。这种情况适合使用 StartsWith,EndsWith 和 Contains 等方法。

    这就是.NET 的 String 方法的工作原理。以不同方式实现它不仅非常困难,而且用户不会期望这种行为。

    以下示例显示了此行为:

    1 string s = null;
    2 // All checks below will fail.
    3 s.Requires().Contains(String.Empty);
    4 String.Empty.Requires().Contains(null);
    5 String.Empty.Requires().EndsWith(null);

    检查空引用是否属于某种类型将始终成功。

    这对于反向操作也是有效的。

    警告:

    设计的行为已更改。与下面的文本描述的相反,在最终版本中,使用null参数调用IsOfType将始终失败,并且使用null参数调用IsNotOfType现在将始终成功。

    下面的代码是违反直觉的,最终的 API 模仿了C# 'is' 运算符的行为。

    在这里,我们看到了与之前看到的集合和字符串相同的问题。用户可以确定空引用有效。

    如果没有,他将不得不在他的代码中添加一个IsNotNull检查。以下示例显示了此行为:

    1 object o = null;
    2 // Both checks below will pass, because o equals null.
    3 o.Requires().IsOfType(typeof(string));
    4 o.Requires().IsNotOfType(typeof(object));
    5 // Use IsNotNull() when null is not a valid value.
    6 o.Requires().IsNotNull().IsOfType(typeof(string));
    7 o.Requires().IsNotNull().IsNotOfType(typeof(object));

    在具体翻之前觉得验证似乎是一件很简单的事情,但是这篇文档最后这几条体现出了这一方面的水深。

    该文档与现在的 API 差距还是有一些的。而且该类库也无人维护了。原本我是打算用这个类库,或者是参考一下实现做些改进。

    现在我觉得,我还是看看其他的类库吧。如果有时间的话就自己去实现一套。(估计没有的啦~



    转载请标明出处

    作者:AaXuan

    地址:http://www.cnblogs.com/Aaxuan

    知识共享许可协议

    本作品采用  知识共享署名 3.0 未本地化版本许可协议  进行许可。

  • 相关阅读:
    JQuery对象操作支持链式法则源码分析
    JQuery + JSON作为前后台数据交换格式实践
    JQuery html API支持解析执行Javascript脚本功能实现-代码分析
    跨域访问实践
    XP下安装MAC OS虚拟系统
    Android APP开发笔记
    CSS浮动与清浮动
    LUA 模块化编程例子
    JavaScript解决命名冲突的一种方法
    XML中文本节点存储任意字符的方法
  • 原文地址:https://www.cnblogs.com/Aaxuan/p/9298118.html
Copyright © 2011-2022 走看看