- Add MCP servers for Redmine, Jenkins, Gitea, Obsidian - Add setup_mcp.sh to generate .mcp.json from .env (no secrets in VCS) - Add .env.example with required variable names for team onboarding - Add .gitignore to exclude secrets (.env, .mcp.json, settings.local.json) - Fix obsidian_mcp_server.py stdio_server async context manager usage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.9 KiB
6.9 KiB
소프트웨어 디자인 패턴 참조 가이드
디자인 패턴은 반복적인 문제를 해결하는 검증된 방법론입니다. 코드 작성 전, 아래 빠른 선택 가이드로 패턴을 먼저 고르세요.
빠른 선택 가이드
| 상황 | 적합한 패턴 | 분류 |
|---|---|---|
| 인스턴스를 하나만 유지해야 할 때 | 싱글톤 | 생성 |
| 객체 생성이 복잡하거나 조건부일 때 | 팩토리 | 생성 |
| 매개변수가 많거나 선택적인 객체 생성 | 빌더 | 생성 |
| 기존 객체를 복제해서 확장할 때 | 프로토타입 | 생성 |
| 복잡한 API를 단순하게 감싸야 할 때 | 퍼사드 | 구조 |
| 호환되지 않는 인터페이스를 연결할 때 | 어댑터 | 구조 |
| 접근 제어·캐싱·지연 로딩이 필요할 때 | 프록시 | 구조 |
| 상태 변화를 여러 곳에 알려야 할 때 | 옵저버 | 행위 |
| 컬렉션을 표준 방식으로 순회할 때 | 이터레이터 | 행위 |
| 알고리즘을 런타임에 교체해야 할 때 | 전략 | 행위 |
| 복잡한 조건문(if/switch)을 제거할 때 | 스테이트 | 행위 |
| 객체 간 직접 통신을 줄이고 싶을 때 | 메디에이터 | 행위 |
1. 생성 패턴 (Creational Patterns)
객체를 어떻게 만들지를 다루는 패턴
싱글톤 (Singleton)
- 목적: 클래스 인스턴스를 전역에서 단 하나만 보장
- 사용 예: 앱 설정 객체, 전역 로거, 캐시 관리자
- 특징: 어디서 접근해도 동일한 인스턴스 반환
팩토리 (Factory)
- 목적: 객체 생성 로직을 캡슐화, 클라이언트에서
new를 직접 사용하지 않음 - 사용 예: 플랫폼별 UI 컴포넌트 생성, 버거 주문 시스템
- 특징: 생성할 구체 클래스를 호출자로부터 분리
빌더 (Builder)
- 목적: 복잡한 객체를 단계별로 조립
- 사용 예: SQL 쿼리 빌더, HTTP 요청 객체, 설정이 많은 DTO
- 특징: 메서드 체이닝 →
build()로 최종 객체 완성
프로토타입 (Prototype)
- 목적: 기존 객체를 복제(clone)하여 새 객체 생성
- 사용 예: JavaScript 프로토타입 상속, 깊은 복사가 필요한 객체
- 특징: 클래스 상속 없이 기능 확장 가능
2. 구조 패턴 (Structural Patterns)
객체와 클래스를 어떻게 조합할지를 다루는 패턴
퍼사드 (Facade)
- 목적: 복잡한 내부 시스템을 단순한 인터페이스로 노출
- 사용 예: 외부 API 래퍼, 복잡한 라이브러리의 진입점
- 특징: 내부 구현 세부사항을 숨기고 고수준 API 제공
어댑터 (Adapter)
- 목적: 호환되지 않는 두 인터페이스를 연결
- 사용 예: 레거시 코드 통합, 서드파티 라이브러리 래핑
- 특징: 기존 클래스를 수정하지 않고 새 인터페이스에 맞춤
프록시 (Proxy)
- 목적: 실제 객체 앞에 대리 객체를 두어 접근 제어·부가 기능 추가
- 사용 예: Vue.js 반응형 시스템, 지연 로딩(Lazy Loading), API 캐싱
- 특징: 실제 객체 교체 없이 동작 가로채기 가능
3. 행위 패턴 (Behavioral Patterns)
객체 간 통신과 책임 분배를 다루는 패턴
옵저버 (Observer / Pub-Sub)
- 목적: 한 객체의 상태 변화를 구독자들에게 자동으로 알림
- 사용 예: Firebase 실시간 업데이트, 유튜브 구독 알림, 이벤트 버스
- 특징: 1:N 관계, 발행자와 구독자의 느슨한 결합
이터레이터 (Iterator)
- 목적: 컬렉션 내부 구조 노출 없이 요소를 순회
- 사용 예: 배열·연결 리스트·트리의 통일된 순회 인터페이스
- 특징: 자료구조 구현과 순회 로직을 분리
전략 (Strategy)
- 목적: 알고리즘을 외부에서 주입받아 런타임에 교체 가능하게 함
- 사용 예: 정렬 알고리즘 선택, 결제 수단 선택, 압축 방식 선택
- 특징: Open-Closed 원칙 준수, 기존 코드 수정 없이 동작 확장
메디에이터 (Mediator)
- 목적: 객체들이 서로 직접 통신하지 않고 중재자를 통해 소통
- 사용 예: Express.js 미들웨어 체인, 채팅방 서버, 항공 관제 시스템
- 특징: 객체 간 결합도 감소, 관계가 M:N → 1:N으로 단순화
스테이트 (State)
- 목적: 객체 내부 상태에 따라 동작이 완전히 달라지게 분리
- 사용 예: 게임 캐릭터 상태 머신, 주문 처리 흐름, UI 컴포넌트 모드
- 특징: 복잡한
if/switch조건문을 상태 클래스로 대체
모듈 설계 원칙: 단순하고 테스트하기 쉽게
좋은 코드는 읽기 쉽고, 좋은 모듈은 테스트하기 쉽습니다. 복잡한 패턴보다 작고 명확한 단위로 나누는 것이 먼저입니다.
단순하게 만들기
- 하나의 모듈 = 하나의 책임: 파일 하나가 하는 일을 한 문장으로 설명할 수 없다면 쪼개야 함
- 의존성은 주입받기: 모듈 안에서 직접
new하거나 전역 참조 대신, 필요한 객체는 인자로 받기 - 외부와의 접점 최소화: 공개 함수(export)는 꼭 필요한 것만, 나머지는 내부에 숨기기
- 설정과 로직 분리: 환경변수·상수는 별도 파일로, 핵심 로직 안에 하드코딩 금지
테스트하기 쉽게 만들기
- 순수 함수 우선: 같은 입력 → 항상 같은 출력, 부작용 없는 함수는 테스트가 단 한 줄
- I/O는 가장자리로: DB·파일·HTTP 호출은 모듈 경계 바깥으로 밀어내고, 핵심 로직은 순수하게 유지
- 인터페이스 기반 설계: 구체 구현 대신 인터페이스(프로토콜)에 의존하면 테스트 시 Mock으로 쉽게 교체 가능
- 작은 단위로 분리: 함수 하나가 20줄을 넘기 시작하면 쪼갤 신호
체크리스트
테스트 작성 전, 아래 질문에 "예" 가 되도록 설계하세요.
| 질문 | 목표 |
|---|---|
| 이 함수를 Obsidian·Jenkins 없이 단독 실행할 수 있는가? | 외부 의존성 격리 |
| Mock 없이 입력값만 바꿔 결과를 검증할 수 있는가? | 순수 함수화 |
| 이 모듈의 역할을 한 문장으로 말할 수 있는가? | 단일 책임 |
| 추가 기능이 생겼을 때 기존 코드를 수정하지 않아도 되는가? | 확장 가능성 |
주의사항
패턴은 도구이지 목적이 아닙니다. 단순한 문제에 복잡한 패턴을 적용하면 오히려 코드 가독성과 유지보수성이 떨어집니다.
- 과도한 패턴 남용 금지: 문제가 패턴을 정당화할 만큼 복잡한지 먼저 판단
- 성능 고려: 프록시·옵저버 등 일부 패턴은 런타임 오버헤드 발생 가능
- 팀 이해도 우선: 팀원 모두가 이해할 수 있는 수준의 패턴을 선택