我在C++环境下工作:
a)我们被禁止使用异常
b)它是应用程序/数据服务器代码,用于评估许多不同类型的请求
我有简单的封装服务器操作的类结果,它也在内部用于那里的许多函数。
class OpResult
{
.....
bool succeeded();
bool failed(); ....
... data error/result message ...
};当我试图让所有函数都变得简单小巧时,出现了很多类似这样的问题:
....
OpResult result = some_(mostly check)function(....);
if (result.failed())
return result;
...问题是,让宏看起来像这样并到处使用是不是一种糟糕的做法?
#define RETURN_IF_FAILED(call) \
{ \
OpResult result = call; \
if (result.failed()) \
return result; \
}我知道有人会说它很脏,但有没有更好的方法呢?你建议用什么其他方法来处理结果和避免大量臃肿的代码?
发布于 2021-05-06 01:05:16
10年后,我将回答我自己的问题,让我满意,如果我有一台时光机...
我在新项目中多次遇到类似的情况。即使异常是允许的,我也不想总是用它们来处理“正常的失败”。
我最终找到了一种写这类语句的方法。
对于包含消息的通用结果,我使用以下代码:
class Result
{
public:
enum class Enum
{
Undefined,
Meaningless,
Success,
Fail,
};
static constexpr Enum Undefined = Enum::Undefined;
static constexpr Enum Meaningless = Enum::Meaningless;
static constexpr Enum Success = Enum::Success;
static constexpr Enum Fail = Enum::Fail;
Result() = default;
Result(Enum result) : result(result) {}
Result(const LocalisedString& message) : result(Fail), message(message) {}
Result(Enum result, const LocalisedString& message) : result(result), message(message) {}
bool isDefined() const { return this->result != Undefined; }
bool succeeded() const { assert(this->result != Undefined); return this->result == Success; }
bool isMeaningless() const { assert(this->result != Undefined); return this->result == Enum::Meaningless; }
bool failed() const { assert(this->result != Undefined); return this->result == Fail; }
const LocalisedString& getMessage() const { return this->message; }
private:
Enum result = Undefined;
LocalisedString message;
};然后,我在这个表单中有一个特殊的helper类,(类似于其他返回类型)
class Failed
{
public:
Failed(Result&& result) : result(std::move(result)) {}
explicit operator bool() const { return this->result.failed(); }
operator Result() { return this->result; }
const LocalisedString& getMessage() const { return this->result.getMessage(); }
Result result;
};当它们组合在一起时,我可以编写如下代码:
if (Failed result = trySomething())
showError(result.getMessage().str());是不是很漂亮?
发布于 2010-11-13 20:56:12
这是一种权衡。你是在用代码大小来混淆逻辑。我更喜欢将逻辑保留为可见。
我不喜欢这种类型的宏,因为它们破坏了Intellisense (在Windows上)和程序逻辑的调试。尝试在函数中的所有10个return语句上设置断点--不是检查,而是return。尝试单步执行宏中的代码。
最糟糕的是,一旦你接受了这一点,就很难反驳那些30行的怪兽宏,一些程序员喜欢用它来处理常见的小任务,因为它们“澄清了一些事情”。我见过这样的代码,其中不同的异常类型由四个级联宏处理,导致在源文件中有4行,宏实际上扩展到> 100个实际行。现在,你正在减少代码膨胀吗?不是的。使用宏是不可能轻易分辨出来的。
另一个反对宏的一般论点,即使在这里显然不适用,也是能够将它们嵌套在难以破译的结果中,或者传递导致奇怪但可编译的参数的参数,例如,在使用两次参数的宏中使用++x。我总是知道我对代码的立场,但我不能对宏说这一点。
编辑:我应该补充的一点是,如果你真的一遍又一遍地重复这个错误检查逻辑,也许代码中有重构的机会。这不是一个保证,但如果它确实适用,则是减少代码膨胀的更好方法。寻找重复的调用序列,并将公共序列封装在它们自己的函数中,而不是解决如何单独处理每个调用。
发布于 2010-11-13 20:56:23
实际上,我更喜欢其他的解决方案。问题是内部调用的结果不一定是外部调用的有效结果。例如,内部故障可能是“找不到文件”,而外部故障可能是“配置不可用”。因此,我的建议是重新创建OpResult (可能会将“内部”OpResult打包到其中,以便更好地调试)。这一切都指向.NET中的"InnerException“方向。
从技术上讲,在我的例子中,宏看起来像
#define RETURN_IF_FAILED(call, outerresult) \
{ \
OpResult innerresult = call; \
if (innerresult.failed()) \
{ \
outerresult.setInner(innerresult); \
return outerresult; \
} \
}然而,这个解决方案需要一些内存管理等。
一些纯粹主义者认为没有显式的返回会阻碍代码的可读性。然而,在我看来,将显式RETURN作为宏名称的一部分就足以防止任何熟练和细心的开发人员感到困惑。
我的观点是,这样的宏不会混淆程序逻辑,相反,它会让程序变得更清晰。有了这样的宏,你可以用一种清晰而简洁的方式声明你的意图,而另一种方式似乎过于冗长,因此容易出错。让维护人员在脑海中解析与OpResult r = call(); if (r.failed) return r浪费时间的构造相同的结构。
没有早期返回的另一种方法是对每个代码行应用类似于带有#define CHECKEDCALL(r, call) do { if (r.succeeded) r = call; } while(false)的CHECKEDCALL(r, call)的模式。在我看来,这要糟糕得多,而且绝对容易出错,因为人们在添加更多代码时往往会忘记添加CHECKEDCALL()。
对我来说,普遍需要对宏执行检查返回(或一切),这似乎是缺少语言功能的一个轻微迹象。
https://stackoverflow.com/questions/4172545
复制相似问题