[이제와서 시작하는 Claude AI 마스터하기 #15] 디버깅과 문제 해결
[이제와서 시작하는 Claude AI 마스터하기 #15] 디버깅과 문제 해결
AI와 함께하는 스마트 디버깅
Claude Code의 디버깅 능력은 단순히 에러를 찾는 것을 넘어 근본 원인을 분석하고, 해결책을 제시하며, 미래의 문제를 예방하는 방법까지 제공합니다.
지능형 디버깅 기능
1. 에러 분석 프로세스
graph TD
A[에러 발생] --> B[스택 트레이스 분석]
B --> C[코드 컨텍스트 파악]
C --> D[관련 파일 검색]
D --> E[근본 원인 추론]
E --> F[해결 방안 제시]
F --> G[코드 수정]
G --> H[테스트 검증]
E --> I[유사 문제 예방]
I --> J[리팩토링 제안]
2. 실시간 에러 진단
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 에러가 발생한 코드
async function processUserData(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const total = orders.reduce((sum, order) => sum + order.amount, 0);
return {
name: user.name,
email: user.email,
totalSpent: total,
loyaltyPoints: calculateLoyaltyPoints(total)
};
}
// 실행 시 에러
/*
TypeError: Cannot read property 'reduce' of undefined
at processUserData (index.js:4:23)
at async handleRequest (server.js:15:18)
*/
// Claude의 분석 결과
/**
* 🔍 에러 분석:
*
* 1. 직접적 원인:
* - getOrders()가 undefined를 반환
* - orders가 undefined이므로 reduce 메서드 호출 불가
*
* 2. 가능한 근본 원인:
* a) 해당 사용자의 주문이 없는 경우
* b) getOrders() 함수의 에러 처리 미흡
* c) 데이터베이스 연결 문제
* d) API 응답 형식 변경
*
* 3. 추가 발견 사항:
* - user가 null일 경우 처리 없음
* - 에러 전파 메커니즘 부재
* - 입력 검증 없음
*/
// Claude의 수정 제안
async function processUserData(userId) {
// 입력 검증
if (!userId) {
throw new Error('UserId is required');
}
try {
// null 체크 추가
const user = await getUser(userId);
if (!user) {
throw new Error(`User not found: ${userId}`);
}
// 기본값 설정으로 undefined 방지
const orders = await getOrders(user.id) || [];
// 안전한 reduce 사용
const total = orders.reduce((sum, order) => {
// 개별 order 검증
if (order && typeof order.amount === 'number') {
return sum + order.amount;
}
console.warn(`Invalid order found for user ${userId}:`, order);
return sum;
}, 0);
return {
name: user.name,
email: user.email,
totalSpent: total,
loyaltyPoints: calculateLoyaltyPoints(total)
};
} catch (error) {
// 상세한 에러 로깅
console.error(`Failed to process user data for ${userId}:`, error);
// 에러 타입별 처리
if (error.message.includes('not found')) {
throw new NotFoundError(`User ${userId} not found`);
}
throw new ProcessingError(`Failed to process user ${userId}`, { cause: error });
}
}
3. 복잡한 버그 추적
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// 간헐적으로 발생하는 메모리 누수 문제
class EventManager {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
emit(event: string, ...args: any[]) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(cb => cb(...args));
}
}
// Claude의 메모리 누수 분석
/**
* 🚨 메모리 누수 감지:
*
* 문제점:
* 1. 리스너가 제거되지 않음 (off 메서드 없음)
* 2. 컴포넌트 언마운트 시 정리 안 됨
* 3. 익명 함수 사용으로 제거 불가능
*
* 메모리 프로파일링 결과:
* - 1시간 후: 450MB (+350MB)
* - 리스너 수: 15,420개
* - GC 불가능한 객체: 8,234개
*/
// 수정된 버전
class EventManager {
private listeners: Map<string, Set<Function>> = new Map();
private listenerRefs: WeakMap<object, Set<Function>> = new WeakMap();
on(event: string, callback: Function, context?: object): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
// 컨텍스트별 추적
if (context) {
if (!this.listenerRefs.has(context)) {
this.listenerRefs.set(context, new Set());
}
this.listenerRefs.get(context)!.add(callback);
}
// 언마운트 함수 반환
return () => this.off(event, callback);
}
off(event: string, callback: Function) {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.delete(callback);
if (callbacks.size === 0) {
this.listeners.delete(event);
}
}
}
// 컨텍스트의 모든 리스너 제거
removeAllListeners(context: object) {
const contextListeners = this.listenerRefs.get(context);
if (contextListeners) {
contextListeners.forEach(callback => {
this.listeners.forEach(callbacks => {
callbacks.delete(callback);
});
});
this.listenerRefs.delete(context);
}
}
emit(event: string, ...args: any[]) {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(cb => {
try {
cb(...args);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
});
}
}
}
성능 문제 디버깅
1. 성능 병목 지점 찾기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 느린 API 엔드포인트
app.get('/api/products', async (req, res) => {
const products = await Product.findAll();
const enrichedProducts = [];
for (const product of products) {
const category = await Category.findById(product.categoryId);
const reviews = await Review.findAll({ productId: product.id });
const avgRating = reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length;
enrichedProducts.push({
...product,
category: category.name,
reviewCount: reviews.length,
avgRating
});
}
res.json(enrichedProducts);
});
// Claude의 성능 분석
/**
* ⚡ 성능 분석 결과:
*
* 실행 시간: 3.2초 (100개 제품)
*
* 병목 지점:
* 1. N+1 쿼리 문제
* - Product 쿼리: 1회
* - Category 쿼리: 100회
* - Review 쿼리: 100회
* 총 201회 DB 쿼리
*
* 2. 순차적 처리
* - for 루프로 인한 직렬 처리
* - 각 제품당 ~32ms
*
* 3. 메모리 사용
* - 모든 리뷰 데이터 로드
* - 불필요한 필드 포함
*/
// 최적화된 버전
app.get('/api/products', async (req, res) => {
// 1. 조인과 집계를 활용한 단일 쿼리
const products = await Product.findAll({
include: [
{
model: Category,
attributes: ['name']
}
],
attributes: {
include: [
[
sequelize.literal(`(
SELECT COUNT(*)
FROM reviews
WHERE reviews.product_id = Product.id
)`),
'reviewCount'
],
[
sequelize.literal(`(
SELECT AVG(rating)
FROM reviews
WHERE reviews.product_id = Product.id
)`),
'avgRating'
]
]
},
raw: false,
nest: true
});
// 2. 응답 변환 (메모리 효율적)
const response = products.map(product => ({
id: product.id,
name: product.name,
price: product.price,
category: product.Category?.name,
reviewCount: product.get('reviewCount') || 0,
avgRating: product.get('avgRating') || 0
}));
res.json(response);
});
// 성능 개선 결과:
// - 실행 시간: 3.2초 → 0.08초 (40배 개선)
// - DB 쿼리: 201회 → 1회
// - 메모리 사용: 45MB → 8MB
2. 비동기 처리 문제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 데드락이 발생하는 코드
class ResourceManager {
private locks: Map<string, Promise<void>> = new Map();
async acquireLock(resource: string): Promise<() => void> {
while (this.locks.has(resource)) {
await this.locks.get(resource);
}
let releaseLock: () => void;
const lockPromise = new Promise<void>(resolve => {
releaseLock = resolve;
});
this.locks.set(resource, lockPromise);
return releaseLock!;
}
}
// Claude의 데드락 분석
/**
* 🔒 데드락 시나리오 발견:
*
* 1. 경쟁 조건:
* - Thread A: lock('resource1') 획득
* - Thread B: lock('resource1') 대기
* - Thread A: 크래시 발생
* - Thread B: 영원히 대기 (releaseLock 호출 안 됨)
*
* 2. 순환 대기:
* - Process 1: lock('A') 후 lock('B') 시도
* - Process 2: lock('B') 후 lock('A') 시도
*/
// 개선된 버전
class ResourceManager {
private locks: Map<string, { promise: Promise<void>, timeout: NodeJS.Timeout }> = new Map();
private readonly DEFAULT_TIMEOUT = 30000; // 30초
async acquireLock(
resource: string,
timeout: number = this.DEFAULT_TIMEOUT
): Promise<() => void> {
const startTime = Date.now();
// 타임아웃 체크
while (this.locks.has(resource)) {
if (Date.now() - startTime > timeout) {
throw new Error(`Lock acquisition timeout for resource: ${resource}`);
}
try {
await Promise.race([
this.locks.get(resource)!.promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Lock wait timeout')), 1000)
)
]);
} catch (e) {
// 타임아웃 발생 시 재시도
}
}
let releaseLock: () => void;
const lockPromise = new Promise<void>(resolve => {
releaseLock = () => {
resolve();
this.releaseLock(resource);
};
});
// 자동 해제 타이머
const autoReleaseTimer = setTimeout(() => {
console.warn(`Auto-releasing stale lock for resource: ${resource}`);
this.releaseLock(resource);
}, timeout);
this.locks.set(resource, {
promise: lockPromise,
timeout: autoReleaseTimer
});
return releaseLock!;
}
private releaseLock(resource: string) {
const lock = this.locks.get(resource);
if (lock) {
clearTimeout(lock.timeout);
this.locks.delete(resource);
}
}
}
프로덕션 디버깅
1. 로그 분석 자동화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Claude가 생성한 로그 분석기
interface LogAnalyzer {
patterns: RegExp[];
anomalies: AnomalyDetector;
alerts: AlertManager;
}
class ProductionDebugger {
async analyzeLogPatterns(logs: string[]): Promise<DebugReport> {
const patterns = {
errors: /ERROR.*?$/gm,
warnings: /WARN.*?$/gm,
slowQueries: /Query took (\d+)ms/g,
memoryLeaks: /Memory usage: (\d+)MB/g,
statusCodes: /HTTP (\d{3})/g
};
const analysis = {
errorFrequency: this.countPattern(logs, patterns.errors),
warningFrequency: this.countPattern(logs, patterns.warnings),
slowQueries: this.extractSlowQueries(logs, patterns.slowQueries),
memoryTrend: this.analyzeMemoryTrend(logs, patterns.memoryLeaks),
httpErrors: this.groupStatusCodes(logs, patterns.statusCodes)
};
return {
summary: this.generateSummary(analysis),
criticalIssues: this.identifyCriticalIssues(analysis),
recommendations: this.generateRecommendations(analysis),
timelineView: this.createTimeline(logs)
};
}
private identifyCriticalIssues(analysis: any): Issue[] {
const issues: Issue[] = [];
// 에러 급증 감지
if (analysis.errorFrequency.trend === 'increasing') {
issues.push({
severity: 'critical',
type: 'error_spike',
description: 'Error rate increased by 300% in last hour',
action: 'Investigate recent deployment or infrastructure changes'
});
}
// 메모리 누수 감지
if (analysis.memoryTrend.slope > 10) {
issues.push({
severity: 'high',
type: 'memory_leak',
description: 'Memory usage growing at 10MB/hour',
action: 'Profile application memory usage and check for leaks'
});
}
return issues;
}
}
2. 분산 시스템 디버깅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 분산 트레이싱 설정
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config
data:
config.yaml: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
attributes:
actions:
- key: environment
value: production
action: insert
- key: service.version
from_attribute: app.version
action: insert
exporters:
jaeger:
endpoint: jaeger-collector:14250
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:8889"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, attributes]
exporters: [jaeger]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
3. 실시간 디버깅 도구
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Claude가 생성한 실시간 디버거
class LiveDebugger {
private debugSessions: Map<string, DebugSession> = new Map();
async attachToProcess(processId: string, filters?: DebugFilters) {
const session = new DebugSession(processId);
// 조건부 브레이크포인트
session.on('breakpoint', async (context) => {
if (filters?.condition && !eval(filters.condition)) {
return session.continue();
}
// 컨텍스트 수집
const debugInfo = {
stackTrace: context.stack,
localVariables: context.locals,
globalState: await this.collectGlobalState(),
metrics: await this.collectMetrics(),
recentLogs: await this.getRecentLogs(processId)
};
// AI 분석
const analysis = await this.analyzeDebugContext(debugInfo);
return {
suggestion: analysis.suggestion,
variables: analysis.suspiciousVariables,
nextSteps: analysis.recommendedActions
};
});
this.debugSessions.set(processId, session);
return session;
}
async injectProbe(code: string, location: CodeLocation) {
// 런타임 코드 주입으로 디버깅
const probe = `
console.log('[PROBE] Executing at ${location}');
console.log('[PROBE] Variables:', ${this.captureVariables()});
console.log('[PROBE] Stack depth:', ${this.getStackDepth()});
// 원본 코드 실행
${code}
`;
return this.hotReload(location, probe);
}
}
예방적 디버깅
1. 코드 품질 게이트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 문제 예방을 위한 자동 검사
const qualityGates = {
complexity: {
max: 10,
message: "함수 복잡도가 너무 높습니다. 리팩토링을 고려하세요."
},
coverage: {
min: 80,
message: "테스트 커버리지가 부족합니다."
},
duplication: {
max: 5,
message: "코드 중복이 감지되었습니다."
},
dependencies: {
maxDepth: 3,
message: "의존성 깊이가 너무 깊습니다."
}
};
2. 자동 에러 패턴 학습
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Claude의 에러 패턴 학습 시스템
class ErrorPatternLearner {
private patterns: ErrorPattern[] = [];
async learnFromHistory(errors: HistoricalError[]) {
// 빈번한 에러 패턴 추출
const patterns = this.extractPatterns(errors);
// 각 패턴에 대한 해결책 매핑
for (const pattern of patterns) {
const solutions = await this.findSolutions(pattern);
this.patterns.push({
...pattern,
solutions,
preventionStrategy: this.generatePreventionStrategy(pattern)
});
}
}
suggestPrevention(code: string): Prevention[] {
const suggestions = [];
for (const pattern of this.patterns) {
if (this.matchesPattern(code, pattern)) {
suggestions.push({
risk: pattern.risk,
description: pattern.description,
prevention: pattern.preventionStrategy,
autoFix: this.generateAutoFix(code, pattern)
});
}
}
return suggestions;
}
}
디버깅 베스트 프랙티스
효과적인 디버깅 전략
- 체계적 접근: 추측보다는 데이터 기반 분석
- 재현 가능성: 버그 재현 환경 구축
- 격리: 문제 범위를 좁혀가며 진단
- 문서화: 발견 사항과 해결 과정 기록
도구 활용 팁
1
2
3
4
5
6
7
// 디버깅 도구 통합
const debugToolchain = {
local: ['Chrome DevTools', 'VS Code Debugger', 'Claude Code'],
testing: ['Jest', 'Cypress', 'Playwright'],
production: ['Sentry', 'DataDog', 'New Relic'],
analysis: ['Claude AI', 'Jaeger', 'Grafana']
};
다음 편 예고
다음 편에서는 “실전 프로젝트 - 풀스택 앱 개발”을 다룰 예정입니다. Claude Code를 활용해 처음부터 끝까지 완전한 애플리케이션을 구축하는 과정을 살펴보겠습니다.
💡 오늘의 과제: 최근에 겪은 버그를 Claude Code와 함께 다시 분석해보세요. 근본 원인을 찾고, 재발 방지 대책을 수립해보세요!
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.