问题:
长期以来,我对这种exceptions
机制感到担心,因为我觉得它并不能真正解决应有的问题。
要求:关于该主题的争论很长时间,而且大多数人都在努力比较exceptions
和返回错误代码。这绝对不是这里的主题。
尝试定义错误,我同意Bjarne Stroustrup和Herb Sutter的CppCoreGuidelines
错误表示该功能无法实现其广告目的
要求:该exception
机制是用于处理错误的语言语义。
要求:对我来说,没有“没有借口”的功能不能完成任务:要么我们错误地定义了前后条件,所以该功能无法确保结果,或者某些特殊情况对于花时间在开发上没有足够重要的意义。一个办法。考虑到IMO,常规代码和错误代码处理之间的区别(在实施之前)是非常主观的。
要求:使用异常指示何时不保留前置条件或后置条件是该exception
机制的另一个目的,主要是用于调试目的。我的目标不是这里的用法exceptions
。
在许多书籍,教程和其他资料中,它们倾向于将错误处理显示为一门相当客观的科学,可以解决这一问题,exceptions
而您只需要catch
它们具有强大的软件就可以从任何情况下恢复。但是,作为开发人员的几年时间使我从另一种方法来看问题:
- 程序员倾向于通过抛出异常来简化他们的任务,而这种特殊情况似乎太少而无法仔细实现。典型的情况是:内存不足问题,磁盘已满问题,文件损坏等,这可能就足够了,但并不总是从体系结构级别决定。
- 程序员往往不会仔细阅读有关库中异常的文档,并且通常不知道函数何时何地抛出。而且,即使他们知道了,他们也并没有真正管理它们。
- 程序员往往没有足够早地捕获异常,当他们捕获异常时,主要是记录并抛出更多异常。(请参阅第一点)。
这有两个结果:
- 经常发生的错误可以在开发的早期发现并进行调试(很好)。
- 罕见的异常无法管理,并且会使系统在用户家崩溃(带有一条漂亮的日志消息)。有时会报告错误,甚至不会报告。
考虑到这一点,海事组织错误机制的主要目的应该是:
- 在不管理某些特定情况的代码中使可见。
- 发生这种情况时,将问题运行时与相关代码(至少是调用方)进行通信。
- 提供恢复机制
exception
语义作为错误处理机制的主要缺陷是IMO:很容易看到a throw
在源代码中的位置,但是绝对不明显,通过查看声明来知道特定功能是否会抛出。这带来了我上面介绍的所有问题。
该语言不会像对语言的其他方面(例如强类型的变量)那样严格执行和检查错误代码
尝试解决方案
为了改善这一点,我开发了一个非常简单的错误处理系统,该系统试图使错误处理的重要性与普通代码相同。
这个想法是:
- 每个(相关)功能都接收到对
success
非常轻的对象的引用,并可能将其设置为错误状态。在保存文本错误之前,该对象非常轻。 - 如果提供的对象已经包含错误,则鼓励函数跳过其任务。
- 绝对不能覆盖错误。
完整的设计显然会全面考虑每个方面(约10页),以及如何将其应用于OOP。
Success
该类的示例:
class Success
{
public:
enum SuccessStatus
{
ok = 0, // All is fine
error = 1, // Any error has been reached
uninitialized = 2, // Initialization is required
finished = 3, // This object already performed its task and is not useful anymore
unimplemented = 4, // This feature is not implemented already
};
Success(){}
Success( const Success& v);
virtual ~Success() = default;
virtual Success& operator= (const Success& v);
// Comparators
virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}
// Retrieve if the status is not "ok"
virtual bool operator!() const { return status!=ok;}
// Retrieve if the status is "ok"
operator bool() const { return status==ok;}
// Set a new status
virtual Success& set( SuccessStatus status, std::string msg="");
virtual void reset();
virtual std::string toString() const{ return stateStr;}
virtual SuccessStatus getStatus() const { return status; }
virtual operator SuccessStatus() const { return status; }
private:
std::string stateStr;
SuccessStatus status = Success::ok;
};
用法:
double mySqrt( Success& s, double v)
{
double result = 0.0;
if (!s) ; // do nothing
else if (v<0.0) s.set(Error, "Square root require non-negative input.");
else result = std::sqrt(v);
return result;
}
Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;
我在许多(自己的)代码中都使用了它,这迫使程序员(我)对可能的特殊情况以及如何解决它们(好的)进行进一步的思考。但是,它有一个学习曲线,并且不能与现在使用它的代码很好地集成。
问题
我想更好地了解在项目中使用这种范例的含义:
- 问题的前提是否正确?还是我错过了一些相关的东西?
- 解决方案是好的构架思想吗?还是价格太高?
编辑:
方法之间的比较:
//Exceptions:
// Incorrect
File f = open("text.txt"); // Could throw but nothing tell it! Will crash
save(f);
// Correct
File f;
try
{
f = open("text.txt");
save(f);
}
catch( ... )
{
// do something
}
//Error code (mixed):
// Incorrect
File f = open("text.txt"); //Nothing tell you it may fail! Will crash
save(f);
// Correct
File f = open("text.txt");
if (f) save(f);
//Error code (pure);
// Incorrect
File f;
open(f, "text.txt"); //Easy to forget the return value! will crash
save(f);
//Correct
File f;
Error er = open(f, "text.txt");
if (!er) save(f);
//Success mechanism:
Success s;
File f;
open(s, "text.txt");
save(s, f); //s cannot be avoided, will never crash.
if (s) ... //optional. If you created s, you probably don't forget it.