Glean 拾遗
专辑 / Paul Graham 文集 / 自底向上编程:Lisp 哲学如何重塑程序与语言的边界

自底向上编程:Lisp 哲学如何重塑程序与语言的边界

原文 www.paulgraham.com 收录 2026-07-07 16:39 阅读 6 min
AI 解读

Paul Graham 在本文中系统阐述了 Lisp 社区独特的自底向上设计方法:不是从需求向下分解为子程序,而是先构建语言工具,让语言与问题域自然匹配。他指出,在 Lisp 中,程序员同时做两件事:向下写程序,向上建语言,最终语言与程序沿问题天然边界融为一体。这种风格催生了更短、更易读、更可复用的代码,并且特别适合小团队发挥高生产力。文章对比了传统自顶向下设计,强调了抽象层提升带来的组件减少和错误降低,也讨论了其在小团队中的放大效应。尽管写于 1993 年,其思想对现代 DSL 设计、框架构建和 API 设计仍有深刻启示。适合对编程语言、软件架构、抽象设计感兴趣的工程师。

原文 6 分钟
原文 www.paulgraham.com ↗
§ 1

Programming Bottom-Up

自底向上编程

§ 2

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.

编程风格中有一条长期存在的原则:程序的功能元素不应过大。如果某个组件膨胀到难以理解的程度,它就会变成一团复杂体,像大城市隐藏逃犯一样轻易地隐藏错误。这样的软件将难以阅读、测试和调试。

根据这一原则,大型程序必须被拆分成小块,程序越大,拆分得越细。如何拆分?传统方法是所谓的自顶向下设计:你说“这个程序要做七件事,所以我把它分成七个主要子程序。第一个子程序要做四件事,因此它又有四个子程序”,依此类推。这个过程持续到整个程序达到合适的粒度——每部分足够大以完成实质功能,但又足够小以作为一个独立单元被理解。

§ 3

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 程序员以不同的方式划分程序。除了自顶向下设计,他们还遵循一条可称为自底向上设计的原则——改变语言以适应问题。在 Lisp 中,你不仅向下把程序写成语言,还向上把语言构建成适合你的程序。编写程序时,你可能会想:“要是 Lisp 有某某操作符就好了。”于是你就去写一个。之后你发现,使用这个新操作符会简化程序中另一部分的设计,如此循环。语言和程序共同演化。就像两个交战国之间的边界,语言和程序的分界线不断被绘制和重绘,最终沿着你问题的天然疆界——山川河流——安顿下来。最终你的程序看起来就像语言是为它量身定制的一样。当语言和程序彼此完美契合时,你得到的代码清晰、紧凑且高效。

§ 4

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:

值得强调的是,自底向上设计并不意味着简单地用不同的顺序编写相同的程序。当你自底向上工作时,最终得到的程序通常是不同的。你不会得到一个单一、庞大的程序,而是得到一个包含更抽象操作符的更大语言,以及用该语言编写的更小程序。你不会得到一根过梁,而会得到一个拱门。在典型代码中,一旦你将那些仅仅是簿记性质的部分抽象出来,剩下的部分就会短得多;你把语言构建得越高,从顶层向下到达它的距离就越短。这带来了几个优势:

§ 5

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 程序员使用自底向上设计来减少程序的规模和复杂度。

§ 6

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 的一小部分。

§ 7

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]

§ 8

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.

由于自底向上工作总是促使你留意代码中的模式,它有助于澄清你在程序设计上的想法。如果程序中两个相距很远的组件在形式上相似,你会注意到这种相似性,并可能以更简单的方式重新设计程序。

§ 9

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 不仅仅是一种不同的语言,而是一种完全不同的编程方式。

§ 10

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.

诚然,这种开发风格更适合可以由小团队编写的程序。但同时,它扩展了小团队所能做到的极限。在《人月神话》中,弗雷德里克·布鲁克斯提出,一组程序员的生产力并不随团队规模线性增长。随着团队规模增大,单个程序员的生产力反而下降。Lisp 编程的经验提供了一种更乐观的表达方式:随着团队规模减小,单个程序员的生产力上升。小团队相对而言赢,仅仅因为更小。当小团队同时利用 Lisp 所提供的技术时,它可以完胜。

§ 11

[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 节。

打开原文 ↗