изменить стиль кодирования, чтобы избежать вложенных блоков if

Я кодирую приложение на основе openGL и использую различные флаги, блоки if и счетчики для управления внешним видом определенных объектов в цикле рисования. Например. следующий фрагмент взят из функции (вызываемой из моего основного цикла рисования на каждой итерации), которая отвечает за съемку фотографии после обнаружения лица: сначала он напечатает некоторые текстовые инструкции, обратный отсчет 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 в качестве общего интерфейса для ряда подклассов, таких как VideoFrame3, VideoFrame2, VideoFrameProcessingImage и т.д., затем передайте video_frame вместе со счетчиками в общий интерфейс и позвольте переопределенным функциям рисования выполнять рисование. Тем не менее, этот подход кажется мне еще более глупым … Я не могу оправдать целый новый класс просто потому, что текстовое сообщение меняется с «3» на «2» на «1» … не говоря уже о том, что оно будет, вероятно, медленнее и с дополнительными накладными расходами.

Мне было бы интересно услышать мнение людей по этому поводу.

1

Решение

  } 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 функция-член.

Вот полный пример работающей игрушки:

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

Это все идеи. Вы должны судить, применимы ли они к вашей проблеме и насколько они применимы.


Обратите внимание, что то, что мы сделали здесь, — это преобразование программной логики программы в, возможно, динамические данные времени выполнения. Вы можете легко добавлять и удалять элементы с карты или даже делать такие сложные вещи, как чтение их из файла конфигурации.

4

Другие решения

Проблема с вашим кодом на самом деле не уровень вложенности ifs.

Хотя технически, 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;
}
}

Хотя это не уменьшило количество строк кода или количество ifs, более простой код имеет меньше отвлекающих факторов и его легче анализировать для человеческого глаза.

Теперь мы можем увидеть причину сложного кода. То, что у нас есть, это в основном цикл, записанный в виде if … else if … else каскад. Я не знаю достаточно об остальной логике вашей программы, чтобы точно сказать, как вы будете выполнять рефакторинг этого, но мне кажется, что вы могли бы удалить все эти photos_wait_timeX переменные и соответствующие ifs — которых, честно говоря, здесь только три — и вместо этого вычислить интервал.

int get_current_step() {
// I'm not sure whether your logic actually works that way…
return (glfwGetTime() - capture_counter_m) / WAIT_TIME_BETWEEN_PHOTOS;
}

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_time1)) {
const auto step = get_step();
render_text_center(std::to_string(step).c_str());
} 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;
}
}

Это все еще не красивый код, но я бы посчитал это приемлемым.

2

Чтобы расширить ответ @ πάντα ῥεῖ, вы также можете использовать старый добрый goto, Нормальные предостережения goto все еще применяется, хотя.

if (!firstCondition) {
goto done;
}

// Do something.

if (!secondCondition) {
goto done;
}

// Do something.

done:
// End here.
1

Один из наиболее часто используемых методов, чтобы избежать глубоко вложенных 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);
0
По вопросам рекламы [email protected]