좋은 리팩토링과 나쁜 리팩토링

0

코드 리팩토링은 개발자가 항상 마주하는 중요한 작업 중 하나입니다. 기존 코드베이스를 개선하고, 더 읽기 쉽고 유지보수하기 쉽게 만들기 위해 코드의 내부 구조를 바꾸는 과정이 바로 리팩토링입니다. 하지만 리팩토링이 언제나 긍정적인 결과만을 가져오는 것은 아닙니다. 잘못된 리팩토링은 코드의 복잡도를 증가시키고 유지보수를 더 어렵게 만들 수 있습니다. 이번 글에서는 좋은 리팩토링과 나쁜 리팩토링을 구분하고, 나쁜 리팩토링의 함정을 피하는 방법을 살펴보겠습니다.

리팩토링의 기본 개념

리팩토링은 코드의 기능을 변경하지 않고, 내부 구조를 개선하는 작업입니다. 즉, 사용자에게 보이는 외부 동작은 그대로 두면서, 코드를 더 효율적이고 읽기 쉽게 만드는 것입니다. 하지만 이 과정에서 너무 복잡하거나 지나치게 새로운 방법론을 도입하면, 오히려 코드의 유지보수성이 떨어질 수 있습니다. 리팩토링의 궁극적인 목표는 코드를 간결하게 하고, 개발자들이 더 쉽게 이해할 수 있도록 하는 것입니다.

나쁜 리팩토링의 예시

1. 코딩 스타일의 급격한 변화

리팩토링 과정에서 가장 흔히 저지르는 실수는 기존 코딩 스타일을 완전히 바꿔버리는 것입니다. 새로운 프로그래밍 패러다임이나 라이브러리를 도입하여 코드를 변경하는 것은, 특히 팀 내 다른 개발자들이 이러한 스타일에 익숙하지 않다면 문제가 될 수 있습니다.

잘못된 예:
import * as R from 'ramda';

const processUsers = R.pipe(
  R.filter(R.propSatisfies(R.gte(R.__, 18), 'age')),
  R.map(R.applySpec({
    name: R.pipe(R.prop('name'), R.toUpper),
    age: R.prop('age'),
    isAdult: R.always(true)
  }))
);

이 코드에서는 `Ramda`라는 외부 라이브러리를 도입하여 기존 코드를 완전히 다른 스타일로 변경했습니다. 이런 급격한 변화는 팀 내 개발자들에게 혼란을 줄 수 있습니다.

올바른 예:
function processUsers(users) {
  return users
    .filter(user => user.age >= 18)
    .map(user => ({
      name: user.name.toUpperCase(),
      age: user.age,
      isAdult: true
    }));
}

이 코드는 더 간결하고 이해하기 쉬운 방식으로 기존 기능을 유지하면서 개선되었습니다.

2. 불필요한 추상화

추상화를 도입하는 것은 때때로 유용할 수 있지만, 모든 경우에 해당되는 것은 아닙니다. 지나치게 많은 추상화는 코드를 복잡하게 만들고 이해하기 어렵게 합니다.

잘못된 예:
class UserProcessor {
  constructor(users) {
    this.users = users;
  }

  process() {
    return this.filterAdults().formatUsers();
  }

  filterAdults() {
    this.users = this.users.filter(user => user.age >= 18);
    return this;
  }

  formatUsers() {
    return this.users.map(user => ({
      name: this.formatName(user.name),
      age: user.age,
      isAdult: true
    }));
  }

  formatName(name) {
    return name.toUpperCase();
  }
}

이 예시는 지나치게 많은 클래스를 사용하여 불필요한 추상화를 도입한 경우입니다. 코드를 이해하기 어렵게 만들 뿐 아니라, 유지보수 또한 복잡해집니다.

올바른 예:
const isAdult = user => user.age >= 18;
const formatUser = user => ({
  name: user.name.toUpperCase(),
  age: user.age,
  isAdult: true
});

function processUsers(users) {
  return users.filter(isAdult).map(formatUser);
}

이 코드는 간결하고 재사용 가능한 함수로 리팩토링되어, 추상화를 과도하게 도입하지 않고도 기존 기능을 충실히 유지합니다.

###END

좋은 리팩토링을 위한 팁

1. 점진적인 변화

리팩토링은 한 번에 모든 코드를 재작성하는 것이 아니라, 작은 단위로 점진적으로 진행해야 합니다. 작은 변경은 테스트가 용이하며, 문제 발생 시 원인을 빠르게 파악할 수 있습니다.

2. 기존 코드 스타일 존중

리팩토링은 기존 코드 스타일과 일관성을 유지하는 것이 중요합니다. 스타일의 일관성은 팀의 유지보수성을 크게 향상시킬 수 있습니다.

3. 불필요한 복잡성 도입 자제

추상화나 새로운 라이브러리를 도입하기 전에, 그것이 정말로 필요한지 생각해야 합니다. 단순한 코드는 유지보수가 용이하며, 복잡성을 필요할 때만 추가하는 것이 바람직합니다.

결론

리팩토링은 소프트웨어 개발에 필수적인 과정이지만, 올바르게 수행해야 합니다. 지나친 추상화나 코딩 스타일의 급격한 변화는 오히려 코드를 더 어렵고 복잡하게 만들 수 있습니다. 좋은 리팩토링은 코드의 외부 동작을 변경하지 않으면서, 개발자들이 더 쉽게 이해하고 유지할 수 있도록 코드를 개선하는 것입니다. 다음 프로젝트에서 리팩토링을 진행할 때는 작은 단위로, 기존 스타일을 존중하며, 불필요한 복잡성을 피하는 것이 중요합니다.

참고 자료: Builder.io, “Good Refactoring vs Bad Refactoring”

Leave a Reply