通常我发现自己处于一种情况,我必须处理捕获接口实现者抛出的一些异常。当不同的实现处理完全不同类型的设备等时,这通常会产生问题:根据实现,抛出的异常的数量和类型会有很大差异。这是一个典型的例子:
interface DataStream
{
Data ReadNext();
}
class DeserializingFileDataStream : DataStream
{
//this one does file operations + serialization,
//so can throw eg IOException, SerializationException, InvalidOperationException, ...
}
class NetworkDataStream : DataStream
{
//get data over tcp
//so throws IOException, SocketException
}
class HardwareDeviceDataStream : DataStream
{
//read from a custom hardware device implemented in unmanaged code
//so throws mainly custom exceptions
}
可能所有这些也会抛出ArgumentExceptions等等,但我对捕获它们并不感兴趣:在这种情况下,它们会指示编程错误。但是,当文件损坏,网络电缆拔出或自定义设备发狂时,我不希望程序崩溃,因此应该处理其他异常。
我已经尝试了几种解决方案但是我没有特别满意,所以问题是:是否有一些常见的做法/模式来处理这样的情况?请具体,不要告诉我'看ELMAH'。这是我过去使用过的一些东西:
catch( Exception )
很明显,问题是什么TryAndCatchDataStream( Action what, Action<Exception> errHandler )
方法,包含一个try,后跟一个catch,用于任何实现的任何感兴趣的异常。这意味着必须在实现更改或添加/删除时更新它。然后我有一个关于异常处理的第二个更普遍的问题(不要认为需要为此单独发布):我有一些情况,方法A调用B调用C调用D,D抛出一个SomeException A的调用者捕获异常。这不是代码有问题吗?因为能够做到这一点,A的调用者需要知道A最终将调用D.除非A文件它可以抛出SomeException;但在这两种情况下,这意味着当更新A的实现细节(即使其调用除D之外的其他内容)时,A的用户可以看到此更改。
答案 0 :(得分:4)
没有办法知道&#34;什么是未知的例外。您所能做的就是捕捉您所知道的例外情况,并记录您不知道的情况并重新抛出它们。
你可以捕获异常,然后在单独的程序集中调用一个方法来确定如何处理excpetion。然后,您可以根据需要更新程序集,而无需更新其余代码。
关于第二个问题,A需要记录任何可能从中冒出的异常,包括依赖对象抛出的异常。 B需要记录它抛出的异常,因此A可以了解它们等等。
如果更改A以执行导致更改异常的更改,则必须更改文档。从调用应用程序的角度来看,它所知道的只是A. A可以做任何事情,并且调用应用程序并不关心它是否是B,C或D.A负责任何冒泡的内容来电者。
这样想。假设您聘请了一家建筑公司为您建造房屋。反过来,他们雇用分包商。那些Sub-Contracots可能会雇用自己的劳工。如果底层劳工搞砸了,那么你雇佣的承包商最终会有错,无论是否有其他公司雇佣他们。你不在乎,你只是希望你的房子符合规格要求,而且建筑公司也是可以接受的。
答案 1 :(得分:3)
您可以使用抽象类而不是接口在基类中包含各种实现中的异常,以便能够以通用方式包装基础异常。这样,您就可以通过一个中心点来更改异常包装和分析逻辑。一些例外情况可能很严重,但仍然会导致您的流程终止,例如OutOfMemoryExcepptions,它很可能不再安全。
如果你坚持使用接口,你应该等到接口可以有基本实现的未来CLR版本。有一段Vance Morrison的视频谈论这个功能,但我再也没有听说过。
如果您不能等待CLR vNext,您还可以选择创建一个包装类(让我们称之为DataStreamReader),它采用IDataStream接口实现IDataStream,但可以根据需要进行包装。如果你在你只读的代码中注意使用DataStreamReader实例,你应该没问题。
该设计强制任何继承者实现ReadNextImpl方法,其中您的消费者确实实现了他们的逻辑。你可以像之前一样调用ReadNext,并使用当前的简单实现来获取DataStreamException。这是我能想到的最简单的解决方案。
class Data { }
public class DataStreamException : Exception
{
public DataStreamException(string message, Exception inner)
: base(message, inner)
{ }
}
abstract class DataStream
{
protected abstract Data ReadNextImpl();
public Data ReadNext()
{
try
{
return ReadNextImpl();
}
catch (Exception ex)
{
throw new DataStreamException("Could not read from stream. See inner exception for details.", ex);
}
}
}
class DeserializingFileDataStream : DataStream
{
protected override Data ReadNextImpl()
{
throw new NotImplementedException();
}
}
class NetworkDataStream : DataStream
{
protected override Data ReadNextImpl()
{
throw new Exception();
}
}
答案 2 :(得分:1)
从根本上说,捕获异常的代码需要知道很多事情:
不幸的是,许多语言和框架(包括C ++,Java和.net)尝试使用一种相当笨重的信息(抛出的异常对象的类型)来传达所有三条信息,即使在实践中它们也是如此。通常很大程度上正交。这种设计的一个特别值得注意的困难是,可能同时出现多个异常情况,这样适用于其中任何一个的处理程序应该运行,但异常应继续向上运行,直到适用于所有这些处理程序的处理程序运行。 / p>
尽管基于类型的设计存在局限性,但必须使用它。在设计一个自己的新类/接口以及从它们泄漏的异常时,应该尝试使用不同的异常类型来回答上面的问题#3-#4,因为这些是调用者最感兴趣的问题。我进一步建议,如果从方法中抛出的异常可能使类处于无效状态,可能使用类似的模式,那么类可能需要具有状态变量或标志,该状态变量或标志可以无效。
... before doing anything that might cause object to enter invalid state even temporarily ... if (state != MyClassState.HappyIdle) throw new StateCorruptException(....); state = MyClassState.UnhappyOrBusy; ... manipulate state in a fashion that will end up with a valid state when complete ... state = MyClassState.HappyIdle;
如果一直使用这样的模式,调用者就不必过分担心导致异常的操作可能使对象处于损坏状态,以致尝试继续操作可能导致进一步的数据丢失。如果对象被破坏但是调用代码忽略了发生这种情况时发生的异常,那么进一步尝试使用该对象将会彻底失败。
不幸的是,许多班级使用这样的警卫并没有得到很好的保护,所以假设未知的例外是“无害的”可能并不安全。另一方面,从实际角度来看,没有任何既健全又安全的方法。要么冒一个人的代码不必要地为一个事实上无害的异常而冒险,要么冒着腐败的数据结构失控和破坏其他数据结构的风险。在设计得更好的例外系统中,会有更好的替代方案,但不存在存在的系统。
答案 3 :(得分:0)
您可以实现自己的自定义异常。如果接口捕获自己的异常并抛出自定义异常,同时为内部设置真正的异常,请执行每个实现。根据你的情况,这可能没问题。
因为例外应该是例外。这样,您至少可以知道是否捕获了实现知道要注意的异常。
不确定这是不是一个好主意。我以为我会提出这个主意。
答案 4 :(得分:0)
Java迫使你声明你抛出了哪些异常,这是一个真正令人讨厌的痛苦,只有很少的好处 - 通常是因为在特殊情况下会抛出异常,并且很难规划这些异常。
Java程序员经常最终做的事(在被教导不使用throws Exception
之后)是你的第三种选择。第三个选项对于您不希望调用代码知道如何处理的异常有意义 - 如果您希望调用代码处理它们(比如,在IOException或NetworkException的情况下重复三次),您最好坚持下去人们已经知道的事情。
因此,简而言之,我建议您捕获您知道如何处理调用代码的异常,并报告您无法处理和退出(或操作失败)的异常。