테마 전환

Cursor로 코드를 리팩터링하시나요? 효율을 두 배로 높이는 실전 팁

Easton editorial illustration: signal tracing instrument

프로젝트를 넘겨받아 코드 파일을 열었는데, 함수 하나가 200줄이 넘고 if-else가 일곱여덟 겹으로 중첩되어 있으며 변수 이름도 data1, temp2 같은 식이라고 해보죠. 속으로 ‘이걸 누가 짰지?’라고 생각하며 git blame을 확인했는데, 석 달 전의 나 자신이었다면 어떨까요……

리팩터링하자니 버그가 생길까 걱정되고, 그대로 두자니 요구 사항을 수정할 때마다 지뢰밭에서 춤추는 기분입니다.

코드 리팩터링에서는 Cursor의 기능이 생각보다 훨씬 믿음직할 수 있습니다. 이번 글에서는 Cursor를 활용해 이른바 ‘스파게티 코드’를 사람이 읽을 수 있는 형태로 바꾸는 방법을 알아보겠습니다.

Cursor 리팩터링은 무엇이 강력할까요?

구체적인 사용법에 앞서, Cursor가 리팩터링에 특히 적합하다고 생각하는 이유부터 살펴보겠습니다.

전체 코드베이스를 실제로 이해합니다

구문 분석에만 의존하는 기존 IDE의 리팩터링 도구와 달리 Cursor는 코드를 실제로 ‘이해’합니다. 함수 이름을 바꾸면 호출부를 수정하는 데 그치지 않고, 해당 함수가 전체 시스템에서 어떤 역할을 하는지 파악해 더 합리적인 리팩터링 방안을 제안합니다.

Agent 모드로 여러 파일을 리팩터링할 수 있습니다

제가 가장 좋아하는 기능입니다. 예를 들어 유틸리티 함수를 A 파일에서 B 파일로 옮긴다고 해보겠습니다. 기존 방식이라면 코드 복사 → 기존 코드 삭제 → 모든 import 업데이트 → 빠뜨린 곳이 없기를 기도하는 순서였을 겁니다.

Cursor Agent 모드에서는 ‘이 함수를 utils.js로 옮겨 줘’라고 말하기만 하면 모든 참조 업데이트까지 자동으로 처리합니다. 처음 사용했을 때는 정말 놀랐습니다.

Plan 모드: 먼저 계획하고 실행합니다

복잡한 리팩터링을 위한 Plan 모드도 있습니다. Shift+Tab으로 활성화하면 먼저 코드베이스를 분석하고 몇 가지 확인 질문을 한 뒤, 수정할 파일과 수정 방법을 담은 상세 실행 계획을 제시합니다. 계획에 동의해야 실제 작업을 시작합니다.

숙련된 개발자가 미리 코드 리뷰를 해 주는 것과 비슷해서 많은 문제를 사전에 피할 수 있습니다.

실전 1: 함수를 추출해 코드의 책임을 명확히 하기

실제 사례를 살펴보겠습니다.

이전에 유지보수하던 주문 처리 시스템에는 다음과 같은 함수가 있었습니다(간략화한 버전입니다).

function processOrder(order) {
  // 验证订单
  if (!order.items || order.items.length === 0) {
    throw new Error('订单为空');
  }
  if (!order.userId) {
    throw new Error('缺少用户信息');
  }

  // 计算价格
  let total = 0;
  for (let item of order.items) {
    let price = item.price;
    if (item.discount) {
      price = price * (1 - item.discount);
    }
    total += price * item.quantity;
  }

  // 检查库存
  for (let item of order.items) {
    const stock = db.getStock(item.productId);
    if (stock < item.quantity) {
      throw new Error(`${item.name} 库存不足`);
    }
  }

  // 创建订单记录
  const orderRecord = {
    id: generateId(),
    userId: order.userId,
    items: order.items,
    total: total,
    status: 'pending',
    createdAt: new Date()
  };

  db.saveOrder(orderRecord);
  return orderRecord;
}

이 함수는 40줄이 넘으며 검증, 계산, 재고 확인, 저장이라는 네 가지 일을 합니다. 로직 자체는 복잡하지 않지만 읽기 피곤하고 테스트하기도 어렵습니다.

Cursor로 함수 추출하기

저는 다음과 같이 작업했습니다.

  1. 검증 로직 부분을 선택합니다(앞부분의 if 문들).
  2. 단축키 Cmd/Ctrl + K를 눌러 Cursor 편집 패널을 엽니다.
  3. 명령을 입력합니다: validateOrder 함수로 추출

Cursor는 자동으로 다음 코드를 생성합니다.

function validateOrder(order) {
  if (!order.items || order.items.length === 0) {
    throw new Error('订单为空');
  }
  if (!order.userId) {
    throw new Error('缺少用户信息');
  }
}

그리고 기존 코드를 validateOrder(order);로 바꿉니다.

같은 방식으로 calculateTotal, checkStock, createOrderRecord 함수도 추출했습니다.

최종 코드는 다음과 같이 바뀝니다.

function processOrder(order) {
  validateOrder(order);
  const total = calculateTotal(order.items);
  checkStock(order.items);
  const orderRecord = createOrderRecord(order, total);
  db.saveOrder(orderRecord);
  return orderRecord;
}

이제 함수는 6줄뿐이지만 비즈니스 흐름을 한눈에 이해할 수 있습니다.

주의 사항(실수 방지 가이드)

1. AI에 추출 의도를 명확히 전달하세요

코드를 선택하고 단축키만 누르면 Cursor가 원하는 작업을 정확히 추측하지 못할 수 있습니다. ‘검증 함수로 추출’ 또는 ‘이 계산 로직을 별도 함수로 분리’처럼 명확하게 말하는 것이 좋습니다.

2. 함수 이름을 확인하세요

AI가 만든 함수 이름은 handleData, processItems처럼 지나치게 일반적일 때가 있습니다. 이름이 명확하지 않다면 calculateOrderTotal, validateUserPermissions처럼 더 구체적으로 직접 바꾸세요.

3. 매개변수와 반환값을 확인하세요

추출이 끝나면 함수에 필요한 매개변수가 빠지거나 불필요한 값이 전달되지는 않았는지 확인합니다. AI가 사용하지 않는 변수까지 매개변수로 넘기는 경우가 있습니다.

실전 2: 중첩 로직을 최적화해 코드를 평탄하게 만들기

중첩이 지나치게 깊은 코드도 흔한 문제입니다.

다음 권한 검사 코드를 예로 들어보겠습니다.

function canUserEditPost(user, post) {
  if (user) {
    if (user.role === 'admin') {
      return true;
    } else {
      if (post.authorId === user.id) {
        if (post.status === 'draft') {
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    }
  } else {
    return false;
  }
}

이런 ‘화살표형’ 코드는 읽기가 특히 어렵습니다. 논리를 파악하려면 중첩을 한 단계씩 따라가야 합니다.

Cursor에 최적화 맡기기

저는 Chat 모드를 바로 사용합니다.

  1. 이 코드를 선택합니다.
  2. Cursor Chat을 엽니다(Cmd/Ctrl + L).
  3. 이 코드는 중첩이 너무 깊어. 조기 반환(early return)을 사용해 리팩터링해 줘라고 입력합니다.

Cursor가 제시한 최적화 버전은 다음과 같습니다.

function canUserEditPost(user, post) {
  if (!user) return false;
  if (user.role === 'admin') return true;
  if (post.authorId !== user.id) return false;
  return post.status === 'draft';
}

코드가 17줄에서 5줄로 줄었고, 로직도 한눈에 들어옵니다.

최적화 기법 요약

조기 반환(Early Return)
조건을 만족하지 않으면 즉시 return해 깊은 중첩을 피합니다.

가드 절(Guard Clauses)
예외 상황과 경계 조건을 함수 시작 부분에서 먼저 처리합니다.

조건식 추출
조건이 복잡하다면 다음처럼 별도 함수로 추출할 수 있습니다.

function isPostEditable(post) {
  return post.status === 'draft';
}

function isPostOwner(user, post) {
  return post.authorId === user.id;
}

이렇게 하면 핵심 로직이 더 명확해집니다.

실전 3: 타입 주석을 추가해 코드 안전성 높이기

JavaScript나 Python처럼 타입 시스템을 필수로 사용하지 않는 언어에서는 리팩터링 중 문제가 생기기 쉽습니다. 함수 반환값을 변경해도 호출부에서는 이를 알아채지 못할 수 있습니다.

이럴 때 Cursor에 타입 주석을 추가하도록 요청하면 유용합니다.

TypeScript 타입 추론

다음과 같은 JavaScript 함수가 있다고 가정해 보겠습니다.

function getUserInfo(userId) {
  const user = db.getUser(userId);
  return {
    name: user.name,
    email: user.email,
    age: calculateAge(user.birthDate)
  };
}

이 함수에 TypeScript 타입을 추가하려면 다음과 같이 진행합니다.

  1. 함수를 선택합니다.
  2. Cursor Chat에 이 함수에 TypeScript 타입 주석을 추가해 줘라고 입력합니다.

Cursor는 코드 문맥을 분석해 다음과 같이 생성합니다.

interface UserInfo {
  name: string;
  email: string;
  age: number;
}

function getUserInfo(userId: string): UserInfo {
  const user = db.getUser(userId);
  return {
    name: user.name,
    email: user.email,
    age: calculateAge(user.birthDate)
  };
}

매개변수와 반환값 타입을 추론할 뿐 아니라 UserInfo 인터페이스도 정의해 줍니다.

Python Type Hints

Python도 마찬가지입니다. 다음과 같은 코드가 있다고 해보겠습니다.

def calculate_discount(price, user_level):
    if user_level == 'vip':
        return price * 0.8
    elif user_level == 'premium':
        return price * 0.9
    else:
        return price

Cursor에 타입을 추가하도록 요청하면 다음과 같이 바뀝니다.

def calculate_discount(price: float, user_level: str) -> float:
    if user_level == 'vip':
        return price * 0.8
    elif user_level == 'premium':
        return price * 0.9
    else:
        return price

타입 힌트가 있으면 IDE의 코드 자동 완성 품질이 좋아지고, 리팩터링 중 타입 호환성도 검사할 수 있습니다.

실전 4: Agent 모드로 대규모 리팩터링하기

앞서 살펴본 내용은 모두 단일 파일 안에서의 리팩터링이었습니다. 그렇다면 여러 파일에 걸친 리팩터링은 어떻게 해야 할까요?

예를 들어 UserService 클래스를 services/user.js에서 services/user/UserService.js로 옮기고, 관련 보조 함수도 별도 파일로 분리한다고 해보겠습니다.

이런 파일 간 리팩터링을 수동으로 처리하면 빠뜨리는 부분이 생기기 쉽고, 수정 후 코드가 실행되지 않을 가능성도 큽니다.

Agent 모드 시작하기

이럴 때 Cursor의 Agent 모드를 사용하면 됩니다.

  1. Cursor Chat을 엽니다.
  2. ‘Agent’ 모드를 클릭합니다(또는 @agent 입력).
  3. 리팩터링 목표를 설명합니다.
UserService 클래스를 독립 모듈로 리팩터링해 줘:
- services/user/UserService.js로 이동
- 보조 함수 formatUserData와 validateEmail을 services/user/utils.js로 이동
- 이 함수들을 참조하는 모든 곳 업데이트

Plan 모드로 리팩터링을 더 안전하게 제어하기

복잡한 리팩터링에는 Plan 모드를 권합니다. Agent 입력창에서 Shift+Tab을 누르면 Cursor가 Plan 모드로 전환됩니다.

먼저 코드베이스를 분석한 뒤 다음과 같은 실행 계획을 제시합니다.

📋 리팩터링 계획

1. 새 파일 구조 생성
   - services/user/UserService.js
   - services/user/utils.js

2. UserService 클래스 이동
   - services/user.js에서 services/user/UserService.js로 이동
   - export default UserService 추가

3. 보조 함수 이동
   - formatUserData → services/user/utils.js
   - validateEmail → services/user/utils.js

4. 참조 업데이트(업데이트할 파일 5개 감지)
   - controllers/userController.js
   - routes/userRoutes.js
   - tests/userService.test.js
   - ...

실행하시겠습니까? (y/n)

이 계획을 검토하고 직접 수정할 수도 있습니다. 문제가 없으면 y를 입력하고, Cursor가 계획대로 실행하도록 합니다.

모든 과정은 자동화되며 import 문도 함께 업데이트됩니다.

Agent 모드가 적합한 상황

  • 클래스/함수/변수 이름 변경(여러 파일에 걸친 작업)
  • 모듈 분리 또는 병합
  • 코드를 새 파일 구조로 이전
  • 특정 패턴 일괄 변경(예: 모든 varconst로 변경)

리팩터링이 끝났다면 반드시 검증하세요

AI가 아무리 똑똑해도 리팩터링한 코드는 직접 검증해야 합니다.

제 체크리스트

1. 모든 테스트 실행

npm test

테스트가 실패한다면 먼저 코드 문제인지, 테스트를 업데이트해야 하는지 확인합니다.

2. 타입 오류 확인(TypeScript 프로젝트)

npm run type-check

3. AI의 변경 사항 코드 리뷰

git diff로 AI가 무엇을 수정했는지 확인하세요. 원하지 않은 부분까지 바꾸는 경우가 있습니다.

4. 핵심 경로 수동 테스트

특히 비즈니스 로직과 관련된 리팩터링이라면 주요 흐름을 반드시 직접 실행해 봅니다.

5. 빠뜨린 부분이 없는지 확인

기존 함수명이나 변수명을 검색해 모두 업데이트되었는지 확인합니다.

AI 리팩터링으로 인한 버그를 피하는 방법

작은 단위로 반복하기

한 번에 너무 많은 부분을 리팩터링하지 마세요. 함수 하나를 수정하고 테스트가 통과하면 다음 함수로 넘어갑니다.

git 기록을 명확하게 유지하기

리팩터링 하나를 끝낼 때마다 커밋하세요. 문제가 생겨도 빠르게 되돌릴 수 있습니다.

git add .
git commit -m "refactor: 提取订单验证逻辑为独立函数"

AI에 변경 이유를 설명하도록 요청하기

AI가 수정한 이유를 이해하기 어렵다면 다음과 같이 물어보세요.

왜 이 매개변수를 선택 항목으로 바꿨어?

AI가 추론 과정을 설명해 주므로 변경이 합리적인지 판단하는 데 도움이 됩니다.

모범 사례: Cursor 리팩터링의 효율 높이기

그동안 사용하며 효율을 높일 수 있는 몇 가지 방법을 정리했습니다.

1. 먼저 방법을 논의하고 실행하기

처음부터 AI에 코드 수정을 맡기지 마세요. 먼저 Ask 모드(Cursor Chat 기본 모드)에서 리팩터링 방안을 논의합니다.

이 200줄짜리 함수를 리팩터링하고 싶은데, 어떤 방법을 추천해?

AI가 여러 아이디어를 제시합니다. 방안이 괜찮다고 판단되면 Agent 모드로 전환해 실행합니다.

2. @로 문맥 참조 활용하기

리팩터링이 여러 파일에 걸쳐 있다면 @로 관련 코드를 참조합니다.

@services/user.js @controllers/userController.js
사용자 인증 로직을 controller에서 service로 옮기되 인터페이스는 유지하고 싶어

이렇게 하면 AI가 의도를 더 정확하게 파악할 수 있습니다.

3. 작업 복잡도 조절하기

  • AI가 한 번에 해결할 수 있다면 작업 범위를 조금 더 넓힙니다.
  • 자주 실패한다면 작업을 더 작은 단위로 나눕니다.

예를 들면 다음과 같습니다.

  • 너무 단순함: ‘이 함수 추출’
  • 적절함: ‘사용자 인증 모듈을 리팩터링하고 로그인, 회원가입, 비밀번호 찾기 파일로 분리’
  • 너무 복잡함: ‘전체 백엔드 시스템의 권한 모듈 리팩터링’

4. 리팩터링 후 AI에 요약 생성 요청하기

리팩터링이 끝나면 AI에 PR 설명을 작성하도록 요청할 수 있습니다.

이번 리팩터링에서 변경한 내용을 요약해 줘

AI가 구조화된 요약을 제공하며, Pull Request에 바로 활용할 수 있습니다.

5. 복잡한 리팩터링 계획 저장하기

대규모 리팩터링이라면 Plan 모드에서 만든 계획을 .cursor/plans/ 디렉터리에 저장할 수 있습니다.

다음과 같은 장점이 있습니다.

  • 다른 팀원도 리팩터링 방향을 확인할 수 있습니다.
  • 작업이 중단되어도 이어서 진행할 수 있습니다.
  • 다음에 비슷한 리팩터링을 할 때 참고할 수 있습니다.

마무리하며

몇 달 동안 Cursor로 코드를 리팩터링하면서 가장 크게 느낀 점은, AI가 정말 많은 ‘번거롭고 반복적인 일’을 대신해 주어 아키텍처와 설계를 고민하는 데 더 많은 시간을 쓸 수 있다는 것입니다.

하지만 AI는 도우미이지 모든 것을 책임지는 존재가 아닙니다. 리팩터링 방안을 어떻게 설계할지, 어떤 코드를 분리할지, 최종 코드가 기대에 맞는지를 결정하는 권한은 여전히 개발자에게 있습니다.

마지막으로 작은 부분부터 시작해 보기를 권합니다. 처음부터 핵심 모듈을 리팩터링하지 말고, 중요도가 낮은 유틸리티 함수부터 연습해 보세요. Cursor의 리팩터링 기능에 익숙해진 뒤 점차 범위를 넓히면 됩니다.

아무리 좋은 도구라도 제대로 사용할 줄 알아야 하니까요.

Cursor로 코드를 리팩터링해 본 경험이 있나요? 댓글로 공유해 주세요.

Cursor로 코드를 리팩터링하는 일반적인 절차

단일 파일의 함수 추출부터 여러 파일에 걸친 Agent 리팩터링까지의 전체 단계

⏱️ Estimated time: 30 min

  1. 1

    Step 1: 단일 파일에서 함수 추출

    추출할 코드 블록 선택 → Cmd/Ctrl+K로 편집 패널 열기 → ‘xxx 함수로 추출’ 입력 → 함수 이름, 매개변수, 반환값 확인. 40줄이 넘는 긴 함수에 적합합니다.
  2. 2

    Step 2: 중첩 로직 평탄화

    중첩 코드 선택 → Cmd/Ctrl+L로 Chat 열기 → ‘조기 반환으로 리팩터링’ 입력 → 가드 절 스타일 코드 확인. 복잡한 조건은 별도 함수로 다시 추출할 수 있습니다.
  3. 3

    Step 3: 타입 주석 추가

    함수 선택 → Chat에 ‘TypeScript 타입 주석 추가’ 또는 ‘Python type hints 추가’ 입력 → AI가 매개변수와 반환값 타입을 추론하고 필요하면 인터페이스를 정의합니다.
  4. 4

    Step 4: 여러 파일 리팩터링에는 Agent 사용

    Chat을 열고 Agent 모드로 전환 → 목표(이동/이름 변경/모듈 분리) 설명 → 복잡한 작업은 Shift+Tab으로 Plan 모드를 사용해 계획을 먼저 확인한 뒤 실행합니다.
  5. 5

    Step 5: 검증 및 커밋

    npm test / type-check 실행 → git diff로 변경 사항 검토 → 핵심 경로 수동 테스트 → ‘refactor: 주문 검증 로직을 별도 함수로 추출’처럼 작은 단위로 commit합니다.

FAQ

Cursor 리팩터링은 기존 IDE 리팩터링과 무엇이 다른가요?
기존 IDE는 주로 구문 분석을 바탕으로 이름 변경과 이동을 처리하지만, Cursor는 넓은 코드 영역의 의미와 프로젝트 내 역할을 이해해 비즈니스 맥락에 더 적합한 리팩터링을 제안합니다. Agent는 여러 파일의 참조를 수정할 수 있고 Plan 모드는 먼저 계획을 제시한 뒤 실행하므로 대규모 리팩터링에도 적합합니다.
함수를 추출할 때 AI가 이름을 정확히 짓지 못하면 어떻게 하나요?
생성 후 함수 이름을 calculateOrderTotal이나 validateUserPermissions처럼 더 구체적인 이름으로 직접 바꾸세요. 매개변수가 너무 많거나 빠진 것은 없는지도 확인하고, 필요하면 AI에 ‘실제로 사용하는 매개변수만 남겨 줘’라고 요청합니다.
여러 파일에 걸친 리팩터링은 어떻게 하나요?
Agent 모드를 사용해 Chat에 목표를 명확히 작성합니다. 예를 들어 UserService를 services/user/UserService.js로 옮기고, 보조 함수는 utils.js로 이동하며, 모든 참조를 업데이트하라고 요청할 수 있습니다. 복잡한 작업은 Shift+Tab으로 Plan 모드로 전환해 계획을 먼저 검토한 뒤 실행을 승인합니다.
리팩터링 후 버그가 생기지 않았는지 어떻게 검증하나요?
전체 테스트(npm test)를 실행하고 TypeScript 프로젝트라면 type-check도 수행합니다. git diff로 AI의 변경 내용을 확인하고 핵심 비즈니스 경로를 직접 실행해 보세요. 단계마다 명확한 단위로 커밋하면 문제가 생겼을 때 쉽게 되돌릴 수 있습니다.
Plan 모드에서 만든 계획을 저장할 수 있나요?
.cursor/plans/ 디렉터리에 저장할 수 있습니다. 팀이 함께 확인하거나 중단 후 이어서 작업하고, 다음에 비슷한 리팩터링을 할 때 참고하기에도 좋습니다.

3분 읽기 · 게시일: 2026년 1월 22일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog