Вопрос или проблема
У меня есть проект дизайна, который я хотел бы обсудить. У меня есть следующая функция, где заглавные буквы обозначают макросы:
errorType someFunc(params) {
INTEGRITY_CHECK(params)
SET_DATA_1(params, 0, 1, 2)
SET_DATA_2(params, 'a', 'b', 'c')
}
Эти макросы определены в общем заголовочном файле, который подключается в другие файлы. Каждый из этих макросов по сути является вызовом функции, которая условно возвращает значение.
#define INTEGRITY_CHECK(val) \
if (is_empty_func(val)) \
{ \
LOG_ERR("Пустые данные"); \
return errorCode_1; \
}
#define SET_DATA_1(params, \
v1, \
v2, \
v3) \
if (errorType retCode = some_function_set_data_1(params, \
v1,v2,v3); \
retCode != errorCode_success) \
{ \
return errorCode_2; \
}
#define SET_DATA_2(params, \
v1, \
v2, \
v3) \
if (errorType retCode = some_function_set_data_2(params, \
v1,v2,v3); \
retCode != errorCode_success) \
{ \
return errorCode_3; \
}
Params – это указатель на структуру.
Извините за неоднозначность и неопределенность.
Я понимаю, что цель использования вышеописанного дизайна – избежать повторения кода. Мы можем условно возвращать значение из функции, так как макросы разворачиваются и по сути становятся функцией. Есть ли современный способ в C++ / использование функций для этой модели, который был бы сжатым? Или это именно тот случай для макросов?
Почему макросы полезны? Предположим, мы заменим макрос на функцию, она будет возвращать значение, и в функции, где вызываются макросы, нам придется повторно проверять возвращенное значение с помощью if.
Ответ или решение
Безусловно, макросы могут упростить код и избавиться от дублирования, но их использование также может привести к нечитаемости и сложности отладки. В современном C++ действительно есть более элегантные способы решения этой проблемы, без использования макросов.
Вот пример, как вы можете переписать вашу логику с помощью функций, которые могут исключить прямое использование макросов, сохраняя при этом читаемость и гибкость:
- Создание вспомогательных функций: Вместо макросов, создайте функции, которые будут реализовывать необходимую проверку и логику.
enum class errorType {
success,
errorCode_1,
errorCode_2,
errorCode_3
// Добавьте другие коды ошибок по мере необходимости
};
errorType integrityCheck(const ParamsType* params) {
if (is_empty_func(params)) {
LOG_ERR("Empty Data");
return errorType::errorCode_1;
}
return errorType::success;
}
errorType setData1(ParamsType* params, int v1, int v2, int v3) {
return some_function_set_data_1(params, v1, v2, v3);
}
errorType setData2(ParamsType* params, char v1, char v2, char v3) {
return some_function_set_data_2(params, v1, v2, v3);
}
- Основная функция: Теперь в вашей основной функции вы сможете использовать эти вспомогательные функции и обрабатывать их результаты.
errorType someFunc(ParamsType* params) {
if (auto retCode = integrityCheck(params); retCode != errorType::success) {
return retCode; // Возврат кода ошибки
}
if (auto retCode = setData1(params, 0, 1, 2); retCode != errorType::success) {
return retCode; // Возврат кода ошибки
}
if (auto retCode = setData2(params, 'a', 'b', 'c'); retCode != errorType::success) {
return retCode; // Возврат кода ошибки
}
return errorType::success; // Успешное завершение
}
Преимущества такого подхода:
- Читаемость: Код становится более понятным и читаемым. Каждая функция выполняет свою задачу, и ее поведение легко понять.
- Отладка: С отладкой будет проще, так как у вас есть четкое определение функций, которые можно тестировать по отдельности.
- Типобезопасность: Функции могут иметь четко определенные типы параметров и возвращаемые значения, что снижает вероятность ошибок.
Заключение
Использование функций вместо макросов в этом случае дает вам больше контроля и предсказуемости в коде, что является важной частью практик современного программирования на C++. Хотя макросы имеют свое место, их использование следует ограничивать для внешнего видимости и обеспечения лучшего качества кода.