将代码包装在 Try-Catch 块中
解决的问题
在编码过程中,会发生意想不到的事情——尤其是在处理我们无法控制的系统和代码时;这种情况几乎总是会发生。当发生意外情况时,这被称为异常——我们编写的代码是规则,而发生的问题就是异常。我们通过结构化错误处理来控制发生异常时会发生什么——在 C# 中,结构化错误处理的主要机制是 try-catch 块。
创建可靠的结构化错误处理需要想象力。弗兰克·博尔曼将阿波罗 1 号事故(一场导致三名宇航员丧生的可怕火灾)的原因描述为“想象力的失败”——一系列意想不到的情况——异常情况——出现了,没有人能解释或预见到。当我们编写代码时,我们必然会想象一条幸福的道路,其中我们所有的前提和计划都顺利进行。因为事情会崩溃,因为事情没有按计划进行,我们必须试着想象它们会如何崩溃,这样我们才能使我们的系统健壮并能够从这些异常中恢复过来。
我们的场景
考虑一个依赖于偶尔更新价格的应用程序——也许我们有一个存储价格的缓存文件,并且我们定期连接到互联网上的 REST 服务来更新价格。当一切正常时,我们的网络更新服务就可以正常工作。但是服务器的网络连接不可靠——每隔几天,就会出现网络流量风暴,与服务的连接就会失败,并引发异常。由于我们的代码没有结构化的错误处理,当发生此失败连接异常时,我们的更新服务就会崩溃。
我们的代码创建一个 PriceClient 对象,连接到远程服务器,并调用一个函数来检索价格:
public class ServiceUpdater
{
public static decimal GetCurrentPrice()
{
var priceClient = new PriceClient();
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
}
只要这一行代码正确运行,一切都会正常进行:
priceClient.Connect();
但是,正如我们提到的,偶尔会失败并抛出异常。由于没有结构化的错误处理来捕获异常,异常会通过调用堆栈上升,直到耗尽并导致应用程序崩溃。
我们现在知道了这个问题——我们不必想象——我们当然可以想象它会再次发生。让我们将此代码包装在 try-catch 块中:
public static decimal GetCurrentPrice()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
}
catch
{
}
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
我们想象中可能存在问题的代码,即对 Connect() 的调用,现在被包装在 try 块中。catch 块内的代码中发生的任何异常都将立即转移到 catch 块中的代码,我们可以在其中指定如果调用失败我们希望发生什么。因此,如果 priceClient.Connect() 在尝试连接时抛出 ConnectionFailedException,则将处理该异常,并且代码的执行将继续而不会崩溃。但是对于这样的代码,它不会持续很长时间。
Try-Catch 和变量范围
这种简单方法的问题在于,如果对 Connect() 的调用失败,则意味着服务未连接 - 我们无法获取价格,而这正是我们接下来要尝试做的事情:
var updatedPrice = priceClient.GetPrice();
因此,看起来我们也需要将该行移到 try 块内:
public static decimal GetCurrentPrice ()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
}
catch
{
}
return updatedPrice;
}
到目前为止一切顺利。但是当我们尝试编译它时,我们收到以下错误:
当前上下文中不存在名称“updatedPrice”。
因为我们在 try 块内声明了 updatedPrice -
var updatedPrice = priceClient.GetPrice();
- 它不可用于范围之外。如果我们尝试在 catch 块内返回价格,我们也会遇到同样的问题。以下是关键点:
在 try 或 catch 块内声明的变量是块范围内的局部变量。
这是我们必须解决的问题:解决我们想要 updatedPrice 变量的作用域究竟如何工作的问题。它还促使我们考虑当调用失败时我们到底想要发生什么,以及我们想要如何向系统的其余部分表示这一事实。默认方式是我们一开始采用的方式,只是将异常抛出堆栈并导致应用程序崩溃 - 这不是我们想要的。
我们可以处理这种情况的一种方法是将更新价格的失败尝试表示为空结果。我们的方法目前返回的是 decimal 类型 - decimal 是一种值类型,因此它本质上不是可空的。为了使其可空,我们添加了一个问号:
public static decimal? GetCurrentPrice ()
这实质上意味着“此函数要么返回小数,要么可能为空。”因此,用简单的英语或伪代码来说,我们的函数的工作原理如下:
- 创建客户端对象。
- 尝试连接到该服务。
- 如果尝试失败,则停止并返回 null。
- 如果成功,则获取价格并返回。
我们将整个顺利路径(所有事情都按我们预期进行)移到 try 块内,并将不顺利路径(如果出现错误会发生什么情况)移到 catch 块内:
public static decimal? GetCurrentPrice ()
{
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch
{
return null;
}
}
现在,只要try 块之外的代码(创建 priceClient 对象的调用)不引发任何异常,我们的 GetCurrentPrice 调用就永远不会崩溃。
使用新的空值
重要的是要考虑到,现在函数返回值的含义已经发生了轻微变化,从纯十进制值变为可空十进制值,使用该值的代码也必须进行更改,以便知道当返回空值时该做什么。使用 GetCurrentPrice 的旧代码可能如下所示:
var updatedPrice = ServiceUpdater.GetCurrentPrice();
SaveUpdatedPriceToCache(updatedPrice);
为了让系统知道如果价格为空我们不想更新价格,我们可以将其更改为:
var updatedPrice = ServiceUpdater.GetCurrentPrice();
if (updatedPrice != null)
{
SaveUpdatedPriceToCache(updatedPrice);
}
现在,如果获取价格的尝试失败,代码将不会执行任何操作——这正是我们想要的。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~