Richard P. Gabriel이 소프트웨어 설계 철학에서 '더 나쁜 것이 더 나은 것(worse is better)' 개념에 대해 논의한 고전적 에세이. MIT와 뉴저지 접근의 차이, 그리고 Unix와 C의 성공 요인을 다룬다.
"Lisp: Good News, Bad News, How to Win Big."의 발췌문 [html]
저와 거의 모든 Common Lisp 및 CLOS 설계자는 MIT/Stanford 스타일의 설계에 크게 노출되어 있었습니다. 이 스타일의 정수는 ‘옳은 것(the right thing)’이라는 문구로 요약할 수 있습니다. 이러한 설계자들에게 다음 특징들을 모두 충족하는 것이 중요합니다:
대부분의 사람들이 이러한 특성이 좋다고 여길 것이라고 생각합니다. 저는 이러한 설계 철학을 _MIT 접근_이라 부르겠습니다. Common Lisp(및 CLOS), 그리고 Scheme이 대표적인 MIT 접근 방식입니다.
‘더 나쁜 것이 더 나은 것(worse-is-better)’ 철학은 약간 다릅니다:
초기 Unix와 C는 이 설계 방침의 예시이며, 저는 이를 _뉴저지 접근_이라고 부르겠습니다. 저는 여러분을 설득하기 위해 ‘더 나쁜 것이 더 나은 것’ 철학을 일부러 다소 우스꽝스럽게 표현했습니다. 이 철학이 분명히 나쁜 철학이고, 뉴저지 접근이 나쁜 방법임을 보여주고 싶었죠.
그런데도 저는 ‘더 나쁜 것이 더 나은 것’이, 그것이 허수아비 논리라 할지라도, ‘옳은 것’보다 더 뛰어난 생존 특성을 가지며, 소프트웨어에 한정할 때 뉴저지 접근이 MIT 접근보다 더 낫다고 믿습니다.
MIT/뉴저지의 구별이 유효하고, 각 철학 지지자들이 실제로 자신의 방식을 더 좋다고 믿는다는 점을 보여주는 이야기를 하나 들려드리겠습니다.
MIT 출신의 유명 인사와 버클리에서(하지만 Unix 작업을 하던) 또 다른 유명 인사가 운영체제 이슈에 대해 만난 적이 있습니다. MIT 인사는 ITS(MIT AI Lab 운영체제)에 정통했으며 Unix 소스도 읽고 있었습니다. 그는 Unix가 PC loser-ing 문제를 어떻게 해결하는지 궁금해 했죠. PC loser-ing 문제는 사용자 프로그램이 I/O 버퍼 같은 상당한 상태를 가진 시스템 루틴을 호출해 오래 걸리는 연산을 하는 중 인터럽트가 발생하면, 프로그램의 상태를 저장해야하는데, 호출이 보통 한 명령어로 이뤄지므로 사용자 프로그램의 PC(프로그램 카운터)만으로는 상태 포착이 충분치 않습니다. 시스템 루틴은 백아웃하거나 진행해야 합니다. 옳은 방법은 백아웃하고, 사용자 프로그램의 PC를 호출 시점 명령어로 돌려놓아 인터럽트 후 복귀 시 시스템 루틴에 다시 진입하게 하는 것입니다. ‘PC loser-ing’이란, MIT에서 사용자를 ‘loser(루저)’라 애칭했던 데서, PC를 _loser 모드_로 강제전환한다는 의미입니다.
MIT 인사는 관련 처리 코드를 못 찾겠다며 뉴저지 인사에게 문제 해결책을 물었습니다. 뉴저지 인사는 Unix 쪽도 문제를 알고 있으나, 시스템 루틴이 항상 동작을 완료하고, 실패할 땐 에러 코드로 처리한다는 것이었습니다. 즉, 올바른 사용자 프로그램이 에러 코드를 점검하고 다시 시스템 루틴을 시도하도록 구현하는 거죠. MIT 인사는 이런 방식이 ‘옳은 것’이 아니라고 반박했습니다.
뉴저지 인사는 Unix의 설계 철학이 단순성이므로 옳은 방법은 복잡해서 안 맞다고 했고, 프로그래머들이 이런 추가 테스트와 루프를 쉽게 넣을 수 있다고 했습니다. MIT 인사는 구현은 단순하지만, 그렇게 되면 인터페이스가 복잡해진다고 했죠. 뉴저지 인사는 Unix가 구현 단순성을 인터페이스 단순성보다 우선한 올바른 절충을 했다고 답했습니다.
MIT 인사는 때론 질긴 남자가 부드러운 치킨을 만든다며 중얼거렸지만, 뉴저지 인사는 이해하지 못했습니다(저도 잘 모르겠네요).
이제 저는 ‘더 나쁜 것이 더 나은 것’이 정말로 낫다고 주장해 보려고 합니다. C는 Unix 작성을 위해 뉴저지 접근으로 설계된 프로그래밍 언어입니다. 따라서 컴파일러 구현이 쉽고, 필요한 자원이 적으며, 텍스트 자체가 컴파일러에 해석되기 쉽게 되어 있습니다. 누군가는 C를 ‘화려한 어셈블리어’라고도 했습니다. 초기 Unix와 C 컴파일러는 모두 구조가 단순하고, 이식이 쉽고, 적은 하드웨어 자원으로도 돌아가며, 운영체제와 언어 필요의 50~80% 정도를 충족해줍니다.
언제나 존재하는 컴퓨터의 절반은 중앙값 이하입니다(더 작거나 느림). Unix와 C는 이런 기기에서도 잘 동작합니다. ‘더 나쁜 것이 더 나은 것’ 철학은 구현 단순성을 최우선하므로 Unix와 C를 이런 기기들로 손쉽게 이식할 수 있게 해줍니다. 만약 Unix와 C가 제공하는 50% 기능이 충분하다면, 이들이 빠르게 번져나가는 것은 당연합니다. 그리고 실제로 그렇게 되었습니다.
Unix와 C는 최고의 컴퓨터 바이러스입니다.
더 나쁜 것이 더 나은 것 철학의 추가 이점은, 프로그래머가 어느 정도의 안정성, 편의성, 번거로움을 희생하여 좋은 성능과 적절한 자원 사용을 얻는 데 익숙해진다는 점입니다. 뉴저지 접근으로 작성된 코드는 소형 기계와 대형 기계 모두에서 잘 작동하며, ‘바이러스’ 위에서 작성되기에 이식성도 높습니다.
처음의 바이러스가 기본적으로 좋아야 한다는 점은 중요합니다. 만약 그렇다면, 다 이식만 된다면 전파는 보장됩니다. 한 번 바이러스가 퍼지면, 그 기능을 90%까지 늘리라는 압박이 있을 수 있지만, 사용자는 이미 ‘옳은 것’보다 못한 것에 익숙해졌습니다. 그래서, 더 나쁜 것이 더 나은 것 소프트웨어는 1) 먼저 받아들여지고, 2) 사용자는 적은 것에 익숙해지고, 3) 거의 옳은 것에 가까운 수준까지 개선됩니다. 실제로, 1987년경 Lisp 컴파일러도 C 컴파일러만큼 좋았으나, 더 많은 컴파일러 전문가들이 C 컴파일러 향상에 관심을 보였습니다.
좋은 소식은 1995년에는 좋은 운영체제와 프로그래밍 언어를 가질 거란 것이고, 나쁜 소식은 그게 Unix와 C++일 거라는 점입니다.
마지막으로 더 나쁜 것이 더 나은 것의 또 다른 이점이 있습니다. 뉴저지 언어나 시스템은 복잡한 거대 소프트웨어를 만들 만큼 강력하지 않기 때문에, 큰 시스템은 구성 요소의 재사용을 염두에 두고 설계되어야만 합니다. 그 결과 통합의 전통이 생깁니다.
그럼 ‘옳은 것’은 어떨까요? 두 가지 대표적인 시나리오가 있습니다: _거대 복합 시스템 시나리오_와 _보석처럼 다이아몬드 같은 시나리오_입니다.
거대 복합 시스템 시나리오는 이렇게 진행됩니다:
먼저 ‘옳은 것’을 설계합니다. 그런 다음 구현 방안을 설계합니다. 마지막으로 구현합니다. ‘옳은 것’이니 당연히 기능의 100%에 가깝고, 구현의 단순성엔 신경 안 쓰므로 완성까지 시간이 많이 듭니다. 결과적으로 크고 복잡합니다. 제대로 사용되려면 복잡한 도구가 필요합니다. 마지막 20%를 구현하는 데 전체 노력의 80%가 들어가므로, ‘옳은 것’은 출시까지 오래 걸리고, 최고급 하드웨어에서만 만족스럽게 동작합니다.
보석 같은 다이아몬드 시나리오는:
‘옳은 것’은 설계에 정말 오래 걸리지만, 언제나 크기는 꽤 작습니다. 이를 빠르게 구현하는 것은 거의 불가능하거나, 대부분의 구현자가 할 수 없는 수준입니다.
이 두 시나리오는 각각 Common Lisp와 Scheme에 대응됩니다.
첫 번째 시나리오는 고전적인 인공지능 소프트웨어의 시나리오이기도 합니다.
‘옳은 것’은 종종 일체형(모놀리식) 소프트웨어인 경우가 많지만, 꼭 그럴 이유가 있다기보다, 설계가 그런 식으로 이루어진 우연의 결과일 때가 많습니다.
여기서 얻을 교훈은, 처음부터 ‘옳은 것’을 추구하는 것이 바람직하지 않을 때가 많다는 점입니다. 차라리 50%만이라도 만들어서 바이러스처럼 퍼지게 하고, 사람들이 익숙해진 뒤에 90%로 개선하는 것이 낫습니다.
잘못된 교훈은 이 우화를 문자 그대로 받아, C가 인공지능 소프트웨어에 적합하다고 결론짓는 것입니다. 50% 해답이 ‘기본적으로 옳아야’ 하며, 이 경우는 그렇지 않습니다.
결론적으로, 리스프 커뮤니티는 리스프 설계에 대한 자세한 재고가 필요하다는 점만 말씀드리겠습니다. 이에 대해서는 나중에 좀 더 이야기하겠습니다.