<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>해로, HAERO, 海路</title>
        <link>https://velog.io/</link>
        <description>어려울수록 기본에 미치고 열광하라</description>
        <lastBuildDate>Sun, 11 Dec 2022 12:04:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>해로, HAERO, 海路</title>
            <url>https://images.velog.io/images/haero_kim/profile/fa67c66b-a190-404c-9c1b-10549038868f/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 해로, HAERO, 海路. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/haero_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Dagger] Activity 에서 필드 주입은 언제 일어나야 할까?]]></title>
            <link>https://velog.io/@haero_kim/Dagger-Activity-%EC%97%90%EC%84%9C-%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@haero_kim/Dagger-Activity-%EC%97%90%EC%84%9C-%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sun, 11 Dec 2022 12:04:47 GMT</pubDate>
            <description><![CDATA[<h3 id="본문에-앞서-갑분-자아성찰-타임">본문에 앞서 갑분 자아성찰 타임</h3>
<blockquote>
<h4 id="세상에-마지막-글이-4월이다-🙁"><strong>세상에.. 마지막 글이 4월이다 🙁</strong></h4>
<p>회사 일이 바빠서 라는 핑계는 댈 수가 없다.. 충분히 여기저기 놀러다니고 기타 치고 놀았으면서
과연 블로그 글 쓸 시간이 없었을까?? 이건 100번 반성해야 한다.. 😞
제발 정신줄 꽉 잡고 블로그 활동 좀 하자!! 초심을 잃지 말자 ㅠㅠ</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/d439fe92-7689-48db-842b-c855ee088939/image.png" alt=""></p>
<blockquote>
<h4 id="🔥-해당-포스팅은-dagger-에-대한-기본적인-이해를-전제로-합니다">🔥 해당 포스팅은 Dagger 에 대한 기본적인 이해를 전제로 합니다</h4>
<p>아래 자료들에 의거하여 작성된 포스팅입니다.
<a href="https://developer.android.com/training/dependency-injection/dagger-android?hl=ko">https://developer.android.com/training/dependency-injection/dagger-android?hl=ko</a>
<a href="https://dagger.dev/dev-guide/android.html">https://dagger.dev/dev-guide/android.html</a></p>
</blockquote>
<h2 id="android-에서-dagger-를-사용할-때">Android 에서 Dagger 를 사용할 때</h2>
<p>Dagger 에서는 여러 DI 기법 중 <strong>생성자 주입(<code>Constructor Injection</code>) 을 가장 권장</strong>하고 있다. 생성자 주입을 사용한다면, 컴파일러 차원에서 의존성 주입 이전에 객체가 참조되는 등의 상황을 방지해주기 때문이다. (즉, <code>NullPointerException</code> 이 발생하는 상황을 막아준다)</p>
<blockquote>
<h4 id="dagger-문서-中-링크">Dagger 문서 中 (<a href="https://dagger.dev/dev-guide/android.html">링크</a>)</h4>
<p><em>Constructor injection is preferred whenever possible because javac will ensure that no field is referenced before it has been set, which helps avoid NullPointerExceptions.</em></p>
</blockquote>
<p><strong>그런데</strong> 일부 상황에선 어쩔 수 없이 <code>Constructor Injection</code> 을 사용하지 못할 때가 있다. <strong>Android 컴포넌트(Activity, Fragment 등) 를 사용</strong>하는 경우가 그 예다. <strong><code>Fragment</code> 의 생성자는 비워둬야 하는 것이 원칙이고, Activity 의 생성자는 건드릴 방법이 전혀 없기 때문이다.</strong></p>
<h3 id="field-injection-지원">Field Injection 지원</h3>
<p>Dagger 에서는 이러한 상황에서 <strong>필드 주입 (<code>Field Injection</code>) 을 사용할 수 있도록 <code>inject()</code> 메소드</strong>를 제공해준다. 예를 들어 아래와 같은 <code>Component</code> 와 <code>Activity</code>, <code>ViewModel</code> 이 있다고 하자. 필드 주입을 위한 준비 과정은 주제에 벗어나므로 생략한다.</p>
<pre><code class="language-kotlin">class MyApplication: Application() {
    val appComponent = DaggerApplicationComponent.create()
}

@Component
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }</code></pre>
<p>이 경우 <code>Activity</code> 는 Dagger 에서 제공해주는 다음과 같은 코드를 추가함으로써 필드 주입을 받을 수 있다.</p>
<pre><code class="language-kotlin">(applicationContext as MyApplication).appComponent.inject(this)</code></pre>
<h3 id="그럼-field-injection-은-oncreate-시점에-일어나야겠군">그럼 Field Injection 은 onCreate() 시점에 일어나야겠군!</h3>
<p>필드 주입은 클래스가 인스턴스화되고 난 뒤 최대한 빠른 시점에 일어나야 한다. 우선 <strong><code>Activity</code></strong> 의 상황에서는, 우리가 흔히 알고 있는 Lifecycle Callback 인 <strong><code>onCreate()</code></strong> 시점에서 필드 주입이 일어나야 맞다.</p>
<p>따라서 아래와 같이 필드 주입을 실행할 수 있다.</p>
<pre><code class="language-kotlin">class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (applicationContext as MyApplication).appComponent.inject(this)
    }
}</code></pre>
<p><strong>그러나 위와 같이 코드를 작성할 경우 문제가 발생한다.</strong> 얼핏 보기에는 잘못된 부분이 없지만, <code>super.onCreate()</code> 에서 일어나는 일에 의거하면 문제가 발생하는 것이 맞다.</p>
<p><code>Activity</code> 에서 Configuration Change 등이 일어날 때 화면을 구성 및 복구하기 위해 <code>super.onCreate(savedInstanceState)</code> 가 호출되면, <strong>해당 액티비티에 속해있는 <code>Fragment</code> 들을 차례대로 Attach</strong> 하게 된다. 그런데 해당 <code>Fragment</code> 들은 자신이 속한 <code>Activity</code> 에 접근할 수 있기 때문에, <code>Fragment</code> 가 Attach 되기 전에 상위 <code>Activity</code> 가 먼저 완성되어 있어야 한다. <strong>즉, DI 가 모두 이루어지고 난 뒤 <code>super.onCreate()</code> 가 호출되어야 한다</strong>. 따라서 코드는 다음과 같이 작성되어야 한다.</p>
<pre><code class="language-kotlin">class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // 반드시 super.onCreate() 보다 먼저 불려야 한다!
        (applicationContext as MyApplication).appComponent.inject(this)
        super.onCreate(savedInstanceState)
    }
}</code></pre>
<h3 id="추가로-fragment-에선-어느-시점에-필드-주입이-일어나야-할까">추가로, <code>Fragment</code> 에선 어느 시점에 필드 주입이 일어나야 할까?</h3>
<p>우리가 알고 있는 <code>Fragment</code> 의 생명주기에 따르면, <strong><code>onAttach()</code> ** 시점에 필드 주입이 일어나야 맞다. 인스턴스화 이후 최대한 빠르게 필드 주입이 일어나야하기 때문이다. 동작 시간 상 <code>onAttach()</code> 와 그리 차이가 나지 않는 **<code>onCreate()</code></strong> 도 고려해볼 수 있겠지만, <code>Fragment</code> 의 <code>onCreate()</code> 는 <code>Fragment</code> 가 Attach 되고 난 뒤에 두 번 다시 호출되지 않는다. </p>
<p>즉, <code>Fragment</code> 가 <strong>Re-attach</strong> 되는 상황에서 DI 가 정상동작 안 할 수 있기 때문에 <strong><code>onAttach()</code></strong> 시점에 필드 주입이 일어나야 한다.</p>
<blockquote>
<p>아래 사진을 보면, <strong>Re-attach</strong> 상황에서 onCreate() 를 스킵하는 것을 확인할 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/864fcfdd-59f6-4875-b241-9508e666fbc0/image.png" alt=""></p>
<p>참고로 <code>Fragment</code> 에서는 <code>Activity</code> 상황과 다르게 화면 복원과 관련하여 더이상 문제될 것이 없기 때문에 <code>super.onAttach()</code> 전후 상관없이 필드 주입을 할 수 있다.</p>
<pre><code class="language-kotlin">
override fun onAttach(context: Context) {
    super.onAttach(context)
    (applicationContext as MyApplication).appComponent.inject(this)
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[신입의 카카오페이 좌충우돌 수습 탈출기 (feat. 1Q 회고)]]></title>
            <link>https://velog.io/@haero_kim/%EC%8B%A0%EC%9E%85%EC%9D%98-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%8E%98%EC%9D%B4-%EC%88%98%EC%8A%B5-%ED%83%88%EC%B6%9C%EA%B8%B0-feat.-1Q-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@haero_kim/%EC%8B%A0%EC%9E%85%EC%9D%98-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%8E%98%EC%9D%B4-%EC%88%98%EC%8A%B5-%ED%83%88%EC%B6%9C%EA%B8%B0-feat.-1Q-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 15 Apr 2022 15:22:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/haero_kim/post/550a0e98-fec7-4040-bf5e-480204ce2607/image.jpg" alt=""></p>
<blockquote>
<p>벌써 2022년 1분기가 끝나고 <strong>4월 중순</strong>이 다가왔다. 패딩을 입어도 추웠던 날씨가 <strong>언제 그랬냐는 듯 벚꽃잎을 감싸고 있다.</strong> (라고 글 써놓고 며칠 지나니 이미 벚꽃잎들이 다 떨어졌다)</p>
</blockquote>
<p>필자는 올해 1월에 카카오페이에 입사하여 <strong>1분기동안 수습기간</strong>을 거쳤다. 누구나 마찬가지겠지만, <em>&#39;만약 수습기간에 짤리면 어떡하지..&#39;</em> 라는 생각은 도저히 떨쳐낼 수 없었다. 그런 최악의 상황을 면하기 위해 닿는 대로 최대한 노력했을 뿐이다.</p>
<p>결론부터 말하자면 다행히도 면수습을 할 수 있었다. 계속 걱정이 앞서긴 했지만 꾸준히 팀원들이 이끌어주시고, 온보딩 중간중간에 팀장님께서 1대1 미팅을 통해 격려해주시고 다독여주셔서 더욱이 안심하고 본분을 다 할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/9b8c2d27-8ff1-40e6-9014-b6d42ad88be1/image.JPEG" alt=""></p>
<br>

<h2 id="on-boarding-과정">On-Boarding 과정</h2>
<p>수습 3개월 동안 카카오페이의 온보딩 프로세스는 다음과 같았다. (필자가 속한 팀 기준)</p>
<blockquote>
<p>코틀린 및 안드로이드 스터디 ➡️ 입사 과제 리팩토링 ➡️ 담당 업무 지정 및 인수인계</p>
</blockquote>
<h3 id="1-코틀린-및-안드로이드-스터디">1. 코틀린 및 안드로이드 스터디</h3>
<p>들어가고 첫 한 달 동안은 <strong>코틀린 및 안드로이드</strong>에 대해 공부했다. 기존에 공부해왔던 것과 별개로, <strong>팀에서 중요시하는 키워드 및 기술</strong>에 대해 공부해볼 수 있는 시간이다. 주기적으로 공부한 내용에 대해 <strong>팀원들에게 발표하고, 피드백</strong>을 받는 시간을 가지기도 했다. 이 과정에서 부족한 점, 더 공부해보면 좋은 지식 등 다양한 피드백을 받아볼 수 있었기 때문에 굉장히 좋은 시간이었다.</p>
<p>다만 필자 개인적으로 아쉬웠던 점은, 1개월 안에 많은 토픽을 공부하고 발표해야 하다보니 어느 한 토픽에 Deep-Dive 해볼 시간이 부족했다. 물론, 그렇다고 팀 입장에서는 온보딩에 너무 많은 리소스가 들어가면 업무에 지장이 있기 때문에 스터디 기간을 늘리면 좋겠다는 이야기는 아니다. 더 공부해볼 토픽에 대해서는 앞으로 자투리 시간이 날 때마다 차근차근 공부해볼 계획이다.</p>
<h3 id="2-입사-과제-리팩토링">2. 입사 과제 리팩토링</h3>
<p>앞선 스터디 기간동안 익힌 지식들을 기반으로, 입사 지원 시 <strong>제출했던 과제를 리팩토링</strong>하는 시간을 가졌다. 입사 지원 당시에는 필자가 보유한 기술 스택을 기반으로 과제를 개발했었다면, 온보딩 과정에선 <strong>팀에서 사용하는 기술 스택, 아키텍처를 기반으로 과제를 리팩토링</strong> 해보는 것이다. 실무에 투입되기 앞서, 팀에서 사용하는 기술, 개발 스타일에 수월하게 적응할 수 있도록 도와주는 듯 했다. </p>
<p>과제는 실제로 업무를 수행하는 것처럼 PR 을 올리고, 리뷰를 받은 뒤 하나 둘 Merge 해나가는 식으로 진행한다. 그런데 정말, <strong>팀원들이 시간적 여유가 날 때마다 필자의 PR 에 뼈가 되고 살이 되는 리뷰를 달아주셨다</strong>. 신입으로서 과분한 케어를 받고 있는 느낌이 들면서 팀원들에게 <em><strong>정말 감동받았다</strong></em> 🥺.. 따라서, 팀원들이 주시는 관심에 부응하기 위해 더욱 열심히 과제를 수행할 수 있었던 것 같다.</p>
<p>완성 후에는 <strong>팀원들에게 발표를 해야 한다</strong>. 팀장님, 길드장님까지 참석한 발표는 결코 쉽지 않았지만, 최대한 보여줄 수 있는 것들을 모두 보여주는 데에는 성공했다. *<em>발표를 끝냈을 때 후련함은 잊을 수 없다 ㅋㅋㅋ *</em>(뭐 대단한 거 한 것 마냥..)</p>
<p>스터디부터 과제 발표까지의 과정을 겪으며 팀 적응에 있어 도움이 될 만한 많은 경험을 해볼 수 있었다. 팀에서 사용하는 기술 스택에 익숙해질 수 있었고, 실제 업무는 어떤 식으로 진행되는 지에 대한 이야기도 들어볼 수 있었다. 또한 팀원들과 많은 소통을 해보며 팀 분위기를 어느 정도 파악할 수 있었다.</p>
<h3 id="3-담당-업무-지정-및-인수인계">3. 담당 업무 지정 및 인수인계</h3>
<p>필자는 카카오페이의 주요 기능 중 하나인 <strong>자산관리(PFM) 기능을 개발하는 마이데이터 업무</strong>를 맡게 됐다. 온보딩 기간에 팀장님과의 1 on 1 미팅에서 팀장님이 &#39;해보고 싶은 업무가 있냐&#39;고 말씀하셔서, 딱히 막 &#39;어떤 걸 꼭 해보고 싶다&#39; 이런 업무가 없다고 말씀을 드렸더니, &#39;마이데이터&#39; 팀에 가면 많은 것을 배울 수 있을거라고 말씀하시면서 마이데이터 업무를 지정해주셨다. 무엇이든 새롭고 짜릿한 시점인 필자에게는 심쿵 모먼트였다. &#39;드디어 실무에 투입되는구나&#39; 하면서 말이다.</p>
<p>마이데이터 업무를 위한 단톡방에 초대되자, 팀원분들이 인수인계를 도와주셨다. 그런데 여기서 또 한 번의 감동을 느낀게, <strong>팀원들이 너무 자상하고 친절하게 대해주신 점이 너무 인상깊었다</strong>. 바쁜 시간을 쪼개어 미팅을 따로 잡아서 기존 코드를 설명해주시고, 업무에 필요한 도메인 지식들을 차근차근 설명해주셨다. 게다가 필자는 조금이라도 궁금한 점이 있다면 곧장 물어보는 성격임에도 불구하고, 필자의 질문에 모두 친절하게 답변해주셨다. 정말 &#39;<strong>최고의 복지는 동료다</strong>&#39; 라는 말이 단 번에 와닿을 정도였다. </p>
<p>현재 조그마한 업무부터 투입되어 한창 피쳐 개발을 해보고 있는데, 좋은 동료들과 일해서 그런지 일이 너무 재밌다. <strong>계속하여 부담을 덜어주시고, 격려해주신다</strong>. 신입으로서 첫 회사의 첫 업무를 이러한 동료들과 함께 할 수 있어서 정말이지 영광이다. 이를 영광으로 알고, 기대에 부응하기 위해 앞으로 정말 열심히 성장해볼 계획이다. <em><code>(제스, 원더 정말 감사해요 ㅎㅎ♥️)</code></em></p>
<br>


<h2 id="팀원들과-여행">팀원들과 여행</h2>
<p>온보딩이 끝나고 실무에 투입되기 직전에 팀원들과 어쩌다 인천 을왕리로 여행을 가게 됐다. 
이 여행이 기획될 때, <strong>아무것도 모르는 필자</strong>는 이러한 생각이 들었다.</p>
<blockquote>
<p>🤷🏻‍♂️ &quot;와 팀원들이 같이 여행을 갈 정도로 끈끈하다고?&quot;</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/a6a4ce42-b493-4b19-b09a-2856d9387454/image.JPG" alt=""></p>
<p>뭔가 주변에서 들려오는 회사 생활을 간접적으로 경험해본 바로는, &#39;업무 시간 이외에는 회사 사람들이랑 상종도 하기 싫다&#39;며 회식 자리도 기피하고, 여행은 꿈도 못꾸는 느낌이었다. 그런데 이러한 느낌과는 전혀 상반된 팀 분위기가 필자를 의아하게 만들었다. 그도 그럴 것이, 팀원들이 모두 착하고 배려넘치는 덕분에 이러한 분위기가 형성된 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/cbb31315-ba9a-4046-a3cf-5716fa0ca3b7/image.jpg" alt=""></p>
<blockquote>
<h4 id="👆🏻-팀원이-수비드해서-구워주신-티본-스테이크-완전-맛있었다"><em>👆🏻 팀원이 수비드해서 구워주신 티본 스테이크, 완전 맛있었다!</em></h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/98672443-431b-4112-8107-f44b82079c0c/image.JPG" alt=""></p>
<blockquote>
<h4 id="👆🏻-팀장님이-사주신-블루라벨-ㅋㅋㅋ-팀장님-감사합니다-🥰"><em>👆🏻 팀장님이 사주신 블루라벨 ㅋㅋㅋ 팀장님 감사합니다 🥰</em></h4>
</blockquote>
<p>바다 구경도 하고, 맛있는 것도 실컷 먹고 술잔을 기울이며 밤새 떠들었다. 팀 평균 나이대가 상당히 젊은 편이라 그런지, 말이 잘 통하는 느낌을 받았다. 게다가 팀원들이 다 재밌기도 하고 잘 챙겨주셔서 즐겁게 놀다 올 수 있었던 것 같다. 아무래도 재택근무로 인해 팀원들과 친해질 기회가 많이 없었어서 아쉬워했는데, 이렇게 여행을 가면서 더욱 친해질 수 었었기에 더욱 뜻깊었던 것 같다. </p>
<p>기회가 된다면 또 팀원들과 놀러가고싶다!</p>
<br>

<h2 id="수습-기간을-돌아보며">수습 기간을 돌아보며</h2>
<p><img src="https://velog.velcdn.com/images/haero_kim/post/df89a7ef-c240-48b5-9276-0b7a63af5ced/image.jpeg" alt=""></p>
<blockquote>
<h4 id="정말-눈-깜빡할-사이에-3개월이-지난-것-같다">정말 눈 깜빡할 사이에 3개월이 지난 것 같다.</h4>
</blockquote>
<p>문득 <strong>입사 1주일 전의 감정</strong>들이 새록새록 떠오른다. <strong><em>&#39;내가 과연 잘 할 수 있을까&#39;, &#39;피해 끼치면 어떡하지&#39;</em></strong> 되뇌이며 온갖 사념에 빠지곤 했다. 그런데 정작 입사하고 나니 팀에 적응하고 회사의 시스템에 적응하느라 바빠서 그런지, 그런 사념이 절로 떨쳐진 듯하다. 닿는 대로 열심히 하다보니 <strong>3개월이 훌쩍 지나갔다</strong>. </p>
<p>카카오페이 안드로이드 팀에서의 수습 기간은 무작정 실무에 투입시키는 것이 아닌, <strong>업무에 앞서 준비 운동을 시켜주는 느낌</strong>이었던 것 같다. 차후 업무에 잘 적응할 수 있도록 충분히 기본기를 다져주고, 팀원들과 친해지고 체계에 적응할 수 있는 발판을 마련해주신 듯하다. 이를 달리 해석해보면, <strong>&#39;애초에 같이 일 할 생각으로 뽑았기 때문에, 나중에 실무에 투입되어도 알잘딱깔센 잘 할 수 있도록 3개월 동안 팀 적응에만 힘써달라&#39;</strong> 는 듯한 뉘앙스였던 것 같다. (물론 필자는 신입으로 들어간 것이기 때문에 이러한 부분이 더욱이 중요시된 것 같다)</p>
<p>이러한 부분에서 <strong>신입 크루에 대한 배려심이 넘치는 것</strong>을 느낄 수 있었다. 다른 회사를 경험해본 적은 없지만, 적어도 필자가 느낀 바로는 <strong>엄청난 케어를 받는 기분이라 정말 부담없이, 편하게 임할 수 있었다.</strong></p>
<h3 id="스스로-아쉬웠던-점">스스로 아쉬웠던 점</h3>
<p>본인 스스로에게 <strong>아쉬운 점</strong>은, <strong>의견 전개에 아직 소극적</strong>이라는 점이다. 평소에는 의견 전개를 마다하지 않는 성격인데,** 왠지 모르게 회사에서는 성격이 조금 조심스러워지는 것 같다**. 그런데 팀 회의같은 자리에서 활발하게 의견을 전개하고, 다른 팀원들과 의견을 주고받는 모습을 보며 분명 나도 익숙해져야겠다는 생각이 들었다. </p>
<p>이러한 부분을 가장 극심하게 느꼈던 일화가 있다. 어떤 피처를 개발할 때 여러 방식이 존재할텐데, 필자는 필자의 방식대로 작업하여 PR 을 올렸다. 그런데 한 팀원분이 &#39;이렇게 말고 저렇게 하는 건 어때요?&#39; 하며 본인의 의견을 주셨다. 그러자 필자는 &#39;아 그럼 그렇게 수정하겠습니다&#39; 라고 내뱉어버린 것이다. 방식에는 그 어떤 정답도 없는데, 팀원의 의견이 정답이라는 것 마냥 필자의 주관을 아예 배제해버린 것이다. 이런 필자 스스로를 보며 많이 실망했다. <strong>의견을 주고받으며 오목조목 효율을 따져보고 코드의 품질을 높이는 것이 중요한데, 자신의 의견을 전개하지 못하는 자세는 개발자로서 별로 좋지 못한 자세라고 생각한다</strong>. 따라서, 이후에는 의견을 적극적으로 전개하는 연습을 해보고 있다. <strong>현재로써 가장 먼저 이루고자 하는 목표이다</strong>.</p>
<p>정말 좋은 팀원들과 일할 수 있게 되어 진심으로 기쁘고, 앞으로가 기대된다. 주니어 개발자가 성장할 수 있는 많은 기회들이 마련되어 있기 때문에, 이 기회들을 잘 활용하고 팀원들의 기대에 부응하기 위해 더욱이 노력해야겠다. 아직도 업무 체계를 완벽히 숙지하지 못했지만, <strong>하루빨리 알잘딱깔센 일잘러</strong>가 되어야 겠다. 스스로에게 홧팅! 🔥</p>
<br>

<h2 id="언제든-환영해요">언제든 환영해요</h2>
<blockquote>
<p>*<em>회사의 코드를 처음 봤을 땐 안드로이드를 진심으로 사랑하는 사람들이 고민하고 함께 머리를 맞대며 만들어진 것 같다는 생각이 절로 들었다.
*</em>그만큼 주니어 개발자들이 성장할 수 있는 엄청난 환경이기도 하다. 앞으로 많은 것을 배울 수 있을 것 같다.</p>
</blockquote>
<p>복지 이야기도 잠깐 하자면, 현재 전사 재택근무 기간이라서 꼭 회사에 와야 하는 크루들, 혹은 회사에서 능률이 더 잘 나오는 <strong>크루들을 위해 출퇴근 택시가 전액 지원</strong>된다.
게다가 <strong>식대 20만원, 복지 포인트 30만원</strong>이 더해져 월급이 50만원 추가되는 격이다. _복지 포인트가 정말 개꿀이다.. _
카카오페이 포인트 형식으로 나오는데, 필자는 <strong>무신사나 머스트잇에서 옷을 사는 데에 사용하고 있다</strong>. 월급이랑 별개로 옷 살 돈이 있다니 정말 좋은 것 같다!</p>
<p>이외에도 다양하고 엄청난 복지들이 있다. (아래는 카카오페이 링크드인에 게시됐던 글이다)
<img src="https://velog.velcdn.com/images/haero_kim/post/8c8875ae-6817-470a-a603-ec6805bfe657/image.jpg" alt=""></p>
<p>필자의 이야기를 듣고, 조금 더 팀에 대해 궁금해진다면 언제든지 아래 채용공고를 통해 지원해주길 바란다. 항상 열려있다!</p>
<blockquote>
<h4 id="혹여-팀에-대해서-채용에-관해서-궁금한-점이-있다면-메일-주세요">혹여 팀에 대해서, 채용에 관해서 궁금한 점이 있다면 메일 주세요!</h4>
<p><a href="https://kakaopay.recruiter.co.kr/app/jobnotice/view?systemKindCode=MRS2&amp;jobnoticeSn=46711">https://kakaopay.recruiter.co.kr/app/jobnotice/view?systemKindCode=MRS2&amp;jobnoticeSn=46711</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] DI 개념 & 라이브러리 없이 직접 구현해보기]]></title>
            <link>https://velog.io/@haero_kim/Android-DI-%EA%B0%9C%EB%85%90-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%97%86%EC%9D%B4-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Android-DI-%EA%B0%9C%EB%85%90-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%97%86%EC%9D%B4-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 23 Jan 2022 08:21:53 GMT</pubDate>
            <description><![CDATA[<h1 id="dependency-injection">Dependency Injection</h1>
<p>어떤 클래스는 다른 클래스에 대한 참조가 필요한 경우가 있다. 예를 들어, <code>Car</code> 클래스는 <code>Engine</code> 클래스 참조가 필요할 것이다.
이 때 <code>Car</code> 가 <code>Engine</code> 에 의존하고 있다고 말하고, <code>Engine</code> 을 <code>Car</code> 의 종속 항목 (디펜던시) 이라고 한다.</p>
<p>특정 클래스가 자신이 의존하고 있는 객체를 얻는 방법은 3가지가 있다. (<code>Car</code> 와 <code>Engine</code> 예제 활용)</p>
<ol>
<li><code>Car</code> 클래스 안에서 <code>Engine</code> 인스턴스를 생성하여 초기화한다.</li>
<li>다른 곳에서 객체를 가져온다. Android 로 치면 <code>Context</code>, <code>getSystemService()</code> 등에 해당한다.</li>
<li><strong>객체를 파라미터로 제공받는다. <code>Car</code> 의 생성자가 <code>Engine</code> 을 파라미터로 받는다.</strong></li>
</ol>
<p>세 번째 방법이 바로 <strong>Dependency Injection</strong> 기법 중 하나이다. </p>
<br>

<h2 id="의존관계에-있어-di-를-사용하지-않을-때">의존관계에 있어 DI 를 사용하지 않을 때</h2>
<p>DI 없이 코드에서 자체적으로 <code>Engine</code> 을 생성하는 <code>Car</code> 를 나타낸 모습이다.</p>
<pre><code class="language-kotlin">class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}</code></pre>
<p>이 코드는 다음과 같은 문제를 갖고 있다.</p>
<ul>
<li><code>Car</code> 의 <code>Engine</code> 에 대한 의존성이 너무 강하다. <code>Car</code> 클래스가 <code>Engine</code> 을 직접 인스턴스화하기 
때문에, <code>Engine</code> 의 서브클래스인 <code>GasEngine</code>, <code>ElectricEngine</code> 등을 사용할 수 없게 된다. 
또한, <code>Engine</code> 의 생성자가 변경된 경우 <code>Car</code> 클래스에서도 수정이 이루어져야 한다.</li>
<li>이러한 강력한 의존관계는 테스트를 어렵게 만든다. <code>Engine</code> 의 실제 인스턴스를 사용하기 때문에 다양한 시나리오를 고려하지 못한다. (Unit Test 에 불리함)</li>
</ul>
<br>

<h2 id="의존관계에-있어-di-를-사용할-때">의존관계에 있어 DI 를 사용할 때</h2>
<p>DI 를 사용한다면 <code>Car</code> 의 각 인스턴스는 초기화할 때 <code>Engine</code> 객체를 생성자 파라미터로 받게 된다.</p>
<pre><code class="language-kotlin">class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}</code></pre>
<p><code>main()</code> 에서 <code>Engine</code> 인스턴스를 생성하고, 이를 활용하여 <code>Car</code> 인스턴스를 만들게 된다.</p>
<p>이렇게 구현하게 되면 다음과 같은 이점을 챙길 수 있다.</p>
<ul>
<li><strong><code>Car</code> 의 재사용성이 높아진다.</strong> 예를 들어 <code>ElectricEngine</code> 과 같은 <code>Engine</code> 의 서브클래스를 넘겨주는 등, <code>Engine</code> 의 다양한 구현을 <code>Car</code> 에 전달할 수 있다.</li>
<li><code>Engine</code> 의 생성자 등 구현이 변경되어도, <strong><code>Car</code> 클래스를 수정하지 않아도 된다.</strong></li>
<li><code>Car</code> 에 대한 <strong>유닛 테스트가 편리</strong>해진다. 즉, 다양한 시나리오를 테스트해볼 수 있다. (<code>MockEngine</code> 등)</li>
</ul>
<br>

<h2 id="안드로이드에서의-di-구현-방법">안드로이드에서의 DI 구현 방법</h2>
<p>안드로이드에서 DI 를 구현하는 방법은 크게 두 가지가 있다.</p>
<ul>
<li><strong>Constructor Injection</strong> (생성자 삽입) : 위에서 설명한 방법대로, 생성자 파라미터를 통해 의존성을 주입해주는 것이다.</li>
<li><strong>Field Injection</strong> (필드 삽입) : <code>Activity</code> 나 <code>Fragment</code> 는 시스템이 인스턴스화하기 때문에 생성자 삽입 기법이 불가능하다. 
따라서 다음과 같이 필드 삽입을 사용할 수 있다.</li>
</ul>
<pre><code class="language-kotlin">class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}</code></pre>
<br>

<h2 id="di-의-이점">DI 의 이점</h2>
<ol>
<li><p><strong>의존성 분리</strong> 
클래스가 더이상 <strong>디펜던시의 생성 (인스턴스화) 에 관여하지 않기</strong> 때문에, 종속 항목이 변경되어도
(생성자 변경 등) 영향을 받지 않고 유연하게 동작한다. 즉, <strong>리팩토링이 편리해진 것이다.</strong></p>
</li>
<li><p><strong>클래스 재사용성 증가</strong>
의존하는 객체의 구현을 쉽게 갈아끼울 수 있다. 서브 타입 등 다양한 구현을 수용할 수 있고,
때문에 <strong>다양한 곳에서 클래스를 재사용</strong>할 수 있다.</p>
</li>
<li><p><strong>테스트 편의성
의존성이 분리되어</strong>, 테스트 시 다양한 구현을 전달하여 <strong>여러 시나리오를 검증</strong>해볼 수 있다. 
(즉, Mocking 이 쉬워 진다 : <strong>Test Double 이 가능해졌다</strong>)</p>
</li>
</ol>
<br>

<h1 id="직접-di-구현해보기">직접 DI 구현해보기</h1>
<p>안드로이드 개발자들이 주로 사용하는 Dagger2, Hilt 와 같은 라이브러리들이 있지만, <strong>DI 의 원리를 이해하기 위해서는 우선 직접 구현해보는 편이 낫다.</strong>
예시로 <strong>로그인 플로우</strong>를 구현함에 있어 DI 를 직접 구현해보자. 디펜던시 그래프는 다음과 같다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/853093e8-1116-4031-9434-1f20ada00e9e/flow.png" alt=""></p>
<p>이 플로우에 있어 <code>Repository</code> 및 <code>DataSource</code> 클래스는 다음과 같다.</p>
<pre><code class="language-kotlin">class UserRepository(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource { ... }
class UserRemoteDataSource(
    private val loginService: LoginRetrofitService
) { ... }</code></pre>
<p><code>LoginActivity</code> 는 아래와 같다.</p>
<pre><code class="language-kotlin">class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // In order to satisfy the dependencies of LoginViewModel, you have to also
        // satisfy the dependencies of all of its dependencies recursively.
        // First, create retrofit which is the dependency of UserRemoteDataSource
        val retrofit = Retrofit.Builder()
            .baseUrl(&quot;https://example.com&quot;)
            .build()
            .create(LoginService::class.java)

        // Then, satisfy the dependencies of UserRepository
        val remoteDataSource = UserRemoteDataSource(retrofit)
        val localDataSource = UserLocalDataSource()

        // Now you can create an instance of UserRepository that LoginViewModel needs
        val userRepository = UserRepository(localDataSource, remoteDataSource)

        // Lastly, create an instance of LoginViewModel with userRepository
        loginViewModel = LoginViewModel(userRepository)
    }
}</code></pre>
<p>위 코드들을 놓고봤을 때, 아래와 같은 문제들을 발견할 수 있다.</p>
<ol>
<li><p><strong>보일러플레이트</strong>가 너무 많다. 다른 부분에서 <code>LoginViewModel</code> 의 다른 인스턴스를 만들려면
중복된 코드가 발생할 수 있다.</p>
</li>
<li><p><strong>객체를 재사용하기 어렵다</strong>. 여러 군데에서 <code>UserRepository</code> 를 재사용하려면 싱글톤 패턴을 따르게 해야
한다. 그런데 만약 싱글톤으로 구현한다해도, 모든 테스트가 동일한 인스턴스를 공유하므로 다양한 시나리오의 테스트가 어려워지게 된다.</p>
</li>
</ol>
<br>

<h2 id="container-로-dependency-관리">Container 로 Dependency 관리</h2>
<p>객체 재사용 문제를 해결하려면, 디펜던시를 가져오기 위해 사용할 자체적인 <strong>&#39;Dependency Container&#39;</strong> 클래스를 만들면 된다.
이 컨테이너에서 제공하는 인스턴스는 외부로 공개될 수 있다. 지금 예시에서는 <code>UserRepository</code> 인스턴스만 있으면 되므로 얘만 <code>public</code> 상태로 둔다.</p>
<pre><code class="language-kotlin">// Container of objects shared across the whole app
class AppContainer {

    // Since you want to expose userRepository out of the container, you need to satisfy
    // its dependencies as you did before
    private val retrofit = Retrofit.Builder()
                                .baseUrl(&quot;https://example.com&quot;)
                                .build()
                                .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    // userRepository is not private; it&#39;ll be exposed
    val userRepository = UserRepository(localDataSource, remoteDataSource)
}</code></pre>
<p>이러한 디펜던시는 앱 전체에 걸쳐 사용될 수 있으므로 모든 액티비티에서 사용할 수 있는, 즉 <code>Application</code> 클래스에 배치해야 한다.
그러므로 <code>AppContainer</code> 인스턴스를 갖고 있는 <code>Application</code> 클래스를 만들자.</p>
<pre><code class="language-kotlin">// Custom Application class that needs to be specified
// in the AndroidManifest.xml file
class MyApplication : Application() {

    // Instance of AppContainer that will be used by all the Activities of the app
    val appContainer = AppContainer()
}</code></pre>
<p>이젠 액티비티에서도 해당 클래스를 가지고 <code>AppContainer</code> 인스턴스를 가져와서 <code>UserRepository</code> 인스턴스를 얻을 수 있다.</p>
<pre><code class="language-kotlin">class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Gets userRepository from the instance of AppContainer in Application
        val appContainer = (application as MyApplication).appContainer
        loginViewModel = LoginViewModel(appContainer.userRepository)
    }
}</code></pre>
<p>싱글톤으로 구현하지 않고, 모든 액티비티에게 공유되는 <code>AppContainer</code> 를 통해  <code>UserRepository</code> 를 필요로 하는 모든 액티비티에서 인스턴스를 제공할 수 있게 됐다.</p>
<p>만약 <code>LoginViewModel</code> 도 다른 곳에서 재사용되는 경우, <code>LoginViewModel</code> 인스턴스를 만들어주는 곳 역시
있으면 좋다. 마찬가지로 이를 컨테이너로 옮기고, 새 <code>LoginViewModel</code> 객체를 생성하는 팩토리를 만들어주자.</p>
<pre><code class="language-kotlin">// Definition of a Factory interface with a function to create objects of a type
interface Factory&lt;T&gt; {
    fun create(): T
}

// Factory for LoginViewModel.
// Since LoginViewModel depends on UserRepository, in order to create instances of
// LoginViewModel, you need an instance of UserRepository that you pass as a parameter.
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
    override fun create(): LoginViewModel {
        return LoginViewModel(userRepository)
    }
}</code></pre>
<p><code>LoginViewModelFactory</code> 를 <code>AppContainer</code> 로 옮겨주고, 이를 <code>LoginActivity</code> 에서 사용해보자.</p>
<pre><code class="language-kotlin">// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory
class AppContainer {
    ...
    val userRepository = UserRepository(localDataSource, remoteDataSource)

    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}

class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Gets LoginViewModelFactory from the application instance of AppContainer
        // to create a new LoginViewModel instance
        val appContainer = (application as MyApplication).appContainer
        loginViewModel = appContainer.loginViewModelFactory.create()
    }
}</code></pre>
<p>재사용성을 높였지만, 여전히 다음과 같은 문제들이 남아있다.</p>
<ol>
<li><code>AppContainer</code> 를 직접 관리하기 때문에 모든 디펜던시의 인스턴스를 수동으로 만들어줘야 한다.</li>
<li>여전히 보일러플레이트 코드가 많다. 객체를 다른 곳에서 재사용할지에 따라
팩토리, 파라미터 등을 만들어줘야 한다.</li>
</ol>
<br>

<h2 id="플로우가-더-많아진다면">플로우가 더 많아진다면?</h2>
<p>지금 예제에서는 별 문제가 없어보이지만, 프로젝트에 기능을 더 많이 포함한다면 <code>AppContainer</code> 는
더더욱 복잡해지고 다음과 같은 문제가 발생한다.</p>
<ol>
<li>만약 또 다른 기능 플로우가 있다면, 객체가 플로우의 범위 내에서만 존재하길 원할 수 있다. 예를 들어, 로그인 플로우에서만 사용하는 &#39;username&#39; 과 &#39;password&#39; 로 이루어진 <code>LoginUserData</code> 라는 객체를 생성할 때, 다른 사용자의 이전 로그인 플로우에서 사용된 데이터를 유지하면 안 될 수 있다. 즉, 새로운 플로우엔 새로운 인스턴스를 사용해야 할 수 있다.</li>
<li>플로우에 따라 필요하지 않은 인스턴스를 삭제해야 할 수 있다.</li>
</ol>
<br>

<p>예를 들어, <code>LoginActivity</code> 와 <code>LoginUsernameFragment</code>, <code>LoginPasswordFragment</code> 등의
프래그먼트로 구성된 로그인 플로우를 가정해보자. 아래의 요구사항을 만족시키려 한다.</p>
<ol>
<li>로그인 플로우가 끝날 때까지 동일한 <code>LoginUserData</code> 인스턴스에 액세스한다.</li>
<li>로그인 플로우가 다시 시작되면, 새로운 <code>LoginUserData</code> 인스턴스를 생성한다.</li>
</ol>
<p>그렇다면, 오로지 <strong>로그인 플로우를 위한 전용 컨테이너</strong>로 위와 같은 요구사항을 만족시킬 수 있다. 
이 컨테이너는 로그인 플로우가 시작될 때 만들어지고, 끝나면 메모리에서 삭제되게끔 한다.</p>
<br>

<p>아래와 같이 <code>LoginConatiner</code> 를 추가하고, <code>AppContainer</code> 클래스에 넣어둔다.</p>
<pre><code class="language-kotlin">class LoginContainer(val userRepository: UserRepository) {

    val loginData = LoginUserData()

    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}

// AppContainer contains LoginContainer now
class AppContainer {
    ...
    val userRepository = UserRepository(localDataSource, remoteDataSource)

    // LoginContainer will be null when the user is NOT in the login flow
    var loginContainer: LoginContainer? = null
}</code></pre>
<p>이제 로그인 플로우를 담당하는 <code>LoginActivity</code> 에서 <code>LoginContainer</code> 의 생성과 삭제를 관리하면 된다. 
통상적인 방법으로는 <strong><code>onCreate()</code> 에서 새로운 인스턴스를 만들고 <code>onDestroy()</code> 에서 삭제</strong>할 수 있다.</p>
<pre><code class="language-kotlin">class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel
    private lateinit var loginData: LoginUserData
    private lateinit var appContainer: AppContainer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appContainer = (application as MyApplication).appContainer

        // Login flow has started. Populate loginContainer in AppContainer
        appContainer.loginContainer = LoginContainer(appContainer.userRepository)

        loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
        loginData = appContainer.loginContainer.loginData
    }

    override fun onDestroy() {
        // Login flow is finishing
        // Removing the instance of loginContainer in the AppContainer
        appContainer.loginContainer = null
        super.onDestroy()
    }
}</code></pre>
<p>이렇게 하면  <code>LoginUsernameFragment</code> , <code>LoginPasswordFragment</code> 등의 프래그먼트들은 <code>AppContainer</code> 에서 <code>LoginContainer</code> 를 접근하여 액티비티와 공유하는 <code>LoginUserData</code> 의
고유한 인스턴스를 사용할 수 있다.</p>
<br>

<h2 id="라이브러리-없이-직접-di-를-구현했을-때의-고충">라이브러리 없이 직접 DI 를 구현했을 때의 고충</h2>
<p>지금까지 간략하게 수동으로 DI 를 구현해보았다. DI 를 사용하지 않았을 때보단 코드의 재사용성과 유지보수 효율이 높아졌다. 컨테이너를 사용하여 앱의 다양한 부분에서 클래스 인스턴스를 공유할 수 있게 됐다.</p>
<p>그러나, <strong>앱이 커지면 커질수록 Container, Factory 등 보일러플레이트코드를 많이 작성하게 되고</strong>, 그러한 곳들에서 <strong>오류가 발생하기 쉽다</strong>. 그리고 컨테이너가 더이상 필요하지 않을 때 메모리에서 삭제하는 등 컨테이너의 수명 주기를 직접 관리해야한다. 만일 이러한 곳에서 실수한다면 <strong>자잘한 버그와 메모리 릭이 발생</strong>할 수 있다.</p>
<p><strong><code>Dagger</code>, <code>Hilt</code></strong> 와 같은 DI 라이브러리들은 이러한 고충을 덜어준다. 지금까지의 과정들을 자동화해준다.
다음 포스팅에선, 이러한 라이브러리를 활용하여 DI 를 구현했을 때, 직접 구현하는 방식에 비해 어떤 이점을 챙길 수 있는지에 대해 알아보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] SparseArray 찍먹해보기]]></title>
            <link>https://velog.io/@haero_kim/Android-SparseArray-%EC%B0%8D%EB%A8%B9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Android-SparseArray-%EC%B0%8D%EB%A8%B9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 04 Jan 2022 15:21:02 GMT</pubDate>
            <description><![CDATA[<p>안드로이드 개발시 사용할 수 있는 다양한 자료구조 중, <strong><code>SparseArray</code></strong> 라는 것이 있다. 사람들은 이것을 종종 키 값을 정수형으로 가지는 <code>HashMap</code> 자료구조를 사용할 때 알게 된다. 왜냐하면 안드로이드 스튜디오에서 <code>Integer</code> 타입을 키 값으로 가지는 <code>HashMap</code> 을 사용하면, <code>SparseArray</code> 를 사용하라는 워닝을 띄우기 때문이다. <strong>그렇다면 <code>SparseArray</code> 는 뭐가 그리 잘났길래 안드로이드 차원에서 밀어주는 것일까?</strong></p>
<h2 id="sparsearray">SparseArray</h2>
<p>우선, 영어단어 Sparse 에 대해 알아보면 다음과 같은 뜻을 지니고 있다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/709e1182-9da3-4402-ac43-398ff4d466d5/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-04%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.08.02.png" alt=""></p>
<blockquote>
<p>‘드문’, ‘희박한</p>
</blockquote>
<p><code>SparseArray</code> 의 특성을 이해한다면, 이름이 왜 <strong>Sparse</strong> 인지 이해될 것이다. </p>
<br>


<h3 id="sparsearray-개념">SparseArray 개념</h3>
<p>우선, 공식문서에서 소개하는 <code>SparseArray</code> 의 개념은 다음과 같다.</p>
<blockquote>
<p><code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects, its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient than a <code>[HashMap](https://developer.android.com/reference/java/util/HashMap)</code>, because it avoids auto-boxing keys and its data structure doesn&#39;t rely on an extra entry object for each mapping.</p>
</blockquote>
<p>위 설명대로, <strong><code>SparseArray</code></strong> 는 <strong>Integer 키 값에 대해 Object를 매핑</strong>해주는 자료구조이다. 그리고 배열임에도 각 인덱스 사이에 빈 공간을 만들 수 있다. 예를 들어 1번에 객체가 담겨있는데, 2번을 건너뛰고 3번에 객체가 담겨있을 수 있다는 의미이다. <strong>즉, Sparse(드문드문)하게 객체가 담겨있는 배열이라는 닉값</strong>을 하는 녀석이다.</p>
<p>이 때, <strong><code>HashMap</code> 과는 달리 내부에서 별다른 객체를 생성하지 않고</strong>, 그렇기 때문에 <strong>Wrapper 클래스로 Boxing 할 필요가 없어</strong> <strong>공간적으로</strong> <strong>퍼포먼스가 더욱 우수</strong>하다. (즉, <strong>Auto-Boxing</strong> 을 피한다)</p>
<p>공식 문서는 아래와 같이 <code>SparseArray</code> 에 적용된 최적화 관련 설명도 덧붙여주고 있다.</p>
<blockquote>
<p>To help with performance, the container includes an optimization when removing keys: instead of compacting its array immediately, it leaves the removed entry marked as deleted. The entry can then be re-used for the same key or compacted later in a single garbage collection of all removed entries. This garbage collection must be performed whenever the array needs to be grown, or when the map size or entry values are retrieved.</p>
</blockquote>
<p>위 설명에 따르면, <code>SparseArray</code> 는 성능 향상을 위해 특정 <strong>Key-Value 매핑 데이터 제거 시</strong>에 실제로 제거하여 배열 컴팩팅을 진행하는 것이 아닌, <strong><code>Deleted</code> 상태로 표시</strong>만 해둔다. 만약 <strong>해당 Value 를 가리키는 Key 에 또 다른 데이터가 추가</strong>된다면 다시 채워진다. 이러한 최적화 덕에 <strong>삭제 연산 효율이 높아진다.</strong></p>
<br>


<p><code>SparseArray</code> 는 다음과 같이 사용할 수 있다. (이번 포스팅에선 자세한 사용법은 다루지 않겠다)</p>
<pre><code class="language-kotlin">private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
    append(Surface.ROTATION_0, 90)
    append(Surface.ROTATION_90, 0)
    append(Surface.ROTATION_180, 270)
    append(Surface.ROTATION_270, 180)
}

private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
    append(Surface.ROTATION_0, 270)
    append(Surface.ROTATION_90, 180)
    append(Surface.ROTATION_180, 90)
    append(Surface.ROTATION_270, 0)
}</code></pre>
<br>


<h3 id="sparsearray-사용시-주의점">SparseArray 사용시 주의점</h3>
<p><code>SparseArray</code> 는 <strong>배열 구조로 Key→Value 매핑 정보를 저장하고, 해당 배열을 이분 탐색하여 키를 찾게 된다</strong>. 이러한 특성때문에, <strong>매우 많은 수의 아이템을 저장하는 상황에는 그리 적합하지 않다</strong>. 이분 탐색을 수행한다는 점, 그리고 데이터가 방대한 경우 삽입 삭제 연산이 배열 자료구조에선 느리게 동작한다는 점 등이 원인이다. <strong>즉, 시간 복잡도 측면에서의 성능은 비교적 안 좋다</strong>. </p>
<p>다만 수백 개 데이터가 넘어가지 않는 한 <strong>이분 탐색 소요시간도 상수시간에 수렴하고, 삽입 삭제 등의 연산도 빠르기 때문에 <code>HashMap</code> 보다 좋은 성능</strong>을 낸다. 따라서 <strong>적절히 적은 데이터를 매핑할 때 사용하면 좋을 것 같다</strong>.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고록] 2021년을 돌아보며]]></title>
            <link>https://velog.io/@haero_kim/%ED%9A%8C%EA%B3%A0%EB%A1%9D-2021%EB%85%84%EC%9D%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</link>
            <guid>https://velog.io/@haero_kim/%ED%9A%8C%EA%B3%A0%EB%A1%9D-2021%EB%85%84%EC%9D%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</guid>
            <pubDate>Mon, 27 Dec 2021 12:26:34 GMT</pubDate>
            <description><![CDATA[<p>어느덧 연말이다. 2021년은 정말 폭풍처럼 지나간 한 해였다. <strong>졸업작품과 취업 준비, SW 마에스트로 3개 활동을 병행</strong>하며 <strong>모두 좋은 성과</strong>를 얻었다는 점에서 살면서 <strong>가장 뿌듯한 한 해</strong>기도 했다. 졸업작품으로 <strong>제 9회 K-Hackathon 대상 (과학기술정보통신부 장관상)</strong> 을 수상했고, <strong>SW 마에스트로 12기 우수 연수생으로 선발</strong>되기도 했으며 꿈에만 그리던 <strong>카카오페이라는 기업에 최종 합격</strong>할 수 있었다. 결과는 노력을 배신하지 않는다는 말이 와닿았던 뜻깊은 한해였기에, 돌아보며 회고를 해보려 한다.</p>
<p>딱히 시간 순서로 정리하기보단, 모든 활동이 정말 병행 그 자체였기 때문에 활동 별로 정리해서 회고해보려 한다.</p>
<hr>
<h2 id="알고리즘-스터디-운영">알고리즘 스터디 운영</h2>
<p>필자는 학과 내에서 알고리즘 공부에 관심있는 인원들을 모아 총 2개의 알고리즘 스터디를 운영했다. 재학중인 학과 자체가 신설 학과이다보니 <strong>기존에 스터디 자체가 존재하지 않기도 했고, 이번 기회를 통해 학과에 스터디 문화가 조성되었으면 하는 바람에서 비롯</strong>되었다. </p>
<p>알고리즘 문제 풀이는 취업을 준비하는 <strong>신입 개발자에게 필수적인 사항</strong>이 되었다. 대부분의 기업 공개 채용에서는 <strong>알고리즘 코딩 테스트</strong>를 요구하게 되고, 해를 거듭할 수록 <strong>난이도가 상승</strong>하기 때문이다. 어떻게 보면, 알고리즘만 잘하면 기업 입사가 그리 어렵지 않다. 그러나 또 다른 한 편으로 보면 그 <strong>알고리즘 하나 잘하기가 그렇게 어렵다.</strong> 그래도 다행인 것은 노력의 영역 안에 있다는 점이다. 재능이 없어도, 점진적인 노력이 뒤따른다면 대기업 입사 가능한 수준으로 알고리즘 문제 해결 능력을 길러볼 수 있다고 생각한다.</p>
<p>알고리즘 스터디를 참여하게 되면, 다양한 것들을 얻을 수 있다고 생각한다. 대표적인 이점으로는, <strong>서로 으쌰으쌰하는 분위기를 조성하여 구성원 모두의 역량을 향상</strong>시킬 수 있다는 점이 있다. 그리고 꼭 정답 풀이가 딱 하나로 정해져있는 것이 아니기 때문에 같은 문제에 대한 다양한 풀이를 서로 공유해볼 수 있고, 이를 통해 <strong>다양한 관점으로 문제를 접근하는 능력</strong>을 길러볼 수 있다. 그리고 자신의 코드를 <strong>남들에게 이해하기 쉽게 설명하는 연습</strong>을 해볼 수 있는 기회이기도 하다. 필자 생각에 <strong>가장 큰 이점</strong>은, <strong>스터디 참여 자체가 습관 형성의 기회라는 점</strong>이다. 알고리즘 스터디 역시 꾸준히 하는 것이 중요한데, <strong>습관이 되지 않는다면 꾸준히 하는 것이 어렵다</strong>. 따라서 <strong>스터디 참여를 통해 강제적으로라도 습관을 형성해볼 수 있다는 것 자체가 좋은 기회</strong>로 와닿는다. 필자는 이러한 이점들을 챙기자는 취지로 스터디를 운영해왔다. 운영하는 입장에서도 분명 도움이 되는 부분들이 있기 때문에 마냥 봉사하기만 하는 것은 아니였다.</p>
<p><strong>점차 실력이 늘어가는 스터디원들의 모습이 운영의 원동력</strong>이 되어줬던 것 같다. 필자가 대단한 걸 해주진 못했지만, 그래도 스터디 활동이 조금이라도 좋은 영향을 줬다고 생각한다. 그렇게 생각하면 조금 뿌듯하기도 하고, <strong>필자의 능력이 그리 뛰어나지 못해 대단한 걸 해주지 못했다는 점에서 스터디원들에게 미안하기도 했다</strong>. 그래도 어느정도 학과에 <strong>스터디 문화가 자리잡은 것 같아, 그것만으로 보람</strong>을 느끼곤 한다. </p>
<p>입사 이후에는 스터디를 원활히 진행할 수 있을지 잘 모르겠어서, 스터디 리더를 다른 학부생에게 인수인계한 상태이다(?). 그러나 만약 입사 이후에도 스터디를 할 수 있는 여건이 된다면, 다시 이어나갈 예정이다.</p>
<br>

<hr>
<h2 id="sw-마에스트로-12기-활동">SW 마에스트로 12기 활동</h2>
<p>올해는 2020년 말에 &#39;<strong>졸업하기 전에 소마 한 번쯤은 해봐야지</strong>&#39; 하며 <strong>목표로 수립</strong>했었던 <strong>SW 마에스트로 12기 활동</strong>을 해볼 수 있었다. SW 마에스트로라는 확실한 목표를 이루기 위해 올해 초부터 소마 지원까지 알고리즘 문제를 가장 열심히 풀었던 것 같다. 그 결과 운 좋게 두 번의 코딩 테스트를 합격할 수 있었고, 면접까지 어찌저찌 합격하여 목표를 이뤘다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/bc0c247d-8de9-41b6-9a4b-6b46def79868/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.47.32.png" alt=""></p>
<blockquote>
<p><a href="https://velog.io/@haero_kim/%EC%A0%9C-12%EA%B8%B0-SW-%EB%A7%88%EC%97%90%EC%8A%A4%ED%8A%B8%EB%A1%9C-%ED%95%A9%EA%B2%A9-%EC%88%98%EA%B8%B0-%EB%B0%8F-%EC%A7%80%EC%9B%90-%EA%BF%80%ED%8C%81-jfg9n5hs">SW 마에스트로 합격 수기</a> 보러가기 </p>
</blockquote>
<p>많이들 오해하는 부분이 있는데, <strong>SW 마에스트로는 교육 형태의 프로그램이 절대 아니다</strong>. 이미 개발을 어느정도 할 줄 아는 사람들을 데려다가, 규모가 있는 프로젝트 개발에 온전히 집중시킬 수 있도록 하는 프로그램이다. 프로젝트 개발을 위해 전폭적인 지원을 해주고, 1팀당 전문가(현직자 등) 멘토 3명도 붙여준다. 따라서 실무에선 어떤 식으로 프로젝트를 진행하고 관리하는지와 같은 실무에서의 노하우를 전수받을 수 있는 좋은 기회도 갖게 된다.</p>
<p>총 3명이서 한 팀을 꾸리게 되는데, 필자는 축복받은듯 정말 최고의 팀원들을 만날 수 있었다. 서로에 대한 이해와 존중, 관용으로 가득찬 팀원들과 스트레스를 전혀 받지 않고 프로젝트를 진행할 수 있었다. 6개월동안 한 번쯤은 말싸움을 하며 다툴 법도 한데, 단 한 번도 그런 일이 없었다. 누군가 져주고 참은 것이 아니라, <strong>정말 팀 분위기 자체가 단연 최고였다. 팀원 모두 의사소통에 거리낌 없었고, 의욕이 타올라 매우 열심히 프로젝트를 개발</strong>했다.</p>
<p>그래서인지, <strong>프로젝트 진전이 매우 원활했고 기획한 내용들을 성공적으로 모두 완성</strong>할 수 있었다. 최종 발표 직전까지 다른 팀들의 피드백도 받아보며 최종 발표를 연습했고, <strong>노력의 결과 팀원 3명 모두 다 우수 연수생</strong>으로 선발될 수 있었다. 개발을 좋아하는 3명이 모여, <strong>누구보다 즐겁게 프로젝트를 진행</strong>했기 때문에 얻을 수 있었던 성과인 것 같다. 그리고 필자가 팀을 이끄는 <strong>팀장</strong>이었기 때문에, 필자가 이끌어간 팀이 이렇게 좋은 성과를 얻어볼 수 있었다는 점에서 팀원들에게 미안하기도 하고 너무 고맙기도 하다. 팀원들 덕분에 많은 것을 배울 수 있었다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/52c33261-6125-4b47-b73e-468fd9da0ee8/FullSizeRender.jpg" alt=""></p>
<p><strong>SW 마에스트로 활동을 하면서 정말 많은 것들을 얻었다</strong>. 전문가들의 멘토링을 받으며 개발 역량 뿐만 아니라 프로젝트 기획부터 관리까지 모든 프로세스를 경험해볼 수 있었기에 실무 역량또한 길러볼 수 있었다. 그리고 6개월동안 내가 개발하고 싶은 프로젝트를 전폭적인 지원을 받으며 개발할 수 있다는 점에서 인생 최고의 경험 중 하나로 자리 잡았다. 또한, &#39;<strong>세상엔 정말 대단한 사람들이 많구나, 나같은건 밑바닥 그 자체구나</strong>&#39; 하며 자기 객관화를 통해 큰 동기부여를 받아볼 수 있다는 점에서도 엄청난 매력이 있다. 그리고 무엇보다 가장 큰 것은 &#39;소중한 사람&#39;들을 얻은 것이다. 이렇게 좋은 인맥을 쌓을 수 있는 기회가 얼마 없는데, <strong>소중한 팀원들과 멘토님들을 얻을 수 있었단</strong> 점에서 SW 마에스트로를 하길 참 잘했다는 생각이 든다.</p>
<p>SW 마에스트로를 조금이라도 고민하고 있거나 그렇지 않더라도 대학교를 다니고 있는 분들이라면 졸업 전에 <strong>꼭 한 번 해보는 것을 추천</strong>한다. (물론 졸업 후에 해봐도 충분히 값진 경험일 것이다)</p>
<br>

<hr>
<h2 id="카카오-채용연계형-인턴십-코딩-테스트-합격">카카오 채용연계형 인턴십 코딩 테스트 합격</h2>
<p><img src="https://images.velog.io/images/haero_kim/post/7c6ad89b-b3f0-45a5-af13-0d5be7691bde/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.48.38.png" alt=""></p>
<p>시기상으로 SW 마에스트로를 최종 합격한 시점으로부터 얼마 되지 않아 기대도 안 했던 <strong>카카오 인턴십 코테 합격</strong> 소식을 들어볼 수 있었다. 알고리즘 문제 풀이 공부를 시작하고 난 이후, SW 마에스트로 코딩 테스트 합격에 이어 카카오 인턴십 코테 합격까지 했기 때문에 필자에겐 그 성과들이 엄청난 공부의 원동력으로 다가왔다. 노력이 결실을 맺기 시작한 것이다.</p>
<p>그러나, <strong>SW 마에스트로와 카카오 인턴 둘 중 하나를 포기</strong>해야 하는 상황이었다. 왜냐하면 SW 마에스트로에서는 연수 활동 이외의 영리 활동은 강력하게 제재하기 때문이다 (때문에 매달 장학금 100만원을 쥐어준다). 따라서 둘 중 하나를 포기해야 하는 선택의 기로에 놓였다.</p>
<p>필자는 극심한 고민 끝에, <strong>카카오 인턴십을 포기</strong>했다. 아무래도 운빨(?)로 붙은 감도 있고, 지금 상태로 카카오 인턴을 한다고 해도 채용으로 연계될만큼 역량이 뛰어나지 못하다고 생각했기 때문이다. 따라서, SW 마에스트로 활동을 하면서 충분한 실무 역량을 더 길러보기로 결심했다. 이 선택은 지금도 후회하지 않고, 다시 돌아가더라도 SW 마에스트로를 택할 것이다. <strong>그만큼 SW 마에스트로에서 얻은 것이 많다고 생각한다.</strong>   </p>
<blockquote>
<p>이래놓고 &#39;<strong>카카오에게 프로세스 진행 거절 메일 보내본 남자</strong>&#39; 라는 인생 최대 업적 타이틀을 얻었다. (농담)</p>
</blockquote>
<br>

<hr>
<h2 id="제-9회-k-hackathon-과학기술정보통신부-장관상">제 9회 K-Hackathon 과학기술정보통신부 장관상</h2>
<p>필자가 재학중인 학과는 3학년 2학기에 졸업작품 설계를 마치고, 4학년 1학기에 설계한대로 개발을 마치는 식으로 졸업작품을 만들게 된다. <strong>졸업작품은 좋은 팀원들을 만나 우여곡절 끝에 성공적으로 완성할 수 있었고, 한 번에 교수님들로부터 패스</strong>를 받을 수 있었다. 그러나 <strong>졸업 심사 통과만 받고 갖다버리기엔 너무 열심히 만든 작품</strong>이었기 때문에, 이를 활용하여 대회를 출전하기로 했다. 마침 <strong>과학기술정보통신부에서</strong> 주최하는 &#39;<strong>K-Hackathon</strong>&#39; 이라는 전국대회를 발견하여, 참가 신청을 했다.</p>
<p>대회는 <strong>예선 → 본선 → 결선</strong> 형태로 진행이 됐는데, 주어진 기간동안 작품을 조금씩 디벨롭해가며 순조롭게 결선까지 갈 수 있었다. 결선까지만 가도 최소 장려상이 확보된 상태이다. 따라서 결선 진출 소식에 팀원들과 기뻐했던 기억이 있다.</p>
<p>사실 큰 상에 대한 욕심은 내지 않았다. SW 마에스트로, 취업준비, 동아리 등 팀원 모두 각자의 사정으로 대회에 오랜 시간을 투자할 수 없는 노릇이었고, 전국 대회이기 때문에 결선 진출 팀들의 수준이 매우 높아보여 주눅들기 마련이었기 때문이다. 장려상에도 만족할만한 처지였지만, <strong>자투리 시간을 활용해서 최대한 대회 준비</strong>를 해보았다. </p>
<p><img src="https://images.velog.io/images/haero_kim/post/d6052591-5ca4-46ad-9df7-25f7bc2223ad/IMG_4837.jpg" alt=""></p>
<p>그 결과 상상도 안 했던 <strong>대상</strong>, <strong>과학기술정보통신부 장관상</strong>을 받을 수 있었다. 아이디어나 기술 난이도, 시장성 및 수익성으로 보았을 때, <strong>복합적으로 좋은 점수</strong>를 받아볼 수 있었던 것 같다. 개인적으로, SW 마에스트로 프로젝트를 기획하면서 얻은 인사이트를 녹여냈던 것이 강한 승부수로 작용했다고 생각한다. 장려상 발표에 우리 팀 이름이 불리지 않고, 우수상 최우수상에도 우리 팀 이름이 불리지 않아 점점 심장이 뛰었던 기억이 있다. <strong>힘들게 오랜 기간을 걸쳐 완성한 졸업작품인만큼, 좋은 성과까지 얻어볼 수 있었기에 무척이나 기뻐했다</strong>. 팀원들도 같은 심정이었을 것이다. 팀원들이 아니였다면 얻지도 못했을 성과인만큼, 팀원들에게도 매우 고맙다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/e4f423f3-0fa4-49ad-9868-c5d163d6da7e/IMG_3525.jpg" alt=""></p>
<p>(그리고 주접 대마왕 필자의 담당 교수님 덕분에 현수막에도 이름이 걸려졌다..)</p>
<br>

<hr>
<h2 id="취업-뽀개기">취업 뽀개기</h2>
<p>2021년 가장 큰 이슈였다. <strong>대학교 4학년들에게 찾아오는 고통의 취준 기간</strong>이다. 필자가 말하는 취업 준비라 함은, 이력서 및 포트폴리오 정리나 기업 탐색, 알고리즘 문제 풀이 및 기술 면접 준비 등의 과정을 일컫는 말이다. 만일 프로젝트 경험이 부족한 상태였더라면 취업 준비 기간을 조금 더 늘렸을테지만, 그나마 프로젝트는 꾸준히 해왔기 때문에 한 눈에 보기 좋게 포트폴리오 형태로 정리하고 그 속에 이야기를 녹이는 것이 관건이었다.</p>
<p>그러나 보통의 &#39;<strong>네카라쿠배</strong>&#39;라고 불리우는 기업들의 신입 공채에선 포트폴리오를 보여주기도 전에 <strong>고난이도의 코딩 테스트</strong>가 따르곤 했다. 필자는 알고리즘 문제 풀이를 약 6개월동안 꾸준히 했으나, 노력이 부족했던 것인지 <strong>하반기</strong> <strong>대기업 코딩 테스트들은 모두 광탈</strong>하고 말았다. 위에 기재한 카카오 인턴십 코딩 테스트 합격 이후로 마가 꼈는지, 모든 코딩 테스트를 광탈했다. 절망을 하려던 참, 순전히 필자의 노력 부족이라고 생각되었기 때문에 겸허히 결과를 받아들였다.</p>
<p>그렇게 지원한 모든 <strong>대기업 공개채용을 광탈</strong>하고, 마음은 추스린 뒤 <strong>스타트업 혹은 대기업의 수시 채용 전형</strong>을 노리고자 했다. 때문에 이력서와 포트폴리오를 더욱이 신경쓰곤 했다. 필자가 정해놓은 기업 선정 기준에 의거하여 다양한 기업들을 지원했고, <strong>서류에 많은 공을 들인 탓인지 서류전형은 거의 모두 합격</strong>했었다.  (2주 정도를 서류 완성에 투자했었다)</p>
<p>수시 채용은 보통 알고리즘 테스트보단, <strong>사전과제 및 라이브 코딩</strong>을 보는듯 했다. 이러한 전형은 문제 출제 스펙트럼이 워낙 넓어 그 날의 운에 맡기는 것이 상책이다. 기업의 평판이나 수준과 상관없이 채용 절차의 난이도는 들쑥날쑥이었고, 절차도 워낙 다양해서 <strong>풍부한 경험</strong>을 해볼 수 있었다. 워낙 채용 절차는 스펙트럼이 폭넓기 때문에, 어느 한 자료를 맹신하여 준비하기보단 많은 경험이 중요하다고 생각됐다. </p>
<p><strong>시간 흐름상으로 놓고보았을 때 면접 경험이 부족했을 즈음 면접을 보았던 기업들은 면접 광탈이 허다했고, 경험이 점차 쌓이면서 면접 전형 합격을 하나둘씩 이뤄낸 것 같다.</strong> 그러던 중 스포츠 테크 기업인 <strong>카카오VX</strong> 라는 기업을 처음으로 <strong>최종합격</strong>했고, 이를 원동력 삼아 더욱 열심히 취준에 임했다. 그러고는 이러한 경험들을 밑거름 삼아, 마지막으로 <strong>카카오페이</strong>라는 기업의 채용 절차를 밟아 <strong>최종합격</strong>을 할 수 있었던 것 같다. 따라서 필자와 비슷한 취업준비생들에게 &#39;<strong>조급해하지말고 최대한 면접 경험을 쌓아보라</strong>&#39;고 조언해주곤 한다. <strong>당연한 이야기지만, 경험이 쌓일수록 점차 능숙해질테니까.</strong></p>
<p><img src="https://images.velog.io/images/haero_kim/post/3470144a-46eb-41b7-a086-acc776d3985d/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-27%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.16.30.png" alt=""></p>
<p>필자의 취업준비 기간은 <strong>정말 스스로에게 가혹</strong>했던 것 같다. 다양한 활동들을 함께 병행했기 때문이다. 게다가 <strong>대기업들의 채용</strong> 시즌이 <strong>SW 마에스트로 활동 통틀어 가장 바쁜 중간평가 ~ 최종평가 기간과 겹치기</strong>도 했고, <strong>수시채용도 꾸준히 지원</strong>해보고 있었기 때문이다. 그렇다고 <strong>SW 마에스트로 활동을 손놓는 것은 팀원들에게 엄청난 민폐를 끼치는 행위</strong>이기도 하고, SW 마에스트로 활동을 통해 <strong>얻는 것이 정말 많다고 느꼈기</strong> 때문에 활동 포기는 생각조차 안 했다. 따라서 <strong>프로젝트 개발은 개발대로, 취업 준비는 취업 준비</strong>대로 정말 온몸을 불살랐다.</p>
<p>그럼에도 필자는 힘들다는 생각이 앞서지 않았다. <strong>결과는 노력을 배신하지 않을 것을 알고 있기 때문이다</strong>. 내가 이렇게 노력을 쏟으면 분명 이에 <strong>상응하는 결과</strong>가 따라올 것이고, 결국 필자는 이 결과를 향해 나아가고 있기 때문에 <strong>노력하는 과정이 그렇게 힘들지 않았다</strong>. (<del>육체적으로 힘든 것 빼면 ㅎㅎ</del>) 후회없게 노력해보자는 마인드였다.</p>
<p>실제로 잠자는 시간 줄여 <strong>취업준비와 소마 활동을 열심히 병행하여, SW 마에스트로 우수 연수생 선정과 카카오페이 취업 등 노력에 상응하는 결과</strong>가 따라와줬다. 물론 운도 어느정도 따라줬지만, <strong>이를 통해 얻은 교훈을 엄청난 원동력으로 삼아 개발자로서의 인생을 즐겁게 살아갈 수 있을 것</strong> 같다. <strong>노력을 하면 그에 따른 결과가 항상 뒤따른다</strong>는 교훈 말이다.</p>
<br>

<hr>
<h2 id="돌아보며">돌아보며</h2>
<p>필자는 다른 것은 몰라도, <strong>목표 의식</strong> 하나는 확실했던 것 같다. <strong>세워둔 목표에 대해서는 수단과 방법을 가리지 않고 이루고자 노력</strong>했다. 실제로 2020년에 세워둔 목표를 거의 모두 달성했다. 지금 돌아보니 매일같이, 꾸준히 루틴을 지켜왔던 것 같다. 일어나서 블로그 글 작성하고, 알고리즘 문제 풀고, 프로젝트 개발하고, CS 공부하고, 기업 탐색하고. 이것을 매일같이 반복했다. 사실 졸업 전 취업은 목표에도 없었는데, 열심히 달려온 결과 보너스로 이뤄낼 수 있었던 성과였던 것 같다. </p>
<p>모든 것들이 끝나고 입사를 앞둔 지금, <strong>무척이나 설레면서도 알 수 없는 공허함</strong>이 밀려온다. 몸을 불태워가며 달려왔던 세월들이 주마등처럼 지나간다. 그러나 지난 세월들이 결코 힘들지 않았다. 지금 이 기분을 느낄 수 있으리라 자신했기 때문이다. 정말 자신에게 가혹했고 혹독했지만, 꿈을 이뤘다. 그것만으로 모든 것이 치유된다.</p>
<p>취업 준비에 있어 가장 중요한 요소는 바로 ‘<strong>확실한 목표의식</strong>’인 것 같다. 목표의식이 명확하지 않다면, 그저 흘러가는대로 현실에 안주할 것이기 때문이다. 즉 <strong>동기부여나 원동력</strong>이 없다는 것이다. 어떤 형태이든 상관하지 않고, 어떤 뚜렷한 목표가 있어야 한다. 필자 역시 그 목표를 원동력 삼아 달려왔고, 끝까지 의심하지 않았다.</p>
<p>웃기게 보일 수도 있겠지만, 필자는 목표를 향해 달려가다 중간 중간 힘이 빠질 때면 <strong>판교를 방문</strong>하곤 했다. 신기하게도, 판교에는 말로 표현할 수 없는 특유의 정취가 있다. 판교역 출구로 나오면, 알 수 없는 가슴 설레오는 감정이 들곤 한다. 즐비해있는 IT 기업들을 보며, 멋있는 사원증을 매고 점심 시간 식사하러 나온 개발자들을 보며, &#39;저들과 함께하는 멋있는 개발자가 되기로 약속했잖아&#39; 라며 스스로에게 상기시키곤 했다. <strong>판교의 풍경은 다시 한 번 신발끈을 묶고 달릴 수 있는 힘이 되어줬다.</strong></p>
<p><strong>카카오페이 최종 합격 이후, 또 한 번 판교를 방문</strong>했다. 내가 해냈다는 듯이, <strong>앞으로 잘 부탁한다는 듯이</strong> 말이다. 이전에 판교를 방문했을 때와는 사뭇 다른 느낌이었다. 그렇게 로망으로 가득했던 판교로 출근한다는 사실이 믿기지 않는다. 그리고 <strong>내가 끝내 해냈다는 이 감정</strong>은 정말 이루 말 할 수 없다. </p>
<p>필자는 다음주면 <strong>신입 안드로이드 앱 개발자</strong>로서의 커리어가 시작된다. 대학교 4년이 훌쩍 지나가버려 많이 아쉽지만, 이젠 누릴 수 없는 백수 생활을 누리기 위해 최종 입사 확정이래 한 달 남짓 백수 기간을 격렬하게(?) 보냈다. (한 가지 아쉬운 점이라면 하필 거리두기 강화 때문에 노는 데에 제약이 생겼다는 점 ㅠㅠ) 그리고 <strong>아직 부족한 점이 수없이 많아, 지금까지 성과를 이뤄오면서 얻은 것들을 밑거름 삼아 입사 이후 발 빠르게 성장해보려 한다.</strong> 그렇게 또 2022년 목표들을 달성하기 위해 꾸준히 나아가본다.</p>
<p>2021년 올 한해는 정말 많은 것을 배울 수 있었고, 값진 성과들을 낼 수 있었다. 그런 한해동안 필자를 응원해주고, 지지해주고 버팀목이 되어준 주변 사람들에게 모두 감사하다는 말씀을 드리고 싶다. 그리고 2022년도 모두가 목표를 이룰 수 있는 한해가 되길 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오페이 신입 Android 개발자 최종합격 후기]]></title>
            <link>https://velog.io/@haero_kim/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%8E%98%EC%9D%B4-%EC%8B%A0%EC%9E%85-Android-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%B5%9C%EC%A2%85%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%8E%98%EC%9D%B4-%EC%8B%A0%EC%9E%85-Android-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%B5%9C%EC%A2%85%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 02 Dec 2021 09:15:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/haero_kim/post/3f8e75eb-3b46-42a9-97c2-d318f263ec7c/%E1%84%91%E1%85%B3%E1%84%85%E1%85%A6%E1%84%8C%E1%85%A6%E1%86%AB%E1%84%90%E1%85%A6%E1%84%8B%E1%85%B5%E1%84%89%E1%85%A7%E1%86%AB1.png" alt=""></p>
<p><strong>아직도 실감이 나지 않는다</strong>. 그저 꿈에만 그리던 기업이고, 더군다나 신입 공개채용으로 합격한 것이 아니기 때문이다. 지금 필자에게 날아오는 <strong>입사 안내 메일부터 필수 서류 구비 안내까지, 모든 과정이 믿기지 않는다.</strong></p>
<p>이번 포스팅에선 <strong>신입 안드로이드 개발자</strong>인 필자가 <strong>인재풀 전형 (수시채용)</strong>으로 <strong>카카오페이 Android 개발팀 크루</strong>로 합류한 과정과 후기를 담아보고자 한다.</p>
<h2 id="신입인데-신입-공채로-합격한게-아니라고">신입인데 신입 공채로 합격한게 아니라고?</h2>
<p><strong>필자는 올해 카카오 블라인드 신입 공채는 진작 떨어진 상태였다</strong>. 코딩테스트에서 벽을 느끼며 광탈했고, 다른 기업들의 코딩 테스트도 자꾸만 아쉽게 탈락하곤 했다. <strong>그러다보니 어느새 신입 공채 시즌이 끝나가고 있었고, 필자는 아쉬운대로 미리 대비해두었던 이력서와 포트폴리오를 기반으로 상시 채용을 노리고자 했다.</strong></p>
<p><img src="https://images.velog.io/images/haero_kim/post/c9ce4c4a-5d72-44ff-ad7b-539fa7aefd1f/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.12.03.png" alt=""></p>
<p>그러던 중 취업 준비와 별개로, 안드로이드 개발자들의 연례 행사 중 하나인 <strong>드로이드 나이츠</strong>라는 행사가 다가왔다. <strong>현직 안드로이드 개발자분들의 노하우와 지식을 전수받을 수 있는 다양한 세션</strong>을 들어볼 수 있어 매우 알차고 좋은 행사라고 생각되어, 매년 꾸준히 참가해왔다. 드로이드 나이츠의 가장 큰 특징 중 하나는, <strong>드로이드 나이츠를 후원 및 지원하는 스폰서사들이 부스를 운영</strong>한다는 점이다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/1302b40f-54c7-4046-a86f-5246726cac35/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.18.03.png" alt=""></p>
<p>올해 드로이드 나이츠에는 <strong>헤이딜러, 타다 (VCNC), 토스, 카카오페이</strong> 등의 회사가 스폰서로 참여했다. 스폰서사들이 운영하는 부스에서는 그 회사의 문화를 소개하거나, 취업 상담을 해주기도 했다. 평소에 <strong>카카오페이에 유난히 관심이 많았던 필자는, 카카오페이 부스를 방문</strong>하게 됐다.</p>
<h3 id="드로이드-나이츠-카카오페이-부스">드로이드 나이츠 카카오페이 부스</h3>
<p>카카오페이 부스에선, <strong>실제로 카카오페이에서 안드로이드 앱 개발자로 재직중이신 분께서 상담</strong>을 해주셨다. <strong>카카오페이의 개발 문화</strong>에 대해서 소개해주시기도 했고, 필자는 나름대로 카카오페이에서 커리어를 시작하고싶어 하는 마음과 지금까지 역량 향상을 위해 노력해왔던 것들을 어필하곤 했다. 사람 대 사람으로써 재미있게 대화했던 기억이 있다. 그러던 중, 그 분께서 이런 말을 하곤 했다.</p>
<blockquote>
<p>🧑🏻‍💻 : <strong>&#39;일반적으로 저희측에 전달되는 이력서들보다, 이렇게 안드로이드 개발자들의 행사에 참여하고 저희에게 관심 보여주신 게 훨씬 더 설득력있게 다가오네요! 신입이시니까 우선 저희 측 인재풀에 이력서 올려주세요. 팀에 현준님 이름 넘겨놓을게요!&#39;</strong></p>
</blockquote>
<p>그야말로 <strong>심쿵 모먼트</strong>였다. 아주 신나서 행사가 끝나자마자 카카오페이 채용 사이트로 달려갔다. 그러고는 &#39;<strong>인재풀 등록</strong>&#39; 메뉴를 통해 <strong>지원서와 이력서를 등록</strong>해두었다.</p>
<br>

<h2 id="닥쳐오는-일에-집중하던-중">닥쳐오는 일에 집중하던 중..</h2>
<p>인재풀 지원을 하고나서는, 해외 배송 기다리듯 지원 사실도 잊어버린 채 올해 초부터 활동하고 있던 <strong>SW 마에스트로 연수 활동에 완전히 집중</strong>했다. 팀원들과 진행하고 있던 프로젝트를 계속하여 개발했고, <strong>11월에 있는 최종평가를 향해 달려가고</strong> 있었다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/e96c8503-7de6-473c-a7f8-716bbe173ece/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.13.04.png" alt=""></p>
<p>그런데 인재풀 지원 이후 1-2달이 지난 <strong>11월 초에 난데 없이 카카오페이 서류 합격 연락</strong>이 왔다. 정말 기뻤지만, 한편으로는 <strong>SW 마에스트로 최종 평가를 앞두고 갑자기 연락을 받게 되어 걱정</strong>이 들기도 했다. 그나마 다행이었던 것은, <strong>프로젝트 개발이 어느정도 완료된 상태였고, 최종 발표자료를 준비하던 중</strong>이었다는 것이다.</p>
<p>카카오페이의 채용 프로세스는 <strong>&#39;서류 합격 - 사전 과제 - 1차 인터뷰 - 2차 인터뷰 - 최종 합격&#39;</strong> 순이었다. 따라서 필자는 사전과제 전형를 최대한 미룬 뒤, <strong>소마 최종 발표자료 준비를 마친 뒤 과제에 임할 수 있도록</strong>했다.</p>
<br>

<h2 id="사전-과제-전형">사전 과제 전형</h2>
<p>과제 세부 내용은 대외비 사항이라 공개할 순 없지만, <strong>살짝 까다로운 API 호출과 예외 처리 동작이 가미된 안드로이드 앱을 일주일 내로 개발</strong>하는 것이었다. 평소에 많이 개발해봤던 기능들이었어서, 과제 안내 당일 모두 요구사항에 맞게 개발할 수 있었다. 그렇다고 바로 제출하기보다는, <strong>개선할 부분이나 놓친 부분을 꼼꼼히 검토해가며 3일이 지나고 제출했다.</strong></p>
<p><strong>과제 요구사항대로 구현하는 것은 그리 어려운 난이도가 아니였기 때문에, 합격할 수 있었다.</strong></p>
<p><img src="https://images.velog.io/images/haero_kim/post/9d6f9f4e-57bb-46a5-b39d-852d229b2dce/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.45.20.png" alt=""></p>
<br>

<h2 id="대망의-면접-전형-시작-1차-인터뷰">대망의 면접 전형 시작, 1차 인터뷰</h2>
<p>1차 인터뷰는 <strong>안드로이드 개발팀의 실무진 인터뷰</strong>라고 안내를 받았다. 그리고 <strong>사전과제에 대한 코드 리뷰가 진행된다는 점도 안내해주셔서</strong> 확실하게 대비할 수 있는 부분이 생긴 것 같아 좋았다. <strong>면접에는 총 4명의 카카오페이 안드로이드 개발자분들이 참석</strong>해주셨다.</p>
<p>면접의 큰 흐름은 다음과 같았다.</p>
<blockquote>
<p><strong>자기소개 및 포트폴리오 질문 → 과제 코드 리뷰 → 코드 질문 → 안드로이드 및 코틀린 기본기 검증 + 약간의 CS 질문 → 카카오페이에 궁금한 점</strong></p>
</blockquote>
<p>면접관님들도 차례대로 자기소개를 해주셨던 것이 인상깊었다. 그런데 <strong>카카오페이 실무 면접이 매우 어렵다고 소문으로만 들어왔었는데, 왜 그러한 소문이 도는 것인지 단 번에 느낄 수 있었다.</strong> 자세한 질문들은 대외비 사항이라 기술하지 못하지만, <strong>정말 면접관 4분께서 쉴 틈 없이 질문을 주셨고</strong>, 면접이 끝나고서는 &#39;<strong>하얗게 불태웠다</strong>&#39;는 말 밖에 떠오르지 않았다. 조금 과장해서 면접 내내 오갔던 질문이 거의 <strong>7-80</strong>개에 가까웠다. 아주 어질어질했다.</p>
<p><strong>필자는 기본기 검증 질문에서 몇몇 질문에 대해 대답하지 못했다</strong>. <strong>정말 생각도 못한 부분들에서 질문</strong>이 들어오다보니, 모르는 부분에 대해 <strong>&#39;잘 모르겠습니다. 해당 개념에 대해 조금 더 공부해보겠습니다&#39;</strong> 라고 답하며 넘어가곤 했다. 면접이 끝나고나면 다들 그렇듯이, <strong>자신이 대답하지 못했던 질문에 대한 기억만 가슴을 후벼판다</strong>. 왜 그랬을까. 왜 조금 더 준비하지 않았을까. 실제로 필자는 &#39;카카오페이 안녕~&#39; 하고는 가슴 속에서 멀리 떠나보냈다. 그러고는 면접 때 대답하지 못한 개념들에 대해 블로그에 하나씩 정리해보곤 했다.</p>
<p>그런데, <strong>금요일 면접이 끝나고 주말이 지나 월요일에 말도 안되는 연락을 받았다</strong>.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/59711247-1f30-4ed6-975b-3a72e56ce101/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.47.49.png" alt=""></p>
<p>(참고로 카카오페이는 지원자들에게 전형 결과를 메일보다 전화로 먼저 안내해준다. 지원자에 대한 애정과 배려가 느껴졌다)</p>
<p><strong>생각도 못한 결과에, 채용 담당자님께 재차 &#39;...진짜요?&#39; 하고 물었던 것이 기억난다</strong> ㅋㅋㅋㅋ 아무리 생각해도 대답 못한 질문도 몇 개 있었는데 어떻게 뽑혔지 싶었다. 정말 <strong>면까몰</strong> (면접 결과는 까보기 전까지 아무도 모른다) 이라는 말이 와닿는 순간이었다. </p>
<p>1차 인터뷰 합격 이후 돌이켜보니 <strong>전체적인 면접 분위기는 매우 좋았던 것</strong> 같다. 포트폴리오 및 블로그에 대해 칭찬해주시기도 했고, 코드 리뷰를 할 때도 필자의 코드에 대한 비판은 없었으며(?), <strong>왜 이러한 코드를 작성했는지에 대해 적절히 잘 설명했던 것</strong> 같다. 코드리뷰 면접의 핵심은 코드에 대한 &#39;<strong>Why</strong>&#39; 를 어떻게 잘 풀어나가냐인 것 같다. </p>
<br>

<h2 id="생각도-못한-2차-인터뷰">생각도 못한 2차 인터뷰</h2>
<p>2차 인터뷰에 대해 안내받을 때, <strong>안드로이드 팀 리더님과 인사 담당자 한 분이 참석하는 인터뷰</strong>라고 안내받았다. 보통의 기업들은 마지막 면접의 경우 임원 및 인성면접으로, 기술 질문이 오가지 않는 경우가 많지만 CTO 도 아니고 <strong>안드로이드 팀 리더님이 들어오신다는 이야기를 듣고 무언가 쎄한 느낌</strong>이 들었다. 따라서 <strong>카카오페이 최종 면접 후기를 찾아보니, 아니나 다를까 인성 질문보다 기술 질문의 비중이 훨씬 더 크다는 이야기가 많았다.</strong></p>
<p>필자의 인성 면접 준비 마인드는 항상 &#39;<strong>인성 질문은 그 날의 나에게 맡겨야 한다</strong>&#39; 이다. 인성 질문의 스펙트럼은 말도 안 되게 넓기 때문에, 이를 따로 대비해두기 보다는 면접 당일 필자의 <strong>임기응변 능력에 순전히</strong> 맡기곤 한다. 따라서 <strong>기본적인 지원동기, 성격 장단점 등 국룰 질문들에 대해서만 간단히 대비</strong>를 하기로 했다. 따라서 면접 당일까지 <strong>기술 질문 위주로 대비</strong>를 했다.</p>
<p>기술 질문 대비는 <strong>1차 인터뷰때 대답하지 못했던 질문들, 그리고 블로그에 적었던 글들을 복기해보며 새로운 지식을 더 알려고 하기보단 기존의 지식들을 조금 더 보완해보자는 마인드</strong>로 임했다. 즉, <strong>기초적인 CS 지식, 코틀린 및 안드로이드 기본 지식</strong>들에 대해 중점적으로 대비해보았다.</p>
<p>2차 인터뷰의 큰 흐름은 다음과 같았다.</p>
<blockquote>
<p><strong>자기소개 및 지원동기, 포트폴리오 질문 → 안드로이드 팀 리더님의 기술 질문 폭격 → 인사 담당자님의 인성 질문 → 카카오페이에 궁금한 점</strong></p>
</blockquote>
<p><strong>면접관님이 던져주시는 기술 질문 스펙트럼에는 정말 천운</strong>이 깃들었다. 정말 <strong>필자가 대비한대로 나와주었고</strong>, 거의 모든 질문에 <strong>적절한 답을 할 수 있었다</strong>. 따라서 &#39;<strong>블로그를 허투루 쓰지 않으시네요!</strong>&#39; 라며 칭찬을 해주시기도 했다.</p>
<p>인성 질문은 솔직히 말하면 어떤 질문이 오갔는지 잘 기억이 나지 않는다. 그래도 나름대로 임기응변 능력이 잘 발휘한 것 같았다. 인사 담당자님께서 &#39;<strong>답변이 시원시원해서 좋네요 ㅎㅎ</strong>&#39;라고 말씀해주신 것이 기억에 남는다.</p>
<blockquote>
<p>기술 질문 : 인성 질문 = 7 : 3 정도 비율로 질문을 주셨다.</p>
</blockquote>
<p><strong>그런데 사람이라는게 어쩔 수가 없는게, 면접이 끝나고 계속해서 실수한 부분을 떠올리게 된다</strong>. 내가 제대로 답 한게 맞나, 나보다 더 뛰어난 지원자가 있지 않을까 하며 되뇌이곤 했다. 금요일에 면접을 보곤 했는데, 주말이 정말 야속했다. 그리고 <strong>심지어는 월요일에 결과가 나오지 않아 반쯤 포기한 상태</strong>였다. &#39;뽑을 계획이셨다면 1차 인터뷰 때처럼 월요일에 결과를 주시지 않았을까..&#39; 하며 말이다.</p>
<p><strong>그런 필자에게 화요일 낮 즈음, 또 한 번 말도 안되는 기적이 찾아왔다.</strong> </p>
<br>

<h2 id="그렇게-최종-합격까지">그렇게 최종 합격까지</h2>
<p><img src="https://images.velog.io/images/haero_kim/post/c50fcfc3-d662-4be1-979a-28f4b319ce65/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.48.11.png" alt=""></p>
<p>합격 연락을 받고 절대 실감이 안 났다. <strong>잠이 덜 깬 상태에서 받은 전화라 비몽사몽</strong>하기도 했고, 월요일 날 반쯤 포기했기 때문이다. <strong>정말이지, 기쁘다는 느낌이 들지도 않을 정도로 실감이 안 났다.</strong></p>
<p><strong>오퍼 레터, 입사 안내 메일 등을 받을 때마다 조금씩 실감이 나기 시작했고, 세상을 다 가진 기분이 들었다.</strong></p>
<blockquote>
<p>사실 카카오페이 최종 합격 이전에 다른 카카오 계열사에 합격을 한 상태였지만, 필자는 카카오페이를 보다 더 꿈꿔왔다보니 해당 기업에는 정중히 입사 취소 요청을 드렸다.</p>
</blockquote>
<p>이번 카카오페이 지원 프로세스를 통해 많은 것을 배울 수 있었다. <strong>신입이라고 꼭 신입 공채를 노리지 않아도 되고</strong>, <strong>경력 수시 채용</strong>이더라도 <strong>자신감과 깡으로 승부하면</strong> 분명 승산이 있는 것 같다. 그리고 카카오페이 지원 이전에 진행했던 <strong>많은 면접 경험들이 하나같이 소중한 밑거름</strong>이 되어준 것 같은 느낌이다. <strong>면접 경험의 중요성</strong>을 절실히 느꼈다.</p>
<br>

<hr>
<p>2022년 1월 즈음 카카오페이에 입사하여, 주니어 안드로이드 앱 개발자로서의 커리어를 힘차게 시작해보려 한다. 꿈에 그리던 기업인 만큼 설렘과 기대로 가득찬 상태다. 아직 부족한 점이 많기 때문에, 입사 후 많은 것을 배우고자 능동적으로 노력해봐야 겠다. 그리고 여기까지 올 수 있었던 것은 주변인들의 응원도 한몫 한 것 같다. 친구들, 지인들에게 다시 한 번 매우 감사의 말을 전하고 싶다.</p>
<blockquote>
<p><strong>궁금한 점 메일 혹은 댓글로 언제든지 남겨주세요!</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TCP 흐름제어 기법 살펴보기]]></title>
            <link>https://velog.io/@haero_kim/TCP-%ED%9D%90%EB%A6%84%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/TCP-%ED%9D%90%EB%A6%84%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 24 Nov 2021 08:46:26 GMT</pubDate>
            <description><![CDATA[<h2 id="tcp-의-특징">TCP 의 특징</h2>
<p><strong>TCP (Transmission Control Protocol)</strong> 는 이름에서 알 수 있듯이, 패킷 교환 방식 네트워크에서 <strong>패킷들이 안전하게 이동할 수 있도록 보장</strong>해주는 프로토콜이다. 크게 <strong>흐름제어, 혼잡제어</strong> 동작을 수행하는데, 이번 포스팅에선 그 중에서도 <strong>흐름제어</strong> 기법에 대해 살펴볼 것이다.</p>
<h2 id="흐름제어란">흐름제어란?</h2>
<ul>
<li><strong>송신측과 수신측의 데이터 처리 속도 차이를 해결하기 위한 기법</strong></li>
</ul>
<p>즉, <strong>수신측이 너무 많은 패킷을 수신받지 않도록 하기 위함</strong>이다. 수신측에는 패킷을 수신받는 버퍼의 크기가 정해져있는데, 만약 송신측의 전송 속도가 너무 빨라 한 번에 수많은 패킷을 수신받아버린다면, <strong>버퍼가 가득차 손실되는 패킷들이 발생</strong>할 것이다.</p>
<p>수신측의 처리 속도가 더 빠른 것은 문제가 되지 않지만, 위와 같은 상황처럼 송신측의 처리 속도가 더 빠를 경우 분명히 이를 제어할 수단이 필요하다. 따라서 TCP 에서는 <strong>흐름제어 기법을 사용</strong>한다.</p>
<blockquote>
<p>흐름제어의 기본 개념은, <strong>수신측이 송신측에게 자신의 상태를 계속하여 알리는 것</strong>이다. 즉, 데이터를 더 받을 준비가 되어있다는 피드백이 이루어졌을 때 송신측에서 패킷을 이어서 보내도록 하는 것이다.</p>
</blockquote>
<br>

<h2 id="패킷-전송-과정">패킷 전송 과정</h2>
<p>우선, TCP/IP 계층에 의거하여 <strong>패킷이 전송되는 과정</strong>을 간략히 살펴보자.</p>
<ol>
<li><p><strong>Application</strong> Layer : 송신측 Application Layer 가 소켓에 데이터를 입력</p>
</li>
<li><p><strong>Transport</strong> Layer : 데이터를 세그먼트로 감싸고 Network Layer 에 전달</p>
</li>
<li><p><strong>수신측 노드</strong>로 세그먼트가 <strong>전송</strong>됨. 동시에 <strong>송신측의 Send Buffer 와 수신측의 Receive Buffer 각각에 데이터가 저장</strong>됨.</p>
</li>
<li><p><strong>수신측 Application Layer</strong> 에서 준비가 되면, R<strong>eceive Buffer 에 있는 데이터를 읽기</strong> 시작함</p>
</li>
<li><p><strong>따라서, Receive Buffer 가 넘쳐나지 않도록 하는 것이 흐름 제어의 핵심!</strong></p>
<p> → 이를 위해 RWND(Receive Window, Receive Buffer 의 남은 공간) 을 송신측에 계속하여 피드백함</p>
</li>
</ol>
<br>

<h2 id="흐름제어-기법-종류">흐름제어 기법 종류</h2>
<h3 id="stop-and-wait">Stop-And-Wait</h3>
<p><strong>매번 전송한 패킷에 대한 확인 응답을 받아야만 그 다음 패킷을 전송</strong>하는 기법</p>
<p><img src="https://images.velog.io/images/haero_kim/post/d0d48e5e-55df-4936-8328-796fa8b53f56/263B7D4E5715ECEB32.png" alt=""></p>
<h3 id="sliding-window-go-back-n-arq">Sliding Window (Go-Back-n ARQ)</h3>
<p>수신측에서 설정한 <strong>윈도우 크기만큼 송신측에서 패킷 각각에 대한 확인 응답없이 세그먼트를 전송</strong>하게 하고, 데이터 <strong>흐름을 동적으로 조절</strong>하는 기법</p>
<blockquote>
<h4 id="전송은-되었으나-ack-을-받지-못한-byte-크기를-파악하기-위해-사용">전송은 되었으나, <strong>ACK 을 받지 못한 Byte 크기를 파악하기 위해 사용</strong></h4>
<p>LastByteSent - LastByteAcked ≤ ReceiveWindowAvertised
<strong>→ 마지막에 보낸 바이트 수 - 마지막에 확인된 바이트 수 ≤ 버퍼 남은 공간</strong>
(현재 이도저도 못하고 있는 패킷 수 ≤ 슬라이딩 윈도우 크기)</p>
</blockquote>
<p>윈도우 크기만큼 패킷을 모두 전송하고, 그 <strong>패킷들의 전달이 확인되는대로 해당 윈도우를 옆으로 슬라이딩</strong>하면서 그 다음 패킷을 전송하는 방식으로 동작한다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/193e1e14-c3bb-4049-a07f-df0a2c9de843/253F7E485715ED5F27.png" alt=""></p>
<p>TCP/IP 를 사용하는 모든 호스트들은 <strong>송신 그리고 수신을 위한 2개의 Window</strong> 를 가지고 있다. 호스트들은 실제 데이터를 보내기 전에 3-Way Handshake 를 통해 연결 설정을 해줄 때 <strong>수신 호스트의 Receive Window 크기에 자신의 Send Window 크기를 맞춰 설정</strong>한다.</p>
<p>세부 동작을 살펴보면 다음과 같다.</p>
<br>

<ol>
<li><strong>송신 버퍼</strong></li>
</ol>
<p><img src="https://images.velog.io/images/haero_kim/post/71089f91-675e-43dd-8874-0521a0a3c211/22532F485715EDF218.png" alt=""></p>
<ul>
<li>200 이전 바이트는 전송 성공했고, 확인 응답을 받은 상태</li>
<li>그런데 <strong>200~202 바이트는 아직 확인 응답을 받지 못한 상태</strong></li>
<li>203~211 바이트는 아직 전송이 되지 않은 상태</li>
</ul>
<br>

<ol start="2">
<li><strong>수신 윈도우</strong></li>
</ol>
<p><img src="https://images.velog.io/images/haero_kim/post/239f4dbf-7e74-4b55-bf23-1a92e9abcf36/25403A485715EE362B.png" alt=""></p>
<br>

<ol start="3">
<li><strong>송신 윈도우</strong></li>
</ol>
<p><img src="https://images.velog.io/images/haero_kim/post/b24a934c-a837-4ea3-aee5-f7165e871af5/2520244B5715EE6A14.png" alt=""></p>
<ul>
<li><strong>수신 윈도우보다 작거나 같은 크기로 송신 윈도우를 지정</strong>하여 흐름제어</li>
</ul>
<br>

<ol start="4">
<li><strong>송신 윈도우 슬라이딩 (이동)</strong></li>
</ol>
<p><img src="https://images.velog.io/images/haero_kim/post/b25f6bcf-0668-4ae0-af95-b6b951a3f9af/227DC8505715EEBA0A.png" alt=""></p>
<ul>
<li>Before 에서 <strong>203~204를 전송</strong>하면 수신측에서는 <strong>확인응답 203</strong>을 보내고, 송신측은 이를 받아 After 상태와 같이 송신 <strong>윈도우를 203~209로 이동</strong>함</li>
<li>After 는 <strong>205~209가 전송 가능한 상태</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kotlin] with vs run 명확한 차이점 톺아보기]]></title>
            <link>https://velog.io/@haero_kim/Kotlin-with-vs-run-%EB%AA%85%ED%99%95%ED%95%9C-%EC%B0%A8%EC%9D%B4%EC%A0%90-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Kotlin-with-vs-run-%EB%AA%85%ED%99%95%ED%95%9C-%EC%B0%A8%EC%9D%B4%EC%A0%90-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 23 Nov 2021 07:39:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><em><strong>옛날 옛적</strong>, 필자는 코틀린의 Scope Function 의 종류와 각각의 특징에 대해 포스팅을 한 적 있다. 그런데, 그 때 정말 글을 <strong>무책임하게 썼다는 생각</strong>이 문득 들었다. 왜냐하면 최근 본 면접에서 관련한 내용에 대해 물어보셨는데, 나름대로 잘 대답했다고 생각했으나 <strong>다시 제대로 공부할 필요가 있다는 피드백</strong>을 받았기 때문이다.</em></p>
</blockquote>
<p>문제의 파트는 다음과 같다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/e06c303c-a113-4550-8c19-03d91babe478/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-23%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.12.35.png" alt=""></p>
<p><code>with</code> 에 대해 설명할 때, <strong><code>run</code> 과 특징상 별 차이가 없다며 입맛대로 골라쓰라는 몰상식한 발언</strong>을 해버렸다. <del>지금 생각해도 멍청하다</del></p>
<p>따라서, 오늘 포스팅은 이 둘의 엄밀한 차이에 대해 다뤄보고자 한다.</p>
<h2 id="with">with</h2>
<p><code>with</code> 는 모양새를 보면 알 수 있듯, <strong>일반 함수</strong>다. 파라미터로 <strong>직접 객체를 입력받고, 객체를 사용하기 위한 람다 블록</strong>을 받는다. 이렇게 생겼다.</p>
<pre><code class="language-kotlin">fun &lt;T, R&gt; with(receiver: T, block: T.() -&gt; R): R</code></pre>
<p>이렇게 <code>receiver</code> 로 객체를 입력받으면, <code>it</code> 이나 <code>this</code> 등 키워드 없이 <strong>객체의 속성을 참조하거나 변경</strong>할 수 있다.</p>
<pre><code class="language-kotlin">val person = Person(&quot;H43RO&quot;, 23)

with(person) {
    println(name)
    println(age)
    // 자신을 참조해야 하는 경우 this 키워드 사용
}

// H43RO
// 23</code></pre>
<p>따라서, <strong><code>with</code> 는 <code>non-null</code> 객체를 이용할 때</strong>만 사용할 수 있고 아무런 리턴을 할 수 없기 때문에 <strong>별다른 반환값이 필요하지 않을 때 사용</strong>한다.</p>
<p>즉, <strong>객체의 함수나 속성을 여러 번 호출</strong>할 때 <strong>코드들을 그룹핑</strong>하는 용도로 활용할 수 있다.</p>
<br>

<h2 id="run">run</h2>
<p><code>run</code> 은 두 가지 형태로 선언되어 있는데, 우선 첫 번째는 다음과 같다.</p>
<pre><code class="language-kotlin">fun &lt;T, R&gt; T.run(block: T.() -&gt; R): R</code></pre>
<p><code>with</code> 와 다른 점이라면, <strong><code>T</code> 의 확장함수로 선언</strong>되어 있다는 점이다. 확장함수이기 때문에 <code>with</code> 와 다르게 <strong>Safe Call 을 붙인다면 <code>null</code> 객체가 들어와도 <code>non-null</code> 검사를 하고 실행</strong>할 수 있다. </p>
<pre><code class="language-kotlin">val person = Person(&quot;H43RO&quot;, 23)
val ageNextYear = person.run {
    ++age  // Return
}

println(&quot;$ageNextYear&quot;)

// 24</code></pre>
<p>또한, <strong>마지막 실행문의 결과를 반환</strong>하게 된다.</p>
<p>따라서, <strong>객체의 특성이나 메소드 등을 활용하여 어떤 값을 계산</strong>할 필요가 있거나, <strong>여러 개의 지역변수 범위를 제한</strong>하고자 할 때 사용한다.</p>
<p>그리고 <strong>두 번째</strong> <code>run</code> 선언 방식은 다음과 같다.</p>
<pre><code class="language-kotlin">fun &lt;R&gt; run(block: () -&gt; R): R</code></pre>
<p>보이다시피 <strong>이것은 확장함수가 아니고, 블록에 입력값도 없다</strong>. 객체 속성을 이용하려는 상황에 사용되는 함수가 아니고, <strong>어떤 객체를 생성하기 위한 실행문들을 하나로 묶어 리더빌리티를 높이는 역할을 수행</strong>한다.</p>
<pre><code class="language-kotlin">val person = run {
    val name = &quot;H43RO&quot;
    val age = 23
    Person(name, age)  // Return
}</code></pre>
<p>이러면, <code>Person(name=&quot;H43RO&quot;, age=23)</code> 객체가 <code>person</code> 에 담기게 된다.</p>
<hr>
<p>알아본 것 처럼, <code>with</code> 와 <code>run</code> 은 엄밀히 따지면 차이가 분명하다. 선언 방식도 다르고, 그에 따라 용법의 차이가 있다.</p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://blog.yena.io/studynote/2020/04/15/Kotlin-Scope-Functions.html">https://blog.yena.io/studynote/2020/04/15/Kotlin-Scope-Functions.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kotlin] by 키워드의 역할 알아보기]]></title>
            <link>https://velog.io/@haero_kim/Kotlin-by-%ED%82%A4%EC%9B%8C%EB%93%9C%EC%9D%98-%EC%97%AD%ED%95%A0-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Kotlin-by-%ED%82%A4%EC%9B%8C%EB%93%9C%EC%9D%98-%EC%97%AD%ED%95%A0-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 22 Nov 2021 05:33:58 GMT</pubDate>
            <description><![CDATA[<p>디자인 패턴 중 <strong>Delegate Pattern</strong> 이라는 것이 있다. 코틀린의 <strong><code>by</code> 키워드</strong>는 해당 디자인 패턴 구현을 쉽게 할 수 있도록 도와주는 키워드이다. 그럼, Delegate 패턴에 대해 간략히 살펴보자. Delegate 의 사전적 정의는 다음과 같다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/dbbd9e90-629d-480a-8772-32b901edad1b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-22%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.33.37.png" alt=""></p>
<p>동사 뜻을 보면 &#39;<strong>위임하다</strong>&#39; 라고 나와있다. 어떤 일을 다른 이에게 떠넘기는 것이다. <strong>Delegate Pattern</strong> 은 <strong>어떤 기능을 자신이 수행하지 않고 다른 객체가 수행하도록 하는 패턴</strong>이다. 즉, <strong>기능을 위임</strong>시키는 것이다. </p>
<p>이 Delegate Pattern 을 설명할 땐 <strong>상속과 구성 (Inheritance &amp; Composition) 의 차이</strong>에 대한 이야기가 필연적이다. 상위 클래스의 요소들을 사용할 때 상속 혹은 구성을 사용할 수 있다. <strong>상속은 모든 요소를 물려받기 때문에 변수나 메소드 등을 다시 구현할 필요가 없어 편리</strong>하지만, 객체의 <strong>유연성이 떨어진다</strong>는 치명적인 <strong>단점</strong>이 있다. 만약 상위 클래스가 변경된다면, 하위 클래스 역시 반드시 고쳐져야 한다. </p>
<p>따라서 유연성을 높이기 위해 , <strong>구성 (Composition) 관계</strong>를 통해 상위 클래스를 이용하는 것을 권장한다. 상속이 아닌 <strong>객체 소유로써 상위 클래스의 요소를 활용</strong>하는 것이다. </p>
<blockquote>
<h4 id="💡-상속과-구성-관계-차이">💡 상속과 구성 관계 차이</h4>
<p>상속 : <code>is-a</code>
구성 : <code>has-a</code></p>
</blockquote>
<p>Delegate Pattern 은 <strong>구성 (Composition) 을 활용한 패턴</strong>이다. 구성을 통해 특정 객체를 소유하고, <strong>모든 동작</strong>들을 <strong>소유하고 있는 객체에게 모두 위임하는 형식</strong>이다.</p>
<br>

<h2 id="delegate-pattern-예제">Delegate Pattern 예제</h2>
<blockquote>
<h4 id="⛔️-예제-코드는-아래-블로그-글에서-따왔음을-밝힙니다">⛔️ 예제 코드는 아래 블로그 글에서 따왔음을 밝힙니다</h4>
<p><a href="https://codechacha.com/ko/kotlin-deligation-using-by/">https://codechacha.com/ko/kotlin-deligation-using-by/</a></p>
</blockquote>
<p>간략히 Delegate Pattern 사용 예제를 살펴보자. 먼저, <strong><code>IWindow</code> 라는 인터페이스</strong>가 있다고 가정해보자.</p>
<pre><code class="language-kotlin">interface IWindow {
    fun getWidth() : Int
    fun getHeight() : Int
}</code></pre>
<p>그리고 위 인터페이스를 구현한 <strong><code>TransparentWindow</code></strong> 라는 클래스가 있다.</p>
<pre><code class="language-kotlin">open class TransparentWindow : IWindow {
    override fun getWidth(): Int {
        return 100
    }

    override fun getHeight() : Int{
        return 150
    }
}</code></pre>
<p>마지막으로 <strong><code>UI</code> 라는 클래스</strong>가 있는데, 이는 <strong><code>TransparentWindow</code> 를 상속받진 않으나 <code>IWindow</code> 를 구현</strong>했고, <strong>Composition</strong> 관계로 <code>TransparentWindow</code> 를 받기 위해 <strong><code>mWindow</code> 객체</strong>를 갖게 된다. 그리고 <strong>Delegate Pattern</strong> 을 구현하기 위해 모든 메소드에 대해서 <strong><code>mWindow</code> 이 갖고있는 메소드를 호출함</strong>으로써 기능을 위임시키게 된다.</p>
<pre><code class="language-kotlin">class UI(window: IWindow) : IWindow {
    val mWindow: IWindow = window

    override fun getWidth(): Int {
        return mWindow.getWidth()
    }

    override fun getHeight(): Int {
        return mWindow.getHeight()
    }
}</code></pre>
<p>이들을 활용하여 다음과 같이 활용해볼 수 있을 것이다.</p>
<pre><code class="language-kotlin">fun main() {
    val window: IWindow = TransparentWindow()
    val ui = UI(window)
    System.out.println(&quot;Width : ${ui.getWidth()}, height: ${ui.getHeight()}&quot;)
}

// Width : 100, height: 150</code></pre>
<p>Delegate Pattern 을 활용하면 <strong>상속을 사용하지 않아 유연성을 갖춘 채로</strong> <strong>상위 클래스의 요소들을 사용할 수 있으며, 새로운 기능을 자유자재로 덧붙여 사용할 수 있다는 장점</strong>이 있다.</p>
<blockquote>
<p>그러나 가만 살펴보면, Delegate Pattern 을 사용할 때 <strong>모든 메소드에 대해 일일히 Wrapper 메소드를 작성해줘야 함</strong>을 알 수 있다. 지금 예제에서는 메소드가 2개이기에 가뿐해보이지만, 만약 <strong><code>IWindow</code> 의 메소드가 계속 늘어난다면 그만큼 보일러 플레이트 코드가 증가할 것</strong>이다.</p>
</blockquote>
<br>

<h2 id="by-키워드의-역할"><code>by</code> 키워드의 역할</h2>
<p>따라서, 코틀린에서는 Delegate Pattern 구현 시 이러한 <strong>보일러 플레이트 코드들을 줄이기 위해서 <code>by</code> 키워드를 제공</strong>하게 된다. 아래처럼 <code>by</code> 키워드를 사용하게 되면, <strong>컴파일러가 자동으로 Delegate Pattern 코드를 작성</strong>해주게 된다. 따라서 <strong>일일히 <code>IWindow</code> 의 메소드들을 구현해줄 필요가 없다.</strong></p>
<pre><code class="language-kotlin">class UI(window: IWindow) : IWindow by window { }</code></pre>
<p>위와 같이 <code>UI</code> 클래스를 수정해도, 동작상 변화가 없는 것을 확인할 수 있다.</p>
<h3 id="java-로-변환된-코드">Java 로 변환된 코드</h3>
<p>그럼, <code>by</code> 키워드가 어떻게 자동으로 Delegate Pattern 을 구현해주는 지 살펴보기 위해 자바로 변환된 코드를 살펴보도록 하자.</p>
<pre><code class="language-kotlin">public interface IWindow {
   int getWidth();

   int getHeight();
}

public class TransparentWindow implements IWindow {
   public int getWidth() {
      return 100;
   }

   public int getHeight() {
      return 150;
   }
}

public final class UI implements IWindow {
   private final IWindow $$delegate_0;

   public UI(@NotNull IWindow window) {
      Intrinsics.checkParameterIsNotNull(window, &quot;window&quot;);
      super();
      this.$$delegate_0 = window;
   }

   public int getHeight() {
      return this.$$delegate_0.getHeight();
   }

   public int getWidth() {
      return this.$$delegate_0.getWidth();
   }
}
public final class Kotlin20Kt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, &quot;args&quot;);
      IWindow window = (IWindow)(new TransparentWindow());
      UI ui = new UI(window);
      System.out.println(&quot;Width : &quot; + ui.getWidth() + &quot;, height: &quot; + ui.getHeight());
   }
}</code></pre>
<p>구현을 보면, 메소드에 인자로 전달받은 <code>TransparentWindow</code> 객체와 동일한 이름의 메소드를 호출하도록 구현되어 있는 것을 확인할 수 있다. 결국은 <code>IWindow</code> 에 대한 메소드들이 모두 구현되어 있다. <code>by</code> 키워드가 이러한 수고를 덜어준 것이다.</p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://codechacha.com/ko/kotlin-deligation-using-by/">https://codechacha.com/ko/kotlin-deligation-using-by/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 다크 모드 스타일 비활성화 하기]]></title>
            <link>https://velog.io/@haero_kim/Android-%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EC%8A%A4%ED%83%80%EC%9D%BC-%EB%B9%84%ED%99%9C%EC%84%B1%ED%99%94-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Android-%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EC%8A%A4%ED%83%80%EC%9D%BC-%EB%B9%84%ED%99%9C%EC%84%B1%ED%99%94-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 01 Nov 2021 09:04:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="keepit---당신을-위한-똑똑한-쇼핑-리마인더-킵잇"><strong>KeepIt! - 당신을 위한 똑똑한 쇼핑 리마인더, 킵잇!</strong></h4>
<p><a href="https://play.google.com/store/apps/details?id=com.haero_kim.pickmeup">https://play.google.com/store/apps/details?id=com.haero_kim.pickmeup</a></p>
</blockquote>
<p>최근에 필자는 <strong>킵잇</strong> 이라는 안드로이드 앱을 출시한 적 있다. (<del>많이 다운받아주세요</del>) 그러나 출시 이후 몇몇 사용자들에게서 <strong>중대한 버그</strong>를 보고받았다.</p>
<h3 id="리젝-없이-출시-성공-그런데">리젝 없이 출시 성공! 그런데...</h3>
<p><strong><em>&#39;해로님, 이거 원래 UI 가 이런가여?&#39;</em></strong></p>
<p><img src="https://images.velog.io/images/haero_kim/post/4cfaabd7-1cf1-4e66-8a0a-acfa9e423831/KakaoTalk_Photo_2021-11-01-17-41-48.jpeg" alt=""></p>
<p>아니다... 아니다... 절대 아니다... 원래 앱의 모습은 아래와 같다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/12d25dcf-4532-44ed-9fd5-0de74cc345fc/%E1%84%8F%E1%85%B5%E1%86%B8%E1%84%8B%E1%85%B5%E1%86%BA%E1%84%80%E1%85%B3%E1%84%85%E1%85%A2%E1%84%91%E1%85%B5%E1%86%A8.png" alt=""></p>
<p>게다가 <strong>글씨 색깔조차 흰 색으로 되어버려서 흰 배경에서는 아무 글씨도 안 보이는 문제</strong>까지 발생했다. 무언가 분명히 잘못됐다! 정말 아차 싶었다. 원인은 바로 <strong>&#39;다크모드 미구현&#39;</strong>이었다. 이러한 멍청한 짓을 하게 된 계기는 다음과 같다.</p>
<br>

<h2 id="night-style">Night Style</h2>
<p><img src="https://images.velog.io/images/haero_kim/post/aa78b3a4-6ef6-42a7-844b-f244a3b4dced/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-01%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.44.22.png" alt=""></p>
<p>안드로이드 앱은 스타일을 지정해줄 때 기본적으로 <strong>Day와 Night를 구분하여 작성</strong>한다. 이는 라이트 / 다크 모드 지원을 위함이다. 그러나 필자는 다크모드를 별로 선호하지 않는 탓에 <strong>테스트 기기를 항상 라이트 모드로 구동</strong>해둔 상태였고, <strong>테스트할 때 다크모드를 완전히 망각</strong>하고 있었던 것이다. </p>
<pre><code class="language-kotlin">&lt;resources xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;
    &lt;!-- Base application theme. --&gt;
    &lt;style name=&quot;Theme.App&quot; parent=&quot;Theme.MaterialComponents.DayNight.DarkActionBar&quot;&gt;
        &lt;!-- Primary brand color. --&gt;
        &lt;item name=&quot;colorPrimary&quot;&gt;@color/purple_200&lt;/item&gt;
        &lt;item name=&quot;colorPrimaryVariant&quot;&gt;@color/purple_700&lt;/item&gt;
        &lt;item name=&quot;colorOnPrimary&quot;&gt;@color/black&lt;/item&gt;
        &lt;!-- Secondary brand color. --&gt;
        &lt;item name=&quot;colorSecondary&quot;&gt;@color/teal_200&lt;/item&gt;
        &lt;item name=&quot;colorSecondaryVariant&quot;&gt;@color/teal_200&lt;/item&gt;
        &lt;item name=&quot;colorOnSecondary&quot;&gt;@color/black&lt;/item&gt;
        &lt;!-- Status bar color. --&gt;
        &lt;item name=&quot;android:statusBarColor&quot; tools:targetApi=&quot;l&quot;&gt;?attr/colorPrimaryVariant&lt;/item&gt;
        &lt;!-- Customize your theme here. --&gt;
    &lt;/style&gt;
&lt;/resources&gt;</code></pre>
<p>따라서 <strong>Night 스타일 기본 코드가 위처럼 처음 그대로</strong> 남아있었고, 앱이 갑자기 <strong>보라색 돌연변이마냥 괴이하게 변형</strong>되어 버린 것이다. (물론 라이트모드에선 정상 동작했다)</p>
<p>킵잇은 아직 다크모드를 지원하지 않기 때문에, 조속히 <strong>다크모드 비활성화 업데이트가 필요</strong>했다. 재빨리 업데이트를 제출하고나서 검토가 너무 오래걸리길래 발을 동동 구르곤 했다. 무려 2일 만에 패치가 이루어졌다 ㅠㅠ</p>
<p>암튼, 본론으로 돌아가서 <strong>다크 모드를 비활성화</strong>하는 방법을 알아보자.</p>
<br>

<h2 id="다크-모드-비활성화">다크 모드 비활성화</h2>
<p>꽤나 간단한 방법으로 다크 모드를 비활성화할 수 있다. <strong>최상위 실행단위인 <code>Application</code> 클래스의 <code>onCreate()</code></strong> 에, 아래와 같은 코드를 기입해준다.</p>
<pre><code class="language-kotlin">AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)</code></pre>
<p>만약 이를 진입점 <strong><code>Activity</code></strong> 에 적용하면 <strong>여러 번 실행</strong>되어 버리기 때문에, <strong>꼭 최상위 실행단위인 <code>Application</code> 클래스에 추가</strong>해주도록 하자. 최종적인 형태는 다음과 같다.</p>
<pre><code class="language-kotlin">override fun onCreate() {
    super.onCreate()
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}</code></pre>
<h3 id="그-다음은요">그 다음은요?</h3>
<p>... 없다. 저렇게만 해주면 <strong>다크 모드 활성화 상태</strong>에서 앱을 키더라도, <strong>Day 스타일이 적용되어 표시</strong>된다. 이 간단한 작업을 안 해주고 출시를 해버려서, 꽤나 많은 사용자들에게 안 좋은 첫인상을 남기고 말았다 🤦🏻‍♂️</p>
<p>스타일을 나누어 작성하는 것이 기본값인 만큼 앞으로 앱 출시 전에 꼭 조심해야 할 것 같다.</p>
<hr>
<p>물론 킵잇도 추후 다크 모드 스타일을 제공해줄 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] DiffUtil 사용법 알아보기]]></title>
            <link>https://velog.io/@haero_kim/Android-DiffUtil-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Android-DiffUtil-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 31 Oct 2021 07:59:40 GMT</pubDate>
            <description><![CDATA[<h2 id="diffutil-의-등장-배경">DiffUtil 의 등장 배경</h2>
<p>안드로이드 앱을 개발하다보면 필수적으로 쓰이는 <strong><code>RecyclerView</code> 사용</strong>에 있어서, 동적으로 데이터가 변경되는 경우 우리는 <strong><code>notifyDataSetChanged()</code></strong> 한 줄로 리사이클러뷰를 갱신하곤 했다. 만약 아이템 딱 하나가 바뀌는 상황이더라도 <strong><em>귀찮아서</em></strong> <code>notifyDataSetChanged()</code> 를 통해 갱신하곤 한다. 그러나 이러한 행위는 <strong>성능에 치명적인 악영향</strong>을 미치게 된다. 그야말로 멍청한 짓이다 (<em>사실 필자 이야기다</em>). <del>사실 개발자한텐 너무너무 편하다.</del></p>
<p><code>notifyDataSetChanged()</code> 는 사실상 <strong>리스트를 싹 지우고 다시 처음부터 끝까지 객체를 하나하나 만들어 새로 렌더링하는 과정</strong>을 거치게 된다. 때문에 <strong>비용이 매우 크게 발생</strong>한다. 따라서 효율적으로 동적인 리사이클러뷰를 구성하는 방법이 필요했다.</p>
<p>이러한 이유로 등장한 것이 바로 <strong><code>DiffUtil</code></strong> 클래스이다. <strong>이전 데이터 상태와 현재 데이터간의 상태 차이를 계산</strong>하고, 반드시 업데이트해야 할 <strong>최소한의 데이터에 대해서만 갱신</strong>하게 된다. 데이터 업데이트 횟수를 최소한으로 가져가는 것이다.</p>
<blockquote>
<h4 id="💡-tmi">💡 TMI</h4>
<p>두 데이터간 차이 계산에는 Eugene W.Myners 의 Diff Algorithm 이 사용됐다고 한다.</p>
</blockquote>
<br>

<h2 id="diffutil-사용-방법">DiffUtil 사용 방법</h2>
<p><code>DiffUitl</code> 이 <strong>원래 목록과 새로 들어온 목록간의 차이를 계산</strong>하고 난 뒤 <strong><code>DiffUtil.Callback</code></strong> 이라는 추상 클래스를 콜백 클래스로 활용하게 된다. <code>DiffUtil.Callback</code> 은 <strong>4개의 추상 메소드와 1개의 일반 메소드</strong>로 이루어져있는데, 이러한 메소드를 오버라이딩하여 사용한다. (4개 추상 메소드의 경우 당연하게도 오버라이딩 필수)</p>
<blockquote>
<h4 id="getoldlistsize"><code>getOldListSize()</code></h4>
<p>이전(원래) 목록의 크기를 반환한다.</p>
</blockquote>
<blockquote>
<h4 id="getnewlistsize"><code>getNewListSize()</code></h4>
<p>새로 들어온 목록의 크기를 반환한다.</p>
</blockquote>
<blockquote>
<h4 id="areitemsthesameint-olditemposition-int-newitemposition"><code>areItemsTheSame(int oldItemPosition, int newItemPosition)</code></h4>
<p>두 아이템이 같은 객체인지 여부를 반환한다.</p>
</blockquote>
<blockquote>
<h4 id="arecontentsthesameint-olditemposition-int-newitemposition"><code>areContentsTheSame(int oldItemPosition, int newItemPosition)</code></h4>
<p>두 아이템이 같은 데이터를 갖고 있는지 여부를 반환한다. <strong><code>areItemsTheSame()</code> 이 <code>true</code> 를 반환할 때만 호출</strong>된다. 애초에 객체가 다르다면 데이터를 비교하는 것은 의미가 없기 때문이다.</p>
</blockquote>
<blockquote>
<h4 id="getchangepayloadint-olditemposition-int-newitemposition"><code>getChangePayload(int oldItemPosition, int newItemPosition)</code></h4>
<p>만약 <strong><code>areItemTheSame()</code>이 <code>true</code>를 반환하고, <code>areContentsTheSame()</code>이 <code>false</code>를 반환</strong>했다면 새로운 녀석이 들어왔다는 것으로 간주하고 해당 메소드가 호출되어 <strong>변경 내용에 대한 페이로드</strong>를 가져온다. 해당 메소드는 추상 메소드가 아니기 때문에 꼭 오버라이드할 필요는 없다.</p>
</blockquote>
<p>그럼, 한 번 만들어보자. 우선 <strong><code>DiffUtil.Callback</code> 을 상속받는 콜백 클래스</strong>를 만들어주고, 비교 대상을 지정해준다.</p>
<pre><code class="language-kotlin">class DiffUtilCallback(private val oldList: List&lt;Any&gt;, private val newList: List&lt;Any&gt;) :
    DiffUtil.Callback() {
    override fun getOldListSize(): Int = oldList.size

    override fun getNewListSize(): Int = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]

        return if (oldItem is Person &amp;&amp; newItem is Person) {
            oldItem.id == newItem.id
        } else {
            false
        }
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
        oldList[oldItemPosition] == newList[newItemPosition]
}</code></pre>
<p>그러고 난 뒤 리사이클러뷰의 어댑터에 아래와 같은 함수를 만들어두고, <strong><code>updateList()</code> 를 통해 새로 들어온 데이터</strong>를 집어넣게 되면, <code>DiffUtil.calculateDiff</code> 에 해당 데이터를 집어넣게 되고 인자로는 우리가 <strong>구현한 콜백 클래스 객체</strong>를 전달한다. </p>
<p>이후 <strong><code>diffResult.dispatchUpdatesTo(어댑터)</code></strong> 를 호출하게 되면, 최소한의 업데이트 연산으로 리사이클러뷰를 갱신해줄 수 있는 것이다.</p>
<pre><code class="language-kotlin">fun updateList(items: List&lt;Person&gt;?) {
    items?.let {
        val diffCallback = DiffUtilCallback(this.items, items)
        val diffResult = DiffUtil.calculateDiff(diffCallback)

        this.items.run {
            clear()
            addAll(items)
            diffResult.dispatchUpdatesTo(this@Adapter)
        }
    }
}</code></pre>
<p>생각보다 간단하지 않은가!? 메소드 이름들이 매우 직관적이라 편리한 것 같다.</p>
<br>

<h2 id="알아두어야-할-것들">알아두어야 할 것들</h2>
<h3 id="recyclerviewadapter-와의-연관성">RecyclerView.Adapter 와의 연관성</h3>
<p><code>DiffUtil</code> 은 <strong>리사이클러뷰 어댑터</strong>의 <strong>다양한 데이터 업데이트 관련 메소드를 활용</strong>한다.</p>
<ul>
<li>notifyItemMoved()</li>
<li>notifyItemRangeChanged()</li>
<li>notifyItemRangeInserted()</li>
<li>notifyItemRangeRemoved()</li>
</ul>
<h3 id="엄청-큰-데이터를-다루는-경우">엄청 큰 데이터를 다루는 경우</h3>
<p>목록이 매우 방대한 경우라면, <strong><code>DiffUtil</code> 연산이 오래 걸릴 수 있기 때문에 백그라운드 쓰레드</strong>에서 이를 실행하고, <strong>메인 쓰레드에서 <code>DiffUtil.DiffResult</code> 를 가져온 뒤 적용</strong>하는 것을 권장한다. (목록의 최대 사이즈는 2²⁶ 개 이다)</p>
<h3 id="예상-성능">예상 성능</h3>
<p><strong><code>DiffUtil</code></strong> 은 두 목록 간의 추가 및 제거 작업의 최소 수를 찾기 위해 O(N) 공간이 필요하다. 예상되는 성능은 O(N + D²) 정도이다.</p>
<ul>
<li>N: 추가 및 제거 된 항목의 총 수</li>
<li>D: 스크립트 길이</li>
</ul>
<blockquote>
<p>예상 성능 계산은 아래 포스팅에서 그대로 인용했음을 밝힙니다.
<a href="https://blog.kmshack.kr/RecyclerView-DiffUtil%EB%A1%9C-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81%ED%95%98%EA%B8%B0/">https://blog.kmshack.kr/RecyclerView-DiffUtil로-성능-향상하기/</a></p>
</blockquote>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://blog.kmshack.kr/RecyclerView-DiffUtil%EB%A1%9C-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81%ED%95%98%EA%B8%B0/">https://blog.kmshack.kr/RecyclerView-DiffUtil로-성능-향상하기/</a>
<a href="https://onemask514.tistory.com/48">https://onemask514.tistory.com/48</a>
<a href="https://taehyungk.github.io/posts/android-RecyclerView-DiffUtil/">https://taehyungk.github.io/posts/android-RecyclerView-DiffUtil/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LRU Cache 이해하기]]></title>
            <link>https://velog.io/@haero_kim/LRU-Cache-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/LRU-Cache-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 30 Oct 2021 07:22:53 GMT</pubDate>
            <description><![CDATA[<h2 id="cache">Cache</h2>
<p>캐시는 연산에 필요한 <strong>데이터, 값을 미리 갖다놓는 임시 메모리</strong>이다. 본래 CPU 에서 주기억장치, 보조기억장치까지 도달하는 비용은 매우 크다. 물리적으로도 거리가 멀다고 할 수 있다. 그런데 <strong>캐시의 경우 CPU 바로 옆에 딱 달라붙어있기 때문에 물리적으로 거리가 매우 짧아 접근 비용이 매우 적다</strong>. 따라서 자주 사용되는 값이나, 사용될 예정인 값을 <strong>미리 캐시에 적재해놓는다면 참조 시간을 대폭 줄여 성능을 높일 수</strong> 있다. </p>
<p><img src="https://images.velog.io/images/haero_kim/post/edeb68c6-3e72-47bf-9cc2-d208c2320cca/screenshot.png" alt=""></p>
<p>CPU 는 어떤 값이 필요할 때 <strong>가장 먼저 캐시를 방문</strong>하고 만약 캐시에 원하는 값이 있을 경우 이를 사용하는데, <strong>만약 없다면 주기억 장치를 방문</strong>하게 된다. 따라서, <strong>CPU 가 자주 사용하는 값, 필요로 하는 값 등을 적절히 캐시에 배치</strong>해두어야 한다. 즉, <strong>캐시 히트율을 높여야</strong> 성능을 높일 수 있는 것이다.</p>
<p>위의 설명만 봐도 캐시의 내용물들을 자주 갈아끼워질 필요가 있다. 캐시의 <strong>용량은 한정적인데다가 자주 사용되는 값 혹은 사용될 예정인 값은 시시각각 변하기</strong> 때문이다. 그런데 막무가내로 내용물을 갈아끼우면 캐시 히트율을 항상 높게 유지하기 어려울 것이고, 성능 악화로 이어질 수 있다. 따라서 <strong>캐시 히트율을 높게 유지하는 메모리 교체 알고리즘</strong>이 필요하다. 이를 위해 고안된 여러 알고리즘이 있고, 이번 포스팅에선 그 중 하나인 LRU 알고리즘을 적용한 <strong>LRU Cache</strong> 에 대해 알아보고자 한다.</p>
<br>

<h2 id="lru-least-recently-used">LRU (Least Recently Used)</h2>
<p>운영체제의 페이지 교체 알고리즘 중 하나이다. 페이지를 교체할 때 <strong>가장 오랫동안 사용되지 않은 페이지</strong>를 교체 대상으로 삼는 기법이다. </p>
<br>

<h2 id="lru-cache">LRU Cache</h2>
<p>그렇다면, <strong>LRU Cache 는 캐시에 공간이 부족할 때 가장 오랫동안 사용하지 않은 항목을 제거하고 새로운 녀석을 배치하는 형식으로 동작</strong>된다고 유추할 수 있다. LRU Cache 의 전제 이론은 &#39;<strong>오랫동안 사용되지 않은 항목은 앞으로도 사용되지 않을 가능성이 농후</strong>하기 때문에, <strong>가장 오랫동안 참조되지 않은 녀석을 캐시에서 제거</strong>하자&#39; 이다. 이 이론에 따라 캐시 메모리를 운영한다면 <strong>캐시 히트율을 높게 유지할 수 있다</strong>는 가정이 깔려있다. 실제로 성능이 입증됐으며, 가장 많이 사용되는 알고리즘이기도 하다.</p>
<h3 id="구현-방식">구현 방식</h3>
<p>LRU Cache 의 구현은 <strong>Double Linked List</strong> 를 통해 이루어질 수 있다. <strong>Head</strong> 에 가까운 데이터일 수록 <strong>최근에 사용된 데이터</strong>이고, <strong>Tail</strong> 에 가까울 수록 <strong>오랫동안 사용되지 않은 데이터</strong>로 간주한다. 따라서 새로운 데이터를 삽입할 때, <strong>Tail 값을 가장 먼저 삭제시키고 Head 에 데이터를 삽입하도록 하여 캐시 교체 시간 복잡도를 O(1)</strong> 로 갖게 된다.</p>
<p>그리고 만약 캐시에 적재된 어떤 데이터를 사용한 경우, <strong>해당 데이터를 Head 로 옮겨 가장 최근에 사용된 값임을 명시</strong>한다. 즉, 삭제 우선순위에서 멀어지게 하는 것이다.</p>
<p>이를 도식화하면 아래와 같이 표현할 수 있다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/8bde175e-366a-4973-9e28-373a9c370019/68747470733a2f2f6b2e6b616b616f63646e2e6e65742f646e2f6257377038492f6274717635686a6d4235452f58336c774d4e5150384e70324a746c326b516731454b2f696d672e706e67%20(2).png" alt=""></p>
<br>

<h2 id="번외-android-에서의-lru">[번외] Android 에서의 LRU</h2>
<p>안드로이드는 <strong><code>LruCache</code> 라는 자료구조</strong>를 제공해준다. 내부 구현을 보면 <strong><code>LinkedHashMap</code></strong> 을 사용한 모습을 확인해볼 수 있다. <strong>Key-Value</strong> 형식으로 값을 참조할 수 있게끔 하기 위함이다.</p>
<pre><code class="language-kotlin">public class LruCache&lt;K, V&gt; {
    @UnsupportedAppUsage
    private final LinkedHashMap&lt;K, V&gt; map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;
    private int maxSize;

        ...
}</code></pre>
<p><code>LruCache&lt;K, V&gt;</code> 는 아래와 같이 사용해볼 수 있다.</p>
<pre><code class="language-kotlin">val cache = LruCache&lt;String, Int&gt;(5)  // maxSize = 5

cache.put(&quot;A&quot;,0)  // [A]
cache.put(&quot;B&quot;,0)  // [A, B]
cache.put(&quot;C&quot;,0)  // [A, B, C]
cache.put(&quot;D&quot;,0)  // [A, B, C, D]
cache.put(&quot;E&quot;,0)  // [A, B, C, D, E] - A부터 E까지 캐싱 완료
cache.put(&quot;F&quot;,0)  // [B, C, D, E, F] - F를 캐싱하면, A는 제거됨
cache.put(&quot;D&quot;,0)  // [B, C, E, F, D] - D를 다시 캐싱하면 최근 참조된 상태로 변경
cache.get(&quot;C&quot;)    // [B, E, F, D, C] - C를 통해 캐시된 데이터 접근시 최근 참조된 상태로 변경</code></pre>
<p>이를 응용하면, 안드로이드에서의 <strong><code>Bitmap</code> 캐싱을 구현</strong>할 수 있다. 실제로 <code>Glide</code> 이미지 라이브러리도 비트맵 캐싱 방식을 활용하여 성능을 최적화하게 된다.</p>
<pre><code class="language-java">int cacheSize = 4 * 1024 * 1024; // 4MB
LruCache&lt;String, Bitmap&gt; bitmapCache = new LruCache&lt;String, Bitmap&gt;(cacheSize) {
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount();
    }
}</code></pre>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.charlezz.com/?p=44551">https://www.charlezz.com/?p=44551</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로세스 동기화 이야기]]></title>
            <link>https://velog.io/@haero_kim/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%8F%99%EA%B8%B0%ED%99%94-%EC%9D%B4%EC%95%BC%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EB%8F%99%EA%B8%B0%ED%99%94-%EC%9D%B4%EC%95%BC%EA%B8%B0</guid>
            <pubDate>Thu, 28 Oct 2021 18:06:23 GMT</pubDate>
            <description><![CDATA[<h2 id="critical-section-임계-영역">Critical Section (임계 영역)</h2>
<p><img src="https://images.velog.io/images/haero_kim/post/a5d193e1-2586-4a3f-a158-dc305d884004/25748E4E592E3B1E0F.png" alt=""></p>
<p>어떤 동일한 자원을 동시에 접근하는 작업을 실행하는 코드 영역을 <strong>Critical Section</strong> 이라고 한다. <strong>공유하는 변수를 사용한다든가, 동일한 파일</strong>을 읽고 쓰는 등의 작업에 해당한다. <strong>멀티 쓰레딩 환경</strong>에선 기본적으로 공유 자원의 동기화가 이루어지지 않기 때문에 이를 대처하지 않으면 <strong>분명 문제가 될 수 있다</strong>.</p>
<p>따라서, 많은 프로세스들이 <strong>Critical Section</strong> 을 함께 사용할 수 있는 <strong>어떠한 규칙을 설계</strong>해야 한다. 
임계영역 문제를 해결하기 위해 갖춰야 할 기본 조건은 아래와 같다.</p>
<ul>
<li><p><strong>Mutual Exclusion (상호 배제)</strong>
만약 프로세스 P1 이 임계영역에서 실행중이다? 그렇다면 다른 프로세스들은 임계영역에 진입하지 못한다.</p>
</li>
<li><p><strong>Progress (진행)</strong>
임계영역에서 실행중인 프로세스가 없고, 별도의 동작이 없는 프로세스들만 임계영역 진입 후보 자격이 있다.</p>
</li>
<li><p><strong>Bounded Waiting (한정된 대기)</strong>
프로세스 P1 이 임계영역에 들어가고 싶다고 신청하고 받아들여질 때까지 다른 프로세스들이 임계영역에 진입하게 되는 횟수는 한정적이어야 한다.</p>
</li>
</ul>
<p>그럼, 프로세스간의 임계영역 동기화를 위한 해결책 몇 가지를 알아보도록 하자.</p>
<hr>
<h2 id="locking">Locking</h2>
<p>동시에 공유 자원에 접근하는 것을 막기 위해 <strong>임계영역에 진입하는 프로세스는 Lock 을 획득하고, 임계영역을 빠져나왔을 때 Lock 을 릴리즈함</strong>으로써 두 개 이상의 프로세스가 동시에 임계영역에 진입할 수 없도록 하는 방법이다.</p>
<p>보통 상가 건물에 있는 식당에서 <strong>상가의 화장실을 이용하려는 경우 열쇠를 필요</strong>로 하는 경우가 있다. 그런데 만일 <strong>어떤 손님이 이미 열쇠를 챙겨 화장실에 간 경우, 다른 사람은 그 손님이 돌아올 때까지 화장실을 이용하지 못한다</strong>. 화장실을 임계영역으로 놓고 봤을 때, 이 상황은 <strong>Locking</strong> 매커니즘과 유사하다고 볼 수 있다.</p>
<br>

<h2 id="semaphores">Semaphores</h2>
<p><img src="https://images.velog.io/images/haero_kim/post/a2733297-1fdf-48d6-be0a-3cb02a2cebae/flag-semaphore.jpg" alt=""></p>
<p>세마포, 세마포어라고 읽는다. 영단어 뜻은 &#39;수기 신호&#39; 라는 뜻이다. 임계영역 문제를 제어하고 해결하기 위한 수기 신호, 즉 프로세스 <strong>동기화 도구</strong> 중 하나이다.</p>
<p>세마포어의 경우에는 <strong>Counting, Binary</strong> 이렇게 두 가지가 존재한다.</p>
<h3 id="counting-semaphores">Counting Semaphores</h3>
<p><strong>제한된 개수를 가진 자원에 대한 접근 제어용</strong>으로 사용되며, <strong>세마포는 사용 가능한 자원의 개수</strong>로 초기화된다. 예를 들어 자원이 6개면 세마포어도 6인 것이다. <strong>자원을 사용하면 세마포어가 감소, 방출하면 세마포어가 증가하게 된다.</strong></p>
<p><img src="https://images.velog.io/images/haero_kim/post/0bad3da0-e703-4081-94c4-5e65330e2ff1/99F46C3359E0A53A09.png" alt=""></p>
<p>Locking 매커니즘을 설명하면서 화장실 열쇠를 예로 들었는데, 어떤 건물에 여러 식당이 있고 화장실은 단 하나라고 가정해보자. 그렇다면 <strong>식당별로 해당 화장실을 열기 위한 열쇠</strong>를 갖고있을 것이다. 만약 식당이 4개라면 열쇠는 4개일 것이고, 최대 4명이 수용가능한 화장실이 되는 것이다. 이것은 <strong>4 짜리 세마포어를 활용</strong>한 것이다.</p>
<h3 id="binary-semaphores-mutex">Binary Semaphores (MUTEX)</h3>
<p>뮤텍스라고 불리는 녀석이 바로 <strong>Binarty Semaphores</strong> 를 의미하는 것이다. 이름 그대로 <strong>이진값 (0, 1)</strong> 만 가질 수 있고, 다중 프로세스들 사이의 <strong>임계영역 문제를 해결하기 위해 사용</strong>된다. 위에서 소개했던 <strong>Lock 매커니즘의 일종</strong>이기도 하다. (어떤 자료에서는 MUTEX 를 세마포의 일종으로 보지 않고 별개의 개념으로 소개하기도 한다)</p>
<p><img src="https://images.velog.io/images/haero_kim/post/703cde2f-d8f7-48c9-adf4-1a16ea7c05fd/9955D43359E0A5891E.png" alt=""></p>
<h3 id="busy-waiting-이슈">Busy Waiting 이슈</h3>
<p>세마포어는 <strong>Busy Waiting</strong> 이슈가 있다. Spin Lock 이라는 불리는 <strong>세마포어 초기 버전에서 임계영역에 진입해야하는 프로세스는 진입 코드를 무한 루프 형태로 구현</strong>하기 때문에 <strong>CPU 시간을 낭비</strong>하곤 했다. 이를 &#39;Busy Waiting&#39; 이라고 부르며 <strong>상당히 비효율적</strong>이라고 할 수 있다.</p>
<p>물론, 일반적으로는 세마포어에서 임계영역에 <strong>진입을 실패한 프로세스에 대해 Block 시킨 뒤, 임계영역에 자리가 날 때 다시 깨우는 방식</strong>을 사용한다. 이렇게 하면 Busy Waiting 이슈가 해결된다.</p>
<h3 id="deadlock-이슈">Deadlock 이슈</h3>
<p>세마포어가 Ready Queue 를 가지고 있고, 두 개 이상의 프로세스가 임계영역 진입을 무한정 기다리고 있을 때 임계영역에서 실행되는 프로세스는 진입 대기 중인 프로세스가 실행되어야만 빠져나올 수 있는 상황을 지칭한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PCB 와 Context Switching 알아보기]]></title>
            <link>https://velog.io/@haero_kim/PCB-%EC%99%80-Context-Switching-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/PCB-%EC%99%80-Context-Switching-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 26 Oct 2021 19:05:45 GMT</pubDate>
            <description><![CDATA[<h2 id="프로세스-관리">프로세스 관리</h2>
<p>구동중인 프로세스가 여러 개일 때, <strong>CPU 스케줄링을 통해 프로세스를 관리하는 것</strong>을 의미한다. 당연하게도, CPU 들은 각 프로세스들에 대해서 구분할 수 있어야 지지고 볶고 관리가 가능하다. 따라서 <strong>각기다른 프로세스들의 본연의 특징을 갖고 있는  <code>Process Metadata</code> 라는 정보를 활용</strong>한다.</p>
<h3 id="프로세스-메타데이터가-담고있는-정보">프로세스 메타데이터가 담고있는 정보</h3>
<ul>
<li>프로세스 고유 ID (<strong>PID</strong>)</li>
<li>프로세스 상태</li>
<li>프로세스 우선순위</li>
<li>Program Counter (<strong>PC</strong>)</li>
<li>CPU 레지스터</li>
<li>Owner</li>
<li>Memory Limit</li>
<li>기타 등등</li>
</ul>
<p>메타데이터는 프로세스가 생성될 떄마다 <strong><code>PCB (Process Control Block)</code></strong> 이라는 곳에 저장된다.</p>
<blockquote>
<h4 id="💡-간단히-짚고-넘어가기">💡 간단히 짚고 넘어가기</h4>
<p>Program Counter (PC) 레지스터 값은 해당 프로세스가 다음으로 수행할 명령의 정보를 담고 있다.</p>
</blockquote>
<br>

<h2 id="pcb-process-control-block">PCB (Process Control Block)</h2>
<p><img src="https://images.velog.io/images/haero_kim/post/8985c46a-6544-4f0e-a625-1cc691623163/img%20(7).png" alt=""></p>
<p>위에서 설명한 것처럼 <strong>프로세스들의 메타데이터를 저장하는 곳</strong>이며, 한 <strong>PCB</strong> 안에는 <strong>한 프로세스의 정보</strong>가 담기게 되는 구조이다. 프로그램이 실행되어 메모리에 적재됐을 때 <strong>프로세스가 생겨나고, 프로세스 주소 공간에 코드&amp;데이터&amp;스택 공간이 생성</strong>된다. 이후 <strong>해당 프로세스의 메타데이터들이 PCB 에 저장</strong>된다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/dd3d9ba6-bb62-4ade-bce7-341a2e1a3751/25673A5058F211C224.png" alt=""></p>
<p>CPU 에서는 프로세스의 상태에 따라 <strong>프로세스 교체작업</strong>이 이루어지게 된다. 만약 어떤 프로세스로부터 <strong>인터럽트가 발생</strong>해서, <strong>현재 프로세스가 잠시 대기 상태</strong>가 되고 <strong>인터럽트가 발생된 프로세스를 실행 상태</strong>로 바꿔치기 할 때, <strong>대기 중인 프로세스의 정보를 잃어버리게 되면</strong> 프로그램을 처음부터 <strong>다시 시작</strong>해야 한다. 이렇게 되면 사용자 입장에선 당혹스러울 것이다.</p>
<p>따라서, 대기하다가 다시 실행할 때 <strong>대기 상태로 바뀌기 직전의 실행 정보를 고스란히 저장</strong>해둔다면 다시 실행 상태로 돌아왔을 때 <strong>아무 일도 없었단 듯이 흘러갈 수 있을</strong> 것이다. 이 동작을 위해 <strong>PCB</strong> 가 필요한 것이다.</p>
<blockquote>
<h4 id="💁🏻♂️-정리하자면-프로세스-a-에서-b-로-교체될-때-아래와-같은-과정이-일어나게-된다">💁🏻‍♂️ 정리하자면, 프로세스 A 에서 B 로 교체될 때 아래와 같은 과정이 일어나게 된다.</h4>
</blockquote>
<ol>
<li>B 에서 인터럽트 발생</li>
<li>A 의 현재 실행 정보를 PCB 에 저장</li>
<li>A 를 대기 상태로 돌리고 B 를 실행 상태로 전환</li>
<li>B 의 PCB 정보를 기반으로 실행 재개</li>
<li>B 가 원하는 동작을 모두 수행함</li>
<li>B 의 현재 실행 정보를 PCB 에 저장</li>
<li>B 를 대기 상태로 돌리고 A 를 실행 상태로 전환</li>
<li>A 의 PCB 정보를 기반으로 실행 재개</li>
</ol>
<br>

<h3 id="pcb-의-관리-방식">PCB 의 관리 방식</h3>
<p>한 프로세스에 한 PCB가 생성된다. 따라서 <strong>PCB 는 프로세스가 생성될 때마다 하나씩 늘어나게</strong> 되는데, 이 때 PCB 들을 관리하는 자료구조는 바로 <strong>Linked List</strong> 형태이다. <strong>PCB List Head 에 PCB 들이 생성될 때</strong>마다 하나씩 <strong>이어붙게 된다</strong>. 주소값으로 연결되는 형태이기 때문에 <strong>새로운 녀석이 들어오거나, 기존의 녀석이 나갈 때 뛰어난 효율</strong>을 보이게 된다. (삽입 삭제에 용이한 Linked List 의 특성 활용)</p>
<blockquote>
<p>당연한 이야기지만, <strong>프로세스가 종료된다면 PCB 도 제거</strong>된다!</p>
</blockquote>
<br>

<h2 id="context-switching">Context Switching</h2>
<p>위에서 프로세스를 갈아끼우는 사례를 예로 들었는데, 이 <strong>프로세스를 갈아끼우는 행위</strong> 자체를 <strong><code>Context Switching</code></strong> 이라고 한다. 원래 <strong>실행중이던 프로세스의 상태를 PCB 에 보관</strong>하고, <strong>새로 들어오는 프로세스의 PCB 정보를 바탕으로 레지스터에 값을 적재</strong>하는 과정을 일컫는 말이다.</p>
<p>보통 인터럽트 발생, 혹은 현재 프로세스의 선점 허용 기간을 모두 소모한 상황, 입출력을 위해 대기하는 경우에 <strong>Context Switching</strong> 이 발생하게 된다. </p>
<p><img src="https://images.velog.io/images/haero_kim/post/bbc56488-d299-4c5d-9f1c-c9808b6f016b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202020-08-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.06.12.png" alt=""></p>
<p>사실 Context Switching 을 하는 동안에는 CPU 가 아무것도 하지 못하게 된다. 따라서, 만일 쓰레드 및 프로세스의 개수가 엄청 많아져 Context Switching 이 빈번히 일어나게 된다면, 오버헤드가 잦아져 성능이 악화될 가능성도 있다. 이를 <strong>Context Switching Overhead</strong> 라고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] Clean Architecture 맛보기]]></title>
            <link>https://velog.io/@haero_kim/Android-Clean-Architecture-%EB%A7%9B%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Android-Clean-Architecture-%EB%A7%9B%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 26 Oct 2021 14:28:26 GMT</pubDate>
            <description><![CDATA[<h2 id="clean-architecture-란">Clean Architecture 란</h2>
<p>클린 코드로 소프트웨어 공학의 대가 로버트 C.마틴이 제시한 <strong>소프트웨어 디자인 철학</strong>이다. <strong>소프트웨어의 관심사를 계층별로 분리하여 코드 종속성이 외부로부터 내부로 의존</strong>하도록 하는 것이 주요 원칙이다. 따라서 내부 계층의 코드는 외부 계층의 기능을 알 수 없고,내부 계층 코드에서는 외부 계층에 존재하는 변수 및 함수, 클래스 등이 등장해선 안 된다. 즉, <strong>내부 계층으로 갈수록 추상화된 계층이며 이는 SOLID 원칙의 DIP 를 따른 것</strong>이라고 볼수 있다. </p>
<p>설명에 따르면 위 그림에서 가장 내부에 있는 <strong><code>Entities</code> 라는 영역이 가장 추상적인 영역</strong>이다. 이 영역은 소프트웨어의 <strong>비즈니스 로직을 포함</strong>하게 되고<strong>, 사용 중인 프레임워크 (안드로이드 등) 에 의존해선 안 된다</strong>. <strong>반면 외부 계층</strong>은 네트워크 및 DB 의 접근처럼 <strong>프레임워크, 플랫폼에 특정한 구체적인 구현이 포함</strong>된다.</p>
<h3 id="clean-architecture-를-사용함으로써-얻는-이점">Clean Architecture 를 사용함으로써 얻는 이점</h3>
<p>가장 큰 장점은 코드의 <strong>재사용성이 용이해진다</strong>는 점이다. 관심사에 따라 계층을 분리하고 계층간 의존성을 외부에서 내부로, 즉 단방향으로 만들었기 때문이다. 또한 <strong>유닛 테스트도 더욱 간편</strong>해질 것이다.</p>
<p>따라서, 궁극적으로 아래와 같은 사항들을 달성하기 위해 클린 아키텍처를 사용한다고 할 수 있다.</p>
<ul>
<li><strong>프레임워크 독립성</strong> : 프레임워크가 라이브러리에 의존하지 않음</li>
<li><strong>테스트 용이성</strong> : 비즈니스 규칙은 UI, DB, 백엔드 서버등 외부와 무관하게 테스팅 가능</li>
<li><strong>UI 독립성</strong> : UI 변경이 시스템의 나머지 부분에 영향을 미치지 않음</li>
<li><strong>DB 독립성</strong> : DB 를 어떤 시스템으로 갈아끼우든 상관없음</li>
<li><strong>외부 기능 독립성</strong> : 비즈니스 규칙은 외부 기능에 대해 알지 못함</li>
</ul>
<p><strong>내부 계층에서 외부 계층의 존재를 알면 안 된다</strong>는 규칙만 지킨다면 꼭 위 사진처럼 계층을 나눌 필요가 없고 계층을 몇 개로 나누어도, 어떻게 나눠도 상관이 없다. 의존성 규칙만 잘 지키면되고, 항상 가장 바깥 계층에서 안쪽으로 참조하며, <strong>안쪽 계층으로 들어갈 수록 추상화 및 캡슐화 수준이 높아지게 하면 된다.</strong></p>
<p>가장 보편화된 클린 아키텍처 구조에선 크게 <strong><code>Entities</code>, <code>Use Cases</code>, <code>Interface Adapters</code>, <code>Frameworks and Drivers</code></strong> 이렇게 4개 계층으로 나누게 된다. 따라서 이번 포스팅에선 각각 계층에 대해 살펴보도록 하자.</p>
<hr>
<h2 id="entities">Entities</h2>
<p><strong>Domain Logic</strong> 이라고도 한다. 소프트웨어의 <strong>비즈니스 규칙을 캡슐화</strong>한다. 즉, 비즈니스 로직을 위한 데이터의 구조나 메소드 등을 포함하는 객체이다. 모든 어플리케이션은 하나 이상의 비즈니스 로직을 갖고 있다. 번역기 앱이라면 번역, 메모장 앱이라면 메모가 비즈니스 로직이 된다.</p>
<p><strong>엔티티 계층은 어플리케이션의 비즈니스 로직</strong>을 담고, 가장 <strong>일반적이고 상위 수준의 규칙들을 캡슐화</strong>하는 것이다. 외부에서 무언가 변경되더라도 거의 영향을 받지 않아야 한다. 예를 들어 UI 가 변경되거나 DB 가 변경되어도 엔티티 계층은 영향받지 않아야 한다.</p>
<p>네트워킹 혹은 DB 동작과 관련된 <strong>데이터 클래스</strong>를 작성할텐데, 그러한 것들이 이 <strong>계층에 속한다</strong>고 보면 된다. 단 <strong>안드로이드 프레임워크와 관련된 코드를 포함해서는 안 된다</strong>. <strong>순수한 코틀린 및 자바 코드</strong>만을 담고 있어야 한다.</p>
<p>만약 프레임워크 관련 코드가 담겨있을 경우 <strong>유닛 테스트가 불편해지기 때문</strong>이다.</p>
<br>

<h2 id="use-cases">Use Cases</h2>
<p><strong>Business Rules</strong> 라고도 한다. 어플리케이션과 관련된 <strong>비즈니스 규칙</strong>을 포함하고, 시스템의 모든 <strong>Use Case 구현체들을 캡슐화하는 계층</strong>이다. 이러한 유즈 케이스들은 <strong>엔티티로부터의 데이터 흐름들을 관리</strong>한다. 관심사별로 계층이 잘 분리된 형태이기 때문에 해당 계층은 엔티티에 영향을 미치지 않고, 마찬가지로 UI 나 DB, 프레임워크 등 외부 계층에서도 영향받지 않는다.</p>
<p>안드로이드로 따지면 <code>Model</code>, <code>Repository</code>, <code>Executor</code> 등과 관련된 내용이 해당 계층에 속한다.</p>
<br>

<h2 id="interface-adapters">Interface Adapters</h2>
<p>해당 계층은 <strong>Entity</strong> 나 <strong>Use Case</strong> 로부터 얻은 <strong>데이터를 가공하는 계층</strong>이다. 즉, 얻은 데이터들을 <strong>DB 및 HTTP 요청, 혹은 UI 업데이트에 적용할 수 있는 형태로 변환</strong>한다. 순수한 <strong>비즈니스 로직만을 담당</strong>하게 된다. 해당 게층의 목적은 <strong>비즈니스 로직과 프레임워크 코드를 스무스</strong>하게 <strong>연결</strong>하는 것이다.</p>
<p>안드로이드로 따지면 <code>ViewModel(MVVM)</code>, <code>Presenter(MVP)</code>, <code>View(MVVM, MVP)</code>, <code>Controller(MVC)</code> 등이 포함된다.</p>
<h2 id="frameworks--drivers">Frameworks &amp; Drivers</h2>
<p>가장 외부 계층이다. <strong>일반적으로 안드로이드에서는 UI 와 관련된 액티비티 (혹은 프래그먼트), Intent 전달, Room 과 같은 DB, Retrofit 과 같은 네트워킹 관련 프레임워크 코드</strong>들이 속한다. 따라서 이쪽 부분은 앱 개발자가 직접 건드릴 일이 없긴 하다.</p>
<hr>
<p>이렇듯 관심사가 완전히 딱딱 분리된 형태를 사용하게 되면, 많은 코드가 방해받지 않고 특정 문제 및 상황에 집중할 수 있다. SOLID 원칙을 잘 따른 패턴이다. 다만 클린 아키텍처에는 절대 정답이 없다. 관심사만 잘 분리된다면 어떤 형태를 가지든 원칙은 변하지 않는 것이다.  </p>
<p>기회가 된다면 다음 포스팅에선, 안드로이드에 보다 더 특화된 클린 아키텍처를 소개하고, 이에 따라 구현한 앱 프로젝트에 대해 소개해보려고 한다.</p>
<h3 id="참고자료">참고자료</h3>
<p>도서 &#39;아키텍처를 알아야 앱 개발이 보인다&#39; - 옥수환 저</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] 팔방미인 Subject 알아보기]]></title>
            <link>https://velog.io/@haero_kim/RxJava-%ED%8C%94%EB%B0%A9%EB%AF%B8%EC%9D%B8-Subject-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/RxJava-%ED%8C%94%EB%B0%A9%EB%AF%B8%EC%9D%B8-Subject-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 25 Oct 2021 11:49:21 GMT</pubDate>
            <description><![CDATA[<h2 id="subject">Subject</h2>
<p>지금까지 Observable, Single, Flowable 등의 데이터 스트림에 대해 알아보았다. 이번 포스팅에서 알아볼 녀석은 <strong><code>Subject</code></strong> 라는 녀석인데, 이는 <strong>관찰 가능한 데이터 스트림과 관찰자(구독자)의 성격을 모두</strong> 갖고 있는 특이한 녀석이다. 즉 <strong>Observable 과 Observer 를 모두 구현한 추상 타입</strong>으로, 하나의 <strong>소스로부터 다중의 구독자에게 멀티 캐스팅이 가능</strong>하다는 특징을 갖고 있다.</p>
<pre><code class="language-kotlin">public abstract class Subject&lt;T&gt; extends Observable&lt;T&gt; implements Observer&lt;T&gt; {
    public Subject() {
    }

    public abstract boolean hasObservers();

    public abstract boolean hasThrowable();

    public abstract boolean hasComplete();

    @Nullable
    public abstract Throwable getThrowable();

    @NonNull
    public final Subject&lt;T&gt; toSerialized() {
        return (Subject)(this instanceof SerializedSubject ? this : new SerializedSubject(this));
    }
}</code></pre>
<p>Observer 를 구현한다는 특징때문에, <strong><code>onNext()</code>, <code>onError()</code>, <code>onComplete()</code> 등의 이벤트를 수동으로 발생하여 구독자들에게 전달</strong>해줄 수 있다.</p>
<p>그럼, 다양한 Subject 의 종류 중 몇 가지에 대해 하나씩 알아보도록 하자.</p>
<hr>
<h2 id="publishsubject">PublishSubject</h2>
<p><code>PublishSubject</code> 는 <strong>가장 단순한 <code>Subject</code> 구현체</strong>중 하나이다. 구독자들에게 <strong>이벤트를 널리 전달</strong>할 수 있으며,  <strong>Hot Observable 특성</strong>을 갖고 있어 <strong>구독한 시점부터 발생하는 데이터를 전달</strong>한다. 따라서 데이터가 모두 발행되고 난 뒤 구독을 하면 아무 데이터도 받아볼 수 없게 된다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/262c2406-9fb7-4d63-81d4-a06dfdf89337/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3%20(29).png" alt=""></p>
<p>아래 코드를 보면 여러 구독자를 붙여줄 수 있고, 직접 <code>Emitter</code> 이벤트를 발생시킬 수 있는 점을 확인해볼 수 있다.</p>
<pre><code class="language-kotlin">fun main() {
    val src = PublishSubject.create&lt;Int&gt;()
    src.subscribe {
        println(&quot;A : $it&quot;)
    }
    src.subscribe {
        println(&quot;B : $it&quot;)
    }

    src.onNext(10)
    src.onNext(20)
    src.onNext(30)
}</code></pre>
<pre><code class="language-kotlin">A : 10
B : 10
A : 20
B : 20
A : 30
B : 30</code></pre>
<p>그리고 다른 데이터 스트림의 <strong>구독자로서의 행세</strong>도 할 수 있기 때문에, <strong>구독자로서 전달받은 데이터를 발행하는 식의 동작도 구현</strong>할 수 있다. <strong>다른 <code>Observable</code> 로부터 전달받은 데이터를 자신의 구독자에게 전달</strong>해주는 것이다.</p>
<pre><code class="language-kotlin">fun main() {
    val src1 = Observable.interval(1, TimeUnit.SECONDS)
    val src2 = Observable.interval(500, TimeUnit.MILLISECONDS)
    val subject = PublishSubject.create&lt;String&gt;()

    src1.map { &quot;A : $it&quot; }.subscribe(subject)
    src2.map { &quot;B : $it&quot; }.subscribe(subject)
    subject.subscribe(System.out::println)

    Thread.sleep(3000)
}</code></pre>
<pre><code class="language-kotlin">B : 0
B : 1
A : 0
B : 2
B : 3
A : 1
B : 4
A : 2
B : 5</code></pre>
<p>두 <code>Observable</code> 로 부터 데이터를 전달받는다는 점에서 <code>Observable</code> 의 <strong><code>merge</code> 연산자와 비슷한 동작</strong>을 수행하는 것을 확인해볼 수 있다.</p>
<br>

<h2 id="behaviorsubject">BehaviorSubject</h2>
<p><code>BehaviorSubject</code> 는 특이한 성질을 갖고 있다. <code>PublishSubject</code> 와 동일하게 동작하지만, <strong>새로운 구독자가 들어온 경우 해당 구독자에게 구독 시점에 가장 마지막 데이터를 발행</strong>한다는 점이 특징이다. 따라서 가장 <strong>최신값</strong>을 가져오는 등의 동작을 구현할 때 유용하게 사용될 수 있다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/5d2198f6-907a-450e-99ce-b0271eed8469/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3%20(28).png" alt=""></p>
<pre><code class="language-kotlin">fun main() {
    val src = BehaviorSubject.create&lt;Int&gt;()
    src.subscribe { println(&quot;첫번째 $it&quot;) }
    src.onNext(1)
    src.subscribe { println(&quot;****두번째 $it&quot;) }
    src.onNext(2)
    src.onNext(3)
    src.subscribe { println(&quot;********세번째 $it&quot;) }
    src.onNext(4)
    src.onComplete()
}</code></pre>
<pre><code class="language-kotlin">첫번째 1
****두번째 1
첫번째 2
****두번째 2
첫번째 3
****두번째 3
********세번째 3
첫번째 4
****두번째 4
********세번째 4</code></pre>
<p>구독을 한 이후에는 <code>PublishSubject</code> 와 동일하게 모두 수신할 수 있다.</p>
<br>

<h2 id="replaysubject">ReplaySubject</h2>
<p><code>PublishSubject</code> 에 <strong><code>cache</code> 연산자를 적용한 것과 유사한 동작을 수행</strong>한다. 새로운 구독자가 생겼을 경우 <strong>이전에 발행했던 데이터들을 모두 해당 구독자에게 전달</strong>해주는 특징을 갖고 있다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/92da61d1-c4aa-46d2-b8cd-c34df01297f5/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3%20(30).png" alt=""></p>
<pre><code class="language-kotlin">fun main() {
    val src = ReplaySubject.create&lt;Int&gt;()
    src.onNext(1)
    src.onNext(2)
    src.onNext(3)

    src.subscribe(System.out::println)

    src.onNext(4)
}</code></pre>
<pre><code class="language-kotlin">1
2
3
4</code></pre>
<p>데이터 <strong>&#39;1, 2, 3&#39;</strong> 이 발행되고난 뒤 <strong>새로운 구독자</strong>가 들어왔을 때, 이미 발행했던 <strong>&#39;1, 2, 3&#39; 이 다시금 반복</strong>되는 것이다. 그러고나서는 <code>PublishSubject</code> 과 다름없이 &#39;4&#39;가 정상적으로 발행되는 것을 확인할 수 있다. </p>
<blockquote>
<p>ReplaySubject 를 사용할때는 <strong>수많은 혹은 무한한 데이터를 발행하는 소스에 대해 적용하면 자칫 OOM (OutOfMemoryException) 이 발생할 가능성</strong>이 높다. 조심히 사용하도록 하자!</p>
</blockquote>
<br>

<h2 id="asyncsubject">AsyncSubject</h2>
<p>AsyncSubject 는 <strong><code>onComplete()</code> 호출 직전에 발행된 아이템만 구독자들에게 전달</strong>한다. 즉, <code>onComplete()</code> 가 발생할 때까지는 아무 데이터도 전달하지 않다가 <strong><code>onComplete()</code> 가 발생하면 가장 마지막에 발행된 데이터를 전달</strong>하는 것이다.
<img src="https://images.velog.io/images/haero_kim/post/7fcdb668-79fa-4986-9ca9-5b774d2d6629/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3%20(27).png" alt=""></p>
<pre><code class="language-kotlin">fun main() {
    val src = AsyncSubject.create&lt;Int&gt;()
    src.subscribe {
        println(&quot;A : $it&quot;)
    }
    src.onNext(10)
    src.subscribe {
        println(&quot;B : $it&quot;)
    }
    src.onNext(20)
    src.subscribe {
        println(&quot;C : $it&quot;)
    }
    src.onNext(30)
    src.onComplete()
}</code></pre>
<pre><code class="language-kotlin">A : 30
B : 30
C : 30</code></pre>
<p>구독자는 연달아 3번에 거쳐 들어오게 되고, 그 사이 데이터는 3회 발행됐다. 끝으로 <strong><code>onComplete()</code> 가 호출되자 3마리 구독자에게 가장 마지막으로 발행된 &#39;30&#39;이라는 데이터가 전달</strong>됐다.</p>
<br>

<h2 id="unicastsubject">UnicastSubject</h2>
<p>다른 <code>Subject</code> 들과 비슷하지만, 핵심적인 차이점을 갖고있다. 어떤 구독자가 <strong>UnicastSubject 를 구독하기 전까지는 발행하는 데이터들을 계속 버퍼에 저장해뒀다가 구독을 시작할 때 버퍼의 데이터들을 싹 발행</strong>하고 버퍼를 깨끗이 비워낸다. </p>
<p>그렇다면 <strong>처음으로 들어온 구독자가 모든 데이터들을 소비</strong>할 것이고, <strong>두 번째로 들어온 구독자들은 아무 데이터도 받아볼 수 없을</strong> 것이다. 따라서 구독자를 딱 하나만 둘 수 있고, 때문에 <strong>&#39;Unicast&#39;</strong> 라는 용어가 제격인 것이다. (만약 두 개 이상 구독자가들어올 시 <code>IllegalStateException</code> 을 발생시킨다)</p>
<p><img src="https://images.velog.io/images/haero_kim/post/0efd691f-2158-4f5e-8b83-405d2b40f407/UnicastSubject.png" alt=""></p>
<pre><code class="language-kotlin">fun main() {
    val src = UnicastSubject.create&lt;Long&gt;()
    Observable.interval(1, TimeUnit.SECONDS)
        .subscribe(src)

    Thread.sleep(3000)

    src.subscribe {
        println(&quot;A : $it&quot;)
    }
    Thread.sleep(2000)
}</code></pre>
<pre><code class="language-kotlin">A : 0
A : 1
A : 2
A : 3
A : 4</code></pre>
<p>3초가 흘러갈 동안 Observable 은 &#39;0, 1, 2&#39; 이렇게 3개의 데이터를 발행했다. <code>UnicastSubject</code> 는 이들을 <strong>버퍼에 쌓아두고</strong>, 첫번째 구독자가 들어왔을 때 <strong>한 번에 발행하여 &#39;0, 1, 2&#39; 가 동시에 출력</strong>되는 것을 확인할 수 있다. 이후 발행되는 데이터들은 그대로 전달받게 된다.</p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://duzi077.tistory.com/178">https://duzi077.tistory.com/178</a>
<a href="https://chanhyeok.tistory.com/15">https://chanhyeok.tistory.com/15</a>
도서 &#39;아키텍처를 알아야 앱 개발이 보인다&#39; - 옥수환 저</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 테스트 코드 작성해보기]]></title>
            <link>https://velog.io/@haero_kim/Android-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/Android-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 24 Oct 2021 13:32:05 GMT</pubDate>
            <description><![CDATA[<h2 id="테스트-코드를-작성하는-이유">테스트 코드를 작성하는 이유</h2>
<p>당연한 답변이 될 수도 있겠지만, 구현한 동작이 올바르게 동작하는지 검증하기 위해 <strong>테스트 코드</strong>를 작성하는 것이다. 특히, 어떤 코드를 단순히 리팩토링할 때 기존과 다르게 동작할 수 있는 노릇이다. 따라서 이런 상황을 대비하여 일일히 수동으로 테스트를 해보기 보단, 다양한 엣지 케이스들을 고려한 <strong>테스트 케이스를 코드로써 작성해두면 편리하게 동작의 무결성을 보장</strong>할 수 있을 것이다.</p>
<br>

<h2 id="andriod-에서의-테스트">Andriod 에서의 테스트</h2>
<p>안드로이드는 사용자와 인터랙션하는 UI 까지 구현하기 때문에, 단순히 화면에 보여지지 않는 테스트 뿐만 아니라 UI 인터랙션이 정상적으로 이루어지는지 검증하기 위한 UI 테스트까지 고려하게 된다. 따라서 아래와 같이 두 가지 종류의 테스트가 존재한다.</p>
<h3 id="unit-test">Unit Test</h3>
<p>&#39;테스트 코드&#39;라고 했을 때 가장 일반적으로 떠올리게 되는 테스트이다. 말 그대로 코드를 <strong>유닛 단위로 기능을 검증</strong>하는 것이다. 유닛 단위라 함은 <strong>메소드, 클래스</strong> 등이 될 수 있다.</p>
<p>안드로이드에서는 <code>JUnit</code>, <code>Mockito</code>, <code>PowerMock</code> 등을 사용할 수 있다.</p>
<h3 id="ui-test">UI Test</h3>
<p>위에서 말한 것처럼 안드로이드에서는 <strong>UI 테스트</strong>를 고려하게 된다. <strong>사용자와의 인터랙션을 검증</strong>하게 된다. 사용자 인터랙션이라 함은 버튼 클릭, 테스트 입력, 스크롤 등이 될 수 있다.</p>
<p><code>Espresso</code>, <code>UIAutomator</code>, <code>Robotium</code>, <code>Calabash</code>, <code>Robolectric</code> 등의 툴을 사용하여 테스트 코드를 작성해볼 수 있다.</p>
<br>

<h2 id="android-프로젝트에서의-테스트">Android 프로젝트에서의 테스트</h2>
<p>안드로이드 스튜디오에서 프로젝트를 생성하면 테스트 관련 툴이 자동으로 추가된다. <strong><code>app</code> 단위의 <code>build.gradle</code> 파일의 의존성</strong> 쪽을 보면 아래와 같은 구문들이 추가되어 있는 것을 볼 수 있다.</p>
<pre><code class="language-kotlin">testImplementation &#39;junit:junit:4.+&#39;
androidTestImplementation &#39;androidx.test.ext:junit:1.1.2&#39;
androidTestImplementation &#39;androidx.test.espresso:espresso-core:3.3.0&#39;</code></pre>
<p><code>JUnit</code>, <code>Espresso</code> 등 <strong>Unit 테스트 툴과 UI 테스트 툴이 기본적</strong>으로 탑재된다.</p>
<p>그리고 프로젝트 디렉토리만 보더라도 테스트 관련 파일을 확인해볼 수 있다.</p>
<p><code>androidTest</code>, <code>test</code> 등 디렉토리 안에 테스트 파일들이 있다. <strong><code>androidTest</code> 디렉토리 안에는 UI 테스트</strong> 관련 파일들이 있고, <strong><code>test</code> 디렉토리 안에는 Unit 테스트</strong> 관련 파일들이 있다.</p>
<p>이번 포스팅에선 <code>JUnit</code> 을 통한 <strong>Unit 테스트를 사용</strong>해보자.</p>
<br>

<h2 id="junit-사용방법">JUnit 사용방법</h2>
<p><code>ExampleUnitTest</code> 파일을 보면 아래와 같은 모습이다.</p>
<pre><code class="language-kotlin">import org.junit.Test

import org.junit.Assert.*

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}</code></pre>
<p><code>@Test</code> 라는 어노테이션과 함께 <strong>덧셈 연산을 테스트</strong>하는 메소드가 있다. 이 메소드 안에서는 <strong><code>assertEquals()</code></strong> 라는 메소드를 통해 <strong>4와 2 + 2</strong> 를 비교하는듯한 모습을 보이고 있는데, <code>assertEquals()</code> 의 모양을 살펴보자.</p>
<pre><code class="language-java">/**
 * Asserts that two longs are equal. If they are not, an
 * {@link AssertionError} is thrown.
 *
 * @param expected expected long value.
 * @param actual actual long value
 */
public static void assertEquals(long expected, long actual) {
    assertEquals(null, expected, actual);
}</code></pre>
<p><code>expected</code> 와 <code>actual</code> <strong>파라미터 두 개를 비교</strong>하여 <strong>같은 값을 갖고 있는 지에 대해 검사하는 동작</strong>을 한다.</p>
<blockquote>
<p>Assert 는 &#39;<strong>단언하다, 주장하다</strong>&#39; 라는 뜻을 갖고 있다. 실생활에서 &#39;주장&#39;이라 함은 올바르고 틀린 것을 따지기보단 단지 자신의 의견을 표출하는 것 뿐이다. 마찬가지로 테스트 코드에 있어 <strong>Assert 도 일단 실행을 해보고, 이것이 올바르게 동작했다면 통과하고 만약 올바르게 동작하지 않았다면 <code>AssertionError</code> 를 쓰로잉</strong>하게 된다.</p>
</blockquote>
<p>아까 살펴봤던 기본 예제 코드에서 <strong>&#39;4&#39; 와 &#39;2 + 2&#39;</strong> 를 비교했는데, 이 두 값은 같으므로 <strong>Unit 테스트를 통</strong>과하게 되는 것이다.</p>
<p>*<em>그럼 이 테스트는 어떻게 실행하는 것일까?
*</em>
테스트 코드는 이렇게 실행할 수 있다. 테스트 파일을 우클릭해보면 다음과 같은 메뉴가 있는데, 이를 실행하면 된다. (혹은 적혀있는 단축키를 눌러도 된다)</p>
<p><img src="https://images.velog.io/images/haero_kim/post/37288dee-3be9-49b1-9759-1e2378d51ff6/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.56.42.png" alt=""></p>
<p>그렇게 되면 아래와 같이 <strong>테스트 성공</strong>이라는 표시가 뜬다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/2e66c3e6-b664-46c5-ac26-2311303b7311/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.59.34.png" alt=""></p>
<p>만약 테스트 코드를 다음과 같이 작성했다고 해보자. 이는 기능이 <strong>제대로 동작되지 않는 상황을 가정</strong>한 것이다. <strong>4 라는 값을 기대하는 유닛 테스트인데, 연산 결과가 5인 상황</strong>이다.</p>
<pre><code class="language-java">class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 3)
    }
}</code></pre>
<p>이 상태로 실행하게 되면 아래와 같이 표시된다.</p>
<p><img src="https://images.velog.io/images/haero_kim/post/72fa67d3-3a86-4cf2-bd75-af0b04a21609/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%207.02.22.png" alt=""></p>
<p><strong>&#39;Tests Failed&#39; 라고 뜨며 어떤 테스트 케이스가 통과되지 못했는지 (실패했는지) 에 대한 정보를 제공</strong>해준다. 이런 식으로 테스트가 실패한 동작에 대해 디버깅을 수행하면 된다.</p>
<p>유닛 테스트 코드를 작성하면 (귀찮지만) <strong>다양한 테스트 케이스들을 고려</strong>해볼 수 있기 떄문에 기능 추가, 변경, 혹은 코드 리팩토링에 있어 강력한 이점을 지닌다. <strong>다른 기능에 비해 특히나 동작의 무결성이 보장</strong>되어야 하는 부분이라면, <strong>테스트 코드의 중요성은 더욱 높아진다.</strong></p>
<br>

<h2 id="직접-unit-테스트-코드-작성해보기">직접 Unit 테스트 코드 작성해보기</h2>
<p>실제 사례(?)를 바탕으로 연습해보자. 간단하게 계산기 클래스를 만들어 <strong>덧셈, 뺄셈</strong>이 정상적으로 이루어지는지에 대해 검증해보자.</p>
<p>클래스를 아래와 같이 만들어주자.</p>
<pre><code class="language-kotlin">class Calculator {

    fun add(a: Int, b: Int): Int {
        return a + b
    }

    fun sub(a: Int, b: Int): Int {
        return a - b
    }

}</code></pre>
<p>그리고 아까 <code>ExampleUnitTest</code> 파일이 있던 경로에 계산기 기능 검증 테스트를 위한 <strong><code>CalculatorTest</code> 라는 테스트 파일</strong>을 만들어준다.</p>
<p>그리고 아래와 같이 코드를 작성해볼 수 있다.</p>
<pre><code class="language-kotlin">import org.junit.Test
import org.junit.Assert.assertEquals
import org.junit.Before

class CalculatorTest {
    private var calculator: Calculator? = null

    @Before
    fun setUp() {
        calculator = Calculator()
    }

    @Test
    fun 기본_덧셈() {
        val result = calculator?.add(5, 6)
        assertEquals(11, result)
    }

    @Test
    fun 기본_뺄셈() {
        val result = calculator?.sub(10, 5)
        assertEquals(5, result)
    }

}</code></pre>
<p>몇 가지 어노테이션에 대해 설명하자면 다음과 같다.</p>
<h3 id="before">@Before</h3>
<ul>
<li><strong>테스트 시작전 실행되어야 할 동작 (객체 초기화 등)</strong></li>
</ul>
<h3 id="test">@Test</h3>
<ul>
<li><strong>@Before 이후 돌아가는 테스트 케이스 코드</strong></li>
</ul>
<p>그리고 위 코드에선 그닥 의미가 없지만 이런식으로 <strong>엣지 케이스들도 고려</strong>해줄 수 있겠다.</p>
<pre><code class="language-kotlin">import org.junit.Test
import org.junit.Assert.assertEquals
import org.junit.Before

class CalculatorTest {
    private var calculator: Calculator? = null

    @Before
    fun setUp() {
        calculator = Calculator()
    }

    @Test
    fun 기본_덧셈() {
        val result = calculator?.add(5, 6)
        assertEquals(11, result)
    }

    @Test
    fun 음수끼리_덧셈() {
        val result = calculator?.add(-5, -4)
        assertEquals(-9, result)
    }

    @Test
    fun 기본_뺄셈() {
        val result = calculator?.sub(10, 5)
        assertEquals(5, result)
    }

    @Test
    fun 음수끼리_뺄셈() {
        val result = calculator?.sub(-5, -4)
        assertEquals(-1, result)
    }

}</code></pre>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://thdev.tech/androiddev/2016/05/04/Android-Test-Example/">https://thdev.tech/androiddev/2016/05/04/Android-Test-Example/</a>
<a href="https://black-jin0427.tistory.com/107">https://black-jin0427.tistory.com/107</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kotlin] Infix Function 이 뭐게?]]></title>
            <link>https://velog.io/@haero_kim/Kotlin-Infix-Function-%EC%9D%B4-%EB%AD%90%EA%B2%8C</link>
            <guid>https://velog.io/@haero_kim/Kotlin-Infix-Function-%EC%9D%B4-%EB%AD%90%EA%B2%8C</guid>
            <pubDate>Fri, 22 Oct 2021 18:31:14 GMT</pubDate>
            <description><![CDATA[<h2 id="infix-function">Infix Function</h2>
<p>코틀린을 사용하다보면 아래와 같이 <strong><code>Pair</code></strong> 객체를 만들어주는 경우가 많다.</p>
<pre><code class="language-kotlin">val monday: Pair&lt;String, String&gt; = &quot;Monday&quot; to &quot;월요일&quot;</code></pre>
<p>그런데 <code>to</code> 가 키워드 하이라이팅되는 것을 보면, 그냥 쓰는 구문이 절대 아니고 <strong>어떤 동작을 하는 것이 분명</strong>하다. 내부 구현을 들여다보면, 아래와 같다.</p>
<pre><code class="language-kotlin">package kotlin

public infix fun &lt;A, B&gt; A.to(that: B): kotlin.Pair&lt;A, B&gt; { /* compiled code */ }</code></pre>
<p>제너릭으로 두 개 객체를 받고, <strong>A의 확장함수를 만들어 주고 A와 B를 엮어서 <code>Pair</code> 형태로 만들어 반환해주는 코드</strong>가 내장되어 있다.</p>
<p>이러한 구문을 사용하면 <strong>확실히 코드가 훨씬 간결해지고, 가독성이 향상</strong>되는 것을 확인할 수 있다. 코틀린에서 <strong><code>Map</code></strong> 을 만들때, 더욱 차이점을 명확히 느낄 수 있다.</p>
<pre><code class="language-kotlin">val day1 = mapOf(Pair(&quot;Monday&quot;, &quot;월요일&quot;), Pair(&quot;Tuesday&quot;, &quot;화요일&quot;))
val day2 = mapOf(&quot;Monday&quot; to &quot;월요일&quot;, &quot;Tuesday&quot; to &quot;화요일&quot;)</code></pre>
<blockquote>
<p>코틀린에서는 <strong>두 개의 객체 중간에 들어가게 되는 함수 형태를 <code>Infix Function</code></strong> 이라고 부른다.
뭔지는 알았으니까, 한 번 직접 만들어보자. 생각보다 정말 간단하다!</p>
</blockquote>
<br>

<p><img src="https://images.velog.io/images/haero_kim/post/23e2d8fc-f55c-4c02-b54f-4d470b5748b9/giphy%20(15).gif" alt=""></p>
<h2 id="직접-만들-수-있어요">직접 만들 수 있어요</h2>
<p><code>Infix</code> 함수는 위처럼 기본 내장 함수 뿐만 아니라 <strong>개발자가 직접 정의</strong>할 수도 있다. 
직접 Infix 함수를 정의한다고 하면, 아래와 같은 형태를 유지하면 된다.</p>
<pre><code class="language-kotlin">infix fun dispatcher.함수명(receiver): 리턴타입 { }</code></pre>
<br>


<p>만약 아래 예제로 들면,</p>
<pre><code class="language-kotlin">val monday: Pair&lt;String, String&gt; = &quot;Monday&quot; to &quot;월요일&quot;</code></pre>
<p><strong>&quot;Monday&quot; 가 <code>dispatcher</code> 이고 &quot;월요일&quot; 이 <code>receiver</code></strong> 인 것이다.</p>
<br>


<p>아래와 같이 직접 <strong><code>add</code> 라는 Infix 함수</strong>를 만들어보자. <strong><code>String</code> 에 확장함수 형태</strong>로 달아줄 것이며, 결과적으로 <strong>왼쪽과 오른쪽 <code>String</code></strong> 을 하나로 합쳐서 반환해주는 기능을 정의해보자.</p>
<pre><code class="language-kotlin">infix fun String.add(other:String): String {
    return this + other 
}</code></pre>
<p>눈치챘겠지만 <strong><code>this</code> 키워드는 왼쪽 객체, 즉 <code>dispatcher</code></strong> 를 지칭한다.</p>
<br>

<p>그런 뒤, 아래와 같이 사용해볼 수 있다.</p>
<pre><code class="language-kotlin">infix fun String.add(other:String): String {
    return this + other
}

fun main() {
    println(&quot;월요일&quot; add &quot;싫어&quot;)
}</code></pre>
<pre><code class="language-kotlin">월요일싫어</code></pre>
<br>

<h3 id="클래스-내에-정의하기">클래스 내에 정의하기</h3>
<blockquote>
<p>아래와 같이 클래스 내부에 정의를 하게 되면 <strong><code>dispatcher</code> 는 자기 자신</strong>일 것이므로 생략할 수 있다. 아래는 <code>String</code> 을 누적할 수 있는 형태의 클래스와 메소드를 구현한 형태이다.</p>
</blockquote>
<pre><code class="language-kotlin">class StringAcc {
    var data = &quot;&quot;
    infix fun add(receiver: String) {
        this.data += receiver
    }
}

fun main() {
    val stringAcc = StringAcc()

    stringAcc add &quot;월요일&quot;
    stringAcc add &quot; 진짜&quot;
    stringAcc add &quot; 싫다&quot;

    println(stringAcc.data)
}</code></pre>
<pre><code class="language-kotlin">월요일 진짜 싫다</code></pre>
<hr>
<p>Infix Function 은 용도에 맞게 잘 사용한다면 가독성을 크게 향상시킬 수 있는 수단이다.</p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://kotlinlang.org/docs/functions.html#infix-notation">https://kotlinlang.org/docs/functions.html#infix-notation</a>
<a href="https://codechacha.com/ko/kotlin-infix-functions/">https://codechacha.com/ko/kotlin-infix-functions/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kotlin] Collection 날먹하는 함수들]]></title>
            <link>https://velog.io/@haero_kim/Kotlin-Collection-%EB%82%A0%EB%A8%B9%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98%EB%93%A4</link>
            <guid>https://velog.io/@haero_kim/Kotlin-Collection-%EB%82%A0%EB%A8%B9%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98%EB%93%A4</guid>
            <pubDate>Thu, 21 Oct 2021 18:44:09 GMT</pubDate>
            <description><![CDATA[<p>Kotlin 에서는 다양한 프로그래밍 언어와 다를 것 없이 여러가지 Collection 자료구조를 제공한다. 조금 특별한 점이라면 Mutable Collection 과 Immutable Collection 을 구분한다는 점이다. </p>
<p>코틀린을 활용해본 사람들이라면 <strong>Collection 자료구조인 List, Set, Map</strong> 등을 활용해보았을 것이다. 이번 포스팅에선 이러한 <strong>Collection 자료구조를 사용할 때 유용하게 접목해볼 수 있는 여러 기본 내장 함수</strong>를 소개하고자 한다. 필자는 개인적으로 코틀린의 Collection 관련 내장 함수들은 타 언어에 비해 정말 빵빵하다고 생각한다. 왜냐하면, 코틀린이라는 언어 자체가 <strong>고차함수를 지원</strong>하기 때문에 <strong>더욱 강력한 동작</strong>을 수행할 수 있기 때문이다. 같이 한 번 살펴보자.</p>
<h2 id="sort">sort()</h2>
<p>대부분 언어가 갖고있는 내장 함수이다. 말 그대로 <strong>Collection 을 정렬</strong>해주는 역할을 수행한다. <strong><code>sorted()</code> 를 활용하면 정렬이 된 새로운 객체를 반환</strong>한다. 그리고 <strong>내림차순 정렬의 경우 <code>sortByDescending()</code></strong> 등을 사용할 수 있다.</p>
<pre><code class="language-kotlin">fun main() {
    val a = mutableListOf(3, 2, 1)
    a.sort()  // 오름차순 정렬
    println(a)

    val sorted = a.sortedByDescending { it } // 내림차순, 정렬된 새로운 Collection 객체 반환
    println(sorted)

    // sortBy() : 각 객체가 갖고있는 프로퍼티를 기준으로 정렬
    val list = mutableListOf(1 to &quot;A&quot;, 2 to &quot;B&quot;, 100 to &quot;C&quot;, 50 to &quot;D&quot;, 10 to &quot;E&quot;)
    list.sortBy { it.second }
    println(list)
}</code></pre>
<p>함수형 프로그래밍 언어답게 <strong><code>sortBy()</code> 라는 고차함수를 통해 Collection 을 구성하는 각 객체들의 특정 프로퍼티를 기준으로 정렬</strong>을 할 수 있도록 해준다. 위 예제에서는 <code>Pair</code> 객체의 <code>second</code> 값을 기준으로 정렬하게 된다. 따라서 위 예제를 실행해보았을 때, 아래와 같은 결과를 보인다.</p>
<pre><code class="language-kotlin">[1, 2, 3]
[3, 2, 1]
[(1, A), (2, B), (100, C), (50, D), (10, E)]</code></pre>
<br>

<h2 id="map">map()</h2>
<p>Collection 에 사용할 수 있는 고차 함수이다. <strong>Collection 을 구성하는 각 요소들에 대해 특정 표현식에 의거하여 변형을 거친 뒤 새로운 Collection 을 반환</strong>해준다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3)
    val b = a.map { it * 10 }
    println(b)
}</code></pre>
<p>각 요소들에 대해 <code>10</code> 을 곱해주도록 매핑시켜줬다. <code>it</code> 키워드를 통해 각 요소의 값을 접근할 수 있다. 따라서 아래와 같은 결과를 보여준다.</p>
<pre><code class="language-kotlin">[10, 20, 30]</code></pre>
<br>

<h2 id="foreach">forEach()</h2>
<p>Collection 을 구성하는 요소들을 예쁘게(?) 하나씩 순회할 수 있다. <strong><code>forEachIndexed()</code> 라는 고차함수</strong> 역시 제공하는데, 이는 Python 의 <strong><code>enumerate()</code></strong> 와 같은 동작을 수행한다. 즉, 각 요소들의 <strong>값 뿐만 아니라 인덱스도 함께 사용할 수 있도록 해주는 녀석</strong>이다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Char&gt; = listOf(&#39;A&#39;, &#39;B&#39;, &#39;C&#39;)

    a.forEach {
        println(it)
    }

    a.forEachIndexed { index, c -&gt;
        println(&quot;$index : $c&quot;)
    }
}</code></pre>
<pre><code class="language-kotlin">A
B
C
0 : A
1 : B
2 : C</code></pre>
<br>

<h2 id="filter">filter()</h2>
<p>해당 고차함수는 이름에서 알 수 있듯 <strong>특정 조건에 부합하는 요소만 걸러내서 새로운 Collection</strong> 을 반환해주는 녀석이다. Boolean 값을 반환하는 표현식을 주입해준다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3, 4, 5, 6)
    val b = a.filter {
        it % 2 == 0
    }
    println(b)
}</code></pre>
<p>위와 같이 짝수만 걸러내서 새로운 Collection 을 반환하는 동작을 구현할 수 있다.</p>
<pre><code class="language-kotlin">[2, 4, 6]</code></pre>
<br>

<h2 id="find">find()</h2>
<p><code>filter()</code> 는 조건에 맞는 모든 녀석들을 걸러내서 새로운 Collection 에 담아줬다고 한다면, <strong><code>find()</code> 는 최초로 조건에 부합하는 녀석을 반환</strong>해주는 녀석이다. 만약 <strong>조건에 부합하는 녀석이 끝까지 발견되지 않는다면 <code>null</code></strong> 을 반환한다는 특징이 있다. </p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3, 4, 5, 6)

    val b = a.find {
        it % 2 == 0
    }

    val c = a.findLast {
        it % 2 == 0
    }

    println(b)
    println(c)
}</code></pre>
<p>반대로 <strong><code>findLast()</code></strong> 라는 친구는 조건에 부합하는 녀석들 중 가장 마지막 녀석을 반환해준다.</p>
<pre><code class="language-kotlin">2
6</code></pre>
<br>

<h2 id="any-all-none">any(), all(), none()</h2>
<p>Collection 각 구성 요소들을 하나씩 검사해보며 Boolean 을 반환하는 함수들이다. 아래를 통해 각 함수들이 어떤 것을 검사하는지 익힐 수 있을 것이다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3, 4, 5, 6)

    if (a.any { it % 2 == 0 }) {
        println(&quot;짝수 데이터가 존재합니당&quot;)
    }

    if (a.all { it % 2 == 0 }) {
        println(&quot;모두 짝수 데이터입니다!&quot;)
    } else {
        println(&quot;홀수 데이터도 섞여있네용..&quot;)
    }

    if (a.none { it &gt; 7 }) {
        println(&quot;7보다 큰 원소가 없습니당&quot;)
    }
}</code></pre>
<ul>
<li><code>any()</code> 는 <strong>조건을 만족하는 녀석이 하나라도 있다면 <code>true</code> 를 반환</strong>하는 녀석이다.</li>
<li><code>all()</code> 은 <strong>모든 녀석이 조건을 만족할 때 <code>true</code> 를 반환</strong>한다.</li>
<li><code>none()</code> 은 <strong>모든 녀석이 조건을 만족하지 않을 때 <code>true</code> 를 반환</strong>한다.</li>
</ul>
<br>

<h2 id="flatmap">flatMap()</h2>
<p>RxJava 에서도 볼 수 있는 연산자인데, 매우 비슷한 동작을 수행한다. <strong>Collection 을 구성하는 요소들 각각마다 원하는 형태로 Collection 을 새로 만들고, 이들을 하나의 Collection 으로 Flatten 하여 반환</strong>한다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3)

    val flatA = a.flatMap {
        listOf(it * 3, it * 4)
    }

    println(flatA)
}</code></pre>
<pre><code class="language-kotlin">[3, 4, 6, 8, 9, 12]</code></pre>
<p>위 동작을 풀어쓰자면, <code>listOf(3, 4)</code> 와 <code>listOf(6, 8)</code> 과 <code>listOf(9, 12)</code> 가 하나의 List 로 Flatten 된 것이다. 각 원소에 대해 <strong><code>listOf(it * 3, it * 4)</code> 형태로 새로운 Collection</strong> 을 만들도록 했기 때문이다.</p>
<br>

<h2 id="partition">partition()</h2>
<p>어떤 원소에 대해 특정 조건을 걸어서, <strong>조건에 부합하는 녀석들과 부합하지 않는 녀석들 이렇게 두 Collection 으로 분리</strong>해준다. 이 때 <strong><code>Pair</code> 형태로 분리</strong>되게 되는데, <strong>조건에 부합하는 녀석들이 <code>first</code> 로 가고 아닌 녀석들을 <code>second</code> 로</strong> 간다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3, 4, 5, 6)

    val partition = a.partition { it % 2 == 0 }

    println(partition.first)   // 조건에 만족하는 녀석들
    println(partition.second)  // 조건에 만족하지 않는 녀석들
}</code></pre>
<pre><code class="language-kotlin">[2, 4, 6]
[1, 3, 5]</code></pre>
<br>

<h2 id="getorelse">getOrElse()</h2>
<p>이 녀석은 Collection 에 인덱스로 값을 참조했을 때, <strong>만약 해당 인덱스에 값이 없을 경우 지정된 스코프 내에서 원하는 동작들을 수행할 수 있고, 스코프 내 가장 마지막 줄 코드의 반환값을 뱉는다</strong>.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 2, 3, 4, 5, 6)

    println(a.getOrElse(2) { 10 })

    println(a.getOrElse(10) {
        println(&quot;10번째 원소가 없습니다&quot;)
        &quot;ㄹㅇㅋㅋ&quot;
    })
}</code></pre>
<p>위 코드를 실행하면 어떤 결과를 뱉을까?</p>
<pre><code class="language-kotlin">3
10번째 원소가 없습니다
ㄹㅇㅋㅋ</code></pre>
<p>이러한 결과가 나타난 이유는, <strong>우선 2로 인덱싱했을 때 3이란 값이 담겨 있으니 정상적으로 반환됐고, 10으로 인덱싱했을 땐 값이 없으니 스코프로 진입</strong>한다. 이 때 <strong>&quot;10번째 원소가 없습니다&quot; 라는 문구를 출력하는 코드가 실행</strong>됐고, 맨 마지막 <strong>&quot;ㄹㅇㅋㅋ&quot; 라는 String 이 반환</strong>되어 이것이 가장 바깥 <strong><code>println()</code> 에 의해 출력</strong>된 것이다. <del>ㄹㅇㅋㅋ</del></p>
<br>

<h2 id="reduce-fold">reduce(), fold()</h2>
<p>Collection 을 구성하는 모든 원소들에 대해 <strong>누적합을 계산하는 함수</strong>들이다. <strong>고차함수이기 때문에 누적합을 어떻게 쌓아올리는 지에 대해 표현식</strong>을 걸어줄 수 있다. <strong><code>fold()</code> 의 경우 초기값을 설정해줄 수 있다. <code>reduce()</code> 는 첫 번째 요소를 acc 로 사용하고, 두 번째 요소 부터 연산</strong>하게 된다.</p>
<pre><code class="language-kotlin">fun main() {
    val a: List&lt;Int&gt; = listOf(1, 3, 5)

    println(&quot;Fold : ${a.fold(0) { acc, i -&gt;
        acc + i * 2
    }}&quot;)

    println(&quot;Reduce : ${a.reduce { acc, i -&gt; 
        acc + i * 2
    }}&quot;)
}</code></pre>
<pre><code class="language-kotlin">Fold : 18
Reduce : 17</code></pre>
<p>위 예제의 경우 <strong><code>현재 누적합 + (현재 값 * 2)</code> 라는 표현식을 통해 누적합</strong>을 쌓아가게 된다.</p>
<h3 id="fold-의-결과가-18이-나온-과정">fold() 의 결과가 18이 나온 과정</h3>
<ol>
<li>acc : 0, i : 1 → 0 + (1 * 2) = 2</li>
<li>acc : 2, i : 3 → 2 + (3 * 2) = 8</li>
<li>acc : 8, i = 5 → 8 + (5 * 2) = 18</li>
</ol>
<h3 id="reduce-의-결과가-17이-나온-과정">reduce() 의 결과가 17이 나온 과정</h3>
<ol>
<li>acc : 1, i : 3 → 1 + (3 * 2) = 7</li>
<li>acc : 7, i = 5 → 7 + (5 * 2) = 17</li>
</ol>
<hr>
<p>이번 포스팅에선 함수형 프로그래밍 언어인만큼 고차함수를 활용하여 빵빵하게 지원되는 코틀린의 Collection 내장 함수 몇 가지에 대해 살펴보았다. 정말 유용하고 다양한 구현을 쉽게 해볼 수 있어 편리한 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] Scheduler 설정하기]]></title>
            <link>https://velog.io/@haero_kim/RxJava-Scheduler-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@haero_kim/RxJava-Scheduler-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 20 Oct 2021 18:51:36 GMT</pubDate>
            <description><![CDATA[<h2 id="scheduler-스케줄러">Scheduler (스케줄러)</h2>
<p>RxJava 는 <strong>다양한 문제를 해결할 수 있는 범용적인 솔루션</strong>이다. 특히 <strong>멀티 쓰레딩과 같은 비동기 작업을 효율적</strong>으로 구현할 수 있는 환경을 제공해준다. 이는 <strong>스케줄러</strong>라는 녀석을 활용하게 된다. 스케줄러는 <strong>데이터 스트림이 어떤 쓰레드에서 데이터를 발행하는지, 구독자는 어떤 쓰레드에서 이벤트 발생을 통보받는지</strong>에 대해 지정해줄 수 있다. </p>
<p>RxJava 에서는 Schedulers 클래스에서 제공하는 <strong>정적 패토리 메소드</strong>를 통해 스케줄러를 설정해줄 수 있다.</p>
<pre><code class="language-kotlin">val singleSchedulers = Schedulers.single()
val ioSchedulers = Schedulers.io()
val newThreadSchedulers = Schedulers.newThread()
val computationSchedulers = Schedulers.computation()
val trampolineSchedulers = Schedulers.trampoline()

// 안드로이드에서만 제공하는 특별한 스케줄러
// val mainThread = AndroidSchedulers.mainThread()</code></pre>
<br>

<h2 id="scheduler-종류">Scheduler 종류</h2>
<blockquote>
<h3 id="single-스케줄러">Single 스케줄러</h3>
</blockquote>
<pre><code class="language-kotlin">val singleSchedulers = Schedulers.single()</code></pre>
<p>Single 스케줄러는 단일 쓰레드를 생성하여 이를 계속 재사용하는 방식을 활용한다. RxJava 내부에서 쓰레드를 별도로 생성하여, 한 번 생성된 쓰레드를 활용하며 여러 작업을 처리하게 된다.</p>
<blockquote>
<h3 id="io-스케줄러">IO 스케줄러</h3>
</blockquote>
<pre><code class="language-kotlin">val ioSchedulers = Schedulers.io()</code></pre>
<p>이 녀석은 <strong>네트워킹</strong> 작업이나 <strong>DB 트랜잭션, 파일 시스템</strong> 환경 등 블로킹이 발생할 수 있는 곳에서 <strong>비동기적으로 작업</strong>을 처리하기 위해 사용되는 스케줄러이다. <strong>쓰레드 풀을 사용하여 새로운 쓰레드가 필요할 때마다 쓰레드를 계속 생성</strong>하되, <strong>이전에 생성했던 쓰레드가 존재한다면 이를 재사용</strong>한다. 내부적으로 <strong><code>CachedThreadPool</code></strong> 을 채택했다.</p>
<blockquote>
<h3 id="newthread-스케줄러">newThread 스케줄러</h3>
</blockquote>
<pre><code class="language-kotlin">val newThreadSchedulers = Schedulers.newThread()</code></pre>
<p>newThread 스케줄러는 <strong>매번 새로운 쓰레드를 생성</strong>하여 작업을 처리하도록 지정해주는 녀석이다.</p>
<blockquote>
<h3 id="computation-스케줄러">Computation 스케줄러</h3>
</blockquote>
<pre><code class="language-kotlin">val computationSchedulers = Schedulers.computation()</code></pre>
<p>해당 스케줄러는 <strong>단순한 반복 작업, 콜백 처리</strong> 등등 컴퓨팅 및 계산적인 작업에 사용한다. CPU 에 대응하는 계산용 스케줄러이고, 내부적으로 쓰레드 풀을 활용한다. 기본적으로 쓰레드 개수는 프로세서 개수와 같다.</p>
<blockquote>
<h3 id="trampoline-스케줄러">Trampoline 스케줄러</h3>
</blockquote>
<pre><code class="language-kotlin">val trampolineSchedulers = Schedulers.trampoline()</code></pre>
<p><strong>새로운 쓰레드를 생성하지 않고, 현재 쓰레드에 무한한 크기의 큐를 생성</strong>한다. 큐의 특성인 <strong>FIFO</strong> 에 따라, 모든 작업을 들어온 <strong>순서대로 (순차적으로) 실행하는 것을 보장</strong>하게 된다.</p>
<blockquote>
<h3 id="mainthread-스케줄러-rxandroid-에만-포함">mainThread 스케줄러 (RxAndroid 에만 포함)</h3>
</blockquote>
<pre><code class="language-kotlin">val mainThread = AndroidSchedulers.mainThread()</code></pre>
<p>RxAndroid 에서는 <strong>안드로이드 메인 쓰레드를 지정하는 스케줄러를 제공</strong>한다.</p>
<br>

<h2 id="scheduler-연산자">Scheduler 연산자</h2>
<p>RxJava 에서 스케줄러를 활용하기 위해선, <code>subscribeOn</code> 메소드와 <code>observeOn</code> 메소드를 활용해볼 수 있다. 이것들만 있다면 정말 간단하게 멀티 쓰레딩을 구현해볼 수 있다.</p>
<p>우선, 0 부터 3까지 데이터를 발행하는 <strong>Observable</strong> 을 생성해보자. 아래와 같이 구현할 수 있을 것이다.</p>
<pre><code class="language-kotlin">fun main() {
    Observable.create&lt;Int&gt; {
        for (i in 0..3){
            val threadName = Thread.currentThread().name
            println(&quot;#발행 [$threadName] : $i&quot;)
            it.onNext(i)
            Thread.sleep(100)
        }
    }.subscribe {
        val threadName = Thread.currentThread().name
        println(&quot;#구독 [$threadName] : $it&quot;)
    }
}</code></pre>
<p>위 코드는 스케줄러 연산자를 사용하지 않았다. 그랬더니 결과는 다음과 같이 나온다.</p>
<pre><code class="language-kotlin">#발행 [main] : 0
#구독 [main] : 0
#발행 [main] : 1
#구독 [main] : 1
#발행 [main] : 2
#구독 [main] : 2
#발행 [main] : 3
#구독 [main] : 3</code></pre>
<p>스케줄러를 지정해주지 않는다면, <strong>데이터 발행과 구독이 모두 메인 쓰레드</strong>에서 진행된다.</p>
<br>

<blockquote>
<h3 id="subscribeon"><code>subscribeOn()</code></h3>
</blockquote>
<p>그럼 이제 한번, <strong><code>subscribeOn()</code></strong> 메소드를 활용하여 스케줄러를 지정해보자.</p>
<pre><code class="language-kotlin">fun main() {
    Observable.create&lt;Int&gt; {
        for (i in 0..3) {
            val threadName = Thread.currentThread().name
            println(&quot;#발행 [$threadName] : $i&quot;)
            it.onNext(i)
            Thread.sleep(100)
        }
    }.subscribeOn(Schedulers.io())
        .subscribe {
            val threadName = Thread.currentThread().name
            println(&quot;#구독 [$threadName] : $it&quot;)
        }
    Thread.sleep(500)
}</code></pre>
<p>실행해보면 아래와 같이 결과를 출력한다.</p>
<pre><code class="language-kotlin">#발행 [RxCachedThreadScheduler-1] : 0
#구독 [RxCachedThreadScheduler-1] : 0
#발행 [RxCachedThreadScheduler-1] : 1
#구독 [RxCachedThreadScheduler-1] : 1
#발행 [RxCachedThreadScheduler-1] : 2
#구독 [RxCachedThreadScheduler-1] : 2
#발행 [RxCachedThreadScheduler-1] : 3
#구독 [RxCachedThreadScheduler-1] : 3</code></pre>
<p>위 결과에서 알 수 있듯, 해당 연산자는 <strong>Observable 데이터 스트림에 어떤 스케줄러를 사용하여 데이터를 발행할지 지정</strong>해주는 메소드이다. 만약 <code>subscribeOn</code> 이 메소드 체이닝되어 있는데 <strong><code>observeOn</code> 이 체이닝되지 않은 경우 발행되는 데이터를 구독하는 쓰레드도 동일한 쓰레드에서 동작</strong>하도록 한다.</p>
<br>

<blockquote>
<h3 id="observeon"><code>observeOn()</code></h3>
</blockquote>
<p>그렇다면 <strong><code>observeOn</code> 은 발행되는 데이터를 구독하는 쓰레드를 지정해주는 연산자로 유추</strong>할 수 있다. 일단 코드에 적용해보자. 위 예제 코드에 <code>observeOn()</code> 을 덧붙여 아래와 같이 스케줄러를 지정해준 뒤 결과를 확인해보자.</p>
<pre><code class="language-kotlin">fun main() {
    Observable.create&lt;Int&gt; {
        for (i in 0..3) {
            val threadName = Thread.currentThread().name
            println(&quot;#발행 [$threadName] : $i&quot;)
            it.onNext(i)
            Thread.sleep(100)
        }
    }.subscribeOn(Schedulers.io())
        .observeOn(Schedulers.computation())
        .subscribe {
            val threadName = Thread.currentThread().name
            println(&quot;#구독 [$threadName] : $it&quot;)
        }
    Thread.sleep(500)
}</code></pre>
<pre><code class="language-kotlin">#발행 [RxCachedThreadScheduler-1] : 0
#구독 [RxComputationThreadPool-1] : 0
#발행 [RxCachedThreadScheduler-1] : 1
#구독 [RxComputationThreadPool-1] : 1
#발행 [RxCachedThreadScheduler-1] : 2
#구독 [RxComputationThreadPool-1] : 2
#발행 [RxCachedThreadScheduler-1] : 3
#구독 [RxComputationThreadPool-1] : 3</code></pre>
<p>결과를 보니 <strong>발행과 구독이 각기 다른 쓰레드에서 실행</strong>되고 있음을 확인할 수 있다. 비로소 <strong>멀티 쓰레딩</strong>을 구현하게 된 것이다.</p>
<p><code>observeOn</code> 연산자를 활용하여 스케줄러를 지정해준다면, <strong>Observable 데이터 스트림에서 발행한 데이터를 가로채서 지정한 스케줄러에서 이를 구독</strong>한다. 따라서 위와 같은 결과가 나오게 된다.</p>
<br>

<h3 id="android-에서는">Android 에서는</h3>
<p>일반적으로 안드로이드에서 RxJava 를 사용 목적에 맞게 가장 많이 사용하는 부분은 <strong>백엔드 서버와 네트워킹 동작을 하거나, DB 쿼리 동작을 수행</strong>하는 부분이다. 따라서 이에 가장 적합한 <strong>IO 스케줄러</strong>를 <strong><code>subscribeOn()</code></strong> 을 통해 지정해줌으로써 <strong>IO 스케줄러 상으로 결과 데이터를 발행</strong>할 수 있도록 한다.</p>
<p>그리고 안드로이드에선 보통 위와 같은 <strong>비동기 동작의 결과물을 메인 쓰레드 (UI 쓰레드) 에서 UI 를 갱신</strong>하는 등 활용하기 때문에 이를 <strong><code>AndroidSchedulers.mainThread()</code></strong> 로 지정한다.</p>
<pre><code class="language-kotlin">repository.getData()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())</code></pre>
<blockquote>
<p>거의 <strong>국룰이다시피 활용되는 스케줄러</strong>들이기 때문에, 아래와 같이 <strong><code>Observable</code> 혹은 <code>Single</code> 데이터 스트림에 확장함수 형태로 스케줄러 지정 코드를 정의</strong>해두기도 한다. (필자도 애용한다)</p>
</blockquote>
<pre><code class="language-kotlin">fun &lt;T&gt; Single&lt;T&gt;.applySchedulers() =
    subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
fun &lt;T&gt; Observable&lt;T&gt;.applySchedulers() =
    subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())</code></pre>
]]></description>
        </item>
    </channel>
</rss>