대규모 소프트웨어 시스템의 설계는 그 시스템을 실제로 다루는 엔지니어만이 제대로 할 수 있으며, 구체적 맥락을 모르는 일반론적 설계 조언은 대부분의 실무 문제에 별 도움이 되지 않는다.
URL: https://www.seangoedecke.com/you-cant-design-software-you-dont-work-on/
대규모 소프트웨어 시스템에서는 그 시스템에서 실제로 일하는 엔지니어만이 설계 과정에 의미 있게 참여할 수 있다. 그 이유는 시스템의 구체적인 세부사항을 속속들이 이해하지 못하면 좋은 소프트웨어 설계를 할 수 없기 때문이다. 다시 말해, 일반론적 소프트웨어 설계 조언은 대부분의 실무 소프트웨어 설계 문제에서는 대체로 쓸모가 없다.
일반론적 소프트웨어 설계란 무엇일까? “문제에 맞춰 설계하기”다. 즉 도메인 에 대해서는 어느 정도 이해하고 있지만, 기존 코드베이스 에 대해서는 거의 모르는 상태에서 내놓는 조언을 말한다. 안타깝게도 소프트웨어 책이나 블로그 글에서 읽게 되는 조언은 거의 전부 이런 종류다1. 엔지니어들이 일반론적 설계 조언을 주는 걸 좋아하는 이유는, 모든 기술직 종사자들이 “업계 얘기”를 좋아하는 것과 같은 이유다. 하지만 이런 일반 조언을 일상적인 구체적 업무 문제에 적용할 때는 매우 조심해야 한다2.
실제 일을 할 때는 일반적 요인보다 구체적 요인이 지배적이다. 지금 당장 코드가 어떤 모습인지 명확히 이해하는 것은, 일반적인 설계 패턴이나 원칙을 잘 아는 것보다 훨씬, 훨씬 더 중요하다. 예를 들면:
원한다면 시스템 전체를 마음대로 다시 쓸 수 있는 세계라면, 일반론적 소프트웨어 설계 조언은 훨씬 실용적일 것이다. 실제로 그런 프로젝트도 있다! 하지만 대다수 소프트웨어 엔지니어링 업무는 안전하게 재작성할 수 없는 시스템에서 수행된다. 이런 시스템은 “소프트웨어 설계”에 의존할 수 없고, 대신 내부 일관성과 엔지니어들의 신중함에 의존해야 한다.
그렇다면 좋은 소프트웨어 설계는 어떤 모습일까?
내 경험상, 가장 유용한 소프트웨어 설계는 시스템을 매일 다루며 깊이 이해하는 소수의 엔지니어들 사이의 대화에서 나온다. 이런 설계 논의는 종종 외부인에게 정말 지루하게 느껴지는데, 어떤 기술자라도 이해하고 의견을 낼 수 있는 일반 원칙이 아니라, 시스템의 난해하고 구체적인 세부사항을 중심으로 돌아가기 때문이다.
논의되는 주제는 “DRY가 WET보다 나은가” 같은 것이 아니라, 오히려 이런 식이다. “이 새로운 동작을 서브시스템 A에 넣을 수 있을까? 아니, 컨텍스트 C에서는 그 서브시스템이 정보 B를 필요로 하는데 B가 그 컨텍스트에서 제공되지 않아. 그런데 그걸 노출하려면 서브시스템 D를 재작성해야 하고, 대신 여기서 서브시스템 E를 이 지점과 저 지점에서 분리하면…“
설계에 대한 깊고 철학적인 포인트는 대개 논의에서 거의 중요하지 않다. 대신 가장 결정적인 기여는, “아, 너는 C에서 B를 쓸 수 없다고 생각했구나. 그런데 우리가 최근에 C를 리팩터링해서 이제 필요하면 B를 흘려보낼(thread in) 수 있어” 같은 식으로, 구체적인 지점에 대한 작은 오해를 짚어 주는 것이다.
일반론적 소프트웨어 설계 조언이 실무 설계 문제에는 유용하지 않다고 해서, 완전히 쓸모없다는 뜻은 아니다.
일반론적 소프트웨어 설계 조언은 완전히 새로운 프로젝트를 만들 때 유용하다. 위에서 주장했듯이, 기존 시스템에 새 기능을 설계할 때는 시스템의 구체적 요소가 지배적이다. 하지만 새 시스템 을 설계할 때는 구체적 요소가 없으므로, 일반 조언만으로도 충분히 방향을 잡을 수 있다.
일반론적 소프트웨어 설계 조언은 구체적 설계 결정을 ‘타이브레이크’할 때 유용하다. 나는 일반 설계로 시작해야 한다고 생각하진 않지만, 허용 가능한 구체적 경로가 몇 개 있고 모두 괜찮아 보인다면, 일반 원칙이 그중 하나를 고르는 데 도움을 줄 수 있다.
이는 특히 회사 전체 수준에서 더 그렇다. 즉 일반론적 설계 조언은 서로 다른 코드베이스들 사이의 일관성을 보장하는 데 도움을 줄 수 있다. 공식적인 “소프트웨어 아키텍트” 역할이 가장 유용해지는 지점 중 하나가 바로 이것이다. 개별 엔지니어들이 구체적인 결정을 내릴 때 동일한 방향으로 타이브레이크할 수 있도록, 일반 원칙의 집합을 제공하는 것 말이다3.
일반론적 소프트웨어 설계 원칙은 회사 차원의 아키텍처 결정을 이끄는 데도 도움이 된다. 서비스를 자체 데이터센터에서 운영할까, 클라우드에서 운영할까? k8s를 쓸까? AWS냐 Azure냐? 충분히 범위를 넓히면 개별 서비스의 구체적 세부사항은 거의 중요하지 않게 되는데, 어느 쪽이든 엄청난 작업이기 때문이다. 그렇지만 이런 결정에서도 구체적 세부사항은 여전히 매우 중요하다. 클라우드에서는 할 수 없는 일(예: 맞춤형 하드웨어 셋업에 의존하는 것)도 있고, 자체 데이터센터에서는 할 수 없는 일(예: 12개 지역의 엣지에 서비스를 배포하는 것)도 있다. 코드베이스의 구체적 세부사항이 그런 것들 중 하나에 의존하고 있다면, 회사 차원의 아키텍처 결정을 내릴 때 그것을 무시하면 큰 곤란을 겪게 된다.
이것들이 일반론적 소프트웨어 설계를 하는 좋은 이유들이다. 반대로 회사들이 일반론적 소프트웨어 설계를 하는 나쁜 이유도 하나 있는데, 그것은 소프트웨어 엔지니어가 아닌 사람들에게 그게 그냥 굉장히 좋아 보이기 때문이다. 일단 시작하면, 인센티브 구조 때문에 멈추기 어렵다. 많은 기술 회사들이 이런 로컬 미니마에 빠진다.
왜 가장 높은 연봉을 받는 소프트웨어 엔지니어들이 오로지 가장 추상적이고, 가장 임팩트 큰 결정을 내리는 데만 시간을 쓰게 하지 않을까? 구조 엔지니어에게는 벽돌을 쌓기보다 도면을 그리게 하고 싶을 테니까. 구조 공학이 정말 그런 방식인지는 모르겠지만, 소프트웨어 공학은 그렇지 않다는 것은 안다. 실제로는 소프트웨어 아키텍처 조언이 현장 사람들에게 무시되어야 하는 경우가 자주 있다. 현재 존재하는 시스템의 맥락 안에서, 그것을 실제로 구현 가능한 것으로 번역해낼 방법이 없기 때문이다.
하지만 잘 작동하지 않는 관행치고는 “최고급 엔지니어는 일반 설계만 하라”는 방식이 놀라울 정도로 견고하다. 아키텍트는 이해관계가 걸려 있지 않다4. 그들의 설계는 실제 구현을 맡는 엔지니어링 팀에게 넘겨지기 때문이다. 그 설계는 결코 완벽히 구현될 수 없으므로, 아키텍트는 성공에 대해서는 공을 주장할 수 있고(결국 그들의 설계였으니까), 실패에 대해서는 발뺌할 수도 있다(“내 설계를 저 바보들이 제대로 따랐더라면!”).
대규모 기존 코드베이스에서 작업할 때, 유용한 소프트웨어 설계 논의는 사람들이 생각하는 것보다 훨씬, 훨씬 더 구체적이다. 보통 개별 파일, 심지어는 코드 한 줄 한 줄에 대해 이야기하게 된다. 따라서 코드베이스를 속속들이 알지 못하면 유용한 소프트웨어 설계를 할 수 없다(실제로는, 이는 거의 항상 सक्रिय 기여자(active contributor)라는 뜻이다).
순수하게 일반론적인 아키텍처가 쓸모없는 것은 아니지만, 그 역할은 (a) 완전히 새 시스템을 위한 ‘포장된 길(paved paths)’을 제시하는 것, (b) 기존 시스템에서의 결정을 타이브레이크하는 것, (c) 회사가 광범위한 기술 선택을 하는 데 도움을 주는 것에 제한되어야 한다.
내 생각에, 프로젝트의 초기 설계를 내놓는 데만 시간을 쓰는 공식적인 “큰 그림의 소프트웨어 아키텍트” 역할은 실패할 운명이다. 그럴듯해 보이지만(그리고 공은 챙기고 책임은 회피할 수 있는 아키텍트에게는 좋은 거래지만), 실제로 코드를 작성해야 하는 엔지니어링 팀에게 제공하는 가치는 매우 적다.
개인적으로 나는 소프트웨어 프로젝트의 설계를 떠올렸다면, 그 프로젝트의 성공/실패에 대한 책임도 져야 한다고 믿는다. 그렇게 하면 소프트웨어 시스템을 설계하는 사람들이 곧 소프트웨어 시스템을 실제로 출시할 줄 아는 사람들이 되도록 빠르게 정렬될 것이다. 또한 코드베이스의 거친 모서리와 흉터까지 고려해야 하는 진짜 소프트웨어 설계자들—즉 어려운 설계 작업을 실제로 해내는 사람들—이 그 공을 인정받도록 만들 것이다.
편집: 이 글은 Hacker News에서 댓글을 좀 받았다. 일부 댓글러들이 내가 일관성에 대해 말한 부분에 동의하지 않는 것을 보고 놀랐다. Mistakes engineers make in large established codebases의 반응이 꽤 긍정적이었던 것으로 기억한다. “하하, 이 글 자체가 일반 조언이니 위선적이다”라는 지적을 하는 댓글러들이 있을 것이라는 점은 놀랍지 않았다. 나는 위의 “일반 설계가 유용할 때” 섹션에서 이 점을 다뤘다.
이 글이 좋았다면, 새 글 업데이트 이메일을 받기 위해 구독하는 것을 고려해 보거나, Hacker News에 공유해 보라. 아래는 이 글과 태그가 겹치는 관련 글의 미리보기다.
순수한(“pure”) 소프트웨어 엔지니어링과 불순한(“impure”) 소프트웨어 엔지니어링
왜 1인 게임 개발자는 대형 테크 기업 엔지니어들과 자주 싸우게 될까? 왜 대기업의 유명한 외부 영입은 종종 흐지부지될까? 왜 AI 보조 개발은 어떤 엔지니어에게는 놀라운데, 다른 엔지니어에게는 완전히 쓸모없을까?
내 생각에는 어떤 엔지니어들은 다른 엔지니어들과는 매우 다른 종류의 일을 하고 있기 때문이다. 이 두 종류의 엔지니어는 종종 상대가 그냥 무능하다고 가정하지만, 사실은 서로 다른 분야에서 일하고 있을 뿐이다.