Читаем Искусство программирования для Unix полностью

Для языка С классическим примером такой проблемы является макрос, подобный следующему.

#define max(x, у) х > у ? х : у

Данный макрос создает как минимум две проблемы. Одна из них заключается в том, что он может вызвать непредсказуемые результаты, в случае если один из аргументов является выражением, включающим в себя оператор меньшего приоритета, чем > или ?:. Рассмотрим выражение max (а = Ь, ++с). Если программист забыл, что шах является макросом, то он будет ожидать, что присваивание а = b и преинкрементная операция с с будут выполнены до того, как результирующие значения будут переданы max в качестве аргументов.

Однако это не так. Вместо этого препроцессор преобразует данное выражение в а = b > ++с ? а = b : ++с, которое правила приоритета компилятора С заставляют интерпретировать как а = (b > ++с ? а = b : ++с). Результат будет присвоен а.

Подобное неверное взаимодействие можно предотвратить, кодируя определение макроса более безопасно.

#define max(x, у) ((х) > (у) ? (х) : (у))

С таким определением выражение будет развернуто как ((а = Ь) > (++с) ? (а = Ь) : (++с) ), что решает одну проблему, однако следует заметить, что переменная с может быть инкрементирована дважды. Существуют менее очевидные варианты данной проблемы, такие как передача макросу вызова функции с побочными эффектами.

Как правило, взаимодействие между макросами и выражениями с побочными эффектами может привести к неудачным результатам, которые трудно диагностировать. Макропроцессор С умышленно создан легковесным и простым. Более мощные макропроцессоры способны действительно вызвать более серьезные проблемы.

Язык форматирования TeX (см. главу 18) хорошо иллюстрирует общую проблему. ТеХ — умышленно разрабатывался как язык Тьюринга (в нем имеются условные операции, циклы и рекурсия), однако, несмотря на то, что его можно заставить делать поразительные вещи, TeX-код часто нечитабельный и трудный в отладке. Исходные коды для LaTeX, наиболее широко используемого ТeX-макропакета, являются поучительным примером: они созданы в очень хорошем TeX-стиле, но даже несмотря на это их крайне трудно понять.

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

Данная проблема особенно характерна для препроцессоров и макросов, которые могут иметь многострочные расширения, условно включать или исключать текст, или иным путем изменять количество строк в развернутом тексте.

Каскады макрорасширений, встроенные в язык, могут выполнять самокоррекцию, обрабатывая номера строк так, чтобы создать ссылки на текст до расширения. Так работает, например, макросредство утилиты ргс( 1). Данную проблему гораздо труднее решить, когда макрорасширение выполняется препроцессором.

В препроцессоре С проблема решается путем создания директив #line каждый раз, когда выполняется включение или многострочное расширение. Ожидается, что С-компилятор интерпретирует их и соответственно скорректирует номера строк в отчетах об ошибках. К сожалению, в макропроцессоре т4 подобное средство отсутствует.

Выше были описаны причины для крайне осторожного использования макрорасширений. Один из долгосрочных уроков опыта Unix заключается в том, что макросы склонны создавать больше проблем, чем решают. Современные конструкции языков и мини-языков отходят от их использования.

8.3.5. Язык или протокол прикладного уровня

Не менее важно определить, будут ли другие программы интерактивно вызывать ядро мини-языка в качестве подчиненного процесса. Если это так, то конструкция, вероятно, должна меньше походить на диалоговый язык для взаимодействия с человеком и быть более похожей на протоколы прикладного уровня, рассмотренные в главе 5.

Основное отличие заключается в том, насколько тщательным является обозначение границ транзакций. Люди хорошо определяют, где заканчивается диалоговый вывод CLI-интерфейса и где находится приглашения для следующего ввода. Человек может использовать контекст, для того чтобы определить, что значительно, а что следует проигнорировать. Компьютерным программам гораздо сложнее справиться с этой задачей. Без однозначных маркеров окончания вывода или указания его точной длины они не способны определить, когда необходимо прекратить чтение.

Еще хуже, когда ввод программы буферизируется (часто непреднамеренно, как в stdio). Программа, которая очевидно прекращает чтение в верной позиции, может, несмотря на это, впоследствии принять ввод.

Дуг Макилрой.

Перейти на страницу:

Похожие книги

Программист-прагматик. Путь от подмастерья к мастеру
Программист-прагматик. Путь от подмастерья к мастеру

Находясь на переднем крае программирования, книга "Программист-прагматик. Путь от подмастерья к мастеру" абстрагируется от всевозрастающей специализации и технических тонкостей разработки программ на современном уровне, чтобы исследовать суть процесса – требования к работоспособной и поддерживаемой программе, приводящей пользователей в восторг. Книга охватывает различные темы – от личной ответственности и карьерного роста до архитектурных методик, придающих программам гибкость и простоту в адаптации и повторном использовании.Прочитав эту книгу, вы научитесь:Бороться с недостатками программного обеспечения;Избегать ловушек, связанных с дублированием знания;Создавать гибкие, динамичные и адаптируемые программы;Избегать программирования в расчете на совпадение;Защищать вашу программу при помощи контрактов, утверждений и исключений;Собирать реальные требования;Осуществлять безжалостное и эффективное тестирование;Приводить в восторг ваших пользователей;Формировать команды из программистов-прагматиков и с помощью автоматизации делать ваши разработки более точными.

А. Алексашин , Дэвид Томас , Эндрю Хант

Программирование / Книги по IT
97 этюдов для архитекторов программных систем
97 этюдов для архитекторов программных систем

Успешная карьера архитектора программного обеспечения требует хорошего владения как технической, так и деловой сторонами вопросов, связанных с проектированием архитектуры. В этой необычной книге ведущие архитекторы ПО со всего света обсуждают важные принципы разработки, выходящие далеко за пределы чисто технических вопросов.?Архитектор ПО выполняет роль посредника между командой разработчиков и бизнес-руководством компании, поэтому чтобы добиться успеха в этой профессии, необходимо не только овладеть различными технологиями, но и обеспечить работу над проектом в соответствии с бизнес-целями. В книге более 50 архитекторов рассказывают о том, что считают самым важным в своей работе, дают советы, как организовать общение с другими участниками проекта, как снизить сложность архитектуры, как оказывать поддержку разработчикам. Они щедро делятся множеством полезных идей и приемов, которые вынесли из своего многолетнего опыта. Авторы надеются, что книга станет источником вдохновения и руководством к действию для многих профессиональных программистов.

Билл де Ора , Майкл Хайгард , Нил Форд

Программирование, программы, базы данных / Базы данных / Программирование / Книги по IT