클린 코드와 리팩토링 (Clean Code and Refactoring)

한눈에 보기

클린 코드는 읽고 수정하기 쉬운 코드, 리팩토링은 동작을 바꾸지 않고 구조를 개선하는 활동이다. 코드 스멜, 대표 리팩토링 기법, 테스트 안전망을 갖춘 안전한 절차와 판단 기준을 정리한다.

최종 업데이트 2026.09.17

클린 코드(Clean Code)는 다른 사람이 읽고 이해하고 수정하기 쉬운 코드를 말하며, 리팩토링(Refactoring)은 겉으로 드러나는 동작을 바꾸지 않으면서 코드의 내부 구조를 개선하는 활동이다. 클린 코드가 지향점이라면 리팩토링은 이미 있는 코드를 그 지향점으로 옮겨 가는 구체적인 수단이다. 코드는 쓰는 시간보다 읽히는 시간이 훨씬 길기 때문에, 두 가지 모두 장기적인 개발 생산성을 좌우한다.

클린 코드의 핵심 원칙

클린 코드의 기준은 결국 읽는 사람의 인지 부담을 줄이는 데 있다. 실무에서 통용되는 원칙을 추리면 다음과 같다.

  • 의도가 드러나는 이름을 쓴다. 변수 d보다 elapsedDays가 낫고, 함수 이름만 보고 무엇을 하는지 짐작할 수 있어야 한다.
  • 함수는 작게 만들고 한 가지 일만 하게 한다. 추상화 수준이 섞인 긴 함수는 읽는 사람이 머릿속에서 다시 분해해야 한다.
  • 주석보다 코드로 설명한다. 코드가 무엇을 하는지 설명하는 주석은 코드를 고치라는 신호이고, 주석은 왜 그렇게 했는지 같은 코드로 표현할 수 없는 맥락에 쓴다.
  • 중복을 제거한다. 같은 지식이 두 곳에 있으면 한 곳만 고쳐지는 버그가 생긴다. 이를 DRY(Don't Repeat Yourself) 원칙이라 부른다.
  • 보이스카우트 규칙(Boy Scout Rule)을 따른다. 코드를 처음 봤을 때보다 조금이라도 깨끗하게 만들고 떠나면, 큰 결단 없이도 코드베이스가 점진적으로 좋아진다.

코드 스멜: 리팩토링이 필요하다는 신호

코드 스멜(Code Smell)은 당장 버그는 아니지만 설계에 문제가 있음을 암시하는 징후다. 대표적인 예는 다음과 같다.

  • 긴 함수와 거대한 클래스: 하나의 단위가 너무 많은 책임을 진다.
  • 긴 매개변수 목록: 함께 다니는 값들이 객체로 묶이지 못했다는 신호다.
  • 산탄총 수술(Shotgun Surgery): 하나의 변경을 위해 여러 파일을 조금씩 고쳐야 한다.
  • 기능 욕심(Feature Envy): 어떤 메서드가 자기 클래스보다 다른 클래스의 데이터에 더 관심이 많다.
  • 기본 타입 집착과 매직 넘버: 의미 있는 개념이 이름 없는 숫자와 문자열로 흩어져 있다.

대표적인 리팩토링 기법

리팩토링은 감이 아니라 이름 붙은 기법의 목록이다. 가장 자주 쓰는 것은 함수 추출(Extract Function), 변수 추출(Extract Variable), 이름 바꾸기(Rename), 조건문을 다형성으로 바꾸기, 매개변수 객체 도입 등이다. 다음은 함수 추출과 조기 반환(Early Return)을 적용한 예다.

// 리팩토링 전
function price(order) {
  let t = 0;
  for (const i of order.items) {
    t += i.qty * i.unitPrice;
  }
  if (order.coupon != null && order.coupon.rate > 0) {
    t = t - t * order.coupon.rate;
  }
  return t;
}

// 리팩토링 후
function price(order) {
  const subtotal = calculateSubtotal(order.items);
  return applyCoupon(subtotal, order.coupon);
}

function calculateSubtotal(items) {
  return items.reduce((sum, item) => sum + item.qty * item.unitPrice, 0);
}

function applyCoupon(amount, coupon) {
  if (coupon == null || coupon.rate <= 0) return amount;
  return amount * (1 - coupon.rate);
}

동작은 같지만 각 함수가 하나의 개념을 담고, 이름이 계산의 의도를 설명한다. 할인 정책이 바뀌어도 수정 범위가 applyCoupon 하나로 좁혀진다.

안전한 리팩토링 절차

리팩토링의 정의에서 가장 중요한 부분은 동작을 바꾸지 않는다는 조건이며, 이를 보장하는 안전망이 테스트다. 실무 절차는 다음과 같다.

  1. 대상 코드에 동작을 고정하는 테스트가 있는지 확인하고, 없으면 먼저 작성한다. 레거시 코드라면 현재 출력을 그대로 스냅숏으로 남기는 특성화 테스트(Characterization Test)를 쓴다.
  2. 기법 하나를 아주 작은 단위로 적용한다. 함수 하나 추출, 이름 하나 변경 수준이다.
  3. 테스트를 돌려 초록 상태를 확인하고 커밋한다. 실패하면 원인을 찾기보다 되돌리는 편이 빠르다.
  4. 이 과정을 반복한다. 기능 추가 커밋과 리팩토링 커밋은 섞지 않는다.

마틴 파울러는 이를 두 개의 모자 비유로 설명한다. 기능을 추가하는 모자를 쓰고 있을 때는 구조를 건드리지 않고, 리팩토링 모자를 쓰고 있을 때는 동작을 바꾸지 않는다. 두 모자를 동시에 쓰면 테스트가 깨졌을 때 원인이 구조 변경인지 동작 변경인지 구분할 수 없게 되고, 코드 리뷰에서도 의미 있는 변경이 정리 변경에 묻혀 버린다. 같은 이유로 대규모 이름 변경이나 파일 이동은 별도 커밋으로 분리하는 것이 리뷰어에 대한 예의다.

최근에는 통합 개발 환경(Integrated Development Environment, IDE)이 이름 바꾸기와 함수 추출 같은 기법을 자동으로 수행해 주므로, 수작업보다 도구의 자동 리팩토링을 우선 사용하는 것이 안전하다.

리팩토링의 비용과 판단 기준

리팩토링에도 비용이 있으므로 언제 할지 판단이 필요하다. 아래 비교처럼 상황에 따라 접근이 달라진다.

상황권장 접근이유
기능을 추가하려는데 구조가 방해됨준비 리팩토링 후 기능 추가구조를 먼저 고치면 변경이 쉬워짐
버그 수정 중 이해가 어려운 코드 발견이해한 내용을 코드에 반영하는 소규모 정리다음에 읽을 사람의 비용 절감
동작을 고정할 테스트가 전혀 없음테스트 확보가 우선안전망 없는 리팩토링은 위험
곧 폐기될 코드리팩토링하지 않음회수할 수 없는 투자
전면 재작성 욕구점진적 리팩토링 우선 검토빅뱅 재작성은 실패 위험이 큼

기술 부채(Technical Debt)라는 비유가 여기서 유용하다. 빠른 출시를 위해 진 부채는 이자가 붙으므로, 자주 수정되는 핫스팟부터 갚는 것이 투자 대비 효과가 크다. 거의 수정되지 않는 코드는 지저분해도 이자가 발생하지 않는다.

면접에서 이렇게 물어본다

  • 리팩토링의 정의를 말하고, 성능 최적화나 버그 수정과 어떻게 구분되는지 설명해 보라.
  • 코드 스멜의 예를 세 가지 들고 각각 어떤 리팩토링 기법으로 해결할지 말해 보라.
  • 테스트가 없는 레거시 코드를 리팩토링해야 한다면 어떤 순서로 접근하겠는가.
  • 좋은 함수의 조건은 무엇이라고 생각하는가.
  • 일정 압박 속에서 리팩토링의 우선순위를 어떻게 정하겠는가.
소프트웨어 공학 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기