데이터 지향 프로그래밍 스터디 마무리 후기

데이터 지향 프로그래밍 마지막 스터디 후기

마지막 모임에서는 책을 읽으며 각자 어떤 생각을 했는지, 배운 내용을 실제 개발에 어떻게 가져갈 수 있을지 이야기를 나눴습니다. 데이터와 코드를 분리하고 불변 데이터를 다루는 방식은 이제 제법 익숙해졌지만, 실제로 적용할 곳을 떠올려 보면 여전히 의견이 갈렸습니다. 객체 없이도 복잡한 프로그램을 잘 구성할 수 있을까, 상태가 계속 바뀌는 게임에도 DOP가 어울릴까 하는 질문들이 이어졌습니다.

저는 데이터 지향 프로그래밍(Data-Oriented Programming, 이하 DOP)을 읽으며 함수형 프로그래밍을 떠올렸던 경험에서 발표를 시작했습니다. 이후에는 디버깅과 빠른 피드백에 관한 이야기를 들었고, 대화는 자연스럽게 AI와 함께 개발하는 방식까지 이어졌습니다. 중간에는 준비해 주신 꽈배기를 나누며 잠시 쉬기도 했습니다. 책의 마지막 내용을 정리하는 시간이면서, 서로의 개발 경험을 조금 더 들여다보는 시간이기도 했습니다.

상태를 없애기보다 의존하는 범위를 줄이기

제가 준비한 발표의 출발점은 ‘상태를 줄이면 복잡성이 퍼지는 것을 막을 수 있을까’라는 질문이었습니다. DOP에서 강조하는 데이터와 코드의 분리, 불변 데이터, 범용 함수의 조합이 함수형 프로그래밍과 닮아 보였기 때문입니다.

쿠폰을 적용한 뒤 주문 금액을 계산하는 경우와, 금액을 계산한 뒤 쿠폰을 적용하는 경우를 예로 들었습니다. 같은 주문을 다뤄도 호출 순서에 따라 계산 결과는 달라집니다. 계산 함수의 인자만 보는 것으로는 부족하고, 그 전에 어떤 메서드가 호출되어 내부 상태가 바뀌었는지도 알아야 하는 셈입니다. 이런 의존 관계가 늘어나면 코드를 수정할 때 확인해야 할 범위도 넓어집니다.

반면 필요한 데이터를 입력으로 받고 결과를 반환하는 순수 함수는 같은 입력에 같은 결과를 내며, 함수 밖의 상태를 바꾸지 않습니다. 호출 이력을 계속 따라가지 않아도 입력과 결과를 중심으로 이해할 수 있다는 점이 매력적으로 느껴졌습니다. 현재 시각이나 난수, 데이터베이스에서 읽은 값처럼 결과에 영향을 주는 요소들도 대화에 등장했습니다. 함수가 무엇에 의존하는지 드러내는 일이 왜 중요한지 생각해 볼 수 있었습니다.

그렇다고 실제 애플리케이션에서 상태를 없앨 수는 없습니다. 주문이 접수되고 게임이 진행되면 시스템은 달라진 상황을 기억해야 합니다. 그래서 발표를 준비하며 질문도 조금 바뀌었습니다. 상태 자체를 문제로 삼기보다, 여러 코드가 암묵적으로 같은 상태에 의존하고 변경하는 범위를 줄이는 것이 중요하지 않을까 싶었습니다.

객체 지향 프로그래밍도 같은 관점에서 다시 봤습니다. 객체의 상태와 수명을 제한하고, 외부에 미치는 영향을 잘 구분하면 복잡성을 관리하는 데 도움이 됩니다. DOP를 공부한 결과가 객체를 쓰지 말자는 결론일 필요는 없었습니다. 오히려 지금의 객체가 어떤 상태를 얼마나 오래 가지고 있어야 하는지 되묻는 계기가 됐습니다.

image

그림 1. 각각 10만 원 주문에서 시작해 10% 쿠폰을 적용하는 예시입니다. 쿠폰 적용은 내부 할인율만 바꾸고 자동 재계산하지 않는다고 가정했습니다. 먼저 반환받은 10만 원이라는 숫자는 그대로이며, 쿠폰 적용 후 다시 계산하면 새 결과로 9만 원을 얻습니다. 금액과 할인율은 이해를 돕기 위해 정한 값이며, 도표는 새로 제작했습니다.

익숙한 객체와 새로운 데이터 표현 사이에서

이야기를 나누다 보니 DOP가 완전히 낯선 방식만은 아니라는 의견도 나왔습니다. 이미 입출력을 담당하는 코드와 핵심 로직을 분리하고, 일반적인 데이터 형태를 사용하면서 그 바깥을 클래스로 감싸 왔다는 경험이 있었습니다. 책을 읽으며 기존에 하던 설계와 닮은 부분을 발견한 것입니다.

반대로 클래스 없이 규모가 큰 프로그램을 어떻게 구성할지 잘 그려지지 않는다는 의견도 있었습니다. 클래스가 제공하는 메서드만 알면 내부의 자세한 구조를 몰라도 사용할 수 있는데, 데이터를 직접 다루는 방식에서는 필드와 구조를 더 많이 알아야 하는 것 아니냐는 질문이었습니다.

이 지점이 흥미로웠습니다. 한쪽에서는 데이터와 함수를 자유롭게 조합할 수 있다는 장점을 봤고, 다른 쪽에서는 알아야 할 범위를 정해 주는 객체의 역할을 중요하게 봤습니다. 특정 패러다임이 더 좋다고 정리하기보다는, 각자 익숙한 코드에서 무엇이 이해를 돕고 무엇이 부담이 됐는지 비교하는 대화에 가까웠습니다.

실무 경험 중에는 게임 세션이 이미 관리하는 값을 화면 쪽에서도 별도로 관리하게 된 사례가 나왔습니다. 같은 사실을 두 곳에서 각각 기억하기 시작하면 어느 쪽을 기준으로 삼아야 할지, 변경을 어떻게 맞춰야 할지가 문제가 됩니다. 추상적인 상태 관리 이야기가 실제 코드에서 어떤 모습으로 나타나는지 떠올리기 좋은 사례였습니다.

게임 개발에도 DOP를 적용할 수 있을까

게임 개발 이야기가 나오면서 논의는 더 구체적이 됐습니다. 캐릭터는 게임이 진행되는 동안 위치와 상태를 유지하고, 다른 대상과 계속 상호작용합니다. 이런 역할과 행동을 객체로 표현하는 방식이 자연스럽다는 의견이 있었습니다.

한편 바둑이나 보드게임을 떠올리면 데이터와 규칙을 분리해서 생각하기가 비교적 수월해 보인다는 의견도 나왔습니다. 곧이어 보드게임 역시 행동의 제약과 상태 관리가 필요하다는 이야기가 이어졌습니다. 장르의 이름만으로 적용 가능성을 나누기보다는, 어떤 규칙을 계산하고 어떤 상태를 유지해야 하는지 구체적으로 봐야 했습니다.

대화 중에는 전체 구조를 객체 중심으로 유지하면서, 불변 데이터가 도움이 되는 부분에 DOP의 방식을 가져올 수 있지 않겠느냐는 의견도 있었습니다. 다만 실제로 어느 부분에 적용할지는 쉽게 답이 나오지 않았습니다. 그 막막함까지 이야기할 수 있어서 좋았습니다. 책의 예제에서 이해한 원칙을 자기 도메인으로 옮기는 일은 또 다른 공부라는 생각이 들었습니다.

계산 결과를 어디에 반영할 것인가

발표에서는 앞선 모임에서 인상 깊게 들었던 포트와 어댑터 이야기로도 연결해 봤습니다. 데이터베이스에서 값을 읽어 오는 일, 그 값으로 결과를 계산하는 일, 결과를 저장하거나 응답하는 일을 구분하면 핵심 계산이 담당할 범위가 또렷해집니다.

예를 들어 쿠폰 적용 함수가 기존 주문을 직접 바꾸는 대신 변경된 주문 데이터를 반환한다면, 그 결과를 실제 상태에 반영하는 일은 호출한 쪽에서 맡습니다. 계산과 반영을 구분하는 방식으로 이해하니, 애플리케이션이 중간 상태를 얼마나 오래 붙잡고 있어야 하는지 다시 생각하게 됐습니다.

기존 기록을 덮어쓰지 않고 새 기록을 추가하는 append-only 방식도 함께 떠올렸습니다. 특히 변경 사건을 기록하고 그 순서대로 적용해 현재 상태를 구성하는 이벤트 소싱은, 지금의 값뿐 아니라 그 값에 이른 과정도 남긴다는 점에서 연결해 볼 만했습니다. 이것을 DOP와 같은 개념으로 보려던 것은 아닙니다. 불변 데이터를 공부하며 저장 방식까지 관심이 넓어졌다는 쪽에 가까웠습니다.

버그를 다시 실행할 수 있는 형태로 만들기

이어진 디버깅 이야기에서 가장 기억에 남은 것은 짧은 피드백 주기였습니다. 문제가 생겼을 때 코드를 오래 들여다보는 것만큼, 문제가 발생하는 조건을 붙잡아 빠르게 다시 실행하는 일이 중요하다는 내용이었습니다.

여기서 REPL도 다시 보게 됐습니다. REPL은 코드를 입력하면 곧바로 실행 결과를 확인할 수 있는 대화형 실행 환경입니다. 잠깐 식을 계산해 보는 도구 정도로 생각했는데, 실제로 사용해 본 도구에서는 실행 중인 프로그램의 데이터나 객체에 접근해 동작을 확인하는 작업도 가능했다는 경험을 들었습니다. 어떤 기능을 제공하는지는 언어와 도구마다 다르겠지만, 디버깅 수단을 넓혀 볼 계기가 됐습니다.

필요한 데이터를 따로 꺼내 함수에 넣어 볼 수 있다면, 매번 전체 애플리케이션을 같은 순서로 조작하지 않고도 계산을 확인할 수 있습니다. DOP에서 다뤄 온 데이터와 코드의 분리, 데이터를 저장하고 다시 읽을 수 있는 표현 방식이 디버깅과 연결되는 대목이었습니다. 물론 데이터만으로 재현되지 않는 문제도 있지만, 먼저 재현 가능한 작은 단위를 찾으려는 접근은 실무 경험과도 잘 맞닿아 있었습니다.

AI에게 버그 수정을 맡길 때 실패하는 테스트를 먼저 만들어 전달한다는 경험도 나왔습니다. 무엇이 잘못됐는지 실행 가능한 형태로 보여 주면, 수정 후 확인할 기준도 분명해집니다. 다만 테스트가 실제 문제를 제대로 나타내는지부터 확인해야 합니다. 테스트가 통과한다는 사실과 사용자가 겪은 문제가 해결됐다는 사실을 무조건 같게 볼 수는 없기 때문입니다.

서버를 띄우고 전체 흐름을 확인하기 전에 작은 테스트로 검증할 수 있는 부분부터 확인하자는 이야기도 이어졌습니다. 코드를 고치고 결과를 확인하는 간격을 줄이자는 생각은, 직접 개발할 때나 AI와 함께 작업할 때나 여전히 유효하게 느껴졌습니다.

그림 2. 오른쪽 실선은 수정과 재검증의 반복을, 점선은 재현 조건을 다시 확인하는 경로를 나타냅니다. 작은 단위의 확인을 마친 뒤 필요한 전체 흐름도 검증합니다. 이해를 돕기 위해 새로 제작했습니다.

AI가 일을 할수록 사람은 무엇을 이해해야 할까

저는 LLM 에이전트를 만드는 경험에서도 DOP와 연결되는 부분을 느꼈습니다. 모델의 내부 동작을 모두 이해하기는 어렵더라도, 어떤 데이터를 주고 어떤 형태의 결과를 받을지 정하고, 도구가 하는 일을 구분하려는 접근이 익숙하게 다가왔습니다. 다만 입력과 출력을 정리한다고 LLM이 순수 함수가 되거나 같은 결과를 보장하는 것은 아닙니다. 비슷한 점을 빌려 생각해 본 것이지, 두 방식을 같다고 보려는 것은 아니었습니다.

다른 발표에서는 AI 작업에 사람이 얼마나 개입할지에 관한 이야기가 이어졌습니다. 매 단계에서 사람이 판단하는 방식, 시스템의 진행을 지켜보다 필요한 순간에 개입하는 방식, 대부분의 작업을 맡기는 방식은 각각 얻는 것과 부담이 달랐습니다. 개입을 줄이는 방향이 언제나 더 발전된 형태라는 뜻은 아니라는 점이 특히 기억에 남았습니다.

결과물이 나오는 속도와 별개로, 그 과정에서 내가 무엇을 배우고 있는지도 생각해야 했습니다. 잘 모르는 영역에서 결과만 받아들이면, 이후 무엇을 바꿀지 판단하거나 다른 사람과 설계를 논의하기가 어려워질 수 있습니다. 이해가 필요한 부분에서는 직접 확인하고 질문하며, 충분히 파악한 부분에서는 더 많이 맡기는 식으로 오갈 수 있겠다는 이야기를 나눴습니다.

코드를 이해해야 하는 이유가 단지 오류를 찾기 위해서만은 아니라는 말도 인상적이었습니다. 무엇을 만들고 있는지 알아야 다음 결정을 내리고 개발에 참여할 수 있습니다. 코드 생성이 빨라진 상황에서도, 문제를 이해하고 결과를 확인하는 과정은 여전히 개발자의 몫으로 남아 있었습니다.

책을 덮고 각자의 코드로 돌아가기

마지막 모임에서 DOP를 어디에나 적용할 수 있다는 결론을 내리지는 않았습니다. 오히려 객체가 이해를 돕는 경우와 데이터 중심의 표현이 편한 경우를 함께 이야기했고, 실제 코드에서는 두 방식을 섞어 쓰고 있다는 경험도 들었습니다.

제게 남은 것은 상태가 어디에서 바뀌는지 조금 더 주의 깊게 보고, 문제가 생겼을 때 다시 실행할 수 있는 형태를 찾아보자는 생각입니다. 새로운 패러다임의 이름을 익힌 것보다, 익숙하게 작성하던 코드를 다른 관점에서 질문해 볼 수 있게 된 것이 이번 스터디의 수확이었습니다.

모임을 마치며 뒤풀이와 다음에 함께 읽고 싶은 책 이야기도 나눴습니다. 같은 책을 읽어도 각자의 업무와 경험에 따라 눈에 들어오는 부분이 달랐고, 그 차이 덕분에 혼자 읽을 때보다 더 많은 질문을 가져갈 수 있었습니다. 퇴근 후 시간을 내어 발표와 토론을 함께해 주신 모든 분께 감사드립니다.

사진 및 뒷풀이

image

image
image