소프트웨어 유지보수와 테스트 자동화를 어렵게 만드는 주요 요인 중 하나는 의존성(Dependency)입니다. 레거시 코드에서는 특히 static 메서드가 이러한 문제를 심화시키는 경우가 많습니다. static 메서드는 특정한 상태를 가지지 않고 클래스 레벨에서 호출되기 때문에, 객체를 주입하여 대체하기 어렵습니다. 이러한 문제를 해결하는 방법 중 하나가 Introduce Instance Delegator 기법입니다.
이 기법은 기존 static 메서드를 인스턴스 메서드로 감싸서 객체를 통해 호출할 수 있도록 만드는 방식입니다. 이를 활용하면 기존 코드를 크게 변경하지 않으면서도 의존성을 줄이고, 테스트 가능성을 높일 수 있습니다.
Introduce Instance Delegator 기법 적용
Introduce Instance Delegator를 적용하려면 먼저 static 메서드를 직접 호출하는 부분을 찾아야 합니다. 이후 해당 static 메서드를 감싸는 인스턴스 메서드를 추가하여 위임하는 구조를 만듭니다. 마지막으로 기존 static 메서드 호출을 인스턴스 메서드 호출로 변경하여 객체를 통한 접근이 가능하도록 수정합니다.
이 과정에서 필요한 경우, Parameterize Method 또는 Dependency Injection(DI) 기법을 활용하여 객체를 유연하게 주입할 수도 있습니다.
코드 예제
다음은 static 메서드를 활용하는 기존 코드입니다.
public class BankingServices {
public static void updateAccountBalance(int userId, double amount) {
// 은행 계좌 잔액 업데이트 로직
}
}
위 코드의 static 메서드는 직접 호출되기 때문에 테스트하기 어렵습니다. Introduce Instance Delegator 기법을 적용하면 다음과 같이 변경할 수 있습니다.
public class BankingServices {
public static void updateAccountBalance(int userId, double amount) {
// 기존 static 메서드 유지
}
public void updateBalance(int userId, double amount) {
updateAccountBalance(userId, amount);
}
}
이제 기존 static 호출을 인스턴스 호출로 변경하면 됩니다.
변경 전:
public class SomeClass {
public void someMethod() {
BankingServices.updateAccountBalance(1001, 500.0);
}
}
변경 후:
public class SomeClass {
private final BankingServices bankingServices;
public SomeClass(BankingServices bankingServices) {
this.bankingServices = bankingServices;
}
public void someMethod() {
bankingServices.updateBalance(1001, 500.0);
}
}
이제 SomeClass는 BankingServices 객체를 생성자에서 주입받도록 변경되었으며, 이를 통해 테스트 환경에서 쉽게 Fake 객체를 활용할 수 있습니다.
테스트 코드 작성
Introduce Instance Delegator 기법을 적용한 후, 단위 테스트를 작성할 수 있습니다.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class FakeBankingServices extends BankingServices {
private int lastUpdatedUserId;
private double lastUpdatedAmount;
@Override
public void updateBalance(int userId, double amount) {
this.lastUpdatedUserId = userId;
this.lastUpdatedAmount = amount;
}
public boolean verifyUpdate(int userId, double amount) {
return this.lastUpdatedUserId == userId && this.lastUpdatedAmount == amount;
}
}
public class SomeClassTest {
@Test
public void testSomeMethod() {
// Fake 객체 생성
FakeBankingServices fakeService = new FakeBankingServices();
SomeClass someClass = new SomeClass(fakeService);
// 동작 수행
someClass.someMethod();
// 검증
assertTrue(fakeService.verifyUpdate(1001, 500.0));
}
}
위와 같이 SomeClass가 BankingServices를 직접 호출하는 대신, FakeBankingServices를 활용하여 테스트할 수 있도록 변경되었습니다.
Introduce Instance Delegator 기법 적용 시 고려할 점
Introduce Instance Delegator 기법을 적용할 때 몇 가지 고려해야 할 사항이 있습니다.
Utility 클래스와 Singleton 패턴
유틸리티 클래스는 본래 static 메서드로 구성되어 있어 인스턴스 메서드를 추가하는 것이 어색할 수 있습니다. Singleton 패턴을 사용하는 경우에도 객체를 어떻게 주입할 것인지 고민해야 합니다.
객체 주입 방식
Introduce Instance Delegator 기법을 사용할 때는 객체를 어떻게 주입할지 결정해야 합니다. Dependency Injection 프레임워크(Spring, Guice 등)와 함께 사용하면 더 효과적이며, 테스트 환경에서는 Fake 객체를 활용하는 것이 유리합니다.
결론
Introduce Instance Delegator 기법은 static 메서드의 직접 호출을 피하고, 인스턴스 메서드를 통해 대체하는 방식입니다. 이를 통해 코드의 테스트 가능성을 높이고, 의존성을 제거하며, 유연한 설계를 가능하게 합니다. 특히 레거시 코드에서 static 의존성이 많은 경우, 점진적인 개선 방법으로 유용한 전략이 될 수 있습니다.
레거시 코드의 테스트 가능성을 높이고 유지보수를 쉽게 만들고 싶다면, Introduce Instance Delegator 기법을 적극적으로 활용해 보시는 것을 추천드립니다.
'SW 개발 일반 > 레거시코드와 놀기' 카테고리의 다른 글
소프트웨어 엔트로피 (Software Entropy) (0) | 2025.03.06 |
---|---|
레거시 코드와 놀기: 인터페이스 추출 (Extract Interface) (0) | 2025.01.31 |
레거시 코드와 놀기: Getter 메소드 추출과 재정의 (Extract and Override Getter) (0) | 2025.01.27 |
레거시 코드와 놀기 백서: Working Effectively with Legacy Code (0) | 2025.01.25 |
레거시 코드와 놀기: 호출 추출과 재정의 (Extract and Override Call) (0) | 2025.01.24 |