들어가며

개발자는 다양한 업무를 수행한다. 하지만 그중에서 가장 중요한 일은 무엇일까? 필자는 모델링이라 생각한다. 필자는 꽤 다양한 서비스와 시스템을 설계해왔고, 그 과정에서 많은 문제를 겪기도 했다. 개발자가 많은 시간을 소요하는 문제는 어떤 것일까? 필자의 경험에 따르면 탑 3에 드는 문제는 다음과 같다.

  • 요구사항을 잘못 해석하여 재작성
  • 요구사항 변경으로 인한 기존 코드 분석과 재작성
  • 디버깅을 위한 코드 분석

내 경험에 비롯하여 말하자면, 극단적인 면이 있을지 몰라도 모든 문제는 잘못된 모델링이 원인이라고 생각한다. 잘못된 모델링은 유지보수에 어려움을 유발한다. 반대로 잘된 모델링은 문제를 해결하는 데 큰 도움이 된다.

사실 모델링은 옛날부터 중요하게 다뤄졌고 지금까지도 많은 개발자가 중요하다 말한다. 그런데 갑자기 모델링을 정리하는 이유가 뭘까? 최근 여러 업체에서 AI가 가능한 직종은 신규 채용을 하지 않는다는 소식이 들려온다. 앞으로 이런 소식이 더 등장할 것을 생각하면 AI 시대에 어떻게 경쟁력을 가질지 고민해야 한다. 필자는 AI 시대에도 살아남기 위한 최고의 전략 중 하나를 모델링 능력이라 생각한다. 모델링은 문제 해결을 위해 문제 자체를 재정의하는 요령이며 이것이야말로 AI에게 '일을 잘 시킬 수 있는 핵심'이기 때문이다. 모델링은 고전이지만 오히려 AI시대에 핵심이 될 수 있다.

이번 글에서는 모델링이란 무엇인지, 왜 중요한지에 대해 알아볼 것이다. 그리고 이후엔 이 시리즈를 통해 유용한 모델링 패턴을 소개할 것이다.

모델링에 대한 오해

모델링이라고 하면 흔히 UML 다이어그램을 그리거나, 데이터베이스 테이블을 설계하는 일만을 떠올리기 쉽다. 또는 거창한 아키텍처 설계, 시스템 구조도를 만드는 일로 오해되기도 한다.

이런 것만 모델링인건 아니다

하지만 모델링은 그보다 훨씬 더 넓고 깊은 개념이다. 모델링은 문제를 바라보는 관점을 정리하고, 그 관점을 코드로 표현 가능한 형태로 구조화하는 일이다. 모델링은 특정한 기술이나 산출물에 국한되지 않는다. 다음과 같은 것들도 모두 모델링이다.

  • API 응답을 어떤 구조로 만들 것인가를 고민하는 것
  • 하나의 함수가 어떤 책임을 가져야 하는지 결정하는 것
  • 상태를 어떻게 표현할 것인지, 그 흐름을 어디서 제어할 것인지 정하는 것
  • 복잡한 조건을 명확한 규칙으로 치환하는 것
  • 여러 개념을 어디까지 하나로 묶고, 어디서 나눌지를 판단하는 것

모델링은 어쩌다 가끔 하는 작업이 아니다. 오히려 하루에도 수십 번씩 반복되는 작업이라 할 수 있다. 단지 우리가 그것을 '모델링'이라는 단어로 인식하지 못하고 있을 뿐이다.

코드 레벨에서의 모델링은 마치 객체지향 프로그래밍과 유사하게 보인다. 하지만 엄밀하게 같은 것은 아니며 코드 작성에 대한 방법론인 객체지향 프로그래밍과 달리 모델링은 문제 해석에 대한 사고 방식과 그에 따른 구현이라고 보는 것이 더 적절하다.

모델은 곧 추상화와 구조화다

모델은 단순히 데이터를 담는 그릇이 아니다. 그것은 우리가 문제를 어떻게 바라보고 해석할 것인가에 대한 표현이며, 설계이며, 설명서다. 즉, 모델이란 개발자 시선에서 해결할 문제를 추상화하고 구조화한 결과물이다.

추상화와 구조화가 뭘까? 먼저 추상화는 복잡한 객체에서 본질적인 특징을 뽑아내는 일이다. 모든 것을 담는 것이 아니라, 목적에 따라 무엇을 담고 무엇을 버릴지 선택하는 것이다. 그리고 목적을 달성하기 위해선 여러 관점으로 볼 수 있는 능력이 중요하다.

관점에 따라 보이는 것이 다르다

예를 들어, '사용자(User)'라는 개념을 모델링한다고 해보자. 어떤 시스템은 이메일만 알면 충분하다. 어떤 시스템은 나이, 성별, 구매 이력까지 알아야 한다. 같은 '사용자'라는 말도 관심사가 다르면 다른 모습으로 추상화된다. 이처럼 추상화는 우리가 무엇을 중요하게 여기는지, 어떤 문제를 풀고자 하는지를 반영한다. 다시 말해, 모델은 문제를 바라보는 관점의 결과물이다.

구조화는 추상화된 개념들을 어떻게 구성할지에 대한 결정이다. 어떤 속성은 하나의 객체로 분리되고, 어떤 책임은 메서드로 들어가며, 어떤 요소들은 서로 관계를 맺는다. 즉, 어떠한 시스템에 대한 구성을 정의하는 것이라 할 수 있다. 좋은 구조화는 복잡한 것들을 겹치지 않게 나누고, 빈틈없이 연결한다. 이를 통해 유지보수를 쉽게 만들고, 시스템을 이해하기 쉽게 만든다.

한 가지 예시로 '피자 주문 시스템'을 모델링한다고 가정해보자.

  • 시스템의 목적이 재고 관리라면,
    모델은 피자의 구성 재료(치즈, 도우, 소스 등), 각 재료의 수량, 유통기한 등을 중심으로 구성될 것이다.
  • 시스템의 목적이 마케팅이라면,
    모델은 고객의 주문 이력, 선호 토핑, 할인 쿠폰 사용 내역 등으로 중심이 이동할 것이다.
  • 시스템의 목적이 배달 최적화라면,
    주소 정보, 배달 소요 시간, 라이더 위치와 스케줄이 주요 모델링 대상이 될 것이다.
  • 혹은 이 모든 것을 다뤄야 한다면,
    각각의 추상화 영역을 나눠 서로 협력하게끔 구조화할 수 있다.

이처럼 같은 ‘피자 주문 시스템’이라는 문제도, 관심사에 따라 전혀 다른 모델이 나온다. 모델은 현실을 그대로 흉내 내기보다는, 관점을 담은 해석에 가깝다.

좋은 모델이란 무엇인가?

좋은 모델이란 단순히 잘 짜인 클래스나 예쁜 다이어그램을 의미하지 않는다. 그 모델이 문제를 얼마나 효과적으로, 그리고 정확하게 표현하느냐가 핵심이다. 다음은 좋은 모델을 구성하는 네 가지 기준이다.

문제를 해결해야 한다

모델은 결국 문제를 해결하기 위한 도구다. 코드의 아름다움, 구조의 정교함, 테스트의 견고함 모두 중요하지만, 그 모든 것에 앞서 모델이 실제로 문제를 해결하고 있는지가 더 중요하다. 아름다운 설계여도 실제 문제에 부딪혔을 때 쓸 수 없다면 그 모델은 실패한 것이다.

좋은 모델은 항상 다음 질문에 답할 수 있어야 한다.

  • 이 모델은 어떤 문제를 해결하기 위해 존재하는가?
  • 그 문제는 이 모델만으로 충분히 해결되는가?
  • 지금 모델이 해결하는 문제는 명확히 정의되어 있는가?

모델은 설계의 결과물이기 이전에, 의사소통과 문제 해결의 도구다. 좋은 모델은 쓸모 있어야 한다.

내부가 블랙박스여도 상관없어야 한다

좋은 모델은 내부 구현을 몰라도 사용할 수 있어야 한다. 메서드 이름, 필드 구성, 인터페이스만 보아도 어떤 역할을 하고 어떻게 써야 하는지 명확해야 한다. 이는 곧 모델의 외부 계약이 잘 정의되어 있다는 것을 의미한다. 가장 이상적인 모델은 블랙박스로 취급할 수 있는 모델이다. 즉, "이 객체는 이런 역할을 한다"는 점만 알고 있으면 내부가 어떻게 구현되어 있는지는 몰라도 될 때 가장 좋다.

예를 들어, 돈이라는 의미를 담은 객체 Money가 있다고 가정해보자.

val a = Money.won(1000)
val b = Money.won(500)
val c = a.add(b)

이 코드를 읽는 사람은 a와 b가 원화 금액을 나타내며, add는 두 금액을 더해주는 역할을 한다는 것을 쉽게 유추할 수 있다. 심지어 내부적으로 add가 BigDecimal을 사용하든, Int를 사용하든, 통화 단위를 비교하든 사용자 입장에선 중요하지 않다.

이처럼 모델은 의도를 명확히 표현하면서도 내부 구현은 감출 수 있어야 한다. 그럴수록 사용자는 더 적은 인지 부하로 모델을 사용할 수 있고, 유지보수와 리팩터링도 쉬워진다.

개발자의 인지 범위를 벗어나면 안 된다

심리학 이론에 따르면 사람은 동시에 7±2개의 정보 단위만을 기억할 수 있다. 즉, 훈련된 전문가가 아니라면 인지해야 하는 정보가 7개가 넘는 순간부터 이해가 급격하게 어려워진다. 모델도 마찬가지다. 사용하는 개발자의 인지 범위를 초과하는 모델은 좋은 모델이 아니다. 모델은 한눈에 역할과 구조가 파악되어야 하며, 내부의 상태 변화나 복잡한 의존 관계는 최소화되어야 한다.

모델이란 결국 문제를 코드로 표현한 구조다. 그런데 이 구조를 이해하기 위해 너무 많은 맥락을 요구한다면, 그 모델은 이미 설계의 실패에 가깝다. 그렇다면 좋은 모델은 어떻게 구성해야 할까?

  • 모델 하나만 봐도 역할과 책임이 명확히 드러나야 한다.
  • 전체 모델 구성이 하나의 문제 해결을 위한 구조여야 한다.
  • 협력 객체는 적을수록 좋고, 의존성은 단순할수록 좋다.
  • 예상 가능한 흐름으로 동작해야 한다.

모델은 결국 사람이 이해하고 사용할 수 있어야 의미가 있다. 아무리 정교한 추상화라 하더라도, 그것이 팀의 개발자들에게 부담을 준다면 좋은 모델이라 할 수 없다. 따라서 모델의 크기, 깊이, 의존 수는 모두 개발자의 인지 부하를 기준으로 설계되어야 한다.

예측할 수 있어야 한다

좋은 모델은 예측 가능해야 한다. 예측 가능하다는 것은, 이 모델이 어떻게 동작할지, 어디에서 사용될지, 무엇을 바꾸면 어디에 영향이 갈지를 쉽게 짐작할 수 있다는 뜻이다. 예측 가능한 모델은 문제가 발생했을 때 원인을 좁히기 쉬워지고, 새로운 기능을 추가하거나 변경할 때도 안전하게 수정할 수 있다.

반대로, 예측이 어려운 모델은 다음과 같은 문제를 낳는다.

  • 동작을 확인하기 전까지는 어떻게 작동할지 짐작조차 어렵다.
  • 코드 수정을 하려면 이 모델이 사용되는 모든 곳을 추적해야 한다.
  • 구조가 복잡해질수록 수정이 다른 곳에 어떤 영향을 줄지 확신할 수 없다.

좋은 모델은 내부 구현이 감춰진 블랙박스여도 예측 가능성을 보장하기 위해 외부 계약은 명확하게 드러나야 한다. 무조건 숨기는 것은 캡슐화의 본질이 아니다. 오히려 적절히 드러낸 구조가 있어야 사용자는 모델을 믿고 사용할 수 있다. 예측 가능성은 숨김이 아니라 명확한 표현에서 나온다.

  • 메서드 이름이 의도를 표현해야 하고,
  • 파라미터가 어떤 역할인지 드러나야 하며,
  • 어떤 경우에 실패하거나 예외가 발생하는지도 코드를 보며 유추할 수 있어야 한다.

다음 예제를 살펴보자.

class PointManager {
  fun apply(userId: String, point: Int): Boolean {
    if (point > 0) { // 차감
      return deduct(userId, point)
    } else { // 적립
      return earn(userId, -point)
    }
  }

  private fun deduct(userId: String, point: Int): Boolean { /* ... */ }
  private fun earn(userId: String, point: Int): Boolean { /* ... */ }
}

이 코드는 포인트를 적립하거나 차감하는 기능을 제공한다. 하지만 이 코드의 문제는 apply 메서드만 봐서는 동작을 정확히 알 수 없다. 인자값이 양수면 차감, 음수면 적립이라는 암묵적 규칙을 알아야만 이 코드의 동작을 이해할 수 있다. 따라서 실수하기 쉽고, 디버깅도 어렵고, 코드를 볼 때마다 주석이 필요하다. 위 코드에서 가장 큰 실수는 apply 메서드의 의도가 감춰져 있다는 점이다. 반면, deductearn을 직접 호출하는 방식으로 바꾼다면, 메서드 이름만으로도 의도가 드러나고, 사용자가 실수할 여지가 줄어든다.

결국 모델이 예측 가능하다는 것은, 개발자의 머릿속에 그 모델의 사용법, 의도, 작동 방식이 명확히 떠오른다는 뜻이다. 그리고 이 명확함이야말로 유지보수성과 확장성을 결정짓는 핵심 요소다.

이 시리즈를 쓰려는 이유는?

필자는 꽤 다양한 서비스와 시스템을 설계해왔고, 그 과정에서 유독 자주 반복되는 구조들이 있었다. 특정한 도메인, 특정한 프레임워크, 특정한 기술 스택을 넘어서도 반복되는 패턴들이었다. 이러한 경험 속에서 유용하게 사용한 모델링 패턴을 정리해보려 한다. 이 모델들은 단순한 코드 구조가 아니라, 문제를 해석하고 구조화하는 사고 방식이었다. 그리고 그 사고 방식은 반복 가능했고 유용했다.

어쩌면 AI 시대에 불필요하다고 느껴질 수 있다. IT 산업은 첨단 산업처럼 보이지만 개발 업무는 수공업에 가깝다. 그 과정 속에서 우리는 지식과 경험을 살려 부족한 것과 과한 것 사이에서 효율을 찾아낸다. 그것이 오늘날 많이 회자되는 아키텍처고 패턴이다. 필자 개인적인 생각으로 AI 시대에 들어서며 개발 업무는 코드를 마치 한땀 한땀 장인처럼 만드는 수공업에서 공장처럼 자동으로 생산하는 제조업으로 넘어가고 있는 중으로 보인다. 공장에서 물건을 자동으로 생산하기 위해선 정확한 수치와 재료, 프로세스가 필요하다. 개발 업무도 마찬가지일 것이다. 오히려 앞으로는 정확한 지식이 더 중요해질 것이라 생각한다.

모델링은 큰 그림을 그리는 일이다. 모델에 대한 세부 구현은 AI가 잘 해줄 수 있다. 우리가 좋은 모델링 방법에 대해 이해하고 있다면 생산성과 견고함을 동시에 잡을 수도 있을 것이다.