본문 바로가기

Vibe Coding

[리팩토링] LLM을 사용하여 목적 기반으로 리팩토링 수행하기

Disclamer: 이 문서는 사내용 세미나 진행을 위해, 사외에서 취득 가능한 정보만를 활용하여 사외에서 작성되었습니다.

◼︎ 목적 기반 리팩토링: "그냥 고치지 말고, 이유가 있어야 한다"

목적 기반 리팩토링(goal-oriented refactoring)이란 용어는 소프트웨어 공학에서 공식적으로 사용하는 용어는 아닙니다.

그런데 LLM을 사용하여 리팩토링을 진행하다 보면, 왜 리팩토링의 "목적"이 중요한지 어렵지 않게 체감할 수 있습니다.

사내 LLM에게 대규모 코드베이스를 대상으로 리팩토링을 요청하거나, 단순히 "리팩토링 해줘"와 같은 목적이 불분명한 요청을 하면,

주어진 맥락과 목적을 스스로 해석한다거나, 갈피를 못잡고 산만해지는 경우를 마주합니다. 이는 본래의 의도와는 다른 방향으로 코드를 변경하는 결과를 낳게 됩니다. 이 글에서는 어떻게 LLM에게 명확한 리팩토링의 목표를 알려주고, 이 목표를 유지하면서 성공적인 리팩토링을 수행할 수 있는지 알아봅니다.

 

◼︎ 개발자가 하는 리팩토링 vs. LLM 활용 리팩토링

  • 기존 리팩토링
    개발자가 머릿속에 명확한 목적을 두고, 코드 구조를 직접 고쳐 나갑니다. 목적은 자연스럽게 내재화 되어 있습니다.
    (목적 없이 리팩토링을 위한 리팩토링을 하면 안되겠죠?)
  • LLM 리팩토링
    목적을 명시적인 언어로 전달하지 않으면, 모델이 방향을 잡지 못합니다.
    따라서, "테스트 가능 구조", "가독성 향상", "성능 개선" 처럼 리팩토링의 목적을 구체적으로 써주는 것이 필수입니다.

◼︎ 예제. "테스트 가능(testable) 구조"를 만드는 목적 기반 리팩토링

현재 운영에 사용하고 있는 아래와 같은 python 코드가 있습니다. 기능 동작에는 문제가 없지만, 여러 기능이 하나의 함수에 뭉쳐 있어서 유닛 테스트 작성이 불가능합니다.

import re

def parse_logs(log_lines):
    results = []
    for line in log_lines:
        match = re.search(r"ERROR (\d+): (.+)", line)
        if match:
            results.append({
                "code": int(match.group(1)),
                "message": match.group(2)
            })
    return results

 

이 상태에서 사내 LLM에게 "리팩토링 해줘"라고 요청하면 아래와 같은 결과를 얻게 됩니다.

def parse_logs(log_lines):
    import re
    return [
        {"code": int(m.group(1)), "message": m.group(2)}
        for line in log_lines
        if (m := re.search(r"ERROR (\d+): (.+)", line))
    ]

 

코드가 정리되긴 했지만, (머릿속으로) 의도했던 테스트 가능한 구조는 만들어지지 않았습니다. 

 

Step1. 목적 정의하기

먼저 아래와 같이 리팩토링의 목적을 명확한 언어로 기술합니다.

"유닛 테스트 가능한 구조로 리팩토링한다."

 

그리고 가능한 경우 내용을 구체화 하여 LLM이 지나친 추측을 하지 않도록 제한합니다.

(1) 로그 한 줄을 처리하는 함수를 별도로 분리 한다.
(2) parse_logs는 (1)의 함수를 호출하도록 변경한다.
(3) pytest 기반 유닛 테스트 코드를 작성한다.

 

Step2. 프롬프트 작성하기

이전 프롬프트 전략 가이드에서 알아 본 것처럼 구조화된 프롬프트를 작성합니다. 이때 구체적으로 현재 코드 또는 참고 코드를 제공하면 답변 품질이 크게 향상됩니다.

목적: 유닛 테스트가 가능하도록 리팩토링
현재 코드: 
(붙여 넣기)
요구사항:
(1) 로그 한 줄을 처리하는 함수를 별도로 분리 한다.
(2) parse_logs는 (1)의 함수를 호출하도록 변경한다.
(3) pytest 기반 유닛 테스트 코드를 작성한다.

 

Step3. LLM에게 리팩토링을 요청

목적 기반으로 구조화된 프롬프트를 작성하고, LLM에게 요청하면 아래와 같은 출력을 얻을 수 있습니다.

import re

def parse_log_line(line: str):
    match = re.search(r"ERROR (\d+): (.+)", line)
    if match:
        return {
            "code": int(match.group(1)),
            "message": match.group(2)
        }
    return None

def parse_logs(log_lines):
    return [res for line in log_lines if (res := parse_log_line(line))]

# pytest 유닛 테스트
def test_parse_log_line():
    line = "ERROR 404: Not Found"
    result = parse_log_line(line)
    assert result["code"] == 404
    assert result["message"] == "Not Found"

 

목적없는 단순 리팩토링 요청과 비교하여 답변이 크게 향상된 것을 확인 할 수 있습니다.

 

◼︎ Wrap-up

  • LLM은 "왜 리팩토링을하는지"를 스스로 판단하지 못합니다.
  • 목적을 명시적으로 제시하면, 산만한 코드 대신 운영 환경에 바로 적용 가능한 리팩토링 결과가 나옵니다.
  • 특히 우리 부서의 프로젝트처럼 테스트 코드가 부족하고, 리뷰어가 부족한 환경에서는 목적 기반 리팩토링이 효율적인 생존 전략이 될 수 있습니다.

 

다음 글에서는 목적 기반 리팩토링의 몇 가지 사례에 대해 알아보겠습니다.

 

좋아요 & 댓글 많이 남겨주세요~~