如何调度异常以便以集中,用户友好的方式处理错误处理和诊断?
例如:
正确的错误处理策略取决于异常的类型和正在尝试的操作。 (在间歇性信号上,重试X次然后告诉用户;在驱动程序错误时,记录错误并重新启动驱动程序;等等)如何调用此错误处理策略?
catch
块:由于从许多不同的地方调用DataHW类,因此每个调用站点都必须复制每个catch
块。这看起来很糟糕。catch
块,使用基于RTTI的ExceptionDispatch
语句调用某些switch
函数:RTTI和switch
通常表示无法应用OO设计,但这似乎是最不好的选择。答案 0 :(得分:6)
通过捕获(...)并调用重新抛出和分派的共享处理函数,避免在每个调用站点复制catch块:
f()
{
try
{
// something
}
catch (...)
{
handle();
}
}
void handle()
{
try
{
throw;
}
catch (const Foo& e)
{
// handle Foo
}
catch (const Bar& e)
{
// handle Bar
}
// etc
}
答案 1 :(得分:2)
我一直在考虑的一个想法是异常应该由可以处理它们的级别捕获。例如,CRC错误可能被传输数据的函数捕获,并且在捕获此异常时,它可能会尝试重新传输,而“无信号”异常可能会被捕获到更高级别并丢弃或延迟整个操作
但我的猜测是,大多数这些异常都会被同一个函数捕获。 是一个好主意,可以单独捕获和处理它们(如soln#2),但是你说这会导致很多重复的代码(导致解决#3。)
我的问题是,如果要复制很多代码,为什么不把它变成函数呢?
我正在考虑......
void SendData(DataHW* data, Destination *dest)
{
try {
data->send(dest);
} catch (CRCError) {
//log error
//retransmit:
data->send(dest);
} catch (UnrecoverableError) {
throw GivingUp;
}
}
我想这就像你的ExceptionDispatch()函数,只是代替switch
异常类型,它会包装异常生成调用本身和catch
异常。
当然,这个函数过于简单了 - 你可能需要一个围绕DataHW的整个包装类;但我的观点是,最好有一个集中点来处理所有DataHW异常 - 如果类的不同用户处理它们的方式是相似的。
答案 2 :(得分:1)
我认为有三种方法可以解决这个问题。
为每个可以抛出异常处理异常的函数编写一个包装函数。然后,所有调用者调用该包装器,而不是原始投掷函数。
另一个解决方案是采用更通用的方法并编写一个函数来获取函数对象并处理所有异常。这是一些例子:
class DataHW {
public:
template<typename Function>
bool executeAndHandle(Function f) {
for(int tries = 0; ; tries++) {
try {
f(this);
return true;
}
catch(CrcError & e) {
// handle crc error
}
catch(IntermittentSignalError & e) {
// handle intermittent signal
if(tries < 3) {
continue;
} else {
logError("Signal interruption after 3 tries.");
}
}
catch(DriverError & e) {
// restart
}
return false;
}
}
void sendData(char const *data, std::size_t len);
void readData(char *data, std::size_t len);
};
现在,如果你想做某事,你可以这样做:
void doit() {
char buf[] = "hello world";
hw.executeAndHandle(boost::bind(&DataHW::sendData, _1, buf, sizeof buf));
}
由于您提供了功能对象,因此您也可以管理状态。假设sendData更新len,以便它知道读取了多少字节。然后,您可以编写读取和写入的函数对象,并维护到目前为止读取的字符数。
第二种方法的缺点是你不能访问throw函数的结果值,因为它们是从函数对象包装器中调用的。没有简单的方法来获取函数对象绑定器的结果类型。一种解决方法是在执行函数对象成功后编写由executeAndHandle调用的结果函数对象。但是,如果我们在第二种方法中投入太多工作只是为了完成所有的内务工作,那么结果就不值得了。
还有第三种选择。我们可以将两个解决方案(包装器和函数对象)结合起来。
class DataHW {
public:
template<typename R, typename Function>
R executeAndHandle(Function f) {
for(int tries = 0; ; tries++) {
try {
return f(this);
}
catch(CrcError & e) {
// handle crc error
}
catch(IntermittentSignalError & e) {
// handle intermittent signal
if(tries < 3) {
continue;
} else {
logError("Signal interruption after 3 tries.");
}
}
catch(DriverError & e) {
// restart
}
// return a sensible default. for bool, that's false. for other integer
// types, it's zero.
return R();
}
}
T sendData(char const *data, std::size_t len) {
return executeAndHandle<T>(
boost::bind(&DataHW::doSendData, _1, data, len));
}
// say it returns something for this example
T doSendData(char const *data, std::size_t len);
T doReadData(char *data, std::size_t len);
};
技巧是return f();
模式。即使f返回void,我们也可以返回。这最终将成为我的最爱,因为它允许两者将句柄代码集中在一个地方,但也允许在包装函数中进行特殊处理。您可以决定是否最好将其拆分并创建一个具有该错误处理函数和包装器的自己的类。可能这将是一个更清洁的解决方案(我想到Separation of Concerns这里。一个是基本的DataHW功能,一个是错误处理)。
答案 3 :(得分:1)
也许你可以为DataHW类编写一个包装类? 包装器将提供与DataHW类相同的功能,但也包含所需的错误处理代码。好处是您可以在一个地方使用错误处理代码(DRY原则),并且可以统一处理所有错误。例如,您可以将所有低级I / O异常转换为包装器中的更高级别异常。 基本上防止向用户显示低级异常。
正如巴特勒兰普森所说:计算机科学中的所有问题都可以通过另一层间接来解决