为什么抛出一个非例外被认为是糟糕的设计?

时间:2013-12-09 20:01:02

标签: c++ exception

我有一些代码来对对象的矢量进行排序。如果任何对象无效,我想立即停止排序并报告错误。在错误中,我想要包含一个无效对象的描述(无论哪一个有多少)。

这是我的代码(不完整,但我希望你能跟着我):

int sortProc(const Bulk & b1, const Bulk & b2) {
    if (!b1.isValid()) throw b1;
    if (!b2.isValid()) throw b2;
    return b1.compareTo(b2);
}

vector<Bulk> * items = getItems();
try {
    sort(items->begin(), items->end(), sortProc);
} catch (const Bulk & item) {
    cout << "Cannot sort item: " << item.description();
}

现在,我对我的代码有点不确定,因为我听说所有异常应该是异类的子类,并且抛出不是异常实例的对象被认为是不好的做法,但我真的不明白为什么。我上面的代码有效,它有什么问题吗?这是一个严肃的问题,所以如果你看到任何问题,我会很高兴知道。我不是在寻找道德问题。

6 个答案:

答案 0 :(得分:7)

  

我不是在寻找道德问题。

你不能问一个风格问题然后根据“道德问题”禁止所有答案,如果你希望弄明白的话。

有些人认为只抛出派生std::exception类型的对象可以提供界面的一致性,因为你可以在所有这些对象上调用.what()并在程序的顶层一起捕获它们。您还可以保证其他翻译单元 - 那些从未听说过您的课程Bulk的人 - 能够在他们想要的情况下捕获异常(如果仅作为std::exception })。

  • 您的程序是否错误?否

  • 有效吗?是的,当然可以。

但有时单纯“工作”还不够,我们希望对事情更加整洁。

真的是......

答案 1 :(得分:3)

  

为什么抛出一个非常规被认为是糟糕的设计?

因为真空系统很少存在。

例外有一个基本目的:生成有关错误或其他“异常”条件的信息包,并跨越边界传播该信息,而不考虑这些边界的位置。

仔细考虑最后一部分:

  

,不考虑这些边界的位置。

您可以将例外情况视为必须以某种方式处理的情况。如果有一段代码可以处理它,那么该代码应该有机会。如果没有可以处理它的代码,那么程序必须死掉。

在这种情况下,描述异常情况的信息包必须可以自由地流经程序的任何部分 - 即使是那些你没有亲自编写的部分,或者甚至认为有一天你的项目是只是你眼中的微光。但是,为了使其工作,必须使用熟悉的协议编写所有异常。也就是说,远程异常处理程序必须至少能够理解数据包中包含的基本信息。每个人都必须说同一种语言。

通常在C ++中完成此操作的方法是从std::exception派生所有异常对象。通过这种方式,代码中遥远的部分中的处理程序 - 甚至可能甚至从未编写代码的代码 - 至少可以在程序遇到它的消亡之前报告异常情况。它甚至可能能够处理这种情况并允许程序继续存在,但这通常是不可能的。

答案 2 :(得分:1)

这不是道德问题。这与功能无关。这也不是正确性。这是关于代码背后的逻辑。

记住代码反映了您的想法。一个头脑清醒的开发人员永远不会抛出“异常”,这确实不是一个例外,因为它只会混淆逻辑是什么。

Joker_vD关于代码的生产力也是正确的,但我认为你还没有。

答案 3 :(得分:1)

我看到一个(可能是严重的)问题,有批量传输输入和故障数据。 使用BulkException派生形式std :: exception更清洁了!

虽然有一个表明失败的状态是好的,但我认为使用该类传输失败并不好。

BulkException可以收集对正常操作无用的附加信息(如堆栈跟踪)。

答案 4 :(得分:0)

这里的代码与你的代码非常相似,只是它没有随机访问流控制:

std::unordered_set<std::string> errors;
auto sortProc = [&errors](const Bulk & b1, const Bulk & b2)->int {
  if (!b1.isValid()) errors.insert(b1.description());
  if (!b2.isValid()) errors.insert(b2.description());
  if (!b1.isValid()||!b2.isValid())
    return b1.isValid()<b2.isValid();
  return b1.compareTo(b2);
}

vector<Bulk> * items = getItems();
sort(items->begin(), items->end(), sortProc);
if (!errors.empty()) {
  std::cout << "Unsortable items found while sorting:\n";
  for (auto const& e : errors) {
    std::cout << e << "\n";
  }
}

除了特殊情况外,我更喜欢避免随机访问流量控制。 sort函数可能会继续忽略元素为isValid的失败,因此默认情况下它应该是基于接口解耦的基于异常的流控制。

虽然很有可能使用异常作为返回“第二个返回值”的方法,但结果是代码的界面变得模糊,并且与您的代码和使用代码的代码的实现细节相结合。

根据程序的不同,真正特殊的情况可能会有所不同,从“无法分配内存”到“硬盘故障”,在这种情况下无法恢复或让您的代码无法发现错误。

请注意,您甚至可以通过注意首先或最后排序无效元素来使上述代码更少耦合。然后在排序之后,检查您的列表,并检查是否存在已在其中收集的无效元素,而不是依赖于累积错误的日志。

答案 5 :(得分:0)

好的设计是关于质量的。您的代码是否可证明是正确的?它是否最小化对外部对象的依赖?它可以重复使用吗?这可以理解吗?它是否消除了冗余?你能测试一下吗?其他人可以阅读并知道它的作用吗?最后一个问题真的很难,因为你不是其他人 - 你是作者。