我正在编写一个几何建模程序的插件,我有一个基于Curve对象的抽象类。如果曲线是平面的,闭合的并且自身不相交,则该曲线仅被视为有效。
然后我有一系列其他方法来引用这条曲线,它可以做一些表面,或者将它拉伸到一个体积中。如果在BaseCurve无效时调用它们,这些方法将抛出异常。现在,如果用户使其中一条曲线无效,我的程序就会崩溃,而我正在试图找出让我的程序处理这个无效输入的最佳方法。
这是我班级的样子:
public abstract class AbsCurveBasedObject
{
public abstract Curve BaseCurve
{
get;
}
public bool BaseCurveIsValid
{
get
{
Curve c = this.BaseCurve;
...
//checks that curve is valid
...
return true/false;
}
}
public Surface GetSurface()
{
Curve c = this.BaseCurve();
...
//magic that converts c to a surface
//exception is thrown if c is invalid
...
return surface;
}
public Surface GetVolume()
{
Surface s = this.GetSurface();
...
//magic that converts s into a volume
...
return volume;
}
}
如果曲线无效或者我应该抛出异常,我不确定 GetSurface()是否应返回 NULL 。
基本曲线无效并不出人意料,因为我知道用户在使用我的程序时最终会创建无效曲线。我的理解是,当程序达到意外发生并且不知道如何继续的程度时,通常只应抛出异常?
如果曲线无效,我应该只从GetSurface()返回NULL,然后让GetSurface()的每个方法在GetSurface()的情况下返回null吗?这似乎更难调试。我知道我最终会忘记检查返回值是否为NULL并最终得到一些ArgumentNullException,它跟踪一直回到AbsCurveBasedObject.GetSurface()
因此,如果用户有一些如何使基本曲线属性无效,那么在整个地点跟踪中使用if / else或try / catch块会更好吗?
答案 0 :(得分:2)
理想情况下,如果可以避免异常,你永远不想抛出异常。但是,这并不一定意味着返回null是正确的做法!这可能是错误的。
以这种方式看待它。您的代码可能如下所示:
Blah MakeMeABlah(Foo foo)
{
if (!IsValid(foo)) throw new InvalidArgumentException("foo");
// [make a blah from a foo]
}
你可以让调用者看起来像这样:
Foo foo = GetFooFromUser();
try
{
blah = MakeMeABlah(foo);
}
catch(...)
{
// Tell user that input foo was invalid
}
那不太好。您的问题的解决方案是将IsValid变为公共方法:
Foo foo = GetFooFromUser();
if (!IsValid(foo))
// Tell user that input foo was invalid
else
blah = MakeMeABlah(foo);
嘿,没有异常处理,的代价是必须两次调用IsValid 。如果验证便宜,谁在乎呢?如果验证成本很高,那么您可能希望进一步重构这个内容版本的MakeMeABlah,它不会在发布版本中重新进行检查。
答案 1 :(得分:1)
官方C#Design guidelines说要抛出异常并让用户界面层处理异常。
答案 2 :(得分:0)
如果曲线无效,为什么还要构建曲线?使用构建器模式,您可以公开仅构建有效Curve对象的功能,并允许UI在那里处理无效状态。
这是(我认为)Eric的建议的本质,在构造无效对象之前公开了一种验证状态的外部方法。
注意:一旦构建了有效的Curve对象,就可以删除可怕的if-creating布尔值“bool BaseCurveIsValid”。 :)