newsmode
search
Меню
arrow_back Назад

Programming Bottom-Up

auto_awesomeКраткое саммари

Эссе Пола Грэма 1993 года из введения к книге On Lisp противопоставляет классическому нисходящему проектированию (top-down) восходящий подход (bottom-up), характерный для опытных Lisp-программистов. Суть метода в том, чтобы не только писать программу вниз к языку, но и наращивать язык вверх к программе, добавляя нужные операторы. В результате язык и программа эволюционируют вместе, и итоговый код получается компактнее, понятнее и эффективнее. Грэм перечисляет преимущества: уменьшение размера и числа компонентов, повторное использование утилит, улучшение читаемости и прояснение замысла за счёт замечаемых паттернов. Ссылаясь на Фредерика Брукса и его «Мифический человеко-месяц», автор переформулирует закон производительности: чем меньше группа, тем выше выработка отдельного программиста, а Lisp позволяет маленькой команде побеждать вчистую.

1993

1993

(This essay is from the introduction to On Lisp.)

(Это эссе — из введения к книге On Lisp.)

It's a long-standing principle of programming style that the functional
elements of a program should not be too large. If some component of a
program grows beyond the stage where it's readily comprehensible,
it becomes a mass of complexity which conceals errors as easily
as a big city conceals fugitives. Such software will be
hard to read, hard to test, and hard to debug.

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

In accordance with this principle, a large program must be divided
into pieces, and the larger the program, the more it must be divided.
How do you divide a program? The traditional approach is
called top-down design: you say "the purpose of the
program is to do these seven things, so I divide it into seven major
subroutines. The first subroutine has to do these four things, so
it in turn will have four of its own subroutines," and so on.
This process continues until the whole program has the right level
of granularity-- each part large enough to do something substantial,
but small enough to be understood as a single unit.

В соответствии с этим принципом большую программу необходимо делить на части, причём чем больше программа, тем сильнее её приходится дробить. Как же её делить? Традиционный подход называется нисходящим проектированием (top-down design): вы говорите: «Цель программы — выполнить вот эти семь задач, значит, я разобью её на семь основных подпрограмм. Первая подпрограмма должна делать вот эти четыре вещи, значит, у неё, в свою очередь, будет четыре собственных подпрограммы», — и так далее. Этот процесс продолжается, пока вся программа не приобретёт нужный уровень детализации: каждая часть достаточно велика, чтобы делать нечто существенное, но достаточно мала, чтобы её можно было охватить как единое целое.

Experienced Lisp programmers divide up their programs differently.
As well as top-down design, they follow a principle which
could be called bottom-up design-- changing the language
to suit the problem.
In Lisp, you don't just write your program down toward the language,
you also build the language up toward your program. As you're
writing a program you may think "I wish Lisp had such-and-such an
operator." So you go and write it. Afterward
you realize that using the new operator would simplify the design
of another part of the program, and so on.
Language and program evolve together.
Like the border between two warring states,
the boundary between language and program is drawn and redrawn,
until eventually it comes to rest along the mountains and rivers,
the natural frontiers of your problem.
In the end your program will look as if the language had been
designed for it.
And when language and
program fit one another well, you end up with code which is
clear, small, and efficient.

Опытные Lisp-программисты делят свои программы иначе. Наряду с нисходящим проектированием они следуют принципу, который можно назвать восходящим проектированием (bottom-up design) — подгонкой языка под задачу. В Lisp вы не просто пишете программу, спускаясь к языку, — вы ещё и поднимаете язык навстречу программе. Работая над программой, вы можете подумать: «Жаль, что в Lisp нет такого-то оператора». Тогда вы садитесь и пишете его. Потом вы понимаете, что использование нового оператора упростило бы проектирование другой части программы, — и так далее. Язык и программа развиваются вместе. Подобно границе между двумя враждующими государствами, рубеж между языком и программой проводится и перепроводится, пока в конце концов не ложится по горам и рекам — естественным границам вашей задачи. В итоге ваша программа будет выглядеть так, словно язык был разработан специально для неё. А когда язык и программа хорошо подогнаны друг к другу, на выходе получается код понятный, компактный и эффективный.

It's worth emphasizing that bottom-up design doesn't mean
just writing the same program in a different order. When you
work bottom-up, you usually end up with a different program.
Instead of a single, monolithic program,
you will get a larger language with more abstract operators,
and a smaller program written in it. Instead of a lintel,
you'll get an arch.

Стоит подчеркнуть: восходящее проектирование — это не просто написание той же программы в ином порядке. Работая снизу вверх, вы, как правило, получаете другую программу. Вместо единой монолитной программы вы получите более широкий язык с более абстрактными операторами и более короткую программу, написанную на этом языке. Вместо перемычки у вас получится арка.

In typical code, once you abstract out the parts which are
merely bookkeeping, what's left is much shorter;
the higher you build up the language, the less distance you
will have to travel from the top down to it.
This brings several advantages:

В типичном коде, если вынести за скобки те части, которые сводятся к чистой бухгалтерии, оставшееся оказывается значительно короче; чем выше вы надстраиваете язык, тем меньшее расстояние вам придётся проходить сверху вниз до него. Это даёт несколько преимуществ:

By making the language do more of the work, bottom-up design
yields programs which are smaller and more agile. A shorter
program doesn't have to be divided into so many components, and
fewer components means programs which are easier to read or
modify. Fewer components also means fewer connections between
components, and thus less chance for errors there. As
industrial designers strive to reduce the number of moving parts
in a machine, experienced Lisp programmers use bottom-up design
to reduce the size and complexity of their programs.

Заставляя язык брать на себя больше работы, восходящее проектирование порождает программы более компактные и подвижные. Более короткую программу не приходится дробить на такое количество компонентов, а меньше компонентов — значит, программу легче читать и изменять. Меньше компонентов означает и меньше связей между ними, а значит, меньше возможностей для ошибок на этих стыках. Подобно тому как промышленные дизайнеры стремятся сократить число подвижных частей в механизме, опытные Lisp-программисты используют восходящее проектирование, чтобы уменьшить размер и сложность своих программ.

Bottom-up design promotes code re-use.
When you write two
or more programs, many of the utilities you wrote for the first
program will also be useful in the succeeding ones. Once you've
acquired a large substrate of utilities, writing a new program can
take only a fraction of the effort it would require if you had to
start with raw Lisp.

Восходящее проектирование способствует переиспользованию кода. Когда вы пишете две и более программы, многие утилиты, написанные для первой, окажутся полезны и в последующих. Накопив большой пласт утилит, вы сможете писать новую программу за малую долю тех усилий, которые потребовались бы, начни вы с «голого» Lisp.

Bottom-up design makes programs easier to read.

Восходящее проектирование делает программы более удобными для чтения.

An instance of this type
of abstraction asks the reader to understand a general-purpose operator;
an instance of functional abstraction asks the reader to understand
a special-purpose subroutine. [1]

Использование такой абстракции требует от читателя понимания оператора общего назначения; использование функциональной абстракции требует понимания специализированной подпрограммы. [1]

Because it causes you always to be on the lookout for patterns
in your code, working bottom-up helps to clarify your ideas about
the design of your program. If two distant components of a program
are similar in form, you'll be led to notice the similarity and
perhaps to redesign the program in a simpler way.

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

Bottom-up design is possible to a certain degree in languages
other than Lisp. Whenever you see library functions,
bottom-up design is happening. However, Lisp gives you much broader
powers in this department, and augmenting the language plays a
proportionately larger role in Lisp style-- so much so that
Lisp is not just a different language, but a whole different way
of programming.

Восходящее проектирование в той или иной мере возможно и в языках, отличных от Lisp. Всякий раз, когда вы встречаете библиотечные функции, перед вами проявление этого подхода. Однако Lisp даёт вам в этой области куда более широкие возможности, и расширение языка играет в стиле Lisp пропорционально бо́льшую роль — настолько, что Lisp — это не просто другой язык, а совершенно другой способ программировать.

It's true that this style of development is better suited to
programs which can be written by small groups. However, at the
same time, it extends the limits of what can be done by a small
group. In The Mythical Man-Month,
Frederick Brooks
proposed that the productivity of a group of programmers
does not grow linearly with its size. As the size of the
group increases, the productivity of individual programmers
goes down. The experience of Lisp programming
suggests a more cheerful way
to phrase this law: as the size of the group decreases, the
productivity of individual programmers goes up.
A small group wins, relatively speaking, simply because it's
smaller. When a small group also takes advantage of the
techniques that Lisp makes possible, it can
win outright.

Верно, что такой стиль разработки лучше подходит для программ, которые могут быть написаны небольшими группами. Но в то же время он раздвигает границы того, на что способна маленькая группа. В книге The Mythical Man-Month Фредерик Брукс высказал мысль о том, что производительность группы программистов растёт нелинейно с её размером. По мере увеличения группы производительность отдельного программиста падает. Опыт программирования на Lisp подсказывает более жизнерадостную формулировку того же закона: по мере уменьшения группы производительность отдельного программиста растёт. Маленькая группа выигрывает — относительно — просто потому, что она меньше. А когда маленькая группа ещё и пользуется приёмами, которые открывает Lisp, она может выиграть вчистую.

[1] "But no one can read
the program without understanding all your new utilities."
To see why such statements are usually mistaken,
see Section 4.8.

[1] «Но никто ведь не сможет прочитать программу, не разобравшись во всех ваших новых утилитах». Чтобы понять, почему такие утверждения, как правило, ошибочны, см. раздел 4.8.