Skip to content

코드 품질과 리팩터링

들어가며

코드가 작동하면 그만일까요? 여러분은 이런 코드를 작성해 본 적이 있을 것입니다: 기능은 구현했지만 2주가 지나자 스스로도 이해할 수 없게 된 코드. 또는 팀원이 퇴사하면서 "신과 그 사람만 이해할 수 있는" 코드를 남긴 경우도 있죠.

이 장에서는 좋은 코드가 무엇인지, 나쁜 코드를 어떻게 식별하는지, 그리고 안전하게 개선하는 방법을 알아봅니다.

이 글에서 무엇을 배울 수 있을까요?

내용핵심 개념
1장코드 스멜일반적인 문제 식별
2장리팩터링 기법안전하게 코드 개선하기
3장코드 리뷰팀 협업에서의 품질 보장
4장품질 측정데이터로 코드 건강도 측정

이 장을 마치면 코드 문제를 식별하고, 안전하게 리팩터링하며, 팀 협업을 통해 코드 품질을 지속적으로 향상시키는 방법을 습득하게 됩니다.


0. 전경도: 코드의 수명 주기

소프트웨어 개발에서 자주 간과되는 사실이 있습니다: 코드는 작성되는 횟수보다 읽히는 횟수가 훨씬 많습니다.

코드는 탄생부터 폐기까지 대략 다음과 같은 여정을 겪습니다:

코드의 일생

  • 작성 단계: 개발자가 첫 번째 구현을 작성하고, 기능이 작동하고, 테스트를 통과합니다.
  • 리뷰 단계: 팀원들이 코드를 읽고 개선 제안을 합니다.
  • 유지보수 단계: 버그 수정, 기능 추가, 새로운 요구사항 적용 — 이 단계가 코드 수명 주기의 80% 이상을 차지합니다.
  • 리팩터링 단계: 코드가 유지보수하기 어려워지면, 외부 동작을 변경하지 않고 내부 구조를 개선해야 합니다.
  • 폐기 단계: 기술이 발전하고, 기존 코드가 새로운 방식으로 대체됩니다.

Martin Fowler는 《리팩터링》에서 이렇게 말했습니다: "어떤 바보든 컴퓨터가 이해할 수 있는 코드를 작성할 수 있다. 좋은 프로그래머만이 인간이 이해할 수 있는 코드를 작성할 수 있다."


1. 코드 스멜: 일반적인 문제 식별

1.1 코드 스멜이란?

"코드 스멜(Code Smell)"이라는 개념은 Kent Beck이 제안했으며, 코드에서 버그는 아니지만 더 깊은 설계 문제를 암시하는 특징을 말합니다. 방에서 이상한 냄새가 나는 것과 같습니다 — 즉시 아프게 하지는 않지만, 어딘가 청소가 필요하다는 신호입니다.

아래의 인터랙티브 컴포넌트를 통해 가장 일반적인 코드 스멜을 식별해 보세요:

Code Smell Detector - click to switch examples
Problem code
function processOrder(order) {
  // Validate order... (20 lines)
  // Calculate price... (15 lines)
  // Check inventory... (10 lines)
  // Send notification... (15 lines)
  // Update database... (10 lines)
  // Generate report... (10 lines)
  // 80+ lines in total!
}

📏 Long function

A function exceeds 50 lines, does too many things, and becomes hard to understand and test.

Improvement:Split the large function into focused functions such as validateOrder(), calculatePrice(), and checkInventory().

1.2 일반적인 코드 스멜 목록

코드 스멜증상위험성
긴 함수함수가 50줄 초과이해, 테스트, 재사용이 어려움
매직 넘버코드에 86400000 직접 작성의미 불명확, 수정 시 누락 위험
중복 코드유사한 로직이 여러 곳에 등장수정 시 여러 곳을 동기화해야 하며 누락 위험
과도한 중첩3단계 이상의 if/for논리가 미로처럼 되어 추적이 어려움
긴 매개변수 목록함수 매개변수가 4개 초과호출이 어렵고 순서를 혼동하기 쉬움
갓 클래스하나의 클래스/모듈이 너무 많은 일을 함책임이 불명확, 하나를 변경하면 전체에 영향

핵심 인사이트

코드 스멜은 "오류"가 아니라 "신호"입니다. 여기의 설계는 개선이 필요할 수 있다는 것을 알려줍니다. 모든 코드 스멜을 즉시 수정할 필요는 없지만, 식별하는 능력은 필요합니다.


2. 리팩터링 기법: 안전하게 코드 개선하기

2.1 리팩터링이란?

리팩터링(Refactoring)의 정의는 매우 정확합니다: 코드의 외부 동작을 변경하지 않고 내부 구조를 개선하는 것.

핵심은 "외부 동작을 변경하지 않는 것"입니다. 리팩터링은 재작성이 아니고, 기능 추가도 아니며, 버그 수정도 아닙니다. 코드 내부의 "정리정돈"입니다.

아래의 컴포넌트를 통해 여러 리팩터링 기법의 전후 변화를 비교해 보세요:

Refactoring technique comparison - choose a technique to compare before and after
Extract Function: move a code block out of a large function into a clearly named new function.
Before
function printReport(invoice) {
  console.log("=== Invoice ===")
  // Calculate total
  let total = 0
  for (let item of invoice.items) {
    total += item.price * item.qty
  }
  console.log(`Total: ${total}`)
}
After
function printReport(invoice) {
  console.log("=== Invoice ===")
  const total = calcTotal(invoice.items)
  console.log(`Total: ${total}`)
}

function calcTotal(items) {
  return items.reduce(
    (s, i) => s + i.price * i.qty, 0
  )
}
Key point:Extract Function is one of the most common refactorings. A good function name is the best comment.

2.2 자주 사용하는 리팩터링 기법

함수 추출하기(Extract Function)

가장 자주 사용되는 리팩터링 기법입니다. 코드의 일부분을 의미 있는 이름으로 요약할 수 있다면, 함수로 추출해야 합니다.

javascript
// 리팩터링 전
function printReport(data) {
  // 총액 계산
  let total = 0
  for (const item of data.items) {
    total += item.price * item.qty
  }
  // 출력...
}

// 리팩터링 후
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0)
}

function printReport(data) {
  const total = calculateTotal(data.items)
  // 출력...
}

이름 변경하기(Rename)

좋은 이름은 가장 저렴하면서도 가장 효과적인 문서입니다. 변수나 함수의 의미를 설명하기 위해 주석을 작성해야 한다면, 이름이 충분히 좋지 않다는 뜻입니다.

javascript
// 리팩터링 전
const d = new Date() - startTime  // 경과 시간
const arr = users.filter(u => u.a) // 활성 사용자

// 리팩터링 후
const elapsedMs = new Date() - startTime
const activeUsers = users.filter(user => user.isActive)

중첩 조건문을 보호 구문으로 교체하기(Replace Nested Conditional with Guard Clauses)

javascript
// 리팩터링 전
function getPayAmount(employee) {
  if (employee.isSeparated) {
    return { amount: 0 }
  } else {
    if (employee.isRetired) {
      return { amount: employee.pension }
    } else {
      return { amount: employee.salary }
    }
  }
}

// 리팩터링 후
function getPayAmount(employee) {
  if (employee.isSeparated) return { amount: 0 }
  if (employee.isRetired) return { amount: employee.pension }
  return { amount: employee.salary }
}

리팩터링의 안전망

리팩터링의 가장 큰 위험은 "수정하다가 버그가 발생하는 것"입니다. 따라서 리팩터링의 전제 조건은 테스트 커버리지입니다. 매번 작은 단계로 리팩터링한 후 테스트를 실행하여 동작이 변하지 않았는지 확인합니다. 테스트가 없는 코드는 먼저 테스트를 작성한 후 리팩터링하세요.


3. 코드 리뷰: 팀 협업에서의 품질 보장

3.1 왜 코드 리뷰가 필요한가?

코드 리뷰(Code Review)는 팀에서 가장 효과적인 품질 보장 수단 중 하나입니다. 그 가치는 버그를 찾는 것에만 있지 않습니다:

  • 지식 공유: 팀원이 서로의 코드를 이해하고 "버스 팩터"(누군가 버스에 치이면 프로젝트가 계속될 수 있는가?)를 낮춥니다.
  • 스타일 통일: 리뷰를 통해 점진적으로 팀의 코딩 규칙이 형성됩니다.
  • 설계 문제 조기 발견: 버그보다 수정하기 어려운 것은 잘못된 아키텍처 결정입니다.
  • 상호 학습: 다른 사람의 코드를 보는 것은 프로그래밍 능력을 향상시키는 지름길입니다.

3.2 무엇을 리뷰할 것인가?

차원초점
정확성논리가 올바른가? 경계 조건이 처리되었는가?
가독성이름이 명확한가? 구조가 이해하기 쉬운가?
보안인젝션 위험이 있는가? 민감한 데이터가 노출되었는가?
성능명백한 성능 문제가 있는가? N+1 쿼리?
테스트해당하는 테스트가 있는가? 주요 경로를 커버했는가?

3.3 리뷰의 예절

좋은 코드 리뷰는 코드에 대한 논의이지, 사람에 대한 비판이 아닙니다:

  • "너"가 아닌 "우리"를 사용하세요: "여기 틀렸어" → "여기는 보호 구문을 사용하는 건 어떨까요?"
  • 명령이 아닌 질문을 하세요: "const로 바꿔" → "이 변수는 나중에 재할당되나요? 아니라면 const가 더 안전할 것 같아요"
  • 이유를 제시하세요: "나쁘다"고만 하지 말고 "왜 나쁜지"와 "어떻게 개선할 수 있는지"를 설명하세요

4. 코드 품질 측정

4.1 순환 복잡도

순환 복잡도(Cyclomatic Complexity)는 코드 내 독립적인 경로의 수를 측정합니다. 각 if, for, case, &&, ||가 복잡도를 증가시킵니다.

복잡도평가제안
1-10단순이해하고 테스트하기 쉬움
11-20보통분할 고려
21-50복잡리팩터링 필수
50+유지보수 불가긴급 리팩터링

4.2 코드 커버리지

코드 커버리지는 테스트가 코드의 얼마나 많은 비율을 실행했는지 측정합니다. 일반적인 지표:

  • 라인 커버리지: 실행된 코드 줄 수가 전체 줄 수에서 차지하는 비율
  • 분기 커버리지: 실행된 조건 분기가 전체 분기에서 차지하는 비율

커버리지의 함정

80%의 커버리지가 코드 품질이 좋다는 것을 의미하지 않습니다. 커버리지는 "어떤 코드가 테스트되지 않았는지"만 알려줄 뿐, "테스트가 의미 있는지"는 알려주지 않습니다. expect(true).toBe(true) 같은 테스트는 커버리지를 높이지만 아무런 가치가 없습니다.

4.3 유용한 도구

도구용도
ESLintJavaScript/TypeScript 정적 분석
Prettier코드 포매팅, 스타일 통일
SonarQube종합 코드 품질 플랫폼
HuskyGit hooks, 커밋 전 자동 검사

5. AI 활용: 대형 언어 모델로 코드 품질 향상

대형 언어 모델은 코드 품질 분야에서 매우 실용적입니다. "24시간 온라인 코드 리뷰어" 역할을 할 수 있습니다.

5.1 코드 스멜 식별

프롬프트:

다음 코드를 리뷰하고 코드 스멜(Code Smell)을 식별해 주세요:
긴 함수, 매직 넘버, 중복 코드, 과도한 중첩, 긴 매개변수 목록 등.
각 문제에 대해 구체적인 위치, 문제 설명, 개선 제안을 제시해 주세요.

[코드를 여기에 붙여넣으세요]

5.2 자동 리팩터링

프롬프트:

다음 코드를 리팩터링해 주세요. 요구사항:
1. 외부 동작을 변경하지 않기
2. 함수 추출, 보호 구문 교체 등의 기법 사용
3. 이름 개선, 매직 넘버 제거
4. 각 리팩터링 단계의 이유 설명

[코드를 여기에 붙여넣으세요]

5.3 코드 리뷰 시뮬레이션

프롬프트:

시니어 개발자의 관점에서 다음 코드를 리뷰하고, 다음 차원에서 피드백을 제공해 주세요:
- 정확성: 논리에 버그가 있는가? 경계 조건이 처리되었는가?
- 가독성: 이름이 명확한가? 구조가 이해하기 쉬운가?
- 성능: 명백한 성능 문제가 있는가?
- 보안: 인젝션이나 데이터 유출 위험이 있는가?
"명령"이 아닌 "제안"의 어조로 개선 방안을 제시해 주세요.

[코드를 여기에 붙여넣으세요]

AI 사용 제안

AI의 리팩터링 제안은 직접 검증해야 합니다 — 테스트를 실행하여 동작이 변경되지 않았는지 확인하세요. AI를 "제안을 하는 동료"로 대하고, "무조건 신뢰하는 권위자"로 대하지 마세요.


6. 요약

지금까지 문제 식별부터 해결까지, 완전한 코드 품질 개선 체계를 구축했습니다:

  1. 식별: 코드 스멜을 감지하고, 어디를 개선해야 하는지 파악하기
  2. 리팩터링: 안전한 리팩터링 기법을 습득하고, 테스트 보호 하에 작은 단계로 개선하기
  3. 협업: 코드 리뷰를 통해 팀이 공동으로 코드 품질을 지키기
  4. 측정: 객관적인 지표로 코드 건강도 추적하기

핵심 성찰

코드 품질은 일회성 작업이 아니라 지속적인 습관입니다. 방을 깨끗하게 유지하는 것과 같습니다 — 너무 어지러워질 때까지 기다렸다가 대청소하는 것이 아니라, 매일 조금씩 정리하는 것입니다. 보이스카우트 규칙이 말하듯: 떠날 때 코드를 도착했을 때보다 조금 더 깨끗하게 만들어 두세요.


추가 읽기

  • 고전 서적: Martin Fowler의 《리팩터링: 기존 코드의 설계를 개선하는 방법》은 이 분야의 바이블입니다.
  • 클린 코드: Robert C. Martin의 《Clean Code》는 다양한 실용적인 코딩 원칙을 제공합니다.
  • 실용 도구: 프로젝트에 ESLint + Prettier + Husky를 설정해 보고, 자동화된 코드 품질 보장을 경험해 보세요.
  • 코드 리뷰: Google의 Code Review 가이드는 업계 표준이며, 학습할 가치가 있습니다.