// education

소프트웨어 설계

요구사항에서 구조로

11장까지는 "책마루 온라인 주문 시스템이 무엇을 해야 하는가"를 다뤘다. 유스케이스로 손님과 직원의 행동을 정리하고, 클래스 다이어그램으로 개념을 모델링했다. 이제부터는 질문이 바뀐다. "무엇을 만들 것인가"에서 "어떻게 만들 것인가"로.

설계(design)는 요구사항 분석의 산출물 — 유스케이스, 클래스 다이어그램, 자료흐름도 — 을 실제로 구현 가능한 소프트웨어 구조로 바꾸는 작업이다. "주문을 받는다"는 요구사항 하나만 놓고 봐도 설계 단계에서 결정해야 할 것은 많다. 주문 접수 모듈과 재고 확인 모듈을 나눌 것인가 합칠 것인가, 결제 모듈은 어떤 정보를 주고받을 것인가, 데이터는 어떤 테이블 구조로 저장할 것인가. 이런 결정들이 쌓여 소프트웨어의 뼈대를 이룬다.

설계는 관리적 관점에서 크게 두 단계로 나뉜다. **기본 설계(architectural design)**는 시스템을 몇 개의 큰 하위 시스템으로 나누고 그 사이의 관계를 정하는 단계다 — 책마루라면 "주문 처리", "재고 관리", "배송 연동" 정도로 나누는 수준이다. **상세 설계(detailed design)**는 각 하위 시스템 안의 모듈이 구체적으로 어떤 절차와 알고리즘으로 동작할지를 정하는 단계다. 이 장에서는 주로 기본 설계 수준의 원칙을 다루고, 상세 설계는 15장에서 별도로 살펴본다.

한 가지 강조할 점은, 설계의 품질이 요구사항 분석의 품질에 크게 좌우된다는 것이다. 요구사항이 모호하면 설계자는 추측으로 빈틈을 채워야 하고, 그 추측이 틀리면 설계 전체가 흔들린다. 설계는 요구사항 분석이라는 준비 과정 위에 세워지는 건축물이다.

설계의 네 가지 기술적 활동

구조적 설계 기법에서는 설계 작업을 네 가지 활동으로 나눠 본다. 책마루의 "주문하기" 기능을 예로 들어 각각이 무엇을 결정하는지 살펴보자.

활동 결정하는 것 책마루 예시
데이터 설계 자료구조와 데이터베이스 스키마 주문 테이블, 재고 테이블, 그 사이의 관계
구조 설계 모듈을 어떻게 나누고 어떤 계층으로 배치할지 주문 접수 모듈 → 재고 확인 모듈 → 결제 모듈의 호출 구조
절차 설계 각 모듈 내부의 처리 절차와 알고리즘 "재고 확인" 모듈이 재고 수량을 비교하는 구체적 로직
인터페이스 설계 모듈 사이, 그리고 사용자와 시스템 사이의 소통 방식 주문 화면의 버튼 배치, PG사 API 호출 규격

네 활동은 순서대로 진행되기보다 서로 영향을 주고받으며 함께 다듬어진다. 데이터 설계에서 재고 테이블에 "예약 수량" 컬럼을 추가하기로 하면, 구조 설계의 재고 확인 모듈도 그 컬럼을 다루도록 바뀌어야 한다. 이 장의 나머지 부분은 주로 구조 설계 — 즉 모듈을 어떻게 나누고 배치해야 좋은 설계인가 — 에 초점을 맞춘다.

좋은 설계를 만드는 네 가지 원칙

좋은 구조 설계에는 공통으로 등장하는 몇 가지 사고방식이 있다. 책마루 시스템에 적용해 보자.

추상화 — 지금 필요한 만큼만 본다

**추상화(abstraction)**는 세부 사항을 감추고 핵심만 드러내는 것이다. 주문 처리 모듈을 설계할 때 "재고를 차감한다"는 한 줄로 표현할 수 있다면, 그 안에서 실제로 어떤 SQL이 실행되고 어떤 락(lock)을 거는지는 지금 당장 몰라도 된다. 설계 초반에는 시스템을 큰 덩어리로 추상화해서 전체 그림을 먼저 그리고, 점점 세부로 내려가며 구체화하는 것이 일반적인 흐름이다.

정보 은닉 — 몰라도 되는 것은 숨긴다

**정보 은닉(information hiding)**은 각 모듈이 자신의 내부 구현 방식을 다른 모듈에게 감춰야 한다는 원칙이다. 책마루의 재고 모듈이 재고 수량을 배열로 저장하든 해시맵으로 저장하든, 주문 모듈은 "재고를 조회한다", "재고를 차감한다"는 인터페이스만 알면 된다. 나중에 재고 저장 방식을 바꾸더라도 그 인터페이스만 유지되면 주문 모듈은 코드를 한 줄도 고칠 필요가 없다. 정보 은닉이 잘 되어 있을수록 한 모듈의 내부 변경이 다른 모듈로 번지지 않는다.

단계적 정제 — 큰 덩어리를 점점 잘게 쪼갠다

**단계적 정제(stepwise refinement)**는 추상화의 반대 방향 작업이다. "주문을 처리한다"는 최상위 기능을 "주문을 접수한다 → 재고를 확인한다 → 결제를 진행한다 → 배송을 등록한다"로 쪼개고, 각각을 다시 더 구체적인 단계로 나눠간다. 처음부터 모든 세부사항을 한 번에 설계하려 하면 실수하기 쉽지만, 단계적으로 정제하면 각 단계에서 다뤄야 할 결정의 양이 관리 가능한 수준으로 유지된다.

모듈화 — 적절한 크기로 나눈다

**모듈화(modularity)**는 시스템을 독립적으로 개발·시험·수정할 수 있는 단위로 나누는 것이다. 다만 모듈을 무조건 잘게 쪼갠다고 좋은 것은 아니다. 모듈 수가 늘어날수록 모듈 하나를 만드는 비용은 줄지만, 모듈 사이의 연결(인터페이스)을 관리하는 비용은 늘어난다. 책마루 시스템 전체를 하나의 거대한 모듈로 만들면 이해하기도 수정하기도 어렵고, 반대로 "재고 1개 차감"까지 별도 모듈로 쪼개면 모듈 사이를 잇는 배선이 복잡해져 오히려 비용이 커진다. 이 두 비용의 합이 최소가 되는 지점이 적정 모듈 크기다.

적정 모듈 크기를 가늠하는 감각: 한 모듈이 화면 한두 페이지를 넘어가는 코드를 담고 있거나, 이름 하나로 그 역할을 설명하기 어렵다면 너무 크다는 신호다. 반대로 모듈 열 개를 열어봐야 기능 하나의 흐름을 이해할 수 있다면 너무 잘게 쪼갠 것이다.

설계 품질의 핵심 척도: 응집도와 결합도

네 가지 원칙이 방향을 제시한다면, **응집도(cohesion)**와 **결합도(coupling)**는 그 방향을 얼마나 잘 지켰는지 재는 구체적인 잣대다. 응집도는 "한 모듈 안의 요소들이 얼마나 밀접하게 관련되어 있는가"를, 결합도는 "모듈과 모듈 사이가 얼마나 강하게 얽혀 있는가"를 나타낸다. 좋은 설계는 응집도는 높고 결합도는 낮은 모듈들로 이루어진다.

응집도 스펙트럼

응집도는 낮은 것부터 높은 것까지 일곱 단계로 나눠 볼 수 있다. 낮을수록 나쁜 설계, 높을수록 좋은 설계다.

응집도 의미 책마루 예시
우연적 (coincidental) 아무 관련 없는 기능을 편의상 한 모듈에 몰아넣음 "유틸 모듈"에 재고 계산, 로그 출력, 날짜 포맷팅을 뒤섞어 넣음
논리적 (logical) 비슷한 종류의 기능을 모아두었지만 실제 처리는 서로 다름 "입력 처리 모듈" 하나가 주문 입력, 재고 입력, 회원 입력을 매개변수로 구분해 처리
시간적 (temporal) 같은 시점에 실행된다는 이유만으로 묶임 "오픈 초기화 모듈"이 재고 캐시 로딩, 결제 모듈 연결, 알림 설정을 한꺼번에 처리
절차적 (procedural) 순서상 연달아 실행되지만 데이터를 주고받지는 않음 "주문 접수 후처리 모듈"이 알림 발송과 통계 갱신을 순서대로만 실행
대화적 (communicational) 같은 데이터를 두고 여러 기능이 함께 작업함 "주문 정보 모듈"이 주문 데이터로 배송지 검증과 재고 차감을 함께 수행
순차적 (sequential) 앞 기능의 출력이 다음 기능의 입력이 됨 "재고 차감 모듈"이 차감 결과를 그대로 "품절 판정 모듈"의 입력으로 전달
함수적 (functional) 오직 하나의 명확한 기능만 수행 "우편번호로 배달 가능 여부를 판정한다" 모듈 하나만 담당

함수적 응집도를 가진 모듈은 이름만 보고도 무슨 일을 하는지 알 수 있고, 그 기능 하나만 시험하면 되며, 수정할 때 다른 기능을 건드릴 걱정이 없다. 책마루 시스템을 설계할 때 "이 모듈의 이름을 한 문장으로 설명할 수 있는가"를 스스로 물어보는 것이 응집도를 점검하는 실용적인 방법이다. "그리고"라는 접속사 없이 설명되지 않는다면 — 예를 들어 "재고를 차감하고 알림을 보내는 모듈"이라면 — 응집도가 낮다는 신호다.

결합도 스펙트럼

결합도는 모듈 사이에 얼마나 많은 정보가, 어떤 방식으로 오가는지를 기준으로 낮은 것부터 높은 것까지 일곱 단계로 나뉜다. 이번에는 반대로 낮을수록 좋은 설계다.

결합도 의미 책마루 예시
무결합 (no direct coupling) 서로 직접 부르거나 데이터를 주고받지 않음 재고 모듈과 UI 색상 설정 모듈은 서로 존재를 몰라도 됨
데이터 (data) 단순한 값만 매개변수로 주고받음 주문 모듈이 상품 코드(문자열)만 넘겨 재고 모듈을 호출
스탬프 (stamp) 배열·구조체 같은 복잡한 자료구조 전체를 넘김 주문 모듈이 "주문 객체" 전체를 배송 모듈에 넘기지만 배송 모듈은 배송지 필드만 씀
제어 (control) 상대 모듈의 동작 방식을 지정하는 플래그를 넘김 재고 모듈 호출 시 isRestock 플래그로 "입고냐 출고냐"를 지시
외부 (external) 특정 하드웨어·통신 규격·OS 기능에 종속됨 특정 결제 단말기 모델의 비표준 통신 규격에 맞춰 짜인 모듈
공통 (common) 여러 모듈이 전역 변수를 공유함 여러 모듈이 전역 변수 currentStock을 직접 읽고 씀
내용 (content) 한 모듈이 다른 모듈 내부의 코드나 데이터를 직접 건드림 배송 모듈이 재고 모듈 내부 변수를 직접 참조하거나 그 중간으로 실행을 건너뜀

결합도가 높을수록 한 모듈의 변경이 다른 모듈로 퍼지는 **파문 효과(ripple effect)**가 커진다. 책마루 시스템에서 전역 변수 currentStock을 여러 모듈이 공유하고 있다면(공통 결합), 재고 계산 방식을 바꾸는 순간 그 변수를 읽는 모든 모듈을 다시 점검해야 한다. 반면 재고 모듈이 "재고 수량 조회 함수"라는 인터페이스만 노출하고 내부 저장 방식은 숨긴다면(데이터 결합), 저장 방식을 자유롭게 바꿔도 다른 모듈은 영향받지 않는다.

결합도를 낮추는 가장 손쉬운 습관: 모듈 사이에 전역 변수를 공유하지 않고, 필요한 값은 그때그때 매개변수로 전달하는 것이다. 이것만 지켜도 공통 결합과 내용 결합이라는 가장 위험한 두 단계는 대부분 피할 수 있다.

이해도와 적응도

응집도와 결합도 외에도 설계 품질을 가늠하는 척도가 두 가지 더 있다.

**이해도(understandability)**는 다른 모듈이나 문서를 참조하지 않고도 한 모듈의 동작을 이해할 수 있는 정도다. 응집도가 높고 결합도가 낮은 시스템일수록 이해도도 자연히 높아진다 — 모듈 하나만 읽으면 그 역할이 끝까지 파악되기 때문이다.

**적응도(adaptability)**는 환경 변화에 맞춰 소프트웨어를 얼마나 쉽게 고칠 수 있는가다. 책마루가 나중에 택배사를 추가하거나 결제 수단을 늘려야 할 때, 결합도가 낮게 설계되어 있다면 관련된 모듈 한두 개만 손보면 되지만 결합도가 높다면 시스템 곳곳을 함께 고쳐야 한다. 소프트웨어는 환경 변화를 피할 수 없는 존재이므로, 설계 단계에서부터 "이 부분은 앞으로 바뀔 가능성이 높다"고 예상되는 지점을 느슨하게 결합해 두는 것이 장기적으로 유지보수 비용을 크게 줄인다.

핵심 정리

다음 장(13장)에서는 이 설계 원칙을 실제로 어떻게 적용하는지 — 자료흐름도를 프로그램 구조로 바꾸는 자료 흐름 중심 설계 기법과, 책마루의 주문-재고 정보를 실제 테이블로 옮기는 데이터베이스 설계 과정을 다룬다.

← 이전유스케이스와 UML 다음 →자료 흐름 중심 설계와 데이터베이스 설계