레거시 코드 현대화, 버그 추적, 성능 최적화... 개발자의 일상이지만 가장 시간이 많이 걸리는 작업입니다.
Claude Code가 이 모든 것을 자동화합니다.
이번 가이드에서는 실전 예제와 함께 Claude Code로 코드 리팩토링과 디버깅을 마스터하는 방법을 소개합니다.

왜 Claude Code로 리팩토링&디버깅인가?
전통적인 방법의 한계
수동 리팩토링:
- 시간 소모적 (콜백 → async/await 변환에 수 시간)
- 실수 위험 (한 곳만 빼먹어도 버그)
- 컨텍스트 파악 어려움 (10개 이상 파일에 걸친 변경)
전통적인 디버깅:
- 스택 트레이스 수동 분석
- 콘솔 로그 반복 추가
- 원인 파악에만 수 시간 소요
Claude Code의 강점
1. 대규모 컨텍스트 이해
- 200K+ 토큰 윈도우 (최대 1M 토큰)
- 수백 개 파일 동시 분석
- 전체 코드베이스의 맥락 파악
2. 지능적 분석
- 코드의 의도를 이해하고 리팩토링
- 단순 find-and-replace가 아님
- 근본 원인까지 추적
3. 안전한 실험
- 자동 체크포인트로 언제든 되돌리기
- Plan Mode로 위험 없이 분석
- 테스트 자동 실행으로 검증
코드 리팩토링 실전 예제
예제 1: 콜백 지옥 → Async/Await
Before: 콜백 지옥
// user.js
function fetchUserData(userId, callback) {
getUser(userId, function(err, user) {
if (err) {
callback(err);
} else {
getProfile(user.id, function(err, profile) {
if (err) {
callback(err);
} else {
getPosts(user.id, function(err, posts) {
if (err) {
callback(err);
} else {
callback(null, { user, profile, posts });
}
});
}
});
}
});
}
Claude Code 명령어
user.js의 fetchUserData 함수를 async/await로 리팩토링해줘.
에러 핸들링도 try-catch로 개선하고, 가독성을 높여줘.
After: 깔끔한 Async/Await
// user.js
async function fetchUserData(userId) {
try {
const user = await getUser(userId);
const profile = await getProfile(user.id);
const posts = await getPosts(user.id);
return { user, profile, posts };
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}
개선 사항:
- 중첩 제거 (5단계 → 1단계)
- 에러 핸들링 통합
- 가독성 대폭 향상
예제 2: React 클래스 → 함수형 컴포넌트 + Hooks
Before: Class Component
// UserProfile.js
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true,
error: null
};
}
componentDidMount() {
this.fetchUser();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
}
}
fetchUser = async () => {
try {
this.setState({ loading: true });
const response = await fetch(`/api/users/${this.props.userId}`);
const user = await response.json();
this.setState({ user, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
}
render() {
const { user, loading, error } = this.state;
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
}
export default UserProfile;
Claude Code 명령어
UserProfile.js를 함수형 컴포넌트와 Hooks로 변환해줘.
useState, useEffect를 사용하고 기존 기능은 모두 유지해줘.
After: Functional Component + Hooks
// UserProfile.js
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
export default UserProfile;
개선 사항:
- 라인 수 감소 (45줄 → 28줄)
- 현대적 React 패턴
- 더 간결한 상태 관리
예제 3: 대규모 멀티파일 리팩토링
시나리오: API 엔드포인트 변경
여러 파일에 걸쳐 있는 /api/v1/users → /api/v2/users 변경
파일 목록:
src/services/userService.jssrc/components/UserList.jssrc/components/UserProfile.jssrc/utils/apiClient.jstests/userService.test.js
Claude Code 명령어
프로젝트 전체에서 `/api/v1/users` 엔드포인트를
`/api/v2/users`로 변경해줘.
조건:
1. 모든 파일을 찾아서 일괄 변경
2. 테스트도 함께 업데이트
3. 변경 후 테스트 실행
4. 모든 테스트가 통과하면 커밋
Claude의 작업 프로세스
[1/5] 프로젝트 전체 검색 중...
- 5개 파일에서 `/api/v1/users` 발견
[2/5] 파일 변경 중...
✓ src/services/userService.js
✓ src/components/UserList.js
✓ src/components/UserProfile.js
✓ src/utils/apiClient.js
✓ tests/userService.test.js
[3/5] 테스트 실행 중...
npm test
✓ 23 tests passed
[4/5] Git 커밋 생성 중...
git add .
git commit -m "Update API endpoints from v1 to v2"
[5/5] 완료!
소요 시간: 2분 (수동 작업 시 30분 이상)
디버깅 실전 예제
예제 4: TypeError 추적 및 수정
에러 발생
$ npm test
TypeError: Cannot read property 'name' of undefined
at getUserInfo (user.js:15:25)
at processUser (index.js:42:18)
at Object.<anonymous> (test.js:10:5)
Claude Code 명령어
npm test를 실행했는데 TypeError가 발생해.
에러: "Cannot read property 'name' of undefined"
user.js:15 라인에서 발생.
근본 원인을 찾아서 수정해줘.
Claude의 분석 프로세스
1단계: 스택 트레이스 분석
[분석] 스택 트레이스를 따라가보겠습니다:
- test.js:10 → processUser 호출
- index.js:42 → getUserInfo 호출
- user.js:15 → user.name 접근 시 에러
user 객체가 undefined인 것 같습니다.
2단계: 코드 검토
// user.js:15
function getUserInfo(user) {
return {
name: user.name, // ← 여기서 에러 발생
email: user.email
};
}
3단계: 근본 원인 파악
[근본 원인]
processUser 함수가 null/undefined user를 전달하고 있습니다.
index.js:42를 확인하니 데이터베이스 쿼리가 빈 결과를 반환할 때
null을 전달하고 있었습니다.
4단계: 수정 제안
두 가지 수정 방법을 제안합니다:
**방법 1: Null 체크 추가**
function getUserInfo(user) {
if (!user) {
throw new Error('User not found');
}
return {
name: user.name,
email: user.email
};
}
**방법 2: Optional Chaining**
function getUserInfo(user) {
return {
name: user?.name ?? 'Unknown',
email: user?.email ?? 'N/A'
};
}
방법 1은 명시적 에러 처리, 방법 2는 기본값 제공입니다.
어떤 방법을 선호하시나요?
사용자 선택 후 적용
방법 1로 해줘. 테스트도 업데이트해서 null user 케이스를 커버해줘.
After: 수정 완료
// user.js
function getUserInfo(user) {
if (!user) {
throw new Error('User not found');
}
return {
name: user.name,
email: user.email
};
}
// test.js (새로운 테스트 추가)
describe('getUserInfo', () => {
it('should throw error when user is null', () => {
expect(() => getUserInfo(null)).toThrow('User not found');
});
it('should throw error when user is undefined', () => {
expect(() => getUserInfo(undefined)).toThrow('User not found');
});
});
결과:
✓ 25 tests passed (including 2 new tests)
예제 5: 메모리 누수 디버깅
증상
프로덕션 서버에서 메모리 사용량이 계속 증가하고 있습니다.
시간당 약 100MB씩 증가 중.
Claude Code 명령어
Node.js 서버에서 메모리 누수가 의심됩니다.
메모리 프로파일링을 통해 원인을 찾아줘.
힌트:
- WebSocket 연결 사용 중
- Redis 캐싱 사용 중
- 주기적인 크론 작업 실행 중
Claude의 조사 프로세스
1단계: 프로파일링 설정
# 힙 스냅샷 생성
node --inspect --heap-prof app.js
2단계: 코드 분석
[발견된 문제]
1. WebSocket 핸들러에서 리스너 제거 누락:
server/websocket.js:45
2. Redis 연결 풀 미해제:
server/cache.js:78
3. 크론 작업에서 누적되는 타이머:
server/cron.js:23
3단계: 수정 적용
문제 1: WebSocket 리스너 누수
// Before
socket.on('message', handleMessage);
socket.on('disconnect', () => {
console.log('disconnected');
// removeListener 호출 없음!
});
// After
socket.on('message', handleMessage);
socket.on('disconnect', () => {
socket.removeAllListeners('message');
console.log('disconnected');
});
문제 2: Redis 연결 미해제
// Before
async function getData(key) {
const client = redis.createClient();
await client.connect();
const data = await client.get(key);
return data;
// 연결 종료 안 함!
}
// After
async function getData(key) {
const client = redis.createClient();
try {
await client.connect();
const data = await client.get(key);
return data;
} finally {
await client.disconnect();
}
}
문제 3: 크론 타이머 누적
// Before
setInterval(() => {
runCleanupTask();
}, 60000);
// After
let cleanupInterval;
function startCron() {
if (cleanupInterval) {
clearInterval(cleanupInterval);
}
cleanupInterval = setInterval(() => {
runCleanupTask();
}, 60000);
}
process.on('SIGTERM', () => {
clearInterval(cleanupInterval);
});
결과:
메모리 사용량 안정화:
- 이전: 시간당 +100MB
- 수정 후: 시간당 +5MB (정상 범위)
예제 6: 비동기 경합 조건 (Race Condition) 디버깅
증상
간헐적으로 사용자 프로필 업데이트가 덮어씌워집니다.
재현이 어렵고 로그에도 명확한 에러가 없습니다.
Claude Code 명령어
사용자가 프로필을 빠르게 여러 번 업데이트하면
마지막 변경이 반영되지 않는 경우가 있어.
비동기 경합 조건이 의심되는데 원인을 찾아줘.
src/controllers/profileController.js를 분석해줘.
Claude의 분석
의심 코드:
// profileController.js
async function updateProfile(req, res) {
const { userId, updates } = req.body;
// 현재 프로필 가져오기
const currentProfile = await getProfile(userId);
// 업데이트 적용
const updatedProfile = { ...currentProfile, ...updates };
// 저장
await saveProfile(userId, updatedProfile);
res.json(updatedProfile);
}
Claude의 설명:
[경합 조건 발견!]
문제: 동시에 두 요청이 들어오면:
시간 t0: 요청A - getProfile() 호출
시간 t1: 요청B - getProfile() 호출
시간 t2: 요청A - saveProfile() 호출 (name 변경)
시간 t3: 요청B - saveProfile() 호출 (email 변경)
결과: 요청B가 요청A의 name 변경을 덮어씁니다!
해결책: 낙관적 잠금(Optimistic Locking) 또는
비관적 잠금(Pessimistic Locking) 사용
수정: 낙관적 잠금 구현
// profileController.js
async function updateProfile(req, res) {
const { userId, updates, version } = req.body;
try {
// 트랜잭션 시작
const result = await db.transaction(async (trx) => {
// 버전과 함께 프로필 가져오기
const currentProfile = await trx('profiles')
.where({ user_id: userId, version })
.first();
if (!currentProfile) {
throw new ConflictError('Profile was updated by another request');
}
// 버전 증가하며 업데이트
const updatedProfile = {
...currentProfile,
...updates,
version: currentProfile.version + 1
};
await trx('profiles')
.where({ user_id: userId, version })
.update(updatedProfile);
return updatedProfile;
});
res.json(result);
} catch (error) {
if (error instanceof ConflictError) {
res.status(409).json({
error: 'Please refresh and try again'
});
} else {
throw error;
}
}
}
프론트엔드 재시도 로직 추가:
// frontend/api.js
async function updateProfile(userId, updates, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const profile = await getProfile(userId);
const response = await fetch('/api/profile', {
method: 'PUT',
body: JSON.stringify({
userId,
updates,
version: profile.version
})
});
if (response.status === 409) {
// 충돌 발생, 재시도
continue;
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
}
고급 기법
체크포인트를 활용한 안전한 리팩토링
시나리오: 대규모 아키텍처 변경
전체 프로젝트를 MVC에서 Hexagonal Architecture로 리팩토링해줘.
단계별로 진행하고, 각 단계마다 테스트를 실행해줘.
문제가 생기면 이전 단계로 되돌릴 수 있게 해줘.
Claude의 작업 방식:
[체크포인트 1] 현재 상태 저장
[단계 1] 도메인 레이어 분리
✓ 테스트 통과
[체크포인트 2] 도메인 레이어 분리 완료
[단계 2] 애플리케이션 레이어 추가
✗ 2개 테스트 실패
[되돌리기] 체크포인트 2로 복원
[분석] 실패 원인 파악
[재시도] 수정된 방법으로 단계 2 진행
✓ 테스트 통과
체크포인트 명령어:
/rewind- 이전 체크포인트로 되돌리기Esc두 번 - 되돌리기 메뉴 열기
Plan Mode로 위험 없이 분석
언제 사용?
- 대규모 변경 전 영향도 파악
- 여러 접근 방법 비교
- 위험 요소 사전 식별
사용 방법:
- Plan Mode 활성화
Shift + Tab (Plan Mode로 전환)- 분석 요청
인증 시스템을 JWT에서 OAuth2로 변경하려고 해. 영향을 받는 파일들과 변경 순서를 분석해줘.- 결과 검토
[영향 분석] 변경 필요 파일: 12개 - 인증 미들웨어 (4개) - 라우터 (3개) - 테스트 (5개) 위험도: 높음 - 세션 저장소 마이그레이션 필요 - 기존 토큰 무효화 전략 필요 권장 순서: 1. OAuth2 클라이언트 설정 2. 병렬 인증 지원 (JWT + OAuth2) 3. 단계적 마이그레이션 4. JWT 폐기- 실행 모드로 전환
Shift + Tab (Normal Mode로 전환) 좋아, 1단계부터 시작해줘.
멀티파일 리팩토링 전략
대규모 프로젝트 예시: API 버전 마이그레이션
REST API를 GraphQL로 전환해줘.
작업 범위:
- REST 엔드포인트: 30개
- 컨트롤러: 15개
- 테스트: 45개
조건:
1. 기존 REST API 유지 (하위 호환성)
2. GraphQL 스키마 생성
3. 리졸버 구현
4. 테스트 마이그레이션
5. 문서 업데이트
Claude의 접근 방법:
[Phase 1] 분석
- REST 엔드포인트 매핑
- GraphQL 스키마 설계
- 의존성 그래프 작성
[Phase 2] 인프라 구축
- GraphQL 서버 설정
- 미들웨어 통합
- 인증/인가 레이어
[Phase 3] 점진적 마이그레이션
- 그룹 1: User 관련 API (10개)
- 그룹 2: Product 관련 API (12개)
- 그룹 3: Order 관련 API (8개)
[Phase 4] 검증
- 통합 테스트
- 성능 테스트
- 문서 생성
베스트 프랙티스
리팩토링 체크리스트
| 단계 | 내용 | 완료 |
|---|---|---|
| 1. 준비 | 테스트 커버리지 확인 (90% 이상 권장) | ☐ |
| 2. 분석 | Plan Mode로 영향도 파악 | ☐ |
| 3. 계획 | 단계별 작업 순서 수립 | ☐ |
| 4. 실행 | 작은 단위로 점진적 변경 | ☐ |
| 5. 검증 | 각 단계마다 테스트 실행 | ☐ |
| 6. 문서화 | 변경사항 문서 업데이트 | ☐ |
| 7. 리뷰 | 코드 리뷰 요청 | ☐ |
디버깅 체크리스트
| 단계 | 내용 | 완료 |
|---|---|---|
| 1. 재현 | 버그 재현 단계 작성 | ☐ |
| 2. 수집 | 에러 메시지, 스택 트레이스 수집 | ☐ |
| 3. 분석 | Claude에게 상세한 컨텍스트 제공 | ☐ |
| 4. 가설 | 원인 가설 수립 | ☐ |
| 5. 검증 | 가설 테스트 | ☐ |
| 6. 수정 | 근본 원인 해결 | ☐ |
| 7. 테스트 | 회귀 테스트 추가 | ☐ |
효과적인 Claude 사용법
DO ✅
- 구체적인 컨텍스트 제공
- 예상 동작과 실제 동작 명시
- 테스트 케이스 포함
- 단계별로 진행
DON'T ❌
- 막연하게 "고쳐줘"만 요청
- 에러 메시지 없이 "작동 안 해" 말하기
- 한 번에 너무 많은 변경 요청
- 테스트 없이 프로덕션 적용
실전 워크플로우
워크플로우 1: 레거시 코드 현대화
# 1단계: 프로젝트 분석
claude
> 이 프로젝트의 기술 부채를 분석해줘.
오래된 패턴, 개선이 필요한 부분을 찾아줘.
# 2단계: 우선순위 설정
> 가장 위험한 기술 부채 3가지를 골라서
각각의 개선 계획을 세워줘.
# 3단계: 단계별 실행
> 첫 번째 항목부터 시작해줘.
콜백 기반 코드를 async/await로 변환.
# 4단계: 테스트 및 커밋
> 테스트를 실행하고, 통과하면 커밋해줘.
워크플로우 2: 버그 추적 및 수정
# 1단계: 버그 리포트
claude
> npm test를 실행하니 이런 에러가 나와:
[에러 메시지 붙여넣기]
재현 방법:
1. 로그인
2. 프로필 페이지 방문
3. 이메일 변경 시도
# 2단계: 근본 원인 분석
> 스택 트레이스를 따라가서 근본 원인을 찾아줘.
# 3단계: 수정 적용
> 근본 원인을 수정하고, 비슷한 버그가 다른 곳에도
있는지 확인해줘.
# 4단계: 회귀 테스트 추가
> 이 버그를 커버하는 테스트를 추가해줘.
워크플로우 3: 성능 최적화
# 1단계: 프로파일링
claude
> 이 API 엔드포인트가 느려.
/api/users/search
평균 응답 시간: 3초
목표: 300ms 이하
병목 지점을 찾아줘.
# 2단계: 최적화 제안
> 데이터베이스 쿼리 최적화 방법을 제안해줘.
인덱스, 쿼리 개선, 캐싱 등.
# 3단계: 적용 및 측정
> 최적화를 적용하고 벤치마크 테스트를 실행해줘.
개선 전후를 비교해줘.
자주 묻는 질문 (FAQ)
Q1. 리팩토링 중에 실수로 중요한 로직을 지웠어요. 되돌릴 수 있나요?
A: 네! Claude Code는 모든 변경마다 자동 체크포인트를 생성합니다. /rewind 명령어 또는 Esc 두 번으로 이전 상태로 되돌릴 수 있습니다.
Q2. 테스트가 없는 레거시 코드를 리팩토링하고 싶어요.
A: 먼저 Claude에게 테스트를 생성하도록 요청하세요:
이 코드에 대한 특성 테스트(characterization test)를 작성해줘.
현재 동작을 있는 그대로 검증하는 테스트야.
테스트 생성 후 안전하게 리팩토링할 수 있습니다.
Q3. Claude가 제안한 리팩토링이 마음에 안 들어요.
A: 체크포인트로 되돌리고 다른 접근을 요청하세요:
/rewind
다른 방법으로 다시 해줘. 이번에는 디자인 패턴 X를 사용해줘.
Q4. 버그를 찾았는데 같은 패턴이 다른 곳에도 있을까 걱정돼요.
A: Claude에게 패턴 검색을 요청하세요:
이 버그와 비슷한 패턴이 프로젝트 다른 곳에도 있는지 찾아줘.
[버그 코드 패턴]
Q5. 대규모 리팩토링은 어떻게 관리하나요?
A: Plan Mode를 사용하여 단계별 계획을 세우고, 각 단계를 독립적으로 실행하세요. 각 단계마다 테스트를 실행하여 안전성을 확보합니다.
Q6. 리팩토링 vs 재작성, 어떻게 판단하나요?
A: Claude에게 분석을 요청하세요:
이 코드를 리팩토링할지 재작성할지 판단해줘.
- 현재 코드 복잡도
- 테스트 커버리지
- 비즈니스 로직 복잡도
- 시간/비용 추정
Q7. 디버깅할 때 Claude에게 어떻게 정보를 제공하나요?
A: 다음 정보를 포함하세요:
- 에러 메시지 전문
- 스택 트레이스
- 재현 단계
- 예상 동작 vs 실제 동작
- 관련 코드 파일 (@-멘션 사용)
Q8. 프로덕션 버그를 빠르게 수정하려면?
A:
[긴급] 프로덕션 버그 수정 필요
에러: [에러 메시지]
영향: [사용자 수, 빈도]
재현: [단계]
최소한의 변경으로 빠른 핫픽스를 제공해줘.
근본 해결은 나중에 할게.
다음 단계
더 알아보기
리팩토링과 디버깅을 마스터했다면 다음 글들을 확인하세요:
- 11편: Claude Code GitHub Actions 자동화 - PR 자동 리뷰 설정
- 12편: CLAUDE.md 작성법 - 프로젝트별 최적화 컨텍스트 만들기
추천 리소스
결론 - AI와 함께하는 코드 개선
Claude Code는 리팩토링과 디버깅을 시간 소모적 작업에서 전략적 개선 활동으로 변모시킵니다.
핵심 요약:
- 대규모 컨텍스트로 멀티파일 리팩토링 가능
- 체크포인트로 안전한 실험과 되돌리기
- Plan Mode로 위험 없이 영향도 분석
- 지능적 디버깅으로 근본 원인까지 추적
- 자동 테스트 생성으로 안전성 보장
다음 편에서는 GitHub Actions 자동화로 PR 자동 리뷰 시스템을 구축합니다!
📌 관련 글:
- 8편: Claude Code VS Code 연동 - 개발환경 세팅 가이드
- 9편: Claude Code MCP 서버 설정법 - GitHub, 파일시스템 연동
- 11편: Claude Code GitHub Actions 자동화
[Claude] Claude Code MCP 서버 설정법 - GitHub, 파일시스템 연동
Claude Code가 단순한 코드 생성을 넘어 GitHub, 데이터베이스, Slack까지 제어할 수 있다면?MCP (Model Context Protocol)가 바로 그 해답입니다.이번 가이드에서는 MCP 서버 설정부터 실전 활용까지,Claude Code를
observerlife.tistory.com
[Claude] Claude Code VS Code 연동 - 개발환경 세팅 가이드
터미널에서만 사용하던 Claude Code, 이제 VS Code에서도 직접 사용할 수 있습니다.에디터 안에서 코드 작성, 리팩토링, 디버깅을 AI와 협업하세요.이번 가이드에서는 Claude Code VS Code 확장 설치부터 실
observerlife.tistory.com
[Claude] Claude Code GitHub Actions 자동화 - PR 자동 리뷰 설정
PR마다 수동 코드 리뷰, 테스트 실행, 문서 업데이트... 시간이 부족하신가요?Claude Code GitHub Actions로 모든 것을 자동화하세요.이번 가이드에서는 한 줄의 명령어로 PR 자동 리뷰부터 배포까지,완전
observerlife.tistory.com
🔗 참고 자료:
Sources:
'개발&프로그래밍' 카테고리의 다른 글
| [Claude] CLAUDE.md 작성법 - 프로젝트별 최적화 컨텍스트 만들기 (0) | 2026.02.13 |
|---|---|
| [Claude] Claude Code GitHub Actions 자동화 - PR 자동 리뷰 설정 (0) | 2026.02.12 |
| [Claude] Claude Code MCP 서버 설정법 - GitHub, 파일시스템 연동 (0) | 2026.02.12 |
| [Claude] Claude Code VS Code 연동 - 개발환경 세팅 가이드 (0) | 2026.02.11 |
| [Claude] Claude Code 핵심 명령어 & 슬래시 커맨드 총정리 (0) | 2026.02.11 |
댓글