모든 글

소프트웨어는 경계를 한 단계씩 키워야 한다

동작하는 코드에서 시작해 실제 응집과 규모가 더 강한 경계를 요구할 때 함수, 클래스, 패키지, 모듈과 프로젝트를 단계적으로 추출합니다.

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

저는 소프트웨어가 코드, 함수, 클래스, 패키지, 모듈, 프로젝트라는 경계를 거치며 성장해야 한다고 생각합니다.

모든 기능이 이 목록을 끝까지 통과해야 한다는 체크리스트는 아닙니다. 구조가 이해를 따라가게 만드는 방향입니다. 동작을 잘 표현할 수 있는 가장 작은 형태에서 시작합니다. 구현을 통해 무엇이 함께 속하고 무엇을 분리해야 하는지 보일 때 더 강한 그릇을 추가합니다.

프로젝트 그림부터 만들고 아래로 내려오면 이 학습 순서가 뒤집힙니다. 가장 모르는 시점에 가장 큰 경계를 고르게 됩니다.

처음 만난 경계는 함수였다

제 첫 실무는 C를 주로 사용하는 펌웨어 개발이었습니다. 기존 프로그램에는 아주 긴 main 함수가 자주 있었습니다. 새 로직은 그 흐름 안에 작성했고, 명확히 공통으로 쓰이거나 재사용되는 동작만 함수로 뺐습니다.

시간 지연 코드에서 그 기준을 구체적으로 배웠습니다. delay는 CPU와 하드웨어 특성에 맞춰야 했고, 하드웨어를 조정하며 오실로스코프로 결과를 확인했습니다. 이 동작에는 구분되는 목적이 있었고 재사용할 수 있었기 때문에 함수가 될 이유가 있었습니다. 주변 로직의 많은 부분은 여러 주석과 함께 main에 남았습니다.

당시에는 풍부한 클래스 모델로 생각하지 않았습니다. 설계 질문은 더 작았습니다. 이 코드 블록에 별도 이름을 붙이고 호출할 만큼 응집된 목적이 있는가?

C++과 MFC 작업으로 옮기면서 클래스라는 새 경계를 만났습니다. 언어는 공부했지만 실제 소켓 서버와 UI 애플리케이션에서 클래스를 쓰는 일은 책에서 문법을 보는 것과 달랐습니다. 어떤 함수와 데이터가 하나의 개념을 이루는지 결정해야 했습니다. 기존 코드와 경험 많은 동료의 구현을 참고하면서 소프트웨어는 helper를 꺼내 쓰는 하나의 흐름에서 협력하는 클래스 묶음으로 바뀌었습니다.

더 큰 C#, Java와 Spring 시스템에서는 클래스가 가장 큰 그룹으로 충분하지 않았습니다. 관련 클래스를 패키지로 묶어야 했고, 관계가 큰 패키지에는 명시적 의존성을 가진 모듈이 필요할 때가 있었습니다. 모듈 묶음이 하나의 애플리케이션을 만들고, 충분히 독립적인 애플리케이션이나 관심사는 다른 프로젝트가 될 수 있었습니다.

여기서 배운 내용은 모듈이 고급이고 함수가 초보적인 수단이라는 것이 아닙니다. 각 경계는 더 작은 경계로 다루기 어려워진 문제에 답했습니다.

응집을 발견한 뒤 추출하기

진행은 코드를 잘 작성하는 일에서 시작합니다. 어떤 동작에 선명한 목적이 생기면 함수로 이름을 붙입니다. 함수와 데이터가 하나의 책임을 지킨다면 클래스가 적절한 집이 될 수 있습니다. 클래스가 많아져 한 네임스페이스를 이해하기 어려워지면 응집된 동작을 기준으로 패키지를 구성합니다.

패키지 관계를 강제할 필요가 생기면 모듈을 검토합니다. 모듈은 의존성을 격리하고 기술 선택을 숨기며, 다른 영역이 의도한 인터페이스를 사용하게 만들 수 있습니다. 운영 방식이나 소유권이 다른 모듈이 많아지면 하나의 프로젝트가 여전히 올바른 경계인지 묻습니다.

크기만으로 결정하지는 않습니다. “클래스가 스무 개이니 패키지를 만든다”는 설계 규칙이 아닙니다. 함께 바뀌고, 하나의 동작을 지원하고, 같은 생명주기를 가지거나, 나머지 코드로부터 보호해야 한다는 관계가 쓸 만한 신호입니다. 구현을 통해 성장하면서 그 관계가 드러납니다.

그래서 저는 시작부터 멀티모듈 프로젝트를 만드는 일을 조심합니다. 모듈은 수준 높은 장식이 아니라 제약입니다. 의존성을 이해하기 전에 만들면 초기 추측을 표현한 경계와 프로젝트 내내 협상해야 할 수 있습니다.

같은 실수는 패키지나 클래스에서도 생깁니다. 동작이 하나도 없을 때 만든 깊은 패키지 트리는 빈 이름에 모델의 권위를 줍니다. 함수가 길다는 이유만으로 클래스를 꺼내면 하나의 응집된 흐름이 흩어질 수 있습니다. 구조는 정해진 레이어 개수를 만족시키는 대신 이해와 변경 비용을 줄여야 합니다.

작게 키운다는 말은 엉망이 될 때까지 기다리자는 뜻이 아니다

이 조언을 나쁘게 해석하면 모든 것을 한 파일에 넣고 견딜 수 없을 때 분리하라는 말이 됩니다. 그렇게 하면 추출 비용만 커집니다.

작게 시작하는 설계에도 주의가 필요합니다. 현재 코드를 잘 작성하고, 정말 공통인 동작을 함수로 뺍니다. 응집된 함수는 클래스로 묶고, 클래스가 늘면 패키지로 정리합니다. 패키지가 많아질 때 모듈을 고민하고, 모듈 묶음이 커질 때 프로젝트를 나눕니다.

차이는 최종 경계를 안다고 가장하지 않는 데 있습니다. 현재 코드를 바꿀 수 있을 만큼 응집되게 만들고, 근거가 생길 때 경계를 강화합니다.

리뷰할 때는 단계에 따라 질문할 수 있습니다.

  1. 이 동작은 코드 그대로 이해할 수 있는가, 아니면 이름 붙인 함수가 목적을 선명하게 하는가?
  2. 여러 함수와 데이터가 클래스에서 지킬 하나의 책임을 이루는가?
  3. 관련 클래스가 가까이 있는가, 아니면 패키지가 응집을 표현해야 하는가?
  4. 모듈이 물리적으로 격리할 의존성이나 구현 세부사항이 있는가?
  5. 모듈의 소유권, 런타임, 릴리스나 운영 조건이 달라져 프로젝트를 나눌 이유가 생겼는가?

어떤 단계는 건너뛸 수 있습니다. 함수형 스타일은 같은 클래스 단계를 필요로 하지 않을 수 있습니다. 작은 프로그램은 수명 내내 함수 몇 개로 충분할 수 있습니다. 이 순서는 객체지향 형식을 강요하는 것이 아니라 경계의 강도가 문제에 맞는지 묻는 방법입니다.

소프트웨어가 구조를 가르치게 하기

Java와 Spring 실무를 시작할 때 동료가 이미 모듈 구조로 만든 시스템을 보여줘서 비교적 일찍 모듈을 접했습니다. 하나의 프로젝트에 관계 있는 모듈을 두고 애플리케이션을 조립할 수 있다는 사실은 제가 생각하던 프로젝트의 크기를 넓혔습니다. 동시에 어느 경계에서 도움이 되는지 알기 전까지 여러 시행착오도 만들었습니다.

그래서 멀티모듈 설계를 시작점의 성취처럼 설명하지 않습니다. 도구를 아는 시점과 도구를 쓸 판단이 생기는 시점은 다릅니다. 구현하고, 바꾸고, 운영하고, 잘못 만든 경계를 제거하면서 판단이 생깁니다.

소프트웨어는 세포처럼 성장해야 한다는 표현이 있습니다. 완성된 크기의 빈 골격을 조립하는 것과 성장의 차이를 보여줘서 좋아합니다. 살아 있는 시스템은 발달하면서 내부 구조를 만듭니다. 구조는 모든 단계를 미리 맞히는 대신 다음 단계를 지탱합니다.

코드를 작성하고, 이름 붙일 수 있는 동작을 추출하십시오. 응집이 증명된 것들을 묶고, 지킬 가치가 생긴 의존성을 강제하십시오. 각 부분이 정말 독립된 생명주기를 필요로 할 때 프로젝트를 나누십시오. 모든 단계는 소프트웨어가 실제로 겪는 압력을 해결해야 합니다.