捕获不同类型的异常
解决的问题
不同的异常类型代表不同的错误。当不同的错误发生时,我们可能希望使用另一个过程让系统恢复到我们想要的状态——我们可以通过在交替的 catch 块中处理不同的异常类型来实现这一点。当我们看到这样的 try-catch 块时:
try
{
// perform actions that may throw an exception
}
catch
{
// perform a non-specific action to recover
}
这个 catch 块的结果实际上是“我不在乎出了什么问题、为什么出错或细节是什么——不管发生了什么,我们都会按照以下方法恢复”。就目前而言,这很好,但不同的异常可能有不同的恢复方案。从尝试容纳太大而无法放入数据类型的值中恢复应该与从无法获取文件句柄中恢复非常不同。即使我们的处理大部分是通用的,知道您无法获取哪个文件句柄,或者我们无法放入数据类型的值是什么不是很好吗?即使只是在调试器中,最好有这种关于出了什么问题的更详细的信息。我们将看看如何实现这一点。
我们的场景
我们第一个目标的价格更新代码运行良好——通过一些诊断,我们确定它每周会间歇性地失败三到四次。但在与我们的网络人员交谈时,他说“这很奇怪……我们进行了一些升级,上周只有一次可能是网络故障。你确定这里没有发生其他事情吗?”经过一些棘手的故障排除后,你会发现问题——有时,服务会将价格返回为$ 30.84 ,而不是30.84。我们的代码目前如下所示:
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch
{
return null;
}
我们的 catch 块捕获两种类型的异常。第一种是我们预期的 - ConnectionFailedException - 当网络出现故障时。第二种是 InvalidCastException - 我们的 priceClient 在尝试将$30.84更改为十进制类型时内部失败。问题只是开头的美元符号 - 如果我们能够将其剪掉,我们就可以更频繁地更新价格并拥有更好的系统。但是我们的 catch 块捕获了每种类型的异常,并且没有给我们单独处理不同问题的灵活性。
类型异常捕获块
如果遇到 ConnectionFailedException,我们希望继续返回 null,但如果遇到 InvalidCastException,我们希望去掉美元符号并返回价格。为了实现这一点,我们将在通用 catch 块上方添加一个新的 catch 块:
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
// handle the bad price
}
catch
{
return null;
}
使用此流程,如果我们得到 ConnectionFailedException,它将跳过我们的 InvalidCastException 块并返回 null,正如我们想要的那样。我们的 priceClient 对象有一个 ReturnedValue 属性,其中包含价格的原始字符串 - 我们所要做的就是使用 Substring 函数修剪第一个字符并将其解析为十进制数,如下所示:
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1);
}
最终结构如下:
public static decimal? GetCurrentPrice()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch
{
return null;
}
}
我们的代码通过返回 null 来处理连接失败,并通过修复数据和执行适当的转换来处理转换问题,从而使我们的系统能够更频繁地更新。
异常变量
您又和 IT 伙伴交谈了 - 他解释说,您调用的服务实际上是一系列负载平衡服务器。因此,您拨打的每次电话都可能打到不同的物理机器上。他确信网络问题发生在池中的特定服务器上,如果我们能告诉供应商哪台服务器出现故障,他们就可以将其取出并修复。与供应商交谈后,他们告诉我们 ConnectionFailedException 上有一个 ServerName 属性。如果我们可以在日志中捕获该值,那就行了 - 但我们还没有用我们的代码捕获异常详细信息。要捕获该详细信息,我们需要做两件事:
- 为 ConnectionFailedException 类型添加特定的异常块
- 添加异常变量:
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
通过在 catch 块中定义异常变量<connectionFailed>,我们可以在 catch 块的范围内访问其 ServerName 属性。我们在下一行捕获该异常,记录详细信息并返回 null。
顶级错误处理程序
在与你的 IT 伙伴交谈时,他指出:“你知道,如果你只捕获连接失败而不是所有问题,服务就会继续崩溃,我们就会更快地发现它。但我想,只有当我们注意到服务崩溃时,这种情况才会发生。”你考虑到这一点并更新了你的代码:
public static decimal? GetCurrentPrice()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
catch
{
return null;
}
}
如果末尾没有通用的 catch 块,如果出现您未考虑到的新异常,该异常将上升到堆栈并使服务崩溃。如果您将整个服务包装在一个通用的 catch 块中,当发生意外异常时,它会向您发送电子邮件吗?或者 Slack 通知,或者其他什么。当这种情况发生时,您现在就会知道存在您未能考虑到的场景,您可以将该场景添加到代码中的正确位置。您需要添加这个顶级错误处理程序并删除末尾的 catch 块:
public static decimal? GetCurrentPrice()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
}
现在,如果发生 ArgumentException、OutofMemoryException 或 NeverThoughtThisCouldHappenException,它将上升堆栈并通知您,以便您可以创建一个考虑新场景的系统。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~