프레드 브룩스의 「No Silver Bullet」를 바탕으로 소프트웨어 개발의 본질적 어려움과 우연적 어려움을 살펴보고, 생산성 향상의 실제 원천과 Docker 및 메시지 큐 같은 ‘다소 은빛인’ 기술들을 논합니다.
2023년 10월 20일Django, Programming, Python
생산성, 신뢰성, 단순성에서 10년 안에 단 한 자릿수 차수의 향상조차 그 자체만으로 약속하는 단일한 발전은, 기술이든 관리 기법이든, 존재하지 않는다.
만약 『The Mythical Man-Month』가 — 또는 적어도 그것이 한 개의 간결한 경구로 대중적으로 증류된 형태가 — 고 프레드 브룩스가 쓴 것 가운데 가장 유명한 것이라면, 위에서 인용한 에세이인 「No Silver Bullet」은 분명 가장 악명 높은 글, 혹은 아마도 가장 많은 논쟁을 불러온 글이어야 할 것입니다. 웹 이전 시대였음에도 그랬는데, 브룩스는 자신의 요점을 더 설명하고 비평가들과 맞서 논쟁하기 위해 후속 글(「‘No Silver Bullet’ Refired」)까지 써야 한다고 느꼈고, 그것도 자신의 최초 예측에서 말한 10년이 완전히 지나기도 전에 그랬습니다.
그건 아쉬운 일입니다. 제 생각에는 「No Silver Bullet」이 브룩스가 쓴 것 가운데 가장 뛰어난 글이었을지도 모르기 때문입니다.
그래서 오늘은 「No Silver Bullet」에 대한 제 생각을 조금 풀어놓고, 브룩스 식 의미에서 진정한 silver bullet은 아닐지라도 이야기를 꺼내고 논의할 가치가 있을 만큼 충분히 은빛인 몇 가지를 살펴보고 싶습니다.
하지만 그 전에, 그리고 이 글을 더 읽기 전에, 여기서 잠깐 멈추고 직접 그 글을 읽어보시라고 권하고 싶습니다. 그것의 Wikipedia 요약만 읽지 마세요. 다른 사람이 요약한 것만 읽지도 마세요. 어설픈 “AI”에게 그것에 대해 거짓말을 지어내 달라고 하지도 마세요. 직접 구해서 전체를 읽어보세요. 그렇게 길지 않습니다. 1995년판 『The Mythical Man-Month』 “20주년” 판에는 그 글과 후속편인 “Refired”가 모두 실려 있고, 개인 서가에 두기 위해 몇 달러를 쓸 만한 가치가 있습니다. 그것을 읽고 나면, 이 글로 돌아오셔도 됩니다.
이 에세이가 1980년대 중반에 쓰였다는 점을 이해하는 것이 중요합니다. 그 시기에는 컴퓨팅 하드웨어 가 엄청난 성능 향상을 꾸준히 제공하고 있었습니다(Moore’s Law 참조). 「No Silver Bullet」은 왜 소프트웨어 생산성에서는 그에 상응하는 엄청난 향상이 나타나지 않았는지를 설명하려고 시도합니다.
브룩스의 주장을 대략 요약하면, 소프트웨어 개발의 어려움은 두 종류로 나눌 수 있습니다. 우연적 어려움과 본질적 어려움입니다. 아마 제 충고를 따르지 않고 실제로 에세이 전체를 읽지 않으셨을 테니, 그가 이 용어들을 어떻게 정의하는지 여기 옮기겠습니다.
아리스토텔레스를 따라, 나는 그것들을 본질—소프트웨어의 본성에 내재한 어려움—과 우연—오늘날 그것의 생산에 수반되지만 내재적이지는 않은 어려움—으로 나눈다.
“우연적” 어려움의 예로는 저수준 프로그래밍 언어를 들 수 있습니다. 이 경우 프로그래머는 메모리 블록 같은 자원을 수동으로 관리하는 데 지나치게 많은 시간을 써야 합니다. 컴파일러와/또는 런타임이 이런 자원을 자동으로 관리하고 필요한 모든 “장부 정리”를 수행하는 더 고수준의 언어로 바꾸면 생산성이 향상될 것입니다.
한편 “본질적” 어려움은 복잡성(특히 시스템이 구성 요소 수와 그 상호작용 면에서 커질수록), 시간에 따른 변화, 그리고 소프트웨어 시스템을 적절히 시각화하고 따라서 개념화하며 추론하기 어려운 점 같은 요인에서 나옵니다.
이어서 브룩스는, 전체 소프트웨어 난이도 중 본질적 부분이 얼마나 되는지에 따라, 단지 우연적 어려움을 줄이는 것만으로는 큰 향상을 달성하지 못할 수도 있다고 지적합니다.
오늘날 소프트웨어 엔지니어들이 하는 일 가운데 얼마나 많은 부분이 본질적인 것이 아니라 우연적인 것에 여전히 바쳐지고 있는가? 전체 노력의 9/10보다 많지 않다면, 모든 우연적 활동을 0시간으로 줄인다 해도 한 자릿수 차수의 향상은 얻을 수 없다.
그 직후에는 이 생각을 더 강하게 정식화한 표현이 나옵니다(여기서의 강조는 제 책에 실린 것과 일치합니다).
소프트웨어 개체의 본질은 서로 맞물리는 개념들의 구성물이다. 데이터 집합, 데이터 항목들 사이의 관계, 알고리즘, 그리고 함수 호출들이다. 이 본질은 추상적이며, 그 개념적 구성물은 여러 다른 표현 아래에서도 동일하다. 그럼에도 불구하고 그것은 대단히 정밀하고 풍부하게 세부적이다.
나는 소프트웨어를 만드는 데 어려운 부분은 이 개념적 구성물의 명세, 설계, 그리고 시험이지, 그것을 표현하고 그 표현의 충실성을 시험하는 노동이 아니라고 믿는다. 물론 우리는 여전히 문법 오류를 만든다. 하지만 그것들은 대부분의 시스템에서 개념적 오류에 비하면 잡음에 불과하다.
이것이 사실이라면, 소프트웨어를 만드는 일은 언제나 어려울 것이다. 본질적으로 silver bullet은 없다.
다만 브룩스가 비관하려 했거나 시간이 지나며 생산성이 향상될 가능성 자체를 완전히 배제하려 했던 것은 아니라는 점은 언급할 가치가 있습니다. 다만 그는 그것이 하드웨어에서처럼 끊임없는 거대한 도약의 결과가 아니라, 더 어렵고 점진적인 진보의 결과일 것이라고 믿었습니다.
질병 관리로 가는 첫걸음은 악마 이론과 체액 이론을 세균 이론으로 대체한 것이었다. 바로 그 한 걸음, 희망의 시작은, 마법 같은 해결책에 대한 모든 희망을 그 자체로 산산이 부쉈다. 그것은 일하는 사람들에게 진보는 단계적으로, 큰 노력 끝에 이루어질 것이며, 청결이라는 규율에 대해 끈질기고 쉼 없는 주의를 기울여야 한다고 말했다. 오늘날 소프트웨어 공학도 그러하다.
에세이의 중간 부분은 몇몇 역사적 진보와, 1980년대 중반에 주요한 발전 가능성으로 유행하던 아이디어들(객체지향 프로그래밍, 여러 형태의 “artificial intelligence” 같은 것들)을 다루지만, 브룩스는 그것들이 엄청난 향상을 낳을 것이라고 믿지 않았습니다.
마지막 부분은 제 생각에 가장 중요하면서도 동시에 가장 자주 간과되는 부분 중 하나입니다. 브룩스가 유망하다고 믿은 본질적 어려움에 대한 잠재적 공격 방법 목록이 나옵니다. 첫 번째 제안은 가능하다면 소프트웨어를 만들지 말고 사라는 것입니다. 구매 대 자체 구축 결정은 거의 40년이 지난 지금도 여전히 시의적절한 주제입니다. 그다음 두 가지는 결국 “Agile”이라는 브랜드 아래 흡수된 것들입니다. 요구사항의 신속한 프로토타이핑/정제, 그리고 점진적 개발입니다(브룩스의 표현으로는 “소프트웨어를 구축하지 말고 성장시켜라”). 마지막으로 그는 소프트웨어 설계 능력을 훈련하고 길러야 한다고 제안합니다. 저는 브룩스가 “위대한” 설계자들이 보통 사람들과 구별되는 하나의 계급으로 존재한다는 생각에는 다소 동의하지 않습니다(『The Mythical Man-Month』 3장에서 소개된 개념인 “10x developer”에 지금도 문제를 느끼는 것과 비슷하게 말입니다). 하지만 적어도 좋은 소프트웨어 설계는 가르칠 수 있는 기술이라고 그가 믿었던 점은 높이 평가합니다.
「No Silver Bullet」이 처음 발표된 이후 (이제는) 37년 동안 많은 사람들이 그것에 반박해 왔습니다. 9년 뒤 나온 후속편 “Refired” — 이것도 읽어볼 가치가 있습니다 — 은 초기의 많은 반론을 다루며 브룩스가 원래 말하고자 했던 바를 더 분명히 하려 했습니다.
하지만 단순한 사실은 브룩스가 옳았다는 것입니다. 우리는 소프트웨어의 우연적 어려움에서는 진전을 이루었고, 본질적 어려움에서도 어느 정도는(대체로 개선된 실천과 설계를 통해) 진전을 이루었지만, 소프트웨어 개발 전체의 진보는 하드웨어에서 보아 온 향상에 비할 바가 전혀 아니었습니다.
사실 우리는 하드웨어에서의 Moore’s Law 수준 향상에 당연히 미치지 못했을 뿐 아니라, 그것에 근접조차 하지 못했습니다. 그리고 소프트웨어의 어려운 부분은 여전히 브룩스가 1986년에 설명했듯 추상적인 “개념적 구성물”로서의 소프트웨어 시스템에 대한 “명세, 설계, 그리고 시험”입니다. 실제로 편집기에 코드를 타이핑하는 일은 전체 소프트웨어 개발 과정에서 마지막 단계이며, 가장 시간이 적게 들고, 아마도 가장 덜 중요한 단계입니다(그 단계에 이르렀는데 그렇게 느껴지지 않는다면, 거의 확실하게 훨씬 이전 어딘가에서 문제가 생긴 것입니다).
이것은 브룩스가 제안한 종류의 실천을 따르는 팀에서도 마찬가지입니다. 빠르게 프로토타이핑하고, 피드백을 모으고, 재빨리 반복하고, 시스템을 “성장”시키는 경우에도, 팀이 편집기에 코드를 타이핑하는 것 이 아닌 일을 하는 데 쓰는 시간은 여전히 그것인 작업에 쓰는 시간보다 훨씬 많을 것입니다. 그리고 커다란 아이러니 중 하나는, 소프트웨어 개발 과정을 더 잘하게 될수록 그런 비코드 작성 작업이 점점 더 지배적이 된다는 점입니다(그것만이 이유는 아니지만 중요한 이유 하나는, 실천이 좋아질수록 평균적인 코드 변경이 작아질 가능성이 커지기 때문입니다).
소프트웨어 개발 직함의 사다리를 올라갈수록, 여러분은 개인적인 생산성으로는 점점 덜 평가받고, 동료들의 생산성에 미치는 영향으로는 점점 더 평가받아야 합니다. 그러므로 왜 소프트웨어 개발이 어려운지, 그리고 실제로 의미 있는 향상이 어디에서 발견되는지를 이해하는 것은 경력 성장과 발전을 위한 필수 조건입니다.
예를 들어, 현재 생산성을 높이기 위한 인기 있는 유행 중 하나는 “developer experience”(또는 “DX”, “DevEx”, 혹은 생산성을 아껴 준다고 주장하는 다른 약어들)라는 우산 용어 아래 들어가는 여러 가지입니다. 하지만 여기에는 불편한 비밀이 있습니다. “DX”는 거의 전적으로 우연적 어려움을 공격하는 데 관한 것이며, 이는 곧 수확 체감의 게임이라는 뜻입니다. 그렇다고 해서 그것을 해서는 안 된다는 뜻은 아닙니다 — 이 분야에도 최첨단의 상태가 있고, 많은 팀이 거기에 더 가까이 가려고 노력함으로써 상당한 초기 이득을 볼 것입니다 — 다만, 음, 그것이 silver bullet은 아니라는 뜻입니다.
예를 들어, 표준적인 온갖 장식이 다 붙은 새로운 백엔드 웹 서비스를 부트스트랩하는 데 필요한 시간을, 말하자면 대부분 수작업으로 일주일 이상 걸리던 것에서 대부분 자동화가 돌아가는 것을 지켜보는 반나절(혹은 그 이하)로 줄일 수 있다면, 그것은 큰 개선입니다. 그리고 더 시니어한 개발자들의 시간을 그런 것들에 투자하는 것을 여러분은 분명 고려해야 합니다.
하지만 중요한 질문 하나도 기억해야 합니다. 그 작업을 얼마나 자주 수행하나요? 그리고 그 전에는, 그 후에는 무엇이 일어나나요? 새 서비스를 시작하자는 결정을 어떻게 내리나요? 그것이 무엇을 할지, 어떻게 보일지, 무엇이 필요할지를 어떻게 결정하나요? 설정은 코드베이스마다 한 번 일어나는 일입니다. 그리고 그 경험이 아무리 매끈하고 근사하고 빠르더라도, 그것이 끝나면 책임은 다시 개발자의 어깨 위로 돌아와 이렇게 말합니다. “좋아요, 코드베이스는 초기화됐습니다. 이제 그걸로 무엇을 하실 건가요?”
이것이 「No Silver Bullet」의 핵심 교훈입니다. (지금 이 예를 계속 들자면) 개발자 환경이나 새 코드베이스 등을 부트스트랩하기 위한 근사한 DX를 갖는 것은 좋은 일입니다. 하지만 그보다 더 중요한 것은 좋은 실천과 좋은 소프트웨어 아키텍처 및 설계를 알고 그것을 구현하고 따를 수 있는 사람들을 갖추는 것입니다. 그런 지식과 기술을 가진 사람들이 있다면, DX는 아마도 부수 효과로서 조만간 생겨날 것입니다. 반대로 멋진 DX 도구들을 도입하면서 사람과 실천과 설계 능력에는 투자하지 않는다면, 결과는 그리 좋지 않을 것입니다.
하지만 업계 전체로 보면 우리는 좋은 실천과 좋은 설계 및 아키텍처를 전달하고 가르치는 일에서, 마땅히 그래야 하는 수준에 한참 못 미칩니다. 이것 역시 직함의 사다리를 올라가는 사람들이 크게 집중해야 할 영역이어야 하지만, 그런 것들은 대체로 멋지거나 번쩍이지 않는 반면 “DX” 도구는 그렇습니다. 그래서 주의가 쉽게 분산됩니다. 저는 제가 아는 누구 못지않게 그 점에서 유죄입니다(현재 제 직함은 “Principal”이지만, 여전히 그 역할 안으로 많이 성장해 가는 중입니다). 실천과 설계를 아주 조금만 개선해도, 어떤 번쩍이는 “DX” 도구를 아무리 많이 도입하는 것보다 더 크고 더 장기적인 이득을 가져올 가능성이 큽니다. 그리고 그런 이득은 이직을 해도 지속됩니다 — 특정 도구를 잘 아는 것은 항상 잘 이전되지는 않지만, 좋은 소프트웨어를 설계하는 방법을 아는 것은 그렇기 때문입니다.
위에서 제가 일부러 “수확 체감”이라는 표현을 썼는데, 소프트웨어 생산성을 생각하는 좋은 방식이 바로 그것이라고 믿기 때문입니다. 브룩스는 더 고수준의 프로그래밍 언어가 처음 등장했을 때(그리고 1980년대에는 그런 언어들 중 많은 수가 2020년대의 기준으로 보면, 심지어 그보다 더 이른 몇몇 시기의 기준으로 보더라도 여전히 아주 저수준 이었습니다) 예전 언어들이 강요했던 수작업의 지루함에서 프로그래머를 해방시킴으로써 실제로 상당한 생산성 향상을 가능하게 했다는 점을 흔쾌히 인정했습니다. 하지만 우연적 어려움의 큰 덩어리 몇 개를 제거하고 나면, 그 접근법으로 얻을 수 있는 남은 이득은 다시는 예전만큼 크거나 쉽게 얻어지지 않을 것입니다.
그리고 제 생각에 대체로 그것이 소프트웨어 개발 전체의 궤적이었고, 그 내부의 개별 하위 분야들 역시 마찬가지였습니다. 예를 들어 제가 몸담아 온 백엔드 웹 개발이라는 하위 분야는 1990년대 후반/2000년대 초반에 PHP의 프로그래밍 및 배포 모델 덕분에, 그 이전의 대부분을 밀어내고 초라하게 만들 만큼 큰 향상을 얻었습니다. 그리고 더 단순하고(이전의 현상 유지 와 비교해서) 더 의견이 강한 신속 개발 프레임워크들, 예를 들어 Rails(그리고 Django!)가 부상하면서 다시 한 번 큰 향상을 얻었습니다. 이들은 인기 있던 “엔터프라이즈” 도구들이 유연성 자체를 목표로 하며 쌓아 올린 추상화 계층의 더미를 대체로 던져버리고, 더 분명하고 더 노골적인 “여러분은 이 방식으로 할 것입니다”라는 접근을 택했습니다. 하지만 그 이후로는 식었고, 그 뒤로 우리는 그런 종류의 향상을 다시 보지 못했습니다. 앞으로 다시 보게 될지 궁금합니다. 적어도 Python 세계에 대해서는 따로 글 하나가 필요할 생각이 조금 있습니다.
하지만 Moore’s Law식의 거대한 향상이 없다고 해서 향상이 전혀 없다는 뜻은 아닙니다. 그래서 저는 두 가지를 언급하려고 합니다. 그것들은 진정한 silver bullet은 아닐지라도(즉, 생산성에서 전체적으로 한 자릿수 차수의 향상을 가능하게 하는 의미에서는), 여전히 — 제 의견과 경험상, 물론 이것들은 객관적으로도 옳고 보편적입니다 — 충분히 은빛에 가까운 색조를 띠고 있어서 들여다볼 가치가 있습니다.
여기서 저는 “Docker”라는 이름을, containers, containerization 등으로 이루어진 훨씬 더 큰 생태계 전체를 가리키는 약칭으로 사용하고 있습니다. 여기에는 특별히 “Docker”라는 이름의 원래 구현뿐 아니라, Docker라는 이름은 아니지만 비교적 상호 운용 가능한 다른 경쟁 구현들도 포함됩니다.
저는 Docker에 늦게 합류한 편이었습니다. 부분적으로는 제 경력 단계에서 저는 많은 것들의 열렬한 얼리어답터가 아니며, 초기 과장 국면이 지나고 어떤 것들이 살아남는지 지켜보는 편을 선호하기 때문입니다. 또 부분적으로는 제가 초기에 Docker를 접한 많은 경우가 Kubernetes와 함께였기 때문입니다. 그런데 Kubernetes는, 만약 anti-silver bullet 같은 것이 존재한다면, 꽤 괜찮은 후보일지도 모릅니다. 아마 탄환의 한 종류라기보다, Looney Tunes 만화에서 한 캐릭터가 다른 캐릭터의 총을 방아쇠를 당기면 사수 얼굴 앞에서 터질 물건으로 바꿔치기하는 그런 장면 같은 것에 더 가깝겠지요. Kubernetes에 대한 제 느낌이 그렇습니다.
하지만 Docker는요? Docker는 훌륭합니다. 가장 분명한 점부터 시작해 봅시다. 제가 써 본 것 중 실제로 대부분 잘 작동하는 첫 번째 “virtual machine”(또는 그 비슷한 것) 기술입니다. 제가 이 업계에 들어왔을 때 “로컬 개발 환경”을 갖는다는 것은 Apache와 애플리케이션 코드와 데이터베이스 서버, 아마 캐시 서버 인스턴스까지도 돌리는 것을 의미했고, 자기 컴퓨터에 그런 것들을 설치하고 관리하는 법을 알아야 했습니다. 그러다 VM이 등장했고, vagrant up 같은 것을 실행해 놓고 한 시간쯤 자리를 비웠다가 돌아와 보면 실패해 있는데, 같은 운영체제를 돌리는 같은 모델의 노트북에서 같은 명령을 실행한 바로 옆자리 사람에게는 왜 됐는지, 그리고 내 것은 왜 안 됐는지 도무지 알아낼 수 없는 그런 상황이 벌어졌습니다.
지금은요? Docker입니다. 완벽하진 않고 성공률 100%에는 못 미치지만, 제 본업에서 다루는 것들(모두 하나 이상의 Dockerfile과 docker-compose.yml 표준을 함께 배포합니다)에 대해서는 “첫 시도에 동작했다” 비율이 Vagrant로 누구라도 달성하는 것을 제가 보았던 것보다, 하물며 옛날식 비가상화 설정보다도, 거의 한 자릿수 차수만큼 더 높아지고 있다고 짐작합니다.
그것만으로도 채택할 가치가 있습니다. 하지만 그것은 로컬 환경과 배포 환경 사이의 일관성을 훨씬 더 높여 주고, 이전에는 사실상 불가능했던 방식으로 이것저것 그냥 시도해 볼 수 있는 능력도 제공합니다. 예전에는 로컬 데이터베이스를 설정해야 하면, 하물며 로컬 Kafka 클러스터 같은 더 복잡한 것을 설정해야 하면(복선입니다!), 신음을 내고 인상을 썼습니다. 지금은요? 요즘 잘 관리되는 거의 모든 도구가 최소한 표준 Dockerfile 하나는 제공하고, 더 복잡한 것들은 docker-compose.yml도 제공합니다. 그냥 docker run 또는 docker compose up을 하면, 놀라울 만큼 높은 비율로 동작합니다. 그리고 끝나면 내리고 버리면 됩니다. 더 이상 6개월 뒤에 왜 노트북이 부팅할 때마다 데이터베이스 세 개와 엔터프라이즈 Java 클러스터를 시작하는지 걱정할 필요가 없습니다(혹은 설치한 것들이 너무 많아서 왜 Homebrew 업그레이드에 하루 종일 걸리는지도요).
이제는 진지하게 받아들여지길 원하는 소프트웨어 프로젝트라면, “이 Docker 또는 docker-compose 파일을 실행하세요”라는 쉬운 이야기를 갖추는 것이 아마도 기본 참가 자격쯤 된다고 생각하게 될 정도입니다. 웹 프레임워크 세계에서 정확히 누가 먼저 거기에 도달했는지는 모르겠지만, 제가 처음으로 정말 훌륭한 통합을 본 곳은 C#/.NET 진영이었습니다. 그곳에서는 dotnet 명령줄 인터페이스가 여러분이 거의 아무 작업도 하지 않아도 코드베이스를 Docker화해 줍니다. 누군가가 간단한 dotnet publish로 자기 앱을 컨테이너화한다고 말하는 걸 볼 때마다, 저는 질투가 나서 그날 남은 시간 내내 Django에도 비슷한 것이 있었으면 하고 바랍니다.
아무튼 제 생각에 Docker는 꽤 은빛인 기술 조각입니다. 특히 docker-compose 쪽이 그렇습니다. YAML의 결함이 아무리 많다 해도(그리고 많습니다), 그 YAML 파일을 열어서 “이 서비스는 이런 것들로 이루어져 있다”라고 읽는 경험은 본질적 어려움을 공격하는 데 놀랍도록 가까이 다가갑니다. 브룩스가 지적했듯 그중 한 부분은 소프트웨어 시스템을 시각화하기 어렵다는 점인데, docker-compose.yml 파일 같은 것에서 시작해서 특정 코드 단위의 구조를 보여주는 편집기/IDE 뷰까지 서로 다른 해상도 수준을 도입함으로써 그 문제를 해결하려는 시도는 꽤 제대로 된 방향으로 가고 있습니다.
여기서 저는 일부러 “events”라는 단어를 쓰지 않고 있습니다. 제가 말하는 것은 event-driven/microservice 아키텍처에 전면적으로 뛰어드는 것이 아니기 때문입니다. 그러니 지금은 messages라고 부르겠습니다.
제가 어릴 때 TV 만화는 “아는 것이 전투의 절반이다”라고 가르쳐 주었습니다. 하지만 소프트웨어에서는 때때로 아는 것 자체가 우리가 맞서 싸워야 할 대상입니다. Law of Demeter 같은 설계 원칙은 소프트웨어 시스템의 서로 다른 부분들이 서로를 아는 것이 언제나 좋은 것은 아니라는 깨달음에 기반합니다. 구성 요소들이 서로에 대해 더 많이 알수록, 그것들은 더 강하게 결합되고, 시스템 전체는 이해하고 작업하기 더 어려워집니다.
예를 들어, 얼마 전 저는 Django 애플리케이션에서의 “service layer” 추상화에 대해 논쟁을 한 적이 있는데, 누군가 주문 시스템의 예를 들었습니다. 이를테면 Order를 취소하려면 다른 구성 요소들에서도 할 일이 많다는 것이었습니다. Shipment 단계까지 갔는지 확인해서 그것도 취소하거나 경로를 바꿔야 하고, Payment를 취소하거나 Refund를 만들어 발행해야 할 수도 있는 식입니다. 요지는 “이건 Order 모델의 단일 cancel() 메서드 안에 넣기에는 너무 복잡하다!”였습니다. 그건 사실입니다. 하지만 문제는 그 다음 말이었습니다. “그러므로 이 모든 것을 대신 OrderService 클래스의 단일 cancel() 메서드 안에 넣겠다.” 그렇게 한다고 해서 문제(Order든 OrderService든 Payment, Shipment 등을 알고 있어야 하고 그것들을 조작하는 방법도 알아야 한다는 문제)가 실제로 해결되지는 않습니다. 그저 문제를 조금 옮겨 놓을 뿐입니다.
그렇다면 무엇이 실제로 문제를 해결할까요? 그런 것들을 서로 분리하는 것입니다. 방법은 여러 가지가 있지만, 제가 당시 제안했던 하나는 — 그리고 제가 효과를 본 방식이기도 한데 — 일종의 메시지 버스를 도입하고, Order는 자기 필드만 조정한 뒤 “order cancelled” 메시지를 발행하게 하는 것이었습니다. 그러면 다른 모든 구성 요소들 — 배송, 결제 등 — 이 그 메시지를 지켜보다가 적절히 반응할 수 있습니다.
그리고 이것이 꼭 엄청 복잡하거나, 엄청난 인프라를 요구하거나, “event-driven” 아키텍처에 전면적으로 뛰어들어야 하는 것은 아닙니다. 메시지 큐를 도입해 단일 코드베이스 안에서만 사용하는 방법도 충분히 많습니다. “microservices”는 필요 없습니다. 예를 들어 Django에는 단순한 동기식 메시지 버스에 대한 공개 API가 포함되어 있습니다(Django는 이것을 “signals”라고 부릅니다). 많은 사람들이 이미 캐싱에 사용하는 Redis에도 메시지 큐 기능이 있습니다. 그리고 특화된/전용 도구와 서비스들도 있습니다. RabbitMQ, Kafka, SNS/SQS 등, 그리고 그것들에 쉽게 연결하고 사용할 수 있게 해 주는 라이브러리들도 많습니다.
생각하면 할수록, 또 실제로 함께 작업해 볼수록, 저는 메시지 큐가 꽤 은빛이라고 생각하게 됩니다. 완벽하진 않지만, 그것이 없을 때 얻는 것에 비하면 여전히 상당한 개선이며, 아마 그래서 다른 많은 프로그래밍 분야의 수많은 시스템과 프레임워크들이 어떤 형태로든 이벤트 기반 또는 event-driven 모델을 갖고 있는 것일 겁니다. 백엔드 웹 개발은 지금 그것을 발견하는 과정에 있는 듯합니다.
메시지 큐가 우연적 어려움에 주는 큰 이득은, 앞서 말했듯, 서로를 직접 호출하거나 심지어 서로를 알 필요조차 없이도 많은 구성 요소가 협력할 수 있다는 점입니다. 큐에 메시지만 넣고, 그 메시지 유형에 관심 있는 것이 읽고 반응하게 두면 됩니다. 이는 다시 시스템 내 결합의 양과, 구성 요소들이 서로에 대해 해야 하는 장부 정리의 양을 줄여 줍니다. 하지만 이것 역시 본질적 어려움을 공격하는 데 놀랍도록 가깝습니다. 메시지 큐를 사용한다고 해서 시스템 전체를 통째로 더 시각화하거나 더 쉽게 추론할 수 있게 되는 것은 아니지만, 직접 결합을 제거함으로써 개별 구성 요소를 고립해서 추론하는 일을 촉진하고, 그것은 더 작고 따라서 바라건대 더 쉬운 과제입니다.
소프트웨어 개발은 어렵습니다. 오랫동안 어려웠습니다. 그리고 앞으로도 상당한 기간 동안은 계속 그럴 것입니다. 여러분이 가장 좋아하는 “AI” 코드 도우미가 있더라도 그렇습니다. 설령 그것이 운 좋게 그럴듯한 코드를 뱉어낸다 하더라도, 그것은 소프트웨어 개발의 어려운 부분이 아니며 원래도 아니었기 때문입니다.
「No Silver Bullet」은 그것이 어떻게 그리고 왜 어려운지를 생각하고, 우리가 생산성 향상을 어디서 어떻게 달성할 수 있는지를 이해하는 데 유용한 틀을 제공합니다.
그리고 브룩스가 틀렸다고 주장하며 문자 그대로 수십 년을 보낸 사람들이 얼마나 많았든, 우리는 여전히 하드웨어에서 보아 온 것과 같은 종류의 일관되고 거대한 소프트웨어 생산성 향상을 보지 못했습니다. 이것은 줄이기가 훨씬 더 어려운 어떤 본질적 어려움이 있다는 점에서 브룩스가 옳았다는 강한 신호입니다.
이것은 진보가 불가능하다는 뜻도 아니고, 우리가 우연적 어려움을 공격해서는 안 된다는 뜻도 아닙니다. 우연적 어려움을 줄이면 초기에 상당한 이득을 얻을 수 있습니다. 다만 본질적 어려움이 결국 지배적으로 자리 잡기 때문에, 그런 이득은 빠르게 줄어들 뿐입니다.
소프트웨어 개발에서 어려움의 근본 원인을 이해하고, 생산성 향상이 어디에서 가능할지를 평가할 수 있는 능력은 모든 개발자가 경력 내내 성장하고 발전하면서 반드시 길러야 할 것입니다. 본질적 어려움을 공격하는 방법을 이해하는 것도 마찬가지입니다.
주로 우연적 어려움을 공격하는 일부 도구나 기술이나 기법도 여전히 정말 유용하며 채택할 가치가 있습니다. 코드나 프로세스를 더 단순하고 더 분명하고 더 쉽게 추론할 수 있게 도와주는 것이라면 더욱 그렇습니다. 그런 것은 본질적 어려움의 영역에 가까워지고 있기 때문입니다.
우리는 모두 좋은 소프트웨어 설계와 아키텍처, 그리고 그것을 새롭게 성장하는 동료들에게 가르치는 일에서 지금보다 훨씬 더 나아져야 합니다.