본문 바로가기
Data Engineering & Automation/GBIF Darwin Core

PySide6 리팩토링 1편: 프로젝트 구조 다시 읽기

by JINJINC 2026. 5. 27.
728x90
반응형

처음 만든 파이썬 프로젝트 리팩토링하기 1편: 기능별로 나누지 못했던 코드를 다시 읽기

이 프로젝트는 내가 처음으로 만들어본 파이썬 프로젝트에 가깝다. 처음에는 기능을 완성하는 것이 가장 중요했다. 버튼을 만들고, 클릭 이벤트를 연결하고, Excel 파일을 읽고, 결과를 테이블에 보여주는 흐름을 하나씩 붙이다 보니 일단 동작하는 프로그램은 만들 수 있었다.

하지만 기능이 늘어날수록 코드가 점점 읽기 어려워졌다. 특히 PySide6로 만든 데스크톱 GUI 프로그램에서는 화면 코드, 버튼 이벤트, 파일 처리, 데이터 변환 로직이 자연스럽게 한 클래스 안으로 모이기 쉬웠다. 그 결과 MainWindow가 앱의 거의 모든 일을 알고 있는 상태가 되었다.

이번 글은 “처음부터 잘 나누지 못했던 코드”를 다시 읽으면서, 기능별로 책임을 나누는 기준을 세워보는 기록이다. 당장 모든 코드를 멋지게 고치는 것이 목표는 아니다. 내가 왜 이 코드를 이렇게 작성했는지 돌아보고, 앞으로 어떤 단위로 리팩토링하면 좋을지 정리해보려 한다.

프로젝트가 하는 일

이 프로젝트는 특정 업무에서 반복적으로 발생하던 데이터 정리 과정을 더 효율적으로 처리하기 위해 개인적으로 제작하여 공유한 프로젝트입니다.

이 프로젝트는 생물다양성 데이터를 GBIF에 업로드하기 쉬운 형태로 정리하는 데스크톱 도구다. 사용자는 Excel 또는 CSV 파일을 불러오고, 실제 헤더 행을 선택한 뒤, 원본 컬럼을 Darwin Core 필드에 매핑한다. 이후 변환 결과를 미리 보고, 비어 있는 값을 확인하거나 좌표를 지도에서 검토한 다음 Excel 파일로 저장할 수 있다.

사용자 흐름을 간단히 정리하면 이렇다.

  1. 원본 파일을 불러온다.
  2. 미리보기에서 실제 헤더 행을 선택한다.
  3. 원본 컬럼을 GBIF/Darwin Core 필드에 연결한다.
  4. 자동 매핑 또는 직접 입력값을 보정한다.
  5. 결과 테이블을 확인하고 필요한 값을 수정한다.
  6. 좌표를 지도에서 확인한다.
  7. 최종 Excel 파일로 저장한다.

이 흐름만 보면 단순한 변환 도구처럼 보이지만, 실제로는 UI, 파일 입출력, 도메인 필드 정의, 자동 매핑, 좌표 검증, 지도 HTML 생성, 결과 편집까지 여러 책임이 함께 움직인다.

현재 구조 살펴보기

현재 프로젝트는 크게 ui, services, config, utils로 나뉘어 있다.

app/
  ui/
    main_window.py
    paste_table_widget.py
  services/
    excel_service.py
    mapping_service.py
  config/
    app_info.py
    auto_mapping.py
    country_code.py
    dwc_fields.py
  utils/
    paths.py

겉으로는 이미 역할별 디렉터리가 나뉘어 있다. 이 점은 좋다. 리팩토링의 출발점이 완전히 엉킨 상태는 아니다. 다만 실제 책임을 따라가 보면 main_window.py에 아직 많은 일이 모여 있다.

MainWindow가 맡고 있는 일

app/ui/main_window.pyMainWindow는 PySide6의 QMainWindow를 상속한 메인 화면 클래스다. 이 클래스는 화면을 만들고 버튼 이벤트를 연결하는 역할을 한다. 그런데 현재는 그 이상의 책임도 함께 갖고 있다.

대표적으로 이런 일들이 MainWindow 안에 있다.

  • 전체 화면 구성과 스타일 정의
  • Excel/CSV 파일 선택과 로딩
  • 시트 선택
  • 헤더 행 선택과 적용
  • 매핑 UI 생성
  • 자동 매핑 설정 화면 관리
  • 선택된 매핑 값 수집
  • 변환 결과 생성 호출
  • 결과 테이블 표시와 수정값 수집
  • 빈 값 요약 생성
  • 좌표 컬럼 선택
  • 좌표 유효성 검사
  • Leaflet 기반 지도 HTML 생성
  • 결과 Excel 저장

이 정도면 MainWindow는 단순한 창 객체가 아니라, 앱의 조정자이자 일부 서비스이자 일부 도메인 로직까지 겸하고 있다. 작은 앱에서는 이런 방식이 빠르게 기능을 만들 수 있다는 장점이 있다. 하지만 기능이 늘어날수록 다음과 같은 문제가 생긴다.

  • UI 코드를 읽다가 데이터 변환 규칙까지 함께 읽어야 한다.
  • 좌표 검증처럼 테스트하고 싶은 로직이 GUI 클래스 안에 갇힌다.
  • 버튼 하나를 수정하려고 해도 파일 입출력, 매핑, 지도 생성 코드가 같은 파일 안에 보여 집중이 흐려진다.
  • 새로운 기능을 추가할 때 어느 위치에 넣어야 할지 애매해진다.

그래서 앞으로의 리팩토링 방향은 MainWindow를 없애는 것이 아니라, MainWindow가 화면 조립과 이벤트 연결에 집중하도록 만드는 것이다.

이미 잘 분리된 부분

좋은 소식도 있다. 이미 서비스와 설정으로 빠져 있는 코드가 있다.

ExcelService는 파일 입출력 책임을 맡는다. Excel과 CSV를 구분하고, 여러 CSV 인코딩을 시도하고, 헤더 없는 원본 읽기와 헤더 적용 읽기를 제공한다. 저장할 때는 날짜나 좌표처럼 문자열로 유지해야 하는 컬럼의 서식도 처리한다.

MappingService는 원본 데이터프레임을 Darwin Core 형태의 데이터프레임으로 바꾼다. 자동 매칭, 날짜 포맷, 좌표 문자열 유지 같은 변환 규칙도 여기에서 관리한다.

dwc_fields.py는 Darwin Core 필드 목록과 기본 노출 필드를 정의한다. UI가 필드 이름을 직접 하드코딩하지 않고 설정에서 받아올 수 있게 해준다.

PasteTableWidget은 결과 테이블에서 Ctrl+V 붙여넣기를 처리하는 작은 위젯이다. 이런 분리는 좋다. 특정 UI 동작을 메인 윈도우에서 떼어내 독립적인 위젯으로 만든 예시이기 때문이다.

즉, 이 프로젝트는 이미 “분리할 수 있다”는 방향을 갖고 있다. 앞으로의 리팩토링은 이 방향을 더 밀어주는 작업에 가깝다.

기능별 책임 지도

현재 코드를 기능 기준으로 다시 묶으면 다음처럼 볼 수 있다.

기능 현재 주요 위치 앞으로의 방향
파일 읽기/저장 ExcelService, MainWindow 파일 다이얼로그는 UI에 두고, 읽기/저장은 서비스에 유지
Darwin Core 필드 정의 dwc_fields.py 설정 모듈로 유지
자동 매핑 규칙 auto_mapping.py, MainWindow 규칙 저장/로드는 설정, 편집 화면은 별도 UI로 분리 가능
매핑 변환 MappingService, MainWindow 변환 규칙은 서비스에 집중
결과 미리보기 MainWindow 테이블 표시와 데이터 수집을 별도 helper 또는 위젯으로 분리 가능
빈 값 요약 MainWindow 데이터 분석 로직은 서비스 함수로 분리 가능
좌표 검증/지도 생성 MainWindow 좌표 검증과 HTML 생성은 별도 서비스 후보
붙여넣기 UX PasteTableWidget 현재처럼 위젯에 유지

이 표가 앞으로의 리팩토링 로드맵이 된다. “파일이 크니까 쪼갠다”가 아니라, “테스트하거나 재사용하고 싶은 책임을 UI 밖으로 꺼낸다”는 기준을 세우는 것이 중요하다.

첫 번째 리팩토링 기준

처음 프로젝트를 만들 때는 “이 기능이 어디에 있어야 하는가?”보다 “어떻게 하면 지금 바로 동작하게 만들 수 있을까?”를 더 많이 생각했다. 이제는 반대로, 이미 동작하는 코드를 보면서 다음 기준으로 나눠보려고 한다.

  1. UI 이벤트와 순수 로직을 분리한다.
  2. 파일 입출력처럼 외부 세계와 닿는 코드는 서비스에 모은다.
  3. 도메인 지식은 설정 또는 전용 서비스에 둔다.

여기서 말하는 UI 이벤트는 “버튼을 눌렀을 때 무엇을 호출할지”에 가깝다. 반면 순수 로직은 “어떤 값이 비어 있는지 계산한다”, “좌표가 올바른 범위인지 판단한다”, “Darwin Core 필드에 맞춰 데이터프레임을 만든다” 같은 코드다.

순수 로직을 UI 밖으로 꺼내면 테스트하기 쉬워진다. PySide6 창을 띄우지 않아도 입력 데이터만 넣어서 결과를 확인할 수 있기 때문이다. 그리고 블로그로 정리할 때도 코드의 의도를 설명하기 쉬워진다.

다음 편에서 할 일

다음 편에서는 가장 경계가 분명한 기능부터 다룰 예정이다. 후보는 Excel 입력/출력이다.

이미 ExcelService가 있지만, MainWindowload_excel, apply_selected_header, export_mapped_excel에는 여전히 UI 흐름과 파일 처리 흐름이 섞여 있다. 다음 글에서는 이 코드를 읽으면서 다음 질문에 답해볼 것이다.

  • 파일 다이얼로그는 어디까지 UI 책임인가?
  • 파일 형식 검증은 어디에 있어야 하는가?
  • 헤더 행 선택 이후의 데이터 로딩은 어떻게 읽기 쉬워질 수 있는가?
  • 저장할 때 UI에서 알아야 하는 정보와 서비스가 알아야 하는 정보는 무엇인가?

처음 만든 프로젝트라서 아쉬운 부분은 많지만, 그래서 오히려 좋은 리팩토링 재료가 되었다. 기능을 완성하느라 한곳에 모아둔 코드를 다시 읽어보면, 그때는 보이지 않았던 책임의 경계가 조금씩 보인다.

리팩토링의 목표는 코드를 멋있게 보이게 만드는 것이 아니다. 다음에 기능을 고칠 내가 덜 헤매게 만드는 것이다. 이번 편에서는 그 첫 단계로 현재 구조를 지도처럼 펼쳐봤다. 이제 다음 편부터 기능별로 하나씩 떼어 보면서, MainWindow를 조금씩 가볍게 만들어볼 것이다.

 

이번 글에서 다룬 프로젝트 코드는 아래 GitHub 저장소에서 확인할 수 있다.

https://github.com/jin123346/BioFlow-GBIF-Darwin-Core-Mapper

 

GitHub - jin123346/BioFlow-GBIF-Darwin-Core-Mapper: GBIF Darwin Core Mapper

GBIF Darwin Core Mapper. Contribute to jin123346/BioFlow-GBIF-Darwin-Core-Mapper development by creating an account on GitHub.

github.com

 

이 프로젝트는 반복적인 데이터 정리 과정을 효율화하기 위해 개인적으로 제작한 도구이며, 실제 업무 데이터나 기관 내부 자료는 포함하지 않았다.

 

 

728x90
반응형

댓글