Правила программирования на С и С++. Главы 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 не будет работать. Я обычно соглашаюсь с мнением, что программист, занимающийся сопровождением, не должен менять значения идентификаторов, как я это проделал в предыдущем примере.