Не путайте привычность с читаемостью
(Или синдром "настоящего программиста, который может программировать на любом языке как на ФОРТРАНе"). Многие люди пытаются злоупотреблять препроцессором для того, чтобы придать Си большее сходство с каким-нибудь другим языком программирования. Например:
#define begin {
#define end }
while ( ... )
begin
// ...
end
Эта практика ничего не дает, кроме того, что ваш код становится нечитаемым для кого-нибудь, кто не знает того языка, который вы стараетесь имитировать. Для программиста на Си код станет менее читаемым, не более того.
Родственная проблема связана с использованием макросов препроцессора для скрытия синтаксиса объявлений Си. Например, не делайте следующего:
typedef const char *LPCSTR;
LPCSTR str;
Подобные вещи вызывают проблемы с сопровождением, потому что кто-то, не знакомый с вашими соглашениями, будет должен просматривать typedef,
чтобы разобраться, что происходит на самом деле. Дополнительная путаница возникает в Си++, потому что читатель может интерпретировать происходящее, как определение объекта Си++ из класса LPCSTR. Большинству программистов на Си++ не придет в голову, что LPCSTR
является указателем. Объявления Си очень легко читаются программистами на Си. (Кстати, не путайте вышеупомянутое с разумной практикой определения типа word в виде 16-битового числа со знаком для преодоления проблемы переносимости, присущей типу int, размер которого не определен ни в ANSI Си, ни в ANSI Си++).
К тому же, многие программисты избегают условной операции (?:) просто потому, что она им кажется непонятной. Тем не менее, это условное выражение может существенно упростить ваш код и, следственно, сделать его лучше читаемым. Я думаю, что:
printf("%s", str ? str : "пусто");
гораздо элегантнее, чем:
if ( str == NULL )
printf( "пусто" );
else
printf( "%s", str );
Вы к тому же экономите на двух вызовах printf(). Мне также часто приходится видеть неправильное использование операций ++ и --. Весь смысл автоинкремента или автодекремента заключается в соединении этих операций с другими. Вместо:
while ( *p )
{
putchar ( *p );
++p;
}
или:
for( ; *p ; ++p )
putchar ( *p );
используйте:
while( *p )
putchar ( *p++ );
Этот код вполне читаем для компетентного программиста на языке Си, даже если ему нет эквивалентной операции в ФОРТРАНе или Паскале.
Вы также никогда не должны прятать операторы в макросах из-за того, что вам просто не нравится, как они выглядят. Я однажды видел следующее в реальной программе:
struct tree_node
{
struct tree_node *lftchld;
};
#define left_child(x) ((x)-lftchld)
//...
traverse( tree_node *root )
{
if( left_child(root) )
traverse( left_child( root ) );
// ...
}
Программист намеренно сделал определение структуры труднее читаемым для того, чтобы избежать конфликта имен между полем и совершенно необходимым макросом, и все из-за того, что ему не нравился внешний вид оператора -. Для него было бы гораздо лучшим выходом просто назвать поле left_child и совсем избавиться от макроса.
Если вы действительно думаете, что программа должна внешне выглядеть как на Паскале, чтобы быть читаемой, то вы должны программировать на Паскале, а не на Си или Си++.