Правила программирования на Си и Си++

       

Именованные константы для булевых величин редко необходимы


Выбор неверного имени может добавить значительную ненужную сложность в вашу программу. Рассмотрим следующую простейшую функцию, которая подсчитывает количество слов в строке:

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

Содержание раздела