<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>현욱 정리장</title>
    <link>https://c004245.tistory.com/</link>
    <description>현욱 정리장</description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 04:12:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>현욱 정리장</managingEditor>
    <image>
      <title>현욱 정리장</title>
      <url>https://tistory1.daumcdn.net/tistory/7113492/attach/1fa61e7599fa4e418dfcb6aa99e8c75f</url>
      <link>https://c004245.tistory.com</link>
    </image>
    <item>
      <title>실용적인 코틀린 딥 다이브: 내부 코틀린을 마스터하고, 다음 기술 인터뷰를 준비하기</title>
      <link>https://c004245.tistory.com/131</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@skydoves/practical-kotlin-deep-dive-master-kotlin-internals-and-ace-your-next-technical-interview-33ff30f91493&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@skydoves/practical-kotlin-deep-dive-master-kotlin-internals-and-ace-your-next-technical-interview-33ff30f91493&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771807481702&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Practical Kotlin Deep Dive: Master Kotlin Internals and Ace Your Next Technical Interview&quot; data-og-description=&quot;Most Kotlin developers can write a data class, launch a coroutine, or use lazy without thinking twice. These features work, the code&amp;hellip;&quot; data-og-host=&quot;skydoves.medium.com&quot; data-og-source-url=&quot;https://medium.com/@skydoves/practical-kotlin-deep-dive-master-kotlin-internals-and-ace-your-next-technical-interview-33ff30f91493&quot; data-og-url=&quot;https://skydoves.medium.com/practical-kotlin-deep-dive-master-kotlin-internals-and-ace-your-next-technical-interview-33ff30f91493&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wKCS9/dJMb9kTZNnN/EC5vQu6VLUEf3Yi1zbycd0/img.png?width=1200&amp;amp;height=520&amp;amp;face=0_0_1200_520&quot;&gt;&lt;a href=&quot;https://medium.com/@skydoves/practical-kotlin-deep-dive-master-kotlin-internals-and-ace-your-next-technical-interview-33ff30f91493&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@skydoves/practical-kotlin-deep-dive-master-kotlin-internals-and-ace-your-next-technical-interview-33ff30f91493&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wKCS9/dJMb9kTZNnN/EC5vQu6VLUEf3Yi1zbycd0/img.png?width=1200&amp;amp;height=520&amp;amp;face=0_0_1200_520');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Practical Kotlin Deep Dive: Master Kotlin Internals and Ace Your Next Technical Interview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Most Kotlin developers can write a data class, launch a coroutine, or use lazy without thinking twice. These features work, the code&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;skydoves.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 코틀린 개발자는 data class 를 쓰고, 코루틴을 실행하고, lazy를 사용하는 일을 고민없이 해낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능들은 잘 동작하고 &amp;nbsp;코드는 컴파일되며, 앱이 실행됩니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;하지만 코틀린을 사용하는 방법과, 너의 코드에서 컴파일러가 어떻게 실행되는지 이해하는데에는 분명한 간극이 존재합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 간극은 성능에 대한 직관, 디버깅 스킬, 기술 면접에서의 자신감이 만들어지는 지점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실용적인 코틀린 딥 다비에서는 이 간격을 메우기 위해 설계된 책입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 492페이지에 걸쳐 70개의 코틀린 주제를 다루며, &lt;br /&gt;바이트코드 디컴파일, 컴파일러 소스코드 참조, KEEP 분석 제안을 통해&lt;br /&gt;우리가 매일 사용하는 기능들 뒤에서 실제로 어떠 메커니즘이 작동하는지를 드러냅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는, 책을 설명하고 그에 연계된 강의가 어떤 내용을 다루는지 살펴봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 6개의 챕터를 하나씩 따라가며,&amp;nbsp;&lt;br /&gt;책속에서 얻을 수 있는 인사이트 예시도 함께 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이 책이 일반적인 코틀린 자료들과 어떻게 다른 방식으로 주제를 접근하는지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Pro tips for mastery&quot; 섹션이 내부 메커니즘에 대해 어떤 통찰을 제공하는지도 살펴봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 인터렉티브 강의 형식이 Code Playground와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;158 퀴즈 질문을 통해 해당 지식을 단순한 이해가 아닌 검증된 이해 로 전환하는 과정을 보여줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문법을 넘어서: 내부 구조가 왜 중요한가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lazy가 처음 접근될 때 값을 캐싱한다는 사실을 아는 것은 유용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 기본 lazy 구현이 synchronized 블록과 함께 이중 검사 잠금 방식을 사용한 다는것,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 LazyThreadSafetyMode.PUBLICATION을 전달하면 락 없는 compareAndSet 전략으로 전환할 수 있다는 것을 아는 것은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 기능을 사용하는 것과, 그 기능에 대해 근거있는 의사 결정을 내리는 것 사이의 차이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원리는 코틀린 언어 전반에 적용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차 함수에 전달되는 모든 람다가 힙에 Function 객체를 생성한다는 사실을 이해하면 왜 inline이 존재하는지도 자연스럽게 설명된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 data class를 디컴파일 했을 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7개의 자동 생성 메서드로 변환되는 것을 보면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 개발자를 대신해 실제로 어떤 작업을 수행하고 있는지 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 바로 Practical Kotlin Deep Dive가 전반적으로 취하는 접근방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 코틀린 기능이 무엇을 하는지 설명하는 대신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것들이 내부적으로 어떻게 동작하는지를 분석합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것들이 어떤 바이트코드를 생성하는지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 설계자들이 어떤 설계상의 트레이드 오프를 선택했는지,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 KEEP 제안 문서가 그러한 결정의 배경 논리에 대해 무엇을 드러내는지 까지 분석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 목표는 학문적인 것이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 실용적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 구조를 이해하게 되면, 더 나은 코드를 작성하게 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅을 더 빠르게 할 수 있으며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시니어 엔지니어를 구분 짓는 깊이 있는 수준으로 기술 면접 질문에 답할 수 있게됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 책이 다루는 내용: 여섯 개의 챕터, 70개의 주제&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 6개의 챕터로 구성되어 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 챕터는 코틀린 개발의 서로 다른 영역에 초점을 맞추고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 챕터는 심층 설명, 실제 코드 예제, 그리고 표면 아래에서 어떤 일이 일어나는지 드러내는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Pro Tips for mastery&quot; 섹션이 포함되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1장: 코틀린 언어 (24개 토픽)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 장은 모든 코틀린 개발자가 매일 다루는 언어의 기초 개념들을 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Null 안정성, 타입 시스템, 클래스와 상속, sealed 클래스, 제네릭, 인라인 함수, delegation 등입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 사용 패턴에서 멈추지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 주제는 해당 기능을 그 설계 기원까지 거슬러 올라갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Null 안정성 섹션은 단순히 ?. 안전 호출 연산자를 설명하는 데 그치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 섹션은 Kotlin 컴파일러가 non-nullable 파라미터를 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 함수 경계에서 Intrinsics.checkNotNullParameter 호출을 어떻게 삽입하는지를 분석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 null 위반이 타입 경계를 넘어서는 순간 이를 포착하는 런타임 안전망을 형성하며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 전체로 조용히 전파되도록 방치하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data class 섹션에서는 한 줄 짜리 data class (User(val name: String, val age: Int)를 완전한 Java 코드로 디컴파일 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 null 체크가 포함된 생성자를 드러내고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 분해를 위한 componentN() 메서드와 비트 마스크 기반 기본값 처리를 사용하는 synthetic copy$default, 메서드&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내용 기반 equals(0, hashCode, toString() 구현을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장 전반에 걸쳐 KEEP 제안 문서와 JetBrains YouTrack 토론에 대한 참조를 발견할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 언어가 왜 지금과 같은 방식으로 설계되었는지를 설명하며, 단순히 무엇을 하는지를 설명하는데 그치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2장 표준 라이브러리 주제 (7개 주제)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 라이브러리 장에서는 컬렉션 프레임 워크, 시퀀스, 스코프 함수,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코틀린 코드를 간결하고 표현력 있게 만들어주는 유틸리티들을 다룹니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장의 초점은 성능과 정확성에 영향을 미치는 내부적인 차이를 이해하는 데 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 핵심 통찰은 구현 수준에서 listOf()와 emptyList()의 차이에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자가 없는 listOf()는 emptyList()와 동일한 싱글톤 빈 리스트를 반환하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 함수는 표준 라이브러리 내부에서 서로 다른 경로를 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 내부 경로를 이해하고, eager 컬렉션 연산과 lazy Sequence 평가의 차이를 함께 이해하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능에 민감한 상황에서 올바른 선택을 할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스코프 함수 섹션(let, run, apply, also, with)은 일반적인 null 체크에서는 let을 사용하라는 조언을 넘어섭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수들이 어떻게 컴파일 되는지, 그리고 inline 키워드가 언제 오버헤드를 완전히 제거하는지를 분석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3장 코루틴 (19개 주제)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 장은 이 책에서 가장 큰 분량을 차지하며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 코틀린 비동기 프고르맹에 접근하는 방식의 깊이와 복잡성을 반영합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장은 suspend 함수, 코루틴 빌더(launch, async, runBlocking), 구조화된 동시성, CoroutineScope, 디스패쳐,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡과 supervisorJob, 예외 처리, flow, StateFlow, ShareFlow 채널 등 다양한 주제를 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장을 차별화 하는 요소는 내부분석입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CancellationException에 대한 섹션이 좋은 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 개발자들은 코루틴에서 취소가 예외에 사용한다는 것을 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 책은 CancellationException 이 코루틴 계층 구조를 통해 정확히 어디에 전파되는지를 추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 왜 다른 예외들과 비교했을 때 특별한 취급을 받는지도 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실수로 catch(e:Exception)과 같은 광범위한 예외 처리 블록으로 이를 잡았을 때 어떤 일이 발생하는지도 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 실제 프로덕션 환경에서 버그를 유발하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 면접에서도 자주 등장하는 미묘하지만 중요한 디테일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow 섹션에서는 code stream 모델을 분석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SharedFlow의 내부 버퍼링 전략과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 stateln과 sharedln의 구현 수준 차이도 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 stateflow는 항상 초기값을 요구하는 반면 sharedFlow는 그렇지 않은지 궁금했던 적이 있다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장에서 그 아키텍처적 이유를 설명합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4장: KotlinX 라이브러리 (4개 주제)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장은 공식 Kotlin 확장 라이브러리인 kotlin-serialization, kotlin-datetime, kotlinx-collections-immutable, kotlin-io를 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 젯브레인스가 더 넓은 코틀린 생태계의 일부로 유지 관리하는 라이브러리들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이들의 내부 구조를 이해하면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 더 효과적으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin-serialization 섹션은 특히 많은 것을 드러내는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gson이나 Jackson과 같은 리플렉션 기반 직렬화 라이브러리와 달리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin-serialization은 컴파일 시점에 serializer 클래스를 생성하기 위해 컴파일러 플러그인을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 @Seriallization 어노테이션이 컴파일 파이프라인에서 무엇을 유발하는지를 단계적으로 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 생성된 Kserializer 구현이 어떻게 동작하는지도 설명합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이러한 접근 방식이 리플렉션 기반 대안들과 비교했을 때 더 나은 성능과 완전한 멀티플랫폼 지원을 제공하는 이유도 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5장: 코틀린 컴파일러와 플러그인 (7개 주제)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장은 컴파일 과정 그 자체의 내부로 여러분을 안내합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장은 컴파일러의 아키텍쳐를 다루고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애노테이션 프로세싱을 위한 KAPT와 KSP의 차이,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 FIR을 사용하는 K2 컴파일러, IR 백엔드, 그리고 컴포즈나 Kotlin-serialization과 같은 컴파일러 플러그인이 컴파일 파이프라인에 어떻게 연결되는지도 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KAPT와 KSP 비교는 이 장에서 가장 실용적인 섹션 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KAPT는 Kotlin 소스 파일로부터 JAVA 스텁 파일을 생성한 뒤,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 스텁에 대해 표준 자바 어노테이션 프로세서를 실행하는 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 KAPT가 본질적으로 부분 컴파일을 두 번 수행한다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 빌드 시간에 상당한 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 KSP는 스텁을 생성하지 않고, 코틀린 컴파일러 심볼에 직접 접근합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 많은 프로젝트에서 2배 이상 더 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 이러한 성능 차이가 정확히 어디에서 비롯되는지를 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 KAPT에서 KSP로 마이그레이션 하는 것이 언제 노력할 가치가 있는지를 판단하도록 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6장: 코틀린 멀티플랫폼 (9개 주제)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 장은 Kotlin 멀티플랫폼 (KMP)을 다루며, 프로젝트 구조, 소스셋 계층 구조, 플랫폼 별 API를 위한 expect/actual 매커니즘,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift와 object-C의 상호 운용성,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 공유 UI 개발을 위한 컴포즈 멀티플랫폼을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;expect/actual 섹션은 KMP 가 크로스 플랫폼 개발의 근본적인 문제를 어떻게 해결하는지를 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 각 플랫폼에서 다르게 동작해야 하는 공유 코드를 작성하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 expect 선언이 컴파일 타임 계약으로 작동하여,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랫폼별 모듈이 이를 반드시 구현해야 한다는 점을 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 컴파일러가 각 타깃 플랫폼에 대해 모든 expect 선언이 대응되는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;actual 구현을 가지고 있는지 검증하는 방식도 설명합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이러한 접근 방식이 런타임 다형성보다 더 강력한 안정성을 제공하는 이유도 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 모듈이 플랫폼별 API를 호출하는 구체적인 예시도 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android(Log.d), iOS(NSLog), 그리고 JVM(println) 각각에 대해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 것이 하나의 공유 인터페이스를 통해 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pro tips for Mastery: 차이를 만들어내는 깊이&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 전반에 걸쳐, 눈에 띄는 표시로 구분된 Pro Tips for mastery 섹션들을 만나게 될 것이빈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 섹션들은 본문보다 더 깊이 들어가며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이트코드 디컴파일, 컴파일러 소스 코드, 그리고 대부분의 코틀린 자료가 다루지 않는 구현 세부 사항 까지 분석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책에는 이러한 Pro tips가50 개 이상 포함되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 예를 들면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Value Class의 소거와 박싱 경계&lt;br /&gt;컴파일러가 생성된 바이트 코드에서 vallue class 래퍼를 어떻게 제거하는지&lt;br /&gt;그리고 어떤 상황 (제너릭 컨텍스트, Nullable 타입, Any로 저장되는 경우 등)에서 컴파일러가 해당 값을 실제 힙에 할당된 객체로 박싱하는지를 정확히 설명합니다.&lt;/li&gt;
&lt;li&gt;Lazy 위임의 스레드 안정성 내부 구조&amp;nbsp;&lt;br /&gt;lazy() 뒤에는 세 가지 서로 다른 구현 (UnsafeLazyImpl, SynchronizedLazyImpl, SafePublicationLazyImpl)이 존재하며, 각각이 어떤 동시성 트레이드 오프를 선택하는지도 설명합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;확장 함수의 컴파일 방식&amp;nbsp;&lt;br /&gt;모든 확장 함수가 JVM에서 static 메서도르 컴파일된다는 점, 그리고 수신 객체가 첫 번째 파라미터로 전달된다는 점&amp;nbsp;&lt;br /&gt;그리고 이것이 확장 함수가 다형적 디스패치가 아니라, 정적 디스패치를 사용한다는 의미인 이유도 설명합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;고차 함수의 할당 비용&amp;nbsp;&lt;br /&gt;모든 람다가 힙에 Function 객체를 생성한다는 점 ,&lt;br /&gt;그리고 inline이 함수와 람다 본문을 호출 지점에 직접 삽입함으로써 이를 제거하는 방식도 설명합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 통찰은 여러분이 작성하는 코드를 바라보는 방식을 완전히 바꿔놓습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이러한 지식은 기술 면접에서 지원자를 차별화하는 요소가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강의: 코드 플레이그라운드를 활용한 인터렉티브 학습&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Practical Kotlin Deep Dive 강의는 책과 동일한 ISBN을 가진 동일한 콘텐츠를 기반으로 구성되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 강의는 단순히 읽는데에서 그치지 않고, 직접 연습하고, 이해도를 검증하고자 하는 개발자들을 위해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계된 인터렉티브한 학습 요소를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 강의 페이지의 &quot;What This Corse Cover&quot; 섹션에서 강의에 포함된 내용을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 플레이 그라운드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1장 부터 4장까지 전반에 걸쳐,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 레슨에는 실행 가능한 Kotlin 코드가 포함된 코드 플레이그라운드 섹션이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들은 서로 분리된 개별 연습문제가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 해당 레슨에서 다룬 개념들과 직접적으로 연결되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 여러분들이 방금 배운 바로 그 기능들을 직접 체험할 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 플레이그라운드는 핵심 학습 목표를 포함하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 출력 주석이 포함된 독립 실행형 코드 예제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계별 설명, 그리고 더 깊은 탐구를 위한 열린 문제들도 포함되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;158개의 인터렉티브 퀴즈 문항&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 장은 여러분들의 이해도를 평가하는 퀴즈와 연습문제 섹션으로 마무리 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 문제마다 3번의 시도 기회가 주어집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분의 최고 점수가 기록되며, 그리고 암기가 아니라 진정한 이해를 보장하기 위해 정답 선택지는 무작위로 섞입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제들은 실질적인 지식 확인과 더 깊은 개념적 이해를 모두 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수료증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 퀴즈와 연습 문제를 합격 점수로 완료하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자가 서명한 Kotlin Deep Dive 수료증을 받게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 수료증은 코틀린에 대한 여러분의 포괄적인 이해를 인증해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 기초부터 컴파일러 내부 구조, 그리고 멀티플랫폼 개발에 이르기까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 형식을 선택할 것인가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책과 강의 모두 동일한 70개 주제를 동일한 깊이로 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점은 그 내용을 어떻게 학습하느냐에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 Leanpub, Gumroad, amazon을 통해 PDF, EPUB, 그리고 인쇄본 형태로 제공됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이책은 자신의 속도에 맞춰 읽고, 메모하고, 다시 참고 자료로 활용하길 선호하는 개발자들에게 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PDF 형식은 특정 주제를 빠르게 검색하는 데에 특히 유용하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의는 Code PlayGround, 158개의 인터렉티브 평가, 장별 요약, 용어집, 그리고 수료증을 추가로 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 강의는 능동적인 실습을 통해 더 잘 학습하고, 자신이 내용을 충분히 습득했는지를 체계적으로 검증받고자 하는 개발자들을 위해 설계되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이 강의는 한국어, 일본어, 중국어, 스페인어, 독일어, 프랑스어, 포루투갈어, 다양한 언어로 읽기를 선호하는 개발자들을 위해 브라우저 번역 기능을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색하고 메모할 수 있는 참고 자료를 원한다면 책을 선택하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈와 수료증이 포함된 실습 중심 학습을 원한다면 강의를 선택하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지를 모두 원한다면 강의에는 책의 전체 내용이 포함되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 책은 누구를 위한 책인가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Practical Kotlin Deep Dive는 튜토리얼을 넘어, 언어를 더 깊이 이해하고자 하는 코틀린 개발자들을 위해 쓰였다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중급 개발자들은 자신이 매일 사용하는 기능들에 대한 명확한 설명을 발견하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 기능이 어떻게 동작하는지뿐만 아니라 왜 그렇게 설계되었는지도 이해하도록 돕는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시니어 개발자들은 바이트 코드 디컴파일 분석, 컴파일러 아키텍쳐 분석, 그리고 KEEP 제안 문서 참조를 통해 큰 도움을 받을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 아키텍쳐 결정과 코드 리뷰에 필요한 깊이를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 면접을 준비하는 개발자들은 피상적인 답변과 진정한 숙련도를 보여주는 심층적인답변을 구분 짓는 내부 지식을 얻게 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 애플리케이션으로 개발을 하든, 코틀린으로 백엔드 서비스를 구축하든, 코틀린 멀티 플랫폼으로 크로스 플랫폼 개발을 탐색하든&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법 아래에서 실제로 어떤 일이 일어나는지를 이해하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하고 디버깅하며 사고하는 방식이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 보기에는 동일해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이면의 결정들은 근본적으로 달라진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/131</guid>
      <comments>https://c004245.tistory.com/131#entry131comment</comments>
      <pubDate>Mon, 23 Feb 2026 11:10:57 +0900</pubDate>
    </item>
    <item>
      <title>Gemini vs Claude: 현실적인 안드로이드 AI 워크플로우</title>
      <link>https://c004245.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@pratistha_05/gemini-vs-claude-a-realistic-android-ai-workflow-5b7b4b812b7d&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@pratistha_05/gemini-vs-claude-a-realistic-android-ai-workflow-5b7b4b812b7d&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770697704513&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Gemini vs Claude: A Realistic Android AI Workflow&quot; data-og-description=&quot;The last two years have completely transformed how Android developers write code. What used to take hours &amp;mdash; structuring architecture&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@pratistha_05/gemini-vs-claude-a-realistic-android-ai-workflow-5b7b4b812b7d&quot; data-og-url=&quot;https://medium.com/@pratistha_05/gemini-vs-claude-a-realistic-android-ai-workflow-5b7b4b812b7d&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsjIWm/dJMb8RjXLF4/u8AsOrSYGzvprpLYDPochK/img.png?width=628&amp;amp;height=372&amp;amp;face=0_0_628_372&quot;&gt;&lt;a href=&quot;https://medium.com/@pratistha_05/gemini-vs-claude-a-realistic-android-ai-workflow-5b7b4b812b7d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@pratistha_05/gemini-vs-claude-a-realistic-android-ai-workflow-5b7b4b812b7d&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsjIWm/dJMb8RjXLF4/u8AsOrSYGzvprpLYDPochK/img.png?width=628&amp;amp;height=372&amp;amp;face=0_0_628_372');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Gemini vs Claude: A Realistic Android AI Workflow&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The last two years have completely transformed how Android developers write code. What used to take hours &amp;mdash; structuring architecture&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 2년 동안은 안드로이드 개발자가 코드를 작성하는 방식 자체를 완전히 바꿔놓았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 아키텍쳐를 설계하고, 서비스를 작성하고, Room Entities를 만들고, 크래시를 디버깅하거나 컴포저블 UI를 설계하는대 몇 시간이 걸렸던 작업들이 이제는 AI 코딩 어시스턴트를 활용하면 몇 분만에 끝날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude, GPT, Gemini와 같은 다양한 AI 도구의 등장으로 안드로이드 개발은 더 이상 단순히 코드를 타이핑하는 일이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 아키텍쳐를 이해하고, 변경 사항을 제안하고, 놀러울 정도의 정확도로 전체 구현을 생성해주는 지능형 시스템과 협업하는 과정이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 이러한 AI 도구들이 안드로이드 개발을 어떻게 재편하고 있는지, 그리고 이것이 앞으로 개발자에게 어떤 의미를 가지는지 살펴봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그에 앞서 AI 어시스턴트가 우리의 작업을 얼마나 매끄럽고 부드럽게 만들어주는지부터 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩 어시스턴트는 어떻게 동작하는가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;코딩 어시스턴트는 흔히&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context -&amp;gt; Thought -&amp;gt; Action -&amp;gt; OBservation 이라고 불리는 4단계의 핵심 사이클을 중심으로 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Context(맥락)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에이전트는 코드베이스 중 어떤 부분이 관련이 있는지, 그리고 요청에 포함해야 할 파일이 무엇인지 식별합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Thought(사고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LLM은 다음에 무엇을 할지 판단합니다. 기존 파일을 수정할지, 새로운 코드를 작성할지, 혹은 완전히 새로운 컴포넌트를 생성할지를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Action(행동)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에이전트가 실제 작업을 수행합니다. 적절한 도구를 올바른 인자와 함께 호출하며, 이 단계에서 실제 코드가 작성되거나 수정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Observation (관찰)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모델을 행동의 결과를 살펴보고, 이를 바탕으로 한 번 더 판단합니다. 추가 정보가 필요한지, 아니면 최종 결과를 제공해도 되는지를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 과정은 지속적인 루프로 반복되며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트는 반복할수록 점점 더 나은 답변을 만들어냅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 AI 에이전트는 일상적인 사무 업무부터 사이드 프로젝트까지 폭 넓게 도움을 줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Claude Code란 무엇인가 ?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드 코드란 Anthropic이 개발자를 위해 만든 AI 어시스턴트로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ANdroid Studio나 VS Code 같은 IDE 내부에서 직접 실행되도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도구는 대용량 파일과 복잡한 코드베이스를 그대로 읽고 다룰 수 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 처럼 코드를 복사해서 프롬프트에 붙여넣을 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로 말하면, 당신의 코드 안으로 직접 들어오는 AI 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cluade Code는 앤트로픽이 제공하는 클로드 모델을 래핑한 형태로 동작하며, 다음과 같은 모델을 기반으로 합니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Claude Opus 4&lt;/li&gt;
&lt;li&gt;Cluade Sonnet 4.5&lt;/li&gt;
&lt;li&gt;Cluade Haiku 4.5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설치와 연동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Node.js 18 이상이 설치되어있는지 확인 후 다음 명령어를 실행합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770699676468&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g @anthropic-ai/claude-code&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 터미널에서 다음 명령어를 실행해 인증을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 클로드는 IDE에서 정상적으로 사용하기 위해 Anthropic API 사용량 결제 또는 Claude Pro 모델 구독중 하나를 선택하라고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 전자를 선택했고, 지금까지 문제없이 매우 잘 작동하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드 코드의 초기 설정을 변경하고 싶다면, 터미널에서 다음과 같은 명령어를 실행하면됩니다. /config&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 제 경우를 예로 들어볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 날짜 별로 리마인더 목록이 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시간에 사용자가 알림을 받도록 하는 프로젝트를 만들고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Cluade가 제 일을 어떻게 더 쉽게 만들어주는지 한 번 보겠습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prompt: 코드를 읽고 ReminderList.kt에 대해 변경사항을 제안해줘&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 시니어 개발자 처럼 얼마나 자연스럽게 수정 사항을 제안했는지 알아차리 셨나요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 관리, 에러핸들링, 빠진 유효성 검사들요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 말은 이것들이 작은 허점일수도 있지만, 그래도 다뤄야 한다는 점이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음으로, 게으른 저는 에이전트의 실시간 읽기와 쓰기 속도를 테스트해보려고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트: 이 변경 사항을 코드에 구현해줘&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 클로드가 이전에 언급했던 모든 제안들을 완료하라면서요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에 끝부분에서 저는 해당 파일들에 변경 사항이 이미 코드로 작성되고 있는 것을 볼 수 있었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 클로드는 제안을 해주는 방식으로 그 프롬프트를 완료했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아! 로딩 UI 상태를 처리하는것을 완전히 잊고 있었네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 항상 더 낫고, 더 선호되는 사용자 경험입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 마침내 저는 말할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드는 자신을 하나의 자율 에이전트 또는 특정 작업을 위해 고용한 시니어 엔지니어링 처럼 포지셔닝합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 터미널에서 실행되고, 파일들의 컨텍스트를 사용해 파일 시스템 수준에서 작업합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이는 매크로 작업에 더 강합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Agent Mode란 무엇인가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글은 마침내 한번에 클릭 (수락 또는 거절) 만으로 개발자를 도와주는 내장 AI 어시스턴트를 선보였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 어시스턴트는 코드 읽기와 수정 , 프로젝트 빌드, 코드베이스 검색 등 다양한 IDE 도구를 활용해 Gemini가 최소한의 사용자 개입만으로 복잡한 작업을 처음부터 끝까지 완수 할수 있도록 돕습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 저는 제미나이가 제공하는 UI가 개발자 특히 초보자에게 친화적이라고 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전혀 새로운 것처럼 느껴지지 않으면서도 동시에 상호작용적이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Gemini는 에이전트모드와 askmode를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASK는 기본적으로 사용자가 제공한 프롬포트에 답변해주는 챗봇입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다른 것은 마치 친구처럼 작업을 수행하는 AI 코딩 어시스턴트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떻게 동작하는지 살펴볼까요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이 에이전트 통합을 위해 어떤 API키도 생성하지 않고 일일 제한이나 쿼터에 대해 생각할 필요도 없이 번거로움없이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 AI 어시스턴트에 접근하기 위해 어떤 플랜도 필요하지않으며, 내부적으로 Gemini Pro 모델을 사용합니다. 그래서 무료로 쓰기에는 정말 좋은 딜입니다.&amp;nbsp;&lt;br /&gt;그래서 Claude로 했던 것과 마찬가지로 Gemini AI도 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트: 코드를 읽고&lt;span&gt;&amp;nbsp;&lt;/span&gt;ReminderList.kt에 대해 변경 사항을 제안해줘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그것은 클로드와 다르게 변경을 제안했고,&amp;nbsp;그것들은 구현하기에 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면 관심사의 분리 같은 것들을요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 여기에서 우리는 에이전트가 우리에게 SOLID 원칙을 따르라고 요구하고 있는것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 당신은 매 단계에서 에이전트의 결과물을 검토하고, 다듬고, 방향을 잡아줄 수 있으며, 제안들을 수락할지, 거절할지도 선택할 수 있기에 코드에 대해 완전히 통제하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 만약 운에 맡기고 싶다면 Auto-accept 를 활성화 할수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 가능한 아이디어를 빠르게 반복하고 싶을 때 매우 유용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gemini API Key로 에이전트 모드 업그레이드하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Gemini 모델은 넉넉한 무료 일일 쿼터를 제공하지만, 컨텍스트 윈도우는 제한적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이제 자신의 Gemini API 키를 추가하면, Gemini 2.5 pro를 사용해 Agent Mode의 컨텍스트 윈도우를 최대 100만 토큰까지 확장할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 큰 컨텍스트 윈도우는 Gemini에 더 많은 지시사항, 코드 그리고 첨부파일을 전달할 수 있게 해주며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 더 높은 품질의 응답으로 이어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 특히 복잡한 멀티모듈 프로젝트를 작업할 때 유용한데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 프로젝트는 큰 컨텍스트와 장시간 작업되는 작업을 위한 더 많은 논리적 추론을 요구하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 활성화 하려면, Google AI Studio로 이동해 Gemini API키를 발급받으세요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 한뒤에 Get API Key 버튼을 누르면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 안드로이드 스튜디오로 돌아와 File -&amp;gt; Settings -&amp;gt; Tools -&amp;gt; Gemini &amp;nbsp;경로로 이동해 Gemini API 키를 입력하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key를 입력하면, 해당 키의 사용 범위에 따라 지원 가능한 모델 목록을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 API키와 연결된 Gemini API 사용에는 추가 요금이 부과되므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini API 키를 반드시 안전하게 보호하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 키 사용량은 AI Studio에서 Get API Key &amp;gt; Usage &amp;amp; Billing 을 선택해 모니터링할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어느 쪽이 승자인가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보는 것처럼, Claude Code와 Gemini Agent 는 모두 AI 코딩 어시스턴트로써 훌륭한 도우미지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발자의 워크플로우에서 서로 매우 다른 문제를 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 차이점을 이해하는것이야 말로, 이 도구들을 효과적으로 사용하는 핵심입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드베이스 인식 및 컨텍스트 이해&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드는 큰 컨텍스트 윈도우와 깊은 추론 능력으로 동작하며, 파일 관 일관성을 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 다층 구조의 로직이 구축되어있거나 관심사의 분리가 유지되는 앱에서 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 클로드는 로직을 훼손하지않으면서 더 나은 아키텍처를 구축하는 데에 도움을 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 제미나이는 대부분 열려있는 파일들을 기준으로 컨텍스트를 파악하고 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 대규모 코드베이스에서 변경을 수행하려면, 사용자가 명시적으로 가이드를 제공해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능 개발 및 스캐폴딩&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드는 MVVM, Clean Aritecture, 단방향 흐름 과 같이 일반적인 아키택처 패턴을 이해합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기능 개발 시간과 같은 보일러플레이트 코드를 크게 줄여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드가 제안하는 스니핏은 계획과 구조화에 강점이 있기 때문에 성능, 가독성, 확장성 측면에서 더 나은 방향에 부합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini는 처음부터 컴포넌트와 UI를 만드는데 도움을 주는 주니어 개발자가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 꽤 번거로운 작업이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini는 특히 초보 개발자에게 더 나은 구현을 제안하는 데 도움을 줄 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면 구성 변경에도 UI가 유지되도록 코드를 ViewModel로 옮기자는 제안 같은 것들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디버깅 및 오류 분석&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드는 프로젝트 전체에 대한 더 나은 분석 능력을 가지고 있기 때문에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처, 상태 관리, 그리고 기타 최적화에 대한 리팩토링을 제안합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식으로 코드를 리뷰하는 것은 종종 매우 좋지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 제한되어 있을 때는 저는 빠른 개선과 에러처리를 제공해주는 Gemini를 선호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드 코드와 제미나이 에이전트는 모두 안드로이드 개발자에게 가치 있는 AI 코딩 어시턴트이지만 서로 다른 방식으로 가치가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 저는 둘 중 하나로 대체하기 보다는 두 어시스턴트 모두를 활용하는 편을 더 선호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini Agent 는 고속 지식 및 지원 도구로 이해하는 것이 가장 적절합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 안드로이드에 특화된 질문에 답하고, API를 설명하며, 작은 코드 스니핏을 생성하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교적 단순한 오류를 해결하는 데에 강점을 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 조회, 새로운 제트팩 라이브러리를 학습할 때, 또는 코틀린 문법을 명확히 하고싶을때, Gemini는 즉각적이고 정확한 응답을 제공하며 짧은 피드백 루프에 자연스럽게 들어맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드 코드는 훨씬 더 깊은 엔지니어링 수준에서 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 프로젝트 전체의 컨텍스트를 이해하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 레이어에 걸쳐 추론하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 아키텍쳐 패턴에 부합하는 구조적이고 유지가능한 코드를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 단위완성, 파일 전반에 걸친 리팩토링, 제트팩 컴포즈 성능 최적화,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아키텍처 관점의 코드 리뷰 능력은 클로드 코드를 프로덕션 개발과 장기적인 유지보수에 훨씬 더 적합하게 만듭니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/129</guid>
      <comments>https://c004245.tistory.com/129#entry129comment</comments>
      <pubDate>Tue, 10 Feb 2026 14:53:26 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 앱 시작: 모든 개발자가 알아야 할 7가지 패턴</title>
      <link>https://c004245.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@trricho/android-app-startup-7-optimization-patterns-every-developer-must-know-5fbff354f32e&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@trricho/android-app-startup-7-optimization-patterns-every-developer-must-know-5fbff354f32e&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 앱의 시작 성능은 사용자의 경험과 유지율에 직접적인 영향을 미칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로덕션 앱에서 시작 시간을 최적화 한 경험을 바탕으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜드 시작 시간을 50~70%까지 줄이고 사용자 경험을 크게 개선할 수 있는 7가지의 핵심 패턴을 찾아냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 단순한 이론이 아니라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로덕션 앱에서 검증된 솔루션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업 최적화를 통해 사용자유지율은 15% 증가했고, 앱 삭제율은 23% 감소했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느린 콜드 스타트, 무거운 초기화, 또는 Content Provider 오버헤드로 고민하고 있더라도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴들을 적용하면 1초 미만 스타트업 시간을 달성하는데에 도움이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴 1: 지연 초기화 전략&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자들이 Application.onCreate()에서 모든 것을 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 메인 스레드가 막히고 첫 프레임 렌더링이 지연됩니다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// DON'T DO THIS
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // All initialized synchronously on main thread
        initializeAnalytics()
        initializeCrashReporting()
        initializeImageLoader()
        initializeDatabase()
        initializeNetworkClient()
        loadUserPreferences()
        fetchRemoteConfig()
        setupPushNotifications()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;왜 중요한가 ?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application.onCreate()에서 동기 초기화를 하면 다음과 같은 심각한 문제가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 스레드 차단: 모든 초기화는 Ui 스레드에서 발생하며, 첫번째 프레임이 지연됩니다.&lt;/li&gt;
&lt;li&gt;느린 콜드 스타트: 유저가 화면을 보기전까지 2~5초를 대기해야합니다&lt;/li&gt;
&lt;li&gt;나쁜 첫 인상 : 느린 앱 시작은 앱 삭제비율을 높입니다.&lt;/li&gt;
&lt;li&gt;리소스 낭비: 실제로 쓰이지 않을 것까지 다 초기화합니다.&lt;/li&gt;
&lt;li&gt;우선순위 없음: 중요하지않은것과 중요한것을 구분할 수 없습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결 책&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위 기반에 지연 초기화를 적용합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;isbl&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// CORRECT APPROACH
class MyApplication : Application() {
    private val applicationScope = CoroutineScope(
        SupervisorJob() + Dispatchers.Default
    )
    
    override fun onCreate() {
        super.onCreate()
        
        // Critical only - must happen before UI
        initializeCrashReporting()
        
        // Defer non-critical initialization
        applicationScope.launch {
            // Wait for first frame
            delay(100)
            
            // Initialize in background
            withContext(Dispatchers.IO) {
                initializeAnalytics()
                initializeImageLoader()
                initializeDatabase()
            }
            
            // Defer even further
            delay(500)
            initializeNetworkClient()
            loadUserPreferences()
            fetchRemoteConfig()
        }
    }
    
    override fun onTerminate() {
        super.onTerminate()
        applicationScope.cancel()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 이점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 프레임이 빨라지고, 유저 경험이 개선되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화가 우선순위에 따라 효율적으로 수행되고, 스타트업이 메인 스레드를 막지않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴2: App StartUp 라이브러리를 통한 의존성 기반 초기화 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 Content Provider와 수동 초기화 코드는 앱 시작 시 오브헤드를 만듭니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Content Provider 하나 당 스타트업 시간이 2~20ms씩 증가하며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화 순서를 직접 관리하는 방식은 쉽게 깨집니다&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// DON'T DO THIS
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Manual initialization order - easy to break
        initializeDatabase() // Must be first
        initializeAnalytics() // Depends on database
        initializeImageLoader() // Depends on analytics
        // What if order changes? Breaks silently
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;왜 중요한가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동 초기화 방식은 다음과 같은 문제를 야기합니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의존성 관리가 불가능합니다: 잘못된 순서로 초기화를 합니다&lt;/li&gt;
&lt;li&gt;Content PRovider 오버헤드가 누적: 각 프로바이더가 시작 시간에 쌓입니다&lt;/li&gt;
&lt;li&gt;테스트 하기 어렵습니다: 단독으로 테스트 하기가 어렵습니다&lt;/li&gt;
&lt;li&gt;코드 결합도 증가: 코드베이스에 흩어져있는 초기화 코드&amp;nbsp;&lt;/li&gt;
&lt;li&gt;레이지 로딩 불가능: 필요하지 않아도 모든게 초기화 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결책&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;androidx app startup 라이브러리를 사용해 초기화 의존성을 선언적으로 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// CORRECT APPROACH
// build.gradle
dependencies {
    implementation &quot;androidx.startup:startup-runtime:1.1.1&quot;
}

// Analytics Initializer
class AnalyticsInitializer : Initializer&amp;lt;Analytics&amp;gt; {
    override fun create(context: Context): Analytics {
        return Analytics.getInstance(context).apply {
            setEnabled(true)
            setLogLevel(LogLevel.INFO)
        }
    }
    
    override fun dependencies(): List&amp;lt;Class&amp;lt;out Initializer&amp;lt;*&amp;gt;&amp;gt;&amp;gt; {
        // Analytics depends on Database
        return listOf(DatabaseInitializer::class.java)
    }
}
// Database Initializer
class DatabaseInitializer : Initializer&amp;lt;AppDatabase&amp;gt; {
    override fun create(context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            &quot;app_database&quot;
        ).build()
    }
    
    override fun dependencies(): List&amp;lt;Class&amp;lt;out Initializer&amp;lt;*&amp;gt;&amp;gt;&amp;gt; {
        return emptyList() // No dependencies
    }
}
// Image Loader Initializer (lazy - only when needed)
class ImageLoaderInitializer : Initializer&amp;lt;ImageLoader&amp;gt; {
    override fun create(context: Context): ImageLoader {
        return ImageLoader.getInstance(context)
    }
    
    override fun dependencies(): List&amp;lt;Class&amp;lt;out Initializer&amp;lt;*&amp;gt;&amp;gt;&amp;gt; {
        return emptyList()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dust&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;
&amp;lt;provider
    android:name=&quot;androidx.startup.InitializationProvider&quot;
    android:authorities=&quot;${applicationId}.androidx-startup&quot;
    android:exported=&quot;false&quot;
    tools:node=&quot;merge&quot;&amp;gt;
    &amp;lt;meta-data
        android:name=&quot;com.app.DatabaseInitializer&quot;
        android:value=&quot;androidx.startup&quot; /&amp;gt;
    &amp;lt;meta-data
        android:name=&quot;com.app.AnalyticsInitializer&quot;
        android:value=&quot;androidx.startup&quot; /&amp;gt;
    &amp;lt;!-- ImageLoader marked as lazy --&amp;gt;
    &amp;lt;meta-data
        android:name=&quot;com.app.ImageLoaderInitializer&quot;
        android:value=&quot;androidx.startup&quot;
        tools:node=&quot;remove&quot; /&amp;gt;
&amp;lt;/provider&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// Lazy initialization when needed
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Initialize ImageLoader only when needed
        AppInitializer.getInstance(this)
            .initializeComponent(ImageLoaderInitializer::class.java)
        
        setContent {
            MyApp()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;핵심 이점&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 의존성 관리, ContentProvider 오버헤드 감소, lazy loading 지원, 쉬운 테스트, 더 깔끔한 코드 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴 3: Content Provider 통합 전략&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 Content Provider는 각각 2~20ms의 스타트업 시간을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 라이브러리들이 자체 Provider를 생성하면서 불필요한 오버헤드가누적됩니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- DON'T DO THIS --&amp;gt;
&amp;lt;!-- Each provider adds startup overhead --&amp;gt;
&amp;lt;provider android:name=&quot;com.firebase.provider.FirebaseInitProvider&quot; /&amp;gt;
&amp;lt;provider android:name=&quot;com.facebook.FacebookContentProvider&quot; /&amp;gt;
&amp;lt;provider android:name=&quot;com.crashlytics.android.CrashlyticsInitProvider&quot; /&amp;gt;
&amp;lt;provider android:name=&quot;androidx.startup.InitializationProvider&quot; /&amp;gt;
&amp;lt;!-- Total: 8-80ms overhead --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;왜 중요한가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 Content Provider는 상당한 오버헤드를 만듭니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시작 지연 : 각각 프로바이더는 2~20ms씩 콜드 스타트를 추가합니다&lt;/li&gt;
&lt;li&gt;메인 스레드 차단: Provider 초기화는 UI 스레드를 차단하며 실행됩니다.&lt;/li&gt;
&lt;li&gt;불필요한 오버헤드: 당장 필요하지 않은 기능들까지 앱 시작 시점에 초기화됩니다.&lt;/li&gt;
&lt;li&gt;최적화가 어렵습니다: 초기화 순서를 쉽게 제어할 수 없습니다&lt;/li&gt;
&lt;li&gt;라이브러리 비대화: 서드파티 라이브러리들이 자동으로 프로바이더를 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결책 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Content Provider를 통합하고, App Startup 라이브러리를 활용합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// CORRECT APPROACH
// Single consolidated provider
class AppInitProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        val context = context ?: return false
        
        // Initialize all libraries in single provider
        initializeLibraries(context)
        return true
    }
    
    private fun initializeLibraries(context: Context) {
        // Critical initialization only
        Firebase.initializeApp(context)
        Crashlytics.initialize(context)
        
        // Non-critical can be deferred
        // Analytics, ImageLoader, etc. initialized lazily
    }
    
    // Other methods return null/false - not a real provider
    override fun query(uri: Uri, projection: Array&amp;lt;String&amp;gt;?, selection: String?, 
                      selectionArgs: Array&amp;lt;String&amp;gt;?, sortOrder: String?): Cursor? = null
    override fun getType(uri: Uri): String? = null
    override fun insert(uri: Uri, values: ContentValues?): Uri? = null
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array&amp;lt;String&amp;gt;?): Int = 0
    override fun update(uri: Uri, values: ContentValues?, selection: String?, 
                       selectionArgs: Array&amp;lt;String&amp;gt;?): Int = 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dust&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;
&amp;lt;!-- Disable library providers, use consolidated one --&amp;gt;
&amp;lt;provider
    android:name=&quot;com.firebase.provider.FirebaseInitProvider&quot;
    tools:node=&quot;remove&quot; /&amp;gt;
    
&amp;lt;provider
    android:name=&quot;com.app.AppInitProvider&quot;
    android:authorities=&quot;${applicationId}.appinit&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;핵심 효과&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업 오버헤드 감소, 단일 초기화 진입점 확보, 초기화 제어력 향상, 성능 최적화 용이, 더 깔끔하고 관리가능한 아키텍처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴4: 런치 테마 최적화&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 실행되는 동안 빈 화면이 잠깐 표시되면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제보다 앱이 더 느리게 느껴지고 사용자에게 좋지않은 첫인상을 줍니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- DON'T DO THIS --&amp;gt;
&amp;lt;!-- Blank white screen during startup --&amp;gt;
&amp;lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.Material3.DayNight&quot;&amp;gt;
    &amp;lt;item name=&quot;android:windowBackground&quot;&amp;gt;@android:color/white&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;왜 중요한가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출시 경험이 좋지 않으면 다음과 같은 문제가 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;체감 성능 저하: 빈 화면은 실제보다 느리게 느껴지게 만듭니다&lt;/li&gt;
&lt;li&gt;나쁜 첫 인상: 유저는 UI가 나타나기전에 빈 화면이 먼저 보여집니다.&lt;/li&gt;
&lt;li&gt;브랜드 이미지 손상: 앱 실행 시 앱 브랜딩이 전혀 매치되지않습니다&amp;nbsp;&lt;/li&gt;
&lt;li&gt;사용자 혼란 : 앱이 멈춘것처럼 오해할 수 있습니다&lt;/li&gt;
&lt;li&gt;삭제율 증가: 좋지 않은 첫인상으로 인해 앱 삭제로 이어질 가능성이 높습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결책&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런치 테마를 활용해 스플래시 스크린을 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- CORRECT APPROACH --&amp;gt;
&amp;lt;!-- styles.xml --&amp;gt;
&amp;lt;style name=&quot;SplashTheme&quot; parent=&quot;Theme.AppCompat.NoActionBar&quot;&amp;gt;
    &amp;lt;!-- Branded splash background --&amp;gt;
    &amp;lt;item name=&quot;android:windowBackground&quot;&amp;gt;@drawable/splash_background&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;android:windowFullscreen&quot;&amp;gt;true&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;android:windowContentOverlay&quot;&amp;gt;@null&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;
&amp;lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.Material3.DayNight&quot;&amp;gt;
    &amp;lt;!-- Regular app theme --&amp;gt;
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;
&amp;lt;activity
    android:name=&quot;.MainActivity&quot;
    android:theme=&quot;@style/SplashTheme&quot;&amp;gt;
    &amp;lt;!-- ... --&amp;gt;
&amp;lt;/activity&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Switch to app theme before super.onCreate()
        setTheme(R.style.AppTheme)
        super.onCreate(savedInstanceState)
        
        setContent {
            MyApp()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!-- drawable/splash_background.xml --&amp;gt;
&amp;lt;layer-list xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&amp;gt;
    &amp;lt;item&amp;gt;
        &amp;lt;color android:color=&quot;@color/splash_background&quot; /&amp;gt;
    &amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;
        &amp;lt;bitmap
            android:gravity=&quot;center&quot;
            android:src=&quot;@drawable/splash_logo&quot; /&amp;gt;
    &amp;lt;/item&amp;gt;
&amp;lt;/layer-list&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;핵심 효과&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 빠르게 느껴지는 체감 성능&lt;/li&gt;
&lt;li&gt;브랜드가 반영된 런칭 경험&lt;/li&gt;
&lt;li&gt;스플래시에서 실제로 부드러운 전환&lt;/li&gt;
&lt;li&gt;전문적인 앱 인상&lt;/li&gt;
&lt;li&gt;사용자 유지율 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴5: 무거운 작업 지연 처리&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 베이스 쿼리, 네트워크 호출, 이미지 처리와 같은 무거운 작업을&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onCreate()에서 수행하면 UI 스레드를 차단하여 첫 프레임 렌더링을 지연시킵니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// DON'T DO THIS
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Heavy operations block UI
        val users = database.getAllUsers() // Blocks main thread
        val images = loadImagesFromDisk() // More blocking
        processData(users) // CPU-intensive
        
        setContent {
            MyApp()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;왜 중요한가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onCreate()에서 작업 차단은 다음과 같은 심각한 문제를 유발합니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 프레임 지연: 모든 작업이 끝날 때까지 UI가 표시되지않습니다.&lt;/li&gt;
&lt;li&gt;ANR 발생 위험 : 5초를 초과하는 작업은 ANR 다이얼로그를 유발할 수 있습니다.&lt;/li&gt;
&lt;li&gt;나쁜 사용자 경험 : 사용자는 빈화면이나 멈춘 앱을 보게 됩니다.&lt;/li&gt;
&lt;li&gt;메인 스레드 차단: 부드러운 애니메이션과 사용자 상호작용이 불가능해집니다.&lt;/li&gt;
&lt;li&gt;리소스 낭비: 사용자가 당장 필요하지도 않은 데이터를 미리 로딩합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결책&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI를 먼저 표시하고, 데이터는 비동기로 로딩합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// CORRECT APPROACH
class MainActivity : ComponentActivity() {
    private val viewModel: MainViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Show UI immediately
        setContent {
            MyApp(viewModel.uiState.collectAsState().value)
        }
        
        // Load data asynchronously
        lifecycleScope.launch {
            viewModel.loadData()
        }
    }
}

class MainViewModel : ViewModel() {
    private val _uiState = MutableStateFlow&amp;lt;UiState&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;&amp;gt;(UiState.Loading)
    val uiState: StateFlow&amp;lt;UiState&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;&amp;gt; = _uiState.asStateFlow()
    
    fun loadData() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            
            try {
                val users = withContext(Dispatchers.IO) {
                    database.getAllUsers()
                }
                _uiState.value = UiState.Success(users)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: &quot;Error loading data&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 효과&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 즉시 표시&amp;nbsp;&lt;/li&gt;
&lt;li&gt;UI 스레드를 차단하지 않는 비동기 처리&lt;/li&gt;
&lt;li&gt;더 나은 사용자 경험&lt;/li&gt;
&lt;li&gt;ANR 방지&amp;nbsp;&lt;/li&gt;
&lt;li&gt;효율적인 리소스 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴6: 안드로이드13+ 을 위한 baseline profiles 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android는 JIT 컴파일 방식을 사용하기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 처음 실행할 때 코드가 실행중에 컴파일되며 속도가 느려집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 콜드 스타트 성능이 크게 영향을 받습니다&lt;/p&gt;
&lt;pre class=&quot;1c&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// DON'T DO THIS
// No baseline profile - relies on JIT compilation
// First launch: Slow (JIT compilation)
// Subsequent launches: Faster (cached compilation)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 중요한가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;baseline profile이 없을 경우 다음과 같은 문제가 발생합니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;느릿 첫 실행:JIT 컴파일로 인해 200~500ms 스타트업 시간이 추가됩니다.&lt;/li&gt;
&lt;li&gt;일관되지 않은 성능: 첫 실행이 이후 실행보다 훨씬 느립니다&lt;/li&gt;
&lt;li&gt;나쁜 콜드 경험: 신규 사용자에게는 최악에 경험이 됩니다.&lt;/li&gt;
&lt;li&gt;최적의 기회 상실: 안드로이드13에서 무료로 제공되는 최적화를 활용하지 못합니다.&lt;/li&gt;
&lt;li&gt;경쟁력 저하: Baseline Profile을 적용한 앱 보다 시작 속도가 느립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결책&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 13+ 대상으로 Baseline Profile을 적용합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// CORRECT APPROACH
// build.gradle
android {
    defaultConfig {
        // ...
    }
    
    buildTypes {
        release {
            // Enable baseline profiles
            isMinifyEnabled = true
            isShrinkResources = true
        }
    }
}

dependencies {
    implementation &quot;androidx.profileinstaller:profileinstaller:1.3.1&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;vim&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// Generate baseline profile
// 1. Run app and use key user flows
// 2. Use Android Studio Baseline Profile Generator
// 3. Or use command line:

// adb shell am start -W -n com.package/.MainActivity
// adb shell cmd package compile -m speed-profile com.package
// adb pull /data/misc/profman/com.package.prof baseline-prof.txt&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// src/main/baseline-prof.txt
// Generated profile includes hot paths:
HSPLcom/example/MainActivity;-&amp;gt;onCreate(Landroid/os/Bundle;)V
HSPLcom/example/MainViewModel;-&amp;gt;&amp;lt;init&amp;gt;()V
HSPLcom/example/Repository;-&amp;gt;getData()Ljava/util/List;
// ... more hot paths&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;효과&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 13+ 에서 30~40% 개선&amp;nbsp;&lt;/li&gt;
&lt;li&gt;핫 패스 기준 실행 성능 20% 향상&lt;/li&gt;
&lt;li&gt;Play Store 설치 시 자동 최적화 적용&lt;/li&gt;
&lt;li&gt;런타임 오버헤드 없음 - 컴파일 타임 최적화만 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 효과&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 빠른 콜드 스타트&lt;/li&gt;
&lt;li&gt;일관된 성능&amp;nbsp;&lt;/li&gt;
&lt;li&gt;향상된 사용자 경험&lt;/li&gt;
&lt;li&gt;경쟁력 확보&lt;/li&gt;
&lt;li&gt;무료로 제공되는 성능 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패턴7 스타트업 시간 측정 모니터링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 측정 없이, 스타트업 성능을 개선하는것은 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병목을 식별할수도없고, 개선효과를 추적할 수도없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자들이 데이터 없이 감으로 최적화를 진행합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// DON'T DO THIS
// No measurement - flying blind
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // No idea how long this takes
        initializeEverything()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;왜 중요한가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;측정이 없으면 다음과 같은 문제가 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기준점 부재: 개선 전후를 비교할 수 없습니다&lt;/li&gt;
&lt;li&gt;병목 미확인: 무엇이 느린지 알 수 없습니다&lt;/li&gt;
&lt;li&gt;추적 불가: 성능 회귀를 감지 할 수 없습니다.&lt;/li&gt;
&lt;li&gt;잘못된 최적화: 느리지 않은 부분을 최적화 하게됩니다.&lt;/li&gt;
&lt;li&gt;데이터 부재: 최적화 작업의 필요성을 설명하거나 설득할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 해결책&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합적인 스타트업 측정 로직을 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// CORRECT APPROACH
class MyApplication : Application() {
    private val startupTimeTracker = StartupTimeTracker()
    
    override fun onCreate() {
        val startTime = System.currentTimeMillis()
        super.onCreate()
        
        startupTimeTracker.trackApplicationStart(startTime)
        
        // Track initialization phases
        startupTimeTracker.trackPhase(&quot;crash_reporting&quot;) {
            initializeCrashReporting()
        }
        
        startupTimeTracker.trackPhase(&quot;database&quot;) {
            initializeDatabase()
        }
        
        startupTimeTracker.trackPhase(&quot;analytics&quot;) {
            initializeAnalytics()
        }
        
        startupTimeTracker.logResults()
    }
}

class StartupTimeTracker {
    private val phases = mutableListOf&amp;lt;Phase&amp;gt;()
    private var applicationStartTime: Long = 0
    
    fun trackApplicationStart(startTime: Long) {
        applicationStartTime = startTime
    }
    
    fun trackPhase(name: String, block: () -&amp;gt; Unit) {
        val startTime = System.currentTimeMillis()
        block()
        val duration = System.currentTimeMillis() - startTime
        phases.add(Phase(name, duration))
    }
    
    fun logResults() {
        val totalTime = System.currentTimeMillis() - applicationStartTime
        Log.d(&quot;Startup&quot;, &quot;Total startup time: ${totalTime}ms&quot;)
        phases.forEach { phase -&amp;gt;
            Log.d(&quot;Startup&quot;, &quot;${phase.name}: ${phase.duration}ms&quot;)
        }
        
        // Send to analytics
        Analytics.logEvent(&quot;app_startup_time&quot;, mapOf(
            &quot;total_time&quot; to totalTime,
            &quot;phases&quot; to phases.map { &quot;${it.name}:${it.duration}&quot; }
        ))
    }
    
    data class Phase(val name: String, val duration: Long)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;jboss-cli&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// ADB measurement
// adb shell am start -W -n com.package/.MainActivity
// Output:
// ThisTime: 850        (Activity launch time)
// TotalTime: 1523      (App startup + Activity launch)
// WaitTime: 1545       (Total including system overhead)

// Logcat measurement
// adb logcat | grep &quot;Displayed&quot;
// Output:
// ActivityManager: Displayed com.package/.MainActivity: +1s523ms&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// Production monitoring
class StartupMonitor {
    fun trackColdStart() {
        val startTime = SystemClock.uptimeMillis()
        
        // Measure time to first frame
        val firstFrameTime = measureFirstFrame()
        
        // Log to Firebase Performance
        FirebasePerformance.getInstance()
            .newTrace(&quot;cold_start&quot;)
            .apply {
                putAttribute(&quot;first_frame_ms&quot;, firstFrameTime)
                start()
                stop()
            }
    }
    
    private fun measureFirstFrame(): Long {
        // Measure time until first frame rendered
        return SystemClock.uptimeMillis() - startTime
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;핵심 효과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 기반 최적화&lt;/li&gt;
&lt;li&gt;명확한 병목 지점 식별&lt;/li&gt;
&lt;li&gt;성능 회긔감지&lt;/li&gt;
&lt;li&gt;지속적인 성능 추적&lt;/li&gt;
&lt;li&gt;측정 가능한 개선 결과&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론은 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 7가지 패턴은 빠른 안드로이드 앱 스타트업을 위한 핵심 기반을 이룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴들을 일관되게 적용한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초 미만의 콜드 스타트 시간,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나은 사용자 경험,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리텐션 향상 유지율을 달성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억해야할 점은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업 최적화는 단순히 코드의 문제가 아니라는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 사용자 경험을 이해하고 실제 영향을 측정하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 측정부터 시작하고, 병목을 식별한뒤, 이 패턴들을 적용하고, 개선 결과를 지속적으로 추적하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 스타트업 시간으을 향한 여정은 한번으로 끝나지않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 패턴들을 도구 상자에갖추고 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 실행부터 즉각적이고 반응성 좋은 앱을 만드는데 충분히 준비된 상태라고 할 수 있어요.&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/127</guid>
      <comments>https://c004245.tistory.com/127#entry127comment</comments>
      <pubDate>Mon, 19 Jan 2026 16:50:58 +0900</pubDate>
    </item>
    <item>
      <title>모바일 시스템 디자인 인터뷰: 내가 FAANG 인터뷰를 준비한 과정과 무료 연습자료</title>
      <link>https://c004245.tistory.com/126</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@jain.ayusch10/the-mobile-system-design-interview-my-faang-prep-journey-free-practice-resource-96afce1f9583&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@jain.ayusch10/the-mobile-system-design-interview-my-faang-prep-journey-free-practice-resource-96afce1f9583&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인에 대해 이야기할 때, 온라인에 잇는 대부분에 자료와 경험담은 백엔드 시스템 디자인에 매우 치중되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checkout: mockingly.ai는 시스템 디자인 인터뷰를 준비하기 위한 최고의 도구중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 FAANGs중 한 곳에서 모바일 엔지니어(AOS,iOS)를 목표로 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 까다로운 모바일 시스템 인터뷰를 반드시 거쳐야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 FAANG 5곳중 3곳에서 인터뷰를 봤고, 그 시스템 디자인 라운드를 통과했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 6년동안 모바일 엔지니어로 일하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 회사들에게서 시스템 디자인 라운드가 어떻게 구성되는지에 대해 꽤 충분한 경험을 갖고 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모바일에 초점을 맞춘 시스템 디자인 준비 자료는 매우 부족합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 블로그에서 이 회사들에서 인터뷰한 경험과 시스템 디자인 인터뷰를 준비하는 몇가지 팁을 공유하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글이 곧 있을 인터뷸르 앞둔 모바일 개발자들에게 도움이 되길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 경험&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FaANG의 인터뷰는 3개의 주요 구성요소로 이뤄져있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 코딩 인터뷰: 자료구조와 알고리즘에 초점을 둔 인터뷰가 보통 2~3라운드로 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 시스템 디자인 인터뷰: 대부분 1라운드지만, 최근에는 2라운드로 늘어나는 추세입니다. 나 역시 빅 테크에서 이를 직접 경험했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 행동(경험) 인터뷰: 과거 경험을 포커스로 한 인터뷰로, 상황 기반 질문이 나오며 STAR 기법으로 답변하는 것이 가장 좋은 방법입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 시스템 디자인에 대해 이야기 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 인터뷰에서 실제로 받았던 시스템 디자인 질문들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Glide와 같은 이미지 캐싱 라이브러리를 설계하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이미지와 비디오를 내보낼수있는 1:1 챗 어플리케이션을 설계하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 앱 스토어를 설계하라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이커머스의 홈 메인을 설계하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 주식 가격을 표시하는 주식 앱의 티커 페이지를 설계하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 기억하는 대표적인 것은 이정도이다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 질문들의 대한 답은 온라인에서 찾을 수 있지만 단순히 달달 외우는것이 솔루션이 아닙니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 나도 외우는 방식으로 준비했는데, 면접 날이 다가올수록 불안해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 컴포넌트를 그리는걸 깜빡하지 않을까 하는 불안감에 시달렸습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 번에 인터뷰에 떨어진뒤에야 깨달았는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 지식이 부족해서 실패한게 아니라, 아키텍처 처럼 생각하지 못하고 세부사항에 집착했기 때문에 실패했던 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인 인터뷰에서는 당신이 원하는대로 이끌수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터뷰의 흐름을 어떻게 가져갈건지는 너한테 달려있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 깊게 팦고들수도있고, 설계에 기본 구성 요소들에 집중한뒤, 필요하다면 자신있는 블록위주로 설명할수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 컴포넌트에 대해 면접관이 질문하도록 맡겨야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 인터뷰를 어렵게 만들지마세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;나의 준비 과정&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 엔지니어로서 인터뷰를 준비할 수 있는 자료를 많이 찾지못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기저기 흩어진 질문은 있었지만 백엔드 중심으로 체계화된 Grokking the system design interview와 같은 자료는 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구들과 모의 인터뷰도 해보았지만, 크게 도움이 되진 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구들 대부분이 백엔드 엔지니어였고, 모두 업무가 바빠 시간을 맞추기도 어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말에 하자는 계획도 성사되지 않았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FAANG 출신 엔지니어와 시스템 디자인 인터뷰를 모의로 제공하는 사이트도 발견했지만 비용이 너무 비쌌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홍보: 나는 이 문제를 해결하기 위해서 mockingly.ai를 만들었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 전용 시스템 디자인 질문, 즉각적인 AI 피드백, 그리고 온디맨드 연습을 제공하는 최초의 모의 인터뷰 플랫폼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24시간 언제든지 FAANG 엔지니어가 모의인터뷰를 해주는것과 같은 경험입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 첫번쨰 세션은 무료입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로 실제로 도움이 되었던 자료는 weebox의 깃허브 저장소였고 모바일 중심의 디자인질문들이 담겨있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나는 최악에 선택을했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 모든 답을 외우려고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서말했듯이 디자인 인터뷰는 매우 주관적이기 때문에 암기는 전혀 도움이되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말로 간절히 원하든 회사의 디자인 인터뷰에서 처참하게 떨어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 크게 낙담했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머릿속에 모든 답을 가지고 있다고 생각했기 때문에 디자인 라운드는 통과할수있을거라고 꽤 자신했었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 인터뷰를 망쳤다는 사실이 도저히 믿기지가 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에도 시스템 디자인 인터뷰를 몇번망친적은 있었지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때는 그렇게 간절히 원하던 회사는 아니였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번에는 달랐습니다. 뭔가 바뀌어야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 변화는 저의 준비전략이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 암기식 준비가 전혀 도움이 되지 않는다는 것을 깨달았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인이 실제로 어떻게 동작하는지, 어떤 컴포넌트와 개념에 집중해야하는지를 진짜로 이해할 필요가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 인터뷰에 접근하는 방식도 달라져야했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내가 실제로 준비한 방법은 이렇습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. weebox의 모바일 시스템 디자인 질문을 안드로이드, iOS 관점이 아니라 아키텍트 관점에서 보기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 화면, 프레젠테이션 레이어, 데이터 레이어 단위로 생각하기 시작했고 레포지토리 패턴이 어떻게 동작하는지도 공부했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. WorkerManager, Foreground Service &amp;nbsp;같은 안드로이드 구현 개념으로도 생각하지 않으려고 노력했습니다. 아키텍트처럼 더 높은 레벨에서 사고하고싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. weebox에서 1개의 질문을 골라 혼자서 설계해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 내가 그린 다이어그램을 weebox의 답안과 비교하며 리뷰했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 다음으로 HTTP, HTTPS, REST, WEBSOCKET vs GRAPHQL 같은 개념들을 골라 학습했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 왜 REST 를 선택했고 웹소켓을 선택하지않았는지, 등 내 설계 선택을 적극적으로 비판적으로 검토하기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 모의 인터뷰도 시도했지만, 함께 할 사람을 찾기 어려웠고, 시간도 잘 맞지않았습니다. 이런 상황에서는 mockingly.ai 처럼 온디맨드 구조의 모이 인터뷰를 할 수 있는 도구가 시스템 디자인 준비에 매우 유용했을 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 라이브러리 설계도 다르지 않았습니다. 화면은 없지만 반드시 엔트리 포인트가 있습니다. 예를들어 캐싱 라이브러리라면 cachemanager가 진입점이 되고, repository, 파일캐시, sqlite와 같은 다른 컴포넌트와 통신합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인 인터뷰를 이해하는데 4~5달 이 걸렸고, 가장 큰 변화는 사고방식이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 엔지니어, iOS 엔지니어와 같은 도메인 전문가가 아니라 아키텍트 처럼 생각하기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 인터뷰를 잡았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터뷰 경험&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비를 마친뒤 몇몇 인터뷰에 도전했고, 운이 좋게도 FAANG중 한곳에서 기회를 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 불안하지 않았습니다. 적어도 시스템 디자인 인터뷰에 대해서는요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념이 머리속에서 정리되어있었고, 충분히 준비됐다는 걸 알고있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 무엇을 하고 있는지, 어떻게 생각하고 있는지, 왜 그런결정을 내렸는지만 잘 정리할 수 있다면 아주 수월할 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로도 그랬습니다. 피드백을 받았고, 비록 코딩 라운드 때문에 최종 합격은 하지못했지만, 시스템 디자인 인터뷰는 통과했고 평가도 string hire였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 기뻤습니다. 인터뷰 준비 과정에서 내가 유일하게 넘지못했던 장애물을 극복하기 시작했기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인은 나에게 미스테리였지만 나는 그걸 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 코딩 라운드 처럼 시스템 디자인 라운드도 여전히 준비는 필요하지만 더이상 두렵지는 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 인터뷰 한달전쯤에 다시 꺼내서 준비해도 괜찮을 나만의 자료와 기준이생겼습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;준비 팁&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 시스템 디자인 인터뷰 준비에 도움이 될만한 것들을 정리한다. 모바일 중심으로 다룰 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 언제든지 시스템 디자인 인터뷰 연습을 할수있는 mockingly.ai&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Weebox의 github저장소 질문과 답변을 살펴볼수있고 문제 은행느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Alex Lementuevsdml 유튜브 채널, 일부 인터뷰가 도움이 됩니다. 답변 자체를 그대로 외우기 보다는 인터뷰 흐름과 지원자의 사고 과정을 보는데 집중하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 말하세요. 많이말하세요. 생각을 소리내어하는연습이 필요합니다. 인터뷰를 주도하는데 필수입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. HTTP, REST, GRAPHQL, 웹소켓이 어떻게 동작하는지 그리고 왜 하나를 다른것보다 선택하는지를 이해하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 특정 도메인 엔지니어가 아니라 아키텍츠 처럼 생각하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 연습&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 어떤 시스템 인터뷰에도 적용할 수 있는 단계별 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 요구사항을 명확히 하라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자의 유형과 플로우를 식별한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 레이어로 분해한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 트레이드 오프를 강조한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 한 컴포넌트를 골라 깊게 파고든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글이 도움이 됐는지를 알려주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 엔지니어가 모방리 시스템 디자인 인터뷰를 준비할 수 있는 가이드가 있으면 좋겠다고 생각하기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글이 그걸 만드려는 나의 시도다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/126</guid>
      <comments>https://c004245.tistory.com/126#entry126comment</comments>
      <pubDate>Tue, 13 Jan 2026 17:49:12 +0900</pubDate>
    </item>
    <item>
      <title>MVI 보일러플레이트는 이제 작별, 코드를 대신 작성해주는 BuildKt MVI 라이브러리 소개</title>
      <link>https://c004245.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Jetpack Compose 로 개발하는 안드로이드 개발자라면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVI (Model-View-Intent) 가 정답이라는 말을 한번씩은 들어봤을 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로도 종종 그렇죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단방향 데이터 흐름과 명확한 책임 분리는 앱을 예측가능하게하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 하기 쉽고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅하기 편합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 솔직히 말해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 하나의 화면을 만들때마다 똑같고 반복적이며 영혼을 갈아넣는 보일러 플레이트를 계속 작성하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVI 화면 하나 만들 때 보통 이런걸 매번 직접한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;state와 model을 보관하기 위한 &amp;nbsp;ViewModel을 생성한다&lt;/li&gt;
&lt;li&gt;네비게이션 인자를 처리하기 위해 ViewModelProvider.Factory 를 작성한다.&lt;/li&gt;
&lt;li&gt;UI 상태를 관리하기 위한 StateFlow를 직접생성한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;한 번만 발생하는 Ui 이벤트(토스트, 네비게이션 등)를 위해 SharedFlow 또는 채널을 직접구성한다.&lt;/li&gt;
&lt;li&gt;화면 이동을 위하 NavGraphBuilder 확장 함수를 작성한다.&lt;/li&gt;
&lt;li&gt;SavedStateHandle에서 네비게이션 인자를 하나하나 신경 써서 직접 꺼낸다.&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이런 작업들을 계속해서 반복합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 지루하고 ,실수하기 쉽고, 우리가 정말로 만들고싶은 것 훌륭한 기능으로부터 우리의 집중력을 빼았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이런 반복적인 사이클에 지쳐버렸습니다. 그래서 이렇게 생각했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;더 나은 방법이 분명히 있을텐데&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 오늘 이러한 보일러 플레이트를 거의 전부 제거해주는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jetpack compose를 위한 어노테이션 기반 MVI framework인 BuildKT MVI의 오픈소스 공개를 기쁘게 소개합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BuildKt MVI란 무엇인가요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BuildKt MVI는 가볍지만 강력한 프레임워크로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KSP (Kotlin symbol PRocessing)을 사용해 각 화면에 필요한 전체 MVI 스캐폴딩을 컴파일 타임에 자동 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자의 역할은 비즈니스 로직과 UI에 집중하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지는 프레임워크가 대신 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 실제로 무엇을 의미하는지, 이제 직접 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아하 모먼트: Before &amp;amp; After&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 프로필을 만든다고 상상해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BuildKt MVI를 사용하지 않는다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 하나의 화면을 위해서도 파일 구조는 다음과 같을 것입니다.&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767664399972&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❌ ProfilePane.kt // Your UI
❌ ProfileViewModel.kt // The ViewModel
❌ ProfileViewModelFactory.kt // The factory for the ViewModel 
❌ ProfileNavGraph.kt // The NavGraphBuilder extension&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;실제로 기능을 구현하기도전에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일들 안에서 각종 플러밍 코드를 작성하는 데 상당한 시간을 쓰게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 BuildKt MVI 를 사용하면 필요한 것이 이것이 전부입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;✅ ProfilePane.kt             // Your UI, annotated
✨ The rest is auto-generated!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 하나를 작성하고, 어노테이션 하나만 추가하면&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크가 컴파일 타임에 VIewModel, Factory, NavGraphBuilder 확장 함수까지 전부 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떻게 동작하나요? 3단계 가이드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5분 안에 그 프로필 화면을 만들어봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. State와 Intent를 정의합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계는 순수한 비즈니스 로직입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에 어떤 데이터가 필요하고, 사용자가 어떤 행동을 할 수 있는지를 정의합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767664714715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class ProfileUiState(
    val isLoading: Boolean = true,
    val userName: String = &quot;&quot;
)

sealed interface ProfileIntent {
    @TriggersSideEffect
    data object LoadUserName : ProfileIntent

    data class OnUserNameLoaded(val name: String) : ProfileIntent
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 @TriggersSideEffect 어노테이션에 주목하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 어노테이션은 LoadUserName이 API 호출과 같은 비동기 작업을 포함한 다는 것을 프레임워크에 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Composable을 만들고 어노테이션을 추가합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소처럼 Composable 을 작성하되&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@MviScreen 어노테이션만 추가하면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 어노테이션이 모든 코드 생성의 트리거가 됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@MviScreen(
    uiState = ProfileUiState::class,
    intent = ProfileIntent::class
)
@Composable
fun ProfilePane(
    state: ProfileUiState,
    onIntent: (ProfileIntent) -&amp;gt; Unit,
) {
    if (state.isLoading) {
        CircularProgressIndicator()
    } else {
        Text(&quot;Hello, ${state.userName}!&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로세서는 state 파라메터와 onIntent 콜백이 존재할 것을 기대하며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 기반으로 전체 구조를 자동으로 연결합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 전부입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. NavHost에서 로직을 구현합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BuildKt MVI는 타입 세이프하고, DSL 기반의 NavGraphBuilder 확장 함수를 자동으로 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 빈칸만 채우면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reducer를 살펴보면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 설계상 순수 함수이며,오직 상태 전이 만 책임집니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767665236385&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NavHost(navController, startDestination = &quot;profilePane&quot;) {

    // KSP로 생성된 `profilePane` 함수
    profilePane(navController = navController) {

        // 상태를 업데이트하는 순수 함수 Reducer
        reducer = { state, intent -&amp;gt;
            when (intent) {
                is ProfileIntent.OnUserNameLoaded -&amp;gt; {
                    state.copy(isLoading = false, userName = intent.name)
                }
                else -&amp;gt; state
            }
        }

        // SideEffect는 모든 비동기 작업을 처리
        sideEffects {
            loadUserName = sideEffect {
                // API 호출 시뮬레이션
                delay(2000)
                // 결과를 담은 새로운 Intent 반환
                newIntent(ProfileIntent.OnUserNameLoaded(&quot;Matias&quot;))
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 끝입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동 보일러 플레이트 없이 완전히 동작하고 테스트 가능한 MVI 화면이 완성되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션 인자가 필요하신가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable에 @NavArgument가 붙은 파라메터만 추가하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지는 자동으로 처리됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 BuildKt MVI 인가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중요한 것에 집중하세요: 배관 코드를 작성하는대신 기능 구현에 집중할 수 있습니다.&lt;/li&gt;
&lt;li&gt;보일러플레이트 대폭감소: 단 하나의 어노테이션이 수백 줄에 코드를 대체합니다.&lt;/li&gt;
&lt;li&gt;설계 단계부터 타입 세이프: Reducer와 SideEffect를 위한 DSL이 자동 생성되어, 런타임 오류를 방지합니다.&lt;/li&gt;
&lt;li&gt;현대적인 안드로이드를 위하 설계됨: KSP와 jetpack Compose를 전제로 설계되었습니다.&lt;/li&gt;
&lt;li&gt;확장성과 테스트 용이성: 유지보수와 테스트가 쉬운, 깔끔하고 분리된 아키텍처를 지향합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금 바로 시작해보세요!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 실제 프로덕션 환경에서 겪은 고통에서 출발했으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈된 형태로 계속해서 발전하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분의 피드백, 이슈 제보, 아이디어 하나하나가 이 프로젝트의 미래를 직접 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BuildKtMVI는 오픈소스이며, Maven Central에서 바로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 프로젝트에서 직접 사용해보고, 문서를 읽어보며 얼마나 많은 시간을 절약할 수 있는지 직접 확인해보시길 권합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 프로젝트를 확인해보시고 유용하다면 스탈르 눌러주세요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈를 열거나 기요도 언제든 환영입니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/125</guid>
      <comments>https://c004245.tistory.com/125#entry125comment</comments>
      <pubDate>Tue, 6 Jan 2026 11:15:44 +0900</pubDate>
    </item>
    <item>
      <title>보일러플레이트 코드를 그만작성하세요: 매일 사용하는 컴포즈 헬퍼 유틸리티</title>
      <link>https://c004245.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@sixtinbydizora/stop-writing-boilerplate-compose-helper-utilities-for-everyday-use-b4e77d180b2a&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@sixtinbydizora/stop-writing-boilerplate-compose-helper-utilities-for-everyday-use-b4e77d180b2a&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복되는 컴포즈 코드를 줄여서 개발 속도를 높일수 있는 재사용 가능한 코틀린 헬퍼 함수 툴킷을 구축하자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번주만 47번째로 같은 Modifier 체인, 로딩 래퍼, 조건부 UI 분기 , 거의 같은 애니메이션 타이밍을 또 쓰고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 팀전체의 반복 10분을 어떻게 한 줄 호출로 바꿀까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그떄 헬퍼 유틸이 유용하고, 생각보다 만들기 쉽다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 헬퍼 유틸리티가 진짜로 중요한가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈는 강력하지만 구조 없이 쓴다면 그냥 기술부채가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㅇㅕ러 화면에서 복잡한 UI를 만들다보면, 같인 Modifier, 상태 처리, 애니메이션 조합이 반복됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 헬퍼들은 단순 편의가 아니라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트당 10개 기능을 내느냐 6개를 내느냐, 코드가 느껴지느냐 퍼즐처럼 흩어지느냐의 차이입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 팀에 필수적인 헬퍼 유틸리티&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조건부 표시 헬퍼&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 거창한 말이 아니라, 조건부 UI를 만들때 if문을 그만쓰자는 이야기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 컴포넌트 합성에 익숙해져야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 유틸리티는 투명도 애니메이션과 레이아웃 재배치를 자동으로 처리해줍니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun ConditionalDisplay(
    visible: Boolean,
    modifier: Modifier = Modifier,
    animationDuration: Int = 300,
    content: @Composable () -&amp;gt; Unit
) {
    val alpha by animateFloatAsState(
        targetValue = if (visible) 1f else 0f,
        animationSpec = tween(durationMillis = animationDuration),
        label = &quot;visibility_alpha&quot;
    )
    
    if (visible || alpha &amp;gt; 0f) {
        Box(
            modifier = modifier
                .graphicsLayer { this.alpha = alpha }
        ) {
            content()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실제 사용은 if로 감싸는 대신 ConditionalDisplay(isLoading) { LoadingSpinner() } 처럼 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 하나당 8~12줄을 줄일수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중앙 로딩 오버레이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙 로딩 상태는 모든 앱에 필수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋 중에 두번은 쓰이니, 한 번 만들어두고 계속 재사용하자&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun LoadingOverlay(
    isVisible: Boolean,
    modifier: Modifier = Modifier,
    backgroundColor: Color = Color.Black.copy(alpha = 0.3f),
    content: @Composable () -&amp;gt; Unit = { CircularProgressIndicator() }
) {
    if (isVisible) {
        Box(
            modifier = modifier
                .fillMaxSize()
                .background(backgroundColor)
                .clickable(enabled = false) { },
            contentAlignment = Alignment.Center
        ) {
            content()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용: 화면 콘텐츠를 감싸고 isLoading만 넘기면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오버레이가 입력 차단과, 중앙 정렬을 자동으로 처리해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적응형 패딩 헬퍼&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 화면 크기에 따라 패딩을 다르게 줘야한다. 값 하드 코딩은 그만하자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 헬퍼는 화면 너비에 따라 패딩을 자동으로 선택해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실사용에서는 Modifier.adaptivePadding() 하나로, 컴포저블 전반에 흩어진 화면 크기 체크를 대체합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;fun Modifier.adaptivePadding(
    compact: Dp = 8.dp,
    medium: Dp = 16.dp,
    expanded: Dp = 24.dp
): Modifier {
    return this.then(
        Modifier.padding(
            when (LocalConfiguration.current.screenWidthDp) {
                in 0..600 -&amp;gt; compact
                in 601..840 -&amp;gt; medium
                else -&amp;gt; expanded
            }
        )
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;검색을 위한 상태 디바운서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색어가 너무 자주 변경되면 요청이 과도하게 발생합니다. 이 헬퍼는 네트워크 스팸을 막아줍니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun &amp;lt;T&amp;gt; rememberDebouncedValue(
    value: T,
    delayMillis: Long = 300L
): T {
    var debouncedValue by remember { mutableStateOf(value) }
    
    LaunchedEffect(value) {
        delay(delayMillis)
        debouncedValue = value
    }
    
    return debouncedValue
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;val debouncedQuery = rememberDebouncedValue(searchText) 처럼 사용하고, API 호출은 debounedQuery 를 관찰해서 수행합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;닫힘 처리를 캡슐화한 바텀시트 헬퍼&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바텀 시트는 닫힘 로직도 보작합니다. 그러니 이걸 캡슐화해야합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun DismissibleBottomSheet(
    isVisible: Boolean,
    onDismiss: () -&amp;gt; Unit,
    modifier: Modifier = Modifier,
    content: @Composable () -&amp;gt; Unit
) {
    if (isVisible) {
        ModalBottomSheet(
            onDismissRequest = onDismiss,
            modifier = modifier
        ) {
            content()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 하면 바텀시트 상태를 5개 화면에서 따로 관리할 필요가없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번만 호출해서 쓰면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자체 유틸리티 라이브러리 구축하기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헬퍼 유틸리티를 만들 떄에 모범 사례&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 목적만 유지하라: 한 가지 명확한 목적만, 과도한 설계는 피하라&lt;/li&gt;
&lt;li&gt;설정 가능하게 만들어라: duration, colors, size 등을 노출해 포크 없이 조정가능하게하라&lt;/li&gt;
&lt;li&gt;문서보다는 예제가 더 중요하다: 파라메터 설명 보다 실제 사용 예를 보여라&lt;/li&gt;
&lt;li&gt;엣지 케이스를 처리하라 : 비어있는상태, 상태 전환, 회전 등 설정 변경을 고려하라&lt;/li&gt;
&lt;li&gt;쉽게 리팩토링하지마라: 유틸리티 코드 변경은 코드 전반에 호환성 영향을 준다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ROI는 실제로 나온다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 설계된 헬퍼 유틸리티로 배포하는 팀들로부터 이런이야기를 듣습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;툴셋이 안정된 이후에는 장기적으로 기능 구현속도가 빨라졌습니다. (약30~40%)&lt;/li&gt;
&lt;li&gt;화면 전반의 동작이 일관돼 UI 버그 감소&lt;/li&gt;
&lt;li&gt;온보딩이 쉬워짐 신규 개발자는 복붙 대신 헬퍼를 읽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 이론이아닙니다. 개발자 4명이 1년에 200번 같은 조건부 표시 로직을 안쓰고 함 수 호출 한번으로 끝내면 실제로 이런 결과가 나옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작게 시작하고, 체계적으로 확장하라&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ui/composables/helpers 패키지를 하나만들자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 스프린트에서는 팀이 반복하고 있는 유틸 3개부터 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서화하고, 팀에 공유하고 점진적으로 확장하라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2~3스프린트만 지나면 팀을 실제로 더 빠르게 만드는 툴킷이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 좋은 점은 이 유틸리티들이 이후에 모든 컴포즈 프로젝트에도 따라온다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 올바른 시점에 올바른 추상화를 만드는 힘입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 정리&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 패턴을 인식하라 - 코드에서 어떤 컴포저블 로직이 반복되는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 가차 없이 캡슐화 하라 - 하나의 컴포저블은 하나의 명확한 이유만 가져야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 설정가능하게 하라 - 유틸에 가정을 하드코딩하지마라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 팀과 공유할 가치가 있는 것만 - 많이 쓸수록 그 가치가 더 커진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 버전 관리를 의도적으로하라 - 규모가 커질 수록 breaking change는 비싸진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애퓰리케이션 헬퍼 유틸은 단순히 코드 양을 줄이는게 아니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관성을 보장하고, 보일러플레이트에 쓰는 사고 시간을 줄여 기능에 집중하게 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 시작하라. 내일은 더 빠르게 배포하라&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/124</guid>
      <comments>https://c004245.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 5 Jan 2026 09:44:34 +0900</pubDate>
    </item>
    <item>
      <title>왜 hilt때문에 너의 앱이 느려지는가? (어떻게 수정할지)</title>
      <link>https://c004245.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/proandroiddev/why-your-app-might-be-slower-because-of-hilt-and-how-to-fix-it-a0a3c89a9724&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/proandroiddev/why-your-app-might-be-slower-because-of-hilt-and-how-to-fix-it-a0a3c89a9724&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 hilt로 마이그레이션했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입 구조의 난장판을 정리하고, 보일러플레이트를 줄이고, 팀 온보딩을 쉽게 만들기 위해 도입했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 공정하게 말하자면 실제로 그 모든 역할을 해주긴했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그다음에 나는 뭔가 이상한점을 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 앱에 콜드 스타트가 눈에 띄게,, 전혀 콜드하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 대놓고 느렸어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 코드를 확인했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드를 탓했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 스튜디오를 재시작했습니다. (확실하게 하게위해 2번이나요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 모든 개발자가 꺼리는 행동을 했습니다. 프로파일러를 열었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 이랬습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hilt는 자기 역할을 잘 하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 명령한 모든 것들을 전부 초기화하고 있었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불행하게도 너무 많은 것을 시킨 사람이 바로 저였다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제: 즉시 주입으로 인한 사망&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 벌어진 일은 이렇습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 네트워크 클라이언트, 레포지토리, Room DB 접근 객체, 애널리틱스 로거 같은 여러 의존성들을 전부 @SingletoneComponent로 옮겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐면 대부분 튜토리얼이 그렇게하니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 깨닫지못했던 점은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hilt가 싱글톤을 앱 실행시점에 즉시 생성한다는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스플래시 화면에 애니메이션이 제대로 유연하게 한 번 움직이기도 전에 말이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 제 Application.onCreate()는 점점 병목 지점이 되어가고있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법: 짧은 패닉 이후&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 수성 역행 때문도 아니고, 어딘가에서 날뛰는 컴포즈 재구성 떄문이 아니라는 걸 받아들이고 나서야&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 자리에 앉아서 리팩토링을 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 실제로 동작했던 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 무거운 의존성에는 Provider&amp;lt;T&amp;gt; 또는 @Lazy를 사용해라&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 즉시 필요하지 않은 것이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 주입을 하지마세요&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Inject lateinit var heavyService: Provider&amp;lt;MyHeavyService&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게 하면 get()을 호출하는 순간에만 실제로 객체가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 실행 시점에 세상 전체를 한꺼번에 초기화할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 모든 것을 SingletonComponent에 주입하지마라&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 저를 깨워주는 계기였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 기능단위로만 쓰이는 로직까지 전부 전역스코프에 주입하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이말에 의미는 곳 온보딩 플로우에서만 쓰이는 것들 조차 앱 시작 시점에 생성되고 있었다는 뜻이었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 대신 다음과 같이 바꾸기 시작했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ViewModels에는 ActivityRetainedComponent&lt;/li&gt;
&lt;li&gt;화면 단위 로직에는 ActivityComponent 또는 FragmentComponent&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스코프를 조금 더 의미있게 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 콜드 스타트 속도는 거의 즉각적으로 빨라졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 꼭 필요하지않다면 최대한 지연시켜라&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말로 크래시 리포팅, 리모트 컨피그, 애널리틱스, A/B 테스트 그리고 서너개의 SDK가 앱 실행 후 0.5초 안에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전부 돌아가고 있어야하나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 그렇지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그런 것들은 전부 지연초기화를 사용하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 백그라운드로 밀어냈습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;CoroutineScope(Dispatchers.Default).launch {
    Analytics.init()
    RemoteConfig.sync()
    OtherStuff.wakeUp()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사용자는 즉시 UI를 보게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무거운 작업은 뒤에서 조용히 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 윈윈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. @Provides 메서드는 최대한 깔끔하게 유지하라&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 @Provides 메서드는 어느새 자기만의 비즈니스 로직을 가진 작은 팩토리 처럼 변해있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 말해서, 자랑스러울 만한 상태는 아니었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 복잡한 초기화 로직은 헬퍼 클래스나 설정 빌더로 옮기고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hilt binding은 가볍고 예측가능하게 유지했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 @Provdes 블록이 몇 줄을 넘기거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.build()가 3번씩 등장한다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마도 너무 많은 일을하고있는것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 추측하지말고, 측정하라&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일러는 거짓말하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 onCreate가 얼마나 걸리는지 확인하기 위해 로그 마커를 추가했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 스튜디오의 프로파일러를 통해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 것들이 너무 이른시점이 초기화되고 있는지를 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 손에 들어오자 리팩토링 방향은 명확해졌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀에 설명하고 설득하는 것도 훨씬 수월해졌습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배운점들&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hilt가 문제인건아닙니다. @SingletoneComponent를 과도하게 사용하고, 너무 많은 것들을 너무 이른시점에 주입한게 문제였죠&lt;/li&gt;
&lt;li&gt;중요하지 않은 의존성을 지연시키는 것만으로도 앱 시작 성능은 크게 개선될 수 있습니다.&lt;/li&gt;
&lt;li&gt;올바른 스코프 설계는 좋은 아키텍처일 뿐만 아니라 좋은 UX기도 합니다.&lt;/li&gt;
&lt;li&gt;지연 주입은 훌륭한 친구입니다. 프로파일러도 마찬가지이고요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hilt로 마이그레이션을 계획중이거나, 이미 사용하고 있다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 하나의 리마인더로 받아들여주세요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강력한 도구 일 수록 신중한 사용이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 힐트로 깔끔하게 정리한뒤에 앱이 더 느려진거같다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 있는건 앱이 아니라 앱에 시작전략일지도몰라요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/123</guid>
      <comments>https://c004245.tistory.com/123#entry123comment</comments>
      <pubDate>Fri, 2 Jan 2026 09:19:15 +0900</pubDate>
    </item>
    <item>
      <title>RemoteCompose: 컴포즈에서 서버 주도 UI를 위한 또 다른 패러다임</title>
      <link>https://c004245.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/proandroiddev/remotecompose-another-paradigm-for-server-driven-ui-in-jetpack-compose-92186619ba8f&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/proandroiddev/remotecompose-another-paradigm-for-server-driven-ui-in-jetpack-compose-92186619ba8f&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적인 사용자 인터페이스를 구축하는 일은 안드로이드 개발에서 오랫동안 중요한 과제로 여겨져 왔습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 방식에서는 UI를 변경할 때마다 전체 어플리케이션을 다시 컴파일하고 배포해야 하는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 A/B 테스트, 기능 플래그, 실시간 콘텐츠 업데이트에 큰 비효율을 초래합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 마케팅 팀이 새로운 결제 버튼 디자인을 테스트하고 싶다고 가정해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방식에서는 이렇게 단순한 변경조차도 개발자 작업, 코드 리뷰, QA 테스트, 앱 스토어 제출, 그리고 사용자가 업데이트를 받아들이기까지 몇 주가 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose는 이러한 문제를 해결하기 위한 강력한 솔루션으로 등작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들이 Compose UI 레이아웃을 재 컴파일 없이 런타임에 생성하고, 전송하며, 렌더링 할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 RemoteCompose가 무엇인지 살펴보고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아키텍처를 이해하며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose 로 동적인 화면을 설계할때 RemoteCompose가 가져오는 이점들을 알아 볼 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 라이브러리 사용 방법을 설명하는 튜토리얼이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Remote Config가 안드로이드 UI 개발에 가져오는 패러다임 변화를 탐구하는 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Integration and Dependencies&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념을 살펴보기 전에, 프로젝트에 RemoteCompose를 추가하는 방법을 먼저 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 의존성이 없는 JVM 기반 서버나 백엔드에서는 다음과 같이 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 RemoteCompose는 Androidx 팀에 의해 개발중이며 공식적으로 배포된 상태가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 AndroidX Snapshot Maven 저장소를 통해서만 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764657909240&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// settings.gradle
repositories {
  maven {
    url = uri(&quot;https://androidx.dev/snapshots/builds/14511716/artifacts/repository&quot;)
  }
}

// JVM server - no Android dependencies
dependencies {
    implementation(&quot;androidx.compose.remote:remote-core:1.0.0-SNAPSHOT&quot;)
    implementation(&quot;androidx.compose.remote:remote-creation-compose:1.0.0-SNAPSHOT&quot;)
}

// Compose-based app
dependencies {
    implementation(&quot;androidx.compose.remote:remote-player-compose:1.0.0-SNAPSHOT&quot;)
    implementation(&quot;androidx.compose.remote:remote-tooling-preview:1.0.0-SNAPSHOT&quot;)
}

// View-based app
dependencies {
    implementation(&quot;androidx.compose.remote:remote-player-view:1.0.0-SNAPSHOT&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Understanding the core abstraction&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose의 핵심은 Compose UI 컴포넌트를 원격에서 렌더링 할 수 있도록 해주는 프레임워크라는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 UI방식과 구별되는 점은 두 가지 근본적인 원칙을 따른 다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 선언적 문서 직렬화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 플랫폼에 의존하지 않는 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들은 단순한 기술적 기능이 아니라,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 배포 방식을 근본적으로 재정의하는 아키텍쳐적 결정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Declarative Document Serialization&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언적 문서 직렬화란&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose 레이아웃을 작고 효율적인 직렬화 포맷으로 캡쳐할 수 있다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 UI의 스크린샷을 찍는것과 비슷하지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀을 저장하는 것이 아니라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 UI를 그리는 명령어를 담아내는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 캡쳐된 문서에는 UI를 복원하는데 필요한 모든 정보가 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도형, 색상, 텍스트, 이미지, 애니메이션, 상호작용 터치 영역&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이 문서 하나만으로 UI전체를 클라이언트에서 재 구성 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764658188263&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val document = captureRemoteDocument(
    context = context,
    creationDisplayInfo = displayInfo,
    profile = profile
) {
    RemoteColumn(modifier = RemoteModifier.fillMaxSize()) {
        RemoteText(&quot;Dynamic Content&quot;)
        RemoteButton(onClick = { /* action */ }) {
            RemoteText(&quot;Click Me&quot;)
        }
    }
}
view raw&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크를 통해 전송할 수 있는 ByteArray입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 접근 방식의 핵심은 UI를 만드는쪽(서버, 백엔드 포함) 이 기존 Jetpack compose 코드를 그대로 작성한다는 점에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 DSL을 배울 필요도 없고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지 해야 할 JSON 스키마도 없으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 템플릿 언어를 익힐 필요도 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose로 작성할 수 있다면, RemoteCompose로 캡처할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Compose UI도 캡처 할 수 있는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에는 그리기(draw) 호출을 캡처하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이러한 draw 호출은 매우 정적이기 때문에 실용성이 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일반적으로는 직렬화와 원격 재생을 위해 설계된&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose와 1:1로 대응되는 Remote 전용 API(RemoteColumn, RemoteButton, RemoteText) 등을 사용하는것이 더 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Platform-Independent Rendering&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랫폼에 독립적인 렌더링이란,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 캡쳐된 문서를 네트워크를 통해 전송하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래의 Compose 코드가 없이도 어떤 안드로이드 기기에서도 렌더링 할 수 있다는 의미입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 기기는 당신의 Composable 함수, ViewModel, 비즈니스 로직을 전혀 알 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단지 문서 (bytearray)와 그것을 재생 할 플레이어만 있으면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764658551298&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// On the client or player side
RemoteDocumentPlayer(
    document = remoteDocument.document,
    documentWidth = windowInfo.containerSize.width,
    documentHeight = windowInfo.containerSize.height,
    onAction = { actionId, value -&amp;gt;
        // Handle user interactions
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특성들은 단순히 편의성을 높이기 위한것이아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 정의를 앱 배포 과정과 완전히 분리하기 위한 아키텍쳐적 제약 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 포맷은 단순한 정적 레이아웃만 담는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태, 애니메이션, 상호작용 까지 포함하여 UI 경험 전체를 완전하게 표현할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Comparing Approaches: Why Not JSON or WebView?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 깊이 들어가기전에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose가 왜 이런 방식을 선택했는지 다른 접근방식과 비교해볼 가치가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 기반의 서버 주도 UI(예 airbnb epoxy, shopify의 방식) 은 JSON 스키마를 정의하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 네이티브 UI 컴포넌트에 매핑해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 구조화된 콘텐츠를 다룰 때는 잘 작동하지만, 다음과 같은 것들에는 약합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 애니메이션과 화면 전환&amp;nbsp;&lt;/li&gt;
&lt;li&gt;커스텀 드로잉과 그래픽&lt;/li&gt;
&lt;li&gt;인라인 스타일링이 포함된 리치 텍스트&lt;/li&gt;
&lt;li&gt;그라데이션, 그림자 등 시각 효과&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹뷰는 매우 높은 유연성을 제공하지만 다음과 같은 문제가 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도 렌더링 프로세스 때문에 성능 오버헤드발생&lt;/li&gt;
&lt;li&gt;웹 스타일과 네이티브 디자인 간 일관되지 않은 룩앤필&amp;nbsp;&lt;/li&gt;
&lt;li&gt;WebView 하나가 매우 무겁기 때문에 메모리 압박&amp;nbsp;&lt;/li&gt;
&lt;li&gt;제스쳐 충돌 등 터치 처리 복잡성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReomteCompse는 이 두 방식 과 다른 세번째 길을 택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Compose가 실제로 수행하는 그리기 동작 자체를 캡처하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 곧 Compose에서 만들 수 있는 어떤 UI든&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 드로잉, 복잡한 애니메이션, Material Design 컴포넌트 까지 모두&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이티브 성능에서 원격으로 캡쳐하고 재생 가능하다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;The Document-Based Architecture: Creation and Playback&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReomteCompose의 아키텍쳐는 문서 생성 , 문서 재생 이라는 두 단계가 명확히 분리되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 분리를 이해하는 것이 RemoteCompose의 강력함을 이해하는 핵심입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Document Creation: Capturing UI as Data&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이문서 생성 단계에서는 Compose UI 코드를 직렬화된 문서로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 안드로이드 렌더링 파이프라잉네 가장 낮은 레벨인 Canvas 레벨에서 그리기 연산을 가로채 수행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764659148953&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable Content
        &amp;darr;
RemoteComposeCreationState (Tracks state and modifiers)
        &amp;darr;
CaptureComposeView (Virtual Display - no actual screen needed)
        &amp;darr;
RecordingCanvas (Intercepts every draw call)
        &amp;darr;
Operations (93+ operation types covering all drawing primitives)
        &amp;darr;
RemoteComposeBuffer (Efficient binary serialization)
        &amp;darr;
ByteArray (Network-ready, typically 10-100KB for complex UIs)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서를 생성하는 쪽에서는 완전한 Compose 통합 계층을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 평소처럼 일반적인 @Compose 함수를 작성하면 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크는 다음과 같은 모든 요소를 캡처합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이아웃 계층 구조,&lt;/li&gt;
&lt;li&gt;Modifier 정보&lt;/li&gt;
&lt;li&gt;텍스트 스타일&lt;/li&gt;
&lt;li&gt;이미지&lt;/li&gt;
&lt;li&gt;애니메이션&lt;/li&gt;
&lt;li&gt;터치 핸들러(상호작용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose가 특별한 이유는 캡쳐된 문서가 완전히 자급자족형이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서안에는 다음과 같은 모든 UI요소가 포함됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도형, 색상, 그라데이션, 그림자 같은 시각 요소&lt;/li&gt;
&lt;li&gt;문자열, 폰트, 크기, 스타일링이 포함된 텍스트&lt;/li&gt;
&lt;li&gt;이미지는 비트맵으로 직접 포함하거나 지연 로딩을 위한 URL로도 포함 가능&amp;nbsp;&lt;/li&gt;
&lt;li&gt;레이아웃 정보에는 크기, 위치, 패딩, 정렬 등이 포함됨&lt;/li&gt;
&lt;li&gt;인터렉션에서는 터치 영역, 클릭 핸들러, 명명된 액션이 정의됨&amp;nbsp;&lt;/li&gt;
&lt;li&gt;상태 변수는 런타임에 업데이트 가능&lt;/li&gt;
&lt;li&gt;애니메이션은 시간 기반 표현 으로 동작을 정의함&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이 문서 하나만으로 UI전체 경험을 재현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신자클라이언트는 당신의 코드 베이스에 접근할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서만 있으면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점은 다른 Server-driven UI방식과 근본적으로 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방식에서는 클라이언트가 스키마를 해석하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 만들어둔 UI 컴포넌트를 가지고 있어야 UI를 렌더링 할 수 있기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Document Playback: Rendering Without Compilation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이백 단계에서는 직렬화된 문서를 받아 화면에 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어는 문서 안에 포함된 연산들을 순서대로 처리하면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 연산을 Canvas에 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념은 마치 비디어 플레이어가 프레임을 디코딩하는 것과 유사하지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀을 디코딩하는 것이 아니라 그리기 명령어를 디코딩 한다는 점이 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose는 다양한 아키텍처 요구에 대응하기 위해 두 가지 렌더링 백엔드를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 최신 앱에서는 Compose 기반 플레이어를 사용할 것을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764659671087&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun DynamicScreen(document: CoreDocument) {
    RemoteDocumentPlayer(
        document = document,
        documentWidth = screenWidth,
        documentHeight = screenHeight,
        modifier = Modifier.fillMaxSize(),
        onNamedAction = { name, value, stateUpdater -&amp;gt;
            // Handle named actions from the document
            when (name) {
                &quot;addToCart&quot; -&amp;gt; cartManager.addItem(value)
                &quot;navigate&quot; -&amp;gt; navController.navigate(value)
                &quot;trackEvent&quot; -&amp;gt; analytics.logEvent(value)
            }
        },
        bitmapLoader = rememberBitmapLoader()  // For lazy image loading
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈 기반 플레이어는 기존 Compose UI와 자연스럽게 통합됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 하나의 Composable 함수로 제공되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Compose 트리 어디에든 배치할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Modifier를 적용하거나 다른 Composable처럼 애니메이션을 줄 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 View 기반 UI 계층과의 호환성을 위해 View 기반 플레이어도 함께 제공됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764659811062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LegacyActivity : AppCompatActivity() {
    private lateinit var player: RemoteComposePlayer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        player = RemoteComposePlayer(this)
        setContentView(player)

        // Load document from network
        lifecycleScope.launch {
            val bytes = api.fetchDocument(&quot;home-screen&quot;)
            player.setDocument(bytes)
        }

        player.onNamedAction { name, value, stateUpdater -&amp;gt;
            // Handle actions
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 플레이어는 동일한 렌더링 품질을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택은 단지 앱의 아키텍처에 따라 달라질 뿐입니다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 완전히 Compose 기반이라면 -&amp;gt; Composable Player 사용&lt;/li&gt;
&lt;li&gt;View 기반에서 Compose로 마이그레이션 중이거나, View 계층안에 RemoteCompose를 삽입해야 한다면 -&amp;gt; View 기반 PLayer 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;The Operation Model: A Comprehensive Drawing Vocabulary&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose의 강점은 포괄적인 Operation Model에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크는 UI 렌더링의 모든 측면을 표현하기 위해 93개 이상의 개별 그리기 명령을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 숫자는 임의로 정해진 것이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canvas 기반 렌더링에서 필요한 모든 그리기 동작을 표현하기 위한 완전한 어휘를 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Why Operations Matter&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 Server driven UI 방식에서는 서버가 텍스트가 Submit인 버튼을 랜더링해라 같은 고수준 컴포넌트 설명을 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 클라이언트는 이 설명을 해석하고, 이를 네이티브 UI 컴포넌트로 매핑해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 서버와 클라이언트가 모두 버튼이 무엇인지, 어떻게 동작해야 하는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확하게 합의하고 있어야 하므로 강한 결합이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RemoteCompose는 훨씬 더 낮은 레벨에서 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 렌더링하라 가 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 좌표에 이 색상에 둥근 사각형을 그려라, 그리고 그 위치에 이 폰트로 submit이라는 텍스트를 그려라. 와 같은 실제 그리기 명령을 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 버튼이라는 개념을 알 필요가 없고, 그저 명령어들을 순서대로 Canvas에 실행하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 저수준 접근 방식은 큰 의미를 갖습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스키마 동기화 필요 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버와 클라이언트가 button, card와 같은 컴포넌트 정의에 합의 할 필요없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;완벽한 시각 충실도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compose로 가능한 모든 시각 효과는 그대로 캡처 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앞으로의 디자인 변화에도 호환&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 디자인이 나와도 클라이언트는 그저 새로운 drawing operation을 실행하면됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;커스텀 컴포넌트도 자동 지원
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도 등록 없이도 Canvas 기반으로 그려지는 UI라면 모두 동작함&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/119</guid>
      <comments>https://c004245.tistory.com/119#entry119comment</comments>
      <pubDate>Tue, 2 Dec 2025 16:24:26 +0900</pubDate>
    </item>
    <item>
      <title>Bosch 안드로이드 개발자 인터뷰 경험</title>
      <link>https://c004245.tistory.com/118</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@avula.koti.realpage/bosch-android-developer-interview-experience-d6d15cfe207c&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@avula.koti.realpage/bosch-android-developer-interview-experience-d6d15cfe207c&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들이 물어본 Kotlin과 Compose 질문들,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 한 시니어 개발자가 실제로 어떻게 답변했는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 달, 제 동료 아라빈드가 보쉬에서 시니어 안드로이드 개발자 (코틀린, 컴포즈, 아키텍처) 인터뷰를 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 당신이 보쉬인터뷰를 경험해본적이 있다면 이미 그 분위기를 알고 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체계적이고, 분석적이며, 진짜 엔지니어링 깊이에 집중하는 면접스타일&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드는 8년이상의 안드로이드 개발경험을 가지고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄탄한 코틀린 기초와 견고한 아키텍쳐 지식을 갖추고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그는 자신있게 면접장에 들어갔지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나와서 이렇게말했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;최근 몇 년간 봤던 안드로이드 면접 중 가장 기술적인 인터뷰 였습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 그가 어떤 질문을 받았는지, 왜 그 질문을 했는지, 그가 어떻게 답변했는지까지 라운드별로 전체 정리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1라운드: 기술 심층 면접 - 코틀린, 동시성, 컴포즈&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라운드는 완전히 기술 중심으로 진행되었고, 70분 동안 이어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관은 보쉬의 시니어 안드로이드 엔지니어였으며, 코틀린 내부 동작 원리, 코루틴, 성능, 그리고 컴포즈 기본기에 강하게 초점을 맞추고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 코틀린: 왜 보쉬는 자바보다 코틀린을 더 선호하나요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드의 대답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 null-safety, 코루틴, 더 깔끔한 데이터클래스, 상태 모델링을 위한 sealed class를 제공하고, 전체적으로 코드의 예측 가능성을 높여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보일러 플레이트가 줄어들기 때문에 버그 도 감소합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보쉬의 자동차 산업용처럼 규모가 큰 앱에서는 코드의 신뢰성이 매우중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보쉬는 예측 가능성, 유지보수성을 중시하기 때문에 이 답변을 좋게 평가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 코루틴 vs 스레드 각각 언제 선택해야하나요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드의 답변&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;실제 병렬성이 필요하거나, 로우 레벨 네이티브 코드화 상호작용 할때에는 쓰레드를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 , 앱 내부의 구조화된 동시성이 필요한 작업&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 네트워크 요청, 데이터 베이스 처리, 스트리밍, UI 업데이트, 백그라운드 작업 에는 코루틴을 사용합니다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그는 이어서 이렇게 설명했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;코루틴은 가벼운 스레드가 아닙니다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스패쳐 위에서 동작하는 동시성 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴의 진짜 힘은 취소와 구조화된동시성입니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 답변이 면접관에게 강한 인상을 주었습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. CoroutineScope 잘못 사용 - 'GlobalScope에서 코루틴을 실행하면 어떤일이 발생하나요?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그의 답변&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GlobalScope로 실행된 코루틴을 직접 취소하지 않는 한 계속 살아남습니다.&lt;/li&gt;
&lt;li&gt;이로 인해 메모리 누수가 발생할 수 있습니다&lt;/li&gt;
&lt;li&gt;그리고 구조화된 동시성 원칙을 깨뜨립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그는 실제 사례도 언급했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Analytics 모듈에서 GlobalScope를 잘못 사용해 절대로 종료되지 않는 job이 생성된 것을 본적이 있습니다. 이를 applicationScope와 올바른 supervision을 사용하도록 수정했습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관들은 이런 실무 경험 기반 답변을 높게 평가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Compose - 리컴포즈션은 실제로 어떻게 동작하나요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드는 이렇게 명확하게 설명했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Composable은 모두 상태가 변경되면 다시 실행될 수 있는 함수입니다&lt;/li&gt;
&lt;li&gt;Compose는 컴포저블 내부에서 어떤 state 를 읽는지 추적합니다.&lt;/li&gt;
&lt;li&gt;그 state가 변경되면, 컴포즈는 그 state에 의존하는 컴포저블만 다시 실행합니다.&lt;/li&gt;
&lt;li&gt;모든 UI가 리컴포지션 되는것이 아니라, 변경한 state에 의존하는 노드만 리컴포지션됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그가 이렇게 말했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키는 정말 중요합니다 LazyColumn에서 키를 잘못사용하면 전체 리컴포지션이나 리스트 깜빡임이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라운드는 난이도가 높았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈를 그냥 쓸줄아는지가 아니라 정확히 이해하고 있는지를 확인하려는 질문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. State Hosting: 왜 컴포즈는 state hosting을 권장하나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드의 설명&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI를 stateless하게 만들기 때문에 테스트하기 쉬워집니다.&lt;/li&gt;
&lt;li&gt;부모 컴포넌트가 UI 상태를 제어하고 관리할 수 있게 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;화면 간에 상태 불일치를 방지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그는 실제로 한 예시를 보여줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈 내부에서 잘못 관리된 상태가 어떻게 문제를 만드는지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 상태를 hoisting 하여 해결한 사례&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 본 면접관이 말했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;좋습니다. 대부분에 지원자들이 컴포즈에서 상태 때문에 어려움을 겪습니다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2라운드: 시스템 디자인 + 안드로이드 아키텍쳐 (난이도 높은 라운드)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라운드는 정말 보쉬 스타일이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깊고, 체계적이며, 장기적인 유지보수성을 중심으로 진행됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 보쉬 차량 관리 (fleet management) 앱을 위한 확장 가능한 아키텍처를 설계하세요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 라운드의 핵심 질문이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관이 원하는 것&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티모듈 아키텍쳐&lt;/li&gt;
&lt;li&gt;오프라인 우선 기능&lt;/li&gt;
&lt;li&gt;Domain-Data-UI의 깨끗한 레이어링&lt;/li&gt;
&lt;li&gt;명확한 경계&lt;/li&gt;
&lt;li&gt;컴포즈 기반 UI 구조&lt;/li&gt;
&lt;li&gt;백그라운드 동기화 아키텍쳐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라반드의 제안&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아키텍쳐 레이어&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 -&amp;gt; Usecase, 비즈니스 로직&lt;/li&gt;
&lt;li&gt;데이터 -&amp;gt; Repository, caching, network source&lt;/li&gt;
&lt;li&gt;UI -&amp;gt; 컴포즈 스크린 + ViewModel&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모듈 구성&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크&lt;/li&gt;
&lt;li&gt;데이터베이스&lt;/li&gt;
&lt;li&gt;트래킹&lt;/li&gt;
&lt;li&gt;map-core&lt;/li&gt;
&lt;li&gt;vehicle-profile&lt;/li&gt;
&lt;li&gt;app-ui&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티모듈을 사용하는 이유&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빌드 시간 단축&lt;/li&gt;
&lt;li&gt;재사용 가능한 컴포넌트(보쉬에는 내부에 여러 앱이 있음)&lt;/li&gt;
&lt;li&gt;신규 개발자 온보딩 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Offline-first 전략&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Room + Flow -&amp;gt;로컬 데이터 변경을 즉시 UI에 공급&lt;/li&gt;
&lt;li&gt;WorkManager -&amp;gt; 실패 시 재시도 &amp;amp; 백오프 로직&lt;/li&gt;
&lt;li&gt;도메인 로직 기반 캐시 무효화 규칙 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;면접관이 인상깊게 본 핵심 포인트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10초마다 차량 상태를 동기화 한다고 해서 UI가 10초마다 리컴포지션 될 필요는 없습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기화는 Repository 층에서 격리 시키고, UI에 필요한 변화만 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보쉬는 '실전적이고 깊은 아키텍처 사고방식' 을 매우 좋아합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 컴포즈를 많이 사용하는 화면에서 성능을 어떻게 보장하나요 ?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드가 언급한 내용&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;remember 올바르게 사용하기&lt;/li&gt;
&lt;li&gt;무거운 로직은 컴포저블 내부가 아닌 외부로 이동시키기&lt;/li&gt;
&lt;li&gt;파생 계산에는 derivedStateOf 사용&lt;/li&gt;
&lt;li&gt;상태 변화를 관찰 할 때는 SnapShotFlow 사용&amp;nbsp;&lt;/li&gt;
&lt;li&gt;LazyColumn에서는 Key를 올바르게 지정하기&lt;/li&gt;
&lt;li&gt;불필요한 리컴포지션을 막기 위해 Stable 클래스를 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관의 평가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;좋습니다, 많은 시니어 개발자도 이 질문에 제대로 답 못합니다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. UI 상태를 어떤 디자인 패턴으로 관리하나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그의 답변&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVI 패턴 사용&lt;/li&gt;
&lt;li&gt;UI State는 sealed class 로 표현&lt;/li&gt;
&lt;li&gt;Reactive UI를 위하 Flow 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그는 Compose에 MVI가 적합한 이유를 이렇게 설명했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Compose는 단방향 데이터 흐름을 선호함으로, MVI가 자연스럽게 잘 맞습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3라운드 코딩 과제&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보쉬는 90분짜리 코딩 챌린지를 내줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 리트코드 문제 트릭이 아니라 실제 앱 구조 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과제 내용:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API에서 장비 목록을 받아오고, Compose 리스트로 보여주며, 즐겨 찾기 기능을 제공하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즐겨찾기 상태를로컬에 저장하는 작은 앱을 구현하세요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드의 풀이 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 통신 Ktor&lt;/li&gt;
&lt;li&gt;로컬 저장 : Room&lt;/li&gt;
&lt;li&gt;아키텍처: MVVM과 Repository&lt;/li&gt;
&lt;li&gt;리스트 렌더링: LazyColumn과 키 설정&lt;/li&gt;
&lt;li&gt;UI 상태 유지: rememberSaveable&lt;/li&gt;
&lt;li&gt;로컬 변경 감지: Flow 기반 업데이트&lt;/li&gt;
&lt;li&gt;sealed class로 상태 예외 처리&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;며접관들이 특히 집중해서 본 부분&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클린 코드&lt;/li&gt;
&lt;li&gt;관심사 분리 soc)&lt;/li&gt;
&lt;li&gt;테스트 가능 구조&lt;/li&gt;
&lt;li&gt;컴포즈 성능 관리&amp;nbsp;&lt;/li&gt;
&lt;li&gt;거대 클래스 없이 역할 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그는 95% 를 구현해내며 매우 강한 퍼포먼스를 보여줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4라운드: 매니저 면접 + 문화 적합성&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라운드는 예상보다 솔직하고, 실제 업무에서 마주치는 문제들에 초점을 두었습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 아키텍트와 의견 충돌이 있을 때 어떻게 대처하나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그의 답변:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;데이터에 집중합니다. 아키텍쳐 결정을 증명할 때에는 앱 실행 시간, 메모리 사용량,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리컴포지션 발생 횟수, 빌드 시간 영향 같은 지표로 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접관들은 극 감정적으로 대처하지 않고 엔지니어링 기반 사고로 해결한다는 점을 높게 평가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 주니어 개발자들을 어떻게 멘토링하나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판단하려는 마음이 아니라, 가르칠려는 마음으로 코드리뷰를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 주니어가 기능을 처음부터 끝까지 직접 맡아보게하여 자신감을 쌓도록 돕습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 앱 모듈을 재 설계했던 경험을 말해보세요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그는 실제 사례를 설명했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 거대한 모듈을 5개의 작은 모듈로 나누어 빌드 시간 40%을 감소 시킨경험&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보쉬는 모듈화 철학을 중요하게 여기기 때문에 이부분을 높게 평가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 HR 라운드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 간단했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 항목만 확인했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커뮤니케이션&lt;/li&gt;
&lt;li&gt;퇴사 시 인수기간&lt;/li&gt;
&lt;li&gt;연봉 기대치&lt;/li&gt;
&lt;li&gt;왜 보쉬인가&lt;/li&gt;
&lt;li&gt;장기적인 안정성에 대한 생각&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아라빈드는 보쉬에 안정성, 엔지니어링 중심 문화가 자신과 잘 맞는다고 강조했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 결과&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일주일 후 그는 합격 제안을 받았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그에게 전달된 피드백&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탄탄한 Kotlin 기초&amp;nbsp;&lt;/li&gt;
&lt;li&gt;매우 강한 컴포즈 이해도&lt;/li&gt;
&lt;li&gt;뛰어난 아키텍처 사고&lt;/li&gt;
&lt;li&gt;클린 코드 + 모듈러 디자인&lt;/li&gt;
&lt;li&gt;조직 문화와의 높은 적합성&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/118</guid>
      <comments>https://c004245.tistory.com/118#entry118comment</comments>
      <pubDate>Mon, 1 Dec 2025 15:48:29 +0900</pubDate>
    </item>
    <item>
      <title>불필요한 Recompositions 줄이기: Compose를 위한 실용적인 최적화 기법 3가지</title>
      <link>https://c004245.tistory.com/117</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://proandroiddev.com/recomposition-all-in-one-5bd1f4aedf8b&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://proandroiddev.com/recomposition-all-in-one-5bd1f4aedf8b&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764050544264&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Reducing Unnecessary Recompositions: 3 Practical Optimization Techniques for Jetpack Compose&quot; data-og-description=&quot;This guide explains how to reliably eliminate unnecessary recompositions.&quot; data-og-host=&quot;proandroiddev.com&quot; data-og-source-url=&quot;https://proandroiddev.com/recomposition-all-in-one-5bd1f4aedf8b&quot; data-og-url=&quot;https://proandroiddev.com/recomposition-all-in-one-5bd1f4aedf8b&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nxAgk/hyZNyptBaK/8yUaZuww0xHZxbgXKUtctK/img.png?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800&quot;&gt;&lt;a href=&quot;https://proandroiddev.com/recomposition-all-in-one-5bd1f4aedf8b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://proandroiddev.com/recomposition-all-in-one-5bd1f4aedf8b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nxAgk/hyZNyptBaK/8yUaZuww0xHZxbgXKUtctK/img.png?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Reducing Unnecessary Recompositions: 3 Practical Optimization Techniques for Jetpack Compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This guide explains how to reliably eliminate unnecessary recompositions.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;proandroiddev.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈는 선언적 UI구조기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변화 -&amp;gt; 재구성 -&amp;gt; UI 업데이트 흐름을 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 상태를 잘못 다루면 불필요한 Recompositions이 발생하게 되고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 성능 저하로 직결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 잘못된 예제와 올바른 예제를 비교하며 Recomposition을 줄이는 기법을 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태는 가능한 좁은 범위에서만 읽기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 예제&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Composable
private fun Parent() {
    var count by remember { mutableIntStateOf(0) }
    // Parent reads state directly &amp;rarr; Parent undergoes full recomposition
    ChildA(count = count)
}

@Composable
private fun ChildA(count: Int) {
    Text(&quot;Count: $count&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Compose 에서는 상태를 선언할 때 by를 자주사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;by 키워드는 프로퍼티 위임을 의미하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 State 객체에 value 접근을 위임합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 변수를 단순히 참조하는 것만으로도 내부적으로 state.value를 읽게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose는 이러한 value 읽기를 추적하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 변경 되면 그 값을 읽은 컴포저블만 자동으로 재구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 부모 컴포저블이 상태를 직접 사용하지않으면서 자식에게 내려보내는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 해당 상태를 읽지 않도록 구조를 잡아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 부모 내부에 모든 컴포저블이 불필요한 재구성의 영향을 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 예제&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun Parent() {
    val count = remember { mutableStateOf(0) }

    // Pass a lambda that reads the state
    ChildB(count = { count.value })
    // Pass without reading the state
    ChildC(count = count)
}

@Composable
fun ChildB(count: () -&amp;gt; Int) {
    Text(&quot;Count: ${count()}&quot;) // Here, the state is actually read.
}

@Composable
fun ChildC(count: State&amp;lt;Int&amp;gt;) {
    Text(&quot;Count: ${count.value}&quot;) // Here, the state is actually read.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 문제를 피하는 방법은 두 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째는 상태를 직접 읽지 않고 State&amp;lt;T&amp;gt; 객체 자체를 자식에게 전달하는 방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 state.value를 호출하지 않는다면 부모는 상태를 읽은 것으로 추적되지 않으며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오직 자식 컴포저블이 필요할 때만 state.value를 읽고 재구성이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, 상태 접근을 람다로 전달하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다는 값이 아니라 코드 이므로, 람다 내부에서 state.value를 일는 동작은 실제 호출 시점에만 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 상태 읽기 타이밍이 호출시점으로 지연되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 필요한 위치에서만 읽게되는 장점이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 이러한 방식이 가능한가요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈의 상태는 SnapShot System에 의해 관리되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 상태 읽기와 쓰기 연산은 스냅샷 단위로 추적됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SnapShot 시스템은 상태 변화가 발생하면 이를 감지하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 상태를 읽었던 컴포저블 (Composable 함수의 실행 범위)를 재실행해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 스냅샷은 상태 변화로 인해 발생하는 재구성 트리거를 추적하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 추적이 가능하려면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 읽힐 때 해당 read 연산이 전역 스냅샷에 등록되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 Compose는 &quot;어떤 컴포저블이 어떤 상태를 읽었는지&quot; 기록할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 그 상태가 변경되면 정확한 범위만 재 구성할 수 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// Inside the mutableStateOf implementation
override var value: T
        get() = next.readable(this).value
        set(value) =
            next.withCurrent {
                if (!policy.equivalent(it.value, value)) {
                    next.overwritable(this, it) { this.value = value }
                }
            }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 코드에서 value라는 프로퍼티를 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 value가 우리가 사용해온 state.value에 해당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 getter를 조금 더 자세히 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;public fun &amp;lt;T : StateRecord&amp;gt; T.readable(state: StateObject): T {
    val snapshot = Snapshot.current
    // This is where the snapshot system indicates that the state has been read.
    snapshot.readObserver?.invoke(state)
    return readable(this, snapshot.snapshotId, snapshot.invalid)
        ?: sync {
            val syncSnapshot = Snapshot.current
            @Suppress(&quot;UNCHECKED_CAST&quot;)
            readable(state.firstStateRecord as T, syncSnapshot.snapshotId, syncSnapshot.invalid)
                ?: readError()
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;state.value가 호출되면 스냅샷의 readObserver가 실행되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 readObserver는 현재 어떤 위치에서 이 상태가 읽히고 있는지&quot; 를 스냅샷 시스템에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포저는 &quot;어떤 컴포저블이 어떤 상태를 읽었는지&quot;를 기록할 수 있고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 상태가 변경 될 때 그 부분만 정확히 재 구성 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 불필요한 재구성을 방지하려면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태는 정말 필요한 시점에서만 읽어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 단순히 상태를 읽는 위치만 조정하는것만으로도 재구성 범위를 효과적으로 최소화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빠르게 변하는 State를 다루는 방법&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 예&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;val showTopBar = lazyListState.firstVisibleItemIndex &amp;gt; 20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;우리는 때때로 한 상태로부터 다른 상태를 파생 해 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 LazyListState처럼 사용되는 상태가 매우 빠르게 변하는 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재 구성이 매우 자주 발생할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 예&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;// case of derivedStateOf
val showTopBar by remember {
    derivedStateOf { lazyListState.firstVisibleItemIndex &amp;gt; 20 }
}

// case of snapshotFlow
var showTopBar by remember { mutableStateOf(false) }

LaunchedEffect(lazyListState) {
    snapshotFlow { lazyListState.firstVisibleItemIndex }
        .map { it &amp;gt; 20 }
        .distinctUntilChanged()
        .collect { visible -&amp;gt; showTopBar = visible }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;빠르게 변하는 상태를 다룰 때에는 두 가지 방법을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 derivedStateOf를 사용하는 것이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 snapShotFlow를 사용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;derivedStateOf를 사용하면 내부에 계산 블록을 정의할 수 있으며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 블록에서 사용되는 값이 변경 될 때 해당 상태가 자동으로 갱신됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 상태 변화에 따라 필요한 계산 결과가 자동으로 생성되는 방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 snapshotFlow는 상태를 Flow로 변환 해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 원하는 방식으로 처리할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산 로직을 직접 정의 할 수 있기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변화에 따라 다양한 처리 단계를 유연하게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 상황에서 어떤 방식을 사용해야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 계산을 통해 파생된 상태가 필요하다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;derivedStateOf를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 상태 변화에 따라 비동기 처리가 필요하거나,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스트 같은 추가적인 효과를 발생시켜야 한다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snapShotFlow를 사용하는 것이 더 적합합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 파생 상태만 필요하다면 derivedStateOf를,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변화로 인해 부수 효과 까지 처리해야 한다면 snapShotFlow를 선택하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태 읽기를 가능한 늦추는 방법&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 예&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;Text(
    modifier = Modifier
        .align(Alignment.Center)
        .offset(y = 60.dp * animatedValue)  // The state is read at the time of composition phase.
        .scale(animatedValue),              // The state is read at the time of composition phase.
    text = &quot;Animate&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 컴포저블의 위치, 크기, 투명도 등을 동적으로 변경하기 위해 상태와 Modifier를 자주 사용합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 빠르게 변하는 상태를 modifier를 통해 컴포지션 단계에서 읽게되면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 상태가 급격히 변할 때마다 불필요한 재구성이 자주 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 예&lt;/p&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #f9f9f9; color: #242424; text-align: start;&quot;&gt;&lt;code&gt;Text(
    modifier = modifier
        .offset { IntOffset(0, (60 * animatedValue).toInt()) } // read state on layout phase
        .drawWithContent { // read state on draw phase
              scale(scale = animatedValue) {
                  this@drawWithContent.drawContent()
              }
         },
    text = &quot;Animate&quot;
)

// or

Text(
    modifier = modifier
        .offset { IntOffset(0, (60 * animatedValue).toInt()) } // read state on layoutphase
        .graphicsLayer { // read state on draw phase
            scaleX = animatedValue
            scaleY = animatedValue
        },
   text = &quot;Animate&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;레이아웃 단계에서 상태를 읽는 람다 형태의 Modifier,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 drawWithContent, graphicsLayout 처럼 드로우 단계에서 상태를 읽는 방법들을 활용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 상태를 Composition 이후 단계에서 읽어야 할까 ?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈에서 UI가 화면에 렌더링 되는 과정은 Composition, Layout, Draw의 세 단계로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 Composition 단계에서 상태가 읽히고 그 상태가 변하면 즉시 재구성이 발생한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포지션 단계는 상태를 기반으로 UI 트리를 구성하는 단계입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 트리가 한번 구성되면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Layout 단계에서 각 컴포넌트의 배치를 계산하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Draw 단계에서 화면에 실제로 렌더링 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Composition 단계에서 상태를 읽으면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 상태가 바뀔 때마다 UI 트리를 다시 만들어야 하므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재구성이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Layout 또는 Draw 단계에서 상태를 읽으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치, 크기, 투명도 같은 UI 속성만 갱신 할 수 있어 재구성을 발생시키지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마무리하며&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재 구성이 생각만큼 큰 성능 저하를 유발하는 것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 재구성에 신경을 쓰면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향후 발생 할 수 있는 성능 저하의 잠재적 원인을 하나 이상 제거할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>영어 데일리</category>
      <author>현욱 정리장</author>
      <guid isPermaLink="true">https://c004245.tistory.com/117</guid>
      <comments>https://c004245.tistory.com/117#entry117comment</comments>
      <pubDate>Tue, 25 Nov 2025 15:53:27 +0900</pubDate>
    </item>
  </channel>
</rss>