테스트 코드가 없는 레거시 코드를 안정적으로 리팩토링하기

김동균·2022년 11월 11일
0

회사에서 레거시 시스템의 코드를 리팩토링 하는 업무를 진행했다.
문제는 기반이 되는 핵심 비즈니스의 테스트 코드가 없다는 것이었는데, 이는 리팩토링 이후 시스템의 정상 동작을 보장할 수 있는 안정장치가 없다는 의미기에 이를 우아하게 해결할 방법을 찾기 위해 관련 아티클들을 찾아보았다.
그 중 Intellij IDEA 유튜브 채널에서 진행한 Testing and Refactoring Legacy Code 세미나가 많은 도움이 되었다.
영상의 예제에서 사용된 code kata는 여기에서 확인할 수 있다.

영상에서는 레거시 코드를 리팩토링 할 때 deepest branch, shallowest branch 를 파악하는 것이 가장 핵심이라고 소개한다.
deepest branch란 코드의 가장 깊은 분기를 의미하고, shallowest branch는 그 반대의 의미이다.
코드의 복잡성을 줄여 더욱 shallowest한 branch를 만들기 위해 guard clause 개념도 함께 찾아보면 좋다.

shallowest branch는 테스트하기 가장 쉬운 부분이기에, shallowest branch를 찾아 테스트코드를 작성하고 그 과정에서 seam이라는 객체간의 연결점에서 발생하는 의존관계를 끊어내기 위한 개념을 도입한다.
seam이 존재하는 부분을 extract하고, 의존성을 끊기 위해 의존관계인 객체의 테스트용 구현체를 생성하여 테스트 코드가 성공하도록 필요한 부분을 override한다.
이 과정을 다음 shallowest branch를 찾아가며 대해 반복하여 수행하여 목표한 만큼의 테스트 커버리지를 달성한다.

테스트 코드 작성이 마무리 되었으면, 의존성이 가장 적은 deepest branch부터 리팩토링을 진행한다. 영상의 코드 카타에서는 객체를 캡슐화하지 않아 객체에게 명령을 내리지 않고, 객체에서 구현되어야 할 내부 코드를 외부에 노출하여 디미터 법칙을 위반하고 있다. 또한 SOLID 원칙중에서 SRP 원칙을 위배하고 있기 때문에, method를 extract하고 객체를 캡슐화하여 내부 로직 은닉, 이후 객체에게 명령을 내리도록 리팩토링한 다음 테스트 코드를 작성한다. 불필요한 if, switch문 등으로 인해 코드 복잡성이 올라간다면 guard clause를 적용한다. 애플리케이션 서비스인 TripService와, 상위 layer에서 처리해야 할 UserSession의 종속성을 끊기 위해 User를 외부에서 주입받도록 한다. 객체 지향의 메세지 전달을 위반하고, 코드 결합을 높이며 다형성을 위반하는 등 객체지향에서 벗어난 TripDAO의 static method 호출부를 리팩토링한다. TripDAO의 instance method를 만들어 기존의 static method를 호출하도록 하고, TripDAO를 TripService에서 DI한다.

영상에서 소개한 내용을 핵심적인 부분만 간략하게 추렸는데, 내용에 관심이 있는 분들은 간략하게 요약한 글보다는 원본 코드와 영상을 직접 보는게 이해하기 더 좋을 것이다.

profile
백엔드 개발자

0개의 댓글