모든 글

공통 모듈의 함정: 구체적인 기능으로 코드를 응집하라

소유권이 드러날 때까지 공통 모듈을 늦추고, 로깅·예외·공유 규격을 구체적인 기능과 책임 중심으로 묶는 방법을 다룬다.

  • modules
  • architecture
  • maintainability

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

common 모듈은 여러 곳에서 쓰는 코드를 쉽게 모을 자리를 약속한다. 바로 그 편리함 때문에 위험하다. 소프트웨어의 경계가 아직 분명하지 않을 때 ‘두 곳에서 사용한다’는 사실이 소유권을 대신하기 시작한다. 로깅 설정, 예외, 응답 모델, 유틸리티와 나중에는 비즈니스 규칙까지 같은 모듈로 흘러 들어간다.

더 안전한 기본값은 공통 모듈을 늦추는 것이다. 먼저 구체적인 기능이 드러나게 두고, 다음 유지보수자가 무엇이 들어가야 하는지 이름만 보고도 판단할 수 있는 책임 주위로 코드를 모은다. 실제 필요가 확인되면 공유해도 된다. 다만 ‘공통’보다 분명한 경계를 얻은 뒤여야 한다.

재사용은 소유권을 정의하지 않는다

주문과 회원 코드에서 함께 쓰는 보조 기능을 생각해 보자. 이를 common으로 옮기면 중복은 사라지지만 무엇 때문에 이 코드가 바뀌는지는 알 수 없다. 회원 정책은 바뀌고 주문 행위는 유지되어야 한다면, 공통 클래스 안에 ‘주문용’과 ‘회원용’ 분기가 자라기 시작한다. 코드는 중앙에 모였지만 이를 지배하는 개념에서는 멀어진다.

프로젝트가 커질수록 유지보수는 더 어려워진다. 한 사용자를 위한 변경이 다른 사용자에게 뜻밖의 영향을 줄 수 있다. 리뷰어는 영향 범위를 이해하려고 멀리 있는 호출부까지 찾아야 한다. 새 개발자는 소유자가 애매한 코드를 두는 공식 장소로 common을 받아들이므로 모호함은 계속 쌓인다.

경고 신호는 특정한 클래스 개수가 아니다. 공통 영역이 지원하려던 핵심 기능보다 더 많은 의미를 품는 상태다. 여러 모듈에서 호출한다는 이유로 비즈니스 로직이 그곳에 있다면, 재사용이 책임의 방향을 지운 것이다.

프로젝트를 열었을 때 핵심 기능보다 common 쪽에서 더 많은 개념과 클래스를 발견한다면 특히 의심해 볼 만하다. 주문의 부수 기능과 회원 정책이 모두 여러 곳에서 호출된다는 이유로 그 안에 섞이면, 변경이 생길 때 common-for-order, common-for-member 같은 구분이 같은 클래스 안에서 다시 만들어진다. 처음 추출할 때 줄인 중복보다 이후에 영향 범위를 찾고 분기를 관리하는 비용이 커질 수 있다.

실제로 만들고 있는 기능의 이름을 붙여라

로깅은 흔히 횡단 관심사라고 불리지만 그렇다고 일반적인 공통 바구니가 필요한 것은 아니다. 진단 컨텍스트를 다루고, 추적 정보를 전파하고, 관련 동작을 설정해야 한다면 이 코드는 로깅 기능으로 응집될 수 있다. logging 모듈이라는 이름은 기여자에게 구체적인 질문을 준다. 이 코드는 로깅과 추적을 위한 것인가, 아니면 단지 공유하기 편해서 이곳에 두려는가?

모니터링도 같은 방식으로 접근할 수 있다. 그렇다고 모든 기능에 즉시 독립 모듈을 만들 필요는 없다. 기존 프로젝트 안의 패키지로 충분할 수 있다. 기능의 크기나 의존성 요구가 경계를 정당화할 때 모듈이 쓸모를 갖는다.

이름은 의도를 보존한다. 책임이 실제로 보조 역할이라면 supportcommon보다 나을 수 있고, 가능한 경우에는 더 구체적인 이름이 좋다. 이름은 들어올 수 있는 코드의 범위를 좁혀야지 축적을 부추겨서는 안 된다.

예외는 그 의미를 소유한 경계에 두어라

예외를 다루다 보면 모든 모듈이 쓸 하나의 기반 타입을 정의하고 싶어진다. 전역 처리가 편해 보일 수 있지만 서로 떨어져 있던 모듈이 하나의 중앙 추상화에 연결되는 결과도 만든다.

기반 예외 하나에 공통 처리를 연결하면 초기에는 깔끔해 보일 수 있다. 그러나 그 기반 타입을 모든 모듈이 의존하는 순간, 예외를 표현하는 각 경계의 차이도 함께 묶인다. 처음 만든 사람들이 암묵적인 사용법을 모두 아는 동안에는 버틸 수 있어도 새 동료가 오고 담당자가 이동하면 그 규칙은 쉽게 흐려진다. 그래서 편리함이 보이는 시점보다 책임이 안정된 시점까지 추출을 늦출 필요가 있다.

예외가 가진 의미는 흔히 서로 다르다. 도메인 모듈은 내부 규칙의 실패를 소유한다. API 모듈은 오류를 외부 클라이언트에 어떻게 전달할지 소유한다. 둘 다 코드에서 예외로 표현되더라도 서로 다른 위상에 있다. 각 모듈이 자신이 소유한 오류를 관리하면 내부 개념이 외부 응답 계약 때문에 변형되는 일을 막을 수 있다.

모든 공유 예외 기반을 금지하자는 뜻은 아니다. 반복되고 안정된 행위가 무엇을 공유해야 하는지 보여줄 때까지 미루자는 것이다. 경계를 알기도 전에 회사 전체의 기반 예외부터 만들면 의존성을 추가하기는 쉽고 제거하기는 어려워진다.

구체적인 공유 규격만 추출하라

어떤 중복은 공유 모듈을 정당화할 수 있다. 여러 내부 또는 외부 API 클라이언트가 하나의 안정된 응답 형식을 따라야 하고, 변경할 때 수많은 연동 코드를 함께 고쳐야 한다고 해보자. internal-api-spec이나 특정 클라이언트 규격처럼 역할이 분명한 모듈은 의미가 있다.

이 경계는 역할이 명확하기 때문에 유용하다. 호출자가 쓰는 모든 유틸리티가 아니라 통신 규격을 소유한다. 클래스가 이 모듈에 속하는지는 그 규격을 표현하는지 물어 판단할 수 있다. 또한 관련 없는 보조 기능의 저장소가 되지 않고 연동 책임과 함께 변화할 수 있다.

이 경우에도 실제 비용을 관찰한 뒤 추출해야 한다. 호출부가 몇 개 없는 작은 프로젝트는 지역적인 코드가 더 명확할 수 있다. 중복과 동시 변경의 부담이 충분히 커졌을 때 구체적인 규격 모듈의 근거가 생긴다.

코드를 이어받을 사람을 위해 설계하라

원래 작성자가 모든 암묵 규칙을 기억하는 동안에는 잘 관리된 공통 모듈이 문제없이 작동할 수 있다. 그러나 팀은 바뀐다. 새 동료가 오고 유지보수자는 이동하며, 모든 수정을 누군가 곁에서 안내할 수 없다. 모호한 경계는 올바른 배치가 구조에 드러나지 않은 지식에 의존하기 때문에 빠르게 무너진다.

충분한 교육 기간과 체계적인 인수인계를 언제나 제공할 수 있는 조직이라면 암묵 규칙을 지킬 여지가 더 클 수 있다. 현실에서는 새로 온 사람이 곧바로 코드를 고치거나 담당 팀이 바뀌는 경우도 고려해야 한다. 회사 코드가 특정 작성자의 기억이 아니라 조직의 자산으로 남으려면, 설명을 듣지 못한 사람도 이름과 의존성만으로 실수하기 어려운 구조가 유리하다.

구체적인 기능 모듈은 더 오래가는 자산을 남긴다. 소유권을 보이게 하고 변경이 퍼지는 범위를 제한하며, 낯선 개발자도 추측하지 않고 코드의 자리를 찾게 한다. 같은 생명주기와 책임이 드러날 때까지 공유를 늦추자. 공유해야 한다면 실제로 무엇을 공유하는지 이름에 담자. 중복이 조금 더 오래 남을 수는 있지만, 서로 다른 변화를 시스템에서 가장 모호한 곳에 집중시키는 일은 피할 수 있다.