코드 리팩토링, IF문을 제거하자 (전략패턴)

배성균
직방 기술 블로그
6 min readFeb 11, 2022

--

개요

레거시 코드를 리팩토링하며 얻은 경험을 공유합니다. 스프링부트환경에서 전략패턴을 활용했습니다.

우리 직방에서는 스마트주차장이라는 서비스를 제공하고 있습니다. 스마트주차장은 아파트단지의 차량 입출 차 관련 서비스입니다. 차량 입출 차를 제어하는 회사는 매우 많기 때문에 연동하는 업체도 매우 많습니다. 업체와 연동하는 부분도 아주 많은데 그 중 방문차량 예약, 취소하는 기능을 구현한 코드의 문제점과 해결 과정을 공유합니다.

AS-IS

위 코드 중 연예인 이름은 모두 외부업체 이름입니다.

이슈

레거시 코드를 분석하며 몇가지 문제점을 정리했습니다.

1번부터 보겠습니다. 테스트 코드가 없는 경우 리팩토링에 큰 문제가 생깁니다. 제가 생각하는 리팩토링 베스트 시나리오는 테스트코드를 통해 리팩토링이 문제없는지 계속 확인하며 리팩토링을 진행하는 것입니다. 하지만 테스트코드가 없었습니다. 어떻게 해야 하나.. 생각이 들었고 실제 업체 서버 호출을 통해 점검할 수 있도록 했습니다. 몇몇 업체의 경우 dev 서버가 살아있었기 때문에 안전한 검증을 할 수 있었습니다. 그렇지 못한 업체의 경우 prod 서버를 통해 점검을 진행했습니다. (안전한 방법으로)

2번 중복코드 문제를 보겠습니다. 중복코드는 중복을 강제합니다. 무슨 말이냐면 같은 테스트코드가 여러 곳에 생긴다는 뜻입니다. 또, 중복코드 한곳에서 문제가 발생한 경우 모두 찾아 고쳐야 합니다. 중복코드는 코드량을 증가시키며 그에 따라 큰 비용이 발생합니다. (시간이 오래 걸림) 마지막으로 중복코드끼리 동일한지 계속 확인해야 하는 문제까지 있습니다. 중복코드 문제는 IDE의 리팩토링 기능을 통해 해결이 가능합니다. (적극적으로 활용합시다)

예시) 인텔리제이의 코드리팩토링 기능

3번 switch case (IF문) 문제를 보겠습니다. 좋은 친구지만 다중조건문으로 쓰이기 시작했을 때 안 좋아집니다. 어떻게 안 좋은지 알 수 있을까요? 극단적으로 늘려보면 되는데 조건이 100개 혹은 1,000개가 된다면 어떨까요. 조건이 계속 늘어나는 상황이면 조심할 필요가 있습니다. 유지 보수하기 어려워져요.

4번 호출부와 구현부가 같은 코드에 존재하는 경우의 문제를 보겠습니다. 호출부와 구현부가 같은 파일에 있을 때 해당 파일에서만 사용하는 코드를 구분하기 어려워지는 문제가 있습니다. 따라서 코드양이 많아지는 경우 호출부와 구현부를 분리하는 것이 좋습니다.

결국, 기능이 추가(업체 추가)될 때마다 악순환..

개선방법

개선해보도록 하죠. 중복코드를 정리하고, 다중조건문부분을 패턴을 적용해보겠습니다. 중복코드를 IDE의 리팩토링 -> Extract Method 기능을 활용해 정리합니다.

1. 중복코드정리

2. 전략패턴적용

다음은 디자인패턴 적용입니다. 이 상황에서 쓸 수 있는 많은 디자인패턴이 있습니다. 그중 전략패턴을 선택했습니다. 몇 가지 이유 때문인데 설명해보도록 하겠습니다. 첫 번째 이유는 상속을 사용하지 않는다는 것입니다. 개인적으로 상속을 선호하지 않습니다. 경험상 상속으로 코드구현이 되어있는 경우 처음에는 부모 클래스와 자식 클래스 간 코드 정리가 잘됩니다. 여러 개발자의 손을 거치며 코드가 금방 망가지는데 불필요한 코드를 부모에 넣는다든지 부모의 공통코드를 잘 못 고친다든지 하는 경우를 많이 보았습니다. 따라서 회사에서 코드를 관리할 때는 적절한 정책과 많은 PR(코드리뷰)을 요구하게되는데(이 주제도 할 얘기가 많지만, 범위가 벗어나 이만 줄입니다.) 상속은 잘못 관리되는 경우를 많이 봐서 개인적으로 선호하지 않습니다.

두 번째 이유는 의존성이 낮아지기 때문입니다. 결합도는 낮추고 응집도는 높은 코드가 좋은 코드인데 전략패턴에서는 각 전략끼리 관련이 없어지고 서비스영역과 전략영역이 분리되기 때문에 의존성이 낮습니다. 의존성이 낮다는 얘기는 변경사항이 발생해도 사이드이팩트가 발생할 확률이 떨어진다는 뜻입니다. 그리고 코드를 다시 리팩토링할 일이 생겼을 때 손쉽게 할 수 있습니다.

세 번째 이유는 직관적으로 코드를 관리할 수 있기 때문입니다. 패키지 구조를 예쁘게 만들어 둘 수 있습니다. 따라서 다른 개발자가 이어 작업을 하더라도 러닝커브가 낮아지는 효과가 있습니다.

이런식으로 누가봐도 쉽게, 보는순간 “아! 업체마다 하나씩 생기는구나” 알 수 있죠.

위의 큰 이유 말고도 다중조건문을 제거 할 수 있는 것도 장점이고, 유연하게 기능을 추가하기 쉽다는 것도 장점입니다.

리팩토링 정리

파일 하나에서 작성, 관리되던 코드를 분리했습니다.

  1. 공통 인터페이스
  2. 업체 구분용 어노테이션
  3. 업체별 전략 구현부 (실제 구현 코드)
  4. 전략을 찾아주는 부분

총 네 부분으로 코드를 분리했습니다. 하나씩 살펴볼게요.

1. 공통 인터페이스

방문차량을 예약하고, 취소하는 부분입니다.

2. 업체 구분용 어노테이션

저 같은 경우에는 이름 기반(파일명)으로 빈을 찾을 수 있도록 하는 코드를 더 선호합니다. 어노테이션이 생기면 허들이 생긴다고 생각하기 때문이죠. 하지만 여기서는 어쩔 수 없었는데, 이유는 비슷한 이름의 업체가 많고 업체별 테스트도 코드에 함께 있는 구조이기 때문에 여러 경우의 수가 생기는 상황이었습니다. 따라서 명확하게 빈을 찾을 수 있도록 룰을 잡았어야 하는데 그 방법을 어노테이션으로 해결했습니다.

3. 업체별 전략 구현부 (실제 구현 코드)

인터페이스로 정의된 부분을 구현합니다. 지리산-소울 업체에서 기존과 다른통신을 하더라도 맞춰주는 부분을 지리산-소울 구현부에서만 하면됩니다.

4. 전략을 찾는 부분

위의 2번 어노테이션 기반으로 찾고 없으면 기본 구현체를 리턴해 처리하도록 했습니다.

최종코드

방문차량신청, 방문차량신청취소 부분만 존재하는 코드로 변경되었습니다. 새로운 업체와 연동하게되면 위의 “3. 업체별 구현부” 를 추가하면 됩니다. 코드가 정리되고 양이 줄어들며 분석하기도 쉬워졌습니다. 테스트코드도 마찬가지구요.

저의 경험이 도움되면 좋겠네요. 감사합니다.

--

--