코드 품질 개선 기법 18편: 함수만 보고 관계는 보지 못한다 (새 탭에서 열림)
코드를 리팩토링할 때 단순히 중첩된 내부 루프를 별도의 함수로 분리하는 것만으로는 가독성을 근본적으로 개선할 수 없습니다. 진정한 코드 품질 향상은 기술적인 구조를 따라가는 것이 아니라, '코드가 무엇을 하는지'라는 의미 단위에 맞춰 함수의 경계를 재설정할 때 이루어집니다. 이를 위해 데이터 조회와 처리 로직을 분리하여 중첩된 구조를 평탄화하는 접근 방식이 필요합니다.
단순한 함수 추출의 한계와 문제점
페이지나 청크 단위로 분할된 데이터를 처리할 때 흔히 while과 for가 중첩된 루프 구조가 나타납니다. 이를 개선하기 위해 내부 루프만 별도 함수로 추출하는 방식은 다음과 같은 한계를 가집니다.
- 의미 단위의 파편화: '모든 아이템 조회'라는 하나의 논리적 흐름이 여러 함수에 걸쳐 분산되어 코드의 전체적인 의도를 파악하기 더 어려워집니다.
- 가독성 개선 미비: 함수의 경계와 의미 단위의 경계가 일치하지 않으면, 호출부의 복잡도는 여전히 높게 유지됩니다.
- 구조적 종속성: 단순히 기존의 중첩 구조를 유지한 채 함수를 나누는 것은 데이터가 가진 물리적 구조(페이지, 청크 등)에 로직이 강하게 결합되는 결과를 초래합니다.
의미 단위를 반영한 숲 보기 리팩토링
단순 추출에서 벗어나 코드의 의미를 재구성하는 리팩토링은 로직의 복잡도를 획기적으로 낮춥니다. '모든 아이템 조회'와 '메타데이터 저장'이라는 두 가지 핵심 역할에 집중하여 코드를 재설계해야 합니다.
- 추상화된 열(Sequence) 활용: Kotlin의
Sequence나Iterator를 사용하여 중첩된 페이지 구조를 하나의 연속된 데이터 흐름으로 변환합니다. - 중첩 루프의 평탄화: 데이터를 가져오는 복잡한 로직(페이징 처리 등)을 별도의 생성 함수로 캡슐화하고, 이를 사용하는 쪽에서는 단일
for루프만 사용하도록 단순화합니다. - yieldAll을 이용한 지연 계산:
sequence { ... }블록 내에서yieldAll을 사용하면 다음 페이지가 필요한 시점에만 데이터를 요청하면서도, 외부에는 단일 리스트처럼 보이게 할 수 있습니다.
실용적인 결론
리팩토링 시 단순히 추출하기 쉬운 부분을 떼어내는 것이 아니라, 기존 구조를 유지할지 혹은 의미에 맞게 재구성할지 먼저 고민해야 합니다. 루프 중첩뿐만 아니라 조건 분기나 데이터 구조가 복잡하게 얽혀 있을 때도 '의미 단위'를 기준으로 경계를 나누면 훨씬 읽기 쉽고 관리하기 편한 코드를 작성할 수 있습니다.