
문제 인식
기능을 추가할 때마다 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. 순환 참조 해결.
설계 원칙
- 폴더명이 기능을 명확히 표현
cli/: CLI 도구 (명확)security/: 보안 전용 (명확)tools/: 개발 도구 (명확)src/: 뭐 하는 거? (불명확)
- 각 폴더는 독립적 기능 단위
- GUI와 Core는 분리
- CLI는 Core 사용
- 순환 참조 없음
- Mixin으로 기능 분리
- 단일 책임 원칙 (한 클래스 한 기능)
- 테스트 용이 (각 Mixin 독립 테스트)
- 재사용 가능 (다른 프로젝트에도 쓸 수 있음)
성과
코드 품질:
- 모듈화: 2,600줄 → 7개 모듈 (평균 377줄)
- 컴파일: 100% 성공
- 순환 참조: 0건
유지보수성:
- 최대 파일: 914줄 (이전 2,600줄)
- 함수당 평균: 20-30줄 (읽을 만하다)
- 클래스 책임: 단일 책임 원칙 (깔끔)
2,600줄 괴물이 7개 모듈로 분리됐다.
이제 새 기능 추가할 때 어디에 넣어야 할지 안다.
배운 것
- 리팩토링은 필수다
2,600줄은 유지보수 재앙. 3개월 뒤 내가 못 읽는다. - 폴더명이 중요하다
src/같은 애매한 이름 쓰지 말기.cli/,security/같이 명확하게. - Mixin 패턴의 힘
다중 상속으로 기능 조합. 레고 블록 조립하듯이. - Lazy import
순환 참조는 Lazy import로 해결. 필요할 때만 import. - 한번에 하지 말자
모듈화 → 폴더 정리 → Import 수정 → 순환 참조 해결. 단계별로.
다음 편: 보안, 워크플로우, 일단 완성
'개발관련' 카테고리의 다른 글
| Unity 게임 번역기 개발기 #6: 최적화와 크로스플랫폼 (0) | 2025.10.19 |
|---|---|
| Unity 게임 번역기 개발기 #5: RPG 메이커? 그것도 된다 (0) | 2025.10.18 |
| Unity 게임 번역기 개발기 #4: 개발은 디테일이다 (1) | 2025.10.12 |
| Unity 게임 번역기 개발기 #2: 돈을 쓰기로 했다 (0) | 2025.10.10 |
| Unity 게임 번역기 개발기 #1: 이게 시작이었다 (0) | 2025.10.09 |