Paul Graham의 Programming Bottom-Up

Categories Writings

Viaweb(야후에 인수됨)과 Y Combinator의 창업자이자 Lisp의 선구자인 Paul Graham은 자신의 웹사이트 paulgraham.com에 프로그래밍과 스타트업에 대해 훌륭한 에세이들을 기고해왔다. Programming Bottom-Up은 그가 bottom-up 프로그래밍 방식의 장점에 대해서 강조하기 위해 쓴 에세이이다. 아래에 에세이의 전문 번역을 포스팅한다.


This essay is from the introduction to On Lisp. The red text explains the origins of Arc’s name.

이 에세이는 Paul Graham의 저서 On Lisp: : Advanced Techniques for Common Lisp (1993) 의 서문에 실린 글입니다. Paul Graham은 On Lisp에서 프로그래밍 언어 Lisp 을 이용한 프로그래밍 기법과 bottom-up 프로그래밍에 대해 저술하였습니다.

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 디자인이라고 불립니다. 예를 들자면 “만약 전체 프로그램이 일곱개의 기능을 가져야 한다고 하면, 프로그램을 일곱개의 서브루틴으로 나눌 수 있고, 첫번째 서브루틴은 다시 4개의 하위 기능을 가져야 하므로 네개의 하위 서브루틴으로 나눌 수 있고…” 이런 과정이 반복되는 방식 입니다. 이렇게 프로그램을 쪼개는 과정은 전체 프로그램이 충분히 잘게 쪼게질때까지 반복되는데 나뉘어진 각 부분은 충분히 구현되어야 하는 기능을 충실히 수행하면서도 하나의 단위로서 충분히 쉽게 이해될 수 있어야 합니다.

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 프로그래머들은 top-down 디자인 만큼이나 bottom-up 디자인이라고 불리는 방식을 자주 이용합니다. 이 디자인 원칙의 핵심은, 한마디로 요약하면 이용하는 프로그래밍 언어를 개발하려는 프로그램에 최적화되도록 만들어 나간다는 것에 있습니다. 즉, Lisp을 이용해서 프로그램을 작성할 때 프로그램을 쪼개어 나가면서 (위에서 아래로 내려간다는 표현을 씁시다.) 가장 하위의 언어의 단계로까지 내려가게 하는 것이 아니라, 동시에 언어 자체를 확장해나가며 프로그램의 단계로 올라가게 한다는 것입니다. 만약 프로그램의 어떤 부분을 작성하면서 “나는 Lisp이 이런 연산자를 가지고 있으면 좋을 것 같다.”라고 생각하면 그 기능을 바로 구현하기 시작하는 것입니다. 후에 개발을 진행하면서 그 새로운 연산자가 다른 부분에서도 유용하게 쓰여 프로그램의 설계를 훨씬 단순화시킬 수 있다는 것을 발견할 수 있을 것입니다. 좀 더 쉽게 말해서, bottom-up 방식에서는 프로그램과 프로그래밍 언어가 동시에 진화합니다. 마치 전쟁 중인 두 국가의 국경선이 끊임없이 다시 그려지다가 마침내 산과 강을 따라서 수렴하게 되는 것처럼, 언어와 프로그램 사이의 경계선도 끊임없이 다시 그어지다가 프로그램에 최적화되어 있는 경계선으로 확정되게 됩니다. 이렇게 완성된 프로그램에선 마치 프로그램 언어가 처음부터 이 프로그램을 위해서 디자인되어 있었던 것처럼 보이게 됩니다. 프로그램과 프로그래밍 언어가 서로에게 가장 알맞는 모습이 되면, 프로그래머가 짠 코드는 굉장히 명료하고, 간단하며 효율적인 형태가 되어 있을 것입니다.

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.

여기서 중요한 것은 bottom-up 디자인이 단지 프로그램을 작성할때 top-down 방식으로 프로그램을 작성할때의 반대 순서로 작성하는 것만을 의미하는 것은 아니라는 점입니다. 프로그래머가 bottom-up 방식으로 프로그램을 작성하면, 완성된 프로그램은 top-down 방식으로 작성한 프로그램과는 전혀 다른 모습이 됩니다. 하나의 통일된 프로그램 대신에 좀 더 추상화된 연산자들을 가지게 되며, 훨씬 더 간결한 프로그램으로 재탄생하게 됩니다. 건축에 비유하자면 천장을 지탱하기 위해서 두개의 기둥을 쌓아올리고 그 위에 대들보(lintel)를 올려놓는 대신에 처음부터 아치(arch)를 쌓아올리는 것으로 표현 할 수 있을 것입니다. (역주: 참고로 이러한 Paul Graham의 아치 비유는 Paul Graham과 Robert Morris가 개발한 Lisp의 방언인 Arc의 이름의 기원이 되었습니다.)

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:

일단 프로그래머가 필요하다고 생각하는 부품들을 추상화하여 쌓아올리고 나면, 남은 작업은 매우 간단합니다. 프로그래밍 언어를 더 높이 쌓아올릴 수록, top-down을 해야 하는 거리가 줄어들게 되므로 따라서 작업이 훨씬 간단해지는 것입니다. 이는 다음의 몇가지 장점들을 불러옵니다.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  1. 프로그래밍 언어의 기능이 확장됨으로써, 프로그램의 구현은 훨씬 간결하고(짧고) 또 훨씬 유연해집니다. 간결하고 짧은 프로그램은 여러개의 컴포넌트로 나뉘어질 필요가 없습니다. 이는 코드를 읽고 고치기가 훨씬 더 자유로워진다는 것을 의미합니다. 또한 컴포넌트의 개수가 줄어든다는 것은 컴포넌트 간의 연결이 훨씬 줄어든다는 것을 의미하기도 합니다. 따라서 에러에 가장 취약할 수 있는 이 연결 고리들 사이에서 에러가 발생할 가능성을 줄이게 됩니다. 기계를 설계하는 산업 디자이너나 엔지니어들이 움직이는 부속의 수를 줄이기 위해서 고군분투하는 것과 마찬가지로, 숙련된 Lisp 프로그래머들은 bottom-up 디자인을 이용해서 프로그램의 크기와 복잡도를 줄이는데 몰두합니다.
  2. Bottom-up 디자인은 코드의 재활용성(reusability)을 증대시킵니다. 흔히, 여러개의 프로그램을 작성할때 처음 프로그래머가 작성했던 유틸리티(역주: 앞에서 프로그래머가 언어 레벨에서 필요하다고 생각했던 기능을 Bottom-up 방식으로 구현한 구현체들)들은 그 다음 프로그램들을 작성할 때 요긴하게 사용됩니다. 경험 많은 프로그래머가 이전의 프로그램들을 위해서 충분히 이런 유틸리티들을 많이 구현해놓고 있다면 raw Lisp (이런 유틸리티들이 전혀 구현되어 있지 않은 순수한 Lisp)으로 처음부터 프로그램을 작성하는 것보다 훨씬 적은 노력으로 프로그램을 작성할 수 있게 됩니다.
  3. (프로그래머가 프로그래밍 언어를 확장하기 위해서 짠 유틸리티에 대해 이해하고 있다는 전제 하에) Bottom-up 디자인은 프로그램의 가독성을 높여줍니다. Bottom-up 디자인과 같은 추상화 방식에서는 연산자들을 좀 더 일반적으로 사용하는 것이 가능합니다. 반면에 함수를 이용한 추상화 방식에서는 프로그램을 읽는 사람이, 프로그래머가 정의한 이 새로운 함수를 이해할 것을 요구합니다. (역주: 예를 들어 "Hello"라는 문자열과 "World"라는 문자열을 잇는 작업을 한다고 했을 때 Bottom-up 디자인에서는 "Hello" + "World" 와 같이 일반적인 + 연산자를 사용할 수 있게 언어를 확장하는 것이 가능합니다. Bottom-up 방식을 사용하지 않고 함수로 이를 구현하는 경우에는 이 작업을 해주는 함수, 가령 concat("Hello", "World") 와 같은 함수를 만들고 호출하는 식으로 구현이 됩니다. 전자가 후자보다 가독성이 낫다는 것은 다소 자명한 것입니다.)
  4. 마지막으로, Bottom-up으로 프로그램을 작성하기 위해선 전체 코드의 패턴을 살피고 이해하고 있어야 하므로, 프로그래머가 작성하는 프로그램의 디자인에 대한 아이디어를 명확히 이해하는데 도움을 줍니다. 동일한 패턴을 가지고 있는 두개의 컴포넌트가 꼭 코드 상에서 붙어있으리라는 보장은 없습니다. 만약 멀리 떨어져있는 두개의 컴포넌트가 유사성을 가지고 있다면 프로그래머는 그 유사성을 파악하고, 전체 프로그램을 다시 좀 더 간단한 형태로 설계할 수 있도록 유도될 것입니다.

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.

Bottom-up 디자인은 Lisp 말고도 다른 언어들에도 어느정도 적용될 수 있습니다. 라이브러리 함수들은 bottom-up 디자인의 대표적인 예라고 볼 수 있을 것입니다. 물론 Lisp에서 bottom-up 디자인은 훨씬 더 빛을 발합니다. 이 것은 Lisp의 스타일이 단지 C와 Java와 같은 프로그램과 다른 문법을 가진 프로그래밍 언어라서가 아니라, 완전히 다른 방식의 프로그래밍 패러다임이기 때문입니다. (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.

사실 이런 소프트웨어 개발 스타일은 큰 규모의 팀보다는 작은 규모의 팀에서 작업할 때 더 적합합니다. 대신 이런 방식은 작은 규모의 팀이 할 수 있는 일의 한계를 놀라울 정도로 증대시킵니다. Frederick Brooks는 그의 저서 Mythical Man-Month에서 프로그래머 그룹의 생산성은 팀의 규모와 정비례하지 않는다는 유명한 이론을 제안한 바 있습니다. 실제로 많은 경우에, 팀의 규모가 커질수록 프로그래머 개개인의 생산성은 떨어지게 됩니다. 반대로 팀의 규모가 줄어들수록 프로그래머 개개인의 생산성은 향상됩니다. 작은 팀이 큰 팀에 승리하는 이유는 단지 규모가 작기 때문입니다. 그러나 만약에 작은 규모의 팀이 Lisp 프로그래밍이 가능하게 만든 bottom-up 프로그래밍의 장점을 취할 수 있게 된다면 어떤 일이 일어날까요? 그 팀은 천하무적의 팀이 될 것입니다.

Paul Graham은 늘 전세계의 개발자들에게 자신이 믿고 있는 프로그래밍 방법론들과 패러다임의 장점에 대해 설파하는데 열심이다. 이 글에서도 새로운 신도들을 만드려는 듯한 그의 의지가 여전하다.

이 글을 온전히 이해하기 위해서는 함수형 프로그래밍 패러다임에 대한 깊이 있는 이해가 필요한 것처럼 보이지만 그 방대한 내용들을 공부하기 위해서는 많은 시간이 걸릴 것이므로 일단 논외로 두려고 한다. 그러나 bottom-up 이라는 용어를 처음들어본 사람들에게도 익숙할 사용자 정의 라이브러리나 API와 같은 개념을 ‘언어의 확장’이라는 측면에서 접근했다는 점에서 매우 신선하다는 생각이 들었다. ‘마치 그 프로그램을 위해서 존재하는 언어를 만들 듯이’ 프로그래밍 한다는 그의 표현은, 분명 이런 설계 기법이 생소한 사람들에게도 단박에 와닿는 설명이다. 더불어서 왜 이 기법이 top-down 방식에 비해서 이점을 가지는지에 대한 설명도 너무나 논리적이고 탁월하다.

마치 기계를 설계할 때 표준화된 부품들을 사용함으로서 그 종류의 개수를 줄이려는 시도를 하려는 것처럼 프로그래밍에서도 처음부터 이런 ‘표준화된 부품(본문의 컴포넌트 혹은 유틸리티)’들을 미리 만들어 놓음으로서 효율적인 구현을 하겠다는 것은, 비록 이를 염두에 두고 작업을 해보지는 않았지만 분명 고개를 끄덕일 수 있을만한 합당한 시도처럼 보인다.

다만 그가 지적하는 것처럼, 그 ‘표준화된 부품’을 만들기 위해선, 어떤 부품을 표준화 해야 할 것인가에 대한 프로그래머의 통찰력이 분명 필요할 것이며 그 것은 경험이 많은 프로그래머들만이 익숙하게 해낼 수 있는 작업일 것이다. 그러나 그의 주장을 염두에 두고 프로그래밍하면 분명 효율 면에서 큰 개선을 이룰 수 있을 것 같다.

저자의 주장의 유효성에 대해서 논하는 것을 떠나서, 늘 테크 구루들이나 뛰어난 엔지니어들의 글에선, 사소하게 지나갈 수 있는, 너무나도 당연해 보이는 접근이나 방식들을, 그들은 얼마나 잘 체계화하고 이론으로 만들어 방법론화하는지 실감하게 된다. 이런 패러다임화를 통해서 탄탄한 시스템을 구축하고 좀 더 효율적인 작업을 꾀하는 이들의 노력이, 수준 높은 차원의 엔지니어링에는 필수적인 요소라는 것을 다시 한번 느끼게 된다.

2 Comments

  • mars
    February 16, 2016

    영어 잘하시네요.
    좋은 글 감사합니다.

    • EastskyKang
      February 17, 2016

      감사합니다!

Comments are closed.