grep

Engineering

리팩토링 여정, 더 나은 코드로의 항해 part 2

Teemo Lee여기어때

2024년 12월 31일

원문에서 보기 ↗

리팩토링 여정, 더 나은 코드로의 항해 part 2. 새로운 구조로 나아가는 길

안녕하세요! 숙박전시개발팀 티모입니다! 😄

오늘은 기존 구조에서 벗어나 새로운 구조를 설계하고 이를 적용하기까지의 과정, 그리고 그 이유와 기대 효과를 공유해 보려고 합니다.

잠깐! 그 전에 저희 팀이 리팩토링을 결심하게 된 이유가 궁금하시다면, 시나몬의 “리팩토링 여정, 더 나은 코드로의 항해 part 1. 리팩토링 계기와 문제 진단” 편을 먼저 봐주세요! 🙌

그래서 어떻게 바뀌었나요?

이전 포스팅에서 보셨듯이 전시 프로젝트의 가장 큰 문제는 거대한 부모 클래스를 여러 곳에서 상속받아 쓰는 거예요. 이를 벗어나기 위해 추상 팩토리 패턴을 이용하여 A/B 테스트 실험에 필요한 컴포넌트를 동적으로 선택하도록 했어요. 추상 팩토리 패턴을 쉽게 설명드리자면 어떤 제품을 만들기 위해 필요한 구성 요소들을 하나의 세트로 미리 준비해 두고, 사용자의 요구에 따라 그 세트 중 하나를 골라 사용하는 방식이에요.

예를 들어볼게요. A 회사는 가구를 판매하는 회사예요. 이 회사는 모던 스타일 가구와 앤틱 스타일 가구를 만들어요.

고객이 두 스타일 중 하나를 고르면, 그에 맞는 가구 세트(의자, 책상, 소파 등)를 일괄 구성해서 판매하죠. 이처럼 스타일(추상) 하나만 선택하면, 그에 맞는 정해진 구성의 세트 가 자동으로 만들어지는 방식이 바로 추상 팩토리 패턴이에요. 고객은 스타일만 고르면 되고, 세트 안의 구성품은 따로 고를 필요 없이 알아서 일관되게 맞춰져 나오니까 편리하고 일관성도 있죠.

전시 프로젝트에는 어떻게 적용할 수 있었을까요? 기존에는 만능 클래스 역할을 하던 PlaceMapper에 핵심 비즈니스 로직을 모두 모아놨다면 리팩토링을 하면서 각 클래스에 권한을 위임했어요.

public class PLPCreator {
 private final PLPMetaService metaService;
 private final PLPRoomService roomService;
 public ListModel createListItem(PLPParam param) {
        // 앱버전, Hackle 그룹, 카테고리로 AB Test Context를 만들어 한 번에 전달
  final var hackleABTestContext = HackleABTestContext.create(param);
        // AB Test Context로 필요한 세트 생성
  final var meta = metaService.createMeta(hackleABTestContext);
  final var room = roomService.createRoom(hackleABTestContext);
  // 세트로 만든 데이터를 결합하여 모델 생성 후 반환
  return buildPLPModel(..);
 }
public class PLPMetaFactory {
    private final ReviewTransformer reviewTransformer;
 private final ImageTransformer imageTransformer;
 private final CouponTransformer couponTransformer;
 public PLPMeta createMeta(HackleABTestContext hackleABTestContext) {
        final var review = reviewTransformer.create(hackleABTestContext);
        final var coupon = couponTransformer.create(hackleABTestContext);
        final var image = imageTransformer.create(hackleABTestContext);
  return buildPLPMeta(..);
 }
}

이 설계를 통해 앱에서는 앱 버전, 카테고리, 유저의 A/B 테스트 그룹 정보만 전달하면, 복잡한 내부 로직을 몰라도 그에 맞는 데이터 세트를 자동으로 제공받을 수 있게 되었습니다.

왜 이렇게 바꿨나요?

저희가 리팩토링한 이유는 간단해요. 더 나은 유연성과 유지보수성을 확보하기 위해서죠. 기존에는 복잡하게 얽혀있는 구조여서 하나의 수정이 여러 곳에 예상치 못한 파급효과를 일으키는 경우가 많았어요.

저희는 이 부분에 중점을 두고 설계를 진행했습니다.

  1. 의존성과 파급효과 문제 기존 코드에서는 모든 모듈이 얽히고설켜있어 특정 부분만 수정하더라도 전체 시스템에 영향을 미칠 수 있었어요. 이런 상황은 배포 때마다 긴장감을 유발했고, 문제 발생 시 롤백이 빈번했죠. 리팩토링을 통해 우리는 모듈 간 의존성을 명확히 정의하고, 각 컴포넌트의 역할과 책임을 재정의했습니다. 이를 통해 수정의 영향 범위를 쉽게 추적할 수 있는 구조로 단순화했어요.
  2. 복잡한 조건문 제거 기존 구조에서는 새로운 A/B 테스트나 기능 추가 시 조건문이 계속해서 누적되었어요. 이로 인해 코드의 가독성이 떨어지고, 유지보수가 어려워졌죠. 이를 해결하기 위해 각 기능을 독립적인 클래스로 분리하고, 조건문을 대체할 수 있는 객체 지향적인 설계를 도입했어요.
  3. A/B 테스트와 API의 빠른 대응 A/B 테스트는 빠르게 실행되고 종료되기 때문에, API도 신속하게 변화에 대응할 수 있어야 했습니다. 그러나 기존 구조는 이를 따라가지 못했어요. 새로운 설계를 통해 간결하고 명확한 로직을 유지하며 신속하게 대응할 수 있게 되었습니다.
  4. 하위 버전 관리의 어려움 숙박전시개발팀은 다양한 앱 버전에서 들어오는 트래픽을 처리해야 하기 때문에, 하위 버전과의 호환성이 필수입니다. 버전 별로 컴포넌트를 만들어 책임을 분리하고, 특정 버전에만 필요한 코드를 참조할 수 있도록 구조를 개선했습니다.
  5. 중복 코드 제거 기존 구조에서는 비슷한 기능을 위해 if문을 추가하거나 거의 동일한 메서드를 반복적으로 작성하는 경우가 많았습니다. 중복 코드 제거를 위해 코드 흐름을 직관적으로 만들고, 새로운 인원도 쉽게 이해할 수 있는 구조로 바꾸었습니다.

리팩토링 후 효과는요?

리팩토링의 효과는 대단했습니다. A/B 테스트를 시작하고 정리하는 것이 너무나도 간단해졌고, 간단해진만큼 빨라졌습니다. 또, 조건에 맞는 컨테이너를 선택하다보니 문제가 발생했을 때 문제 범위가 더 작아지고 어떤 컨테이너에서 문제가 발생하는지 명확히 알 수 있었는데요. 이 외에도 5개의 장점이 더 있습니다.

  1. 유지보수성과 확장성 증가 모듈화된 설계 덕분에 새로운 기능이나 테스트를 추가할 때 기존 코드를 수정하지 않아도 됩니다.
  2. 코드 간결화와 가독성 향상 중복 코드와 복잡한 조건문을 제거해 코드의 직관성과 유지보수성이 크게 향상되었습니다.
  3. 안정성과 신뢰도 향상 모듈 간 의존성을 줄이고, 리팩토링 과정에서도 안정성을 유지했습니다.
  4. 하위 버전 호환성 확보 버전 별 컴포넌트 관리로 하위 버전에서도 이슈가 발생하지 않도록 보장했습니다.
  5. 팀 생산성 향상 새로운 인원이 투입되어도 코드를 쉽게 이해하고 작업할 수 있는 구조로 개선되었습니다.

검증은 어떻게 했나요?

리팩토링은 새로운 설계를 바탕으로 기존 코드를 변경하는 작업이었어요. 여기에서 가장 중요했던 것은 안정성이었습니다. 여기어때 서비스는 24시간 실시간 운영되기 때문에, 리팩토링 과정에서 작은 실수라도 서비스 장애로 이어질 수 있었습니다.

이를 방지하기 위해 다음과 같은 원칙을 세웠습니다.

  1. 작은 단위로 나누어 적용 전체 시스템을 한 번에 변경하지 않고, 모듈별로 작은 단위로 나누어 점진적으로 적용했습니다. 이렇게 하면 문제가 발생했을 때 그 영향을 최소화하고, 빠르게 원인을 찾아 수정할 수 있었습니다.
  2. 쉐도잉(Shadowing) 새로운 설계로 변경한 코드가 기존 코드와 동일하게 동작하는지 철저히 검증하기 위해 쉐도잉 기법을 활용했습니다. 새로운 코드를 실제 서비스에 적용하기 전에 기존 코드와 함께 실행해 결과를 비교했습니다. API 응답, 로그 데이터를 꼼꼼히 점검하며 안정성을 확인했습니다.
  3. 리팩토링 우선순위 설정 A/B 테스트가 많이 발생하는 모듈부터 우선적으로 리팩토링을 진행했습니다. 이렇게 하면 새로운 설계의 장점을 더 빨리 체감할 수 있었고, 이후 작업에 대한 동기 부여도 자연스럽게 이어졌습니다.

오늘은 우리 팀이 어떻게 변화를 유연하게 받아들이는 프로그램으로 변경했는지에 대해 설명드렸어요. 최대한 상세하게 설명한다고 했지만, 어려우신 부분도 있을 거라고 생각해요. 댓글을 남겨주시면 꼭! 답변 드릴게요.

리팩토링은 단순히 코드의 개선을 넘어 팀의 협업 방식과 문제 해결 능력을 키우는 계기가 돼요. 그래서 다음 포스트는 팀워크와 회고를 통해 우리가 어떻게 헤쳐나갔는지 이야기 하려고 합니다.

우리 팀의 이야기가 궁금하시다면, 다음 포스트도 기대해 주세요!