“나중에 시간 날 때 리팩토링해야지.” “일단 기능 구현이 먼저니까…”

개발자라면 누구나 한 번쯤 이런 생각을 해봤을 겁니다. 하지만 우리 모두 알고 있죠. 그 ‘나중’은 영원히 오지 않을 수도 있다는 것을요. 쌓여가는 기술 부채는 결국 발목을 잡고, 코드베이스는 누구도 건드리기 힘든 괴물이 되어버립니다.

이 글에서는 코드 리팩토링이 왜 ‘지금 당장’ 중요한지, 그리고 어떻게 하면 더 효과적으로 코드를 개선할 수 있는지에 대한 실질적인 가이드를 공유하고자 합니다.

리팩토링, 대체 왜 해야 할까요?

리팩토링(Refactoring)은 **’소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드의 구조를 개선하는 작업’**을 의미합니다. 버그를 잡거나 새로운 기능을 추가하는 것이 아닙니다. 오직 코드의 가독성, 유지보수성, 확장성을 높이는 데 집중하는 과정이죠.

마치 어질러진 방을 청소하는 것과 같습니다. 당장은 물건을 찾는 데 문제가 없더라도, 잘 정리된 방에서는 필요한 물건을 훨씬 빨리 찾고 새로운 물건을 놓을 공간도 쉽게 마련할 수 있습니다. 코드도 마찬가지입니다.

‘코드 스멜’을 맡아보세요: 리팩토링이 필요한 신호들

“좋아, 리팩토링의 중요성은 알겠어. 근데 언제 해야 하지?”

마틴 파울러는 그의 저서 <리팩토링>에서 ‘코드 스멜(Code Smell)’이라는 개념을 소개했습니다. 코드 스멜은 버그는 아니지만, 코드의 품질 저하를 유발할 수 있는 잠재적인 위험 신호들을 의미합니다. 몇 가지 대표적인 코드 스멜을 살펴보겠습니다.

1. 중복 코드 (Duplicate Code)

똑같거나 거의 유사한 코드가 여러 곳에 흩어져 있는 경우입니다. DRY(Don’t Repeat Yourself) 원칙을 위배하는 가장 흔한 스멜이죠.

[Before]

function printOrderDetails(order) {
  const customerName = order.customer.name;
  const address = order.customer.address;
  console.log(`고객명: ${customerName}`);
  console.log(`주소: ${address}`);
  // ... 중략 ...
}

function createInvoice(order) {
  const customerName = order.customer.name;
  const address = order.customer.address;
  // 청구서 생성 로직
  console.log(`청구서 발송: ${customerName}, ${address}`);
}

[After]

function printCustomerInfo(customer) {
  console.log(`고객명: ${customer.name}`);
  console.log(`주소: ${customer.address}`);
}

function printOrderDetails(order) {
  printCustomerInfo(order.customer);
  // ... 중략 ...
}

function createInvoice(order) {
  // 청구서 생성 로직
  const customer = order.customer;
  console.log(`청구서 발송: ${customer.name}, ${customer.address}`);
}

printCustomerInfo라는 함수로 중복을 제거하여 코드가 더 간결해지고, 고객 정보 출력 방식이 변경될 때 한 곳만 수정하면 됩니다.

2. 긴 함수 (Long Method)

하나의 함수가 너무 많은 일을 하고 있는 경우입니다. 함수의 이름만 보고는 무슨 일을 하는지 파악하기 어렵고, 코드를 이해하고 수정하기가 매우 힘들어집니다.

[Before]

function processPaymentAndOrder() {
  // 1. 사용자 정보 가져오기
  // 2. 결제 유효성 검사
  // 3. 외부 결제 API 호출
  // 4. 결제 결과 확인
  // 5. 주문 상태 '결제 완료'로 변경
  // 6. 재고 업데이트
  // 7. 사용자에게 확인 이메일 발송
  // ... 수많은 코드 라인 ...
}

[After]

function processPaymentAndOrder() {
  const user = getUserInfo();
  const paymentResult = processPayment(user);
  if (paymentResult.isSuccess) {
    const order = updateOrderStatus('PAID');
    updateInventory(order);
    sendConfirmationEmail(user);
  }
}

각 단계를 의미 있는 작은 함수들로 분리하면, 전체적인 흐름을 파악하기 훨씬 쉬워집니다. 각 함수는 하나의 책임만 갖게 되죠 (단일 책임 원칙, SRP).

3. 의미 없는 이름 (Mysterious Name)

a, b, data, processData처럼 변수나 함수의 이름이 그 역할이나 의미를 제대로 설명하지 못하는 경우입니다.

[Before]

let d = 0; // 경과 시간(일)
const list1 = [];

for (const i of data) {
  if (i.status === 'active') {
    list1.push(i);
  }
}

[After]

let elapsedTimeInDays = 0;
const activeUsers = [];

for (const user of allUsers) {
  if (user.isActive()) { // 혹은 user.status === 'active'
    activeUsers.push(user);
  }
}

이름만으로도 코드의 의도가 명확하게 드러납니다. 주석이 필요 없을 정도로요.

안전하고 효과적인 리팩토링을 위한 팁

  1. 테스트 코드를 먼저 작성하세요. 리팩토링의 가장 중요한 전제 조건은 ‘기능 변경 없이 구조만 개선’하는 것입니다. 이를 보장하는 가장 확실한 방법은 바로 테스트 코드입니다. 리팩토링 전에 코드의 동작을 검증하는 테스트를 마련해두면, 변경 후에도 시스템이 이전과 동일하게 동작하는지 자신감을 갖고 확인할 수 있습니다.
  2. 작은 단위로, 자주 하세요. 한 번에 너무 많은 것을 바꾸려고 하지 마세요. ‘변수 이름 바꾸기’, ‘함수 추출하기’ 등 아주 작은 단계로 나누어 진행하고, 각 단계마다 테스트를 실행하세요. 이렇게 하면 문제가 발생했을 때 원인을 찾기 쉽습니다. 보이스카우트 규칙을 기억하세요. “캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라.” 코드도 마찬가지입니다.
  3. IDE 기능을 적극 활용하세요. 요즘 IDE(통합 개발 환경)는 안전하고 편리한 리팩토링 기능을 많이 제공합니다. ‘Rename(이름 변경)’, ‘Extract Method(함수 추출)’, ‘Introduce Variable(변수 추출)’ 등의 기능을 사용하면 실수를 줄이고 작업 속도를 높일 수 있습니다.
  4. 팀과 함께 하세요. 리팩토링은 혼자만의 작업이 아닙니다. 코드 리뷰를 통해 동료의 의견을 구하고, 팀 전체가 코드 품질에 대한 공감대를 형성하는 것이 중요합니다. 페어 프로그래밍도 좋은 방법입니다.

마무리하며

리팩토링은 특별한 날에 하는 이벤트가 아니라, 코드를 작성하는 모든 순간에 함께해야 하는 ‘습관’입니다. 처음에는 번거롭고 시간이 더 드는 것처럼 느껴질 수 있습니다. 하지만 깨끗하고 건강한 코드가 주는 장기적인 이점은 그 비용을 훨씬 뛰어넘습니다.

오늘, 지금 당장 여러분의 코드에서 나는 ‘스멜’은 없나요? 작은 변수 이름 하나를 바꾸는 것부터 시작해보세요. 그 작은 한 걸음이 여러분과 팀의 개발 경험을 훨씬 더 즐겁고 생산적으로 만들어 줄 것입니다.

Leave a Reply

Your email address will not be published. Required fields are marked *