Именованные константы для булевых величин редко необходимы
Выбор неверного имени может добавить значительную ненужную сложность в вашу программу. Рассмотрим следующую простейшую функцию, которая подсчитывает количество слов в строке:
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_WORD и
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 не будет работать. Я обычно соглашаюсь с мнением, что программист, занимающийся сопровождением, не должен менять значения идентификаторов, как я это проделал в предыдущем примере.