본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 5장: LLM 기반 자동 리팩터링
2026년 3월 13일·AI / ML·

5장: LLM 기반 자동 리팩터링

LLM을 활용한 자동 리팩터링의 패턴, 멀티에이전트 아키텍처, 검증 파이프라인을 학습합니다. 37%에서 98%로 정밀도를 끌어올리는 실전 기법을 다룹니다.

15분990자7개 섹션
code-qualityaillmdevtools
공유
code-analysis5 / 10
12345678910
이전4장: 코드 스멜 감지와 기술 부채 정량화다음6장: 코드 마이그레이션 자동화

학습 목표

  • 주요 리팩터링 패턴(함수 추출, 이름 변경, 메서드 이동)을 이해합니다
  • 멀티에이전트 리팩터링 시스템(RepoAI)의 아키텍처를 학습합니다
  • 검증 파이프라인을 통해 37%에서 98%로 정밀도를 높이는 기법을 파악합니다
  • Moderne 플랫폼의 대규모 자동 리팩터링 접근법을 이해합니다

리팩터링 패턴의 이해

리팩터링(Refactoring)은 외부 동작을 변경하지 않으면서 코드의 내부 구조를 개선하는 작업입니다. LLM은 Martin Fowler가 정리한 주요 리팩터링 패턴을 코드 맥락에 맞게 적용할 수 있습니다.

함수 추출 (Extract Function)

가장 빈번하게 적용되는 리팩터링 패턴입니다. 4장에서 본 process_order 함수를 예로 들어보겠습니다.

before_extract.py
python
# 리팩터링 전: 하나의 함수에 모든 로직
def process_order(order_data: dict) -> dict:
    # 유효성 검사
    if not order_data.get("customer_id"):
        raise ValueError("고객 ID가 필요합니다")
    if not order_data.get("items"):
        raise ValueError("주문 항목이 필요합니다")
    # ... 50줄 이상의 코드
after_extract.py
python
# 리팩터링 후: 책임별로 분리
def process_order(order_data: dict) -> dict:
    """주문 처리의 오케스트레이션 함수"""
    validate_order(order_data)
    pricing = calculate_pricing(order_data["items"], order_data["customer_id"])
    verify_and_reserve_stock(order_data["items"])
    order = save_order(order_data, pricing)
    notify_customer(order_data["customer_id"], order)
    return order
 
 
def validate_order(order_data: dict) -> None:
    """주문 데이터의 유효성을 검사합니다"""
    if not order_data.get("customer_id"):
        raise ValueError("고객 ID가 필요합니다")
    if not order_data.get("items"):
        raise ValueError("주문 항목이 필요합니다")
    if not isinstance(order_data["items"], list):
        raise TypeError("주문 항목은 리스트여야 합니다")
    for item in order_data["items"]:
        _validate_order_item(item)
 
 
def _validate_order_item(item: dict) -> None:
    if not item.get("product_id"):
        raise ValueError("상품 ID가 필요합니다")
    if not item.get("quantity") or item["quantity"] <= 0:
        raise ValueError("수량은 양수여야 합니다")
 
 
@dataclass
class OrderPricing:
    subtotal: float
    tax: float
    shipping: float
    total: float
 
 
def calculate_pricing(
    items: list[dict], customer_id: str
) -> OrderPricing:
    """주문 금액을 계산합니다"""
    subtotal = sum(
        get_product_price(item["product_id"])
        * item["quantity"]
        * (1 - get_discount(item["product_id"], customer_id))
        for item in items
    )
    tax = subtotal * 0.1
    shipping = 3000 if subtotal < 50000 else 0
    return OrderPricing(
        subtotal=subtotal,
        tax=tax,
        shipping=shipping,
        total=subtotal + tax + shipping,
    )

이름 변경 (Rename)

명확한 이름은 코드의 가독성을 극적으로 향상시킵니다. LLM은 코드의 맥락을 이해하여 의미 있는 이름을 제안할 수 있습니다.

rename_example.py
python
# 리팩터링 전
def proc(d, f=None, t=None):
    r = []
    for i in d:
        if f and not f(i):
            continue
        if t:
            i = t(i)
        r.append(i)
    return r
 
# 리팩터링 후
def filter_and_transform(
    items: list,
    predicate: Callable | None = None,
    transformer: Callable | None = None,
) -> list:
    results = []
    for item in items:
        if predicate and not predicate(item):
            continue
        if transformer:
            item = transformer(item)
        results.append(item)
    return results

메서드 이동 (Move Method)

기능 선망(Feature Envy) 스멜이 감지되면 메서드를 더 적절한 클래스로 이동합니다.

move-method-before.ts
typescript
// 리팩터링 전: OrderService가 Product의 내부에 지나치게 의존
class OrderService {
  calculateItemPrice(product: Product, quantity: number): number {
    const basePrice = product.price * quantity;
    const discount = product.category === "premium"
      ? basePrice * 0.1
      : product.category === "sale"
        ? basePrice * 0.2
        : 0;
    return basePrice - discount;
  }
}
move-method-after.ts
typescript
// 리팩터링 후: 가격 계산 로직을 Product로 이동
class Product {
  price: number;
  category: string;
 
  calculatePrice(quantity: number): number {
    const basePrice = this.price * quantity;
    return basePrice - this.getDiscount(basePrice);
  }
 
  private getDiscount(basePrice: number): number {
    const discountRates: Record<string, number> = {
      premium: 0.1,
      sale: 0.2,
    };
    return basePrice * (discountRates[this.category] ?? 0);
  }
}
 
class OrderService {
  calculateItemPrice(product: Product, quantity: number): number {
    return product.calculatePrice(quantity);
  }
}

멀티에이전트 리팩터링: RepoAI 아키텍처

RepoAI는 멀티에이전트 방식으로 리팩터링을 수행하는 프레임워크입니다. 단일 LLM 호출이 아닌 여러 전문 에이전트의 협력으로 정확도를 높입니다.

5단계 파이프라인

각 에이전트의 역할

multi_agent_refactor.py
python
from dataclasses import dataclass
from abc import ABC, abstractmethod
 
 
@dataclass
class RefactorPlan:
    target_files: list[str]
    changes: list[dict]
    rationale: str
    test_strategy: str
 
 
@dataclass
class RefactorResult:
    original_code: dict[str, str]
    refactored_code: dict[str, str]
    compilation_passed: bool
    tests_passed: bool
    reflection_notes: list[str]
    iterations: int
 
 
class BaseAgent(ABC):
    def __init__(self, llm_client):
        self.llm_client = llm_client
 
    @abstractmethod
    async def execute(self, context: dict) -> dict:
        pass
 
 
class PlannerAgent(BaseAgent):
    """1단계: 리팩터링 계획 수립"""
 
    async def execute(self, context: dict) -> dict:
        prompt = f"""다음 코드의 리팩터링 계획을 수립하세요.
 
코드 스멜: {context['smells']}
영향받는 파일: {context['files']}
의존성 그래프: {context['dependencies']}
 
다음을 포함하세요:
1. 변경할 파일과 변경 내용
2. 변경 순서 (의존성 고려)
3. 각 변경의 근거
4. 테스트 전략"""
 
        plan = await self.llm_client.generate(prompt)
        return {"plan": plan}
 
 
class GeneratorAgent(BaseAgent):
    """2단계: 리팩터링된 코드 생성"""
 
    async def execute(self, context: dict) -> dict:
        prompt = f"""다음 계획에 따라 리팩터링된 코드를 생성하세요.
 
원본 코드:
{context['original_code']}
 
리팩터링 계획:
{context['plan']}
 
이전 시도의 피드백:
{context.get('reflection', '첫 번째 시도')}
 
규칙:
- 외부 동작은 변경하지 않습니다
- 기존 테스트가 모두 통과해야 합니다
- 타입 안전성을 유지합니다"""
 
        code = await self.llm_client.generate(prompt)
        return {"refactored_code": code}
 
 
class CompilerAgent(BaseAgent):
    """3단계: 컴파일 검증"""
 
    async def execute(self, context: dict) -> dict:
        # 실제로는 subprocess로 컴파일러를 실행
        code = context["refactored_code"]
        compilation_result = await self._compile(code)
 
        return {
            "compilation_passed": compilation_result["success"],
            "errors": compilation_result.get("errors", []),
        }
 
    async def _compile(self, code: str) -> dict:
        # TypeScript: tsc --noEmit
        # Python: py_compile + mypy
        # Java: javac
        return {"success": True}
 
 
class TesterAgent(BaseAgent):
    """4단계: 테스트 실행"""
 
    async def execute(self, context: dict) -> dict:
        test_result = await self._run_tests(context["refactored_code"])
        return {
            "tests_passed": test_result["all_passed"],
            "failed_tests": test_result.get("failures", []),
            "coverage": test_result.get("coverage", 0),
        }
 
    async def _run_tests(self, code: str) -> dict:
        # pytest, jest, junit 등 실행
        return {"all_passed": True}
 
 
class ReflectionAgent(BaseAgent):
    """5단계: 실패 시 자기 반성 및 피드백"""
 
    async def execute(self, context: dict) -> dict:
        prompt = f"""리팩터링이 실패했습니다. 원인을 분석하세요.
 
컴파일 오류: {context.get('compile_errors', '없음')}
실패한 테스트: {context.get('failed_tests', '없음')}
이전 리팩터링 코드: {context['refactored_code']}
 
다음을 제공하세요:
1. 실패 원인 분석
2. 수정 방향
3. Generator Agent에 전달할 구체적 지시"""
 
        reflection = await self.llm_client.generate(prompt)
        return {"reflection": reflection}

오케스트레이터

refactor_orchestrator.py
python
class RefactorOrchestrator:
    """멀티에이전트 리팩터링 오케스트레이터"""
 
    MAX_ITERATIONS = 5
 
    def __init__(self, llm_client):
        self.planner = PlannerAgent(llm_client)
        self.generator = GeneratorAgent(llm_client)
        self.compiler = CompilerAgent(llm_client)
        self.tester = TesterAgent(llm_client)
        self.reflector = ReflectionAgent(llm_client)
 
    async def refactor(
        self,
        original_code: dict[str, str],
        smells: list[dict],
        dependencies: dict,
    ) -> RefactorResult:
        # 1단계: 계획 수립
        context = {
            "smells": smells,
            "files": list(original_code.keys()),
            "dependencies": dependencies,
            "original_code": original_code,
        }
        plan_result = await self.planner.execute(context)
        context.update(plan_result)
 
        iterations = 0
        reflection_notes = []
 
        while iterations < self.MAX_ITERATIONS:
            iterations += 1
 
            # 2단계: 코드 생성
            gen_result = await self.generator.execute(context)
            context.update(gen_result)
 
            # 3단계: 컴파일 검증
            compile_result = await self.compiler.execute(context)
 
            if not compile_result["compilation_passed"]:
                context["compile_errors"] = compile_result["errors"]
                reflect_result = await self.reflector.execute(context)
                context.update(reflect_result)
                reflection_notes.append(
                    f"반복 {iterations}: 컴파일 실패"
                )
                continue
 
            # 4단계: 테스트 실행
            test_result = await self.tester.execute(context)
 
            if test_result["tests_passed"]:
                return RefactorResult(
                    original_code=original_code,
                    refactored_code=context["refactored_code"],
                    compilation_passed=True,
                    tests_passed=True,
                    reflection_notes=reflection_notes,
                    iterations=iterations,
                )
 
            # 5단계: 반성
            context["failed_tests"] = test_result["failed_tests"]
            reflect_result = await self.reflector.execute(context)
            context.update(reflect_result)
            reflection_notes.append(
                f"반복 {iterations}: 테스트 실패 "
                f"({len(test_result['failed_tests'])}건)"
            )
 
        raise RuntimeError(
            f"{self.MAX_ITERATIONS}회 반복 후에도 리팩터링 실패"
        )

37%에서 98%로: 검증 파이프라인의 힘

LLM이 생성한 리팩터링 코드의 초기 기능적 정확도는 약 37%입니다. 이는 3건 중 2건이 어딘가에 기능적 오류를 포함한다는 의미입니다. 하지만 체계적인 검증 파이프라인을 거치면 98%까지 정밀도가 상승합니다.

검증 단계별 정확도 향상

각 검증 단계의 역할

단계방법검출하는 오류
구문 검증파서/컴파일러구문 오류, 누락된 괄호
타입 검증TypeScript tsc, mypy타입 불일치, 누락된 import
단위 테스트pytest, jest기능적 오류, 엣지 케이스
자기 반성LLM 재분석논리적 오류, 의미 변경
통합 테스트E2E 테스트모듈 간 호환성
Warning

검증 파이프라인 없는 LLM 리팩터링은 위험합니다. 37%의 초기 정확도는 사람의 검토 없이는 프로덕션에 적용할 수 없는 수준입니다. 자동화된 검증 단계가 LLM 리팩터링의 실용성을 결정합니다.


Moderne 플랫폼의 대규모 접근

Moderne은 단일 리포지토리가 아닌 수천 개 리포지토리에 걸쳐 리팩터링을 수행하는 플랫폼입니다.

OpenRewrite 레시피

Moderne의 핵심은 OpenRewrite 엔진입니다. 선언적 레시피(Recipe)를 정의하면 AST 수준에서 코드를 변환합니다.

recipe-example.yml
yaml
# Spring Boot 2에서 3으로 마이그레이션 레시피
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
displayName: Migrate to Spring Boot 3.0
description: >
  Spring Boot 2.x 애플리케이션을 3.0으로 마이그레이션합니다.
  Jakarta EE 전환, 설정 변경, 더 이상 사용하지 않는 API 교체를 포함합니다.
recipeList:
  - org.openrewrite.java.spring.boot3.RemoveEnableBatchProcessing
  - org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
  - org.openrewrite.java.spring.boot3.ConfigurationOverEnableSecurity

Moderne은 이러한 레시피에 LLM을 결합하여, 레시피가 커버하지 못하는 복잡한 변환(비즈니스 로직 관련 변경 등)을 처리합니다.


정리

LLM 기반 자동 리팩터링은 함수 추출, 이름 변경, 메서드 이동 등 전통적 리팩터링 패턴을 코드 맥락에 맞게 자동 적용합니다. RepoAI 같은 멀티에이전트 시스템은 계획-생성-검증-반성의 루프를 통해 정확도를 높이며, 검증 파이프라인을 통해 초기 37%의 정확도를 98%까지 끌어올릴 수 있습니다.

핵심은 LLM의 출력을 무조건 신뢰하지 않고, 체계적인 검증 단계를 거치는 것입니다. Moderne 같은 플랫폼은 이를 대규모로 자동화하여 수천 개 리포지토리에 일관된 리팩터링을 적용합니다.

다음 장 미리보기

6장에서는 리팩터링의 특수한 형태인 코드 마이그레이션 자동화를 다룹니다. Java에서 Kotlin으로, React Class 컴포넌트에서 Hooks로의 전환 등 언어/프레임워크 마이그레이션을 LLM으로 자동화하는 기법과 의미 보존 검증 방법을 학습합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#code-quality#ai#llm#devtools

관련 글

AI / ML

6장: 코드 마이그레이션 자동화

LLM을 활용한 언어/프레임워크 마이그레이션 자동화를 학습합니다. Java에서 Kotlin, React Class에서 Hooks로의 전환과 의미 보존 검증 기법을 다룹니다.

2026년 3월 15일·16분
AI / ML

4장: 코드 스멜 감지와 기술 부채 정량화

LLM 기반 코드 스멜 탐지와 CodeScene Code Health 메트릭을 활용한 기술 부채 정량화를 학습합니다. 우선순위 기반 리팩터링 계획 수립까지 다룹니다.

2026년 3월 11일·17분
AI / ML

7장: 보안 취약점 분석과 자동 수정

SAST와 LLM을 결합한 보안 취약점 탐지, OWASP Top 10 자동 검출, 취약점 자동 수정 제안과 CI/CD 보안 게이트 구축을 학습합니다.

2026년 3월 17일·16분
이전 글4장: 코드 스멜 감지와 기술 부채 정량화
다음 글6장: 코드 마이그레이션 자동화

댓글

목차

약 15분 남음
  • 학습 목표
  • 리팩터링 패턴의 이해
    • 함수 추출 (Extract Function)
    • 이름 변경 (Rename)
    • 메서드 이동 (Move Method)
  • 멀티에이전트 리팩터링: RepoAI 아키텍처
    • 5단계 파이프라인
    • 각 에이전트의 역할
    • 오케스트레이터
  • 37%에서 98%로: 검증 파이프라인의 힘
    • 검증 단계별 정확도 향상
    • 각 검증 단계의 역할
  • Moderne 플랫폼의 대규모 접근
    • OpenRewrite 레시피
  • 정리
  • 다음 장 미리보기