소프트웨어 설계 (Software Design)
핵심 인사이트 (3줄 요약)
요구사항을 구현 가능한 구조로 변환하는 핵심 활동으로, 아키텍처 설계(전체 구조)와 상세 설계(모듈 내부)로 분류된다. 응집도는 높게, 결합도는 낮게, 모듈화·추상화·정보 은닉이 핵심 원칙이다. 기술사 시험에서는 SOLID 원칙, GoF 패턴, UML 표기법이 핵심이다.
Ⅰ. 개요 (필수: 200자 이상)
개념: 소프트웨어 설계(Software Design)는 요구사항 명세서를 기반으로 소프트웨어의 구조, 구성요소, 인터페이스, 특성을 정의하는 활동으로, "어떻게 만들 것인가"를 결정하는 단계다.
💡 비유: 소프트웨어 설계는 "건축 설계도면 작성" 같아요. "방 3개짜리 집 주세요"(요구사항)라는 말만으로는 집을 지을 수 없어요. 어떤 구조로, 어떤 자재로, 어떻게 지을지 설계도면이 필요하죠!
등장 배경 (필수: 3가지 이상 기술):
-
기존 문제점 - 무계획 개발: 요구사항만 있고 설계 없이 바로 코딩 시작 → 스파게티 코드, 유지보수 불가능
-
기술적 필요성 - 복잡성 관리: 현대 소프트웨어는 수백만 라인 코드. 체계적 구조 없이는 이해·변경 불가능. 추상화·모듈화 필수
-
시장/산업 요구 - 유지보수 비용: 소프트웨어 수명 주기 중 유지보수 비용이 60~80%. 좋은 설계로 유지보수 비용 절감
핵심 목적: 요구사항을 만족하는 유지보수 가능한 소프트웨어 구조 정의
Ⅱ. 구성 요소 및 핵심 원리 (필수: 가장 상세하게)
구성 요소 (필수: 최소 4개 이상):
| 구성 요소 | 역할/기능 | 특징 | 비유 |
|---|---|---|---|
| 아키텍처 설계 | 시스템 전체 구조 정의 | 컴포넌트, 인터페이스 | 건물 골조 |
| 상세 설계 | 모듈 내부 구조 정의 | 알고리즘, 자료구조 | 인테리어 |
| 데이터 설계 | DB 스키마, 데이터 모델 | ERD, 정규화 | 배관 설계 |
| 인터페이스 설계 | 모듈 간 통신 방식 | API, 프로토콜 | 출입구 |
| UI/UX 설계 | 사용자 인터페이스 | 와이어프레임 | 창문·문 |
구조 다이어그램 (필수: ASCII 아트):
┌─────────────────────────────────────────────────────────────────────────┐
│ 소프트웨어 설계 단계 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 아키텍처 설계 (Architectural Design) │ │
│ │ │ │
│ │ • 시스템 전체 구조 정의 │ │
│ │ • 주요 컴포넌트 식별 │ │
│ │ • 컴포넌트 간 인터페이스 정의 │ │
│ │ • 기술 스택 선정 │ │
│ │ • 비기능 요구사항(성능, 보안, 확장성) 반영 │ │
│ │ │ │
│ │ 산출물: 아키텍처 문서(SAD), 컴포넌트 다이어그램 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 상세 설계 (Detailed Design) │ │
│ │ │ │
│ │ • 모듈 내부 구조 정의 │ │
│ │ • 알고리즘, 자료구조 설계 │ │
│ │ • 클래스, 함수 설계 │ │
│ │ • DB 스키마 설계 │ │
│ │ • UI/UX 설계 │ │
│ │ │ │
│ │ 산출물: 설계 문서(SDD), 클래스 다이어그램, 시퀀스 다이어그램 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 응집도와 결합도 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [응집도 (Cohesion) - 높을수록 좋음] │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 기능적 > 순차적 > 교환적 > 절차적 > 시간적 > 논리적 > 우연적 │ │
│ │ (最强) (最弱) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 기능적 응집도: 모든 요소가 단일 기능 수행 (이상적) │
│ 우연적 응집도: 공통점 없는 요소들이 모임 (피해야 함) │
│ │
│ [결합도 (Coupling) - 낮을수록 좋음] │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 자료 < 스탬프 < 제어 < 외부 < 공통 < 내용 │ │
│ │ (最弱) (最强) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 자료 결합도: 단순 데이터만 주고받음 (이상적) │
│ 내용 결합도: 다른 모듈의 내부 데이터 직접 접근 (피해야 함) │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Good Design │ │
│ │ 높은 응집도 + 낮은 결합도 │ │
│ │ │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ A │──▶──▶│ B │──▶──▶│ C │ │ │
│ │ └─────┘ └─────┘ └─────┘ │ │
│ │ (자료 결합도: 데이터만 전달) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ SOLID 원칙 (객체지향 설계 5원칙) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ S - Single Responsibility (단일 책임 원칙) │
│ 클래스는 하나의 책임만 가져야 한다 │
│ 예: User 클래스는 사용자 정보만, 이메일 발송은 EmailService │
│ │
│ O - Open/Closed (개방-폐쇄 원칙) │
│ 확장에는 열려 있고, 수정에는 닫혀 있어야 한다 │
│ 예: 새로운 결제수단 추가 시 기존 코드 수정 없이 확장 │
│ │
│ L - Liskov Substitution (리스코프 치환 원칙) │
│ 하위 타입은 상위 타입을 대체할 수 있어야 한다 │
│ 예: Bird → Penguin 치환 시 fly() 오류 (펭귄은 못 날아!) │
│ │
│ I - Interface Segregation (인터페이스 분리 원칙) │
│ 인터페이스를 작게 분리하여 클라이언트마다 필요한 것만 구현 │
│ 예: Worker 인터페이스를 Workable, Eatable로 분리 │
│ │
│ D - Dependency Inversion (의존성 역전 원칙) │
│ 고수준 모듈이 저수준 모듈에 의존하지 말고 추상화에 의존 │
│ 예: OrderService → PaymentInterface ← CreditCardPayment │
│ │
└─────────────────────────────────────────────────────────────────────────┘
동작 원리 (필수: 단계별 상세 설명):
① 요구사항 분석 → ② 아키텍처 설계 → ③ 상세 설계 → ④ 설계 검토 → ⑤ 구현
- 1단계 (요구사항 분석): 기능적/비기능적 요구사항 파악, 제약사항 식별
- 2단계 (아키텍처 설계): 시스템 구조 정의, 컴포넌트 식별, 인터페이스 정의, 아키텍처 패턴 선택
- 3단계 (상세 설계): 모듈 내부 설계, 알고리즘/자료구조, 클래스 설계, DB 설계
- 4단계 (설계 검토): 설계 리뷰, 인스펙션, 프로토타입 검증
- 5단계 (구현): 코딩, 단위 테스트
핵심 알고리즘/공식:
[설계 품질 지표]
1. 응집도 측정
LCOM (Lack of Cohesion of Methods)
LCOM = |P| - |Q|
P: 서로 공통 속성을 사용하지 않는 메서드 쌍 수
Q: 서로 공통 속성을 사용하는 메서드 쌍 수
LCOM이 높을수록 응집도 낮음 (나쁨)
2. 결합도 측정
CBO (Coupling Between Objects)
CBO = 다른 클래스와의 결합 수
CBO가 낮을수록 좋음
3. 복잡도 측정
사이클로매틱 복잡도 (Cyclomatic Complexity)
V(G) = E - N + 2P
E: 엣지 수, N: 노드 수, P: 연결 컴포넌트 수
복잡도 기준:
1-10: 단순 (좋음)
11-20: 복잡 (주의)
21+: 매우 복잡 (리팩토링 권장)
[설계 원칙 체크리스트]
✓ 모든 요구사항이 설계에 반영되었는가?
✓ 모듈 간 결합도가 낮은가?
✓ 모듈 내 응집도가 높은가?
✓ SOLID 원칙을 준수했는가?
✓ 중복 코드가 없는가? (DRY)
✓ 필요한 것만 구현했는가? (YAGNI)
✓ 인터페이스가 명확한가?
✓ 예외 처리가 적절한가?
코드 예시 (필수: Python SOLID 원칙):
"""
소프트웨어 설계 원칙 예시
- SOLID 원칙 구현
- 응집도/결합도 예시
"""
from abc import ABC, abstractmethod
from typing import List, Protocol
from dataclasses import dataclass
# ============================================================
# 1. Single Responsibility Principle (SRP)
# ============================================================
# Good: 각 클래스가 단일 책임
class User:
"""사용자 정보만 관리"""
def __init__(self, name: str, email: str):
self.name = name
self.email = email
class UserRepository:
"""DB 저장 책임"""
def save(self, user: User):
print(f"[SRP] 사용자 저장: {user.name}")
class EmailService:
"""이메일 발송 책임"""
def send(self, to: str, message: str):
print(f"[SRP] 이메일 발송: {to}")
# ============================================================
# 2. Open/Closed Principle (OCP)
# ============================================================
class PaymentProcessor(Protocol):
"""결제 처리 인터페이스"""
def pay(self, amount: float) -> bool:
...
class CreditCardPayment(PaymentProcessor):
def pay(self, amount: float) -> bool:
print(f"[OCP] 신용카드 결제: {amount}원")
return True
class KakaoPayment(PaymentProcessor):
def pay(self, amount: float) -> bool:
print(f"[OCP] 카카오페이 결제: {amount}원")
return True
class OrderService:
"""기존 코드 수정 없이 새 결제수단 사용 가능"""
def __init__(self, payment: PaymentProcessor):
self.payment = payment
def checkout(self, amount: float):
return self.payment.pay(amount)
# ============================================================
# 3. Liskov Substitution Principle (LSP)
# ============================================================
class Bird(ABC):
@abstractmethod
def move(self):
pass
class FlyingBird(Bird):
def move(self):
return self.fly()
@abstractmethod
def fly(self):
pass
class Sparrow(FlyingBird):
def fly(self):
print("[LSP] 참새가 날아갑니다")
class Penguin(Bird):
def move(self):
return self.swim()
def swim(self):
print("[LSP] 펭귄이 헤엄칩니다")
# ============================================================
# 4. Interface Segregation Principle (ISP)
# ============================================================
class Workable(Protocol):
def work(self):
...
class Eatable(Protocol):
def eat(self):
...
class HumanWorker:
def work(self):
print("[ISP] 사람이 일합니다")
def eat(self):
print("[ISP] 사람이 밥을 먹습니다")
class RobotWorker:
"""로봇은 일만 함 (eat 불필요)"""
def work(self):
print("[ISP] 로봇이 일합니다")
# ============================================================
# 5. Dependency Inversion Principle (DIP)
# ============================================================
class Switchable(Protocol):
def turn_on(self):
...
def turn_off(self):
...
class Lamp:
def turn_on(self):
print("[DIP] 전등 켜짐")
def turn_off(self):
print("[DIP] 전등 꺼짐")
class Switch:
"""고수준 모듈이 추상화(Switchable)에 의존"""
def __init__(self, device: Switchable):
self.device = device
self.on = False
def toggle(self):
if self.on:
self.device.turn_off()
else:
self.device.turn_on()
self.on = not self.on
# ============================================================
# 6. 응집도/결합도 예시
# ============================================================
@dataclass
class OrderItem:
product: str
quantity: int
price: float
class Order:
"""높은 기능적 응집도: 주문 관련 기능만 수행"""
def __init__(self):
self.items: List[OrderItem] = []
def add_item(self, item: OrderItem):
self.items.append(item)
def calculate_total(self) -> float:
return sum(item.price * item.quantity for item in self.items)
class OrderPrinter:
"""단일 책임: 출력만 담당"""
def print_receipt(self, order: Order):
print(f"\n[응집도/결합도] 영수증")
for item in order.items:
print(f" {item.product} x {item.quantity}: {item.price}원")
print(f" 합계: {order.calculate_total()}원")
# ============================================================
# 사용 예시
# ============================================================
if __name__ == "__main__":
print("=" * 50)
print("SOLID 원칙 예시")
print("=" * 50)
# SRP
print("\n1. Single Responsibility Principle")
user = User("홍길동", "hong@example.com")
repo = UserRepository()
email = EmailService()
repo.save(user)
email.send(user.email, "회원가입 축하!")
# OCP
print("\n2. Open/Closed Principle")
order_service = OrderService(KakaoPayment())
order_service.checkout(50000)
# LSP
print("\n3. Liskov Substitution Principle")
birds: List[Bird] = [Sparrow(), Penguin()]
for bird in birds:
bird.move()
# DIP
print("\n4. Dependency Inversion Principle")
switch = Switch(Lamp())
switch.toggle()
switch.toggle()
# 응집도/결합도
print("\n5. 높은 응집도 + 낮은 결합도")
order = Order()
order.add_item(OrderItem("사과", 3, 1000))
order.add_item(OrderItem("바나나", 2, 1500))
printer = OrderPrinter()
printer.print_receipt(order)
Ⅲ. 기술 비교 분석 (필수: 2개 이상의 표)
장단점 분석 (필수: 최소 3개씩):
| 장점 | 단점 |
|---|---|
| 유지보수성: 체계적 구조로 변경 용이 | 시간 소요: 설계 단계에 시간 투자 필요 |
| 재사용성: 모듈화로 코드 재사용 | 오버엔지니어링: 과도한 추상화 위험 |
| 품질 향상: 설계 단계 결함 조기 발견 | 학습 곡선: 설계 기법·패턴 학습 필요 |
| 의사소통: UML 등으로 설계 공유 | 문서 동기화: 코드 변경 시 문서 갱신 |
설계 방법론 비교 (필수: 2개 대안):
| 비교 항목 | 구조적 설계 | 객체지향 설계 | 도메인 주도 설계 |
|---|---|---|---|
| 핵심 단위 | 함수/모듈 | 클래스/객체 | 도메인 모델 |
| 데이터 처리 | 데이터 중심 | 캡슐화 | 유비쿼터스 언어 |
| 접근 방식 | Top-Down | Bottom-Up | 도메인 중심 |
| 재사용 | 함수 라이브러리 | 상속/컴포지션 | 바운디드 컨텍스트 |
| 적합 환경 | 임베디드/시스템 | 엔터프라이즈 | 복잡한 비즈니스 |
★ 선택 기준: 하드웨어 제어 → 구조적, 일반 앱 → 객체지향, 복잡한 비즈니스 로직 → DDD
Ⅳ. 실무 적용 방안 (필수: 기술사 판단력 증명)
기술사적 판단 (필수: 3개 이상 시나리오):
| 적용 분야 | 구체적 적용 방법 | 기대 효과 (정량) |
|---|---|---|
| 마이크로서비스 | 도메인별 서비스 분리, API 설계 | 배포 독립성 100%, 장애 격리 |
| 레거시 현대화 | 점진적 리팩토링, Strangler Pattern | 리스크 70% 감소 |
| 임베디드 | 하드웨어 추상화 계층, 인터페이스 설계 | 하드웨어 교체 시 코드 변경 90% 감소 |
실제 도입 사례 (필수: 구체적 기업/서비스):
-
사례 1: 구글 - 마이크로서비스 아키텍처로 설계. 각 서비스 독립 배포, 하루 4,000회 배포 가능
-
사례 2: 우버 - DDD로 도메인별 서비스 분리. 드라이버, 라이더, 결제 등 독립적 개발
-
사례 3: 삼성전자 - 임베디드 HAL(Hardware Abstraction Layer) 설계. 칩셋 교체 시 앱 코드 변경 0%
도입 시 고려사항 (필수: 4가지 관점):
- 기술적: 아키텍처 패턴 선택, 기술 스택, 성능 요구사항, 확장성
- 운영적: 팀 구조, 개발 프로세스, 코드 리뷰 문화
- 보안적: 보안 설계(Defense in Depth), 데이터 암호화, 접근 제어
- 경제적: 설계 시간 투자 vs 유지보수 비용 절감
주의사항 / 흔한 실수 (필수: 최소 3개):
- 오버엔지니어링: 불필요한 추상화, "나중에 필요할지도" → YAGNI 위반
- 설계 문서만 작성: 실제 코드와 다른 문서 → 동기화 필수
- 저수준 설계 생략: 아키텍처만 하고 상세 설계 안 함 → 구현 단계 혼란
관련 개념 / 확장 학습 (필수: 최소 5개 이상 나열):
┌─────────────────────────────────────────────────────────────────┐
│ 소프트웨어 설계 핵심 연관 개념 맵 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [요구사항 공학] ←──→ [소프트웨어 설계] ←──→ [구현] │
│ ↓ ↓ ↓ │
│ [유스케이스] [아키텍처] [코딩] │
│ ↓ ↓ ↓ │
│ [SRS] [디자인 패턴] [리팩토링] │
│ │
└─────────────────────────────────────────────────────────────────┘
| 관련 개념 | 관계 | 설명 | 문서 링크 |
|---|---|---|---|
| 소프트웨어 아키텍처 | 상위 개념 | 전체 구조 설계 | [software_architecture](./software_architecture.md) |
| 디자인 패턴 | 설계 기법 | 검증된 설계 템플릿 | [design_pattern](./design_pattern.md) |
| UML | 표현 도구 | 설계 시각화 | [uml](./uml.md) |
| 요구사항 공학 | 선행 활동 | 요구사항 도출 | [requirements_engineering](../methodology/requirements_engineering.md) |
| 소프트웨어 테스트 | 후속 활동 | 설계 검증 | [software_testing](../testing/software_testing.md) |
Ⅴ. 기대 효과 및 결론 (필수: 미래 전망 포함)
정량적 기대 효과 (필수):
| 효과 영역 | 구체적 내용 | 정량적 목표 |
|---|---|---|
| 유지보수성 | 체계적 구조로 변경 용이 | 변경 비용 50% 절감 |
| 재사용성 | 모듈화로 코드 재사용 | 개발 시간 30% 단축 |
| 품질 | 설계 단계 결함 조기 발견 | 결함 밀도 40% 감소 |
| 의사소통 | UML 등으로 설계 공유 | 오해로 인한 이슈 60% 감소 |
미래 전망 (필수: 3가지 관점):
-
기술 발전 방향: AI 기반 설계 자동화, 모델 기반 개발(MDD), 로우코드 플랫폼
-
시장 트렌드: 마이크로서비스·클라우드 네이티브 설계, 이벤트 드리븐 아키텍처
-
후속 기술: AI Copilot과 결합한 설계 지원, 자동 리팩토링
결론: 소프트웨어 설계는 요구사항을 구현으로 변환하는 핵심 다리다. 좋은 설계는 "높은 응집도 + 낮은 결합도"를 달성하여 유지보수 가능한 코드를 만든다. 과도한 설계(Over-engineering)도 피해야 한다.
※ 참고 표준: IEEE 1016(SDD), ISO/IEC/IEEE 42010(아키텍처), UML 2.5, SOLID(Martin)
어린이를 위한 종합 설명
소프트웨어 설계는 마치 "레고 조립 설명서 만들기" 같아요!
레고로 멋진 성을 지으려면 어떻게 해야 할까요?
1. 그냥 만들기 (설계 없음): "대충 이거랑 이거 붙여볼까?" → 결과: 이상한 덩어리 😅
2. 설계도면 그리기: "탑은 여기, 문은 여기, 창문은 여기!" → 결과: 멋진 성 🏰
설계의 핵심 3가지:
① 모듈화 (블록 나누기)
- 탑 블록, 문 블록, 지붕 블록
- 각각 따로 만들어서 나중에 조립!
② 높은 응집도 (가족 같이)
- 탑 블록에는 탑 관련 부품만
- "엉뚱한 거 섞이지 마!"
③ 낮은 결합도 (이웃처럼)
- 탑과 문은 딱 맞는 곳에만 연결
- "너무 꽉 붙어 있으면 떼기 힘들어!"
SOLID 원칙:
- S: 한 가지 일만 해요 (학생은 공부만!)
- O: 새 기능은 추가로 (추가만 하고 수정은 NO)
- L: 교체해도 잘 돼요 (부모 대신 자식이 일해도 OK)
- I: 필요한 것만 (다 할 필요 없어!)
- D: 약속만 지키면 돼요 (인터페이스만 맞으면 OK)
이게 바로 소프트웨어 설계예요! 🏗️