Читаемость Си-кода: грустный ликбез, чтобы жить стало веселее
Многие коллеги по цеху подтвердят, что читаемость кода на языке Си иногда оставляет желать лучшего. Как подтвердят и те, кто им плохо владеет, но так или иначе сталкивается в связи с рабочими задачами.
И всему виной (ожидаемо) выражения препроцессора и, как следствие, макросы. Да, вы правильно подумали. Именно те части кода, в которых вызываются такие легенды как:

Последние два очень легко спутать, если читать код невнимательно. Вместо них иногда рекомендуют использовать
#if defined // вместо #ifdef
#if !defined // вместо #ifndef
Но если же в компании/проекте/отделе нет определенного код-стайла, или он не предполагает написание длинного варианта, то, естественно, разрабы пишут короткую форму (я, честно признаться, тоже).
Чего далеко ходить - все open source проекты пестрят именно сокращенными вариантами этих выражений и с этим уже ничего не поделаешь.
А вот первое выражение из первой тройки игроков иногда вообще открывает врата ада, когда используется многострочный макрос с бэкслэш-символами (\
) на концах строк.
Конечно же выражения препроцессора - это очень гибкий и полезный инструмент, позволяющий делать всё:
условную компиляцию;
выравнивание структур;
предотвращать повторные включения файла;
работать со строками;
грамотно оборачивать повторяющийся код и т.д. и т.п.
Но бывают случаи, когда упрощение кода с точки зрения алгоритма делает его слабо читаемым для того самого бедного программиста, пытающегося прочесть этот шедевр машинописного текста:
struct {
const char *name;
const char *value;
#define _SPECIAL(x) { .name = #x, .value = b->x, }
} specials[] = {
{ .name = "object", .value = b->object_string, },
_SPECIAL(host),
_SPECIAL(endpoint),
#undef _SPECIAL
};
*ну все, можно начинать грустить*
Чтобы разобраться, давайте очистим код от макросов, но сохраним суть:
struct {
const char *name;
const char *value;
} specials[] = {
{ .name = "object", .value = b->object_string, },
{ .name = "host", .value = host, },
{ .name = "endpoint", .value = endpoint, },
};
Что понятно из очищенного варианта:
инициализируется массив
specials[]
;типом данных этого массива является структура с полями
*name
и*value
;поля элементов массива задаются вручную.
Но как можно этот процесс немного автоматизировать и не прописывать вручную одинаковые строки?
Правильно, с помощью макроса:
#define _SPECIAL(x) { .name = #x, .value = b->x, }
который определяется после объявления полей структуры.
Как обрабатывается аргумент x
:
имя аргумента преобразуется в строку с помощью макроса "
#x
" и присваивается полю*name
;полю
*value
присваивается значение поля структурыb
с названием аргументаx
(тут нужно убедиться, что поле с именемx
действительно существует в структуреb
).
То есть чтобы при заполнении массива не писать каждый раз одинаковые строчки:
{ .name = "host", .value = host, },
{ .name = "endpoint", .value = endpoint, }
можно вызвать выражение _SPECIALS
:
_SPECIAL(host),
_SPECIAL(endpoint),
Ну и в самом конце вызываем удаление созданного макроса, чтобы оно не было использовано в коде в дальнейшем:
#undef _SPECIAL
Теперь после прочтения этого небольшого ликбеза вы можете смело пользоваться макросами и создавать более сложные и нечитаемые шедевры наконец-то разобрались в том, насколько гибкими могут оказаться выражения препроцессора ( вообще не понимаю, как вы жили без них раньше...)
А какие выражения и макросы в Си видели вы?