Первоначально большинство строк кода были длинными. Я решил использовать макросы, чтобы быть кратким и понятным. Я бродил, если использование макросов, как это плохая практика. Лично я считаю, что с макросами все выглядит чище, но с макросами, скрывающими имена и функции, некоторые могут запутаться в том, как я реализовал свои методы.
Оригинальный код
...
namespace ConwaysGameOfLife
{
class Grid
{
private:
//Members
...
using set_of_ints = std::unordered_set<int>;
using set_of_sizes = std::unordered_set<std::size_t>;
//Members to be used in Macros
set_of_sizes cells_at_t_minus_one;
set_of_sizes cells_at_t;
...
private:
//Functions
...
//Function showing Lengthy Conditionals
void rule(const std::size_t& cell_position)
{
std::size_t live_neighbors{0};
for(const auto& neighbor_offset : neighbor_offsets)
/*! Total Neighbors !*/
{
//Lengthy Conditional
if(cells_at_t_minus_one.find(cell_position + neighbor_offset) != cells_at_t.end())
{
live_neighbors++;
}
}
//Lengthy Conditional
if(cells_at_t_minus_one.find(cell_position) != cells_at_t.end() and live_neighbors < 2)
/*! Underpopulation !*/
{
cells_at_t.erase(cell_position);
}
//Lengthy Conditional
else if(cells_at_t_minus_one.find(cell_position) != cells_at_t.end() and (live_neighbors == 2 or live_neighbors == 3))
/*! Aging of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Lengthy Conditional
else if(cells_at_t_minus_one.find(cell_position) == cells_at_t.end() and live_neighbors == 3)
/*! Birth of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Lengthy Conditional
else if(cells_at_t_minus_one.find(cell_position) != cells_at_t.end() and live_neighbors > 3)
/*! Overpopulation !*/
{
cells_at_t.erase(cell_position);
}
}
public:
...
};
}
...
Код с макросами
...
#define neighbor cells_at_t_minus_one.find(cell_position + neighbor_offset)
#define cell cells_at_t_minus_one.find(cell_position)
#define dead cells_at_t.end()
#define is_live != dead
#define is_dead == dead
#define result second
namespace ConwaysGameOfLife
{
class Grid
{
private:
//Members
...
using set_of_ints = std::unordered_set<int>;
using set_of_sizes = std::unordered_set<std::size_t>;
//Members used in Macros
set_of_sizes cells_at_t_minus_one;
set_of_sizes cells_at_t;
...
private:
//Functions
...
void rule(const std::size_t& cell_position)
{
std::size_t live_neighbors{0};
for(const auto& neighbor_offset : neighbor_offsets)
/*! Total Neighbors !*/
{
//Macros used
if(neighbor is_live)
{
live_neighbors++;
}
}
//Macros used
if(cell is_live and live_neighbors < 2)
/*! Underpopulation !*/
{
cells_at_t.erase(cell_position);
}
//Macros used
else if(cell is_live and (live_neighbors == 2 or live_neighbors == 3))
/*! Aging of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Macros used
else if(cell is_dead and live_neighbors == 3)
/*! Birth of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Macros used
else if(cell is_live and live_neighbors > 3)
/*! Overpopulation !*/
{
cells_at_t.erase(cell_position);
}
}
public:
...
};
}
#undef neighbor
#undef cell
#undef dead
#undef is_live
#undef is_dead
#undef result
...
Это:
if (neighbor is_live) { ... }
абсолютно невозможно понять. Он выглядит как плохо сформированный код, и любой, кто его читает, в первую очередь подумает, что там что-то не так. Забудьте о своих пэрах, это тот код, к которому вы вернетесь через несколько месяцев и не понимаете в течение достаточно долгого времени.
Это также приносит много проблем в таких вещах, как neighbor
, dead
, cell
, result
, а также is_live
разумные имена для использования в качестве идентификаторов для этой конкретной программы. Так что, если вам случится использовать их в качестве идентификаторов, вы получите довольно непостижимые ошибки.
Рассмотрим альтернативу:
if (is_live(cell_position + neighbor_offset)) { ... }
где у нас просто есть функция is_live
это только делает правильную вещь:
bool is_live(size_t idx) { return !cells_at_t_minus_one.count(idx); }
Это намного лучше, потому что:
neighbor_offset
просто быть offset
изменение имени влияет только на neighbor_offset
(который в вашем коде даже не используется). Мне не нужно менять определение моего макроса!Примечание: ваша проверка в реальном времени сравнивает итераторы из одного контейнера (cells_at_t_minus_one
) с другим контейнером (cells_at_t
).
Я решил использовать макросы, чтобы быть кратким и понятным.
перевод: я решил, что c ++ не для меня, поэтому я изобрел новый предметно-ориентированный язык с собственным синтаксисом. Это действительно выглядит довольно хорошо, но, поскольку он полностью переведен в препроцессор, вы можете выразить только те концепции, которые я явно ожидал.
Я бродил, если использование макросов, как это плохая практика.
Если под плохой практикой вы подразумеваете, что по мере роста и изменения вашей программы она станет неуправляемой, тогда да, это плохая практика.
Если под плохой практикой вы подразумеваете, что ваши коллеги будут расстроены из-за того, что вы создаете не поддерживаемый код, тогда да, это плохая практика.
В итоге…
не просто плохая практика, а самое худшее.
Мои эмпирические правила относительно макросов:
Избегайте их использования, если можете. Вместо этого используйте встроенные функции или шаблоны, потому что макросы являются просто заменой текста и могут вызвать серьезные проблемы.
Если вам нужно их использовать, назовите их очень четко. Попробуйте использовать имена, которые вряд ли появятся где-либо еще, потому что макросы — это просто замена текста.
Номер 2, чтобы избежать таких проблем, как:
#define equals ==
int main()
{
bool equals = 2 equals 3;
if( equals )
printf( "2==3 is true?" );
}
который заканчивается как:
int main()
{
bool == = 2 == 3;
if( == )
printf( "2==3 is true?" );
}
когда макрос будет обработан.