Realays Logo Realays
← 블로그 목록으로
DevLog 2025. 11. 12.

[Dalendar DevLog 2] 견고한 앱의 뼈대 - 아키텍처와 데이터 구조 설계

View 계층과 Core Logic 계층을 분리하는 아키텍처 설계와 EAF 알고리즘을 위한 데이터 구조에 대해 설명합니다.

[Dalendar DevLog 2] 견고한 앱의 뼈대 - 아키텍처와 데이터 구조 설계

2편: 견고한 앱의 뼈대 - 아키텍처와 데이터 구조 설계

서론: 뼈대를 세우는 이유

이전 1편에서는 달력 앱을 만들기 위한 기본적인 준비 과정을 다루었습니다. 이번 2편에서는 시리즈를 이어가며, 앱의 핵심 ‘뼈대’가 되는 아키텍처와 데이터 구조 설계에 대해 심도 있게 논의하고자 합니다. 소프트웨어 개발 초기에 견고한 구조를 설계하는 것은 단순히 좋은 습관을 넘어, 미래의 유지보수성과 확장성을 결정짓는 매우 중요한 과정입니다. 잘 설계된 뼈대는 변화에 유연하게 대응하고 새로운 기능을 쉽게 추가할 수 있는 튼튼한 기반이 됩니다.

1. 프로젝트 아키텍처: 관심사의 분리

흔한 실수 중 하나는 날짜 계산 로직을 Adapter와 같은 UI 컴포넌트에 직접 결합하는 것입니다. 예를 들어, 안드로이드의 Calendar 객체를 뷰 계층에서 직접 조작하는 방식은 코드의 재사용성을 저해하고 테스트를 어렵게 만듭니다. 저희는 이러한 접근법을 의도적으로 피하기 위해, 프로젝트의 전체 구조를 ‘View(화면) 계층’과 ‘Core Logic(핵심 로직) 계층’으로 명확하게 분리하는 전략을 채택했습니다. 이처럼 깨끗한 경계를 만드는 것은 안드로이드 프레임워크와 완전히 독립적인 순수 고성능 수학 엔진(EAF)을 통합하기 위한 필수적인 설계 결정이었습니다. 이러한 관심사의 분리(Separation of Concerns) 원칙은 각 계층이 독립적으로 발전할 수 있게 하여 코드의 복잡도를 낮추고 유지보수성과 확장성을 극대화하는 핵심 전략입니다.

1.1. View 계층: 효율적인 달력 UI 구조

사용자 인터페이스(UI)를 담당하는 View 계층은 사용자가 직접 상호작용하는 화면을 구성합니다. 현재 프로젝트의 파일 구조를 보면, MainActivity.kt가 전체 화면을 담는 컨테이너 역할을 수행합니다. 실제 달력 표시는 adapterMonth.kt와 adapterDay.kt가 담당하는 구조로, 이중 RecyclerView를 사용하여 효율성을 높였습니다.

  • 바깥쪽 RecyclerView (A): 이 RecyclerView는 월(month) 단위의 아이템을 가지며, 사용자가 좌우로 스크롤하여 이전 달과 다음 달을 탐색할 수 있도록 합니다.
  • 안쪽 RecyclerView (B): 바깥쪽 RecyclerView의 각 월(month) 아이템 내부에는 또 다른 RecyclerView가 존재합니다. 이것은 각 월에 해당하는 일(day)들을 6행 7열의 그리드 형태로 표시하는 역할을 합니다.

이러한 이중 RecyclerView 구조는 화면에 보이지 않는 뷰를 재활용하여 메모리 사용량을 최적화하고, 무한 스크롤을 효율적으로 구현하는 데 매우 효과적입니다.

1.2. Core Logic 계층: 최적화된 날짜 계산 엔진

모든 날짜 관련 계산은 “Euclidean Affine Functions and Applications to Calendar Algorithms” 논문에서 제시된 고도로 최적화된 수학적 알고리즘을 통해 처리됩니다. 이 계층은 달력의 두뇌와 같으며, 사용자 인터페이스와는 완전히 독립적으로 동작합니다.

단순 라이브러리 호출을 지양하고 EAF를 채택한 것은 의도적인 성능 최적화 전략입니다. 범용 라이브러리가 사용하는 룩업 테이블(look-up tables) 방식은 L1 캐시 미스를 유발하는 예측 불가능한 메모리 접근 패턴을 보이는 반면, EAF는 순수 산술 연산으로 CPU 파이프라인을 효율적으로 채워넣기 때문입니다. 또한, 유클리드 아핀 함수(EAF) 기반 알고리즘은 분기(branching)를 최소화하여 프로세서의 실행 파이프라인 지연(stall) 가능성을 줄이고, 빠르고 정확한 결과를 도출합니다.

1.3. 이 구조의 장점: 유지보수와 확장

View 계층과 Core Logic 계층을 명확하게 분리한 구조는 다음과 같은 실질적인 이점을 제공합니다.

  • 유지보수성: UI 디자인을 변경해야 할 때, 핵심 날짜 계산 로직에는 전혀 영향을 주지 않습니다. 마찬가지로, 날짜 계산 알고리즘의 성능을 개선하거나 버그를 수정할 때에도 UI 관련 코드를 수정할 필요가 없습니다. 각 계층은 독립적으로 수정 및 테스트가 가능하여 유지보수가 매우 용이해집니다.
  • 확장성: 향후 음력 변환 기능이나 특정 국가의 공휴일 계산과 같은 복잡한 기능을 추가해야 할 경우, 개발자는 Core Logic 계층에만 집중하여 새로운 모듈을 추가하거나 기존 로직을 확장할 수 있습니다. UI 계층은 변경된 데이터를 받아 표시하기만 하면 되므로, 신규 기능 추가가 매우 체계적이고 효율적으로 이루어집니다.

2. 핵심 데이터 구조와 흐름

아직 사용자 일정을 영구적으로 저장하기 위한 데이터베이스 스키마는 설계 전 단계입니다. 하지만 달력의 기본 동작을 위해서는 앱 내부에서 데이터를 어떻게 표현하고 처리할지에 대한 핵심 데이터 구조와 흐름을 명확히 정의하는 것이 중요합니다.

2.1. 데이터의 표현: 날짜(YMD)와 Rata Die

“Euclidean affine functions” 논문에서 제시된 ‘Rata Die’ 개념을 앱의 핵심 데이터 표현 방식으로 채택했습니다. Rata Die는 특정 기준일(epoch)로부터 경과한 날짜 수를 하나의 정수(integer)로 표현하는 방식입니다.

이 접근 방식은 가변적인 월의 길이와 윤년의 복잡성을 우아하게 회피합니다. 예를 들어, 특정 날짜로부터 35일 뒤의 날짜를 계산하는 것은 단순히 rata_die + 35라는 정수 덧셈으로 처리됩니다. 이는 월의 경계나 연말 처리를 확인해야 하는 기존 방식의 복잡하고 오류 발생 가능성이 높은 연산보다 훨씬 효율적이고 계산 비용이 저렴합니다. 앱 내부에서는 모든 날짜 관련 로직을 이 Rata Die 값을 기준으로 처리하고, 사용자에게 보여줄 필요가 있을 때만 우리가 흔히 아는 년/월/일(YMD) 형태로 변환하여 출력합니다.

또한, Rata Die는 UI 성능에도 기여합니다. 모든 내부 계산에 단순 정수를 사용한다는 것은 UI 계층으로 전달되는 로직이 믿을 수 없을 정도로 가볍다는 의미입니다. adapterMonth는 복잡한 Date나 Calendar 객체를 조작할 필요 없이, 해당 월의 시작 Rata Die 값만 계산하면 나머지는 단순한 정수 반복문으로 처리됩니다. 이는 어댑터 내에서의 객체 생성 오버헤드를 줄여 RecyclerView의 뷰 재활용 메커니즘을 보완하는 효과를 낳습니다.

2.2. 데이터의 흐름: 달력이 그려지기까지

사용자가 앱을 실행하고 월을 스크롤할 때, 데이터는 다음과 같은 단계적 흐름을 통해 최종적으로 화면에 그려집니다.

  1. 초기화: 사용자가 앱을 실행하면 MainActivity.kt는 월 단위 RecyclerView(A)의 초기 표시 위치를 Int.MAX_VALUE / 2로 설정합니다. 이렇게 하면 현재 달이 스크롤 가능한 범위의 중앙에 위치하게 되어 사용자가 과거와 미래로 자유롭게 스크롤할 수 있습니다.
  2. 월(Month) 생성: adapterMonth.kt는 RecyclerView로부터 전달받은 position 값을 사용해 표시할 연도와 월을 계산합니다. position - center 연산을 통해 Int.MAX_VALUE / 2를 기준으로 현재 날짜에 해당하는 상대적인 월을 결정합니다.
  3. 일(Day) 목록 생성: 계산된 월의 1일을 기준으로, 해당 월의 달력 그리드(6행 7열, 총 42칸)를 채우기 위한 날짜 목록(dayList: MutableList)을 생성합니다. 완벽하게 정렬된 그리드를 위해, 먼저 해당 월 1일의 요일(calendar.get(Calendar.DAY_OF_WEEK))을 파악한 후, calendar.add(Calendar.DAY_OF_MONTH, (1-calendar.get(Calendar.DAY_OF_WEEK)) + k) 로직을 통해 그리드의 시작점이 될 이전 달의 일요일 날짜를 역산합니다. 그 후 42개의 날짜를 순차적으로 생성하여 dayList를 채웁니다.
  4. 데이터 전달: 생성된 42개의 날짜가 담긴 dayList는 해당 월 아이템 내부에 있는 안쪽 RecyclerView(B)와 그 어댑터인 adapterDay.kt로 전달됩니다.
  5. UI 렌더링: adapterDay.kt는 전달받은 dayList의 각 날짜 데이터를 순서대로 그리드의 각 칸에 표시하여 최종적인 달력 UI를 완성합니다.

3. 왜 이 기술을 선택했는가?

지금까지 설명한 아키텍처와 데이터 흐름 설계에 사용된 핵심 기술들을 선택한 이유를 유지보수성과 확장성 관점에서 다시 한번 구체적으로 살펴보겠습니다.

3.1. RecyclerView: 안드로이드 UI 성능의 핵심

RecyclerView는 안드로이드에서 목록 형태의 UI를 구현할 때 성능을 보장하는 핵심적인 위젯입니다. 그 핵심 원리는 ‘뷰 재활용(view recycling)’ 메커니즘에 있습니다. 화면에 표시될 수 있는 최소한의 뷰 홀더(ViewHolder) 틀을 몇 개만 생성한 뒤, 스크롤이 발생하면 화면 밖으로 사라지는 뷰 홀더를 버리지 않고 새로운 데이터를 담아 다시 화면에 등장시킵니다.

이 방식은 불필요한 뷰 객체 생성을 최소화하여 시스템 자원을 극도로 효율적으로 사용하게 해줍니다. 결과적으로 앱의 전반적인 성능과 반응성이 향상되며, 이는 곧 사용자 경험의 질을 높이고 복잡한 UI에서도 유지보수를 용이하게 만듭니다.

3.2. EAF 알고리즘: 비교 불가능한 계산 속도와 확장성

날짜 계산에 EAF(Euclidean Affine Functions) 기반 알고리즘을 선택한 이유는 명확합니다. 바로 압도적인 성능과 그로 인한 확장성입니다. 논문의 성능 분석(Performance analysis) 섹션에 따르면, 이 알고리즘은 glibc, OpenJDK, Boost와 같이 널리 사용되는 표준 라이브러리들의 날짜 계산 함수보다 2배 이상 빠른 성능을 보입니다.

이러한 성능은 앱이 수백, 수천 년에 걸친 방대한 날짜 범위를 다루더라도 지연 없이 빠르고 정확한 계산을 보장합니다. 이것이 바로 이 앱이 추구하는 ‘확장성’의 핵심입니다. EAF는 단순한 나눗셈 연산을 곱셈과 비트 시프트(bit shift) 연산으로 최적화하는데, 이는 컴파일러가 자동으로 해주는 수준을 넘어섭니다.

이 최적화의 진정한 힘은 데이터 의존성(data dependency)을 깨뜨려 최신 CPU의 명령어 수준 병렬 처리(instruction-level parallelism)를 가능하게 하는 데 있습니다. 논문의 Example 3.12에서 볼 수 있듯이, 전통적인 접근법에서는 나머지(n2 % 1461)를 계산하기 위해 몫(n2 / 1461)의 계산이 먼저 완료되어야 합니다. EAF 최적화는 이 문제를 변환합니다. 먼저 64비트 중간값 u2 = 2939745 * n2를 계산합니다. 이 단일 값으로부터, 몫 q2는 상위 32비트(u2 / 2^32)에서, 나머지 r2는 하위 32비트(u2 % 2^32)에서 동시에(concurrently) 계산될 수 있습니다. CPU는 몫 계산이 끝날 때까지 기다릴 필요 없이 나머지 계산을 시작할 수 있습니다. 이 병렬 실행 경로는 연산당 단 몇 클럭 사이클을 절약하더라도, 수십 년의 달력 데이터를 빠르게 스크롤할 때 성능에 막대한 영향을 미칩니다.

결론: 다음 단계를 위한 견고한 발판

이번 2편에서는 앱의 견고한 뼈대를 구성하는 세 가지 핵심 요소를 설계했습니다. 이처럼 View/Logic 분리 아키텍처는 EAF라는 강력한 엔진을 탑재할 수 있는 ‘섀시(chassis)‘를, Rata Die는 이 엔진에 공급되는 고효율 ‘연료’를, 그리고 EAF 알고리즘은 앱의 심장인 ‘엔진’ 그 자체를 담당합니다. 이 세 가지가 완벽하게 맞물려 돌아가기에, 우리는 성능 저하에 대한 걱정 없이 풍부한 기능을 확장해 나갈 수 있습니다.

이렇게 탄탄하게 다져진 기초 위에서, 다음 편에서는 본격적으로 사용자 일정 추가 기능과 이를 영구적으로 저장하기 위한 데이터베이스 연동에 대해 다룰 예정입니다. 견고한 뼈대 위에 어떻게 살을 붙여 나가는지 다음 이야기를 기대해 주시기 바랍니다.

관련 포스트