Правила программирования на С и С++. Главы 1-6 - Именованные константы для булевых величин редко необходимы
ОГЛАВЛЕНИЕ
49. Именованные константы для булевых величин редко необходимы.
Выбор неверного имени может добавить значительную ненужную сложность в вашу программу. Рассмотрим следующую простейшую функцию, которая подсчитывает количество слов в строке:
int nwords(const char *str){
typedef enum { IN_WORD, BETWEEN_WORDS } wstate;
int word_count = 0;
wstate state = BETWEEN_WORDS;
for (; *str ; ++str )
{
if ( isspace(*str) ) state = BETWEEN_WORDS;else if ( state != IN_WORD ){
++word_count;state = IN_WORD;
}}return word_count;
}
Неправильно выбранное имя state заставило нас ввести два ненужных идентификатора: IN_WORD и BETWEEN_WORDS. Теперь взгляните на этот вариант: int nwords2(const char *str){
int word_count = 0;
int in_word = 0;
for (; *str ; ++str )
{
if ( isspace(*str) ) in_word = 0;else if ( !in_word ){
++word_count;in_word = 1;
}}return word_count;
}
Переименование нечетко названной переменной state во что-нибудь, что действительно описывает назначение переменной, позволило мне исключить булевые именованные константы IN_WORDS и BETWEEN_WORDS. Получившаяся подпрограмма меньше и легче читается.Вот другой пример. Следующая программа:
enum child_type { I_AM_A_LEFT_CHILD, I_AM_A_RIGHT_CHILD };struct tnode
{
child_type position;
struct tnode *left,
*right;} t;//...
t.position = I_AM_LEFT_CHILD;
if ( t.position == I_AM_LEFT_CHILD )
//...может быть упрощена подобным образом: struct tnode{
unsigned is_left_child ;
struct tnode *left,
*right;} t;t.is_left_child = 1;
if ( t.is_left_child )
//...
тем самым исключая два ненужных идентификатора. И вот последний пример: enum { SOME_BEHAVIOR, SOME_OTHER_BEHAVIOR, SOME_THIRD_BEHAVIOR };f( SOME_BEHAVIOR , x);
f( SOME_OTHER_BEHAVIOR, x);
f( SOME_THIRD_BEHAVIOR, x);
требующий четырех идентификаторов (три именованные константы и имя функции). Лучше, хотя это не всегда возможно, исключить селекторную константу в пользу дополнительных функций: some_behavior(x);some_other_behavior(x);
some_third_behavior(x);
Обратной стороной этой монеты является вызов функции. Рассмотрим следующий прототип: create_window( int has_border, int is_scrollable, int is_maximized );Я снова выбрал рациональные имена для исключения необходимости в именованных константах. К сожалению, вызов этой функции плохо читаем: create_window( TRUE, FALSE, TRUE );Просто взглянув на такой вызов, я не получу никакого представления о том, как будет выглядеть это окно. Несколько именованных констант проясняют обстоятельства в этом вызове: enum { UNBORDERED =0; BORDERED =1}; // Нужно показать значенияenum { UNSCROLLABLE=0; SCROLLABLE =1}; // или create_window()
enum { NORMAL_SIZE =0; MAXIMIZED =1}; // не будет работать.
//...
create_window( BORDERED, UNSCROLLABLE, MAXIMIZED );
но теперь у меня другая проблема. Я не хочу использовать именованные константы внутри самой create_window(). Они здесь только для того, чтобы сделать ее вызов более читаемым, и я не хочу загромождать эту функцию таким кодом, как: if ( has_border == BORDERED )//...
сравнивая его с более простым: if ( has_border )//...
Первый вариант уродлив и многословен. К сожалению, если кто-то изменит значение именованной константы BORDERED, второй оператор if не будет работать. Я обычно соглашаюсь с мнением, что программист, занимающийся сопровождением, не должен менять значения идентификаторов, как я это проделал в предыдущем примере.