改变编码风格以避免嵌套的if-blocks

时间:2016-02-20 12:07:50

标签: c++ nested-if

我正在编写一个基于openGL的应用程序,我正在使用各种标志,if-blocks和counter来控制绘图循环中某些对象的外观。例如。以下片段来自一个函数(在每次迭代时从我的主要绘图循环调用),负责在检测到面部后捕获照片:它将首先打印一些文本指令和3-2-1完成的倒计时等。

if (auto face = detect_face(video_frame, draw_frames_flag_m)) { // detect face
  helper::gl::display_cv_mat(video_frame); // display face
  face_out_of_range_msg_flag_m = false;
  if (!photo_capture_flag_m) { // start capturing 
    photo_capture_flag_m = true;
    capture_counter_m = glfwGetTime() + 5 + WAIT_TIME_BETWEEN_PHOTOS; 
  } else if (glfwGetTime() < (capture_counter_m - photos_wait_time4)) {
    draw_frames_flag_m = true;
    render_text("Face detected! Look at the camera and stand still",10,window_height_m-76);
  } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time3))
    render_text("3",window_width_m/2,window_height_m/2,200);
  else if (glfwGetTime() <= (capture_counter_m - photos_wait_time2)) 
    render_text("2",window_width_m/2,window_height_m/2,200);
  else if (glfwGetTime() <= (capture_counter_m - photos_wait_time1)) 
    render_text("1",window_width_m/2,window_height_m/2,200);
  else if (glfwGetTime() <= (capture_counter_m - photos_wait_time_almost_done)) {
    render_text("done",window_width_m/2,window_height_m/2,200);
    draw_frames_flag_m = false; // don't draw frames the next few times so that the photo can be taken
  } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time_done)) {
    render_text("done",window_width_m/2,window_height_m/2,200);
    load_and_save_portait(video_frame, *face);
  } else if (glfwGetTime() < capture_counter_m - 1) 
    render_text("...processing photo..",(window_width_m/2)-50,window_height_m/2);
  else { // reset
    photo_capture_flag_m = false;
    capture_done_flag_m = false;    
  }
} else {
  helper::gl::display_cv_mat(video_frame);
  if (photo_capture_flag_m) photo_capture_flag_m = false;
  if (capture_done_flag_m) capture_done_flag_m = false;
}

这段代码依赖于其他成员变量/函数和一些全局常量等,这里没有给出,因为它不是困扰我的功能,而是结构,这在这里已经很明显了。我发现这样的代码很糟糕:嵌套循环难以阅读,而且我们在一天结束时并不是80年代......但不幸的是我发现自己经常编写这样的代码..

困扰我的是这里的嵌套循环表明某种多态行为 - 在全局绘图循环的每次迭代中,我都有一个可视化的video_frame,但每次都有一些其他元素,如文本,边框等。因此必须有一种方法来构建某种类型的多态对象的代码。

我一直在考虑在其他情况下有效的类继承(例如绘制不同形状的系列) - 但这似乎并不合理。我想,例如将VideoFrameAbstractClass作为VideoFrame3VideoFrame2VideoFrameProcessingImage等一系列子类的公共接口,然后将video_frame与计数器一起传递到公共接口并让覆盖的绘图函数执行绘图。然而,这种方法对我来说似乎更加愚蠢。我不能仅仅因为文本消息从“3”变为“2”到“1”而证明整个新课程的合理性。撇开它可能会更慢以这种方式增加开销。

我有兴趣听听人们对此的看法。

4 个答案:

答案 0 :(得分:4)

  } else if (glfwGetTime() < (capture_counter_m - photos_wait_time4)) {
    draw_frames_flag_m = true;
    render_text("Face detected! Look at the camera and stand still",10,window_height_m-76);
  } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time3))
    render_text("3",window_width_m/2,window_height_m/2,200);
  else if (glfwGetTime() <= (capture_counter_m - photos_wait_time2)) 
    render_text("2",window_width_m/2,window_height_m/2,200);
  else if (glfwGetTime() <= (capture_counter_m - photos_wait_time1)) 
    render_text("1",window_width_m/2,window_height_m/2,200);
  else if (glfwGetTime() <= (capture_counter_m - photos_wait_time_almost_done)) {
    render_text("done",window_width_m/2,window_height_m/2,200);
    draw_frames_flag_m = false; // don't draw frames the next few times so that the photo can be taken
  } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time_done)) {
    render_text("done",window_width_m/2,window_height_m/2,200);
    load_and_save_portait(video_frame, *face);
  } else if (glfwGetTime() < capture_counter_m - 1) 
    render_text("...processing photo..",(window_width_m/2)-50,window_height_m/2);
  else { // reset

这一系列的if都有共同点,就是检查glfwGetTime()返回的值是否不大于某个其他值。

从概念上讲,这构成了一个具有某些界限的范围,映射到操作。

换句话说,std::map<int, std::function<void()>,您可以在其上调用upper_bound member function

这是一个完整的玩具示例:

#include <string>
#include <map>
#include <functional>
#include <iostream>

void render_text(std::string const& s, int a, int b, int c )
{
    std::cout << s << " " << a << b << c << "\n";
}

void load_and_save_portait()
{
    std::cout << "load_and_save_portait\n";
}

int main()
{
    int glfwGetTime = 90;
    bool draw_frames_flag_m = false;
    int window_width_m = 0;
    int window_height_m = 0;
    int capture_counter_m = 0;
    int photos_wait_time3 = 0;
    int photos_wait_time2 = -100;
    int photos_wait_time1 = -99;
    int photos_wait_time_almost_done = 0;
    int photos_wait_time_done = 0;

    std::map<int, std::function<void()>> range = {
        { capture_counter_m - photos_wait_time3, [&]() { render_text("3",window_width_m/2,window_height_m/2,200); } },
        { capture_counter_m - photos_wait_time2, [&]() { render_text("2",window_width_m/2,window_height_m/2,200); } },
        { capture_counter_m - photos_wait_time1, [&]() { render_text("1",window_width_m/2,window_height_m/2,200); } },
        { capture_counter_m - photos_wait_time_almost_done, [&] {
            render_text("done",window_width_m/2,window_height_m/2,200);
            draw_frames_flag_m = false; // don't draw frames the next few times so that the photo can be taken
        }},
        { capture_counter_m - photos_wait_time_done, [&]() {
            render_text("done",window_width_m/2,window_height_m/2,200);
            load_and_save_portait();
        }}
    };

    auto const upper_bound_iter = range.upper_bound(glfwGetTime);
    if (upper_bound_iter != range.end()) {
        auto const function = upper_bound_iter->second;
        function();
    } else {
        std::cout << "no match\n";
    }

}

现在,您当然可以找到其他方法来使这更简洁。例如,在不同的操作中可能存在某种可以概括的模式。忽略布尔标志和load_and_save_portait调用片刻,似乎所有操作都调用render_text。因此,您可以将参数存储到std::function,而不是存储render_text个对象的地图:

#include <string>
#include <map>
#include <iostream>

struct render_text_arguments
{
    std::string s;
    int a;
    int b;
    int c;
};

void render_text(std::string const& s, int a, int b, int c )
{
    std::cout << s << " " << a << b << c << "\n";
}

void render_text(render_text_arguments const& arguments)
{
    render_text(arguments.s, arguments.a, arguments.b, arguments.c);
}

int main()
{
    int glfwGetTime = 90;
    int window_width_m = 0;
    int window_height_m = 0;
    int capture_counter_m = 0;
    int photos_wait_time3 = 0;
    int photos_wait_time2 = -100;
    int photos_wait_time1 = -99;
    int photos_wait_time_almost_done = 0;
    int photos_wait_time_done = 0;

    std::map<int, render_text_arguments> range = {
        { capture_counter_m - photos_wait_time3,            { "3",window_width_m/2,window_height_m/2,200    }},
        { capture_counter_m - photos_wait_time2,            { "2",window_width_m/2,window_height_m/2,200    }},
        { capture_counter_m - photos_wait_time1,            { "1",window_width_m/2,window_height_m/2,200    }},
        { capture_counter_m - photos_wait_time_almost_done, { "done",window_width_m/2,window_height_m/2,200 }},
        { capture_counter_m - photos_wait_time_done,        { "done",window_width_m/2,window_height_m/2,200 }}
    };

    auto const upper_bound_iter = range.upper_bound(glfwGetTime);
    if (upper_bound_iter != range.end()) {
        auto const arguments = upper_bound_iter->second;
        render_text(arguments);
    } else {
        std::cout << "no match\n";
    }
}

这些都是想法。您必须判断它们是否以及如何适用于您的问题。

请注意,我们在这里所做的是将硬连线程序逻辑转换为可能的动态运行时数据。您可以轻松地在地图中添加和删除项目,甚至可以执行从配置文件中读取项目等精心设计的内容。

答案 1 :(得分:2)

您的代码的问题实际上不是嵌套if的级别。

虽然从技术上讲,else if增加了一个级别的嵌套,但这个习惯用法很常见,因此它将被理解为案例选择。这里的关键是使用正确的格式。我建议如果你的一些条款需要花括号,你可以使用它们。否则,控制流程在视觉上难以掌握。如果采用编码样式,每个级别使用的缩进数超过两个字符,也可能会有所帮助。

有问题的代码的另一个问题是它太复杂了,无论你使用什么样的控制流构造。虽然简单的案例选择如

if (std::strcmp("alpha", text) == 0) {
    return get_alpha();
} else if (std::strcmp("beta", text) == 0) {
    return get_beta();
} else if (std::strcmp("gamma", text) == 0) {
    return get_gamma();
} else if ( … ) {
    …
} else {
    throw std::invalid_argument {};
}
即使案例数量变大(基本上是字符串switch除了C ++本身不能这样做),

也很容易理解,你的函数包含太多复杂的逻辑。尝试重构它并将其分解为多个函数。

例如,从外部if开始。

if (auto face = detect_face(video_frame, draw_frames_flag_m)) {
    handle_face(face);
} else {
    helper::gl::display_cv_mat(video_frame);
    if (photo_capture_flag_m) {
        photo_capture_flag_m = false;
    }
    if (capture_done_flag_m) {
        capture_done_flag_m = false;
    }
}

此代码不再难以理解。

现在您可以将注意力转移到提取的handle_face功能。格式化后......

void handle_face(face_t * face) {
    helper::gl::display_cv_mat(video_frame);  // display face
    face_out_of_range_msg_flag_m = false;
    if (!photo_capture_flag_m) {
        // start capturing
        photo_capture_flag_m = true;
        capture_counter_m = glfwGetTime() + 5 + WAIT_TIME_BETWEEN_PHOTOS;
    } else if (glfwGetTime() < (capture_counter_m - photos_wait_time4)) {
        draw_frames_flag_m = true;
        render_text("Face detected; look at the camera and stand still.", 10, window_height_m - 76);
    } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time3)) {
        render_text("3", window_width_m / 2, window_height_m / 2, 200);
    } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time2)) {
        render_text("2", window_width_m / 2, window_height_m / 2, 200);
    } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time1)) {
        render_text("1", window_width_m / 2, window_height_m / 2, 200);
    } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time_almost_done)) {
        render_text("done", window_width_m / 2, window_height_m / 2, 200);
        draw_frames_flag_m = false;
        // Don't draw frames the next few times so that the photo can be taken
    } else if (glfwGetTime() <= (capture_counter_m - photos_wait_time_done)) {
        render_text("done", window_width_m / 2, window_height_m / 2, 200);
        load_and_save_portait(video_frame, *face);
    } else if (glfwGetTime() < capture_counter_m - 1) {
        render_text("processing photo...", (window_width_m / 2) - 50, window_height_m / 2);
    } else {  // reset
        photo_capture_flag_m = false;
        capture_done_flag_m = false;
    }
}

......它看起来 不再令人畏惧。

当然,仍有改进的余地。例如,您正在重复

render_text("…", window_width_m / 2, window_height_m / 2, 200);

相当多。因为唯一正在改变的是第一个参数,为什么没有辅助函数呢?

void render_text_center(const char * text) {
    render_text(text, window_width_m / 2, window_height_m / 2, 200);
}

各种

glfwGetTime() <= (capture_counter_m - photos_wait_timeX)

测试可以包含在函数中

bool before_wait_time(const wait_time_t wt) {
    return glfwGetTime() <= (capture_counter_m - wt);
}

现在我们明白了。

void handle_face(face_t * face) {
    helper::gl::display_cv_mat(video_frame);  // display face
    face_out_of_range_msg_flag_m = false;
    if (!photo_capture_flag_m) {
        // start capturing
        photo_capture_flag_m = true;
        capture_counter_m = glfwGetTime() + 5 + WAIT_TIME_BETWEEN_PHOTOS;
    } else if (before_wait_time(photos_wait_time4)) {
        draw_frames_flag_m = true;
        render_text_top_left("Face detected; look at the camera and stand still.");
    } else if (before_wait_time(photos_wait_time3)) {
        render_text_center("3");
    } else if (before_wait_time(photos_wait_time2)) {
        render_text_center("2");
    } else if (before_wait_time(photos_wait_time1)) {
        render_text_center("1");
    } else if (before_wait_time(photos_wait_time_almost_done)) {
        render_text_center("done");
        draw_frames_flag_m = false;
        // Don't draw frames the next few times so that the photo can be taken
    } else if (before_wait_time(photos_wait_time_done)) {
        render_text_center("done");
        load_and_save_portait(video_frame, *face);
    } else if (before_wait_time(capture_counter_m)) {
        render_text_center_right("processing photo...");
    } else {  // reset
        photo_capture_flag_m = false;
        capture_done_flag_m = false;
    }
}

虽然它没有减少代码行数或if的数量,但更简单的代码具有更少的干扰,更容易解析人眼。

现在我们实际上可以看到复杂代码的根本原因。我们所拥有的基本上是一个写成if … else if … else级联的循环。我不太了解你的其余程序逻辑,以确定你将如何重构这一点,但我似乎很可能会删除所有photos_wait_timeX变量和相应的{{1 s - 总的来说,这里只有三个 - 而是计算间隔。

if

这仍然不是很好的代码,但我认为这是可以接受的。

答案 2 :(得分:1)

要扩展@πάνταῥεῖ答案,您还可以使用旧的goto。但goto的正常警告仍然适用。

if (!firstCondition) {
    goto done;
}

// Do something.

if (!secondCondition) {
    goto done;
}

// Do something.

done:
    // End here.

答案 3 :(得分:0)

避免深层嵌套if / else if级联的一种常用技巧是在检查下一个条件之前使用do {} while(false);break

而不是像

这样的代码
if(firstCondition) {
   // Do something
}
else {
   if(secondCondition) {
        // Do something
   }
}

你可以写

do {
    if(!firstCondition) {
         break;
    }

    // Do something

    if(!secondCondition) {
         break;
    }

    // Do something

    // ...
} while(false);