bol*_*lov 5 c++ error-handling exception
当我们在学校学习时,用户数据验证最常被公然忽视。我自学了使用异常机制验证数据,但是当我尝试打印有用的用户消息时,从异常中执行此操作感觉非常笨拙。我尝试的越多,我就越觉得异常是针对“代码内部”的,它们不应该“逃逸”给最终用户。
我们举一个简单的例子。用户需要输入命令。格式可以是:cmd i j或cmd使用cmd一组预定义的命令i和j数字。然后我们执行该命令。
我所拥有的,从下到上是这样的:
// 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;
}
}
Run Code Online (Sandbox Code Playgroud)
我认为上面的逻辑很容易遵循。
现在,如果我只想显示神秘的“无效命令”和“错误执行命令”,那么一切都会很容易。但我想要有意义的消息,就像我上面试过的那样。一个问题是e.what感觉不到合适的容器。它包含简洁的,通常是技术细节。另一个问题是,例如错误“无法将 <input_string> 转换为枚举”到达用户。虽然他得到了他输入的真实字符串,但枚举部分对他来说是实现细节和批评。
我看到的是2个解决方案:
使用 std::exception 错误。
std::exception并基本上打印e.what()为每种类型的错误(例如invalid_command,invalid_no_args等等)创建异常类。
我显然采用了第一种方法。最大的缺点是用户获得的信息不完美。
对于第二个,我觉得努力与收获不成比例。会有很多无聊的多余的工作只是为了创建自定义异常类。我尝试过一次并在第 7 个几乎相同的类之后放弃了每个完全配备的自定义数据成员和构造函数和what方法(设置和使用这些成员)
此外,忍不住觉得我在重新发明轮子。
我的问题是:异常是否仅仅是向最终用户传达错误消息的好工具?如果是这样,正确的做法是什么?如果没有,怎么办?
第一种方法不是正确的方法,原因有二:
正如您所看到的,它代表了大量的工作。
它假定程序员可能设法将可能对用户有意义的错误消息编码到异常对象中。他们不能;不会。如果不是出于任何其他原因,那么至少从统计上来说,程序员编写消息所用的语言不太可能是用户理解的语言。(我的意思主要是从语言的角度来看,尽管“技术水平”的角度也值得考虑。)
第二种方法是正确的方向,但有一些修改:
通过先发制人地检查错误并显示诸如“我不允许你这样做”之类的错误消息而不是诸如“你所做的事情很糟糕,但失败了”之类的消息,大大减少了系统可能引发的异常数量。
通过使每个异常简单地代表“未能[做这个级别试图做的事情],因为某个级别抛出了某某异常,从而大大减少了必须在系统的每个级别定义的异常类的数量以下”。通过将“因果”异常的引用存储到新异常中,您不必在每个级别编写大量新异常,以及大量成员变量等。
意识到异常中的人类可读的错误消息是完全没有用的:永远不要写这样的消息,永远不要向用户显示这样的消息。 异常的消息是异常的类名。 如果您有 RTTI(运行时类型信息),请使用它。否则,只需确保每个异常对象都知道它自己的类的名称。这只是你的眼睛,当你看到它时,你会毫不含糊地知道它是哪个例外。
在系统的顶层,仅向用户显示有关您可以测试的异常的错误消息。是的,这意味着您必须能够设置系统以便实际抛出异常,以便您的测试代码可以确保捕获异常并发出正确的错误消息。
对于您无法测试的异常,甚至不要费心显示消息。只需显示一个通用的“出现问题”的错误(可能还有一个很好的幽默图像),并将尽可能多的信息附加到应用程序的日志中,以便稍后进行取证分析。
| 归档时间: |
|
| 查看次数: |
768 次 |
| 最近记录: |