functional-programming

3 개의 포스트

코드 품질 개선 기법 29편: 고르디우스 변수 (새 탭에서 열림)

코드 내 데이터의 의존성이 복잡하게 얽혀 로직을 파악하기 어려운 상태를 '고르디우스의 매듭'에 비유하며, 이를 해결하기 위한 설계 기법을 제시합니다. 복잡한 조건문과 데이터 가공이 반복되는 경우, 최종 로직에 필요한 이상적인 중간 데이터 구조를 먼저 정의하고 이를 생성하는 방식으로 코드를 재구성하면 가독성과 유지보수성을 동시에 높일 수 있습니다. **데이터 의존성 과다로 인한 가독성 저하** * 원격과 로컬 데이터를 동기화할 때 추가, 업데이트, 삭제 대상을 구분하는 과정에서 데이터 의존성이 복잡해지기 쉽습니다. * 단순히 ID 목록을 비교해 차집합을 구하는 방식은 실제 데이터를 처리할 때 다시 원본 리스트에서 객체를 찾아야 하거나, 맵(Map)에서 데이터를 꺼낼 때 발생할 수 없는 예외 상황을 처리해야 하는 번거로움을 유발합니다. * 이로 인해 비즈니스 로직의 핵심인 '동기화 액션'보다 데이터를 분류하고 가공하는 '준비 과정'이 코드의 흐름을 방해하게 됩니다. **이상적인 중간 데이터 설계를 통한 역설계** * 복잡한 매듭을 풀기 위해서는 최종적으로 필요한 데이터의 형태를 먼저 상상하고, 그 지점부터 함수의 구성을 역으로 설계하는 것이 효과적입니다. * 이번 사례에서는 추가(`created`), 업데이트(`updated`), 삭제(`deleted`)될 대상들을 명확히 분리한 세 가지 리스트를 중간 데이터로 정의했습니다. * 로컬과 원격의 모든 ID 집합을 기준으로 `Pair<RemoteData?, LocalData?>` 형태의 시퀀스를 만들고, 이를 상태에 따라 분류하는 것이 핵심입니다. **`partitionByNullity`를 활용한 로직 단순화** * `partitionByNullity`라는 유틸리티 함수를 도입하여 데이터의 존재 여부에 따라 세 그룹(Remote만 존재, 둘 다 존재, Local만 존재)으로 깔끔하게 분리합니다. * 이 함수를 사용하면 메인 함수인 `synchronizeWithRemoteEntries`에서는 복잡한 필터링이나 조건문 없이 각각의 리스트에 대해 `forEach`를 돌며 추가, 업데이트, 삭제 로직만 수행하면 됩니다. * 결과적으로 런타임 에러를 방지하기 위한 불필요한 null 체크가 사라지고, 전체적인 실행 흐름이 일관성 있게 정돈됩니다. **실용적인 제언** 코드의 흐름을 따라가기 벅차다면 데이터의 흐름이 꼬여있지 않은지 점검해야 합니다. 구현에 매몰되기보다 "어떤 모양의 데이터가 있으면 이 로직이 가장 깔끔해질까?"를 먼저 고민하고, 그 중간 구조를 만들어내는 로직을 별도로 분리하면 코드 품질을 획기적으로 개선할 수 있습니다.

토스인컴 세금 환급 서비스 : 빠른 속도에서 품질을 지키기 위한 E2E 자동화 여정 (새 탭에서 열림)

토스인컴은 빠른 배포 속도에 대응하기 위해 기존의 수동 검증과 복잡한 클래스 기반 POM(Page Object Model)에서 벗어나, 함수형 POM 중심의 자동화 시스템을 구축했습니다. 이를 통해 4시간 이상 소요되던 검증 시간을 20분(병렬 실행 시)으로 단축하고 테스트 성공률 100%를 달성하며, QA가 제품의 품질을 초기부터 설계하고 실행 속도를 높이는 핵심 동력으로 자리 잡았습니다. ### 클래스 기반 POM에서 함수형 POM으로의 전환 * **무상태(Stateless) 함수 설계**: 상태를 갖는 클래스 대신 `page` 객체를 입력받아 동작을 수행하고 다시 `page`를 반환하는 단순한 함수 구조를 채택했습니다. * **가독성 및 유지보수성 향상**: 테스트 코드를 '사람이 읽는 시나리오'처럼 작성할 수 있게 되었으며, 버튼 문구 등 UI 변경 시 수십 개의 파일 대신 POM 함수 한 곳만 수정하면 되도록 캡슐화했습니다. * **명확한 네이밍 컨벤션**: `goto`, `click`, `enter`, `verify` 등 동작 중심의 접두사를 사용하여 코드만 보고도 어떤 테스트 단계인지 직관적으로 이해할 수 있게 했습니다. ### 여정 중심의 단계 분리와 레고식 조립 * **사용자 여정 기반 설계**: 단순히 화면 단위로 나누지 않고, '로그인/약관', '공제 확인', '결제', '신고' 등 세금 환급의 4가지 핵심 단계로 파일을 분리해 관리합니다. * **독립적 모듈화**: 의료비, 신용카드, 인적공제 등 각 공제 항목을 독립된 함수로 만들어, 새로운 테스트 시나리오가 필요할 때 필요한 기능만 레고 블록처럼 조립해 빠르게 생성할 수 있습니다. ### 테스트 안정성을 높이는 기술적 전략 * **4단계 클릭 폴백(Robust Click)**: React 렌더링 타이밍 문제로 발생하는 클릭 실패를 방지하기 위해 'Enter 키 입력 → 일반 클릭 → Force 클릭 → JS 직접 실행' 순의 단계별 재시도 전략을 유틸리티화했습니다. * **최신 페이지 자동 감지**: 리다이렉트나 새 창 열림이 빈번한 환경에서 `getLatestNonScrapePage` 유틸을 통해 항상 유효한 최신 탭을 추적하고 `currentPage`를 갱신하여 페이지 닫힘 에러를 방지했습니다. * **네트워크 대기 최적화**: Playwright의 기본 `networkidle` 방식 대신, 타임아웃이 발생해도 테스트를 중단하지 않고 UI 앵커(텍스트, role 등)로 화면 준비를 판단하는 `waitForNetworkIdleSafely`를 구현했습니다. ### 자동화 도입이 가져온 성과 * **정량적 지표 개선**: 검증 시간 77% 감소, 테스트 커버리지 600% 증가, 코드 중복률 76% 감소 등 모든 지표에서 비약적인 발전을 이루었습니다. * **업무 문화의 변화**: 24시간 자동 검증 시스템을 통해 개발 완료 즉시 기능을 점검하고 결과를 슬랙으로 공유하며, QA는 단순 반복 검증이 아닌 세금 엔진의 금액 정합성 검증 및 성능 지표 분석 등 고부가가치 업무에 집중하게 되었습니다. **실전 팁** 가장 빈번하게 오류가 발생하는 구간(예: 로그인)부터 자동화를 시작하고, 추상화에 너무 매몰되기보다 누구나 읽고 고칠 수 있는 명료한 함수 구조를 유지하는 것이 중요합니다. 특히 페이지 전환이 일어날 때마다 최신 페이지 객체를 새로 할당하는 원칙만으로도 상당수의 자동화 실패를 예방할 수 있습니다.

코드 품질 개선 기법 23편: 반환의 끝이 에지 케이스의 끝 (새 탭에서 열림)

조기 반환(Early Return)은 에러 케이스를 미리 배제하여 함수의 주요 로직에 집중하게 돕는 훌륭한 기법이지만, 모든 상황에서 정답은 아닙니다. 만약 에러 케이스와 정상 케이스의 처리 방식이 본질적으로 같다면, 이를 분리하기보다 하나의 흐름으로 통합하는 것이 코드의 복잡성을 낮추는 데 더욱 효과적입니다. 무분별한 조기 반환 대신 언어의 특성과 라이브러리 기능을 활용해 에지 케이스를 정상 흐름에 포함시키는 것이 코드 품질 개선의 핵심입니다. ### 조기 반환 대신 정상 케이스로 통합하기 * **빈 컬렉션 순회 활용**: `map`, `filter`, `sum`과 같은 고차 함수는 컬렉션이 비어 있어도 오류 없이 자연스럽게 동작하므로, `isEmpty()`를 통한 별도의 조기 반환 처리가 불필요한 경우가 많습니다. * **Safe Call과 엘비스 연산자**: `null`을 체크하여 조기 반환하는 대신, `?.`(세이프 콜)이나 `?:`(엘비스 연산자)를 사용하면 `null`을 정상적인 데이터 흐름의 일부로 처리할 수 있어 코드가 간결해집니다. * **인덱스 범위 체크의 추상화**: 리스트 인덱스를 직접 조사하기보다 `getOrNull`이나 `getOrElse` 같은 함수를 사용하면, 범위를 벗어난 경우를 `null` 처리 흐름에 통합하여 조건문을 줄일 수 있습니다. ### 속성 의존성 및 예외 처리의 최적화 * **무의미한 대입 배제 지양**: UI 요소의 가시성(`isVisible`)에 따라 텍스트 대입 여부를 결정할 때, 조기 반환으로 대입을 막기보다는 가시성 여부와 상관없이 값을 대입하도록 로직을 통합하는 것이 상태 관리에 더 유리할 수 있습니다. * **flatMap을 이용한 연쇄 함수 호출**: 여러 단계에서 발생하는 예외를 각각 `try-catch`와 조기 반환으로 처리하면 흐름이 복잡해집니다. 이때 `Result` 객체와 `flatMap`을 활용하면 성공과 실패 케이스를 동일한 파이프라인에서 처리할 수 있습니다. * **성능과 가독성의 균형**: 로직을 통합하는 과정에서 인스턴스 생성 등으로 인한 미세한 성능 저하가 발생할 수 있으나, 대부분의 경우 코드의 명확성과 유지보수성이 주는 이점이 더 큽니다. 조기 반환을 작성하기 전, 현재 다루고 있는 에지 케이스가 정말로 '별도의 처리'가 필요한 예외 상황인지, 아니면 '일반적인 처리' 과정에 자연스럽게 녹여낼 수 있는 데이터의 한 형태인지 고민해보는 것이 좋습니다. 에러 케이스와 정상 케이스의 경계를 허물 때 코드는 더욱 단순하고 견고해집니다.