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

PySide6 리팩토링 4편 : 헤더 적용과 컬럼명 정리 분리하기

by JINJINC 2026. 6. 4.
728x90
반응형

3편에서는 Excel 파일을 처음 불러오는 흐름을 “파일 선택 → 시트 선택 → 원본 읽기 → 화면 반영” 단계로 나눴다. 그 작업을 마친 뒤 다음 리팩토링 후보로 apply_selected_header()를 남겨두었다.

사용자가 원본 미리보기에서 헤더 행을 선택하고 버튼을 누르면, 프로그램은 선택한 행을 실제 컬럼명으로 사용해 파일을 다시 읽는다. 기존 메서드는 이 작업뿐 아니라 컬럼명 정리와 화면 갱신까지 모두 직접 처리하고 있었다.

이번에는 헤더 적용 흐름에서 데이터 처리와 UI 반영의 경계를 찾아 분리해봤다.

기존 apply_selected_header()가 하던 일

기존 메서드의 작업을 순서대로 펼치면 다음과 같다.

  1. 파일과 원본 데이터가 로드되었는지 확인한다.
  2. 사용자가 헤더 행을 선택했는지 확인한다.
  3. 선택한 행을 헤더로 사용해 파일을 다시 읽는다.
  4. 비어 있거나 Unnamed:로 시작하는 컬럼명을 정리한다.
  5. 헤더가 적용된 데이터를 미리보기에 표시한다.
  6. 매핑 UI를 만든다.
  7. 사용할 수 있는 버튼 상태를 변경한다.
  8. 완료 또는 오류 메시지를 보여준다.

검증과 메시지 표시는 버튼 이벤트를 처리하는 MainWindow에 자연스럽게 어울린다. 미리보기와 버튼 상태 변경도 UI 책임이다. 하지만 컬럼명을 정리하는 규칙은 화면과 관계없는 데이터 처리다.

기존에는 이 규칙이 apply_selected_header() 안에 직접 들어 있었다.

cleaned_columns = []
for idx, col in enumerate(self.source_df.columns):
    col_text = str(col).strip()
    if not col_text or col_text.lower().startswith("unnamed:"):
        col_text = f"Column_{idx + 1}"
    cleaned_columns.append(col_text)

self.source_df.columns = cleaned_columns

이 코드는 PySide6 위젯을 사용하지 않는다. 컬럼 목록을 입력받아 정리된 컬럼 목록을 만드는 로직이므로 ExcelService에서 독립적으로 다룰 수 있다.

컬럼명 정리를 서비스로 옮기기

컬럼명 정리 규칙을 ExcelService.clean_column_names()로 옮겼다.

@staticmethod
def clean_column_names(columns) -> list[str]:
    cleaned_columns = []

    for index, column in enumerate(columns, start=1):
        column_name = str(column).strip()
        if not column_name or column_name.lower().startswith("unnamed:"):
            column_name = f"Column_{index}"
        cleaned_columns.append(column_name)

    return cleaned_columns

이 메서드는 전달받은 컬럼 객체를 직접 바꾸지 않고 새로운 문자열 목록을 반환한다. 따라서 GUI를 띄우지 않고도 입력과 결과만으로 동작을 확인할 수 있다.

columns = [" scientificName ", "Unnamed: 1", ""]

cleaned = ExcelService.clean_column_names(columns)

# ["scientificName", "Column_2", "Column_3"]

파일을 읽는 것과 컬럼명을 정리하는 것은 서로 다른 작업이지만 둘 다 Excel/CSV 데이터를 사용할 수 있는 형태로 준비하는 책임에 가깝다. 반면 MainWindow는 어떤 정리 규칙을 사용하는지 자세히 알 필요 없이 서비스에 요청하면 된다.

헤더 적용 결과를 화면에 반영하는 메서드

헤더가 적용된 데이터프레임을 읽고 정리한 뒤에는 여러 UI 요소를 함께 갱신해야 한다. 이 작업을 _apply_header_data()로 묶었다.

def _apply_header_data(self, source_df):
    self.source_df = source_df
    self.show_preview_with_header(source_df)
    self.preview_position_label.setText(...)
    self.build_mapping_ui(source_df.columns.tolist())
    self._set_header_applied_buttons_enabled()

_apply_header_data()는 헤더 적용이 성공했을 때 화면이 어떤 상태가 되어야 하는지 표현한다. 데이터프레임을 내부 상태에 저장하고, 미리보기와 매핑 UI를 갱신한 뒤 관련 버튼을 활성화한다.

버튼 상태 변경은 _set_header_applied_buttons_enabled()로 한 번 더 분리했다. 헤더 적용 후에는 컬럼 추가, 원본 좌표 확인, 결과 확인 버튼을 사용할 수 있지만 결과 생성 전이므로 좌표 확인과 저장 버튼은 아직 비활성화해야 한다.

이처럼 함께 바뀌어야 하는 버튼 상태를 한곳에 모으면, 새로운 버튼이 추가될 때 헤더 적용 완료 상태를 찾기 쉬워진다.

짧아진 apply_selected_header()

리팩토링 후 try 블록은 다음 흐름만 보여준다.

source_df = ExcelService.read_excel_with_header(
    self.current_file_path,
    header_row=self.selected_header_row,
    sheet_name=self.current_sheet_name,
)
source_df.columns = ExcelService.clean_column_names(source_df.columns)
self._apply_header_data(source_df)

이제 코드를 읽으면 “선택한 헤더로 읽기 → 컬럼명 정리 → 화면에 적용” 순서가 바로 보인다. apply_selected_header()는 버튼 클릭 이벤트의 흐름을 조정하고, 세부 데이터 규칙과 UI 갱신 묶음은 각각 다른 메서드에 맡긴다.

어디까지 서비스로 옮겨야 할까

컬럼명 정리를 서비스로 옮겼다고 해서 헤더 적용과 관련된 모든 코드를 서비스로 보내는 것은 아니다.

예를 들어 다음 작업은 여전히 MainWindow에 남겨두었다.

  • 파일을 먼저 업로드하라는 경고 표시
  • 헤더 행을 먼저 선택하라는 경고 표시
  • 미리보기 테이블 갱신
  • 매핑 UI 생성
  • 버튼 활성화 상태 변경
  • 완료 및 오류 메시지 표시

이 작업들은 사용자가 보고 조작하는 화면 상태와 직접 연결되어 있다. 서비스가 QMessageBox나 버튼을 알기 시작하면 오히려 책임 경계가 흐려진다.

리팩토링의 목적은 가능한 모든 코드를 다른 파일로 옮기는 것이 아니다. 화면 없이도 설명하고 테스트할 수 있는 규칙을 UI 밖으로 꺼내고, UI에 남은 코드는 사용자 흐름을 읽기 쉽게 만드는 것이다.

이번 리팩토링에서 배운 점

이번에는 다음 기준으로 책임을 나눴다.

  • 입력을 받아 결과를 계산하는 규칙은 서비스로 옮긴다.
  • 여러 UI 요소가 함께 바뀌는 성공 상태는 하나의 메서드로 묶는다.
  • 이벤트 메서드는 검증과 전체 작업 순서를 보여준다.
  • UI 서비스 경계를 넘길 때는 데이터프레임이나 문자열 목록처럼 단순한 값을 사용한다.

3편에서 파일 로드 성공 결과를 _apply_loaded_file()로 모았던 것처럼, 이번에는 헤더 적용 성공 결과를 _apply_header_data()로 모았다. 비슷한 형태가 반복되면서 MainWindow가 담당해야 할 역할도 조금씩 선명해지고 있다.

다음 리팩토링에서는 원본 데이터 컬럼을 Darwin Core 필드에 연결하는 매핑 UI를 살펴볼 수 있다. build_mapping_ui()는 화면 생성뿐 아니라 자동 매핑 규칙 적용과 기존 입력값 복원까지 담당한다. 이 메서드에서도 화면 구성과 데이터 결정 규칙을 구분할 수 있는지 확인해볼 예정이다.

728x90
반응형

댓글