상향식 프로그래밍
1993
(On Lisp 서문에서 발췌한 글입니다.)
프로그램의 기능적 요소들이 너무 커서는 안 된다는 것은 프로그래밍 스타일의 오랜 원칙입니다. 만약 프로그램의 어떤 구성 요소가 쉽게 이해할 수 있는 단계를 넘어설 정도로 커지면, 그것은 마치 대도시가 도망자들을 숨기듯이 오류를 쉽게 감추는 복잡성의 덩어리가 됩니다. 그런 소프트웨어는 읽기 어렵고, 테스트하기 어려우며, 디버깅하기 어려울 것입니다.
이 원칙에 따라, 큰 프로그램은 여러 조각으로 나뉘어야 하며, 프로그램이 클수록 더 많이 나뉘어야 합니다. 프로그램을 어떻게 나눌까요? 전통적인 접근 방식은 _하향식 설계(top-down design)_라고 불립니다. 즉, "이 프로그램의 목적은 이 일곱 가지를 하는 것이므로, 나는 그것을 일곱 개의 주요 서브루틴으로 나눌 것이다. 첫 번째 서브루틴은 이 네 가지를 해야 하므로, 그것은 다시 네 개의 자체 서브루틴을 가질 것이다"와 같이 말하는 식입니다. 이 과정은 전체 프로그램이 적절한 세분화 수준(granularity)을 가질 때까지 계속됩니다. 즉, 각 부분이 실질적인 작업을 수행할 만큼 충분히 크면서도 단일 단위로 이해될 만큼 충분히 작아야 합니다.
숙련된 Lisp 프로그래머들은 프로그램을 다르게 나눕니다. 그들은 하향식 설계뿐만 아니라, _상향식 설계(bottom-up design)_라고 불릴 수 있는 원칙을 따릅니다. 즉, 문제를 해결하기 위해 언어를 변경하는 것입니다. Lisp에서는 프로그램을 언어에 맞춰 작성할 뿐만 아니라, 언어를 프로그램에 맞춰 구축하기도 합니다. 프로그램을 작성하면서 "Lisp에 이런저런 연산자(operator)가 있었으면 좋겠다"고 생각할 수 있습니다. 그러면 직접 만들러 갑니다. 그 후에는 새로운 연산자를 사용하는 것이 프로그램의 다른 부분 설계를 단순화할 수 있다는 것을 깨닫게 되고, 이런 식으로 계속됩니다. 언어와 프로그램은 함께 진화합니다. 마치 두 전쟁 중인 국가 사이의 국경처럼, 언어와 프로그램 사이의 경계는 그려지고 다시 그려지기를 반복하다가, 결국 문제의 자연적인 경계인 산과 강을 따라 자리 잡게 됩니다. 결국 당신의 프로그램은 마치 언어가 그 프로그램을 위해 설계된 것처럼 보일 것입니다. 그리고 언어와 프로그램이 서로 잘 맞을 때, 당신은 명확하고, 작으며, 효율적인 코드를 얻게 됩니다.
상향식 설계가 단순히 같은 프로그램을 다른 순서로 작성하는 것을 의미하지 않는다는 점을 강조할 가치가 있습니다. 상향식으로 작업할 때, 당신은 보통 다른 프로그램을 만들게 됩니다. 단일하고 거대한(monolithic) 프로그램 대신, 더 추상적인 연산자들을 가진 더 큰 언어와 그 언어로 작성된 더 작은 프로그램을 얻게 될 것입니다. 상인방(lintel) 대신, 아치(arch)를 얻게 될 것입니다.
일반적인 코드에서, 단순히 장부 정리(bookkeeping)에 해당하는 부분을 추상화하고 나면, 남는 것은 훨씬 짧아집니다. 언어를 더 높이 구축할수록, 위에서 아래로 내려올 거리가 줄어듭니다. 이는 다음과 같은 몇 가지 장점을 가져다줍니다:
-
언어가 더 많은 작업을 수행하게 함으로써, 상향식 설계는 더 작고 민첩한 프로그램을 만들어냅니다. 더 짧은 프로그램은 그렇게 많은 구성 요소로 나눌 필요가 없으며, 구성 요소가 적다는 것은 프로그램을 읽거나 수정하기 더 쉽다는 것을 의미합니다. 구성 요소가 적다는 것은 구성 요소 간의 연결도 적다는 것을 의미하며, 따라서 오류 발생 가능성도 줄어듭니다. 산업 디자이너들이 기계의 움직이는 부품 수를 줄이려고 노력하듯이, 숙련된 Lisp 프로그래머들은 상향식 설계를 사용하여 프로그램의 크기와 복잡성을 줄입니다.
-
상향식 설계는 코드 재사용을 촉진합니다. 두 개 이상의 프로그램을 작성할 때, 첫 번째 프로그램을 위해 작성한 많은 유틸리티들이 다음 프로그램들에서도 유용할 것입니다. 일단 방대한 유틸리티 기반(substrate)을 확보하고 나면, 새로운 프로그램을 작성하는 데 필요한 노력은 '날것의 Lisp(raw Lisp)'에서 시작해야 할 때 필요한 노력의 일부에 불과할 수 있습니다.
-
상향식 설계는 프로그램을 더 읽기 쉽게 만듭니다. 이러한 유형의 추상화(abstraction)는 독자에게 범용 연산자를 이해하도록 요구합니다. 반면 기능적 추상화는 독자에게 특수 목적의 서브루틴을 이해하도록 요구합니다. [1]
-
코드에서 패턴을 항상 찾도록 유도하기 때문에, 상향식 작업은 프로그램 설계에 대한 당신의 아이디어를 명확히 하는 데 도움이 됩니다. 만약 프로그램의 두 개의 멀리 떨어진 구성 요소가 형태적으로 유사하다면, 당신은 그 유사성을 알아차리게 될 것이고, 아마도 프로그램을 더 간단한 방식으로 재설계하게 될 것입니다.
상향식 설계는 Lisp 외의 다른 언어에서도 어느 정도 가능합니다. 라이브러리 함수를 볼 때마다 상향식 설계가 이루어지고 있는 것입니다. 하지만 Lisp는 이 분야에서 훨씬 더 광범위한 권한을 제공하며, 언어를 확장하는 것이 Lisp 스타일에서 비례적으로 더 큰 역할을 합니다. 그래서 Lisp는 단순히 다른 언어가 아니라, 완전히 다른 프로그래밍 방식입니다.
이러한 개발 방식이 소규모 그룹이 작성할 수 있는 프로그램에 더 적합하다는 것은 사실입니다. 하지만 동시에, 그것은 소규모 그룹이 할 수 있는 일의 한계를 확장합니다. _맨먼스 미신(The Mythical Man-Month)_에서 프레더릭 브룩스(Frederick Brooks)는 프로그래머 그룹의 생산성이 그 규모에 비례하여 선형적으로 증가하지 않는다고 제안했습니다. 그룹의 규모가 커질수록 개별 프로그래머의 생산성은 떨어집니다. Lisp 프로그래밍 경험은 이 법칙을 더 낙관적인 방식으로 표현할 수 있음을 시사합니다. 즉, 그룹의 규모가 줄어들수록 개별 프로그래머의 생산성은 올라갑니다. 소규모 그룹은 상대적으로 말해서, 단순히 규모가 작기 때문에 이깁니다. 소규모 그룹이 Lisp가 가능하게 하는 기술들을 활용할 때, 그들은 완전히 승리할 수 있습니다.
새로운 소식: 『On Lisp』 무료 다운로드.
[1] "하지만 당신의 새로운 유틸리티들을 모두 이해하지 않고는 아무도 프로그램을 읽을 수 없습니다." 왜 그런 진술이 대개 잘못되었는지 보려면, 4.8절을 참조하십시오.