C#) Team Text RPG 3 (StackOverflow)
StackOverflow 예외 발생 경험
오늘 C#을 학습하며 처음으로 StackOverflowException을 경험하게 되었다.
이전에 다른 언어를 학습할 때 종종 접했지만, 개인 작업과는 또 다른 의미가 있었다. 특히, 팀 프로젝트 중에 발생한 오류였기에 문제 해결 과정에서 많은 것을 배울 수 있었다.
이번 글에서는 어디에서, 어떤 식으로 스택 메모리가 초과되었는지, 그리고 이를 어떻게 해결했는지 정리해 보려고 한다.
문제점 분석
StackOverflow 발생 원인
우리 팀의 InventoryManager 클래스는 Reward 객체를 내부에서 관리해야 했다.
이를 위해 InventoryManager의 생성자에서 Reward를 인스턴스화했는데, 동시에 Reward에서는 InventoryManager의 멤버 변수를 참조하는 코드가 포함되어 있었다.
이로 인해 무한 재귀 호출이 발생하며 StackOverflowException이 발생했다.
문제 발생 시나리오
-
Reward객체가 생성됨 -
Reward의 인스턴스 필드mountableItems가InventoryManager.Instance.mountableItems를 참조하려고 함 -
InventoryManager.Instance가 초기화됨 (new InventoryManager()) -
InventoryManager생성자에서Reward를 또 호출하는 코드가 있음 -
다시
Reward의 인스턴스 필드mountableItems가InventoryManager.Instance.mountableItems를 참조함 → 무한 루프 발생
스택 오버플로우(StackOverflowException) 발생
문제 발생 코드
// InventoryManager.cs
private InventoryManager()
{
reward = new Reward();
}
public List<MountableItem> mountableItems = new List<MountableItem>();
public PotionItem potion = new PotionItem();
// Reward.cs
List<MountableItem> mountableItems = InventoryManager.Instance.mountableItems;
PotionItem potion = InventoryManager.Instance.potion;
위 두 코드파일에서 계속 순환 및 생성을 거치다가 StackOverflow를 발생시켰다.
해결 방법
static
// Reward.cs
static List<MountableItem> mountableItems = InventoryManager.Instance.mountableItems;
static PotionItem potion = InventoryManager.Instance.potion;
우선 자체 해결 방법은 static으로 찾았다.
static 키워드를 추가하면 클래스가 처음 로드될 때 한 번만 할당되므로, 이후 재할당이 이루어지지 않는다.
이를 통해 Reward 객체가 반복적으로 생성되는 문제를 방지할 수 있다.
그러나, 이 방식은 Reward와 InventoryManager 간의 결합도를 더욱 강하게 만들고, Reward가 InventoryManager에 강하게 의존하는 구조가 된다.
따라서 더 나은 해결 방법을 모색했다.
매개변수 활용
우선, 이 방식은 튜터님의 도움을 받아 찾게된 방법이다. 결론적으로 이 방법을 채택했다.
// Reward.cs
public Reward(List<MountableItem> _mountableItems, PotionItem _potion)
{
this.mountableItems = _mountableItems;
this.potion = _potion;
}
위처럼 InventoryManager의 멤버 변수를 직접 참조하지 않고, InventoryManager에서 Reward 객체를 생성할 때 필요한 데이터를 매개변수로 전달하는 방식으로 변경했다.
// InventoryManager.cs
private InventoryManager()
{
reward = new Reward(mountableItems, potion);
}
기존 코드에서는 Reward가 직접적으로 InventoryManager에 의존하고 있어서, InventoryManager 내부 구조가 변경될 경우 Reward도 영향을 받는다.
하지만, 매개변수 방식(Dependency Injection)을 사용하면 Reward가 InventoryManager에 직접 의존하지 않게 되므로 결합도가 낮아지고 유지보수가 쉬워진다.
또한, Reward를 독립적으로 테스트하기 쉬워진다.
기존 코드에서는 Reward가 InventoryManager.Instance를 참조하기 때문에, 단위 테스트 시 실제 InventoryManager를 생성해야 하는 문제가 발생했다.
반면, 매개변수 방식이면 다른 테스트용 데이터를 쉽게 주입할 수 있어 단위 테스트가 쉬워진다.
결론
이번 StackOverflowException을 해결하면서 클래스 간 의존성이 높은 코드가 어떻게 문제를 일으킬 수 있는지를 경험했다.
초기에는 static 키워드를 활용해 문제를 해결하려했지만, 궁극적인 해결책은 Dependency Injection을 적용하여 Reward가 InventoryManager에 직접 의존하지 않도록 하는 것이었다.
이를 통해 유지보수성 증가, 단위 테스트 가능, 결합도 감소 등의 장점을 얻을 수 있었다.
앞으로도 객체 간 의존성을 최소화하는 방향으로 코드를 작성하며, 확장성과 유지보수성이 좋은 구조를 고민해야겠다.
댓글남기기