try/catch 与 if/then/else - 具体案例
try/catch vs. if/then/else - Specific Case
在每个人都拥护代码正确性之前,我意识到通常正确的做事方式是不使用 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;
}
}
因此,在上面的示例中,第一个方法检查字典是否包含键,如果包含,则检查字段是否为 null,如果子值 returns true不等于特定值,否则 return 为假。这避免了 try/catch,但每次访问代码时,它都会对字典执行检查以查看密钥是否存在(假设没有底层 caching/etc。-如果有,请告诉我有任何问题),然后检查字段是否为空,然后是我们关心的实际检查。
在第二种方法中,我们使用try/catch并避免了多层控制逻辑。我们立即去 "straight to the point" 并尝试访问有问题的值,如果它以两种已知方式中的任何一种失败,它将 return false。如果代码执行成功,可能returntrue或false(同上)
这两种方法都能完成工作,但我的理解是,如果在大多数情况下没有出错,第一种方法平均会更慢。第一种方法每次都必须检查所有内容,而第二种方法只需要处理出现问题的情况。将有一个额外的 space 用于堆栈上的异常,以及一些用于跳转到不同捕获 blocks/etc 的指令。这应该不会影响正常情况下的性能,除此之外没有任何错误一个堆栈变量,但是,如果我正确理解我在这里阅读的内容:
Do try/catch blocks hurt performance when exceptions are not thrown?
当然,对于这个确切的例子,差异可以忽略不计 - 然而,想象一个复杂的场景,在一个复杂的 if/then/else 树中进行大量检查,而 try/catch 具有一个列表故障条件。我意识到是的,这种代码总是可以分解成更小的部分或以其他方式重构,但为了论证,我们假设除了控制流之外不能改变它(在某些情况下,改变实际逻辑需要重新验证科学算法,例如,costly/slow 需要最小化)。
教科书 我意识到答案是使用第一种方法,但在某些情况下,第二种方法可能 显着 更快。
同样,我知道这有点迂腐,但我真的很想确保在重要的地方编写尽可能高效的代码,同时保持所有内容的可读性和可维护性(以及良好的注释)。在此先感谢您为我提供有关此事的意见!
Exception
s 应该用在 异常 情况下(例如,当出现问题时:找不到文件,连接断开等)。它们非常慢(堆栈跟踪消耗时间)。在你的实现中,密钥的缺失是一个相当routin的情况:
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";
}
另一个问题是 KeyNotFoundException
和 NullReferenceException
不应该被抛出,除非例程有 bug。调试似乎是一个艰难的过程,请不要把它变成噩梦。
您似乎同意不使用 boneheaded exceptions 来控制程序流是正确的事情。
尽管如此,您仍然更喜欢 try-catch 解决方案,因为它是一种简洁且相对清晰的方法,并且由于缺少 null/presence 检查以及异常抛出和处理,它可能会更快可能没那么糟糕 performance-wise.
这是一个很好的推理,但有几个时刻我们应该更加小心:
性能。
对于与性能相关的任何事情,您只能(相对)确定一件事 - 基准测试。 测量一切。
我们不知道 dictionary
中数据的确切模式。如果在 属性 钻取过程中没有任何东西可以抛出异常,那么使用异常可能会更好。
但是与异常抛出相比,实际空值比较/TryGetValue
方法的成本微不足道。
再次 - 根据示例数据对其进行衡量,考虑此代码的执行频率,然后才对 acceptable/unacceptable 性能做出任何决定。
可读性
没有人会争辩说这段代码比有很多 if
的代码短。而且更难出错。
但是所有这些可读性背后隐藏着一个谬误 - 这样的 try-catch
代码并不完全等同于原始代码。
Correctness/equivalence.
为什么不完全等价?
- 因为使用的字典可能不是
Dictionary<String, T>
,而是一些可能有缺陷的自定义IDictionary
,导致在内部计算期间NullReferenceException
。
- 或者存储的
T
对象可能是具有内部对象的代理对象,在某些情况下开始初始化为 null,导致 NullReferenceException
in T.SomeProp
。
- 或者
T
可以是另一个 Dictionary
中某个对象的代理,它实际上不存在,因此 KeyNotFoundException
.
在所有这些情况下,我们都会忽略潜在的缺陷。
这难道不是 movie plot threat 在您的特定情况下不会发生,您可能会说?也许,也许不是。您可能会决定接受这种相当小的风险,但这种情况并非不可能,因为系统会更改,而此代码不会。
总而言之,我宁愿坚持第一个 noexcept
解决方案。它有点长,但它完全按照它说的去做,无论如何不会有明显更差的性能,并且不会无缘无故地抛出异常。
在每个人都拥护代码正确性之前,我意识到通常正确的做事方式是不使用 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;
}
}
因此,在上面的示例中,第一个方法检查字典是否包含键,如果包含,则检查字段是否为 null,如果子值 returns true不等于特定值,否则 return 为假。这避免了 try/catch,但每次访问代码时,它都会对字典执行检查以查看密钥是否存在(假设没有底层 caching/etc。-如果有,请告诉我有任何问题),然后检查字段是否为空,然后是我们关心的实际检查。
在第二种方法中,我们使用try/catch并避免了多层控制逻辑。我们立即去 "straight to the point" 并尝试访问有问题的值,如果它以两种已知方式中的任何一种失败,它将 return false。如果代码执行成功,可能returntrue或false(同上)
这两种方法都能完成工作,但我的理解是,如果在大多数情况下没有出错,第一种方法平均会更慢。第一种方法每次都必须检查所有内容,而第二种方法只需要处理出现问题的情况。将有一个额外的 space 用于堆栈上的异常,以及一些用于跳转到不同捕获 blocks/etc 的指令。这应该不会影响正常情况下的性能,除此之外没有任何错误一个堆栈变量,但是,如果我正确理解我在这里阅读的内容: Do try/catch blocks hurt performance when exceptions are not thrown?
当然,对于这个确切的例子,差异可以忽略不计 - 然而,想象一个复杂的场景,在一个复杂的 if/then/else 树中进行大量检查,而 try/catch 具有一个列表故障条件。我意识到是的,这种代码总是可以分解成更小的部分或以其他方式重构,但为了论证,我们假设除了控制流之外不能改变它(在某些情况下,改变实际逻辑需要重新验证科学算法,例如,costly/slow 需要最小化)。
教科书 我意识到答案是使用第一种方法,但在某些情况下,第二种方法可能 显着 更快。
同样,我知道这有点迂腐,但我真的很想确保在重要的地方编写尽可能高效的代码,同时保持所有内容的可读性和可维护性(以及良好的注释)。在此先感谢您为我提供有关此事的意见!
Exception
s 应该用在 异常 情况下(例如,当出现问题时:找不到文件,连接断开等)。它们非常慢(堆栈跟踪消耗时间)。在你的实现中,密钥的缺失是一个相当routin的情况:
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";
}
另一个问题是 KeyNotFoundException
和 NullReferenceException
不应该被抛出,除非例程有 bug。调试似乎是一个艰难的过程,请不要把它变成噩梦。
您似乎同意不使用 boneheaded exceptions 来控制程序流是正确的事情。
尽管如此,您仍然更喜欢 try-catch 解决方案,因为它是一种简洁且相对清晰的方法,并且由于缺少 null/presence 检查以及异常抛出和处理,它可能会更快可能没那么糟糕 performance-wise.
这是一个很好的推理,但有几个时刻我们应该更加小心:
性能。
对于与性能相关的任何事情,您只能(相对)确定一件事 - 基准测试。 测量一切。
我们不知道 dictionary
中数据的确切模式。如果在 属性 钻取过程中没有任何东西可以抛出异常,那么使用异常可能会更好。
但是与异常抛出相比,实际空值比较/TryGetValue
方法的成本微不足道。
再次 - 根据示例数据对其进行衡量,考虑此代码的执行频率,然后才对 acceptable/unacceptable 性能做出任何决定。
可读性
没有人会争辩说这段代码比有很多 if
的代码短。而且更难出错。
但是所有这些可读性背后隐藏着一个谬误 - 这样的 try-catch
代码并不完全等同于原始代码。
Correctness/equivalence.
为什么不完全等价?
- 因为使用的字典可能不是
Dictionary<String, T>
,而是一些可能有缺陷的自定义IDictionary
,导致在内部计算期间NullReferenceException
。 - 或者存储的
T
对象可能是具有内部对象的代理对象,在某些情况下开始初始化为 null,导致NullReferenceException
inT.SomeProp
。 - 或者
T
可以是另一个Dictionary
中某个对象的代理,它实际上不存在,因此KeyNotFoundException
.
在所有这些情况下,我们都会忽略潜在的缺陷。
这难道不是 movie plot threat 在您的特定情况下不会发生,您可能会说?也许,也许不是。您可能会决定接受这种相当小的风险,但这种情况并非不可能,因为系统会更改,而此代码不会。
总而言之,我宁愿坚持第一个 noexcept
解决方案。它有点长,但它完全按照它说的去做,无论如何不会有明显更差的性能,并且不会无缘无故地抛出异常。