Правила программирования на С и С++. Главы 1-6 - Не путайте привычность с читаемостью
ОГЛАВЛЕНИЕ
50. Не путайте привычность с читаемостью.
(Или синдром "настоящего программиста, который может программировать на любом языке как на ФОРТРАНе"). Многие люди пытаются злоупотреблять препроцессором для того, чтобы придать С большее сходство с каким-нибудь другим языком программирования. Например:
#define begin {#define end }
while ( ... )
begin
// ...
end
Эта практика ничего не дает, кроме того, что ваш код становится нечитаемым для кого-нибудь, кто не знает того языка, который вы стараетесь имитировать. Для программиста на С код станет менее читаемым, не более того.Родственная проблема связана с использованием макросов препроцессора для скрытия синтаксиса объявлений С. Например, не делайте следующего:
typedef const char *LPCSTR;LPCSTR str;
Подобные вещи вызывают проблемы с сопровождением, потому что кто-то, не знакомый с вашими соглашениями, будет должен просматривать typedef, чтобы разобраться, что происходит на самом деле. Дополнительная путаница возникает в С++, потому что читатель может интерпретировать происходящее, как определение объекта С++ из класса LPCSTR. Большинству программистов на С++ не придет в голову, что LPCSTR является указателем. Объявления С очень легко читаются программистами на С. (Кстати, не путайте вышеупомянутое с разумной практикой определения типа word в виде 16-битового числа со знаком для преодоления проблемы переносимости, присущей типу int, размер которого не определен в 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 и совсем избавиться от макроса.Если вы действительно думаете, что программа должна внешне выглядеть как на Паскале, чтобы быть читаемой, то вы должны программировать на Паскале, а не на С или С++.