소프트웨어 개발에서 전역 변수는 편리하지만, 유지보수성과 테스트 가능성을 크게 저하시킬 수 있습니다. 전역 참조 캡슐화(Encapsulate Global Reference)는 이러한 문제를 해결하기 위해 전역 변수를 추상화 계층 뒤로 숨기고, 코드의 결합도를 줄이며, 테스트 가능성을 높이는 의존성 제거 기법입니다. 이 글에서는 해당 기법의 개념, 적용 방법 및 테스트 코드 작성 예제를 살펴봅니다.
1. 전역 참조 캡슐화란?
전역 참조 캡슐화는 전역 변수나 전역 함수와 같은 전역 상태를 직접 참조하지 않도록 하여 코드의 결합도를 줄이는 기법입니다. 이를 위해 전역 참조를 캡슐화한 클래스나 인터페이스를 생성하고, 해당 인터페이스를 통해 전역 상태를 간접적으로 관리합니다.
이 기법은 다음과 같은 이점을 제공합니다:
- 유지보수성 향상: 전역 참조를 추적하고 변경하는 작업이 쉬워집니다.
- 테스트 용이성: 전역 상태를 Mock 객체로 대체할 수 있어 단위 테스트가 가능해집니다.
- 확장성 증가: 코드 수정 없이 전역 상태의 구현을 교체하거나 확장할 수 있습니다.
2. 전역 참조 캡슐화 적용 예제
기존 코드
다음은 전역 함수를 직접 사용하는 코드입니다. 전역 함수 ::GetOption은 전역 상태를 관리하며, 테스트하기 어렵고 유지보수가 까다롭습니다.
void ColumnModel::update() {
alignRows();
Option resizeWidth = ::GetOption("ResizeWidth");
if (resizeWidth.isTrue()) {
resize();
} else {
resizeToDefault();
}
}
변경된 코드
전역 참조를 캡슐화하여 독립적인 인터페이스와 구현 클래스를 도입한 코드입니다.
class OptionSource {
public:
virtual ~OptionSource() = 0;
virtual Option GetOption(const string& optionName) = 0;
virtual void SetOption(const string& optionName, const Option& newOption) = 0;
};
class ProductionOptionSource : public OptionSource {
public:
Option GetOption(const string& optionName) override {
return ::GetOption(optionName);
}
void SetOption(const string& optionName, const Option& newOption) override {
::SetOption(optionName, newOption);
}
};
class ColumnModel {
private:
OptionSource* optionSource;
public:
ColumnModel(OptionSource* source) : optionSource(source) {}
void update() {
alignRows();
Option resizeWidth = optionSource->GetOption("ResizeWidth");
if (resizeWidth.isTrue()) {
resize();
} else {
resizeToDefault();
}
}
};
3. 주요 변경점과 이점
- 추상화 계층 추가: OptionSource 인터페이스를 추가하여 전역 상태와의 직접적인 의존성을 제거.
- Mocking 지원: ProductionOptionSource를 테스트 환경에서는 Mock 객체로 대체할 수 있어 유연성 증가.
- 의존성 주입: ColumnModel 클래스가 전역 상태에 직접 접근하지 않고 OptionSource 인터페이스를 통해 상태를 관리.
4. 테스트 코드 작성
아래는 ColumnModel 클래스에 대한 단위 테스트 코드입니다. 테스트에서는 Mock 객체를 사용하여 전역 참조를 대체합니다.
MockOptionSource 클래스
class MockOptionSource : public OptionSource {
private:
std::map<std::string, Option> options;
public:
Option GetOption(const string& optionName) override {
return options[optionName];
}
void SetOption(const string& optionName, const Option& newOption) override {
options[optionName] = newOption;
}
};
테스트 코드
#include <gtest/gtest.h>
#include "ColumnModel.h"
#include "MockOptionSource.h"
TEST(ColumnModelTest, UpdateWithResizeWidthTrue) {
// Arrange
MockOptionSource mockSource;
mockSource.SetOption("ResizeWidth", Option(true));
ColumnModel model(&mockSource);
// Act
model.update();
// Assert
// resize() 메서드가 호출되었는지 확인하는 로직 추가
// (보통 Spy 객체를 사용하거나 다른 검증 방법을 활용)
}
TEST(ColumnModelTest, UpdateWithResizeWidthFalse) {
// Arrange
MockOptionSource mockSource;
mockSource.SetOption("ResizeWidth", Option(false));
ColumnModel model(&mockSource);
// Act
model.update();
// Assert
// resizeToDefault() 메서드가 호출되었는지 확인하는 로직 추가
}
위 테스트 코드는 MockOptionSource를 사용하여 전역 상태를 대신하며, resize() 및 resizeToDefault() 호출 여부를 검증할 수 있습니다.
5. 결론
전역 참조 캡슐화는 기존 코드의 전역 의존성을 제거하여 유지보수성과 테스트 가능성을 크게 향상시키는 효과적인 리팩토링 기법입니다. 특히, 전역 변수를 직접 사용하는 코드를 인터페이스와 구현 클래스로 대체하면 코드의 유연성과 확장성이 증가합니다.
위에서 제시한 예제와 테스트 코드를 참고하여 여러분의 프로젝트에서 전역 참조를 캡슐화하고 보다 안정적인 코드를 작성해 보세요.
'SW 개발 일반 > 레거시코드와 놀기' 카테고리의 다른 글
레거시 코드와 놀기: 정적 메소드 드러내기 (Expose Static Method) (0) | 2025.01.22 |
---|---|
느리지만 그나마 쉬운 테스트 개발하기 (0) | 2025.01.20 |
레거시 코드와 놀기: 메소드 객체 추출 (Breaking Out Method Object) (0) | 2025.01.19 |
레거시 코드와 놀기: 매개변수 적응 기법 (Adapt Parameter) (0) | 2025.01.17 |
코드 리뷰와 멘탈 모델의 중요성: 더 나은 소프트웨어를 위한 기틀 (0) | 2025.01.16 |