Programming Bottom-Up
Эссе Пола Грэма 1993 года из введения к книге On Lisp противопоставляет классическому нисходящему проектированию (top-down) восходящий подход (bottom-up), характерный для опытных Lisp-программистов. Суть метода в том, чтобы не только писать программу вниз к языку, но и наращивать язык вверх к программе, добавляя нужные операторы. В результате язык и программа эволюционируют вместе, и итоговый код получается компактнее, понятнее и эффективнее. Грэм перечисляет преимущества: уменьшение размера и числа компонентов, повторное использование утилит, улучшение читаемости и прояснение замысла за счёт замечаемых паттернов. Ссылаясь на Фредерика Брукса и его «Мифический человеко-месяц», автор переформулирует закон производительности: чем меньше группа, тем выше выработка отдельного программиста, а Lisp позволяет маленькой команде побеждать вчистую.
1993
(Это эссе — из введения к книге On Lisp.)
Давний принцип стиля программирования гласит: функциональные элементы программы не должны быть слишком крупными. Если какой-то компонент программы разрастается настолько, что его уже нельзя легко охватить умом, он превращается в ком сложности, который скрывает ошибки так же легко, как большой город скрывает беглецов. Такой код трудно читать, трудно тестировать и трудно отлаживать.
В соответствии с этим принципом большую программу необходимо делить на части, причём чем больше программа, тем сильнее её приходится дробить. Как же её делить? Традиционный подход называется нисходящим проектированием (top-down design): вы говорите: «Цель программы — выполнить вот эти семь задач, значит, я разобью её на семь основных подпрограмм. Первая подпрограмма должна делать вот эти четыре вещи, значит, у неё, в свою очередь, будет четыре собственных подпрограммы», — и так далее. Этот процесс продолжается, пока вся программа не приобретёт нужный уровень детализации: каждая часть достаточно велика, чтобы делать нечто существенное, но достаточно мала, чтобы её можно было охватить как единое целое.
Опытные Lisp-программисты делят свои программы иначе. Наряду с нисходящим проектированием они следуют принципу, который можно назвать восходящим проектированием (bottom-up design) — подгонкой языка под задачу. В Lisp вы не просто пишете программу, спускаясь к языку, — вы ещё и поднимаете язык навстречу программе. Работая над программой, вы можете подумать: «Жаль, что в Lisp нет такого-то оператора». Тогда вы садитесь и пишете его. Потом вы понимаете, что использование нового оператора упростило бы проектирование другой части программы, — и так далее. Язык и программа развиваются вместе. Подобно границе между двумя враждующими государствами, рубеж между языком и программой проводится и перепроводится, пока в конце концов не ложится по горам и рекам — естественным границам вашей задачи. В итоге ваша программа будет выглядеть так, словно язык был разработан специально для неё. А когда язык и программа хорошо подогнаны друг к другу, на выходе получается код понятный, компактный и эффективный.
Стоит подчеркнуть: восходящее проектирование — это не просто написание той же программы в ином порядке. Работая снизу вверх, вы, как правило, получаете другую программу. Вместо единой монолитной программы вы получите более широкий язык с более абстрактными операторами и более короткую программу, написанную на этом языке. Вместо перемычки у вас получится арка.
В типичном коде, если вынести за скобки те части, которые сводятся к чистой бухгалтерии, оставшееся оказывается значительно короче; чем выше вы надстраиваете язык, тем меньшее расстояние вам придётся проходить сверху вниз до него. Это даёт несколько преимуществ:
Заставляя язык брать на себя больше работы, восходящее проектирование порождает программы более компактные и подвижные. Более короткую программу не приходится дробить на такое количество компонентов, а меньше компонентов — значит, программу легче читать и изменять. Меньше компонентов означает и меньше связей между ними, а значит, меньше возможностей для ошибок на этих стыках. Подобно тому как промышленные дизайнеры стремятся сократить число подвижных частей в механизме, опытные Lisp-программисты используют восходящее проектирование, чтобы уменьшить размер и сложность своих программ.
Восходящее проектирование способствует переиспользованию кода. Когда вы пишете две и более программы, многие утилиты, написанные для первой, окажутся полезны и в последующих. Накопив большой пласт утилит, вы сможете писать новую программу за малую долю тех усилий, которые потребовались бы, начни вы с «голого» Lisp.
Восходящее проектирование делает программы более удобными для чтения.
Использование такой абстракции требует от читателя понимания оператора общего назначения; использование функциональной абстракции требует понимания специализированной подпрограммы. [1]
Поскольку восходящий стиль вынуждает вас постоянно высматривать в своём коде закономерности, он помогает прояснить ваши представления о структуре программы. Если два далёких друг от друга компонента программы похожи по форме, вы заметите это сходство и, возможно, перепроектируете программу более простым образом.
Восходящее проектирование в той или иной мере возможно и в языках, отличных от Lisp. Всякий раз, когда вы встречаете библиотечные функции, перед вами проявление этого подхода. Однако Lisp даёт вам в этой области куда более широкие возможности, и расширение языка играет в стиле Lisp пропорционально бо́льшую роль — настолько, что Lisp — это не просто другой язык, а совершенно другой способ программировать.
Верно, что такой стиль разработки лучше подходит для программ, которые могут быть написаны небольшими группами. Но в то же время он раздвигает границы того, на что способна маленькая группа. В книге The Mythical Man-Month Фредерик Брукс высказал мысль о том, что производительность группы программистов растёт нелинейно с её размером. По мере увеличения группы производительность отдельного программиста падает. Опыт программирования на Lisp подсказывает более жизнерадостную формулировку того же закона: по мере уменьшения группы производительность отдельного программиста растёт. Маленькая группа выигрывает — относительно — просто потому, что она меньше. А когда маленькая группа ещё и пользуется приёмами, которые открывает Lisp, она может выиграть вчистую.
Новое: Скачать On Lisp бесплатно.
[1] «Но никто ведь не сможет прочитать программу, не разобравшись во всех ваших новых утилитах». Чтобы понять, почему такие утверждения, как правило, ошибочны, см. раздел 4.8.