C# 保护值与数据的方法?

C# 保护值与数据的方法?,c#,validation,design-patterns,C#,Validation,Design Patterns,有人知道防止无效数据而不是无效值的技术或方法吗?我知道这似乎是一个奇怪的区别,但请容忍我 最近我遇到了一个问题,关于如何更好地防止无效变量值。我们目前正在将异常包装到一个Guard类中,但建议进一步将异常包装到扩展中。换言之,这: int myVar = 0; // Basic if (myVar < 1) throw new InvalidArgumentException("myVar cannot be less than 1"); // Guard -- wraps the a

有人知道防止无效数据而不是无效值的技术或方法吗?我知道这似乎是一个奇怪的区别,但请容忍我

最近我遇到了一个问题,关于如何更好地防止无效变量值。我们目前正在将异常包装到一个
Guard
类中,但建议进一步将异常包装到扩展中。换言之,这:

int myVar = 0;

// Basic
if (myVar < 1) throw new InvalidArgumentException("myVar cannot be less than 1");

// Guard -- wraps the above exception
Guard.AgainstValuesLessThan(1, myVar, nameof(myVar), "Value cannot be less than 1");

// Extension -- wraps the above guard
myVar.EnsureValid();
所以我想找到一个更好的方法,这就是我绊倒的地方。到目前为止,我遇到的问题是,从根本上说,我的问题是,我在考虑作为数据保护什么(“我需要保护坏的客户ID”),但我在保护值(“我需要确保这个整数至少为1”)

我不知道接下来该怎么办。我觉得这可能是那些“我不知道它存在,但这很有帮助”的案例之一

有人对看什么有什么建议吗?到目前为止,我有三个想法,我不确定其中一个是否比另一个好,或者是否有某种方法将它们结合起来以实现我的目标:

  • 代码合同
  • 我缺少一些设计模式
  • 其他一些技巧或技巧(例如属性?
    InvokerParameterName
    ?)

最后一点注意:我知道ReSharper提供了一个注释库,但不是我团队中的每个人都使用ReSharper(包括我),所以我不想依赖它。

从可维护性的角度来看,我认为一些OOP/设计模式是最好的方法。我同意你的看法,如果不是所有开发人员都需要使用ReSharper,那么就尝试将它嵌入到代码中

您拥有Guard子句的方式简单且易于重用;我不会改变这一点。如果我理解正确的话,您是在防范业务规则,而不一定是基本价值观。您希望封装您的业务规则“不能具有小于1的ID”,这绝对是一个值得警惕的好规则

我建议您考虑以下几个选项:

  • 将逻辑放入业务对象的构造函数中。遵循的一个好原则是,对象应该负责维护自己的状态。在构造函数中传递一个有效ID,并让构造函数包含guard子句和错误消息,如下面的代码段中所示
  • 
    公共雇员(内部id)
    {
    防范价值小于(1,id,姓名(id),“员工id不能小于1”);
    }

    如果您在代码中的其他地方使用该对象,您肯定知道它处于有效状态,因为您成功地构造了该对象。这也解决了记住调用调用方函数/类中的Guard子句的问题

  • 使用装饰图案。这一个稍微高级一点,需要在项目中进行更多的设置,但其基本思想是您可以编写一个类来为您执行验证。您可以连接一个DI库,如SimpleInjector,以拦截实现特定接口的请求(在本例中,类似于IEmployeeID),并在执行任何代码之前执行验证。这对其他业务规则非常有用,可以解决诸如“此员工ID是否存在于数据库中?”之类的问题。如果它不存在,则可以取消对其余代码的任何执行。通过这种方式,您可以将验证逻辑划分为一个单独的类

  • 我倾向于首先选择选项1,因为它可以帮助您集中精力创建具有有效状态的对象,这是一个很好的原则。如果你需要更多的例子,或者我错过了你的问题,请告诉我,我可以给出更好的答案:)

    这并不能完全回答你的问题,但这正是我在评论中所说的:

    public static class AssertValid
    {
    
        public static void TestAssertValid()
        {
            var customerId = 0;
            AssertValid.MinimumFor(customerId, 1, nameof(customerId));
    
        }
        public static void RangeFor<T>(T variableValue, T min, T max, string varName,
            string message = "Variable {0} outside of range {1} to {2} in function {3}",
            [CallerMemberName] string inFunc = "") where T : IComparable
        {
            if (variableValue.CompareTo(min) < 0 || variableValue.CompareTo(max) > 0)
            {
                var msg = string.Format(message, varName, min, max, inFunc);
                throw new ArgumentOutOfRangeException(varName, variableValue, msg);
            }
        }
    
        public static void MinimumFor<T>(T variableValue, T min, string varName,
            string message = "Variable {0} less than minimum of {1} in function {2}",
            [CallerMemberName] string inFunc = "") where T : IComparable
        {
            if (variableValue.CompareTo(min) < 0)
            {
                var msg = string.Format(message, varName, min, inFunc);
                throw new ArgumentOutOfRangeException(varName, variableValue, msg);
            }
        }
    
        public static void MaximumFor<T>(T variableValue, T min, string varName,
            string message = "Variable {0} greater than maximum of {1} in function {2}",
            [CallerMemberName] string inFunc = "") where T : IComparable
        {
            //...
        }
    
        public static void StringLengthRangeFor(string variableValue, int min, int max, string varName,
            string message = "Length of string variable {0} outside of range {1} to {2} in function {3}",
            [CallerMemberName] string inFunc = "")
        {
            if (variableValue.Length < min || variableValue.Length > max)
            {
                var msg = string.Format(message, varName, min, max, inFunc);
                throw new ArgumentOutOfRangeException(varName, variableValue, msg);
            }
        }
    
        public static void StringLengthMinFor(string variableValue, int min, int max, string varName,
            string message = "Length of string variable {0} less than {1} characters in function {2}",
            [CallerMemberName] string inFunc = "")
        {
            if (variableValue.Length < min)
            {
                var msg = string.Format(message, varName, min, inFunc);
                throw new ArgumentOutOfRangeException(varName, variableValue, msg);
            }
        }
    
        public static void StringLengthMaxFor(string variableValue, int max, string varName,
            string message = "Length of string variable {0} greater than {1} characters in function {2}",
            [CallerMemberName] string inFunc = "")
        {
            //...
        }
    
        public static void StringLengthPatternFor(string variableValue, string regexPattern, string varName,
            string message = "String variable {0} does not match acceptable pattern in function {1}",
            [CallerMemberName] string inFunc = "")
        {
            //... Use ArgumentException
        }
    
    }
    
    最终会出现如下异常:

    System.ArgumentOutOfRangeException: 'Variable customerId less than minimum of 1 in function TestAssertValid
    Parameter name: customerId
    Actual value was 0.'
    
    您可能要做的另一件事是将其变成一个可实例化的类,所有方法都是实例方法。创建类的一个实例,对该实例执行所有断言,最后断言一切正常。如果不是,则将所有故障一起吐出(在一个异常中)


    这样,所有测试都会在抛出异常之前完成。

    那么,使用这些是代码契约式的先决条件吗?在继续您的逻辑之前,您想断言一组先决条件吗?这些是您“保护”的局部变量,而不是字段或属性,对吗?您看过(现在已弃用的)代码契约内容和ComponentModel注释吗?另一个需要注意的是MSTest Assert类(我比较积极,我喜欢断言满足了前提条件,而不是防止坏数据)。您可能还希望将其混合在()中,以便可以在异常中获得一些调用方信息。如果是我,可能会有一些静态类(比如
    AssertValid
    ),其方法有“Range
    Minimum
    Maximum`(其中
    T:IComparable
    )、StringLengthRange、StringLengthMinimum、StringLengthMaximum、StringPattern(正则表达式),等等。你真正看到的是一种专门化类型的方法,而不是你在C#中所能做的。您希望创建一个
    CustomerId
    EmployerId
    类型,以了解作为客户或雇主的本质(您可以使用一些语言,例如oCaml,参见@EricLippert的博客)。但是,你不能在C#中这样做。你可以创建像Customer和Employer这样的类型,让他们声明他们的ID符合规范,但我认为你不能再进一步了。这与我在阅读问题时的想法非常接近+1.
    var customerId = 0;
    AssertValid.MinimumFor(customerId, 1, nameof(customerId));
    
    System.ArgumentOutOfRangeException: 'Variable customerId less than minimum of 1 in function TestAssertValid
    Parameter name: customerId
    Actual value was 0.'