From 31a0726c2597ef36e25a4e810fdb8db073f3458e Mon Sep 17 00:00:00 2001 From: Antigravity Agent Date: Sun, 3 May 2026 20:03:27 +0900 Subject: [PATCH] Wikification: Migrate 128 software engineering topics from out_wiki with P-Reinforce v3.0 standards --- .../Architecture/3의 법칙 (Rule of Three).md | 20 +++++ ...Assisted Refactoring (AI 기반 리팩토링).md | 64 ++++++++++++++++ .../Approval Testing (승인 테스트).md | 17 +++++ ...- Snapshot Testing (승인-스냅샷 테스트).md | 17 +++++ .../Automated Refactoring Tools.md | 20 +++++ .../Automated Testing (자동화된 테스트).md | 76 +++++++++++++++++++ .../Automated Testing Frameworks.md | 20 +++++ .../Architecture/Breaking Dependencies.md | 18 +++++ 10_Wiki/Topics/Architecture/CRC Cards.md | 16 ++++ .../Characterization Tests (특성화 테스트).md | 58 ++++++++++++++ .../Architecture/Clean Code (클린 코드).md | 29 +++++++ .../Code Rot (코드 부패 - 소프트웨어 부패).md | 18 +++++ .../Architecture/Code Smell (코드 스멜).md | 70 +++++++++++++++++ .../Architecture/Cyclomatic Complexity.md | 17 +++++ .../DRY (Don't Repeat Yourself).md | 17 +++++ .../Architecture/Dependencies (의존성).md | 57 ++++++++++++++ .../Dependency Injection (의존성 주입).md | 15 ++++ .../Design Patterns (디자인 패턴).md | 76 +++++++++++++++++++ .../Duplicated Code (중복 코드).md | 19 +++++ .../Enabling Point (활성화 지점).md | 63 +++++++++++++++ .../Extract Class (클래스 추출하기).md | 38 ++++++++++ .../Extract Method (함수 추출하기).md | 29 +++++++ ...ntroduce Null Object (널 객체 도입하기).md | 27 +++++++ .../Architecture/Legacy Code (레거시 코드).md | 72 ++++++++++++++++++ .../Architecture/Link Seam (링크 접점).md | 19 +++++ .../Architecture/Mock Objects (가짜 객체).md | 70 +++++++++++++++++ .../Mocking and Stubbing (테스트 대역).md | 18 +++++ 10_Wiki/Topics/Architecture/Mockito.md | 15 ++++ .../Null Object Pattern (널 객체 패턴).md | 64 ++++++++++++++++ .../Architecture/Object Seam (객체 접점).md | 23 ++++++ .../Open-Closed Principle (개방-폐쇄 원칙).md | 17 +++++ .../Over-engineering (오버엔지니어링).md | 18 +++++ .../Architecture/Performance Optimization.md | 38 ++++++++++ .../Architecture/Polymorphism (다형성).md | 74 ++++++++++++++++++ .../Preprocessing Seam (전처리 접점).md | 17 +++++ ...serve Whole Object (객체 통째로 넘기기).md | 21 +++++ .../Primitive Obsession (기본 타입 집착).md | 23 ++++++ .../Architecture/Program Dependence Graph.md | 15 ++++ 10_Wiki/Topics/Architecture/Pull Up Method.md | 21 +++++ .../Architecture/Red-Green Refactoring.md | 61 +++++++++++++++ .../Architecture/Refactoring (리팩토링).md | 75 ++++++++++++++++++ .../Refactoring Techniques (리팩토링 기법).md | 72 ++++++++++++++++++ ...lymorphism (조건식을 다형성으로 바꾸기).md | 25 ++++++ ...h Exception (에러 코드를 예외로 바꾸기).md | 23 ++++++ ...sses (분류 부호를 하위 클래스로 바꾸기).md | 30 ++++++++ .../Architecture/Rule of Three (3의 법칙).md | 58 ++++++++++++++ ...Scratch Refactoring (스크래치 리팩토링).md | 53 +++++++++++++ 10_Wiki/Topics/Architecture/Seam (접점).md | 20 +++++ 10_Wiki/Topics/Architecture/Seams (이음새).md | 20 +++++ .../Singleton Pattern (싱글톤 패턴).md | 22 ++++++ .../Special Case Pattern (특수 사례 패턴).md | 19 +++++ ... Wrap Techniques (스프라우트 & 랩 기법).md | 26 +++++++ .../Sprout Method (스프라우트 메서드).md | 59 ++++++++++++++ ...State-Strategy Pattern (상태-전략 패턴).md | 60 +++++++++++++++ ...Static Code Analysis (정적 코드 분석기).md | 57 ++++++++++++++ .../Architecture/Strangler Fig Pattern.md | 18 +++++ .../Strangler Pattern (교살자 패턴).md | 19 +++++ .../Switch Statements (Switch 문).md | 19 +++++ .../Architecture/TDD (테스트 주도 개발).md | 20 +++++ .../Technical Debt (기술 부채).md | 68 +++++++++++++++++ ...ate Method Pattern (템플릿 메서드 패턴).md | 62 +++++++++++++++ .../Architecture/Test Automation Pyramid.md | 57 ++++++++++++++ .../Test Doubles (테스트 대역).md | 28 +++++++ .../Test Pyramid (테스트 피라미드).md | 19 +++++ ...t-Driven Development (테스트 주도 개발).md | 16 ++++ 10_Wiki/Topics/Architecture/The Two Hats.md | 27 +++++++ .../Architecture/Two Hats (두 개의 모자).md | 21 +++++ .../Architecture/Unit Test (단위 테스트).md | 31 ++++++++ 10_Wiki/Topics/Architecture/Unit Testing.md | 20 +++++ .../Architecture/Unit Tests (단위 테스트).md | 25 ++++++ .../Architecture/Wrap Method (랩 메서드).md | 62 +++++++++++++++ .../YAGNI (You Aren't Gonna Need It).md | 16 ++++ .../기술 부채 (Technical Debt).md | 33 ++++++++ .../Architecture/다형성 (Polymorphism).md | 37 +++++++++ .../Architecture/단위 테스트 (Unit Tests).md | 31 ++++++++ .../Architecture/레거시 코드 (Legacy Code).md | 32 ++++++++ 10_Wiki/Topics/Architecture/리팩토링 원칙.md | 76 +++++++++++++++++++ .../보이스카우트 규칙 (Boy Scout Rule).md | 18 +++++ .../추출 및 인라인 (Extract & Inline).md | 22 ++++++ .../Architecture/클린 코드 (Clean Code).md | 30 ++++++++ .../Architecture/테스트 주도 개발 (TDD).md | 17 +++++ .../AI Productivity Paradox.md | 17 +++++ .../Agile Methodology (애자일 방법론).md | 58 ++++++++++++++ ...re Development (애자일 소프트웨어 개발).md | 56 ++++++++++++++ .../Bottleneck Migration.md | 15 ++++ .../Built-in Quality.md | 15 ++++ ...ploratory Refactoring (탐색적 리팩토링).md | 21 +++++ .../Extreme Programming (XP).md | 60 +++++++++++++++ .../Scaled Agile Framework (SAFe).md | 27 +++++++ .../Continuous Delivery (지속적 제공, CD).md | 17 +++++ ...gration & Continuous Deployment (CI-CD).md | 18 +++++ ...ontinuous Integration (지속적 통합, CI).md | 55 ++++++++++++++ 10_Wiki/Topics/DevOps_and_Security/DevOps.md | 15 ++++ ... 테스트 (Consumer-Driven Contract Tests, CDC).md | 27 +++++++ .../지속적 통합 (CI) 및 지속적 배포 (CD).md | 25 ++++++ 95 files changed, 3256 insertions(+) create mode 100644 10_Wiki/Topics/Architecture/3의 법칙 (Rule of Three).md create mode 100644 10_Wiki/Topics/Architecture/AI-Assisted Refactoring (AI 기반 리팩토링).md create mode 100644 10_Wiki/Topics/Architecture/Approval Testing (승인 테스트).md create mode 100644 10_Wiki/Topics/Architecture/Approval Testing - Snapshot Testing (승인-스냅샷 테스트).md create mode 100644 10_Wiki/Topics/Architecture/Automated Refactoring Tools.md create mode 100644 10_Wiki/Topics/Architecture/Automated Testing (자동화된 테스트).md create mode 100644 10_Wiki/Topics/Architecture/Automated Testing Frameworks.md create mode 100644 10_Wiki/Topics/Architecture/Breaking Dependencies.md create mode 100644 10_Wiki/Topics/Architecture/CRC Cards.md create mode 100644 10_Wiki/Topics/Architecture/Characterization Tests (특성화 테스트).md create mode 100644 10_Wiki/Topics/Architecture/Clean Code (클린 코드).md create mode 100644 10_Wiki/Topics/Architecture/Code Rot (코드 부패 - 소프트웨어 부패).md create mode 100644 10_Wiki/Topics/Architecture/Code Smell (코드 스멜).md create mode 100644 10_Wiki/Topics/Architecture/Cyclomatic Complexity.md create mode 100644 10_Wiki/Topics/Architecture/DRY (Don't Repeat Yourself).md create mode 100644 10_Wiki/Topics/Architecture/Dependencies (의존성).md create mode 100644 10_Wiki/Topics/Architecture/Dependency Injection (의존성 주입).md create mode 100644 10_Wiki/Topics/Architecture/Design Patterns (디자인 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Duplicated Code (중복 코드).md create mode 100644 10_Wiki/Topics/Architecture/Enabling Point (활성화 지점).md create mode 100644 10_Wiki/Topics/Architecture/Extract Class (클래스 추출하기).md create mode 100644 10_Wiki/Topics/Architecture/Extract Method (함수 추출하기).md create mode 100644 10_Wiki/Topics/Architecture/Introduce Null Object (널 객체 도입하기).md create mode 100644 10_Wiki/Topics/Architecture/Legacy Code (레거시 코드).md create mode 100644 10_Wiki/Topics/Architecture/Link Seam (링크 접점).md create mode 100644 10_Wiki/Topics/Architecture/Mock Objects (가짜 객체).md create mode 100644 10_Wiki/Topics/Architecture/Mocking and Stubbing (테스트 대역).md create mode 100644 10_Wiki/Topics/Architecture/Mockito.md create mode 100644 10_Wiki/Topics/Architecture/Null Object Pattern (널 객체 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Object Seam (객체 접점).md create mode 100644 10_Wiki/Topics/Architecture/Open-Closed Principle (개방-폐쇄 원칙).md create mode 100644 10_Wiki/Topics/Architecture/Over-engineering (오버엔지니어링).md create mode 100644 10_Wiki/Topics/Architecture/Performance Optimization.md create mode 100644 10_Wiki/Topics/Architecture/Polymorphism (다형성).md create mode 100644 10_Wiki/Topics/Architecture/Preprocessing Seam (전처리 접점).md create mode 100644 10_Wiki/Topics/Architecture/Preserve Whole Object (객체 통째로 넘기기).md create mode 100644 10_Wiki/Topics/Architecture/Primitive Obsession (기본 타입 집착).md create mode 100644 10_Wiki/Topics/Architecture/Program Dependence Graph.md create mode 100644 10_Wiki/Topics/Architecture/Pull Up Method.md create mode 100644 10_Wiki/Topics/Architecture/Red-Green Refactoring.md create mode 100644 10_Wiki/Topics/Architecture/Refactoring (리팩토링).md create mode 100644 10_Wiki/Topics/Architecture/Refactoring Techniques (리팩토링 기법).md create mode 100644 10_Wiki/Topics/Architecture/Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기).md create mode 100644 10_Wiki/Topics/Architecture/Replace Error Code with Exception (에러 코드를 예외로 바꾸기).md create mode 100644 10_Wiki/Topics/Architecture/Replace Type Code with Subclasses (분류 부호를 하위 클래스로 바꾸기).md create mode 100644 10_Wiki/Topics/Architecture/Rule of Three (3의 법칙).md create mode 100644 10_Wiki/Topics/Architecture/Scratch Refactoring (스크래치 리팩토링).md create mode 100644 10_Wiki/Topics/Architecture/Seam (접점).md create mode 100644 10_Wiki/Topics/Architecture/Seams (이음새).md create mode 100644 10_Wiki/Topics/Architecture/Singleton Pattern (싱글톤 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Special Case Pattern (특수 사례 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Sprout & Wrap Techniques (스프라우트 & 랩 기법).md create mode 100644 10_Wiki/Topics/Architecture/Sprout Method (스프라우트 메서드).md create mode 100644 10_Wiki/Topics/Architecture/State-Strategy Pattern (상태-전략 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Static Code Analysis (정적 코드 분석기).md create mode 100644 10_Wiki/Topics/Architecture/Strangler Fig Pattern.md create mode 100644 10_Wiki/Topics/Architecture/Strangler Pattern (교살자 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Switch Statements (Switch 문).md create mode 100644 10_Wiki/Topics/Architecture/TDD (테스트 주도 개발).md create mode 100644 10_Wiki/Topics/Architecture/Technical Debt (기술 부채).md create mode 100644 10_Wiki/Topics/Architecture/Template Method Pattern (템플릿 메서드 패턴).md create mode 100644 10_Wiki/Topics/Architecture/Test Automation Pyramid.md create mode 100644 10_Wiki/Topics/Architecture/Test Doubles (테스트 대역).md create mode 100644 10_Wiki/Topics/Architecture/Test Pyramid (테스트 피라미드).md create mode 100644 10_Wiki/Topics/Architecture/Test-Driven Development (테스트 주도 개발).md create mode 100644 10_Wiki/Topics/Architecture/The Two Hats.md create mode 100644 10_Wiki/Topics/Architecture/Two Hats (두 개의 모자).md create mode 100644 10_Wiki/Topics/Architecture/Unit Test (단위 테스트).md create mode 100644 10_Wiki/Topics/Architecture/Unit Testing.md create mode 100644 10_Wiki/Topics/Architecture/Unit Tests (단위 테스트).md create mode 100644 10_Wiki/Topics/Architecture/Wrap Method (랩 메서드).md create mode 100644 10_Wiki/Topics/Architecture/YAGNI (You Aren't Gonna Need It).md create mode 100644 10_Wiki/Topics/Architecture/기술 부채 (Technical Debt).md create mode 100644 10_Wiki/Topics/Architecture/다형성 (Polymorphism).md create mode 100644 10_Wiki/Topics/Architecture/단위 테스트 (Unit Tests).md create mode 100644 10_Wiki/Topics/Architecture/레거시 코드 (Legacy Code).md create mode 100644 10_Wiki/Topics/Architecture/리팩토링 원칙.md create mode 100644 10_Wiki/Topics/Architecture/보이스카우트 규칙 (Boy Scout Rule).md create mode 100644 10_Wiki/Topics/Architecture/추출 및 인라인 (Extract & Inline).md create mode 100644 10_Wiki/Topics/Architecture/클린 코드 (Clean Code).md create mode 100644 10_Wiki/Topics/Architecture/테스트 주도 개발 (TDD).md create mode 100644 10_Wiki/Topics/Business_and_Management/AI Productivity Paradox.md create mode 100644 10_Wiki/Topics/Business_and_Management/Agile Methodology (애자일 방법론).md create mode 100644 10_Wiki/Topics/Business_and_Management/Agile Software Development (애자일 소프트웨어 개발).md create mode 100644 10_Wiki/Topics/Business_and_Management/Bottleneck Migration.md create mode 100644 10_Wiki/Topics/Business_and_Management/Built-in Quality.md create mode 100644 10_Wiki/Topics/Business_and_Management/Exploratory Refactoring (탐색적 리팩토링).md create mode 100644 10_Wiki/Topics/Business_and_Management/Extreme Programming (XP).md create mode 100644 10_Wiki/Topics/Business_and_Management/Scaled Agile Framework (SAFe).md create mode 100644 10_Wiki/Topics/DevOps_and_Security/Continuous Delivery (지속적 제공, CD).md create mode 100644 10_Wiki/Topics/DevOps_and_Security/Continuous Integration & Continuous Deployment (CI-CD).md create mode 100644 10_Wiki/Topics/DevOps_and_Security/Continuous Integration (지속적 통합, CI).md create mode 100644 10_Wiki/Topics/DevOps_and_Security/DevOps.md create mode 100644 10_Wiki/Topics/DevOps_and_Security/소비자 주도 계약 테스트 (Consumer-Driven Contract Tests, CDC).md create mode 100644 10_Wiki/Topics/DevOps_and_Security/지속적 통합 (CI) 및 지속적 배포 (CD).md diff --git a/10_Wiki/Topics/Architecture/3의 법칙 (Rule of Three).md b/10_Wiki/Topics/Architecture/3의 법칙 (Rule of Three).md new file mode 100644 index 00000000..a7f672a7 --- /dev/null +++ b/10_Wiki/Topics/Architecture/3의 법칙 (Rule of Three).md @@ -0,0 +1,20 @@ +# [[3의 법칙 (Rule of Three)]] + +## 📌 Brief Summary +3의 법칙(Rule of Three)은 돈 로버츠(Don Roberts)가 제안하고 마틴 파울러(Martin Fowler)가 대중화한 소프트웨어 리팩토링의 핵심 경험 법칙(Rule of Thumb)이다 [1, 2]. 이 법칙은 유사한 형태의 코드가 세 번 반복해서 사용될 때 비로소 중복을 피하기 위해 리팩토링을 수행해야 한다고 명시한다 [1, 3]. 이는 무분별한 리팩토링으로 인한 오버 엔지니어링과 기술 부채 사이의 균형을 맞추고, 실용적인 설계 조정을 권장하는 가이드라인이다 [3]. + +## 📖 Core Content +* **3단계 진행 방식 (Three strikes and you refactor)**: + 1. 첫 번째로 무언가를 구현할 때는 단순히 기능을 완성하는 데 집중하여 일단 진행한다 [4-6]. + 2. 두 번째로 비슷한 작업을 수행할 때는 코드 중복으로 인해 다소 꺼림칙하거나 불편함을 느끼더라도, 같은 방식으로 중복을 허용하여 코드를 작성한다 [2, 4, 6]. + 3. 세 번째로 동일한 형태의 작업이 반복되는 시점이 오면, 그때 비로소 리팩토링을 시작하여 중복된 코드를 새로운 프로시저나 클래스로 추출 및 공통화한다 [1, 2, 4, 5]. +* **패턴 발견과 정확한 추상화 유도**: 단 두 번의 사례만으로는 코드의 공통점을 명확히 찾기 어려울 수 있다. 하지만 중복이 세 번 이상 발생할 경우, 공통점과 차이점의 패턴을 훨씬 쉽게 파악할 수 있어 더 정확하고 올바른 추상화 수준을 결정하는 데 도움이 된다 [2, 7, 8]. +* **경제성 관점**: 코드의 중복은 코드를 유지보수하기 어렵게 만들지만, 3의 법칙은 세 개의 복사본이 존재하게 될 때 발생하는 유지보수 비용이 리팩토링을 수행하는 비용 및 잠재적인 나쁜 설계의 위험성을 확실히 초과하게 된다는 것을 내포하고 있다 [1, 9]. + +## ⚖️ Trade-offs & Caveats +* **성급한 추상화(Premature Refactoring)의 위험성**: 세 번의 중복이 나타나기 전에 너무 일찍 리팩토링을 시도하면 잘못된 추상화를 선택할 위험이 커진다 [2, 8]. 잘못된 추상화 모델이 시스템에 고착되면, 새로운 요구사항(유스케이스)을 처리하기 위해 부자연스러운 매개변수나 if 문 등을 억지로 추가해야 하므로 코드 유연성이 저하된다 [2, 10]. 결론적으로 잘못된 추상화보다 차라리 코드의 중복을 남겨두는 것이 유지보수 비용 측면에서 훨씬 저렴하다 [11]. +* **도그마화(Dogmatic)에 대한 경계**: 3의 법칙은 반드시 100% 지켜야 하는 엄격한 교리가 아니라 지침으로 활용해야 한다 [12, 13]. 세 번의 중복이 발견되었더라도 올바른 추상화 방법을 찾기 어렵거나 새로운 개념에 명확한 이름을 붙일 수 없다면, 억지로 추상화(over-abstraction)를 강요해서는 안 된다 [12]. 이런 경우에는 상황에 대한 더 많은 컨텍스트를 얻고 추가적인 중복 사례가 나타날 때까지 리팩토링을 유보하고 기다리는 것이 더 안전하다 [10]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/AI-Assisted Refactoring (AI 기반 리팩토링).md b/10_Wiki/Topics/Architecture/AI-Assisted Refactoring (AI 기반 리팩토링).md new file mode 100644 index 00000000..2c9764de --- /dev/null +++ b/10_Wiki/Topics/Architecture/AI-Assisted Refactoring (AI 기반 리팩토링).md @@ -0,0 +1,64 @@ +# [[AI-Assisted Refactoring (AI 기반 리팩토링)]] + +## 📌 Brief Summary +AI 기반 리팩토링은 대형 언어 모델(LLM)과 같은 인공지능 도구를 활용하여 소프트웨어의 외부 동작을 변경하지 않으면서 내부 코드 구조를 분석하고 개선하는 과정을 의미한다 [1, 2]. 이 접근법은 보일러플레이트 코드 작성이나 자동화된 단위 테스트 생성을 통해 리팩토링의 생산성을 크게 높일 수 있지만, 기존 시스템의 암묵적 지식을 파악해야 하는 복잡한 아키텍처 작업에서는 숙련된 개발자의 작업 속도를 오히려 늦출 위험도 존재한다 [3-5]. 따라서 AI 도구는 자동화된 테스트 및 인간 개발자의 철저한 검토와 결합되어 기술 부채를 통제하는 보조 수단으로 전략적으로 활용되어야 한다 [6-8]. + +## 📖 Core Content +* **분석 및 대안 제시 (Analysis & Recommendations):** AI 모델은 방대한 코드베이스를 분석하여 코드 스멜(Code Smell)을 실시간으로 탐지하고, 프로젝트의 코딩 규칙에 맞는 리팩토링 대안을 제안한다 [1, 2, 9]. 다양한 설계 대안을 비교하거나, 레거시 코드를 더 모듈화되고 재사용 가능한 서비스로 일괄 변환(Batch transformation)하는 데 유용하다 [2, 9]. +* **계층적 다중 에이전트 활용 (Hierarchical Multi-Agent Approach):** 전체 아키텍처와 교차 모듈 간의 의존성을 분석해 리팩토링 계획을 수립하는 플래너 모델(예: Gemini 2.5 Pro)과, 파일 단위의 국지적인 리팩토링과 테스트 생성을 수행하는 실행 모델(예: Cursor)을 분리하여 리팩토링을 수행하는 체계가 연구 및 적용되고 있다 [10, 11]. +* **작업 유형별 효율성 차이 (Task-Specific Efficiency):** 반복적인 보일러플레이트 코드 작성, 언어 변환, 문서화 및 단위 테스트 생성 등 단순한 작업에서는 AI가 20~55% 수준의 높은 생산성 향상을 제공한다 [5, 12, 13]. 특히 도메인 지식이 부족한 주니어 개발자에게 설계 지식을 보완해주어 큰 효율성(최대 39% 향상)을 제공할 수 있다 [8, 14]. +* **자동화 테스트를 통한 가드레일 (Test-Guarded Validation):** AI는 코드를 환각(Hallucination)하거나 오류를 유발할 수 있으므로 리팩토링의 안전성을 보장하기 위해서는 인간이 감독(Human-in-the-loop)하고, 리팩토링 전에 AI를 활용하여 포괄적인 단위 테스트(안전망)를 먼저 생성하는 방식이 요구된다 [6, 7, 15, 16]. + +## ⚖️ Trade-offs & Caveats +* **AI 생산성 역설 (AI Productivity Paradox):** 숙련된 시니어 개발자가 익숙한 코드베이스에서 AI를 사용할 경우, 개발자는 자신이 20% 더 빨라졌다고 느끼지만 실제로는 작업 속도가 19% 하락하는 39%의 '인식 격차(Perception Gap)'가 발생할 수 있다 [3, 8, 17, 18]. +* **암묵적 지식 및 인지 비용 (Implicit Knowledge Problem):** 전문가들이 머릿속에 지니고 있는 아키텍처 결정 사항, 과거 버그 내역, 팀의 관례 등의 암묵적 지식을 AI에게 프롬프트로 설명하고(Context-giving) 결과를 검토하는 데 걸리는 시간이, 개발자가 직접 코드를 리팩토링하는 시간보다 더 오래 걸릴 수 있다 [4, 19, 20]. +* **병목 현상의 전이 (Bottleneck Migration):** AI 도구가 코드 생성 자체의 속도는 크게 단축시키지만, 생성된 코드를 테스트하고 아키텍처에 통합하며 리뷰하는 시간(PR Review Time)이 91%나 증가하게 되어 개발 워크플로우의 병목이 후속 단계로 이동하게 된다 [21, 22]. +* **핵심 기술 위축 (Skills Atrophy):** AI에 과도하게 의존하여 리팩토링을 수행하면 개발자의 구문 기억력, 문제 분해 능력, 수동 디버깅 직관, 그리고 코드를 읽고 이해하는 깊이 있는 인지 능력이 저하될 위험이 존재한다 [22, 23]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A: 리팩토링 및 검증 기반 (Refactoring & Validation Foundations)] +- [[Test-Driven Development (TDD)]] + - 연결 이유: AI를 통해 구조를 변경할 때 기존 기능이 보존됨을 보장하려면 선행되는 테스트 구축(Red-Green-Refactor)이 필수적인 안전망으로 작용하기 때문이다 [18, 24]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: AI가 생성한 코드를 인간이 검토하기 전에 자동화된 피드백 루프를 통해 결함을 조기에 발견하고 검증하는 메커니즘을 이해할 수 있다 [24, 25]. + +- [[Code Smells]] + - 연결 이유: 리팩토링이 필요한 구조적 결함의 지표이며, 현대 AI 코딩 도구들은 방대한 데이터를 학습하여 이 냄새들을 실시간으로 자동 탐지한다 [2, 26, 27]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: AI가 어떤 논리(긴 함수, 중복 코드 등)를 기준으로 리팩토링 대안을 제안하는지 그 근간이 되는 식별 원칙을 이해할 수 있다 [27, 28]. + +#### [관계 유형 B: AI 활용의 한계 및 조직적 현상 (AI Usage Limits & Phenomena)] +- [[AI Productivity Paradox]] + - 연결 이유: AI 코딩 보조 도구가 모든 상황에서 리팩토링 속도를 높여주는 것이 아니며, 작업 복잡도 및 개발자 숙련도에 따라 오히려 지연을 초래할 수 있음을 입증하는 현상이다 [3, 8, 29]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 시니어 개발자가 구조적이고 복잡한 리팩토링을 할 때 AI 사용을 전략적으로 취사선택(Task Selector)해야 하는 이유를 이해할 수 있다 [20, 30]. + +- [[Bottleneck Migration]] + - 연결 이유: AI 리팩토링으로 코드 작성 속도가 빨라진 대신, 생성된 코드의 정확성을 검토하는 리뷰(Code Review) 단계로 병목이 이동하는 현상이다 [21, 22]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: AI 도구 도입 시 단순히 작성 속도만 고려할 것이 아니라, 품질 보증(QA) 및 코드 리뷰 역량을 동시에 확장해야 함을 이해할 수 있다 [22]. + +### Deeper Research Questions + +- AI를 활용하여 대규모 레거시 코드베이스의 '접점(Seam)'을 자동으로 식별하고 테스트 불가능했던 의존성을 끊어내는 과정을 어디까지 자동화할 수 있는가? +- 숙련된 개발자가 복잡한 아키텍처 리팩토링을 수행할 때 AI에게 컨텍스트를 주입(Context-giving)하는 비용을 최소화하기 위한 최적의 프롬프트 및 도구 환경(예: Cursor, Claude Code) 조합은 무엇인가? +- AI가 생성한 리팩토링 코드의 무결성과 행동 보존(Behavior Preservation)을 완벽하게 입증하기 위해, 돌연변이 테스트(Mutation Testing) 등과 같은 기법을 어떻게 결합할 수 있는가? +- AI 리팩토링 도입 후 증가하는 '코드 리뷰 병목 현상'을 해결하기 위해 개발 조직의 리뷰 프로세스와 자원 분배를 어떻게 재설계해야 하는가? +- 개발자의 '기술 위축(Skills Atrophy)'을 방지하면서도 AI 리팩토링의 이점을 취하기 위해, 개발팀 내에 어떠한 의도적 훈련(Deliberate Practice) 사이클을 구축해야 하는가? + +### Practical Application Contexts + +- **Implementation:** 테스트가 부족한 레거시 프론트엔드/백엔드 코드를 리팩토링하기 전, AI 코딩 어시스턴트(예: Gemini 2.5 Pro)를 활용해 기존 동작을 포착하는 포괄적인 단위 테스트(승인 테스트)를 수 시간 내에 일괄 생성하는 데 적용할 수 있다 [31, 32]. +- **System Design:** 방대한 절차 지향적 코드나 비대해진 모놀리식 클래스의 책임을 분리할 때, AI에게 추상 구문 트리(AST) 수준의 분석을 맡겨 응집도 높은 여러 개의 도메인 모델로 분리하는 구조적 대안을 탐색하는 데 사용할 수 있다 [2, 33]. +- **Operation / Maintenance:** 매직 넘버 교체, 단순 데이터 객체(Data Class)로의 변환, 변수명 일괄 렌더링 및 보일러플레이트 코드 패턴 수정 등 상대적으로 리스크가 적고 반복적인 리팩토링 작업의 유지보수 속도를 크게 가속할 수 있다 [5, 13]. +- **Learning Path:** 주니어 개발자가 이해하기 난해한 타인의 레거시 코드를 읽을 때(이해를 위한 리팩토링), AI 모델의 설명 기능(Reasoning)을 결합하여 코드의 원래 의도를 학습하고 개선안을 도출하는 멘토링 도구로 활용 가능하다 [8, 13]. +- **My Project Relevance:** 프로젝트에 AI 어시스턴트를 도입할 때, 단순 코드 완성과 문서화에는 AI를 적극 활용하되, 핵심 비즈니스 규칙 변경이나 아키텍처 결정 시에는 의존성을 AI에게 맡기지 않고 전문가 판단과 엄격한 동료 리뷰를 거치도록 'AI Task Selector' 가이드를 수립할 수 있다 [5, 20, 30]. + +### Adjacent Topics + +- [[Automated Testing Frameworks]] + - 확장 방향: AI가 리팩토링한 코드가 기존 로직과 동일하게 동작하는지 자동으로 보장하기 위한 단위 테스트 도구(JUnit, Mockito 등)와의 통합 및 파이프라인(CI) 구성 방법론 탐구 [34-36]. +- [[Strangler Fig Pattern]] + - 확장 방향: 레거시 시스템 전체를 한 번에 재작성(Rewrite)하지 않고, 점진적으로 의존성을 끊고 새로운 아키텍처로 대체하는 과정에서 AI의 모듈 분리 능력을 어떻게 결합할 수 있는지에 대한 연구 [37, 38]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Approval Testing (승인 테스트).md b/10_Wiki/Topics/Architecture/Approval Testing (승인 테스트).md new file mode 100644 index 00000000..5035be01 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Approval Testing (승인 테스트).md @@ -0,0 +1,17 @@ +# [[Approval Testing (승인 테스트)]] + +## 📌 Brief Summary +승인 테스트(Approval Testing)는 코드의 올바름을 검증하기보다, 현재 코드의 실제 동작을 있는 그대로 기록(snapshot)하여 향후 변경 시 동작이 달라지지 않음을 보장하는 테스트 기법입니다 [1, 2]. 특성화 테스트(Characterization Test), 스냅샷 테스트(Snapshot Testing), 골든 마스터(Golden Master)라고도 불리며, 주로 이해하기 어려운 레거시 코드를 안전하게 리팩토링하기 위한 빠른 안전망을 구축할 때 사용됩니다 [1, 3]. + +## 📖 Core Content +* **동작의 캡처 및 보호:** 포괄적인 단위 테스트(comprehensive unit tests)를 하나하나 작성하는 대신, 현재 코드가 수행하는 실제 결과물의 스냅샷을 찍어 코드의 동작을 특성화(characterize)합니다 [1, 3]. 이 테스트는 향후 코드를 수정할 때 기존 동작이 의도치 않게 변경되는 것을 막아주는 역할을 합니다 [1]. +* **레거시 시스템에서의 실용성:** 대부분의 레거시 시스템에서는 코드가 '어떻게 동작해야 하는가(what it should do)'보다 '실제로 어떻게 동작하고 있는가(what it actually does)'가 훨씬 더 중요합니다 [3]. 승인 테스트는 명세서가 부족한 레거시 코드의 실제 동작 자체를 기준점으로 삼아 보호합니다. +* **빠른 안전망 구축과 리팩토링 지원:** 코드를 분석하고 이해하기 어렵거나 시간이 촉박한 상황에서, 승인 테스트는 레거시 코드를 빠르게 테스트로 덮을 수 있는 방법을 제공합니다 [3]. 이렇게 구축된 안전망을 통해 개발자는 기존 동작을 망가뜨릴 걱정 없이 코드를 안심하고 리팩토링할 수 있는 기반을 마련하게 됩니다 [2, 3]. + +## ⚖️ Trade-offs & Caveats +* **정합성(Correctness) 검증의 부재:** 승인 테스트의 주된 목적은 시스템의 '올바름'을 검증하는 것이 아니라 '현재 어떻게 도는지'를 기록하는 것에 불과합니다 [2]. 따라서 기존 코드에 버그나 잘못된 동작이 포함되어 있더라도, 이를 정상적인 현재의 상태(snapshot)로 간주하여 예상 동작으로 고착화시킬 수 있는 한계와 위험성이 있습니다. +* **포괄적 단위 테스트의 완전한 대체 불가:** 이 기법은 단기간 내에 리팩토링을 위한 안전망을 제공하는 데는 매우 효과적이지만, 코드가 어떻게 설계되었고 각 구성 요소가 어떤 논리로 작동하는지에 대한 포괄적 단위 테스트(comprehensive unit tests)를 완전히 대체하지는 못합니다 [1]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Approval Testing - Snapshot Testing (승인-스냅샷 테스트).md b/10_Wiki/Topics/Architecture/Approval Testing - Snapshot Testing (승인-스냅샷 테스트).md new file mode 100644 index 00000000..a30ca992 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Approval Testing - Snapshot Testing (승인-스냅샷 테스트).md @@ -0,0 +1,17 @@ +# [[Approval Testing / Snapshot Testing (승인/스냅샷 테스트)]] + +## 📌 Brief Summary +승인/스냅샷 테스트(Approval/Snapshot Testing)는 테스트가 없는 레거시 코드를 다룰 때 코드의 '실제 동작(actual behavior)'을 파악하고 캡처하기 위해 사용하는 테스트 기법입니다 [1, 2]. 포괄적인 단위 테스트를 작성하는 대신 코드 동작의 '스냅샷'을 찍어두고, 리팩토링 과정에서 해당 동작이 변하지 않도록 보장합니다 [1, 2]. '특성화 테스트(Characterization Testing)' 또는 '골든 마스터(Golden Master)'라고도 불리며, 레거시 코드에 신속하게 안전망을 제공하는 역할을 합니다 [2]. + +## 📖 Core Content +* **동일한 목적을 가진 다양한 명칭:** 승인 테스트(Approval Testing)와 스냅샷 테스트(Snapshot Testing)는 마이클 페더스(Michael Feathers)가 레거시 코드 작업을 위해 제시한 '특성화 테스트(Characterization Test)'와 본질적으로 동일한 기술입니다 [2]. 실무(in the wild)에서는 '골든 마스터(Golden Master)'라는 용어로도 불립니다 [2]. +* **테스트의 목적 및 원리:** 이 테스트는 코드가 "무엇을 해야 하는지(what it should do)"를 검증하는 대신 "현재 실제로 무엇을 하는지(what it actually does)"를 있는 그대로 파악하여 특성화합니다 [1, 2]. 테스트를 통해 현재 동작의 스냅샷을 찍고, 코드 변경 후에도 이 동작이 그대로 유지되는지 확인하는 데 집중합니다 [1, 2]. +* **레거시 시스템에서의 실용성:** 대부분의 레거시 시스템에서는 코드가 원래 의도했던 동작보다 '현재 실제로 동작하는 방식' 자체가 훨씬 중요합니다 [2]. 코드를 이해하기 어렵고 마감 기한이 촉박한 상황에서, 이 기법을 활용하면 레거시 코드를 테스트로 빠르게 덮어(cover) 리팩토링을 진행할 수 있는 강력한 안전망(safety net)을 얻을 수 있습니다 [1, 2]. + +## ⚖️ Trade-offs & Caveats +승인/스냅샷 테스트는 코드가 "무엇을 해야 하는지(올바른 동작)"가 아니라 "실제로 무엇을 하는지(현재 동작)"에만 초점을 맞춥니다 [1, 2]. 이는 기존 코드의 동작을 있는 그대로 스냅샷으로 캡처하기 때문에, 레거시 코드 내에 포함된 잠재적 버그나 비합리적인 동작까지도 '유지해야 할 동작'으로 취급하여 고정하게 되는 특징을 가집니다 [1, 2]. + +(스냅샷 파일 관리에 따른 오버헤드나 테스트의 깨지기 쉬움(brittleness) 등 이 기법의 구체적인 부작용이나 추가적인 기술적 제약 사항에 대해서는 주어진 소스에 관련 정보가 부족합니다.) + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Automated Refactoring Tools.md b/10_Wiki/Topics/Architecture/Automated Refactoring Tools.md new file mode 100644 index 00000000..b335b5d1 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Automated Refactoring Tools.md @@ -0,0 +1,20 @@ +# [[Automated Refactoring Tools]] + +## 📌 Brief Summary +자동화된 리팩토링 도구는 소프트웨어의 외부 동작을 보존하면서 내부 구조를 기계적으로 변경해 주는 소프트웨어 유틸리티 및 IDE 기능을 의미합니다 [1, 2]. 과거 스몰토크(Smalltalk)의 리팩토링 브라우저부터 현대의 통합 개발 환경(IDE)에 이르기까지, 이 도구들은 코드 재구성의 속도와 안정성을 높이는 데 기여해 왔습니다 [3, 4]. 최근에는 대형 언어 모델(LLM)을 기반으로 한 AI 도구들이 등장하여 다중 파일 리팩토링 및 단위 테스트 생성을 지원하는 등 그 역할과 능력이 더욱 확장되고 있습니다 [5, 6]. + +## 📖 Core Content +* **통합 개발 환경(IDE)의 기본 지원**: IntelliJ IDEA, Eclipse, Visual Studio, PyCharm 등의 현대적 IDE는 변수 이름 변경, 필드 캡슐화, 메서드 추출과 같은 마이크로 리팩토링(Micro-refactorings)을 자동화하여 제공합니다 [4, 7, 8]. +* **정적 코드 분석기(Static Code Analyzers)**: Codacy, PMD, JArchitect, NDepend, RuboCop 등의 분석 도구들은 코드를 실행하지 않고도 프로그래밍 결함이나 코드 스멜을 식별하여 개발자가 리팩토링할 대상을 쉽게 찾도록 돕습니다 [9]. +* **AI 기반 리팩토링 도구**: IBM Bob, Amazon CodeGuru Reviewer, GitHub Copilot, Cursor AI, Claude Code 등의 생성형 AI 도구는 방대한 코드베이스의 문맥을 파악하여 실시간으로 리팩토링을 제안하고 실행합니다 [6, 10, 11]. 특히 IBM watsonx Code Assistant와 같은 도구는 COBOL 등의 레거시 애플리케이션 구조를 동적으로 리팩토링하고 현대화하는 데 활용되기도 합니다 [11]. +* **의존성 및 아키텍처 분석 도구**: 마이크로소프트의 'MaX'와 같은 도구는 함수 및 바이너리 모듈 수준의 의존성을 분석하고 바람직하지 않은 의존성 사이클을 식별하여, 대규모 시스템의 아키텍처 리팩토링 결정을 지원합니다 [12, 13]. +* **자동화 도구의 기술적 요구사항**: 올바른 리팩토링 도구는 단순한 텍스트 검색이 아닌 구문 분석 트리(Parse Trees)와 의미론적 분석을 통해 프로그램 데이터베이스를 구축해야 합니다 [2, 14, 15]. 또한, 개발자가 안전하게 코드 설계를 탐색할 수 있도록 빠른 실행 속도와 신뢰할 수 있는 '실행 취소(Undo)' 기능을 필수적으로 갖추어야 합니다 [16, 17]. + +## ⚖️ Trade-offs & Caveats +* **수동 리팩토링 선호 및 도구의 한계**: 많은 개발자들이 자동화 도구의 존재를 알면서도 리팩토링의 약 86%를 수동으로 처리합니다 [18, 19]. 이는 IDE가 제공하는 리팩토링이 개발자가 의도하는 고차원적인 아키텍처 변경 수준에 미치지 못하는 경우가 많기 때문입니다 [20, 21]. 더불어, 리팩토링 도구 자체에 버그가 존재하거나 에러를 제대로 소통하지 못해 개발자가 도구 사용을 기피하는 경우도 발생합니다 [22, 23]. +* **AI 도구의 환각 및 검증 부담**: AI를 활용한 리팩토링은 유용하지만, 환각(Hallucination) 현상으로 인해 코드에 새로운 오류를 도입할 위험을 수반합니다 [24, 25]. 따라서 자동화된 테스트 제품군(Test Suite)을 통한 지속적인 검증과 개발자의 꼼꼼한 코드 리뷰(Human-in-the-loop)가 필수적으로 동반되어야 합니다 [24-26]. +* **AI 생산성 역설(Productivity Paradox)**: 복잡한 레포지토리나 익숙한 코드베이스에서 작업하는 숙련된 시니어 개발자의 경우, 프롬프트를 정교하게 작성하고 AI의 결과물을 검토 및 수정하는 데 드는 인지적 오버헤드 때문에 오히려 작업 속도가 19%까지 저하될 수 있습니다 [27-30]. 또한 AI를 통해 코드 생성과 리팩토링 속도를 높이더라도, 코드 리뷰 단계의 소요 시간이 급증(예: PR 리뷰 시간 91% 증가)하여 결과적으로 프로젝트의 병목 위치만 하류(Downstream)로 이동하는 부작용이 나타날 수 있습니다 [31, 32]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Automated Testing (자동화된 테스트).md b/10_Wiki/Topics/Architecture/Automated Testing (자동화된 테스트).md new file mode 100644 index 00000000..1f042855 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Automated Testing (자동화된 테스트).md @@ -0,0 +1,76 @@ +# [[Automated Testing (자동화된 테스트)]] + +## 📌 Brief Summary +자동화된 테스트는 소프트웨어 도구를 사용하여 사전 정의된 테스트를 실행하고, 사람의 개입 없이 예상 결과와 실제 결과를 비교하는 프로세스이다 [1]. 소프트웨어 리팩토링의 맥락에서 자동화된 테스트는 기존 코드의 외부 동작이 변경되지 않았음을 보장하는 필수적인 '안전망'이자 실행 가능한 명세서 역할을 수행한다 [2-5]. 개발자가 버그 도입의 두려움 없이 안전하게 코드의 구조를 개선할 수 있도록 매우 빠른 피드백 루프를 제공하며, 지속적 배포(CI/CD)와 애자일 개발을 가능하게 하는 핵심 기반이다 [6-8]. + +## 📖 Core Content + +* **리팩토링의 필수 전제 조건 (Prerequisite for Refactoring)** + 안전한 리팩토링을 수행하기 위한 가장 기본적인 조건은 견고하고 자가 검사(Self-testing)가 가능한 자동화된 테스트 스위트를 구축하는 것이다 [9, 10]. "테스트가 없는 코드는 레거시 코드"라는 정의가 있을 정도로, 테스트의 부재는 안전한 코드 변경을 불가능하게 만든다 [11, 12]. 자동화된 테스트는 코드 변경에 따른 회귀 버그(Regression bugs)를 감지하는 강력한 결함 탐지기 역할을 한다 [5, 13]. +* **테스트 피라미드 (The Test Automation Pyramid)** + 효율적이고 유지보수 가능한 테스트 스위트를 구축하기 위한 구조적 모델이다 [14]. + * **단위 테스트 (Unit Tests):** 피라미드의 가장 하단에 위치하며 전체 테스트의 대부분(약 70%)을 차지해야 한다. 개별 컴포넌트나 함수를 격리하여 테스트하며 밀리초 단위로 실행된다 [15, 16]. + * **통합 테스트 (Integration Tests):** 중간 계층으로, 컴포넌트 간의 상호작용(API 통신, 데이터베이스 연동 등)이 올바르게 이루어지는지 검증한다 [17, 18]. + * **엔드 투 엔드 테스트 (End-to-End Tests):** 피라미드 최상단으로 실제 사용자의 여정을 시뮬레이션한다. 가장 느리고 유지보수 비용이 비싸므로 전체 테스트의 소수(약 10%)로 유지해야 한다 [19]. +* **레거시 코드와 특성화 테스트 (Legacy Code & Characterization Tests)** + 의존성이 강하게 얽혀 있고 테스트가 없는 레거시 시스템을 리팩토링할 때는 '특성화 테스트'를 활용한다 [20]. 이는 코드가 '무엇을 해야 하는지'가 아니라 현재 '실제로 어떻게 동작하는지'의 스냅샷을 찍어 기존 동작이 변하지 않도록 보장하는 기법이다 [20, 21]. +* **테스트 격리를 위한 모조 객체 및 접점 활용 (Mocks, Stubs & Seams)** + 단위 테스트의 속도와 독립성을 확보하기 위해 데이터베이스나 네트워크 등 외부 의존성을 스텁(Stub)이나 모의 객체(Mock) 같은 테스트 대역(Test Doubles)으로 대체한다 [22, 23]. 레거시 코드에 이를 주입하기 위해 소스 코드를 편집하지 않고도 프로그램의 동작을 변경할 수 있는 '접점(Seam)'을 찾아 분리한다 [24-26]. +* **Red-Green-Refactor 사이클 (TDD)** + 자동화된 테스트는 테스트 주도 개발(TDD) 주기와 긴밀하게 결합된다. 실패하는 테스트를 먼저 작성(Red)하고, 이를 통과시키는 최소한의 코드를 작성(Green)한 뒤, 중복을 제거하고 구조를 개선(Refactor)하는 반복적인 과정을 거친다 [27-30]. + +## ⚖️ Trade-offs & Caveats + +* **역전된 테스트 피라미드 안티패턴 (The Inverted Test Pyramid Anti-Pattern):** 하향식으로 자동화 전략을 수립할 경우, 느리고 깨지기 쉬운(brittle) E2E 테스트 및 UI 테스트가 대부분을 차지하고 단위 테스트가 부족해지는 현상이 발생한다 [19, 31]. 이는 테스트 실행 시간을 몇 시간씩 지연시켜 지속적 통합(CI)의 이점을 없애고, 테스트 실패의 원인을 파악하기 어렵게 만들어 팀이 테스트 결과를 무시하게 만든다 [19]. +* **테스트 스위트 비대화 및 유지보수 비용 (Test Suite Bloat & Maintenance):** 중복되거나 더 이상 가치가 없는 테스트들이 쌓이면 테스트 스위트의 실행이 느려지고 관리 부채(Liability)가 된다 [32]. 프로덕션 코드와 마찬가지로 테스트 코드 역시 일급 시민(First-class code)으로 취급하여 리뷰하고, 리팩토링할 시간을 지속적으로 할당해야 한다 [33]. +* **구현에 지나치게 결합된 테스트 (Tests Coupled to Implementation):** 단위 테스트가 클래스의 내부 구조나 프라이빗 메서드에 지나치게 밀착되어 있으면, 기능 변경이 없는 단순 구조 리팩토링 시에도 테스트가 깨져(Flaky) 방해물이 될 수 있다 [34]. 이를 피하려면 구현 세부 사항이 아닌 "관측 가능한 외부 동작(Public Interface)"에 대해 테스트해야 한다 [35, 36]. +* **초기 학습 곡선과 시간 투자 (Learning Curve and Time Constraints):** 테스트를 작성하는 것은 추가적인 코드를 작성해야 하므로 초기에는 오버헤드처럼 느껴지며 일정을 지연시킬 수 있다 [37, 38]. 모조 객체 프레임워크(Mocking frameworks) 적용이나 접점(Seam) 설계 등의 테스트 기술 격차가 있을 경우, 잘못 설계된 깨지기 쉬운 테스트가 생성될 위험이 있다 [31]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A (소프트웨어 설계 및 아키텍처 원칙)] +- [[Test-Driven Development (TDD)]] + - 연결 이유: Red-Green-Refactor 사이클을 통해 테스트 작성을 리팩토링과 구현의 필수적인 선행 과정으로 만들기 때문이다 [27, 29]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링이 단순한 사후 코드 정리가 아니라, 설계와 테스트가 유기적으로 통합된 애자일 개발의 핵심 사이클임을 깊이 이해할 수 있다 [28, 39]. +- [[Test Automation Pyramid]] + - 연결 이유: 소프트웨어 시스템 내에서 자동화 테스트(단위, 통합, E2E)의 적절한 비율과 책임을 정의하는 구조적 프레임워크이기 때문이다 [14, 15]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 시 빠른 피드백을 제공하는 단위 테스트의 비중을 왜 늘려야 하는지, 그리고 테스트 유지보수 비용을 어떻게 최적화하는지 파악할 수 있다 [40, 41]. + +#### [관계 유형 B (레거시 코드 제어 및 테스트 기법)] +- [[Characterization Tests (특성화 테스트)]] + - 연결 이유: 테스트가 없는 레거시 코드를 리팩토링하기 전에, 현재 시스템의 '실제 동작'을 캡처하여 회귀 버그를 방지하는 안전망 역할을 하기 때문이다 [20, 21]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 명세가 부족한 기존 코드의 리팩토링 과정에서 리스크를 통제하고 의존성을 끊어내는 전략적 접근법을 배울 수 있다 [42]. +- [[Seams (접점)]] + - 연결 이유: 레거시 코드의 얽힌 의존성을 끊고 테스트 대역(Test Doubles)을 주입하기 위해 소스 코드 수정 없이 프로그램의 동작을 변경할 수 있는 논리적 위치이기 때문이다 [24, 25]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트하기 어려운 모놀리식 코드를 객체 지향적(Object Seam) 혹은 전처리기 방식(Preprocessing Seam)으로 분리하여 단위 테스트의 영역 안으로 가져오는 방법을 이해할 수 있다 [43, 44]. +- [[Mocking and Stubbing (테스트 대역)]] + - 연결 이유: 데이터베이스나 외부 API 등의 느리고 부수 효과가 큰 의존성을 가짜 객체로 대체하여, 리팩토링의 대상이 되는 단위를 완벽히 격리하고 테스트 속도를 높이기 때문이다 [22, 23]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: '고립된 단위 테스트(Solitary Unit Tests)'를 작성하는 방법과 함께, 리팩토링 시 외부 환경의 영향 없이 내부 로직의 변경만을 안전하게 검증하는 방법을 이해할 수 있다 [45]. + +### Deeper Research Questions + +- 역전된 테스트 피라미드(Inverted Test Pyramid) 패턴에 빠진 대규모 레거시 시스템을 안정적이고 빠른 단위 테스트 중심의 구조로 점진적으로 마이그레이션하는 최적의 전략은 무엇인가? +- 코드 리팩토링 시 내부 구조 변경에 의해 쉽게 깨지는 '구현에 밀착된 테스트'를 방지하고, '관측 가능한 동작'만을 테스트하도록 보장하는 테스트 설계 원칙은 무엇인가? +- 객체 지향 언어에서 제공하는 Object Seams를 활용할 수 없는 절차적 레거시 C 코드베이스에서 Preprocessing Seam이나 Link Seam을 활용하여 테스트 덮개를 씌우는 구체적인 방법과 한계는 무엇인가? +- AI 코딩 보조 도구(LLM)를 사용하여 누락된 레거시 시스템의 특성화 테스트(Characterization Tests)를 자동 생성할 때 발생하는 환각(Hallucination) 리스크와 이를 검증하기 위한 인간 개발자의 개입 범위는 어디까지인가? +- 테스트 대역(Mock/Stub)을 과도하게 사용하여 시스템의 결합도를 숨기는 '모의 객체 남용'이 오히려 리팩토링의 품질을 저해하는 사례와 그 방지책은 무엇인가? + +### Practical Application Contexts + +- **Implementation:** 새로운 기능 구현 전에 테스트를 추가하여 보호 구문을 세우고, Red-Green-Refactor 워크플로우에 따라 코드를 작성하며 내부 구조를 점진적으로 개선할 때 사용된다 [29, 30]. +- **System Design:** 소프트웨어 아키텍처 설계 시부터 의존성을 주입(Dependency Injection)하기 쉬운 구조로 모듈을 분리하여, 빠르고 신뢰성 있는 단위 테스트 환경을 보장한다 [24, 46]. +- **Operation / Maintenance:** CI/CD 파이프라인 상에 자동화 테스트 스위트를 배치하여, 개발자가 리팩토링을 수행하여 커밋할 때마다 몇 분 안에 회귀 버그 유무를 즉각 피드백받게 함으로써 배포 신뢰성을 유지한다 [7, 8]. +- **Learning Path:** JUnit 등 단위 테스트 프레임워크 기초 숙지 -> Mockito/Wiremock을 활용한 외부 의존성 분리 훈련 -> 테스트가 없는 레거시 코드에 Sprout/Wrap 기법으로 테스트 덮개를 씌우는 고급 기법 학습 [47-50]. +- **My Project Relevance:** 현재 유지보수 중인 레거시 시스템에서 코드를 구조적으로 변경하거나 기술 부채를 해결하기 위해, 가장 먼저 특성화 테스트를 작성하여 리팩토링의 안전망을 구축하는 과정과 직결된다. + +### Adjacent Topics + +- [[Continuous Integration (지속적 통합)]] + - 확장 방향: 자동화된 테스트가 실제 빌드 및 배포 파이프라인에 편입되어 리팩토링과 코드 병합의 리스크를 줄여주는 DevOps의 핵심 실천법으로 지식을 확장할 수 있다 [7, 8]. +- [[Technical Debt (기술 부채)]] + - 확장 방향: 자동화된 테스트가 부재하여 발생하는 시스템 유지보수 비용의 증가와 이를 점진적 리팩토링으로 상환하는 비즈니스 관점의 관리 전략으로 이해를 넓힐 수 있다 [51, 52]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Automated Testing Frameworks.md b/10_Wiki/Topics/Architecture/Automated Testing Frameworks.md new file mode 100644 index 00000000..1bba31b4 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Automated Testing Frameworks.md @@ -0,0 +1,20 @@ +# [[Automated Testing Frameworks]] + +## 📌 Brief Summary +자동화된 테스트 프레임워크는 소프트웨어의 기능이 의도한 대로 작동하는지 수동 개입 없이 검증하고 평가하기 위해 사용되는 도구 및 환경이다 [1, 2]. 이러한 프레임워크는 리팩토링 과정에서 기존 기능이 변경되지 않았음을 보장하는 필수적인 안전망 역할을 수행하여 개발자가 코드의 내부 구조를 자신감 있게 변경할 수 있도록 돕는다 [3, 4]. 소프트웨어 개발 조직은 단위 테스트부터 UI 테스트까지 다양한 계층에 특화된 테스트 프레임워크를 결합하여 빠르고 안정적인 지속적 배포(CI/CD) 파이프라인을 구축한다 [5, 6]. + +## 📖 Core Content +* **단위 테스트(Unit Tests) 프레임워크**: 자바 환경의 사실상 표준인 JUnit은 `TestCase`와 `TestSuite` 패턴을 기반으로 테스트를 구조화하고 실행하여 오류를 빠르게 감지하는 단위 테스트 프레임워크이다 [2, 7, 8]. 단위 테스트 프레임워크를 활용하면 전체 테스트 스위트의 기반을 형성하는 작고 빠른 테스트를 대량으로 구축할 수 있다 [9]. +* **모킹(Mocking) 및 스터빙(Stubbing) 프레임워크**: 복잡한 의존성을 가진 코드를 격리하여 테스트하기 위해 Mockito와 같은 프레임워크가 활용된다 [8, 10]. 외부 REST API와의 연동을 테스트할 때는 실제 서버 대신 가짜 서버를 띄워 응답을 모방하는 Wiremock 등의 프레임워크가 유용하게 쓰인다 [10, 11]. +* **계약 테스트(Contract Tests) 프레임워크**: 마이크로서비스 아키텍처 환경에서는 서비스 간의 인터페이스 사양을 검증하기 위해 Pact와 같은 소비자 주도 계약(CDC, Consumer-Driven Contract) 테스트 프레임워크가 사용된다 [10, 12]. 이는 소비자가 기대하는 API 스펙을 정의하면 제공자가 이를 지속해서 검증할 수 있도록 돕는다 [13, 14]. +* **엔드투엔드(End-to-End) 및 UI 테스트 프레임워크**: 전체 시스템의 스택을 통합적으로 검증하기 위해 브라우저를 자동화하는 Selenium 기반의 도구들이나 REST API 검증을 위한 REST-assured 프레임워크가 사용된다 [10, 15, 16]. +* **테스트 피라미드 전략**: 프레임워크들을 효과적으로 사용하려면, 빠르고 실행 비용이 저렴한 단위 테스트(JUnit 등)를 하단에 넓게 배치하고, 중간에 통합 테스트를, 상단에는 가장 적은 수의 E2E 테스트(Selenium 등)를 배치하는 '테스트 피라미드' 전략을 준수해야 한다 [17-19]. + +## ⚖️ Trade-offs & Caveats +* **역방향 테스트 피라미드(Inverted Test Pyramid) 안티 패턴**: GUI 및 E2E 자동화 프레임워크에 과도하게 의존하여 하향식으로 접근할 경우, 테스트 스위트가 비대해지고 유지 관리가 매우 어려워진다 [20]. 이러한 도구들은 실행 속도가 느리고 깨지기 쉽기 때문에(Brittle) 잦은 오탐(False positive)을 발생시키며, 결국 자동화 자체에 대한 팀의 신뢰도를 떨어뜨리게 된다 [20-22]. +* **유지보수 부채 증가**: 자동화된 테스트 코드도 운영(Production) 코드와 동일한 1급 시민으로 취급하여 주기적으로 리팩토링하고 리뷰하지 않으면 큰 짐이 될 수 있다 [23, 24]. 불필요하거나 중복된 고수준 테스트를 프레임워크 상에 방치하면 실행 시간만 길어지고 가치가 없으므로, 과감히 삭제하거나 하위 수준의 테스트로 대체해야 하는 제약이 따른다 [25, 26]. +* **AI 기반 테스트 생성의 가치 정렬 실패(Value Misalignment)**: LLM(대형 언어 모델) 등 AI 프레임워크를 사용하여 단위 테스트를 자동 생성할 경우, 모델이 단순히 커버리지 비율만 높이고 실제 결함을 잡는 데는 무의미한(Ineffective) 테스트를 대량 양산할 위험이 있다 [27]. 따라서 돌연변이 테스트(Mutation testing)와 같은 기법으로 테스트의 유효성을 통제하는 안전장치가 동반되어야 한다 [27]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Breaking Dependencies.md b/10_Wiki/Topics/Architecture/Breaking Dependencies.md new file mode 100644 index 00000000..dd12814e --- /dev/null +++ b/10_Wiki/Topics/Architecture/Breaking Dependencies.md @@ -0,0 +1,18 @@ +# [[Breaking Dependencies]] + +## 📌 Brief Summary +'Breaking Dependencies(의존성 제거)'는 단위 테스트 작성을 방해하거나 시스템의 유연성을 저해하는 코드 간의 강한 결합을 끊어내는 리팩토링 과정이다 [1-3]. 주로 데이터베이스 연결이나 외부 서드파티 서버 호출과 같이 코드를 독립적으로 실행하기 어렵게 만드는 무거운 자원을 식별하고, 이를 가짜 객체(Mock) 등 가벼운 대체물로 교체하기 위해 수행된다 [1-3]. 이를 통해 개발자는 레거시 코드에 안전한 테스트를 추가하고, 궁극적으로 더 큰 규모의 리팩토링과 새로운 기능을 안전하게 추가할 수 있는 기반을 마련할 수 있다 [2, 4, 5]. + +## 📖 Core Content +* **레거시 코드 딜레마 극복의 핵심**: 레거시 코드를 안전하게 리팩토링하려면 테스트가 필수적이지만, 테스트를 작성하려면 먼저 코드를 수정해 복잡한 의존성을 끊어내야 하는 모순적인 상황에 직면하게 된다 [2, 6]. 단위 테스트 작성을 가로막는 문제의 99%는 의존성 문제(Dependency Problem)로 귀결되며, 외부 API 호출, 전역 의존성(Global Dependency), 혹은 생성하기 까다로운 매개변수 등이 이에 해당한다 [1, 7]. 의존성 제거는 이 딜레마를 돌파하는 첫 번째 관문이다 [2]. +* **접점(Seam)의 식별과 활용**: 의존성을 끊기 위해 개발자는 소스 코드를 직접 수정하지 않고도 프로그램의 동작을 변경할 수 있는 지점인 '접점(Seam)'을 찾아야 한다 [1, 2, 4]. 예를 들어, 객체 지향 언어에서는 문제가 되는 클래스를 확장(Extend)하여 테스트 환경에서 실제 DB에 연결되지 않도록 메서드의 동작을 덮어쓰는 방식을 취해 의존성을 우회할 수 있다 [8, 9]. +* **의존성 제거를 위한 구체적 기법들**: 마이클 페더스(Michael Feathers)는 안전하게 의존성을 끊어내기 위한 24가지의 '의존성 제거 기법(Dependency-Breaking Techniques)' 카탈로그를 제시한다 [10]. 여기에는 매개변수 적응시키기(Adapt Parameter), 메서드 객체 추출(Break Out Method Object), 인터페이스 추출(Extract Interface), 인스턴스 위임자 도입(Introduce Instance Delegator), 생성자/메서드 매개변수화(Parameterize Constructor/Method), 하위 클래스화 및 메서드 재정의(Subclass and Override Method) 등의 실용적인 패턴들이 포함된다 [11, 12]. +* **모듈 및 아키텍처 수준의 의존성 제거**: 의존성 분리는 단일 클래스나 메서드 단위의 테스트 목적을 넘어, 시스템 아키텍처를 개선하기 위해서도 사용된다. 윈도우(Windows) 리팩토링 사례에서는 여러 바이너리 간에 복잡하게 얽혀 있던 원치 않는 모듈 간 의존성(inter-module dependencies)을 끊어내기 위해 특정 API 기능을 다른 바이너리로 이동시키고 분할하는 시스템 규모의 리팩토링을 수행했다 [13, 14]. + +## ⚖️ Trade-offs & Caveats +* **코드의 미학적 손상**: 기존 코드에 단위 테스트를 적용하기 위해 의존성을 제거하는 과정에서, 불가피하게 임시적인 간접 계층이나 불필요해 보이는 메서드를 추가해야 할 수 있다. 이로 인해 리팩토링의 특정 단계에서는 코드가 이전보다 미학적으로 약간 더 지저분해지거나(uglier) 복잡해지는 제약이 발생한다 [15, 16]. +* **병적인 코드 베이스에서의 어려움**: 운이 좋다면 의존성이 작고 국소적으로 모여있지만, 코드가 심각하게 망가진 병적인(pathological) 시스템의 경우 의존성이 수없이 많고 코드 전반에 광범위하게 퍼져 있어 이를 끊어내는 과정 자체가 극도로 고통스럽고 오랜 시간이 소요될 수 있다 [5]. +* **전처리기 및 링커 접점의 부작용**: 객체 접점(Object Seams)을 사용할 수 없는 C/C++ 같은 언어에서 전처리기 매크로나 링커를 활용해 의존성을 끊는 방식(Preprocessing / Link Seams)은 테스트 환경과 프로덕션 환경의 차이를 불명확하게 만들어, 파악하기 힘든 까다로운 버그를 유발할 수 있다 [17, 18]. 이러한 방식은 테스트의 유지보수성도 저하시키므로 더 나은 대안이 없는 최후의 수단으로만 사용해야 한다 [19]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/CRC Cards.md b/10_Wiki/Topics/Architecture/CRC Cards.md new file mode 100644 index 00000000..b9d39400 --- /dev/null +++ b/10_Wiki/Topics/Architecture/CRC Cards.md @@ -0,0 +1,16 @@ +# [[CRC Cards]] + +## 📌 Brief Summary +CRC(Class, Responsibility, and Collaborations)는 클래스와 그 프로토콜을 정의하는 데 초점을 맞추는 객체지향 설계 기법입니다 [1, 2]. 하지만 구체적인 개념 및 적용 방법에 대해 소스에 관련 정보가 부족합니다. + +## 📖 Core Content +소스에 관련 정보가 부족합니다. + +제공된 소스에서는 CRC가 객체지향 설계 기법 중 하나로서 클래스의 책임과 협력을 정의하는 데 사용된다는 점과, 마이클 페더스(Michael Feathers)의 저서 『Working Effectively with Legacy Code』의 목차에서 'Naked CRC'라는 주제로 간략히 언급되는 수준에 그치고 있습니다 [1-3]. 그 외의 상세한 메커니즘이나 활용 사례는 포함되어 있지 않습니다. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Characterization Tests (특성화 테스트).md b/10_Wiki/Topics/Architecture/Characterization Tests (특성화 테스트).md new file mode 100644 index 00000000..a81a3347 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Characterization Tests (특성화 테스트).md @@ -0,0 +1,58 @@ +# [[Characterization Tests (특성화 테스트)]] + +## 📌 Brief 신Summary +캐릭터리제이션 테스트(특성화 테스트)는 코드가 '무엇을 해야 하는지'가 아니라 '실제로 무엇을 하는지' 현재의 동작을 그대로 캡처하고 기록하는 테스트 기법이다 [1, 2]. 주로 테스트가 없는 난해한 레거시 코드(Legacy Code)에 빠르고 효과적으로 안전망(Safety net)을 구축하여, 코드를 안전하게 리팩토링할 수 있도록 돕는 데 사용된다 [1, 2]. + +## 📖 Core Content +* **현재 동작의 캡처 (Capturing Current Behavior):** + 포괄적인 단위 테스트(Comprehensive Unit Tests)를 작성하여 기대하는 결과를 검증하는 대신, 기존 코드의 현재 동작 상태에 대한 스냅샷(Snapshot)을 찍어 특성화한다 [1]. +* **실제 동작의 우선성:** + 대부분의 레거시 시스템에서는 코드가 '어떻게 동작해야 하는가(what it should do)'보다 '실제로 어떻게 동작하고 있는가(what it actually does)'가 훨씬 더 중요하게 취급된다 [2]. 이 테스트는 이러한 실제 동작이 변경되지 않았음을 강력하게 보장한다 [2]. +* **실무적 대체 용어:** + 현업 및 개발 생태계에서는 이 기법을 승인 테스트(Approval Testing), 스냅샷 테스트(Snapshot Testing) 또는 골든 마스터(Golden Master)라는 이름으로도 부른다 [2, 3]. +* **리팩토링을 위한 빠르고 든든한 안전망:** + 테스트가 없고 코드를 이해하기조차 어려운 상황에서, 개발자는 이 테스트를 통해 레거시 코드를 빠르게 커버하고 리팩토링을 시작하기 위한 1차적인 안전망을 확보할 수 있다 [2]. + +## ⚖️ Trade-offs & Caveats +* **의도된 로직의 올바름을 검증하지 않음:** 캐릭터리제이션 테스트는 코드의 현재 동작을 그대로 기록(Snapshot)하여 그것이 변하지 않았는지를 확인하는 데 목적이 있다 [1, 2]. 즉, 코드가 논리적으로 올바르게 작성되었는지 검증하는 것이 아니며, 기존 코드에 버그가 있다면 그 버그가 포함된 동작 자체를 정상 기준으로 삼아 캡처하게 된다는 제약이 있다 [2]. +* **단위 테스트의 완벽한 대체재가 아님:** 이 테스트는 코드를 이해하기 어렵거나 시간이 부족할 때 안전하게 리팩토링을 수행하기 위한 수단(Safety net)으로 사용된다 [1, 2]. 시스템의 개별 컴포넌트를 설계적 관점에서 고립시켜 검증하는 전통적인 포괄적 단위 테스트(Unit Tests)를 완전히 대체하는 것은 아니다 [1]. +* 그 외 캐릭터리제이션 테스트의 구체적인 부작용이나 최적화 시 발생할 수 있는 기술적 반대 급부(Trade-off)에 대해서는 **소스에 관련 정보가 부족합니다.** (단지 도입을 위한 휴리스틱 등이 목차 수준에서만 언급됨 [4]). + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [레거시 코드 제어 (Legacy Code Control)] +- [[Legacy Code (레거시 코드)]] + - 연결 이유: 캐릭터리제이션 테스트는 본질적으로 '테스트가 없는 코드'인 레거시 코드를 안전하게 다루고 리팩토링하기 위해 고안된 기법이다 [2, 5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 피드백(자동화된 테스트) 없이 코드를 변경하는 것이 왜 위험하며, 리팩토링에 앞서 안전망 구축이 왜 필수적인지 그 당위성을 깊이 이해할 수 있다. + +- [[Seam (접점)]] + - 연결 이유: 레거시 코드에 캐릭터리제이션 테스트를 적용하려면, 소스 코드를 직접 편집하지 않고도 프로그램의 동작을 변경하거나 의존성을 끊을 수 있는 '접점(Seam)'을 먼저 식별해야 한다 [6, 7]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트 불가능해 보이는 복잡한 의존성을 가진 코드를 어떻게 테스트 가능한 상태로 격리하는지 원리를 파악할 수 있다. + +#### [유사 테스트 기법 (Similar Testing Techniques)] +- [[Approval Testing / Snapshot Testing (승인/스냅샷 테스트)]] + - 연결 이유: 캐릭터리제이션 테스트와 동일한 목적(기존 동작의 보존)과 방식(결과값 캡처 및 비교)을 공유하는 실무적 동의어이다 [2, 3]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 소프트웨어의 현재 동작을 스냅샷으로 저장하고, 변경 후의 상태와 비교하는 구체적인 구현 패턴 및 생태계의 도구들을 이해할 수 있다. + +### Deeper Research Questions +- 캐릭터리제이션 테스트(Characterization Tests)와 일반적인 단위 테스트(Unit Tests)의 본질적인 목적과 구현 방식의 가장 큰 차이는 무엇인가? +- 레거시 코드 환경에서 캐릭터리제이션 테스트를 적용하기 위해 의존성을 끊고 접점(Seam)을 식별하는 구체적인 과정은 어떻게 진행되는가? +- 시스템의 '실제 동작(what it actually does)'을 캡처할 때, 기존 코드에 내재된 버그나 비효율성은 어떻게 취급하고 이후에 어떻게 수정해야 하는가? +- 승인 테스트(Approval Testing)나 스냅샷 테스트(Snapshot Testing) 도구를 실제 복잡하고 거대한 레거시 시스템에 적용할 때 발생하는 한계점은 무엇인가? +- 마이클 페더스(Michael Feathers)가 제안한 '캐릭터리제이션 테스트 작성을 위한 휴리스틱(Heuristic)'은 구체적으로 어떤 기준과 절차로 구성되어 있는가? + +### Practical Application Contexts +- **Implementation:** 복잡하고 이해하기 힘든 레거시 시스템의 함수나 클래스의 현재 반환값(상태)을 스냅샷 파일로 저장하고, 리팩토링을 수행한 뒤 변경 후의 결과가 저장된 스냅샷과 완벽히 일치하는지 비교하는 테스트 코드를 작성한다 [1, 2]. +- **System Design:** 강하게 결합되어 테스트가 불가능한 시스템에 대해 1차적인 캐릭터리제이션 안전망을 씌운 뒤, 객체 지향적이고 독립적인 모듈로 점진적인 아키텍처 재설계(Refactoring)를 진행하는 발판으로 사용한다 [2, 6]. +- **Operation / Maintenance:** 기존 기능의 변경 없이 버그 수정이나 성능 개선(유지보수)을 수행해야 할 때, 기존 시스템의 '실제 동작'이 훼손되지 않았음(회귀 버그가 발생하지 않았음)을 빠르고 확실하게 보장하는 용도로 활용한다 [2, 8]. +- **Learning Path:** 단위 테스트 작성법 학습 -> 레거시 코드의 문제점(의존성) 이해 -> 접점(Seam) 식별을 통한 의존성 분리 -> 캐릭터리제이션 테스트를 통한 안전망 구축 -> 본격적인 구조적 리팩토링 기법 적용의 순서로 학습한다 [1, 8]. +- **My Project Relevance:** 마감 기한이 촉박하고 기존 코드가 복잡하여 정규 단위 테스트를 면밀히 작성할 수 없는 프로젝트 모듈에서, 빠르고 신뢰할 수 있는 보호벽을 쳐 놓고 기능 추가나 코드 정리를 수행하고자 할 때 적용할 수 있다 [1, 2]. + +### Adjacent Topics +- [[Sprout & Wrap Techniques (스프라우트 & 랩 기법)]] + - 확장 방향: 레거시 코드에 캐릭터리제이션 테스트조차 작성할 시간이 부족하거나 클래스가 너무 거대할 때, 기존 코드를 직접 수정하지 않고 완전히 분리된 위치에 새로운 테스트 가능한 코드를 싹 틔우거나(Sprout), 기존 메서드를 감싸서(Wrap) 새 로직을 안전하게 추가하는 우회 기법으로 확장이 가능하다 [9-11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Clean Code (클린 코드).md b/10_Wiki/Topics/Architecture/Clean Code (클린 코드).md new file mode 100644 index 00000000..c140d44d --- /dev/null +++ b/10_Wiki/Topics/Architecture/Clean Code (클린 코드).md @@ -0,0 +1,29 @@ +# [[Clean Code (클린 코드)]] + +## 📌 Brief Summary +클린 코드(Clean Code)는 읽고 이해하기 쉬우며, 구조가 잘 잡혀 있고 유지보수를 염두에 두고 작성된 효율적인 코드를 의미합니다 [1]. 이는 단순히 컴퓨터에게 작업을 지시하는 것을 넘어, 다른 프로그래머에게 시스템의 의도를 명확하게 전달하기 위한 소통 수단입니다 [2]. 리팩토링을 통해 복잡한 코드(Dirty Code)를 클린 코드로 변환함으로써 기술 부채를 줄이고 버그 발생 가능성을 낮추며, 결과적으로 소프트웨어 개발의 경제성과 생산성을 높이는 것이 핵심 목적입니다 [1, 3, 4]. + +## 📖 Core Content + +* **클린 코드의 비즈니스 및 인지적 가치** + 클린 코드는 인간의 인지 부하를 줄여주어 버그를 더 빨리 발견하게 하고, 새로운 기능을 추가할 때 필요한 분석 시간을 단축시킵니다 [4]. 유지보수가 쉬워지기 때문에 장기적으로 개발 속도를 가속화하며, 비즈니스 가치를 더 빠르게 전달할 수 있는 토대가 됩니다 [4]. 반면 지저분한 코드는 기술 부채를 쌓게 하고 생산성을 떨어뜨리며, 코드를 변경하거나 이해하는 데 과도한 시간을 소모하게 만듭니다 [5, 6]. + +* **가독성과 유지보수성의 본질** + 코드를 작성할 때는 다른 사람이 읽을 것을 전제로 작성해야 하며, "이해하기 쉬운 코드가 곧 유지보수하기 쉬운 코드"입니다 [2]. 짧은 코드가 항상 가독성이 좋은 것은 아니며, 코드 라인 수(LOC)를 유지보수성의 지표로 삼아서는 안 됩니다 [2, 7]. 명확한 의도를 보여주고 올바른 책임을 분리하는 제대로 된 '추상화'를 갖추는 것이 진정한 의미의 클린 코드입니다 [8, 9]. + +* **리팩토링과 클린 코드의 관계** + 클린 코드를 달성하고 유지하는 지속적인 코드 위생 관리(code hygiene)의 핵심 실천 방법이 리팩토링입니다 [3, 10]. 로버트 C. 마틴(Robert C. Martin)의 저서 『Clean Code』가 '새로운 코드를 잘 작성하는 것'에 초점을 맞추고 있다면, 마틴 파울러(Martin Fowler)의 『Refactoring』은 '기존 코드를 개선하는 것'에 특화되어 있어 두 개념은 상호 보완적으로 작용합니다 [11]. 리팩토링은 함수 길이 단축, 모호한 변수명 변경, 중복 코드 제거, 조건문 단순화 등을 통해 혼란스러운 기존 코드를 클린 코드로 변환시킵니다 [12-14]. + +## ⚖️ Trade-offs & Caveats + +* **미학을 위한 리팩토링 지양** + 단순히 코드를 '예쁘게' 만들거나 '클린 코드'라는 미학적인 목적 자체만을 위해 리팩토링을 수행해서는 안 됩니다 [15, 16]. 생산적인 프로그래머는 행동 변경(기능 추가나 버그 수정)을 빠르게 수행하기 위한 경제적 목적에서 리팩토링을 하는 것이지, 아름다움을 위해 하는 것이 아닙니다 [15, 17]. + +* **과도한 추상화와 분절화의 위험** + 가독성을 위한다는 명목으로 모든 것을 단일 호출 헬퍼 함수로 추출하거나 맹목적으로 원칙을 100% 적용하는 것은 오히려 코드를 읽기 어렵게 만들 수 있습니다 [8, 18, 19]. 무리하게 쪼개진 코드는 인지적 오버헤드나 미묘한 결합을 유발할 수 있습니다 [8, 20]. + +* **잘못된 추상화 vs 코드 중복** + "잘못된 추상화는 단순한 코드 중복보다 훨씬 더 큰 비용을 초래합니다" [9]. 단순히 코드가 비슷해 보인다고 해서 성급하게 억지 추상화를 만들어 내면, 향후 새로운 사용 사례를 덮어씌우기 위해 조건문이나 매개변수가 늘어나면서 코드가 더욱 지저분해지는 부작용을 겪게 됩니다 [9, 19]. 따라서 맹목적인 코드 정리보다는 '3의 법칙(Rule of Three)'을 적용하여 중복이 세 번 이상 발생할 때까지 기다린 후 공통점을 찾아 명확한 추상화를 도출하는 것이 권장됩니다 [21, 22]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Code Rot (코드 부패 - 소프트웨어 부패).md b/10_Wiki/Topics/Architecture/Code Rot (코드 부패 - 소프트웨어 부패).md new file mode 100644 index 00000000..16f46e1b --- /dev/null +++ b/10_Wiki/Topics/Architecture/Code Rot (코드 부패 - 소프트웨어 부패).md @@ -0,0 +1,18 @@ +# [[Code Rot (코드 부패 / 소프트웨어 부패)]] + +## 📌 Brief Summary +코드 부패(Code Rot) 또는 소프트웨어 부패란 소프트웨어 개발 과정에서 지속적인 리팩토링 없이 코드 변경과 기능 추가가 누적되면서 초기 설계의 구조적 무결성이 점차 무너지고 복잡도가 상승하는 현상을 의미한다 [1, 2]. 이는 중복 코드, 건강하지 못한 의존성, 잘못된 책임 할당 등 코드 내의 혼란과 잡동사니가 증가하는 상태로 나타난다 [1]. 시간이 지남에 따라 코드를 읽고 설계를 파악하기 어려워지며, 설계 보존을 어렵게 만들어 부패 속도를 더욱 가속화한다 [3]. + +## 📖 Core Content +* **원인 및 발생 과정:** 요구사항 변화나 단기적인 목표 달성을 위해 소프트웨어의 전체적인 설계를 완전히 이해하지 못한 채 코드를 지속적으로 변경할 때 발생한다 [2, 3]. 코드를 만지고 수정하는 사람이 많아질수록 코드 부패가 발생할 확률은 더욱 높아지며 [4], 이는 자연스러운 엔트로피 증가로 인해 소프트웨어가 체계적인 공학(engineering)에서 단순 해킹(hacking) 수준으로 가라앉는 과정이다 [2, 5]. +* **주요 증상:** 소프트웨어가 코드 부패를 겪고 있음을 알리는 대표적인 징후는 '코드 스멜(Code Smells)'이다 [6]. 구체적으로는 중복된 코드, 클래스나 패키지 간의 건강하지 못한 의존성, 클래스 책임의 불량한 할당, 하나의 메서드나 클래스에 너무 많은 책임이 부여되는 등의 형태로 나타난다 [1]. +* **부패의 누적 효과:** 코드의 구조 상실은 누적되는 성질이 있다 [3]. 코드를 읽어서 설계를 파악하기가 어려워지면 원래의 설계를 유지하기가 힘들어지고, 결과적으로 시스템의 쇠퇴와 부패는 더욱 빠른 속도로 통제하기 어렵게 진행된다 [3]. +* **대응 방안:** 리팩토링은 이러한 소프트웨어의 부패(decay)를 방지하고 자연스러운 엔트로피 증가에 저항하여 시스템의 유연성을 회복시키는 핵심 해결책이다 [2, 7]. 규칙적이고 지속적인 리팩토링은 코드의 형태를 올바르게 유지시키고 설계의 쇠퇴를 멈추게 한다 [3, 8]. + +## ⚖️ Trade-offs & Caveats +코드 부패를 해결하기 위해 리팩토링을 수행하는 것은 장기적인 유지보수성에 기여하지만, 단기적으로는 기능 추가나 버그 수정과 달리 표면적으로 드러나는 즉각적인 이득이나 새로운 기능을 제공하지 않는 것처럼 보일 수 있다 [9-11]. 부패를 걷어내기 위한 구조적 변경은 세심하게 진행하지 않으면 기존에 작동하던 기능에 예기치 않은 미묘한 버그나 회귀(regression)를 유발할 위험을 동반한다 [12, 13]. + +특히 테스트 코드가 없는 심하게 부패한 레거시 코드의 경우, 안전망 없이 공중 곡예를 하는 것과 같아 코드를 명확하게 정리하는 것 자체가 극도로 위험하고 어려운 작업이 된다 [12, 14, 15]. 또한, 코드의 부패 정도가 너무 심해 버그를 잡고 시스템을 안정화할 수 없을 지경에 이르렀다면, 무리하게 리팩토링을 시도하기보다는 처음부터 다시 작성(Rewrite)하는 편이 나을 수 있다 [16, 17]. 마감 기한이 임박한 상황에서는 리팩토링으로 인한 이점보다 지연으로 인한 비즈니스 손실이 클 수 있으므로 코드 부패 해결을 일시적으로 유보해야 할 때도 있다 [18, 19]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Code Smell (코드 스멜).md b/10_Wiki/Topics/Architecture/Code Smell (코드 스멜).md new file mode 100644 index 00000000..f41af77e --- /dev/null +++ b/10_Wiki/Topics/Architecture/Code Smell (코드 스멜).md @@ -0,0 +1,70 @@ +# [[Code Smell (코드 스멜)]] + +## 📌 Brief 주Summary +켄트 벡(Kent Beck)이 고안하고 마틴 파울러(Martin Fowler)가 대중화한 '코드 스멜'은 소프트웨어 시스템 내부에 더 깊은 설계적 문제가 있음을 암시하는 표면적인 징후이다 [1, 2]. 이는 개발자가 직관적으로 감지할 수 있는 구조적 결함의 지표로, 시스템의 코드가 부패하거나 기술 부채가 축적되고 있음을 나타낸다 [1-3]. 그러나 스멜 자체가 항상 맹목적인 오류나 버그를 의미하는 것은 아니며, 리팩토링을 통해 코드의 유지보수성, 가독성, 그리고 유연성을 높일 수 있는 훌륭한 개선 기회를 제공한다 [4, 5]. + +## 📖 Core Content +* **코드 스멜의 정의와 의의:** 코드 스멜은 코드가 어떻게 썩어가고 있는지를 보여주는 신호로, 이를 방치하면 코드 유지보수가 어려워지고 기술 부채가 쌓이게 된다 [3, 6]. 반면, 코드 스멜을 조기에 인지하고 해결하면 기술 부채의 축적을 예방할 수 있으며, 팀 내에서 스멜에 대한 공통의 어휘를 공유함으로써 코드 리뷰와 협업의 질을 크게 높일 수 있다 [5, 7]. +* **코드 스멜의 주요 분류 (Taxonomy):** + * **비대화 (Bloaters):** 긴 함수, 거대 클래스, 기본 타입 집착, 데이터 뭉치, 긴 매개변수 목록 등을 포함한다 [2, 8]. 코드가 너무 거대해져서 한눈에 파악하기 어렵고 수정 시 부수 효과가 발생할 확률이 높은 상태이다 [2]. + * **객체지향 남용 (OO Abusers):** 반복되는 switch 문, 거부된 유산, 임시 필드, 인터페이스가 다른 대안 클래스 등 객체지향의 이점(다형성, 상속 등)을 제대로 살리지 못하고 절차지향적으로 굳어진 상태를 말한다 [2, 8]. + * **변경 방해 (Change Preventers):** 뒤엉킨 변경, 산탄총 수술, 평행 상속 계층 등이 속한다 [2, 9]. 하나의 요구사항을 변경하기 위해 시스템의 여러 곳을 동시에 고쳐야 하는 등 구조적 경직성을 초래한다 [2]. + * **불필요 요소 (Dispensables):** 중복 코드, 불필요한 주석, 게으른 클래스, 데이터 클래스, 죽은 코드 등 가독성을 해치고 관리 포인트만 쓸데없이 늘리는 무의미한 요소들이다 [2, 8]. + * **결합자 (Couplers):** 기능 욕심, 부적절한 친밀함, 메시지 체인, 미들맨 등이 포함되며 객체 간의 의존성이 지나치게 강해 독립적인 변경이 불가능해진 상태이다 [2, 9]. +* **가장 치명적인 스멜과 대처법:** '중복 코드(Duplicate Code)'는 로직 변경 시 모든 중복 지점을 찾아 수정해야 하므로 가장 치명적인 스멜로 간주된다 [10, 11]. 또한, '긴 함수(Long Method)'는 추상화가 뒤섞여 인지 부하를 높이는 주범이다 [11]. 마틴 파울러는 함수 본문에 주석을 달고 싶은 유혹이 들 때가 바로 그 코드를 별도의 함수로 추출해야 할 시점이라고 조언한다 [11, 12]. +* **테스트 코드의 스멜:** 스멜은 프로덕션 코드뿐만 아니라 테스트 코드에서도 발생한다. 원치 않는 중복, 설명이 없는 단언(assertion), 자원 간섭 등이 테스트 코드의 스멜에 해당하며, 이 역시 테스트 전용 리팩토링을 통해 개선해야 한다 [13]. + +## ⚖️ Trade-offs & Caveats +* **지표일 뿐, 절대적 오류는 아님:** 스멜은 내부 문제의 '지표'일 뿐, 그 자체가 무조건 나쁜 것은 아니다 [4]. 예를 들어, 어떤 긴 함수는 오히려 분리하지 않는 것이 더 타당할 수 있다 [4]. 스멜이 보인다고 해서 기계적으로 무조건 수정하는 것은 불필요한 오버엔지니어링을 초래할 수 있다 [4]. +* **목적 없는 리팩토링의 위험성:** 명확한 계획이나 비즈니스 목적 없이 논리 구조를 재조정하려다 보면 수정 범위가 통제할 수 없이 커지는 '범위 변질(Scope Creep)'에 빠질 수 있다 [14]. 아름다운 코드를 만드는 것 자체를 목적으로 삼지 말고, 기능 변경을 더 쉽게 만들거나 경제적 이득이 명확할 때 집중해야 한다 [15, 16]. +* **레거시 코드에서의 치명적 위험:** 단위 테스트 등 안전망이 결여된 레거시 시스템에서 눈에 보이는 코드 스멜을 고치려 시도하는 것은 예기치 않은 회귀 버그(Regression Bug)를 발생시킬 위험이 매우 높다 [17, 18]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A: 문제 식별 및 관리 (Problem Identification)] +- [[Technical Debt (기술 부채)]] + - 연결 이유: 코드 스멜을 방치하고 지름길을 택하면 자연스럽게 기술 부채로 축적된다 [19]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 코드 스멜을 청소하는 리팩토링이 어떻게 장기적인 개발 비용(기술 부채)을 줄이고 소프트웨어의 경제성을 높이는지 이해할 수 있다 [16, 19]. +- [[Rule of Three (3의 법칙)]] + - 연결 이유: '중복 코드(Duplicate Code)'라는 대표적인 코드 스멜을 언제 리팩토링해야 할지 판단하는 실용적 기준을 제공한다 [10]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 모든 중복(스멜)을 즉시 고치는 대신, 동일한 코드가 세 번 나타날 때 추상화를 적용하는 것이 성급한 구조화를 피하는 길임을 학습할 수 있다 [10, 20]. + +#### [관계 유형 B: 해결책 및 검증 (Solutions & Verification)] +- [[Refactoring Techniques (리팩토링 기법)]] + - 연결 이유: 식별된 특정 코드 스멜을 해결하기 위해 직접적으로 적용하는 처방전 역할을 한다 [21]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: '긴 함수'에는 '함수 추출하기', '데이터 뭉치'에는 '클래스 추출하기' 등 스멜의 종류에 따라 적용되는 구체적인 구조적 변환 기법들을 매핑하여 배울 수 있다 [12, 22-39]. +- [[Automated Testing (자동화된 테스트)]] + - 연결 이유: 코드 스멜을 제거하기 위해 내부 구조를 변경할 때 기능이 깨지지 않았음을 보장하는 필수 안전망이다 [17, 18]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트 없는 상태에서의 스멜 제거가 왜 재앙으로 이어질 수 있는지, 그리고 단위 테스트 피라미드가 어떻게 리팩토링을 지탱하는지 파악할 수 있다 [18]. +- [[AI-Assisted Refactoring (AI 기반 리팩토링)]] + - 연결 이유: 현대 소프트웨어 환경에서 LLM(거대언어모델) 기반 AI는 미세한 코드 스멜을 빠르게 식별하고 리팩토링 방향을 제안하는 역할을 한다 [40, 41]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기존 개발자가 수동으로 발견하던 스멜을 인공지능이 어떻게 효율적으로 탐지하는지, 그리고 이 과정에서 발생하는 검증 비용과 생산성의 역설(Productivity Paradox) 한계를 짚어볼 수 있다 [41, 42]. + +### Deeper Research Questions + +- 구조적 결함(코드 스멜)을 인지했음에도 불구하고, 비즈니스적이나 기술적인 이유로 이를 즉각 리팩토링하지 않고 의도적으로 남겨두는 것이 유리한 구체적 사례는 무엇인가? +- 대규모 레거시 시스템에서 여러 범주의 코드 스멜(예: 결합자 vs 불필요 요소)이 혼재할 때, 가장 높은 투자 대비 수익(ROI)을 달성하기 위해 어떤 스멜부터 우선순위를 두고 제거해야 하는가? +- 테스트 코드에서 발생하는 스멜(Test Smells)이 방치될 경우, CI/CD 파이프라인과 프로덕션 리팩토링 과정에 어떤 부정적 연쇄 효과를 미치는가? +- 생성형 AI와 정적 코드 분석기(Static Code Analyzer)가 코드 스멜을 식별하는 방식의 근본적인 차이점은 무엇이며, 각각의 한계는 무엇인가? +- 개발 조직 내에서 주니어 개발자들이 '코드 스멜'에 대한 민감도를 높이고, 코드 리뷰 과정에서 공통된 어휘로 소통하도록 유도하는 효과적인 훈련 프레임워크는 무엇인가? + +### Practical Application Contexts + +- **Implementation:** 새로운 기능을 개발하거나 코드 리뷰를 진행할 때, '기능 욕심'이나 '데이터 뭉치' 같은 코드 스멜 징후를 조기에 포착하고 즉각적인 '함수 추출'이나 '클래스 분리'를 수행한다 [7, 43]. +- **System Design:** 아키텍처를 설계하거나 검토할 때 '산탄총 수술'이나 '뒤엉킨 변경'과 같은 스멜 지표를 활용하여 단일 책임 원칙(SRP) 위반 여부를 확인하고, 모듈 간 응집도와 결합도를 최적화한다 [2]. +- **Operation / Maintenance:** 기술 부채가 심한 레거시 코드를 유지보수할 때, 코드 스멜을 식별하고 접점(Seams)을 만들어 테스트를 부착한 뒤, 국소적으로 냄새나는 부위를 청소해 나가는 점진적 개선 작업에 적용한다 [44-46]. +- **Learning Path:** 개발 팀의 온보딩 과정에서 신입 개발자가 좋은 설계와 나쁜 설계를 구분할 수 있도록 특정 코드 스멜을 '이주의 스멜'로 선정해 스스로 문제를 탐지하고 개선해 보도록 학습시킨다 [7]. +- **My Project Relevance:** 현재 진행 중인 개발 프로젝트 내에서, 동일한 버그가 반복 발생하거나 기능 확장이 어려운 모듈의 스멜(예: 거대 클래스, 긴 매개변수 목록)을 분석하고 이를 적절한 리팩토링 기법에 매핑하여 코드 구조를 체계적으로 정돈하는 데 활용한다. + +### Adjacent Topics + +- [[Static Code Analysis (정적 코드 분석기)]] + - 확장 방향: 소스 코드를 실행하지 않고 잠재적 결함이나 코드 스멜(예: PMD, Codacy 사용)을 자동으로 찾아내는 도구의 활용 기법 및 한계를 파악하는 방향으로 확장 [47]. +- [[Test-Driven Development (TDD)]] + - 확장 방향: Red-Green-Refactor 사이클의 마지막 단계인 Refactor 과정에서, 구현된 코드 내의 스멜을 감지하고 지속적으로 설계를 개선하여 코드베이스를 건강하게 유지하는 방법론 학습으로 연결 [48, 49]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Cyclomatic Complexity.md b/10_Wiki/Topics/Architecture/Cyclomatic Complexity.md new file mode 100644 index 00000000..9f162849 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Cyclomatic Complexity.md @@ -0,0 +1,17 @@ +# [[Cyclomatic Complexity]] + +## 📌 Brief Summary +Cyclomatic Complexity(순환 복잡도)는 소프트웨어 공학에서 코드의 복잡성을 평가하기 위해 사용되는 소프트웨어 측정 지표 중 하나입니다 [1]. 리팩토링의 결과와 품질을 평가하는 추상 구문 트리(AST) 기반 지표로 활용되며, 코드의 결합도 분석과 함께 사용됩니다 [2]. 성공적인 구조적 리팩토링을 거친 코드는 일반적으로 함수당 평균 순환 복잡도 수치가 감소하여 유지보수성이 향상되는 특징을 보입니다 [3, 4]. + +## 📖 Core Content +* **리팩토링을 통한 복잡도 감소**: AI를 활용한 리팩토링 사례 연구에 따르면, 비대한 라우팅 레이어의 로직을 여러 기능별 전용 레이어로 모듈화하여 재분배한 결과 전체 함수의 수는 늘어났음에도 함수당 평균 순환 복잡도는 2.24에서 2.13으로 낮아졌으며, 라우팅 레이어 자체의 평균 복잡도는 1.97까지 하락했습니다 [3, 4]. +* **실증적 연구에서의 검증**: 마이크로소프트의 대규모 시스템(Windows 7) 진화 기록 분석에서도, 리팩토링이 우선적으로 집중된 상위 5%의 모듈은 나머지 95%의 일반 모듈에 비해 순환 복잡도(Cyclomatic complexity)를 비롯한 여러 복잡성 지표(Fan-in, Fan-out 등)를 더 큰 폭으로 감소시키는 것으로 확인되었습니다 [5, 6]. +* **품질 측정의 객관적 척도**: 순환 복잡도는 아키텍처의 의존성 분석과 같은 AST 기반 메트릭과 함께 사용되어, 변경된 코드가 단순히 재배치된 것을 넘어 실제적인 구조적 개선(예: 응집도 높은 자기 완결적 모듈로의 전환)을 이루었는지 검증하는 도구로 쓰입니다 [2, 3]. +* *(참고: 제공된 소스 내에 순환 복잡도를 계산하는 정확한 수학적 공식이나 구체적인 이론적 산출 기준에 대한 관련 정보는 부족합니다.)* + +## ⚖️ Trade-offs & Caveats +* **구성 요소 및 코드 라인 수(LOC) 증가**: 순환 복잡도를 낮추기 위해 응집도 높은 여러 개의 작은 모듈이나 함수로 논리를 분리하는 최적화를 거치면, 필연적으로 시스템 내 전체 함수 및 컴포넌트의 수는 증가하게 됩니다(예: 806개에서 1,022개로 증가) [3]. 결과적으로 개별 함수의 복잡도는 감소하지만 전체 코드 라인 수(LOC)와 파일 수는 오히려 증가하는 반대 급부(Trade-off)가 발생합니다 [7-9]. +* **다차원적인 평가의 필요성**: 순환 복잡도와 같은 지표가 개선되었다고 해서 소프트웨어 수정 비용이 모든 측면에서 줄어드는 것은 아닙니다. 리팩토링된 모듈은 복잡도 수치가 낮아지더라도, 구조적 변경으로 인해 수정 사항이 시스템 여러 곳에 흩어지는 '교차 절단 변경(crosscutting changes)'을 유발할 수 있으므로 단일 지표가 아닌 다차원적인 영향 평가가 반드시 수반되어야 합니다 [8, 9]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/DRY (Don't Repeat Yourself).md b/10_Wiki/Topics/Architecture/DRY (Don't Repeat Yourself).md new file mode 100644 index 00000000..5391c294 --- /dev/null +++ b/10_Wiki/Topics/Architecture/DRY (Don't Repeat Yourself).md @@ -0,0 +1,17 @@ +# [[DRY (Don't Repeat Yourself)]] + +## 📌 Brief Summary +DRY(Don't Repeat Yourself)는 소프트웨어 개발에서 널리 쓰이는 지혜이자 코드의 중복을 피하기 위한 핵심 원칙입니다 [1, 2]. 프로그래밍에서 코드 중복은 코드를 유지보수하기 어렵게 만들기 때문에 피해야 할 나쁜 관행(bad practice)으로 간주됩니다 [3]. 그러나 이 원칙은 종종 오해를 받으며, 중복을 무조건 제거하기 위해 잘못된 추상화를 도입하기보다는 때로는 우연한 중복을 그대로 두는 것이 더 나은 선택일 수 있습니다 [1]. + +## 📖 Core Content +* **중복의 위험성:** 코드 중복은 소프트웨어의 유지보수성을 떨어뜨리는 주요 원인입니다 [3]. 복제된 코드에 인코딩된 규칙이나 로직이 변경될 경우, 코드를 관리하는 개발자는 중복되어 있는 모든 위치를 찾아내어 정확하게 수정해야 하는 어려움을 겪게 됩니다 [3]. +* **올바른 추상화의 필요성:** 개발자는 이러한 중복을 방지하기 위해 올바른 추상화(Correct abstractions)를 찾아 책임을 적절히 분리하고 코드의 의도를 명확히 해야 합니다 [4]. +* **오해와 우연한 중복:** DRY 원칙은 매우 타당하지만 널리 오해받고 있는 원칙이기도 합니다 [1]. 두 개의 코드 조각이 겉보기에 동일해 보일지라도, 실제로는 서로 전혀 다른 개념이나 추상화를 나타내는 경우가 있습니다 [1]. 이 경우 두 코드의 유사성은 우연한(accidental) 중복에 불과합니다 [1]. + +## ⚖️ Trade-offs & Caveats +* **잘못된 추상화의 비용:** 우연히 발생한 중복을 맹목적으로 제거하기 위해 억지로 추상화를 도입하면 코드의 복잡성이 커지고 유지보수가 더 힘들어질 수 있습니다 [1]. 전문가인 샌디 메츠(Sandi Metz)는 "잘못된 추상화보다 중복이 훨씬 더 저렴하다(Duplication is far cheaper than the wrong abstraction)"라고 지적하며, 어설픈 추상화보다 차라리 중복을 유지하는 것이 낫다고 강조합니다 [1]. +* **성급한 리팩토링의 위험:** 중복을 피하고자 성급하게 리팩토링을 시도하면 잘못된 추상화를 선택할 위험이 큽니다 [3]. 나쁜 추상화는 새로운 요구사항이 발생할 때마다 불리언(boolean) 매개변수나 if 문 등을 억지로 덧대어 구현을 왜곡하게 만듭니다 [5]. 결국 코드가 더 악화되어 훗날 다시 리팩토링해야 하는 상황을 초래합니다 [3]. +* **대안적 접근 (3의 법칙):** 잘못된 추상화를 억지로 강요하는 오류를 피하려면, 중복이 세 번 발생할 때까지 기다렸다가 리팩토링을 수행하는 '3의 법칙(Rule of Three)'을 사용하는 것이 좋습니다 [6]. 명확한 이름조차 지을 수 없는 불명확한 추상화라면 차라리 중복을 허용하고, 상황에 대한 충분한 컨텍스트가 확보될 때까지 기다려야 합니다 [5, 7]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Dependencies (의존성).md b/10_Wiki/Topics/Architecture/Dependencies (의존성).md new file mode 100644 index 00000000..c7b9c950 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Dependencies (의존성).md @@ -0,0 +1,57 @@ +# [[Dependencies (의존성)]] + +## 📌 Brief Summary +의존성(Dependencies)은 하나의 코드 조각(모듈, 클래스, 메서드 등)이 기능하기 위해 다른 코드 조각이나 외부 리소스에 의존하는 관계를 의미합니다 [1-3]. 테스트가 누락된 레거시 코드 등에서 강하게 결합된 의존성은 코드를 이해하고 수정하며 테스트하기 어렵게 만드는 주요 원인이 됩니다 [3, 4]. 성공적이고 안전한 리팩토링과 시스템 아키텍처의 지속 가능성을 확보하기 위해서는 이러한 불필요한 의존성을 식별하고 끊어내는 작업이 필수적입니다 [4-6]. + +## 📖 Core 대Content +* **의존성의 문제점과 레거시 코드:** 클래스나 패키지 간의 건강하지 못한 의존성은 코드의 부패를 초래합니다 [7]. 특히 레거시 코드에서는 데이터베이스 연결, 외부 서버 API 호출, 복잡한 매개변수 등 다루기 힘든 의존성 때문에 코드를 테스트 환경에서 격리하여 실행하는 것이 매우 어렵습니다 [3, 4]. 마이클 페더스(Michael Feathers)는 코드를 안전하게 변경하기 위해 가장 먼저 해결해야 할 과제로 '의존성 끊기(Breaking Dependencies)'를 꼽았습니다 [6]. +* **의존성 분리 전략 - 접점(Seams)의 활용:** 기존 레거시 코드에 테스트를 추가하고 의존성을 제거하기 위해 '접점(Seam)'이라는 개념이 활용됩니다 [4, 8]. 접점이란 소스 코드를 직접 편집하지 않고도 프로그램의 동작을 변경할 수 있는 지점을 뜻합니다 [4, 9]. 이를 통해 프로덕션 코드의 변경 없이 테스트 시에만 가짜 객체(Mock)를 주입하여 외부 의존성을 우회하고 독립적인 단위 테스트를 수행할 수 있습니다 [4, 10]. +* **모듈 간 의존성 관리와 아키텍처 개선:** 대규모 소프트웨어 시스템에서 리팩토링의 핵심 목표는 모듈 간의 바람직하지 않은 의존성을 줄이는 것입니다 [5, 11]. 마이크로소프트 윈도우 7(Windows 7)의 리팩토링 사례 연구에 따르면, 집중적인 리팩토링을 거친 모듈들은 모듈 간 의존성 수가 유의미하게 감소하였으며, 이는 전체적인 시스템 복잡도를 낮추고 병렬 개발의 효율성을 극대화하는 결과를 가져왔습니다 [5, 12]. + +## ⚖️ Trade-offs & Caveats +* **'객체 통째로 넘기기(Preserve Whole Object)' 리팩토링의 부작용:** 긴 매개변수 목록을 줄이기 위해 여러 데이터 값을 넘기는 대신 해당 데이터가 포함된 전체 객체를 넘기는 리팩토링을 수행할 수 있습니다 [13]. 하지만 이 방식을 사용하면 호출되는 메서드가 원래는 몰라도 되었을 '전체 객체'에 대해 새로운 의존성을 가지게 됩니다 [14]. 이러한 새로운 의존성이 전체 의존성 구조를 망가뜨리거나 복잡하게 만든다면, 해당 리팩토링 기법은 피해야 합니다 [14]. +* **의존성 분리의 높은 비용:** 소프트웨어의 설계가 얼마나 훌륭한지와 무관하게, 기존 프로젝트에서 클래스를 분리하고 의존성을 끊어내어 테스트 가능한 상태로 만드는 작업은 상당한 시간과 노력을 필요로 합니다 [15]. +* **잘못된 추상화의 위험:** 중복을 피하고자 성급하게 리팩토링하여 잘못된 추상화를 도입할 경우, 새로운 요구사항이 등장함에 따라 코드를 맞추기 위해 오히려 의존성이 더 꼬이게 되고 결과적으로 유지보수 비용이 증가할 수 있습니다 [16]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [아키텍처/기반 기술] +- [[Seams (접점)]] + - 연결 이유: 소스코드를 변경하지 않고도 의존성을 끊고 동작을 바꿀 수 있게 해주는 핵심 개념이기 때문입니다 [8, 9]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 의존성이 강한 레거시 코드를 어떻게 테스트 가능한 단위로 격리할 수 있는지 그 메커니즘(객체 접점, 전처리 접점, 링크 접점)을 구체적으로 파악할 수 있습니다 [17-19]. +- [[Legacy Code (레거시 코드)]] + - 연결 이유: 테스트가 없고 의존성 문제로 인해 변경하기 가장 어려운 상태의 코드를 의미하기 때문입니다 [4, 20]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 의존성이 극도로 얽힌 환경에서 왜 리팩토링보다 의존성 제거와 테스트 도입이 우선되어야 하는지 실무적 맥락을 이해할 수 있습니다 [6]. + +#### [구현/활용 도구] +- [[Mock Objects (가짜 객체)]] + - 연결 이유: 의존성을 성공적으로 끊어낸 후(접점 확보 후), 실제 의존성을 대체하여 테스트 환경을 구성하는 도구이기 때문입니다 [4]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 복잡한 외부 리소스(DB, 외부 서비스 등)에 대한 의존성을 어떻게 통제 가능한 상태로 시뮬레이션할 수 있는지 파악할 수 있습니다 [4, 21]. +- [[Preserve Whole Object (객체 통째로 넘기기)]] + - 연결 이유: 코드를 단순화하는 리팩토링 기법임과 동시에, 잘못 사용하면 새로운 의존성을 창출하는 양날의 검이기 때문입니다 [14]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 데이터 전달 방식의 최적화가 시스템의 의존성 결합도(Coupling)에 어떤 영향을 미치는지 이해하고 설계의 트레이드오프를 평가할 수 있습니다 [14]. + +### Deeper Research Questions +- 레거시 시스템에서 테스트를 추가하기 위해 의존성을 끊을 때, '접점(Seams)'의 세 가지 주요 유형(Object Seams, Preprocessing Seams, Link Seams)은 각각 어떤 프로그래밍 언어 환경과 상황에서 가장 효과적으로 적용될 수 있는가? [17-19] +- 양방향 연관관계를 단방향으로 변경(Change Bidirectional Association to Unidirectional)하여 의존성을 줄일 때 고려해야 할 시스템 아키텍처적 제약 사항과 데이터 동기화 문제는 무엇인가? [22] +- 마이크로소프트 윈도우 7(Windows 7) 사례와 같이 대규모 시스템에서 모듈 간 의존성을 정량적으로 분석하고 리팩토링으로 이를 낮추었을 때, 이것이 실제 상용 환경의 결함 발생률(Post-release Defects) 감소에 미치는 구체적인 인과관계는 무엇인가? [11, 23] +- 리팩토링 시 '객체 통째로 넘기기(Preserve Whole Object)'를 통해 감소하는 매개변수 목록의 가독성 이점과, 새롭게 증가하는 클래스 간 의존성의 부작용 비용을 어떻게 정량적 혹은 정성적으로 비교 평가할 것인가? [14] +- 의존성이 심하게 얽혀 도저히 테스트를 작성하기 힘든 환경에서 '스프라우트 메서드(Sprout Method)'를 적용하는 것이, 기존 의존성 구조를 건드리지 않으면서도 안전하게 신규 기능을 테스트하는 데 어떤 방식으로 기여하는가? [24, 25] + +### Practical Application Contexts +- **Implementation:** 매개변수 전달 구조를 리팩토링할 때, 데이터를 낱개로 보낼지 객체 전체로 보낼지를 결정하는 기준으로 해당 객체 간의 '의존성 증가'가 시스템에 미칠 영향을 평가하여 코드를 구현합니다. [14] +- **System Design:** 초기 시스템 아키텍처 및 클래스를 설계할 때, 상호작용하는 모듈 사이에 과도한 결합이 발생하지 않도록 의존성을 관리하며 향후 '접점(Seam)'을 통한 대체와 확장이 가능한 유연한 구조를 마련합니다. [5, 8] +- **Operation / Maintenance:** 기존의 복잡한 레거시 코드를 유지보수하고 기능을 추가해야 할 때, 프로덕션 코드를 직접 수정하기 전에 의존성을 분리하고 가짜 객체를 주입하여 안전한 테스트 하네스(Test Harness)를 먼저 구축합니다. [4, 6] +- **Learning Path:** 리팩토링 원칙 학습 -> 코드 냄새(Code Smells) 식별 -> 의존성 분리 및 접점(Seams) 활용 -> 테스트 주도 리팩토링(TDD) -> 지속 가능한 아키텍처 유지의 순서로 학습을 전개합니다. [6, 26] +- **My Project Relevance:** 현재 진행 중인 프로젝트에서 기능 추가 시 기존 코드가 얽혀 테스트가 불가능하다면, 기능 개발을 멈추고 데이터베이스나 외부 API와 같은 강한 의존성을 분리하는 '준비적 리팩토링(Preparatory Refactoring)'부터 수행해야 합니다. [3, 27] + +### Adjacent Topics +- [[Test-Driven Development (TDD)]] + - 확장 방향: 테스트를 코드 작성 전에 미리 작성함으로써, 처음부터 외부 의존성이 낮고 격리가 쉬운(테스트하기 좋은) 유연한 아키텍처 설계로 이어지는 과정을 확장하여 학습할 수 있습니다. [28, 29] +- [[Technical Debt (기술 부채)]] + - 확장 방향: 무분별한 의존성 방치 및 더러운 코드(Dirty Code)의 축적이 향후 유지보수와 기능 확장 시 비용을 얼마나 기하급수적으로 증가시키는지 그 경제적 영향을 연구할 수 있습니다. [6, 30] + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Dependency Injection (의존성 주입).md b/10_Wiki/Topics/Architecture/Dependency Injection (의존성 주입).md new file mode 100644 index 00000000..52eecddc --- /dev/null +++ b/10_Wiki/Topics/Architecture/Dependency Injection (의존성 주입).md @@ -0,0 +1,15 @@ +# [[Dependency Injection (의존성 주입)]] + +## 📌 Brief Summary +의존성 주입(Dependency Injection)은 프로그램의 규모가 커짐에 따라 모듈 간의 의존성을 리팩토링할 때 서비스 로케이터(Service Locator)와 함께 도입할 수 있는 설계 패턴입니다 [1]. 이 패턴은 여러 팀에서 제공하는 모듈들을 동적으로 결합할 수 있게 해주어, 개발자가 전체 시스템을 다 이해하지 않고도 작은 부분을 수정할 수 있도록 돕습니다 [1]. 그 외 구체적인 개념에 대해서는 소스에 관련 정보가 부족합니다. + +## 📖 Core Content +* **모듈 의존성 리팩토링의 일환:** 프로그램이 성장함에 따라 모듈을 분리하는 것은 필수적이며, 프레젠테이션-도메인-데이터(Presentation-Domain-Data) 계층화를 활용해 프로그램을 나눈 뒤 모듈 간의 의존성을 해결하는 과정에서 의존성 주입 패턴이 활용될 수 있습니다 [1]. +* **다양한 언어에서의 적용:** 이 패턴은 자바(Java)나 클래스 개념이 없는 자바스크립트(JavaScript) 스타일 등 각기 다른 프로그래밍 언어 환경에 모두 적용될 수 있으나, 언어의 특성에 따라 그 구현 형태는 다르게 나타납니다 [1]. +* 그 외 상세한 동작 원리나 구체적 구현 사례에 대해서는 소스에 관련 정보가 부족합니다. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Design Patterns (디자인 패턴).md b/10_Wiki/Topics/Architecture/Design Patterns (디자인 패턴).md new file mode 100644 index 00000000..834c3cbd --- /dev/null +++ b/10_Wiki/Topics/Architecture/Design Patterns (디자인 패턴).md @@ -0,0 +1,76 @@ +# [[Design Patterns (디자인 패턴)]] + +## 📌 Brief 단기 Summary +디자인 패턴은 소프트웨어 개발 과정에서 자주 발생하는 설계 문제들에 대한 재사용 가능한 객체지향적 해결책이자, 리팩토링이 도달하고자 하는 '목표 지점(Target)'입니다. 리팩토링과 디자인 패턴은 자연스러운 공생 관계를 맺고 있으며, 리팩토링은 현재의 구조적 결함(코드 스멜)을 가진 시스템을 유연하고 견고한 디자인 패턴 구조로 안전하게 이끌어가는 구체적인 방법론 역할을 합니다. 이를 통해 개발자는 조건부 로직, 중복 코드, 엉킨 의존성을 다형성과 객체 간의 명확한 역할 분담으로 대체할 수 있습니다. + +## 📖 Core Content +소스 데이터를 기반으로 분석한 디자인 패턴과 리팩토링의 핵심 관계 및 적용 원리는 다음과 같습니다. + +* **리팩토링의 목표로서의 디자인 패턴 (Targets for Refactoring)** + * 'Gang of Four (GoF)'의 디자인 패턴은 소프트웨어가 지향해야 할 훌륭한 아키텍처의 모습을 제시합니다. 마틴 파울러(Martin Fowler)는 디자인 패턴이 리팩토링의 훌륭한 목표(Target)가 된다고 강조합니다. + * 리팩토링은 다른 어딘가(엉망인 코드)에서 출발하여 이러한 디자인 패턴 구조에 도달하기 위해 거치는 '안전하고 점진적인 경로(Ways to get there from somewhere else)'를 제공합니다. + +* **리팩토링에 빈번하게 활용되는 핵심 디자인 패턴** + * **상태/전략 패턴 (State/Strategy Pattern):** 클래스의 행동에 영향을 미치는 타입 코드(Type Code)를 서브클래싱할 수 없거나 생명주기 동안 변경되어야 할 때 사용됩니다(`Replace Type Code with State/Strategy` 기법). 이를 통해 다형성을 활용하여 복잡한 조건문(switch, if-else)을 제거할 수 있습니다. + * **템플릿 메서드 패턴 (Template Method Pattern):** 두 서브클래스에서 순서는 같지만 세부 구현이 다른 메서드를 가졌을 때, 공통된 단계를 상위 클래스의 템플릿으로 올리고 차이점만 다형성으로 위임하여 중복을 제거합니다(`Form Template Method` 기법). + * **팩토리 메서드 패턴 (Factory Method Pattern):** 단순히 객체를 생성하는 것 이상의 작업이 필요하거나, 생성 요청을 받는 곳과 실제 생성되는 서브클래스를 분리하여 은닉하고 싶을 때 생성자(Constructor)를 대체합니다(`Replace Constructor with Factory Method` 기법). + * **널 객체 패턴 (Null Object Pattern / Special Case):** 코드 베이스 전반에 걸쳐 나타나는 반복적인 `null` 확인 로직을 다형성으로 대체합니다. 기본 동작(아무것도 하지 않음 등)을 수행하는 널 객체를 반환하게 하여(`Introduce Null Object` 기법) 클라이언트 코드를 단순화합니다. + +* **디자인 패턴의 범주 (Categories of Patterns)** + * 목적에 따라 객체의 생성 메커니즘을 다루는 **생성형 패턴**(Factory Method, Abstract Factory, Singleton 등), 객체들의 구성을 다루는 **구조형 패턴**(Adapter, Composite, Decorator 등), 객체 간의 통신과 책임 분배를 다루는 **행동형 패턴**(State, Strategy, Template Method, Command, Observer 등)으로 분류됩니다. + +## ⚖️ Trade-offs & Caveats +디자인 패턴의 도입 및 이를 향한 리팩토링이 항상 긍정적인 결과만을 보장하는 것은 아니며 다음과 같은 부작용과 제약 사항을 수반합니다. + +* **간접 참조(Indirection)의 증가와 복잡성:** 디자인 패턴을 도입하면 대개 객체나 메서드를 작게 쪼개고 위임(Delegation)하게 되므로 시스템 내의 간접 참조가 늘어납니다. 이는 코드의 유연성을 높이지만, 코드를 읽는 흐름이 여러 객체로 분산되어 직관적인 이해를 방해할 수 있습니다. +* **추측성 일반화 (Speculative Generality):** '언젠가 필요할 것'이라는 추측만으로 당장 필요하지 않은 유연성을 확보하기 위해 복잡한 패턴을 도입하면, 오버엔지니어링(Over-engineering)이 발생합니다. 이는 불필요한 위임과 추상 클래스를 양산하여 유지보수 비용을 증가시킵니다. 가장 단순한 형태(Simple Solution)에서 출발해, 필요할 때 패턴으로 리팩토링하는 것이 권장됩니다. +* **잘못된 추상화 (Wrong Abstraction):** 잘못된 추상화로 디자인 패턴을 도입하면 오히려 코드 중복보다 유지보수성이 떨어집니다. 패턴에 억지로 코드를 끼워 맞추기 위해 새로운 플래그(Flag)나 조건문이 남발될 수 있으며, 이는 설계가 잘못된 방향으로 가고 있다는 강력한 경고입니다. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [아키텍처/설계 목표 (Architecture/Design Goals)] +- [[Refactoring (리팩토링)]] + - 연결 이유: 리팩토링은 소프트웨어의 외부 동작을 변경하지 않고 디자인 패턴이라는 목표 구조로 내부를 개선해 나가는 구체적인 실행 체계(수단)이기 때문입니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 완성된 디자인 패턴을 처음부터 완벽하게 설계하는 대신, 지속적이고 작은 변환 단계를 통해 패턴을 도입하는 점진적 설계 진화 과정을 이해할 수 있습니다. +- [[Polymorphism (다형성)]] + - 연결 이유: State, Strategy, Null Object 등 주요 패턴들이 장황한 조건부 로직(Switch/If-else)을 제거하기 위해 활용하는 핵심적인 객체지향 원리이기 때문입니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 분기 처리 로직이 어떻게 객체의 타입에 따른 동적 바인딩으로 깔끔하게 대체되는지 그 원리를 명확히 파악할 수 있습니다. + +#### [구현/활용 패턴 (Implementation/Utilization Patterns)] +- [[State/Strategy Pattern (상태/전략 패턴)]] + - 연결 이유: 타입 코드(Type Code)를 상속으로 풀 수 없는 경우, 객체의 상태나 알고리즘을 유연하게 교체할 수 있도록 돕는 대표적인 행동형 패턴이기 때문입니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: `Replace Type Code with State/Strategy` 리팩토링이 어떻게 변경에 닫혀 있고 확장에 열려 있는(Open-Closed) 구조를 만드는지 구체적으로 이해할 수 있습니다. +- [[Template Method Pattern (템플릿 메서드 패턴)]] + - 연결 이유: 여러 서브클래스에 흩어진 유사한 로직들의 순서를 통일하고 중복을 제거할 때 사용되는 패턴이기 때문입니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 상속 계층 내에서 `Form Template Method` 기법을 통해 공통점(알고리즘 뼈대)과 차이점(구체적 단계 구현)을 어떻게 우아하게 분리하는지 이해할 수 있습니다. +- [[Null Object Pattern (널 객체 패턴)]] + - 연결 이유: 코드 곳곳에 산재한 예외 처리용 `null` 검사를 다형성을 지닌 하나의 특수 케이스(Special Case) 객체로 응집시키기 때문입니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 예외적인 상황조차 일반적인 객체 간 협력망(Message Passing) 속으로 흡수하여 클라이언트 코드를 단순화하는 방법을 이해할 수 있습니다. + +### Deeper Research Questions + +- 디자인 패턴을 시스템에 적용하는 과정에서 발생하는 간접 참조(Indirection)의 증가로 인한 인지적 복잡도 상승을 어떻게 정량적으로 측정하고 통제할 수 있는가? +- '추측성 일반화(Speculative Generality)' 코드 스멜을 피하면서, 단순한 로직을 언제 디자인 패턴(예: State/Strategy)으로 리팩토링할 것인지 결정하는 실용적인 판단 기준(예: Rule of Three)은 무엇인가? +- 대규모 레거시 시스템에서 테스트 코드 없이 디자인 패턴을 도입하기 위해, 시스템 구조를 훼손하지 않으면서 '접점(Seam)'을 형성하여 의존성을 끊어내는 전략적 접근법은 무엇인가? +- 객체지향 남용(OO Abusers) 스멜을 제거하기 위해 적용되는 다양한 디자인 패턴들이 오히려 기능 욕심(Feature Envy)이나 미들맨(Middle Man)과 같은 새로운 스멜을 유발하지 않도록 경계하는 아키텍처 가이드라인은 무엇인가? +- 디자인 패턴 도입을 위한 리팩토링이 전체 시스템의 결함 발생률(Defect Rate)이나 변경 영향 범위(Locality of Change)에 미치는 장기적인 효과는 경험적 연구(Empirical Study)를 통해 어떻게 입증되었는가? + +### Practical Application Contexts + +- **Implementation:** 비즈니스 요구사항 변경에 따라 코드가 복잡해지고 조건문(Switch문 등)이 남발될 때, 코드를 분해하여(Extract Method) 궁극적으로 State나 Strategy 등의 디자인 패턴 구조로 재편하는 리팩토링 작업을 수행합니다. +- **System Design:** 소프트웨어의 초기 아키텍처를 잡거나 진화시킬 때, 객체의 생성(Factory), 구조 결합(Decorator), 행동 제어(Observer) 등 발생 가능한 변경 요소를 예측하여 GoF 디자인 패턴을 설계의 이정표로 활용합니다. +- **Operation / Maintenance:** 유지보수 과정에서 이해하기 어려운 레거시 코드의 복잡성을 낮추기 위해, 산재된 로직을 패턴화하여 개발자의 인지 부하를 줄이고 후속 수정 시 발생할 수 있는 부수 효과(Side effects)를 예방합니다. +- **Learning Path:** 리팩토링 카탈로그에 명시된 개별 단위의 리팩토링 기법(예: 함수 추출, 필드 이동)을 익히고, 이러한 마이크로 단위의 변화가 축적되어 어떻게 거대한 디자인 패턴 아키텍처를 형성하는지 훈련합니다. +- **My Project Relevance:** 현재 참여 중인 프로젝트에서 기능 확장 시 반복해서 고쳐야 하는 산탄총 수술(Shotgun Surgery)이나 중복 코드가 발견되면, 이를 식별하고 디자인 패턴을 목표로 점진적인 리팩토링을 추진하여 기술 부채(Technical Debt)를 청산합니다. + +### Adjacent Topics + +- [[Code Smells (코드 스멜)]] + - 확장 방향: 어떤 징후가 나타났을 때 구조적 개선(디자인 패턴 적용)이 필요한지 진단하는 지표로서, 코드의 부패 상태를 식별하는 다양한 카테고리(비대화, 객체지향 남용 등)를 학습합니다. +- [[Test-Driven Development (TDD)]] + - 확장 방향: 디자인 패턴을 도입하는 리팩토링 과정에서 외부 동작이 보존됨을 보장하는 핵심 안전망인 'Red-Green-Refactor' 워크플로우를 함께 조사하여 안정적인 구조 변환 절차를 이해합니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Duplicated Code (중복 코드).md b/10_Wiki/Topics/Architecture/Duplicated Code (중복 코드).md new file mode 100644 index 00000000..66881f4b --- /dev/null +++ b/10_Wiki/Topics/Architecture/Duplicated Code (중복 코드).md @@ -0,0 +1,19 @@ +# [[Duplicated Code (중복 코드)]] + +## 📌 Brief Summary +중복 코드는 동일한 코드 구조가 두 곳 이상에 반복되어 나타나는 현상으로, 가장 대표적이고 해결이 시급한 코드 스멜(Code Smell) 중 하나로 꼽힌다 [1]. 기능이나 규칙 변경 시 여러 곳을 빠짐없이 찾아 동일하게 수정해야 하므로 소프트웨어 유지보수 비용을 증가시키고 버그 발생 위험을 높인다 [2, 3]. 이를 해결하기 위해 메서드 추출, 클래스 추출 등의 기법으로 중복을 제거하여 단일 진실 공급원(Single Source of Truth)을 확보하고 시스템의 설계를 개선한다 [1, 4]. + +## 📖 Core 소스 Content +* **중복 코드의 위치에 따른 리팩토링 기법**: + * **동일한 클래스 내 두 메서드의 중복**: '메서드 추출(Extract Method)'을 사용하여 공통 코드를 새로운 메서드로 분리하고, 원래 있던 두 곳에서 이 메서드를 호출하도록 수정한다 [1]. + * **동일한 부모를 가진 하위 클래스 간의 중복**: '메서드 추출'을 적용한 뒤 '필드 올리기(Pull Up Field)'나 '메서드 올리기(Pull Up Method)'를 통해 부모 클래스로 중복 코드를 이동시킨다 [1, 5]. 코드가 비슷하지만 완전히 같지는 않다면, 차이점을 분리한 뒤 '템플릿 메서드 형성(Form Template Method)'을 사용하거나 알고리즘 자체가 다르다면 '알고리즘 전환(Substitute Algorithm)'을 적용할 수 있다 [1, 4]. + * **관련 없는 두 클래스 간의 중복**: '클래스 추출(Extract Class)'을 통해 새로운 컴포넌트를 만들어 두 클래스 모두에서 이를 참조하게 하거나, 해당 메서드가 논리적으로 속해야 할 제3의 클래스로 이동시킨다 [4]. + * **조건문 내의 중복**: 조건문의 모든 분기(Branch)에서 동일한 코드가 반복 실행된다면, '조건부 중복 구문 통합(Consolidate Duplicate Conditional Fragments)'을 통해 해당 코드를 조건문 밖으로 빼낸다 [6, 7]. +* **중복 제거의 핵심 목적**: 중복을 제거한다고 해서 프로그램의 실행 속도나 성능이 크게 향상되는 것은 아니지만, 향후 코드를 수정할 때 단 한 곳만 변경하면 되도록 구조를 개선하는 훌륭한 설계의 핵심이 된다 [2]. + +## ⚖️ Trade-offs & Caveats +* **잘못된 추상화의 위험성 (Wrong Abstraction)**: 두 코드가 겉보기에 같아 보여도 실제로는 전혀 다른 개념이나 추상화를 나타내는 우연한 중복일 수 있다 [8]. 잘못된 추상화를 억지로 적용하면 이후 새로운 요구사항이 생길 때마다 불필요한 부울(boolean) 매개변수나 if 문을 추가하여 코드를 복잡하게 구부려야 한다 [9]. 따라서 성급하게 리팩토링하는 것보다 우연한 중복을 그대로 두는 것이 유지보수 측면에서 훨씬 저렴하고 안전할 수 있다 [3, 8]. +* **'3의 법칙 (Rule of Three)' 적용**: 코드가 두 번 중복되었다고 해서 무조건 리팩토링하는 것은 권장되지 않는다 [10]. 첫 번째는 그냥 코드를 작성하고, 두 번째로 비슷한 작업을 할 때는 중복이 거슬리더라도 일단 그대로 작성하며, 세 번째로 비슷한 코드를 작성하게 될 때 비로소 리팩토링을 수행하라는 '3의 법칙'이 권장된다 [11-13]. 세 번의 중복 사례가 확보될 때까지 기다리면 추출해야 할 공통점과 차이점을 정확히 파악하기 쉬워져 섣부른 추상화로 인한 위험을 예방할 수 있다 [3, 11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Enabling Point (활성화 지점).md b/10_Wiki/Topics/Architecture/Enabling Point (활성화 지점).md new file mode 100644 index 00000000..b137bc29 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Enabling Point (활성화 지점).md @@ -0,0 +1,63 @@ +# [[Enabling Point (활성화 지점)]] + +## 📌 Brief Summary +Enabling Point(활성화 지점)는 소프트웨어 프로그램 내에서 접점(Seam)을 이용해 어떤 동작을 사용할지 결정을 내릴 수 있는 장소를 의미합니다 [1]. 레거시 코드에 테스트를 추가하기 위해 소스 코드를 직접 수정하지 않고도 동작을 변경해야 하는데, 이때 프로덕션 코드 외부나 인터페이스에서 변화를 주어 특정 동작을 대체할 수 있게 만드는 지점이 바로 활성화 지점입니다 [2]. 이를 통해 기존 프로덕션 코드의 동작과 분리하여 시스템의 의존성을 안전하게 끊고 리팩토링할 수 있는 기반을 제공합니다 [1-3]. + +## 📖 Core 소스 Content +**활성화 지점의 정의와 역할** +* 모든 접점(Seam)은 반드시 활성화 지점을 갖습니다 [1, 2]. +* 접점이 프로그램 내에서 코드를 편집하지 않고도 동작을 변경할 수 있는 '위치'라면, 활성화 지점은 테스트용 동작을 사용할지 프로덕션용 동작을 사용할지 **'결정을 내리는 곳'**입니다 [1]. +* 소스 코드는 프로덕션과 테스트 환경에서 동일해야 하므로, 접점을 활용하기 위해서는 반드시 다른 어딘가(활성화 지점)에서 변화를 주어야만 합니다 [2]. + +**접점(Seam) 유형별 활성화 지점의 형태** +* **전처리 접점 (Preprocessing Seams):** 코드가 컴파일되기 전에 텍스트를 교체하는 방식입니다 [4]. 이 접점의 활성화 지점은 **전처리기 지시자(preprocessor define, 예: `TESTING` 매크로 정의)**를 켜거나 끄는 곳이 됩니다 [2, 5]. +* **링크 접점 (Link Seams):** 이 접점의 활성화 지점은 항상 **프로그램 텍스트 외부**에 존재합니다 [6]. 예를 들어, 빌드 스크립트, 배포 스크립트, makefile의 설정이나 Java의 클래스패스(classpath) 환경 변수가 활성화 지점이 되어, 프로덕션 라이브러리 대신 테스트용 라이브러리를 연결하도록 결정합니다 [6-8]. +* **객체 접점 (Object Seams):** 객체 지향 언어에서 가장 널리 사용되는 접점입니다 [6]. 어떤 객체를 넘길지 결정할 수 있는 **메서드의 인수 목록(argument list)**이나, 프로덕션 객체를 생성할지 테스트용 서브클래스(Mock/Fake)를 생성할지 결정하는 **객체 생성 위치(instantiation point)**가 활성화 지점이 됩니다 [5, 9]. + +## ⚖️ Trade-offs & Caveats +* **가시성 저하:** 전처리 접점과 링크 접점은 활성화 지점이 소스 코드 텍스트 외부(예: 매크로 정의, makefile, 빌드 스크립트)에 존재하기 때문에 변경 사항을 눈치채기 어려울 수 있습니다 [3, 6]. +* **유지보수의 어려움:** 외부 환경에 의존하는 활성화 지점(링크/전처리 접점)을 이용한 테스트는 상대적으로 명시적이지 않아 유지보수하기 까다로울 수 있습니다 [3]. +* **적용의 우선순위:** 객체 지향 언어에서는 가장 명시적이고 관리하기 쉬운 **객체 접점(Object Seams)을 우선적으로 사용**하는 것이 좋습니다 [3]. 전처리 접점과 링크 접점은 의존성이 시스템 전반에 너무 넓게 퍼져 있어 더 나은 대안이 없는 불가피한 경우에만 제한적으로 사용하는 것이 권장됩니다 [3]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A (핵심 개념/이론)] +- [[Seam (접점)]] + - 연결 이유: 활성화 지점은 접점을 실제로 동작하게 만들기 위해 반드시 짝을 이루어 존재하는 필수 개념입니다 [1, 2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 코드를 직접 수정하지 않고도 소프트웨어의 특정 부분의 동작을 변경하거나 격리할 수 있는 근본적인 메커니즘을 이해할 수 있습니다. + +#### [관계 유형 B (구현/테스트 기법)] +- [[Object Seam (객체 접점)]] + - 연결 이유: 객체 지향 환경에서 활성화 지점(매개변수나 객체 생성부)을 위치시키기에 가장 권장되는 안전한 접점 유형입니다 [3, 6]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 다형성을 활용하여 테스트용 객체를 주입하고 의존성을 끊어내는 구체적인 설계 기법을 배울 수 있습니다. +- [[Link Seam (링크 접점)]] + - 연결 이유: 클래스패스나 빌드 스크립트 등 프로그램 코드 외부를 활성화 지점으로 사용하여 의존성을 끊는 기술입니다 [6, 7]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 컴파일 및 링킹 단계에서 테스트 환경을 분리하는 빌드 시스템 수준의 대체 전략을 이해할 수 있습니다. +- [[Preprocessing Seam (전처리 접점)]] + - 연결 이유: 매크로(`#define` 등)를 활성화 지점으로 삼아 컴파일 전 단계에서 코드를 교체하는 방법입니다 [2, 4, 5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: C/C++ 같은 언어에서 테스트용 빌드를 별도로 구축하는 조건부 컴파일 및 코드 치환 기술을 이해할 수 있습니다. + +### Deeper Research Questions +- 객체 접점의 활성화 지점을 시스템에 안전하게 도입하기 위한 최적의 리팩토링 기법(예: Parameterize Constructor, Extract and Override Call 등)은 어떤 기준에 따라 선택해야 하는가? +- 링크 접점의 활성화 지점을 활용할 때, 테스트 환경과 실제 프로덕션 환경 간의 빌드 구성 차이로 인해 발생할 수 있는 잠재적 결함을 어떻게 최소화할 수 있는가? +- 전처리 접점의 활성화 지점을 과도하게 사용할 경우 발생하는 코드 가독성 저하와 유지보수성 하락 문제를 완화할 수 있는 관리 기법은 무엇인가? +- 극도로 얽혀있는 레거시 시스템에서 여러 계층의 활성화 지점을 동시에 관리하고 추적해야 할 때, 이를 효과적으로 시각화하고 문서화하는 전략은 무엇인가? +- 객체 지향 패러다임을 지원하지 않는 언어(예: C 언어)에서 활성화 지점을 식별하고 적용하기 위해 활용할 수 있는 아키텍처적 접근 방식은 무엇인가? + +### Practical Application Contexts +- **Implementation:** 테스트하기 까다로운 레거시 코드를 테스트 하네스(Test Harness) 안으로 가져오기 위해, 활성화 지점을 찾아 가짜 객체(Fake Object)나 스텁(Stub)을 주입하여 원래의 소스 코드 수정 없이 안전하게 격리합니다 [2, 10]. +- **System Design:** 소프트웨어 설계 시 메서드의 매개변수나 생성자 등을 통해 명시적인 활성화 지점을 마련해 두면, 향후 컴포넌트의 동작을 쉽게 교체하고 테스트하기 용이한 유연한 시스템(Testable Architecture)을 구축할 수 있습니다 [9]. +- **Operation / Maintenance:** 빌드 스크립트, Makefile, 클래스패스(Classpath) 등의 외부 환경을 활성화 지점으로 관리함으로써, 프로덕션 환경의 코드를 오염시키지 않고 격리된 테스트 환경을 효과적으로 유지보수할 수 있습니다 [6, 7]. +- **Learning Path:** '레거시 코드의 복잡성 이해' -> '의존성 끊기의 필요성 인식' -> '접점(Seam) 식별' -> '적절한 활성화 지점(Enabling Point) 파악 및 적용' -> '안전망(테스트) 구축 및 리팩토링'의 순서로 개발 역량을 향상시킬 수 있습니다 [1, 3, 11]. +- **My Project Relevance:** 현재 다루고 있는 시스템 중 결합도가 높아 테스트 작성이 불가능한 모듈에 대해 접점과 활성화 지점을 찾아내고, 이를 통해 의존성을 분리하여 자동화된 테스트를 작성하는 데 직접적인 지침으로 활용할 수 있습니다. + +### Adjacent Topics +- [[Dependency Injection (의존성 주입)]] + - 확장 방향: 객체 접점의 활성화 지점(예: 생성자, 세터, 매개변수)을 통해 의존성을 주입하고 관리하는 현대적 디자인 패턴 및 프레임워크 기술 체계로의 학습 확장. +- [[Mock Objects (가짜 객체)]] + - 확장 방향: 활성화 지점을 통해 시스템에 주입되어, 실제 의존성을 안전하게 대체하고 다양한 시나리오에 대한 테스트를 가능하게 하는 테스트 더블(Test Double) 생성 및 활용 기법으로의 확장. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Extract Class (클래스 추출하기).md b/10_Wiki/Topics/Architecture/Extract Class (클래스 추출하기).md new file mode 100644 index 00000000..49d8d4f2 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Extract Class (클래스 추출하기).md @@ -0,0 +1,38 @@ +# [[Extract Class (클래스 추출하기)]] + +## 📌 Brief Summary +'Extract Class(클래스 추출하기)'는 두 개의 클래스가 나누어 맡아야 할 역할을 하나의 클래스가 과도하게 수행하고 있을 때, 새로운 클래스를 생성하여 기존 클래스의 관련 필드와 메서드를 새 클래스로 이동시키는 리팩토링 기법이다 [1]. 소프트웨어가 진화함에 따라 단일 클래스에 책임과 데이터가 무분별하게 추가되어 비대해지고 복잡해지는 문제를 해결하기 위해 사용된다 [1]. 이 기법을 통해 클래스의 역할을 명확히 분담하고 응집도를 높임으로써, 시스템의 유연성과 유지보수성을 크게 향상시킬 수 있다 [2, 3]. + +## 📖 Core Content +**적용해야 하는 주요 코드 스멜(Code Smells)** +* **거대 클래스(Large Class):** 클래스가 너무 많은 인스턴스 변수와 코드를 가지고 있어 비대해졌을 때, 관련된 변수와 메서드를 묶어 새로운 클래스로 추출하여 역할을 분담한다 [3, 4]. +* **데이터 뭉치(Data Clumps):** 여러 메서드나 클래스에 걸쳐 함께 몰려다니는 데이터 그룹이 있다면, 이를 독립된 클래스 객체로 추출하여 데이터 중복을 제거한다 [5, 6]. +* **뒤엉킨 변경(Divergent Change):** 하나의 클래스가 여러 가지 다른 원인과 목적에 의해 잦은 수정이 일어나는 경우, 특정 변경 원인에 해당하는 데이터와 메서드들을 분리하여 새 클래스로 추출한다 [7]. +* **임시 필드(Temporary Field) 및 부적절한 친밀함(Inappropriate Intimacy):** 특정 상황에서만 할당되는 고아 변수들을 모아둘 새로운 클래스를 만들거나 [8], 두 클래스 간의 공유 관심사를 안전하게 추출하여 결합도를 낮추는 데 활용된다 [9]. + +**추출 기준 및 징후** +* 클래스 내 데이터의 특정 부분집합과 메서드의 특정 부분집합이 논리적으로 밀접하게 어울리는 경우 좋은 추출의 징후가 된다 [10]. +* 데이터의 특정 부분이 주로 함께 변경되거나, 특정 데이터들 사이에 강한 의존성이 있을 때 분리하는 것이 적절하다 [10]. 특정 데이터나 메서드를 제거했을 때 다른 필드와 메서드들이 논리적으로 의미를 잃는다면 이들을 하나의 클래스로 추출해야 한다 [10]. + +**클래스 추출 절차 (Mechanics)** +1. 클래스의 책임을 어떻게 분할할지 결정하고, 분리될 책임을 표현할 새로운 클래스를 생성한다 [11]. +2. 기존 클래스에서 새로운 클래스로의 링크(참조)를 생성한다 [11]. +3. '필드 옮기기(Move Field)'와 '메서드 옮기기(Move Method)'를 차례로 사용하여 기존 클래스의 데이터와 기능을 새 클래스로 이동시킨다 [12, 13]. +4. 각 이동 과정마다 컴파일과 테스트를 반복하여 시스템의 동작이 보존되는지 확인한다 [13]. +5. 각 클래스의 인터페이스를 검토하여 불필요한 부분을 줄이고, 새 클래스를 클라이언트에게 노출할지 여부를 결정한다 [13]. + +## ⚖️ Trade-offs & Caveats +**위임(Delegation)과 상속(Inheritance)의 선택 교환** +'클래스 추출하기(Extract Class)'와 '서브클래스 추출하기(Extract Subclass)'는 객체의 책임을 분리할 때 마주하는 위임과 상속 사이의 선택이다 [14]. 서브클래스 추출이 구현하기는 더 간단하지만, 객체가 생성된 이후에는 클래스 기반의 동작을 동적으로 변경할 수 없으며 하나의 변화 축(variation)만 표현할 수 있다는 제약이 있다 [14]. 반면, 클래스 추출하기(위임)를 사용하면 런타임에 다른 컴포넌트를 끼워 넣는 방식으로 동작을 유연하게 변경할 수 있는 이점이 있다 [14]. + +**에일리어싱(Aliasing)의 위험성** +추출된 새로운 클래스를 클라이언트에게 직접 노출(expose)하기로 결정했다면 에일리어싱의 위험을 신중하게 고려해야 한다 [15]. 클라이언트가 참조를 통해 새로운 클래스 객체의 내부 상태(예: 전화번호 객체의 지역 번호 등)를 직접 변경할 경우, 이 객체를 참조하고 있는 다른 곳에서 예기치 않은 부수 효과가 발생할 수 있다 [15]. + +**클래스 인라인하기(Inline Class)의 필요성** +클래스 추출 및 기타 리팩토링의 결과로 기존 클래스에 남은 책임과 역할이 너무 적어져서 클래스로서의 존재 가치(비용 대비 효용)를 상실할 수 있다 [3, 16]. 이러한 경우, 최적화를 위해 반대 기법인 '클래스 인라인하기'를 사용하여 남은 기능들을 다른 클래스로 다시 병합하고 빈 클래스를 삭제해야 하는 반대 급부가 발생할 수 있다 [16]. + +**인터페이스 추출의 코드 중복 완화** +'인터페이스 추출하기(Extract Interface)'를 적용할 때 발생할 수 있는 코드 중복 문제를 완화하기 위한 수단으로 클래스 추출하기가 사용된다 [17]. 공통된 동작을 클래스로 추출하여 하나의 컴포넌트로 만들고 이를 인터페이스 구현체들이 위임받아 사용하게 하면 코드 중복을 피할 수 있다 [17]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Extract Method (함수 추출하기).md b/10_Wiki/Topics/Architecture/Extract Method (함수 추출하기).md new file mode 100644 index 00000000..08e353c8 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Extract Method (함수 추출하기).md @@ -0,0 +1,29 @@ +# [[Extract Method (함수 추출하기)]] + +## 📌 Brief Summary +Extract Method(함수 추출하기)는 그룹화할 수 있는 코드 조각을 찾아 그 목적을 잘 설명하는 이름을 가진 독립된 메서드로 분리하는 가장 일반적인 리팩토링 기법입니다 [1, 2]. 이 기법은 주로 메서드가 너무 길거나, 코드의 의도를 파악하기 위해 주석이 필요한 경우에 사용됩니다 [2]. 복잡한 함수를 잘게 쪼개어 메서드 이름 자체가 문서 역할을 하도록 만들어 코드의 가독성과 테스트 용이성을 크게 높이는 것이 핵심 목적입니다 [1]. + +## 📖 Core Content +* **적용 시기 (When to apply):** 40줄을 초과하는 긴 메서드, 여러 곳에서 중복되는 로직, 또는 복잡성으로 인해 단위 테스트가 불가능한 코드를 발견했을 때 적용합니다 [1]. 또한 긴 메서드(Long Method)나 중복 코드(Duplicated Code)와 같은 코드 스멜을 해결하거나, 코드 블록에 주석을 달아야만 이해할 수 있을 때 주로 사용합니다 [2]. +* **효과 및 장점 (Why it works):** + * 메서드의 이름이 코드의 의도(How가 아닌 What)를 설명하는 문서 역할을 하여 코드 파악(Scannability)을 돕습니다 [1, 2]. + * 잘게 쪼개진 메서드는 다른 메서드에서 재사용될 확률을 높이고, 오버라이딩(Overriding)을 쉽게 만듭니다 [2]. + * 복잡했던 단위 테스트 과정을 직관적인 검증 과정으로 변환시켜 줍니다 [1]. + * 상위 수준의 메서드가 마치 일련의 주석을 읽는 것처럼 자연스럽게 읽히도록 합니다 [2]. +* **실행 절차 (Steps/Mechanics):** + 1. 메서드의 의도를 설명하는 이름으로 새로운 메서드를 생성합니다 [1, 2]. + 2. 추출할 코드를 원본 메서드에서 새 메서드로 복사합니다 [2]. + 3. 추출된 코드 내에서 원본 메서드의 지역 변수 및 매개변수를 참조하는지 확인합니다 [2]. + 4. 추출된 코드 내에서만 사용되는 임시 변수는 새 메서드 내의 임시 변수로 선언합니다 [2]. + 5. 읽기 전용으로 사용되는 지역 변수는 새 메서드의 매개변수로 전달합니다 [2]. + 6. 수정되는 지역 변수가 하나 있다면, 추출된 코드를 질의(Query) 함수로 취급하여 결과를 반환(return)받아 변수에 할당하도록 변수 스코프를 업데이트합니다 [1, 2]. + 7. 원본 메서드의 추출된 코드 부분을 새 메서드 호출로 대체한 후 전체 테스트를 실행하여 동작 보존 여부를 검증합니다 [1, 2]. + +## ⚖️ Trade-offs & Caveats +* **지역 변수와 임시 변수 처리의 어려움:** Extract Method의 가장 큰 난관은 지역 변수, 특히 임시 변수(Temp)를 처리하는 것입니다 [2]. 매개변수와 임시 변수가 많은 코드를 무작정 추출하면, 새 메서드에 너무 많은 매개변수를 전달하게 되어 오히려 원래 코드보다 가독성이 떨어질 수 있습니다 [2]. 이러한 경우에는 '임시 변수를 질의 함수로 바꾸기(Replace Temp with Query)', '매개변수 객체 도입하기(Introduce Parameter Object)' 또는 '메서드를 메서드 객체로 대체하기(Replace Method with Method Object)'를 먼저 적용하여 매개변수와 변수를 줄이는 사전 작업이 필요할 수 있습니다 [2]. +* **다중 반환 값 문제:** 추출하려는 코드가 두 개 이상의 지역 변수를 수정하여 반환해야 하는 경우 추출 작업이 매우 까다로워집니다 [2]. 이럴 때는 단일 값을 반환하도록 추출할 코드를 다르게 선택하거나, 각각을 별도의 메서드로 분리하는 것이 좋습니다 [2]. +* **과도한 단편화(Over-fragmentation) 및 결합도:** 코드를 너무 얇게 쪼개면 로직이 사방으로 흩어져 전체 흐름을 파악하기 어려워질 수 있으므로 주의해야 합니다 [1]. 또한 지역 변수에 의존하는 추출된 메서드는 미묘한 결합(Subtle coupling)을 유발할 수 있으므로 변수 스코프를 주의 깊게 모니터링해야 합니다 [1]. +* **이름 짓기의 제약:** 작게 쪼개진 메서드는 '좋은 이름'을 가질 때만 그 진가를 발휘합니다 [2]. 코드의 의도를 명확하게 드러내는 의미 있는 이름을 지을 수 없다면 해당 코드는 억지로 추출하지 않는 것이 낫습니다 [2]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Introduce Null Object (널 객체 도입하기).md b/10_Wiki/Topics/Architecture/Introduce Null Object (널 객체 도입하기).md new file mode 100644 index 00000000..7247f834 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Introduce Null Object (널 객체 도입하기).md @@ -0,0 +1,27 @@ +# [[Introduce Null Object (널 객체 도입하기)]] + +## 📌 Brief Summary +Introduce Null Object(널 객체 도입하기)는 코드 내에 반복적으로 등장하는 널(Null) 값 검사 로직을 제거하기 위해, 널 값을 대체할 수 있는 '널 객체(Null Object)'를 도입하는 리팩토링 기법이다 [1, 2]. 이 기법은 객체 지향 프로그래밍의 다형성(Polymorphism)을 활용하여 객체가 자신의 타입에 맞는 적절한 동작을 스스로 수행하게 함으로써 조건부 로직을 단순화한다 [2, 3]. 널 객체는 실제 객체와 동일한 인터페이스에 응답하여, 클라이언트가 널 값 여부를 직접 확인하지 않고도 기본 동작을 일관되게 처리할 수 있도록 돕는다 [2, 4]. + +## 📖 Core Content + +* **다형성을 통한 조건문 제거**: 시스템 전반에서 특정 객체가 존재하는지 검사한 후 메서드를 호출하는 중복 코드(`if (foo == null)`)가 많을 때 유용하다 [2]. 조건문을 다형성으로 대체하여 절차적 코드를 크게 줄이고, 객체의 행위를 단일한 위치에 집중시킬 수 있다 [5, 6]. +* **구현 방법 (Mechanics)**: + * 원본 클래스의 하위 클래스(Subclass)로 널 객체 클래스를 생성하거나 테스트 인터페이스를 활용한다 [7, 8]. + * 기존 클래스와 널 클래스에 `isNull` 연산을 추가하여 원본은 `false`를, 널 클래스는 `true`를 반환하도록 한다 [7, 9]. + * 기존에 널을 반환하던 코드가 널 객체를 반환하도록 수정하고, 기존의 `foo == null` 형태의 비교문을 `foo.isNull()`로 교체한다 [7, 10-12]. +* **주요 활용 사례**: + * 화면 표시(Display) 로직에서 수많은 인스턴스 변수의 널 여부를 확인해야 하는 복잡성을 제거할 때 사용된다 [5]. + * 테스트 환경에서 실제 데이터베이스를 사용하지 않기 위해 가짜(Missing) 데이터베이스 세션 객체로 사용된다 [13]. + * 급여 계산 시 비어있는 데이터 집합을 빈 컨테이너로 제공하여 0원으로 일괄 처리하는 등 중복된 객체 생성과 로직을 피하기 위해 활용된다 [13]. +* **싱글톤(Singleton) 패턴 적용**: 널 객체는 일반적으로 내부 상태가 변하지 않는 상수(Constant)와 같은 성격을 띠므로, 싱글톤 패턴을 이용해 단일 인스턴스로 구현하는 것이 권장된다 [4]. +* **특수 사례(Special Case) 패턴으로의 확장**: 널 객체 패턴은 더 큰 개념인 '특수 사례(Special Case)' 패턴의 일환이다 [14]. 단순히 비어있다는 것을 넘어 "알 수 없는 고객"과 "아직 입주하지 않은 새 건물(고객 없음)"을 구분하기 위해 여러 종류의 널 클래스를 별도로 구현하거나, 나중에 사용할 수 있도록 데이터를 임시로 담아두는 널 객체를 만들 수도 있다 [14, 15]. + +## ⚖️ Trade-offs & Caveats + +* **오류 발견의 어려움**: 널 객체는 실제 객체와 동일한 메시지에 정상적으로 응답하기 때문에, 널 객체가 시스템 내의 잘못된 위치에 존재하더라도 프로그램이 중단(blow up)되거나 예외를 던지지 않는다 [4]. 시스템이 겉으로는 정상적으로 동작하는 것처럼 보여, 실제로 버그가 발생했을 때 즉각적으로 발견하거나 원인을 추적하기 어려워질 수 있다 [4]. +* **제한적인 적용 조건**: 널 객체로 로직을 이동시키는 것은 '대부분의 클라이언트가 널 상황에 대해 동일한 기본 응답(동작)을 원할 때'에만 이점을 얻는다 [16]. 클라이언트마다 널 상황에서 수행해야 하는 동작이 제각각 다르다면 널 객체의 기본 동작에만 의존할 수 없으므로, 여전히 `isNull` 검사를 통해 개별적인 분기 처리를 해야 한다 [16]. +* **초기 적용의 번거로움과 복잡성**: 널 객체를 도입하는 과정은 매우 까다로울 수 있다. 널을 반환하는 모든 코드 출처와 이를 검사하는 조건문을 샅샅이 찾아내 수정해야 한다 [17]. 만약 객체가 시스템의 여러 곳으로 광범위하게 전달되고 있다면, 이를 추적하고 변경하는 것을 안전한 작은 단계로 나누어 리팩토링하기 어려울 수 있다 [17]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Legacy Code (레거시 코드).md b/10_Wiki/Topics/Architecture/Legacy Code (레거시 코드).md new file mode 100644 index 00000000..986d9e6e --- /dev/null +++ b/10_Wiki/Topics/Architecture/Legacy Code (레거시 코드).md @@ -0,0 +1,72 @@ +# [[Legacy Code (레거시 코드)]] + +## 📌 Brief Summary +마이클 페더스(Michael Feathers)는 레거시 코드를 단순히 다른 사람에게서 물려받은 코드가 아니라 "테스트가 없는 코드"로 정의합니다 [1, 2]. 잘 작성되고 객체 지향적인 코드라도 테스트가 없다면 시스템의 동작을 빠르고 검증 가능하게 변경할 수 없으므로 나쁜 코드(레거시)로 간주됩니다 [3]. 레거시 코드는 이해하기 어렵고 수정이 두려운 구조적 특징을 지니며, 이를 안전하게 개선하고 기능을 추가하려면 의존성을 끊고 테스트를 먼저 작성하는 체계적인 과정이 필수적입니다 [3-5]. + +## 📖 Core Content + +* **레거시 코드의 딜레마와 해결 알고리즘** + 레거시 코드를 안전하게 변경하려면 테스트가 필요하지만, 테스트를 작성하려면 기존 코드의 얽힌 의존성을 끊어내기 위해 코드를 먼저 수정해야 하는 모순에 직면합니다 [5, 6]. 이 딜레마를 돌파하는 표준 알고리즘은 1) 변경 지점 식별, 2) 의존성 제거, 3) 테스트 작성, 4) 기능 변경, 5) 리팩토링 순으로 진행됩니다 [6, 7]. +* **접점 (Seams)을 통한 의존성 분리** + 레거시 코드에 테스트를 적용하려면 소스 코드를 직접 편집하지 않고도 프로그램의 동작을 바꿀 수 있는 지점인 '접점(Seam)'을 찾아야 합니다 [5, 8]. 객체 지향 언어에서는 다형성을 활용하여 테스트 시 가짜 객체를 주입할 수 있는 '객체 접점(Object Seams)'이 가장 널리 사용되며, C/C++와 같은 환경에서는 전처리 접점(Preprocessing Seams)이나 링크 접점(Link Seams)을 활용하여 의존성을 우회합니다 [9-12]. +* **특성화 테스트 (Characterization Tests)와 승인 테스트** + 명세가 없거나 코드가 불투명한 레거시 환경에서는 코드가 '무엇을 해야 하는지'보다 '실제로 어떻게 동작하는지'를 있는 그대로 포착하는 것이 중요합니다. 이를 위해 현재 시스템의 동작 상태를 기록하여 향후 예기치 않은 변경(부수 효과)을 방지하는 특성화 테스트 또는 승인 테스트(Approval Testing)를 도입해야 합니다 [7, 13]. +* **시간이 부족할 때의 우회 전략 (Sprout & Wrap)** + 테스트 없는 거대한 레거시 클래스(예: 수천 줄의 코드)에 당장 테스트를 작성할 시간이 부족할 때, 위험을 최소화하며 새 기능을 추가하는 두 가지 주요 기법이 있습니다 [14]. + * **스프라우트 기법 (Sprout Technique):** 새로운 로직을 완전히 분리된 다른 곳에서 작성하고 단위 테스트를 거친 뒤, 기존 레거시 코드에서는 그 함수를 호출(삽입)만 하도록 처리합니다 [15, 16]. + * **랩 기법 (Wrap Technique):** 기존 메서드의 이름을 변경한 후, 기존 메서드와 동일한 서명을 가진 새 메서드를 만듭니다. 새 메서드 내에서 기존 메서드를 호출하면서 그 앞이나 뒤에 새로운(테스트된) 로직을 추가하여 레거시 로직을 감쌉니다 [16, 17]. +* **스크래치 리팩토링 (Scratch Refactoring)** + 파악조차 불가능한 레거시 코드를 이해하기 위한 기법입니다. 테스트 없이 자유롭게 코드를 분리하고 변수명을 바꾸는 등 실험적 리팩토링을 수행하여 코드의 구조를 파악한 후, 작업이 끝나면 반드시 변경 사항을 원래대로 롤백(Revert)해야 합니다 [18, 19]. + +## ⚖️ Trade-offs & Caveats +* **안전망 부재의 위험성:** 테스트가 완비되지 않은 상태에서 레거시 코드의 구조를 대규모로 변경하는 것은 "그물 없이 공중 곡예를 하는 것"처럼 매우 큰 위험(회귀 버그 발생 등)을 초래합니다 [2, 20]. +* **코드의 미학적 저하 발생 가능성:** 의존성을 깨고 테스트를 도입하는 수술적인(surgery) 과정에서, 단기적으로는 코드가 일시적으로 더 복잡하거나 못생겨질 수 있습니다. 하지만 이는 더 건강한 상태(테스트 가능한 상태)로 가기 위한 불가피한 트레이드오프입니다 [21]. +* **스프라우트(Sprout)와 랩(Wrap) 기법의 한계:** 시간이 부족할 때 안전하게 기능을 추가하는 훌륭한 방법이지만, 이 기법들을 남용하면 결국 원본 클래스의 크기와 복잡도를 더 증가시키는 부작용(Pitfalls)이 발생할 수 있습니다 [14, 17]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [레거시 대처 및 분석 기법] +- [[Seams (접점)]] + - 연결 이유: 레거시 코드 내에서 테스트 목적으로 동작을 변경하거나 의존성을 대체할 수 있는 구조적 틈새를 의미합니다 [8, 22]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 객체지향에서 테스트 더블(Mock, Fake)을 주입하여 레거시 코드를 안전한 격리 환경에 배치하는 원리. +- [[Characterization Tests (특성화 테스트)]] + - 연결 이유: 문서나 명세가 없는 레거시 시스템에서 코드를 변경하기 전, 현재의 실제 동작을 보호하기 위해 작성하는 안전망입니다 [13, 23-26]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 전 동작 보존을 확인하기 위한 실용적인 테스트 작성 및 회귀 방지 접근법. +- [[Scratch Refactoring (스크래치 리팩토링)]] + - 연결 이유: 복잡하게 얽힌 레거시 코드를 이해하기 위해 안전망 없이 구조를 이리저리 수정해 보고 파악한 뒤 되돌리는 지식 탐색 기법입니다 [18, 19]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 정적 분석만으로 파악하기 힘든 레거시 도메인 맥락을 동적이고 안전하게 습득하는 노하우. + +#### [우회적 기능 추가 패턴] +- [[Sprout Method (스프라우트 메서드)]] + - 연결 이유: 거대한 레거시 코드 덩어리에 직접 새로운 제어문을 추가하는 대신, 신규 로직을 독립적으로 추출해 테스트한 뒤 호출점만 추가하는 기법입니다 [14-16]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 레거시 시스템의 기술 부채 증가를 억제하면서 새로운 요구사항을 안전하게 통합하는 방법. +- [[Wrap Method (랩 메서드)]] + - 연결 이유: 기존 메서드의 앞뒤에 새로운 기능을 추가해야 할 때, 기존 메서드를 이름 변경하여 숨기고 새 래퍼(Wrapper) 메서드로 기존 동작과 신규 동작을 연결하는 기법입니다 [16, 17]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기존 코드의 내부를 훼손하지 않고 외부 인터페이스를 유지한 채 부가 기능을 안전하게 탑재하는 구조적 패턴. + +### Deeper Research Questions +- 객체지향 언어(Java, C++)와 절차적 언어(C)에서 시스템의 동작을 가로채기 위해 사용되는 접점(Preprocessing, Link, Object Seam)의 적용 방식과 그 한계는 어떻게 다른가? +- '스프라우트 기법'과 '랩 기법'을 장기간 지속적으로 적용할 경우 발생하는 아키텍처적 부작용은 무엇이며, 이를 근본적으로 해결하기 위한 후속 리팩토링 전략은 무엇인가? +- 문서화되지 않은 레거시 시스템에 특성화 테스트(Characterization Test)를 적용할 때, 의도된 비즈니스 로직과 단순한 버그를 어떻게 구분하여 테스트로 승인할 것인가? +- 거대언어모델(LLM)을 기반으로 한 AI 코딩 도구가 의존성이 극도로 얽힌 레거시 환경에서 접점을 찾아내고 테스트 코드를 자동 생성하는 데 어디까지 기여할 수 있는가? +- 마이클 페더스가 제안한 "의존성 제거 후 테스트 작성"의 일반적 알고리즘을 적용조차 하기 힘든 '거대 괴물 메서드(Monster Method)'를 분해하는 효과적인 단계적 접근법은 무엇인가? + +### Practical Application Contexts +- **Implementation:** 긴급한 릴리즈 일정이 잡힌 상황에서 레거시 로직 사이에 새로운 요구사항을 구현해야 할 때, 기존 함수를 오염시키지 않고 스프라우트(Sprout) 메서드를 활용하여 격리된 테스트 코드를 동반한 구현을 진행합니다. +- **System Design:** 초기 시스템 설계 시, 나중에 레거시 코드가 되더라도 쉽게 테스트를 붙일 수 있도록 인스턴스 주입이 용이한 객체 접점(Object Seams)을 적극 반영한 의존성 역전 구조를 설계합니다. +- **Operation / Maintenance:** 유지보수 담당자가 인수인계받지 못한 오래된 모듈을 분석할 때, 스크래치 리팩토링 기법을 적용해 로컬 환경에서 코드를 해체하고 재조립하며 도메인 지식을 확보한 뒤, 코드를 원상 복구합니다. +- **Learning Path:** 리팩토링 기법(예: 마틴 파울러의 기법)을 실제 레거시 프로젝트에 적용하기 전, 마이클 페더스의 "레거시 코드 다루기"를 먼저 학습하여 안전망(테스트)을 먼저 구축하는 기술을 습득해야 합니다. +- **My Project Relevance:** 현재 유지보수 중인 테스트가 전혀 없는 수천 줄의 코드베이스에 리팩토링을 적용하기 전에, 데이터베이스 및 외부 서비스와의 의존성을 끊어내고 특성화 테스트를 구축하는 단계적 접근에 활용할 수 있습니다. + +### Adjacent Topics +- [[Technical Debt (기술 부채)]] + - 확장 방향: 레거시 코드가 쌓이게 된 근본 원인이자 결과인 기술 부채를 정량적으로 측정하고, 장기적으로 상환 및 관리하기 위한 조직적, 기술적 접근법으로 개념을 확장합니다. +- [[Test-Driven Development (TDD)]] + - 확장 방향: 레거시 코드를 수정할 때 TDD의 Red-Green-Refactor 사이클을 부분적으로 차용하는 방법을 넘어, 향후 작성하는 코드가 다시 레거시(테스트 없는 코드)로 전락하지 않게 하는 필수 개발 방론으로 탐구합니다. +- [[Code Smells (코드 스멜)]] + - 확장 방향: 레거시 코드 내부를 진단할 때, 복잡성과 유지보수 어려움을 암시하는 구조적 지표들(예: 데이터 뭉치, 거대 클래스)을 식별하고 이를 구체적인 리팩토링 카탈로그와 매핑하여 학습을 확장합니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Link Seam (링크 접점).md b/10_Wiki/Topics/Architecture/Link Seam (링크 접점).md new file mode 100644 index 00000000..377301cc --- /dev/null +++ b/10_Wiki/Topics/Architecture/Link Seam (링크 접점).md @@ -0,0 +1,19 @@ +# [[Link Seam (링크 접점)]] + +## 📌 Brief Summary +링크 접점(Link Seam)은 소스 코드를 직접 수정하지 않고 프로그램의 동작을 변경하거나 대체할 수 있는 접점(Seam)의 한 종류로, 프로그램 빌드 프로세스의 링킹(Linking) 단계에서 발생합니다 [1]. 언어의 컴파일 또는 빌드 시스템이 외부 코드를 연결하는 방식을 활용하여, 테스트 시 프로덕션 코드 대신 테스트용 스텁(Stub)이나 가짜(Fake) 라이브러리를 연결하도록 유도합니다 [1, 2]. 이는 특히 타사 라이브러리처럼 변경하기 어려운 전역적인 의존성을 끊어내고 코드를 독립적으로 테스트할 수 있게 만드는 데 유용하게 활용됩니다 [2]. + +## 📖 Core Content +* **동작 원리와 언어별 구현 방식:** 링크 접점은 소스 코드가 중간 표현(Intermediate Representation)으로 컴파일된 후, 링커(Linker)나 동적 링킹 시스템에 의해 외부 참조가 해결되는 과정을 이용합니다 [1]. + * **C/C++의 정적 링킹(Static Linking):** C나 C++과 같이 별도의 링커를 사용하는 언어에서는 대체하고자 하는 클래스나 함수를 위한 별도의 테스트용 라이브러리를 생성한 뒤, 빌드 스크립트를 수정하여 프로덕션 라이브러리 대신 해당 테스트 라이브러리를 링크하는 방식으로 구현할 수 있습니다 [1, 2]. + * **Java의 동적 링킹(Dynamic Linking):** Java와 같은 언어에서는 컴파일러가 이면에서 링킹을 수행합니다 [1]. 이 경우 `classpath` 환경 변수를 조작하여 시스템이 원래의 클래스 대신 다른 디렉토리에 배치된 동명(同名)의 테스트용 클래스를 먼저 찾도록 함으로써 링크 접점을 활용할 수 있습니다 [3, 4]. +* **활성화 지점 (Enabling Point):** 링크 접점의 동작을 결정짓는 활성화 지점은 항상 프로그램 소스 코드 **외부**에 위치합니다 [5]. 환경 변수 설정(`classpath` 등), 빌드 스크립트(makefile 등), 또는 배포 스크립트 등 코드 밖의 구성 요소를 통해 테스트용 동작과 프로덕션 동작 중 어떤 것을 사용할지 결정합니다 [4-6]. +* **의존성 분리와 감지 (Separation and Sensing):** 코드베이스 전반에 걸쳐 타사 그래픽 라이브러리와 같은 의존성이 깊게 퍼져 있을 때 링크 접점은 매우 강력한 도구가 됩니다 [2, 7]. 테스트를 위해 단순히 아무 동작도 하지 않는 빈 함수를 만들어 의존성을 분리(Separation)할 수도 있고, 더 나아가 큐(Queue)와 같은 데이터 구조를 추가해 함수 호출 내역과 전달된 파라미터를 기록함으로써 코드의 내부 상태를 테스트에서 검증할 수 있도록 감지(Sensing)하는 용도로도 활용할 수 있습니다 [2, 8]. + +## ⚖️ Trade-offs & Caveats +* **인지의 어려움:** 링크 접점의 활성화 지점이 소스 코드 텍스트 외부(예: 빌드 스크립트나 실행 환경)에 존재하기 때문에, 개발자가 이러한 접점이 사용되고 있다는 사실을 명확히 알아차리기 어려울 수 있습니다 [5]. +* **환경 관리의 복잡성:** 링크 접점을 사용할 때는 테스트 환경과 프로덕션 환경 간의 차이를 매우 명확하게 관리해야만 배포나 테스트 과정에서 혼란이 발생하는 것을 방지할 수 있습니다 [9]. +* **유지보수의 한계와 사용 원칙:** 객체 지향 언어에서는 객체 접점(Object Seam)이 가장 명시적이고 권장되는 방식입니다 [10]. 링크 접점이나 전처리 접점(Preprocessing Seam)은 객체 접점에 비해 명시적이지 않으며, 이에 의존하는 테스트 코드는 유지보수하기 까다로울 수 있습니다 [10]. 따라서 이러한 접점들은 의존성이 시스템 전반에 퍼져 있어 객체 접점을 사용하기 등 더 나은 대안이 없는 경우에 한해 제한적으로 사용해야 합니다 [10]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Mock Objects (가짜 객체).md b/10_Wiki/Topics/Architecture/Mock Objects (가짜 객체).md new file mode 100644 index 00000000..c841ff2a --- /dev/null +++ b/10_Wiki/Topics/Architecture/Mock Objects (가짜 객체).md @@ -0,0 +1,70 @@ +# [[Mock Objects (가짜 객체)]] + +## 📌 Brief Summary +Mock Objects(가짜 객체)는 단위 테스트(Unit Test)를 수행할 때 완벽한 격리(Isolation)를 달성하고 사이드 이펙트를 방지하기 위해 실제 협력 객체(Collaborator)를 대체하는 테스트 대역(Test Double)의 일종입니다 [1, 2]. 테스트를 위해 실제 시스템의 구성 요소처럼 보이게 만들고, 테스트 작성자가 미리 정의해 둔 응답(canned responses)을 반환하도록 설정됩니다 [2]. 리팩토링의 맥락에서는 특히 레거시 코드에서 복잡한 외부 자원(DB, 네트워크 등)의 의존성을 끊어내고 안전한 테스트 환경을 구축하는 데 핵심적인 역할을 수행합니다 [3, 4]. + +## 📖 기Core Content +* **테스트 격리와 부수 효과(Side Effect) 방지** + 단위 테스트 작성 시, 테스트 대상이 되는 코드가 데이터베이스 접근이나 네트워크 호출 등 느리거나 부수 효과가 큰 클래스를 호출할 때, 이를 Mock 객체나 Stub(스텁)으로 대체합니다 [1]. 이를 통해 복잡한 테스트 데이터 설정 없이 시스템을 완벽하게 격리(solitary)하여 테스트할 수 있습니다 [1, 2]. + +* **레거시 코드에서의 의존성 분리 도구** + 테스트가 없는 레거시 시스템을 리팩토링하기 위해서는 먼저 테스트를 작성하여 기존 동작을 기록해야 합니다 [4]. 마이클 페더스(Michael Feathers)는 코드를 직접 수정하지 않고도 프로그램의 동작을 바꿀 수 있는 '접점(Seam)'을 찾아, 외부 API 호출 등을 인터페이스로 추상화하고 테스트 시에 가짜 객체(Mock)를 주입하는 방식을 강조합니다 [3]. 이러한 방식으로 무거운 자원의 의존성을 제거하고 가벼운 대체물로 갈아 끼워 테스트라는 안전망을 확보한 뒤에 리팩토링을 수행해야 합니다 [4]. + +* **테스트의 구조화 및 예측 가능성 향상** + Mock 객체는 실제 생산 코드에 사용될 객체를 가짜로 교체하여, 예상 가능한 반응을 미리 정의할 수 있게 해줍니다 [2]. 예를 들어, Mockito 같은 프레임워크를 이용하면 데이터베이스 저장소 대신 스텁을 사용하여 대상 메서드가 특정 파라미터를 받을 때 정의된 응답을 반환하도록 설정할 수 있으며, 이는 테스트를 단순하고 예측 가능하게 만듭니다 [5]. + +* **AI 기반 테스트 생성에서의 활용** + 최근 LLM(거대언어모델)을 통한 자동 테스트 생성 시에도 Mock 객체가 중요하게 다뤄집니다. 연구에 따르면 모델이 공통의 설정 로직, 표준화된 Mocking 패턴, 그리고 테스트 데이터와 로직의 분리를 적절히 활용하는 인프라를 자가 생성하기도 하며, 요청 가로채기에 기반한 API Mocking 계층을 구성하여 코드의 재사용성을 높인 사례가 있습니다 [6, 7]. + +## ⚖️ Trade-offs & Caveats +* **구현 세부 사항과의 과도한 결합 (Brittleness)** + 단위 테스트가 프로덕션 코드의 내부 구현에 너무 밀접하게 연관되면(예: 지나치게 많은 Mocking 사용), 코드를 리팩토링할 때마다 테스트가 깨지는 취약성이 발생할 수 있어 유지보수가 번거로워집니다 [8]. +* **Mock과 Stub의 용어 혼용** + Mock과 Stub은 서로 다른 종류의 테스트 대역(Test Double)이지만, 개발자들 사이에서 종종 용어가 엄격히 구분되지 않고 혼용되어 사용되므로 주의가 필요합니다 [2]. +* **AI 테스트 생성 시의 한계** + LLM을 활용해 자동화된 테스트를 작성할 때 모델이 충분한 Mocking 능력을 보이지 못하는(insufficient mocking capabilities) 문제점이 보고된 바 있습니다 [9]. 이를 방지하기 위해 내부 훅(internal hooks)에 대한 Mocking을 금지하는 등의 명시적인 프롬프트 및 규칙(rule file) 설정이 필요할 수 있습니다 [10]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [테스트 및 품질 관리 아키텍처] +- [[Test Doubles (테스트 대역)]] + - 연결 이유: Mock Objects와 Stub 모두 테스트 대역의 구체적인 구현 형태이기 때문입니다 [2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 실제 코드를 흉내 내어 테스트 환경을 구축하는 원리와 객체 간의 격리 메커니즘. + +- [[Seam (접점)]] + - 연결 이유: 레거시 코드 리팩토링 시, 코드를 직접 수정하지 않고 Mock 객체를 주입하여 동작을 변경할 수 있는 위치를 의미합니다 [3]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 외부 의존성을 단절시키고 테스트 가능성을 높이는 소프트웨어 설계 기법 및 리팩토링의 준비 과정. + +- [[Unit Tests (단위 테스트)]] + - 연결 이유: Mock Objects는 가장 작은 단위의 코드를 검증하는 단위 테스트의 의존성 제거를 위해 집중적으로 사용됩니다 [1, 11]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트 피라미드의 기초이자, 빠른 리팩토링 피드백 루프를 달성하기 위한 기본 환경. + +#### [구현/활용 도구] +- [[Mockito]] + - 연결 이유: Java 환경에서 실제 클래스를 대체하여 깡통 응답(canned responses)을 정의하는 Stub/Mock을 생성할 때 널리 쓰이는 표준 프레임워크입니다 [5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 코드 상에서 Mock 객체를 실제로 설정하고, 'Arrange, Act, Assert' 패턴을 적용하는 구체적 구현 방식. + +### Deeper Research Questions +- Mock 객체와 Stub 객체의 구조적, 행동적 차이점은 정확히 무엇이며 각각 어떤 테스트 상황에서 선택하는 것이 가장 효율적인가? +- 레거시 코드 시스템에서 접점(Seam)을 찾아내고 기존 구조를 망가뜨리지 않으면서 Mock 객체를 안전하게 주입할 수 있는 리팩토링 패턴은 무엇인가? +- 내부 구현과 밀접하게 결합된 과도한 Mocking이 리팩토링 시 테스트를 깨지게 만드는 취약성(Brittleness)을 어떻게 최소화할 수 있는가? +- AI 코딩 에이전트에게 복잡한 의존성 구조의 코드를 주고 Mocking 기반의 단위 테스트 생성을 지시할 때, 할루시네이션을 막고 정확도를 높이기 위한 프롬프팅 전략은 무엇인가? +- 시스템의 모든 의존성을 Mocking하는 고립형(Solitary) 단위 테스트와 실제 객체를 사용하는 협력형(Sociable) 테스트 사이의 밸런스를 어떻게 조정해야 하는가? + +### Practical Application Contexts +- **Implementation:** Mockito와 같은 프레임워크를 도입하여 의존 객체를 스텁(stub)으로 변환하고, 특정 입력 시나리오에 대해 예상되는 결과값을 정의해 단위 테스트를 구현합니다 [5]. +- **System Design:** 테스트 시점에 진짜 자원(DB 등) 대신 가짜 객체를 주입할 수 있도록, 외부 서비스나 데이터베이스 호출부를 인터페이스로 추상화하여 결합도를 낮추는 아키텍처를 설계합니다 [3, 4]. +- **Operation / Maintenance:** 네트워크 연결이나 무거운 데이터베이스 로딩 없이 단위 테스트를 빠르게 실행할 수 있는 독립적 환경을 조성하여, 리팩토링이나 새로운 기능 추가 시 회귀 버그를 즉각 파악하는 지속적인 유지보수를 지원합니다 [1, 3, 12]. +- **Learning Path:** TDD 및 단위 테스트의 기본 구조(Arrange, Act, Assert) 학습 → 테스트 대역(Test Doubles) 개념 이해 → 레거시 코드의 의존성 제거(Seam) 훈련 → 안전하고 과감한 리팩토링 수행 순서로 나아갑니다 [2, 3, 13]. +- **My Project Relevance:** 방대하게 얽혀 있는 레거시 코드를 리팩토링하기 위해, 먼저 외부 API와 통신하는 부분의 의존성을 끊고 Mock 객체로 테스트 안전망을 구축하여 구조 개선의 위험을 최소화합니다 [3, 4]. + +### Adjacent Topics +- [[Test-Driven Development (TDD)]] + - 확장 방향: 테스트를 먼저 작성한 후 코드를 구현하고 리팩토링(Red-Green-Refactor)하는 반복 개발 방식에 대한 이해 [14]. +- [[Technical Debt (기술 부채)]] + - 확장 방향: 테스트 없이 작성되어 의존성이 엉켜있는 레거시 코드가 시스템의 유지보수성을 어떻게 떨어뜨리는지(엔트로피 증가), 이를 리팩토링으로 상환하는 개념에 대한 탐구 [15]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Mocking and Stubbing (테스트 대역).md b/10_Wiki/Topics/Architecture/Mocking and Stubbing (테스트 대역).md new file mode 100644 index 00000000..0d2eccbe --- /dev/null +++ b/10_Wiki/Topics/Architecture/Mocking and Stubbing (테스트 대역).md @@ -0,0 +1,18 @@ +# [[Mocking and Stubbing (테스트 대역)]] + +## 📌 Brief Summary +Mocking과 Stubbing은 프로덕션 환경에서 사용하는 실제 객체를 테스트 목적에 맞게 구현된 가짜 객체로 대체하는 '테스트 대역(Test Doubles)'의 두 가지 다른 종류이다 [1]. 이는 실제 클래스, 모듈, 함수를 대신하여 테스트 시작 시 미리 정의된 응답(canned responses)을 반환하도록 설정된다 [2]. 이를 통해 개발자는 데이터베이스나 네트워크 통신과 같이 속도가 느리거나 부수 효과가 큰 의존성을 분리하고, 복잡한 테스트 설정을 피해 코드를 통제된 환경에서 완벽히 격리하여 테스트할 수 있다 [2, 3]. + +## 📖 Core Content +* **테스트 대역(Test Doubles)의 정의 및 역할**: Mock과 Stub은 실제 컴포넌트와 동일한 서명(signature)을 가지면서도 테스트 시나리오에 맞춰 조작된 응답을 제공하는 가짜(fake) 버전의 컴포넌트이다 [2]. 단위 테스트뿐만 아니라 시스템의 특정 부분을 통제된 방식으로 시뮬레이션하기 위해 폭넓게 활용되며, 개발자는 Mockito나 Wiremock과 같은 서드파티 라이브러리를 통해 의존성을 손쉽게 Mocking하거나 외부 서비스를 Stubbing할 수 있다 [2, 4]. +* **격리(Isolation)와 의존성 분리**: 테스트를 작성할 때 외부 협력자(collaborator)로 인해 부수 효과가 발생하거나 설정이 복잡해지는 것을 막기 위해 테스트 대역을 사용한다 [3]. 특히 마이클 페더스(Michael Feathers)의 레거시 코드 리팩토링 전략에서는 테스트를 방해하는 무거운 외부 자원(DB, 네트워크 등)에 대한 직접적인 호출을 추상화하여, 테스트 시에 가짜 객체(Mock)를 주입할 수 있도록 '접점(Seams)'을 확보하는 것을 의존성 제거의 핵심으로 본다 [5, 6]. +* **테스트의 단순화 및 예측 가능성 확보**: Stubbing을 활용하면 테스트 데이터를 쉽게 설정할 수 있으며, 테스트가 훨씬 단순해지고 예측 가능해진다 [7]. 예를 들어, 서드파티 API(예: 날씨 정보 제공 서비스)와의 연동을 테스트할 때 실제 서버 대신 Fake 서버를 구축하여 테스트를 수행하면, 외부 서버의 할당량 제한이나 서버 점검 등의 가용성에 영향을 받지 않고 애플리케이션의 응답 파싱 능력을 독립적으로 검증할 수 있다 [8]. +* **LLM을 활용한 테스트 생성에서의 활용**: AI를 활용한 테스트 스위트(Test Suite) 구축 사례에서도 요청 가로채기(request interception)를 기반으로 한 API Mocking 계층을 중앙 집중화하여 중복을 줄이고 테스트 인프라의 안정성을 높인 바 있다 [9]. + +## ⚖️ Trade-offs & Caveats +* **고립(Solitary) vs. 사교적(Sociable) 테스트의 트레이드오프**: 모든 협력자를 Mock과 Stub으로 대체하여 완벽한 격리를 추구할 것인지(Solitary), 아니면 부수 효과가 큰 데이터베이스나 네트워크 호출 등 외곽 부분만 Stubbing하고 나머지 실제 협력자의 상호작용은 허용할 것인지(Sociable)에 대한 논쟁이 존재한다 [3]. 실제 객체와의 상호작용을 모두 Mocking하게 되면, 실제 협력자를 포함했을 때 얻을 수 있는 테스트의 통합적 신뢰성을 잃게 될 위험이 있다 [10]. +* **가짜 객체(Test Double)의 신뢰성 한계 및 거짓 양성(False Positives)**: 외부 REST API 등을 Stubbing하여 가짜 서버(Fake server)를 만들 경우, 실제 외부 서비스의 API 스펙이 변경되더라도 가짜 서버를 바라보는 내부 테스트는 여전히 통과(Green)하는 취약점이 발생할 수 있다 [11]. 즉, 가짜 서버가 실제 서버의 동작을 완벽히 대변(faithful test double)하지 못할 위험이 있다 [11]. 이 제약을 극복하기 위해서는 가짜 서버와 실제 서버 모두에 대해 '계약 테스트(Contract Tests)'를 실행하여 Stub이 실제 스펙과 일치하는지 지속적으로 검증해야 한다 [11]. +* **AI 생성 테스트의 한계**: 거대 언어 모델(LLM)을 활용하여 자동 생성된 테스트의 경우, 일반적인 기법에 비해 문장 및 분기 커버리지는 우수하지만 여전히 Mocking 기능이 불충분하거나 테스트 자체가 깨지기 쉬운(brittleness) 문제가 발생할 수 있다 [12]. 이러한 한계는 프롬프팅 전략의 개선 등을 통해 보완해야 한다 [12]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Mockito.md b/10_Wiki/Topics/Architecture/Mockito.md new file mode 100644 index 00000000..a2cab667 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Mockito.md @@ -0,0 +1,15 @@ +# [[Mockito]] + +## 📌 Brief Summary +Mockito는 소프트웨어 테스트 작성 시 의존성을 모의(mocking)하기 위해 사용되는 도구 및 라이브러리입니다 [1]. 테스트 환경에서 실제 클래스를 스텁(stub)이나 모의 객체(mock)로 교체하여 테스트를 단순하고 예측 가능하게 만듭니다 [2, 3]. 이를 통해 개발자는 미리 정의된 상태에 대한 응답과 테스트 데이터를 쉽게 설정할 수 있습니다 [2, 3]. + +## 📖 Core Content +* **의존성 대체 및 스텁(Stubbing) 활용**: 단위 테스트(Unit Test)를 작성할 때 Mockito를 사용하면 실제 운영에 사용되는 클래스(예: `PersonRepository`)를 테스트용 스텁으로 대체할 수 있습니다 [2]. +* **응답 제어와 테스트 단순화**: 개발자는 스텁 처리된 메서드가 테스트 중에 반환해야 할 '미리 준비된 응답(canned responses)'을 Mockito를 통해 정의할 수 있습니다 [2]. 이러한 방식은 테스트 데이터 설정을 용이하게 하며, 테스트를 더욱 단순하고 예측 가능하게 만들어 줍니다 [2]. +* **계약 테스트(Contract Test)에서의 활용**: 제공자 테스트(Provider Test)를 구현할 때, 사전에 정의된 상태(pre-defined states)에 맞게 테스트 데이터를 제공하는 방식을 정의하는 데에도 Mockito의 모의 객체(mocks)가 활용됩니다 [3]. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. (Mockito 도구 자체를 선택하거나 최적화할 때 발생하는 구체적인 부작용이나 제약 사항은 제공된 소스 데이터에 포함되어 있지 않습니다.) + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Null Object Pattern (널 객체 패턴).md b/10_Wiki/Topics/Architecture/Null Object Pattern (널 객체 패턴).md new file mode 100644 index 00000000..c05397d6 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Null Object Pattern (널 객체 패턴).md @@ -0,0 +1,64 @@ +# [[Null Object Pattern (널 객체 패턴)]] + +## 📌 Brief 시 Summary +널 객체 패턴은 객체가 null인지 반복적으로 확인하는 조건문을 제거하기 위해, null 값을 널 객체(Null Object)로 대체하는 리팩토링 기법이다 [1]. 다형성(Polymorphism)을 활용하여 객체의 타입이나 null 여부를 묻는 대신 객체에 직접 동작을 호출하게 함으로써 절차적인 코드를 크게 줄일 수 있다 [2, 3]. 널 객체는 일반적으로 프로그램에 오류를 발생시키지 않는 안전한 기본 동작을 제공하여 시스템이 정상적으로 작동하도록 돕는다 [4]. + +## 📖 Core Content +* **다형성을 통한 조건문 제거:** 코드베이스 전체에 흩어져 있는 `if (obj == null)` 형태의 반복적인 확인을 제거하고 이를 널 객체로 대체하여 방대한 양의 절차적 코드를 줄일 수 있다 [2, 3]. 객체가 존재하는지 묻는 대신, 다형성을 통해 널 객체에 바로 메시지를 보내 적절한 기본 동작(예: 요금 0원 반환 등)을 수행하게 한다 [2, 3]. +* **불변성과 싱글톤 패턴 활용:** 널 객체는 항상 상수(Constant) 상태를 유지하며, 어떠한 속성도 결코 변하지 않는다 [4]. 상태가 변하지 않기 때문에 메모리 낭비를 막기 위해 일반적으로 싱글톤(Singleton) 패턴을 이용하여 구현된다 [4]. +* **특수 사례 패턴(Special Case Pattern)의 일종:** 널 객체 패턴은 더 큰 범주인 '특수 사례 패턴'의 특정한 형태이다 [5]. 특수 사례 클래스는 특별한 동작을 수행하는 특정 인스턴스(예: '알 수 없는 고객(UnknownCustomer)'이나 '고객 없음(NoCustomer)')로 정의되어 오류 처리를 줄여주는 역할을 한다 [5, 6]. +* **연쇄적인 널 객체 반환:** 널 객체의 접근자(Accessor)를 호출하면 또 다른 널 객체를 반환하는 경우가 많다 [5, 7]. 예를 들어, 널 고객 객체에게 결제 내역을 요청하면 다시 '널 결제 내역 객체'를 반환하여 안전한 처리를 이어간다 [7]. + +## ⚖️ Trade-offs & Caveats +* **버그 탐지의 어려움:** 널 객체는 실제 객체와 동일한 메시지에 응답하도록 구현되므로, 시스템이 중단되거나 터지는(blow up) 일이 거의 없이 정상적으로 행동하는 것처럼 보인다 [4]. 이로 인해 실제로는 객체가 없어서 발생한 논리적 오류나 문제를 탐지하고 원인을 찾아내기 매우 어려워질 수 있다 [4]. +* **기본 동작의 한계:** 널 객체로 동작을 이동시키는 것은 대부분의 클라이언트가 null에 대해 동일한 응답(기본 동작)을 원할 때만 유용하다 [8]. 소수의 클라이언트가 이와 다른 응답을 원한다면 널 객체 내에서 처리하기 어려우며, 여전히 `isNull`과 같은 검사를 개별적으로 수행해야 한다 [8]. +* **수정의 번거로움:** `foo == null` 형태의 검사를 `foo.isNull()` 형태로 대체하고 다양한 소스의 null 값을 널 객체로 바꾸는 과정은 여러 곳에 산재된 변수를 추적해야 하므로 변경을 작게 나누어 진행하기 까다롭고 번거로운(messy) 작업이 될 수 있다 [9, 10]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [아키텍처/기반 기술] +- [[Polymorphism (다형성)]] + - 연결 이유: 널 객체 패턴은 객체의 타입을 묻는 대신 다형성을 이용해 직접 동작을 호출하도록 설계하는 핵심 원리를 바탕으로 하기 때문이다 [2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 조건문을 다형성으로 바꾸는 리팩토링의 근본적인 목적과, 인터페이스에 의존하는 객체지향 설계의 본질. + +- [[Special Case Pattern (특수 사례 패턴)]] + - 연결 이유: 널 객체 패턴은 예외적이거나 유효하지 않은 상태를 특별한 클래스로 모델링하여 오류 처리를 줄이는 '특수 사례 패턴'의 일종이기 때문이다 [5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 단순한 null을 넘어 다양한 특수 상태(예: 무한대 숫자, NaN 등)를 객체로 다루어 예외 상황을 일관되게 관리하는 방법 [5]. + +#### [구현/활용 도구] +- [[Introduce Null Object (널 객체 도입하기)]] + - 연결 이유: 널 객체 패턴을 실제 코드베이스에 적용하기 위해 사용하는 구체적인 리팩토링 기법이다 [1]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: null 확인 조건문을 찾아 널 클래스의 서브클래스를 생성하고 `isNull` 메서드를 추가하여 기존 코드를 안전하게 대체하는 리팩토링 절차 [9, 11]. + +- [[Singleton Pattern (싱글톤 패턴)]] + - 연결 이유: 널 객체는 그 상태가 절대 변하지 않는 상수 특성이 있어 다수의 인스턴스를 만들 필요가 없으므로 싱글톤으로 생성되기 때문이다 [4]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 널 객체의 메모리 효율성을 높이고 전역적으로 안전하게 접근하는 객체 생성 패턴. + +### Deeper Research Questions + +- 널 객체 패턴을 적용했을 때 시스템이 오류를 내지 않고 조용히 실패하여 버그 추적이 어려워지는 현상을 예방하거나 모니터링할 수 있는 구체적인 로깅 전략은 무엇인가? +- 널 객체가 또 다른 널 객체를 연쇄적으로 반환하도록 구현할 때, 시스템의 복잡도가 오히려 증가하거나 의도치 않은 논리적 오류를 발생시킬 부작용은 없는가? +- 모든 클라이언트가 널(Null) 상황에서 동일한 기본 동작을 원하지 않을 때, 널 객체 패턴의 일관성과 `isNull` 검사를 통한 개별 처리 로직을 어떻게 설계적으로 조화시킬 수 있는가? +- 특수 사례 패턴(Special Case Pattern)과 널 객체 패턴(Null Object Pattern)을 구분 짓는 정확한 설계적 경계는 어디에 있으며, 실무에서 어떤 기준으로 두 패턴을 선택해야 하는가? +- 레거시 코드베이스에서 대규모로 널 객체 패턴을 도입할 때, 시스템 전반에 퍼진 null 참조를 안전하게 식별하고 단계적으로 교체하기 위한 자동화된 도구나 접근법은 무엇인가? + +### Practical Application Contexts + +- **Implementation:** 소스 코드 내에 반복되는 `if (obj == null)` 조건문을 제거하고 `obj.doSomething()`으로 통일되게 작성하여 절차적 코드를 대폭 줄일 때 활용된다. +- **System Design:** 도메인 모델에서 데이터가 없거나 유효하지 않은 상태(예: MissingPerson, MissingBin 등)를 나타내는 안전한 대체 객체를 설계하여 도메인의 무결성을 높일 때 사용된다. +- **Operation / Maintenance:** Null 참조 예외로 인해 프로그램이 예기치 않게 강제 종료되는 상황을 방지하고 일관된 기본 동작을 보장하여 시스템의 안정성을 유지하는 데 도움이 된다. +- **Learning Path:** 리팩토링 원칙 학습 시 '조건부 로직 분해(Decompose Conditional)' -> '다형성 활용(Replace Conditional with Polymorphism)' -> '널 객체 도입(Introduce Null Object)'의 흐름으로 복잡성을 줄이는 방법을 익힐 수 있다. +- **My Project Relevance:** 모듈이나 서비스에서 반환값이 없는 경우, 예외를 던지거나 null을 반환하는 대신 널 객체를 반환하여 클라이언트 코드에서 불필요한 null 검증 로직이 작성되지 않도록 하는 데 적용할 수 있다. + +### Adjacent Topics + +- [[Replace Error Code with Exception (에러 코드를 예외로 바꾸기)]] + - 확장 방향: 예외적 상황을 처리할 때 널 객체를 반환하는 방식과 오류 코드나 예외(Exception)를 사용하는 방식을 비교하고 언제 어떤 기법을 선택할지 트레이드오프를 탐구한다 [12]. + +- [[Test-Driven Development (테스트 주도 개발)]] + - 확장 방향: 널 객체가 실제 객체와 동일하게 행동함을 보장하기 위해 리팩토링 과정에서 동작을 보존하는 테스트 케이스를 어떻게 구축하는지 확장하여 알아본다 [2, 11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Object Seam (객체 접점).md b/10_Wiki/Topics/Architecture/Object Seam (객체 접점).md new file mode 100644 index 00000000..53bc2871 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Object Seam (객체 접점).md @@ -0,0 +1,23 @@ +# [[Object Seam (객체 접점)]] + +## 📌 Brief Summary +객체 접점(Object Seam)은 소스 코드를 직접 수정하지 않고도 프로그램의 동작을 변경하거나 대체할 수 있는 코드 내의 특정 지점을 의미한다 [1-3]. 객체지향 프로그래밍 언어에서 활용할 수 있는 가장 명시적이고 유용한 접점 유형으로, 다형성(Polymorphism)이나 상속을 이용하여 실제 실행되는 객체나 메서드를 외부에서 제어할 수 있게 해준다 [4, 5]. 레거시 시스템을 리팩토링할 때 기존 의존성을 끊어내고 가짜 객체(Mock)를 주입하여 안전한 테스트 환경을 확보하는 핵심 수단으로 사용된다 [1, 3, 6]. + +## 📖 Core Content +* **접점(Seam)과 활성화 지점(Enabling Point)의 개념** + 접점은 코드를 직접 편집하지 않고도 해당 위치에서의 동작을 바꿀 수 있는 곳을 뜻하며, 모든 접점에는 어떤 동작을 사용할지 결정할 수 있는 '활성화 지점(Enabling Point)'이 존재한다 [7]. 객체 접점에서는 주로 메서드의 매개변수 목록이나 객체를 생성하는 지점이 활성화 지점 역할을 한다 [8, 9]. +* **객체 접점의 작동 원리와 식별** + 객체지향 프로그램에서는 메서드 호출이 실제로 어떤 메서드의 실행으로 이어질지 고정되어 있지 않다 [4, 10]. 예를 들어 `cell.Recalculate();`라는 코드는 `cell`이 어떤 인스턴스인지에 따라 다르게 동작한다 [10]. 주변 코드를 바꾸지 않고 이 호출의 대상을 바꿀 수 있다면 그것이 바로 객체 접점이다 [10]. 반대로, 메서드 내부에서 직접 특정 클래스의 인스턴스를 생성하고 바로 사용하는 경우에는 코드를 수정하지 않고는 대상 객체를 바꿀 수 없으므로 접점이 아니다 [11]. +* **테스트 격리를 위한 객체 접점 생성 기법** + * **매개변수화:** 내부에서 직접 생성하던 객체를 매개변수로 전달받도록(`buildMartSheet(Cell cell)`) 수정하면, 호출 시 테스트용 객체를 넘겨줄 수 있는 접점이 형성된다 [12]. + * **상속과 오버라이드(Override):** 외부 데이터베이스 연결이나 API를 호출하는 문제가 되는 클래스가 있다면, 이를 상속받는 테스트용 하위 클래스를 만들어 해당 메서드를 오버라이드하거나 가짜 객체를 주입하도록 코드를 비틀 수 있다 [1, 3, 13]. + * **정적(Static) 메서드 변환:** 정적 메서드를 호출하는 부분은 본래 객체 접점이 아니지만, `static` 키워드를 제거하고 `protected` 등의 접근 제어자로 바꾸어 가상(virtual) 메서드처럼 만들면 테스트 시 하위 클래스에서 오버라이드하여 접점화할 수 있다 [14, 15]. +* **객체지향 환경에서의 권장성** + 객체 접점은 객체지향 언어에서 가장 권장되는 접근법이다 [5]. C나 C++에서 사용하는 전처리(Preprocessing) 접점이나 링크(Link) 접점도 존재하지만, 객체 접점이 훨씬 명시적이며 이에 의존하는 테스트가 유지보수하기 더 쉽기 때문이다 [5]. + +## ⚖️ Trade-offs & Caveats +* **우회(Indirection)로 인한 구조적 부자연스러움:** 기존 코드를 건드리지 않고 동작을 변경할 수 있게 하려면 결국 활성화 지점을 만들기 위해 코드 구조를 약간 비틀거나 간접화(Indirection)해야 한다 [15, 16]. 정적 메서드를 인스턴스 메서드로 바꾸거나 하위 클래스를 억지로 만드는 등의 과정이 다소 간접적이고 부자연스럽게 느껴질 수 있다 [15]. +* **레거시 환경에서의 현실적 타협:** 마음에 들지 않는 의존성을 소스 코드에서 직접 삭제하거나 통째로 고치고 싶을 수 있지만, 테스트가 완비되지 않은 엉망인 레거시 코드에서는 그 자체로 거대한 위험을 초래한다 [15]. 따라서 이상적이고 완벽한 설계로 한 번에 가려고 하기보다는, 다소 우회적일지라도 객체 접점을 이용해 테스트를 안전하게 배치할 수 있을 정도로만 코드를 최소한으로 타협하고 수정하는 것이 훨씬 바람직하다 [3, 15]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Open-Closed Principle (개방-폐쇄 원칙).md b/10_Wiki/Topics/Architecture/Open-Closed Principle (개방-폐쇄 원칙).md new file mode 100644 index 00000000..7f32e436 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Open-Closed Principle (개방-폐쇄 원칙).md @@ -0,0 +1,17 @@ +# [[Open/Closed Principle (개방-폐쇄 원칙)]] + +## 📌 Brief Summary +개방-폐쇄 원칙(Open/Closed Principle)은 기존의 코드를 수정하지 않고도 시스템의 기능을 확장할 수 있도록 하는 소프트웨어 설계 원칙입니다 [1, 2]. 주로 복잡한 분기를 가진 조건문을 다형성(Polymorphism)으로 대체하는 리팩토링 기법을 통해 구현됩니다 [1, 2]. 이 원칙을 준수하면 새로운 사례가 추가될 때 기존 로직을 수정하는 대신 새로운 클래스를 추가하는 방식으로 아키텍처 변경을 관리할 수 있습니다 [1-3]. + +## 📖 Core 소스 Content +* **조건문을 다형성으로 대체 (Replace Conditional with Polymorphism):** 개방-폐쇄 원칙은 복잡한 `switch` 문이나 여러 갈래의 조건문을 객체지향의 상속 구조와 동적 바인딩으로 해결할 때 준수됩니다 [1, 2]. +* **동작 방식:** 다양하게 변화하는 동작을 위한 인터페이스나 기본 클래스(base class)를 생성한 뒤, 각 조건 분기에 해당하는 구체적인 클래스(concrete class)를 구현합니다 [1]. 이후 기존의 조건부 로직을 다형성을 활용한 다형성 메서드 호출로 대체합니다 [1]. +* **유지보수 및 테스트 용이성 확보:** 새로운 타입이나 사례가 추가될 때, 기존의 논리 구조에 손을 대는(surgery) 대신 완전히 새로운 클래스를 작성하기만 하면 됩니다 [2, 3]. 각 행동이 클래스 단위로 격리되므로, 각 구현 클래스를 독립적으로 테스트하기가 매우 수월해집니다 [1, 3]. + +## ⚖️ Trade-offs & Caveats +* **사전 구조화 요구:** 이 원칙을 따르기 위해 다형성을 도입하려면, 우선적으로 적절한 상속 구조가 마련되어 있어야 합니다 [4]. 이를 위해 '타입 코드를 하위 클래스로 바꾸기(Replace Type Code with Subclasses)'나 '상태/전략 패턴으로 바꾸기(Replace Type Code with State/Strategy)'와 같은 리팩토링이 선행되어야 하는 부담이 있습니다 [4]. +* **과도한 엔지니어링(Overkill) 위험:** 동일한 조건문이 프로그램 여러 곳에 흩어져 있을 때는 다형성 도입이 큰 이점을 제공하지만, 단일 메서드에만 영향을 미치는 소수의 조건문이며 향후 변경될 것으로 예상되지 않는다면 다형성을 도입하는 것은 과도한 엔지니어링이 될 수 있습니다 [5, 6]. 이 경우 '매개변수를 명시적 메서드로 바꾸기(Replace Parameter with Explicit Methods)' 등 더 단순한 접근이 적절할 수 있습니다 [5]. +* **객체 수명 주기 제약:** 객체 생성 이후에 객체의 타입(Type Code)이 변할 수 있는 상황이라면 단순한 하위 클래스화(subclassing)를 사용할 수 없습니다 [4]. 이 경우에는 상대적으로 더 복잡한 상태나 전략 패턴(State/Strategy Pattern)을 필수적으로 도입해야 하는 제약이 따릅니다 [4]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Over-engineering (오버엔지니어링).md b/10_Wiki/Topics/Architecture/Over-engineering (오버엔지니어링).md new file mode 100644 index 00000000..1b9870e0 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Over-engineering (오버엔지니어링).md @@ -0,0 +1,18 @@ +# [[Over-engineering (오버엔지니어링)]] + +## 📌 Brief Summary +오버엔지니어링은 어떠한 기능에서도 요구하지 않는 불필요한 아키텍처나 과도한 추상화를 시스템에 도입하는 현상을 의미합니다 [1, 2]. 주로 미래의 불확실한 요구사항을 대비하기 위한 추측성 일반화(Speculative Generality)나, 지나치게 유연성을 부여하려는 사전 설계로 인해 발생합니다 [3, 4]. 이러한 접근은 코드를 복잡하게 만들고 오히려 유지보수를 어렵게 하는 부작용을 낳으므로, 단순한 솔루션을 먼저 구축한 후 유연성을 확보하는 방향으로 점진적인 리팩토링을 수행하는 것이 권장됩니다 [5]. + +## 📖 Core Content +* **발생 원인과 특징:** 오버엔지니어링은 '언젠가 이런 종류의 기능이 필요할 것'이라고 예상하여 요구되지 않은 기능이나 특수 사례를 미리 처리하기 위한 장치를 만들 때 발생합니다 [4]. 이는 코드를 작성하기도 전에 시스템에 모든 긍정적인 품질을 부여하려는 '신중한 사전 설계(Upfront design)'에서 자주 비롯되며, 이 과정에서 미래를 잘못 예측할 위험이 큽니다 [6]. 유연성을 지나치게 추구하다 보면 실제 필요한 수준 이상의 유연성을 강제로 부여하게 되어, 시스템이 단순한 솔루션보다 훨씬 복잡해지는 결과를 초래합니다 [3]. +* **리팩토링에 대한 오남용:** 오버엔지니어링은 개발자들이 리팩토링을 수행할 때 직면할 수 있는 주요 위험(Risk) 요소 중 하나로 꼽힙니다 [7, 8]. 코드를 깔끔하게 만든다는 목적만으로 성급하게 조건문을 캡슐화하거나, 명확하게 정의할 수 없는 추상화를 코드베이스에 억지로 강요하는 행위 등은 문제 해결에 도움이 되지 않는 오버엔지니어링입니다 [9, 10]. +* **방지 및 해결 전략:** 사전에 오버엔지니어링을 하기보다는 우선 단순한 해결책을 구축하고, 필요에 따라 유연성을 확보하는 방향으로 리팩토링을 진행해야 합니다 [5]. 이를 위한 실용적인 지침으로 코드가 세 번 중복될 때까지 기다렸다가 추상화와 리팩토링을 수행하는 '3의 법칙(Rule of Three)'이 권장됩니다 [11, 12]. 명확한 이름을 붙일 수 없을 정도로 모호한 추상화라면 코드를 무리하게 추상화하지 말고 더 많은 컨텍스트가 쌓일 때까지 기다리는 것이 좋습니다 [10]. + +## ⚖️ Trade-offs & Caveats +* **유지보수 비용 증가:** 오버엔지니어링으로 생성된 불필요한 아키텍처는 모든 코드 조각들이 해당 구조에 억지로 적응하도록 강제하므로, 결과적으로 시스템을 더 이해하기 어렵고 유지보수하기 힘들게 만듭니다 [1, 2, 4]. +* **잘못된 추상화의 위험성:** 중복을 피하고자 너무 이른 시점(Prematurely)에 리팩토링을 시도하여 잘못된 추상화를 선택하게 되면, 새로운 요구사항이 등장했을 때 코드가 이전보다 더 악화되며 결국 또다시 리팩토링을 해야 하는 비효율이 발생합니다 [12]. +* **불필요한 코드의 방해 효과:** 당장 사용되지 않는 훅(hooks)이나 장치들은 향후의 개발 과정에서 걸림돌로 작용할 뿐이므로, 추측성 일반화(Speculative Generality)로 판단되는 코드 덩어리는 과감하게 제거해야 합니다 [4]. +* **기술 부채와의 타협점:** 오버엔지니어링을 경계하는 것과 기술 부채를 방치하는 것 사이에는 적절한 균형이 필요합니다. 실용적인 설계 조정을 장려하는 '3의 법칙'을 통해 과도한 엔지니어링의 위험과 유지보수성을 저해하는 기술 부채 사이의 트레이드오프를 적절히 조율해야 합니다 [11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Performance Optimization.md b/10_Wiki/Topics/Architecture/Performance Optimization.md new file mode 100644 index 00000000..aac086e3 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Performance Optimization.md @@ -0,0 +1,38 @@ +# [[Performance Optimization]] + +## 📌 Brief Summary +성능 최적화(Performance Optimization)는 소프트웨어의 기능적 동작을 동일하게 유지하면서 시간이나 메모리와 같은 자원 사용량을 개선하기 위해 내부 구조를 변경하는 과정입니다 [1-3]. 코드를 더 이해하고 수정하기 쉽게 만드는 리팩토링(Refactoring)과 유사하게 기능은 유지하지만, 구조 개선이 아닌 속도 향상이나 자원 효율성을 주된 목적으로 삼는다는 점에서 구별됩니다 [1, 2, 4]. 성공적인 성능 최적화를 위해서는 이해하기 쉬운 코드로 먼저 리팩토링하여 튜닝하기 좋은 상태를 만드는 것이 필수적인 선행 조건으로 간주됩니다 [5-7]. + +## 📖 Core Content + +* **리팩토링과 최적화의 관계:** + 소프트웨어 개발에서 기능 추가나 버그 수정과 달리, 리팩토링과 최적화는 모두 기존 기능을 불변으로 유지하면서 다른 속성을 변경한다는 공통점을 가집니다 [4]. 리팩토링은 프로그램의 구조(Structure)를 변경하는 반면, 최적화는 자원 사용량(Resource Usage)을 변경합니다 [2, 3]. + +* **성능 개선을 위한 3가지 접근 방식 [8-11]:** + 1. **시간 예산 할당 (Time Budgeting):** 심박조율기처럼 지연된 데이터가 치명적인 하드 실시간(Hard real-time) 시스템에서 주로 사용되며, 각 컴포넌트에 엄격한 시간과 자원 예산을 할당하는 방식입니다 [8]. + 2. **지속적 주의 (Constant Attention):** 모든 프로그래머가 항상 성능을 높게 유지하기 위해 노력하는 방식이지만, 실제로는 프로그램을 이해하고 수정하기 어렵게 만들어 개발 속도를 늦추기 때문에 효과적이지 않습니다 [9]. + 3. **프로파일러 기반 튜닝 (Profiler-Driven Tuning):** 대부분의 프로그램은 아주 작은 일부 코드에서 대부분의 시간을 낭비한다는 통계에 기반한 접근입니다 [10]. 성능에 신경 쓰지 않고 구조가 잘 짜인(Well-factored) 프로그램을 먼저 만든 후, 프로파일러를 사용해 시간과 공간을 소비하는 '핫스팟(Hot spots)'을 찾아 집중적으로 최적화합니다 [10, 11]. + +* **잘 리팩토링된 코드가 최적화에 미치는 이점:** + 잘 구조화된 프로그램은 기능 추가 속도를 높여주어 성능 튜닝에 투자할 시간을 더 많이 확보해 줍니다 [6]. 또한, 코드의 의도가 명확하고 구조가 잘게 나누어져 있어 성능 분석 시 더 세밀한 단위(Finer granularity)로 프로파일링하고 튜닝할 수 있는 이점을 제공합니다 [6]. + +* **성능 최적화 관련 기법:** + 기존 알고리즘이 유지보수할 수 없는 성능 병목 지점이 되었을 때 전체 구현을 교체하는 '알고리즘 전환(Substitute Algorithm)' 기법이 사용될 수 있습니다 [12]. 또한, 임시 변수를 질의 함수로 바꾸는 기법(Replace Temp with Query)은 데이터 흐름을 명확히 하여 향후 최적화, 메모이제이션(Memoization) 또는 병렬 실행을 지원하는 토대가 됩니다 [13]. 하드웨어 측면에서는 소프트웨어가 병렬 프로세서나 벡터 유닛의 이점을 취할 수 있도록 조정하는 작업도 포함됩니다 [14]. + +## ⚖️ Trade-offs & Caveats + +* **단기적 성능 저하와 장기적 튜닝 용이성의 상충 (Trade-off):** + 코드를 이해하기 쉽게 만드는 리팩토링 과정은 종종 단기적으로 프로그램의 실행 속도를 느리게 만들 수 있습니다 [5, 7]. 예를 들어, 임시 변수를 제거하는 리팩토링 과정에서 루프가 여러 번 실행되거나 계산이 중복되어 성능 비용을 지불해야 할 수 있습니다 [15, 16]. 그러나 이러한 단기적인 성능 저하를 감수하면 오히려 강력한 최적화 옵션을 발견하기 쉬워지므로 최종적으로는 더 빠른 소프트웨어를 만들 수 있습니다 [7, 17]. + +* **최적화로 인한 코드 가독성 저하:** + 성능을 개선하기 위한 코드 변경은 대개 프로그램의 복잡도를 높이고 코드를 이해하기 어렵게 만듭니다 [1, 9]. 이것이 '지속적 주의' 방식이 오히려 개발을 느리게 만들고 낭비를 초래하는 주된 이유입니다 [9]. + +* **추측에 기반한 최적화의 위험성 (Caveat):** + 성능 최적화 시 가장 주의해야 할 점은 시스템을 잘 안다고 생각하여 병목 지점을 함부로 추측(Speculate)해서는 안 된다는 것입니다 [5, 18]. 경험 많은 개발자의 좋은 아이디어조차 실제 측정 결과와 다를 때가 많으므로, 반드시 프로파일러 도구로 실제 성능을 측정(Measure)한 후 최적화를 진행해야 합니다 [5, 11, 19]. + +* **AI 도구 활용 시 제약 사항:** + 성능이 중요한 최적화(Performance-critical optimization) 작업은 복잡성이 매우 높은 작업이므로, AI 코딩 도구를 사용할 경우 오히려 작업 속도를 지연시키거나 방해가 될 수 있습니다 [20, 21]. 따라서 이러한 맥락에서는 AI 지원 없이 전문가의 판단에 의존하는 것이 권장됩니다 [21]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Polymorphism (다형성).md b/10_Wiki/Topics/Architecture/Polymorphism (다형성).md new file mode 100644 index 00000000..5720118e --- /dev/null +++ b/10_Wiki/Topics/Architecture/Polymorphism (다형성).md @@ -0,0 +1,74 @@ +# [[Polymorphism (다형성)]] + +## 📌 Brief 실 Summary +다형성(Polymorphism)은 객체 지향 프로그래밍에서 객체가 자신의 타입에 따라 적절한 동작을 스스로 수행하게 하는 메커니즘이다 [1, 2]. 리팩토링 원칙에서 다형성은 주로 길고 복잡한 조건문(switch 또는 if-else)을 하위 클래스의 재정의된 메서드로 대체하여 코드의 중복을 제거하고 명확성을 높이는 데 사용된다 [1, 3, 4]. 이를 통해 새로운 타입이나 조건이 추가될 때 기존 코드를 수정할 필요 없이 새로운 클래스를 추가하기만 하면 되는 유연성을 제공하여 아키텍처의 변경을 관리하기 쉽게 만든다 [3, 5, 6]. + +## 📖 Core Content +* **조건부 로직의 단순화 및 유연성 확보:** + 절차지향적 코드에서 흔히 나타나는 긴 분기문(Switch 구문 등)은 중복을 유발하고, 새로운 조건이 추가될 때마다 관련된 모든 조건문을 찾아 수정해야 하므로 유지보수를 매우 어렵게 만든다 [3, 7]. 다형성을 활용하면 명시적인 조건문을 다형성 메시지로 변경하여 분기 로직을 인코딩할 수 있으며, 객체에게 타입을 묻고 행동을 결정하는 대신 단순히 행동을 호출(invoke)하여 객체 스스로 올바른 동작을 수행하도록 만들 수 있다 [2, 4, 5]. + +* **'조건식을 다형성으로 바꾸기(Replace Conditional with Polymorphism)' 프로세스:** + 객체의 타입에 따라 다른 동작을 선택하는 조건문이 있을 때, 각 조건의 분기를 하위 클래스의 재정의(overriding)된 메서드로 옮기고 원본 메서드는 추상(abstract) 메서드로 선언하는 리팩토링 기법이다 [1, 8]. 이 과정의 전제 조건으로 '타입 코드를 하위 클래스로 바꾸기(Replace Type Code with Subclasses)' 또는 '타입 코드를 상태/전략으로 바꾸기(Replace Type Code with State/Strategy)'를 적용하여 다형성 동작을 호스팅할 상속 구조를 우선적으로 구축해야 한다 [9, 10]. + +* **개방-폐쇄 원칙(Open/Closed Principle) 준수:** + 다형성을 통해 분기별 로직을 별도의 클래스로 격리하면, 새로운 조건이나 타입이 요구될 때 기존 로직을 '수술'하는 대신 새로운 구체 클래스(Concrete Class)를 추가하는 것만으로 확장이 가능하다 [3, 6]. 이러한 설계는 단일 조건 로직이 프로그램의 여러 곳에 흩어져 있을 때(중복 코드) 진가를 발휘하며, 변화하는 변형(variant)들을 클라이언트로부터 숨겨 시스템 의존성을 낮춘다 [5, 7, 11]. + +* **Null 객체 패턴(Introduce Null Object)을 통한 다형성 적용:** + Null 값을 확인하는 반복적인 조건문 역시 다형성을 통해 우아하게 해결할 수 있다 [2]. Null 값을 나타내는 특수 하위 클래스(Null Object)를 생성하고 해당 타입의 기본 동작을 재정의함으로써, 명시적인 null 체크 로직 없이도 다형성 호출을 통해 객체가 적절한 행동을 위임받아 수행하게 된다 [2, 12]. + +## ⚖️ Trade-offs & Caveats +* **과도한 엔지니어링(Overkill) 위험:** + 단일 메서드에만 영향을 미치는 소수의 조건만 존재하며 향후 조건이 변경되거나 확장될 것으로 예상되지 않는 경우, 상속과 다형성을 도입하는 것은 오히려 불필요한 복잡성을 초래할 수 있다 [13]. 이 경우에는 다형성 대신 '매개변수를 명시적 메서드로 바꾸기(Replace Parameter with Explicit Methods)' 같은 단순한 기법이 더 적합할 수 있다 [13]. +* **객체 생명주기에 따른 상속 적용 제약:** + 하위 클래스를 활용해 다형성을 구현할 때 주의해야 할 가장 큰 제약은 '객체는 생성된 이후 자신의 클래스를 런타임에 변경할 수 없다'는 점이다 [14]. 객체의 타입 코드나 상태가 수명 주기 동안 변경되어야 하거나, 해당 클래스가 이미 다른 이유로 하위 클래스화되어 있다면 단순 상속을 사용할 수 없다 [9, 15]. 이러한 제약 상황에서는 상속 대신 '상태 패턴(State Pattern)'이나 '전략 패턴(Strategy Pattern)'을 결합하여 간접 참조(indirection) 계층을 추가하는 구조적 타협이 필요하다 [14-16]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [아키텍처 및 기반 원칙] +- [[Open/Closed Principle (개방-폐쇄 원칙)]] + - 연결 이유: 다형성을 통한 리팩토링의 핵심 목표 중 하나가 기존 코드를 변경하지 않고 새로운 동작을 확장할 수 있게 만드는 것이기 때문이다 [3, 6]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 다형성이 어떻게 코드의 결합도를 낮추고 시스템의 유지보수성을 극대화하는지 아키텍처 관점에서 이해할 수 있다. + +#### [코드 구조 결함 (Code Smells)] +- [[Switch Statements (Switch 문)]] + - 연결 이유: 다형성 리팩토링의 가장 주요한 타겟이자 대상이 되는 코드 냄새이다 [5, 7]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 객체 지향 시스템에서 중복된 조건문이 왜 치명적인 유지보수 문제를 일으키는지 파악할 수 있다. +- [[Primitive Obsession (기본 타입 집착)]] + - 연결 이유: 타입 코드(Type Code)를 단순 정수나 문자열 같은 기본 타입으로 사용할 때 다형성 적용을 방해하는 원인이 된다 [17]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기본 타입을 객체나 하위 클래스로 격상시키는 것이 다형성 설계의 첫 단추임을 이해할 수 있다. + +#### [구현 및 리팩토링 패턴] +- [[State/Strategy Pattern (상태/전략 패턴)]] + - 연결 이유: 객체의 타입이나 상태가 런타임에 변경되어야 하는 경우, 단순 상속을 활용한 다형성의 한계를 극복하기 위해 필수적으로 결합되어야 하는 디자인 패턴이다 [14, 15]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 다형성을 통한 위임(Delegation)의 구체적인 구현 방식과 구조적 유연성을 배울 수 있다. +- [[Introduce Null Object (Null 객체 도입)]] + - 연결 이유: 조건문의 일종인 반복적인 Null 체크를 다형성을 사용하여 우아하게 처리하는 특수 사례이다 [2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 다형성의 범위를 상태 및 타입 검증을 넘어 예외/비정상 상태(Null)의 기본 처리로까지 확장하는 방법을 이해할 수 있다. + +### Deeper Research Questions + +- 상태 패턴(State)과 전략 패턴(Strategy)을 이용하여 다형성을 구현할 때, 두 패턴 간의 선택 기준과 각각이 시스템 구조에 미치는 유지보수 비용은 어떻게 다른가? +- 단순한 조건문 분기와 다형성을 이용한 객체 구조화 사이에서, 설계가 '오버엔지니어링'으로 빠지지 않도록 판단할 수 있는 실무적 임계점(Threshold)은 무엇인가? +- 동적 바인딩(Dynamic Binding)이 수반되는 다형성이 성능에 민감한 시스템에서 오버헤드를 발생시킬 수 있는가? 그렇다면 가독성과 성능 사이의 반대급부를 어떻게 조율할 것인가? +- Null Object 패턴을 통해 다형성으로 Null 체크를 제거할 때, 반드시 포착해야 하는 시스템의 오류가 은닉되는 부작용(예외 덮어쓰기)을 방지하기 위한 설계 한계는 무엇인가? +- 다형성을 통해 계층이 분리된 도메인 모델이 데이터베이스의 단일 테이블과 매핑될 때 발생하는 직렬화 및 스키마 구조의 불일치 문제는 어떻게 해결해야 하는가? (소스에 관련 정보가 부족합니다. 후속 조사가 필요합니다.) + +### Practical Application Contexts + +- **Implementation:** 복잡하게 얽힌 Switch문이나 중첩된 If-Else문을 발견했을 때, 다형성을 지원할 수 있는 인터페이스나 상위 클래스를 생성한 뒤 각 조건 분기를 하위 클래스의 재정의된 메서드로 추출하여 구현한다. +- **System Design:** 소프트웨어 설계 초기 단계에서 변화할 수 있는 타입(예: 영화의 분류, 직원의 급여 체계 등)을 수용해야 할 때, 타입 코드를 기반으로 한 다형성 구조를 미리 반영하여 설계 변경에 유연한 아키텍처를 구축한다. +- **Operation / Maintenance:** 레거시 코드 유지보수 중 특정 로직을 변경하기 위해 프로그램 전반에 흩어진 분기문을 모두 찾아야 하는 '산탄총 수술(Shotgun Surgery)' 냄새가 발생할 때, 다형성을 도입하여 변경 지점을 하나로 모은다. +- **Learning Path:** 리팩토링의 기본기인 '메서드 추출(Extract Method)'과 '메서드 이동(Move Method)'을 마스터한 뒤, 객체 지향 프로그래밍의 핵심인 다형성 관련 리팩토링 기법을 학습하여 디자인 패턴의 활용으로 나아간다. +- **My Project Relevance:** 할인 정책, 등급 권한 처리 등 변동성이 높은 규칙이 포함된 비즈니스 로직을 리팩토링하여, 요구사항이 새롭게 추가될 때마다 기존 코드를 건드리지 않고 새로운 정책 클래스만 추가하는 안정적인 프로젝트 관리를 수행한다. + +### Adjacent Topics + +- [[Test-Driven Development (테스트 주도 개발)]] + - 확장 방향: 조건식을 다형성 구조로 대폭 변경하는 리팩토링을 수행할 때, 기존 동작이 변하지 않았음을 보장하기 위해 반드시 갖춰져야 하는 테스트의 자동화 및 안전망 구축 방법론으로 확장하여 학습한다. +- [[Code Smells (코드 냄새)]] + - 확장 방향: 다형성이 필요한 상황을 식별할 수 있는 다양한 징후들(예: 반복되는 조건문, 기능 욕심, 데이터 뭉치 등)을 어떻게 민감하게 포착하고 평가할 것인지에 대한 통찰로 확장이 가능하다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Preprocessing Seam (전처리 접점).md b/10_Wiki/Topics/Architecture/Preprocessing Seam (전처리 접점).md new file mode 100644 index 00000000..2d32c6c0 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Preprocessing Seam (전처리 접점).md @@ -0,0 +1,17 @@ +# [[Preprocessing Seam (전처리 접점)]] + +## 📌 Brief Summary +전처리 접점(Preprocessing Seam)은 C와 C++ 같은 언어에서 컴파일러가 실행되기 전에 매크로 전처리기를 활용해 코드 텍스트를 교체함으로써 프로그램의 동작을 변경할 수 있는 지점을 의미합니다 [1, 2]. 이를 통해 프로덕션 코드를 직접 수정하지 않고도 테스트하기 까다로운 전역 함수나 외부 의존성을 매크로로 재정의하여 테스트 환경에서 대체할 수 있습니다 [2, 3]. 이 접점의 동작 여부를 결정하는 활성화 지점(Enabling Point)은 특정 조건부 매크로(예: `#define TESTING`)를 정의하거나 해제하는 전처리기 지시어입니다 [4, 5]. + +## 📖 Core Content +* **동작 원리 및 개념:** 대부분의 프로그래밍 환경에서는 컴파일러가 프로그램 텍스트를 바로 읽지만, C/C++에는 그보다 앞서 실행되는 매크로 전처리기가 존재합니다 [1]. 전처리 접점은 `#include` 지시어나 `#define`을 사용하여, 코드가 컴파일되기 전 단계에서 특정 함수 호출 텍스트를 교체해 동작을 대체하는 원리입니다 [2]. +* **의존성 분리 및 테스트 적용:** 데이터베이스와 직접 통신하는 전역 함수(예: `db_update`)가 코드에 포함되어 있다면 이를 그대로 테스트하기는 매우 어렵습니다 [3]. 이때 전처리 접점을 활용하여 별도의 로컬 헤더 파일(예: `localdefs.h`)을 추가하고, 테스트 시점에만 해당 함수를 매크로로 덮어쓰도록 정의할 수 있습니다 [2, 6, 7]. 이를 통해 실제 데이터베이스를 갱신하는 부수 효과(side effect) 없이 올바른 매개변수가 전달되었는지를 확인하는 테스트를 작성할 수 있습니다 [2]. +* **활성화 지점(Enabling Point):** 모든 접점에는 어떤 동작을 수행할지 결정 내릴 수 있는 활성화 지점이 존재해야 합니다 [4]. 전처리 접점에서는 `TESTING`과 같은 전처리기 매크로를 정의(define)하여 대체 동작을 켜고 끄는 위치가 바로 활성화 지점이 됩니다 [4, 5]. + +## ⚖️ Trade-offs & Caveats +* **코드 명확성 저하 및 버그 유발 위험:** 프로덕션 코드 내에서 과도한 전처리(예: `#ifdef`, `#ifndef`)를 사용하면 하나의 소스 파일에서 여러 버전의 프로그램을 유지해야 하므로 코드의 명확성이 크게 떨어집니다 [8]. 또한, 매크로는 단순한 텍스트 치환만을 수행하기 때문에 매우 파악하기 힘든 모호한 버그를 숨길 위험이 있습니다 [8]. +* **유지보수의 어려움:** 전처리 접점이나 링크 접점(Link Seam)은 객체 접점(Object Seam)만큼 명시적이지 않기 때문에, 이에 의존하여 작성된 테스트 코드는 유지보수하기가 어렵습니다 [9]. +* **제한적 사용 권장:** 이러한 단점들로 인해 전처리 접점은 객체 지향적인 객체 접점을 사용하기 어려운 상황, 즉 의존성이 시스템 전반에 퍼져 있어 더 나은 대안이 없는 경우에만 제한적으로 사용하는 것이 권장됩니다 [9]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Preserve Whole Object (객체 통째로 넘기기).md b/10_Wiki/Topics/Architecture/Preserve Whole Object (객체 통째로 넘기기).md new file mode 100644 index 00000000..49fdf5e7 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Preserve Whole Object (객체 통째로 넘기기).md @@ -0,0 +1,21 @@ +# [[Preserve Whole Object (객체 통째로 넘기기)]] + +## 📌 Brief Summary +'Preserve Whole Object(객체 통째로 넘기기)'는 단일 객체에서 여러 데이터 값을 추출하여 메서드의 매개변수로 각각 전달하는 대신, 데이터를 추출한 **원본 객체 전체를 매개변수로 전달하는 리팩토링 기법**입니다 [1, 2]. 이 기법은 메서드 호출을 단순화하는 데 사용되며, 길고 복잡한 매개변수 목록을 줄여 코드의 가독성과 유지보수성을 높이는 것을 목적으로 합니다 [3-5]. 주로 '데이터 뭉치(Data Clumps)' 스멜을 해결하거나 '긴 매개변수 목록(Long Parameter List)'을 정리할 때 유용하게 활용됩니다 [4, 6]. + +## 📖 Core Content +* **문제 상황 및 적용:** 특정 객체에서 여러 개의 값을 가져와 다른 메서드의 매개변수로 전달하는 상황에서 주로 적용됩니다. 값을 개별적으로 넘기게 되면, 나중에 호출되는 객체가 새로운 데이터 값을 필요로 할 때마다 해당 메서드의 모든 호출부를 찾아 매개변수를 추가하고 수정해야 하는 번거로움이 발생합니다. 대신 **객체 전체를 전달하면 호출된 객체가 필요한 데이터를 원본 객체에 직접 요청할 수 있으므로 이러한 변경의 어려움이 해결**됩니다 [1, 2]. +* **가독성 향상 및 중복 제거:** 긴 매개변수 목록은 호출자와 피호출자 모두가 어떤 값이 포함되어 있는지 기억해야 하므로 코드를 다루기 어렵게 만듭니다. 또한, 피호출 객체가 원본 객체의 다른 메서드를 활용하여 중간 값을 계산할 수 없게 만들어 결과적으로 코드 중복을 유발합니다. 객체를 통째로 넘기면 **매개변수 목록이 변경에 더 유연해지고 가독성이 크게 향상**됩니다 [2]. +* **실행 절차:** + 1. 데이터를 제공하는 전체 객체에 대한 새로운 매개변수를 생성합니다 [7]. + 2. 전체 객체에서 얻을 수 있는 매개변수를 확인하여, 메서드 본문 내의 기존 매개변수 참조를 전체 객체의 메서드 호출로 대체합니다 [7]. + 3. 기존 매개변수를 삭제하고 컴파일 및 테스트를 진행하며 이를 반복합니다 [7, 8]. + 4. 마지막으로 호출하는 메서드 측에서 삭제된 매개변수를 얻어오던 코드를 제거합니다 [7]. +* **연관된 리팩토링:** 이 기법은 '매개변수 객체 만들기(Introduce Parameter Object)'와 함께 긴 매개변수 목록을 줄이는 데 핵심적인 역할을 합니다 [4, 6]. 또한, 호출된 메서드가 다른 객체의 값을 너무 많이 사용한다는 것은 해당 메서드가 데이터가 있는 객체에 정의되어야 함을 암시하는 신호일 수 있으므로, '객체 통째로 넘기기'를 적용하는 대신 **'메서드 이동(Move Method)'을 대안으로 고려**해볼 수도 있습니다 [9]. + +## ⚖️ Trade-offs & Caveats +* **의존성(Dependency) 증가 위험:** 이 기법의 가장 큰 부작용은 의존성 문제입니다. 값을 개별적으로 전달할 때 피호출 객체는 그 값들에만 의존할 뿐, 값이 추출된 원본 객체 자체에는 의존하지 않습니다. 하지만 **객체 전체를 전달하게 되면, 요구되는 원본 객체와 호출되는 객체 사이에 새로운 의존성이 발생**합니다. 이러한 변경이 시스템의 의존성 구조를 엉망으로 만들 우려가 있다면 '객체 통째로 넘기기'를 사용해서는 안 됩니다 [10]. +* **단일 값 전달에 대한 이견:** 호출하는 객체가 요구되는 객체에서 오직 단일 값(하나의 데이터)만 필요로 할 때는 전체 객체보다 그 값 하나만 전달하는 것이 낫다는 의견도 존재합니다. 하지만 마틴 파울러(Martin Fowler)는 가독성 측면에서 값 하나를 전달하는 것과 객체 하나를 전달하는 것은 큰 차이가 없다고 보며, 결국 **이 기법의 적용을 결정하는 가장 중요한 원동력은 '의존성(Dependency) 문제'**라고 강조합니다 [10]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Primitive Obsession (기본 타입 집착).md b/10_Wiki/Topics/Architecture/Primitive Obsession (기본 타입 집착).md new file mode 100644 index 00000000..cf89251d --- /dev/null +++ b/10_Wiki/Topics/Architecture/Primitive Obsession (기본 타입 집착).md @@ -0,0 +1,23 @@ +# [[Primitive Obsession (기본 타입 집착)]] + +## 📌 Brief Summary +기본 타입 집착(Primitive Obsession)은 개발자가 간단한 작업을 위해 전용 객체를 생성하는 대신 숫자나 문자열과 같은 기본 데이터 타입(Primitive Type)만을 과도하게 사용하는 코드 스멜(Code Smell)입니다 [1, 2]. 이는 코드를 비대하게 만드는 '비대화(Bloaters)' 유형의 문제로 분류되며, 시스템에 의미 있는 추상화가 누락되어 코드 중복과 복잡성을 유발합니다 [3, 4]. 결과적으로 단일 책임 원칙(SRP)을 위반하게 만들어 코드 수정 시 예상치 못한 부수 효과(Side Effect)를 발생시킬 위험을 높입니다 [4]. + +## 📖 Core Content +* **발생 원인과 증상:** + 객체 지향 프로그래밍에 익숙하지 않은 개발자들은 금액(숫자와 통화의 결합), 범위(상한선과 하한선), 전화번호, 우편번호 등의 작은 개념에 대해 새로운 클래스를 만드는 것을 꺼리는 경향이 있습니다 [2]. 그 결과 풍부한 개념(Richer concepts)이 도입되지 못하고 단순 데이터에 기반한 조건 분기문이 많아지며, 의미가 결여된 기본 타입들이 코드 전반에 흩어지게 됩니다 [3, 5]. +* **구조적 악영향:** + 기본 타입 집착은 코드가 너무 커져서 한눈에 파악하기 어렵게 만드는 '비대화(Bloaters)' 스멜로 분류됩니다 [4, 6]. 기본 타입에만 의존하면 누락된 추상화로 인해 코드가 중복되고, 수정 시 여러 곳을 건드려야 하므로 유지보수가 힘들어집니다 [3]. +* **해결을 위한 리팩토링 기법:** + * **데이터 값을 객체로 바꾸기 (Replace Data Value/Primitive with Object):** 개별적인 데이터 값을 전용 객체로 변환하여 기본 타입의 한계를 벗어납니다 [2, 3]. + * **타입 코드를 클래스/서브클래스/상태·전략으로 바꾸기:** 기본 타입이 타입 코드(Type Code)로 사용될 때, 동작에 영향을 주지 않는다면 클래스로(Replace Type Code with Class) 바꿉니다. 만약 조건문에 영향을 준다면 서브클래스나 상태/전략 패턴으로(Replace Type Code with Subclasses or State/Strategy) 변경하며, 관련 조건식은 다형성으로(Replace Conditional with Polymorphism) 교체합니다 [2, 3]. + * **클래스 추출하기 (Extract Class):** 함께 다녀야 할 기본 타입 필드들이 논리적인 그룹을 이루고 있다면 별도의 클래스로 추출합니다 [3, 7]. + * **매개변수 객체 만들기 (Introduce Parameter Object):** 매개변수 목록에 기본 타입들이 길게 나열되어 있다면 이를 하나의 객체로 묶습니다 [3, 7]. + * **배열을 객체로 바꾸기 (Replace Array with Object):** 배열 내의 기본 타입 요소들을 특정 의미로 분해해서 사용하고 있다면 명확한 객체로 변환합니다 [7]. + +## ⚖️ Trade-offs & Caveats +* **레코드 및 객체 생성의 오버헤드:** 기본 타입을 객체나 레코드 타입으로 대체하면 구조적 오버헤드(Overhead)가 발생할 수 있습니다. 예를 들어, 데이터베이스의 추가적인 테이블 생성을 의미할 수도 있고, 단지 한두 개의 단순한 작업을 위해 새로운 객체 구조를 생성하는 것이 번거롭게 느껴질 수 있습니다 [1]. 이것이 많은 개발자가 객체지향의 이점을 누리지 못하고 기본 타입에 집착하게 만드는 주된 진입 장벽입니다 [1, 2]. +* **적절한 추상화 도출의 필요성:** 기본 타입을 무작정 객체로 바꾸는 것만이 능사는 아니며, 코드베이스에 충분한 로직이 쌓였을 때 누락된 추상화(Missing abstractions)를 정확히 찾아내야 합니다. 이를 통해 중복을 제거하고 코드를 단순화하는 올바른 방향으로 개념이 도입되어야 리팩토링의 효과를 거둘 수 있습니다 [3]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Program Dependence Graph.md b/10_Wiki/Topics/Architecture/Program Dependence Graph.md new file mode 100644 index 00000000..d02e0dc8 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Program Dependence Graph.md @@ -0,0 +1,15 @@ +# [[Program Dependence Graph]] + +## 📌 Brief Summary +Program Dependence Graph(프로그램 의존성 그래프, PDG)는 코드 내의 데이터 및 제어 의존성(data and control dependencies)을 명시적으로 표현하는 모델입니다 [1]. 이는 표현식이나 메서드와 같은 두 코드 조각 간의 방향성 있는 관계를 나타내며, 리팩토링 대상이 되는 프로그램의 잠재적 문제를 탐지하기 위한 정적 프로그램 분석(Static program analysis) 등에 활용됩니다 [1, 2]. 개별 PDG들 간의 프로시저 호출을 연결하면 시스템 전체의 의존성을 나타내는 시스템 의존성 그래프(System Dependence Graph)로 확장하여 분석할 수 있습니다 [1]. + +## 📖 Core Content +* **의존성의 정의와 종류:** 소프트웨어에서 의존성이란 코드의 두 부분 사이의 방향성 있는 관계를 의미합니다 [2]. 여기에는 값의 정의와 사용 사이에서 발생하는 '데이터 의존성(data dependencies)'과 함수의 선언부와 실제 호출되는 위치 사이의 '호출 의존성(call dependencies)' 등이 포함됩니다 [2]. +* **정적 분석 도구로서의 역할:** PDG는 유효하지만 표준 이하(substandard)인 프로그램의 구조적 문제를 탐지하기 위한 정적 분석 기법의 핵심적인 표현 도구로 사용됩니다 [1]. +* **시스템 의존성 그래프(System Dependence Graph)로의 확장:** PDG들 사이의 프로시저 호출 관계를 표현하기 위해 시스템 의존성 그래프가 사용됩니다 [1]. 마이크로소프트(Microsoft)의 MaX와 같은 자동화 도구는 함수 수준(호출, 임포트, 익스포트 등)에서 이러한 의존성 정보를 추적하여 시스템 전반의 의존성 그래프를 생성하며, 이를 변경 영향 분석(change impact analysis) 및 통합 테스트에 활용합니다 [2]. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Pull Up Method.md b/10_Wiki/Topics/Architecture/Pull Up Method.md new file mode 100644 index 00000000..1bf76b7d --- /dev/null +++ b/10_Wiki/Topics/Architecture/Pull Up Method.md @@ -0,0 +1,21 @@ +# [[Pull Up Method]] + +## 📌 Brief Summary +Pull Up Method(메서드 올리기)는 형제 하위 클래스(sibling classes)들에 중복으로 존재하는 동일한 메서드를 부모 클래스(superclass)로 끌어올려 중앙집중화하는 리팩토링 기법이다 [1], [2]. 이 기법은 코드 중복을 제거하여 불필요한 노력을 줄이고, 한쪽 메서드만 수정되어 발생할 수 있는 잠재적 버그를 예방하기 위해 사용된다 [1], [3]. 자식 클래스들이 완전히 동일한 본문을 가진 메서드를 공유할 때 가장 직관적으로 적용할 수 있는 일반화(Generalization) 리팩토링 중 하나이다 [4], [5]. + +## 📖 Core Content +* **적용 동기:** 여러 하위 클래스에서 동일한 동작을 수행하는 두 메서드가 그대로 방치될 경우 향후 버그의 온상이 될 수 있다 [3]. 중복된 메서드가 존재하면 코드 수정 시 한쪽만 변경하고 다른 하나는 변경하지 않고 누락할 위험이 있기 때문에 이를 슈퍼클래스로 올려 단일화해야 한다 [3]. 또한, 하위 클래스의 메서드가 상위 클래스의 메서드를 재정의(override)하면서 실제로는 동일한 작업을 수행하는 특수한 경우에도 이 기법이 필요하다 [6]. +* **실행 절차:** + 1. 형제 클래스 간의 메서드를 검사하여 두 메서드의 구현이 정말로 완벽하게 동일한지 확인 및 분석한다 [1], [7]. 만약 시그니처가 다르다면 슈퍼클래스에서 사용할 형태로 시그니처를 일치시킨다 [7]. + 2. 부모 클래스에 새로운 메서드를 생성한 후, 중복 메서드의 본문을 복사하여 붙여넣고 조정한다 [1], [7]. 필요하다면 가시성(visibility)을 `protected`로 변경한다 [1]. + 3. 하위 클래스들에서 중복된 메서드 구현을 삭제하고 코드를 컴파일 및 테스트한다 [1], [8]. + 4. 오직 슈퍼클래스의 메서드만 남을 때까지 모든 하위 클래스에서 해당 메서드를 삭제하고 테스트하는 과정을 반복한다 [8]. +* **관련 기법의 활용:** 두 메서드가 비슷하지만 완전히 같지 않은 경우에는 '메서드 올리기'를 바로 적용할 수 없다 [7]. 이럴 때는 먼저 '알고리즘 전환(Substitute Algorithm)'이나 '템플릿 메서드 형성(Form Template Method)'을 사용하여 차이점을 분리하고 공통점을 추출하는 작업이 선행되어야 한다 [7]. + +## ⚖️ Trade-offs & Caveats +* **하위 클래스 종속성 문제:** '메서드 올리기'를 적용할 때 가장 다루기 까다로운 부분은 끌어올리려는 메서드의 본문이 슈퍼클래스에는 없고 하위 클래스에만 존재하는 기능(메서드나 필드)을 참조하는 경우이다 [6]. +* **하위 메서드 참조 시 제약:** 끌어올린 메서드가 하위 클래스에만 존재하는 다른 메서드를 호출하는 경우, 해당 메서드도 함께 일반화하여 끌어올리거나 슈퍼클래스에 추상 메서드(abstract method)로 선언해야만 정상적으로 컴파일되고 동작할 수 있다 [6], [8]. 이를 해결하기 위해 메서드의 시그니처를 변경하거나 별도의 위임(delegating) 메서드를 만들어야 하는 복잡함이 수반될 수 있다 [6]. +* **하위 필드 참조 시 제약:** 끌어올린 메서드가 하위 클래스의 특정 필드를 사용하는 경우라면, '필드 올리기(Pull Up Field)'나 '필드 자가 캡슐화(Self Encapsulate Field)'를 적용해야 하며, 슈퍼클래스에 추상화된 getter 메서드를 선언하여 접근해야 하는 구조적 제약이 따른다 [8]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Red-Green Refactoring.md b/10_Wiki/Topics/Architecture/Red-Green Refactoring.md new file mode 100644 index 00000000..b35158ec --- /dev/null +++ b/10_Wiki/Topics/Architecture/Red-Green Refactoring.md @@ -0,0 +1,61 @@ +# [[Red-Green Refactoring]] + +## 📌 Brief Summary +Red-Green Refactoring은 애자일 소프트웨어 개발에서 가장 널리 사용되는 코드 리팩토링 기법으로, 테스트 주도 개발(TDD) 워크플로우에 깊이 뿌리를 두고 있습니다 [1, 2]. 실패하는 테스트를 먼저 작성하는 'Red' 단계, 최소한의 코드로 테스트를 통과시키는 'Green' 단계, 그리고 코드를 개선하는 'Refactor' 단계의 세 가지 반복적인 주기로 구성됩니다 [3-5]. 기능 추가와 구조 개선을 엄격히 분리하여, 기존 동작을 훼손하지 않으면서도 시스템의 유연성과 유지보수성을 지속적으로 높이는 데 목적이 있습니다 [3, 5]. + +## 📖 Core 소스 +Red-Green Refactoring은 "테스트 우선(test-first)" 접근법을 따르며, 이는 모든 형태의 리팩토링의 기초를 형성합니다 [1]. 이 전략은 기능 구현과 리팩토링을 위한 세 가지 명확한 단계로 수행됩니다. + +* **RED (실패하는 테스트 작성):** 특정 소프트웨어 동작이나 기능을 검증하기 위한 실패하는 테스트(red-test)를 가장 먼저 작성합니다 [3, 5]. 아직 실제 코드가 작성되지 않았기 때문에 이 테스트는 의도적으로 실패하도록 설계됩니다 [5]. 이 단계를 통해 개발자는 무엇을 개발해야 할지 명확히 확인하게 됩니다 [3]. +* **GREEN (테스트 통과를 위한 최소한의 코드 구현):** 개발이 테스트를 통과(green)할 수 있도록 가장 단순하고 충분한 코드를 작성합니다 [3]. 이 단계의 핵심 목표는 코드의 품질보다는 속도에 있으므로, 테스트를 통과하기 위한 최소한의 코드만을 작성합니다 [5]. +* **REFACTOR (코드 개선 및 최적화):** 테스트가 통과하는 상태(green)를 유지하면서 코드를 개선하고 향상시키는 데 집중합니다 [3]. 소프트웨어의 동작은 그대로 보존하면서, 코드를 더 깨끗하고, 명확하며, 효율적으로 다듬어 최적화합니다 [4, 5]. + +이 기술은 본질적으로 새로운 기능을 시스템에 추가하는 첫 번째 파트(Red, Green)와 그 기능을 수행하는 코드를 개선하는 두 번째 파트(Refactor)로 나뉩니다 [3]. 가장 중요한 원칙은 워크플로우 중에 이 두 가지 작업(기능 추가와 리팩토링)을 동시에 수행해서는 안 된다는 것입니다 [3]. + +## ⚖️ Trade-offs & Caveats +* **Green 단계에서의 품질 저하 감수:** Green 단계에서는 고품질의 완벽한 코드를 작성하는 것보다 빠른 시간 내에 테스트를 통과하는 '속도'를 우선시합니다 [5]. 이는 기능 구현을 빠르게 달성하는 대신 의도적으로 엉성한 코드를 임시로 남기게 되며, 후속 Refactor 단계를 거치지 않으면 기술 부채로 전락할 위험이 있습니다. +* **기능 추가와 리팩토링의 동시 수행 금지:** 워크플로우를 진행할 때 새로운 기능 추가(코드 작성)와 리팩토링을 절대 동시에 진행해서는 안 됩니다 [3]. 이 엄격한 분리를 따르지 않으면 디버깅이 어려워지고, 테스트의 신뢰성이 떨어지는 부작용이 발생합니다 [3, 6]. +* **초기 테스트 작성의 한계:** 코드를 작성하기 전에 실패하는 테스트를 먼저 구현해야 하므로(Red 단계), 테스트를 작성하는 데 추가적인 시간적 제약과 노력이 요구됩니다 [3, 5]. 테스트 인프라가 부족한 상황에서는 리팩토링에 앞서 테스트부터 구축해야 하므로 적용이 어려울 수 있습니다 [7]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [방법론/개발 프로세스] +- [[TDD (Test-Driven Development)]] + - 연결 이유: Red-Green Refactoring은 테스트 주도 개발(TDD) 워크플로우에 내재되어 있으며, TDD 사이클 그 자체를 의미하기 때문입니다 [2, 5, 8]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트 작성이 어떻게 소프트웨어 구현 및 리팩토링을 주도하고 안정성을 부여하는지 시스템 개발 관점에서 이해할 수 있습니다 [5, 8]. +- [[Agile Methodology]] + - 연결 이유: Red-Green 기법은 애자일 소프트웨어 개발 프로세스에서 가장 인기 있고 널리 사용되는 기술이기 때문입니다 [1, 2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 짧은 주기의 반복적(iterative)인 전략이 어떻게 지속적인 리팩토링을 가능하게 하고 변화에 민첩하게 대응하게 하는지 학습할 수 있습니다 [5]. + +#### [리팩토링 원칙/규율] +- [[Two Hats (두 개의 모자)]] + - 연결 이유: 기능 추가의 단계(Red, Green)와 리팩토링 단계(Refactor)를 섞어서 진행하면 안 된다는 Red-Green Refactoring의 기본 규정은 마틴 파울러의 '두 개의 모자' 원칙을 완벽히 실천하는 형태이기 때문입니다 [3, 6]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 과정 중 기존 코드의 구조 개선과 새로운 기능 추가를 심리적, 절차적으로 철저히 분리하여 디버깅 효율을 높이는 원리를 파악할 수 있습니다 [6]. + +### Deeper Research Questions + +- Red-Green Refactoring의 'Green' 단계에서 속도를 우선하여 작성된 최소한의 코드가, 후속 'Refactor' 단계의 설계 결정에 어떠한 인지적 편향을 일으킬 수 있는가? +- TDD 기반의 Red-Green 주기를 도입할 때, 테스트 커버리지가 거의 없는 방대한 레거시 시스템(Legacy System)에서 이 기법을 점진적으로 적용하기 위한 효과적인 시퀀스는 무엇인가? +- Red-Green-Refactor 단계를 매우 짧은 간격(마이크로 커밋)으로 반복할 경우, 소프트웨어 아키텍처의 지속 가능성과 모듈성에 미치는 정량적 변화는 어떠한가? +- 테스트 코드가 외부 API나 데이터베이스와 강하게 결합된 환경에서 Red-Green Refactoring을 안전하게 수행하기 위해 테스트 더블(Mock, Stub)은 어느 단계에 어떻게 설계되어야 하는가? +- 'Refactor' 단계에서 리팩토링을 멈추고 다시 'Red' 단계로 돌아가야 하는 시점(Stopping Condition)을 판단하는 경험적, 객관적 기준은 무엇인가? + +### Practical Application Contexts + +- **Implementation:** 새로운 기능 구현 시, 해당 요구사항을 명세하는 실패하는 테스트를 가장 먼저 코드로 작성하고, 테스트가 통과될 때까지 가장 무식하지만 빠른 코드를 구현한 뒤, 최종적으로 중복 코드를 제거하고 구조를 우아하게 다듬어 완료합니다 [3, 5]. +- **System Design:** 소프트웨어 설계에 앞서 테스트 가능한 구조를 강제함으로써, 결합도가 낮고 응집도가 높은 아키텍처가 점진적이고 반복적으로 도출될 수 있도록 팀의 설계 워크플로우를 구성합니다 [2, 5]. +- **Operation / Maintenance:** 운영 중 발견된 버그를 수정할 때, 버그를 재현하는 실패하는 테스트(Red)를 먼저 만들어 버그를 고치고(Green), 주변의 코드 스멜을 개선(Refactor)하여 추후 동일한 버그가 발생하지 않도록 유지보수 체계를 강화합니다 [9]. +- **Learning Path:** 주니어 개발자가 객체지향 설계와 클린 코드를 학습할 때, 단순히 예쁘게 코딩하는 것을 넘어 TDD 환경에서 동작 보존과 구조 개선을 단계적으로 달성하는 실전 훈련 파이프라인으로 활용됩니다 [5, 10]. +- **My Project Relevance:** 현재 진행 중인 프로젝트에 Red-Green-Refactor 사이클을 적용하여, 기능 추가와 리팩토링 작업을 동시에 수행하다 발생하는 예기치 못한 사이드 이펙트를 차단하고, 항상 테스트로 보호받는 안정적인 코드베이스를 확보합니다 [3, 5]. + +### Adjacent Topics + +- [[Technical Debt (기술 부채)]] + - 확장 방향: 정기적인 Refactor 단계를 무시하고 Red-Green만 반복하거나 편법을 사용했을 때 쌓이는 기술 부채가 장기적인 프로젝트 비용에 미치는 영향과 관리 전략을 확장하여 학습합니다 [11-13]. +- [[Automated Testing (자동화된 테스트)]] + - 확장 방향: Red-Green 리팩토링의 성공을 좌우하는 테스트 피라미드, 단위 테스트(Unit Test) 작성법 및 레거시 코드에 테스트 하네스를 구축하는 방법론으로 이해를 확장합니다 [4, 14, 15]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Refactoring (리팩토링).md b/10_Wiki/Topics/Architecture/Refactoring (리팩토링).md new file mode 100644 index 00000000..5a36e556 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Refactoring (리팩토링).md @@ -0,0 +1,75 @@ +# [[Refactoring (리팩토링)]] + +## 📌 Brief Summary +Refactoring(리팩토링)은 소프트웨어의 외부적인 동작(observable behavior)을 변경하지 않으면서 내부 구조를 개선하여 코드를 더 이해하기 쉽고 수정하기 경제적으로 만드는 일련의 과정이다 [1-3]. 이는 소프트웨어 아키텍처의 부패를 방지하고 기술 부채(Technical Debt)를 체계적으로 상환하기 위한 전략적 도구이다 [3, 4]. 마틴 파울러(Martin Fowler)에 의해 대중화되었으며, 지속적인 리팩토링은 유지보수성을 높이고 버그 발견을 용이하게 하며 장기적인 개발 속도를 가속화하는 핵심 규율로 평가받는다 [5, 6]. + +## 📖 Core Content +* **리팩토링의 경제성과 목적:** + 리팩토링의 근본적인 목적은 코드의 심미적 아름다움이 아닌 '경제성'에 있다 [6, 7]. 클린 코드는 인간의 인지 부하를 줄여 시스템을 디버깅하고 새로운 기능을 추가하는 데 걸리는 시간을 크게 단축시킨다 [6]. 장기적으로 리팩토링이 결핍된 시스템은 기술 부채가 쌓여 생산성이 하락하므로, 리팩토링은 유지보수 비용을 낮추고 비즈니스 가치 전달 속도를 높이는 필수적인 경제 활동이다 [4, 8]. + +* **핵심 원칙: 두 개의 모자 (The Two Hats):** + 개발자는 프로그래밍 과정에서 '기능 추가'와 '리팩토링'이라는 두 가지 작업을 명확히 분리하여 수행해야 한다 [6, 9]. 기능 추가 모자를 썼을 때는 기존 코드를 건드리지 않고 새로운 동작 구현에 집중하며, 리팩토링 모자를 썼을 때는 새로운 기능을 추가하지 않고 오직 코드 구조 개선에만 전념해야 한다 [6, 9]. 이를 섞어서 수행할 경우 디버깅 효율이 급격히 저하된다 [6]. + +* **리팩토링의 타이밍: 3의 법칙 (Rule of Three) 및 적기:** + 마틴 파울러와 돈 로버츠가 제안한 '3의 법칙'에 따르면, 처음 구현할 때는 일단 작성하고, 두 번째 비슷한 작업을 할 때는 중복을 감수하더라도 진행하며, 세 번째 동일한 작업을 하게 될 때 비로소 리팩토링을 수행해야 한다 [10, 11]. 또한, 리팩토링은 별도의 시간을 내서 하는 것이 아니라, 기능 추가 전 준비 과정(Preparatory), 코드를 이해하기 위한 과정(Comprehension), 코드 리뷰 과정 등 일상적인 개발 흐름(Opportunistic) 속에서 지속적으로 수행되어야 한다 [12-14]. + +* **코드 스멜 (Code Smells):** + 시스템에 깊은 설계적 문제가 있음을 암시하는 표면적인 증상들을 '코드 스멜'이라고 부른다 [15, 16]. 주요 스멜로는 동일한 로직이 산재한 '중복 코드(Duplicated Code)', 인지 부하를 높이는 '긴 함수(Long Method)', 다른 클래스의 데이터에 과도하게 의존하는 '기능 욕심(Feature Envy)' 등이 있으며, 이러한 징후를 식별하는 것은 리팩토링을 적용하는 핵심 기준이 된다 [16-18]. + +* **안전망으로서의 테스트:** + 리팩토링 과정에서 외부 동작이 변하지 않았음을 보장하기 위해서는 견고한 자동화 테스트(Automated Tests) 스위트가 필수적이다 [19-21]. 단위 테스트(Unit Tests)를 기반으로 작은 단위의 변환을 적용하고, 매 단계마다 즉시 테스트를 실행하여 결함을 탐지하는 과정을 반복해야 안전한 구조 변경이 가능하다 [19, 21, 22]. + +## ⚖️ Trade-offs & Caveats +* **테스트 부재 시의 높은 위험성:** 견고한 테스트 코드가 없는 환경에서의 리팩토링은 매우 위험한 코드 수정에 불과하며, 예상치 못한 회귀 버그(Regression Bug)를 발생시킬 확률이 높다 [21, 23]. 거대한 레거시 시스템의 경우 의존성을 끊고 접점(Seam)을 만들어 테스트를 끼워넣기 전까지는 코드를 섣불리 재구조화할 수 없다 [24]. +* **성능과의 상충 관계 (Performance Trade-off):** 리팩토링은 코드를 분리하고 이해하기 쉽게 만드는 과정에서 간접 호출(Indirection)을 늘리거나 반복문을 여러 번 실행하게 만들어 단기적으로 소프트웨어의 실행 속도를 늦출 수 있다 [25, 26]. 하지만 리팩토링으로 잘 정돈된 코드는 성능 병목(Hot spot)을 파악하고 최적화(Performance Tuning)하기 훨씬 용이해지므로 장기적으로는 더 빠른 소프트웨어 구축에 유리하다 [26-28]. +* **단기 일정 및 자원 제약:** 빡빡한 마감일(Tight deadlines)이 임박한 경우, 리팩토링으로 얻는 생산성 향상 이점보다 일정 지연으로 인한 손실이 크므로 리팩토링을 유보해야 한다 [29, 30]. 또한 기능 구현에만 집중하는 경영진에게 리팩토링은 부가적인 오버헤드 활동으로 인식될 수 있어 이를 설득해야 하는 조직적 과제가 따른다 [8, 31]. +* **데이터베이스 및 공개된 인터페이스의 제약:** 비즈니스 애플리케이션이 데이터베이스 스키마와 강하게 결합된 경우, 데이터 마이그레이션의 어려움 때문에 리팩토링 진행이 매우 까다로울 수 있다 [32]. 또한 이미 발행된 인터페이스(Published Interfaces)를 변경하는 리팩토링은 해당 인터페이스를 사용하는 클라이언트 코드의 동시 수정을 강제하거나, 전환 기간 동안 구형 인터페이스를 병행 유지해야 하는 부담을 발생시킨다 [23, 33, 34]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [코드 품질 및 검증 (Code Quality & Verification)] +- [[Code Smells]] + - 연결 이유: 리팩토링이 필요한 시점과 부위를 식별하는 핵심 진단 도구이기 때문이다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 긴 함수, 거대 클래스, 기능 욕심 등 구체적인 코드의 악취를 통해 어떤 리팩토링 기법(예: 함수 추출, 메서드 이동)을 적용해야 할지 결정하는 원리를 이해할 수 있다. +- [[Test-Driven Development (TDD)]] + - 연결 이유: 리팩토링의 안전성을 보장하는 핵심 실천법인 Red-Green-Refactor 사이클의 기반이 되기 때문이다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기능을 먼저 통과시킨 후(Green) 구조를 개선(Refactor)하는 반복적인 흐름과, 자가 테스트 코드(Self-Testing Code)가 어떻게 리팩토링의 심리적 안정감을 제공하는지 파악할 수 있다. + +#### [설계 및 실행 원칙 (Design & Execution Principles)] +- [[The Two Hats]] + - 연결 이유: 리팩토링을 기능 개발과 혼동하지 않고 훈련된 방식으로 수행하기 위한 필수 심리/절차적 원칙이기 때문이다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기능 추가와 구조 개선을 명확히 분리함으로써 디버깅 효율을 높이고 버그 추적을 명확히 하는 리팩토링의 체계적인 작업 흐름을 이해할 수 있다. +- [[Rule of Three]] + - 연결 이유: 실무에서 지나치게 성급한 추상화를 방지하고 리팩토링을 수행해야 할 적기를 판단하는 경험 법칙이기 때문이다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 중복 코드가 3번 발생했을 때 리팩토링을 수행함으로써 오버 엔지니어링을 피하고 적절한 시점에 유지보수성을 높이는 판단 기준을 학습할 수 있다. +- [[Technical Debt]] + - 연결 이유: 리팩토링을 수행해야 하는 비즈니스 및 경제적 정당성을 제공하는 근본 개념이기 때문이다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 단기적인 개발 속도를 위해 타협한 더러운 코드(Dirty Code)가 어떻게 유지보수 비용을 기하급수적으로 증가시키며, 리팩토링이 이를 상환하는 과정인지 이해할 수 있다. + +### Deeper Research Questions + +- 테스트가 전혀 존재하지 않는 레거시 시스템에서 테스트 하네스를 도입하기 위해 접점(Seam)을 찾아내고 의존성을 안전하게 끊는 절차는 무엇인가? +- 마이크로소프트 윈도우 7 분석 사례에서 나타난 것처럼, 리팩토링이 모듈 간의 결합도(Dependencies)와 출시 후 결함률(Post-release Defects)을 낮추는 구체적인 메커니즘은 무엇인가? +- AI 기반 코드 생성 및 자동 리팩토링 도구가 도입되었을 때, 숙련된 시니어 개발자들에게 생산성 하락(AI Productivity Paradox)이 발생하는 인지적 원인은 무엇인가? +- 마틴 파울러가 제시한 70여 가지의 리팩토링 기법 중, 가장 광범위하게 쓰이는 '함수 추출하기(Extract Method)'를 수행할 때 지역 변수(Local Variables)를 안전하게 처리하는 단계적 방법은 무엇인가? +- '초기 완벽한 설계(Big Design Up Front)'를 지양하고 리팩토링을 통한 '진화적 설계(Evolutionary Design)'를 채택할 때 요구되는 소프트웨어 아키텍처 측면의 준비 사항은 무엇인가? + +### Practical Application Contexts + +- **Implementation:** 코딩 중 복잡한 조건문이나 10~20줄이 넘어가는 긴 함수를 발견하면, IDE의 자동화 기능(예: Extract Method)을 즉각적으로 활용하여 메서드를 추출하고 목적을 드러내는 명확한 이름을 부여함. +- **System Design:** 프로젝트 초기에 유연성을 위한 복잡한 추상화를 무리하게 구축하기보다, 현재 요구사항에 맞춰 가장 단순한 설계를 구현한 뒤 향후 변경이 필요할 때 리팩토링을 통해 유연한 구조로 점진적으로 진화시킴. +- **Operation / Maintenance:** 기능 추가나 버그 수정을 위해 기존 코드를 살펴볼 때, 먼저 이해하기 쉽도록 구조나 이름을 변경하는 이해를 위한 리팩토링(Comprehension Refactoring)을 수행한 후 실제 수정 작업을 진행함. +- **Learning Path:** TDD 사이클 및 단위 테스트 작성법을 우선 숙지한 다음, 다양한 코드 스멜(Code Smells)을 인지하는 훈련을 하고, 카탈로그에 있는 구체적인 리팩토링 기법들을 하나씩 실습하며 체화함. +- **My Project Relevance:** 유지보수하고 있는 복잡한 레거시 코드베이스에서 기능 추가에 앞서, 의존성을 끊고 보호 구문(Guard Clauses) 등을 추가하는 준비적 리팩토링을 통해 기술 부채를 지속적으로 줄여나감. + +### Adjacent Topics + +- [[Legacy Code]] + - 확장 방향: 마이클 페더스의 "레거시 코드는 테스트가 없는 코드다"라는 정의를 바탕으로, 스파우트 메서드(Sprout Method)나 접점(Seams) 기법을 활용하여 기존 코드를 깨뜨리지 않고 레거시 시스템을 점진적으로 현대화하는 방법을 탐구함. +- [[Performance Optimization]] + - 확장 방향: 리팩토링을 통해 코드를 분리하고 가독성을 확보한 후, 프로파일러(Profiler)를 사용하여 발견된 병목 지점(Hot Spots)의 리소스 사용량(시간, 메모리 등)을 중점적으로 개선하는 최적화 프로세스와의 차이점 및 시너지 방안을 조사함. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Refactoring Techniques (리팩토링 기법).md b/10_Wiki/Topics/Architecture/Refactoring Techniques (리팩토링 기법).md new file mode 100644 index 00000000..61a3f958 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Refactoring Techniques (리팩토링 기법).md @@ -0,0 +1,72 @@ +# [[Refactoring Techniques (리팩토링 기법)]] + +## 📌 Brief Summary +리팩토링 기법(Refactoring Techniques)은 소프트웨어의 외부적인 동작을 변경하지 않으면서 내부 구조를 개선하여 코드를 더 이해하기 쉽고 수정하기 경제적으로 만드는 일련의 체계적인 코드 변환 방법이다[1-3]. 이 기법들은 코드 스멜(Code Smell)과 기술 부채를 해결하기 위해 설계되었으며, 메서드 추출, 클래스 이동, 조건부 로직 단순화 등 매우 작고 안전한 마이크로 리팩토링(Micro-refactoring) 단위로 수행된다[4, 5]. 자동화된 테스트를 기반으로 점진적으로 수행되는 리팩토링은 소프트웨어 아키텍처의 부패를 방지하고 유지보수성 및 확장성을 크게 향상시킨다[3, 6]. + +## 📖 Core 대Content +리팩토링 기법은 적용 목적과 구조적 위치에 따라 여러 범주로 분류되며, 마틴 파울러(Martin Fowler)에 의해 체계적인 카탈로그 형태로 정립되었다[7, 8]. 핵심적인 리팩토링 기법의 분류와 내용은 다음과 같다. + +* **메서드 구성 (Composing Methods):** 지나치게 긴 메서드는 코드를 이해하고 변경하기 어렵게 만드는 주요 원인이다[8]. 이를 해결하기 위해 복잡한 로직을 명확한 이름을 가진 독립적인 함수로 분리하는 '함수 추출하기(Extract Method)'를 가장 널리 사용한다[9-11]. 반대로 불필요한 간접 호출이 있을 경우 '메서드 인라인(Inline Method)'으로 내용을 합친다[12, 13]. '임시 변수를 질의 함수로 바꾸기(Replace Temp with Query)'는 상태 관리를 줄이고 코드 추출을 용이하게 만든다[11, 14, 15]. +* **객체 간 기능 이동 (Moving Features between Objects):** 시스템의 응집도를 높이고 결합도를 낮추기 위해 로직과 데이터를 적절한 클래스로 분배한다[16, 17]. 특정 메서드가 자신이 속한 클래스보다 다른 클래스의 데이터를 더 많이 참조할 때 '메서드 이동(Move Method)' 또는 '필드 이동(Move Field)'을 통해 기능을 자연스러운 위치로 재배치한다[18-20]. 너무 많은 책임을 가진 거대 클래스는 '클래스 추출(Extract Class)'을 통해 분할하고, 반대의 경우 '클래스 인라인(Inline Class)'을 통해 병합한다[10, 21, 22]. +* **데이터 조직화 (Organizing Data):** 원시 타입(Primitive type)이나 얽힌 데이터 구조를 객체 지향적으로 개선한다[23]. '매직 넘버를 기호 상수로 바꾸기(Replace Magic Number with Symbolic Constant)'나 단순 데이터 값을 객체로 변환하는 '데이터 값을 객체로 바꾸기(Replace Data Value with Object)' 기법이 포함된다[17, 24, 25]. 또한 필드나 컬렉션에 대한 직접 접근을 제어하기 위해 '필드 캡슐화(Encapsulate Field)' 및 '컬렉션 캡슐화(Encapsulate Collection)'를 적용한다[26-28]. +* **조건부 로직 단순화 (Simplifying Conditional Expressions):** 시간이 지나면서 비대해진 if-else 및 switch 문은 시스템의 유지보수를 방해한다[29]. 이러한 복잡한 조건문은 '조건식을 다형성으로 바꾸기(Replace Conditional with Polymorphism)'를 통해 상속 구조와 동적 바인딩을 활용한 구조로 변경하거나[30-32], '중첩 조건문을 보호 구문으로 바꾸기(Replace Nested Conditional with Guard Clauses)'를 통해 정상적인 실행 흐름을 방해하는 예외 처리를 전진 배치하여 가독성을 높인다[32, 33]. +* **메서드 호출 단순화 (Simplifying Method Calls):** 클래스 간 상호작용 인터페이스를 단순화하여 버그 발생 위험을 줄인다[34]. 과도하게 긴 매개변수 목록을 줄이기 위해 관련된 매개변수들을 묶어 '매개변수 객체 도입(Introduce Parameter Object)'을 사용하거나, 객체를 통째로 넘기는 '객체 통째로 넘기기(Preserve Whole Object)'를 활용한다[35-37]. 또한, 부수 효과(Side Effect)가 있는 수정 함수와 상태를 반환하는 질의 함수를 분리(Separate Query from Modifier)하여 안전성을 확보한다[17, 38]. +* **일반화 처리 (Dealing with Generalization):** 상속 계층 구조를 최적화한다[39]. 형제 클래스 간 중복되는 메서드나 필드는 '메서드 올리기(Pull Up Method)'나 '필드 올리기(Pull Up Field)'를 통해 슈퍼클래스로 이동시켜 중복을 제거한다[10, 40, 41]. 특정 서브클래스에서만 의미 있는 기능은 '메서드 내리기(Push Down Method)'를 통해 하위로 이동시킨다[32, 42]. + +## ⚖️ Trade-offs & Caveats +리팩토링 기법 적용 시 다음과 같은 부작용과 제약 사항을 주의해야 한다. + +* **버그 도입의 위험 및 테스트 의존성:** 아무리 의미를 보존하는 마이크로 리팩토링이라도 오작동을 유발할 수 있다. 따라서 기존 동작을 검증할 수 있는 포괄적이고 빠른 자동화된 테스트 스위트(Unit Tests 등)가 필수적이다[43-45]. 자동화된 테스트가 결여된 거대한 레거시 코드 시스템에서 맹목적인 리팩토링을 수행하는 것은 극도로 위험하다[46, 47]. +* **일정 지연 및 자원 소모:** 시스템 아키텍처 전반에 걸친 대규모 리팩토링(Big Refactoring)은 몇 주에서 수개월이 소요될 수 있다[48, 49]. 이는 단기적인 새로운 기능 출시 압박과 상충될 수 있으며, 개발 인력이 비즈니스 요구사항 개발 대신 리팩토링에 투입되어야 하는 기회비용이 발생한다[50, 51]. +* **병합 충돌 (Merge Conflicts):** 리팩토링은 클래스 이름, 메서드 서명(Signature), 파일 위치 등을 빈번하게 변경한다. 이로 인해 여러 개발자가 협업하는 브랜치 환경에서 코드 통합 시 심각한 병합 충돌을 일으켜 통합 작업을 매우 고통스럽게 만들 수 있다[52, 53]. +* **과도한 엔지니어링 및 단기적 성능 저하:** 단순히 미래의 변경을 예측하고 불필요한 유연성이나 아키텍처 계층을 추가하는 '과도한 엔지니어링(Over-engineering)'의 위험이 있다[54, 55]. 또한, 임시 변수를 제거하고 질의(Query) 함수로 변경하는 기법 등은 단기적으로 동일한 계산을 여러 번 수행하게 만들어 약간의 성능(Runtime) 저하를 유발할 수 있다[56, 57]. +* **AI 도구의 역설:** 최근 AI 코딩 도구(LLM)를 리팩토링에 활용할 때, 숙련된 개발자는 AI가 생성한 복잡한 코드를 검증하고 교정하는 데 오히려 더 많은 시간을 소비하여 생산성이 약 19% 하락할 수 있는 '생산성 역설' 문제가 제기되었다[58, 59]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [소프트웨어 품질 및 평가 지표] +- [[Code Smells (코드 스멜)]] + - 연결 이유: 리팩토링 기법을 언제, 어디에 적용해야 할지 알려주는 코드 내의 구조적 결함 지표이다[60, 61]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 긴 메서드, 중복 코드, 기능 욕심, 데이터 뭉치 등 리팩토링이 필요한 구체적인 원인과 각 증상에 매칭되는 해결 기법(카탈로그)을 이해할 수 있다[39, 62]. +- [[Technical Debt (기술 부채)]] + - 연결 이유: 일정 압박이나 임시방편적인 개발로 인해 시스템에 누적된 구조적 빚을 의미하며, 리팩토링은 이 부채를 체계적으로 상환하여 유지보수성을 회복시키는 활동이다[63, 64]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링이 단순한 심미적 행위가 아니라 소프트웨어의 전체 수명 주기 비용을 낮추고 개발 속도를 높이는 '경제적 정당성'을 가지는 이유를 깊이 이해할 수 있다[65]. + +#### [프로세스 및 방법론] +- [[Test-Driven Development (TDD)]] + - 연결 이유: TDD의 핵심 사이클인 'Red-Green-Refactor'의 마지막 단계를 구성하며, 리팩토링을 수행하기 위한 필수 전제 조건인 자동화 테스트 기반을 제공한다[66-68]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기능을 변경하지 않고(안전망 안에서) 코드 구조만을 개선하는 리팩토링의 핵심 철학과 점진적 전진(Baby steps)의 중요성을 이해할 수 있다[69]. +- [[Rule of Three (3의 법칙)]] + - 연결 이유: 동일하거나 유사한 코드가 세 번 반복될 때 비로소 리팩토링을 통해 중복을 제거하라는 실용적인 경험 법칙이다[70, 71]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 조급한 추상화(Premature Abstraction)를 방지하고, 리팩토링의 경제적 타이밍을 결정하는 기준을 학습할 수 있다[72, 73]. +- [[Legacy Code (레거시 코드)]] + - 연결 이유: 마이클 페더스(Michael Feathers)는 레거시 코드를 '테스트가 없는 코드'로 정의했으며, 이를 리팩토링하기 위해서는 의존성을 깨는 특수한 리팩토링 기법들이 요구된다[74, 75]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트가 없는 환경에서 접점(Seam)을 찾아내고 우회 기법(Sprout Method, Wrap Method)을 사용하여 안전하게 코드를 리팩토링하는 심화 방법론을 배울 수 있다[76-78]. + +### Deeper Research Questions + +- 여러 리팩토링 기법(예: Replace Temp with Query, 다형성을 활용한 분기 대체 등)이 소프트웨어의 실행 시간 및 메모리 등 성능(Resource Usage)에 미치는 부정적 영향은 실제 프로덕션 환경에서 어떻게 프로파일링되고 최적화되는가? +- 마이크로서비스(Microservices) 및 분산 시스템 아키텍처 환경에서는 단일 프로세스 코드베이스에 맞춰진 기존의 마틴 파울러식 리팩토링 기법이 어떻게 적응 및 변형되어야 하는가? +- 테스트가 전무한 레거시 코드베이스에서 '접점(Seam)'을 활용하여 의존성을 끊어내고 단위 테스트를 삽입하는 과정은 어떤 아키텍처 파괴 위험을 수반하며, 이를 최소화하는 전략은 무엇인가? +- LLM 기반의 AI 코딩 어시스턴트를 활용하여 자동화된 리팩토링을 수행할 때, AI의 환각(Hallucination) 현상이나 미묘한 버그 삽입을 차단하기 위해 개발팀은 어떤 인간-기계 상호작용(Human-in-the-loop) 파이프라인을 구축해야 하는가? +- 경영진이나 비기술 이해관계자에게 리팩토링의 경제적 가치(총 소유 비용 절감, 신규 피처 개발 속도 향상)를 증명하기 위해 가장 신뢰할 수 있는 정량적 코드 품질 지표(Metrics)와 비즈니스 측정 방식은 무엇인가? + +### Practical Application Contexts + +- **Implementation:** 일상적인 프로그래밍 흐름 속에서 '쓰레기 줍기 리팩토링(Litter-Pickup Refactoring)'을 실천하여 함수 길이를 짧게 유지하고 변수명을 직관적으로 변경함으로써 미래의 인지 부하를 줄인다. +- **System Design:** 도메인 클래스에 너무 많은 책임이 몰릴 경우 '클래스 추출(Extract Class)'을 통해 책임을 분리하고, 다형성을 활용해 조건문을 교체하여 단일 책임 원칙(SRP) 및 개방-폐쇄 원칙(OCP)을 준수하는 아키텍처로 개선한다. +- **Operation / Maintenance:** '테스트 없는 코드는 레거시 코드'라는 인식 하에, 유지보수를 위해 오래된 코드를 변경하기 전 최소한의 캐릭터라이제이션 테스트(Characterization Tests)를 추가하여 안전망을 확보한 후 리팩토링을 수행한다. +- **Learning Path:** 마틴 파울러의 저서와 코드 스멜 카탈로그를 팀 내에서 지속적으로 스터디하고 페어 프로그래밍이나 코드 리뷰를 진행할 때, 리뷰 과정에서 직접 리팩토링 기법을 적용해 보며 구조적 통찰을 공유한다. +- **My Project Relevance:** 새로운 기능을 구현하기 직전에 코드를 분석하여 기능 추가가 어렵다고 판단될 경우, '준비적 리팩토링(Preparatory Refactoring)' 모자를 먼저 쓰고 구조를 다듬어(Make the change easy) 확장성을 확보한 다음 기능을 추가한다(Make the easy change). + +### Adjacent Topics + +- [[Design Patterns (디자인 패턴)]] + - 확장 방향: 많은 고급 리팩토링 기법(예: 상태/전략 패턴으로의 타입 코드 대체)은 궁극적으로 널리 알려진 디자인 패턴을 시스템에 자연스럽게 도입하는 과정과 동일하므로, 리팩토링의 '목표'로서 디자인 패턴을 함께 연구하면 시너지가 크다. +- [[Continuous Integration & Continuous Deployment (CI/CD)]] + - 확장 방향: 리팩토링은 잦고 작은 코드 변경을 수반하므로, 변경된 코드가 시스템을 망가뜨리지 않았음을 즉각적으로 피드백해주는 빠르고 강력한 CI/CD 파이프라인의 구축 및 자동화 테스트 통합 전략으로 이어진다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기).md b/10_Wiki/Topics/Architecture/Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기).md new file mode 100644 index 00000000..86fc90bc --- /dev/null +++ b/10_Wiki/Topics/Architecture/Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기).md @@ -0,0 +1,25 @@ +# [[Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기)]] + +## 📌 Brief Summary +Replace Conditional with Polymorphism(조건식을 다형성으로 바꾸기)는 객체의 타입에 따라 다르게 동작하는 복잡한 조건문(switch 또는 if-then-else 등)을 객체 지향의 다형성을 활용하여 해결하는 구조적 리팩토링 기법이다 [1, 2]. 조건문의 각 분기를 하위 클래스(subclass)의 오버라이딩 메서드로 옮기고, 기존 상위 클래스의 메서드는 추상(abstract) 메서드로 변경한다 [1]. 이 기법을 통해 기존 코드를 수정하지 않고도 새로운 하위 클래스를 추가하여 아키텍처 변경을 관리하기 쉽게 만들 수 있다 [3]. + +## 📖 Core Content +* **다형성 도입의 동기와 효과** + * 타입 코드나 문자열에 기반해 동작을 결정하는 명시적 조건문의 작성을 피할 수 있다 [2]. + * 동일한 조건문 패턴이 프로그램의 여러 곳에 산재해 있을 때 가장 큰 이점을 제공한다 [2]. 새로운 타입을 추가하고자 할 때, 모든 조건문을 일일이 찾아 수정할 필요 없이 새로운 하위 클래스를 만들고 적절한 메서드만 제공하면 된다 [2, 4]. + * 각 조건 분기의 행동이 개별 클래스로 독립되어 구현되므로 코드를 테스트하기가 훨씬 쉬워지고, 클라이언트는 하위 클래스에 대해 알 필요가 없어 시스템의 의존성이 감소한다 [2-4]. + +* **실행 절차 (Mechanics)** + 1. **상속 구조 생성:** 리팩토링을 시작하기 전, 다형성 동작을 수용할 적절한 상속 구조(Inheritance Structure)가 준비되어 있어야 한다 [5]. 기존 구조가 없다면 타입 코드를 하위 클래스로 바꾸거나(Replace Type Code with Subclasses) 상태/전략 패턴으로 바꾸는 기법(Replace Type Code with State/Strategy)을 사용하여 생성한다 [5, 6]. + 2. **조건문 추출 및 이동:** 대상 조건문이 더 큰 메서드의 일부라면 먼저 `Extract Method`로 분리해 낸 후, 필요하다면 `Move Method`를 통해 상속 구조의 최상단으로 옮긴다 [7]. + 3. **다형성 메서드 구현:** 각 하위 클래스 중 하나를 선택하여 조건문의 해당 분기 논리를 재정의(override) 메서드에 복사하고 알맞게 수정한다 [7]. + 4. **조건 분기 제거 및 테스트:** 하위 클래스의 구현을 컴파일하고 독립적으로 테스트한 뒤, 원본 조건문에서 해당 분기를 제거한다 [3, 8]. + 5. **추상화 완료:** 조건문의 모든 분기가 하위 클래스의 메서드로 전환될 때까지 동일한 과정을 반복한 후, 상위 클래스의 원본 메서드를 추상(abstract) 메서드로 선언한다 [8]. + +## ⚖️ Trade-offs & Caveats +* **객체 수명 주기 내 클래스 변경 불가 문제:** 하위 클래스를 통해 다형성을 구현하는 방식을 선택할 때의 가장 큰 제약은 한 번 생성된 객체는 수명 주기 동안 자신의 클래스를 변경할 수 없다는 점이다 [9]. 만약 조건의 기준이 되는 속성(예: 영화의 분류 체계나 직원의 직급)이 런타임에 동적으로 변경되어야 한다면 단순한 하위 클래스 생성이 작동하지 않으므로, 다소 복잡하더라도 간접 참조를 포함하는 상태 패턴(State)이나 전략 패턴(Strategy)을 적용하여 다형성을 구성해야 한다 [9-11]. +* **과도한 추상화(Overkill)의 위험:** 단일 메서드에만 영향을 미치고 향후 조건의 종류가 변경될 가능성이 거의 없는 소수의 조건문이라면, 다형성을 도입하는 것은 오히려 불필요한 과잉 설계(overkill)가 될 수 있다 [12]. 이러한 경우에는 `Replace Parameter with Explicit Methods` 기법과 같은 다른 방식을 적용하는 것이 더 나은 선택일 수 있다 [12]. +* **선행 리팩토링 작업의 필수성:** 이 기법을 수행하기 위해서는 다형성을 처리할 상속 구조가 미리 준비되어야 하므로, 이를 위해 기존 코드의 타입 코드나 조건 변수를 객체화하는 선행 리팩토링 과정이 요구되어 초기 작업 비용이 발생한다 [5, 13]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Replace Error Code with Exception (에러 코드를 예외로 바꾸기).md b/10_Wiki/Topics/Architecture/Replace Error Code with Exception (에러 코드를 예외로 바꾸기).md new file mode 100644 index 00000000..3ac56ba2 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Replace Error Code with Exception (에러 코드를 예외로 바꾸기).md @@ -0,0 +1,23 @@ +# [[Replace Error Code with Exception (에러 코드를 예외로 바꾸기)]] + +## 📌 Brief Summary +'에러 코드를 예외로 바꾸기(Replace Error Code with Exception)'는 메서드가 오류를 나타내기 위해 특수한 값을 반환하던 방식을 예외(Exception)를 던지는 방식으로 변경하는 리팩토링 기법이다 [1]. 전통적인 프로그래밍에서는 반환 코드로 오류를 처리하여 정상적인 실행 흐름과 오류 처리 흐름이 뒤섞이는 문제가 있었으나, 예외를 사용하면 이 둘을 명확하게 분리할 수 있다 [2]. 결과적으로 프로그램의 구조가 단순해지고 코드의 가독성과 이해도가 크게 향상된다 [2]. + +## 📖 Core Content +* **배경 및 동기:** 오류를 발견한 프로그램의 특정 루틴이 그 오류를 어떻게 처리해야 할지 직접 결정할 수 없는 경우가 많다 [2]. 유닉스(Unix)나 C 기반 시스템과 같은 전통적인 환경에서는 루틴의 성공이나 실패를 호출자에게 알리기 위해 주로 반환 코드(Return code)를 사용해왔다 [2]. 그러나 자바(Java)를 비롯한 현대 프로그래밍 언어들은 예외 처리 메커니즘을 제공하므로, 에러 코드 대신 예외를 사용하여 정상적인 프로세스와 오류 처리 프로세스를 명확히 분리하는 것이 코드를 이해하기 쉽게 만든다 [2, 3]. +* **예외의 종류 결정:** 리팩토링을 수행할 때 가장 먼저 발생한 예외가 검사 예외(Checked Exception)인지 비검사 예외(Unchecked Exception)인지 결정해야 한다 [4]. + * 호출자가 메서드를 호출하기 전에 상태를 검사해야 할 책임이 있는 상황에서 에러가 발생했다면, 이는 프로그래머의 버그이므로 비검사 예외(`IllegalArgumentException` 등)나 어서션(Assertion)을 사용해야 한다 [4-6]. + * 호출 전에 상태를 확인하는 것이 호출자의 책임이 아니라면, 메서드 시그니처에 검사 예외를 선언하여 호출자가 적절한 예외 처리(`try-catch`)를 하도록 유도해야 한다 [4, 5, 7]. +* **적용 절차 (Mechanics):** + 1. 예외의 종류(검사/비검사)를 결정한 뒤, 비검사 예외라면 호출자들이 메서드 호출 전 적절한 검사를 수행하도록 수정한다 [4]. + 2. 검사 예외라면 적절한 예외 클래스를 생성하거나 기존 예외를 사용하고, 호출자들이 `try-catch` 블록으로 메서드를 호출하도록 조정한다 [4, 7]. + 3. 메서드 내부에서 에러 코드를 반환하던 부분을 예외를 던지는(throw) 코드로 변경하고, 메서드 시그니처를 이에 맞게 갱신한다 [8]. + 4. 수정해야 할 호출자가 너무 많아 한 번에 변경하기 어렵다면, 예외를 던지는 새로운 중간 메서드를 생성한 뒤 기존 코드가 이를 호출하도록 임시로 변경하고, 호출자들을 하나씩 점진적으로 수정하는 방식을 취할 수 있다 [8-11]. + +## ⚖️ Trade-offs & Caveats +* **예외 남용의 위험성:** 에러 코드를 예외로 교체하는 것은 프로그램의 복잡성을 줄이는 훌륭한 방법이지만, 예외를 무분별하게 남용해서는 안 된다 [12]. 예외는 오직 예상치 못한 오류, 즉 '예외적인 동작'에만 국한해서 사용해야 한다 [12]. +* **조건 검사를 대체할 수 없음:** 예외가 일반적인 조건문(Conditional test)을 대체하는 용도로 쓰여서는 안 된다 [12]. 만약 호출자가 작업을 수행하기 전에 조건이 유효한지 합리적으로 확인할 수 있는 상황이라면, 예외를 던지게 두는 대신 호출자가 사전에 테스트(검사)하도록 강제해야 한다 [12]. 만약 조건 검사를 무시하고 예외를 남용하고 있다면, 반대로 '예외를 테스트로 바꾸기(Replace Exception with Test)' 리팩토링을 고려해야 한다 [3, 12]. +* **대규모 변경의 부담:** 검사 예외(Checked Exception)를 새롭게 적용할 경우, 해당 메서드를 호출하는 모든 코드와 호출되는 루틴을 동시에 수정해야 하므로 컴파일러 에러가 유발될 수 있다 [9]. 시스템 규모가 클 경우 한 번에 적용하기에는 리스크가 크고 작업량이 과도해질 수 있다는 제약이 따른다 [9]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Replace Type Code with Subclasses (분류 부호를 하위 클래스로 바꾸기).md b/10_Wiki/Topics/Architecture/Replace Type Code with Subclasses (분류 부호를 하위 클래스로 바꾸기).md new file mode 100644 index 00000000..f25e3d42 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Replace Type Code with Subclasses (분류 부호를 하위 클래스로 바꾸기).md @@ -0,0 +1,30 @@ +# [[Replace Type Code with Subclasses (분류 부호를 하위 클래스로 바꾸기)]] + +## 📌 Brief Summary +'분류 부호를 하위 클래스로 바꾸기(Replace Type Code with Subclasses)'는 객체의 동작에 영향을 미치는 불변(immutable)의 타입 코드(Type Code)를 하위 클래스 상속 구조로 대체하는 리팩토링 기법이다 [1]. 이 기법은 주로 타입 코드에 따라 다르게 동작하는 조건문을 다형성(Polymorphism)으로 해결하기 위한 필수적인 준비 단계로 활용된다 [2]. 결과적으로 클라이언트 코드가 가지고 있던 분기 처리 지식을 클래스 내부로 이동시켜, 새로운 타입이 추가될 때 기존 코드를 수정할 필요 없이 새로운 하위 클래스만 추가하도록 시스템을 유연하게 만든다 [3]. + +## 📖 Core Content +* **적용 목적 및 배경** + 타입 코드가 객체의 동작에 영향을 주지 않는 순수 데이터라면 단순히 '클래스로 타입 코드 대체하기(Replace Type Code with Class)'를 적용하면 되지만, 타입 코드 값에 따라 객체의 동작이 달라질 경우에는 다형성을 활용하여 변형된 동작을 처리하는 것이 가장 이상적이다 [1]. 이러한 동작의 변형은 주로 `switch`나 `if-then-else` 같은 조건문으로 나타나며, 조건문을 다형성으로 바꾸기(Replace Conditional with Polymorphism) 위해서는 먼저 이 기법을 통해 다형성 동작을 수용할 하위 클래스 상속 구조를 구축해야 한다 [2, 4]. + +* **구조적 장점 및 활용** + 이 리팩토링은 변형된 동작에 대한 지식을 클래스의 클라이언트에서 클래스 자체로 이동시킨다 [3]. 다형성을 적용하지 않은 상태에서는 새로운 타입이 추가될 때마다 프로그램 곳곳에 흩어진 모든 조건문을 찾아 수정해야 하지만, 이 기법을 도입하면 새로운 하위 클래스를 생성하기만 하면 되므로 타입 변형이 지속적으로 변경되는 환경에서 특히 가치가 높다 [3, 5]. 또한, 특정 타입 코드에만 해당하는 고유한 기능(메서드나 필드)이 존재할 경우, 이 리팩토링 이후에 '메서드 내리기(Push Down Method)'와 '필드 내리기(Push Down Field)'를 적용하여 각 하위 클래스로 책임을 명확히 분리할 수 있다 [3]. + +* **실행 절차(Mechanics)** + 1. 타입 코드를 자가 캡슐화(Self-encapsulate)한다. 생성자에 타입 코드가 전달된다면 생성자를 팩토리 메서드(Factory Method)로 교체한다 [6, 7]. + 2. 타입 코드의 각 값에 대응하는 하위 클래스를 생성한다. 하위 클래스에서는 상위 클래스의 타입 코드 게터(getter) 메서드를 재정의(override)하여 해당 하위 클래스에 맞는 값을 반환하도록 한다 [6]. + 3. 상위 클래스에서 타입 코드 필드를 제거하고, 타입 코드 접근자를 추상(abstract) 메서드로 선언한다 [6, 8]. + +* **코드 스멜과의 연관성** + 이 기법은 객체지향 원칙을 남용하여 절차적으로 작성된 코드나 '원시 타입 집착(Primitive Obsession)', '반복되는 switch 문(Switch Statements)' 등의 코드 스멜을 해결하는 주요 해결책으로 제시된다 [5, 9-11]. + +## ⚖️ Trade-offs & Caveats +* **객체 생성 후 타입 코드 변경 불가 (상태 가변성 문제)** + 객체가 생성된 이후 생명주기 동안 타입 코드의 값이 변경되어야 하는 상황이라면 이 기법을 사용할 수 없다 [4]. 객체지향 언어에서 객체는 생성 후 자신의 클래스를 변경할 수 없기 때문이다 [12]. +* **기존 상속 구조와의 충돌** + 대상 클래스가 이미 다른 논리적 이유로 인해 하위 클래스화(subclassing)되어 상속 구조를 띄고 있다면 이 기법을 적용할 수 없다 [4]. +* **대안 기법의 필요성** + 위의 두 가지 제약 사항(타입 코드가 가변적이거나 이미 상속이 사용 중인 경우) 중 하나라도 해당한다면, 상속을 통한 '하위 클래스로 바꾸기' 대신 위임(Delegation)을 활용하는 '타입 코드를 상태/전략 패턴으로 바꾸기(Replace Type Code with State/Strategy)' 기법을 대안으로 사용해야 한다 [4, 13, 14]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Rule of Three (3의 법칙).md b/10_Wiki/Topics/Architecture/Rule of Three (3의 법칙).md new file mode 100644 index 00000000..673a5c2e --- /dev/null +++ b/10_Wiki/Topics/Architecture/Rule of Three (3의 법칙).md @@ -0,0 +1,58 @@ +# [[Rule of Three (3의 법칙)]] + +## 📌 Brief Summary +**Rule of Three (3의 법칙)**은 소프트웨어 프로그래밍에서 유사한 코드 조각을 언제 리팩토링하여 중복을 피할지 결정하기 위한 경험 법칙(Rule of thumb)입니다 [1]. 마틴 파울러(Martin Fowler)의 저서를 통해 대중화되었으며 돈 로버츠(Don Roberts)가 제안한 "세 번 스트라이크면 리팩토링하라(Three strikes and you refactor)"는 개념으로 요약됩니다 [1, 2]. 즉, 처음이나 두 번째로 비슷한 코드를 작성할 때는 중복을 허용하되, 세 번째로 동일한 코드가 나타날 때는 새로운 프로시저나 메서드로 추출(Extract)하여 추상화해야 한다는 원칙입니다 [1, 3]. + +## 📖 Core Content +- **개념의 기원 및 정의**: 3의 법칙은 개발자가 리팩토링을 수행해야 하는 최적의 타이밍을 결정하는 지침입니다. 코드를 처음 작성할 때는 그냥 목적에 맞게 구현하고, 두 번째로 비슷한 작업을 할 때는 중복이 신경 쓰이더라도 그대로 진행하며, 세 번째로 동일한 형태의 작업이 반복될 때 비로소 리팩토링을 수행하라는 것입니다 [2]. +- **성급한 추상화(Premature Refactoring) 방지**: 리팩토링을 시도하는 많은 개발자가 범하는 실수는 코드를 무조건 작게 분할하여 추상화하려는 것입니다. 30줄짜리 코드를 가독성을 높인다는 명목하에 여러 클래스로 잘게 쪼개면, 코드를 읽을 때마다 함수 본문을 추적해야 하므로 오히려 유지보수를 어렵게 만듭니다 [4-6]. 3의 법칙은 사례가 3개 이상 모일 때까지 기다림으로써 공통점과 차이점을 더 명확히 파악하고 올바른 추상화를 찾을 수 있도록 돕습니다 [3]. +- **중복 허용 원칙 (WET - Write Everything Twice)**: 3의 법칙은 때로는 코드 중복을 그대로 두는 것이 더 나은 선택일 수 있음을 시사합니다 [7]. 두 번의 중복 코드만 있을 경우, 이를 리팩토링하고 잘못된 설계를 도입하는 비용이 단순히 중복을 유지보수하는 비용보다 더 클 수 있습니다 [8]. 하지만 세 번째 복사본이 나타나면 유지보수 비용이 리팩토링 비용을 초과하게 되므로 이때 개입합니다 [8]. +- **잘못된 추상화보다 저렴한 중복**: 개발자 샌디 메츠(Sandi Metz)는 "중복이 잘못된 추상화보다 훨씬 저렴하다(Duplication is far cheaper than the wrong abstraction)"라고 강조했습니다 [9]. 두 코드가 우연히 비슷해 보일 뿐, 서로 다른 개념을 나타내는 경우라면 중복을 유지하는 것이 낫습니다 [9]. + +## ⚖️ Trade-offs & Caveats +- **잘못된 추상화의 부작용**: 코드를 조기에 추상화할 경우, 새로운 요구사항이 생겼을 때 유연하게 대처하지 못하는 부작용이 발생합니다. 잘못된 추상화를 억지로 새로운 유즈케이스에 맞추기 위해 불리언 매개변수나 if 문 등을 추가하게 되며, 이는 코드를 끔찍하게 꼬이게 만듭니다 [7, 10]. +- **원칙의 도그마화 경계**: 3의 법칙은 어디까지나 경험에 기반한 가이드라인일 뿐 절대적인 규칙은 아닙니다 [1, 7]. 3번의 중복이 발생했다고 해서 기계적으로 추상화해야 하는 것은 아닙니다. 만약 추출하려는 추상화 개념에 명확한 이름을 붙일 수 없다면, 이는 아직 올바른 추상화를 찾지 못했다는 뜻이므로 억지로 리팩토링하지 말고 더 많은 중복 사례가 나타날 때까지 기다리는 것이 좋습니다 [7]. +- **코드 재사용과 유지보수의 역설**: 짧은 코드가 항상 가독성이 높은 것은 아닙니다 [11]. 단지 읽기 좋다는 이유만으로 한 번만 쓰일 헬퍼 함수를 무분별하게 만들어내면, 코드를 이해하기 위한 인지적 컨텍스트 스위칭 비용이 증가하여 유지보수를 저해하는 트레이드오프가 존재합니다 [6]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A: 설계 철학 및 원칙] +- [[DRY (Don't Repeat Yourself)]] + - 연결 이유: 3의 법칙은 코드의 중복을 방지하는 원칙이므로 DRY 원칙과 직접적인 대척점 혹은 상호 보완 관계에 있습니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: DRY 원칙을 맹목적으로 추구할 때 발생하는 '잘못된 추상화'의 부작용을 이해하고, 왜 때로는 WET(Write Everything Twice) 전략이나 3의 법칙이 실무적으로 더 안전한지 비교할 수 있습니다 [3, 9]. +- [[YAGNI (You Aren't Gonna Need It)]] + - 연결 이유: 미래를 미리 예측하여 불필요한 기능이나 구조를 만들지 말라는 원칙입니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 두 번의 중복 시점에서 성급하게 공통화 계층을 만들지 말고 세 번까지 기다리라는 3의 법칙의 밑바탕에 깔린 "확실한 필요성이 증명될 때까지 미뤄라"라는 철학을 깊이 이해할 수 있습니다 [8]. + +#### [관계 유형 B: 구체적 실행 기법 및 식별] +- [[Extract Method (함수 추출하기)]] + - 연결 이유: 세 번째 중복이 발생했을 때 이를 해결하기 위해 가장 우선적으로 실행하게 되는 핵심 리팩토링 무브(Move)입니다 [1]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 3의 법칙이 발동된 이후, 실제로 중복 코드를 어떻게 떼어내고, 변수 유효 범위(Scope)를 어떻게 처리하며, 이름을 어떻게 부여해야 안전하게 함수로 추출할 수 있는지 실천적인 기법을 배울 수 있습니다 [12-16]. +- [[Code Smells (코드 스멜)]] + - 연결 이유: 3의 법칙은 특정 코드 스멜, 즉 '중복 코드(Duplicated Code)'가 발견되었을 때 언제 조치를 취해야 할지를 알려주는 진단 도구 역할을 합니다. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 언제 코드가 악취를 풍기기 시작하는지, 그리고 어떤 스멜이 단순한 방치가 아니라 즉각적인 리팩토링을 필요로 하는 수준의 технический 부채(Technical Debt)인지 진단하는 기준을 확립할 수 있습니다 [17, 18]. + +### Deeper Research Questions +- 성급한 리팩토링으로 인해 이미 잘못 생성된 추상화(Wrong Abstraction)를 다시 원래대로 되돌리는(Defactoring 또는 Inline) 과정에서 결함을 최소화하는 가장 효과적인 전략은 무엇인가? +- 두 번의 중복 코드에서 무리하게 리팩토링을 수행했을 때, 추후 새로운 요구사항(Variation)이 추가됨에 따라 유지보수 비용이 기하급수적으로 증가한 구체적인 실무 사례는 어떠한가? +- TDD(Test-Driven Development)의 'Red-Green-Refactor' 라이프사이클 내에서, 3의 법칙은 어느 시기(개별 커밋 단계 vs 기능 완성 후)에 적용하는 것이 팀 생산성 관점에서 가장 이상적인가? +- 프론트엔드 UI 컴포넌트 개발(예: React, Vue)에서도 비즈니스 로직 중심의 백엔드와 동일하게 3의 법칙을 엄격히 적용할 수 있는가, 아니면 시각적 요소라는 특성상 별도의 임계값이 필요한가? +- 추상화를 진행하기 전, 3개의 중복 코드가 단순히 "우연히 똑같은(Accidental duplication)" 코드인지 아니면 "본질적으로 같은 개념의(Essential duplication)" 코드인지 개발자가 정확히 판별할 수 있는 기준이나 지표는 무엇인가? + +### Practical Application Contexts +- **Implementation:** 코드를 처음 작성하거나 기존 코드에 버그를 수정할 때 유사한 코드가 보인다고 즉시 별도의 헬퍼(Helper) 함수로 분리하지 않고, 동일한 로직을 세 번째로 복사해서 붙여넣어야 하는 순간에만 추상화 작업을 시작합니다. +- **System Design:** 소프트웨어 컴포넌트나 모듈을 설계할 때, 초기부터 완벽한 재사용성을 고려한 인터페이스나 추상 클래스를 설계하는 대신(Big Design Up Front), 충분한 중복 구현 사례가 축적된 후 점진적으로 공통 구조를 뽑아냅니다. +- **Operation / Maintenance:** 기존 레거시 코드를 유지보수할 때, 수많은 플래그(Flag)나 불리언 매개변수로 점철된 이해하기 힘든 공통 함수를 발견하면, 이를 다시 인라인(Inline)하여 중복된 코드로 되돌린 다음 처음부터 올바른 추상화를 재탐색합니다. +- **Learning Path:** 리팩토링의 개별 기법(어떻게 코드를 수정할 것인가)을 습득한 개발자가, 언제 리팩토링을 해야 하고 언제는 하지 말아야 하는지에 대한 타이밍과 트레이드오프를 학습하는 다음 단계의 지식으로 활용됩니다. +- **My Project Relevance:** 팀 프로젝트에서 발생하는 과도한 추상화를 방지하기 위한 코드 리뷰 기준으로 활용할 수 있습니다. 팀원이 두 개의 비슷한 코드를 공통화하려고 할 때, "결합도가 높아질 위험이 있으니 세 번째 유즈케이스가 나올 때까지 기다리자"는 객관적 논거로 쓰입니다. + +### Adjacent Topics +- [[Over-engineering (오버엔지니어링)]] + - 확장 방향: 3의 법칙을 무시하고 너무 조기에 공통화를 시도했을 때, 시스템 아키텍처가 실제로 필요하지 않은 복잡성을 띄게 되는 오버엔지니어링 현상과 그 비용적 손실을 확장하여 조사할 수 있습니다. +- [[Technical Debt (기술 부채)]] + - 확장 방향: 3의 법칙을 지키지 않아 방치된 중복 코드로 인해 발생하는 유지보수 부채와, 반대로 너무 이른 추상화로 인해 발생하는 아키텍처 부채 중 어떤 부채가 이자가 더 비싼지 비교 연구해 볼 수 있습니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Scratch Refactoring (스크래치 리팩토링).md b/10_Wiki/Topics/Architecture/Scratch Refactoring (스크래치 리팩토링).md new file mode 100644 index 00000000..0d20cb13 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Scratch Refactoring (스크래치 리팩토링).md @@ -0,0 +1,53 @@ +# [[Scratch Refactoring (스크래치 리팩토링)]] + +## 📌 Brief 임무 +Scratch Refactoring(스크래치 리팩토링)은 테스트 코드가 없고 파악하기 어려운 레거시 코드를 이해하기 위해 안전장치 없이 자유롭게 코드를 수정해보는 탐색적 기법입니다 [1, 2]. 이 기법의 목적은 코드를 실제로 정리하는 것이 아니라 코드의 동작 방식을 친숙하게 파악하는 데 있습니다 [2]. 탐색이 끝난 후에는 수정한 내용을 반드시 원래 상태로 되돌리고(revert) 정식 프로세스를 시작해야 하는 것이 핵심 규칙입니다 [2]. + +## 📖 Core Content +- **목적과 활용 배경**: 마이클 페더스(Michael Feathers)의 저서 "Working Effectively with Legacy Code"에서 소개된 이 기법은, 개발자가 직접 작성하지 않았고 테스트와 문서화가 부족하여 어디서부터 시작해야 할지 막막한 레거시 코드를 다룰 때 사용됩니다 [1-3]. +- **실행 방식**: 스크래치 리팩토링의 유일한 규칙은 '작업이 끝난 후 변경 사항을 되돌린다(revert)'는 것입니다 [2]. 최종적으로 코드를 폐기할 것이기 때문에, 개발자는 시스템을 망가뜨릴 걱정 없이 불안전한 변경(unsafe changes)을 자유롭게 시도할 수 있습니다 [2]. 코드를 파악하기 위해 함수를 추출해 보거나, 구조를 단순화하거나, 변수 이름을 마음껏 변경하는 등 코드와 적극적으로 상호작용합니다 [2]. +- **사후 처리**: 코드를 충분히 파악하고 나면, 시도했던 모든 변경 사항을 취소하여 원래 상태로 되돌립니다 [2]. 그런 다음 확보한 코드에 대한 이해도를 바탕으로 적절한 자동화 테스트를 작성하고, 이후 실제 적용할 안전한 변경과 리팩토링을 정식으로 다시 시작하게 됩니다 [2, 4]. + +## ⚖️ Trade-offs & Caveats +- **엄격한 원상 복구(Revert)의 의무**: 이 기법은 테스트라는 안전망 없이 코드를 변경하는 것이기 때문에, 실수로 변경 사항을 커밋하거나 남겨두면 시스템에 심각한 버그를 유발할 수 있는 위험(Risk)이 따릅니다 [2, 4]. 따라서 목적 달성 후 반드시 코드를 되돌려야 한다는 엄격한 제약 사항이 존재합니다 [2]. +- **단기적인 시간 소모**: 결국 버려질 코드 변경 작업에 시간을 투자해야 하므로 단기적으로는 비효율적인 것처럼 보일 수 있습니다. 하지만 이는 복잡한 코드를 이해하고 이후에 안전한 테스트를 작성하기 위한 발판을 마련하는 것이므로, 장기적인 관점에서는 기술 부채를 다루기 위한 유용한 투자로 볼 수 있습니다 [2]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [작업 대상 및 환경] +- [[Legacy Code (레거시 코드)]] + - 연결 이유: 스크래치 리팩토링이 주로 적용되는 대상이며, 저서에서는 이를 '테스트가 없는 코드'로 정의합니다 [1]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 스크래치 리팩토링이 왜 필요하며, 어떤 환경(테스트 부재, 높은 복잡도)에서 그 가치를 발휘하는지 이해할 수 있습니다. + +#### [연계 기법 및 후속 조치] +- [[Automated Tests (자동화된 테스트)]] + - 연결 이유: 스크래치 리팩토링으로 코드를 이해하고 원상 복구한 뒤, 가장 먼저 수행해야 하는 핵심 단계입니다 [2, 4]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 시 기능이 깨지지 않았음을 보장하는 피드백 루프의 중요성과 불안전한 변경과 안전한 변경의 차이를 파악할 수 있습니다. +- [[Exploratory Refactoring (탐색적 리팩토링)]] + - 연결 이유: 얽혀있는 코드베이스를 파악하기 위해 다른 접근법으로 시간을 할애하여 코드를 분석하는, 유사한 성격을 지닌 기법으로 함께 언급됩니다 [5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 복잡한 레거시 코드를 파악하는 다양한 실무적 방법론과 마인드셋을 비교하며 확장할 수 있습니다. + +### Deeper Research Questions +- 스크래치 리팩토링 과정에서 얻은 함수 분리 및 변수명 변경의 아이디어를 코드 복구(revert) 이후에 어떻게 유실 없이 효율적으로 정식 리팩토링에 반영할 수 있는가? +- 안전하지 않은 변경(unsafe changes)을 기반으로 코드를 파악할 때, 부수 효과(side effects)가 큰 언어나 프레임워크 환경에서 주의해야 할 한계점은 무엇인가? +- 스크래치 리팩토링을 진행하는 동안 파악하게 되는 의존성 문제들을 'Seams(접점)'을 찾는 과정과 어떻게 체계적으로 연결할 수 있는가? +- 시간적 압박이 심한 스프린트 내에서, 결국 되돌려야 하는 스크래치 리팩토링에 할애할 시간을 어떻게 산정하고 정당화할 수 있는가? +- 순수 탐색 목적인 '스크래치 리팩토링'과 '탐색적 리팩토링(Exploratory Refactoring)' 간의 실무 절차상의 구체적인 차이점은 무엇인가? + +### Practical Application Contexts +- **Implementation:** 테스트가 없고 복잡한 레거시 코드를 처음 수정해야 할 때, 코드를 직접 분해해보고 리네이밍해보는 등 부담 없는 실습 및 분석 도구로 활용합니다 [2]. +- **System Design:** 문서화되지 않아 블랙박스처럼 작동하는 시스템의 내부 동작 흐름과 데이터 의존성을 파악하는 사전 조사 단계로 사용됩니다 [2]. +- **Operation / Maintenance:** 유지보수 과정에서 이해할 수 없는 코드를 만나 변경이 망설여질 때, 원상 복구를 전제로 과감한 변경을 시도하여 시스템의 동작 원리를 터득하는 데 적용합니다 [2, 3]. +- **Learning Path:** "효과적인 레거시 코드 작업(Working Effectively with Legacy Code)"을 배우는 과정 중, 테스트 코드를 작성할 진입점을 찾지 못할 때 돌파구를 마련하는 필수 학습 기법으로 다뤄집니다 [1, 2]. +- **My Project Relevance:** 문서화 및 테스트가 전무한 외부 코드를 인수인계받아 긴급한 기능을 추가해야 할 때, 사전 코드 분석 및 안전한 리팩토링 전략을 수립하기 위해 도입할 수 있습니다. + +### Adjacent Topics +- [[Seams (접점)]] + - 확장 방향: 스크래치 리팩토링을 통해 코드의 흐름을 이해한 후, 테스트를 주입하기 위해 소스 코드를 편집하지 않고도 프로그램의 동작을 바꿀 수 있는 '접점'을 식별하는 방법론으로 학습을 확장합니다 [4, 6]. +- [[Sprout & Wrap Techniques (스프라우트와 랩 기법)]] + - 확장 방향: 스크래치 리팩토링 후에도 전체 클래스나 함수에 테스트를 작성할 시간이 부족할 때, 새로운 코드를 격리하여 추가하는 대안적인 레거시 코드 대처 기법으로 연결하여 살펴볼 수 있습니다 [7-9]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Seam (접점).md b/10_Wiki/Topics/Architecture/Seam (접점).md new file mode 100644 index 00000000..7bca74be --- /dev/null +++ b/10_Wiki/Topics/Architecture/Seam (접점).md @@ -0,0 +1,20 @@ +# [[Seam (접점)]] + +## 📌 Brief 소스 +접점(Seam)은 소스 코드를 직접 수정하지 않고도 프로그램의 동작을 변경하거나 갈아 끼울 수 있는 지점을 의미합니다 [1-3]. 이 개념은 마이클 페더스(Michael Feathers)가 레거시 코드 리팩토링 시 테스트 작성을 방해하는 의존성을 끊어내기 위한 도구로 도입했습니다 [3, 4]. 개발자는 객체(Object), 컴파일 전처리(Preprocessing), 링크(Link) 등의 접점을 활용하여 시스템의 기능을 격리시키고 안전하게 단위 테스트를 작성할 수 있습니다 [5, 6]. + +## 📖 Core Content +* **레거시 코드 딜레마 해결**: 레거시 코드를 안전하게 리팩토링하려면 테스트가 필수적이나, 테스트를 작성하려면 기존 코드를 수정하여 무거운 의존성(DB, 네트워크 등)을 제거해야 하는 모순에 빠집니다 [3, 7]. 접점(Seam)은 원본 코드를 편집하지 않으면서도 외부 의존성 호출 부분을 테스트용 가짜 객체(Mock) 등으로 대체하여 이 딜레마를 해결합니다 [1, 3]. +* **활성화 지점(Enabling Point)**: 모든 접점은 어떤 동작을 사용할지 결정하고 제어할 수 있는 '활성화 지점'을 갖습니다 [8]. 이 지점을 통해 프로덕션 환경의 동작과 테스트 환경의 동작을 다르게 설정할 수 있습니다 [8]. +* **접점의 주요 유형**: + * **객체 접점 (Object Seams)**: 객체지향 언어에서 가장 널리 쓰이고 유용한 접점입니다 [1, 6]. 어떤 메서드가 실행될지가 호출 시점의 객체 타입이나 전달되는 인자(Argument)에 의해 결정되는 다형성 특징을 이용합니다 [6, 9, 10]. 테스트 시 이 활성화 지점을 통해 테스트용 서브클래스나 가짜 객체를 주입할 수 있습니다 [10]. + * **전처리 접점 (Preprocessing Seams)**: C나 C++처럼 매크로 전처리기가 컴파일 이전에 실행되는 언어에서 사용 가능한 접점입니다 [5]. `#include`나 `#define` 매크로를 이용해 코드가 컴파일되기 전에 텍스트 자체를 치환하여 실제 동작 대신 테스트용 코드가 실행되도록 만듭니다 [11, 12]. + * **링크 접점 (Link Seams)**: 컴파일 후 중간 코드를 엮어 완전한 프로그램을 만드는 링커(Linker) 단계에서 발생하는 접점입니다 [13]. Java의 경우 `classpath`를 변경하여 원본 클래스 대신 테스트용 클래스를 로드하게 할 수 있고, C/C++에서는 테스트용 라이브러리를 스텁(Stub)으로 만들어 빌드 시 프로덕션 라이브러리 대신 연결(Link)되게 할 수 있습니다 [14, 15]. + +## ⚖️ Trade-offs & Caveats +* **유형에 따른 명시성과 유지보수성의 차이**: 접점 중 가장 명시적이고 권장되는 방식은 객체 접점(Object Seams)입니다 [16]. 반면 전처리 접점과 링크 접점은 프로그램 소스 텍스트 내부가 아니라 빌드 스크립트나 IDE 설정 같은 '외부'에 활성화 지점을 두기 때문에 눈에 띄기 어렵습니다 [6, 16]. 이로 인해 이러한 접점들에 의존하여 작성된 테스트는 유지보수하기가 매우 까다로울 수 있으며, 다른 대안이 없고 의존성이 시스템 전체에 퍼져있는 제한적인 경우에만 사용하는 것이 바람직합니다 [16]. +* **초기 설계의 미학적 손실 감수**: 레거시 코드를 테스트 하네스(Test Harness) 안으로 가져오기 위해 접점을 만들고 의존성을 분리하는 과정은 불가피하게 코드를 부분적으로 더 지저분하게 만들 수도 있습니다 [17]. 하지만 이는 시스템을 치유하기 위한 외과 수술과 같으므로, 코드를 더 건강한 상태(테스트 가능한 상태)로 만들기 위해서는 일시적인 심미성 하락을 감수해야 합니다 [17, 18]. +* **테스트와 프로덕션 환경의 혼동 주의**: 링크 접점 등을 사용할 때는 시스템이 테스트 환경에서 동작하는지 프로덕션 환경에서 동작하는지 그 차이를 명확히 구분 및 관리하여 혼란을 방지해야 합니다 [6]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Seams (이음새).md b/10_Wiki/Topics/Architecture/Seams (이음새).md new file mode 100644 index 00000000..69ec9986 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Seams (이음새).md @@ -0,0 +1,20 @@ +# [[Seams (이음새)]] + +## 📌 Brief Summary +Seams(이음새 혹은 접점)는 소스 코드를 직접 편집하지 않고도 프로그램의 동작을 변경할 수 있는 지점을 의미한다 [1-3]. 이는 마이클 페더스(Michael Feathers)가 레거시 코드에 테스트를 작성하기 위해 까다로운 의존성을 끊어내는 방법으로 도입한 핵심 개념이다 [1, 3]. 프로그래밍 언어와 빌드 단계에 따라 객체 이음새, 전처리 이음새, 링크 이음새 등으로 나뉘며, 각 이음새는 동작의 변경을 결정하는 '활성화 지점(Enabling Point)'을 동반한다 [4, 5]. + +## 📖 Core Content +* **개념적 배경 및 목적:** 레거시 코드를 안전하게 변경하려면 테스트를 작성해야 하지만, 기존 코드에는 데이터베이스나 서드파티 서버 등 테스트하기 어려운 의존성이 얽혀 있는 경우가 많다 [1, 3, 6]. 이음새는 코드를 직접 수정하지 않고도 이러한 의존성을 분리하거나 코드 내부 상태를 감지(sense)할 수 있게 해주는 구조적 지점이다 [1, 7]. +* **활성화 지점 (Enabling Point):** 모든 이음새는 프로그램의 동작 방식을 선택할 수 있는 '활성화 지점'을 갖는다 [5]. 소스 코드는 프로덕션과 테스트 환경에서 동일하게 유지하되, 이 활성화 지점(예: 전처리기 매크로 정의, 클래스패스, 객체 생성 시점 등)을 통해 테스트 환경에서만 다른 동작을 주입할 수 있다 [5, 8, 9]. +* **이음새의 종류:** 소스 코드가 실행 가능한 프로그램으로 변환되는 각 단계에서 다양한 이음새가 발생한다 [4]. + * **객체 이음새 (Object Seams):** 객체 지향 언어에서 가장 널리 쓰이고 유용한 이음새다 [10]. 메서드 호출 시 다형성을 활용하여 실제 객체 대신 가짜 객체(Mock)나 하위 클래스를 주입하는 방식으로 동작을 변경한다 [3, 11]. + * **전처리 이음새 (Preprocessing Seams):** C나 C++와 같은 언어에서 제공하는 매크로 전처리기를 활용하는 방식이다 [12, 13]. 컴파일 이전 단계에서 특정 함수 호출 등을 테스트용 매크로로 치환하여 의존성을 배제한다 [14]. + * **링크 이음새 (Link Seams):** 컴파일 이후 링커(Linker)가 중간 표현을 결합하는 단계나 자바(Java)의 클래스패스(Classpath)를 활용하는 방식이다 [8, 15]. 프로덕션용 라이브러리 대신 테스트용(Stub) 라이브러리를 연결하여 외부 의존성의 실행을 대체한다 [16, 17]. + +## ⚖️ Trade-offs & Caveats +* **유지보수 및 명시성 문제:** 전처리 이음새나 링크 이음새는 객체 이음새에 비해 명시적이지 않아 테스트 코드를 유지보수하기 어렵게 만들 수 있다 [18]. 따라서 객체 지향 언어에서는 객체 이음새를 최우선으로 사용하고, 전처리 및 링크 이음새는 의존성이 코드 전반에 퍼져 있어 다른 대안이 없을 때만 제한적으로 사용해야 한다 [18]. +* **코드 가독성 저하:** C/C++에서 전처리 이음새를 구현하기 위해 조건부 컴파일 지시자(`#ifdef`, `#ifndef` 등)를 남용하면, 하나의 소스 파일에 여러 버전의 프로그램이 섞이게 되어 코드의 명확성(clarity)이 심각하게 떨어지며 숨겨진 버그를 유발할 수 있다 [19]. +* **테스트 환경 구성의 혼란:** 링크 이음새를 사용할 경우 컴파일러 외부의 빌드 스크립트나 경로 설정에 의존하게 되므로 적용 여부를 눈치채기 어려울 수 있다 [10]. 테스트 환경과 프로덕션 환경 간의 차이가 명백하게 드러나도록 관리하지 않으면 프로덕션 빌드 시 예기치 않은 오류가 발생할 위험이 있다 [10]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Singleton Pattern (싱글톤 패턴).md b/10_Wiki/Topics/Architecture/Singleton Pattern (싱글톤 패턴).md new file mode 100644 index 00000000..26a79a10 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Singleton Pattern (싱글톤 패턴).md @@ -0,0 +1,22 @@ +# [[Singleton Pattern (싱글톤 패턴)]] + +## 📌 Brief Summary +싱글톤 패턴(Singleton Pattern)은 Gang of Four(GoF)가 제안한 디자인 패턴 중 객체 생성(Creational) 패턴에 속하는 기법입니다 [1, 2]. 이 패턴은 특정 클래스에 대해 언제 요청하더라도 항상 동일한 단일 인스턴스(single instance)만을 반환하도록 보장하는 특징이 있습니다 [3]. 제공된 소스 내에서는 주로 상태가 절대 변하지 않는 Null 객체를 구현할 때 이 패턴이 활용된다고 간략히 언급되어 있습니다 [3]. + +## 📖 Core Content +소스에 관련 정보가 부족합니다. + +제공된 텍스트 내에서 싱글톤 패턴의 상세한 메커니즘이나 구체적인 구현 방법에 대한 전문적인 설명은 부족하며, 아래와 같은 제한적인 정보만 확인됩니다. + +* **생성 패턴(Creational Patterns)으로 분류**: 싱글톤은 팩토리 메서드(Factory Method), 추상 팩토리(Abstract Factory), 빌더(Builder), 프로토타입(Prototype) 패턴 등과 함께 객체 생성과 관련된 디자인 패턴으로 분류됩니다 [2]. +* **단일 인스턴스의 보장과 널 객체(Null Object) 구현**: 널 객체는 어떠한 속성도 변하지 않는 상수(constant) 상태를 항상 유지해야 합니다 [3]. 따라서 널 객체 패턴을 구현할 때 싱글톤 패턴을 적용하는 것이 적합합니다. 예를 들어 시스템에서 '누락된 사람(missing person)'을 요청할 때마다 싱글톤 패턴을 통해 매번 동일한 단일 인스턴스만 반환하도록 만듭니다 [3]. +* **객체 지향 설계의 필수 지식**: 전략(Strategy), 책임 연쇄(Chain of Responsibility) 패턴 등과 함께, 객체 지향 설계를 지적으로 논의하기 위해 기본적으로 숙지해야 할 핵심 GoF 패턴 중 하나로 언급됩니다 [1]. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. + +제공된 소스 데이터에는 싱글톤 패턴을 적용할 때 발생할 수 있는 부작용, 제약 사항, 최적화 문제 또는 기술적 반대 급부(Trade-off)에 대한 어떠한 구체적인 내용도 포함되어 있지 않습니다. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Special Case Pattern (특수 사례 패턴).md b/10_Wiki/Topics/Architecture/Special Case Pattern (특수 사례 패턴).md new file mode 100644 index 00000000..c7b9a16a --- /dev/null +++ b/10_Wiki/Topics/Architecture/Special Case Pattern (특수 사례 패턴).md @@ -0,0 +1,19 @@ +# [[Special Case Pattern (특수 사례 패턴)]] + +## 📌 Brief Summary +특수 사례 패턴(Special Case Pattern)은 특정한 형태의 예외적 동작을 수행하는 클래스의 개별 인스턴스를 생성하여 모델링하는 패턴이다 [1]. 이 패턴은 반복적인 널(Null) 확인 코드나 예외 처리 코드를 줄여 다형성을 통해 깔끔하게 시스템이 동작할 수 있도록 돕는다 [1-3]. 대표적으로 '알 수 없는 고객'이나 '숫자가 아님(NaN)' 등의 예외 상황을 독립적인 객체로 다루는 데 활용된다 [1]. + +## 📖 Core Content +* **패턴의 개념과 가치**: 특수 사례 클래스는 특별한 동작을 가진 클래스의 특정 인스턴스를 의미한다 [1]. 이 패턴이 제공하는 가장 핵심적인 가치는 오류를 다루는 코드를 대폭 줄여준다는 점이다 [1]. +* **Null 객체의 확장**: 'Null 객체 도입(Introduce Null Object)'은 특수 사례 패턴의 대표적인 적용 형태이다 [1, 4]. 클라이언트가 객체의 타입을 묻거나 널(Null)인지 조건문으로 확인한 뒤 분기하는 대신, 다형성을 활용하여 객체에게 바로 동작을 지시하도록 코드를 개선한다 [2, 3]. +* **다양한 특수 사례의 세분화**: 널(Null)이나 예외 상황의 종류가 구체적으로 다를 경우, 상황에 맞춰 별도의 특수 사례 클래스들을 구축할 수 있다 [5]. 예를 들어 '아직 입주하지 않아 고객이 없는 경우(NoCustomer)'와 '누구인지 알 수 없는 고객(UnknownCustomer)'을 각각의 특수한 사례로 나누어 다룰 수 있다 [1, 5]. +* **데이터 보존과 활용**: 특수 사례 객체는 단순히 예외를 피하는 것을 넘어 자체적인 데이터를 가질 수도 있다 [5]. 가령 '알 수 없는 고객' 객체에 사용 기록 데이터를 임시로 저장해 두었다가, 나중에 고객의 정체를 알게 되었을 때 비용을 청구하는 식으로 활용이 가능하다 [5]. +* **연산의 연속성 보장**: 특수 사례는 연산 시 예외를 던지지 않고 안전한 형태의 값을 지속적으로 반환하게 할 수 있다 [1]. 자바의 부동 소수점에서 '숫자가 아님(NaN)' 값으로 연산하면 계속 NaN이 반환되는 것처럼, Null 객체의 접근자(accessor)를 호출했을 때 또 다른 Null 객체를 반환하도록 구성하는 방식이 이에 해당한다 [1, 6]. + +## ⚖️ Trade-offs & Caveats +* 특수 사례 클래스나 Null 객체로 동작을 통합하는 것은 대부분의 클라이언트들이 해당 특수 상황에 대해 '동일한(표준적인) 응답'을 원할 때만 실질적인 의미가 있다 [7]. +* 많은 클라이언트가 같은 동작을 원한다면 기본으로 제공되는 특수 사례의 동작에 의존하여 코드를 단순화할 수 있다 [7]. +* 반면, 특정 클라이언트가 표준 응답과는 다른 특별한 대처 방식을 원한다면, 결국 해당 클라이언트는 `isNull()` 등과 같은 메서드를 사용해 직접 상태를 검사하고 별도의 처리를 수행해야 한다는 한계가 존재한다 [7]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Sprout & Wrap Techniques (스프라우트 & 랩 기법).md b/10_Wiki/Topics/Architecture/Sprout & Wrap Techniques (스프라우트 & 랩 기법).md new file mode 100644 index 00000000..2dc8bb6a --- /dev/null +++ b/10_Wiki/Topics/Architecture/Sprout & Wrap Techniques (스프라우트 & 랩 기법).md @@ -0,0 +1,26 @@ +# [[Sprout & Wrap Techniques (스프라우트 & 랩 기법)]] + +## 📌 Brief Summary +스프라우트(Sprout)와 랩(Wrap) 기법은 테스트가 부족하고 당장 전면적인 리팩토링을 수행할 시간이 없는 레거시 시스템에 새로운 코드를 안전하게 추가하기 위해 사용하는 방법이다 [1]. 기존의 방대한 코드를 직접 수정하는 것을 최소화하여 새로운 버그를 도입할 위험을 줄이면서도, 새롭게 추가되는 로직에 대해서는 격리된 상태로 단위 테스트를 작성할 수 있게 해준다 [2, 3]. + +## 📖 Core Content +레거시 코드 베이스는 기존 코드 덩어리가 새로운 코드를 계속 끌어들이는 중력과 같은 힘을 가지는 경우가 많아, 수천 줄짜리 클래스에 불과 몇 줄의 로직을 무분별하게 덧붙이는 결과를 낳기 쉽다 [1]. 테스트를 작성하고 구조를 개선할 시간이 절대적으로 부족한 까다로운 상황에서 다음 두 가지 기법을 활용할 수 있다 [1]. + +* **스프라우트 기법 (Sprout Technique)** + * 기존의 거대한 메서드를 직접 건드리는 대신, 새롭게 추가해야 할 로직을 아예 다른 곳(새로운 메서드나 클래스 등)에 분리하여 작성하는 방식이다 [2, 3]. + * 새로 작성된 코드는 격리되어 있으므로 쉽게 단위 테스트를 수행할 수 있다 [2, 3]. + * 테스트가 완료된 후, 기존 레거시 코드 내의 적절한 삽입 지점(insertion point)에서 이 새로운 코드를 호출하도록 연결한다 [2, 3]. + +* **랩 기법 (Wrap Technique)** + * 새롭게 추가해야 할 변경 사항이 기존 코드의 실행 전이나 후에 발생해야 할 때 사용하는 기법이다 [4]. + * 우선 변경하고자 하는 기존 메서드의 이름을 다른 것으로 바꾼다 [4]. + * 그리고 원래의 이름과 시그니처를 가진 새로운 메서드를 생성한다 [4]. + * 이 새로운 메서드 내부에서 이름이 바뀐 예전 메서드를 호출하고, 그 앞이나 뒤에 테스트가 가능한 새로운 로직을 추가하여 감싸는(Wrap) 형태를 취한다 [4]. + * 이 방식을 통해 옛 메서드는 테스트 시에 프로그램의 동작을 변경할 수 있는 접점(Seam)의 역할을 하게 된다 [5]. + +## ⚖️ Trade-offs & Caveats +* **근본적인 해결책의 부재:** 스프라우트와 랩 기법은 레거시 코드를 다루는 데 있어 매우 유용한 도구이지만, 이들이 이상적인 해결책은 아니며 나름의 함정(pitfalls)을 가지고 있다 [5]. +* **코드 품질 개선의 한계:** 이 기법들은 근본적으로 리팩토링이나 테스트 작성을 위한 충분한 시간이 없을 때 기존 코드의 악화를 막고 새 코드를 안전하게 넣기 위해 사용하는 임시방편에 가깝다 [1]. 따라서 지속적으로 이러한 기법에만 의존할 경우, 기존에 존재하는 거대하고 구조가 엉망인 코드 자체의 기술 부채는 그대로 남게 된다 [1, 3]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Sprout Method (스프라우트 메서드).md b/10_Wiki/Topics/Architecture/Sprout Method (스프라우트 메서드).md new file mode 100644 index 00000000..d8c2ee88 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Sprout Method (스프라우트 메서드).md @@ -0,0 +1,59 @@ +# [[Sprout Method (스프라우트 메서드)]] + +## 📌 Brief Summary +스프라우트 메서드(Sprout Method)는 테스트가 없는 거대한 레거시 코드에 새로운 기능을 추가할 때, 기존 코드를 직접 수정하는 대신 새로운 로직을 별도의 검증된 함수로 분리하여 작성하는 기법입니다 [1-3]. 격리된 새로운 코드에 대해서만 단위 테스트(Unit Test)를 작성한 뒤, 기존 코드의 적절한 삽입 지점(Insertion point)에서 이를 호출하여 수정에 따른 위험을 최소화합니다 [2]. 이 기법은 시간이 부족하여 기존 코드를 전면적으로 리팩토링하거나 테스트를 작성하기 어려울 때 유용하게 활용됩니다 [1]. + +## 📖 Core Content +* **도입 배경:** 거대한 코드 덩어리(Big lumps of code)는 중력을 가진 것처럼 더 많은 코드를 끌어당기는 성질이 있어, 이미 2,000줄이 넘는 클래스에 단순히 `if` 문 몇 개를 추가하는 방식으로 작업하면 유지보수가 매우 어려워집니다 [1]. 스프라우트 메서드는 테스트 코드를 작성할 시간이 없는 긴박한 상황에서 이러한 레거시 코드의 악순환을 끊기 위해 사용됩니다 [1]. +* **실행 단계:** + 1. **새로운 코드 격리 및 생성:** 추가해야 할 신규 비즈니스 로직을 기존 레거시 코드 밖의 완전히 새로운 위치(다른 메서드 등)에 작성합니다 [2]. + 2. **단위 테스트 작성:** 새로 분리된 코드는 기존의 엉킨 의존성과 분리되어 있으므로 쉽게 단위 테스트를 작성할 수 있습니다 [2]. + 3. **삽입 지점 식별:** 기존 레거시 코드 내에서 새로운 코드가 호출되어야 할 정확한 위치(Insertion point)를 찾습니다 [2]. + 4. **호출 연결:** 기존 코드의 삽입 지점에서 새로 작성한 격리된 메서드를 호출하여 최소한의 변경만으로 로직을 통합합니다 [2]. +* **적용 범위:** 단일 메서드(Sprout Method) 수준뿐만 아니라 전체 클래스(Sprout Class), 혹은 새로운 코드를 격리할 수 있는 그 어떤 단위로도 확장하여 적용할 수 있습니다 [2, 4]. 기존의 거대한 메서드를 직접 건드리는 대신 호출만 추가하므로 위험을 최소화할 수 있습니다 [3]. + +## ⚖️ Trade-offs & Caveats +이 기법은 이상적인 해결책은 아니며 몇 가지 함정(pitfalls)을 내포하고 있습니다 [5]. 스프라우트 메서드를 사용하면 새로 추가된 코드의 안정성은 보장할 수 있지만, 기존의 테스트되지 않은 레거시 코드는 여전히 복잡한 상태 그대로 남게 됩니다 [5]. 즉, 근본적인 기술 부채를 상환하는 전면적인 리팩토링이라기보다는, 테스트가 없고 복잡한 레거시 코드를 제한된 시간 내에 다루기 위해 사용하는 실용적이고 우회적인 도구(Workaround)에 가깝습니다 [3, 5, 6]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [레거시 코드 처리 기법] +- [[Wrap Method (랩 메서드)]] + - 연결 이유: 스프라우트 메서드와 함께 레거시 코드에 시간을 들이지 않고 안전하게 새로운 코드를 추가하기 위해 사용되는 대표적인 동반 기법입니다 [1, 7]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 새로운 로직이 기존 로직의 실행 전이나 후에 들어가야 할 때, 기존 메서드 자체를 감싸는(Wrap) 방식으로 테스트 가능한 Seam(접점)을 만드는 접근법을 배울 수 있습니다 [7]. + +#### [테스트 및 구조 분리] +- [[Seam (접점)]] + - 연결 이유: 레거시 코드 내에서 소스 코드를 직접 수정하지 않고도 프로그램의 동작을 바꿀 수 있는 지점으로, 스프라우트 메서드 및 랩 메서드의 근간이 되는 개념입니다 [5, 8, 9]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 객체지향 언어 등에서 테스트를 위해 기존 코드의 의존성을 깨뜨리고 새로운 동작을 주입하거나 격리하는 원리를 파악할 수 있습니다 [8, 9]. +- [[Unit Test (단위 테스트)]] + - 연결 이유: 스프라우트 메서드의 핵심 중 하나는 새로 분리해 낸 로직에 대해서 빠르고 고립된 형태의 단위 테스트를 작성하는 것입니다 [2, 10]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 단위 테스트가 어떻게 레거시 코드의 회귀(Regression) 방지를 위한 가장 빠르고 신뢰할 수 있는 피드백 루프 역할을 하는지 이해할 수 있습니다 [10, 11]. +- [[Legacy Code (레거시 코드)]] + - 연결 이유: 마이클 페더스(Michael Feathers)는 레거시 코드를 "테스트가 없는 코드"로 정의했으며, 스프라우트 메서드는 전적으로 이 레거시 코드를 다루기 위해 고안되었습니다 [6, 9]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트가 부재할 때 발생하는 변경의 위험성과, 이를 극복하기 위한 전체 알고리즘(의존성 깨기 -> 테스트 작성 -> 기능 변경 -> 리팩토링)을 포괄적으로 이해할 수 있습니다 [3, 12]. + +### Deeper Research Questions +- 스프라우트 메서드(Sprout Method)와 랩 메서드(Wrap Method) 중 어느 것을 선택해야 하는지 결정하는 구체적인 코드 구조적 기준과 트레이드오프는 무엇인가? +- 레거시 시스템에서 스프라우트 메서드를 과도하게 남용할 경우 발생하는 클래스나 메서드의 파편화(Fragmentation) 문제를 방지할 수 있는 설계적 가이드라인은 무엇인가? +- 스프라우트 클래스(Sprout Class)를 적용할 때, 기존 레거시 클래스와의 데이터 의존성을 어떻게 최소화하면서 상태를 주고받을 수 있는가? +- 객체 지향 언어가 아닌 절차적 프로그래밍(Procedural Programming) 환경에서 스프라우트 메서드를 적용할 때 삽입 지점을 확보하는 방식은 어떻게 달라지는가? +- 스프라우트 메서드 적용을 통해 단위 테스트 커버리지를 확보한 이후, 궁극적인 전체 리팩토링으로 넘어가기 위한 효과적인 전략은 무엇인가? + +### Practical Application Contexts +- **Implementation:** 거대하고 테스트가 불가능한 레거시 메서드 내부에 새로운 비즈니스 조건 처리를 끼워 넣어야 할 때, 신규 조건 로직을 독립된 함수로 구현하여 테스트하고 기존 메서드에서는 단 한 줄의 함수 호출만 추가한다 [2, 3]. +- **System Design:** 레거시 영역과 신규 개발 영역을 과도기적으로 분리하는 시스템 아키텍처 환경에서, 기존 코드를 크게 손상시키지 않고 신규 로직의 모듈화와 테스트 커버리지를 강제할 수 있다 [1, 2]. +- **Operation / Maintenance:** 운영 환경에서 긴급한 패치나 버그 픽스가 필요하지만 전체 시스템의 회귀 테스트를 작성할 시간이 없을 때, 부수 효과(Side effect)를 통제하며 안전하게 수정 사항을 배포하기 위한 임시 방편으로 사용한다 [1]. +- **Learning Path:** 마이클 페더스의 레거시 대처법을 학습할 때, '레거시 코드 파악' -> '접점(Seam) 식별' -> '스프라우트/랩 기법 적용을 통한 부분적 테스트 확보' -> '안전한 점진적 리팩토링'의 순서로 이어지는 실무 전략을 체득하는 경로로 활용된다 [3, 12]. +- **My Project Relevance:** 시간이 매우 부족하고 테스트 커버리지가 낮은 현재 레거시 유지보수 프로젝트 환경에서, 무리하게 기존 코드를 뜯어고치는 대신 당장의 요구사항을 안전하게 반영하기 위한 실용적 타협안으로 즉시 적용할 수 있다 [1, 3]. + +### Adjacent Topics +- [[Strangler Pattern (교살자 패턴)]] + - 확장 방향: 스프라우트 메서드가 함수나 클래스 레벨의 부분적인 로직 격리라면, 교살자 패턴은 아키텍처 레벨에서 레거시 시스템 전체를 서서히 새로운 시스템으로 점령하고 대체해 나가는 거시적 전략으로 그 이해를 확장할 수 있습니다 [3]. +- [[Approval Testing (승인 테스트)]] + - 확장 방향: 기존 레거시 코드가 내부적으로 어떻게 동작하는지 정확히 모를 때, 결과물만을 기록하여 보호하는 테스트 방식으로, 스프라우트 기법을 적용하기 전후의 회귀(Regression)를 방지하기 위해 함께 연계하여 조사할 가치가 있습니다 [3, 13]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/State-Strategy Pattern (상태-전략 패턴).md b/10_Wiki/Topics/Architecture/State-Strategy Pattern (상태-전략 패턴).md new file mode 100644 index 00000000..cc9262e5 --- /dev/null +++ b/10_Wiki/Topics/Architecture/State-Strategy Pattern (상태-전략 패턴).md @@ -0,0 +1,60 @@ +# [[State/Strategy Pattern (상태/전략 패턴)]] + +## 📌 Brief Summary +State/Strategy Pattern(상태/전략 패턴)은 객체의 생명 주기 동안 분류 부호(Type Code)가 변경되거나, 클래스가 이미 다른 이유로 하위 클래싱되어 상속을 사용할 수 없을 때 분류 부호를 대체하기 위해 사용하는 리팩토링 기법이자 객체지향 패턴이다 [1, 2]. 이 패턴은 객체의 상태 변화나 알고리즘 변형을 별도의 클래스 계층으로 분리하여 위임(Delegation)함으로써 구현된다 [3, 4]. 궁극적으로 이 패턴은 복잡한 조건문(Switch 또는 If-then-else)을 다형성(Polymorphism)으로 교체하기 위한 구조적 기반(Scaffolding) 역할을 한다 [1, 5]. + +## 📖 Core Content +* **적용 동기와 컨텍스트** + 분류 부호(Type Code)가 클래스의 동작에 영향을 미칠 때, 즉 switch 문이나 if-then-else 문과 같은 조건부 로직이 존재할 때 주로 적용된다 [6, 7]. 만약 객체가 생성된 이후에도 분류 부호의 값이 변하거나, 이미 다른 이유로 인해 해당 클래스가 상속(Subclassing)을 사용 중이라면 단순한 '분류 부호를 하위 클래스로 바꾸기(Replace Type Code with Subclasses)' 기법을 사용할 수 없으므로 상태(State)나 전략(Strategy) 패턴을 도입해야 한다 [1, 5, 8]. +* **상태(State)와 전략(Strategy)의 구분** + 구조적으로 두 패턴은 매우 유사하여 적용되는 리팩토링 절차도 동일하다 [9]. 이 둘의 선택은 개발자가 시스템 구조를 어떻게 사고하느냐에 달려 있다 [3]. 단일 알고리즘을 단순화하기 위해 다형성을 활용한다면 '전략(Strategy)' 패턴이라는 용어가 더 적합하며, 상태별 데이터를 이동시키고 객체가 상태를 변경하는 것으로 간주한다면 '상태(State)' 패턴을 사용한다 [9]. +* **리팩토링 실행 절차 (Mechanics)** + 1. 먼저 분류 부호를 자체 캡슐화(Self-encapsulate)한다 [4]. + 2. 분류 부호의 목적을 나타내는 새로운 상태/전략 클래스와 각 분류 부호에 해당하는 하위 클래스들을 생성한다 [4]. + 3. 원본 클래스에 새로운 상태 객체를 위한 필드를 만들고, 원본 클래스의 분류 부호 조회 및 설정 메서드가 이 새로운 상태 객체에 위임하도록 수정한다 [4]. + 4. 이 과정을 통해 다형성을 도입할 준비를 마치게 되며, 이후 '조건식을 다형성으로 바꾸기(Replace Conditional with Polymorphism)'를 통해 조건문 로직을 각 상태/전략 하위 클래스로 분산시킨다 [2, 10, 11]. + +## ⚖️ Trade-offs & Caveats +* **구조적 복잡성 증가**: 상태/전략 패턴을 도입하면 간접 참조(Indirection)의 수준이 하나 더 추가되며, 상태나 전략을 나타내는 별도의 클래스 계층이 생성되어 전체 클래스 수가 늘어난다 [8, 12]. 조건부 로직이 단일 메서드에만 영향을 미치고 향후 변경될 가능성이 거의 없는 경우에는 이러한 다형성 도입이 과도한 설계(Overkill)가 될 수 있다 [13]. +* **생성 시점의 조건문 잔존**: 이 리팩토링을 완료하더라도, 객체를 생성하거나 상태를 변경하여 할당하는 시점(설정 메서드나 팩토리 메서드 내부)에는 적절한 하위 클래스를 생성하기 위한 switch 문이 하나 남게 된다 [14, 15]. 이는 상태가 변경될 때만 실행되는 유일한 조건문으로 고립된다 [15]. +* **단순 대안의 우선 고려**: 분류 부호가 객체의 생명 주기 동안 변하지 않고 다른 상속 구조가 없다면, 보다 구조가 단순한 '분류 부호를 하위 클래스로 바꾸기(Replace Type Code with Subclasses)'를 우선적으로 적용하는 것이 권장된다 [5]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A: 리팩토링 핵심 기법 (Refactoring Core Techniques)] +- [[Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기)]] + - 연결 이유: State/Strategy 패턴은 궁극적으로 복잡한 조건문(switch 등)을 다형성으로 교체하기 위한 기반 구조(Scaffolding) 역할을 수행한다 [1, 5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 상태나 전략 패턴이 적용된 이후, 어떻게 각 하위 클래스로 조건부 로직이 분산되어 복잡성이 제거되는지 이해할 수 있다 [10, 16]. +- [[Replace Type Code with Subclasses (분류 부호를 하위 클래스로 바꾸기)]] + - 연결 이유: State/Strategy 패턴과 목적은 동일하지만(조건부 로직 처리를 위한 타입 코드 교체), 제약 조건(객체 수명 중 타입 변경 여부 등)에 따라 선택이 달라지는 대안적 기법이다 [1, 5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 두 기법 간의 트레이드오프와 언제 더 단순한 상속 구조를 선택해야 하는지 판별하는 기준을 학습할 수 있다 [5]. + +#### [관계 유형 B: 객체지향 설계 원리 (Object-Oriented Design Principles)] +- [[Polymorphism (다형성)]] + - 연결 이유: State/Strategy 패턴이 의존하는 가장 핵심적인 객체지향 원리로, 타입에 따라 다른 동작을 하도록 코드를 구성하는 근간이다 [7, 17]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 조건문을 줄이고 시스템의 결합도를 낮추어 확장을 용이하게 만드는 객체지향의 근본적인 메커니즘을 파악할 수 있다 [17]. + +### Deeper Research Questions +- 단일 알고리즘의 변형을 처리하는 Strategy 패턴과 객체의 상태 전이를 관리하는 State 패턴을 실제 리팩토링 과정에서 구체적으로 어떻게 구분하여 적용하는가? +- 생성 후 타입 코드가 빈번하게 변경되는 객체에 State/Strategy 패턴을 적용할 때, 동적으로 생성되는 상태 객체 간의 데이터 공유와 메모리 관리 문제는 어떻게 최적화할 수 있는가? +- Replace Type Code with Subclasses를 적용할 수 없는 기존 상속 계층의 복잡성이 존재하는 레거시 시스템에서, State/Strategy 패턴의 도입은 아키텍처적 유연성에 어떠한 영향을 미치는가? +- 비즈니스 로직 상 조건문이 거의 변경되지 않거나 그 수가 매우 적은 시스템에서도 State/Strategy 패턴을 도입하는 것이 경제적 관점(유지보수 비용 대비 오버헤드)에서 타당한가? +- 거대 언어 모델(LLM)을 활용한 AI 기반 리팩토링 도구들은 코드 스멜을 분석하여 State/Strategy 패턴 적용을 자동으로 제안할 때, 도메인의 비즈니스 맥락을 어느 수준까지 이해하고 반영할 수 있는가? + +### Practical Application Contexts +- **Implementation:** 클래스의 동작이 동적으로 변하는 분류 부호(Type Code)를 가질 때, 새로운 상태 클래스 계층을 생성하여 원본 객체가 상태 객체에 로직을 위임(Delegation)하도록 구현하는 데 직접적으로 사용된다. +- **System Design:** 다형성을 활용하여 조건부 로직을 각 상태/전략 클래스로 캡슐화함으로써, 새로운 타입이 추가될 때 기존 코드의 수정 없이 하위 클래스만 추가하도록 시스템을 설계할 수 있다. +- **Operation / Maintenance:** 상태나 전략별 로직이 각 클래스로 철저히 분리되므로, 특정 상태와 관련된 버그 수정 및 유지보수 시 타 코드에 미치는 변경 영향 범위를 국소화할 수 있다. +- **Learning Path:** 리팩토링의 핵심 목표 중 하나인 '조건부 로직 단순화(Simplifying Conditional Expressions)' 영역을 마스터하기 위한 필수 선행 디자인 패턴으로 학습된다. +- **My Project Relevance:** 런타임에 동적으로 역할이나 상태가 바뀌어야 하는 도메인 객체(예: 사용자 권한 상태, 상품의 라이프사이클)를 설계하거나 레거시 시스템의 거대한 분기문(Switch)을 객체지향적으로 해체해야 할 때 핵심 가이드라인으로 활용된다. + +### Adjacent Topics +- [[Introduce Null Object (널 객체 도입하기)]] + - 확장 방향: 다형성을 활용하여 조건문을 제거하는 또 다른 리팩토링 패턴으로, null 값을 검사하는 로직을 다형성 구조로 분리하는 방법을 통해 객체지향적 설계 역량을 확장할 수 있다. +- [[Extract Class (클래스 추출하기)]] + - 확장 방향: 비대해진 클래스의 책임을 분리하는 범용적인 기법으로, State/Strategy 패턴 도입 시 위임을 위해 새로운 클래스 계층을 추출해 내는 원리와 연계하여 이해를 넓힐 수 있다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Static Code Analysis (정적 코드 분석기).md b/10_Wiki/Topics/Architecture/Static Code Analysis (정적 코드 분석기).md new file mode 100644 index 00000000..442ca93f --- /dev/null +++ b/10_Wiki/Topics/Architecture/Static Code Analysis (정적 코드 분석기).md @@ -0,0 +1,57 @@ +# [[Static Code Analysis (정적 코드 분석기)]] + +## 📌 Brief Summary +정적 코드 분석기(또는 정적 프로그램 분석/린팅)는 소프트웨어 코드를 직접 실행하지 않고 평가하여 일반적인 프로그래밍 결함과 코드 품질 문제를 탐지하는 기술 및 도구입니다[1]. 유효하지만 표준 이하인 프로그램의 문제를 찾아내며, 지나치게 긴 함수나 중복 코드 같은 구조적 약점을 식별하여 개발자가 소프트웨어 개발 초기에 리팩토링을 수행할 수 있도록 돕습니다[1-3]. + +## 📖 Core 무Content +- **목적 및 역할:** 정적 분석기는 코드를 실행하지 않은 상태에서 분석을 수행하여 결함을 조기에 발견하고 코드 품질을 개선합니다[1]. 덜 엄격한 인터프리터 언어에서는 '린팅(linting)'이라고도 불리며, 문법적으로는 유효하지만 구조적으로는 표준에 미치지 못하는 프로그램의 문제점을 찾아냅니다[2]. +- **리팩토링 후보 식별:** 이 도구들은 인수가 너무 많거나 길이가 지나치게 긴 함수와 같은 구조적 약점을 찾아내어 리팩토링의 후보를 제시합니다[3]. 또한 중복을 암시할 수 있는 구조적 유사성을 감지하는 데에도 사용됩니다[3]. +- **주요 분석 기법:** 정적 프로그램 분석에는 데이터 및 제어 의존성을 명시적으로 표현하는 프로그램 의존성 그래프(Program dependence graph), PDG 간의 프로시저 호출을 나타내는 시스템 의존성 그래프(System dependence graph), 순환 복잡도 분석(Cyclomatic complexity analysis), 초기 상태를 리버스 엔지니어링하여 내부 의존성을 파악하는 소프트웨어 인텔리전스 등이 포함됩니다[2]. +- **도구 및 지원:** 다중 언어를 지원하는 Codacy와 오픈소스 PMD, Java용 JArchitect, .NET용 NDepend, Ruby용 RuboCop 린터 및 포매터 등 다양한 정적 코드 분석기가 존재합니다[1]. 또한 마이크로소프트의 MaX와 같은 의존성 분석 도구는 함수 및 바이너리 모듈 수준의 시스템 전체 의존성 그래프를 생성하여 제거해야 할 유해한 의존성을 식별하는 데 사용됩니다[4-6]. + +## ⚖️ Trade-offs & Caveats +- **코드 의미(Meaning) 이해의 한계:** 정적 분석 도구는 C/C++ 프로그램에 lint를 적용하는 것과 유사하게 구조적인 분석을 수행하지만, 프로그램의 실제 의미나 맥락을 이해할 만큼 지능적이지는 않습니다[7]. +- **개발자의 자율적 판단 요구:** 정적 분석 도구가 구조적 분석을 바탕으로 여러 가지 리팩토링 제안을 하더라도, 그중 일부만이 실제로 시스템에 적용해야 할 의미 있는 변경사항일 수 있습니다[7]. 따라서 도구의 제안을 무조건적으로 맹신하여 적용하기보다는 개발자가 직접 개입하여(make the call) 리팩토링 여부와 방향을 최종적으로 판단해야 하는 제약이 따릅니다[7]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [구조적 결함 및 품질 (Structural Flaws & Quality)] +- [[Code Smell]] + - 연결 이유: 정적 분석 도구는 과도한 인수, 거대 함수, 중복 코드 등과 같은 코드 스멜을 기계적으로 감지하여 리팩토링의 대상을 찾아내기 때문입니다[3]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 정적 분석기가 어떤 비정상적인 패턴을 찾아내려 하는지 그 본질적 기준을 이해할 수 있습니다. + +#### [분석 및 측정 기법 (Analysis & Measurement Techniques)] +- [[Cyclomatic Complexity]] + - 연결 이유: 정적 분석에서 활용되는 대표적인 구조 분석 기법 중 하나로 소스에서 명시적으로 언급되었습니다[2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 조건문의 중첩이나 로직의 얽힘이 어떻게 수치화되며, 이것이 왜 리팩토링(예: 조건문 단순화)의 대상이 되는지 이해할 수 있습니다. +- [[Program Dependence Graph]] + - 연결 이유: 정적 분석기가 프로그램 내 데이터 및 제어 의존성을 명시적으로 표현하는 수단이기 때문입니다[2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 변수와 함수가 내부적으로 어떻게 결합되어 있는지 시각화함으로써 '함수 추출'이나 '변수 이동' 같은 리팩토링의 영향을 깊이 파악할 수 있습니다. + +#### [리팩토링 실행 및 워크플로우 (Refactoring Execution)] +- [[Automated Refactoring Tools]] + - 연결 이유: 정적 분석이 문제를 식별(탐지)하는 역할을 한다면, 자동화된 리팩토링 도구(예: Refactoring Browser)는 발견된 문제를 코드를 깨뜨리지 않고 실제로 안전하게 변환(해결)하는 역할을 수행하기 때문입니다[8, 9]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 코드 문제의 자동 진단에서부터 안전한 자동 수정까지 이어지는 최신 개발 환경의 파이프라인을 이해할 수 있습니다. + +### Deeper Research Questions +- 정적 분석 도구가 탐지한 수많은 구조적 약점 중에서 개발자가 실제로 리팩토링을 수행해야 하는 코드를 선별하는 기준과 우선순위 결정 방식은 무엇인가? +- 프로그램 의존성 그래프(PDG)와 시스템 의존성 그래프(SDG)는 거대한 레거시 시스템을 모듈화하기 위한 리팩토링 계획 수립에 구체적으로 어떻게 활용되는가? +- 정적 코드 분석기로는 식별할 수 없지만 테스트 코드나 동적 분석을 통해서만 발견할 수 있는 런타임 코드 스멜이나 설계 결함에는 어떤 것들이 있는가? +- 마이크로소프트의 MaX와 같이 함수 수준의 의존성을 추출하는 정적 분석 도구는 시스템 전체의 '계층형 아키텍처(Layered Architecture)' 규칙을 강제하는 데 어떠한 원리로 기여하는가? +- 동적 타입 언어(예: JavaScript, Ruby)에서 사용되는 린터(Linter)와 정적 타입 언어(예: Java, C#)의 정적 분석기 간에는 결함 감지 및 리팩토링 제안 수준에 있어 어떤 기술적 한계와 차이가 있는가? + +### Practical Application Contexts +- **Implementation:** 개발자는 코드를 작성하는 동안 IDE와 통합된 정적 분석기(또는 린터)를 활용하여 긴 함수나 포맷 오류 같은 코드 스멜을 실시간으로 감지하고 즉각적으로 작은 단위의 리팩토링을 수행할 수 있습니다. +- **System Design:** 아키텍트는 MaX와 같은 의존성 추출 도구를 사용하여 바이너리 혹은 모듈 수준의 의존성 그래프를 생성하고, 시스템 내에 유해하게 꼬여있는 의존성(의존성 순환 등)을 찾아내어 시스템 재설계 및 대규모 리팩토링 계획을 수립합니다. +- **Operation / Maintenance:** CI/CD 파이프라인에 정적 분석 도구(PMD, Codacy 등)를 통합함으로써 표준에 미치지 못하거나 구조적 약점이 포함된 코드가 병합되는 것을 방지하고, 유지보수 단계에서 기술 부채가 축적되는 것을 시스템적으로 차단합니다. +- **Learning Path:** 리팩토링의 기본 개념과 코드 스멜의 종류를 학습한 후, 정적 분석 도구를 프로젝트에 적용하여 본인의 코드에 어떤 스멜이 존재하는지 확인하고, 도구가 제시하는 경고를 해결하는 방식으로 리팩토링 훈련을 진행합니다. +- **My Project Relevance:** 현재 관리 중인 프로젝트 코드베이스에서 복잡하거나 장황한 클래스/함수를 객관적인 수치와 지표로 색인해 내기 위해 정적 코드 분석기를 도입하여 리팩토링 대상을 선정할 수 있습니다. + +### Adjacent Topics +- [[Technical Debt]] + - 확장 방향: 정적 코드 분석기가 지속적으로 탐지하는 프로그래밍 결함과 스멜을 제때 리팩토링하지 않고 방치했을 때 누적되는 소프트웨어의 중장기적 개발/유지보수 비용(기술 부채) 문제로 이해를 확장할 수 있습니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Strangler Fig Pattern.md b/10_Wiki/Topics/Architecture/Strangler Fig Pattern.md new file mode 100644 index 00000000..f359fb04 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Strangler Fig Pattern.md @@ -0,0 +1,18 @@ +# [[Strangler Fig Pattern]] + +## 📌 Brief Summary +Strangler Fig Pattern(교살자 패턴)은 얽혀있는 거대한 레거시 코드 시스템을 한 번에 모두 재작성(Rewrite)하거나 해결하려 하는 대신, 아주 작은 부분부터 점진적으로 대체해 나가는 코드 개선 및 리팩토링 접근법입니다 [1]. 기존 코드를 서서히 점령해 나가며 테스트 가능한 영역으로 끌어들이는 방식으로, 안전하게 시스템을 현대화하는 데 사용됩니다 [1]. 주어진 소스 데이터에는 이 패턴에 대한 개략적인 개념만 언급되어 있으며, 전반적으로 소스에 관련 정보가 부족합니다. + +## 📖 Core 점진적 레거시 개선 접근 +* **점진적 접근 및 안전한 영역 확장**: 교살자 패턴은 레거시 코드라는 거대한 타래를 한꺼번에 풀려고 시도하는 것을 지양합니다 [1]. 대신 스프라우트 메서드(Sprout Method)와 같이 새로운 로직을 별도로 분리된 검증된 함수로 작성하여 기존 코드에서 호출하게 하는 전략 등과 궤를 같이합니다 [1]. 아주 작은 부분부터 테스트의 영역 안으로 끌어들여 기존 시스템을 서서히 점령해 나가는 방식을 취합니다 [1]. +* **재작성(Rewrite)과의 대안적 평가**: 유지보수나 변경이 어려운 코드를 만났을 때, 완전히 재작성하는 것이 나을지 파악하기 위한 타임박싱(Timeboxing) 전략의 일환으로 사용될 수 있습니다 [2]. 예를 들어, 1시간은 일반적인 리팩토링에 투자하고, 또 다른 1시간은 교살자 패턴 접근법(Strangler Fig approach)을 시도해 본 뒤, 두 방식 중 더 효과적으로 진척된 방법을 선택하는 식으로 활용할 수 있습니다 [2]. + +*(※ 그 외 교살자 패턴의 구체적인 구현 메커니즘이나 상세 절차에 대해서는 소스에 관련 정보가 부족합니다.)* + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. + +(제공된 소스 데이터에는 Strangler Fig Pattern을 적용할 때 발생할 수 있는 구체적인 부작용, 제약 사항, 혹은 반대 급부(Trade-off)에 대한 설명이 포함되어 있지 않습니다.) + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Strangler Pattern (교살자 패턴).md b/10_Wiki/Topics/Architecture/Strangler Pattern (교살자 패턴).md new file mode 100644 index 00000000..34d62be7 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Strangler Pattern (교살자 패턴).md @@ -0,0 +1,19 @@ +# [[Strangler Pattern (교살자 패턴)]] + +## 📌 Brief Summary +교살자 패턴(Strangler Pattern 또는 Strangler Fig)은 얽혀있는 거대한 레거시 시스템을 한꺼번에 전면 재작성(Rewrite)하지 않고, 아주 작은 부분부터 점진적으로 분리하여 서서히 새로운 코드로 대체해 나가는 접근 방식입니다 [1, 2]. 이 방식은 기존 코드를 안전한 테스트 영역 안으로 조금씩 끌어들이며 시스템을 점령해 나가는 전략을 취합니다 [2]. + +## 📖 Core Content +**소스에 관련 정보가 부족합니다.** + +제공된 문서에서 교살자 패턴과 관련하여 확인할 수 있는 제한적인 내용은 다음과 같습니다: +* **점진적 레거시 개선 전략:** 레거시 코드라는 거대한 타래를 한 번에 풀려고 시도하는 대신, 아주 작은 부분부터 테스트가 가능한 영역으로 끌어들여 서서히 시스템을 장악해 나가는 접근법으로 활용됩니다 [2]. +* **재작성(Rewrite)과의 대안적 실험:** 코드가 엉망이어서 재작성하는 것이 더 쉬울지 결정하기 까다로운 상황에서 유용하게 쓰일 수 있습니다. 예를 들어, 1시간은 일반적인 리팩토링에 할애하고 다른 1시간은 교살자 패턴 접근법에 타임박스(Timebox)를 두고 실험하여, 둘 중 더 성과가 좋은 방식을 선택하는 판단 기준으로 삼을 수 있습니다 [1]. + +## ⚖️ Trade-offs & Caveats +**소스에 관련 정보가 부족합니다.** + +(제공된 소스 데이터 내에는 교살자 패턴의 구체적인 부작용, 제약 사항, 최적화 방법에 따른 반대 급부(Trade-off)를 서술한 내용이 존재하지 않습니다.) + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Switch Statements (Switch 문).md b/10_Wiki/Topics/Architecture/Switch Statements (Switch 문).md new file mode 100644 index 00000000..afd79848 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Switch Statements (Switch 문).md @@ -0,0 +1,19 @@ +# [[Switch Statements (Switch 문)]] + +## 📌 Brief Summary +Switch 문은 객체지향 프로그래밍에서 상속이나 다형성(Polymorphism)의 이점을 제대로 살리지 못하고 절차지향적으로 코드가 구현되었음을 나타내는 대표적인 '코드 스멜(OO Abusers)' 중 하나입니다 [1-3]. 이러한 조건문은 프로그램 여러 곳에 흩어져 코드 중복을 유발하며, 새로운 조건이 추가될 때마다 관련된 모든 Switch 문을 찾아 수정해야 하는 구조적 결함을 낳습니다 [2]. 일반적으로 '조건식을 다형성으로 바꾸기(Replace Conditional with Polymorphism)'와 같은 리팩토링 기법을 통해 이 문제를 해결하고 시스템의 유지보수성을 향상시킬 수 있습니다 [4, 5]. + +## 📖 Core Content +* **객체지향 설계의 결함 지표:** 객체지향 코드의 가장 뚜렷한 특징 중 하나는 Switch(또는 case) 문이 상대적으로 적다는 것입니다 [2]. Switch 문이 반복적으로 등장하는 것은 객체지향 구성 요소를 남용했거나(OO Abusers) 잘못 사용했다는 신호로 간주됩니다 [1, 3]. Switch 문의 근본적인 문제는 중복을 발생시킨다는 점이며, 새로운 분기 조건이 생길 때마다 흩어진 코드를 모두 수정해야 하므로 변경을 방해합니다 [2]. +* **다형성을 활용한 리팩토링:** Switch 문을 마주치면 가장 먼저 다형성의 적용을 고려해야 합니다 [4]. + * Switch 문이 종종 타입 코드(Type Code)를 기반으로 작동하는 경우, 우선 '함수 추출하기(Extract Method)'와 '함수 옮기기(Move Method)'를 사용해 해당 Switch 문을 다형성이 필요한 클래스로 이동시킵니다 [4]. + * 그 다음 '타입 코드를 서브클래스로 바꾸기(Replace Type Code with Subclasses)'나 '타입 코드를 상태/전략 패턴으로 바꾸기(Replace Type Code with State/Strategy)'를 통해 상속 구조를 구축합니다 [4]. + * 마지막으로 '조건식을 다형성으로 바꾸기(Replace Conditional with Polymorphism)'를 적용하여 분기 로직을 동적 바인딩으로 대체합니다 [4, 5]. 이를 통해 새로운 타입이 추가될 때 기존 코드를 수정할 필요가 없게 되어 개방-폐쇄 원칙(Open-Closed Principle)을 준수할 수 있습니다 [5, 6]. +* **다형성 외의 대안 기법:** 조건이 Null인 경우를 처리하는 분기가 있다면 '널 객체 도입하기(Introduce Null Object)' 기법을 활용하여 Switch 문의 복잡성을 덜어낼 수 있습니다 [7]. + +## ⚖️ Trade-offs & Caveats +* **다형성 적용의 오버엔지니어링(Overkill) 위험:** Switch 문을 제거하기 위해 무조건 다형성을 도입하는 것은 주의해야 합니다. 단일 메서드에만 영향을 미치는 소수의 조건 케이스만 존재하고 향후 이 조건들이 변경될 것으로 예상되지 않는 경우, 다형성을 도입하는 것은 오히려 불필요한 복잡성을 가중시키는 오버엔지니어링(Overkill)이 될 수 있습니다 [7]. +* 이러한 제약 상황에서는 다형성 대신 '매개변수를 명시적 메서드로 바꾸기(Replace Parameter with Explicit Methods)'와 같이 더 단순한 리팩토링 방식을 선택하는 것이 타당합니다 [7]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/TDD (테스트 주도 개발).md b/10_Wiki/Topics/Architecture/TDD (테스트 주도 개발).md new file mode 100644 index 00000000..ed209841 --- /dev/null +++ b/10_Wiki/Topics/Architecture/TDD (테스트 주도 개발).md @@ -0,0 +1,20 @@ +# [[TDD (테스트 주도 개발)]] + +## 📌 Brief Summary +TDD(테스트 주도 개발)는 설계 및 구현을 위해 '테스트 우선(test-first)' 접근 방식을 따르는 애자일 소프트웨어 개발 기법이다 [1]. 익스트림 프로그래밍(XP) 원칙에 뿌리를 두고 있으며, 기능 구현 전이나 구현과 동시에 단위 테스트를 작성한다 [2, 3]. 단위 테스트를 코드 작성 과정과 밀접하게 결합함으로써, TDD는 단순한 품질 보증 수단을 넘어 소프트웨어 설계를 위한 도구로 작용한다 [2]. 또한 실패하는 테스트 작성, 코드 구현, 리팩토링으로 이어지는 반복 주기를 통해 모든 형태의 리팩토링을 수행할 수 있는 기초를 제공한다 [1]. + +## 📖 Core Content +* **Red-Green-Refactor 워크플로우:** TDD는 일반적으로 세 가지 명확한 단계로 수행된다. + * **RED:** 실패하는 테스트(red-test)를 가장 먼저 작성하여 어떤 기능이 개발되어야 하는지 확인한다 [4, 5]. + * **Green:** 테스트를 통과할 수 있는 가장 단순한 수준의 코드를 작성하여 테스트를 성공(green)시킨다 [4, 5]. + * **Refactor:** 테스트가 통과하는 상태를 유지하면서 코드를 향상시키고 내부 구조를 개선한다 [4, 5]. 이 기법은 시스템에 새로운 기능을 추가하는 작업과 해당 기능을 리팩토링하는 작업을 동시에 수행하지 않도록 워크플로우를 분리해 준다 [4]. +* **안전한 리팩토링의 토대:** TDD 및 지속적 통합(CI) 환경은 리팩토링 과정에서 필수적이다. 리팩토링 중에 작은 변경을 가한 후에는 반드시 테스트를 실행해야 하며, 이를 생략하면 새로운 버그가 유입될 위험이 커진다 [6, 7]. +* **레거시 코드와의 관계:** TDD를 통해 테스트를 갖추지 못한 코드는 사실상 레거시 코드로 간주된다 [8]. 테스트라는 안전망 없이 대규모의 변경이나 리팩토링을 시도하는 것은 공중 곡예를 하는 것과 같이 매우 위험하므로, TDD를 통한 테스트 코드는 향후 코드 수정과 개선을 위한 필수 조건이 된다 [8]. + +## ⚖️ Trade-offs & Caveats +* **불필요한 테스트 작성의 유혹과 비판:** TDD에 반대하는 사람들은 100%의 테스트 커버리지를 달성하기 위해 `getter`나 `setter`처럼 조건 논리조차 없는 사소한 코드까지 테스트해야 한다고 비판하며 이를 무의미한 작업으로 치부하기도 한다 [9]. 하지만 실제로는 명백하게 단순한 구현부는 테스트하지 않는 것이 권장되며, 모든 것을 테스트하려는 강박은 오히려 시간 낭비를 초래할 수 있다 [10]. +* **테스트 유지보수 부채:** TDD로 작성된 테스트 코드 역시 제품 코드와 동일하게 취급되어 정기적인 유지보수와 리팩토링이 필요하다 [11, 12]. 테스트 코드의 품질을 관리하지 않으면 테스트 스위트가 점점 느려지고 깨지기 쉬워지며(brittle), 결과적으로 리팩토링을 방해하는 부채가 될 수 있다 [13, 14]. +* **도입 딜레마 (레거시 코드 환경):** 기존 시스템이 TDD로 작성되지 않은 경우, 테스트를 추가하려면 강하게 결합된 의존성을 끊기 위해 코드를 먼저 변경해야 한다 [15, 16]. 그러나 안전하게 코드를 변경하려면 사전에 테스트가 존재해야 한다는 모순적인 '레거시 코드의 딜레마'에 빠지게 되어, 이를 해결하기 위한 상당한 비용과 노력이 수반된다 [15]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Technical Debt (기술 부채).md b/10_Wiki/Topics/Architecture/Technical Debt (기술 부채).md new file mode 100644 index 00000000..927303e0 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Technical Debt (기술 부채).md @@ -0,0 +1,68 @@ +# [[Technical Debt (기술 부채)]] + +## 📌 Brief Summary +기술 부채(Technical Debt)란 소프트웨어 개발 과정에서 마감 기한 등 단기적인 목표를 맞추기 위해 지름길(Shortcuts)을 택하거나 불완전한 코드를 작성함으로써 발생하는 미래의 유지보수 비용을 의미합니다 [1, 2]. 워드 커닝햄(Ward Cunningham)이 명명한 이 개념은 금융의 부채처럼 시간이 지날수록 이자가 복리로 쌓여 향후 새로운 기능 추가와 코드 수정을 기하급수적으로 어렵게 만듭니다 [3, 4]. 이러한 기술 부채를 체계적으로 상환하고 시스템의 건강한 구조를 회복하는 핵심적이고 경제적인 규율이 바로 '리팩토링'입니다 [5, 6]. + +## 📖 Core 무Content +* **기술 부채의 발생 원인과 형태**: + 기술 부채는 일정 압박 속에서 개발을 진행할 때 중복된 로직, 이해하기 어려운 변수명, 유연하지 않은 아키텍처, 뒤엉킨 의존성 등을 코드에 남겨둘 때 발생합니다 [7-9]. 일상적인 소프트웨어 개발 주기에서 기술 부채의 축적은 어느 정도 불가피한 부분입니다 [10]. + +* **부채 방치의 복리 효과 (Compounding Effect)**: + 부채를 상환(리팩토링)하지 않고 방치하면 코드는 점점 더 부패(decay)하고 수정하기 힘들어집니다 [9, 11]. 새로운 기능이 추가될 때마다 회귀 테스트의 범위가 넓어지고, 수동 테스트와 코드 추적에 드는 시간이 폭발적으로 늘어나 결과적으로 전체적인 개발 및 유지보수 비용을 높이고 팀의 작업 속도를 지연시킵니다 [12-14]. + +* **리팩토링을 통한 부채 상환 전략**: + 코드 리팩토링은 새로운 기능을 만들지 않으면서 지저분한 코드를 클린 코드로 개선하여 기술 부채를 제거하거나 줄이는 과정입니다 [2, 15]. 기능 추가 전 코드를 정돈하는 '준비적 리팩토링(Preparatory Refactoring)'이나 '쓰레기 줍기 리팩토링(Litter-Pickup Refactoring)'을 일상화하면 부채의 급격한 축적을 막을 수 있습니다 [16, 17]. + +* **기술 부채의 체계적 관리 체계**: + 출시 속도를 위해 전략적으로 기술 부채를 발생시킬 수 있지만, 반드시 올바르게 관리되어야 합니다 [18]. 현대적인 엔지니어링 팀은 코드에 직접 연결된 형태로 기술 부채 이슈를 지속적으로 추적(Track)하고 우선순위화(Prioritise)하며, 정기적인 스프린트의 15~20% 시간을 할당하여 부채를 갚아나가는 프로세스를 도입합니다 [19]. + +## ⚖️ Trade-offs & Caveats +* **단기 출시 속도 vs. 장기 유지보수 비용**: 기술 부채를 감수하는 지름길을 택하면 단기적으로는 신기능을 빠르게 배포할 수 있지만, 장기적으로는 코드가 방대하고 복잡해짐에 따라 개발 비용이 급증하고 생산성이 하락하는 반대 급부가 따릅니다 [12, 14]. +* **리팩토링 과정의 리스크**: 기술 부채를 해결하기 위해 코드를 재구조화하는 리팩토링 작업 자체도 기존 기능을 깨뜨리거나 새로운 버그를 도입할 위험(Regression risk)을 수반합니다 [20-22]. 따라서 포괄적인 자동화 테스트 안전망이 없는 상태에서의 리팩토링은 매우 위험합니다 [23, 24]. +* **개발 자원 할당의 제약**: 관리자나 경영진은 리팩토링을 기능 구현을 방해하는 '오버헤드'나 수익을 내지 못하는 불필요한 작업으로 오해할 수 있습니다 [14, 25]. 마감 시한이 코앞인 경우, 리팩토링으로 얻는 이점보다 일정 지연으로 인한 비즈니스 손실이 더 클 수 있으므로 기술 부채 상환 시점은 전략적으로 결정해야 합니다 [26-28]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [설계 및 코드 품질 (Design & Code Quality)] +- [[Code Smell (코드 스멜)]] + - 연결 이유: 기술 부채가 코드베이스에 축적되어 있음을 개발자에게 직관적으로 알려주는 표면적인 증상(지표)입니다 [2, 28, 29]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 긴 함수, 거대 클래스, 기본 타입 집착 등 어떠한 구체적인 코드 패턴들이 구조적 결함을 일으키고 기술 부채를 형성하는지 파악할 수 있습니다 [28]. + +#### [품질 보증 및 유지보수 도구 (QA & Maintenance Tools)] +- [[Refactoring (리팩토링)]] + - 연결 이유: 축적된 기술 부채를 상환하고 소프트웨어의 부패하는 구조를 건강하게 회복시키는 가장 직접적이고 필수적인 행위입니다 [6, 15]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 외부 동작을 보존하면서 코드의 가독성을 높이고 중복을 제거하여 부채를 갚아나가는 구체적인 마이크로-리팩토링 기법들을 이해할 수 있습니다 [30, 31]. +- [[Automated Testing (자동화된 테스트)]] + - 연결 이유: 기술 부채를 갚기 위해 기존 코드를 변경할 때, 시스템이 망가지지 않도록 보장하는 필수적인 안전망입니다 [22, 32]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: TDD(Red-Green-Refactor) 사이클이나 테스트 피라미드를 통해 부작용 없이 기술 부채를 점진적으로 개선하는 원리를 배울 수 있습니다 [22, 33]. +- [[Legacy Code (레거시 코드)]] + - 연결 이유: 테스트 코드가 없거나 방치되어 극단적으로 기술 부채가 팽창한 소프트웨어 시스템을 지칭합니다 [24, 34]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 부채가 심각한 시스템에서 접점(Seams)을 찾아 의존성을 끊고 테스트를 추가하여 부채를 갚아나가는 특수한 대처 전략을 이해할 수 있습니다 [35, 36]. + +### Deeper Research Questions + +- 경영진이나 비기술 이해관계자에게 기술 부채 상환(리팩토링)의 필요성을 설득하기 위해 이 부채의 장기적 비용을 어떤 정량적(ROI) 지표로 측정할 수 있는가? +- 의도적으로 감수한 기술 부채와 개발자의 부주의로 생긴 비의도적 기술 부채를 해결할 때, 각각의 리팩토링 접근법과 우선순위는 어떻게 달라져야 하는가? +- 시스템의 기술 부채가 너무 커서 점진적 리팩토링(Refactoring)과 전면 재작성(Rewrite) 중 하나를 결정해야 할 때 기준이 되는 임계점이나 지표는 무엇인가? +- 코드 스멜을 통해 식별된 기술 부채를 해결할 때, '3의 법칙(Rule of Three)'을 유보하고 발견 즉시 즉각적인 상환을 해야 하는 특수한 아키텍처적 예외 상황은 무엇인가? +- 최근 AI 지원 도구(예: GitHub Copilot)를 활용해 코드를 자동 생성할 때 새롭게 유입되는 형태의 기술 부채는 무엇이며, 개발자는 이를 리팩토링 관점에서 어떻게 방어해야 하는가? + +### Practical Application Contexts + +- **Implementation:** 개발자는 코드를 구현할 때 테스트 주도 개발(TDD)의 Red-Green-Refactor 사이클을 준수하여, 기능이 동작하게 만든 직후(Green) 즉시 리팩토링을 수행함으로써 기술 부채가 쌓이는 것을 원천 차단합니다 [33]. +- **System Design:** 소프트웨어 설계 및 확장 단계에서 기술 부채가 시스템 전반에 퍼지는 것을 막기 위해, 초기부터 객체 간 결합도를 낮추고 도메인과 프레젠테이션을 명확히 분리(Separate Domain from Presentation)하는 등 점진적인 아키텍처 개선을 수행합니다 [37, 38]. +- **Operation / Maintenance:** 기술 부채를 문서나 IDE의 이슈 트래커 등에 직접 코드를 참조하여 기록하고, 스프린트 계획 회의 시 부채 상환에 일정 비율(예: 15-20%)의 시간을 공식적으로 할당하여 주기적인 유지보수를 실천합니다 [19, 39]. +- **Learning Path:** 주니어 개발자가 리팩토링 원칙과 다양한 코드 스멜을 학습하여 단순히 '돌아가는' 코드를 짜는 수준을 넘어 '이해하기 쉽고 수정 비용이 낮은' 경제적인 코드를 작성하는 방법을 터득하는 데 적용됩니다 [6]. +- **My Project Relevance:** 현재 유지 보수 중인 프로젝트 내에서 잦은 버그나 기능 추가의 지연을 일으키는 병목 지점(기술 부채)을 찾아내고, 우선순위가 높은 부분부터 단위 테스트를 구축한 뒤 안전한 마이크로-리팩토링 기법을 적용합니다. + +### Adjacent Topics + +- [[Agile Methodology (애자일 방법론)]] + - 확장 방향: 애자일의 반복적인(Iterative) 개발 주기가 필연적으로 기술 부채를 생성할 수밖에 없는 구조적 이유와, 이를 '스프린트'를 통해 주기적으로 관리하고 해결해 나가는 애자일 실무 전략으로의 확장. +- [[Continuous Integration (지속적 통합, CI)]] + - 확장 방향: 기술 부채 상환 과정(리팩토링)에서 발생할 수 있는 잠재적 결함을 조기에 자동으로 발견하고 파이프라인에서 차단하기 위한 시스템 레벨의 인프라 및 자동화 기술 측면으로의 확장. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Template Method Pattern (템플릿 메서드 패턴).md b/10_Wiki/Topics/Architecture/Template Method Pattern (템플릿 메서드 패턴).md new file mode 100644 index 00000000..75b70269 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Template Method Pattern (템플릿 메서드 패턴).md @@ -0,0 +1,62 @@ +# [[Template Method Pattern (템플릿 메서드 패턴)]] + +## 📌 Brief Summary +Template Method Pattern(템플릿 메서드 패턴)은 하위 클래스에 있는 두 메서드가 동일한 순서로 비슷한 단계를 수행하지만, 각 단계의 세부 구현이 다를 때 사용하는 객체지향 디자인 패턴이자 리팩토링 기법(Form Template Method)이다 [1]. 이 패턴은 메서드의 공통된 실행 순서(시퀀스)를 상위 클래스로 이동시키고, 달라지는 세부 단계는 다형성(Polymorphism)을 활용해 하위 클래스에서 처리하도록 한다 [2]. 이를 통해 필수적인 차이점은 유지하면서 코드의 중복을 효과적으로 제거할 수 있다 [1]. + +## 📖 Core 기법 +- **도입 목적 및 배경:** + 상속(Inheritance)은 중복된 동작을 제거하는 강력한 도구이다 [1]. 하위 클래스들에 전체적인 흐름은 유사하지만 세부 단계가 다른 두 메서드가 존재할 경우, 중복된 코드가 발생하게 된다 [1, 3]. 이 때, 'Form Template Method(템플릿 메서드 형성)' 리팩토링을 통해 전체적인 시퀀스를 상위 클래스로 올리고, 서로 다른 단계들은 하위 클래스가 다형성을 통해 다르게 수행하도록 위임함으로써 중복을 제거할 수 있다 [1, 2]. +- **리팩토링 실행 절차 (Mechanics):** + 1. 기존 메서드들을 완전히 동일한 부분과 완전히 다른 부분으로 분해(Decompose)한다 [2]. + 2. 완전히 동일한 메서드들은 `Pull Up Method`를 사용하여 상위 클래스로 끌어올린다 [2]. + 3. 내용이 다른 메서드들은 `Rename Method`를 사용하여 모든 단계에서 동일한 시그니처(Signature)를 갖도록 변경한다 [2, 4]. + 4. 컴파일과 테스트를 수행한 후, 원래의 메서드(이제 동일한 시그니처의 메서드 호출들로 구성됨) 중 하나에 `Pull Up Method`를 적용하여 상위 클래스로 올린다 [4]. + 5. 상위 클래스로 올라간 템플릿 메서드 내에서 호출되는 서로 다른 세부 메서드들은 상위 클래스에 추상 메서드(Abstract method)로 정의한다 [4]. + 6. 하위 클래스에 남아 있는 중복된 원래 메서드들을 삭제하고 컴파일 및 테스트를 진행한다 [4]. +- **적용 사례:** + 예를 들어, 고객의 대여 내역을 출력하는 시스템에서 일반 ASCII 텍스트로 출력하는 메서드와 HTML로 출력하는 메서드가 있을 때 적용할 수 있다 [4, 5]. 두 메서드는 헤더 출력, 대여 내역 반복 출력, 푸터 출력이라는 동일한 논리적 순서를 갖지만, 포맷팅 방식(세부 구현)만 다르다 [6, 7]. 이를 분해하고 상위 클래스에 템플릿 메서드를 형성하면, 새로운 형태의 출력 방식(예: XML 출력)을 추가할 때 세부 단계의 추상 메서드만 오버라이딩하여 서브클래스를 만들면 되므로 확장이 매우 쉬워진다 [8]. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. (제공된 소스 데이터에서는 Template Method Pattern 또는 Form Template Method 기법의 목적, 절차, 이점 등에 대해 자세히 설명하고 있으나 [1, 2, 4-12], 이 패턴을 적용했을 때 발생할 수 있는 구체적인 부작용, 상속 구조의 경직성, 혹은 단점이나 제약 사항(Trade-off)에 대한 명시적인 설명은 포함되어 있지 않습니다.) + +단, 이 리팩토링을 수행하기 위해서는 반드시 대상이 되는 메서드들이 공통 상위 클래스를 가지는 상속 구조(Inheritance structure) 내에 있어야 하며, 하위 클래스의 메서드들이 '대략적으로 비슷한 순서(broadly similar steps in the same sequence)'를 가지고 있어야 한다는 전제 조건이 요구된다는 점은 절차상 유의해야 할 사항이다 [1, 2, 9]. + +## 🔗 Knowledge Connections + +### Related Concepts +#### [상속 및 계층 구조 리팩토링 (Inheritance/Hierarchy Refactoring)] +- [[Pull Up Method]] + - 연결 이유: Form Template Method 과정에서 분해된 공통 메서드와 최종적인 템플릿 메서드 전체를 상위 클래스로 이동시킬 때 핵심적으로 사용되는 리팩토링 기법이다 [2, 4]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 하위 클래스들 간의 중복된 행위를 식별하고 이를 상위 클래스로 안전하게 일반화(Generalization)하여 버그 발생률을 줄이는 구체적인 메커니즘 [13, 14]. +- [[Extract Method]] + - 연결 이유: 기존의 메서드에서 공통된 부분과 달라지는 부분을 분리하여 템플릿 메서드 패턴을 적용할 수 있는 잘게 쪼개진 상태로 만들기 위해 선행되어야 하는 기법이다 [2, 3, 6]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 함수를 작고 의미 있는 단위로 분해하여 코드의 목적(Intention)을 드러내고 재사용성을 높이는 방법 [15, 16]. + +#### [객체지향 원칙 및 코드 스멜 (OOP Principles & Code Smells)] +- [[Polymorphism (다형성)]] + - 연결 이유: 템플릿 메서드가 상위 클래스에서 전체 흐름을 제어할 때, 세부적인 단계의 실행을 각 하위 클래스의 특성에 맞게 변형하여 다르게 동작하게 만드는 객체지향의 핵심 원리이다 [1, 2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 조건문(switch/if-else)에 의존하지 않고 객체의 타입에 따라 다른 행위를 수행하도록 시스템을 유연하게 설계하는 방법 [17]. +- [[Duplicated Code (중복 코드)]] + - 연결 이유: Form Template Method 리팩토링을 수행하게 만드는 가장 주요한 코드 스멜(문제점)이다 [3]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 형제 하위 클래스 간에 완전히 동일하지는 않지만 비슷한 코드가 존재할 때 발생하는 유지보수상의 문제점과 그 해결 원리 [3, 18]. + +### Deeper Research Questions +- 하위 클래스의 두 메서드가 '대략적으로 비슷한 순서(broadly similar steps in the same sequence)'를 가지지 않고 제어 흐름이 엇갈리는 경우, Template Method Pattern을 어떻게 변형하거나 다른 패턴으로 대체할 수 있는가? +- Template Method Pattern을 적용하기 위해 분해한 세부 메서드들의 시그니처를 동일하게 맞추는 과정(Rename Method)에서, 하위 클래스마다 요구하는 매개변수나 반환 타입이 서로 다를 경우 이를 어떻게 해결하고 추상화해야 하는가? +- 다형성(Polymorphism)을 활용한 Template Method Pattern과 조건문(Conditional logic)을 그대로 유지하는 설계 사이의 성능(Performance) 및 유지보수성(Maintainability) 차이는 구체적으로 어떠한가? +- 상태 패턴(State Pattern)이나 전략 패턴(Strategy Pattern)과 같이 위임(Delegation)을 사용하는 패턴과 비교할 때, 상속 기반의 Template Method Pattern이 가지는 구조적 장단점은 무엇인가? +- Extract Superclass 과정에서 공통점을 식별하고 Form Template Method를 적용하여 설계를 일반화(Generalize)할 때 권장되는 모범 사례(Best Practice)는 무엇인가? + +### Practical Application Contexts +- **Implementation:** 애플리케이션 내에 고객 대여 내역을 Text 형식과 HTML 형식으로 각각 출력하는 메서드를 구현할 때, 데이터를 순회하는 로직과 전체 출력 순서가 같다면 상위 클래스에 템플릿을 두고 포맷팅 단계만 분리하여 하위 클래스에서 오버라이딩하도록 구현할 수 있다 [4-12]. +- **System Design:** 다형성(Polymorphism)을 활용하여 시스템의 공통 실행 시퀀스를 상위 클래스에 정의함으로써, 향후 새로운 요구사항(예: 새로운 종류의 출력 포맷이나 계산 플로우 추가)이 발생했을 때 하위 클래스 하나를 추가하고 필요한 추상 메서드만 구현하면 되도록 유연하게 설계할 수 있다 [8]. +- **Operation / Maintenance:** 중복된 로직을 상위 클래스의 템플릿 메서드 하나로 통합함으로써 핵심 로직에 변경이 생겼을 때 한 곳만 수정하게 하여, 누락으로 인한 버그 발생을 줄이고 유지보수 비용을 낮춘다 [1, 2]. +- **Learning Path:** 중복 코드 제거를 위해 기본적으로 Extract Method와 Pull Up Method를 먼저 학습한 뒤, 두 기법을 복합적으로 사용하여 상속 계층의 구조를 개선하고 일반화(Generalization)하는 고급 과정으로 Form Template Method를 학습한다 [2, 3, 6]. +- **My Project Relevance:** 프로젝트 내에 존재하는 다양한 종류의 파싱 로직이나 데이터 처리 파이프라인에서 공통된 순서를 발견할 때, 이 패턴을 적용하여 중복을 줄이고 새로운 처리 방식의 확장을 용이하게 만들 수 있다. (제공된 소스에 내 프로젝트에 대한 구체적 명시는 없으나, 코드 중복 제거와 확장성 확보를 위한 일반적 적용이 가능하다 [1, 2].) + +### Adjacent Topics +- [[Replace Conditional with Polymorphism (조건식을 다형성으로 바꾸기)]] + - 확장 방향: 템플릿 메서드 패턴과 마찬가지로 다형성을 활용하여 복잡한 조건문(switch 또는 if-else)을 제거하고, 타입에 따른 행위 분리를 객체지향적으로 풀어내는 또 다른 핵심 리팩토링 기법으로 이해를 확장할 수 있다 [17, 19]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Test Automation Pyramid.md b/10_Wiki/Topics/Architecture/Test Automation Pyramid.md new file mode 100644 index 00000000..f26d8505 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Test Automation Pyramid.md @@ -0,0 +1,57 @@ +# [[Test Automation Pyramid]] + +## 📌 Brief 마이크 요약 +Test Automation Pyramid(테스트 자동화 피라미드)는 소프트웨어 테스트를 입도(Granularity)에 따라 여러 계층으로 그룹화하고, 각 계층에서 얼마나 많은 테스트를 작성해야 하는지를 시각적으로 나타낸 메타포입니다 [1]. 이 원칙은 가장 빠르고 독립적인 단위 테스트(Unit Tests)를 피라미드의 하단에 가장 많이 배치하고, 상위 수준인 통합 테스트와 엔드 투 엔드(E2E) 테스트로 갈수록 그 수를 줄일 것을 권장합니다 [2, 3]. 이를 통해 리팩토링과 같은 코드 구조 변경 시 즉각적이고 신뢰할 수 있는 피드백을 제공하는 빠르고 유지보수 가능한 테스트 스위트를 구축할 수 있습니다 [3, 4]. + +## 📖 Core Content +* **개념의 기원 및 기본 원칙:** 마이크 콘(Mike Cohn)이 고안한 이 개념은 테스트를 다양한 입도로 작성하되, 상위 수준(High-level)으로 갈수록 테스트의 수를 적게 유지하라는 두 가지 핵심 원칙을 제시합니다 [1, 3]. +* **단위 테스트 (Unit Tests - 피라미드 하단):** 전체 자동화 테스트의 약 70%를 차지해야 하는 기반 계층입니다 [5]. 밀리초 단위로 매우 빠르게 실행되며, 개별 컴포넌트나 함수 수준의 동작을 격리하여 검증합니다 [5]. 리팩토링 중 실수가 발생했을 때 가장 빠르고 정확한 피드백 루프를 제공합니다 [4, 6]. +* **통합 테스트 (Integration Tests - 피라미드 중간):** 약 20%를 차지하며, 단위 테스트로는 잡을 수 없는 컴포넌트 간의 상호작용(데이터베이스, 외부 API 등)의 결함을 찾아냅니다 [7-9]. +* **엔드 투 엔드 테스트 (End-to-End Tests - 피라미드 상단):** 10% 정도로 유지해야 합니다 [10]. 사용자 인터페이스부터 백엔드 서비스까지 전체 워크플로우를 실제 사용자 여정에 맞춰 검증하지만, 구축 비용이 가장 비싸고, 실행 속도가 매우 느리며, 유지보수가 까다롭습니다(brittle) [4, 10]. +* **테스트 중복 제거와 하향 이동 (Push Tests Down):** 테스트 스위트의 비대화를 막기 위해, 상위 수준 테스트에서 오류를 발견했는데 하위 수준 테스트가 실패하지 않았다면 반드시 하위 수준(단위 테스트 등)에 테스트를 추가해야 합니다 [11]. 가능한 한 모든 테스트는 피라미드의 가장 아래 계층에서 검증하도록 내려보내야 합니다 [11]. + +## ⚖️ Trade-offs & Caveats +* **역 피라미드 및 아이스크림 콘 안티 패턴 (Inverted Pyramid / Ice-Cream Cone):** UI 기반의 E2E 테스트 위주로 하향식(Top-down) 자동화를 시도하면 '역 테스트 피라미드'가 형성됩니다 [3, 10]. 이 경우 테스트 스위트 실행에 수 시간이 걸리고 거짓 실패(False failures)가 잦아져, 팀이 자동화 테스트를 불신하고 결과를 무시하게 되는 부작용이 발생합니다 [10, 12]. +* **계층 명칭의 모호성과 시대적 한계:** 원본 피라미드가 제시한 'Service Test'나 'UI Test'라는 명칭은 현대 개발 환경(예: React, Angular 등 SPA 프레임워크)에서는 오해를 살 수 있습니다 [3]. SPA 환경에서는 UI 자체도 최상단이 아닌 단위 테스트 수준에서 검증할 수 있기 때문입니다 [3]. +* **신뢰성과 실행 속도의 트레이드오프:** 피라미드 상단으로 갈수록 사용자의 실제 사용 환경과 유사해져 비즈니스적 확신(Confidence)은 높아지지만, 실행 시간(Speed)과 유지보수(Maintainability) 비용이 급증합니다 [10, 13]. 따라서 커버리지, 속도, 신뢰성의 균형을 시스템 전체 관점에서 조율해야 합니다 [14]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [구현/테스트 전략] +- [[단위 테스트 (Unit Tests)]] + - 연결 이유: 테스트 피라미드의 근간을 이루며 전체 테스트의 대다수를 차지하는 핵심 요소이기 때문입니다 [5, 15]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 진행 시 기존 기능이 깨지지 않았음을 밀리초 단위로 확인해 주는 가장 강력한 '안전망'의 역할을 이해할 수 있습니다 [4]. +- [[테스트 주도 개발 (TDD)]] + - 연결 이유: 피라미드 하단의 단위 테스트를 작성하는 주요 기법(Red-Green-Refactor)으로, 리팩토링의 필수 전제 조건이기 때문입니다 [5, 16]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트를 먼저 작성하여 소프트웨어의 동작을 보호하고, 이후 리팩토링을 통해 코드의 내부 구조를 안전하게 개선하는 흐름을 배울 수 있습니다 [16, 17]. + +#### [아키텍처/기반 기술] +- [[지속적 통합 (CI) 및 지속적 배포 (CD)]] + - 연결 이유: 테스트 피라미드가 올바르게 구축되어야 CI/CD 파이프라인에서 병목 없이 소프트웨어를 지속적으로 배포할 수 있기 때문입니다 [2, 18, 19]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 후 코드가 커밋될 때마다 빠른 피드백을 제공하여, 지속 가능한 애자일 개발과 DevOps 문화를 유지하는 아키텍처적 기반을 이해할 수 있습니다 [6, 19]. +- [[레거시 코드 (Legacy Code)]] + - 연결 이유: "테스트가 없는 코드"로 정의되는 레거시 코드를 안전하게 리팩토링하려면, 점진적으로 테스트 피라미드를 구축해야 하기 때문입니다 [20, 21]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 의존성이 강하게 결합된 코드에서 '접점(Seams)'을 찾아내어 단위 테스트를 추가하고, 리팩토링을 수행할 수 있는 상태로 만드는 기법을 파악할 수 있습니다 [22, 23]. + +### Deeper Research Questions +- 테스트 피라미드의 단위 테스트와 통합 테스트의 경계는 어떻게 정의되며, 테스트 대역(Mock, Stub)을 사용해야 하는 정확한 기준과 범위는 무엇인가? +- UI 및 E2E 테스트에 편중된 '역 테스트 피라미드(아이스크림 콘)' 형태의 레거시 시스템을 정상적인 피라미드 구조로 마이그레이션하기 위한 점진적 전략은 무엇인가? +- SPA(Single Page Application) 및 마이크로서비스 아키텍처 환경에서 기존 테스트 피라미드의 계층(Service, UI) 개념은 어떻게 현대적으로 재해석되어야 하는가? +- 리팩토링 시 상위 수준(E2E) 테스트는 통과하지만 하위 수준(Unit) 테스트만 깨지는 경우, 이를 어떻게 처리하고 테스트 스위트를 재구성해야 하는가? +- 계약 테스트(Consumer-Driven Contract Tests)는 테스트 피라미드의 어느 계층에 위치하며, 마이크로서비스 간의 통합 테스트를 어떻게 효율적으로 대체하거나 보완하는가? + +### Practical Application Contexts +- **Implementation:** 개발자는 새로운 기능을 추가하거나 기존 코드를 수정할 때, TDD를 활용하여 빠르고 고립된 단위 테스트를 최우선으로 작성해야 합니다 [5]. +- **System Design:** 아키텍처 설계 시 각 컴포넌트가 독립적으로 테스트될 수 있도록 의존성 분리(Decoupling)를 고려하여 단위 테스트 작성이 용이한 모듈식 구조를 설계해야 합니다 [24]. +- **Operation / Maintenance:** CI 파이프라인의 실행 속도를 유지하기 위해 유지보수가 힘든 E2E 테스트 비중은 최소화하고, 단위 및 통합 테스트 중심으로 회귀 버그를 탐지하는 운영 프로세스를 확립해야 합니다 [5, 10, 19]. +- **Learning Path:** TDD 및 Mocking 프레임워크 학습 -> 단위 및 통합 테스트 구축 방법 습득 -> 테스트 피라미드 원리에 따른 테스트 포트폴리오 최적화 -> CI 파이프라인 통합 및 안전한 리팩토링 실습 순으로 역량을 키워나갑니다. +- **My Project Relevance:** 현재 진행 중인 프로젝트가 느리고 깨지기 쉬운 E2E 테스트에만 의존하고 있지 않은지 점검하고, 리팩토링을 두려움 없이 수행할 수 있도록 하위 수준의 단위 테스트 커버리지를 두텁게 확보하는 리팩토링 전략의 핵심 근거로 활용됩니다. + +### Adjacent Topics +- [[소비자 주도 계약 테스트 (Consumer-Driven Contract Tests, CDC)]] + - 확장 방향: 마이크로서비스 환경에서 통합 테스트의 비용을 줄이고, 서비스 간 API 인터페이스(계약)가 깨지지 않았는지 독립적으로 검증하는 기법으로 테스트 피라미드 전략을 확장할 수 있습니다 [25, 26]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Test Doubles (테스트 대역).md b/10_Wiki/Topics/Architecture/Test Doubles (테스트 대역).md new file mode 100644 index 00000000..53602056 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Test Doubles (테스트 대역).md @@ -0,0 +1,28 @@ +# [[Test Doubles (테스트 대역)]] + +## 📌 Brief Summary +Test Doubles(테스트 대역)는 소프트웨어 테스트 환경에서 프로덕션 환경의 실제 객체(클래스, 모듈, 함수 등)를 대체하기 위해 사용하는 가짜(fake) 구현체입니다 [1]. 목(Mock)과 스텁(Stub)이 대표적인 테스트 대역의 종류이며, 실제 객체와 동일하게 작동하는 것처럼 보이지만 사전에 정의된 응답을 반환합니다 [1]. 이를 통해 테스트를 더 단순하고 예측 가능하게 만들며, 단위 테스트뿐만 아니라 통합 테스트 등 다양한 수준의 테스트에서 외부 의존성을 격리하는 데 핵심적인 역할을 합니다 [2-4]. + +## 📖 Core Content +* **테스트 대역의 역할과 원리** + 테스트 대역은 프로덕션 환경에서 사용되는 실제 객체를 테스트를 돕는 가짜 버전으로 대체하는 것입니다 [1]. 이 가짜 객체는 실제 객체와 동일한 메서드 호출에 응답하지만, 테스트 작성자가 사전에 정의해 둔 '준비된 응답(canned responses)'을 반환하여 시스템의 특정 부분을 제어된 방식으로 시뮬레이션합니다 [1, 2]. + +* **목(Mock)과 스텁(Stub)의 활용** + 사람들은 종종 목과 스텁이라는 용어를 혼용하지만, 이들은 테스트 대역의 서로 다른 종류로 고유한 특성을 가집니다 [1]. 스텁(Stub)은 특정 메서드가 호출되었을 때 정해진 객체나 값을 반환하도록 하여 테스트 데이터를 쉽게 설정하고 예측 가능한 상태를 만드는 데 사용됩니다 [3]. 특히 레거시 코드를 테스트하고 리팩토링할 때는, 소스 코드를 직접 수정하지 않고도 동작을 변경할 수 있는 '접점(Seam)'을 활용하여 가짜 객체(Mock)를 주입함으로써 강한 의존성을 끊어냅니다 [5]. + +* **테스트 격리 방식의 차이 (Solitary vs Sociable)** + 테스트 대역의 사용 정도에 따라 단위 테스트의 성격이 나뉩니다. 모든 협력 객체(collaborators)를 목이나 스텁으로 교체하여 완벽한 격리와 부수 효과(side-effects) 차단을 추구하는 방식을 '고립된 단위 테스트(solitary unit tests)'라고 합니다 [6, 7]. 반면, 데이터베이스나 네트워크 호출 등 느리거나 부수 효과가 큰 대상만 스텁으로 대체하고 나머지 실제 협력 객체와의 상호작용은 허용하는 방식을 '사교적 단위 테스트(sociable unit tests)'라고 부릅니다 [6, 7]. + +* **단위 테스트를 넘어선 확장 적용** + 테스트 대역은 단위 테스트에만 국한되지 않습니다 [2]. 통합 테스트(Integration Tests)에서도 데이터베이스나 별도의 외부 서비스를 테스트 대역으로 대체함으로써, 테스트를 훨씬 더 빠르고 독립적이며 추론하기 쉽게 만들 수 있습니다 [4]. + +## ⚖️ Trade-offs & Caveats +* **실제 서비스와의 불일치로 인한 취약성(Brittleness)** + 외부 서비스나 서버를 대체하기 위해 가짜 서버(테스트 대역)를 사용하여 통합 테스트를 구성할 경우, 실제 외부 서비스의 API가 변경되더라도 테스트 대역을 기반으로 한 테스트는 계속해서 통과(Pass)할 수 있다는 위험이 있습니다 [8]. 이는 테스트 대역이 단순히 테스트 내에서 응답을 잘 파싱하는지만 검증할 뿐, 실제 서버의 현재 상태나 동작 방식을 완벽하게 대변하지 못하기 때문입니다 [8]. + +* **계약 테스트(Contract Tests)를 통한 보완 필요성** + 위와 같은 테스트 대역의 불일치 한계를 극복하기 위해서는 계약 테스트(Contract Tests)를 함께 도입해야 합니다 [4]. 가짜 서버와 실제 서버 모두를 대상으로 계약 테스트를 실행함으로써, 통합 테스트에서 사용되는 테스트 대역이 실제 서버의 동작을 충실하게 반영하는 '믿을 수 있는 테스트 대역(faithful test double)'인지 지속적으로 검증하고 보장해야 하는 추가적인 관리 비용이 발생합니다 [8]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Test Pyramid (테스트 피라미드).md b/10_Wiki/Topics/Architecture/Test Pyramid (테스트 피라미드).md new file mode 100644 index 00000000..96717520 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Test Pyramid (테스트 피라미드).md @@ -0,0 +1,19 @@ +# [[Test Pyramid (테스트 피라미드)]] + +## 📌 Brief Summary +테스트 피라미드(Test Pyramid)는 마이크 콘(Mike Cohn)이 고안한 개념으로, 소프트웨어 테스트를 다양한 세분화 수준(granularity)의 계층으로 분류하고 각 계층에서 수행해야 할 테스트의 적정 비율을 나타내는 시각적 메타포입니다 [1]. 하단에는 빠르고 작은 단위 테스트를 많이 배치하고, 상단으로 갈수록 범위가 넓고 느린 엔드투엔드(End-to-End) 테스트를 적게 배치해야 한다는 핵심 원칙을 제시합니다 [2]. 이를 통해 개발 팀은 빠르고 신뢰할 수 있으며 유지보수가 용이한 자동화된 테스트 제품군을 구축하여 소프트웨어 제공 속도와 품질을 높일 수 있습니다 [2, 3]. + +## 📖 Core Content +* **기본 원칙:** 테스트 피라미드는 두 가지 핵심 규칙을 따릅니다. 첫째, 다양한 세분화 수준을 가진 테스트를 작성해야 하며, 둘째, 상위 수준으로 올라갈수록 테스트의 수를 줄여야 합니다 [2]. +* **단위 테스트 (Unit Tests):** 피라미드의 가장 아래층인 기반을 이루며 전체 자동화 테스트의 약 70%를 차지하는 것이 이상적입니다 [4]. 개별 컴포넌트나 함수가 의도한 대로 작동하는지 독립적으로 검증하며, 밀리초 단위로 실행될 만큼 빠르고 작성 및 유지보수 비용이 저렴합니다 [4, 5]. +* **통합 테스트 (Integration Tests):** 피라미드의 중간 계층에 위치하며 약 20%의 비중을 갖습니다 [6]. 데이터베이스, 파일 시스템, 외부 API 등 애플리케이션 외부 요소와의 연동이 올바르게 이루어지는지 검증합니다 [7]. 단위 테스트보다는 느리지만, 개별 컴포넌트가 상호작용할 때 발생하는, 단위 테스트에서 잡을 수 없는 결함을 찾아내는 역할을 합니다 [6, 8]. +* **엔드투엔드 테스트 (End-to-End Tests):** 피라미드의 최상단에 위치하며 약 10%의 비중을 차지해야 합니다 [9]. 실제 사용자의 여정을 시뮬레이션하여 사용자 인터페이스부터 백엔드 서비스까지 전체 시스템이 제대로 기능하는지 검증합니다 [9]. +* **테스트 중복 방지와 하향 배치:** 상위 수준의 테스트가 오류를 발견했는데 하위 수준 테스트가 실패하지 않았다면 하위 수준의 테스트를 추가로 작성해야 합니다 [10]. 가능한 한 테스트를 피라미드의 가장 아래쪽으로 밀어내어 테스트 제품군의 실행 속도를 빠르고 간결하게 유지하는 것이 중요합니다 [10]. + +## ⚖️ Trade-offs & Caveats +* **역방향 테스트 피라미드 (Inverted Test Pyramid) 안티패턴:** 하향식(UI 도구 중심)으로 자동화 전략을 구축하면 피라미드가 역전되어 단위 테스트는 거의 없고 느리고 깨지기 쉬운 엔드투엔드 테스트만 수백 개 쌓이는 현상이 발생합니다 [9]. 이 안티패턴은 테스트 실행에 수 시간이 걸리게 만들어 빌드 빈도를 떨어뜨리고, 간헐적 실패(Flaky Tests)를 유발하여 결국 팀이 테스트 결과를 무시하게 만드는 부작용을 초래합니다 [9, 11]. +* **상위 계층 테스트의 한계:** 엔드투엔드 및 UI 테스트는 구축 비용이 가장 비싸고, 실행 속도가 가장 느리며, 유지보수 측면에서 가장 깨지기 쉽습니다(brittle) [9, 12]. 따라서 하위 계층에서 이미 검증된 예외 케이스(edge cases)를 상위 계층에서 중복으로 테스트하는 것은 높은 유지보수 비용과 오탐(false positives)을 발생시키므로 피해야 합니다 [10, 12]. +* **용어의 모호성과 오해:** 원래의 피라미드 모델에서 사용된 '서비스 테스트(Service Test)'나 'UI 테스트' 같은 용어는 현대의 소프트웨어 아키텍처(예: SPA 프레임워크) 환경에서 혼란을 줄 수 있습니다 [2]. 용어 자체에 얽매이기보다는 일관된 테스트 분류 체계를 팀 내에서 공유하고, 각 계층의 역할을 명확히 인지하는 것이 훨씬 중요합니다 [2, 13]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Test-Driven Development (테스트 주도 개발).md b/10_Wiki/Topics/Architecture/Test-Driven Development (테스트 주도 개발).md new file mode 100644 index 00000000..344555b9 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Test-Driven Development (테스트 주도 개발).md @@ -0,0 +1,16 @@ +# [[Test-Driven Development (테스트 주도 개발)]] + +## 📌 Brief Summary +테스트 주도 개발(TDD)은 설계 및 구현에 있어 '테스트 우선(test-first)' 접근 방식을 따르는 소프트웨어 개발 방법론이다 [1, 2]. 애자일(Agile) 방법론에 필수적으로 내재되어 있으며, 모든 형태의 리팩토링을 위한 튼튼한 기반을 제공한다 [1-3]. 이 기법은 실패하는 테스트를 먼저 작성하고, 이를 통과하는 코드를 구현한 뒤, 코드를 최적화하는 '레드-그린-리팩터(Red-Green-Refactor)' 주기를 통해 코드 변경 중에도 시스템의 동작을 안전하게 보존한다 [3-5]. + +## 📖 Core Content +* **Red-Green-Refactor 주기**: TDD는 크게 세 가지의 명확한 단계로 수행된다 [2, 3]. 첫 번째 **레드(RED)** 단계에서는 시스템의 원하는 동작을 설명하는 실패하는 테스트를 먼저 작성한다 [2, 3, 6]. 두 번째 **그린(GREEN)** 단계에서는 해당 테스트를 통과하기 위한 가장 단순하고 최소한의 코드를 작성한다 [2, 3]. 마지막 **리팩터(REFACTOR)** 단계에서는 테스트를 통과하는 상태(Green)를 유지하면서 코드를 최적화하고 향상시킨다 [2, 3, 5]. +* **리팩토링과의 밀접한 결합**: 성공적인 리팩토링 주기는 TDD 사이클과 떼어놓을 수 없다 [6]. 리팩토링 과정에서 작은 변경을 적용한 후 즉시 TDD를 통해 테스트를 실행해야 하며, 이를 생략하면 새로운 버그가 도입될 위험이 크게 증가한다 [6, 7]. +* **설계 도구 및 내장된 품질(Built-in Quality)**: TDD는 SAFe(Scaled Agile Framework)와 같은 체계에서 동료 검토(Peer Review) 등과 함께 핵심적인 '내장된 품질(Built-in Quality)' 기둥으로 취급된다 [8]. 단위 테스트(Unit Tests)와 결합되어 실천될 때, TDD는 단순한 품질 검증 역할을 넘어 소프트웨어를 구조화하는 훌륭한 설계 도구의 역할을 수행한다 [9]. + +## ⚖️ Trade-offs & Caveats +TDD나 단위 테스트를 적용할 때 높은 테스트 커버리지를 달성하기 위해 getter나 setter와 같은 아주 단순하고 사소한(trivial) 코드까지 테스트를 작성하도록 강요할 경우, 이는 개발자에게 무의미하고 불필요한 작업이 될 수 있다는 단점과 비판이 존재한다 [10]. +또한, 테스트가 관측 가능한 외부 동작(observable behavior)이 아닌 내부 구현 세부 사항에 너무 가깝게 결합되어 작성될 경우, 리팩토링을 진행할 때마다 단위 테스트가 쉽게 깨지는 부작용이 발생한다 [11]. 이러한 취약한 테스트는 코드 변경 시의 안전망으로 작용하기보다는 코드를 수정할 때마다 함께 수정해야 하는 유지보수 부담이 되므로, 내부 구현 방식이 아닌 결과와 동작에 집중하여 테스트를 설계해야 하는 제약 사항이 있다 [11, 12]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/The Two Hats.md b/10_Wiki/Topics/Architecture/The Two Hats.md new file mode 100644 index 00000000..a8d50697 --- /dev/null +++ b/10_Wiki/Topics/Architecture/The Two Hats.md @@ -0,0 +1,27 @@ +# [[The Two Hats]] + +## 📌 Brief Summary +'두 개의 모자(The Two Hats)'는 켄트 벡(Kent Beck)과 마틴 파울러(Martin Fowler)가 제안한 리팩토링의 핵심적인 심리적, 절차적 운영 원칙을 나타내는 은유이다 [1, 2]. 이 원칙은 소프트웨어 개발 시간을 '기능 추가(Adding Function)'와 '리팩토링(Refactoring)'이라는 두 가지 뚜렷한 활동으로 명확하게 나누어야 함을 강조한다 [1, 3]. 즉, 개발자는 자신이 현재 어떤 목적의 모자를 쓰고 작업을 수행하고 있는지 항상 정확히 인지하여, 두 가지 활동을 뒤섞지 않고 분리함으로써 작업의 명확성을 확보해야 한다 [2, 4]. + +## 📖 Core Content +* **원칙의 개념과 목적:** + '두 개의 모자'는 프로그래머가 코드 베이스를 다룰 때 취해야 하는 두 가지 상반된 태도를 설명하는 은유이다 [1, 5]. 이 원칙은 개발자가 프로그래밍을 할 때 현재 수행 중인 작업의 목적을 명확히 구분하여 디버깅 효율성을 높이고 작업의 명확성을 확보하는 것을 목표로 한다 [2, 6]. + +* **기능 추가(Adding Function) 모자:** + '기능 추가' 모자를 쓰고 있을 때는 오직 새로운 동작을 구현하고 테스트를 통과시키는 데에만 집중해야 한다 [1, 2]. 기존 코드의 구조를 변경해서는 안 되며, 단지 새로운 기능을 더하는 것만 수행해야 한다 [1, 3]. + +* **리팩토링(Refactoring) 모자:** + '리팩토링' 모자를 쓰고 있을 때는 기존 코드의 구조를 재구성하고 개선하는 데에만 전념해야 하며, 새로운 기능이나 테스트를 절대로 추가해서는 안 된다 [1, 3]. 이 모자를 썼을 때 테스트 코드를 수정할 수 있는 경우는 오로지 이전에 누락된 케이스를 발견했거나, 인터페이스 변경에 대응해야 할 때뿐이다 [3]. + +* **유연한 모자 교체(Swapping Hats):** + 실제 소프트웨어 개발 과정에서 개발자는 10분 정도의 짧은 시간 동안에도 수시로 모자를 바꿔 쓰게 된다 [7]. 예를 들어, 새로운 기능을 추가하려다 기존 코드를 다르게 구성하면 더 쉽겠다는 것을 깨달으면 잠시 리팩토링 모자로 바꿔 쓰고 코드를 정비한다 [7]. 구조 개선이 끝나면 다시 기능 추가 모자를 쓰고 코드를 완성한 뒤, 마지막으로 다시 리팩토링 모자를 쓰고 코드를 깔끔하게 다듬는 식으로 진행된다 [7]. 이때 핵심은 교체가 빈번하더라도 현재 어떤 모자를 쓰고 있는지 항상 자각하는 것이다 [4, 7]. + +## ⚖️ Trade-offs & Caveats +* **부작용(Side Effect) 및 버그 수정의 유혹 통제:** + 리팩토링 모자를 쓰고 기존 코드를 재구성하다 보면 코드가 제대로 동작하지 않는 부분(버그 등)을 발견할 때가 있다 [8]. 하지만 이때 이를 즉시 수정하려는 유혹을 강력히 참아야 한다 [4, 8]. 리팩토링의 목표는 시작할 때와 똑같은 결과를 도출하는 것이기 때문에, 문제를 발견했다면 코드 변경을 멈추는 대신 인덱스 카드 등에 나중에 수정해야 할 사항(테스트 케이스 추가, 관련 없는 리팩토링, 문서 작성 등)을 메모해 두고 현재 진행 중인 리팩토링 작업에만 집중해야 한다 [4, 8]. + +* **디버깅 효율성 저하 및 커밋(Commit) 분리 제약:** + 기능 추가 활동과 리팩토링 활동을 뒤섞어서 수행할 경우, 추후 코드에서 발생한 버그가 새로운 기능 추가로 인한 것인지 리팩토링 과정에서의 실수 때문인지 그 원인을 파악하기 매우 어려워져 디버깅 효율이 급격히 저하되는 치명적인 제약이 따른다 [2]. 따라서 다른 변경 사항과 리팩토링을 철저히 격리하여 독립적인 커밋(Commit) 단위로 분리하는 것이 요구된다 [5]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Two Hats (두 개의 모자).md b/10_Wiki/Topics/Architecture/Two Hats (두 개의 모자).md new file mode 100644 index 00000000..c0642756 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Two Hats (두 개의 모자).md @@ -0,0 +1,21 @@ +# [[Two Hats (두 개의 모자)]] + +## 📌 Brief Summary +'두 개의 모자(Two Hats)'는 켄트 벡(Kent Beck)이 고안하고 마틴 파울러(Martin Fowler)가 강조한 리팩토링의 핵심적인 심리적 및 절차적 원칙입니다 [1-3]. 이 메타포는 소프트웨어 개발 과정을 '기능 추가'와 '리팩토링'이라는 두 가지 명확히 구분된 활동으로 나누어 수행해야 함을 의미합니다 [1-3]. 개발자는 한 번에 오직 하나의 모자만 써야 하며, 현재 자신이 어떤 목적의 작업을 수행하고 있는지 분명히 인지하여 두 활동을 절대 섞지 않는 것이 이 원칙의 핵심입니다 [1, 3, 4]. + +## 📖 Core Content +* **원칙의 정의와 목적:** 두 개의 모자 이론은 개발자가 작업할 때 기능 추가와 구조 개선 단계를 명확히 분리하여 작업의 명확성을 확보하기 위한 규율입니다 [3, 5]. 실제 개발 과정에서는 단 10분 단위로라도 모자를 수시로 바꿔 쓸 수 있지만, 항상 현재 쓰고 있는 모자가 무엇인지 인식해야 합니다 [1, 2]. +* **기능 추가 모자 (Adding Function Hat):** + * 이 모자를 썼을 때는 기존 코드를 변경하거나 구조를 개선하려 해서는 안 되며, 오직 새로운 기능을 추가하는 데만 집중해야 합니다 [1-3]. + * 새로운 테스트를 추가하고 그 테스트가 통과되도록 구현함으로써 작업의 진척도를 측정합니다 [2]. +* **리팩토링 모자 (Refactoring Hat):** + * 이 모자를 썼을 때는 새로운 기능을 추가하지 않고(새로운 테스트조차 추가하지 않음) 오직 기존 코드의 내부 구조를 재조정(restructure)하는 데에만 전념해야 합니다 [1-3]. + * 리팩토링 전과 후의 코드가 정확히 동일한 외부 동작(결과)을 유지하도록 보장하는 것이 유일한 목표입니다 [6]. + * 인터페이스 변경에 대응해야 하는 필수적인 상황을 제외하고는 테스트 코드 역시 수정하지 않습니다 [2]. + +## ⚖️ Trade-offs & Caveats +* **디버깅 효율성의 급격한 저하 위험:** 기능 추가와 리팩토링 활동을 명확히 분리하지 않고 뒤섞어 수행할 경우, 코드에서 발생한 버그가 새로운 기능 구현 과정에서 발생한 것인지 아니면 리팩토링 과정의 실수인지 파악하기 어렵게 되어 디버깅 효율이 크게 떨어집니다 [3, 5]. +* **강한 자제력과 규율 요구:** 리팩토링 모자를 쓰고 코드를 개선하는 도중에 코드가 잘못 동작하는 것을 발견하거나 다른 개선점을 찾게 되더라도 이를 즉시 수정하려는 유혹을 견뎌야 합니다 [6, 7]. 즉시 고치는 대신 변경해야 할 사항을 인덱스 카드 등에 메모해 두고, 현재 진행 중인 리팩토링 목표(동작 보존)를 달성한 후에 모자를 바꿔 쓰고 해결해야 하므로 개발자에게 강도 높은 통제력을 요구합니다 [6, 7]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Unit Test (단위 테스트).md b/10_Wiki/Topics/Architecture/Unit Test (단위 테스트).md new file mode 100644 index 00000000..2b6990d8 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Unit Test (단위 테스트).md @@ -0,0 +1,31 @@ +# [[Unit Test (단위 테스트)]] + +## 📌 Brief 소감문 +단위 테스트(Unit Test)는 개별 컴포넌트, 함수 또는 클래스가 의도한 대로 작동하는지 고립된 환경에서 검증하는 가장 좁은 범위의 자동화 테스트입니다 [1, 2]. 실행 속도가 매우 빠르고 작성 및 유지보수 비용이 저렴하여 테스트 자동화 피라미드(Test Automation Pyramid)의 기저를 형성하며, 전체 테스트 스위트의 대다수를 차지합니다 [1, 2]. 리팩토링 과정에서 부작용과 버그 도입을 막아주는 안전망(Guardrails)이자 실행 가능한 명세서 역할을 수행합니다 [3, 4]. + +## 📖 Core Content +* **단위 테스트의 범위와 속성 (Scope and Characteristics)** + * 함수형 프로그래밍 언어에서는 단일 함수, 객체 지향 언어에서는 단일 메서드나 클래스가 단위(Unit)가 됩니다 [5]. + * 밀리초(ms) 단위로 매우 빠르게 실행되어야 하며, 최적의 고립성과 속도를 위해 데이터베이스, 파일 시스템, 네트워크 호출 등 외부 인프라와 직접 통신해서는 안 됩니다 [4, 6]. 100ms 이내로 실행되지 않거나 인프라와 통신하는 테스트는 단위 테스트로 간주하지 않습니다 [6]. + +* **리팩토링 및 TDD와의 시너지 (Synergy with Refactoring & TDD)** + * 단위 테스트는 테스트 주도 개발(TDD)과 함께 실천될 때 단순히 품질을 확인하는 도구를 넘어 코드 구조를 개선하는 '설계 도구(Design Tool)'로 기능합니다 [1]. + * 버그 리포트를 접수했을 때, 가장 먼저 해당 버그를 재현하고 노출하는 단위 테스트를 작성하여 버그를 수정하는 것이 모범적인 접근법입니다 [7]. + +* **구조 및 작성 원칙 (Structure and Principles)** + * **관측 가능한 동작 테스트:** 내부 구현 구조가 아닌, 특정 값을 입력했을 때 예상된 결과가 나오는지를 검증하는 '관측 가능한 행동(Observable behavior)'에 초점을 맞춰야 합니다 [8]. + * **퍼블릭 인터페이스 집중:** 클래스의 퍼블릭 인터페이스만 테스트해야 합니다. 단순한 Getter/Setter나 내부 로직이 없는 자명한 코드는 테스트할 필요가 없습니다 [9, 10]. + * **일관된 테스트 구조:** "준비, 실행, 단언(Arrange, Act, Assert)" 또는 "주어진 상황, 행동, 결과(Given, When, Then)"라는 3단계 구조를 따르면 가독성이 높고 간결한 테스트 코드를 유지할 수 있습니다 [10]. + * 테스트 코드 또한 프로덕션 코드와 동일하게 중요하며, 지속해서 유지보수되고 리팩토링되어야 합니다 [11]. + +* **고립(Solitary) 단위 테스트 vs. 사교적(Sociable) 단위 테스트** + * 모든 협력자(Collaborator)를 모의 객체(Mock)나 스텁(Stub)으로 교체하여 완벽한 고립과 빠른 속도를 추구하는 '고립 단위 테스트' 방식과, 실제 협력자와의 연동을 일부 허용하여 테스트의 신뢰도를 높이는 '사교적 단위 테스트' 방식이 혼용되어 사용됩니다 [5, 12]. + +## ⚖️ Trade-offs & Caveats +* **구현 세부 사항 결합으로 인한 유지보수 비용 증가:** 단위 테스트가 퍼블릭 인터페이스가 아닌 코드의 내부 구현 구조에 너무 가깝게 결합(Coupling)될 경우, 내부 구조를 개선하는 리팩토링을 수행할 때마다 테스트가 함께 깨지는(brittle) 부작용이 발생합니다 [13]. 이는 단위 테스트의 본래 목적(코드 변경 시의 안전망)을 잃게 만들고 개발자에게 큰 불편함을 초래합니다 [8]. +* **무의미한 커버리지 달성의 함정:** 100% 테스트 커버리지를 채우기 위해 조건부 논리가 전혀 없는 단순한 Getter/Setter까지 억지로 테스트하는 것은 시간 낭비이며, 실제 코드 품질 향상에 기여하지 못합니다 [10, 14]. +* **프라이빗 메서드 테스트의 제약:** 프라이빗 메서드를 억지로 테스트해야 한다는 필요성을 느낀다면, 이는 테스트 방법의 문제가 아니라 해당 클래스가 너무 많은 일을 하고 있어(단일 책임 원칙 위반) 설계를 분리해야 한다는 신호(Code smell)일 확률이 높습니다 [15]. +* **모의 객체(Mock) 남용의 위험:** 외부 의존성을 스텁이나 모의 객체로 과도하게 대체하면 단위 테스트의 속도와 고립성은 확보되지만, 모의 객체가 실제 외부 서비스의 변경된 스펙을 반영하지 못할 경우 테스트는 통과하더라도 실제 환경에서는 시스템이 오작동할 수 있는 위험이 따릅니다 [16, 17]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Unit Testing.md b/10_Wiki/Topics/Architecture/Unit Testing.md new file mode 100644 index 00000000..d49b8130 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Unit Testing.md @@ -0,0 +1,20 @@ +# [[Unit Testing]] + +## 📌 Brief Summary +유닛 테스트(Unit Testing)는 테스트 자동화 피라미드의 기반을 형성하며 안전한 리팩토링을 수행하기 위한 필수적인 전제 조건입니다 [1-3]. 이는 개별 컴포넌트나 함수의 동작을 격리하여 검증하는 작고 빠른 독립적인 테스트를 의미합니다 [1, 4]. 실행 가능한 명세서이자 가드레일 역할을 함으로써, 개발자가 코드의 외부 동작을 변경하지 않고 내부 구조를 개선할 때 발생할 수 있는 결함을 방지하고 즉각적인 피드백을 제공합니다 [1, 5, 6]. + +## 📖 Core Content +* **유닛 테스트의 정의와 특성**: 유닛 테스트는 코드베이스에서 가장 좁은 범위를 다루며, 특정 컴포넌트나 메서드가 의도한 대로 동작하는지 확인합니다 [4]. 마이클 페더스(Michael Feathers)는 실행 속도가 너무 느리거나(예: 테스트당 100ms 초과), 데이터베이스, 네트워크, 파일 시스템 등 외부 인프라와 통신하는 테스트는 진정한 의미의 유닛 테스트가 아니라고 정의했습니다 [4, 7, 8]. +* **리팩토링 과정에서의 필수 역할**: 리팩토링을 시작하기 전에는 반드시 코드가 자가 테스트(Self-testing)가 가능하도록 견고한 유닛 테스트 스위트를 구축해야 합니다 [2, 3, 6]. 테스트 주도 개발(TDD)의 'Red-Green-Refactor' 원칙에 따라, 테스트는 비즈니스 로직을 변경하지 않고 최적화를 진행할 수 있게 하는 안전망 역할을 수행합니다 [9-12]. 또한 버그가 보고되었을 때는 해당 버그를 드러내는 유닛 테스트를 먼저 작성한 후 수정하는 것이 모범 사례입니다 [13, 14]. +* **테스트 피라미드와 실행 속도**: 유닛 테스트는 작성과 유지보수 비용이 저렴하고 밀리초 단위로 실행되기 때문에 테스트 피라미드의 최하단을 구성해야 합니다 [1]. 건강한 테스트 스위트라면 전체 자동화 테스트의 약 70%가 유닛 테스트로 이루어져야 하며, 강력한 격리를 통해 수 분 내에 수천 개의 테스트를 실행할 수 있어야 합니다 [1, 15]. +* **고립형(Solitary) vs 사교형(Sociable) 테스트**: 외부 의존성으로 인한 부수 효과를 피하기 위해 목(Mock)이나 스텁(Stub)과 같은 테스트 대역(Test Double)을 사용하여 격리하는 '고립형' 테스트와, 실제 협력 객체를 그대로 사용하는 '사교형' 테스트 방식이 모두 활용됩니다 [16-18]. +* **구조와 설계 모범 사례**: 유닛 테스트는 내부 구현 세부 사항이 아니라 클래스의 '퍼블릭 인터페이스'와 '관측 가능한 동작(Observable Behaviour)'을 테스트하는 데 집중해야 합니다 [19, 20]. 또한 테스트 코드 작성 시에는 데이터 설정(Arrange), 대상 메서드 호출(Act), 결과 검증(Assert)의 3단계 구조를 따르는 것이 가독성과 일관성을 높여줍니다 [21]. + +## ⚖️ Trade-offs & Caveats +* **구현에 대한 과도한 결합(Brittle Tests)**: 유닛 테스트가 내부 코드 구조나 프라이빗 메서드(Private Method)의 구현에 너무 밀접하게 결합되면, 리팩토링을 수행할 때마다 테스트가 깨지게 됩니다 [19, 22, 23]. 내부 구현이 바뀔 때마다 실패하는 테스트는 유지보수의 부담을 가중시키고 리팩토링의 안전망이라는 본래 목적을 훼손하여 개발자의 피로도를 높입니다 [20]. +* **단순 코드 테스트 및 100% 커버리지 추구의 함정**: 단순한 Getter/Setter나 복잡한 조건 로직이 없는 사소한 코드까지 모두 테스트하여 100% 커버리지를 달성하려는 노력은 시간 낭비이며 수확 체감의 법칙에 직면하게 됩니다 [21, 24-26]. 테스트는 결함이 발생할 가능성이 있는 위험성과 복잡성이 높은 영역에 집중되어야 합니다 [25, 27]. +* **통합 문제 발견의 한계**: 유닛 테스트는 개별 컴포넌트의 동작만 격리하여 확인하므로, 독립적으로는 올바른 컴포넌트들이 서로 상호작용할 때 발생하는 결함은 잡아낼 수 없습니다 [28]. 따라서 유닛 테스트에만 의존하면 안 되며, 통합 테스트(Integration Tests)나 E2E 테스트 등 상위 계층의 테스트를 병행하여 실제 환경에서의 문제를 예방해야 합니다 [28-30]. +* **AI 생성 테스트의 한계**: 대규모 언어 모델(LLM)을 활용하여 유닛 테스트를 자동 생성할 경우, 테스트 커버리지의 한계에 부딪히거나 모킹(Mocking) 기능이 부족하고 테스트 코드가 쉽게 깨지는(brittleness) 부작용이 발생할 수 있으므로, 적절한 프롬프팅 기법과 개발자의 철저한 검토가 동반되어야 합니다 [5, 31, 32]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Unit Tests (단위 테스트).md b/10_Wiki/Topics/Architecture/Unit Tests (단위 테스트).md new file mode 100644 index 00000000..60044dd3 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Unit Tests (단위 테스트).md @@ -0,0 +1,25 @@ +# [[Unit Tests (단위 테스트)]] + +## 📌 Brief Summary +단위 테스트(Unit Tests)는 소프트웨어 시스템의 개별 컴포넌트나 함수가 의도한 대로 작동하는지 검증하기 위해 격리된 환경에서 실행되는 빠르고 범위가 좁은 테스트이다 [1, 2]. 리팩토링 과정에서 단위 테스트는 코드의 외부 동작을 변경하지 않고 내부 구조를 개선할 수 있도록 보장하는 실행 가능한 명세서이자 필수적인 안전망 역할을 수행한다 [3, 4]. 단위 테스트가 없는 코드는 나쁜 코드(또는 레거시 코드)로 간주되며, 지속적인 구조 개선과 기술 부채 상환을 위해서는 신뢰할 수 있는 단위 테스트의 구축이 선행되어야 한다 [5, 6]. + +## 📖 Core Content +* **리팩토링의 필수 전제 조건:** + 단위 테스트 없이 대규모 변경을 시도하는 것은 그물 없이 공중 곡예를 하는 것과 같이 매우 위험한 작업이다 [5]. 따라서 리팩토링을 시작하기 전의 최우선 단계는 항상 자체 검사(self-checking)가 가능한 견고한 테스트 스위트를 구축하는 것이다 [4, 7]. 이 테스트들은 개발자에게 버그를 유발하지 않으면서 코드를 안전하게 구조화할 수 있는 보안감을 제공한다 [6-8]. 또한, 버그 리포트를 받았을 때는 버그를 수정하기 전에 해당 버그를 노출시키는 단위 테스트를 먼저 작성하는 것이 원칙이다 [9]. +* **테스트 자동화 피라미드의 기반:** + 단위 테스트는 '테스트 자동화 피라미드'의 가장 하단(기반)을 구성하며, 전체 자동화 테스트 스위트의 대다수(약 70%)를 차지해야 한다 [1, 2]. 단위 테스트는 시스템의 경계를 테스트하는 통합 테스트 등과 달리 매우 국소적이며 밀리초 단위로 실행된다 [1, 10, 11]. 지속적 통합(CI) 빌드 과정에서 즉각적인 피드백을 제공하며, 작성 및 유지보수 비용이 저렴하다는 장점이 있다 [1]. 기능 테스트가 품질 보증(QA) 부서를 만족시키기 위한 것이라면, 단위 테스트는 프로그래머 자신의 생산성을 향상시키기 위해 작성된다 [11]. +* **테스트 작성 구조 및 대상:** + 훌륭한 단위 테스트는 '준비, 실행, 단언(Arrange, Act, Assert)' 또는 '주어짐, 발생함, 결과(Given, When, Then)'라는 명확한 3단계 구조를 따른다 [12, 13]. 테스트는 클래스의 내부 구현 세부사항이나 프라이빗(private) 메서드가 아닌, 관찰 가능한 동작 즉 '퍼블릭 인터페이스(public interface)'를 대상으로 해야 한다 [14-16]. 더불어, 데이터의 단순한 읽기/쓰기를 수행하는 사소한(trivial) 코드는 버그가 발생할 확률이 낮으므로 테스트 대상에서 제외하고, 위험이 존재하거나 복잡한 경계 조건(boundary conditions)에 집중하는 것이 효율적이다 [12, 17-19]. +* **TDD와 단위 테스트:** + 애자일 및 테스트 주도 개발(TDD) 방법론에서 단위 테스트는 코드 설계를 주도하는 도구로 활용된다 [1, 20]. 이는 코드가 존재하기 전에 실패하는 테스트를 먼저 작성하는 '적색(Red)' 단계, 테스트를 통과할 수 있는 최소한의 코드를 작성하는 '녹색(Green)' 단계, 그리고 동작을 유지한 채 코드를 다듬는 '리팩토링(Refactor)' 단계의 순환으로 이루어진다 [21-24]. +* **고립과 테스트 대역(Test Doubles):** + 테스트 대상 단위를 분리하기 위해 데이터베이스 접근이나 네트워크 호출과 같이 느리거나 부수 효과가 큰 외부 협력 객체는 목(Mocks)이나 스텁(Stubs)과 같은 가짜 객체로 대체된다 [20, 25, 26]. 이러한 고립 방식을 선호하는 '고독한(solitary)' 단위 테스트 방식이 있는 반면, 실제 협력 객체를 그대로 사용하는 '사교적(sociable)' 단위 테스트 방식도 상황에 맞게 혼용되어 사용된다 [25, 27]. + +## ⚖️ Trade-offs & Caveats +* **구현 세부사항과의 과도한 결합:** 단위 테스트가 프로덕션 코드의 내부 구조를 너무 밀접하게 반영할 경우, 리팩토링 시 내부 로직만 변경해도 테스트가 깨지는 문제가 발생한다 [28]. 이렇게 되면 단위 테스트가 코드 변경의 안전망 역할을 하지 못하고 오히려 유지보수의 큰 짐이 될 수 있으므로, 테스트는 내부 실행 순서가 아닌 외부에서 관찰 가능한 동작(입력에 따른 올바른 출력 결과)에 집중해야 한다 [16, 28]. +* **100% 커버리지 추구의 함정:** 맹목적으로 높은 테스트 커버리지를 달성하기 위해 조건 논리가 없는 단순한 게터(getter)나 세터(setter)까지 테스트하는 것은 시간 낭비이며 실질적인 이득이 없다 [12, 19]. 완벽한 테스트로 모든 버그를 잡겠다는 생각에 집착하기보다는, 핵심 위험 영역에 대해 불완전하더라도 테스트를 우선 작성하고 실행하는 것이 아예 하지 않는 것보다 훨씬 낫다 [29-31]. +* **테스트 코드의 유지보수 부채:** 작성되는 모든 테스트는 부가적인 짐(baggage)이며 코드를 읽고 실행하는 데 유지보수 비용과 시간이 소모된다 [32]. 중복되거나 더 이상 가치를 제공하지 않는 테스트, 또는 상위 수준의 테스트에서 이미 확실히 검증되는 내용을 중복 검증하는 불필요한 하위 테스트는 과감하게 삭제(Prune)하여 테스트 스위트의 팽창과 실행 속도 저하를 막아야 한다 [32-35]. +* **오류 탐지의 한계:** 단위 테스트는 개별 모듈이나 함수의 정확성을 확인하는 데는 탁월하지만, 개별적으로는 정상 작동하는 컴포넌트들이 서로 상호작용할 때 발생하는 오류는 잡아낼 수 없으며 이를 위해서는 통합 테스트(Integration Tests)가 필요하다 [36]. 또한 프로그램의 모든 가능한 실행 경로를 테스트하는 것은 계산적으로 불가능하므로, 단위 테스트 스위트만으로 모든 버그가 없음을 완벽히 보장할 수는 없다는 근본적인 한계가 존재한다 [37]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/Wrap Method (랩 메서드).md b/10_Wiki/Topics/Architecture/Wrap Method (랩 메서드).md new file mode 100644 index 00000000..65b4c6c6 --- /dev/null +++ b/10_Wiki/Topics/Architecture/Wrap Method (랩 메서드).md @@ -0,0 +1,62 @@ +# [[Wrap Method (랩 메서드)]] + +## 📌 Brief 리팩토링 +Wrap Method(랩 메서드)는 레거시 코드를 리팩토링할 시간이 부족하거나 테스트를 작성하기 어려운 상황에서 기존 코드를 감싸(Wrap) 새로운 기능을 추가하는 기법입니다 [1, 2]. 기존 메서드의 이름을 변경하고 원래 이름과 동일한 새 메서드를 만들어 그 안에서 기존 메서드와 새로운 로직을 함께 호출하는 방식으로 동작합니다 [2]. 이 기법을 사용하면 기존 코드를 직접 수정하지 않으면서도 새로운 로직을 안전하게 추가하고 테스트할 수 있습니다 [2]. + +## 📖 Core Content +Wrap Method는 마이클 페더스(Michael Feathers)의 저서 "Working Effectively with Legacy Code"에서 테스트가 없는 레거시 코드에 안전하게 기능을 추가하기 위한 전략으로 소개되었습니다 [3, 4]. + +* **적용 목적 및 맥락**: 기능 추가나 수정을 위해 기존 코드를 리팩토링해야 하지만, 시간적 여유가 없고 기존 코드가 거대하여 테스트 작성이 불가능할 때 우회책으로 사용됩니다 [1]. +* **사용 조건**: 추가해야 하는 새로운 변경 사항(로직)이 기존 코드의 실행 '전'이나 '후'에 발생해야 할 때 적합합니다 [2]. +* **Wrap Method 실행 4단계** [2]: + 1. 래핑(Wrap)하고자 하는 기존 메서드의 이름을 변경합니다. + 2. 기존 메서드와 동일한 이름과 시그니처를 가진 새로운 메서드를 생성합니다. + 3. 새로운 메서드 안에서 이름이 변경된 기존 메서드를 호출합니다. + 4. 기존 메서드 호출 전이나 후에 새로운 로직을 추가합니다. +* **테스트 가능성 (Testability)**: 새로 추가된 로직은 격리되어 테스트가 가능해집니다. 이는 기존에 문제가 되던 구(old) 메서드가 테스트 시에 동작을 변경(alter)할 수 있는 '이음새(Seam)' 역할을 하기 때문입니다 [2]. + +## ⚖️ Trade-offs & Caveats +* **근본적 해결책의 부재**: 이 기법은 코드를 추가하기 위한 임시적이고 실용적인 도구일 뿐, 이상적인 해결책은 아닙니다 [5]. 거대한 코드 덩어리(Big lumps of code)에 코드를 덧붙이려는 유혹에 빠지게 하여 장기적으로는 유지보수해야 할 클래스의 크기를 계속 키우는 부작용이 있습니다 [1]. +* **잠재적 함정**: 소스 코드에서는 Wrap Method 기법이 완벽하지 않으며 특유의 함정(pitfalls)이 있다고 지적합니다 [5]. (구체적으로 어떤 기술적인 함정이 발생하는지에 대해서는 소스에 관련 정보가 부족합니다.) +* **적용의 한계**: 따라서 이 기법은 시간이 극도로 부족하여 안전한 단위 테스트 작성과 전면적인 리팩토링을 수행할 수 없는 상황에서만 제한적이고 전략적으로 사용되어야 합니다 [1]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [레거시 코드 대응 기술] +- [[Seams (이음새)]] + - 연결 이유: Wrap Method를 사용하면 기존 메서드가 이음새(Seam) 역할을 하여 테스트에서 프로그램의 동작을 변경할 수 있게 해줍니다 [2]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기존 소스 코드를 직접 수정하지 않고도 프로그램의 동작을 변경하거나 테스트를 용이하게 만드는 구조적 접근법 [6]. + +- [[Sprout Method (스프라우트 메서드)]] + - 연결 이유: Wrap Method와 함께 테스트가 없는 레거시 코드에 시간이 부족할 때 안전하게 코드를 추가하는 대표적인 우회 기법으로 쌍을 이루어 소개됩니다 [1]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기존 코드 내 특정 삽입 지점(insertion point)에 새로운 로직의 호출을 넣는 방식(Sprout)과 기존 코드를 감싸는 방식(Wrap)의 차이 및 활용 상황 비교 [2, 7]. + +#### [리팩토링 대상 환경] +- [[Legacy Code (레거시 코드)]] + - 연결 이유: Wrap Method 자체가 "테스트가 없는 코드"로 정의되는 레거시 코드 환경에서 안전한 변경을 하기 위해 고안된 방법론이기 때문입니다 [1, 3]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 왜 기존 코드를 바로 리팩토링하지 못하고 Wrap Method 같은 우회 기법을 써야 하는지에 대한 근본적인 배경 [8]. + +### Deeper Research Questions +- Wrap Method와 Sprout Method 중 어떤 상황에서 어느 기법을 선택하는 것이 시스템의 결합도를 낮추는 데 더 유리한가? +- 이음새(Seam)를 활용하여 Wrap Method를 테스트 가능하게 만드는 구체적인 객체지향 언어별 구현 패턴은 무엇인가? +- Wrap Method의 남용이 클래스 설계(예: 단일 책임 원칙)에 미치는 장기적인 악영향은 무엇이며, 이를 다시 온전하게 리팩토링하는 절차는 무엇인가? +- 테스트가 없는 레거시 시스템에서 Wrap Method를 적용한 후, 추후 근본적인 '준비적 리팩토링(Preparatory Refactoring)'으로 전환하기 위한 기술 부채 상환 전략은 무엇인가? +- 소스 코드에서 언급된 Wrap Method의 "잠재적 함정(pitfalls)"은 구체적으로 시스템 구조나 실행 흐름 측면에서 어떻게 나타나는가? + +### Practical Application Contexts +- **Implementation:** 거대하고 테스트되지 않은 레거시 메서드에 새로운 검증 로직이나 데이터 전처리 로직을 추가해야 할 때, 기존 코드 내부에 `if` 문을 무분별하게 추가하는 대신 메서드를 감싸서 구현합니다 [1, 2]. +- **System Design:** 설계가 복잡한 기존 시스템에 긴급한 비즈니스 요구사항을 반영할 때, 기존 로직과의 직접적인 결합을 피하면서 최소한의 모듈성을 유지하는 설계 우회로로 활용됩니다 [2]. +- **Operation / Maintenance:** 유지보수 시 시간이 촉박한 상황에서 테스트 커버리지를 점진적으로 확보하기 위해 사용되며, 기존 기능을 깨트릴 위험(Regression risk)을 최소화합니다 [1, 2]. +- **Learning Path:** 마이클 페더스(Michael Feathers)의 접근법을 통해 레거시 소프트웨어 환경을 다루는 방법과 테스트를 추가하는 실전 기법을 학습할 때 핵심 패턴으로 다뤄집니다 [3, 4]. +- **My Project Relevance:** 시간이 부족하고 전면 리팩토링이 불가능한 레거시 유지보수 태스크에서, 빠른 기능 추가와 단위 테스트 작성을 동시에 달성해야 할 때 즉각 도입할 수 있는 실용적 패턴입니다. + +### Adjacent Topics +- [[Test-Driven Development (TDD)]] + - 확장 방향: 레거시 코드에 Wrap Method 등을 통해 기본 테스트를 확보한 이후, 향후 새로운 기능 개발 시 안전하게 테스트 주도 개발 원칙을 확장 적용해 나가는 방법 [9]. +- [[Code Smells (코드 스멜)]] + - 확장 방향: Wrap Method 적용 대상이 되는 길고 복잡한 메서드(Long Method)나 거대한 클래스 등 코드 내 잠재적 결함 지표를 식별하고 관리하는 방법론 [10, 11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/YAGNI (You Aren't Gonna Need It).md b/10_Wiki/Topics/Architecture/YAGNI (You Aren't Gonna Need It).md new file mode 100644 index 00000000..5515a5de --- /dev/null +++ b/10_Wiki/Topics/Architecture/YAGNI (You Aren't Gonna Need It).md @@ -0,0 +1,16 @@ +# [[YAGNI (You Aren't Gonna Need It)]] + +## 📌 Brief Summary +YAGNI(You Aren't Gonna Need It)는 소프트웨어 개발 시 오버엔지니어링(Overengineering)을 피하고 현재 필요한 기능만 단순하게 구현하도록 권장하는 원칙이다 [1, 2]. 미래에 필요할 것이라고 추측하여 불필요한 유연성이나 특수 상황 처리를 위한 코드를 미리 추가하는 것을 엄격히 지양한다 [3, 4]. 대부분의 경우 미리 예측하여 작성한 복잡한 설계는 결국 사용되지 않고 유지보수에 방해만 되므로, 가장 단순하게 동작할 수 있는 것을 구축해야 한다 [3, 5]. + +## 📖 Core Content +* **오버엔지니어링 방지 및 단순성 추구**: YAGNI 원칙의 핵심은 불필요한 금칠(gold-plating)을 피하고 시스템을 단순하게 유지하는 것이다 [2, 5]. 개발자는 당장 필요하지 않은 복잡성을 억제하고 코드를 보다 단순하고 유지보수하기 쉽게 만들어야 한다 [1]. +* **추측성 일반화(Speculative Generality)의 경계**: 개발자가 "언젠가는 이런 기능이 필요할 것"이라고 추측하여 각종 훅(hooks)과 특수 케이스를 처리하는 로직을 미리 만들어 두면, 결과적으로 코드를 이해하고 유지보수하기 더 어려워진다 [4]. 실제로 사용되지 않는 기능들은 단지 방해만 될 뿐이므로 리팩토링을 통해 제거해야 한다 [4]. +* **유연성과 복잡도의 관계**: 코드에 유연성을 부여하기 위한 솔루션은 필연적으로 단순한 솔루션보다 복잡하다 [6]. 미래의 모든 변경을 예상하여 시스템을 설계하려고 하면, 실제 요구되는 것보다 훨씬 과도한 유연성을 코드에 억지로 넣게 된다 [6]. 따라서 복잡하고 유연한 설계를 처음부터 고민하기보다는, 당장 작동할 수 있는 가장 단순한 형태를 만들어야 한다 [3]. + +## ⚖️ Trade-offs & Caveats +* **지속적 리팩토링에 대한 요구**: 미래를 대비한 복잡한 설계를 미리 추가하지 않고(YAGNI 원칙을 고수하며) 가장 단순한 코드를 작성하는 접근법은, 향후 실제로 요구사항 변경이 필요해진 시점에 코드를 즉각적으로 변경할 수 있다는 **리팩토링에 대한 자신감**과 역량을 전제로 한다 [3]. +* **오버엔지니어링의 부작용**: 당장 필요하지 않은 미래의 상황을 위해 불필요한 아키텍처를 사전에 구축할 경우, 어떤 기능에서도 요구하지 않는 해당 아키텍처에 모든 코드 덩어리들이 억지로 맞춰 적응해야 하는 부작용이 발생할 수 있다 [7]. 즉, 섣부른 유연성 도입은 비용이 많이 들고 시스템 전체의 복잡도만 불필요하게 상승시키는 제약 사항을 낳는다 [4, 6]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/기술 부채 (Technical Debt).md b/10_Wiki/Topics/Architecture/기술 부채 (Technical Debt).md new file mode 100644 index 00000000..6d948655 --- /dev/null +++ b/10_Wiki/Topics/Architecture/기술 부채 (Technical Debt).md @@ -0,0 +1,33 @@ +# [[기술 부채 (Technical Debt)]] + +## 📌 Brief Summary +**기술 부채(Technical Debt)**는 소프트웨어 개발 과정에서 당장의 마감 기한을 맞추기 위해 빠르고 임시방편적인 코드 작성을 선택할 때 발생하는 장기적인 유지보수 비용을 의미합니다 [1-3]. 이는 중복된 로직, 명확하지 않은 변수명, 유연하지 않은 아키텍처 등의 '지저분한 코드(Dirty Code)' 형태로 나타나며 향후 기능 추가와 수정을 어렵게 만듭니다 [4-6]. 기술 부채를 방치하면 이자가 쌓이듯 비용이 기하급수적으로 증가하므로, **리팩토링(Refactoring)**을 통해 이를 체계적으로 상환하고 아키텍처의 부패를 방지해야 합니다 [1, 7]. + +## 📖 Core Content + +* **기술 부채의 원인과 증상:** + * 소프트웨어 개발 중 **시간적 압박(Time constraints)**이 있을 때, 개발자들은 코드를 깔끔하고 체계적으로 구성하기보다는 당장 기능이 동작하게 만드는 지름길(Shortcut)을 택하게 되며 이때 기술 부채가 발생합니다 [2, 8]. + * 이러한 지름길은 코딩 표준을 무시하거나, 로직을 중복 작성하고, 불분명한 변수명을 사용하는 등의 형태로 나타납니다 [5]. + * Stack Overflow 설문조사에 따르면, 개발자의 62%가 중복된 로직, 경직된 아키텍처, 엉킨 의존성 등으로 발현되는 기술 부채로 인해 큰 좌절감을 겪고 있는 것으로 나타났습니다 [4]. + * 특히 분산 시스템에서는 함수 수준의 기술 부채가 수천 번씩 실행되며 시스템 전체에 복합적인 악영향을 미칠 수 있습니다 [9]. + +* **기술 부채 방치의 파급 효과:** + * 워드 커닝햄(Ward Cunningham)은 기술 부채를 '금융 부채'에 비유하며, 빠르고 지저분한 구현은 결국 **늘어난 유지보수 비용이라는 '벌금(이자)'**을 치르게 한다고 설명했습니다 [1, 10]. + * 지저분한 코드를 정리하지 않고 방치하면 부채가 눈덩이처럼 불어나며, 향후 개발자들이 코드를 이해하고 추적하는 데 더 많은 시간을 소모하게 되어 **새로운 기능 개발과 개선 속도가 현저히 느려집니다** [6]. + * 유사한 개념으로 테스트 자동화를 소홀히 할 때 발생하는 **'품질 부채(Quality Debt)'**가 있으며, 이 역시 시간이 지날수록 비용이 복리로 증가하여 궁극적으로 팀의 소프트웨어 배포 능력을 압도하게 됩니다 [11]. + +* **기술 부채 관리를 위한 리팩토링 및 실행 전략:** + * 리팩토링은 단순히 미학적으로 코드를 예쁘게 만드는 것이 아니라, **기술 부채를 상환하여 개발 속도를 가속화하는 필수적인 경제 활동**이자 전략적 도구입니다 [7, 12]. + * 기능 추가 초기 단계에서 기존 코드를 정리하는 '준비적 리팩토링'이나 발견 즉시 수정하는 '쓰레기 줍기 리팩토링' 같은 작은 노력이 모여 기술 부채의 급격한 축적을 막는 방어선이 됩니다 [13, 14]. + * 현대적인 엔지니어링 팀은 기술 부채를 효과적으로 관리하기 위해 **코드와 직접 연결된 이슈를 추적(Track)하고, 영향력이 큰 문제를 우선순위화(Prioritise)하며, 정기적으로 리팩토링**하는 시스템을 갖추어야 합니다 [15, 16]. + * 기술 부채 상환을 위해 매 스프린트 시간의 **15~20%를 할당하거나, 분기당 한 번씩 2주간의 리팩토링 전용 스프린트를 계획**하는 등 구체적인 시간을 배정하는 것이 권장됩니다 [17]. + +## ⚖️ Trade-offs & Caveats + +* **단기적 속도와 장기적 비용의 상충 (Short-term Speed vs. Long-term Cost):** 기술 부채를 감수하면 단기적으로는 소프트웨어 출시와 기능 배포 속도를 높일 수 있지만, 이를 전략적으로 관리하지 않고 계속 방치할 경우 중장기적으로는 개발 비용이 상승하고 속도가 심각하게 저하되는 반대 급부가 따릅니다 [15, 18, 19]. +* **자원 배분의 딜레마 (Resource Allocation):** 기술 부채를 해결하기 위해 리팩토링에 인력을 투입하면, 당장 수익을 창출하는 새로운 기능 개발에서 개발자가 이탈하게 됩니다 [20, 21]. 특히 규모가 작은 팀의 경우 이러한 자원 할당이 프로젝트 마감 기한과 충돌할 수 있으므로 신중한 계획과 균형이 필요합니다 [20, 21]. +* **부채의 불가피성과 문서화의 필요성:** 기술 부채는 소프트웨어 개발 수명 주기에서 **불가피하게 발생하는 부분(Inevitable part)**입니다 [16]. 따라서 지름길을 택해야만 하는 상황이라면, 해당 기술 부채를 적절히 문서화하고 코드베이스와 직접 연결하여 추후 반드시 해결할 수 있도록 관리하는 제약이 따릅니다 [16]. +* **리팩토링 자체의 위험성:** 기술 부채를 해결하기 위해 리팩토링을 수행하는 과정 자체가 새로운 버그를 도입하거나 기존 기능을 망가뜨릴 위험(Risk)을 내포하고 있습니다 [20]. 이를 방지하기 위해서는 철저한 자동화 테스트(Unit Tests 등)가 필수적으로 요구됩니다 [22-24]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/다형성 (Polymorphism).md b/10_Wiki/Topics/Architecture/다형성 (Polymorphism).md new file mode 100644 index 00000000..b4ae2643 --- /dev/null +++ b/10_Wiki/Topics/Architecture/다형성 (Polymorphism).md @@ -0,0 +1,37 @@ +# [[다형성 (Polymorphism)]] + +## 📌 Brief Summary +다형성(Polymorphism)은 객체의 타입이나 종류에 따라 달라지는 행동을 명시적인 조건문(switch나 if-else)으로 확인하는 대신, 객체지향적인 방식으로 자연스럽게 처리할 수 있게 해주는 핵심 개념이다 [1-3]. 리팩토링에서 다형성은 주로 수많은 분기를 가진 복잡한 조건문을 서브클래스의 오버라이딩 메서드로 교체하여 코드의 중복을 제거하고 구조를 개선하는 데 활용된다 [4-6]. 이를 통해 새로운 타입이나 사례가 추가될 때 기존의 로직을 뜯어고칠 필요 없이 새로운 클래스만 추가하면 되므로, 개방-폐쇄 원칙(Open/Closed Principle)을 준수하며 아키텍처의 변경을 관리하기 쉽게 만든다 [1, 3, 4, 7]. + +## 📖 Core Content + +* **조건문(Switch/Case)의 문제점과 객체지향 남용(OO Abusers):** + 객체지향 코드의 주요 특징 중 하나는 switch(또는 case) 문이 상대적으로 적다는 점이다 [5]. 동일한 조건의 switch 문이 프로그램 곳곳에 흩어져 있으면, 새로운 타입을 추가할 때마다 모든 조건문을 찾아 업데이트해야 하는 치명적인 유지보수 문제가 발생한다 [1]. 이러한 상태는 상속과 다형성 같은 객체지향의 이점을 제대로 살리지 못하고 절차지향적으로 구현된 것으로, '객체지향 남용(OO Abusers)'이라는 코드 스멜(Code Smell)로 분류된다 [8]. 대부분의 switch 문을 발견했을 때는 다형성 적용을 고려해야 한다 [9]. + +* **다형성을 통한 조건문 교체 (Replace Conditional with Polymorphism):** + 객체의 타입에 따라 다른 동작을 선택하는 조건문이 있다면, 해당 조건문의 각 분기(leg)를 서브클래스의 오버라이딩 메서드로 이동시키고 원본 메서드는 추상(abstract) 메서드로 만들어야 한다 [6]. 이는 변경의 지식을 클라이언트에서 클래스 내부로 옮겨준다 [10]. 다형성을 통해 복잡한 조건식을 다형성 메서드 호출로 대체함으로써 각 행동을 고립시키고 테스트를 매우 단순하게 만들 수 있다 [4, 7]. + +* **리팩토링 적용 단계:** + 1. 다중 분기를 가진 복잡한 조건문을 식별한다 [4]. 다형성을 적용하기 전에는 이를 지원할 상속 구조(Inheritance Structure)가 먼저 준비되어 있어야 한다 [11]. + 2. 달라지는 행동을 담을 인터페이스나 베이스 클래스(Base Class)를 생성한다 [4]. + 3. 조건 분기마다 해당하는 구체 클래스(Concrete Class)를 구현한다 [4]. + 4. 기존 조건 로직을 다형성을 활용한 메서드 호출로 교체한다 [4]. + 5. 각각의 구현 클래스를 독립적으로 테스트한다 [4]. + +* **Null 객체 도입 (Introduce Null Object):** + 다형성의 본질은 객체에게 어떤 타입인지 묻고 그 대답에 따라 행동을 호출하는 것이 아니라, 단순히 행동을 호출하면 객체가 자신의 타입에 맞게 올바른 일을 수행하도록 하는 것이다 [2]. 반복되는 Null 값 검사 역시 Null 객체라는 특별한 형태의 서브클래스(Special Case)를 도입하여 다형성을 통해 조건문을 제거할 수 있다 [12, 13]. + +## ⚖️ Trade-offs & Caveats + +* **과도한 설계 (Overkill):** + 조건문이 단 하나의 메서드에만 영향을 미치고, 향후에도 새로운 타입의 조건이 추가되거나 변경될 것으로 예상되지 않는다면 다형성을 적용하는 것은 지나친 설계(Overkill)가 될 수 있다 [14]. 이러한 경우에는 '명시적 메서드로 매개변수 교체(Replace Parameter with Explicit Methods)'와 같은 더 단순한 대안이 적합하다 [14]. + +* **객체 수명 주기 동안의 타입 변경 제약:** + 다형성을 구현하기 위해 서브클래싱(Subclassing)을 사용할 때 한 가지 제약이 있다. 객체는 생성된 이후 수명 주기 동안 자신의 클래스(분류)를 변경할 수 없다는 점이다 [15]. 따라서 객체의 상태나 타입 코드가 실행 도중에 변경되어야 하거나, 이미 다른 이유로 인해 서브클래싱이 적용된 상태라면 다형성을 직접 적용할 수 없다 [11, 15, 16]. 이 경우에는 '상태/전략 패턴으로 타입 코드 교체(Replace Type Code with State/Strategy)'를 활용하여 인디렉션(Indirection)을 추가하고 위임을 통해 다형성을 달성해야 한다 [11, 15, 16]. + +* **상속 구조 생성의 선행:** + 다형성을 통해 조건문을 교체하려면 반드시 서브클래싱이나 상태/전략 패턴을 이용한 상속 계층 구조가 사전에 만들어져 있어야 하므로, 초기 구조를 잡기 위한 추가적인 작업이 요구된다 [11, 17]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/단위 테스트 (Unit Tests).md b/10_Wiki/Topics/Architecture/단위 테스트 (Unit Tests).md new file mode 100644 index 00000000..1aa23d1a --- /dev/null +++ b/10_Wiki/Topics/Architecture/단위 테스트 (Unit Tests).md @@ -0,0 +1,31 @@ +# [[단위 테스트 (Unit Tests)]] + +## 📌 Brief 소감 +단위 테스트(Unit Tests)는 개별 함수, 메서드 또는 클래스 등 소프트웨어의 가장 작은 구성 단위(Unit)를 외부 의존성으로부터 격리하여 동작을 검증하는 빠르고 자동화된 테스트 기법이다 [1-3]. 이는 기능 변경이나 리팩토링 시 기존의 동작이 손상되지 않았음을 보장하는 필수적인 안전망 역할을 수행한다 [4-6]. 이상적인 단위 테스트는 스스로 결과를 검증(self-checking)할 수 있어야 하며, 소프트웨어 테스트 자동화 피라미드의 가장 하단에 위치하여 전체 테스트 스위트의 대다수를 차지해야 한다 [1, 7, 8]. + +## 📖 Core Content + +* **리팩토링의 필수 전제 조건** + 리팩토링을 시작하기 전에는 반드시 견고한 단위 테스트 스위트가 구축되어 있어야 한다 [5, 6]. 테스트 코드가 없는 소프트웨어는 단순히 '레거시 코드'로 취급되며, 단위 테스트라는 안전망 없이는 대규모 변경 시 회귀 버그(regression bug)를 피하기 어렵다 [9, 10]. 또한, 새로운 버그 리포트를 받았을 때 가장 먼저 해야 할 일은 해당 버그를 드러내는 단위 테스트를 작성하여, 추후 동일한 문제가 발생하지 않도록 방지하는 것이다 [11-13]. +* **테스트의 구조와 모범 사례** + 단위 테스트는 데이터 설정(Arrange), 메서드 호출(Act), 예상 결과 검증(Assert) 혹은 'Given, When, Then' 구조를 따르는 것이 읽기 쉽고 일관성 있는 테스트를 작성하는 데 유리하다 [14, 15]. 테스트 대상을 선정할 때는 단순한 Getter/Setter 등 사소한 코드는 생략하고, 시스템이 실패할 수 있는 경계 조건(boundary conditions)이나 에러 조건 등 잠재적 위험이 있는 곳에 집중하여 테스트 효율성을 높여야 한다 [16-19]. +* **테스트 대역(Test Doubles)의 활용** + 데이터베이스, 파일 시스템, 외부 네트워크 호출 등은 단위 테스트의 속도를 늦추고 복잡성을 더한다. 이를 방지하기 위해 목(Mock)이나 스텁(Stub)과 같은 가짜 객체를 사용하여 외부 협력자를 대체함으로써, 컴포넌트를 격리하고 예측 가능한 환경에서 빠르게 테스트를 실행할 수 있다 [20-22]. 모든 협력자를 격리하는 '고립된(Solitary)' 방식과, 속도 저하가 없는 일부 협력자를 포함하는 '사교적인(Sociable)' 방식으로 나뉠 수 있다 [20]. +* **테스트 대상의 캡슐화 보존** + 단위 테스트는 구현 세부 구조가 아닌 관측 가능한 동작(observable behavior)과 공개 인터페이스(public interface)를 검증해야 한다 [23, 24]. 클래스의 내부 구현(private 메서드)을 직접 테스트해야 한다는 충동이 든다면, 이는 해당 클래스가 '단일 책임 원칙(SRP)'을 위반하여 너무 많은 일을 하고 있다는 설계 문제의 신호일 가능성이 높으므로 클래스 분리를 고려해야 한다 [25-27]. +* **레거시 코드와 AI 활용** + 테스트가 없는 레거시 코드를 변경할 때는 코드를 직접 변경하지 않고 동작을 우회할 수 있는 '접점(Seams)'을 파악하여 테스트를 주입하는 것이 중요하다 [28]. 최근에는 대규모 언어 모델(LLM) 기반의 AI 코딩 도구를 활용해 기존 시스템의 동작을 포착하는 단위 테스트를 자동으로 대량 생성하고, 이를 가드레일로 삼아 리팩토링을 수행하는 접근법이 높은 브랜치 및 구문 커버리지를 달성하며 효과를 입증하고 있다 [29-31]. + +## ⚖️ Trade-offs & Caveats + +* **테스트의 경직성(Brittleness) 문제** + 단위 테스트를 프로덕션 코드의 내부 구조와 너무 가깝게 결합하여 작성하면, 단순히 내부 설계를 개선하는 리팩토링 작업 중에도 테스트가 깨지는 문제가 발생한다 [32]. 이는 개발자가 코드를 수정할 때마다 깨진 테스트를 고쳐야 하는 번거로움을 야기하며, 결국 단위 테스트가 안전망이라는 본연의 가치를 잃고 유지보수 부담으로 전락할 수 있다 [24]. +* **단위 테스트의 범위 한계** + 단위 테스트는 개별 단위의 논리를 검증하는 데는 탁월하지만, 외부 시스템이나 독립된 서비스, 데이터베이스와의 통합 과정에서 발생하는 결함을 찾아낼 수는 없다 [33, 34]. 따라서 단위 테스트에만 의존해서는 안 되며 상위 수준의 통합 테스트(Integration Tests) 및 엔드투엔드(E2E) 테스트와 병행해야 한다 [33, 35]. +* **레거시 코드 딜레마(Legacy Code Dilemma)** + 기존 코드에 안전하게 단위 테스트를 추가하려면 코드의 의존성을 분리해야 하는데, 의존성을 끊기 위해 코드를 변경하려면 다시 단위 테스트가 필요한 모순적인 상황에 처하게 된다 [36]. 이 과정에서 컴파일러를 활용한 매우 작고 안전한 리팩토링을 수행하거나, 'Sprout/Wrap' 기법 등을 이용해 최소한의 변경만으로 코드를 확장해야 하는 기술적 제약이 따른다 [36, 37]. +* **과도한 테스트의 역효과 및 AI의 가치 정렬 편향** + 소프트웨어의 모든 조합이나 단순 기능까지 100% 테스트하려는 압박은 오히려 개발자를 좌절시켜 꼭 필요한 테스트조차 작성하지 않게 만드는 역효과를 초래할 수 있다 [17, 38]. 또한 AI를 사용해 단위 테스트를 생성할 때, AI 모델은 커버리지 수치만 높이고 실제 결함 검증력은 떨어지는 비효율적인 테스트를 다수 생성하는 편향성(value misalignment)을 보일 수 있어, 변이 테스트(mutation testing)를 통한 개발자의 엄격한 필터링이 필요하다 [39, 40]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/레거시 코드 (Legacy Code).md b/10_Wiki/Topics/Architecture/레거시 코드 (Legacy Code).md new file mode 100644 index 00000000..31079637 --- /dev/null +++ b/10_Wiki/Topics/Architecture/레거시 코드 (Legacy Code).md @@ -0,0 +1,32 @@ +# [[레거시 코드 (Legacy Code)]] + +## 📌 Brief Summary +레거시 코드는 전통적으로 다른 사람이 작성했거나 이해하고 변경하기 어려운 복잡한 코드를 의미하기도 하지만, 마이클 페더스(Michael Feathers)의 정의에 따르면 "테스트가 없는 코드"를 뜻합니다[1-3]. 아무리 구조가 좋고 객체지향적으로 잘 작성되었더라도 테스트가 없다면 동작의 변경을 빠르고 검증 가능하게 수행할 수 없으므로 나쁜 코드로 간주됩니다[4]. 이러한 레거시 코드는 변경 시 즉각적인 피드백을 받을 수 없어 소프트웨어 개발에 소요되는 시간과 비용을 고갈시키는 주된 원인이 됩니다[5]. + +## 📖 Core Content +* **레거시 코드의 딜레마와 의존성 문제** + 레거시 코드를 안전하게 수정하려면 테스트가 필요하지만, 테스트를 작성하기 위해서는 기존 코드의 강한 의존성을 끊어내기 위해 코드를 먼저 수정해야 하는 모순적인 상황이 발생하며 이를 '레거시 코드의 딜레마'라고 부릅니다[6, 7]. 레거시 코드의 가장 핵심적인 문제는 코드가 서로 너무 강하게 결합되어 있어 독립적인 테스트가 불가능하다는 점입니다[7]. + +* **레거시 코드 개선 알고리즘** + 레거시 시스템을 점진적으로 개선하기 위해서는 최소한의 안전한 리팩토링을 수행해야 하며, 다음의 알고리즘을 따릅니다[6, 8]. + 1. **변경 지점 및 테스트 지점 식별:** 수정이 필요한 로직의 위치와 해당 로직을 보호할 수 있는 테스트 작성 위치를 찾습니다. + 2. **접점(Seams) 활용 및 의존성 제거:** 접점이란 소스 코드를 직접 수정하지 않고도 프로그램의 동작을 바꿀 수 있는 지점(예: 인터페이스 추상화 및 가짜 객체 주입)을 의미합니다[7, 9, 10]. 이를 통해 테스트 실행을 방해하는 의존성을 끊어냅니다. + 3. **테스트 작성:** 현재 어떻게 동작하는지를 기록하는 승인 테스트(Approval Tests) 또는 캐릭터리제이션 테스트(Characterization Tests)를 작성하여 안전망을 확보합니다[8]. + 4. **기능 수정 및 리팩토링:** 테스트가 확보된 후 요구사항을 반영하고 코드를 리팩토링합니다[6, 8]. + +* **시간이 부족할 때의 대처 기법 (Sprout & Wrap)** + 테스트가 없는 방대한 코드를 전면적으로 리팩토링할 시간이 없을 때 사용할 수 있는 기법입니다[11]. + * **스프라우트 기법(Sprout Technique):** 기존의 거대한 메서드에 코드를 덧붙이는 대신, 새로운 로직을 별도의 독립되고 검증된 함수(또는 클래스)로 작성한 후 기존 코드의 삽입 지점에서 이를 호출하는 방식입니다[8, 12]. + * **랩 기법(Wrap Technique):** 기존 메서드와 동일한 이름의 새 메서드를 만들고, 그 안에서 기존 메서드를 호출하기 전이나 후에 새로운 테스트 가능한 로직을 배치하여 감싸는 방식입니다[13]. + +* **스크래치 리팩토링 (Scratch Refactoring)** + 이해하기 어렵고 테스트가 없는 코드를 파악하기 위해 사용하는 기법입니다. 변수명을 바꾸거나 함수를 추출하는 등 코드를 자유롭게 변경하며 구조를 이해한 뒤, 파악이 끝나면 작성한 변경 사항을 모두 되돌리고(revert) 적절한 테스트와 함께 처음부터 다시 작업을 시작합니다[14, 15]. + +## ⚖️ Trade-offs & Caveats +* **초기 테스트 도입의 위험성:** 레거시 코드에 테스트를 도입하기 위해 의존성을 끊는 작업 자체는 아직 테스트 안전망이 없는 상태에서 코드를 변경해야 하므로 상당한 위험을 수반합니다[3]. 따라서 아주 국소적이고 안전한 최소한의 변경만을 수행해야 합니다[6]. +* **미학적 타협의 필요성:** 레거시 코드를 테스트 가능한 상태로 만드는 과정(예: 의존성 분리를 위한 임시 조치 등)에서 일시적으로 코드의 형태가 기존보다 다소 더 지저분해질 수 있습니다[16]. 이는 외과 수술을 위해 절개를 하는 것과 같으며, 코드를 더 건강한 상태로 옮기기 위해 미학적 판단을 유보해야 할 때가 있습니다. 완벽함('best')을 추구하느라 더 나아짐('better')을 포기해서는 안 됩니다[16]. +* **스프라우트와 랩 기법의 한계:** 스프라우트(Sprout)와 랩(Wrap) 기법은 기존 레거시 코드를 건드리지 않고 안전하게 새 기능을 추가할 수 있게 해주지만, 이상적인 해결책은 아닙니다[17]. 이 기법들만을 남용할 경우 원래의 레거시 코드는 리팩토링되지 않은 채 계속 방치될 수 있으므로 주의해야 합니다[11, 17]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/리팩토링 원칙.md b/10_Wiki/Topics/Architecture/리팩토링 원칙.md new file mode 100644 index 00000000..33c5dfd7 --- /dev/null +++ b/10_Wiki/Topics/Architecture/리팩토링 원칙.md @@ -0,0 +1,76 @@ +# [[리팩토링 원칙]] + +## 📌 Brief Summary +리팩토링(Refactoring)은 소프트웨어의 외부적 동작(관측 가능한 동작)을 변경하지 않으면서 내부 구조를 개선하여 코드를 더 이해하기 쉽고 수정하기 경제적으로 만드는 체계적이고 훈련된 과정입니다 [1-4]. 이는 단순히 코드를 예쁘게 꾸미는 것이 아니라, 기술 부채를 체계적으로 상환하고 소프트웨어의 생애 주기 동안 아키텍처의 부패를 방지하기 위한 핵심 전략입니다 [5-7]. 올바른 리팩토링은 향후 새로운 기능 추가와 버그 수정을 가속화하며, 전체적인 개발 및 유지보수 비용(ROI)을 낮추는 경제적 필수 활동입니다 [8-10]. + +## 📖 Core Content + +* **두 개의 모자 (Two Hats) 원칙** + 소프트웨어 개발 시 '기능 추가'와 '리팩토링'의 활동은 명확히 분리되어야 합니다 [11-13]. '기능 추가' 모자를 썼을 때는 기존 코드를 재구성하지 않고 새로운 동작을 추가하는 데만 집중하며, '리팩토링' 모자를 썼을 때는 기능을 추가하지 않고 오직 코드 구조 개선에만 전념해야 디버깅 효율이 급감하는 것을 막을 수 있습니다 [13-15]. +* **경제성 중심의 판단 (Economic Justification)** + 리팩토링의 궁극적인 목적은 코드를 미학적으로 완벽하게 만드는 것이 아니라, 개발 속도를 높여 비즈니스 가치를 빠르게 전달하는 경제성에 있습니다 [8-10]. 더러운 코드는 인지 부하를 높이고 기능 수정을 어렵게 만들므로, 이를 리팩토링하여 장기적인 유지보수 비용을 낮추는 것이 핵심입니다 [2, 6, 16]. +* **3의 법칙 (Rule of Three) 및 적절한 시점** + 중복 코드를 무조건 즉시 추상화하는 대신, 처음에는 그냥 구현하고 두 번째에는 중복을 감수하되, 세 번째로 동일한 구조가 반복될 때 리팩토링을 수행하라는 경험적 규칙입니다 [17-20]. 이는 섣부른 추상화로 인한 잘못된 설계를 피하게 해줍니다 [18, 21, 22]. 기능 추가 전 구조를 개선하는 '준비적 리팩토링', 코드를 이해하기 위한 '이해를 위한 리팩토링', 쓰레기 줍기처럼 발견 즉시 수정하는 '기회주의적 리팩토링'이 일상적 개발에 통합되어야 합니다 [23, 24]. +* **작은 단계와 자가 테스트 (Small Steps & Self-Testing Code)** + 리팩토링은 오류를 쉽게 찾을 수 있도록 프로그램의 의미를 보존하는 아주 작고 점진적인 변환 단계(Micro-refactorings)로 나뉘어 수행되어야 합니다 [4, 25-27]. 이를 보장하기 위해 리팩토링 전에는 반드시 자동화된 테스트(자가 테스트 코드)가 구축되어 있어야 하며, 각 작은 단계 직후 테스트를 실행하여 외부 동작이 변경되지 않았음을 지속적으로 검증해야 합니다 [17, 28-31]. +* **코드 스멜 (Code Smells) 인식** + 리팩토링이 필요한 시점을 알려주는 구체적인 신호로, 긴 함수(Long Method), 중복 코드(Duplicate Code), 데이터 뭉치(Data Clumps), 기능 욕심(Feature Envy) 등이 있습니다 [32-34]. 이러한 스멜을 인지하고 해당 문제에 맞는 적절한 리팩토링 카탈로그 기법(예: 함수 추출하기, 메서드 이동 등)을 선별해 적용해야 합니다 [34-36]. + +## ⚖️ Trade-offs & Caveats + +* **레거시 코드의 딜레마 (Legacy Code Dilemma)**: 리팩토링을 안전하게 수행하려면 테스트가 필요하지만, 테스트가 없는 얽힌 레거시 코드에 테스트를 추가하려면 코드를 리팩토링하여 의존성을 분리해야 하는 모순이 발생합니다. 이 경우 '접점(Seam)'을 찾아 소스코드를 최소한으로 건드리면서 우회적으로 테스트를 덮어씌워야 하는 극도의 주의가 요구됩니다 [37-40]. +* **버그 유입의 위험성**: 리팩토링은 코드베이스의 구조를 재배치하므로, 단위 테스트라는 안전망이 부족하거나 한 번에 너무 큰 규모로 변경을 시도할 경우 기존 기능 파괴나 새로운 회귀 버그(Regression bug)를 유발할 수 있는 중대한 위험(Risky Change)이 따릅니다 [31, 41-43]. +* **단기적 속도 저하 및 오버헤드 인식**: 리팩토링을 진행하는 동안에는 일시적으로 기능 추가 속도가 저하될 수 있으며, 개발 시간을 소모하게 됩니다. 때문에 일정이 매우 촉박하거나 마감 시한이 코앞인 경우 리팩토링을 유보해야 할 수 있습니다 [9, 23, 44, 45]. +* **재작성(Rewrite)과의 경계**: 시스템이 심각하게 부패하여 결함이 넘치고 도저히 안정화할 수 없는 최악의 경우에는, 리팩토링을 시도하는 것보다 처음부터 새롭게 재작성(Rewrite from scratch)하는 것이 오히려 경제적일 수 있습니다 [46, 47]. +* **지나친 공학적 완벽주의의 함정**: 단순히 클린 코드를 위한 미학적 이유나, 존재하지도 않는 미래의 확장을 위해 과도하게 리팩토링(추측성 일반화 등)을 수행하면 시스템을 불필요하게 복잡하게 만들 수 있습니다 [48, 49]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A: 개발 및 검증 방법론 (Development & Testing Methodologies)] +- `[[Test-Driven Development (TDD)]]` + - 연결 이유: 리팩토링은 TDD의 핵심 사이클인 'Red-Green-Refactor'에서 가장 마지막에 코드를 정제하는 필수 단계로 작용합니다 [50, 51]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 테스트가 코드의 외부 행동을 어떻게 보장해주어, 개발자가 확신을 가지고 구조(Refactor)를 자유롭게 변경할 수 있는지에 대한 절차적 작동 원리. + +#### [관계 유형 B: 진단 및 지표 (Diagnostics & Metrics)] +- `[[Code Smell (코드 스멜)]]` + - 연결 이유: 코드가 구조적 부패 단계에 접어들었으며 리팩토링이 필요함을 알리는 가장 직관적이고 표면적인 증상들입니다 [34, 52]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 언제 리팩토링을 시작해야 하며, 수많은 카탈로그 중 어떤 특정 리팩토링 기법(예: Extract Method, Replace Type Code with Subclasses 등)을 처방해야 하는지 판단하는 기준. +- `[[Technical Debt (기술 부채)]]` + - 연결 이유: 마감 시한 등의 이유로 타협하여 작성된 더러운 코드가 쌓인 상태로, 리팩토링은 이 기술 부채를 이자와 원금을 상환하는 직접적인 행위입니다 [5, 16, 53]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링이 왜 단순한 코드 청소가 아니라 기업 차원에서의 유지보수 비용 관리이자 비즈니스 전략인지에 대한 당위성. + +#### [관계 유형 C: 레거시 환경 대응 (Legacy Environment Strategies)] +- `[[Legacy Code (레거시 코드)]]` + - 연결 이유: 마이클 페더스의 정의에 따르면 '테스트가 없는 코드'이며, 리팩토링의 혜택이 가장 절실하지만 적용하기 가장 까다로운 환경입니다 [40, 54]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 이상적인 환경이 아닌 실제 엉망인 시스템에서 어떻게 Sprout Method, Wrap Method 등을 사용하여 점진적으로 개선을 시작할 수 있는지의 현실적 대처. +- `[[Seams (접점)]]` + - 연결 이유: 프로그램의 해당 지점을 직접 편집하지 않고도 동작(의존성)을 변경하거나 가로챌 수 있는 곳으로, 레거시 코드에 테스트를 끼워 넣기 위한 핵심 돌파구입니다 [40, 55, 56]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 강하게 결합된 시스템 아키텍처 내부에서 가짜 객체(Mock/Fake)를 주입하고 단위 테스트를 안전하게 배치할 수 있는 구조적 틈새를 식별하는 방법. + +### Deeper Research Questions + +- "3의 법칙(Rule of Three)"을 적용하여 약간의 코드 중복을 의도적으로 허용하는 것과, 즉각적으로 추상화(DRY 원칙 준수)를 수행하는 것 사이의 최적의 경제적 균형점은 어떻게 판단할 수 있는가? +- 테스트 프레임워크가 전무한 강결합된 레거시 시스템에 리팩토링을 위한 "접점(Seams)"을 식별 및 생성할 때, 기존 운영 환경의 안전성을 보장하는 가장 효과적인 우회 기법은 무엇인가? +- 대규모 엔터프라이즈 환경에서 일정 압박을 받는 관리자와 비즈니스 이해관계자에게 리팩토링의 투자 대비 수익(ROI)과 경제적 정당성을 수치화하여 설득할 수 있는 지표(Metric)는 어떻게 구성해야 하는가? +- 객체지향 패러다임에서 권장되는 다형성을 위한 리팩토링(예: Replace Conditional with Polymorphism)이 시스템의 간접성(Indirection)을 증가시켜 오히려 코드를 추적하기 어렵게 만드는 부작용은 어떻게 제어해야 하는가? +- 최근 도입되고 있는 LLM 기반 생성형 AI를 활용한 자동 리팩토링 도구가 코드 스멜을 식별하고 수정안을 제시할 때, "AI 생산성 역설(AI Productivity Paradox)"로 인한 인간의 인지 검증 비용 증가를 어떻게 완화할 수 있는가? + +### Practical Application Contexts + +- **Implementation:** 코드를 작성 중 함수가 10~20줄 이상으로 길어지거나 변수명이 모호한 '코드 스멜'을 감지했을 때, IDE의 자동화 도구를 활용해 '함수 추출하기(Extract Method)'나 '변수 이름 바꾸기' 등을 아주 작은 단위로 즉시 수행합니다. +- **System Design:** 프로젝트 초기부터 변경을 완벽하게 대비하는 거대한 사전 설계(Big Design Up Front)에 집착하기보다, 우선 단순하게 동작하는 설계를 구현한 뒤 비즈니스 요구사항과 도메인 지식이 구체화됨에 따라 리팩토링을 통해 점진적으로 설계를 진화시킵니다. +- **Operation / Maintenance:** 유지보수 과정에서 버그를 수정하거나 새로운 요구사항을 덧붙이기 전, 해당 영역의 기존 코드를 먼저 수정하기 쉬운 구조로 다듬는 '준비적 리팩토링(Preparatory Refactoring)'을 작업 프로세스의 기본 1단계로 강제합니다. +- **Learning Path:** TDD의 'Red-Green-Refactor' 리듬을 학습하고, 마틴 파울러의 70여 가지 리팩토링 카탈로그의 핵심 절차를 연습한 뒤, 마이클 페더스의 레거시 코드 전략을 통해 테스트가 없는 코드의 의존성을 끊고 점진적으로 커버리지를 높이는 역량을 기릅니다. +- **My Project Relevance:** 현재 유지보수 중인 프로젝트 내에서 강하게 얽힌 수천 줄의 몬스터 메서드(Monster Method)가 발견될 경우, 전체를 뒤엎는 대신 Sprout Method 기법 등을 활용해 신규 로직을 완전히 독립된 함수로 분리하여 단위 테스트를 통과시킨 후 연결하는 방식으로 안전한 확장을 도모합니다. + +### Adjacent Topics + +- `[[Design Patterns (디자인 패턴)]]` + - 확장 방향: 리팩토링이라는 구조 변경 행위가 최종적으로 도달하고자 하는 목적지(Target)로서, 시스템의 유연성과 재사용성을 극대화하기 위해 검증된 객체지향적 설계 구조들을 학습합니다. +- `[[Agile Software Development (애자일 소프트웨어 개발)]]` + - 확장 방향: 리팩토링이 가장 활발히 요구되고 그 가치를 발휘하는 개발 환경인 애자일, eXtreme Programming(XP) 문화, 그리고 지속적 통합(CI/CD) 파이프라인과의 강력한 연관성을 탐구합니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/보이스카우트 규칙 (Boy Scout Rule).md b/10_Wiki/Topics/Architecture/보이스카우트 규칙 (Boy Scout Rule).md new file mode 100644 index 00000000..e1488a3b --- /dev/null +++ b/10_Wiki/Topics/Architecture/보이스카우트 규칙 (Boy Scout Rule).md @@ -0,0 +1,18 @@ +# [[보이스카우트 규칙 (Boy Scout Rule)]] + +## 📌 Brief Summary +보이스카우트 규칙(Boy Scout Rule)은 "캠프장을 처음 왔을 때보다 더 깨끗하게 치우고 떠나라"는 격언을 소프트웨어 개발에 적용한 원칙입니다 [1]. 개발자가 작업을 수행하는 코드 영역 근처에서 작은 결함을 발견하거나 개선의 여지가 있는 코드를 마주쳤을 때 즉시 수정하는 '쓰레기 줍기 리팩토링(Litter-Pickup Refactoring)'의 실천을 의미합니다 [1, 2]. 이러한 작은 개선 노력들은 기술 부채가 시스템에 급격히 축적되는 것을 막는 강력한 방어선 역할을 합니다 [2]. + +## 📖 Core Content +* **쓰레기 줍기 리팩토링(Litter-Pickup Refactoring)**: 보이스카우트 규칙은 일상적인 개발 워크플로우에 통합되어 나타납니다. 기존 코드를 바탕으로 작업하던 중 코드가 더 나은 방식으로 동작할 수 있음을 발견했을 때, 이를 즉각적으로 개선하는 방식으로 실천됩니다 [1, 2]. +* **기술 부채 방지와 비즈니스 가치 창출**: 개별적인 코드 정리 작업은 작아 보일 수 있지만, 이러한 노력이 모이면 전체적인 기술 부채의 축적을 방지하는 훌륭한 방어선이 됩니다 [2]. +* **팀 문화로서의 정착**: 보이스카우트 규칙에 따른 리팩토링은 특정 영웅적인 개발자 한 명의 개인적 활동에 머물러서는 안 됩니다 [3]. 이는 팀 전체가 공유하고 실천하는 문화로 자리 잡아야 하며, 궁극적으로 비즈니스 가치 창출을 위한 필수 과정으로 인정받아야 합니다 [3]. + +## ⚖️ Trade-offs & Caveats +보이스카우트 규칙 실천 시 발생할 수 있는 구체적인 부작용이나 제약 사항에 대해서는 소스에 관련 정보가 부족합니다. + +다만 일반적인 리팩토링 주의사항에 비추어 볼 때 다음을 유의해야 합니다. 단순히 코드가 '미학적으로 마음에 들지 않는다'는 이유만으로 규칙을 적용하여 코드를 수정해서는 안 되며, 그 행위가 향후의 변경 용이성 향상이나 버그 감소와 같은 실질적인 비즈니스 가치와 연결되어야 합니다 [4]. 또한, 자가 테스트 코드(Self-Testing Code)와 같은 안전망이 전혀 없는 방대한 레거시 시스템에서는 무작정 코드를 깔끔하게 정리하려 드는 것이 예상치 못한 버그를 유발할 수 있으므로 주의해야 합니다 [5, 6]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/추출 및 인라인 (Extract & Inline).md b/10_Wiki/Topics/Architecture/추출 및 인라인 (Extract & Inline).md new file mode 100644 index 00000000..b5a86393 --- /dev/null +++ b/10_Wiki/Topics/Architecture/추출 및 인라인 (Extract & Inline).md @@ -0,0 +1,22 @@ +# [[추출 및 인라인 (Extract & Inline)]] + +## 📌 Brief Summary +추출(Extract)과 인라인(Inline)은 소프트웨어의 동작을 유지하면서 내부 구조를 개선하는 리팩토링의 가장 기본적인 기법으로, 코드의 복잡성을 관리하고 가독성을 높이기 위해 사용됩니다. '추출'은 길고 복잡한 코드 블록이나 책임이 집중된 클래스를 더 작고 독립적인 단위로 분리하여 명확한 이름을 부여하는 과정입니다. 반대로 '인라인'은 불필요하거나 의미 없는 추상화 계층(메서드, 클래스, 변수 등)을 제거하고 그 내용을 호출자에게 직접 결합하여 개발자의 인지적 부하(Cognitive overhead)를 줄이는 기법입니다. + +## 📖 Core Content +* **추출 (Extract) 기법** + * **메서드 및 함수 추출 (Extract Method/Function):** 코드가 40줄을 초과하거나, 여러 곳에서 중복되는 로직이 있거나, 너무 복잡하여 단위 테스트가 어려운 경우에 주로 사용합니다 [1]. 뚜렷한 책임을 가지는 코드 섹션을 식별한 뒤, 비즈니스 로직을 명확히 설명할 수 있는 직관적인 이름을 가진 새로운 메서드로 분리합니다 [1-3]. 이를 통해 코드는 일련의 주석이나 문서처럼 자연스럽게 읽힐 수 있습니다 [1, 4]. + * **클래스 및 변수 추출 (Extract Class/Variable):** 클래스가 너무 많은 인스턴스 변수와 코드를 가지고 있어 '단일 책임 원칙'을 위반할 때 사용합니다 [5, 6]. 연관된 데이터와 메서드의 하위 집합을 묶어 새로운 클래스로 분리합니다 [5, 7]. 또한, 복잡하고 이해하기 어려운 조건식이나 알고리즘의 특정 단계를 명확히 설명하기 위해 임시 변수를 추출(Introduce Explaining Variable)할 수도 있습니다 [8, 9]. + +* **인라인 (Inline) 기법** + * **메서드 인라인 (Inline Method):** 자체적인 비즈니스 로직이나 추가적인 검증, 변환 없이 단순히 다른 메서드로 작업을 위임하기만 하는 래퍼(Wrapper) 함수를 제거할 때 사용합니다 [10]. 이러한 메서드는 코드 파악 시 불필요한 인지적 오버헤드만 추가하므로, 메서드의 본문 내용을 호출자 위치에 직접 복사하고 원본 메서드를 삭제하여 구조를 단순화합니다 [3, 10-12]. + * **클래스 및 변수 인라인 (Inline Class/Temp):** 수행하는 역할이 거의 남아있지 않은 '게으른 클래스(Lazy Class)'나 기능이 없는 빈 중간 계층을 제거하여 구조를 평탄화할 때 사용합니다 [13-15]. 또한, 한 번만 할당되고 복잡성이 없는 임시 변수가 다른 리팩토링(예: 메서드 추출)을 방해할 경우 해당 변수를 인라인 처리합니다 [16]. + +## ⚖️ Trade-offs & Caveats +* **과도한 추출과 조기 리팩토링의 부작용:** 코드를 무조건 잘게 쪼개는 것만이 항상 가독성과 유지보수성을 높이는 것은 아닙니다 [2, 17]. 단순히 읽기 좋다는 이유만으로 명확한 추상화 개념 없이 한 번만 쓰이는 헬퍼 함수를 무분별하게 추출해 내면, 오히려 코드를 읽을 때마다 여러 함수를 넘나들며 본문을 확인해야 하므로 방해가 될 수 있습니다 [18]. +* **잘못된 추상화의 위험성:** 명확한 이름이나 개념을 부여할 수 없다면 강제로 추상화하기보다는 차라리 약간의 중복(Duplication)을 허용하고 기다리는 것이 더 경제적일 수 있습니다 [19, 20]. 나쁜 추상화는 향후 새로운 유스케이스가 추가될 때 불필요한 매개변수나 조건문을 강제하게 만들어 유지보수를 훨씬 더 어렵게 만듭니다 [20]. +* **추출 시 로컬 변수 결합도 문제:** 원본 메서드 내의 여러 로컬 변수나 임시 변수에 의존하는 코드 블록을 추출할 경우, 파라미터 목록이 지나치게 길어져 추출된 메서드의 가독성이 원본보다 떨어지는 문제가 발생합니다 [2, 21]. 이러한 미묘한 결합을 피하려면 임시 변수를 질의 메서드(Query)로 대체하는 등의 사전 정리가 필요합니다 [2, 22]. +* **인라인의 구조적 제약:** 불필요한 간접 호출을 줄이는 인라인 기법은 유용하지만, 대상 코드가 재귀(Recursion), 다중 반환점(Multiple return points), 접근자(Accessors)가 없는 다른 객체로의 인라인 등 구조적 복잡성을 가진 경우에는 적용하기 매우 까다롭고 오류를 유발할 수 있으므로 피해야 합니다 [23]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/클린 코드 (Clean Code).md b/10_Wiki/Topics/Architecture/클린 코드 (Clean Code).md new file mode 100644 index 00000000..b56bf9c8 --- /dev/null +++ b/10_Wiki/Topics/Architecture/클린 코드 (Clean Code).md @@ -0,0 +1,30 @@ +# [[클린 코드 (Clean Code)]] + +## 📌 Brief Summary +클린 코드란 읽고 이해하기 쉬우며, 구조가 잘 잡혀 있고 효율적인 코드를 의미한다 [1]. 이는 유지보수성을 염두에 두고 작성되어 미래에 버그가 발생할 가능성을 크게 줄여준다 [1]. 리팩토링을 통해 클린 코드를 유지하면 기술 부채가 감소하고, 장기적으로 소프트웨어 개발 속도와 경제성을 높일 수 있다 [2, 3]. + +## 📖 Core Content +* **클린 코드의 특징 및 조건** + * 다른 프로그래머가 보았을 때 의도가 명확해야 하며, 코드의 중복이 없어야 한다 [4]. + * 클래스 수와 같은 움직이는 부품(moving parts)이 최소화되어야 하고, 모든 자동화 테스트를 통과해야 한다 [4]. + * 유지보수하고 업데이트하기 어려운 '더러운 코드(dirty code)'와 대조되며, 다른 사람이 쉽게 읽고 이해할 수 있도록 사람을 위해 작성되어야 한다 [2, 5]. + +* **클린 코드가 제공하는 이점** + * **인지 부하 및 버그 감소:** 개발자의 인지 부하를 줄여주어 오류가 스스로 드러나게 하며, 버그를 더 빠르게 발견할 수 있도록 돕는다 [3, 6]. + * **유지보수 및 확장성 향상:** 새로운 기능을 추가하거나 기존 기능을 수정할 때 필요한 분석 시간을 단축시켜 주며, 이해하기 쉬운 코드는 곧 유지보수하기 쉬운 코드가 된다 [3, 5]. + * **재사용성:** 코드가 깨끗하고 잘 작동하면, 해당 디자인 요소와 코드 모듈을 다른 곳에서도 재사용할 수 있는 기반이 된다 [7]. + * **경제적 가치:** 궁극적으로 장기적인 개발 속도를 가속화하고 비즈니스 가치를 더 빠르게 전달할 수 있는 토대를 제공한다 [3]. + +* **리팩토링과의 관계** + * 코드 리팩토링의 주된 목적 중 하나는 더러운 코드를 클린 코드로 바꾸어 기술 부채를 줄이는 것이다 [2]. + * 그러나 생산성 있는 프로그래머는 단순히 '미학적인 깨끗함'만을 위해 리팩토링하지 않으며, 코드의 동작 변경 속도를 빠르게 유지하기 위한 실용적이고 경제적인 목적으로 리팩토링을 수행한다 [8]. + +## ⚖️ Trade-offs & Caveats +* **테스트의 부재시 무의미함:** 코드가 아무리 깨끗하고 객체지향적이며 캡슐화가 잘 되어 있다 하더라도, 자동화된 테스트가 없다면 여전히 나쁜 코드(레거시 코드)에 불과하다 [9]. 테스트가 뒷받침되지 않은 상태에서 클린 코드를 만들기 위해 대규모 구조 변경을 시도하는 것은 안전망 없는 공중 곡예와 같아 매우 위험하다 [10]. +* **미학적 완벽주의의 함정:** 단순히 '클린 코드'라는 미학적 목표 자체를 위해 리팩토링에 시간을 낭비해서는 안 된다 [8]. 리팩토링은 예술적 행위가 아니라 투자 대비 수익(ROI)과 개발 속도 향상이라는 경제적 정당성을 확보하기 위한 수단이어야 한다 [8, 11]. +* **잘못된 추상화의 위험:** 단순히 코드 라인 수를 줄인 짧은 코드가 항상 가독성이 높은 것은 아니다 [5]. 무리하게 깨끗한 코드를 만들기 위해 모든 것을 한 번만 쓰이는 헬퍼 함수로 추출하거나 잘못된 추상화를 도입하면 오히려 코드를 이해하고 유지보수하기 어렵게 만든다. 차라리 중복을 남겨두는 것이 잘못된 추상화보다 유지보수 비용 측면에서 더 낫다 [12-14]. +* **현실적인 타협과 점진적 개선:** 레거시 코드를 다룰 때 단번에 완벽한 설계를 달성하려는 시도는 무리이다. 환자의 상태를 고쳐 즉시 올림픽 선수로 만들 수 없듯, '최고(best)'가 되려는 욕심이 '더 나은(better)' 상태로 가는 것을 방해해서는 안 되며, 점진적이고 타협 가능한 개선을 목표로 해야 한다 [15]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Architecture/테스트 주도 개발 (TDD).md b/10_Wiki/Topics/Architecture/테스트 주도 개발 (TDD).md new file mode 100644 index 00000000..2405939e --- /dev/null +++ b/10_Wiki/Topics/Architecture/테스트 주도 개발 (TDD).md @@ -0,0 +1,17 @@ +# [[테스트 주도 개발 (TDD)]] + +## 📌 Brief Summary +테스트 주도 개발(TDD)은 애자일 소프트웨어 개발에서 널리 사용되는 '테스트 우선(test-first)' 접근 방식이자 리팩토링의 토대가 되는 핵심 프랙티스이다 [1]. 이 방법론은 '실패하는 테스트 작성(Red)', '테스트를 통과하는 최소한의 코드 작성(Green)', '코드 구조 개선(Refactor)'이라는 세 가지 명확한 주기로 수행된다 [1-3]. TDD는 코드 변경 시 기존의 동작이 보존됨을 보장하며, 빠르고 안전한 리팩토링 루프를 형성하는 데 기여한다 [4, 5]. + +## 📖 Core Content +* **Red-Green-Refactor 워크플로우**: TDD 주기는 세 가지 명확한 단계로 구별된다. 첫째 'Red' 단계에서는 개발해야 할 내용을 확인하고 실패하는 테스트 코드를 작성한다. 둘째 'Green' 단계에서는 테스트를 통과할 수 있는 가장 단순한 코드를 작성한다. 셋째 'Refactor' 단계에서는 테스트가 통과(Green)하는 상태를 유지하면서 코드를 개선하고 향상시킨다 [1, 2]. 이 과정에서 새로운 기능 추가를 위한 코드 작성과 기존 코드를 리팩토링하는 작업은 절대로 동시에 수행되어서는 안 된다 [2]. +* **설계 도구로서의 단위 테스트**: 익스트림 프로그래밍(XP) 원칙에 뿌리를 둔 TDD를 통해 작성된 단위 테스트는 단순한 품질 보증 도구를 넘어 소프트웨어의 설계를 돕는 도구로 기능한다 [6]. 코드가 작성된 이후가 아니라 코드와 나란히, 혹은 그보다 먼저 테스트를 작성하게 되므로 피드백 루프가 극적으로 짧아진다 [3, 7]. +* **리팩토링 안전망 역할**: 성공적인 리팩토링 주기는 TDD 사이클과 매우 밀접하게 연관되어 있다 [5]. TDD는 아주 작은 변환을 적용한 후 즉시 모든 테스트를 다시 실행하여 성공 여부를 확인하게 하므로, 리팩토링이 시스템을 망가뜨리는 것을 방지하는 강력한 가드레일 역할을 수행한다 [5]. 만약 TDD와 지속적 통합(CI) 환경의 테스트를 실행하지 않고 리팩토링을 진행하면 새로운 버그를 유입시킬 위험이 매우 커진다 [8]. + +## ⚖️ Trade-offs & Caveats +* **사소한 코드 테스트로 인한 낭비 위험**: TDD나 단위 테스트에 회의적인 입장의 개발자들은 100%의 테스트 커버리지를 달성하기 위해 Getter나 Setter와 같은 사소한 코드까지 테스트를 작성하도록 강요받는 상황을 문제 삼는다. 조건 논리가 포함되지 않은 단순한 구현을 맹목적으로 테스트하는 것은 실질적인 이득 없이 시간만 낭비하는 결과를 초래할 수 있다 [9, 10]. +* **레거시 코드 적용의 딜레마**: TDD의 원칙을 테스트가 없고 구조가 얽혀 있는 기존 시스템(레거시 코드)에 적용하려고 할 때 심각한 제약이 발생한다. 코드를 안전하게 리팩토링하려면 테스트가 필요하지만, 테스트를 작성하기 위해서는 먼저 코드를 변경하여 의존성을 끊어야 하는 역설적인 '레거시 코드의 딜레마'에 직면하게 된다 [11]. +* **시간 제약 시의 우회적 접근**: 촉박한 마감 시간으로 인해 방대한 레거시 클래스에 테스트를 작성할 여유가 없다면, 정석적인 TDD의 적용이 어려울 수 있다. 이러한 제약 상황에서는 기존 코드를 직접 건드리는 대신 'Sprout'나 'Wrap'과 같은 우회 기법을 사용하여 새로운 로직만을 별도의 위치에 작성 및 격리하고 이를 단위 테스트하는 차선책을 고려해야 한다 [12-14]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/AI Productivity Paradox.md b/10_Wiki/Topics/Business_and_Management/AI Productivity Paradox.md new file mode 100644 index 00000000..b522e109 --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/AI Productivity Paradox.md @@ -0,0 +1,17 @@ +# [[AI Productivity Paradox]] + +## 📌 Brief Summary +**AI 생산성 역설(AI Productivity Paradox)**은 AI 코딩 도구가 개발 속도를 높여줄 것이라는 약속과 달리, 숙련된 개발자가 복잡한 작업을 수행할 때는 오히려 작업 속도를 지연시키는 현상을 의미합니다 [1], [2]. METR의 연구에 따르면, 숙련된 개발자들은 AI를 사용할 때 작업 속도가 19% 하락했음에도 불구하고 스스로는 20% 더 빠르다고 착각하는 **39%의 '인식 격차(Perception Gap)'**를 보였습니다 [1], [3]. AI 도구는 단순하고 반복적인 작업을 가속화하고 주니어 개발자에게는 유익하지만, 복잡한 아키텍처 환경에서는 오히려 병목 현상을 하류(Downstream)로 이동시키고 숙련자의 디버깅 및 리뷰 시간을 늘리는 역설적인 결과를 초래합니다 [4], [5]. + +## 📖 Core 소스 Content +* **인식과 실제의 무서운 괴리 (The Perception Tax):** 개발자들은 즉각적인 코드 출력으로 인한 도파민 분비와 인지 부하의 감소 덕분에 AI가 작업을 가속화한다고 느낍니다 [6]. 그러나 실제로는 프롬프트를 정교하게 작성하는 시간, AI의 응답을 기다리는 시간, 그리고 출력된 코드를 읽고 기존 아키텍처에 맞게 수정하는 데 드는 **숨겨진 시간 비용(Hidden Time Costs)**이 절약된 시간보다 큽니다 [7], [6]. 결과적으로 개발자는 더 서두르는 느낌을 받으면서도 마감 기한을 넘기거나 초과 업무를 안게 되는 '인식의 세금'을 지불하게 됩니다 [7], [8]. +* **개발자 경험 수준에 따른 정반대의 효과 (The Expertise Paradox):** AI 코딩 도구의 효율성은 개발자의 숙련도에 따라 극명하게 갈립니다. 지식이 부족한 주니어 개발자는 AI를 통해 최대 39%의 생산성 향상을 얻을 수 있습니다 [5], [9], [2]. 반면, 기존 코드베이스에 5년 이상 기여한 전문가들은 시스템의 아키텍처, 과거의 버그, 팀의 규칙 등 방대한 **암묵적 지식(Implicit Knowledge)**을 가지고 있습니다 [9], [10]. 이들은 높은 품질 기준을 충족시키기 위해 AI에게 복잡한 컨텍스트를 설명하고 부적절한 제안을 거절하거나 수정하는 데 오히려 자신이 직접 코드를 작성하는 것보다 더 많은 시간을 소비하게 됩니다 [10], [11]. +* **작업 복잡도에 따른 선별적 사용의 중요성:** AI는 보일러플레이트 코드 작성, 인라인 주석 처리, 단순 함수의 단위 테스트 생성 등 **단순하고 반복적인 작업에서는 50~80%의 속도 향상**을 가져옵니다 [4], [12]. 하지만 프로덕션 환경의 복잡한 디버깅, 친숙한 코드베이스 내의 아키텍처 결정, 보안에 민감한 코드 등에서는 AI가 오히려 방해가 되므로 사용을 생략(Skip)하는 것이 전략적으로 유리합니다 [12]. + +## ⚖️ Trade-offs & Caveats +* **병목 현상의 하류 이동 (Bottleneck Migration):** AI를 도입한다고 해서 소프트웨어 개발 파이프라인의 병목 현상이 사라지는 것이 아니라 이동할 뿐입니다 [5], [13]. 코드를 생성하는 속도는 20~55% 빨라질 수 있지만, 이로 인해 생성된 엄청난 양의 코드를 검토하는 **PR(Pull Request) 리뷰 시간이 91% 급증**하고 PR 크기가 154% 커지는 부작용이 발생합니다 [5], [14]. 따라서 AI 도구를 광범위하게 도입하기 전에 증가할 코드 검토 워크로드를 감당할 수 있는지 반드시 평가해야 합니다 [14]. +* **핵심 개발 기술의 퇴화 (Skills Atrophy):** AI에 지속적으로 크게 의존하면 개발자의 기본적인 소프트웨어 개발 능력이 저하될 위험이 있습니다 [14]. 언어 구문에 대한 기억력, 문제를 분해하는 능력, 수동으로 이슈를 추적하는 디버깅 직관과 같은 기술적 능력이 쇠퇴할 수 있습니다 [15]. 또한, 코드를 깊이 이해하기보다 대충 훑어보거나 제안을 무비판적으로 수용하게 되는 인지적 부작용이 발생할 수 있으므로, AI의 도움 없이 의도적으로 코딩을 연습하는 시간을 확보해야 합니다 [15], [16]. +* **초기 학습 곡선과 단기적 생산성 하락 (The J-Curve):** AI 도구를 올바르게 사용하기 위해서는 효과적인 프롬프트 작성과 도구 통합 방법을 익히는 데 약 2~4주의 적응 기간이 필요합니다 [17], [18]. 이 시기에는 기존의 작업 습관이 변하면서 좌절감을 느끼고 오히려 생산성이 떨어지는 'J-커브'의 계곡을 지나야 하며, 약 6개월 이상이 지나야 전략적 사용을 통한 진정한 생산성 향상을 달성할 수 있습니다 [17]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Agile Methodology (애자일 방법론).md b/10_Wiki/Topics/Business_and_Management/Agile Methodology (애자일 방법론).md new file mode 100644 index 00000000..4923a427 --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Agile Methodology (애자일 방법론).md @@ -0,0 +1,58 @@ +# [[Agile Methodology (애자일 방법론)]] + +## 📌 Brief Summary +애자일 방법론은 완벽한 사전 설계(Big Design Up Front)에 의존하는 대신 코드를 실제 작성하면서 얻은 도메인 지식을 바탕으로 설계를 점진적으로 유연하게 개선해 나가는 개발 철학입니다 [1]. 작은 주기로 소프트웨어를 평가하고 기능을 점진적으로 추가하는 애자일 환경에서는 기존 코드를 지속적으로 리팩토링해야만 기술 부채가 누적되는 것을 방지할 수 있습니다 [2]. 이를 위해 테스트 주도 개발(TDD)에 기반한 Red-Green 리팩토링과 자동화 테스트 시스템이 빠르고 안전한 구조 변경을 보장하는 핵심 기반으로 작용합니다 [3, 4]. + +## 📖 Core Content +* **점진적 설계 진화:** 애자일 방법론은 변화하는 시장 환경에 민첩하게 대응하기 위해 코드를 작성하면서 설계를 발전시킬 수 있는 유연성을 중시하며, 이는 리팩토링의 근본 철학과 깊은 궤를 같이합니다 [1]. 작은 점진적 단위로 작업하고 결과를 조정해 나가는 과정에서 깨끗하게 잘 구조화된 코드를 유지해야만 다음 반복 주기(Iteration)에 기존 코드를 무리하게 확장하는 일을 피할 수 있습니다 [2]. +* **Red-Green 리팩토링:** 애자일 소프트웨어 개발 프로세스에서 가장 널리 사용되는 리팩토링 기법입니다 [3]. 이 기법은 '테스트 우선(test-first)' 접근법을 따라 실패하는 테스트를 먼저 작성하고(Red), 개발을 통과시키기 위한 최소한의 코드를 작성한 뒤(Green), 마지막으로 테스트를 유지한 채 코드를 개선(Refactor)하는 세 단계의 뚜렷한 주기를 갖습니다 [3, 5]. +* **내재된 품질(Built-in Quality)과 자동화 테스트:** 대규모 애자일 프레임워크(SAFe)와 같은 환경에서 자동화 테스트는 TDD, 동료 코드 리뷰 등과 함께 협상할 수 없는 필수적인 '내재된 품질' 요소로 취급됩니다 [4, 6]. 이는 단순히 수동 테스트를 스크립트로 대체하는 것을 넘어, 수십 명의 개발자가 다루는 코드베이스에서 지속적 통합(CI)과 지속적 배포를 실현하게 해주는 기반이 됩니다 [6, 7]. +* **실증적 품질 향상 효과:** 산업 현장의 애자일 환경을 대상으로 한 사례 연구에 따르면, 이러한 지속적 리팩토링 실천은 코드의 품질 향상 및 재사용성 관련 지표를 실질적으로 강화하는 데 기여하는 것으로 나타났습니다 [8, 9]. + +## ⚖️ Trade-offs & Caveats +* **기술 부채의 누적 위험:** 애자일의 짧은 반복 주기 속에서 일정을 맞추기 위해 리팩토링 없이 지저분한 코드를 지속적으로 방치하면, 신규 기능을 추가할 때마다 유지보수 노력과 복잡도가 기하급수적으로 커지는 기술 부채(Technical Debt)를 짊어지게 됩니다 [2, 10]. +* **수동 테스트의 한계 병목:** 여러 애자일 팀이 협력하는 대규모 환경(예: SAFe의 Agile Release Train)에서 자동화 테스트 인프라 없이 수동 테스트에만 의존할 경우, 잦은 변경을 소화하지 못해 리팩토링 시도 자체가 매우 위험해지고 배포의 치명적인 병목 현상이 발생합니다 [6, 7]. +* **테스트 부재 시의 치명적 리스크:** 테스트가 없는 상태(레거시 코드)에서 대규모 코드 구조 변경을 감행하는 것은 '그물 없이 공중 곡예를 하는 것'과 같아 매우 큰 위험을 초래합니다 [11]. 테스트가 뒷받침되지 않으면 코드가 나아지고 있는지 알 수 없으며, 예기치 않은 버그나 통합 충돌로 인해 애자일 팀의 전체 개발 속도가 오히려 지연될 수 있습니다 [11]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A: 개발 및 설계 프랙티스] +- [[Red-Green Refactoring]] + - 연결 이유: 애자일 소프트웨어 개발에서 가장 대표적이고 기반이 되는 '테스트 기반' 리팩토링 워크플로우를 구성합니다 [3, 5]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 애자일 환경에서 새로운 기능 구현과 기존 코드의 개선 작업이 어떻게 분리되고 반복 주기에 통합되는지 구체적 실천 절차를 알 수 있습니다. +- [[Test-Driven Development (TDD)]] + - 연결 이유: 코드 작성 전 테스트를 먼저 작성하여 리팩토링 중 시스템의 동작이 변하지 않음을 보장해 주는 핵심 애자일 프랙티스입니다 [6, 12]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 자동화된 피드백 루프를 통해 개발자가 코드를 과감하게 재구성할 수 있게 하는 심리적, 기술적 '안전망'의 역할을 이해할 수 있습니다. + +#### [관계 유형 B: 품질 및 구조 관리 도구] +- [[Automated Testing]] + - 연결 이유: 다수의 애자일 팀이 동시다발적으로 코드를 변경하고 리팩토링할 때, 수동 테스트를 대체하여 지속적 배포(CD)를 가능하게 하는 필수 기술 인프라입니다 [4, 7]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 과정에서 회귀 버그(Regression Bug)를 조기에 차단하고 시스템이 기존 외부 동작을 유지하고 있는지 빠르고 객관적으로 검증하는 방법을 이해할 수 있습니다. +- [[Technical Debt]] + - 연결 이유: 기능 추가를 위해 임시방편으로 더러운 코드를 작성했을 때 발생하는 빚으로, 애자일 팀이 지속적으로 리팩토링을 수행해야 하는 이유이자 대상입니다 [2, 13]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 왜 리팩토링을 미루면 미래의 소프트웨어 개발 및 유지보수 비용이 기하급수적으로 증가하게 되는지에 대한 경제적 논리를 파악할 수 있습니다. + +### Deeper Research Questions +- 애자일 환경에서 지속적 통합(CI)/지속적 배포(CD) 파이프라인과 자동화된 리팩토링 작업은 어떻게 조화롭게 통합되어 운영될 수 있는가? +- 테스트 코드가 부재한 레거시 시스템을 유지보수하는 애자일 팀이 'Red-Green 리팩토링'을 도입하고자 할 때 직면하는 구조적 한계와, 접점(Seam)을 활용한 점진적 도입 전략은 무엇인가? +- 대규모 애자일 프레임워크(SAFe) 내에서 다수의 스크럼 팀이 동시에 코드를 리팩토링할 때 발생하는 코드 병합 충돌과 회귀 결함을 방지하기 위한 시스템 단위의 자동화 테스트 설계 원칙은 무엇인가? +- 애자일 방법론의 짧은 스프린트(Sprint) 주기 내에서 단기적인 비즈니스 기능 요구사항 처리와 장기적인 아키텍처 리팩토링(기술 부채 상환) 간의 자원 할당 비율을 어떻게 최적화할 것인가? +- 사전 설계를 최소화하는 애자일의 특성상 점진적 리팩토링만으로는 극복하기 어려운 아키텍처의 근본적인 한계(대규모 구조적 부패)는 어떤 지표를 통해 감지되며, 언제 재작성(Rewrite)으로 전환하는 것이 경제적인가? + +### Practical Application Contexts +- **Implementation:** 실제 기능 개발 사이클 내에서 'Red-Green-Refactor' 기법을 일상화하여, 새로운 기능을 추가할 때마다 코드를 점진적으로 정리하고 구조를 개선하는 데 적용합니다 [3, 5]. +- **System Design:** 소프트웨어 설계 단계에서 완벽한 사전 설계에 과도한 비용을 지출하지 않고, 시스템을 구현하면서 비즈니스 도메인 이해도가 상승할 때마다 리팩토링을 통해 아키텍처를 유연하게 진화시킵니다 [1]. +- **Operation / Maintenance:** 다수의 애자일 팀(Agile Release Train)이 얽힌 운영 환경에서는 안정적인 유지보수를 위해 지속적 통합 파이프라인과 연계된 자동화 테스트 및 리팩토링 과정을 '완료의 정의(Definition of Done)'에 명시하여 내재된 품질(Built-in Quality)로 보장합니다 [4, 6, 14]. +- **Learning Path:** 애자일 프로세스의 기본인 짧은 피드백 루프와 TDD 방식을 숙달한 뒤, 테스트가 어려운 레거시 코드베이스에서 안전하게 의존성을 분리하고 리팩토링을 도입하는 심화 과정으로 학습을 확장합니다 [11, 15]. +- **My Project Relevance:** 프로젝트 수행 시 당장의 기능 동작 여부만 확인하는 데 그치지 않고, 자동화 테스트 커버리지를 함께 확보하여 지속적인 리팩토링이 가능하도록 프로젝트 관리 프로세스 내에 훈련된 룰을 편입시킵니다 [14, 16]. + +### Adjacent Topics +- [[Scaled Agile Framework (SAFe)]] + - 확장 방향: 단일 애자일 팀의 범위를 넘어 다수의 팀이 동시에 일하는 엔터프라이즈 환경에서 리팩토링과 자동화 테스트 품질 관리를 어떻게 확장(Scaling)하고 통제할 수 있는지 조사합니다 [4, 6]. +- [[Test Automation Pyramid]] + - 확장 방향: 애자일 팀의 안전한 리팩토링을 지탱하기 위해 속도가 빠른 단위 테스트부터 통합 테스트, E2E 테스트까지 어떻게 올바른 구조와 비율로 테스트 전략을 수립해야 하는지 탐구합니다 [17, 18]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Agile Software Development (애자일 소프트웨어 개발).md b/10_Wiki/Topics/Business_and_Management/Agile Software Development (애자일 소프트웨어 개발).md new file mode 100644 index 00000000..13f48056 --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Agile Software Development (애자일 소프트웨어 개발).md @@ -0,0 +1,56 @@ +# [[Agile Software Development (애자일 소프트웨어 개발)]] + +## 📌 Brief Summary +애자일 소프트웨어 개발(Agile Software Development)은 작고 점진적인 단위로 작업을 수행하며 결과를 지속적으로 평가하고 조정하는 개발 방법론이다 [1]. 이 환경에서는 '빅 디자인 업 프론트(Big Design Up Front)'와 같이 초기에 완벽한 설계를 추구하는 대신, 현재 요구사항에 맞게 가장 단순하게 작동하는 코드를 작성한 후 지속적인 리팩토링을 통해 시스템의 설계를 진화시키는 방식을 취한다 [2, 3]. 특히 익스트림 프로그래밍(XP)과 같은 애자일 프랙티스는 테스트 주도 개발(TDD)과 결합된 '무자비한 리팩토링(Refactor Mercilessly)'을 통해 기술 부채를 통제하고 시스템의 장기적인 유지보수성과 개발 속도를 확보할 것을 강조한다 [3-5]. + +## 📖 Core 소스 Content +* **점진적 진화와 리팩토링의 필수성**: 애자일 개발 과정에서는 이터레이션(Iteration)마다 코드가 지속적으로 추가되고 수정된다 [1]. 이 과정에서 코드를 깔끔하게 정리하지 않으면 코드 부패(Code Rot)가 발생하여 다음 단계의 개발이 기하급수적으로 어려워진다 [1, 6]. 따라서 애자일에서 리팩토링은 단순한 유지보수 작업이 아니라, 변화하는 요구사항에 맞춰 소프트웨어 아키텍처를 유연하게 진화시키기 위한 전략적이고 경제적인 핵심 도구다 [7, 8]. +* **Red-Green Refactoring 사이클**: 애자일 소프트웨어 개발 프로세스에서 가장 대중적으로 사용되는 리팩토링 기법이다 [9]. 이 기법은 테스트 주도 개발(TDD)의 흐름을 따르며, 실패하는 테스트 작성(Red), 테스트를 통과하기 위한 가장 단순한 코드 작성(Green), 그리고 동작을 유지한 채 코드를 효율적이고 깔끔하게 개선(Refactor)하는 세 단계로 엄격히 수행된다 [10-12]. +* **초기 설계의 최소화 (Evolutionary Design)**: 애자일은 미래의 불확실한 변경을 대비해 복잡한 유연성을 미리 설계해 두는 '추측성 일반화(Speculative Generality)'를 경계한다 [13, 14]. 대신, 당장의 요구에 맞는 가장 단순한 해결책을 구현하고 추후 새로운 요구사항이 생겼을 때 '준비적 리팩토링(Preparatory Refactoring)'을 통해 코드를 유연한 구조로 개선하는 경제적 접근 방식을 택한다 [15, 16]. +* **자동화된 테스트 기반의 안전망**: 애자일 팀이 다수의 모듈을 독립적으로 배포하고 지속적 통합/배포(CI/CD)를 빈번하게 수행하기 위해서는 자동화된 테스트가 필수적이다 [17, 18]. 테스트가 없는 상태에서의 리팩토링은 "그물 없는 공중 곡예"와 같이 극도로 위험하므로, 애자일 환경에서는 단위 테스트(Unit Tests)를 포함한 자동화 테스트를 먼저 확보하여 기존 기능이 훼손되지 않음을 즉각적으로 검증해야 한다 [19-21]. + +## ⚖️ Trade-offs & Caveats +애자일 환경에서 리팩토링을 수행할 때 직면하는 가장 큰 제약 사항은 **'단기적인 일정 압박'**과 **'테스트 부재로 인한 위험성'**이다. 애자일은 짧은 스프린트(Sprint) 단위의 기능 배포를 요구하므로, 마감일이 임박한 상황에서는 단기적인 생산성 저하를 우려해 리팩토링을 유보하게 될 수 있으며 이는 필연적으로 기술 부채(Technical Debt)의 급증으로 이어진다 [22, 23]. + +또한, 테스트 코드가 없는 방대한 레거시 시스템(Legacy System)을 애자일 방식으로 전환하며 수정할 경우, 기존의 기능이나 동작을 훼손할 회귀(Regression) 위험이 매우 크다 [19, 24]. 반대로, 리팩토링에 과도하게 몰입하거나 오버엔지니어링을 할 경우 잘못된 추상화를 낳아 코드를 오히려 변경하기 어렵게 만들고 애자일의 근본인 '단순성' 원칙을 위배하는 부작용(Wrong Abstraction)을 초래할 수 있다 [14, 25]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [방법론 및 철학적 기반] +* [[Extreme Programming (XP)]] + * 연결 이유: 리팩토링의 중요성을 최초로 인식하고 '무자비한 리팩토링'을 핵심 프랙티스로 정립한 대표적인 애자일 방법론이다 [4, 26]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: 초기 완벽주의 설계(BDUF)를 지양하고 지속적인 리팩토링을 통해 아키텍처를 점진적으로 진화시키는 애자일 설계 철학의 근원을 이해할 수 있다 [2]. +* [[Test-Driven Development (TDD)]] + * 연결 이유: 애자일에서 널리 쓰이는 'Red-Green-Refactor' 리팩토링 기법의 근간이 되는 개발 방법론이다 [9, 10]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: 리팩토링 시 외부 동작이 변경되지 않았음을 즉각적이고 지속적으로 검증하는 '테스트 안전망'이 어떻게 구축되는지 파악할 수 있다 [12, 19]. + +#### [아키텍처 및 품질 지표] +* [[Technical Debt (기술 부채)]] + * 연결 이유: 애자일의 빠른 배포를 위해 임시방편으로 작성된 코드가 빚처럼 쌓여 개발 속도를 늦추는 현상으로, 리팩토링의 주된 상환 대상이다 [1, 27]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: 비즈니스 이해관계자에게 왜 새로운 기능 개발을 멈추고 리팩토링 시간을 할애해야 하는지에 대한 경제적 타당성(ROI)을 제공한다 [8]. +* [[Code Smells (코드 냄새)]] + * 연결 이유: 애자일 팀이 리팩토링이 필요한 시점과 대상을 직관적으로 식별할 수 있게 해주는 구조적 결함의 징후다 [28, 29]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: 긴 함수, 중복 코드, 기능 욕심(Feature Envy) 등 구체적인 리팩토링 기법을 언제 적용해야 할지 결정하는 실무적 판단 기준을 배울 수 있다 [30, 31]. + +### Deeper Research Questions +* 애자일 환경에서 단기적인 기능 배포 일정과 장기적인 기술 부채 상환을 위한 리팩토링 시간을 어떻게 균형 있게 할당(예: 이터레이션의 15~20%)하고 관리자를 설득할 수 있는가? [23, 32] +* TDD의 Red-Green-Refactor 사이클에서 'Green' 단계에 작성된 조잡한 코드를 'Refactor' 단계에서 개선할 때, 불필요한 '추측성 일반화(Speculative Generality)'를 피하기 위한 판단 기준은 무엇인가? [13, 14, 25] +* 자동화된 테스트가 전무한 거대한 레거시 시스템을 애자일 조직이 인수했을 때, 시스템의 의존성을 끊어내고(접점, Seam 활용) 최초의 보호용 테스트를 안전하게 배치하는 구체적인 전략은 무엇인가? [24, 33, 34] +* 다수의 애자일 팀이 참여하는 스케일드 애자일 프레임워크(SAFe) 환경에서, 시스템 팀이 관리하는 테스트 자동화 피라미드(Test Automation Pyramid)의 구조는 팀 단위 리팩토링의 안정성에 어떤 영향을 미치는가? [35-37] +* '빅 디자인 업 프론트(Big Design Up Front)'를 배제하는 애자일의 진화적 설계 방식이, 데이터베이스 스키마 마이그레이션이나 보안 인프라 같이 변경 비용과 위험이 극도로 높은 영역에서 가지는 한계점과 보완책은 무엇인가? [2, 38, 39] + +### Practical Application Contexts +* **Implementation:** 신규 기능을 구현할 때, TDD 방식을 통해 실패하는 테스트를 먼저 작성(Red)하고 가장 단순한 코드로 통과(Green)시킨 후, 코드 냄새(Code Smell)를 식별하여 중복을 제거하고 디자인을 개선하는 리팩토링을 일상화한다 [10, 12]. +* **System Design:** 일어나지 않을지도 모르는 미래의 요구사항을 위해 복잡하고 유연한 아키텍처를 미리 설계하지 않고, 현재 요구에 맞는 단순한 시스템을 구축한 뒤, 향후 변화가 필요할 때 리팩토링을 통해 유연한 구조로 전환한다 [13, 14]. +* **Operation / Maintenance:** CI/CD(지속적 통합/지속적 배포) 파이프라인에 단위 테스트와 통합 테스트를 빈틈없이 연동하여, 애자일 팀원이 리팩토링을 수행할 때마다 기존 기능이 망가지지 않았는지 수 분 내에 자동으로 피드백을 받도록 인프라를 운영한다 [40, 41]. +* **Learning Path:** 코드 냄새를 파악하는 방법을 배우고, 70여 가지의 마이크로 리팩토링 카탈로그(예: 함수 추출, 필드 캡슐화 등)를 숙지한 뒤, 궁극적으로 의존성을 제어하기 위한 접점(Seam) 모델을 학습하여 레거시 코드에 대처하는 능력을 기른다 [30, 33, 34, 42]. +* **My Project Relevance:** 프로젝트의 스프린트 계획(Sprint Planning) 시, 백로그에 기능 개발뿐 아니라 기술 부채 해결을 위한 시간을 명시적으로 배정(예: 전체 용량의 15%~20%)하고, 새로운 기능 추가 전 '준비적 리팩토링(Preparatory Refactoring)'을 우선 수행하여 후속 개발 속도를 향상시킨다 [16, 32, 43]. + +### Adjacent Topics +* [[Test Automation Pyramid]] + * 확장 방향: 애자일 팀이 안전하게 지속적인 리팩토링을 수행하기 위해 필수적인 단위(Unit), 통합(Integration), E2E 테스트의 올바른 비율과 자동화 전략을 이해하는 방향으로 확장 [36, 44]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Bottleneck Migration.md b/10_Wiki/Topics/Business_and_Management/Bottleneck Migration.md new file mode 100644 index 00000000..69510d11 --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Bottleneck Migration.md @@ -0,0 +1,15 @@ +# [[Bottleneck Migration]] + +## 📌 Brief Summary +Bottleneck Migration(병목 이동)은 AI 코딩 도구 등 새로운 기술을 도입할 때 개발 프로세스의 병목 현상이 사라지지 않고 파이프라인의 하위 단계로 이동하는 현상을 뜻합니다 [1, 2]. AI의 도움으로 코드 생성 속도는 크게 빨라지지만, 그로 인해 코드 리뷰, 테스트, 통합 단계에 부하가 몰려 느려지게 됩니다 [2]. 결과적으로 개발 과정의 문제점이 해결되는 것이 아니라 새로운 병목 지점으로 옮겨가게 됩니다 [3]. + +## 📖 Core Content +* **작업 시간 분배의 변화**: 전통적인 소프트웨어 개발 흐름에서는 코딩 단계가 전체 시간의 약 50%를 차지했습니다 [2]. 반면 AI의 지원을 받는 개발 흐름에서는 코딩에 소요되는 시간 비중이 20%로 급격히 감소합니다 [2]. +* **새로운 병목의 발생(코드 리뷰)**: 코딩 단계가 20~55%까지 빨라짐에 따라 병목 현상은 하류(downstream)로 이동합니다 [1]. 설계(10% → 15%), 테스트(15% → 20%), 그리고 특히 코드 리뷰(20% → 40%)에 소요되는 시간 비중이 늘어나면서 코드 리뷰가 개발 파이프라인의 새로운 병목으로 자리 잡습니다 [2, 3]. +* **업무량 및 리뷰 시간의 급증**: 실제 엔터프라이즈 데이터(Faros AI)에 따르면 AI 도입 후 완료된 작업은 21%, 병합된 Pull Request(PR)는 98% 증가합니다 [3]. 하지만 생성된 평균 PR의 크기가 154%나 커지면서, PR 리뷰 시간 역시 91% 급증하게 됩니다 [1, 3]. + +## ⚖️ Trade-offs & Caveats +AI를 활용한 코딩 속도 향상이라는 최적화 선택은, 필수적으로 코드 리뷰 및 통합이라는 후속 단계의 부하를 가중시키는 부작용(Trade-off)을 수반합니다 [1, 2]. 만약 개발 팀 내에서 이미 코드 리뷰 프로세스가 병목을 겪고 있다면, AI 코딩 도구의 도입은 이 문제를 더욱 악화시킬 수 있다는 뚜렷한 제약 사항이 있습니다 [3]. 따라서 AI 도구를 광범위하게 도입하기 전에는 팀의 리뷰 수용 능력을 반드시 평가해야 하며, AI 도입에 발맞춰 코드 리뷰 리소스를 늘리거나 최적화할 계획을 병행해야만 실질적인 생산성 향상을 거둘 수 있습니다 [3]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Built-in Quality.md b/10_Wiki/Topics/Business_and_Management/Built-in Quality.md new file mode 100644 index 00000000..79d3d9c7 --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Built-in Quality.md @@ -0,0 +1,15 @@ +# [[Built-in Quality]] + +## 📌 Brief Summary +Built-in Quality는 SAFe(Scaled Agile Framework) 환경에서 여러 팀이 지속적으로 가치를 전달하는 데 필요한 속도와 신뢰성을 확보하게 해주는 핵심적이고 기초적인 관행이다 [1]. 이는 수동 테스트 실행을 단순히 스크립트로 대체하는 것을 넘어서는 개념으로, 자동화된 테스트, 리팩토링, 체계적인 동료 검토(Peer Review) 등을 포괄한다 [1, 2]. 이 관행은 조직이 자동화를 확장하기 전에 필수적으로 갖추어야 할 코드 품질의 전제 조건으로 작용한다 [2]. + +## 📖 Core Content +* **핵심 관행으로서의 Built-in Quality**: Built-in Quality는 리팩토링, 자동화된 테스트, 그리고 체계적인 동료 코드 리뷰(Peer Review)를 포함하는 조직적 필수 요건이다 [2]. 특히 SAFe 환경에서의 자동화 테스트는 근본적인 Built-in Quality 관행으로 간주된다 [1]. +* **완료의 정의(Definition of Done, DoD)를 통한 공식화**: 자동화된 테스트 요구사항을 DoD에 포함시킴으로써 품질에 대한 약속을 공식화할 수 있다 [3]. '모든 관련 자동화 테스트가 통과하고 새로운 테스트가 새 기능을 커버한다'는 것을 타협 불가능한 DoD 기준으로 삼으면, 배포 압박 속에서도 팀이 품질을 타협하는 것을 방지할 수 있다 [3]. +* **표준화된 체크리스트 활용**: Built-in Quality 체크리스트나 템플릿을 사용하면, 애자일 릴리스 트레인(ART) 내 모든 팀에서 테스트에 대한 '완료(done)'의 의미를 일관되게 표준화하는 데 도움을 받을 수 있다 [3]. + +## ⚖️ Trade-offs & Caveats +Built-in Quality 관행을 통해 탄탄한 코드 품질 기반을 다지지 않은 채 자동화를 성급하게 확장(scaling)하려 하면 심각한 부작용이 발생할 수 있다 [2]. 품질 기반을 건너뛴 팀들은 자신들이 구축한 테스트 스위트가 보호해야 할 대상인 프로덕션 코드만큼이나 깨지기 쉽고(brittle) 유지보수하기 어려운 상태로 전락하는 것을 자주 겪게 된다 [2]. 결과적으로, 견고한 품질 기초 위에 구축되지 않고 취약한 코드베이스 위에 자동화가 덧붙여진다면 흐름 효율성(Flow Efficiency)과 배포 성공률(Deployment Success Rates)이 모두 악화되는 결과를 초래하게 된다 [2]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Exploratory Refactoring (탐색적 리팩토링).md b/10_Wiki/Topics/Business_and_Management/Exploratory Refactoring (탐색적 리팩토링).md new file mode 100644 index 00000000..448bfe6e --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Exploratory Refactoring (탐색적 리팩토링).md @@ -0,0 +1,21 @@ +# [[Exploratory Refactoring (탐색적 리팩토링)]] + +## 📌 Brief Summary +탐색적 리팩토링(Exploratory Refactoring)은 복잡하거나 문서화되지 않은 레거시 코드를 수동적으로 읽기만 하는 대신, 적극적으로 코드를 변경하며 그 동작 방식을 파악하는 기법이다 [1]. 이 과정의 주된 목적은 코드를 영구적으로 정리하는 것이 아니라 시스템에 대한 친숙함과 통찰을 얻는 데 있다 [2]. 종종 '스크래치 리팩토링(Scratch Refactoring)'이라고도 불리며, 코드를 완전히 이해한 후에는 변경 사항을 되돌리고 적절한 테스트와 함께 실제 변경 작업을 다시 시작해야 하는 것이 특징이다 [2]. + +## 📖 Core Content + +* **코드 이해를 위한 능동적 상호작용:** 코드가 어떻게 작동하는지 파악하기 위해 사용하는 방법으로, 코드를 수동적으로 읽기만 하는 것보다 능동적으로 코드를 수정하고 상호작용하는 것이 이해도를 높이는 데 훨씬 효과적이다 [1]. +* **스크래치 리팩토링(Scratch Refactoring) 기법의 활용:** 마이클 페더스(Michael Feathers)가 제안한 스크래치 리팩토링과 궤를 같이하며, 테스트가 없고 불투명한 레거시 코드를 다룰 때 유용하다 [2]. 함수 추출, 코드 단순화, 변수 이름 변경 등 코드를 파악하기 위한 목적의 변경을 자유롭게 시도해볼 수 있다 [2]. +* **이해를 위한 리팩토링(Comprehension Refactoring)과의 연관성:** 코드를 분석하면서 구조가 복잡하거나 변수명이 모호한 부분을 발견했을 때 이를 개선하여, 개발자 머릿속에 생긴 이해를 코드 자체에 반영(박제)하는 과정과 일맥상통한다 [3, 4]. 랄프 존슨(Ralph Johnson)은 이를 "창문의 먼지를 닦아내어 그 너머를 볼 수 있게 하는 것"에 비유했으며, 이를 통해 코드를 읽기만 해서는 놓칠 수 있었던 높은 수준의 이해에 도달할 수 있다 [3]. +* **적용 시점:** 레거시 코드베이스의 작동 원리를 파악하지 못해 막혀 있을 때나, 짧은 시간을 투자해 코드베이스의 지뢰를 제거하듯 탐색적 접근을 시도할 때 큰 가치를 발휘한다 [2, 5]. + +## ⚖️ Trade-offs & Caveats + +* **변경 사항의 폐기(Revert) 필수:** 탐색적 리팩토링(스크래치 리팩토링)은 주로 테스트가 없는 레거시 환경에서 코드를 파악하기 위해 수행되므로, 안전하지 않은 변경(Unsafe changes)이 포함될 수 있다 [2]. 따라서 파악이 끝난 후에는 반드시 모든 변경 사항을 되돌려야(Revert) 한다는 단 하나의 철칙이 존재한다 [2]. +* **운영 코드 적용 불가:** 탐색적 목적으로 얻은 결과물 자체를 운영 코드에 그대로 유지해서는 안 된다 [1]. 자동화된 테스트라는 안전망이 없는 상태에서 리팩토링한 코드를 그대로 반영하려고 시도할 경우, 기존 동작을 훼손하거나 예기치 않은 버그를 도입할 위험이 매우 높기 때문이다 [2, 6]. +* **생산성 저하의 착각:** 변경한 코드를 최종적으로 버려야 하므로 일견 시간 낭비처럼 보일 수 있으나, 이 과정을 통해 의존성과 코드의 구조를 완벽히 파악할 수 있다 [2]. 오히려 이후에 테스트를 작성하고 정식으로 안전하게 리팩토링하는 본 작업을 수행할 때 리스크를 극적으로 줄여준다 [2, 6]. + + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Extreme Programming (XP).md b/10_Wiki/Topics/Business_and_Management/Extreme Programming (XP).md new file mode 100644 index 00000000..cf516f5d --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Extreme Programming (XP).md @@ -0,0 +1,60 @@ +# [[Extreme Programming (XP)]] + +## 📌 Brief 단기 Summary +Extreme Programming(XP)은 켄트 벡(Kent Beck)이 창시한 애자일 소프트웨어 개발 방법론으로, 집중적인 팀 협업과 코드 품질 제어를 강조합니다 [1, 2]. 이 방법론은 리팩토링이 개발 비용을 절감한다고 주장하며, 프로젝트 수명 주기 전반에 걸쳐 "무자비하게 리팩토링(refactor mercilessly)"할 것을 규칙으로 옹호합니다 [3, 4]. 또한, 테스트 주도 개발(TDD)과 결합하여 코드를 작고 유지보수하기 쉬운 단위로 지속적으로 분할하고 개선하는 과정을 소프트웨어 개발 주기의 필수적인 부분으로 여깁니다 [5-7]. + +## 📖 Core Content +* **지속적이고 무자비한 리팩토링 (Continuous & Merciless Refactoring):** XP는 프로젝트 전체 수명 주기 동안 코드를 끊임없이 구조화하고 개선하는 것을 핵심 실천법으로 삼습니다 [3, 4]. 리팩토링은 단순히 코드를 정리하는 것을 넘어 개발 비용을 절감하는 경제적 행위로 간주되며, '함수 추출하기(Extract Method)'와 같은 기법을 통해 코드를 더 이해하기 쉽고 유지보수하기 쉬운 작은 단위로 분할합니다 [7]. +* **테스트 주도 개발과의 결합 (Integration with TDD):** XP 원칙에 뿌리를 둔 테스트 주도 개발(TDD)과 함께 실천될 때, 단위 테스트(Unit Test)는 단순한 품질 보증 도구를 넘어 코드 설계를 위한 도구가 됩니다 [5]. XP를 비롯한 애자일 방법론을 지지하는 사람들은 빠른 단위 테스트를 바탕으로 아주 작은 프로그램 변환을 반복적으로 수행하는 리팩토링 과정을 소프트웨어 개발 주기의 필수 불가결한 부분으로 설명합니다 [6]. +* **사전 설계와 리팩토링의 균형 (Upfront Design and Refactoring):** 극단적인 프로그래머(Extreme Programmers)들은 흔히 코드를 먼저 작성한 뒤 리팩토링을 통해 형태를 잡아가는 방식만을 옹호하는 것으로 묘사되기도 합니다 [8]. 그러나 실제로 오직 리팩토링에만 의존하는 것은 가장 효율적인 작업 방식이 아니며, XP 프로그래머들 역시 코딩과 리팩토링을 시작하기 전에 CRC 카드 등을 활용하여 타당성 있는 초기 솔루션을 도출하는 수준의 사전 설계(Upfront Design)를 수행합니다 [8]. +* **팀 협업과 제어 (Team Collaboration and Control):** XP 실천법은 팀이 자신의 작업에 대한 통제력을 확보하고, 강력하게 협업하며, 작동하는 소프트웨어를 지속적으로 제공할 수 있도록 돕는 역할을 합니다 [2]. + +## ⚖️ Trade-offs & Caveats +* **느린 테스트 속도로 인한 병목 현상:** XP 환경에서의 리팩토링은 아주 작은 코드를 변경하고 즉시 정확성을 검증하는 매우 반복적인(iterative) 사이클을 따릅니다 [6]. 따라서 단위 테스트가 매우 빠르게 실행되지 않으면, 프로그래머가 테스트 완료를 기다리는 데 너무 많은 시간을 허비하게 되어 이 반복적인 프로세스 자체가 실용성을 잃게 되는 제약이 있습니다 [6]. +* **사전 설계 생략의 비효율성:** 리팩토링만으로 완벽한 설계를 도출할 수 있다는 오해가 있으나, 아무런 설계 없이 코딩 후 리팩토링만 진행하는 것은 가장 효율적인 작업 방식이 아닙니다 [8]. XP에서도 코드를 작성하기 전 CRC 카드와 같은 도구를 사용해 초기 설계를 수행해야 하는 타협점이 존재합니다 [8]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [관계 유형 A (개발 방법론 및 실천법)] +- [[Test-Driven Development (TDD)]] + - 연결 이유: XP는 TDD 원칙과 깊이 연관되어 있으며, 리팩토링 시 코드가 안전하게 변경되었음을 즉각적으로 보장하는 단위 테스트가 필수적입니다 [5, 6]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 자동화된 테스트가 어떻게 리팩토링의 안전망(Safety net)이자 설계 도구로 기능하는지 파악할 수 있습니다. +- [[Agile Software Development]] + - 연결 이유: XP는 애자일 소프트웨어 개발의 대표적인 방법론 중 하나로, 지속적 통합 및 배포, 반복적 리팩토링을 통한 민첩성 확보를 공유합니다 [2, 6]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 변화하는 요구사항 속에서 지속적인 리팩토링이 어떻게 애자일의 핵심 가치를 실현하는지 이해할 수 있습니다. + +#### [관계 유형 B (설계 및 구현 도구)] +- [[Extract Method]] + - 연결 이유: XP에서 복잡한 구조를 작고 유지보수하기 쉬운 단위로 쪼갤 때 핵심적으로 사용되는 리팩토링 기법입니다 [7]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 코드를 어떻게 더 작고 응집도 높은 조각으로 나누어 설계적 개선을 이루는지 그 절차적 메커니즘을 배울 수 있습니다. +- [[CRC Cards]] + - 연결 이유: XP에서 본격적인 코딩 및 리팩토링 전, 객체의 책임과 협력을 가볍게 설계하여 초기 아키텍처 방향성을 잡는 데 사용되는 도구입니다 [8]. + - 이 개념을 통해 더 깊게 이해할 수 있는 부분: 사전 설계와 점진적 리팩토링 사이의 균형을 맞추는 방법을 이해할 수 있습니다. + +### Deeper Research Questions + +- Extreme Programming에서 강조하는 '무자비한 리팩토링(refactor mercilessly)' 원칙을 대규모의 노후화된 레거시 시스템(Legacy System)에 적용할 때 초기 테스트 코드를 확보하는 절차는 어떻게 구성되어야 하는가? +- XP 프로세스의 리팩토링 반복 주기에서 단위 테스트의 실행 속도가 임계치를 넘어 느려질 경우, 이를 해결하기 위한 테스트 격리 및 아키텍처 개선 기법은 무엇인가? +- CRC 카드를 통한 가벼운 사전 설계(Upfront Design)가 이후 진행되는 반복적 리팩토링의 방향성과 어떻게 동기화되며, 설계적 결함을 조기에 방지하는 데 어떤 역할을 하는가? +- '함수 추출하기(Extract Method)'와 같은 미시적(micro) 리팩토링 기법들이 XP 팀의 공동 코드 소유권(Collective Code Ownership) 및 코드 리뷰 문화에 미치는 긍정적 시너지 효과는 무엇인가? +- 비즈니스 요구사항이 급변하는 환경에서 TDD와 XP의 리팩토링 사이클이 소프트웨어 아키텍처의 장기적 유연성(Extensibility)을 어떻게 보장하는가? + +### Practical Application Contexts + +- **Implementation:** 코드를 구현할 때, 복잡한 로직을 작성한 직후 '함수 추출하기(Extract Method)' 기법을 적용하여 코드의 의도를 명확히 하고 크기를 작게 유지합니다 [7]. +- **System Design:** 초기부터 완벽한 구조를 설계하려 하기보다는, CRC 카드를 활용해 타당성 있는 첫 번째 솔루션을 구상한 뒤, TDD와 지속적인 리팩토링을 통해 점진적으로 시스템 설계를 완성해 나갑니다 [8]. +- **Operation / Maintenance:** 기능 추가나 버그 수정이 필요할 때, 먼저 리팩토링을 통해 기존 코드가 변경을 수용하기 쉬운 구조가 되도록 정리함으로써 유지보수 비용을 절감합니다 [3, 4]. +- **Learning Path:** 리팩토링 원칙을 이해한 후, TDD를 학습하여 안전한 변경을 보장하는 테스트 작성법을 익히고, 최종적으로 XP의 집중적인 팀 협업 및 무자비한 리팩토링 사이클을 실무에 적용하는 순서로 학습합니다 [2, 5]. +- **My Project Relevance:** 현재 진행 중인 프로젝트에서 기능 구현 속도 저하를 겪고 있다면, XP의 지속적 리팩토링과 TDD 방식을 도입하여 코드의 가독성을 높이고 기술 부채로 인한 병목 현상을 해소할 수 있습니다. + +### Adjacent Topics + +- [[Technical Debt]] + - 확장 방향: 리팩토링을 지속하지 않아 코드가 부패할 때 발생하는 비용적 측면을 학습하고, XP가 이를 어떻게 상환하는지 조사하여 경제적 정당성에 대한 이해를 확장할 수 있습니다. +- [[Code Smells]] + - 확장 방향: 무자비한 리팩토링의 대상이 되는 코드의 구조적 결함 징후들을 파악하여, 언제 리팩토링을 수행해야 하는지에 대한 직관을 기를 수 있습니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/Business_and_Management/Scaled Agile Framework (SAFe).md b/10_Wiki/Topics/Business_and_Management/Scaled Agile Framework (SAFe).md new file mode 100644 index 00000000..47db2d02 --- /dev/null +++ b/10_Wiki/Topics/Business_and_Management/Scaled Agile Framework (SAFe).md @@ -0,0 +1,27 @@ +# [[Scaled Agile Framework (SAFe)]] + +## 📌 Brief Summary +Scaled Agile Framework(SAFe) 환경에서의 자동화 테스트는 단순한 수동 테스트의 대체를 넘어, 여러 팀에 걸쳐 가치를 지속적으로 전달하기 위한 필수적인 내장 품질(Built-in Quality) 관행입니다 [1]. 단일 스크럼 팀의 규모를 뛰어넘어 5~12개의 애자일 팀으로 구성된 애자일 릴리스 트레인(ART, Agile Release Train) 전체의 작업을 조정해야 하므로, 모든 통합 지점에서의 오류 가능성에 대비하는 전략이 필요합니다 [2]. SAFe는 자동화 테스트를 테스트 주도 개발(TDD), 동료 리뷰(Peer Review), 코드 공동 소유권 등과 함께 품질을 보장하기 위한 핵심 기둥으로 간주합니다 [2]. + +## 📖 Core Content +* **책임의 분산 및 시스템 팀의 역할** + SAFe 환경에서 자동화 테스트에 대한 책임은 조직 전반에 분산됩니다. 개발자는 완료 조건(DoD)의 일부로 단위 테스트를 작성 및 유지 관리하고, 테스터는 통합 및 인수 테스트 자동화에 집중합니다 [3]. 시스템 팀은 테스트 환경, CI 파이프라인, 테스트 데이터 관리 등 인프라를 지원하여 테스트 자동화가 원활하게 이루어지도록 돕습니다 [3]. 특히 시스템 팀은 프로덕션 구성과 일치하는 안정적인 통합 테스트 환경을 제공함으로써 역 테스트 피라미드(Inverted Test Pyramid) 안티 패턴을 방지합니다 [4]. +* **ART(Agile Release Train) 규모의 CI 파이프라인** + 수십 명의 개발자가 동일한 코드베이스에 매일 코드를 푸시하는 상황에서 지속적 통합(CI)과 지속적 제공(CD)을 실현하게 해주는 기본 메커니즘이 바로 자동화 테스트입니다 [5]. 개별 팀이 자체 CI 프로세스를 유지하는 한편, 시스템 팀은 전체 ART를 관통하는 통합 인프라를 조정하여 팀 단위의 빌드들이 일관성 있는 전체로 연결되도록 합니다 [6]. +* **테스트 코드의 1급 시민 대우 (First-Class Code)** + 자동화된 테스트 코드는 프로덕션 코드와 동일한 품질 자산으로 취급되어야 합니다 [7]. 동료 리뷰를 거치고, 코딩 표준을 따르며, 취약해질 경우 리팩토링의 대상이 되어야 합니다 [7]. 완료 조건(DoD)에 자동화 테스트 관련 요구사항을 공식화하고, 내장 품질(Built-in Quality) 체크리스트를 활용해 ART 내 모든 팀의 테스트 기대치를 표준화합니다 [8]. +* **Shift-Left 테스트와 지속적인 피드백 루프** + 테스트 생성과 실행을 개발 주기 후반으로 미루지 않고 초기 단계로 당겨야 합니다 [9]. 개발 중에는 TDD를 활용하고, 공유 브랜치에 코드가 병합될 때마다 통합 테스트를 거치며, 정기적인 회귀 테스트를 통해 부작용을 포착합니다 [9]. SAFe의 '검사 및 적응(Inspect and Adapt)' 이벤트는 이러한 테스트 지표를 정기적으로 검토하고 개선 영역을 식별하는 훌륭한 계기를 제공합니다 [10]. + +## ⚖️ Trade-offs & Caveats +* **역 테스트 피라미드 안티 패턴 (Inverted Test Pyramid)** + 단위 테스트나 통합 테스트 대신 GUI 및 E2E(End-to-End) 테스트에 지나치게 의존하면, 테스트 스위트가 느려지고 작은 변화에도 쉽게 깨져(brittle) 유지 보수 비용이 높아집니다 [11]. 이 경우 테스트를 작성하는 시간보다 고치는 데 더 많은 자원이 소모되는 역효과가 발생합니다 [11]. +* **공유 테스트 환경의 병목 현상** + 여러 팀이 동일한 테스트 환경을 동시에 사용하여 테스트를 실행하면 실패가 연쇄적으로 일어날 수 있으며, 어떤 코드 변경이 문제를 일으켰는지 판별하기 매우 어려워집니다(거짓 실패 발생) [11]. 이를 방지하기 위해서는 코드화된 인프라(Infrastructure as Code) 등을 통해 환경을 독립적으로 프로비저닝해야 합니다 [6]. +* **유지보수 예산의 부재 및 취약한 테스트(Flaky Tests)** + 자동화 테스트를 지속적인 관행이 아닌 일회성 프로젝트로 취급하면, 유지보수되지 않은 테스트 코드가 곧 부채로 전락합니다 [11]. 간헐적으로 통과와 실패를 반복하는 '취약한 테스트'를 방치하면 개발자들은 테스트 결과를 신뢰하지 않게 되며, 결국 자동화 이니셔티브 전체의 실패로 이어집니다 [11, 12]. +* **비현실적인 기대와 기술 격차** + 초기부터 수동 테스트 노력이 '0'으로 수렴할 것이라고 기대하는 것은 환상이며, 자동화는 수동 테스트를 대체하는 것이 아니라 사람의 노력을 보완하는 것임을 명심해야 합니다 [11]. 또한, 충분한 자동화 전문 지식이 없는 팀이 복잡한 프레임워크를 도입하려고 하면 개발이 오히려 정체될 수 있습니다 [11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/DevOps_and_Security/Continuous Delivery (지속적 제공, CD).md b/10_Wiki/Topics/DevOps_and_Security/Continuous Delivery (지속적 제공, CD).md new file mode 100644 index 00000000..84c5513b --- /dev/null +++ b/10_Wiki/Topics/DevOps_and_Security/Continuous Delivery (지속적 제공, CD).md @@ -0,0 +1,17 @@ +# [[Continuous Delivery (지속적 제공, CD)]] + +## 📌 Brief Summary +Continuous Delivery(지속적 제공, CD)는 소프트웨어가 언제든지 프로덕션 환경에 릴리스될 수 있도록 보장하는 자동화된 소프트웨어 개발 관행입니다 [1]. 이를 위해 빌드 파이프라인을 활용하여 소프트웨어를 자동으로 테스트하고 테스트 및 프로덕션 환경에 배포합니다 [1]. 수많은 개발자가 매일 동일한 코드베이스에 코드를 푸시하는 환경에서도 품질 저하 없이 개발 속도와 확신을 유지 가능하게 만드는 핵심적인 역할을 합니다 [1, 2]. + +## 📖 Core Content +* **빌드 파이프라인과 완벽한 자동화:** 지속적 제공 환경에서는 빌드부터 테스트, 배포, 인프라에 이르기까지 모든 과정의 자동화가 필수적입니다 [3]. 수동 테스트와 배포로는 빠르게 혁신하는 개발 속도를 따라갈 수 없으며, 자동화된 테스트 메커니즘이 지속적 제공을 실행 가능하게 만드는 근간이 됩니다 [2, 3]. +* **빠른 피드백 루프와 애자일의 시너지:** 자동화된 테스트가 제공하는 대폭 단축된 피드백 루프는 애자일 개발 관행, 지속적 제공 및 DevOps 문화와 밀접하게 맞물려 작동합니다 [4]. 이를 통해 팀은 소프트웨어 품질을 희생하지 않으면서도 더 빠르게 움직일 수 있습니다 [1, 4]. 마틴 파울러(Martin Fowler)가 옹호하는 지속적 제공과 DevOps는 조직의 소프트웨어 배포 방식을 혁신적으로 재편하는 데 기여했습니다 [5]. +* **대규모 환경(SAFe)에서의 확장 및 인프라 조정:** 대규모 애자일 환경에서는 지속적 제공 파이프라인이 전체 ART(Agile Release Train) 시스템에 걸쳐 확장됩니다 [6]. 개별 팀이 고유의 CI(지속적 통합) 프로세스를 유지하는 한편, 시스템 팀은 팀 수준의 빌드 결과물들을 일관된 하나의 소프트웨어로 연결하는 통합 인프라(코드로서의 인프라 등)를 구축하고 조정합니다 [6]. + +## ⚖️ Trade-offs & Caveats +* **신뢰할 수 있는 CI 파이프라인 필수:** 단일 자동화 테스트를 작성하거나 CD를 도입하기 전에 기능적이고 신뢰할 수 있는 지속적 통합(CI) 파이프라인이 먼저 구축되어야 합니다 [7]. 매 커밋 시 트리거되는 자동화된 빌드 환경과 수 분 내에 코드의 오류를 알려주는 명확한 피드백 루프가 없다면 지속적 제공의 가치는 실현될 수 없습니다 [7]. +* **품질 부채(Quality Debt)와 시스템 경직성:** 취약한 코드베이스 위에 자동화를 무리하게 적용하려 할 경우, 테스트 스위트 자체가 유지보수하기 어렵고 깨지기 쉬운 상태가 됩니다 [8]. 결과적으로 흐름 효율성(Flow Efficiency)과 배포 성공률이 모두 악화되므로 리팩토링, 자동화된 테스트, 체계적인 동료 코드 리뷰와 같은 내장된 품질(Built-in Quality) 관행이 반드시 전제되어야 합니다 [8]. +* **사전 인프라 투자 부담:** 지속적 제공을 안정적으로 운영하려면 테스트 환경, CI 파이프라인 구축 및 테스트 데이터 관리 등 인프라를 마련하는 데 상당한 초기 리소스와 시스템 팀 단위의 노력이 요구됩니다 [6, 9]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/DevOps_and_Security/Continuous Integration & Continuous Deployment (CI-CD).md b/10_Wiki/Topics/DevOps_and_Security/Continuous Integration & Continuous Deployment (CI-CD).md new file mode 100644 index 00000000..e2d789fa --- /dev/null +++ b/10_Wiki/Topics/DevOps_and_Security/Continuous Integration & Continuous Deployment (CI-CD).md @@ -0,0 +1,18 @@ +# [[Continuous Integration & Continuous Deployment (CI/CD)]] + +## 📌 Brief Summary +지속적 통합 및 지속적 배포(CI/CD)는 소프트웨어 코드에 변경 사항이 발생할 때마다 빌드 파이프라인을 통해 자동으로 소프트웨어를 테스트하고 배포하는 개발 관행입니다 [1]. 다수의 개발자가 동일한 코드베이스에 변경 사항을 지속적으로 푸시할 때, 자동화된 테스트를 실행하여 코드의 안정성을 검증하고 개발자에게 빠른 피드백을 제공하는 핵심 인프라 역할을 합니다 [2, 3]. 리팩토링이나 새로운 기능 개발 시 코드가 망가지지 않았는지 보장하기 위해서는 자동화된 테스트가 CI/CD 파이프라인에 완벽하게 통합되어야 합니다 [1, 4]. + +## 📖 Core Content +* **CI 파이프라인의 필수 요건**: 단일 자동화 테스트를 작성하기 전에, 기능적이고 신뢰할 수 있는 CI 파이프라인이 먼저 구축되어야 합니다 [3]. 이는 모든 커밋에서 트리거되는 자동화된 빌드, 일관된 빌드 환경, 그리고 코드 변경으로 인한 오류 발생 여부를 수 분 내에 개발자에게 알려주는 명확한 피드백 루프를 포함합니다 [3]. +* **테스트 속도와 파이프라인 단계 구성**: CI/CD 배포 파이프라인의 가장 기본적인 가치는 '빠른 피드백(Fast Feedback)'에 있습니다 [1]. 따라서 실행 시간이 짧고 범위가 좁은 단위 테스트 및 통합 테스트는 파이프라인의 초기 단계에 배치하여 10~15분 내에 신속한 피드백을 받아야 하며, 실행 시간이 긴 테스트는 후반 단계에 배치하여 빠른 피드백의 지연을 막아야 합니다 [5, 6]. +* **대규모 환경(SAFe)에서의 CI/CD 적용**: 규모가 큰 엔터프라이즈 환경에서 지속적 배포 파이프라인은 전체 애자일 릴리스 트레인(ART)에 걸쳐 확장됩니다 [7]. 개별 팀들은 자체적인 CI 프로세스를 유지하며, 통합을 담당하는 시스템 팀은 '코드형 인프라(Infrastructure as Code)'를 통해 수동 구성이 아닌 일관성 있고 반복 가능한 테스트 환경을 프로비저닝합니다 [7]. +* **리팩토링과의 유기적 통합**: 리팩토링 과정에서 작은 구조적 변경을 수행한 직후에는 항상 CI 환경에서 테스트를 실행하여 버그 유입 위험을 차단해야 합니다 [4]. 마틴 파울러가 제시한 리팩토링 방법론 역시 현대의 DevOps 및 CI/CD 파이프라인에 널리 채택되어 안전한 시스템 구조 개선에 기여하고 있습니다 [8]. + +## ⚖️ Trade-offs & Caveats +* **테스트 비대화 및 피드백 지연**: CI 파이프라인 내에서 무겁고 실행 속도가 느린 엔드투엔드(End-to-End) 테스트나 UI 테스트에 과도하게 의존(역 테스트 피라미드 안티 패턴)하게 되면, 빌드 빈도가 급락하고 파이프라인 실행에 수 시간이 소요되는 부작용이 발생합니다 [9]. 커버리지(Coverage) 지표에만 집착하여 실행 속도를 희생하는 것은 잘못된 최적화이며, 적용 범위와 속도, 신뢰성 간의 시스템적 균형을 맞춰야 합니다 [10]. +* **테스트 환경 공유로 인한 거짓 실패(False Failures)**: 여러 팀이 동일한 CI 테스트 환경을 동시에 공유하여 테스트를 실행할 경우, 연쇄적인 실패가 발생하여 정확히 어떤 코드 변경이 문제를 일으켰는지 판별하기 불가능해지는 치명적인 제약이 따를 수 있습니다 [11]. +* **불완전한 인프라로 인한 투자 낭비**: 안정적인 CI 파이프라인 인프라가 사전에 마련되지 않는다면, 개발 팀이 아무리 훌륭한 자동화 테스트를 작성하더라도 이를 실행할 공간과 피드백을 전달할 메커니즘이 존재하지 않아 테스트의 가치가 상실되는 문제가 발생합니다 [3]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/DevOps_and_Security/Continuous Integration (지속적 통합, CI).md b/10_Wiki/Topics/DevOps_and_Security/Continuous Integration (지속적 통합, CI).md new file mode 100644 index 00000000..a2e733e1 --- /dev/null +++ b/10_Wiki/Topics/DevOps_and_Security/Continuous Integration (지속적 통합, CI).md @@ -0,0 +1,55 @@ +# [[Continuous Integration (지속적 통합, CI)]] + +## 📌 Brief Summary +지속적 통합(CI)은 개발자가 코드를 커밋할 때마다 자동으로 빌드와 테스트가 트리거되어 몇 분 내에 명확한 피드백을 제공하는 소프트웨어 개발 실천법입니다 [1]. 여러 개발자가 동일한 코드베이스에 코드를 푸시할 때 발생하는 문제를 방지하고 자동화된 테스트와 지속적 제공(CD)을 가능하게 하는 핵심 메커니즘입니다 [2]. 특히 리팩토링 과정에서 작은 변경 사항을 만든 후 CI와 TDD를 실행하는 것은 시스템을 정상 작동 상태로 유지하고 새로운 버그가 유입되는 것을 막는 필수적인 안전망 역할을 합니다 [3]. + +## 📖 일 Core Content + +* **정의 및 역할:** CI는 일상적인 개발 및 리팩토링 프로세스에 내장되어야 하는 필수적인 기반입니다. 기능적이고 신뢰할 수 있는 CI 파이프라인이 구축되어 있어야만 코드가 커밋될 때 자동화된 빌드가 실행되고, 신속한 피드백 루프가 형성됩니다 [1]. +* **리팩토링 및 테스트를 위한 필수 조건:** CI 파이프라인이라는 기반 없이는 자동화된 테스트가 실행될 공간이 없으며, 빠르고 가치 있는 피드백을 전달할 메커니즘이 존재하지 않게 됩니다 [1]. 리팩토링 프로세스에서 코드를 작게 수정한 후에는 반드시 TDD와 CI를 실행하여 시스템이 깨지지 않았는지 확인해야 하며, 이를 생략하면 버그가 유입될 위험이 큽니다 [3]. +* **지속적 제공(CD) 및 DevOps와의 통합:** CI는 언제든 프로덕션 환경에 소프트웨어를 릴리스할 수 있도록 보장하는 지속적 제공(CD) 및 DevOps 문화와 긴밀하게 연결되어 발전합니다 [4]. 파이프라인은 여러 단계로 나뉘어 점진적으로 소프트웨어 배포에 대한 확신을 주며, 최근의 최신 DevOps 및 CI/CD 파이프라인에서는 마틴 파울러의 리팩토링 방법론이 널리 채택되어 적용되고 있습니다 [5, 6]. +* **미래의 진화:** 향후 인공지능(AI)이 발전함에 따라, 단순히 코드를 커밋할 때마다 빌드/테스트를 수행하는 '지속적인 통합(CI)' 단계를 넘어, AI가 자동으로 리팩토링 기회를 탐지하고 테스트를 통해 안정성이 입증된 최적의 구조를 제안하거나 적용하는 '지속적인 개선(Continuous Improvement)'의 단계로 나아갈 것으로 예측됩니다 [7]. + +## ⚖️ Trade-offs & Caveats +* **초기 인프라 구축 및 신뢰성 확보의 어려움:** CI를 효과적으로 운영하기 위해서는 코드로 프로비저닝된 일관된 빌드 환경과 신뢰할 수 있는 인프라 구축이 선행되어야 합니다 [1, 8]. 이 기반이 불안정하다면 자동화된 테스트도 제대로 기능할 수 없습니다 [1]. +* **테스트 품질에 대한 강한 의존성:** CI는 테스트를 자동으로 실행해 줄 뿐, 테스트 자체의 품질을 보장하지는 않습니다. CI 파이프라인 내의 테스트가 불완전하거나 간헐적으로 실패하는(Flaky) 경우, CI의 결과에 대한 개발자의 신뢰가 떨어지고 성공적인 리팩토링 진행을 방해하게 됩니다 [9]. 원대한 테스트 커버리지 목표를 쫓기 전에 CI 파이프라인의 신뢰성을 먼저 확보하는 것이 중요합니다 [10]. + +## 🔗 Knowledge Connections + +### Related Concepts + +#### [소프트웨어 품질 및 검증 (Software Quality & Verification)] +* [[Automated Testing (자동화된 테스트)]] + * 연결 이유: CI 파이프라인의 핵심은 코드가 커밋될 때마다 실행되는 자동화된 테스트에 있기 때문입니다 [1]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: CI를 통해 리팩토링의 안전성을 보장하는 구체적인 검증 메커니즘과, 피드백 루프를 짧게 유지하는 방법을 깊이 이해할 수 있습니다 [6, 11]. +* [[TDD (테스트 주도 개발)]] + * 연결 이유: 리팩토링 과정에서 작은 변경을 한 후 TDD와 CI를 함께 실행하는 것이 버그 도입 위험을 막는 필수 실천법으로 언급되기 때문입니다 [3]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: 기능 추가와 리팩토링 과정에서 테스트가 어떻게 설계를 주도하고 CI 파이프라인에서 지속적인 피드백을 주는지 파악할 수 있습니다. + +#### [소프트웨어 배포 및 운영 (Software Delivery & Operation)] +* [[Continuous Delivery (지속적 제공, CD)]] + * 연결 이유: CI는 언제든 소프트웨어를 배포할 수 있도록 보장하는 지속적 제공(CD)과 연결되어 파이프라인을 형성하기 때문입니다 [2, 4]. + * 이 개념을 통해 더 깊게 이해할 수 있는 부분: CI가 단지 코드 통합을 넘어 실제 프로덕션 환경에 소프트웨어를 안전하고 지속적으로 배포하기 위한 더 넓은 관점에서의 가치와 목적을 이해할 수 있습니다 [4, 6]. + +### Deeper Research Questions +* CI 파이프라인 내에서 자동화된 테스트의 실행 속도와 커버리지 사이의 이상적인 균형점은 어떻게 찾아야 하는가? +* 수십 명의 개발자가 동시에 코드를 커밋하는 대규모 환경에서 CI 파이프라인의 병목 현상을 방지하기 위한 시스템적, 도구적 접근법은 무엇인가? +* 대규모 레거시 시스템에서 신뢰할 수 없는 CI 파이프라인을 점진적으로 구축하고 안정화하기 위한 최적의 전략은 무엇인가? +* 리팩토링 변경 사항이 포함된 커밋이 CI 파이프라인에서 실패했을 때, 가장 효율적으로 원인을 파악하고 디버깅하는 절차는 무엇인가? +* 미래의 '자가 치유형 코드베이스(Self-Healing Codebase)'에서 AI는 CI 과정 중 어떤 기준으로 최적의 리팩토링 구조를 자동 제안하게 될 것인가? + +### Practical Application Contexts +* **Implementation:** 코드를 커밋할 때마다 즉각적으로 자동 빌드와 테스트가 실행되도록 CI 툴을 설정하며, 리팩토링을 수행한 직후 반드시 CI를 통해 시스템 붕괴 여부를 확인합니다 [1, 3]. +* **System Design:** 시스템 팀의 관점에서 개별 개발팀의 CI 프로세스를 유기적으로 통합하여 일관된 빌드 환경을 제공하고, 전체 릴리스 트레인에 걸친 'Continuous Delivery Pipeline' 구조를 설계합니다 [1, 8]. +* **Operation / Maintenance:** CI 파이프라인의 신뢰성을 모니터링하고 유지보수합니다. 간헐적으로 실패하는(Flaky) 테스트를 조기에 제거하여 팀이 자동화 파이프라인에 대한 신뢰를 잃지 않도록 관리합니다 [9, 10, 12]. +* **Learning Path:** 리팩토링 기법과 단위 테스트 작성법을 배운 이후, 이를 지속적이고 자동화된 환경에서 검증할 수 있도록 CI 도구 활용법 및 파이프라인 구축을 학습합니다 [3, 13]. +* **My Project Relevance:** 기술 부채를 청산하기 위해 코드를 구조적으로 변경할 때마다 CI를 구축하여 몇 분 내에 피드백을 받을 수 있는 환경을 마련함으로써, 안정적으로 아키텍처를 개선해 나갑니다. + +### Adjacent Topics +* [[DevOps]] + * 확장 방향: 개발과 운영의 경계를 허물고 CI/CD 파이프라인을 인프라 자동화 및 문화적 차원으로 확장하여 어떻게 전체적인 소프트웨어 배포 주기를 단축시키고 품질을 향상하는지 조사합니다 [4, 5]. +* [[Test Pyramid (테스트 피라미드)]] + * 확장 방향: CI 환경 내에서 병목 없이 빠른 피드백을 받기 위해, 단위 테스트, 통합 테스트, E2E 테스트를 어떤 비율로 구성하고 배치해야 하는지 탐구합니다 [14, 15]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/DevOps_and_Security/DevOps.md b/10_Wiki/Topics/DevOps_and_Security/DevOps.md new file mode 100644 index 00000000..18f299b1 --- /dev/null +++ b/10_Wiki/Topics/DevOps_and_Security/DevOps.md @@ -0,0 +1,15 @@ +# [[DevOps]] + +## 📌 Brief Summary +DevOps는 조직의 소프트웨어 배포 방식을 재구성하며, 지속적 제공(Continuous Delivery) 및 애자일(Agile) 개발 관행과 밀접하게 연관된 문화이자 방법론입니다 [1, 2]. 이 환경에서는 자동화된 테스트를 통해 피드백 루프를 획기적으로 단축하여 팀이 빠르고 자신감 있게 움직일 수 있도록 지원합니다 [2]. 특히 지속적인 리팩토링 원칙과 자동화된 테스트 인프라는 DevOps 관행과 동전의 양면처럼 긴밀하게 통합되어 작동합니다 [3, 4]. + +## 📖 Core Content +* **자동화된 테스트와의 긴밀한 결합:** DevOps 문화는 자동화된 테스트가 주도하는 대폭 단축된 피드백 루프와 필수적으로 동반됩니다 [2]. 배포와 릴리스를 분리하기 위해 기능 토글(Feature Toggle)을 사용할 때, 자동화된 테스트는 기존 동작을 방해하지 않으면서 새로운 기능이 올바르게 작동하는지 검증하는 역할을 수행하며, 이는 DevOps 관행과 자동화된 테스트 인프라가 불가분의 관계에 있음을 보여줍니다 [3]. +* **리팩토링 기술의 유연한 적용:** 마틴 파울러(Martin Fowler) 등이 주도한 리팩토링 원칙과 기술은 DevOps를 포함한 다양한 소프트웨어 개발 방법론에 유연하게 적응할 수 있는 프레임워크를 제공합니다 [4]. 팀이 더욱 애자일해지고 반복적인 작업을 수행함에 따라, 코드를 깨끗하고 관리하기 쉽게 유지하는 리팩토링 원칙은 DevOps 환경에서도 핵심적인 역할을 합니다 [4]. +* **소프트웨어 배포의 혁신:** 지속적 제공과 DevOps에 대한 옹호는 조직이 소프트웨어를 배포하는 방식을 근본적으로 재편했습니다 [1]. 이는 개발팀이 워크플로우를 개선하고 혁신이 번창할 수 있는 환경을 조성하는 데 중요한 기여를 했습니다 [1]. + +## ⚖️ Trade-offs & Caveats +소스에 관련 정보가 부족합니다. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/DevOps_and_Security/소비자 주도 계약 테스트 (Consumer-Driven Contract Tests, CDC).md b/10_Wiki/Topics/DevOps_and_Security/소비자 주도 계약 테스트 (Consumer-Driven Contract Tests, CDC).md new file mode 100644 index 00000000..e7c29bfd --- /dev/null +++ b/10_Wiki/Topics/DevOps_and_Security/소비자 주도 계약 테스트 (Consumer-Driven Contract Tests, CDC).md @@ -0,0 +1,27 @@ +# [[소비자 주도 계약 테스트 (Consumer-Driven Contract Tests, CDC)]] + +## 📌 Brief Summary +소비자 주도 계약 테스트(CDC)는 마이크로서비스 환경에서 인터페이스를 소비하는 측(Consumer)이 요구사항을 바탕으로 테스트를 작성하여 제공자(Provider)의 API 구현을 주도하는 자동화된 테스트 기법이다 [1]. 소비자가 작성한 테스트 명세를 제공자가 지속적으로 실행함으로써, 인터페이스의 변경으로 인한 시스템의 오류를 조기에 발견할 수 있다 [2]. 이를 통해 조직 내 여러 팀이 런타임 통신 계약을 유지하면서도 자율적이고 빠르게 소프트웨어를 개발할 수 있도록 돕는다 [3, 4]. + +## 📖 Core Content +* **CDC 테스트의 기본 워크플로우:** + 1. 소비하는 팀(Consumer)은 자신이 인터페이스로부터 필요로 하는 모든 데이터와 기대사항을 담아 자동화된 테스트를 작성한다 [1, 2]. + 2. 작성된 테스트(또는 Pact 파일과 같은 계약 명세)를 제공자 팀이 가져갈 수 있도록 배포(publish)한다 [1, 2, 5]. + 3. 제공하는 팀(Provider)은 빌드 파이프라인에서 이 CDC 테스트를 지속적으로 실행하여 API를 개발한다. 모든 테스트가 통과하면 소비자가 요구하는 모든 사항을 구현했음을 확신할 수 있다 [1, 2]. + 4. 만약 제공자가 인터페이스를 변경하여 CDC 테스트가 실패하게 되면, 두 팀이 즉시 소통하여 API 변경에 대해 논의하고 해결책을 찾는다 [2, 3]. + +* **도구와 구현 방식:** + * CDC 테스트의 가장 단순한 형태는 API에 요청을 보내고 필요한 모든 것이 응답에 포함되어 있는지 단언(assert)하는 것이다 [6]. + * 현대적이고 대중적인 도구로는 **Pact**가 있다. 소비자는 Pact를 사용하여 기대사항이 담긴 JSON 형태의 계약 파일(pact file)을 생성하고, 제공자는 이 파일을 읽어들여 자신의 서비스가 기대 상태에 맞게 응답하는지 검증한다 [5-7]. + +* **아키텍처 및 조직적 이점:** + * **불필요한 구현 방지:** 제공하는 팀은 소비자가 작성한 테스트를 통과하는 데 필요한 기능만 정확히 구현하면 되므로, YAGNI(You Aren't Gonna Need It) 원칙을 준수하고 시스템을 단순하게 유지할 수 있다 [2]. + * **독립성과 소통 강화:** 방대하고 무거운 인터페이스 문서를 주고받는 대신, 실행 가능한 테스트를 통해 명확한 소통을 유도한다. 이는 마이크로서비스를 구축하는 자율적인 팀들이 다른 서비스를 망가뜨릴 걱정 없이 빠르게 개발할 수 있게 하는 핵심(Game changer)이 된다 [4, 8]. + +## ⚖️ Trade-offs & Caveats +* **공개 API(Public API) 적용의 한계:** CDC 테스트는 조직 내부의 마이크로서비스 환경에서 가장 유용하다. 불특정 다수가 사용하는 공개 API의 경우, 제공자 측에서 모든 소비자의 요구사항을 반영한 테스트를 일일이 구동하고 맞춰주는 것이 불가능하므로 이 접근 방식을 적용하기 어렵다 [9]. +* **테스트 공유를 위한 인프라 관리:** 소비자가 생성한 계약 파일(Pact 파일 등)을 제공자에게 전달하려면 추가적인 관리 방법이 필요하다. 버전 관리 시스템에 직접 체크인하거나, Amazon S3, 아티팩트 저장소, 또는 전용 Pact Broker와 같은 인프라를 구축하고 관리하는 수고가 발생한다 [10]. +* **팀 간 소통의 필수성:** 자동화된 테스트가 소통을 돕는 도구이긴 하지만, 테스트가 실패했을 때(예: 불가피한 API 변경 시) 결국 관련된 두 팀이 직접 만나 향후 방향성을 논의하고 조율하는 과정이 필수적으로 동반되어야 한다 [3]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file diff --git a/10_Wiki/Topics/DevOps_and_Security/지속적 통합 (CI) 및 지속적 배포 (CD).md b/10_Wiki/Topics/DevOps_and_Security/지속적 통합 (CI) 및 지속적 배포 (CD).md new file mode 100644 index 00000000..e6a3ec15 --- /dev/null +++ b/10_Wiki/Topics/DevOps_and_Security/지속적 통합 (CI) 및 지속적 배포 (CD).md @@ -0,0 +1,25 @@ +# [[지속적 통합 (CI) 및 지속적 배포 (CD)]] + +## 📌 Brief 소스 Summary +지속적 통합(CI) 및 지속적 배포(CD)는 수많은 개발자가 동일한 코드베이스에 코드를 푸시할 때 자동화된 빌드와 테스트를 실행하게 하는 필수적인 메커니즘이다 [1, 2]. 소프트웨어가 변경될 때마다 배포 파이프라인(Deployment Pipeline)을 통해 코드를 검증하며, 문제 발생 시 몇 분 이내에 개발자에게 즉각적인 피드백을 제공하는 것을 목표로 한다 [2, 3]. 리팩토링이나 새로운 기능 추가 과정에서 자동화된 회귀 테스트를 지속적으로 수행함으로써 기술 부채를 안전하게 상환하고 시스템을 지속적으로 개선할 수 있는 안정망 역할을 한다 [4-6]. + +## 📖 Core Content + +* **CI 파이프라인의 필수 역할 및 빠른 피드백 (Fast Feedback)** + 성공적인 자동화 테스트를 위해서는 기능적이고 신뢰할 수 있는 CI 파이프라인 구축이 선행되어야 한다 [3]. CI 파이프라인은 모든 커밋에 대해 빌드를 트리거하며, 변경 사항이 무언가를 망가뜨렸는지 몇 분 안에 개발자에게 알려주는 명확한 피드백 루프를 제공한다 [3]. 이러한 기반 없이는 자동화 테스트가 가치를 발휘할 수 없으며 빠른 피드백을 전달할 메커니즘도 존재할 수 없다 [2, 3]. 피드백 속도를 최적화하기 위해, 빠르고 좁은 범위를 가진 단위 테스트나 통합 테스트를 파이프라인의 초기 단계에 배치하여야 한다 [7]. +* **리팩토링의 안전망 및 코드베이스 신뢰성 확보** + 리팩토링 과정에서 CI와 TDD(테스트 주도 개발)를 연계하여 작은 변경 사항마다 코드를 실행하는 것은 새로운 버그 도입의 위험을 막아준다 [4]. 코드가 변경될 때마다 자동으로 테스트를 실행하게 함으로써 수정으로 인해 발생한 회귀(Regression) 오류를 신속히 포착할 수 있다 [5]. 이는 결과적으로 코드베이스에 대한 신뢰감을 형성하며, 팀이 기존 기능을 불안정하게 만들 염려 없이 혁신하고 리팩토링에 전념할 수 있도록 돕는다 [5]. +* **대규모 조직 (SAFe) 환경에서의 파이프라인 통합** + 애자일 릴리스 트레인(ART)과 같은 대규모 프레임워크 내에서 지속적 배포 파이프라인은 조직 전체로 확장된다 [8]. 개별 팀들은 자신만의 CI 프로세스를 유지 관리하며, 시스템 팀(System Teams)은 개별 팀 수준의 빌드를 일관된 전체로 연결하는 통합 인프라를 조정한다 [8]. 이 과정에서 코드로서의 인프라(Infrastructure as Code)를 사용하여 테스트 환경의 일관성과 반복 가능성을 보장하는 것이 핵심이다 [8]. + +## ⚖️ Trade-offs & Caveats + +* **빠른 피드백 지연과 파이프라인 구성의 함정** + 테스트를 추가하는 데만 급급하여 전체 테스트 스위트 실행에 과도한 시간(예: 전체 스위트 실행에 3일 소요)이 소요된다면, 잘못된 변수를 최적화한 것이다 [9]. 긴 시간이 걸리는 테스트 때문에 피드백이 지연되면, 개발자가 퇴근한 뒤에야 단위 테스트 실패 여부를 알게 될 수 있어 CI/CD의 핵심 가치가 훼손된다 [7]. 따라서 테스트 커버리지, 속도, 신뢰성 간의 시스템적 균형을 맞춰야 하며, 오래 걸리는 테스트는 파이프라인의 후반 단계로 미루어야 한다 [7, 9]. +* **공유 환경에서의 실패 전파 (Cascading Failures)** + 여러 팀이 동일한 테스트 환경을 동시에 공유하여 사용할 경우, 하나의 실패가 다른 실패를 연쇄적으로 유발할 수 있다 [10]. 이 경우 어떤 팀의 변경 사항이 문제를 일으켰는지 판별하기 어려워지며, 지속적인 거짓 실패(False failures)는 자동화 파이프라인에 대한 팀의 신뢰를 무너뜨린다 [10]. +* **성급한 커버리지 확대의 위험성** + 불안정한 테스트(Flaky Tests)나 CI 파이프라인 자체의 신뢰성이 보장되지 않은 상태에서 야심 차게 테스트 커버리지 목표만을 추구하는 것은 위험하다 [11]. 확장하기 전, 작더라도 팀이 완전히 신뢰할 수 있는 안정된 기반을 먼저 확립하고 완료 조건(DoD) 기준 및 CI 파이프라인 신뢰성을 점진적으로 확보해야 한다 [11]. + +--- +*Last updated: 2026-05-03* \ No newline at end of file