BIG BALL OF MUD 라는 논문이 있습니다. 무려 1997년 (지금으로부터 무려 28년전)에 발표되었고 1999년에 정식으로 책에 수록된 글이죠.
소프트웨어 개발의 '이상(아름다운 아키텍처)'과 '현실(시장 압박과 기술 부채)' 사이의 괴리를 다루는 글입니다.
저는 개인적으로 이 논문을 무척이나 좋아하는데요. 저도 여타 개발자들과 비슷하게 이상주의자와 현실 주의자 사이 어딘가에서 살아가고 있기에, 매 순간 이상을 따를 지 아니면 현실에 순응해야 할 지 고민하기 때문인 듯 합니다.
어느 순간에 어느 정도의 현실에 충실하고, 앞으로 다가올 미래의 기술 부채에 대해서 어떻게 균형을 잡을 것인가에 대해 굉장히 좋은 서사점을 주는 글이라 생각합니다.
원문은 http://www.laputan.org/mud/ 에서 보실 수 있습니다.
이 논문은 흔하지만 비난받는 아키텍처 스타일인 거대한 진흙 덩어리(Big Ball of Mud) 를 탐구합니다. 우리는 이것이 왜 그렇게 흔한지, 그리고 종종 성공적인지를 설명하는 일단의 패턴들을 제시합니다. 이러한 패턴들은 이상적인 소프트웨어 아키텍처라는 개념과, 비용, 일정 압박, 기술, 그리고 개발자들의 기량 부족과 같은 현실 세계의 제약 조건 사이의 긴장을 조명합니다. 이 패턴들은 아키텍처가 어떻게 진화할 수 있는지, 그리고 왜 임시방편적인 구조가 때로는 최선의 선택이 될 수 있는지를 보여줍니다. 우리는 건축가 크리스토퍼 알렉산더의 작업을 참고하여, 이러한 시스템이 종종 나타내는 유기적이고 점진적인 성장 과정을 탐구합니다. 이 논문은 또한 지저분한 시스템을 다루고, 개선하고, 때로는 처음부터 다시 만들어야 할 때를 인식하는 전략을 제시합니다. 이 패턴들은 엔지니어링 실용주의의 가치를 옹호하며, 완벽함의 추구가 어떻게 좋은 것의 적이 될 수 있는지를 보여줍니다.
왜 그렇게 많은 시스템이 아키텍처의 관점에서 볼 때 '거대한 진흙 덩어리'일까요?
우리는 이 질문에 오랫동안 흥미를 느껴왔습니다. 우리 둘 다 일리노이 대학교 어바나-샴페인 캠퍼스의 객체 지향 및 재사용 연구 그룹의 일원이었고, 그곳은 건축적으로 순수한 객체 지향 시스템에 대한 열정이 넘치는 곳이었습니다. 우리의 초기 작업은 프레임워크, 패턴, 그리고 객체 지향 기술에 대한 심도 있는 탐구를 포함했습니다. 우리는 깔끔하고, 우아하며, 재사용 가능하고, 확장 가능한 시스템을 만드는 방법에 대해 많은 것을 배웠습니다.
하지만 현실 세계로 돌아와 컨설팅을 시작했을 때, 우리는 우리가 배운 이상적인 모습과는 매우 다른 시스템들을 마주했습니다. 우리는 종종 엉망진창이고, 임시방편으로 가득 차 있으며, 구조가 거의 없는 것처럼 보이는 시스템들을 유지보수하고 확장하는 임무를 맡았습니다. 우리는 이런 시스템들을 '거대한 진흙 덩어리'라고 부르기 시작했습니다.
처음에 우리는 이런 시스템들을 혐오했습니다. 이것들은 우리가 피해야 한다고 배운 모든 것의 전형이었습니다. 하지만 시간이 지나면서, 우리는 한 가지 불편한 사실을 깨달았습니다. 바로 이런 '진흙 덩어리' 시스템들이 놀라울 정도로 흔하고, 심지어 매우 성공적인 경우가 많다는 것이었습니다. 그들은 시장에서 살아남았고, 돈을 벌고 있었으며, 고객에게 가치를 제공하고 있었습니다.
이 논문은 이러한 명백한 모순을 탐구하려는 우리의 시도입니다. 우리는 '거대한 진흙 덩어리'를 단순한 '안티패턴'으로 치부하는 대신, 그것이 왜 그렇게 널리 퍼져 있는지 이해하려고 노력했습니다. 우리는 이것이 단지 나쁜 프로그래밍의 결과가 아니라, 소프트웨어 개발에 작용하는 복잡한 힘들의 결과물일 수 있다는 것을 깨달았습니다.
이 논문에서 우리는 '거대한 진흙 덩어리'와 그와 관련된 몇 가지 패턴들을 제시합니다. 이 패턴들은 완벽한 아키텍처에 대한 우리의 열망과, 제한된 시간, 돈, 그리고 기술이라는 현실 사이의 끊임없는 줄다리기를 포착합니다. 이 패턴들은 당신이 이런 시스템을 만났을 때 무엇을 해야 하는지에 대한 명확한 지침을 제공하지는 않을 수 있습니다. 하지만 우리는 이 패턴들이 당신이 처한 상황을 이해하고, 왜 그런 일이 일어났는지 파악하며, 앞으로 나아갈 길을 찾는 데 도움이 되기를 바랍니다.
모든 아키텍처는 일련의 기술적, 사회적, 경제적 힘들의 균형을 맞추려는 시도의 결과물입니다. '거대한 진흙 덩어리'를 이해하기 위해서는, 아키텍처를 그러한 형태로 몰아가는 강력한 힘들을 이해해야 합니다.
시간 (Time): 아마도 가장 강력한 힘일 것입니다. 시장 출시 시간(Time-to-market)은 종종 다른 모든 고려 사항을 압도합니다. "지금 당장 돌아가는 것"은 "나중에 완벽해질 것"보다 거의 항상 더 가치 있습니다. 이 압박은 설계 단계를 건너뛰고, 임시방편을 사용하며, 기술 부채를 쌓도록 유도합니다.
비용 (Cost): 소프트웨어 개발은 비쌉니다. 숙련된 아키텍트와 개발자를 고용하는 데는 돈이 듭니다. 충분한 시간을 들여 제대로 설계하고 리팩토링하는 데도 돈이 듭니다. 많은 조직은 이러한 장기적인 투자를 감당할 여유가 없거나, 그 가치를 인식하지 못합니다. "가장 저렴한" 솔루션이 선택되는 경우가 많으며, 이는 종종 가장 지저분한 솔루션이기도 합니다.
경험 (Experience): 좋은 아키텍처를 설계하는 것은 어려운 기술입니다. 경험이 부족한 개발자나 팀은 최선의 의도를 가지고 있더라도 복잡성을 관리하는 방법을 모를 수 있습니다. 그들은 점진적으로 성장하는 시스템의 구조가 어떻게 부식되는지 인식하지 못하고, 결국 자신들이 만든 진흙탕에 빠지게 됩니다.
변화 (Change): 소프트웨어의 유일한 상수는 변화입니다. 요구사항은 끊임없이 변하고, 기술은 진화하며, 비즈니스 환경도 바뀝니다. 유연하고 적응 가능한 아키텍처를 만드는 것은 어렵습니다. 변화에 대응하기 위해 계속해서 임시방편을 추가하다 보면, 원래의 깔끔했던 구조는 금세 알아볼 수 없게 됩니다.
복잡성 (Complexity): 소프트웨어는 본질적으로 복잡합니다. 도메인 자체가 복잡할 수 있고, 해결하려는 문제도 복잡할 수 있습니다. 개발자들은 이 복잡성을 관리하기 위해 노력하지만, 때로는 복잡성의 무게에 짓눌리게 됩니다. 추상화는 깨지고, 경계는 무너지며, 모든 것이 서로 얽히기 시작합니다.
엔트로피 (Entropy): 물리적 시스템과 마찬가지로, 소프트웨어 시스템도 시간이 지남에 따라 무질서해지려는 자연적인 경향이 있습니다. 질서를 유지하기 위해서는 지속적인 에너지 투입, 즉 리팩토링과 유지보수 노력이 필요합니다. 이러한 노력이 없다면, 시스템은 필연적으로 '거대한 진흙 덩어리'로 퇴화할 것입니다.
이러한 힘들은 종종 공모하여 가장 원칙 있는 아키텍트마저도 타협하게 만듭니다. '거대한 진흙 덩어리'는 단순히 나쁜 결정의 결과가 아니라, 이러한 압도적인 현실의 힘에 대한 합리적인 (비록 이상적이지는 않지만) 대응인 경우가 많습니다.
거대한 진흙 덩어리(Big Ball of Mud) 는 마구잡이로 구조화되고, 무분별하게 확장되었으며, 엉성하고, 임시방편으로 가득한 스파게티 코드 정글입니다. 이러한 시스템은 규제되지 않은 성장과 반복적인 임시방편 수리의 흔적을 명백하게 보여줍니다. 정보는 시스템의 멀리 떨어진 요소들 사이에서 무분별하게 공유되며, 종종 시스템의 거의 모든 중요 상태(state)를 코드의 어느 부분에서나 수정할 수 있는 지경에 이릅니다. 전체적인 아키텍처는 명확하게 드러나지 않으며, 이를 파악하려는 시도는 좌절감을 안겨주기 쉽습니다. 코드를 들여다보는 사람은 왜 이 시스템이 이렇게 만들어졌는지, 혹은 어떻게 이런 것이 제대로 작동할 수 있는지 이해하기 어렵습니다. 작동은 하지만, 아슬아슬하게 작동할 뿐입니다. 이 시스템에 대해 잘 아는 사람이 아니라면, 어떻게 수정해야 할지 알아내는 것은 거의 불가능합니다.
이 패턴의 이름은 그 경멸적인 어감 때문에 의도적으로 선택되었습니다. 다른 사람들이 이러한 시스템을 묘사할 때 처음 사용했던 용어이기도 합니다. 하지만 이 패턴은 '안티패턴(anti-pattern)'의 한 예로 볼 수 있습니다. 안티패턴은 문제에 대한 나쁜 해결책을 설명하는 패턴입니다. 그렇다면 왜 우리는 이 패턴을 굳이 문서화해야 할까요? 왜냐하면 이 패턴이 너무나도 흔하게 나타나기 때문입니다. 이것은 사실상의 표준(de facto standard) 아키텍처입니다. 모든 아키텍트, 설계자, 프로그래머는 커리어 초반에 이런 시스템을 적어도 하나는 마주치게 되며, 종종 혐오감을 느끼며 그곳을 떠나게 됩니다.
왜 이렇게 많은 시스템이 이런 모습일까요? 어쩌면 이 질문에 대한 답은 명백한지도 모릅니다. 비용과 일정 압박이 그 원인일 수 있습니다. 아마도 개발팀에 숙련된 아키텍트가 없었거나, 있었더라도 그들의 말을 아무도 듣지 않았을 수 있습니다. 어쩌면 담당자들이 너무 빨리 교체되어 누구도 프로젝트의 큰 그림에 책임감을 느끼지 않았을 수도 있습니다. 혹은 고객이 프로토타입을 보고는 "좋네요, 바로 출시합시다!"라고 말했을지도 모릅니다. 아니면 뛰어난 개발자가 떠나고, 그를 대체한 평범한 개발자들이 남은 시스템을 유지보수해야 했을 수도 있습니다. 답은 간단합니다: 이 방식이 쉽기 때문입니다.
숙련된 객체지향 아키텍트가 처음부터 모든 것을 제대로 설계하고 싶어 하는 동안, 다른 사람들은 이미 코드를 짜고 무언가를 동작시키고 있습니다. 프로젝트 아키텍처 진화의 혼란스러운 초기 단계 동안, 프로토타입은 종종 '거대한 진흙 덩어리'로 시작합니다. 이 혼돈은 새로운 아이디어가 시험되고, 실험이 이루어지고, 버려지는 과정에서 생겨나는 자연스러운 결과물일 수 있습니다. 이것이 바로 가장 저항이 적은 길(the path of least resistance)인 셈입니다. 우리가 이상적으로 생각하는, 신중하고 우아하며 잘 구조화된 시스템을 구축하는 데에는 시간, 기술, 그리고 규율이 필요합니다. 지저분한 것을 만드는 데는 그런 것들이 별로 필요하지 않습니다. 엔트로피의 힘이 언제나 우리를 압박합니다. 시스템은 질서정연하게 유지되려는 경향보다 무질서해지려는 경향이 더 강합니다.
실제로, '거대한 진흙 덩어리' 패턴에서 가장 눈에 띄는 점 중 하나는, 그 이름에도 불구하고 꽤 인기 있고 성공적인 아키텍처 스타일이라는 역설입니다. 이러한 시스템은 자주 나타나고 오래 살아남습니다. 왜 그럴까요? 제대로 작동하기 때문입니다. 적어도 대부분의 경우에 말이죠. 이 시스템들은 시장 출시 시간(time-to-market)과 개발 비용이라는 두 가지 매우 중요한 요구사항을 충족시킵니다. 이런 시스템을 만드는 개발자들은 아마도 건축가(architects)가 아닐 수도 있고, 그들이 만든 시스템이 아름답지 않을 수도 있습니다. 하지만 그들은 가치를 전달하고 있습니다. 이런 시스템의 생존을 가능하게 하는 특징 중 하나는, 바로 그 시스템을 만든 사람들이 시스템의 작동 방식에 대해 깊이 이해하고 있다는 점입니다.
아키텍처는 설계와 구축의 예술이자 과학입니다. 아키텍처는 공간, 형태, 질감, 재료, 그리고 이들을 하나로 묶는 원칙들 간의 상호작용을 다룹니다. 우리는 도시, 대성당, 심지어 오두막집에서도 아키텍처를 볼 수 있습니다. 그것들은 물리적 힘뿐만 아니라 그것을 만든 사람들의 미학, 문화, 경제, 기술적 역량을 반영합니다. 시스템의 아키텍처는 그 형태를 잡아주는 뼈대와 같습니다. 그것은 시스템의 핵심 추상화와 메커니즘을 구성하는 부품들의 배열입니다. 아키텍처는 부품들이 어떻게 맞춰지고, 어떤 원칙으로 조립되는지를 결정합니다.
그러나 다양한 힘들이 종종 공모하여 가장 원칙을 잘 지키는 아키텍트마저도 원칙을 타협하게(cut corners) 만듭니다. 시간은 항상 부족합니다. 우리는 코드를 더 빨리 완성해야 합니다. 우리는 "나중에 고치겠다"고 스스로에게 약속합니다. 그리고 물론, 우리는 재사용할 수 있는 무언가를 만들어야 합니다. 하지만 지금 당장은 이 특별한 경우만 처리하면 됩니다. 점차 시스템의 구조는 부식됩니다. 엔트로피는 다시 한번 그 힘을 발휘합니다.
때로는 가장 좋은 방법이 처음부터 다시 시작하는 것일 때도 있다.
— 브룩스, 『맨먼스 미신』
이 패턴의 또 다른 이름은 프로토타이핑(Prototyping)입니다.
어떤 코드는 임시방편으로 만들어집니다. 프로토타입은 새로운 기능의 타당성을 탐구하거나, 인터페이스를 시연하거나, 특정 기술의 작동 방식을 알아내기 위해 만들어질 수 있습니다. 이러한 코드는 처음부터 버려질 운명으로 태어납니다. 사실, 그것이 버려지지 않는다면 문제입니다.
이런 종류의 코드에 대한 문제는 그것이 너무 자주 살아남는다는 점입니다. 관리자는 프로토타입을 보고 눈이 휘둥그레집니다. "이 정도면 거의 다 됐잖아, 안 그래? 조금만 더 다듬으면 출시할 수 있겠어." 고객은 데모를 보고 이렇게 말합니다. "좋네요, 바로 출시합시다!"
이 프로토타입은 성공적이었습니다. 문제는 사람들이 그것을 버리려 하지 않는다는 점입니다.
— 브룩스, 『맨먼스 미신』
이런 상황은 프로토타입을 만든 프로그래머에게 엄청난 딜레마를 안겨줍니다. 프로그래머는 자신이 짠 코드가 얼마나 엉성하고, 얼마나 많은 임시방편을 사용했는지 알고 있습니다. 코드가 제대로 돌아간다는 사실 자체가 놀라울 정도입니다. 코드는 오류 처리, 견고성, 확장성을 전혀 고려하지 않았습니다. 하지만 이제 고객, 혹은 더 나쁘게는 자신의 상사가 그 코드가 거의 완성되었다고 생각합니다. 어떻게 "아니요"라고 말할 수 있을까요?
이런 상황을 피하는 한 가지 방법은 프로토타입을 매우 다른 언어나 환경에서 개발하는 것입니다. 예를 들어, Visual Basic으로 프로토타입을 만든 다음 C++로 최종 제품을 개발하는 식입니다. 이렇게 하면 프로토타입 코드가 최종 제품에 몰래 포함될 가능성이 거의 없습니다. 하지만 프로토타입을 버리는 것은 비용이 듭니다. 프로토타입을 만드는 데 걸린 시간은 프로젝트 일정에 추가되어야 합니다. 또한, C++로 다시 작성하는 과정에서 프로토타입이 가지고 있던 미묘한 기능 중 일부가 사라질 수도 있습니다.
하지만 현실적인 선택지가 C++밖에 없다고 가정해 봅시다. 여기서 딜레마가 생깁니다. 한편으로, 프로토타입을 제대로 설계하면 버려야 할 때 그 비용이 너무 커 보일 수 있습니다. 반면에, 엉성하게 만들면 프로토타입이 결국 제품이 되어버릴 위험이 있습니다.
어쩌면 프로그래머는 그냥 코드를 버려야 할지도 모릅니다.
"자, 이봐, 이 코드는 진짜 쓰레기야. 나한테 6개월만 더 주면 처음부터 제대로 만들 수 있어."
"안 돼, 시간 없어."
프로그래머가 패배하고, '일회용 코드'가 살아남으면, 그 코드는 새로운 시스템의 핵심이 될 수 있습니다. 종종, 전체 시스템이 이 코드 주위에서 자라나며, 이 코드는 마치 암세포처럼 자신의 부실한 구조를 주변으로 퍼뜨립니다. 원래 '일회용'이었던 설계 결정들이 갑자기 시스템 아키텍처의 일부가 되어버립니다. 이러한 결정 중 일부는 쉽게 바꿀 수 없을 수도 있습니다. 예를 들어, 특정 데이터베이스 스키마나 특정 운영 체제에 대한 의존성 같은 것들 말입니다. 심지어 이런 초기의 설계 결정들이 부적절하다는 것이 밝혀지더라도, 시스템은 운명적으로 그 결정에 묶여 있게 됩니다.
프로토타입이 완전한 시스템으로 진화할 때, 초기 코드는 종종 시스템의 유일한 "깨끗한" 부분이 됩니다. 이 코드는 시스템의 핵심이 되는 명확하고 간단한 모델을 가지고 있기 때문입니다. 하지만 시스템이 성장하고, 새로운 요구사항에 적응하면서, 복잡성을 제어하기 위한 노력이 없다면 초기의 우아함은 금세 사라질 것입니다.
결과적으로 '일회용 코드'는 거대한 진흙 덩어리(Big Ball of Mud)를 낳는 가장 흔한 원인 중 하나입니다.
'일회용 코드'는 단순히 프로토타입에만 국한되지 않습니다. 때로는 어떤 기능이 일시적으로만 필요할 것이라고 생각할 때가 있습니다. 예를 들어, 특정 파일 형식을 변환하는 코드를 작성했지만, 그 파일 형식은 곧 폐기될 예정입니다. 그렇다면 왜 이 코드를 깨끗하고 견고하게 만들어야 할까요? 물론, 그 파일 형식이 20년 후에도 여전히 사용될 것이라고는 아무도 예상하지 못했습니다.
우리가 점진적 개발(Incremental Development)을 실천할 때, 우리는 본질적으로 일련의 '일회용 코드'를 작성하게 됩니다. 각 단계에서 우리는 현재의 요구사항을 충족시키기 위해 최소한의 코드를 작성합니다. 그런 다음 다음 단계로 넘어가면서, 우리는 시스템의 요구사항을 더 잘 이해하게 됨에 따라 코드를 수정하고 확장합니다. 만약 우리가 운이 좋다면, 이전 단계에서 내린 설계 결정들이 다음 단계에서도 여전히 유효할 것입니다. 하지만 그렇지 않다면, 우리는 코드를 다시 작성해야 합니다. 이 과정에서 우리는 이전 코드의 일부 또는 전부를 버리게 됩니다. 점진적 개발은 프로토타이핑과 유사하지만, 각 단계가 더 작고, 코드를 버리는 비용이 덜 든다는 점에서 다릅니다.
이 패턴의 핵심은 코드가 버려질 수 있다는 가능성을 인식하는 것입니다. 버려질 코드에 과도하게 투자하지 않는 것과, 그 코드가 살아남을 경우를 대비하는 것 사이에서 균형을 잡아야 합니다.
그들은 모든 것을 한꺼번에 설계하는 것이 불가능하다는 것을 오래전에 깨달았다.
— 크리스토퍼 알렉산더, 『시간을 초월한 건설의 길』
이 패턴의 또 다른 이름은 점진적 개발(Incremental Development), 프랙탈 설계(Fractal Design), 파이 나누기(Pie à la Mode), 혹은 반복적 리파인먼트(Iterative Refinement)입니다.
성공적인 소프트웨어 시스템은 거의 모두 처음부터 완벽하게 설계되지 않았습니다. 성공적인 시스템은 작동하는 더 단순한 시스템에서 진화한 것입니다.
우리는 시스템을 처음부터 완벽하게 만들 수 있다는 생각을 버려야 합니다. 대신, 우리는 시스템이 성장하고, 변화하고, 끊임없이 개선될 수 있도록 시스템을 만들어야 합니다.
— 크리스토퍼 알렉산더, 『시간을 초월한 건설의 길』
이것은 소프트웨어 설계에 대한 깊은 통찰입니다. 너무나 많은 프로젝트가 야심 찬 목표를 가지고 시작하지만, 결국 실패로 끝납니다. 그 이유는 종종 설계자들이 처음부터 모든 것을 올바르게 만들려고 너무 애쓰기 때문입니다. 하지만 현실은, 우리가 시스템의 요구사항을 미리 완벽하게 예측할 수 없다는 것입니다. 요구사항은 시간이 지남에 따라 변하고, 우리는 시스템을 구축하면서 더 많은 것을 배우게 됩니다.
따라서 가장 좋은 접근 방식은 작게 시작하는 것입니다. 먼저 시스템의 핵심 기능을 구현하는 작은 버전을 만듭니다. 그런 다음, 이 버전을 기반으로 점진적으로 기능을 추가하고 개선해 나갑니다. 각 단계에서 우리는 시스템이 여전히 작동하는지, 그리고 올바른 방향으로 가고 있는지를 확인합니다.
이러한 접근 방식은 건축가 크리스토퍼 알렉산더가 건물과 도시에 대해 제안한 아이디어와 유사합니다. 그는 도시가 하나의 거대한 계획에 따라 한 번에 건설될 때보다, 시간이 지남에 따라 점진적으로 성장하고 변화할 때 더 살아있고, 더 아름답고, 더 기능적이 된다고 주장했습니다. 그는 이 과정을 "점진적 성장(Piecemeal Growth)"이라고 불렀습니다.
소프트웨어에서도 같은 원리가 적용됩니다. 시스템은 유기체와 같습니다. 시스템은 시간이 지남에 따라 성장하고 변화해야 합니다. 우리의 역할은 그 성장을 안내하고, 시스템이 건강하고 일관된 구조를 유지하도록 돕는 것입니다.
'점진적 성장'의 한 가지 중요한 측면은 리팩토링(Refactoring)입니다. 리팩토링은 코드의 외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 과정입니다. 시스템에 새로운 기능을 추가할 때, 우리는 종종 기존 코드를 변경해야 합니다. 이때 리팩토링을 통해 코드의 구조를 정리하고, 중복을 제거하며, 이해하기 쉽게 만들 수 있습니다. 이렇게 함으로써 우리는 시스템이 복잡성의 무게에 짓눌리지 않고 계속해서 성장할 수 있도록 돕습니다.
새로운 기능이 기존 아키텍처에 깔끔하게 들어맞지 않는 경우가 있을 수 있습니다. 이런 경우, 우리는 기존 아키텍처를 약간 수정하여 새로운 기능을 수용해야 할 수도 있습니다. 또는, 새로운 기능이 너무 달라서 별도의 컴포넌트로 분리해야 할 수도 있습니다. 핵심은 아키텍처가 고정된 것이 아니라, 시스템의 요구사항에 따라 진화할 수 있어야 한다는 것입니다.
따라서 아키텍처는 시스템이 성장하고 변화할 수 있도록 허용하는 동시에, 전체적인 일관성과 무결성을 유지하는 역할을 해야 합니다. 이것은 섬세한 균형 잡기입니다. 너무 많은 제약은 성장을 방해할 수 있고, 너무 적은 제약은 혼돈으로 이어질 수 있습니다.
복잡한 시스템을 처음부터 설계하는 것은 본질적으로 오류가 발생하기 쉬운 활동입니다.
— 존슨과 푸트, 『객체 지향 시스템 설계』
객체 지향 설계와 프레임워크는 이러한 종류의 점진적 성장을 지원하는 데 특히 효과적입니다. 객체는 잘 정의된 인터페이스 뒤에 복잡성을 숨길 수 있게 해주며, 프레임워크는 재사용 가능한 아키텍처 구조를 제공합니다. 이러한 도구를 사용하면, 우리는 시스템의 다른 부분에 미치는 영향을 최소화하면서 개별 부분을 수정하고 확장할 수 있습니다.
결국, 모든 성공적인 대규모 시스템은 작은 시스템에서 시작하여 시간이 지남에 따라 성장하고 진화한 결과물입니다. 그 과정에서 많은 실수와 막다른 골목이 있었을 것입니다. 하지만 각 단계를 통해 시스템은 더 강해지고, 더 견고해지고, 더 유용해졌습니다. 이것이 바로 거대한 진흙 덩어리(Big Ball of Mud)가 때로는 성공적인 시스템의 불가피한 부산물인 이유입니다. 그것은 완벽한 계획의 결과물이 아니라, 유기적인 성장의 결과물입니다.
우리의 임무는 이 성장을 관리하고, 시스템이 진흙탕에 빠지지 않도록 하는 것입니다. 우리는 깨끗하고, 잘 구조화되고, 이해하기 쉬운 코드를 작성하려고 노력해야 합니다. 하지만 동시에 우리는 완벽함이 좋은 것의 적이 될 수 있다는 것을 인정해야 합니다. 때로는 "그럭저럭 괜찮은(good enough)" 시스템을 만드는 것이 전혀 시스템을 만들지 못하는 것보다 낫습니다.
매일 빌드하고, 매일 테스트하라.
— 짐 코플리엔
이 패턴의 또 다른 이름은 매일 빌드하고 스모크 테스트하기(Daily Build and Smoke Test), 지속적 통합(Continuous Integration), 혹은 생명 유지 장치(Life Support)입니다.
시스템을 점진적으로 변경할 때, 가장 중요한 규칙 중 하나는 "시스템을 망가뜨리지 말라"는 것입니다. 새로운 기능을 추가하거나 기존 코드를 리팩토링하는 과정에서, 시스템이 이전처럼 작동하지 않게 만드는 것은 매우 쉽습니다. 이런 일이 발생하면, 무엇이 잘못되었는지 찾아내고 수정하는 데 많은 시간을 낭비하게 될 수 있습니다.
따라서 중요한 원칙은 변경을 작은 단위로 나누고, 각 변경 후에 시스템이 여전히 올바르게 작동하는지 확인하는 것입니다. 이렇게 하면, 문제가 발생했을 때 방금 변경한 작은 부분만 살펴보면 되므로 원인을 훨씬 쉽게 찾을 수 있습니다.
결코 깨진 창문을 내버려 두지 말라.
— 헌트와 토머스, 『실용주의 프로그래머』
이 원칙을 실천하는 가장 좋은 방법 중 하나는 자동화된 테스트 스위트(automated test suite)를 갖추는 것입니다. 코드를 변경할 때마다 이 테스트 스위트를 실행하여 시스템의 핵심 기능이 여전히 제대로 작동하는지 확인할 수 있습니다. 테스트가 실패하면, 코드를 커밋하기 전에 문제를 수정해야 합니다.
XP(eXtreme Programming) 커뮤니티는 이 아이디어를 한 단계 더 발전시켜 테스트 주도 개발(Test-Driven Development, TDD)이라는 개념을 도입했습니다. TDD에서는 실제 코드를 작성하기 전에 먼저 실패하는 테스트 케이스를 작성합니다. 그런 다음, 그 테스트를 통과할 만큼만 코드를 작성합니다. 이 과정을 반복하면서 점진적으로 시스템을 구축해 나갑니다. 이 접근 방식은 항상 작동하는 코드를 유지하는 데 도움이 될 뿐만 아니라, 설계에 대해서도 더 깊이 생각하게 만듭니다.
대규모 프로젝트에서는 여러 개발자가 동시에 코드를 변경하기 때문에 상황이 더 복잡해집니다. 한 개발자의 변경 사항이 다른 개발자의 작업을 망가뜨릴 수 있습니다. 이러한 문제를 해결하기 위해, 많은 팀이 매일 빌드(Daily Build) 또는 지속적 통합(Continuous Integration)이라는 관행을 채택합니다.
이 접근 방식에서는 모든 개발자가 하루에 최소 한 번 이상 자신의 변경 사항을 중앙 코드 저장소에 통합합니다. 그런 다음, 자동화된 빌드 시스템이 전체 시스템을 처음부터 다시 빌드하고, 모든 테스트를 실행합니다. 만약 빌드나 테스트가 실패하면, 팀 전체에 즉시 알려집니다. 그러면 팀은 다른 작업을 하기 전에 먼저 이 문제를 해결하는 데 집중해야 합니다. "빌드를 고치는 것"이 최우선 순위가 됩니다.
이러한 관행은 시스템이 항상 "건강한" 상태를 유지하도록 보장합니다. 개발자들은 자신의 변경 사항이 전체 시스템에 어떤 영향을 미치는지 즉시 피드백을 받을 수 있으며, 문제가 발생하더라도 초기에 발견하여 쉽게 해결할 수 있습니다.
거대한 진흙 덩어리(Big Ball of Mud)와 같은 복잡한 시스템을 다룰 때, 이 원칙은 특히 더 중요합니다. 시스템의 구조가 명확하지 않고, 변경의 영향을 예측하기 어렵기 때문입니다. 이런 시스템에서는 작은 변경조차도 예상치 못한 부작용을 일으킬 수 있습니다.
이런 상황에서 자동화된 테스트와 지속적 통합은 생명줄과도 같습니다. 그것들은 우리가 어둠 속에서 길을 잃지 않도록 도와주는 안전망 역할을 합니다. 우리는 시스템의 복잡한 내부를 완전히 이해하지 못할 수도 있지만, 적어도 우리가 시스템을 망가뜨리지 않았다는 확신을 가질 수 있습니다.
따라서, 당신이 낡고, 복잡하고, 지저분한 시스템을 유지보수하는 임무를 맡았다면, 가장 먼저 해야 할 일 중 하나는 테스트 스위트를 구축하는 것입니다. 처음에는 어려울 수 있습니다. 코드가 테스트하기 어렵게 작성되었을 수도 있습니다. 하지만 이 노력은 장기적으로 큰 보상을 가져다줄 것입니다. 테스트가 없다면, 당신은 사실상 눈을 가리고 코드를 변경하는 것과 같습니다. 테스트가 있다면, 당신은 자신감을 가지고 시스템을 개선하고 리팩토링할 수 있습니다.
결론적으로, 시스템을 계속 작동하게 유지하는 것은 점진적 개발의 핵심 전제 조건입니다. 그것이 없다면, 점진적 개선은 불가능하며, 시스템은 결국 붕괴될 것입니다.
우리는 변화율이 다른 시스템의 각기 다른 부분들을 분리하는 법을 배워야 한다.
— 브루스 블레어(Bruce Blair)가 스튜어트 브랜드(Stewart Brand)를 인용한 말
이 패턴의 또 다른 이름은 변화율에 따른 계층화(Layers by Rate of Change)입니다.
건축가 프랭크 더피(Frank Duffy)는 건물이 여러 계층으로 구성되어 있으며, 각 계층은 서로 다른 수명 주기를 가지고 변화한다고 관찰했습니다. 스튜어트 브랜드는 그의 저서 『How Buildings Learn』에서 이 아이디어를 다음과 같이 정교하게 설명합니다.
브랜드는 건물에 문제가 생기는 주요 원인 중 하나가, 변화 속도가 다른 계층들이 서로 얽혀있기 때문이라고 지적합니다. 예를 들어, 건물의 구조물 기둥 안에 전기 배선을 집어넣으면, 나중에 전기 시스템을 업그레이드하기가 매우 어려워집니다. 빠르게 변해야 할 '서비스' 계층이 느리게 변하는 '구조물' 계층에 꽉 묶여버리기 때문입니다. 현명한 설계는 이러한 분리 계층(Shearing Layers)을 인식하고, 각 계층이 독립적으로 유지보수되고 교체될 수 있도록 하는 것입니다.
소프트웨어 시스템도 이와 매우 유사한 계층 구조를 가지고 있습니다.
거대한 진흙 덩어리(Big Ball of Mud)의 주요 문제 중 하나는 이러한 계층들이 서로 뒤엉켜 있다는 것입니다. 예를 들어, 비즈니스 로직 코드 안에 SQL 쿼리가 직접 박혀있거나, UI 코드 안에 비즈니스 규칙이 섞여 있는 경우입니다. 이렇게 되면, 데이터베이스 스키마를 약간 변경하는 것이 전체 시스템에 파급 효과를 일으키거나, 버튼 색깔 하나 바꾸려다가 중요한 비즈니스 로직을 건드리는 실수를 할 수 있습니다.
따라서 훌륭한 아키텍처의 핵심 역할 중 하나는, 변화율이 다른 코드들을 서로 분리하는 것입니다.
가장 고전적인 예는 모델-뷰-컨트롤러(Model-View-Controller, MVC) 패턴입니다.
MVC는 UI(뷰)의 잦은 변경이 핵심 비즈니스 로직(모델)에 영향을 주지 않도록 분리하는 효과적인 방법입니다.
데이터 접근 계층(Data Access Layer, DAL)을 두는 것도 '분리 계층'의 좋은 예입니다. 비즈니스 로직이 특정 데이터베이스 기술(예: Oracle, MySQL)에 직접 의존하지 않고, 추상화된 데이터 접근 인터페이스를 통해 소통하게 만듭니다. 이렇게 하면 나중에 데이터베이스를 교체하더라도, 비즈니스 로직 코드를 거의 수정하지 않고 데이터 접근 계층만 새로 구현하면 됩니다.
좋은 프레임워크는 종종 이러한 '분리 계층' 원칙을 내장하고 있습니다. 프레임워크는 안정적인 기반(구조물)을 제공하고, 개발자는 그 위에서 빠르게 변하는 애플리케이션 로직(공간 계획, 가구)을 구축할 수 있도록 돕습니다.
하지만 완벽한 분리는 현실적으로 어렵습니다. 계층 간의 인터페이스는 여전히 존재하며, 이 인터페이스 자체도 진화해야 할 때가 있습니다. 그럼에도 불구하고, 변화율을 기준으로 시스템을 계층화하려는 노력은 장기적인 유지보수성과 유연성을 크게 향상시킬 수 있습니다.
당신이 '거대한 진흙 덩어리'를 마주했다면, 시스템의 어떤 부분이 더 자주 변하고 어떤 부분이 더 안정적인지 식별하는 것부터 시작해 보세요. 그런 다음, 가장 변화가 잦은 부분(예: UI)을 안정적인 부분(예: 비즈니스 로직)으로부터 분리해내는 리팩토링을 시도할 수 있습니다. 이것은 진흙 덩어리를 풀어헤치는 중요한 첫걸음이 될 수 있습니다.
복잡성은 신성하다.
— 테드 넬슨, 『컴퓨터 리브/드림 머신』
이 패턴의 또 다른 이름은 유예(Potemkin Village), 추상화(Abstraction), 또는 외관(Façade)입니다.
모든 문제가 깔끔하게 해결될 수 있는 것은 아닙니다. 때로는 어떤 부분이 너무 복잡하고, 너무 지저분하고, 너무 다루기 힘들어서 제대로 정리할 엄두가 나지 않을 때가 있습니다. 이 코드는 아마도 오래된 레거시 시스템의 일부이거나, 외부 라이브러리의 지저분한 인터페이스이거나, 혹은 그냥 우리가 아직 완전히 이해하지 못한 부분일 수 있습니다.
이런 상황에서 우리가 할 수 있는 최선의 방법은 그 복잡성을 깔끔한 카펫 밑으로 쓸어넣어 숨기는 것입니다. 즉, 지저분한 코드 덩어리 주위에 잘 정의된, 단순하고 명확한 인터페이스(API)를 만드는 것입니다. 이 인터페이스는 그 뒤에 숨겨진 혼돈을 가려주는 외관(Façade) 역할을 합니다.
시스템의 나머지 부분은 이제 이 깔끔한 외관하고만 상호작용하면 됩니다. 그들은 카펫 밑에 무엇이 있는지 알 필요가 없습니다. 그들은 단지 "이 함수를 호출하면 원하는 결과를 얻을 수 있다"는 사실만 알면 됩니다.
이것은 완벽한 해결책이 아닙니다. 복잡성 자체가 사라진 것은 아니기 때문입니다. 우리는 단지 문제를 한 곳에 격리하고, 그 영향력이 시스템의 다른 부분으로 퍼져나가지 않도록 막은 것뿐입니다. 이것은 일종의 전략적 후퇴이자, 현실과의 타협입니다.
예를 들어, 매우 복잡하고 사용하기 어려운 서드파티 라이브러리를 사용해야 한다고 가정해 봅시다. 이 라이브러리의 함수들을 시스템 곳곳에서 직접 호출하는 대신, 우리는 어댑터(Adapter) 또는 래퍼(Wrapper) 클래스를 만들 수 있습니다. 이 클래스는 우리 시스템에 필요한 기능만을 단순하고 일관된 방식으로 노출합니다. 그러면 우리 애플리케이션 코드는 이 복잡한 라이브러리의 세부 사항을 전혀 몰라도 됩니다. 나중에 이 라이브러리를 다른 것으로 교체해야 할 때도, 우리는 이 어댑터 클래스만 수정하면 되므로 변경의 범위가 크게 줄어듭니다.
거대한 진흙 덩어리(Big Ball of Mud)를 다룰 때 이 전략은 매우 유용합니다. 진흙 덩어리 전체를 한 번에 리팩토링하는 것은 거의 불가능합니다. 대신, 우리는 진흙 덩어리의 특정 부분을 식별하고, 그 주위에 명확한 경계를 설정할 수 있습니다. 즉, 진흙 덩어리의 일부 기능을 노출하는 깔끔한 API를 정의하는 것입니다. 그러면 새로 작성되는 코드는 이 API를 통해 진흙 덩어리와 상호작용하게 됩니다.
시간이 지남에 따라, 우리는 점차적으로 진흙 덩어리에 의존하는 낡은 코드를 이 새로운 API를 사용하도록 수정할 수 있습니다. 그리고 궁극적으로는, 카펫 밑에 있는 지저분한 코드를 완전히 새로운 구현으로 교체할 수도 있습니다. 이 모든 과정 동안 시스템의 나머지 부분은 아무런 영향을 받지 않습니다. 왜냐하면 그들은 처음부터 안정적인 '외관'하고만 소통해 왔기 때문입니다.
이 접근 방식의 위험성은, 카펫 밑에 있는 문제가 잊혀질 수 있다는 것입니다. "눈에서 멀어지면, 마음에서도 멀어진다"는 말이 있습니다. 인터페이스가 너무 잘 작동해서 아무도 그 뒤에 있는 끔찍한 코드를 고치려고 하지 않을 수 있습니다. 그 문제는 다음 세대의 불운한 유지보수 개발자에게 그대로 남겨질 것입니다.
또한, '외관'이 너무 단순화되어 내부의 중요한 세부 사항이나 유연성을 가려버리는 경우도 있습니다. 때로는 사용자가 카펫 밑을 들여다보고 내부의 복잡성을 직접 다루어야 할 필요가 있을 수 있습니다. 좋은 '외관' 디자인은 이러한 "탈출구(escape hatch)"를 제공하여 필요할 때 더 낮은 수준의 제어를 허용해야 합니다.
결론적으로, "문제를 카펫 밑으로 쓸어넣기"는 이상적인 해결책은 아닙니다. 하지만 제한된 시간과 자원 속에서 복잡성을 관리해야 하는 현실 세계에서는 매우 실용적이고 효과적인 전략입니다. 그것은 우리가 완벽함을 추구하다가 아무것도 하지 못하는 상황을 피하고, 대신 점진적으로 시스템을 개선해 나갈 수 있도록 도와줍니다.
도시를 한 번에 전부 재건축하려는 계획은 언제나 실패로 끝났다. 성공적인 것은 언제나 점진적인 재건축이었다.
— 크리스토퍼 알렉산더, 『시간을 초월한 건설의 길』
이 패턴의 또 다른 이름은 빅 리라이트(Big Rewrite), 갈아엎기(Throw it Away and Start Over), 혹은 총체적 리팩토링(Total Refactoring)입니다.
때로는 시스템이 너무 낡고, 너무 복잡하고, 너무 엉망진창이 되어서 더 이상 점진적인 개선이 의미가 없는 지점에 도달합니다. 코드는 부서지기 쉽고, 아무도 전체 구조를 이해하지 못하며, 작은 변경 하나가 예측할 수 없는 연쇄적인 실패를 일으킵니다. 기술 부채는 감당할 수 없을 정도로 쌓였고, 이자만 겨우 갚아나가고 있는 실정입니다.
이런 상황에서 유일한 선택지는 재건축, 즉 시스템을 처음부터 다시 작성하는 것일 수 있습니다.
이것은 매우 유혹적인 아이디어입니다. 과거의 모든 실수, 모든 잘못된 결정, 모든 임시방편을 버리고, 우리가 그동안 배운 모든 것을 바탕으로 깨끗하고, 우아하고, 완벽한 시스템을 만들 기회입니다. 더 이상 진흙은 없습니다.
하지만 '빅 리라이트'는 엄청나게 위험하고 비용이 많이 드는 전략입니다. 많은 프로젝트가 이 길을 선택했다가 장렬하게 실패했습니다. 왜 그럴까요?
첫째, 기존 시스템은 겉보기에는 엉망일지 몰라도, 작동하고 있습니다. 그 안에는 수년간의 경험과 시행착오를 통해 축적된 수많은 미묘한 비즈니스 규칙과 예외 처리가 녹아 있습니다. 이 모든 지식을 문서화하거나 새로운 시스템으로 완벽하게 옮기는 것은 거의 불가능합니다. "Second-System Effect(두 번째 시스템 효과)"라는 함정에 빠지기 쉽습니다. 즉, 첫 시스템의 단점을 보완하려다 너무 많은 기능을 넣어서 지나치게 복잡하고 거대한 시스템을 만들게 되는 것입니다.
둘째, 재건축 프로젝트는 시간이 매우 오래 걸립니다. 새로운 시스템이 기존 시스템의 모든 기능을 따라잡는 데 몇 년이 걸릴 수도 있습니다. 그동안 기존 시스템은 계속해서 유지보수되고 새로운 기능이 추가되어야 합니다. 이것은 "움직이는 과녁을 맞추려는" 것과 같습니다. 새로운 시스템이 완성될 때쯤이면, 기존 시스템은 이미 저만치 앞서가 있을 수 있습니다.
셋째, 비즈니스 관점에서 볼 때, 재건축은 당장 새로운 가치를 창출하지 않습니다. 회사는 몇 년 동안 막대한 돈을 투자하지만, 결과물은 단지 '기존에 하던 일을 새로운 기술로 다시 하는 것'일 뿐입니다. 경영진이 이 프로젝트를 끝까지 지원해 줄 것이라는 보장은 없습니다.
그렇다면 '재건축'은 항상 나쁜 생각일까요? 그렇지는 않습니다. 때로는 그것이 유일한 방법일 수 있습니다. 예를 들어, 시스템이 기반으로 하는 기술(플랫폼, 언어 등)이 완전히 사장되어 더 이상 지원을 받을 수 없을 때, 또는 시스템의 아키텍처가 비즈니스의 근본적인 변화를 도저히 수용할 수 없을 때 재건축이 필요할 수 있습니다.
성공적인 재건축의 핵심은 종종 점진적인 접근입니다. 전체 시스템을 한 번에 바꾸려고 하는 대신, 시스템의 한 부분을 선택하여 그것을 먼저 재건축합니다. 예를 들어, 스트랭글러 패턴(Strangler Fig Pattern)을 사용할 수 있습니다. 새로운 시스템이 낡은 시스템을 서서히 "휘감아 조르듯이" 대체하는 전략입니다.
먼저, 낡은 시스템의 특정 기능에 대한 요청을 가로채는 프록시(Proxy)나 외관(Façade)을 만듭니다. 처음에는 모든 요청을 그냥 낡은 시스템으로 전달합니다. 그런 다음, 그 기능 중 하나를 새로운 기술로 재구현합니다. 이제 프록시는 해당 기능에 대한 요청은 새로운 시스템으로 보내고, 나머지 요청은 여전히 낡은 시스템으로 보냅니다. 이 과정을 점진적으로 반복하면, 결국 낡은 시스템의 모든 기능이 새로운 시스템으로 대체되고, 낡은 시스템은 안전하게 은퇴시킬 수 있습니다.
이 방식은 '빅 리라이트'의 위험을 크게 줄여줍니다. 각 단계마다 작은 성공을 거둘 수 있고, 비즈니스에 가치를 지속적으로 제공할 수 있으며, 만약 문제가 생기더라도 쉽게 이전 상태로 되돌아갈 수 있습니다.
'재건축'은 마지막 수단이어야 합니다. 그것은 심각한 질병에 대한 대수술과 같습니다. 위험하고 고통스럽지만, 때로는 생존을 위해 필요합니다. 하지만 가능하면 항상 점진적인 치료와 예방(리팩토링, 좋은 설계)을 통해 대수술을 피하는 것이 현명합니다. 결국, 거대한 진흙 덩어리와 함께 사는 것이, 실패한 재건축 프로젝트의 잿더미 위에 서 있는 것보다 나을 수 있습니다.
우리는 이 논문에서 소프트웨어 아키텍처의 어두운 면, 즉 '거대한 진흙 덩어리'를 탐구했습니다. 우리는 이것이 왜 그렇게 흔하게 나타나는지, 그리고 놀랍게도 왜 종종 효과적인지를 설명하는 일단의 힘과 패턴들을 제시했습니다.
건축가들은 종종 도시를 내려다보는 높은 곳에서, 깨끗하고 잘 계획된 이상적인 도시를 꿈꿉니다. 그러나 거리로 내려오면, 그들은 혼잡하고, 지저분하며, 활기 넘치는 현실의 도시를 마주하게 됩니다. 소프트웨어 아키텍처도 마찬가지입니다. 우리는 우아하고, 일관되며, 잘 구조화된 시스템을 열망하지만, 종종 마감일, 변화하는 요구사항, 그리고 순수한 복잡성의 현실과 씨름하게 됩니다.
'거대한 진흙 덩어리'는 이러한 현실에 대한 대응입니다. 그것은 완벽함의 부재를 의미하지만, 실패를 의미하지는 않습니다. 그것은 종종 생존, 성장, 그리고 진화의 증거입니다. 이 논문에서 제시된 패턴들—일회용 코드, 점진적 성장, 계속 작동하게 하라, 분리 계층, 문제를 카펫 밑으로 쓸어넣기, 재건축—은 이러한 지저분한 현실을 탐색하기 위한 전략들을 제공합니다.
이 패턴들은 '거대한 진흙 덩어리'를 만들라고 권장하는 것이 아닙니다. 오히려, 그것들은 우리가 왜 그런 시스템을 만들게 되는지 이해하고, 그 안에서 효과적으로 작업하며, 가능할 때 점진적으로 개선할 수 있는 방법을 제안합니다. 그것들은 실용주의의 가치를 강조합니다. 때로는 '그럭저럭 괜찮은(good enough)' 것이 최선의 선택일 수 있습니다.
이 논문의 핵심 메시지는 다음과 같습니다. 아키텍처는 중요하지만, 그것이 전부는 아닙니다. 우리는 건축적 순수성을 추구하는 것과, 가치를 전달하고 시스템을 살아있게 유지해야 하는 현실적인 필요 사이에서 균형을 찾아야 합니다. 가장 뛰어난 아키텍트는 가장 아름다운 청사진을 그리는 사람이 아니라, 가장 거센 폭풍우 속에서도 배를 계속 항해하게 할 수 있는 사람일지도 모릅니다.
그러니 다음에 당신이 '거대한 진흙 덩어리'를 마주하게 되거든, 절망하지 마십시오. 당신은 혼자가 아닙니다. 그것은 단지 소프트웨어 개발이라는 지저분하고, 복잡하며, 끝없이 매력적인 세계의 일부일 뿐입니다.
우리는 우리의 동료들에게서 많은 것을 배웠습니다. 특히, 존 브랜트(John Brant), 찰리 헤링(Charlie Herring), 랄프 존슨(Ralph Johnson), 돈 로버츠(Don Roberts), 그리고 빌 오피다이크(Bill Opdyke)를 포함한 일리노이 패턴 그룹(Illinois Patterns Group)의 멤버들은 수년에 걸쳐 우리의 생각에 큰 영향을 주었습니다. 켄 아우어(Ken Auer), 짐 코플리엔(Jim Coplien), 그리고 워드 커닝햄(Ward Cunningham)과의 토론도 많은 도움이 되었습니다. 브루스 블레어(Bruce Blair)는 '분리 계층'이라는 아이디어의 핵심을 우리에게 소개해 주었습니다. 크리스토퍼 알렉산더(Christopher Alexander)의 작업은 이 논문 전반에 걸쳐 깊은 영감을 주었습니다. 마지막으로, 우리는 이 논문의 초기 버전에 대해 귀중한 피드백을 제공해 준 PLoP '97 컨퍼런스의 워크숍 참가자들에게 감사를 표합니다.
이 논문은 여러 중요한 저작에 영감을 받거나 인용하고 있습니다. 주요 참고 문헌은 다음과 같습니다.
[Alexander 1977] Christopher Alexander, A Pattern Language: Towns, Buildings, Construction, Oxford University Press, 1977
패턴 언어의 개념을 정립한 건축 분야의 고전입니다. 이 논문의 사상적 기반이 됩니다.
[Alexander 1979] Christopher Alexander, The Timeless Way of Building, Oxford University Press, 1979
'점진적 성장'과 유기적 설계에 대한 철학을 담고 있습니다.
[Brand 1994] Stewart Brand, How Buildings Learn: What Happens After They're Built, Viking Press, 1994
'분리 계층(Shearing Layers)'의 개념을 소개한 책입니다.
[Brooks 1995] Frederick P. Brooks, Jr., The Mythical Man-Month, Anniversary Edition, Addison-Wesley, 1995
'맨먼스 미신'. '일회용 코드'와 '두 번째 시스템 효과'에 대한 고전적인 통찰을 제공합니다.
[Buschmann et al. 1996] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, and Michael Stal, Pattern-Oriented Software Architecture: A System of Patterns, John Wiley and Sons, 1996
POSA 책으로 알려진 소프트웨어 아키텍처 패턴에 대한 중요 저작입니다.
[Coplien 1995] James O. Coplien, "A Development Process Generative Pattern Language", In Pattern Languages of Program Design, Addison-Wesley, 1995
'매일 빌드'와 같은 개발 프로세스 패턴에 대해 다룹니다.
[Foote 1988] Brian Foote, "Designing to Facilitate Change with Object-Oriented Frameworks", Master's Thesis, University of Illinois at Urbana-Champaign, 1988
[Gamma et al. 1995] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995
'디자인 패턴' 또는 'GoF(Gang of Four) 패턴'으로 알려진 필독서입니다. Façade, Adapter 등의 패턴을 소개합니다.
[Hunt & Thomas 1999] Andrew Hunt and David Thomas, The Pragmatic Programmer: From Journeyman to Master, Addison-Wesley, 1999
이 논문 출간 이후에 나왔지만, '깨진 창문' 이론 등 이 논문과 철학을 공유하는 실용주의 프로그래밍의 고전입니다. (원문에는 없지만 이해를 위해 추가)
[Johnson & Foote 1988] Ralph E. Johnson and Brian Foote, "Designing Reusable Classes", Journal of Object-Oriented Programming, June/July 1988
[Vlissides 1998] John Vlissides, Pattern Hatching: Design Patterns Applied, Addison-Wesley, 1998
아래는 원문이 특별히 길지는 않지만 아무래도 논문이다보니 내용이 좀 어렵다고 느끼실 분들을 위해 간단히 정리된 버전입니다.
이 논문은 소프트웨어 아키텍처의 이상과 현실 사이의 괴리를 다룹니다. 개발자들은 모두 잘 설계된 시스템을 만들고 싶어 하지만, 촉박한 마감 시간, 예산의 한계, 끊임없이 변하는 요구사항 같은 현실적인 압박 때문에, 시스템은 종종 의도와 다르게 복잡하고 무질서한 '거대한 진흙 덩어리'가 되어 갑니다.
이 논문은 이런 현상을 단순히 '나쁜 개발'로 치부하지 않습니다. 대신, 이것이 왜 그토록 흔하며 때로는 비즈니스적으로 성공하는지를 분석합니다. 그리고 이런 지저분한 현실 속에서 개발자가 어떻게 시스템의 붕괴를 막고, 점진적으로 개선하며 생존할 수 있는지에 대한 실용적인 전략들을 제시합니다. 즉, 완벽한 청사진을 좇기보다, 현실의 제약 속에서 최선의 가치를 만들어내는 실용주의적 지혜를 강조합니다.
뚜렷한 아키텍처 없이 마구잡이로 성장하여 내부 구조가 뒤엉킨 시스템을 말합니다. 이는 촉박한 일정, 개발자의 잦은 교체, 부족한 경험 등 다양한 현실적 제약 때문에 발생하는 경우가 많습니다. 비록 기술적으로는 경멸의 대상이지만, 놀랍게도 시장 출시 시간을 맞추고 가치를 전달하며 오랫동안 살아남는 성공적인 시스템에서 흔히 발견되는, 사실상의 표준 아키텍처이기도 합니다.
타당성 검증 등을 위해 임시로 만든 프로토타입 코드가 버려지지 않고 제품의 기반이 되는 상황입니다. 관리자나 고객이 "이 정도면 거의 다 됐네, 출시하자!"라고 말하는 압박 속에서, 개발자는 코드의 구조적 문제를 알면서도 어쩔 수 없이 제품화를 진행하게 됩니다. 이처럼 단기적인 이익을 위해 내린 결정이 장기적으로는 시스템 전체를 부실하게 만드는 '진흙 덩어리'의 씨앗이 됩니다.
복잡한 시스템은 처음부터 완벽하게 설계될 수 없으며, 대부분 작동하는 작은 시스템에서 출발해 점진적으로 성장하고 진화합니다. 이 유기적인 성장 과정은 자연스럽고 효과적이지만, 지속적인 리팩토링과 구조 개선 노력이 없다면 필연적으로 무질서와 복잡성을 낳게 됩니다. '진흙 덩어리'는 이러한 통제되지 않은 성장의 불가피한 결과물일 수 있습니다.
점진적으로 시스템을 개선할 때 가장 중요한 원칙은, 어떤 변경을 하든 시스템이 깨지지 않고 계속 작동하는 상태를 유지하는 것입니다. 특히 구조가 복잡한 '진흙 덩어리'에서는 작은 수정이 예상치 못한 부작용을 낳기 쉽습니다. 따라서 자동화된 테스트와 지속적 통합(CI)은 우리가 어둠 속에서 길을 잃지 않도록 지켜주는 안전망이자, 시스템을 자신감 있게 개선하기 위한 필수적인 생명 유지 장치입니다.
소프트웨어의 각 부분은 변화하는 속도가 다릅니다. 자주 바뀌는 UI와 안정적인 비즈니스 로직처럼, 변화율이 다른 부분들을 서로 분리하는 것은 매우 중요합니다. '진흙 덩어리'의 특징은 이런 계층들이 뒤엉켜 있다는 점이며, 이들을 분리하려는 노력은 장기적인 유지보수성과 유연성을 확보하고 시스템을 개선하는 핵심적인 건축 원칙입니다.
시스템의 어떤 부분이 너무 복잡해서 당장 해결하기 어려울 때, 그 복잡성을 깔끔한 API라는 '외관(Façade)' 뒤로 숨기는 전략입니다. 이는 근본적인 해결책은 아니지만, 문제의 영향력이 시스템 전체로 퍼져나가는 것을 막는 현실적인 타협안입니다. 당장의 완벽한 해결보다, 복잡성을 한 곳에 격리하고 점진적으로 개선할 기회를 만드는 실용적인 접근법입니다.
시스템이 더 이상 개선이 불가능할 정도로 낡고 부패했을 때 고려하는 최후의 수단입니다. 하지만 전체 시스템을 한 번에 다시 만드는 '빅 리라이트'는 기존 시스템에 녹아있는 수많은 암묵적 지식을 놓쳐 실패할 위험이 매우 큽니다. 따라서 재건축을 해야만 한다면, 시스템 전체를 한 번에 교체하기보다 특정 기능부터 서서히 새로운 시스템으로 대체해 나가는 점진적인 '스트랭글러(Strangler) 패턴'이 훨씬 더 안전하고 현실적인 선택입니다.