C ++代码纯度

时间:2010-11-13 12:44:18

标签: c++ error-handling refactoring

我在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;           \
  }

我知道有人可以称之为讨厌,但有更好的方法吗? 你会建议用什么其他方法来处理结果并避免大量的膨胀代码?

8 个答案:

答案 0 :(得分:9)

这是一种权衡。您正在交换代码大小以模糊逻辑。我更喜欢将逻辑保持为可见。

我不喜欢这种类型的宏,因为它们会破坏Intellisense(在Windows上),并调试程序逻辑。尝试在函数中的所有10个return语句中设置断点 - 而不是检查,只需return。尝试单步执行宏中的代码。

最糟糕的是,一旦你接受了这一点,就很难与一些程序员喜欢用于常见迷你任务的30行怪物宏争论,因为他们“澄清了事情”。我已经看到了代码,其中通过四个级联宏以这种方式处理不同的异常类型,导致源文件中的4行,其中宏实际扩展为> 100条实线。现在,你减少代码膨胀吗?不可以。用宏来轻松讲述。

另一个反对宏的一般论据,即使在这里显然不适用,也就是能够用难以破译的结果来嵌套它们,或传递导致奇怪但可编译的参数的参数,例如:在使用该参数两次的宏中使用++x。我总是知道我对代码的立场,我不能说关于宏。

编辑:我应该补充的一点是,如果你真的重复这个错误检查逻辑一遍又一遍,也许代码中有重构机会。如果确实适用,则不是保证,而是更好的代码膨胀减少方法。查找重复的调用序列并将公共序列封装在自己的函数中,而不是解决每个调用的处理方式。

答案 1 :(得分:5)

实际上,我更喜欢其他解决方案。问题是内部调用的结果不一定是外部调用的有效结果。例如,内部故障可能是“找不到文件”,但外部故障可能是“配置不可用”。因此我的建议是重新创建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正在浪费他们的时间。

没有提前退货的替代方法是将CHECKEDCALL(r, call)#define CHECKEDCALL(r, call) do { if (r.succeeded) r = call; } while(false)等模式应用于每个代码行。这在我眼里要糟糕得多,而且肯定容易出错,因为人们在添加更多代码时往往会忘记添加CHECKEDCALL()

热门需要用宏来检查返回(或所有内容)似乎是我遗漏语言功能的一个轻微迹象。

答案 2 :(得分:2)

只要宏定义位于实现文件中并且在不必要时立即定义,我就不会恐惧

// something.cpp

#define RETURN_IF_FAILED() /* ... */

void f1 () { /* ... */ }
void f2 () { /* ... */ }

#undef RETURN_IF_FAILED

但是,我只会在排除所有非宏解决方案之后使用它。

答案 3 :(得分:0)

我同意Steve's POV

我首先想到的是,至少将宏缩小为

#define RETURN_IF_FAILED(result) if(result.failed()) return result;

但是后来我发现这已经 是一个单行,所以宏中没有什么好处。


我认为,基本上,你必须在可写性和可读性之间进行权衡。该宏绝对更容易 。但是, 阅读 是否更容易,这是一个悬而未决的问题。后者是一个非常主观的判断。仍然,客观地使用宏混淆代码。


最终,潜在的问题是你不能使用例外。你还没有说明这个决定的原因是什么,但我当然希望他们能够解决这个问题。

答案 4 :(得分:0)

可以用C ++ 0x lambdas完成。

template<typename F> inline OpResult if_failed(OpResult a, F f) {
    if (a.failed())
        return a;
    else
        return f();
};

OpResult something() {
    int mah_var = 0;
    OpResult x = do_something();
    return if_failed(x, [&]() -> OpResult {
        std::cout << mah_var;
        return f;
    });
};

如果你聪明而绝望,你可以使用常规对象制作同样的技巧。

答案 5 :(得分:0)

在我看来,在宏中隐藏return语句是个坏主意。 '代码obfucation'(我喜欢那个术语..!)达到了最高水平。我对这些问题的通常解决方案是在一个地方聚合函数执行并以下列方式控制结果(假设你有5个无效函数):

std::array<std::function<OpResult ()>, 5>  tFunctions = {
 f1, f2, f3, f4, f5
};

auto tFirstFailed = std::find_if(tFunctions.begin(), tFunctions.end(), 
    [] (std::function<OpResult ()>& pFunc) -> bool {
        return pFunc().failed();
    });

if (tFirstFailed != tFunctions.end()) {
 // tFirstFailed is the first function which failed...
}

答案 6 :(得分:0)

如果呼叫失败,结果中是否有任何实际有用的信息?

如果没有,那么

static const error_result = something;

if ( call().failed() ) return error_result; 

就足够了。

答案 7 :(得分:0)

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;
};

然后,我有一个这种形式的特殊帮助类,(类似于其他返回类型)

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());

是不是很漂亮?