C# 拓展署及;设计建议

C# 拓展署及;设计建议,c#,.net,unit-testing,tdd,C#,.net,Unit Testing,Tdd,我有一个调用操作的方法 公共操作显示错误; 公共方法() { 显示错误(“错误”); } 在这个方法中,我想调用DisplayError,但是我可以看到,如果DisplayError为null,它将抛出一个异常 我可以运行一个测试来证明它会抛出异常 所以我知道我想在我的代码中添加一个if(DisplayError!=null),但我觉得这个设计有点错误。也许测试应该有所不同?我相信是在Steve Yeggie的一次演讲中,我听到了这一点:“虽然静态类型语言确实可以在编译时保护您免受各种错误的影

我有一个调用
操作的方法

公共操作显示错误;
公共方法()
{
显示错误(“错误”);
}
在这个方法中,我想调用DisplayError,但是我可以看到,如果DisplayError为null,它将抛出一个异常

我可以运行一个测试来证明它会抛出异常


所以我知道我想在我的代码中添加一个
if(DisplayError!=null)
,但我觉得这个设计有点错误。也许测试应该有所不同?

我相信是在Steve Yeggie的一次演讲中,我听到了这一点:“虽然静态类型语言确实可以在编译时保护您免受各种错误的影响,但它们并不否定空检查的需要。”

我觉得这很正常


我正在喝早上的咖啡,所以如果我完全没有注意到,请告诉我。

这完全取决于上下文

此类的用户是否需要根据合同设置值?然后它应该抛出一个异常

它是可选的吗?那么您的检查是有效的,但是如果您只是将默认的
DisplayError
设置为空操作,您将得到类似的行为

DisplayError = s => {};
您的方法还可能导致用户可以将
DisplayError
设置为
null
,在这种情况下,只有您可以决定什么是有效的。如果将其设置为属性而不是字段,则会为自己提供更多选项

_displayError = s => {};

public Action<string> DisplayError
{
   get { return _displayError; }
   set 
   { 
       _displayError = value ?? (s => {}); 
       /* or throw on null */
   }
}
\u displayError=s=>{};
公共行动显示错误
{
获取{return\u displayError;}
设置
{ 
_displayError=值??(s=>{});
/*或者抛出空值*/
}
}

与其将操作设置为公共字段,不如将其设置为属性或将其传递到方法中。然后可以在该点执行空检查。我还怀疑您是否需要在设置后检索它,因此:

_displayError = (s) => { throw new ArgumentNullException("Please tell me what to do with this exception!"); };

public Action<string> DisplayError
{
    set 
    {
        if (value == null) { throw new ArgumentNullException("I need this!"); }
        _displayError = value;
    }
}

public void MyMethod()
{
    _displayError("ERROR");
}
\u displayError=(s)=>{抛出新的ArgumentNullException(“请告诉我如何处理此异常!”);};
公共行动显示错误
{
设置
{
如果(value==null){抛出新的ArgumentNullException(“我需要这个!”);}
_显示错误=值;
}
}
公共方法()
{
_显示错误(“错误”);
}
或:

public void MyMethod(操作显示错误)
{
如果(displayError==null){抛出新的ArgumentNullException(“我需要这个!”);}
显示错误(“错误”);
}
其中的第一项不需要用户对代码进行任何更改

当然,如果您只是与使用您的代码的团队一起工作,那么您根本不需要进行空检查。只要找出谁把你的代码用错了,然后友好地聊一聊,找出误解所在。防御性编程不是团队成员相互信任的好替代品,空检查也不如拥有一个定义明确、易于使用的界面那么有效

您需要进行两项测试:

我的班级:

  • 应允许使用者处理任何错误
    • 给定,无论出于何种原因,我都将产生一个错误(在此处设置上下文)
    • 当我产生错误时(调用该方法,让错误处理程序设置一些变量)
    • 然后我应该将错误提供给处理程序(检查设置的变量)
  • 应确保附加了错误处理程序
    • 假设我可能会产生一个错误(您可以在这里编写您喜欢的代码,因为它与此无关)
    • 当使用者在没有错误处理程序的情况下调用我时(使用null错误处理程序调用该方法)
    • 然后我应该立即要求消费者不要这样做(检查例外情况)

我知道我需要添加代码来检查空值,但我认为测试或其他东西不正确。我是否运行了一个测试来证明如果我没有分配它抛出的空操作,或者我是否执行了一个测试来显示它何时被分配执行?如果您认为操作为空的可能性可能是一个问题,我认为添加一个测试是合理的。如果该类在生产之前通过并因此而失败,那么您以后可能会自责。该类中将有一个Windows窗体控件,我想我会在窗体上分配一个与此操作相同的例程(更新GUI)。目前表单实现还不存在,我正在测试这个方法/action@Jon,如果您或您的团队成员负责更新GUI的表单,那么最好确保没有人使用null调用该类,而不是在其中放入null检查。不要编写那些不会在生产中使用的东西——这只会让维护成为一场噩梦。@Jon:你的控件会实现一些必需的接口吗?我选择了后一个选项,但我不确定我的测试应该显示什么更新。在这些“应该”之后命名测试,在注释中添加“给定、何时、然后”,并填补空白。尽可能避免传递/返回null。在这种情况下,可以将DisplayError默认为在调用时不执行任何操作的无操作操作。这是一个像破窗一样的症状,第一个空检查导致空检查开始在各地如雨后春笋般涌现。空引用异常应该意味着有问题,应该尽快通过设计或提高对如何使用库的认识来解决。
_displayError = (s) => { throw new ArgumentNullException("Please tell me what to do with this exception!"); };

public Action<string> DisplayError
{
    set 
    {
        if (value == null) { throw new ArgumentNullException("I need this!"); }
        _displayError = value;
    }
}

public void MyMethod()
{
    _displayError("ERROR");
}
public void MyMethod(Action<string> displayError)
{
    if (displayError == null) { throw new ArgumentNullException("I need this!"); }
    displayError("ERROR");
}