대규모 소프트웨어 시스템에서는 코드베이스를 완전히 이해하지 못한 상태로도 효과적으로 일해야 하는 이유와, 부분적 이해가 때로는 최선인 이유를 설명합니다.
소프트웨어 엔지니어로서, 자신의 코드베이스를 얼마나 잘 이해해야 할까?
내 생각에 규모가 작은 코드베이스와 이직이 적은 팀에서 일하는 사람들(예를 들어 Redis나 The Witness 같은 게임)은 “당연히 완전히 이해해야 한다. 그렇지 않으면 좋은 일을 할 수 없다”라고 말할 것이다. 반대로 규모가 큰 코드베이스와 이직이 많은 팀에서 일하는 사람들(예를 들어 Google 웹 검색 백엔드나 GitHub)은 “당연히 완전히 이해하는 것은 불가능하다. 그저 자기 주변 영역에서 최선을 다해야 한다”라고 말할 것이라고 생각한다.
이 둘은 방법론, 관행, 문화가 크게 다른 프로그래밍 방식이다1. 하지만 소프트웨어 엔지니어링에 대한 온라인 논의에서는 첫 번째 집단이 과도하게 대표된다2. 나는 첫 번째 집단에 맞서 두 번째 집단을 옹호하고 싶다. 많은 소프트웨어 엔지니어링 환경에서, 부분적인 이해 상태에 있는 것은 전혀 잘못이 아니다. 사실 대규모 시스템에서는 부분적 이해가 당신이 할 수 있는 최선이다.
“코드베이스를 이해해야 한다”는 입장을 가장 잘 정식화한 글은 Peter Naur의 유명한 논문 Programming as Theory Building이다. 나는 이 논문을 좋아하지만, 그 방향으로 너무 멀리 나아간다고 생각한다. Naur의 핵심 주장은 프로그래머가 프로그램을 작업할 때 코드 자체는 사실 부산물에 불과하고, 그들이 실제로 만들어 가는 주된 산물은 “프로그램에 대한 이론”이라는 것이다. 이것은 무슨 일이 왜 일어나는지에 대한 직관적 감각으로 이루어져 있으며, 코드나 문서로는 일부만 포착할 수 있다. 코드를 잃어버려도 그들은 프로그램을 쉽게 다시 쓸 수 있다. 하지만 이해를 잃어버리면(예를 들어 팀이 100% 교체되면) 코드의 의미를 파악하는 데 큰 어려움을 겪게 된다.
여기까지는 좋다. 하지만 Naur는 여기서 더 나아간다. 그는 그 이론이 코드로부터 _재구성되어서는 안 된다_고 말한다. Naur에 따르면, 기존 프로그램을 완전히 버리고 새 팀이 처음부터 다시 만들게 하는 편이 더 낫다. 그 과정에서 새로운 이론도 함께 쌓이게 된다는 것이다3:
단지 문서만으로 프로그램의 이론을 다시 확립하는 것은 엄격한 의미에서 불가능하다 … [따라서] 기존 프로그램 텍스트는 폐기되어야 하며 새로 구성된 프로그래머 팀은 주어진 문제를 새롭게 해결할 기회를 부여받아야 한다
대기업에서 효과적인 소프트웨어 엔지니어로 일해 본 사람이라면 누구나 Naur가 이 점에서 완전히 틀렸다는 것을 안다. 이유는 적어도 두 가지가 있다.
첫째, 대규모 소프트웨어 시스템은 애초에 처음부터 다시 만들 수가 없다. 충분히 큰 시스템은(사용자가 있다면) 다시 구현할 수 없는 수천 개의 이상한 경우와 특이점을 포함하고 있다. 시스템에 아주 익숙한 팀이라 해도 이 일을 해낼 수 없다. 한꺼번에 다뤄야 할 것들 이 너무 많기 때문이다. 성공적인 재작성은 항상 기존 코드베이스를 작고 고립된 덩어리로 잘라내는 것에서 시작하고, 그 다음 한 번에 한 덩어리씩 다시 쓴다. 다시 말해, 소프트웨어 시스템을 재작성한다는 것은 옛 시스템에 수많은 변경을 가하는 일이다. 옛 시스템을 변경할 수 없다면, 새로운 것으로 대체하는 것은 더더욱 불가능하다.
둘째, 버려진 시스템은 항상 되살아난다. 수억 줄의 코드와 수천 명의 엔지니어가 있는 기술 회사에서는, 어떤 코드베이스에 그것을 잘 아는 사람이 하나도 남아 있지 않은 상황이 드물지 않다4. 몇 사람이 안 좋은 시점에 퇴사하거나, 코드베이스가 1년 정도 유지보수되지 않기만 해도 충분하다. 나는 다른 팀들이 그렇게 하는 것을 본 적이 있을 뿐 아니라, 직접 버려진 코드베이스의 소유권을 맡아 그것을 파악하고, 효과적으로 작업할 수 있는 수준까지 도달해 본 적도 있다. 시간이 걸리기는 하지만, 코드베이스에 대한 새로운 이론을 세우는 것은 가능하다. 한 흐름을 처음부터 끝까지 이해하는 것에서 시작하고, 거기서 조금씩 가지를 뻗어 나가며 조심스럽게 변경을 가하면 된다.
충분히 큰 코드베이스에서는 모든 사람이 프로그램에 대해 틀린 이론을 가지고 작업한다. 현대 소프트웨어 시스템의 결정적 특징은 그 규모가 너무 커서 누구도(심지어 팀 전체라도) 머릿속에 전부 담아둘 수 없다는 점이다. 아무도 전체를 이해하지 못한다. 효과적으로 일하려면, 부분적으로만 맞는 이론을 가지고도 일할 방법을 찾아야 한다. 이것이 내가 계속 입장을 취하는 것과 자신감에 대해 이야기하는 이유다. 무언가 확실하지 않다고 해서, 완벽한 이해를 가진 누군가가 와서 답을 주기를 마냥 기다릴 수는 없다. 당신이 유능한 엔지니어라면, 그 사람은 바로 당신이다. 이를 악물고 가장 근거 있는 추측을 한 다음, 그 결과를 감당해야 한다.
Naur에게 너그럽게 해석하자면, 1985년에는 평균적인 프로그램의 규모가 오늘날보다 몇 자릿수는 더 작았을 가능성이 있고, Naur가 “대규모 프로그램”이라고 쓸 때 그가 말하는 것은 수천만 줄의 코드가 아닐 수도 있다. Naur가 제시하는 첫 번째 대규모 프로그램의 예시는 200,000줄짜리 산업 모니터링 프로그램이고, 두 번째 예시는 컴파일러다. 1987년에 컴파일러 GCC의 첫 버전은 약 십만 줄의 코드였고, 2015년의 GCC는 천사백만 줄이 넘었다. 10만 줄이나 20만 줄의 코드를 다시 쓰는 일은, 특히 기존 테스트를 재사용할 수 있다면, 비교적 단순할 수 있다고 나는 믿는다. 하지만 100만 줄이나 200만 줄은 그렇지 않다.
LLM은 종종 이론 구축의 일반적인 과정을 방해하기 때문에 나쁜 도구로 언급된다. 나는 이것이 지나치게 단순화된 시각이라고 생각한다. 많은 소프트웨어 도구와 마찬가지로, LLM은 양날의 검이다. 소프트웨어에 대한 정교한 정신적 이론을 구성하기는 더 어렵게 만들지만, 빠르게 부분적 이론을 세우게 해 주고 그 부분적 이론을 더 효과적으로 활용하도록 도와줄 수도 있다. 이것은 복잡한 절충이며, 나는 아직도 이에 대해 생각 중이다.
LLM은 제쳐 두더라도, 소프트웨어에 대한 당신의 이론을 방해하는 것은 무엇이든 나쁘다고 말하는 것은 분명 어리석다. 다음은 이론을 유지하기 더 어렵게 만드는 다른 요소들의 일부 목록이다:
소프트웨어의 대부분의 문제와 마찬가지로, “코드베이스에 대한 이론을 유지하는 것”은 많은 가치 중 하나일 뿐이다. 때로는 그것이 가장 중요한 가치여서 다른 가치를 희생하고서라도 지켜야 하고, 다른 때에는 속도나 법적 준수, 혹은 정치적 이유5를 위해 그것과 절충해야 한다.
거의 모든 엔지니어, 특히 “순수한” 엔지니어들은 자신의 소프트웨어에 대해 정확한 정신 모델을 유지하는 것을 선호한다. 그것이 더 재미있고, 덜 스트레스받으며, 더 “진짜 엔지니어링”처럼 느껴지기 때문이다. 그래서 많은 엔지니어가 여가 시간에 오픈소스 프로젝트를 맡아 작은 코드베이스를 혼자 다룬다. 코드베이스에 대해 정확한 Naur식 이론을 유지할 수 있는 엔지니어링 작업을 하기 위해서다. 나는 그것이 잘못되었다고 생각하지 않는다.
하지만 직장에서 당신은 일을 하라고 돈을 받는다. 다시 말해, 그들은 그들 의 엔지니어링 가치 집합을 받아들이라고 당신에게 돈을 준다. 성능을 개인적으로 아무리 중요하게 생각하더라도, 때로는 직장에서 느린 코드를 써야 한다는 점은 아마 잘 이해되고 있을 것이다(예를 들어 프로젝트를 제때 끝내기 위해서이거나, 다루기 까다로운 요구사항을 수용하기 위해서다). 코드베이스에 대한 이론을 유지하는 것도 같은 종류의 문제다.
↩ 2. 오픈소스 엔지니어들은 자신의 작업에 대해 블로그를 쓸 의욕이 더 크고, 순수한 엔지니어링 콘텐츠는 대개 더 인상적이며(대규모 사유 시스템에서는 조정 문제가 지배적이기 때문이다), 오픈소스 프로젝트는 법적으로 글을 쓸 수 있지만 사유 시스템은 그렇지 못하고, 설령 법적으로 가능하더라도 대규모 코드베이스에 대해 쓰는 일은 너무 많은 구체적 맥락을 필요로 하기 때문에 불가능하다.
↩ 3. 나는 Ryle의 The Concept of Mind 관련 장들을 다시 읽어 보았는데(Naur가 전반에 걸쳐 인용하는 책이다), Ryle 쪽이 이론 구축에 대해 더 관대하다고 생각한다. Ryle에게 이론 구축 또는 노하우는 무언가를 하는 과정에서 자동으로 생겨난다. 기존 코드베이스를 단지 코드만 보고, 순전히 퍼즐을 풀듯이 파악해 나갈 수 있다고 생각하는 것은 Ryle와 완전히 양립 가능하다.
↩ 4. Naur는 이렇게 말한다. “이 결과가 비합리적으로 보일 수 있다는 점에 대해서는, 완전히 죽은 프로그램을 되살릴 필요는 아마 드물게만 발생할 것이라는 점을 지적할 수 있다. 왜냐하면 원래 팀이 가지고 있던 이론에 대한 지식이 최소한 어느 정도도 없는 새로운 프로그래머들에게 그 복구가 맡겨지는 일은 거의 상상할 수 없기 때문이다.”. 그랬다면 얼마나 좋았을까!
↩ 5. 어떤 엔지니어들은 이론을 유지하는 것이 핵심 가치라고 말할지도 모른다. 그것 없이는 다른 어떤 가치도 실현할 수 없기 때문이다. 나는 동의하지 않는다. 가독성, 유지보수성, 정확성, 혹은 다른 여러 엔지니어링 가치들에 대해서도 똑같은 말을 할 수 있다. 우리는 이런 “핵심” 가치들을 늘 절충한다.
이 글이 마음에 들었다면, 새 글에 대한 이메일 업데이트를 받기 위해 구독하는 것을 고려해 보거나, Hacker News에 공유해 보길 바란다.
다음은 이 글과 태그를 공유하는 관련 글의 미리보기다.
에이전트를 만들어라, 파이프라인이 아니라
컴퓨터 프로그램에서 LLM을 사용하는 방법은 오직 두 가지뿐이다. 파이프라인의 일부로 사용하거나, 에이전트로 사용하는 것이다. 다시 말해, 프로그램의 제어 흐름을 코드로 표현하거나, LLM에게 도구를 주고 제어 흐름 자체를 관리하게 하는 것이다.
다음은 사소한 “여러 정보를 요약해서 이메일로 보내 줘” 프로그램을 파이프라인으로 구성하는 방법이다: