复杂类型的方法参数验证
本文关键字:参数 验证 方法 类型 复杂 | 更新日期: 2023-09-27 18:04:39
我很喜欢方法中的参数验证。这样的代码在我的代码中很常见:
public void Foo(string someString)
{
#region parameter validation
if(string.IsNullOrWhiteSpace(someString))
throw new ArgumentNullException("someString");
// Let's ignore the .NET 4.6 nameof-feature for the sake of this question.
#endregion
// do things
}
现在假设该方法不接受字符串,而是接受一个复杂对象。假设我想让这个方法验证是否设置了这个复杂对象的一个属性。正确的做法是什么?到目前为止,我是这样做的:
public void Foo(Person person)
{
#region parameter validation
if(person == null)
throw new ArgumentNullException("person");
if(string.IsNullOrWhiteSpace(person.Name))
throw new ArgumentNullException("person.Name");
#endregion
// do things
}
是否有一些最佳实践?ArgumentNullException
是正确的选择吗?像这样检查属性是否值得推荐?
在我的情况下,我所有的模型都有一个Validate方法,这样我就调用它,如果它返回false(这意味着我得到了错误),我调用另一个方法从它获得错误并显示给用户,像这样:
if (person == null)
throw new ArgumentNullException (nameof (person));
// Validates only 1 property.
if (!person.Validate (nameof (person.MyProperty)))
DisplayErrors (person);
// Validates all properties.
if (!person.Validate ())
DisplayErrors (person);
背后的原因是,在大多数情况下,当用户填充信息时,我必须允许模型无效。我将这些方法与接口INotifyDataErrorInfo一起用于WPF,以便在发生错误时刷新UI。
当方法或操作只需要对象的一部分时,facade模型是一种解决方案,您可以在其中创建一个包含更复杂的类的新类,并且facade类也具有Validate方法来执行部分验证。
您可以考虑代码契约。当然,您所展示的检查是验证参数的完美方法……假设您只验证正在使用的字段。验证您没有引用的字段是自找麻烦(除非这是该方法的明确目的)。
最新版本的c#提供了一个'?'操作符,它允许你做这样的事情:
int? length = customers?.Length;
或
string name = person?.Name;
这允许您保护自己免受空引用异常的影响,并且在某些情况下可能比预验证更可取。
哦,运行代码分析工具通常会在验证参数之前标记使用参数,因此至少该工具认为这是最佳实践。
更新(回复评论):这在很大程度上取决于你想做什么。如果您正在创建一个API,并希望尽可能返回最好的错误消息,那么您所建议的可能是您所能做的最好的。这假定你主要关心的是与API的消费者进行通信。
如果您的主要兴趣是确保提供给您的方法的数据是有效的,并且不会导致您的方法行为不良(CYA),您应该查看合同。契约具有提供后置条件(确保您的方法没有返回意外的内容)和不变条件(确保没有更改您不想更改的内容)的优点。契约既可以提供静态(编译时)保证,也可以提供运行时保证。有一种相当细粒度的方式来管理合约选项。运行时契约违反会生成人们可能不太熟悉的契约异常。编译时的抱怨对用户来说可能也不熟悉。这假定您主要关心的是确保正确的操作或不因滥用API而受到指责。
你可以两者都使用,例如使用契约进行编译时检查,使用更简单的'if'检查运行时。
对我来说,99%的情况听起来像是设计问题。没有名字的人有效吗?我不这么认为。如果没有,为什么你允许一个人在没有人的情况下被创造出来呢?如果你(出于某种原因)允许这样做,难道对象不应该有责任维持它自己的"有效性"吗?我将依靠数据注释之类的东西来标记所需状态的元数据,并简单地检查亲自传递的数据是否"有效"
似乎没有官方的正确方法,所以我将坚持我自己的方法,这是
if(myComplexObject.SomeProperty == null)
throw new ArgumentNullException("myComplexObject.SomeProperty");
呼叫者会立即知道哪里出了问题。
注意,我没有使用命名运算符,因为那样只会给我SomeProperty
而不是myComplexObject.SomeProperty
。我可以做nameof(myComplexObject) + "." + nameof(myComplexObject.SomeProperty
,但我需要再考虑一下,第一眼看起来不太对。
以下是我认为其他方法比我的方法更糟糕的原因:
有人说我应该在模型中验证,我不会这样做,因为模型不知道如何验证对象的方法,它不知道。如果它是整体对象实例的有效性,当然,把它放在模型中,或者如果你问我,甚至一个业务层用于业务验证,但这不是那种情况。
其他人说我可以使用空合并运算符(c#中的?.
),我也不会这样做,因为它不是"如果为空,那么就处理那个"的场景,而是"如果这是空,我不会做任何事情,你应该为给我null感到难过,修复你的代码"的场景。
然后是代码契约,它提供了一种验证参数的好方法,并有一些真正好的好处,但最终出现了同样的问题:我如何告诉调用者一个复杂对象的哪个值是无效的?他需要知道论点Y的性质X是错的,仅此而已。虽然代码契约不是我问题的答案,但我认为如果同样的情况发生,我的答案也可以在那里使用。
然后有人建议我不应该在那个地方验证对象的属性,而应该在需要的函数中验证。问题是,这个属性很少被用来作为一个单独的值传递给另一个函数。此外,它还会与界面的概念发生冲突。