"Abusing"循环,用于减少中频嵌套

"Abusing" loops for reducing if-nesting

本文关键字:嵌套 用于 Abusing 循环      更新时间:2023-10-16

有时必须实现一系列if/else检查。在过去,goto是这方面的标准工具。

由于goto在许多代码风格指南中是不允许的,因此我有时使用循环作为替代,例如:

do
{
  if (a)
  {
   doA();
   break;
  }
  //...
  if (z)
  {
   doZ();
   break;
  }
}while(false);
//all break jump here

这是一个好方法吗?是否有一个好的c++模式(例如使用模板,继承等)来实现这一点,而不需要太多的开销?

由于条件似乎不相关,else if是一个选项:

if (a) {
    doA();
} else if (b) {
//...
} else if (z) {
    doZ();
}

一般来说,为了编写可维护的代码,您应该按照预期的方式使用该语言的工具。您使用的循环应该只循环一次迭代,这显然是一种偏转,而不是由一开始就不赞成goto的同一组代码风格指南所打算的。

语言中有一些可用的工具,使您更容易与其他程序员和您的进一步自我交流您打算在这里做什么。

    其中之一是将该代码块外包到一个单独的方法中,并使用return语句。有趣的是,一些人也不赞成提前退货。
  • 另一个解决方案可能是使用异常,在那里你尝试/catch。它是否符合你的目的主要取决于你正在做的"检查"类型。
  • 最后,if/else链可能看起来不太优雅,但另一方面,这是一个几乎没有误解空间的结构。

使用goto,使用continue,使用多个break,或使用多个return都是,如果滥用,不同口味的意大利面条编码,所有这些都可以用来创建非条件分支。一些滥用的例子:

  • goto被认为非常糟糕,如果用于除向下跳转以外的任何其他用途,例如跳转到错误处理程序。(虽然这可能是goto的一种可接受的用法,但它仍然引发了一场"打死马"的争论,所以我避免使用goto。)

  • continue无条件向上跳跃,这被认为是不好的:意大利面条代码的一个定义是代码无条件向上和向下跳跃。当多个continue被添加到同一个循环中时,情况尤其糟糕。一般来说,continue的存在是一个非常确定的循环标志,需要重写。

  • 多个break和多个return可以被滥用来脱离复杂的嵌套循环,使代码难以阅读和维护。此外,问题中演示的break方法强制使用有点晦涩的do-while(false)循环。

一般来说,所有这些方法都被认为是不好的实践,因为它们很容易被滥用。你只需要选择一个最不坏的:换句话说,最易读和最不晦涩的。

我相信多个returns是最易读的形式,因为它可以与一些函数结果变量混合在一起,您可能无论如何都想要:

result_t func ()
{
  if (a)
  {
    doA();
    return RESULT_A;
  }
  ...  // you'll need lots of if statements here to justify this program design
  if (z)
  {
    doZ();
    return RESULT_Z;
  }
  return RESULT_NORMAL;
}

关于函数内部资源分配的边注。

如果上面的函数需要释放一些已分配的资源,并且它总是需要这样做,那么应该使用RAII这样做,这样当局部变量超出作用域时,它就会自己清理。

如果它只需要在某些情况下(例如在错误时)释放一些已分配的资源,那么您不应该在每个If语句中这样做(不可维护),也不应该实现一些80年代BASIC程序员的"在错误时goto"。相反,可以考虑在main函数之外添加一个包装器函数,以使程序易于维护并最小化混乱:

result_t wrapper ()
{
  stuff = allocate(); // if needed
  result_t result = func(stuff);
  if(result == BAD)
  {
    deallocate(stuff);
  }
  return result;
}

您的非循环对您的程序员同事来说有点滥用,但并不可怕。您可以在断言宏和日志记录器的实现中看到很多这样的代码。

当然,有了模板,滥用的机会呈几何级数地扩大。例如,你可以这样写:

void doA() {
    std::cout << "A" << std::endl;
}
void doB() {
    std::cout << "B" << std::endl;
}

void either_or() {}
template<class T>
void either_or(T&& t) {
    if (std::get<bool>(t)) {
        std::get<void(*)()>(t)();
    }
}
template<class T, class...Ts>
void either_or(T&& t, Ts&&... ts)
{
    if (std::get<bool>(t)) {
        std::get<void(*)()>(t)();
    }
    else {
        either_or(std::forward<Ts>(ts)...);
    }
}
auto main() -> int
{
    using namespace std;
    bool a = false;
    bool b = true;
    either_or(make_tuple(a, &doA),
              make_tuple(b, &doB));
    return 0;
}

这虽然是执行第一个动作的有效方法(在优化器干预之后),对应的标志为真,但对于维护者来说,这是一种更滥用的方法。

恕我直言,我不认为使用goto有任何错误。如果您知道如何、在哪里以及为什么使用goto,那么您的代码将更加清晰易读。

例如,Linux使用了大量的goto语句。正如@Bartek Banachewicz所说,"Linux代码有gotos并不意味着它们不那么糟糕。"

goto并不总是坏的,它们有很多有效的用途,比如打破深嵌套循环。在这种情况下,使用goto将呈现更清晰的代码。只有当它被滥用或使用在不像c++程序那样的地方时,才会导致面条式代码。

注意:不必要使用任何循环,if-else语句,break, continue如果不这样做,将导致代码混乱和难以管理。即使我们让这个世界成为一个un-goto的地方,意大利面条代码将存在。这是这取决于个人,以确保他/她写一个干净,可读,优化代码。

最好是在c++程序中使用exceptions

如果您无法使用异常,那么其他设计可以使用另一种方法,其中包含多个案例组合在一起。