개발관련

Unity 게임 번역기 개발기 #3: 코드가 괴물이 되다

삽질연구소 소장 2025. 10. 11. 12:30

문제 인식

기능을 추가할 때마다 main_window.py에 코드를 계속 때려박았다.

# main_window.py (2,600줄)
class MainWindow(QMainWindow):
    def __init__(self):
        # 프로젝트 관리
        # 번역 엔진
        # Excel 관리
        # UI 생성
        # 세션 저장
        # ... 100줄

    def create_translation_tab(self):
        # 200줄

    def start_translation(self):
        # 150줄

    def export_excel(self):
        # 100줄

    # ... 계속 이어진다... 2,000줄 더...

스크롤을 내리는데 끝이 안 보인다. 어디에 뭐가 있는지 나도 모르겠다.

버그 수정하려고 검색하면 같은 함수가 3번 나온다. 복붙의 역사.

Mixin 패턴 (분할 통치)

기능별로 파일을 쪼갰다.

# handlers/excel_handler.py
class ExcelHandlerMixin:
    def export_excel(self):
        # Excel 내보내기만
        pass

    def import_excel(self):
        # Excel 가져오기만
        pass

# managers/session_manager.py
class SessionManagerMixin:
    def _save_session(self):
        # 세션 저장만
        pass

    def _restore_session(self):
        # 세션 복원만
        pass

# managers/project_manager.py
class ProjectManagerMixin:
    def auto_create_or_select_project(self):
        # 프로젝트 관리만
        pass

# ui/tab_builder.py
class TabBuilderMixin:
    def create_translation_tab(self):
        # UI 생성만
        pass

메인 파일은 Mixin 조합으로.

# main_window.py (914줄)
class MainWindow(
    ExcelHandlerMixin,
    SessionManagerMixin,
    ProjectManagerMixin,
    TabBuilderMixin,
    QMainWindow
):
    def __init__(self):
        # 초기화만
        super().__init__()

결과

Before: main_window.py = 2,600줄 (악몽)

After:
├── main_window.py          = 914줄 (65% 감소!)
├── ui/tab_builder.py       = 459줄
├── managers/
│   ├── project_manager.py = 254줄
│   └── session_manager.py = 204줄
├── handlers/
│   └── excel_handler.py   = 216줄
├── workers/
│   └── translation_worker.py = 460줄
└── dialogs/
    └── chapter_selection.py = 130줄

Total: 2,637줄 (7개 모듈)

이제 어디 봐야 할지 안다. Excel 문제? → excel_handler.py 보면 됨.

폴더 구조 최적화 (이름이 중요하다)

폴더명이 애매했다.

src/     # 뭐 하는 폴더? 다 소스 코드지
utils/   # 보안이랑 도구가 왜 같이?
formats/ # naninovel.py 하나만 덩그러니...

명확하게 바꿨다.

Before                      After
────────────────────────────────────────
src/                   →    cli/
                            (아, CLI 도구구나)

utils/                 →    security/
                            (보안 전용)

                       →    tools/
                            (개발 도구)

formats/naninovel.py   →    core/
                            (핵심 엔진에 통합)

최종 구조

gametranslator/
├── gui/          # PyQt6 GUI (한눈에 알아본다)
├── core/         # 번역 엔진 (핵심)
├── cli/          # CLI 도구 (내부용)
├── security/     # API 키 암호화 (중요)
├── tools/        # 개발 도구 (잡다한 것들)
└── examples/     # 예시 파일 (사용자용)

폴더 열면 바로 안다. "아, 여기 보면 되겠네."

Import 구문 수정 (대공사)

폴더 이름이 바뀌면서 import도 전부 고쳐야 한다.

# Before (옛날)
from src.extractor import UnityTextExtractor
from utils.secure_storage import SecureStorage

# After (지금)
from cli.extractor import UnityTextExtractor
from security.secure_storage import SecureStorage

전체 프로젝트를 검색해서 일괄 수정.

# 검증 (떨리는 순간)
python -m py_compile gui/**/*.py core/**/*.py cli/**/*.py
# 100% 성공!

터미널에서 에러 없이 넘어갔을 때 그 희열.

순환 참조 제거 (무한 루프의 공포)

ImportError: cannot import name 'UniversalTranslator' from partially initialized module 'core.translator'
(most likely due to a circular import)

순환 참조. 모듈 A가 B를 import, B가 A를 import.

Lazy import로 해결.

# Before (순환 참조)
from core.translator import UniversalTranslator

class MainWindow(QMainWindow):
    def __init__(self):
        self.translator = UniversalTranslator()  # 여기서 터짐

# After (Lazy import)
class MainWindow(QMainWindow):
    def __init__(self):
        self.translator = None

    def start_translation(self):
        if not self.translator:
            from core.translator import UniversalTranslator
            self.translator = UniversalTranslator()  # 필요할 때만

필요할 때 import. 순환 참조 해결.

설계 원칙

  1. 폴더명이 기능을 명확히 표현
    • cli/: CLI 도구 (명확)
    • security/: 보안 전용 (명확)
    • tools/: 개발 도구 (명확)
    • src/: 뭐 하는 거? (불명확)
  2. 각 폴더는 독립적 기능 단위
    • GUI와 Core는 분리
    • CLI는 Core 사용
    • 순환 참조 없음
  3. Mixin으로 기능 분리
    • 단일 책임 원칙 (한 클래스 한 기능)
    • 테스트 용이 (각 Mixin 독립 테스트)
    • 재사용 가능 (다른 프로젝트에도 쓸 수 있음)

성과

코드 품질:
- 모듈화: 2,600줄 → 7개 모듈 (평균 377줄)
- 컴파일: 100% 성공
- 순환 참조: 0건

유지보수성:
- 최대 파일: 914줄 (이전 2,600줄)
- 함수당 평균: 20-30줄 (읽을 만하다)
- 클래스 책임: 단일 책임 원칙 (깔끔)

2,600줄 괴물이 7개 모듈로 분리됐다.

이제 새 기능 추가할 때 어디에 넣어야 할지 안다.

배운 것

  1. 리팩토링은 필수다
    2,600줄은 유지보수 재앙. 3개월 뒤 내가 못 읽는다.
  2. 폴더명이 중요하다
    src/ 같은 애매한 이름 쓰지 말기. cli/, security/ 같이 명확하게.
  3. Mixin 패턴의 힘
    다중 상속으로 기능 조합. 레고 블록 조립하듯이.
  4. Lazy import
    순환 참조는 Lazy import로 해결. 필요할 때만 import.
  5. 한번에 하지 말자
    모듈화 → 폴더 정리 → Import 수정 → 순환 참조 해결. 단계별로.

다음 편: 보안, 워크플로우, 일단 완성