从例外到用户错误消息

时间:2016-10-03 20:45:07

标签: c++ exception error-handling

当我们在学校学习时,用户数据验证通常被公然忽略。我自学了使用异常机制验证数据,但是当我尝试打印有用的用户消息时,从异常中做到这一点感觉非常笨拙。我尝试的越多,我觉得异常意味着#34;在代码内部#34;它们不应该"逃避"到最终用户。

我们举一个简单的例子。用户需要输入命令。格式可以是:cmd i jcmd,其中cmd是一组预定义的命令以及ij个数字。然后我们执行该命令。

我所拥有的,从下到上是:

// utility function, converts string to enum
str_to_enum(string str, map<enum, string> s) -> enum
   // may throw std::invalid_argument{"cannot convert " + str + " to enum"}

parse_line(string line)
   tokens = split(line)

   Cmd cmd = tokens[0]; // calls str_to_enum, lets the exception pass by 

   if (tokens.empty())
      throw std::invalid_argument{"empty string as command"s};

   if (...)
      throw std::invalid_argument{cmd.spelling() + " does not take any argument."};

   if (...)
      throw std::invalid_argument{cmd.spelling() + " takes exactly 2 arguments.};

  str_to_int(tokens[1])
  str_to_int(tokens[2]) // throws std::out_of_range


main_loop() {
    get_line(line);

    try {
        Full_cmd full_cmd = line; // calls parse_line, may throw

        if (...)
            throw std::invalid_argument{"Coordinates out of range"};

        try {
            execute_cmd(full_cmd); // may throw      
        }
        catch (std::exception& e) {
             cerr << "error executing command:" << endl;
             cerr << e.what() << endl << endl;
        }
    }
    catch (const std::exception& e) {
        cerr << "Invalid command '"s << line << "': " << endl;
        cerr << e.what() << endl;
    }
}

我认为可以很容易地遵循上述逻辑。

现在,如果我想只显示神秘的&#34;无效命令&#34;和&#34;执行命令错误&#34;那一切都很容易。但我想要有意义的信息,就像我上面尝试的信息一样。一个问题是e.what没有感觉到适当的含义。它包含简洁,通常是技术细节。另一个问题是,例如错误&#34;无法转换为枚举&#34;到达用户。虽然他得到了他输入的真实字符串,但枚举部分是实现细节并且对他有害。

我看到的是2个解决方案:

  1. 使用std :: exception错误。

    • 几乎可以在任何步骤中捕获错误,并使用重写的消息重新抛出。例如。来自&#34;无法转换为枚举&#34;到&#34;未找到命令&#34;。
    • 在最顶层抓住std::exception并基本打印e.what()
  2. 为每种类型的错误创建例外类(例如invalid_commandinvalid_no_args等。

    • 如有必要,请向其添加更多信息(其他数据成员)以获得错误的完整上下文
    • 一旦抛出异常,它就会冒泡到最高层
    • 然后在最顶层,捕获每个自定义异常类型并相应地打印。
  3. 我显然采用了第一种方法。最大的缺点是用户获得的信息不完整。

    对于第二个,我觉得与收益的比例不成比例。为了创建自定义异常类,会有很多无聊的冗余工作。我尝试了一次,放弃了7个几乎相同的类,每个配备齐全的自定义数据成员和构造函数以及what方法(设置和使用这些成员)

    此外,无法帮助,但感觉我正在重新发明轮子。

    我的问题是:例外情况只是向最终用户传达错误消息的好工具吗?如果是这样,这样做的正确方法是什么?如果没有,怎么办呢?

2 个答案:

答案 0 :(得分:2)

第一种方法不是正确的方法,原因有两个:

  1. 正如您所目睹的那样,它代表了大量的工作。

  2. 它假定程序员可能设法将异常对象编码为可能对用户有意义的错误消息。他们不能;它不会。如果不是出于任何其他原因,那么至少从统计学角度来看,程序员编写消息的语言不太可能是用户理解的语言。 (我的意思是这主要是从语言学的角度来看,虽然“技术水平”的立场也值得考虑。)

  3. 第二种方法是正确的方向,但有一些修改:

    1. 通过抢先检查错误并显示错误消息(例如“我不会允许您这样做”)而不是“您所做的事情是坏的,而不是”等消息,大大减少系统可能抛出的异常数量失败”。

    2. 通过使每个例外仅代表“未能[执行此级别尝试做的事情] ,大大减少必须在系统的每个级别定义的异常类的数量因为 这样的例外是由“以下”引发的。通过将“因果”异常的引用存储到新异常中,您可以避免在每个级别编写大量新异常,包含大量成员变量等。

    3. 意识到异常中的人类可读错误消息完全没用:永远不要写这样的消息,也不要向用户显示这样的消息。 异常消息是异常的类名。 如果您有RTTI(运行时类型信息),请使用它。否则,只需确保每个异常对象都知道自己的类的名称。这只适合你的眼睛,当你看到它时,你知道明确它是什么例外。

    4. 在系统的顶层,向用户显示有关您可以测试的异常的错误消息。是的,这意味着您必须能够设置系统以便实际抛出异常,以便您的测试代码可以确保捕获到异常并发出正确的错误消息。

      < / LI>
    5. 对于无法测试的例外情况,请不要打扰显示消息。只是显示一个通用的“出错的地方”错误(也可能是一个很好的幽默图像),并将尽可能多的信息附加到应用程序的日志中,以便稍后进行取证分析。

答案 1 :(得分:1)

<强> TL; DR

  

我的问题是:例外情况只是向最终用户传达错误消息的好工具吗?如果是这样,这样做的正确方法是什么?如果没有,怎么办呢?

是的,例外仅仅是为了向客户传达错误/执行条件。

它们并不是要与catch()final语句一起引入任何程序控制流程。

  

最大的缺点是用户得到的消息不完整。

您可以使用std::runtime_error例外提供有用的消息。