在每个人都支持代码正确之前,我意识到通常正确的做事方法是不使用try / catch来控制流程。然而,这是一个有点边缘的情况,我想在其他人在这种情况下会做些什么。
这是示例代码,而不是实际代码,但它应该得到重点。如果有人想让我为这些类型定义一个词典或一些模拟类,请告诉我,我会。
public bool IsSomethingTrue1(string aString)
{
if (aDictionary.ContainsKey(aString)) //If the key exists..
{
var aStringField = aDictionary[aString].Field; //Get the field from the value.
if (aStringField != null) //If the field isn't null..
{
return aStringField.SubField != "someValue"; //Return whether a subfield isn't equal to a specific value.
}
}
return false; //If the key isn't found or the field is null, return false.
}
public bool IsSomethingTrue2(string aString)
{
try
{
return aDictionary[aString].Field.SubField != "someValue"; //Return whether the subfield isn't equal to a specific value.
}
catch (KeyNotFoundException) //The key wasn't in the dictionary..
{
return false;
}
catch (NullReferenceException) //The field (or the dictionary, if it is dynamically assigned rather than hardcoded) was null..
{
return false;
}
}
因此,在上面的示例中,第一个方法检查字典是否包含密钥,如果是,则检查字段是否为空,如果子值不等于,则返回true特定值,否则返回false。这避免了try / catch,但每次访问代码时都会检查字典以查看密钥是否存在(假设没有引擎盖缓存/等等 - 让我知道是否有任何请求) ,然后检查一个字段是否为空,然后检查我们关心的实际检查。
在第二种方法中,我们使用try / catch并避免使用多层控制逻辑。我们马上就到了#34;并尝试访问有问题的值,如果它以两种已知方式之一失败,它将返回false。如果代码成功执行,则可能返回true或false(与上面相同)。
这两种方法都可以完成工作,但我的理解是,如果在大多数情况下没有出错,第一种方法平均会更慢。第一种方法必须每次检查所有内容,其中第二种方法只需处理出现问题的情况。在堆栈上会有一个额外的空间用于异常,以及跳转到不同的catch块等的一些指令。这应该都不会影响常规情况下的性能,除了那个堆栈变量之外没有任何错误,但是,如果我正确理解了我在这里读过的内容: Do try/catch blocks hurt performance when exceptions are not thrown?
当然,通过这个确切的示例,差异可以忽略不计 - 但是,想象一下复杂的if / then / else树中的大量检查与具有失败条件列表的try / catch相比的复杂场景。我意识到是的,这种代码总是可以分解成更小的位或者重构,但是为了论证,让我们说除了控制流之外它不能被改变(有些情况)改变实际逻辑需要重新验证科学算法,例如,这是昂贵/缓慢的,需要最小化。
教科书我意识到答案是使用第一种方法,但在某些情况下,第二种方法可能显着更快。
同样,我知道这有点迂腐,但我真的很想确保我在其重要的地方编写最有效的代码,同时保持一切可读和可维护(并且评论很好)。在此先感谢您就此事提供意见!
答案 0 :(得分:3)
Exception
应该用于例外案例(例如,当出现问题时:找不到文件,连接断开等)。它们非常慢(堆栈跟踪消耗时间)。在你的实现中,关键是一个非常例程的情况:
public bool IsSomethingTrue2(string aString) {
if (null == aString)
return false;
MyType value;
if (!aDictionary.TryGetValue(aString, out value))
return false;
return (null == value)
? false
: null == value.Field
? false
: value.Field.SubField == "someValue";
}
另一个问题是,除非例程有 bug ,否则不应抛出KeyNotFoundException
和NullReferenceException
。调试看起来很困难,请不要把它变成噩梦。
答案 1 :(得分:1)
您似乎同意不使用boneheaded exceptions来控制程序流是正确的事情。
尽管如此,您仍然更喜欢try-catch解决方案,因为它是一种简洁且相对清晰的方式,并且由于缺少空/存在检查而且可能更快,并且异常抛出和处理可能不是那么糟糕{ {3}}
这是一个很好的推理,但我们应该更加小心的时刻:
任何与绩效相关的事情都只有一件事(相对)可靠 - 基准。 衡量一切。
我们不知道dictionary
中确切的数据模式。如果在物业钻探过程中没有任何东西可以抛出异常,那么你可能会有更好的例外。
但实际的空比较/ TryGetValue
方法的成本与异常抛出相比微不足道。
再次 - 根据示例数据对其进行测量,考虑此代码执行的频率,然后再对可接受/不可接受的性能做出任何决定。
没有人会认为此代码比具有大量if
s的代码短。用它犯下任何错误要困难得多。
但是在所有这些可读性背后隐藏着一个谬误 - 这样的try-catch
代码并不完全等同于原始代码。
为什么不完全等效?
Dictionary<String, T>
,而是某些可能存在缺陷的自定义IDictionary
,因此在内部计算过程中会导致NullReferenceException
。 T
对象可能是具有内部对象的代理对象,在某些情况下开始初始化为null,导致NullReferenceException
中的T.SomeProp
。T
可以是另一个Dictionary
中某个对象的代理,但它实际上不存在,因此KeyNotFoundException
。在所有这些情况下,我们都会忽略潜在的缺陷。
你可能会说,在你的特定情况下不会发生 performance-wise 吗?也许,或许不是。您可能决定接受这样的风险,但这种情况并非如此,但这种情况并非不可能,因为系统会发生变化,而这些代码则不然。
总而言之,我宁愿坚持第一个noexcept
解决方案。它有点长,但它完全符合它的说法,在任何情况下都不会有明显更差的性能,并且不会无缘无故地抛出异常。