[회고] 첫 릴리즈를 마치고.. (나를 돌아보기, AI에 대한 나의 생각)
2월 초, 기획과 디자인을 마무리하고 개발을 시작한지 약 한 달 반.
드디어 첫 릴리즈를 마쳤습니다.
프로젝트를 시작하기 전에는 Micro-Features Architecture, 의존성 관리 같은 개념들이 머리로는 이해되는 것 같으면서도 어딘가 추상적으로 느껴졌습니다. 하지만 실제로 프로젝트를 진행하면서 이 개념들이 왜 필요한지, 어떻게 적용되는지 체감할 수 있었고, 그 과정에서 제 나름의 기준도 조금씩 정립할 수 있었습니다.
이번 프로젝트에서는 평소 해보고 싶었던 것들을 최대한 자유롭게 시도해봤습니다.
복잡한 뷰와 애니메이션을 구현하고, 새로운 구조와 아키텍처를 적용해보기도 하면서 즐겁게 진행했던 프로젝트였습니다.
밤 늦게까지 고민하고, 실패도 하고, 다시 설계하고 했던 과정들이 쌓여 나름 만족스러운(?) 결과물이 만들어졌다고 생각합니다.
이번 글에서는 그 과정에서 고민했던 것들과, 제 자신을 다시 한 번 돌아보려고 합니다.
완벽주의자? 강박?
이번 프로젝트를 하면서 스스로를 많이 돌아보게 되었던 것 같습니다.
예전에 제가 존경하는 대학 선배가 저에게 이런 말을 한 적이 있습니다.
"넌 형이랑 비슷해 완벽주의자 같아"
그때는 사실 그 말이 잘 와닿지 않았습니다. 그저 그냥 그런가? 정도로 넘겼던 것 같습니다.
그런데 이번 프로젝트를 하면서 그 말이 무엇을 의미하는지 조금은 이해하게 되었습니다.
저는 개발을 하면서 아주 사소한 부분 하나에도 쉽게 멈칫하는 것 같습니다.
- 애니메이션 디테일이 아쉬운 것
- 구조가 마음에 들지 않는 것
- 네이밍이 마음에 들지 않는 것
- 이 로직이 뷰컨에 있어야 하는지, 뷰모델에 있어야하는지
- 앱을 조립하는 과정에서 이 의존성이 인터페이스가 아니라 피쳐 구현에 직접 의존하는게 맞는 구조인지
- 재사용 가능한지
- Service를 Core가 아니라 따로 모듈로 분리할지
- 중복 코드가 많은데 어떻게 개선할지
- 콜백이 너무 많은데 해결할 방법은 없을지
- 검색 서비스를 검색 모듈에서만 사용하는데, 코어 모듈에 위치해야하는지? 검색 모듈에만 위치해야하는지?
더 많지만.. 이런 것들이 보이면 그냥 지나치지 못하고, 그 자리에서 바로 고치려고 합니다.
문제는 그게 "지금 당장 중요한가?"와는 별개라는 점이었습니다.
개발 진도를 나가야 하는 상황에서도, 하나의 디테일에 꽂히면 그 문제를 해결하기 전까지는 다음으로 쉽게 넘어가지 못했습니다.
이해가 안 되는 부분이 있으면 끝까지 파고들어야 했고, 해결하지 못한 채 잠들려고 하면 계속 머릿속에 남아서 결국 다시 맥북을 열게 되었습니다. 그래서 밤을 새는 날도 자연스레 많아졌습니다.
애플 디벨로퍼 아카데미에서 팀 프로젝트를 진행할 때도 비슷한 이야기를 자주 들었습니다.
"조금은 쉬면서 해라", "그렇게 까지 해서 뭐하냐", "잠 좀 자라"
그 때는 그냥 농담처럼 들렸지만, 돌이켜보면 저는 스스로에게 꽤 높은 기준을 강요하고 있었던 것 같습니다.
음.. 개인적인 생각이지만, 이런 성향이 꼭 나쁘다고만 생각하지는 않습니다. 이 덕분에 디테일을 놓치지 않았고, 구조를 더 고민하게 되었고, 결과적으로 더 나은 방향을 선택할 수 있었던 순간들도 분명히 있었기 때문입니다.
다만, 모든 것을 완벽하게 만드는 것이 항상 좋은 선택은 아니라는 것은 확실히 느꼈습니다. 어떤 것은 지금 바로 해결해야 하고, 어떤 것은 나중으로 미뤄도 되는 문제라는 것을 구분하는 것도 때로는 필요하다는 것을 느꼈습니다.
조금은 다른 이야기지만, 누군가는 저에게 이렇게 이야기 합니다.
"개발이 그렇게 재밌냐?"
"왜 그렇게 열심히 하냐, 어차피 AI 딸깍 아니냐?"
처음에는 이런 말들이 조금은 흔들리게 만들기도 했습니다. 정말 내가 너무 과하게 하고 있는 건가 하는 생각이 들기도 했습니다.
근데 어쩔 수 없나봅니다.. 저는 제가 좋아해서 이걸 하고 있기 때문에.. 꼭 해야만 직성이 풀리는 것 같습니다.
이런 집요함이나 강박에 가까운 성향도, 결국은 제가 이 일을 좋아하기 때문에 나오는 것이라고 생각합니다. 관심이 없었다면, 그렇게까지 고민하지도 않았을 것이고, 굳이 시간을 더 써가면서 붙잡고 있지도 않았을 것 같습니다.
그리고 한 가지 더 느낀게 있는데, 저는 제 분야에서 누군가에게 "잘한다"라는 말을 듣는 것을 좋아하는 사람인 것 같습니다.
그 인정이 좋아서, 조금 더 잘해보고 싶고, 조금 더 깊게 파보고 싶고, 그래서 더 열심히 하게 되는 것 같습니다.
AI에 대한 나의 생각
저는 한때 AI에 대해 굉장히 회의적이었던 사람이었습니다. 어느 정도였냐면, 작년 애플 디벨로퍼 아카데미 챌린지2 발표에서 "AI와의 전쟁"이라는 주제로 발표를 했을 정도였습니다.
그만큼 AI 사용에 대한 고민이 많았고, 이게 정말 도움이 되는 도구인지 아니면 오히려 방해가 되는 도구인지에 대해 계속 의문을 가지고 있었습니다.
그 과정에서 멘토와 현업에 계신 선배분들께 많은 이야기를 들었습니다. 그리고 그 커피챗을 통해 생각이 조금씩 바뀌게 되었습니다. 지금의 저는 AI를 배척하기보다 "AI를 어떻게 잘 활용할 것인가"를 고민하고 있습니다.
완벽한 비유는 아니지만, 저는 이렇게 생각합니다.
사무직이 수기로 일하던 시대에서 엑셀을 사용하는 시대로 바뀌었다고 해서 사무직 자체가 사라지지는 않았습니다. 대신, 엑셀을 더 잘 다루는 사람이 경쟁력을 가지게 되었을 뿐입니다.
AI도 비슷하다고 생각합니다. AI 때문에 개발자가 완전히 사라진다기보다는, AI를 잘 활용하는 개발자가 더 중요한 시대가 되고 있다고 느꼈습니다. (적어도 지금까지는요.. AI가 더 발전하면 정말 어떻게 될진 모르겠지만ㅠ)
다만, 정말 한 가지 확실한 건 AI코드를 그대로 복붙하는 방식은 절대 통하지 않는다고 생각합니다.
AI는 할루시네이션이 존재하고, 프로젝트 전체 맥락을 완벽하게 이해하지 못한 상태에서 그럴듯한 코드를 만들어내기도 합니다. 결국 그 코드를 제대로 사용하기 위해서는 개발자가 직접 이해하고, 검증할 수 있어야 합니다. 그래서 저는 AI를 메인 도구가 아니라, 보조 도구이자 학습 도구로 생각하고 있습니다.
이 생각은 실제 팀플을 하면서 더 확실해졌는데요.. 아카데미에서 팀플을 진행할 때, AI로 생성된 코드를 그대로 사용하는 경우들이 종종 있습니다. 그 결과, 코드 리뷰가 어려워지는 경우가 있었고, 코드의 의도를 설명하지 못하는 상황이 발생했고, 이후 리팩토링 단계에서 구조를 이해하기 어려운 문제가 생기기도 했습니다.
특히 AI는 우리 프로젝트의 전체 구조를 완전히 이해하고 있는 것이 아니기 때문에, 부분적으로는 맞지만 전체적으로는 어긋난 코드가 만들어지는 경우도 많았습니다.
그래서 지금은 AI를 이렇게 사용하려고 합니다
- 내가 알고 있는 영역을 더 빠르게 확장하는 도구
- 놓치기 쉬운 케이스를 보완해주는 도구
- 새로운 개념을 빠르게 학습하는 도구
그리고 그 모든 과정에서, 최종 판단과 책임은 항상 개발자에게 있어야 한다고 생각합니다.
Flyleaf를 개발하면서 AI를 활용한 방법 (feat. 저만의 프롬프트 작성법을 공개합니다)
실제로 이번 프로젝트에서는 문제를 함께 고민하는 도구로 활용했습니다.
Python 자동화 코드 작성, 데이터 가공, 지도 기반 거리 계산 수식 검증 뿐만아니라 기획, 디자인, QA, UX 라이팅, 단위 테스트 설계까지 개발 전반에 거쳐 AI를 적극적으로 활용했습니다.
특히 디자인이나 기획처럼 제가 상대적으로 익숙하지 않은 영역에서도 AI를 통해 빠르게 방향을 잡고, 결과적으로 작업 범위를 확장할 수 있었습니다.
또, 아키텍처와 구조적인 고민을 AI와 함께 풀어나갔는데요.
예를 들어, 이번 프로젝트에서는 Micro-Features Architecture를 적용하면서 아래와 같은 고민이 있었습니다.
- Feature 내부 구현을 외부에서 직접 참조하는 것이 맞는 구조인지
- Interface를 통해 의존성을 분리하는 것이 실제로 의미가 있는지
- Builder를 통해 화면을 생성하는 방식이 적절한지
- App 모듈에서 화면을 조립하려면 결국 구현체를 참조해야하는데 이것이 맞는 구조인지
- 의존성을 분리하는 것이 단위 테스트에서 실제로 어떤 이점이 있는지
이런 부분들은 단순히 정답이 있는 문제가 아니라 설계 의도를 기반으로 검증해야 하는 영역이었습니다.
그래서 AI를 사용할 떄도 단순한 질문 방식은 지양했습니다."이 구조 맞아?", "이 코드 괜찮아?" 보다는
예를 들어:
- "Micro-Features Architectur를 적용하면서 각 Feature 간 직접 의존을 피하기 위해 Interface를 통해 의존성을 분리하고 있습니다. 그런데 코디네이터에서 화면을 생성하는 과정에서 결국 인터페이스의 구현체(Builder)를 생성해야 하기 때문에, SceneDelegate에서는 Feature 구현 모듈을 직접 참조하게 되는 구조입니다. 이처럼 SceneDelegate에서만 구현 의존을 허용하는 방식이 Micro-Features Architecture 관점에서 적절한 구조인지 궁금합니다. 혹시 이와 유사한 구조나 아키텍처 사례가 있다면 같이 알려주고, 해당 정보의 레퍼런스와 링크를 같이 알려주세요."
- "UICollectionView에서 무한 스크롤을 구현할 때, 현재는 willDisplay를 활용해 마지막 셀이 노출되는 시점에 다음 페이지를 요청하는 구조를 사용하고 있습니다. 다만 이 방식이 사용자 경험이나 성능 측면에서 최적의 방식인지 고민이되어, UICollectionViewDataSourcePrefetching을 활용한 방식과 비교했을 때 어떤 상황에서 더 적절한 방법인지 궁금합니다. 특히 네트워크 요청 타이밍, 스크롤 성능, 중복 요청 방지 측면에서 장단점을 기준으로 설명해주세요. 또, 해당 정보의 레퍼런스와 링크를 같이 알려주세요."
- "최근 검색어 기능을 UserDefaults로 구현했는데, 현재 구조에서는 단순히 최근 검색 기록을 저장하고 빠르게 불러오는 캐시 성격으로 사용하고 있습니다. 이런 경우 UserDefaults를 사용하는 것이 적절한 선택인지, 아니면 데이터의 성격에 따라 별도의 계층이나 로컬 스토리지(CoreData, SwiftData 등)으로 분리하는 것이 더 좋은 구조인지 고민 됩니다. 특히 데이터의 중요도, 데이터의 양, 기능 확장 가능성 관점에서 어떤 기준으로 저장소를 선택하는 것이 적절한지 궁금합니다. 또, 해당 정보의 레퍼런스와 링크를 같이 알려주세요."
이렇게 제가 먼저 가설을 세우고, AI와 함께 검증하는 방식으로 활용했습니다. 확실하게 느낀 건, AI는 질문을 대신해주는 도구가 아니라, 좋은 질문을 했을 때 더 좋은 답을 주는 도구라는 점이었습니다. 그리고 그 질문의 퀄리티는 개발자가 얼마나 이해하고 알고 있는지에 따라 달라진다고 느꼈습니다.
기술적인 이야기도 해봅시다!
1. 생각보다 Feature간 직접 참조할 일이 거의 없다?
프로젝트를 시작하기 전에는 "A 피쳐의 기능이 필요하면, 그 기능 일부를 B 피쳐에서 가져다 써야하지 않을까?"
그래서 Feature 간 기능을 어떻게 분리하고, 어떻게 재사용할지에 대해 꽤 많은 고민을 했었습니다.
하지만 실제로 개발을 진행하면서 느낀 건 예상과 달랐습니다. 생각보다 피쳐 간 직접적인 기능 참조는 거의 발생하지 않았습니다.
대부분의 경우는 A Feature -> B Feature로 화면 전환을 하는 경우였고, 특정 기능을 다른 Feature에서 재사용하는 구조는 거의 등장하지 않았습니다.
이 이유를 구조적으로 생각해보니 생각보다(?) 자연스러운 결과였는데요..
이번 프로젝트에서 실제 비즈니스 로직은 Core 모듈의 Service 레이어에 위치하고 있고, 각 Feature는 해당 Service를 활용해 UI와 상태를 구성하는 역할을 담당하고 있습니다.
또한 Service 역시 인터페이스 기반으로 설계되어 있어, Feature는 구현체가 아니라 추상화에 의존하도록 구성되어 있습니다. 여기에 더해, 코디네이터 역시 중요한 역할을 하고 있었습니다.
일반적으로 피쳐 내부에서 다른 피쳐를 직접 생성하게 되면 자연스럽게 피쳐간 결합도가 높아지게 됩니다. 하지만 이번 프로젝트에서는 화면 전환과 흐름 제어를 코디네이터가 담당하도록 분리했기 때문에, 피쳐는 다른 피쳐를 직접 알 필요가 없는 구조가 되었습니다.
예를 들어 홈에서 서치화면으로 이동하는 경우에도, 홈 피쳐에서 서치 피쳐를 직접 생성하는 것이 아니라, 코디네이터가 서치 피쳐의 빌더를 통해 화면을 생성하고 연결하는 구조로 설계했습니다.
이 구조 덕분에 피쳐 간 직접 의존하지 않고, 화면 흐름은 코디네이터가 관리한다는 구조가 자연스럽게 만들어졌고, 피쳐 간 직접 참조할 일은 거의 없었습니다.
그리고 한 가지 재밌는 지점이 하나 있는데용
인터페이스 모듈을 살펴보면 대부분이 Buildable 형태의 화면 생성 인터페이스만 존재하고 있습니다. 즉, 피쳐 간 관계는 기능 공유가 아니라 화면 이동 중심으로 구성되어 있었습니다.
물론 이런 구조가 항상 정답이라고 생각하지는 않습니다. 프로젝트의 성격이나 도메인에 따라 충분히 달라질 수 있다고 생각합니다.
예를 들어, 쇼핑 도메인(장바구니, 결제, 할인), SNS 도메인(피드, 댓글, 알림)과 같이 기능 간 결합도가 높은 경우에는 피쳐 간 의존이 자연스럽게 발생할 수도 있다고 생각합니다. 이런 경우에는 단순히 피쳐를 분리하는 것만으로는 한계가 있고, 공통 기능을 별도의 영역으로 분리하는 방식이 더 적절할 수 있지 않을까 생각합니다.
2. Core 모듈에 대한 고민
처음 프로젝트 구조를 설계할 때는 서비스 모듈을 별도로 분리하지 않고, 공통 모델과 서비스 로직을 모두 Core 모듈 안에 두는 방식을 선택했습니다. 이유는 단순했습니다. 프로젝트 규모에 비해 서비스 모듈을 처음부터 분리하는 것은 오버엔지니어링이라고 판단했기 때문입니다.
서비스 개수가 많지 않았고, 실제로 존재하는 서비스도 2~3개 수준이었습니다. 이 정도 규모에서 서비스를 별도 모듈로 나누기 시작하면 구조는 더 정교해 보일 수 있지만, 오히려 관리할 포인트만 늘어날 수 있다고 판단했습니다.
그래서 초기에는 Core 모듈 안에 공통 모델, 서비스 프로토콜, 서비스 구현체를 함께 두고, Feature에서는 이를 사용하는 방식으로 구조를 단순하게 유지하는 것을 우선 선택했습니다.
하지만 프로젝트를 진행하면서 조금씩 다른 고민이 생기기 시작했습니다. 처음에는 서비스 개수가 적어서 문제가 되지 않았지만, 점점 기능이 늘어나면서 코어 모듈 안에 여러 역할이 함께 모이기 시작했습니다. 공통 모델, 유틸리티, 서비스 프로토콜, 서비스 구현체 이 모든 것이 하나의 모듈 안에 존재하게 되면서, 코어가 점점 여러 책임을 동시에 가지게 되는 구조가 아닐까 라는 고민이 들기 시작했습니다.
또 하나 고민이 되었던 부분은 의존성이었습니다. 현재 구조에서는 각 피쳐가 코어 모듈을 Import하여 필요한 서비스를 사용하는 방식입니다. 이 구조 자체는 문제가 되지 않지만, 조금 더 생각해보면 한 가지 특징이 있습니다.
피쳐는 실제로 필요한 서비스보다 더 큰 범위의 모듈에 의존하게 됩니다.
예를 들면 SearchFeature는 SearchService, Book 관련 로직 정도만 필요하지만, 현재 구조에서는 코어 모듈 전체를 import하기 때문에 결과적으로 다른 서비스, User 관련 로직, 기타 공통 요소까지 포함된 모듈에 함께 의존하게 됩니다.
물론 이 구조에서 Service가 변경된다고 해서 각 Feature에 직접적인 컴파일 오류가 발생하는 문제는 아닙니다. 하지만 이 구조는 모듈의 책임 범위를 넓히고, Feature가 불필요하게 더 큰 영역에 의존하게 만든다고 생각했습니다.
그래서 지금 고민하고 있는 지점은 단순히 서비스 개수가 많아졌는가가 아니라 서비스가 하나의 독립적인 역할로 분리될 만큼 명확한 경계를 가지게 되었는가입니다.
하지만 코어 모듈 내부에 서로 다른 책임이 계속 쌓이고 있고, 피쳐가 필요 이상의 범위에 의존하고 있으며, 구조적으로 경계를 더 명확히 나눌 수 있는 시점이 보인다면 Service를 별도의 모듈로 분리하는 것이 더 적절한 선택이 될 수도 있다고 생각하고 있습니다.
3. AppCoordinator의 비대함
개발을 진행하면서 가장 많은 고민이 있던 지점 중 하나는 AppCoordinator의 역할이 점점 비대해지고 있다는 점이었습니다.
처음에는 코디네이터를 하나로 두고 앱의 전체 흐름을 관리하는 구조가 단순하고 명확하다고 생각했습니다.
- 앱 시작 흐름 분기
- 탭바 구성
- 각 피쳐별 화면 전환
이 정도 역할만 담당한다면, 하나의 코디네이터로도 충분하다고 판단했습니다.
하지만 기능이 점점 추가되면서 이야기가 조금 달라졌습니다. Wishlist, Journey, History, Search, Home 등 각 피쳐의 플로우가 복잡해지기 시작하면서 AppCoordinator가 모든 화면 흐름을 직접 처리하는 구조가 되었습니다.
예를 들어,
- 위시 리스트 등록 -> 검색 -> 공항 선택 -> 티켓 생성
- 여정 등록 -> 검색 -> 티켓 생성
- 히스토리 등록 -> 상세 조회
이와 같은 플로우들이 모두 앱코디네이터 내부에 정의되면서, 코디네이터가 단순한 앱 진입 흐름 관리를 넘어 각 피쳐의 세부 플로우까지 모두 알고 있는 구조가 되어버렸습니다.
이 구조에서는 두 가지 문제가 있다고 생각합니다.
1. 책임이 과도하게 집중된다.
앱코디네이터 하나가 모든 피쳐의 화면 전환을 알고있고, 각 피쳐의 흐름까지 직접 관리하다 보니 점점 하나의 거대한 클래스가 되어갔습니다.
2. 이벤트 전달 구조가 점점 복잡하다.
현재 구조에서는 빌더를 통해 뷰컨을 생성할 때, onTapBack, onUploadCompleted, onTapRegisterBookSearch 등 여러 콜백을 전달하고 있습니다. 처음에는 간단했지만, 피쳐가 늘어날수록 이 콜백들이 계속 증가하면서 빌더의 시그니쳐가 점점 복잡해지고, 흐름을 파악하기 어려워지는 문제가 발생했습니다.
그래서 현재는 이 구조를 Flow 단위로 코디네이터를 분리하고, 콜백 기반 이벤트를 라우트로 통합하여 개선하는 방식으로 리팩토링을 진행하고 있습니다.
앞으로 어떻게 할 것인가?
1. AppCoordinator 구조 개선 리팩토링
현재 AppCoordinator에 집중된 화면 흐름을 Feature(Flow) 단위 Coordinator로 분리하여, 각 기능의 책임 범위를 명확히 나누고 의존성과 복잡도를 줄이는 방향으로 리팩토링을 진행할 예정입니다. 또한 콜백 기반 이벤트 전달 구조를 Route 기반으로 개선하여, 화면 전환 흐름을 더 명확하게 관리할 계획입니다.
2. 유저 테스트 기반 UX 및 편의성 개선

현재까지는 개발 중심으로 기능을 구현해왔지만, 감사하게도 아카데미 러너들과 지인들을 대상으로 유저 테스트를 진행하며 다양한 피드백을 받을 수 있었습니다. 현재는 해당 피드백을 모두 분석한 상태이며, 이를 바탕으로 개선 작업을 진행할 예정입니다. 특히 입력 흐름, 툴팁, 온보딩과 같은 사용자 경험의 디테일한 부분을 중심으로, 더 직관적이고 자연스러운 사용 흐름을 제공할 수 있도록 보완해 나갈 계획입니다.
3. AI QA 매니저 구축
AI를 활용하여 시나리오 기반 QA를 자동화하는 AI QA 매니저를 구축하여 도입할 예정입니다. QA 프로세스 전반을 보조하는 도구로 활용하는 것을 목표로 하고 있습니다.
4. 새로운 기능 확장
현재 핵심 기능을 기반으로, UX를 더 풍부하게 만들 수 있는 기능들을 단계적으로 확장할 계획입니다. 단순 기능 추가보다는 기존 구조를 유지하면서 자연스럽게 확장 가능한 방향으로 설계하고 구현해 나갈 예정입니다.