BJSolveAssist 메모리 최적화 및 OOP 리팩토링

Hyeon-Uk·2024년 2월 22일
0

BJSolveAssist

목록 보기
4/4

기존의 문제

1. 강한 의존성

일단은 UI의 기본적인 구성은 다음과 같습니다.

노란색은 Toolbar들의 모여있는 UI, 주황색은 테스트 목록을 관리하는 List, 빨간색은 input 과 output, result를 관리할 수 있는 Editor입니다.

이를 MainView안에서 관리하고 있는데, 기존의 코드에선 다음과 같은 의존성을 나타냈습니다.

코드로 보면 다음과 같이 싱글톤 패턴을 이용하여 강한 결합을 나타내는 코드들로 이루어졌습니다.

protected MyTestListPanel(Project project) {
  this.myProject = project;
  myModel = new DefaultListModel<>();
  setCellRenderer(new MyCellRenderer());


  this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
  this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  addListSelectionListener((e) -> {
    int selectedIndex = this.getSelectedIndex();


    if (selectedIndex == -1) {
      return;
    }
    MyTestListItem selectedItem = myModel.getElementAt(selectedIndex);


    JBPanel myEditorPanel = (JBPanel) ComponentManager.getInstance().getComponent("myEditorPanel");


    ComponentManager.getInstance().removeChildrenComponents(myEditorPanel);


    myEditorPanel.add(selectedItem.getMyEditorPanel());
    myEditorPanel.repaint();
  });


  this.setModel(myModel);
}

문제점

이렇게 강한 결합이 나타나게 된다면 다음과 같은 문제점이 생기게됩니다.

  1. 확장에 닫혀있는 구조
    • MyTestListPanel과 같이 Test의 목록을 관리하는 클래스 내에서 MyEditorPanel이라는 구체적인 구현체에 대해서 강제하게 됩니다.
    • 따라서 다른 에디터인 OtherEditorPanel이라는 EditorPanel을 적용하기 위해 구현한 뒤 적용하려면 MyTestListPanel 또한 변경되어야 합니다.
    • 이는 OCP 원칙에 위반된다고 생각합니다.
  2. 테스트하기 어려운 구조
    • 클래스 내에 싱글톤 패턴을 이용하여 구체적인 구현체를 가져오는 코드로 인해 구체적인 구현체를 강제하는 코드들이 존재합니다.
    • 이로인해 순수한 MyTestListPanel의 테스트를 작성할 수 없습니다.

2. 한개의 클래스에 여러가지 책임

위의 의존성을 보면, TestListItem과 MyTestListPanel에서 모두 MyEditorPanel을 참조하고 있는것을 볼 수 있습니다. TestListItem과 MyTestListPanel에서 테스트 결과를 출력하는 코드가 포함이 되어있었습니다.

실제로 위의 코드를 보면 MyTestListPanel안에서 MyEditorPanel에 선택된 아이템을 그려라 라는 책임을 가지고 있습니다.

문제점

  1. 수정에 폐쇠적이지 못한 구조
    • SRP을 만족하지 못하고 있습니다.
    • 이는 참조하고 있는 클래스의 변경으로 인해, 현재의 클래스를 변경해야 할 수 있는 문제점이 발생할 수 있습니다.
    • SRP을 만족하지 못해 다른 클래스의 변경에 영향을 많이 받을 수 있기때문에 OCP 또한 위배됩니다.

3. 비효율적인 메모리 사용

기존 로직은 다음과 같았습니다.
1. 테스트 목록을 추가하면 TestListItem에 TestData를 저장하는 객체 및 EditorPanel이 함께 저장됩니다.
2. 테스트 목록을 추가하면 EditorPanel의 레이아웃에 해당 TestListItem에 담겨있는 EditorPanel이 그려집니다.

문제점

이는 정말 비효율적인 기능입니다. 다음과 같이 테스트 목록에 여러개의 테스트가 생성된다면, 메모리는 비효율적으로 증가합니다.

각 TestListItem에는 TestData가 독립적으로 존재해야하지만, MyEditorPanel이 생성되는것은 메모리 낭비가 매우 큰 테스크라 이를 해결해야 했습니다.

또한 UI에서 사용되는 JLabel들이 대부분 Immutable한 성질을 가지고 있습니다. 만약 이런 JLabel이 불변의 객체들이 각 컴포넌트에서 사용될때마다 new가 된다면 비효율적이라고 생각합니다.

해결과정

먼저 강한 의존성을 가지고 있는 이유가 단 하나만의 책임감을 갖고 있지 않아서라고 생각했습니다.

이를 해결하기 위해 각 클래스의 책임을 아래와 같이 정리했습니다.

책임을 정리하고 보니 기능들을 인터페이스 및 추상클래스로 추출하여 의존성을 역전시킬 수 있었으며, 의존성을 역전시키며 필요한 의존관계를 Factory에서 생성하여 주입을 하니 각 컴포넌트들이 한가지의 책임만을 가지고 일을 진행할 수 있게되었고, 다른 컴포넌트들의 변경에 따른 영향을 받지 않도록 설계할 수 있었습니다.

또한 테스트 코드 작성에 용이한 구조로 변경되었습니다.

이렇게 기능과 책임을 분리하니, 로직을 수정하여 메모리 문제 또한 해결할 수 있었습니다.
1. Editor는 입/출력의 책임만을 가지고 있으니, 한개의 Editor만 띄워준다.
2. 테스트 목록에서 테스트를 클릭했을 때 기존의 테스트에 갱신된 테스트데이터 값을 업데이트 해준 뒤, 클릭한 테스트 목록의 테스트 데이터를 editor에 업데이트 해준다.
3. 테스트를 실행, 전체실행 시작 전에도 현재 editor에 있는 값들을 해당하는 TestData에 업데이트 해준다.

또한 공통적으로 사용되는 JLabel과 같은 요소들은 재활용 할 수 있는 플라이웨이트 패턴(Flyweight pattern) 을 통해 메모리를 아낄 수 있어 적용했습니다.

책임을 분리하니 각 TestListItem마다 Editor를 생성했을 때보다 메모리를 아래와 같이 아낄 수 있었습니다.

이전 코드에서는 10000개의 테스트 목록을 추가하니 OutOfMemory로 인해 프로세스가 죽어버렸습니다.

비교를 해봐야하니 1000개의 테스트 목록을 추가하여 비교해보겠습니다.

리팩토링 이전

리팩토링 이후

리팩토링 결과 1000개의 데이터를 추가했을 때 약 98%의 메모리를 절약할 수 있었습니다.

리팩토링 이후로 수 많은 테스트 데이터를 추가한다고 하더라도, 메모리의 낭비를 막았기 때문에 서비스의 안정성을 더 높였다고 생각합니다.

느낀점

리팩토링을 진행하며 엉망진창인 코드를 OOP를 목적으로 리팩토링을 하다보니 대부분의 문제가 해결되는 신기한 경험을 했습니다.

책임을 분리하고, 의존관계를 역전시키다보니 연관관계가 느슨해지며 테스트코드를 작성하기 용이해지고, 확장과 수정이 더 용이해지는 경험을 했습니다.

또한 책임분리를 통해 불필요한 로직 및 연관관계 제거를 진행했고, 이를 통해 메모리를 아낄 수 있는 경험을 했습니다.

추가적으로 새로운 패턴을 도입하며 메모리를 아낄 수 있는 경험을 했습니다.

이런 경험들을 기반으로, 새로운 문제가 발생했을 때, 어떤식으로 구조를 설계하고 어떤 패턴을 도입해야 하는지에 대한 사고가 확장됐습니다.

profile
Hi🖐

0개의 댓글