Data Loading...

유연한 소프트웨어를 만드는 설계 원칙 Flipbook PDF

https://www.hanbit.co.kr/store/books/look.php?p_code=B3855421165


111 Views
56 Downloads
FLIP PDF 1.87MB

DOWNLOAD FLIP

REPORT DMCA

프로그래머들은 시간에 쫓기는 나머지, 어쩔 수 없이 제한된 용도로만 사용 가능한, 성장의 여지가 거의 없는 코드를 작성한다. 그로 인해 과거의 자신이 쌓은 벽에 가로막혀 코드를 새로 수정해야 하는 상황이 벌 어지기도 한다. 하지만 최고의 시스템은 진화할 수 있는 능력을 갖췄다. 기존 코드를 수정하는 대신 새 코 드를 추가해 새로운 상황에 적응하는 것이다. 이 책은 크리스 핸슨과 제럴드 제이 서스먼이 도합 100년이 넘는 프로그래밍 경험에서 발견한, 프로그래머가 막다른 골목을 피하는 데 도움이 되는 기법들을 담았다.

개한다. 각 장에는 연습문제와 리스프의 방언인 스킴으로 만들어진 예제 코드를 실었다. 천문 거리 계산 에서 자동 미분, 어드벤처 게임에 이르기까지 다양한 예제를 살펴본다.

표준 인터페이스를 따르는 짜맞춤 방식으로 작은 함수부터 산술 패키지 전체까지 다양한 구성 요소를 조립하는 시스템 구성법 측정 단위나 자료 출처 같은 정보를 담은 독립적인 주해를 사용한 데이터 증강 통합이나 전파를 이용한 독립적인 부분 정보 조각 조합

관련 도서

리팩터링 (2판)

처음 배우는 엘릭서 프로그래밍

모던 자바 인 액션 컴퓨터공학 / 개발 방법론

크리스 핸슨, 제럴드 제이 서스먼, 지음

동적으로 확장 가능한 평가자를 이용한 프로그래밍 언어 확장

류광 옮김

영역 모형, 규칙 시스템, 패턴 부합, 전파, 의존성 지향적 역추적을 이용한 제어 구조와 문제 영역 분리

막다른 골목 없이 진화하는 시스템 개발하기

설계 원칙

이 책은 동적으로 확장 가능한 일반적 프로시저처럼 극도로 강력하지만 잠재적으로 위험한 기법들도 소

유연한 소프트웨어를 만드는

누가 코드를 이렇게 짜놨어?

유연한 소프트웨어를 만드는

설계 원칙

93000

예제 소스 github.com/jeffhhk/SoftwareDesignForFlexibility

크리스 핸슨, 제럴드 제이 서스먼 지음 9 791162 245408

ISBN 979-11-6224-540-8

류광 옮김

정가 35,000원

유연한 소프트웨어를 만드는 설계원칙_펼침.indd 1-3

2022. 3. 10. 오전 10:57

유연한 소프트웨어를 만드는

설계 원칙 크리스 핸슨, 제럴드 제이 서스먼 지음 류광 옮김

유연한 소프트웨어를 만드는 설계 원칙 막다른 골목 없이 진화하는 시스템 개발하기 초판 1쇄 발행 2022년 3월 30일 지은이 크리스 핸슨, 제럴드 제이 서스먼 / 옮긴이 류광 / 펴낸이 김태헌 펴낸곳 한빛미디어 (주) / 주소 서울시 서대문구 연희로2길 62 한빛미디어(주) IT출판부 전화 02 – 325 – 5544 / 팩스 02 – 336 – 7124 등록 1999년 6월 24일 제25100 – 2017 – 000058호 / ISBN 979 – 11 – 6224 – 540 – 8 93000 총괄 전정아 / 책임편집 서현 / 기획 이민혁 / 편집 이민혁 / 교정 오현숙 디자인 표지·내지 윤혜원 / 전산편집 백지선 영업 김형진, 김진불, 조유미, 김선아 / 마케팅 박상용, 송경석, 한종진, 이행은, 고광일, 성화정 / 제작 박성우, 김정우 이 책에 대한 의견이나 오탈자 및 잘못된 내용에 대한 수정 정보는 한빛미디어(주)의 홈페이지나 아래 이메일로 알려주십시오. 잘못된 책은 구입하신 서점에서 교환해드립니다. 책값은 뒤표지에 표시되어 있습니다. 한빛미디어 홈페이지 www.hanbit.co.kr / 이메일 [email protected]

Software Design for Flexibility Copyright © 2021 by Massachusetts Institute of Technology All rights reserved. This Korean edition was published by Hanbit Media Inc. in 2022 by arrangement with The MIT Press through KCC(Korea Copyright Center Inc.), Seoul. 이 책은 (주)한국저작권센터(KCC)를 통한 저작권자와의 독점계약으로 한빛미디어(주)에서 출간되었습니다. 저작권법에 의해 한국 내에서 보호를 받는 저작물이므로 무단전재와 복제를 금합니다.

지금 하지 않으면 할 수 없는 일이 있습니다. 책으로 펴내고 싶은 아이디어나 원고를 메일 ( [email protected] ) 로 보내주세요. 한빛미디어(주)는 여러분의 소중한 경험과 지식을 기다리고 있습니다.

막다른 골목 없이 진화하는 시스템 개발하기

유연한 소프트웨어를 만드는

설계 원칙 크리스 핸슨, 제럴드 제이 서스먼 지음 류광 옮김

헌사

컴퓨터는 바이올린과 비슷하다. 바이올린 초보자가 먼저 음반을 재생한 다음에 스스로 바이올 린을 켠다고 상상해 보자. 초보자는 자신의 바이올린 소리가 끔찍하다고 말할 것이다. 우리의 인본주의자( humanist )들과 대다수의 컴퓨터 과학자도 그렇게 주장한다. 그들을 컴퓨터 프로 그램들이 특정한 목적에는 훌륭하지만, 유연하지 않다고 말한다. 바이올린도 타자기도 유연하 지 않다—우리가 사용법을 익히기 전에는. 마빈 민스키, “Why Programming Is a Good Medium for

Expressing Poorly-Understood and Sloppily-Formulated Ideas (프로그래밍이 잘 이해되지 않고 허술하게 정식화된 개념들 을 표현하기에 좋은 매체인 이유)”, Design and Planning ( 1967 )

5

지은이 소개

지은이

크리스 핸슨  Chris Hanson

크리스 핸슨은 데이테라Datera의 기술 직원이다. 여러 해 동안 MIT에서 지내면서 MIT/GNU 스킴 시스템의 주 저자로 일했다. 구글에서도 10년간 일했다.

지은이

제럴드 제이 서스먼  Gerald Jay Sussman

제럴드 제이 서스먼은 MIT 전기공학부 파나소닉 교수( Panasonic Professor of Electrical

Engineering )이다.

6

옮긴이 소개

옮긴이 류광

25년 이상의 번역 경력을 가진 전문 번역가로, 『컴퓨터 프로그래밍의 예술』 ( The Art of Computer Programming ) 시리즈와 『UNIX 고급 프로그래밍』 ( Advanced Programming in UNIX Environment ) 제2 판 및 제3 판, 『인공지능: 현대적 접근방식』 ( Artificial Intelligence: A Modern Approach ) 제3판 및 제4판, 『Game Programming Gems』 시 리즈를 비롯해 80권 이상의 다양한 IT 전문서를 번역했다. 개인 웹사이트 류광의 번역 이야기( http://occamsrazr.net )와 게임 개발 웹사이트 Gpg Study ( http://gpgstudy.com )를 운영한다.

7

옮긴이의 말

‘고전’으로 칭송되는 SICP ( Structure and Interpretation of Computer Programs; 번역서 『컴 퓨터 프로그램의 구조와 해석』)의 저자 중 한 명인 제럴드 서스먼이 공저한 새 책의 번역을 제 안받고 설레는 마음이 컸지만, 한편으로는 걱정스럽기도 했습니다. 무엇보다도, 소프트웨어 개 발에서 유연성 항샹을 주요 목표로 삼는 게 과연 좋은 일일까 하는 의문이 들었습니다. 예를 들 어 유연성 향상 때문에 복잡성( complexity )이 증가할 수도 있습니다. 그러나 번역을 마친 지 금의 생각은, 유연성 향상 때문에 생기는 복잡성의 상당 부분은 소프트웨어를 만들고 고치기 힘들게 하는 부정적인 요인이라기보다는, 유연성을 담보하는 ‘축중성’과 ‘중복성’(§1.3 참고)으 로 작용하는 게 아닌가 하는 것입니다. 그리고 복잡성만큼이나 중요한 두 요인인 효율성(성능) 과 정확성에도 유연성의 추구가 나쁜 영향을 줄 수 있는데, 이에 관해서는 §1.5 “유연성의 비 용”이 답이 될 것입니다. 또 다른 걱정거리는 이 책의 예제 코드가 스킴Scheme이라는, 다수의 번역서 독자에게 생소 한 프로그래밍 언어를 사용한다는 점입니다. SICP는 스킴 입문서 역할도 했다고 하는데, 이 책 은 부록 A에서 스킴 문법을 간단하게만 소개할 뿐입니다. 이 책 같은 고급 개발서는 본문을 힌 트로 코드를 파악하고 코드를 힌트로 본문을 파악하면서 전체적인 이해도를 차츰 높이는 ‘나선 형 독서’가 필요한데, 스킴에 익숙하지 않다면 나선형 루프를 완성하기 어려울 것입니다. 책을 읽어 나가다가 스킴 자체가 학습에 걸림돌이 된다고 판단한다면, 과감하게 책을 덮고 스킴 공 부에 시간을 투자하시길 권합니다. 그렇긴 하지만(그리고 이 책을 계기로 스킴의 세계에 푹 빠지는 것도 물론 좋은 일이지만) 이 책이 주창하는 ‘가산적 프로그래밍’(또는 첨가식 프로그래밍)을 실천하기 위해 자신의 주력 언어를 억지로 스킴으로 바꿀 필요까지는 없을 것입니다. 비록 스킴만큼 간결하고 자연스럽게 표현할 수 없다고 해도, 다른 현대적인 프로그래밍 언어로도 이 책에 나오는 여러 유연성 향상 기법( DSL, 조합, 패턴 부합, 전파, 통합 등등)을 구현할 수 있기 때문입니다. “이 예제를 내가 애용하는 언어로는 어떻게 구현할 수 있을까?”를 생각하면서 읽어 나가면 책을 완독하는 데 도 움이 될 것입니다.

8

예제 코드와 관련해서 한 가지 덧붙이자면, 이 책은 흔히 볼 수 있는 튜토리얼 또는 ‘따라 하 기’식 책이 아니라서 그 자체로는 실행이 안 되는 예제 코드들도 종종 등장함을 주의해야 합니 다. 예를 들어 나중에 정의할 프로시저나 형식을 미리 사용하기도 하고, 본문에는 나오지 않 고 ‘지원 소프트웨어’(부록 B 참고)에만 있는 프로그램 구성요소를 사용하는 경우도 있습니 다. 본문의 예제 코드를 실제로 실행해 보려면 독자가 능동적으로 빈 구멍을 채워나가야 하는 데, 소스 코드 파일들을 빠르게 검색하는 도구가 있으면 한결 편할 것입니다. 번역하면서 저는

GitHub 저장소 https://github.com/jeffhhk/SoftwareDesignForFlexibility를 활용했 습니다. 번역하면서 “이걸 굳이 이렇게?”라는 의구심이 “와, 이게 되네!”라는 감탄으로 변하는 순 간을 여러 번 겪은 것이 기억에 남습니다. 그런 순간들을 독자 여러분도 느낄 수 있도록 번역 과 교정에 힘썼지만, 여전히 오역과 오타, 다듬어지지 않은 문장이 남아 있을 것입니다. 오탈 자 및 오역 제보와 의견 교환을 위한 공간을 제 웹사이트의 https://occamsrazr.net/book/ SD4Flex 페이지에 마련해 두었으니 많이 활용해 주세요.

끝으로, 전체 과정을 매끄럽고 합리적으로 진행해 주신 한빛미디어 이민혁 대리님과 차분하 면서도 생동감 있는 디자인으로 책을 조판해 주신 백지선 실장님을 비롯해 이 책의 탄생에 관 여한 모든 분께 감사 인사 드립니다. 그리고 이번에도 번역 원고를 꼼꼼히 검토하고 무수한 오 타와 비문을 잡아낸 아내 오현숙에게 고맙고 사랑하는 마음을 전합니다. 재미있게 읽으시길! — 옮긴이 류광

9

추천사

프로그램을 작성하다 보면 막다른 곳에 봉착할 때가 종종 있다. 그 이유는 문제의 일부 측면을 제대로 파악하지 못했기 때문일 수도 있지만, 프로그램 설계 초기에 내린 결정, 이를테면 데이 터 구조 선택이나 코드 조직화 방식에 관한 결정이 알고 보니 너무 제한적이었으며, 지금 와서 되돌리기가 너무 어렵기 때문일 때가 훨씬 많다. 이 책은 유연성을 유지하는 구체적인 프로그램 조직화 전략들에 관한 하나의 ‘마스터 클래스 master class’◆이다.

다들 알고 있겠지만, 처리할 데이터를 담기 위해 별생각 없이 고정된 크기

의 배열을 선언해 두면, 나중에 예상보다 더 긴 입력을 받아야 하거나 더 많은 개수의 레코드를 처리해야 할 필요가 생길 때 후회하게 된다. 보안 버그 중에는 고정 된 크기로 메모리 버퍼를 할당하고는 입력 데이터가 그 버퍼에 맞는 크기인지 점검하는 것을 까먹어서 생긴 것이 많다 (특히 인터넷 코드에서). 동적으로 할당된 저장소( C 스타일의 malloc 라이브러리가 제공한 것이든, 자동적인 쓰레기 수거기( garbage collector )가 제공한 것이든)는 고정 크기 배열보 다 훨씬 복잡하지만 유연성은 훨씬 좋으며, 그래서 오류와 실수의 여지가 적다는(특히, 배열 참조 시 색인이 범위 안에 있는지를 항상 점검하는 프로그래밍 언어에서) 추가적인 장점도 있 다. 이외에도 유연성에는 여러 가지 장점이 있다. 여러 초기 프로그래밍 언어 설계들은 하버드 아키텍처Harvard architecture라고 부르는 하드웨 어 조직화 스타일을 반영한 하나의 설계 원리를 따랐다. 그 원리란, 코드와 데이터는 독립적인 존재로서 명확히 분리되며, 데이터를 주무르는( massage ) 것이 코드의 임무라는 것이다. 그러 나 코드와 데이터의 이런 유연하지 않은 분리는 프로그램 조직화에 심대한 제약이 되었다. 20 세기가 끝나기 훨씬 전에 우리는 코드를 데이터처럼 취급하는 능력과 데이터를 코드처럼 취급 하는 능력이 유용하다는 점을, 그리고 작은 코드 조각과 관련 데이터를 묶는 것이 코드와 데이 터를 획일적인 조각들로 분리하는 것보다 유리하다는 점을 함수형 프로그래밍 언어( ML, 스킴, 하스켈 같은)와 객체 지향적 프로그래밍 언어(시뮬라, 스몰토크, C++, 자바 등)로부터 배웠

◆ 옮긴이 마스터 클래스는 특정 분야의 전문가가 해당 분야 학생들에게 한시적으로 제공하는 수업이다. ‘명인 강좌’라고 부르기도 한다.

10

다. 가장 유연한 종류의 데이터는, 수치나 문자 같은 ‘기본 데이터 항목’뿐만 아니라 함수 같은 실행 가능 코드에 대한 참조도 담을 수 있는 레코드 구조이다. 가장 강력한 종류의 코드는 적절 한 양의 선별된 자료와 함께 묶인 다른 코드를 생성하는 코드이다. 그러한 묶음(번들)은 단지 ‘함수 포인터’가 아니라, 하나의 클로저closure (함수형 언어에서) 또는 객체(객체 지향적 언어에 서)이다. 이 책에서 크리스 핸슨과 제리 서스먼은 앞 문단에서 언급한 기본적인 유연성 전략을 더욱 확장하는 일단의 기법을 제시한다. 이 기법들은 합해서 100년이 넘는 두 저자의 프로그래밍 경험에서 뽑은, 그리고 수십 년의 MIT 수업에서 개발하고 검증한 것이다. 꽉 막힌 보통의 함수 대신 “끝이 열려 있는( open-ended )” 일반적( generic ) 함수를 사용하라. 함수를 작게 유지 하라. 종종 함수가 할 수 있는 최선의 일은 다른 함수(선별된 데이터로 커스텀화된)를 돌려주 는 것이다. 데이터를 코드처럼 다루는 데 주저하지 말라. 필요하다면, 여러분의 응용 프로그램 안에 새로운 내장 프로그래밍 언어를 만드는 수준까지 나아가게 될 수도 있다. (사실 스킴 언 어 자체가 이런 관점에서 만들어진 것이다. 리스프의 한 방언(파생 언어)인 MacLisp는 완전 히 일반적인 형태의 함수 클로저를 지원하지 않았기 때문에, 제리와 나는 MacLisp를 이용해서 우리에게 필요한 종류의 함수 클로저를 지원하는 리스프의 또 다른 내장형 방언을 만들었다.) 기존 데이터 구조를, 원래의 데이터 구조를 포함하며 그 능력을 확장하는 좀 더 일반적인 데이 터 구조로 대체하는 데 주저하지 말라. 어떤 데이터 항목이 입력이고 어떤 데이터 항목이 출력 인지에 관한 섣부른 결정을 피하기 위해 자동 제약 전파를 활용하라. 이 책은 개괄서( survey )나 튜토리얼이 아니다. 앞에서 말했듯이 이것은 하나의 마스터 클 래스이다. 각 장에서 여러분은 두 전문가가 고급 기법을 시연하는 모습을 보게 될 것이다. 두 저자는 작동하는 코드 조각을 점차 발전시켜 나가면서 주요 전략을 설명하며, 종종 피해야 할 함정을 알려 주거나 제한을 제거하기 위한 힌트를 제공한다. 여러분 스스로 데이터 구조를 확 장해서, 또는 추가적인 코드를 작성해서 이 기법들을 직접 펼쳐 보여야 할 때를 위해, 더 나아 가서는 저자들이 보여준 것 이상으로 여러분의 상상력과 창조력을 활용해야 할 때를 위해 열심

11

히 공부하기 바란다. 이 책에 담긴 개념과 착안은 풍부하고 심오하다. 문장과 코드 둘 다 세심 하게 읽고 파악한다면 큰 보상이 따를 것이다.

가이 L. 스틸 Jr.Guy L. Steele Jr.



매사추세츠주 렉싱턴



2020년 8월

12

서문

우리 개발자들은, 기존 코드를 작성 당시에는 인식하지 못한 방식으로 사용할 수 있도록 변형 하는 데 너무 많은 시간을 허비해왔다. 이는 끔찍한 시간 낭비이자 노력 낭비이다. 안타깝게도, 우리는 재사용할 수 있는 부품이 몇 개 없는, 아주 특화된 용도 하나로만 매우 잘 작동하는 코 드를 작성해야 한다는 압박을 많이 받는다. 그렇지만 우리 저자들은 꼭 그래야 하는 것은 아니 라고 생각한다. 설계자가 예측한, 좀 더 다양한 부류의 상황들에서 적절하게 행동하는 시스템을 구축하기란 어려운 일이다. 최고의 시스템은 진화 능력을 갖추고 있다. 그런 시스템들은 사소한 수정만으 로도 새 상황에 적응한다. 그런 종류의 유연성을 갖춘 시스템을 설계하려면 어떻게 해야 할까? 기존 코드 기반( code base )을 수정하지 않고 그냥 새 코드를 더 작성하는 것만으로 프로그 램에 새 기능을 추가할 수 있다면 좋을 것이다. 코드 기반을 특정한 몇 가지 조직화 원리를 이 용해서 구축해 둔다면, 그리고 구축 과정에서 적절한 ‘훅hook’들을 코드 기반에 끼워 둔다면 그 런 일이 가능할 때가 많다. 생체계( biological system; 생물학적 시스템)를 관찰하면 유연하고 진화하는 시스템을 만 드는 방법에 관해 많은 것을 배울 수 있다. 원래는 기호적 인공지능을 지원하기 위해 개발된 기 법들을, 프로그램을 비롯한 여러 공학적 시스템의 유연성과 적응성을 개선하는 방법으로도 볼 수 있다. 반면 컴퓨터 과학 분야에서는, 프로그램을 새로운 환경에서 사용하도록 고치기 쉬운 방식으로 시스템을 구축하는 데 오히려 방해되는 관행들이 흔하게 통용된다. 우리 저자들은 프로그램을 짜다가 막다른 골목에 갇히는, 그리고 거기서 벗어나기 위해 코 드를 엄청나게 리팩터링한 경우가 많다. 그러다 보니, 원래의 설계에서는 생각하지 않았던 용 도에 맞게 적응시킬 수 있는 대형 시스템을 구축하는 데 효과적인 전략들과 기법들을 식별하 고, 격리하고, 시연하는 데 충분한 경험이 쌓였다. 이 책에서 우리 저자들은, 합해서 100년이 넘는 프로그래밍 경험에서 얻은 ‘열매’ 중 일부를 공유하고자 한다.

13

이책 이 책은 우리가 MIT에서 컴퓨터 프로그래밍을 가르치는 과정에서 개발된 것이다. 수년 전에 그 강좌를 시작할 때 우리의 의도는 인공지능 응용의 핵심부에 해당하는 프로그램을 작성하는 데 유용한 기법과 기술(수학적 기호 조작과 규칙 기반 시스템 등)을 학부 고학년과 대학원생에 게 가르치는 것이었다. 우리는 수강생들이 그런 시스템들을 조합해서 더욱더 강력한 시스템을 구축할 수 있을 정도로 유연하게 시스템을 구축하게 되길 원했다. 또한 우리는 수강생들에게 의존성을 추적하는 방법과 의존성을 설명과 역추적 제어에 활용하는 방법을 가르치고자 했다. 그 강좌는 지금까지 성공적으로 진행되고 있지만, 초기에는 수업 내용을 우리가 생각하는 것만큼 잘 이해하지는 못했음이 드러났다. 그래서 우리는 도구들을 다듬고 개념들을 더욱 정교 하게 만드는 데 큰 노력을 들였다. 이제는 이런 기법들이 단지 인공지능 응용 프로그램만을 위 한 것이 아님을 알고 있다. 우리는 컴퓨터 언어 컴파일러나 통합 개발 환경 같은 복잡한 시스템 을 구축하는 그 누구에게도 우리의 경험이 이로울 것이라고 믿는다. 이 책은 현재 우리의 수업 에 쓰이는 강의 내용과 문제 집합을 기반으로 한다.

내용 이 책에는 한 학기 강좌에서 다룰 수 있는 것보다 훨씬 많은 내용이 들어 있다. 그래서 MIT 강 좌에서는 이 책에서 몇몇 주제만 선택해서 수업을 진행한다. 제1장에서는 우리의 프로그래밍 철학을 소개한다. 여기서는 유연성을 자연과 공학이라는 좀 더 큰 맥락에서 살펴본다. 여기서 우리는 유연성이 효율성과 정확성만큼이나 중요한 문제라고 주장한다. 이후의 장들에서는 유 연성을 위한 기법들을 소개하고 일단의 예제와 연습문제를 통해서 설명한다. 이는 이 책의 중 요한 구성 원리이다. 제2장에서는 성장의 여지가 있는 시스템을 구축하기 위한, 보편적으로 적용할 수 있는 방법 몇 가지를 살펴본다. 유연한 시스템을 조직화하는 강력한 방법 하나는, DSL (영역 특화 언어) 들을 조립해서 시스템을 구축하는 것이다. 제2장에서는 DSL 개발을 위한 기본 수단들을 개발 한다. 짜맞춤( mix-and-match; 조합식) 부품들을 위주로 하위 시스템들을 조직화하는 방

14

법, 그런 하위 시스템들을 조합자( combinator )를 이용해서 유연하게 조립하는 방법, 부품들 을 래퍼wrappers를 이용해서 일반화하는 방법, 문제 영역 모형( domain model )을 추상화해 서 프로그램을 단순화하는 방법을 배우게 될 것이다.

제3장에서는 극도로 강력하지만 잠재적으로 위험한 유연성 기법인, 술어로 디스패치되는

일반적 프로시저( generic procedure )를 소개한다. 기존 산술 패키지를 기호 수식(기호적 대

수 표현식)도 다룰 수 있도록 확장하는 것으로 출발해서, 데이터에 형식 태그를 적용해서 이러 한 일반화의 효율성을 높이는 방법을 살펴보고, 간단하지만 확장하기 쉬운 어드벤처 게임의 설 계를 예로 들어서 이러한 기법의 위력을 보여준다. 제4장에서는 기호적 패턴 부합( pattern matching )을 소개하고, 이를 이용해서 항 재작성 ( term-rewriting ) 시스템을 구현한다. 제4장의 후반부에서는 통합( unification )을 이용해 서 형식 추론( type inference )을 수월하게 구현하는 방법도 살펴본다. 그 과정에서 역추적 ( backtracking )의 필요성이 제기된다. 통합은 부분 정보( partial-information ) 구조들을 표 현하고 조합하는 기능의 위력이 처음으로 드러나는 지점이다. 제4장 끝에서는 패턴 부합 개념 을 일반적인 그래프로 확장한다. 제5장에서는 해석( interpretation )과 컴파일( compilation )의 위력을 살펴본다. 우리는, 프 로그래머라면 주어진 문제의 해법을 표현하는 데 더 적합한 언어를 위한 해석기(인터프리터) 를 작성해서 현재 사용 중인 프로그래밍 언어의 제약을 벗어나는 방법을 알아 두어야 마땅하다 고 믿는다. 제5장에서는 비결정론적 amb 연산자를 구현해서 역추적 검색을 해석기/컴파일러 시스템에 자연스럽게 통합하는 방법과 후속( continuation ) 프로시저를 사용하는 방법도 살펴 본다. 제6장에서는 계층적 데이터( layered data )와 계층적 프로시저( layered procedure )들로 시 스템을 구축하는 방법을 살펴본다. 그런 시스템에서는 각 데이터 항목에 다양한 메타데이터를 주해( annotation )로 달 수 있다. 그러한 메타데이터가 바탕 데이터의 처리 자체에는 영향을 미치지 않으며, 바탕 데이터를 처리하는 코드는 그런 메타데이터를 참조하지 않으며 알지도 못

15

한다. 메타데이터는 개별 프로시저가 데이터와는 사실상 병렬적으로 처리한다. 이러한 기법을, 수치적 수량에 단위를 부여하는 시스템과 의존성 정보를 전달, 유지하는(그럼으로써 기본 출 처에서 유도된 데이터의 기원을 파악할 수 있는) 시스템을 예로 들어서 살펴본다. 제6장의 기법들이 기반해서, 제7장에서는 컴퓨터 언어들의 표현식 지향적 패러다임에서 벗 어나기 위한 전파( propagation ) 개념을 소개한다. 전파에 기초한 설계는 여러 모듈이 연결 된 배선도( wiring-diagram )에 비유된다. 이러한 설계에서는 다양한 출처에서 온 부분 정 보를 유연하게 통합할 수 있다. 계층적 데이터를 이용해서 의존성을 추적하는 덕분에, 크고 복잡한 시스템의 검색 공간을 크게 줄여주는 의존성 지향적 역추적( dependency-directed

backtracking )을 구현할 수 있다. 이 책을 다양한 종류의 고급 강좌에 사용할 수 있을 것이다. 제2장에서 소개한 조합자 개념 과 제3장에서 소개한 일반적 프로시저는 그 이후의 모든 장에 쓰인다. 그러나 제4장의 패턴 언 어 및 패턴 부합과 제5장의 평가자는 이후 장들에 쓰이지 않는다. 제5장의 내용 중 이후의 장 들에 쓰이는 것은 §5.4와 §5.4.1에서 소개한 amb 뿐이다. 제6장의 계층화 개념은 일반적 프로 시저 개념과 밀접한 관련이 있지만, 고유한 특성도 존재한다. 제6장에서 하나의 예제로 소개 한, 계층화를 이용한 의존성 추적의 구현은 전파(제7장)의 필수 요소로 쓰인다. 제7장은 역추 적 검색을 최적화하기 위해 의존성을 활용한다.

스킴 이 책의 예제 코드는 스킴Scheme으로 작성되었다. 스킴은 리스프의 일종으로, 함수형 언어 ( functional language )에 아주 가까운 언어이다.◆ 스킴이 인기 있는 언어도 아니고 업계에 서 널리 쓰이는 언어도 아니지만, 이 책의 목적에는 딱 맞다.1

◆ 옮긴이 원문은 “a mostly functional language”인데, mostly가 붙은 이유는 “순수한” 함수형 언어에는 없는(또는, 허 용되지 않는) 기능들도 갖추고 있기 때문이다. 1 부록 B에서 스킴 언어의 특징과 문법을 간단하게나마 소개한다.

16

이 책의 목적은 프로그래밍 개념들을 제시하고 설명하는 것이다. 여러 가지 이유로, 스킴을 이용하면 그런 개념들을 보여주는 예제 코드를 다른 인기 있는 언어보다 더 짧고 간단하게 표 현할 수 있다. 그리고 다른 언어로는 보여주기가 거의 불가능한 개념들도 있다. 리스프 계열이 아닌 언어들은 간단한 것을 표현할 때도 사전 ‘의식( ceremony )’이 많이 필 요하다. 반면, 이 책의 예제 코드들에서 코드가 길어지는 유일한 원인은 우리 저자들이 계산 가 능한 객체에 길고 서술적인 이름을 붙이길 좋아한다는 점뿐이다. 스킴의 문법은 극도로 단순하다. 스킴 코드는 그냥 구문 파스 트리를 자연스럽게 표현한 것 일 뿐이라서 파싱이 아주 간단하다. 이 덕분에 해석기나 컴파일러, 수식 조작기처럼 프로그램 텍스트 자체를 다루는 프로그램을 손쉽게 작성할 수 있다. 스킴은 규범적인( normative ) 언어라기보다는 관대한( permissive ) 언어라는 점이 중요하 다. 스킴에는 프로그래머가 “멍청한” 일을 하지 못하게 하는 제약이 없다. 덕분에 산술 연산자 의 의미를 동적으로 변조하는 등의 강력한 기법이 가능하다. 좀 더 제한적인 규칙을 강제하는 언어로는 그런 일이 불가능하다. 스킴이 배정( assignment )을 허용하긴 하지만, 기본적으로 스킴은 함수형 프로그래밍을 권 장한다. 스킴에는 정적 형식( static type )이 없다. 대신, 안전한 동적 저장소 할당과 쓰레기 수 거( garbage collection )를 허용하는 아주 강력한 동적 형식 시스템이 있다. 사용자 프로그램 은 포인터를 날조할 수 없으며, 아무 메모리 장소에나 접근할 수도 없다. 우리도, 정적 형식이 나쁜 것이라고는 생각하지 않는다. 정적 형식 시스템은 다양한 버그를 미연에 방지해 준다. 그 리고 하스켈 비슷한 형식 시스템은 전략을 고찰하는 데 도움이 된다. 그러나 이 책의 목적에서, 정적 형식의 지적( intellectual ) 부담은 잠재적으로 위험한 유연성 전략들을 고찰하는 데 방해 가 된다. 스킴은 또한 다른 대부분의 언어에서는 찾아볼 수 없는, 실체화된( reified ) 후속과 동적 바 인딩 같은 특별한 기능들도 제공한다. 이런 기능들 덕분에 비결정론적 amb 같은 강력한 메커니 즘을 스킴 자체로 직접(해석기나 컴파일러를 거치지 않고) 구현할 수 있다.

17

감사의 글

우리 수업을 들은 수많은 MIT 학생의 도움이 없었다면 이 책이 나오지 못했을 것이다. 학생 들은 실제로 문제를 풀었으며, 우리의 잘못된 결정과 실수를 자주 지적해 주었다. 특히, 수년 간 조교로 일한 학생들에게 감사한다. 마이클 블레어Michael Blair, 알렉세이 라둘Alexey Radul, 파벨 판체카Pavel Panchekha, 로버트 L. 매킨타이어Robert L. McIntyre, 라스 E. 존슨Lars E. Johnson, Muco,

엘리 데이비스Eli Davis, 미카 브로드스키Micah Brodsky, 마뉴샤케 무코Manushaqe

케니 첸Kenny Chen, 레일라니 헨드리나 길핀Leilani Hendrina Gilpin이 특히나 도움이 되

었다. 이 책의 여러 개념은 동료들과 졸업생들의 도움으로 개발된 것이다. 리처드 스톨먼Richard Stallman,

존 도일Jon Doyle, 데이비드 매컬리스터David McAllester, 라민 자비Ramin Zabih, 요한

드 클리어 Johan de Kleer, 켄 포버스Ken Forbus, 제프 시스킨드Jeff Siskind는 우리가 의존성 지 향적 역추적을 이해하는 데 도움을 주었다. 그리고 제7장의 전파에 대한 우리의 이해는 리처드 스톨먼, 가이 루이스 스틸 Jr.Guy Lewis Steele Jr., 알렉세이 라둘Alexey Radul과 수년간 일한 결 과이다. 우리는 함수형 프로그래밍 공동체의, 특히 스킴 팀의 도움과 지원에 특별히 감사한다. 가 이 스틸은 1970년대에 제럴드 제이 서스먼과 함께 스킴을 고안했으며, 거의 매년 우리 수업 에서 초청 강연을 베풀었다. 아서 글레클러Arthur Gleckler, 기예르모 후안 로사스Guillermo Juan Rozas, 조 마셜  Joe Marshall, 제임스 S. 밀러  James S. Miller, 헨리 마냔 우 Henry Manyan Wu

는 MIT/GNU 스킴의 개발을 도왔다. 이 훌륭한 시스템의 주된 기여자는 테일러 캠벨 Taylor Campbell과

맷 버콜즈Matt Birkholz이다. 우리는 또한 구획 변수와 통합을 이해하는 데 도움을

준 윌 버드Will Byrd와 마이클 밸런타인Michael Ballantyne에게도 감사한다. 제럴드 제이 서스먼과 함께 Structure and Interpretation of Computer Programs (컴퓨터 프로그램의 구조와 해석)를 공저한 핼 애빌슨 Hal Abelson과 줄리 서스먼Julie Sussman은 이 책 의 개념들과 착안들을 형성하는 데 도움을 주었다. 이 책은 여러모로 SICP의 고급 후속편이라 할 수 있다. 댄 프리드먼Dan Friedman과 그의 여러 훌륭한 학생들과 친구들은 프로그래밍에 대

18

한 우리의 이해에 깊은 공헌을 했다. 우리는 윌리엄 케이헌William Kahan, 리처드 스톨먼Richard Stallman,

리처드 그린블랫Richard Greenblatt, 빌 고스퍼Bill Gosper,, 톰 나이트Tom Knight 같은

위대한 마법사들과 프로그래밍의 예술( art of programming )에 관해 많은 대화를 나누었다. 여러 해 동안 잭 위즈덤Jack Wisdom과 함께 수학적 동역학( mathematical dynamics )을 연 구한 경험은 이 책에서 다루는 여러 문제를 명확히 하는 데 도움이 되었다. 서스먼은 스승들의 기여에 특별한 감사의 뜻을 표하고자 한다. 이 책이 주되게 언급하는 인 물은 마빈 민스키Marvin Minsky, 세이무어 패퍼트Seymour Papert, 제롬 레트빈Jerome Lettvin, 조 엘 모지스Joel Moses, 폴 펜필드Paul Penfield,, 에드워드 프레드킨Edward Fredkin이다. 그밖에, 민스키와 패퍼트를 함께 사사한 칼 휴윗Carl Hewitt, 데이비드 왈츠David Waltz,, 패트릭 윈스턴 Patrick Winston도

언급된다. 제프 시스킨드와 알렉세이 라둘은 아주 미묘한 버그 몇 가지를 지

적하고 해결을 도왔다. 크리스는 구글과 데이테라Datera에서 일할 때 대규모 프로그래밍에 관해 아주 많은 것을 배 웠다. 이 경험은 이 책의 여러 곳에 영향을 미쳤다. 아서 글레클러는 격주간 점심 식사에서 이 책에 관해 유용한 피드백을 제공했다. 마이크 솔즈베리Mike Salisbury는 구글의 정기 회합에 서 이 책의 진척에 큰 관심을 보였다. 홍타오 후앙Hongtao Huang과 피유시 자나와드카Piyush Janawadkar는

이 책의 초안을 검토했다. 크리스에게 람다 논문들을 소개하고 이 책으로의 긴

여정을 시작하게 한 MIT 동창생 릭 듀크스Rick Dukes에게 특별한 감사의 마음을 전한다. 우리는 MIT 전기공학 및 컴퓨터과학 학부( Department of Electrical Engineering and

Computer Science )와 MIT 컴퓨터과학 및 인공지능 연구실( Computer Science and Artificial Intelligence Laboratory, CSAIL )의 호의와 물적 지원에 감사한다. 우리는 석좌 교수 자리를 통해서 제럴드 제이 서스먼을 지원한 파나소닉사(전 마쓰시타 전기공업사)에 감 사한다. 크리스 핸슨도 이 저술 작업에서 CSAIL의, 그리고 이후에는 구글의 지원을 받았다. PPA 학위를 지닌 줄리 서스먼은 이 책을 세심하게 읽고, 우리가 본문의 주요 부분들을 다시 저술하고 조직화할 수밖에 없었을 정도로 중요한 비평을 제공했다. 줄리는 또한 여러 해 동안

19

제럴드 제이 서스먼을 발전시키고 유지보수했다. 여러 해 동안 크리스의 배우자로 지내온 엘리자베스 비커스Elizabeth Vickers는 크리스와 두 자녀 앨런Alan과 에리카Erica에게 고무적이고 안정적인 환경을 제공했다. 엘리자베스는 또한 두 저자가 메인주에서 긴 작업 세션을 진행하는 동안 훌륭한 음식도 만들어 주었다. 앨런은 가 끔이지만 열정적으로 초안을 검토해 주었다.

20

크리스 핸슨과 제럴드 제이 서스먼

CONTENTS

헌사............................................................................................................................................................... 5 지은이 소개.................................................................................................................................................... 6 옮긴이 소개.................................................................................................................................................... 7 옮긴이의 말.................................................................................................................................................... 8 추천사......................................................................................................................................................... 10 서문............................................................................................................................................................. 13 감사의 글..................................................................................................................................................... 18

제 1 장  자연과 설계의 유연성

27

1.1 계산과 건축..................................................................................................................... 32 1.2 유연성을 위한 똑똑한 부품................................................................................................ 33 1.3 중복성과 축중성............................................................................................................... 38 1.4 탐색 행동......................................................................................................................... 40 1.5 유연성의 비용.................................................................................................................. 43

제 2 장  영역 특화 언어(DSL)

47

2.1 조합자............................................................................................................................. 48 2.1.1 함수 조합자............................................................................................................. 49 2.1.2 조합자와 체제.......................................................................................................... 63

2.2 정규표현식....................................................................................................................... 65 2.2.1 정규표현식 조합자 언어............................................................................................. 66 2.2.2 패턴 번역기 구현...................................................................................................... 67

2.3 래퍼................................................................................................................................ 76 2.3.1 특수화 래퍼............................................................................................................. 78 2.3.2 특화기 구현............................................................................................................. 79 2.3.2 적응자.................................................................................................................... 82

21

CONTENTS

2.4 문제 영역의 추상화........................................................................................................... 83 2.4.1 획일적(monolithic) 구현............................................................................................ 84 2.4.2 문제 영역의 분해...................................................................................................... 90

2.5 요약................................................................................................................................ 97

제 3 장  산술 주제의 변형들

101

3.1 산술 연산의 조합........................................................................................................... 102 3.1.1 단순 상미분방정식 적분기....................................................................................... 102 3.1.2 산술 연산들의 변조................................................................................................ 105 3.1.3 산술 패키지 조합................................................................................................... 108 3.1.4 함수에 대한 산술................................................................................................... 115 3.1.5 조합자의 문제점.................................................................................................... 119

3.2 확장성 있는 일반적 프로시저......................................................................................... 124 3.2.1 일반적 산술 패키지................................................................................................ 127 3.2.2 구축의 순서 의존성 문제......................................................................................... 131 3.2.3 일반적 프로시저의 구현.......................................................................................... 134

3.3 예제: 자동 미분............................................................................................................. 142 3.3.1 자동 미분의 작동 방식............................................................................................ 144 3.3.2 다항 함수의 미분................................................................................................... 150 3.3.3 몇 가지 기술적 세부 사항........................................................................................ 153 3.3.4 미분 객체를 받는 리터럴 함수.................................................................................. 162

3.4 효율적인 일반적 프로시저.............................................................................................. 164 3.4.1 트라이................................................................................................................. 165 3.4.2 캐싱.................................................................................................................... 172

3.5 효율적인 사용자 정의 형식............................................................................................. 174 3.5.1 형식으로서의 술어................................................................................................. 175 3.5.2 술어들 사이의 관계................................................................................................ 176

22

3.5.3 디스패치 키로서의 술어.......................................................................................... 177 3.5.4 예제: 어드벤처 게임................................................................................................ 180

3.6 요약............................................................................................................................. 197

제 4 장  패턴 부합

201

4.1 패턴............................................................................................................................. 202 4.2 항 재작성...................................................................................................................... 205 4.2.1 대수의 구획 변수................................................................................................... 207 4.2.2 규칙 시스템의 구현................................................................................................ 209 4.2.3 곁가지: 마법 매크로학............................................................................................. 212 4.2.4 패턴에 따른 호출................................................................................................... 213

4.3 부합기의 설계............................................................................................................... 216 4.3.1 패턴 컴파일.......................................................................................................... 223 4.3.2 패턴 변수 부합 제한............................................................................................... 226

4.4 통합 부합...................................................................................................................... 231 4.4.1 통합의 작동 방식................................................................................................... 233 4.4.2 응용: 형식 추론..................................................................................................... 242 4.4.3 형식 추론의 작동 방식............................................................................................ 244 4.4.4 실험: 구획 변수 추가............................................................................................... 252

4.5 그래프상의 패턴 부합.................................................................................................... 259 4.5.1 목록으로 구현한 그래프.......................................................................................... 260 4.5.2 그래프 구현.......................................................................................................... 262 4.5.3 그래프 부합.......................................................................................................... 264 4.5.4 체스판과 대안 그래프 뷰......................................................................................... 267 4.5.5 체스 기물 이동...................................................................................................... 273 4.5.6 그래프 부합 구현................................................................................................... 277

4.6 요약............................................................................................................................. 284

23

CONTENTS

제 5 장  평가

287

5.1 일반적 eval/apply 해석기.............................................................................................. 288 5.1.1 eval................................................................................................................... 290 5.1.2 apply.................................................................................................................. 299

5.2 비엄격 인수를 가진 프로시저......................................................................................... 306 5.3 실행 프로시저로의 컴파일.............................................................................................. 317 5.4 탐색 행동...................................................................................................................... 329 5.4.1 amb..................................................................................................................... 330 5.4.2 amb의 구현........................................................................................................... 332

5.5 바탕 후속 프로시저들의 노출......................................................................................... 340 5.5.1 비지역 종료로서의 후속.......................................................................................... 344 5.5.2 제어권의 비지역 전달............................................................................................. 347 5.5.3 후속에서 amb로..................................................................................................... 349

5.6 큰 힘과 큰 책임............................................................................................................. 359

제 6 장  계층화

361

6.1 계층 활용...................................................................................................................... 362 6.2 계층화 구현.................................................................................................................. 364 6.2.1 계층적 데이터....................................................................................................... 365 6.2.2 계층적 프로시저.................................................................................................... 368

6.3 계층적 산술.................................................................................................................. 372 6.3.1 단위 산술............................................................................................................. 374

6.4 값의 의존성 주해 추가................................................................................................... 379 6.4.1 지지층................................................................................................................. 382 6.4.2 근거의 전달.......................................................................................................... 387

24

6.5 계층화의 약속............................................................................................................... 388

제 7 장  전파

393

7.1 예제: 별과의 거리.......................................................................................................... 396 7.2 전파 메커니즘............................................................................................................... 411 7.2.1 셀....................................................................................................................... 412 7.2.2 전파자................................................................................................................. 414

7.3 다중 대안 세계관........................................................................................................... 417 7.4 값들의 병합.................................................................................................................. 420 7.4.1 기반값들의 병합.................................................................................................... 420 7.4.2 지지되는 값들의 병합............................................................................................. 422 7.4.3 값 집합들의 병합................................................................................................... 422

7.5 가능한 세계 검색........................................................................................................... 425 7.5.1 의존성 지향적 역추적............................................................................................. 428 7.5.2 조합 퍼즐 풀기...................................................................................................... 434

7.6 전파는 축중성을 가능하게 한다...................................................................................... 440

제 8 장  맺음말

443

부록 A   지원 소프트웨어........................................................................................................................ 447 부록 B   스킴......................................................................................................................................... 451

참고문헌................................................................................................................................................... 472 찾아보기................................................................................................................................................... 483

25

1

제  장

자연과 설계의 유연성

어떤 특정한 작업을 아주 잘 수행하면서도 범용적인 메커니즘을 설계하기란 어려운 일이라서, 대부분의 공학적 시스템( engineered system )은 기본적으로 특정한 하나의 작업을 수행하도 록 설계된다. 스크루드라이버 같은 범용 발명품은 드물고, 그런 만큼 대단히 중요하다. 디지털 컴퓨터는, 그 어떤 정보 처리 기계도 흉내(시뮬레이션) 낼 수 있는 범용 기계라는 점에서,1 이 런 종류의 발명품 중에서도 획기적이다. 우리는 우리가 하고자 하는 어떤 구체적인 작업을 컴 퓨터가 시뮬레이션하게 만들기 위해 소프트웨어를 작성한다. 우리 소프트웨어 개발자들은 특정한 작업을 아주 잘 수행하는, 과거의 공학적 실천의 확장 으로서의 소프트웨어를 설계해 왔다. 개별 소프트웨어는 비교적 좁은 범위의 작업을 수행하도 록 설계된다. 풀고자 하는 문제가 변하면 소프트웨어도 변해야 한다. 그렇지만, 문제가 조금 만 바뀌어도 소프트웨어는 크게 바꾸어야 할 때가 많다. 소프트웨어의 설계가 너무 빡빡해서 유연성이 부족하다. 그래서 시스템이 우아하게 진화하지 못한다. 소프트웨어는 깨지기 쉬워서 ( brittle ), 문제 영역( problem domain )이 변하면 설계를 완전히 새롭게 갈아치워야 한다.2

1 앨런 튜링[124]이 만능(보편) 기계의 존재를 발견한 것, 그리고 튜링 기계로 계산할 수 있는 함수들의 집합이 알론조 처 치의 λ 산법[17, 18, 16]으로 표현할 수 있는 함수들의 집합과 동치일 뿐만 아니라 쿠르트 괴델[45]과 자크 에르브랑[55]의

일반 재귀 함수들의 집합과도 동치라는 사실이 밝혀진 것은 20세기의 가장 위대한 지적 성취에 속한다. 2 물론 훌륭한 예외도 몇 가지가 있다. 예를 들어 확장성 있는 편집기인 이맥스Emacs[113]는 컴퓨팅 환경의 변화와 사용

자 기대의 변화에 맞춰 우아하게 진화해 왔다. 컴퓨팅 세계가 Microsoft의 .Net이나 Sun의 자바 같은 공학적 ‘프레임워크’ 를 탐색하기 시작한 것은 그리 오래되지 않았다. 이런 프레임워크들은 진화 가능한 시스템을 지원하는 기반구조로 의도 된 것이다.



27

그런 재설계는 시간과 비용이 많이 든다. 공학적 시스템이 꼭 깨지기 쉬워야 하는 것은 아니다. 인터넷은 하나의 작은 시스템에서 전 지구적 규모의 시스템으로 확장되었다. 도시들은 유기적으로 진화하면서 새로운 사업 모형과 생활 양식, 교통 및 통신 수단을 수용하고 제공한다. 사실, 생체계( biological system )들을 관찰하면 개체 수준에서나 진화 집단 수준에서나 환경의 변화에 적응( adaptation )하는 시스 템을 구축하는 것이 불가능하지는 않음을 알 수 있다. 대부분의 소프트웨어를 우리가 그런 식 으로 설계하고 구축하지 않는 이유는 무엇일까? 역사적인 이유도 있지만, 주된 이유는 일반적 으로 그런 설계 방법을 우리가 알지 못하기 때문이다. 현재로서는, 어떤 시스템이 요구사항의 변화에도 강건하게 적응한다면 그냥 우연일 뿐이다.

가산적 프로그래밍 이 책에서 우리 저자들의 목표는, 요구사항의 변화에 수월하게 적응하도록 계산 시스템을 구축하는 방법을 살펴보는 것이다. 요구사항이 변해도 작동 중인 프로그램을 뜯어고칠 필 요 없이, 그냥 새로운 기능을 구현한 코드를 추가하거나 기존 함수를 새로운 요구사항에 맞 게 조정할 수 있어야 한다. 이런 프로그래밍 접근 방식을 우리는 가산적 프로그래밍( additive

programming; 또는 첨가식 프로그래밍)이라고 부른다. 이 책의 기법들이 그러한 코드 추가 의 정확성을 보장하지는 않는다. 추가 코드는 개별적으로 디버깅해야 한다. 그렇지만 추가 코 드가 실수로 기존 기능성에 피해를 주어서는 안 된다. 이 책의 기법 중 다수는 새로운 것이 아니다. 일부는 컴퓨팅 초창기 시절까지 거슬러 올라간 다! 또한, 필요한 모든 기법을 이 책이 제시하지도 않는다. 단지 우리 저자들이 유용하다고 판 단한 몇 가지 기법을 소개할 뿐이다. 우리의 의도는 이 기법들을 사용해야 한다고 주장하는 것 이 아니라, 유연성에 초점을 둔 사고방식을 권장하는 것이다. 가산적 프로그래밍이 가능하려면 프로그램의 작동 방식과 사용 방식에 관한 가정 ( assumption )을 최소화해야 한다. 프로그램을 설계하고 구축하는 과정에서 세운 가정들은 프로그램의 향후 확장 가능성을 줄일 위험이 있다. 우리는 그런 가정들을 세우는 대신, 프로그 램이 실행 환경에 기초해서 즉석에서 결정을 내릴 수 있도록 프로그램을 구축하고자 한다. 이 런 종류의 설계를 지원하는 몇 가지 기법을 이 책에서 살펴볼 것이다.

28

제 1 장 자연과 설계의 유연성

여러 프로그램을 결합해서, 각 프로그램이 지원하는 행동들의 합집합을 얻는 것은 언제라도 가능하다. 그러나 우리는 전체가 부분의 합보다 크길 원한다. 즉, 시스템의 부품들이 연동함으 로써 그 어떤 부품도 혼자서는 하지 못 하는 일을 시스템이 할 수 있게 만들고자 한다. 그러나 여기에는 절충( tradeoff )이 존재한다. 시스템 구축을 위해 결합하는 부품들은 반드시 관심사 ( concern )들을 명확하게 분리해야 한다. 한 가지 일을 극도로 잘 해내는 부품은 서로 무관한 여러 가지 기능을 결합한 부품보다 재사용하기 쉽고 디버깅하기도 쉽다. 시스템을 가산적으로 구축하려면, 의도치 않은 상호작용을 최소화하면서 부품들을 조합할 수 있어야 한다. 가산적 프로그래밍을 위해서는 시스템의 부품들을 최대한 간단하고 일반적으로 만들어야 한다. 예를 들어 풀고자 하는 문제에 꼭 필요한 것보다 더 넓은 범위의 입력을 받는 부품은 그 렇지 않은 부품보다 쓸모가 더 크다. 그리고 한 부품군( family )에 속한 부품들을 표준화된 인 터페이스 명세에 따라 만들면, 그 부품들을 이리저리 조합해서 아주 다양한 시스템을 만들어 낼 수 있다. 이때 중요한 것은, 부품군의 논의 영역( domain of discourse; 또는 담화 영역) 을 식별하고 그 영역에 맞게 부품군을 구축함으로써 부품군의 정확한 추상 수준을 선택하는 것 이다. 이런 요구사항에 관해서는 제2장에서부터 살펴본다. 유연성을 극대화하려면 한 부품의 출력 범위가 상당히 작고 잘 정의되어야 한다. 특히, 부품 의 출력 범위는 그 출력을 받는 모든 부품의 입력 허용 범위보다 훨씬 작아야 한다. 이는 컴퓨 터 시스템 관련 기초 강좌[126]의 학생들에게 우리가 가르치는 디지털 추상의 정적 규율( static

discipline )에 대응되는 원칙이다. 디지털 추상의 핵심은, 출력이 항상 다음 단계의 허용 가능 한 입력보다 좋아야 한다는 것이다(잡음을 억제할 수 있도록). 소프트웨어 공학에서는 이 원리를 인터넷 선구자 존 포스텔 Jon Postel의 이름을 딴 ‘포스텔의 법칙( Postel’s law )’으로 떠받든다. RFC760[97]에서 인터넷 프로토콜( IP )을 서술하면서 포스 텔은 이렇게 썼다: “프로토콜의 구현은 반드시 견고해야( robust ) 한다. 각 구현은 서로 다른 사람이 만든 다른 구현의 상호작용을 반드시 예상해야 한다. 이 명세서의 목표는 프로토콜을 명확하게 서술하는 것이지만, 그 구현들은 서로 다를 수 있다. 일반적으로 구현은 전송에서는 보수적이고( conservative ) 수신에서는 관대하게( liberal ) 행동한다.” 이 원문을 요약한 “당 신이 하는 일은 보수적으로, 다른 사람에게 받을 때는 관대하게”가 흔히 통용된다. 꼭 필요하다고 간주되는 것보다 더 일반적인 부품들을 사용하면 시스템 전체 구조에 어느 정도의 유연성이 추가된다. 그런 시스템은 요구사항의 교란( perturbation )을 견뎌낸다. 왜냐



29

하면 시스템의 모든 구성요소가 교란된(잡음 섞인) 입력을 받아들이도록 설계되어 있기 때문 이다. 특정 논의 영역을 위한 짜맞춤( mix-and-match ) 부품들로 이루어진 부품군은 영역 특화 언어( domain-specific language )의 토대이다. 어려운 문제군을 공략하는 최고의 방법은 그

문제들의 해답을 손쉽게 표현할 수 있는 언어(기본 요소들과 그 조합 수단들, 추상화 수단들의 집합)를 만드는 것일 때가 많다. 따라서 우리에게는 필요에 따라 적절한 영역 특화 언어들을 선택하고 그런 언어들을 유연하게 조합하는 능력이 있어야 한다. 영역 특화 언어는 제2장에서 부터 논의한다. 좀 더 강력하게는, 직접 평가를 이용해서 그런 언어들을 구현할 수 있다. 이런 착안은 제5장에서 좀 더 이야기한다. 유연성을 향상하는 전략 하나는 다수의 프로그래머에게 익숙한 일반적 디스패치( generic

dispatch )이다. 이 개념은 제3장에서 자세히 살펴본다. 일반적 디스패치는 프로시저에 전달 된 인수들의 세부 사항에 기초해 추가적인 처리부( handler )를 추가함으로써 프로시저의 적용 능력을 확장하는 데 유용할 때가 많다. 처리부(혹은 메서드)들이 반드시 서로 다른 인수 집합 들에 반응해야 한다는 제약을 강제하면, 새 처리부를 추가해도 기존 프로그램이 망가지는 일을 피할 수 있다. 그러나 전형적인 객체 지향적 프로그래밍 문맥과는 달리 이 책에서 말하는 일반 적 디스패치에는 클래스나 인스턴스, 상속 같은 개념들이 관여하지 않는다. 그런 개념들은 불 필요한 존재론적 책무( ontological commitment )를 도입함으로써 관심사의 분리를 약하게 만들 뿐이다. 이와는 상당히 다른 전략 하나를 제6장에서 살펴보는데, 바로 데이터와 프로시저를 계층화 ( layering )하는 것이다. 이 전략은, 데이터에는 흔히 데이터와 함께 처리할 수 있는 연관 메타 데이터metadata가 있다는 점을 활용한다. 예를 들어 수치 데이터에는 단위( unit )가 연관된다. 나중에 계층을 추가할 수 있는 유연성이 있으면 프로그램의 기존 코드를 전혀 수정하지 않고도 프로그램에 새로운 기능을 추가할 수 있음을 제6장에서 보게 될 것이다. 또한, 여러 부분 정보( partial information )의 출처들을 조합해서 좀 더 복잡한 해답을 산출 하는 시스템을 구축하는 것도 가능하다. 이 전략은 서로 독립적인 정보 출처들이 부분 정보를 기여하는 상황에서 가장 강력해진다. 제4장에서 형식 추론( type inference )이 사실은 그냥 여러 부분 정보 출처를 조합하는 문제라는 점을 설명한다. 어떤 값의 형식을 지역적으로 연역 할 수 있는 단서들(이를테면 수치 비교는 수치 입력을 요구하며 부울 출력을 산출한다는 점)을

30

제 1 장 자연과 설계의 유연성

다른 지역 형식 제약들과 조합함으로써 비지역 형식 제약들을 산출할 수 있다. 제7장에서는 부분 정보를 조합하는 또 다른 방법을 살펴본다. 가까운 별까지의 거리는 연주 시차( parallax )를 이용해서 기하학적으로 추정할 수 있다. 지구가 태양을 중심으로 공전함에 따라 배경 우주에 비해 별의 이미지가 조금 이동하는데, 그 각도를 측정해서 별까지의 거리를 추정하는 것이 가능하다. 별까지의 거리는 별의 밝기와 스펙트럼으로도 추정할 수 있는데, 이 때는 별의 구조와 진화에 관한 우리의 지식이 동원된다. 이런 추정치들을 조합함으로써 개별 기여들보다 더 정확한 추정치를 얻을 수 있다. 이와 짝을 이루는 착안은 축중성( degeneracy )◆의 활용이다. 축중성을 활용한다는 것은, 뭔가를 계산하는 방법을 여러 개 마련하고 그것들을 필요에 따라 조합하거나 변조하는 것을 말 한다. 축중성은 오류 검출, 성능 관리, 침입 검출 등 여러 용도로 유용하게 쓰인다. 중요한 점 은, 축중성은 가산적이기도 하다는 것이다. 결과에 기여하는 각 부품은 자기 완결적( self-

contained )이라서 그 자신만으로 결과를 산출할 수 있다. 축중성의 흥미로운 용법 하나는, 한 가지 알고리즘을 여러 가지 방법으로 구현하고 문맥에 따라 적절한 구현을 선택하는 것이다. 이렇게 하면 구현이 어떻게 사용될 것인가에 대해 미리 가정을 둘 필요가 없다. 유연성을 위한 설계와 구축에는 명확한 비용이 따른다. 주어진 문제를 푸는 데 필요한 것보 다 더 다양한 입력을 받을 수 있는 프로시저는 꼭 필요한 수준보다 코드가 길 뿐만 아니라 프로 그래머가 고민하고 생각해야 할 것도 꼭 필요한 수준보다 많다. 일반적 디스패치, 계층화, 축중 성 활용에도 메모리 공간, 계산 시간, 프로그래머 시간의 일정한 추가 부담이 따르게 된다. 그 러나 소프트웨어의 주된 비용은 제품의 수명 동안 프로그래머가 소비하는 시간인데, 여기에는 제품을 유지보수하고 요구사항의 변화에 따라 적응시키는 시간도 포함된다. 따라서, 필요한 코 드 재작성과 리팩터링이 최소가 되도록 소프트웨어를 설계하고 점진적인 추가로 제품의 수명 을 관리하면 제품을 완전히 다시 작성할 때보다 전체적인 비용을 줄일 수 있다. 줄여서 말하면, 장기 비용은 승산적( multiplicative )이 아니라 가산적이다.

◆ 옮긴이 축중성은 생물학에서 비롯한 용어로, 퇴행성이라고도 한다. 축중성은 어떤 한 기능을 다수의 요소가 수행할 수 있는 성질을 말하는데, 예를 들어 DNA에서 특정한 종류의 아미노산을 합성할 수 있는 코돈이 한 가지가 아니라 여러 가 지인 것이 축중성이다.



31

프로그래머들은 시간에 쫓기는 나머지, 어쩔 수 없이 제한된 용도로만 사용 가능한, 성장의 여지가 거의 없는 코드를 작성한다. 그로 인해 과거의 자신이 쌓은 벽에 가로막혀 코드를 새로 수정해야 하는 상황이 벌 어지기도 한다. 하지만 최고의 시스템은 진화할 수 있는 능력을 갖췄다. 기존 코드를 수정하는 대신 새 코 드를 추가해 새로운 상황에 적응하는 것이다. 이 책은 크리스 핸슨과 제럴드 제이 서스먼이 도합 100년이 넘는 프로그래밍 경험에서 발견한, 프로그래머가 막다른 골목을 피하는 데 도움이 되는 기법들을 담았다.

개한다. 각 장에는 연습문제와 리스프의 방언인 스킴으로 만들어진 예제 코드를 실었다. 천문 거리 계산 에서 자동 미분, 어드벤처 게임에 이르기까지 다양한 예제를 살펴본다.

표준 인터페이스를 따르는 짜맞춤 방식으로 작은 함수부터 산술 패키지 전체까지 다양한 구성 요소를 조립하는 시스템 구성법 측정 단위나 자료 출처 같은 정보를 담은 독립적인 주해를 사용한 데이터 증강 통합이나 전파를 이용한 독립적인 부분 정보 조각 조합

관련 도서 컴퓨터공학 / 개발 방법론

리팩터링 (2판)

처음 배우는 엘릭서 프로그래밍

모던 자바 인 액션

설계 원칙 크리스 핸슨, 제럴드 제이 서스먼 지음

ISBN 979-11-6224-540-8

유연한 소프트웨어를 만드는 설계원칙_펼침.indd 1-3

유연한 소프트웨어를 만드는

93000

9 791162 245408

예제 소스 github.com/jeffhhk/SoftwareDesignForFlexibility

크리스 핸슨, 제럴드 제이 서스먼, 지음

동적으로 확장 가능한 평가자를 이용한 프로그래밍 언어 확장

류광 옮김

영역 모형, 규칙 시스템, 패턴 부합, 전파, 의존성 지향적 역추적을 이용한 제어 구조와 문제 영역 분리

막다른 골목 없이 진화하는 시스템 개발하기

설계 원칙

이 책은 동적으로 확장 가능한 일반적 프로시저처럼 극도로 강력하지만 잠재적으로 위험한 기법들도 소

유연한 소프트웨어를 만드는

누가 코드를 이렇게 짜놨어?

류광 옮김

정가 35,000원

2022. 3. 10. 오후 5:45