<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>_im_ssu.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 15 Oct 2023 20:06:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>_im_ssu.log</title>
            <url>https://velog.velcdn.com/images/_im_ssu/profile/9c75b41f-b6df-4e0a-9fed-37f8f9c22404/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. _im_ssu.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/_im_ssu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Android, Ktor + OpenApi Generator 도전?적용?기]]></title>
            <link>https://velog.io/@_im_ssu/Android-Ktor-OpenApi-Generator-%EB%8F%84%EC%A0%84%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@_im_ssu/Android-Ktor-OpenApi-Generator-%EB%8F%84%EC%A0%84%EC%A0%81%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Sun, 15 Oct 2023 20:06:53 GMT</pubDate>
            <description><![CDATA[<p>지난 시간에 이어, OpenApi Generator 적용기 2탄입니다.
(1탄: <a href="https://velog.io/@_im_ssu/OpenApi-Generator-in-Android">https://velog.io/@_im_ssu/OpenApi-Generator-in-Android</a>)</p>
<p>1탄에서는, OpenApi Generator 그게 뭔데, 어떻게 하는 건데! 를 알아보기 위한 테스트를 다루었는데요. 
이번 2탄에서는, 실제 적용을 어떻게 할지, 적용이 가능할지를 알기 위한 세팅 및 테스트 작업을 다뤄보도록 할게요.</p>
<h1 id="서론-ktor-이유">서론: Ktor, 이유</h1>
<p>저희가 개발할 서비스는 <strong>우테코 크루들을 위한 공지&amp;출결 앱 서비스</strong> 인데요. 
다만, 
(1) 우테코 내에는 아직 ios 과정은 없어, 아이폰을 쓰는 크루들을 위한 앱 서비스는 부재하게 되어요. (물론 웹 서비스 사용이 가능할 예정이어요!)
(2) 또한, kotlin multiplatform을 경험&amp;시도 해보고자 하는 욕구가 있어요.
(3) 현업 개발자가 아닌 학습을 하고 있는 크루로서, 짱짱 레아와 함께 개발할 수 있는 지금, 이것저것 해보기에 대한 두려움을 갖지 않기로 했습니다. <em>(a.k.a. 낭만주도개발)</em>
물론 러닝 커브에 대한 걱정이 많았지만, 앞으로 개발을 하며 경험해보기 힘들 수 있는 것들을 이번 기회에 도전해보면 좋겠다는 제이슨의 말씀이 많이 와닿았습니다. 뭉클,,감동,,</p>
<p>이러한 이유로, 저희는 kotlin multiplatform 활용에 대한 확장성을 열어두고, <strong>ktor</strong>를 적용하기로 결정하였습니다. <em>(잘부탁해 산군ㅎㅎ)</em></p>
<h1 id="ktor-기본-세팅">Ktor 기본 세팅</h1>
<p><a href="https://openapi-generator.tech/docs/generators/kotlin">공식문서</a>를 보면,
<img src="https://velog.velcdn.com/images/_im_ssu/post/203cbbe8-8193-43fa-aec7-c266d7f30353/image.png" alt="">
다음과 같이 ktor를 지원함을 볼 수 있는데요.
결론부터 말씀드리면, 위의 문서는 업데이트가 더딘 것 같아요.
이유에 대해서는 후술하도록 할게요!</p>
<p>ktor 세팅은 다음과 같이 해줍니다.</p>
<pre><code class="language-kotlin">    // build.gradle.kts (project)
    id(&quot;org.jetbrains.kotlin.plugin.serialization&quot;) version &quot;1.6.21&quot;

    // build.gradle.kts (app)
    plugins {
        ...
        id(&quot;kotlinx-serialization&quot;)
    }

    // ktor
    implementation(&quot;io.ktor:ktor-client-core:2.1.3&quot;)
    implementation(&quot;io.ktor:ktor-client-cio:2.1.3&quot;)
    implementation(&quot;io.ktor:ktor-client-content-negotiation:2.1.3&quot;)
    testImplementation(&quot;io.ktor:ktor-client-mock:2.1.3&quot;)
    // serialization
    implementation(&quot;org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1&quot;)
    implementation(&quot;io.ktor:ktor-serialization-kotlinx-json:2.1.3&quot;) </code></pre>
<p><a href="https://ktor.io/docs/getting-started-ktor-client.html#add-dependencies">ktor client 시작하기 공식문서</a>를 보면,</p>
<blockquote>
<p><code>ktor-client-core</code> is a core dependency that provides the main client functionality, while <code>ktor-client-cio</code> is a dependency for an engine processing network requests.</p>
</blockquote>
<p><code>ktor-client-core</code>는 ktor-client 기능들을 사용하기 위한 핵심 라이브러리고, <code>ktor-client-cio</code>는 네트워크 요청을 처리하는 엔진에 대한 종속성입니다.
Ktor HTTP 클라이언트는 JVM, Android, JS, Native(iOS/desktop)과 같이 다른 플랫폼에서 사용될 수 있고, 플랫폼마다 네트워크 처리 요청을 위한 특정 engine이 필요할 수 있어요.</p>
<p><code>Content Negotiation</code>은 client와 server 간의 media type을 중계해주고 (Accept, Content-Type Header 사용),
요청/응답 콘텐츠를 직렬화/역직렬화합니다.</p>
<p>JSON 직렬화/역직렬화를 위해 <code>kotlinx.serialization</code> 의존성도 추가해주었습니다.</p>
<h1 id="code-generate">code generate</h1>
<p>지난 블로그에서 설명한 방식대로, 환경에 맞게 코드를 generate 합니다.</p>
<pre><code class="language-kotlin">configOptions.set(
    mapOf(
        &quot;library&quot; to &quot;jvm-ktor&quot;,
        &quot;dateLibrary&quot; to &quot;java8&quot;,
        &quot;omitGradleWrapper&quot; to &quot;true&quot;,
        &quot;sourceFolder&quot; to &quot;src/main/java&quot;,
        &quot;useCoroutines&quot; to &quot;true&quot;,
        &quot;serializationLibrary&quot; to &quot;kotlinx_serialization&quot;
    ),
)</code></pre>
<p><code>&quot;useCoroutines&quot; to &quot;true&quot;</code>를 하면 알아서 suspend가 붙은 함수들이 생성되고,
<code>&quot;serializationLibrary&quot; to &quot;kotlinx_serialization&quot;</code>를 추가하면 kotlinx_serialization으로 직렬화/역직렬화 할 수 있도록 모델이 생성됩니다.
추가하지 않으면 기본으로 moshi를 사용하던데, </p>
<blockquote>
<p>To serialize/deserialize JSON data, you can choose one of the following libraries: kotlinx.serialization, Gson, or Jackson.</p>
</blockquote>
<p>공식문서를 보면 moshi를 지원하지 않으니 꼭 serializationLibrary를 설정해주세요! <del>(저는 3시간 삽질햇슴니다..엉엉)</del>
<br/></p>
<p>위 과정을 거쳐 코드를 생성하고,
더불어 함께 새롭게 생겨난 <code>build.gradle</code>을 확인해보시면,</p>
<pre><code class="language-groovy">ext.ktor_version = &#39;2.1.3&#39;</code></pre>
<p>ktor_version을 2.1.3을 사용하고 있음이 보입니다.
이러한 이유로 app gradle에서도 ktor 버전을 <code>1.6.7</code> -&gt; <code>2.1.3</code> 으로 올려주었습니다.
놀랍게도 이 버전을 맞춰주니 generate된 코드들 중 빨간 줄이 뜨던 것들이 싹 사라집니다!! (뿌듯)
<br/></p>
<p>다만, generate된 코드들 중 딱 한 가지 수정사항이 있었는데요.</p>
<pre><code class="language-kotlin">// ApiClient.kt
private val clientConfig: (HttpClientConfig&lt;*&gt;) -&gt; Unit by lazy 
    {
        it.install(ContentNegotiation) {
            json() // 요 한 줄 추가 !!
        }
        httpClientConfig?.invoke(it)
    }
}</code></pre>
<p>HttpClient 인스턴스를 관리하는 ApiClient 클래스가 생성되었는데요.
(마치 Retrofit 인스턴스 - Retrofit.Builder 클래스가 떠오릅니다)</p>
<p><a href="https://ktor.io/docs/serialization-client.html#configure_serializer">공식문서</a>에 따르면,
이는 Json serializer를 등록하기 위한 코드입니다.
즉, 나 앱에서 Json 형태로 통신할거야 ~~ 라고 등록하는 겁니다!</p>
<p>이 코드가 없다면,</p>
<blockquote>
<p>No transformation found: class io.ktor.utils.io.ByteBufferChannel -&gt; class model.{ClassName}</p>
</blockquote>
<p>위의 오류가 발생합니다.</p>
<p>(2023/10/24 update)
다만, open api generator의 특성상, 
api에 수정사항이 생길 때마다 코드를 새롭게 generate 해야 합니다.
그러므로 최대한 코드에 수정을 하지 않는 것이 좋은데요.
<code>kotlinx-serialization</code>이 아닌 <code>gson</code>을 사용하면
해당 코드 위치에 <code>gson()</code>이 굳이 추가하지 않아도 작성되어 있습니다.
번거로움이 싫다면 <code>gson</code>을 사용하시길,,,
<del><em>(이렇게 백로그는 계속 추가되는데...)</em></del></p>
<h1 id="작동-테스트">작동 테스트</h1>
<p>generate된 코드들이 잘 작동하는지, 이거이거 쓸만한건지~ 를 알기 위해 테스트 코드를 <del>kotest로 하려고 했지만 익숙하지 않아 힘들어서 우선 JUnit으로</del> 작성해보았는데요 !</p>
<pre><code class="language-kotlin">@Test
fun codegenTest() = runTest {
    val api = DefaultApi(
        MOCK_SERVER_URL, null, null
    )
    val response = api.resourceIdGet(1)
    val actual = response.body()
    val expected = ResourceIdGet200Response(1, &quot;Example Resource&quot;)
    assertEquals(expected, actual)
}</code></pre>
<p>sample api를 만들고, postman으로 mock server를 만들어 테스트를 진행했습니다.
(mock server 만드는 과정은 <a href="https://velog.io/@_im_ssu/Postman-Mock-Server-%EB%A7%8C%EB%93%A4%EA%B8%B0">이 링크</a>에!)</p>
<p>이미 코드들이 만들어져 있다보니, 사실상 사용할 것은
<code>Api/DefaultApi</code>, <code>model/ResourceIdGet200Response</code>
이 두 클래스 뿐이었어요.</p>
<p>DefaultApi에는 URL만 mock server url을 넣어주고, 
나머지는 null로 두어 기본 default로 생성되도록 했습니다.
이후 api를 호출하고, response의 body를 가져와
예상값(mock server example)과 비교해주었어요.</p>
<p>결과는? <strong>짜잔!</strong> <strong>테스트가 통과했습니다.</strong> 👏👏</p>
<h1 id="급-마무리">급 마무리</h1>
<p>그래서 후기는.. 생각보다 쓸만할지도? 입니다.
우여곡절은 좀 있었지만, 점점 이거 괜찮은데 쓸만하겠는데? 생각이 들어요.
물론, api가 복잡해지고, 로그인 기능이 붙는다면 어떨지는 앞으로 차차 공부고민공부고민을 해보아야겠습니다 🤔</p>
<h1 id="참고-문헌">참고 문헌</h1>
<p>중간중간 링크한 공식문서 외,,</p>
<p>Ktor Client&amp;Engine 
<a href="https://ktor.io/docs/eap/http-client-engines.html">https://ktor.io/docs/eap/http-client-engines.html</a> 
<a href="https://essie-cho.com/kotlin-4/">https://essie-cho.com/kotlin-4/</a>
<a href="https://velog.io/@shins/Kotlin-HTTP-Client-ktor-client">https://velog.io/@shins/Kotlin-HTTP-Client-ktor-client</a>
Content negotiation and serialization
<a href="https://ktor.io/docs/serialization-client.html">https://ktor.io/docs/serialization-client.html</a>
Configure a serializer
<a href="https://jyami.tistory.com/150">https://jyami.tistory.com/150</a>
Ktor request
<a href="https://essie-cho.tistory.com/36">https://essie-cho.tistory.com/36</a> 
Ktor test
<a href="https://ktor.io/docs/http-client-testing.html">https://ktor.io/docs/http-client-testing.html</a>
Ktor 간단 설명
<a href="https://ktor.io/docs/http-client-testing.html#share-config">https://ktor.io/docs/http-client-testing.html#share-config</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Postman Mock Server 만들기]]></title>
            <link>https://velog.io/@_im_ssu/Postman-Mock-Server-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@_im_ssu/Postman-Mock-Server-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 15 Oct 2023 18:32:00 GMT</pubDate>
            <description><![CDATA[<p>아아 오늘은 Postman을 이용하여 MockServer 만들기를 simple&amp;easy하게 소개하겠습니다</p>
<h1 id="api">api</h1>
<p>오늘의 실습을 위해 간단한 api를 만들어보았어요.
아주아주 단순한 @GET 하나, @DELETE 하나랍니다.</p>
<h3 id="get-resourceid">GET /resource/{id}</h3>
<h4 id="request">Request</h4>
<pre><code class="language-json">{
    &quot;id&quot;: 1
}</code></pre>
<h4 id="respond">Respond</h4>
<pre><code class="language-json">200 OK
{
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Example Resource&quot;
}</code></pre>
<h3 id="delete-resourceid">DELETE /resource/{id}</h3>
<h4 id="request-1">Request</h4>
<pre><code class="language-json">{
    &quot;id&quot;: 1,
}</code></pre>
<h4 id="response">Response</h4>
<pre><code class="language-json">204 No Content</code></pre>
<p>아주 간단하죠? 이걸로 Mock Server를 만들어보도록 할게요!</p>
<h1 id="mockserver-만들기">MockServer 만들기</h1>
<p>API 명세가 있으신 여러분, Postman을 켜세요!
<img src="https://velog.velcdn.com/images/_im_ssu/post/64484ac3-0e10-429e-b79d-cb7d2cc2c6bf/image.png" alt="">
Workspace에 들어가면 다음과 같이 보일 텐데요.
여기서 <strong>Mock Servers</strong>를 클릭해서 <strong>Create Mock Server</strong> 클릭 ! 
본격적으로 MockServer를 만들어봅시다.
(만약 Mock Servers가 보이지 않는다면 하단 버튼(초록 네모)을 눌러보면 보입니다!)</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/28b36e4d-5eb9-46f5-81ff-fd961f454991/image.png" alt=""></p>
<p>다음과 같이 Mock Server에 들어갈 Request들의 정보들을 적어준 후,</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/9ce5f934-98c8-4fce-9823-dbb117d105cf/image.png" alt=""></p>
<p>Mock Server 세팅을 해주면 끝 ! <strong>Create Mock Server</strong>를 해줍시다 !</p>
<h3 id="보너스-팁">보너스 팁</h3>
<blockquote>
<p>💡 Save the mock server URL as an new environment variable</p>
</blockquote>
<p>이 부분을 체크해주면, 생성한 mock server의 긴 url을 postman 내의 환경변수로 저장해주어요.
<img src="https://velog.velcdn.com/images/_im_ssu/post/8dd19569-369c-4f67-acce-05bccdb5bc14/image.png" alt="">
짜잔 url이라는 이름으로 변수가 만들어졌네요!
이 변수는 <code>{{url}}</code> 와 같은 형태로 사용이 가능합니다.</p>
<h1 id="example-만들기">Example 만들기</h1>
<p>Mock Server를 만든 뒤, 왼쪽 메뉴 중 <strong>Collections</strong>를 눌러보세요.
<img src="https://velog.velcdn.com/images/_im_ssu/post/82ede075-ffa1-42ad-ac10-069196d6e48f/image.png" alt=""></p>
<p>다음과 같이 <strong>Collection</strong>이 만들어져 있답니다 (짝짝)
api가 만들어져있고, <code>e.g.</code>가 붙어있는 부분이 해당 api의 예시입니다.</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/baad440b-439b-4269-8290-6500ec37fdc4/image.png" alt=""></p>
<p>Params, Authorization, Headers 등의 조건들을 다르게 하여 예시를 계속 추가할 수 있어요.
<br/>
본격적으로 example을 만들기 위해, 우선 <code>e.g.</code>가 붙어있는 예제를 편집해볼게요.
<img src="https://velog.velcdn.com/images/_im_ssu/post/169f07fd-ad14-46d8-80b2-9eab55a4e6c9/image.png" alt=""></p>
<p>상단의 Params, Headers, Body에는 <strong>Request</strong>에 관한 정보,
하단의 Body, Headers에는 <strong>Response</strong>에 관한 정보를 적어줘요.
(저는 하단의 Body에 <strong>Json</strong> 형태로 받을 응답 예시를 적어주었어요!)</p>
<p>Try를 눌러 API를 호출했을 때 원하는 응답값이 잘 나온다면, 
잊지 말고 변경 내역을 Save 해주세요 :)</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/97e0794b-63d4-472b-a785-097fe6cb2f85/image.png" alt="">
저장을 하고 Mock Server의 <strong>View Collection Docs</strong>를 눌러보면,
해당 Example이 추가된 Mock Server 문서를 확인해볼 수 있어요.</p>
<h3 id="example-추가">Example 추가</h3>
<p>만약, 새로운 조건 혹은 새로운 응답값, 또는 실패의 경우에 대해서도 example을 추가하고 싶다면,
<img src="https://velog.velcdn.com/images/_im_ssu/post/6395527a-2aa1-4ae2-a23b-ffc209dc6d34/image.png" alt="">
Add example을 해주면 됩니다. (간단하죵!?)</p>
<h1 id="mock-server-test">Mock Server Test</h1>
<p>만들어진 Mock Server를 이용해서, Android 테스트에 적용해볼게요!</p>
<pre><code class="language-kotlin">// HttpClient ( Retrofit2라면 Retrofit.Builder() )
val api = DefaultApi(
    // BaseUrl에 Mock Server Url 전달
    MOCK_SERVER_URL, null, null
)
// api 호출
val response = api.resourceIdGet(1)

val actual = response.body()
val expected = ResponseModel(1, &quot;Example Resource&quot;)
assertEquals(expected, actual)</code></pre>
<p>저는 <code>Ktor</code>를 활용해 API를 테스트해보았는데요!
<em>(제 이전 게시글을 보셨다면.. 눈치채셨을 수 있지만.. 호출하는 함수들이 code generator를 활용해 만들어진 코드들이므로.. 각 함수의 내부 코드는  저도 이해하지 못해서.. 공개하지 않을게욥... ㅠ_ㅠ)</em></p>
<p>여러분의 RetrofitBuilder, 혹은 HttpClient 등 서버 통신을 위한 클래스를 만들 때, <strong>BaseUrl에 mock server URL을 넘겨주면</strong> Mock server와 통신이 가능해집니다.</p>
<p><br/><br/>
.. 끗 !</p>
<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://devvkkid.tistory.com/219#toc-%E2%9C%8D%EF%B8%8F%20Mock%20Server%20%EB%A7%8C%EB%93%A4%EA%B8%B0">https://devvkkid.tistory.com/219#toc-%E2%9C%8D%EF%B8%8F%20Mock%20Server%20%EB%A7%8C%EB%93%A4%EA%B8%B0</a> 
<a href="https://velog.io/@whytili/Postman-Mock-Server">https://velog.io/@whytili/Postman-Mock-Server</a> 
<a href="https://velog.io/@jeongminji4490/Android-Postman%EC%9C%BC%EB%A1%9C-Mock-Server-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%84%9C-API-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0">https://velog.io/@jeongminji4490/Android-Postman%EC%9C%BC%EB%A1%9C-Mock-Server-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%84%9C-API-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenApi Generator in Android]]></title>
            <link>https://velog.io/@_im_ssu/OpenApi-Generator-in-Android</link>
            <guid>https://velog.io/@_im_ssu/OpenApi-Generator-in-Android</guid>
            <pubDate>Tue, 10 Oct 2023 13:59:13 GMT</pubDate>
            <description><![CDATA[<h1 id="openapi-generator-in-android">OpenApi Generator in Android</h1>
<p>우테코 내의 플랫폼 근로 활동을 하며, <code>code generator</code>을 활용하자는 의견이 나왔습니다.
그에 따라, Android에서 어떻게 시작하고 활용할 수 있을지, 사용하기에 적절한지를 알아보기 위해 다음과 같은 실습을 진행했어요!</p>
<p>연습한 코드 저장소: <a href="https://github.com/sujin9/Android_OpenApi_Generator_Test">링크</a></p>
<blockquote>
<p>OpenApi Generator 공식문서에서 제공하는 실습용 <code>petstore.yaml</code>을 이용하였으며, <br/>
Android Studio Giraffe | 2022.3.1 에서 진행하였습니다.</p>
</blockquote>
<h2 id="안드로이드에서-openapi-generator-시작하기">안드로이드에서 <code>OpenApi Generator</code> 시작하기</h2>
<h3 id="1-openapi-generator-cli-설치하기">1. <code>openapi-generator-cli</code> 설치하기</h3>
<ul>
<li><strong>(맥 기준)</strong> 터미널에서 <code>brew install openapi-generator</code> 을 실행해줍니다.</li>
</ul>
<h3 id="2-안드로이드-프로젝트-및-gradle-설정하기">2. 안드로이드 프로젝트 및 gradle 설정하기</h3>
<p>서버 개발자로부터 받은 api <code>.yaml</code> 파일을 프로젝트 내의 경로에 넣어줍니다.</p>
<p>이후, gradle을 다음과 같이 설정해주세요.</p>
<pre><code class="language-kotlin">// build.gradle.kts (project 모듈)
plugins {
    ...
    id(&quot;org.openapi.generator&quot;) version &quot;7.0.1&quot; apply true
}

buildscript {
    repositories {
        maven(url = &quot;https://repo1.maven.org/maven2&quot;)
    }
}

tasks.register(
    &quot;generateClient&quot;,
    org.openapitools.generator.gradle.plugin.tasks.GenerateTask::class,
) {
    generatorName.set(&quot;kotlin&quot;)
    // .yaml 파일이 위치하는 경로
    inputSpec.set(&quot;$rootDir/specs/petstore-v3.0.yaml&quot;)
    // generate된 코드가 위치할 경로 
    outputDir.set(&quot;$rootDir/network&quot;)
    // genertate된 코드가 가질 상위 경로
    packageName.set(&quot;com.example.network&quot;)
    apiPackage.set(&quot;com.example.network.api&quot;)
    modelPackage.set(&quot;com.example.network.model&quot;)
    invokerPackage.set(&quot;com.example.network.invoker&quot;)
    // 설정하기
    // 프로젝트 설정에 맞는 library 선택 (하단 링크&gt;공식문서 참고)
    configOptions.set(
        mapOf(
            &quot;library&quot; to &quot;jvm-retrofit2&quot;,
            &quot;dateLibrary&quot; to &quot;java8&quot;,
            &quot;omitGradleWrapper&quot; to &quot;true&quot;,
            &quot;sourceFolder&quot; to &quot;src/main/java&quot;,
            &quot;useCoroutines&quot; to &quot;true&quot;,
        ),
    )
}</code></pre>
<p>(이 <a href="https://openapi-generator.tech/docs/generators/kotlin/#config-options">링크</a>에서 library를 참고하세요!)</p>
<h3 id="4-run-generateclient">4. run <code>generateClient</code></h3>
<p>gradle&gt;Tasks&gt;other&gt;generateClient 를 실행해주세요.
<img src="https://velog.velcdn.com/images/_im_ssu/post/bdb57eb5-e533-41cc-96ee-f24d515937e8/image.png" width="200"/>
<img src="https://velog.velcdn.com/images/_im_ssu/post/431728bf-a395-4a9a-bcc1-913dec54aa55/image.png" width="200"/></p>
<h3 id="4-생성된-코드-모듈-분리하기">4. 생성된 코드 모듈 분리하기</h3>
<img src="https://velog.velcdn.com/images/_im_ssu/post/5702fbc9-bb71-4339-a48b-fc81acaf0090/image.png" width="400" />
다음과 같이, 지정해준 경로 (rootDir/network)에 코드가 생성됨을 알 수 있습니다.

<p>다만, 아직 모듈화가 되지 않은 상태로 존재하는데요.
방법은 간단합니다!
<code>settings.gradle.kts</code>에 <code>include(&quot;:network&quot;)</code> 를 추가해주세요.</p>
<h3 id="5-build-오류-해결">5. build 오류 해결</h3>
<p>위의 과정을 거친 후 build를 하면, network 모듈의 gradle에서 오류가 발생하는데요.
<img src = "https://velog.velcdn.com/images/_im_ssu/post/5a1708ae-a38a-4f64-ab59-a0a405eb5a54/image.png" width="600" />
사실 위의 코드를 지워주는 것으로 해결이 됩..니다.
<del>(이유를 찾아봐도 모르겠네요 ㅠㅠ 아시는 분 계시다면 공유 부탁드려요..!)</del>
(2023/10/24 update)</p>
<pre><code class="language-kotlin">// setting.gradle.kts
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)</code></pre>
<p>repositoriesMode는 다음과 같이 세 종류가 있는데요,
<code>FAIL_ON_PROJECT_REPOS</code> (default)
<code>PREFER_PROJECT</code>
<code>PREFER_SETTINGS</code>
이 중에서 <code>PREFER_SETTINGS</code>로 설정해주면 오류가 발생하지 않습니다.</p>
<hr>
<p>위의 5단계를 거치면, 아주 간단하게 api 명세에 맞추어 코드들이 생성되고
app에서 바로 그 코드를 사용할 수 있습니다 :) (짝짝)</p>
<pre><code class="language-kotlin">// 요로코롬 말이죠 !
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val pet = Pet(
            &quot;jiny&quot;,
            listOf(&quot;https://get.pxhere.com/photo/nature-grass-flower-wildlife-kitten-cat-mammal-fauna-close-up-whiskers-vertebrate-domestic-cat-macro-photography-young-cat-red-mackerel-tabby-red-cat-cat-baby-wild-cat-small-to-medium-sized-cats-cat-like-mammal-634099.jpg&quot;),
        )
        ApiClient().createService(PetApi::class.java).addPet(pet).enqueue(object : Callback&lt;Pet&gt; {
            override fun onResponse(call: Call&lt;Pet&gt;, response: Response&lt;Pet&gt;) {
                Log.d(&quot;ssuu&quot;, &quot;onResponse: ${response.body()}&quot;)
            }

            override fun onFailure(call: Call&lt;Pet&gt;, t: Throwable) {
                Log.d(&quot;ssuu&quot;, &quot;onFailure: ${t.message}&quot;)
            }
        })
    }
}</code></pre>
<h2 id="고려할-점--아쉬운-점">고려할 점 &amp; 아쉬운 점</h2>
<p>자동으로 생성된 코드들은 정말 편리하게 느껴지고 참으로 신기하죠!
다만, 바로 사용하기엔 아쉬운 점들이 보이고 많은 비용이 들 것으로 예상되는데요. (공부가 더 필요하다는 얘기,,,)</p>
<p>코드를 처음 생성하고 빌드를 하면, 다음과 같은 오류가 발생했어요.
<img src="https://velog.velcdn.com/images/_im_ssu/post/8f308e11-b6cb-4233-981e-1ecf29cd7681/image.png" alt="">
org.apache.oltu.oauth2.common.Xxxx
org.apache.oltu.oauth2.client.Xxxx
위의 두 패키지로부터의 클래스가 중복된다는 오류가 발생합니다.<br/>
network gradle에서 <code>implementation &quot;org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.2&quot;</code> 를 주석처리해주면, 위의 문제는 해결할 수 있습니다.
다만, 위의 라이브러리를 사용하는 부분에서는 당연히 오류가 발생하겠죠 :) 
<br/>
이번 실습을 진행할 때는, 위의 오류와 관련된 코드들을 주석처리하고 진행하고,
기본적인 기능들이 수행되는지만 볼 수 있도록 테스트를 했어요.
하지만 이러한 오류들을 모두 해결하고 코드를 수정하기에는 많은 비용이 들 것 같아요.</p>
<blockquote>
<p>💡 또는 gradle에서 task를 이용하여 필요없는 파일들은 생성되지 않도록 하는 방법도 추가적으로 찾아봐야겠어요! 
(api, model 파일만 생성할 수 있어도...!?)</p>
</blockquote>
<br/>

<p>또한, 코드들에 수많은 주석이 있고 깔끔하지 못한 모습이에요.
코드 제너레이터 기능을 통하여 api service, model (data class)도 만들어지고,
위에서 언급한 oauth 관련 코드들과
수많은 Adapter를 포함한 infrastructure 코드도 함께 만들어집니다. <br/>
다만, 코드 내에 많은 주석들이 포함되어 있고, 가독성이 좋지 못해요.
api 명세가 수정되는 경우, 다시 generate하는 것도, 파일을 직접 수정하는 것도 번거로울 것으로 보여요.
<br/></p>
<h2 id="결론">결론</h2>
<p>허둥지둥 결론을 내려보자면,
code generator는 굉장히 편리하게 느껴지는 기능입니다.
다만, 아직 실제로 사용하기에는 오류 사항들도 있고 안정적이지 못한 것 같아요.
기능들이 많이 보완되고, 추가적인 학습을 통해 편리한 사용법을 찾게 된다면
더없이 편리한 기능이 될 것 같네요 ! :)</p>
<p>이번 근로 활동에서는, RDD (낭만 주도 설계) 를 할 예정이므로 (^^)
code generator + ktor를 사용해볼 계획입니다.
그럼,, 다음 시리즈를 기대해주세요. 뿅,,</p>
<h3 id="참고-자료">참고 자료</h3>
<p><a href="https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/resolve/RepositoriesMode.html">https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/resolve/RepositoriesMode.html</a> 
<a href="https://jaeryo2357.tistory.com/110">https://jaeryo2357.tistory.com/110</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Compose: Unit 3.(1) Kotlin]]></title>
            <link>https://velog.io/@_im_ssu/Compose-Unit-3.1-Kotlin</link>
            <guid>https://velog.io/@_im_ssu/Compose-Unit-3.1-Kotlin</guid>
            <pubDate>Mon, 27 Feb 2023 09:00:40 GMT</pubDate>
            <description><![CDATA[<h1 id="generics">Generics</h1>
<ul>
<li><p>형태</p>
<pre><code>  class className &lt;generic data type&gt; (properties...)
  val instanceName = class&lt;generic data type&gt;(parameters)</code></pre></li>
<li><p>예시</p>
<pre><code class="language-kotlin">class Question&lt;T&gt;(
    val questionText: String,
     val answer: T,
     val difficulty: String
)

fun main() {
      val question1 = Question&lt;String&gt;(&quot;Quoth the raven ___&quot;, &quot;nevermore&quot;, &quot;medium&quot;)
      val question2 = Question&lt;Boolean&gt;(&quot;The sky is green. True or false&quot;, false, &quot;easy&quot;)
      val question3 = Question&lt;Int&gt;(&quot;How many days are there between full moons?&quot;, 28, &quot;hard&quot;)
}</code></pre>
</li>
</ul>
<h1 id="enum-classes">Enum Classes</h1>
<ul>
<li><p>가능한 값 집합이 제한되어 있는 유형</p>
</li>
<li><p>언제 필요한가? -- 만약 위의 예시의 difficulty가 <code>easy</code>, <code>medium</code>, <code>hard</code>로 정의되어 있다면,</p>
<ul>
<li>실수로 값을 잘못 입력할 경우 버그가 발생</li>
<li>medium을 average로 이름을 바꿀 경우 모두 업데이트 필요함</li>
<li>다른 문자열을 사용하는 실수를 방지할 방법이 없음</li>
<li>difficulty가 더 추가될 경우 코드 관리가 어려워짐</li>
</ul>
</li>
<li><p>형태</p>
<pre><code>enum class className(
    CASE1,   
    CASE2
)</code></pre><p><code>,</code>로 구분하고, 상수 이름을 모두 <code>대문자</code>로 표기한다.</p>
<p><code>className.caseName</code> : <code>.</code> 연산자를 사용하여 참조한다.</p>
</li>
<li><p>예시</p>
<pre><code class="language-kotlin">enum class Difficulty {
  EASY, MEDIUM, HARD
}

class Question&lt;T&gt;(
  val questionText: String,
  val answer: T,
  val difficulty: Difficulty
)

val question1 = Question&lt;String&gt;(&quot;Quoth the raven ___&quot;, &quot;nevermore&quot;, Difficulty.MEDIUM)
val question2 = Question&lt;Boolean&gt;(&quot;The sky is green. True or false&quot;, false, Difficulty.EASY)
val question3 = Question&lt;Int&gt;(&quot;How many days are there between full moons?&quot;, 28, Difficulty.HARD) 
</code></pre>
</li>
</ul>
<h1 id="data-classes">Data Classes</h1>
<ul>
<li>위의 Question class 처럼, 데이터만 포함하고 메서드가 없는 경우 =&gt; data class로 정의 가능</li>
<li>data class로 정의하면 일부 메서드가 자동으로 구현됨<ul>
<li>equals(), hashCode(), toString(), componentN(), copy() 등</li>
</ul>
</li>
<li>형태
<code>data class className()</code></li>
<li>특징<ul>
<li>생성자에 매개변수 하나 이상 필요 (val or var 표시)</li>
<li>abstract, open, sealed, inner 불가능</li>
</ul>
</li>
</ul>
<h1 id="singleton-companion-objects">Singleton, Companion objects</h1>
<ul>
<li>클래스에 인스턴스가 하나만 포함되기를 바라는 경우 
=&gt; 해당 클래스의 인스턴스를 하나만 인스턴스화</li>
</ul>
<h2 id="싱글톤-객체">싱글톤 객체</h2>
<ul>
<li><p>형태</p>
<pre><code>object objectName {}
objectName.propertyName : `.` 연산자를 사용하여 참조한다.</code></pre></li>
<li><p>특징</p>
<ul>
<li>생성자가 포함 X</li>
<li>모든 속성이 { } 안에 정의되고 초기값이 부여된다.</li>
</ul>
</li>
<li><p>예시</p>
<pre><code class="language-kotlin">object StudentProgress {
  var total: Int = 10
  var answered: Int = 3
}

fun main() {
  ...
  println(&quot;${StudentProgress.answered} of ${StudentProgress.total} answered.&quot;)
}</code></pre>
</li>
</ul>
<h2 id="companion-객체">Companion 객체</h2>
<ul>
<li>특징<ul>
<li>컴패니언 객체를 사용하여 다른 클래스 내에서 싱글톤 객체를 정의할 수 있다.</li>
<li>클래스 내에서 속성과 메서드에 엑세스할 수 있어 문법이 간결해진다.</li>
</ul>
</li>
</ul>
<h1 id="extend-properties-and-functions">Extend properties and functions</h1>
<ul>
<li><p>예시
<code>16.dp</code></p>
</li>
<li><p>해당 데이터 유형의 일부인 것처럼 <code>.</code>으로 액세스할 수 있는 속성과 메서드를 추가한다.</p>
</li>
<li><p>형태</p>
<pre><code>val typeName.propertyName: dataType
    property getter

fun typeName.functionName(parameters): returnType { }</code></pre></li>
<li><p>예시</p>
<pre><code class="language-kotlin">class Quiz {
  val question1 = Question&lt;String&gt;(&quot;Quoth the raven ___&quot;, &quot;nevermore&quot;, Difficulty.MEDIUM)
  val question2 = Question&lt;Boolean&gt;(&quot;The sky is green. True or false&quot;, false, Difficulty.EASY)
  val question3 = Question&lt;Int&gt;(&quot;How many days are there between full moons?&quot;, 28, Difficulty.HARD)

  companion object StudentProgress {
      var total: Int = 10
      var answered: Int = 3
  }
}

val Quiz.StudentProgress.progressText: String
  get() = &quot;${answered} of ${total} answered&quot;

</code></pre>
</li>
</ul>
<p>  fun main() {
    println(Quiz.progressText)
}</p>
<pre><code>
# Scope functions
  to access class properties and methods
  ## let() =&gt; 긴 객체 이름 바꾸기
  ``` kotlin
fun printQuiz() {
    question1.let {
        println(it.questionText)
        println(it.answer)
        println(it.difficulty)
    }
  }</code></pre><h2 id="apply--변수-없이-객체의-메서드-호출">apply() =&gt; 변수 없이 객체의 메서드 호출</h2>
<pre><code class="language-kotlin">  Quiz().apply {
    printQuiz()
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM signature error 대처하기]]></title>
            <link>https://velog.io/@_im_ssu/JVM-signature-error-%EB%8C%80%EC%B2%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@_im_ssu/JVM-signature-error-%EB%8C%80%EC%B2%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 27 Feb 2023 06:06:09 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-kotlin">class Lotto(val lotto: List&lt;LottoNumber&gt;</code></pre>
<p>다음과 같이 <code>LottoNumber</code> 리스트를 프로퍼티로 가지고 있는 <code>Lotto</code> 클래스가 있다.</p>
<p>참고로 <code>LottoNumber</code> 객체는 로또 범위 내의 숫자들을 미리 인스턴스화 해두었고 팩토리 함수를 통해 이를 리턴하는 형태이다.</p>
<p>이 경우, <code>Lotto</code> 객체를 생성하려면 다음과 같이 코드가 작성된다.</p>
<pre><code class="language-kotlin">val lotto: Lotto = Lotto(listOf(1,2,3,4,5,6).map { LottoNumber.create(it) })</code></pre>
<p>매번 생성할 때마다 다음과 같이 코드를 작성한다면
이는 일반 코드 뿐만 아니라 테스트 코드에서도 엄청난 가독성 방해(!?!)가 일어나게 될 것이고,
좀 더 간단하게 바꿔주고 싶은 강한 욕구가 든다. </p>
<hr>
<pre><code class="language-kotlin">constructor(numbers: List&lt;Int&gt;) : this(numbers.map { LottoNumber.create(it) })</code></pre>
<p>이를 해결하기 위해 위와 같이 부생성자를 만들게 된다면 어떻게 될까?</p>
<blockquote>
<p>Platform declaration clash: The following declarations have the same JVM signature (<init>(Ljava/util/List;)V):
constructor Lotto(numbers: List<Int>) defined in lotto.model.Lotto
constructor Lotto(lotto: List<LottoNumber>) defined in lotto.model.Lotto</p>
</blockquote>
<p>그렇다.. 다음과 같은 오류를 마주하게 된다. :(
<br/>
이 오류가 무슨 의미냐면,
List&lt; Int &gt;와 List&lt; LottoNumber &gt;가 <strong>같은 JVM signature</strong>를 갖고있다는 의미다.
즉, 이 시그니처가 동일하기 때문에 코틀린 코드가 바이트코드로 변경될 때 오류가 발생하는 것이다.</p>
<p>이는 JVM compiler가 파라미터 타입으로 함수 또는 생성자를 구분할 때 generic type은 구분하지 못하기 때문이다.
List&lt; Int &gt;, List&lt; String &gt;, List&lt; LottoNumber &gt; 까지 &lt;&gt; 안에 무엇이 들어와도 에러가 발생한다.</p>
<hr>
<p>이와 같이 JVM signature 오류가 발생한 경우, 보통 함수 앞에 <strong>@JvmName</strong>를 사용함으로써 해결할 수 있다.
@JvmName은 JVM signature를 변경할 때 사용하는 annotation이다.</p>
<pre><code class="language-kotlin">// 예시
@JvmName(&quot;callFromString&quot;)
fun call(a: List&lt;String&gt; {}
@JvmName(&quot;callFromInt&quot;)
fun call(a: List&lt;Int&gt;) {}</code></pre>
<hr>
<p>하지만 @JvmName은 생성자에서는 사용할 수 없다.
이 경우, 이를 해결하기 위한 방법은 무엇이 있을까 ???</p>
<h2 id="vararg">vararg</h2>
<pre><code class="language-kotlin">constructor(vararg numbers: Int): this(numbers.map { LottoNumber.create(it) })</code></pre>
<p>list&lt; Int &gt; 대신 가변인자 vararg 를 사용할 수 있다.
생성할 때 int array를 넘기면 되는데, 이 때 배열 앞에 * 표시를 붙여주어야 한다.</p>
<h2 id="팩토리-함수">팩토리 함수</h2>
<pre><code class="language-kotlin">companion object {
    fun create(lotto: List&lt;Int&gt;) = Lotto(lotto.map { LottoNumber.create(it) }.sortedBy { it.toInt() })
}</code></pre>
<p>companion object 객체를 활용하여 팩토리 함수를 정의한다.</p>
<h2 id="dummy-implicit">Dummy Implicit</h2>
<pre><code class="language-kotlin">constructor(
    numbers: List&lt;Int&gt;,
    @Suppress(&quot;UNUSED_PARAMETER&quot;) dummyImplicit: Any? = null
): this(numbers.map { LottoNumber.create(it) })</code></pre>
<p>사용하지 않는 매개변수를 하나 추가하여 다른 시그니처가 생성되도록 한다.</p>
<hr>
<p>더 많은 방법이 있을 수도 있겠지만 우선 위의 3가지 방법을 공부해보았다.
물론 무엇이 더 옳은지 나쁜지 정답은 없기에, 상황에 맞게 편한 방법으로 대처하면 될 것 같다 :)</p>
<hr>
<p>참고 || 참고 할 만한 자료
<a href="https://stackoverflow.com/questions/69151001/cant-create-secondary-constructors-in-kotlin">링크</a>
<a href="https://codechacha.com/ko/kotlin-annotations/">Kotlin annotation 정리</a>
<a href="https://kyucumber.github.io/posts/book/effective-kotlin/effective-kotlin-item-33">팩토리 함수</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 최종 코테, 그리고 합격 ]]></title>
            <link>https://velog.io/@_im_ssu/%EC%9A%B0%ED%85%8C%EC%BD%94-%EC%B5%9C%EC%A2%85-%EC%BD%94%ED%85%8C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%A9%EA%B2%A9</link>
            <guid>https://velog.io/@_im_ssu/%EC%9A%B0%ED%85%8C%EC%BD%94-%EC%B5%9C%EC%A2%85-%EC%BD%94%ED%85%8C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%A9%EA%B2%A9</guid>
            <pubDate>Sun, 19 Feb 2023 10:12:13 GMT</pubDate>
            <description><![CDATA[<p><em>최종 코테 끝나자마자.. 졸업 서류 내고.... 외주 했다가... 바로 해외로 떠났다가... 오자마자 우테코 시작해서... 이제서야 남기는 어마무시하게 늦어버린 후기....</em></p>
<h1 id="1차-심사-합격">1차 심사 합격</h1>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/476f8125-70a8-4ae9-8a64-ce0bb94fa60a/image.png" alt=""></p>
<p>바야흐로 두 달 전..
그렇다.. 이 메일을 받았다.
이 날 전공 기말고사 시험이 3시 시작이었는데, 정확히 3시에 이 메일을 받아서
!?!? 아 잠깐만 - 하면서 시험을 봤던 기억이 난다. 
<em>( 다행히 시험은 무사히 마쳤다. A+ 히히 ~ :&gt; )</em></p>
<p>사실 프리코스가 끝나고 나서 밀린 과제 및 졸업 준비, 취준, 
그리고 Compose Camp 까지 하느라 바빠서 
프리코스를 회고하고 복습하는 시간은 현저히 적었기 때문에
기쁜 마음 반, 두려운 마음 반이 들었다.
게다가 최종 코딩테스트 날짜와 다른 전공 기말고사 시험이 완전히 겹쳤는데,
시험에 응시하지 않으면 F라고 하셔서 졸업을 앞둔 나는 불안한 마음이 컸다.</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/96835abb-ef3d-4633-8b5f-6d5163b9351d/image.png" alt=""></p>
<p><em><del>(누가 봐도 교수님st)</del></em>
다행히도 교수님께서 사정을 봐주셔서 다행히 F는 면하게 되었고, 최종 코딩테스트에 참여하게 되었다 !!!! 교수님 감사합니다 (꾸벅)</p>
<h1 id="최종-코딩-테스트">최종 코딩 테스트</h1>
<p>최종 코딩 테스트는 몽촌토성역에 있는 <code>우아한형제들 큰집</code>에서 응시했다.
이 날, 지하철 연착으로 놓치고 뒤에 갈아타는 열차까지 영향이 가서 또 놓치면서 아슬아슬하게 도착했다... 
10분 일찍 출발이 아니라 20분 일찍 출발했어야 하나보다. 😂</p>
<p>무사히 도착하고 건물에 들어서자, 생수 한 병과 배민문방구 굿즈를 나눠주셨다! </p>
<p>그리고 코딩테스트를 진행했는데, 
인터넷 사용도 되고, 이어폰도 써도 되고, 간식도 엄청 많이 준비되어 있어서 다 먹어도 된다...! 주변 사람들에게 방해가 되지 않는 선에서는 꽤나 관대했던 것 같다.
심지어 5시간이라는 긴 시간의 테스트기 때문에, 중간에 쉬는 시간도 강제로(!!) 주셨다. <em>(모두들 노트북 덮고, 쉬세요!)</em></p>
<p>이렇게 자유로운 분위기의 코딩 테스트는 처음이라 무척 놀랐고, 
엄청나게 긴장되는 테스트 속에서 조금이나마 마음이 편하게 시험에 임할 수 있었던 것 같다.
그리고 이런 배려와 자유로움이, 우테코에 더 가고 싶게 만들어주기도 했다.</p>
<hr>
<p>최종 코딩테스트는 &#39;점심 메뉴 추천&#39; 만들기였다. (<a href="https://github.com/sujin9/kotlin-menu/tree/sujin9">레포</a>는 여기)
난이도는 기존 프리코스 과제들과 크게 다르지 않았다.
5시간 내에 할 수 있을까 걱정을 많이 했었는데, 
다행히 5시간 꽉 채워서 무사히 구현해낼 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/47821b3e-aa7f-4231-9b3a-8d579fded43f/image.png" alt=""></p>
<p>이 말만 믿고 사실, 그래 일단 해보자, 자신감을 얻고 진행했던 것 같다. ㅋㅋㅋ
당연히 읽기 좋은, <strong>잘 짜여진</strong> 코드를 작성해야할 것 같았고,
실제로 시간적 제한이 있는 상황에서는 구현을 먼저 하는게 좋은지, 
시간이 걸리더라도 요구사항에 맞춰서 예쁘게(?) 코드를 작성해야 할지 궁금증이 있었는데, 이 메일로 어느 정도 궁금증이 해소되었다...!</p>
<p>그래서 나는 처음 단계에서는 우선 구현을 열심히 했고, 구현을 다 한 뒤에 클래스 분리를 진행하고, 리팩토링을 진행했다.</p>
<h1 id="최종-합격">최종 합격</h1>
<p>코딩테스트가 끝나고 일주일간 매일 매일 후회와 기대를 반복하며 살았던 것 같다. 쉬는게 쉬는게 아니었다구요...! 😂
다행히 이 즈음에 아주 간단한 프로젝트 거리가 하나 생겨서 일부러 더 바쁘게 지내면서 잊고 지내려고 했다. 하하 크리스마스에도 코딩했습니다!!!!</p>
<br/>
그리고 정확히 합격자 발표 당일 오후 3시, 메일이 도착했다. 메일을 열어보니,

<p><img src="https://velog.velcdn.com/images/_im_ssu/post/4f8c8c64-ed11-484f-a292-40e3e62c34be/image.png" alt=""></p>
<p>그 자리에서 옴마낰!!!!! 했다. 내가 붙다니 내가 붙다니 !!!!!
2022년 중 가장 행복한 순간이었던 것 같다. 연말 선물같은 느낌 🥰</p>
<p>사실 1월에 해외에 있는 가족들을 만나러 가기로 했는데, 정말 가도 될까, 내가 이럴 때가 맞나, 불안한 마음도 크고 마음이 많이 싱숭생숭 했는데
2월부터 긴 여정을 시작하게 되니 이제 그 전까지 충분히 휴식을 취하고 오자 - 하는, 그런 편안한 마음을 갖고 떠나게 되었다 :)</p>
<p>그러고서 닉네임도 정하고, 사진도 보내고, 
(중국이라 구글폼 작성 안돼서 당황x10000했지만 다행히 보냈다. 휴 ~)
두근두근 개강일만을 기다리며
앞으로 같은 목표를 가진 좋은 사람들을 만나 열심히 해보아야지 다짐했고,
지금도 그 다짐을 다시금 상기시키며 열심히 살아본다. (뿌듯.. 이제 마저 코딩해야지)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FCM ]]></title>
            <link>https://velog.io/@_im_ssu/FCM</link>
            <guid>https://velog.io/@_im_ssu/FCM</guid>
            <pubDate>Mon, 13 Feb 2023 11:38:55 GMT</pubDate>
            <description><![CDATA[<h1 id="what-is-fcm">What is FCM?</h1>
<p>FCM은,
<strong>Firebase Cloud Messaging</strong> 의 약자입니다.
그렇다면 Firebase Cloud Messaging이 뭐냐구요?</p>
<ul>
<li>무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션</li>
<li>사용자에게 개별적으로, 혹은 그룹을 지어 메시지를 전송 가능하다.</li>
</ul>
<p>이게 무엇인지, 이게 무슨 말인지를 더 이해하기 위해 FCM 메시지가 어떤 식으로 작동되는 지를 보도록 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/61bcb68e-441d-49b3-8a91-8b3df83cd4b4/image.png" alt=""></p>
<ol>
<li>사용자가 모바일 기기에 앱을 설치하고 최초 실행 시에, 토큰을 얻기 위해 클라우드 서버에 <strong>토큰 요청을 보내고 이를 획득</strong>합니다.</li>
<li>획득한 토큰을 서버로 전송하여 <strong>DB에 토큰값을 저장</strong>합니다.
즉, 토큰은 서버가 클라우드에 메시지 전송을 요청할 때 <strong>어디로 메시지를 보낼 것인지를 구분하기 위해</strong> 쓰이는 값입니다.</li>
<li>서버에서 클라우드로 토큰과 메시지 데이터를 보내어 <strong>메시지 전송을 요청</strong>합니다.</li>
<li>클라우드는 요청받은 메시지를 토큰에 해당하는 <strong>단말기로 전송</strong>합니다.</li>
<li>앱이 실행중이 아니어도 리스너를 통해 메시지를 수신할 수 있습니다.</li>
</ol>
<h1 id="why-fcm">Why FCM?</h1>
<p>FCM을 사용하는 이유, 즉 FCM의 이점들은 다음과 같이 있어요.</p>
<ul>
<li>FCM은 <strong>교차</strong> 플랫폼 메시징 솔루션입니다. 즉, Android, iOS 등의 플랫폼에 종속되지 않고 메시지를 전송할 수 있습니다.</li>
<li>실시간 메시지를 받기 위해 계속 접속 상태를 유지하다 보면 배터리와 네트워크의 사용 과다 등으로 인해 문제가 발생할 수 있습니다.
그러나 중간에 클라우드 메시지 서버를 둠으로써, <strong>낮은 배터리와 네트워크 사용만으로 메시지의 실시간 송수신 처리</strong>가 가능합니다. <br/>

</li>
</ul>
<blockquote>
<p>이 이후부터의 내용은 Android 기준입니다!</p>
</blockquote>
<h1 id="start-fcm">Start FCM</h1>
<h2 id="처음-접속">처음 접속</h2>
<ul>
<li><p><code>FCM Console</code>에 접속 -&gt; <code>앱 추가</code> 선택 -&gt; <code>Android</code> 선택</p>
</li>
<li><p>패키지 이름, 앱 이름, SHA-1키를 입력
 <img src="https://velog.velcdn.com/images/_im_ssu/post/6d65dfc1-dae6-4a98-8076-9daf7d258655/image.png" alt=""></p>
<h3 id="how-to-get-sha-1">how to get SHA-1</h3>
<p>SHA-1 키를 받는 방법은 다음과 같아요.
(버전: Android Studio Dolphin 2021.3.1)</p>
</li>
<li><p>오른쪽 <code>Gradle</code>을 선택하고 (빨간 네모), 코끼리 모양 버튼을 선택합니다 (빨간 동그라미).
  <img src="https://velog.velcdn.com/images/_im_ssu/post/f8fffe4b-fb94-449d-a217-c8c2f1d05cef/image.png" alt=""></p>
</li>
<li><p>팝업창에 <code>gradle singingReport</code> 를 입력하면, 하단에서 SHA1 key를 얻을 수 있습니다.
  <img src="https://velog.velcdn.com/images/_im_ssu/post/b0a0c9a6-31a8-400d-9f53-d39718ff06b4/image.png" alt=""></p>
</li>
<li><p>SHA1 key를 얻은 후 app으로 바꿔주는 거 잊지마세요!
  <img src="https://velog.velcdn.com/images/_im_ssu/post/834d34dc-aef5-452c-b7d2-dfcaf4c7a4c1/image.png" alt=""></p>
</li>
</ul>
<h2 id="google-servicesjson-파일-추가">google-services.json 파일 추가</h2>
<ul>
<li>app 하위 디렉토리에 추가해야 합니다.
보기 방식을 project로 두고 하면 편해요 ~
  <img src="https://velog.velcdn.com/images/_im_ssu/post/8796b451-c946-4952-8d87-c6aca54943de/image.png" alt=""></li>
</ul>
<h2 id="sdk-gradle-설정">SDK, gradle 설정</h2>
<h3 id="gradle-app">gradle (app)</h3>
<ul>
<li>plugins에 <code>id &#39;com.google.gms.google-services&#39;</code> 추가</li>
<li>dependencies에 다음 코드 추가<pre><code>  implementation platform(&#39;com.google.firebase:firebase-bom:31.1.1&#39;)
  implementation &#39;com.google.firebase:firebase-analytics-ktx&#39;
  implementation &#39;com.google.firebase:firebase-messaging-ktx&#39;</code></pre><h3 id="gradle-project">gradle (project)</h3>
</li>
<li>제가 사용한 안드로이드 스튜디오 버전(Dolphin 2021.3.1)에서는 project gradle의 구조가 바뀌어서,
Firebase Console에 안내되어 있는 부분과는 조금 달랐습니다. 하지만 당황하지 않고 해주시면 돼요!<pre><code>buildscript {
  repositories {
      google()
  }
  dependencies {
      classpath &#39;com.google.gms:google-services:4.3.13&#39;
  }
}</code></pre>위 코드를 추가해줍니다.</li>
</ul>
<h2 id="google-play-services-설치">Google Play Services 설치</h2>
<ul>
<li><p>SDK Manager -&gt; SDK Tools 에서 <code>Google play services</code> 검색 &amp; 설치</p>
</li>
<li><p><code>FirebaseMessagingService</code>를 상속받는 FCM 서비스 클래스 생성</p>
<pre><code>  import ...
  import com.google.firebase.messaging.FirebaseMessagingService
  import ...

  class Service이름: FirebaseMessagingService() {

  }</code></pre></li>
<li><p>Manifest에 서비스 등록</p>
<pre><code>&lt;application
  ...  &gt;

  &lt;service
      android:name=&quot;.Service이름&quot;
      android:exported=&quot;false&quot;&gt;
  &lt;intent-filter&gt;
      &lt;action android:name=&quot;com.google.firebase.MESSAGING_EVENT&quot; /&gt;
  &lt;/intent-filter&gt;
  &lt;/service&gt;

  ...
</code></pre></li>
</ul>
</application>
```
- Activity에서 서비스 시작 (호출하기)
```kotlin
val fcm = Intent(applicationContext, Service이름::class.java)
startService(fcm)
```

<h1 id="fcm-test">FCM Test</h1>
<ol>
<li>Firebase Console에 접속하여 <code>빌드</code> -&gt; <code>Cloud Messaging</code> 를 선택해줍니다.
<img src="https://velog.velcdn.com/images/_im_ssu/post/2d25f544-be58-4ebe-9cf9-6eece0392b70/image.png" alt=""></li>
<li>캠페인 만들기를 누르고, Firebase 알림 메시지를 선택합니다.</li>
<li>메시지를 작성한 후 테스트 메시지를 전송합니다. (제목(title), 텍스트(body))
<img src="https://velog.velcdn.com/images/_im_ssu/post/cd6d0f04-b816-4afb-9f29-e4f93fbd1fbd/image.png" alt=""></li>
<li>FCM 등록 토큰 추가에서 토큰값을 추가합니다.</li>
</ol>
<ul>
<li><p>이 때 token을 알기 위한 방법은 아래 두 가지가 있어요.
  (1) 앱 최초 실행 시 생성되는 토큰값을 불러오기</p>
<pre><code class="language-kotlin">  override fun onNewToken(token: String) {
        // token이 생성될 때 호출되는 함수
      // token이 생성되는 경우: 앱을 (재)설치 후 첫 실행될 때
      Log.d(TAG, &quot;Token created: $token&quot;)
  }</code></pre>
<p>  (2) FirebaseMessaging의 getInstance로 토큰값 가져오기</p>
<pre><code class="language-kotlin">  fun getToken(): String? {
      var token: String? = null

      FirebaseMessaging.getInstance().token
          .addOnCompleteListener(OnCompleteListener { task -&gt;
              if(!task.isSuccessful) {
                  return@OnCompleteListener
              }
              token = task.result
              Log.d(TAG, &quot;Token: $token&quot;)
      })
      return token
  }    </code></pre>
</li>
</ul>
<h1 id="메세지-수신하기">메세지 수신하기</h1>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/67187692-1b4e-4f50-a076-d7280155a6d4/image.png" alt="">
background 에서는 별도의 작업 없이 알림이 잘 오지만,
foreground에서도 알람을 받기 위해서는 어떻게 해야 할까요?
-&gt; 받은 알람 데이터를 처리하는 <code>onMessageReceived()</code> method에서 notification을 만들어줍니다.</p>
<pre><code class="language-kotlin">override fun onMessageReceived(message: RemoteMessage) {
    message.notification?.let {
        showNotification(it)
    }
}

private fun showNotification(notification: RemoteMessage.Notification) {
    val intent = Intent(this, MainActivity::class.java)
        // PendingIntent: 노티를 터치했을 때 액티비티를 실행하기 위함
    val pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) 

    val channelId = getString(R.string.fcm_channel_id)

    val notificationBuilder = NotificationCompat.Builder(this, channelId)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setSmallIcon(R.mipmap.ic_launcher)              // 알림에 보이는 아이콘
        .setContentTitle(notification.title)             // 알림에 보이는 제목
        .setContentText(notification.body)               // 제목 아래 보이는 텍스트
        .setContentIntent(pIntent)

    getSystemService(NotificationManager::class.java).run {         // 채널 생성
        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, &quot;알림&quot;, NotificationManager.IMPORTANCE_HIGH)
            createNotificationChannel(channel)
        }

        notify(Date().time.toInt(), notificationBuilder.build())     // 노티 등록
    }
}</code></pre>
<h2 id="channelid">channelId</h2>
<p>그런데, 위의 코드에 나오는 <code>channelId</code>는 무엇일까요?
궁금증이 생겨 찾아보았습니다 -&gt; <a href="https://developer.android.com/guide/topics/ui/notifiers/notifications?hl=ko#ManageChannels">공식 링크</a> 및 <a href="https://dev3m.tistory.com/entry/%EB%8B%A4%EC%88%98%EC%9D%98-Notification-channel-%EA%B4%80%EB%A6%AC%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD">참고 링크</a>
위의 링크들을 보았는데, 저는 여러 채널을 이용하지 않을 예정이라 우선 strings.xml에 임의의 아이디를 작성해주는 방식으로 진행했습니다.</p>
<h2 id="pendingintent">PendingIntent</h2>
<p><code>PendingIntent</code>는 당장은 수행되진 않지만, 특정 시점이 되었을 때 실행되는 intent 입니다.
위에서는 notification을 선택했을 때 activity를 실행하기 위해 쓰이고 있어요.</p>
<p>pending intent를 생성하는 코드는 다음과 같습니다.
<code>PendingIntent.getActivity(context, requestCode, intent, flag)</code></p>
<h3 id="flag">FLAG</h3>
<p>Pending Intent의 FLAG는 다음과 같이 있습니다.
<img src="https://velog.velcdn.com/images/_im_ssu/post/eea471f0-982d-45cd-af06-d843b6437bf9/image.png" alt=""></p>
<p>그리고 현재 버전에서는 PendingIntent.MUTABLE , PendingIntent.IMMUTABLE 중 하나는 반드시 포함해야 돼요. (immutable 사용이 권장되고 있습니다.)</p>
<h3 id="requestcode">requestCode</h3>
<p><code>PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)</code> 
에서 0 이 requestCode입니다.
requestCode는 pending intent를 가져올 때 이를 구분하기 위한 고유 코드값이에요.</p>
<p>보통 request code를 0으로 많이 작성하는데, 저는 웹앱 개발 과정에서 다음과 같은 문제가 발생했었어요.</p>
<blockquote>
<p>foreground 상태에서 fcm 메시지가 연달아 도착했을 때, 각각을 클릭하면 각각의 다른 페이지가 아닌 똑같은 페이지로 이동된다.</p>
</blockquote>
<p>peding intent가 중복되는 문제였습니다.
이 때 requestCode를 0이 아니라 <code>(int) System.currentTimeMillis()</code>를 줌으로써 동일한 값이 들어가지 않도록 했더니 해결되었답니다 :)</p>
<hr>
<h4 id="참고자료">참고자료</h4>
<p><a href="https://donghun.dev/Firebase-Cloud-Messaging">https://donghun.dev/Firebase-Cloud-Messaging</a> 
<a href="https://firebase.google.com/docs/cloud-messaging?hl=ko">https://firebase.google.com/docs/cloud-messaging?hl=ko</a> 
<a href="https://doitddo.tistory.com/108">https://doitddo.tistory.com/108</a>
<a href="https://velog.io/@haero_kim/Android-PendingIntent-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0">https://velog.io/@haero_kim/Android-PendingIntent-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0</a> 
<a href="https://developer88.tistory.com/187">https://developer88.tistory.com/187</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Compose: Unit 2.]]></title>
            <link>https://velog.io/@_im_ssu/Compose-Unit-2</link>
            <guid>https://velog.io/@_im_ssu/Compose-Unit-2</guid>
            <pubDate>Mon, 13 Feb 2023 11:30:36 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/courses/android-basics-compose/unit-2">Compose Basic Codelab Unit 2 링크</a>
<a href="https://github.com/sujin9/ComposeCamp2022-for-Beginners/tree/main/Unit2">Compose Camp Unit 1 Repository</a></p>
<hr>
<h1 id="kotlin-기초">Kotlin 기초</h1>
<h2 id="conditionals">Conditionals</h2>
<ol>
<li><p>if - else if - else
이것은 굳이 설명 없이 넘어갈게요!</p>
</li>
<li><p>when
if - else 에서 조건이 많을 경우 when을 사용하면 좋습니다.</p>
<pre><code class="language-kotlin">when (parameter) {
 condition1 -&gt; body1
 condition2 -&gt; body2
}
</code></pre>
</li>
</ol>
<p>// 예시: 다음과 같이 사용할 수 있어요.
when (x) {
    1 -&gt; println(&quot;1&quot;)
    2 -&gt; println(&quot;2&quot;)
    3, 5 -&gt; println(&quot;3 or 5&quot;)
    in 10..19 -&gt; println(&quot;10 ~ 19&quot;)
    is Int -&gt; println(&quot;Int&quot;)
}</p>
<pre><code>
## nullability
? 를 사용하면 nullable,
!! 를 사용하면 non-null 값입니다.
``` kotlin
var str ?= null
println(str?.length)    // print null
println(str!!.length)    // null exception</code></pre><p><code>?:</code> (Elvis 연산자)를 사용할 수 있어요.</p>
<pre><code class="language-kotlin">val len = str?.length ?: 0    
// str이 null인 경우 print 0</code></pre>
<h2 id="class-and-object">class and object</h2>
<h3 id="객체로-인스턴스화">객체로 인스턴스화</h3>
<p>val name = ClassName()</p>
<h3 id="constructor">constructor</h3>
<pre><code class="language-kotlin">class SmartDevice(
    val name: String, val category: String    // primary constructor
) {
    var deviceStatus = &quot;online&quot;

    constructor(    // 부생성자
        name: String, category: String, statusCode: Int
    ) : this(name, category) {  //  this(주생성자의 파라미터)
        deviceStatus = when (statusCode) {
            0 -&gt; &quot;offline&quot;
            1 -&gt; &quot;online&quot;
            else -&gt; &quot;unknown&quot;
        }
    }
    ...
}</code></pre>
<h3 id="is-a--has-a">IS-A , HAS-A</h3>
<pre><code class="language-kotlin">// IS-A (Inherits) - 상속
// A: subClass, B: superClass
open B { fun func() }    // using `open` keyword in superclass 
class A : B() { 
    override fun func() {}    // using `override` keyword in subclass
}

// HAS_A (contains/uses) - 포함
class A(
    val b: B
)</code></pre>
<h3 id="visibility-modifiers">visibility modifiers</h3>
<table>
<thead>
<tr>
<th align="center">Modifier</th>
<th align="center">Accessible in same class</th>
<th align="center">Accessible in subclass</th>
<th align="center">Accessible in same module</th>
<th align="center">Accessible outside module</th>
</tr>
</thead>
<tbody><tr>
<td align="center">private</td>
<td align="center">✔</td>
<td align="center">𝗫</td>
<td align="center">𝗫</td>
<td align="center">𝗫</td>
</tr>
<tr>
<td align="center">protected</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center">𝗫</td>
<td align="center">𝗫</td>
</tr>
<tr>
<td align="center">internal</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center">𝗫</td>
</tr>
<tr>
<td align="center">public</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center">✔</td>
<td align="center">✔</td>
</tr>
</tbody></table>
<h3 id="using-function-as-a-data-type">using function as a data type</h3>
<p>(parameters) -&gt; return type</p>
<pre><code class="language-kotlin">val coins: (Int) -&gt; String { quantity -&gt;
    &quot;$quantity quarters&quot;
}</code></pre>
<h3 id="repeat">repeat</h3>
<p>for (iteration in start..end) { }
=&gt; repeat(times) { iteration -&gt; }</p>
<h1 id="how-to-debug-android-studio">How to Debug (Android Studio)</h1>
<h2 id="앱-프로세스에-디버거-연결">앱 프로세스에 디버거 연결</h2>
<ol>
<li>앱을 실행한다.</li>
<li>기기 or 에뮬레이터가 실행되면 아래 사진 속 버튼을 누르고, 연결할 프로세스를 선택한다.
<img src="https://velog.velcdn.com/images/_im_ssu/post/d86c613f-b14e-4e2c-a5fe-2b8cf1b2f72b/image.png" alt=""></li>
<li>하단에 Debug창이 표시된다.</li>
</ol>
<h2 id="디버거로-앱-실행">디버거로 앱 실행</h2>
<ol>
<li>Debug &#39;app&#39; 클릭
<img src="https://velog.velcdn.com/images/_im_ssu/post/dc55eb18-2632-495d-9fec-11ca9f13fdd3/image.png" alt=""></li>
<li>하단에 Debug창이 표시된다.</li>
</ol>
<h2 id="debug-기능">Debug 기능</h2>
<p>자세한 건 <a href="https://developer.android.com/codelabs/basic-android-kotlin-compose-intro-debugger?hl=ko&amp;continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-2-pathway-2%3Fhl%3Dko%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-intro-debugger#0">링크</a> 참고 !!</p>
<ul>
<li><p>Debug 창에는 Debugger, Console 버튼이 있다.</p>
<ul>
<li><p>Debugger: 별도의 창 3개를 표시 (Frames, Variables, Overhead)</p>
<ul>
<li><p>Frames: 중단점이 설정된 줄이 강조표시 되어있음</p>
<ul>
<li>Variables: 변수 검사 기능</li>
</ul>
</li>
</ul>
</li>
<li><p>Console: logcat 출력 표시</p>
</li>
</ul>
</li>
<li><p>중단점 기능: 특정 코드 줄 번호 옆의 여백을 클릭 (다시 클릭하면 중단점 삭제)</p>
</li>
<li><p>중단 후 재개: Resume Program</p>
</li>
<li><p>Step Into: 명령이 메서드 또는 다른 코드를 호출하는 경우, Step Into를 사용하면 디버거를 실행하여 중단점을 설정하기 전에 수동 탐색할 필요 없이 코드를 입력할 수 있다.</p>
</li>
<li><p>Step Over: 런타임에 앱 코드를 단계별로 실행할 수 있다.</p>
</li>
<li><p>Step Out: Step Into와 반대로 작동, 호출 스택으로 이동한다.</p>
</li>
</ul>
<h1 id="remember-함수를-이용한-상태-저장">remember 함수를 이용한 상태 저장</h1>
<p><code>remember</code> 를 사용하여 리컴포지션에서 객체를 저장할 수 있다.
상태와 업데이트가 UI에 적절하게 반영되도록 일반적으로 <code>remember</code>과 <code>mutableStateOf</code> 함수가 함께 사용된다.</p>
<pre><code class="language-kotlin">import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue    // 직접 가져와야 함
import androidx.compose.runtime.setValue    // 직접 가져와야 함

var amount by remember { mutableStateOf(&quot;&quot;) }
TextField(                
    value = amount,
    onValueChanged = { amount = it },
)</code></pre>
<p><code>by</code> : Kotlin 속성 위임에 해당됨</p>
<h1 id="상태-hoisting">상태 Hoisting</h1>
<h2 id="stateful-vs-stateless">Stateful vs Stateless</h2>
<ul>
<li><p>Stateful: 시간이 지남에 따라 변할 수 있는 상태를 소유하고 있음</p>
</li>
<li><p>Stateless: 구성 가능한 함수에서 상태를 추출할 때 결과로 생성되는 구성 가능한 함수, 상태가 없는 컴포저블임</p>
<h2 id="hoisting이-필요한-경우">hoisting이 필요한 경우</h2>
</li>
<li><p>상태를 여러 구성 가능한 함수와 공유하는 경우</p>
</li>
<li><p>앱에서 재사용할 수 있는 Stateless 컴포저블을 만드는 경우</p>
<h2 id="상태-hoisting을-composable에-적용">상태 Hoisting을 Composable에 적용</h2>
</li>
<li><p>컴포저블에 매개변수 2개가 추가되는 경우가 많다</p>
<ul>
<li><p>value: T</p>
</li>
<li><p>onValueChange: (T) -&gt; Unit   // 값을 업데이트하는 람다 콜백</p>
<pre><code class="language-kotlin">@Composable
fun TipTimeScreen() {
var amountInput by remember { mutableStateOf(&quot;&quot;) }

val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)

Column(
 modifier = Modifier.padding(32.dp),
 verticalArrangement = Arrangement.spacedBy(8.dp)
) {
 Text(
     text = stringResource(R.string.calculate_tip),
     fontSize = 24.sp,
     modifier = Modifier.align(Alignment.CenterHorizontally)
 )
 Spacer(Modifier.height(16.dp))
 EditNumberField(value = amountInput,
     onValueChange = { amountInput = it }
 )
 Spacer(Modifier.height(24.dp))
 Text(
     text = stringResource(R.string.tip_amount, tip),
     modifier = Modifier.align(Alignment.CenterHorizontally),
     fontSize = 20.sp,
     fontWeight = FontWeight.Bold
 )
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>}</p>
<p>@Composable
fun EditNumberField(
       value: String,
       onValueChange: (String) -&gt; Unit
   ) {
   TextField(
       value = value,
       onValueChange = onValueChange,
       label = { Text(stringResource(R.string.cost_of_service)) },
       modifier = Modifier.fillMaxWidth(),
       singleLine = true,
       keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
   )
}</p>
<pre><code>- `amountInput` 상태를 `EditNumberField()`에서 `TipTimeScreen()` composable로 호이스팅</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Compose: Unit 1.]]></title>
            <link>https://velog.io/@_im_ssu/Compose-Unit-1.-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@_im_ssu/Compose-Unit-1.-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sun, 01 Jan 2023 10:50:44 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/courses/android-basics-compose/unit-1">Compose Basic Codelab Unit 1 링크</a>
<a href="https://github.com/sujin9/ComposeCamp2022-for-Beginners/tree/main/Unit1">Compose Camp Unit 1 Repository</a></p>
<hr>
<h1 id="compose-예시">Compose 예시</h1>
<p>우선 Compose 코드는 어떤 구성을 갖고 있을까요?</p>
<pre><code class="language-kotlin">class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           GreetingCardTheme {
               Surface(color = MaterialTheme.colors.background) {
                   Greeting(&quot;Android&quot;)
               }
           }
       }
   }
}

@Composable
fun Greeting(name: String) {
   Surface(color = Color.Magenta) {
       Text(text = &quot;Hello $name!&quot;)
   }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
   GreetingCardTheme {
       Greeting(&quot;Meghan&quot;)
   }
}</code></pre>
<p>위의 코드를 Android Studio에서 작성하고, 우측 상단 Split을 누르면 Preview 화면을 동시에 볼 수 있습니다.
작성한 코드와 Preview 화면은 다음과 같아요!
<img src="https://velog.velcdn.com/images/_im_ssu/post/fb2a1880-1a6e-4222-942e-f2d35a302bfc/image.png" alt=""></p>
<h1 id="compose-관련-코딩-컨벤션-및-특징">Compose 관련 코딩 컨벤션 및 특징</h1>
<h4 id="compose-함수의-특징">Compose 함수의 특징</h4>
<ul>
<li>UI 일부를 설명합니다.</li>
<li>아무것도 반환하지 않습니다.</li>
<li>몇 개의 입력을 받아서 화면에 표시되는 내용을 생성합니다.</li>
<li>여러 UI 요소를 내보낼 수도 있습니다.</li>
</ul>
<h4 id="compose-함수의-이름">Compose 함수의 이름</h4>
<ul>
<li><p>첫 글자는 대문자로 표기한다.</p>
</li>
<li><p>함수 이름은 <strong>명사</strong>여야 한다.</p>
<ul>
<li>동사 / 동사구 ❌ : <code>DrawTextField()</code> </li>
<li>명사화된 전치사 ❌ : <code>TextFieldWithLink</code></li>
<li>형용사 ❌ : <code>Bright()</code></li>
<li>부사 ❌ : <code>Outside()</code></li>
<li>형용사+명사는 ⭕ : <code>RoundIcon()</code></li>
</ul>
</li>
<li><p>함수 앞에 <code>@Composable</code>  주석을 추가한다.</p>
<ul>
<li>Compose 컴파일러에게 이 함수가 데이터를 UI로 변환함을 알리기 위함.</li>
</ul>
</li>
</ul>
<h1 id="preview">Preview</h1>
<ul>
<li><code>@Preview</code>로 미리보기가 가능하다.<pre><code class="language-kotlin">@Preview
@Composable
fun DefaultPreview() {
 GreetingCardTheme {
     Greeting(&quot;Meghan&quot;)
 }
}</code></pre>
</li>
<li>Preview에 매개변수를 전달할 수 있다.<pre><code class="language-kotlin">@Preview(showBackground = true)    // 배경화면 미리보기

</code></pre>
</li>
</ul>
<p>@Preview(
    name = &quot;My Preview&quot;,    // Preview에 제목 지정
    showSystemUi = true     // 시스템UI (휴대전화 화면)을 포함한 미리보기
)</p>
<pre><code>
# Composable
## Text
```kotlin
Text(
    text = message,                    // 표시할 메시지
    fontSize = 36.sp,                // 글씨 크기
    fontWeight = FontWeight.Bold,    
    modifier = Modifier
        .fillMaxWidth()
        .wrapContentWidth(Alignment.CenterHorizontally)
        .padding(start = 16.dp, top = 16.dp)
)</code></pre><h2 id="image">Image</h2>
<ul>
<li>Image 파일을 Resource Manager을 사용하여 import하기 (<a href="https://developer.android.com/studio/write/resource-manager?hl=ko">설명 링크</a>)<pre><code class="language-kotlin">Image(
  painter = painterResource(id = R.drawable.androidparty),
  contentDescription = null,           // (접근성 관련) 컨텐츠에 대한 설명
  modifier = Modifier
      .fillMaxHeight()
      .fillMaxWidth(),
  contentScale = ContentScale.Crop    // 이미지 scale type
)</code></pre>
<em>다양한 이미지 scale type 종류들을 사진으로 잘 정리해둔 링크를 발견해서 함께 첨부합니다! (<a href="https://medium.com/mobile-app-development-publication/jetpack-compose-image-content-scaletype-fully-illustrated-bfdf2de7ef5">여기~</a>)</em></li>
</ul>
<h2 id="-modifier">+) Modifier</h2>
<ul>
<li>Compose UI 구성요소들을 꾸미거나, 행동을 추가하기 위한 요소들의 모임<pre><code class="language-kotlin">modifier = Modifier
  // 높이 및 너비 관련
  .width(100.dp)             
  .height(100.dp)
  .fillMaxWidth(1f)    //  전체 대비 채우는 비율 (1f가 기본)
  .fillMaxHeight()
  .fillMaxSize()
  // 여유 공간
  .padding(15.dp)        // 상하좌우 15dp 여백
                      // start,end,top,bottom 이나 horizontal, vertical 값을 줄 수 있음
                      // margin을 주는 방법 -&gt; 항목들 사이에 Spacer 추가하기
  // 배경 및 모양
  .background(
      color = Color.Blue,                    // 배경색
      shape = RoundedCornerShape(12.dp)    // 모양
  )
</code></pre>
</li>
</ul>
<pre><code>위 코드의 참고 출처 및 그 외의 다양한 Modifier 활용을 보고자 한다면 -&gt; [링크](https://kotlinworld.com/191)로 !

## Spacer
- 요소들 사이 배치, margin을 주는 역할
```kotlin
Text( /* ... */ )
Spacer(Modifier.size(padding))
Row() { /* ... */ }</code></pre><h1 id="표준-레이아웃">표준 레이아웃</h1>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/4d2bfa2a-8d9a-41e5-bec7-1d246f1d0776/image.png" alt=""></p>
<h2 id="row">Row</h2>
<ul>
<li>하위 요소들을 화면에 수직으로 배치</li>
<li><code>LinearLayout(orientation = vertical)</code><pre><code class="language-kotlin">Row {
  Text(&quot;First&quot;)
  Text(&quot;Second&quot;)
}</code></pre>
<h2 id="column">Column</h2>
</li>
<li>하위 요소들을 화면에 수평으로 배치</li>
<li><code>LinearLayout(orientation = horizontal)</code><pre><code class="language-kotlin">Column(
  modifier = Modifier
) {
  Text(&quot;First&quot;)
  Text(&quot;Second&quot;)
}</code></pre>
<h2 id="box">Box</h2>
</li>
<li>위젯을 다른 화면의 위에 배치</li>
<li>하나의 박스 만들고, 안에 요소들을 배치 가능 (하나는 왼쪽 위에, 하나는 오른쪽 아래에 놓는다던가..)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Jetpack Compose: What & Why?]]></title>
            <link>https://velog.io/@_im_ssu/Android-Jetpack-Compose-What-Why</link>
            <guid>https://velog.io/@_im_ssu/Android-Jetpack-Compose-What-Why</guid>
            <pubDate>Fri, 30 Dec 2022 09:35:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://developersonair.withgoogle.com/events/composecamp_22kr">Jetpack Compose Camp 2022 For Beginners</a>에 참여했습니다! (사실 기간 도중에 알게되어 거의 11월 중하순.. 늦게 참여했는데 다행히 이수했답니다😆) 
행사를 통해 얻은 Compose 및 Kotlin 지식들을 정리하고 회고해보고자 합니다.
코드들은 <a href="https://github.com/sujin9/ComposeCamp2022-for-Beginners">여기</a>에 있어요 ~ </p>
</blockquote>
<h4 id="그래서-compose는-무엇이고-왜-사용해야-하는가">그래서, Compose는 무엇이고, 왜 사용해야 하는가?</h4>
<p><em>여담이지만 뭐든 왜 쓰는지 왜 돌아가는지는 아는게 중요한 듯 합니다...</em></p>
<h1 id="what-is-compose">What is Compose?</h1>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/048458b8-aa6b-4155-9f68-0d1f3ea6754a/image.png" alt="">
(출처: <a href="https://developer.android.com/jetpack/compose?gclid=Cj0KCQiAtbqdBhDvARIsAGYnXBO23xrlvxWY2xKr0U7Qhe_uixAbsTB9bz8ezJ3EgzSoXtTy3qGxQLUaAnNhEALw_wcB&amp;gclsrc=aw.ds">android developer 공식 사이트</a>)</p>
<p>Android Jetpack Compose는 Kotlin으로 제작된 라이브러리로 새로운 UI Tool 입니다!
선언형 프로그래밍 방식으로 UI를 그립니다.</p>
<p>기존에는 xml을 사용하여 UI를 그렸는데, 
왜 compose가 생겨났고 그래서 왜 이걸 사용해야 한다는 걸까요?</p>
<h1 id="why-compose">Why Compose?</h1>
<p>안드로이드 Developsers 에서는 Compose를 채택하는 이유를 다음과 같이 소개합니다.</p>
<h3 id="코드-감소">코드 감소</h3>
<p>적은 수의 코드로 더 많은 작업을 하고 전체 버그 클래스를 방지할 수 있으므로 코드가 간단하며 유지 관리가 쉽습니다.</p>
<h3 id="직관적">직관적</h3>
<p>UI만 설명하면 나머지는 Compose에서 처리합니다. 앱 상태가 변경되면 UI가 자동으로 업데이트됩니다.</p>
<h3 id="빠른-개발-과정">빠른 개발 과정</h3>
<p>기존의 모든 코드와 호환되므로 언제 어디서든 원하는 대로 사용할 수 있습니다. 실시간 미리보기 및 완전한 Android Studio 지원으로 빠르게 반복할 수 있습니다.</p>
<h3 id="강력한-성능">강력한 성능</h3>
<p>Android 플랫폼 API에 직접 액세스하고 기본적으로 지원되는 Material Design, Dark Theme, Animation 등을 사용해 멋진 앱을 만들 수 있습니다.</p>
<p>(출처는 <a href="https://developer.android.com/jetpack/compose/why-adopt">여기</a>)</p>
<p><br/>그리고 실제 코드랩을 진행해보면서 직접 느낀 점은,</p>
<ol>
<li><p>오직 Kotlin으로만 작성하면 되기 때문에 처음 하시는 분들의 입장에서는 XML 사용을 익히는 것에 부담이 적어질 것으로 보이고,</p>
</li>
<li><p>기존처럼 xml에서 ui를 구성하고 class에서 컨트롤하는 방식은 두 파일을 왔다갔다 하다보면 번거로움이 있었는데 compose를 통해 <strong>하나의 파일</strong>에서 심플하게 작업할 수 있어 (선언형이기 때문에!) 더욱 편리한 것 같습니다.</p>
</li>
<li><p>그리고 Preview (실시간 미리보기)가 정말 편리한 것 같아요! 버튼 클릭 등의 간단한 동작들도 미리보기로 해볼 수 있어서 좋았어요 :) 아직은 좀 느리지만 이 부분은 점차 개선되겠죠ㅎㅎ!?
<em>(사실 웹 프론트  친구들 웹사이트 보면서 바로바로 코드 수정 부러웠단 말이죠.. 맨날 안드로이드 스튜디오로 run-&gt;기다림-&gt;실행-&gt;수정-&gt;run-&gt;기다림... 이던 나날들 ㅜ)</em></p>
</li>
</ol>
<h1 id="so">So...</h1>
<p>지난 9월, 따끈따끈(?)한 1.2 버전이 출시되어 갓 안정화 단계에 들어선 Compose!</p>
<p>공식 사이트에서 언급한 장점들을 보면 사용하지 않을 이유가 없어보이기도 하고, 
또 구글에서도 열심히 밀어주고 있는 듯하고, 
Compose에 대한 다양한 긍정적 사용 후기들이 보이기도 하고,
실제로 코드랩을 해보면서 편리함을 느끼기도 해서
앞으로 사용도가 많아지지 않을까 생각이 듭니다! :)</p>
<p>...
그래서, Compose 웨않써!? (급마무리)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 프리코스 안드로이드 회고]]></title>
            <link>https://velog.io/@_im_ssu/%EC%9A%B0%ED%85%8C%EC%BD%94-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@_im_ssu/%EC%9A%B0%ED%85%8C%EC%BD%94-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 30 Nov 2022 08:54:01 GMT</pubDate>
            <description><![CDATA[<p>우테코 안드로이드 과정 프리코스 
기간: 2022.10.26 ~ 11.22</p>
<p>한 달간 진행된 프리코스의 늦은 후기를 남겨봅니다,,</p>
<hr>
<h1 id="지원">지원</h1>
<p>처음에는 지원을 많이 고민했었습니다.
휴학을 오래 했기에 빨리 졸업하고 취업을 해야 한다는 부담감이 컸고,
10달 간의 교육을 할 수 있을지 용기가 나질 않기도 했습니다.
하지만, 될지 안될지도 모르는데 일단 지원 해보자!는 마음으로,
프리코스만이라도 하면 일단 좋은 거 아닌가는 마음으로 지원을 하게 되었습니다.</p>
<p>아, 지원서 작성부터도 정말 스스로에게 많은 고민을 하게 만드는 질문들이었어요!
작성 과정에서 스스로에게 많이 질문해보며 나를 돌아볼 수 있었던 것 같아요.</p>
<hr>
<h1 id="미션">미션</h1>
<p>그렇게 4주간의 프리코스 과정에 참여하게 되었습니다.</p>
<p>사실 프리코스의 미션들은 구현 자체는 어려운 미션들은 아니었습니다.
다만, 요구사항과 피드백을 지키면서, <strong>좋은</strong> 코드를 작성하는 데에 시간이 많이 소모되었습니다.</p>
<p>이번 프리코스에서 피드백을 받으면서 제가 중점적으로 공부하고 익숙해지려 노력했던 부분들은 다음과 같아요.</p>
<ul>
<li>코드 작성 및 네이밍 규칙 지키기</li>
<li>Git 컨벤션 지키기</li>
<li>클린 코드 작성하기</li>
<li>코틀린 Collections 문법 익히고 적용하기 (자바와 다른 부분 중점으로!)</li>
<li>함수의 기능 작게 만들기</li>
<li>클래스 분리하고 객체를 객체스럽게 사용하기 </li>
<li>필드 수 줄이기</li>
<li>테스트 코드도 잘 작성하기</li>
<li>enum 클래스 활용하기</li>
</ul>
<br/>
매주 진행된 미션은 다음과 같아요 :) 

<h4 id="1주차-온보딩">1주차: 온보딩</h4>
<p><a href="https://github.com/sujin9/kotlin-onboarding/tree/sujin9">https://github.com/sujin9/kotlin-onboarding/tree/sujin9</a></p>
<p>7개의 문제가 있고 각각을 코틀린으로 푸는 미션이었습니다.
말 그대로 온보딩이기에 문제 난이도가 높지 않았고 요구사항도 많지 않았지만,
이 때부터 네이밍이나 코드를 깔끔하게 작성하기 위해 노력했어요.</p>
<h4 id="2주차-숫자-야구">2주차: 숫자 야구</h4>
<p><a href="https://github.com/sujin9/kotlin-baseball/tree/sujin9">https://github.com/sujin9/kotlin-baseball/tree/sujin9</a></p>
<p>이 때부터 본격적으로 <strong>테스트 코드</strong>에 대한 고민이 시작되었습니다.
테스트 코드가 모든 메소드에 대해 필요한가, (라이브러리를 실행하는 경우 등) 
테스트가 어려운 코드는 어떻게 테스트를 해야 하는가 (입출력이 있는 경우 등)
하는 고민이 많이 되더라구요.</p>
<p>또한 설계 과정에서 어느 정도까지 설계하고 어느 정도까지 유연성을 두는지에 대한 고민도 하였습니다.</p>
<h4 id="3주차-로또">3주차: 로또</h4>
<p><a href="https://github.com/sujin9/kotlin-lotto/tree/sujin9">https://github.com/sujin9/kotlin-lotto/tree/sujin9</a></p>
<p>2주차의 고민들은 피드백을 통해 꽤나 상당부분 해소가 되었지만, 역시나 다른 고민들이 시작됩니다 ㅎㅎ..
우선 <strong>enum</strong> 클래스 사용이 새롭게 요구되면서 이 부분을 많이 공부했어요. 
처음 사용이기 때문에 어떻게 사용하는지를 익히려고 많이 했습니다.
또한, <strong>왜 이걸 사용하지?</strong> 를 많이 생각해보았습니다.
<span style="color: gray">
제 생각으로는, 관련 있는 상수들을 enum 클래스 하나에 열거를 하게되면,
enum이 추상클래스이기 때문에 다양하게 활용이 가능하고 클래스 내에서 함수를 이용할 수도 있어서 단순 상수 열거 이상의 기능을 해낼 수 있더라구요 :)
</span>
또한 클래스 분리를 하는 과정이 많이 어려웠는데요.
이 객체가 어떤 의미를 갖는 객체인지를 분명하게 하려 했지만, 그럼에도 이걸 이렇게 분리하는게 맞는지, 이걸 분리하는게 오히려 복잡해지는 것은 아닌지, 계속 코드를 작성하는 내내 고민하게 되더라구요 ㅠㅠ</p>
<h4 id="4주차-다리-건너기">4주차: 다리 건너기</h4>
<p><a href="https://github.com/sujin9/kotlin-bridge/tree/sujin9">https://github.com/sujin9/kotlin-bridge/tree/sujin9</a></p>
<p>마지막은, 역시 쉽지 않았습니다 ㅜ
이번에는 클래스들이 어느 정도 분리가 되어있는 상태에서 구현을 시작하게 되었습니다.</p>
<p>우선 InputView와 OutputView가 분리가 되었는데, 
그러다보니 input할 때 예외처리 등이 inputView 내에서 또는 이를 호출한 함수에서 처리를 해야할지 등 역할을 명확하게 하는 부분에서 많이 고민하였습니다. 
(저는 우선 이 부분은 exception 관련 클래스를 따로 생성하여 inputView 내에서 이를 호출하고 검사한 후에 return 하도록 구현하였습니다.)</p>
<p>또한 4주차에서 가장 어렵게 느껴졌던 부분은 <strong>단위 테스트 작성</strong>인데,
클래스에 프로퍼티가 있거나 전역변수가 있는 경우 테스트 코드를 어떻게 작성해야 할지 고민이 많았습니다. 
전역변수를 public으로 두고 직접 접근하는 방법도 있겠지만, 이는 적합하지 않은 방법인 것 같거든요!!</p>
<p>그리고 고민의 과정의 끝은, 결국 <strong>함수를 분리하는 것</strong>이었습니다.
예를 들면, 다리 건너기를 시작할 때 관련 변수들을 초기화하는 함수를 따로 만들어주는 거죠. 그렇다면, 테스트 코드 작성할 때 이 함수를 실행해준 후 테스트를 실행하면 되니까요! 코드가 단 한 줄이지만 이를 따로 함수로 만들어줘야 테스트가 용이해지는 경우도 있었습니다. <del>정말,, 함수는 줄여도 줄여도 끝이 없네요 (?)</del></p>
<hr>
<h1 id="회고">회고</h1>
<p><img src="https://velog.velcdn.com/images/_im_ssu/post/583d6f6b-8eae-49d5-b931-87d3d73901f5/image.png" alt=""></p>
<p>미션을 진행하다 보니 정말 시간이 쏜살같이 사라졌네요.
힘들지만 행복했던 4주였습니다. :)
어떻게 보면 굉장히 짧은 시간임에도 불구하고,  코드가 정말 많이 개선되고 스스로 성장했음이 크게 느껴집니다.
과거의 코드를 보면,,, 나 반성해 ,,,,
그렇지만 그만큼 발전했다는 것에 매우 기쁘고, 의미 있는 4주였던 것 같습니다.
<br/></p>
<p>위에서 언급한, 제가 익숙해지려 노력하고 공부한 부분들을 보시면 아시겠지만,
사실 좋은 알고리즘을 구현하고 효율적으로 코드를 짜기 보다는
<strong>가독성이 좋게, 깔끔하게, 그리고 테스트를 하기 용이하게 코드를 짜는 것을 많이 익힐 수 있던 시간이었습니다.</strong>
혼자가 아닌 협업에서는 서로 규칙을 지키고 읽기 쉬운 코드를 작성하는 게 정말 정말 중요하니까요 ! </p>
<p>또한 테스트 코드 작성을 직접 해봄으로써, 사용법을 익히는 것에서 그치지 않고 이것이 왜 필요한지를 직접 느끼게 되었습니다. 긴 코드를 작성하고 프로그램을 짜다 보면 이게 맞는지 잘 모를 수 있지만, <strong>함수가 작은 하나의 기능을 수행하도록 쪼개고 단위 테스트를 각각 실행한다면 더 자주 코드를 점검할 수 있고, 오류가 생기게 되는 경우도 적어지고 오류가 생기더라도 원인을 훨씬 더 빠르게 찾을 수 있었습니다.</strong> 또한 테스트 코드로 함수가 잘 돌아가는지 확인하며 코드를 작성해나가니 더욱 코드를 짜는데 재미가 붙더라구요 (ㅋㅎㅋㅎ) 
<br/></p>
<p>현생에 바빠서 여기에 온전히 몰입하지는 못했고, 그만큼 코드도 아쉬움이 남습니다. 
하지만 만약 합격하게 된다면, 그 때는 졸업 이후이니 정말 여기에만 몰입할 수 있겠지요! 
그렇지만 혹여 합격하지 못하게 되더라도, 4주 간의 이 경험이 있기에 충분히 값진 시간이었고 지원한 것을 전혀 후회하지 않습니다! :) 
<em>(사실 전공 과목 기말고사와 우테코 최종 코테가 시간이 완전히 겹쳐버려서.. 어떻게 될지 모르겠네요 ㅠㅠ눈물)</em></p>
<p>그렇지만 이번에 배운 것을 잊지 않고, 더 공부하면서 앞으로 더 좋은 코드를 작성하도록
꾸준히 노력해야겠습니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IntelliJ Git Commit Template Plugin]]></title>
            <link>https://velog.io/@_im_ssu/IntelliJ-Git-Commit-Template-Plugin</link>
            <guid>https://velog.io/@_im_ssu/IntelliJ-Git-Commit-Template-Plugin</guid>
            <pubDate>Thu, 03 Nov 2022 07:37:01 GMT</pubDate>
            <description><![CDATA[<p>IntelliJ에서 사용할 수 있는 git commit template plugin을 소개하고자 합니다.
commit할 때 이 plugin을 사용하면 
쉽고 편리하게 commit 메시지 컨벤션을 맞출 수 있더라구요!
<em>(git commit 메시지 컨벤션이 궁금하다면 <a href="https://velog.io/@_im_ssu/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%A4%EC%85%98">여기</a>로 !)</em></p>
<p>기업 혹은 단체 내에 지켜야할 특정 컨벤션이 따로 있는 경우가 아니라면, 
개인적으로 편하게 사용하기 매우 좋은 것 같습니다 😊</p>
<h1 id="설치">설치</h1>
<p>IntelliJ - File - Settings - Plugins에 가서 
git commit을 검색하면 Git Commit Template가 나옵니다.
바로 Install 해주면 끝입니다 ! (매우 간단하지요 ~)
<img src="https://velog.velcdn.com/images/_im_ssu/post/6c7dcb9c-97ed-48b1-9c74-00b1dec04f7f/image.png" alt=""></p>
<hr>
<h1 id="사용">사용</h1>
<p>commit 버튼을 누르면 좌측에 이런 부분이 보일 텐데요,
<em>(물론 캡처 사진에는 변경된 파일은 없지만요🤣)</em>
하단에 빨간 동그라미 부분에 보이는 버튼을 눌러줍니다.
<img src="https://velog.velcdn.com/images/_im_ssu/post/31c9f7fd-7993-44e7-ac58-3f5c0465384e/image.png" alt=""></p>
<p>그러면 다음과 같이 새로운 창이 뜹니다!
자주 쓰이는 type들이 있고, 하단에는 제목, 설명(본문), 관련 이슈 등을 작성할 수 있습니다.
<img src="https://velog.velcdn.com/images/_im_ssu/post/189c4bcd-ea3a-43fe-a4c2-cdca95a56818/image.png" alt=""></p>
<p><code>Scope of this change</code> : scope
<code>Short description</code> : subject
<code>Long description</code> : body
<code>Breaking changes</code> : footer - 주요변경내역
<code>Closed issues</code> : footer - 관련 이슈</p>
<p>작성을 하면 다음과 같은 형태로 메시지가 생성됩니다.</p>
<pre><code>&lt;type&gt;(&lt;scope&gt;): &lt;subject&gt;
&lt;BLANK LINE&gt;
&lt;body&gt;
&lt;BLANK LINE&gt;
&lt;footer&gt;</code></pre><hr>
<h3 id="활용">활용</h3>
<p>만약에 다음과 같이 작성을 했다고 하면,
<img src="https://velog.velcdn.com/images/_im_ssu/post/94a4461a-a35a-4421-a867-d98ee508d2c7/image.png" alt=""></p>
<p>다음과 같이 메시지가 작성된 모습을 볼 수 있습니다 😉
<img src="https://velog.velcdn.com/images/_im_ssu/post/78dbdb22-1e31-469a-827e-1348272fe580/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git 커밋 메시지 컨벤션]]></title>
            <link>https://velog.io/@_im_ssu/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%A4%EC%85%98</link>
            <guid>https://velog.io/@_im_ssu/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%A4%EC%85%98</guid>
            <pubDate>Thu, 03 Nov 2022 07:10:49 GMT</pubDate>
            <description><![CDATA[<p>오늘의 주제는 <strong>Git Commit Message Convention</strong></p>
<hr>
<h1 id="span-stylecolorskyblue-왜-필요한가-span"><span style="color:skyblue"> 왜 필요한가 </span></h1>
<p>서로 협업을 할 때, 내가 커밋 메시지를 &#39;잘&#39; 작성했다면
<strong>함께 협업하는 동료들, 그리고 미래의 나</strong> 에게도 
당시 <strong>코드의 의미와 변경사항</strong> 등을 알 수 있게 해줍니다.
물론 코드를 읽는 것으로도 얼추 알 수 있겠지만 (가독성이 좋다면 !?)
메시지를 통해 직관적으로 빠르게 알려준다면 코드를 읽기에도 수월해지고 시간도 절약되겠죠 !</p>
<p>다만, 메시지를 내 맘대로 적으면 상대방이 엥?? 할 수도 있으니,
<strong>간결하고 읽기 쉽게, 규칙을 지켜서</strong> 메시지를 작성하여
읽는 사람 누구나 알기 쉽게 하는 것이 중요합니다 - !</p>
<hr>
<h1 id="span-stylecolorskyblue-구조-및-규칙-span"><span style="color:skyblue"> 구조 및 규칙 </span></h1>
<p>이미 많은 개발자들이 사용하고 있는, 관용적으로 쓰이는 스타일들이 존재합니다.</p>
<p>일반적으로 많이 사용되는 구조는 다음과 같습니다.</p>
<blockquote>
<p>type: subject 
body<br>footer             </p>
</blockquote>
<p>type은 필수로 작성해야 하고,
body와 footer는 상황에 맞게 선택해서 쓰면 됩니다.</p>
<p>또한 각 사항들은 빈 줄로 구분합니다.</p>
<h2 id="subject--what">subject : What</h2>
<p><code>type: subject</code></p>
<ul>
<li><p>가급적 50자 이하로 쓴다. </p>
</li>
<li><p>최대한 간결하게 작성한다.</p>
</li>
<li><p>현재시제, 명령형의 <strong>동사</strong>로 시작한다.</p>
</li>
<li><p>첫 글자는 대문자로 시작한다.</p>
</li>
<li><p>마침표는 생략한다.</p>
</li>
<li><p>type</p>
<blockquote>
<ul>
<li>FEAT : 새로운 기능 추가</li>
<li>FIX : 버그 수정</li>
<li>DOCS : 문서 수정</li>
<li>STYLE : code formatting, 세미콜론 누락, 코드 변경이 없는 경우</li>
<li>REFACTOR : 코드 리팩토링</li>
<li>TEST : 테스트 코드, 리팩토링 테스트 코드 추가</li>
<li>CHORE : 빌드 업무 수정, 패키지 매니저 수정</li>
</ul>
</blockquote>
</li>
</ul>
<h2 id="body--why">body : Why</h2>
<ul>
<li><p>72자 단위로 줄바꿈을 한다.</p>
</li>
<li><p>변경사항 중 무엇을, 왜 했는가를 설명한다.</p>
</li>
<li><p>어떻게(How)는 설명하지 않는다.</p>
</li>
<li><p>제목에서 부족한 내용을 보충한다.</p>
</li>
</ul>
<h2 id="footer">footer</h2>
<ul>
<li>issue tracker ID를 작성하는 경우에 사용한다.</li>
</ul>
<hr>
<h3 id="참고">참고</h3>
<p><a href="https://haesoo9410.tistory.com/299">https://haesoo9410.tistory.com/299</a> 
<a href="https://velog.io/@rladpwl0512/Git-commit-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%A4%EC%85%98">https://velog.io/@rladpwl0512/Git-commit-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%A4%EC%85%98</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[삼성전자 DX 부문 하계 대학생 S/W 알고리즘 역량 강화 특강 후기]]></title>
            <link>https://velog.io/@_im_ssu/%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90-DX-%EB%B6%80%EB%AC%B8-%ED%95%98%EA%B3%84-%EB%8C%80%ED%95%99%EC%83%9D-SW-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%97%AD%EB%9F%89-%EA%B0%95%ED%99%94-%ED%8A%B9%EA%B0%95-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@_im_ssu/%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90-DX-%EB%B6%80%EB%AC%B8-%ED%95%98%EA%B3%84-%EB%8C%80%ED%95%99%EC%83%9D-SW-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%97%AD%EB%9F%89-%EA%B0%95%ED%99%94-%ED%8A%B9%EA%B0%95-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 18 Oct 2022 10:08:34 GMT</pubDate>
            <description><![CDATA[<p>시간은 조금 지났으나, 지난 여름방학 때 참여했던
<strong>삼성전자 DX 부문 하계 대학생 S/W 알고리즘 역량 강화 특강</strong>에 대한 후기를 남겨보고자 합니다.
<em>(이름 짱길다 ㅋㅋㅋ)</em></p>
<p align="center">
<img src="https://velog.velcdn.com/images/_im_ssu/post/f4db5e8a-ab9b-4b41-958d-0740fff47565/image.jpg" height="70%" width="70%"> </p>

<hr>
<h1 id="지원--사전-문제-풀이">지원 &amp; 사전 문제 풀이</h1>
<p>신청 접수를 하고 나면 사전 문제 풀이를 하라고 메일을 받게 됩니다.
사전 문제를 풀 수 있는 삼성 알고리즘 학습 사이트인 <a href="https://swexpertacademy.com">SW Expert Academy</a> 링크를 줍니다.</p>
<p>참고로 위의 사이트에 가입할 때, 
지원할 때 사용한 이메일과 같은 이메일을 사용해야 하기 때문에,
이미 이 사이트에 가입했던 사람은 지원할 때도 같은 이메일 주소를 내는 것이 좋을 것 같아요!</p>
<p>따로 코딩테스트처럼 모두가 같은 시간에 정해진 시간에 푸는 것이 아니고 기간 내에 풀이를 하면 됐는데, 당시에는 포스터에 쓰여있듯이 5일 정도의 시간이 주어졌습니다.
총 2문제였는데, 1번은 간단한 탐색 구현으로 난이도가 어렵지 않았고, 2번은 API 구현이었는데 난이도가 조금 있었습니다. 저는 2번 문제의 일부 테스트케이스가 시간 초과를 했어서 결과를 보니 1.8솔 정도였어요!</p>
<hr>
<h1 id="교육-과정">교육 과정</h1>
<p>그 이후 합격 메일을 받았고, 오리엔테이션을 시작으로 6주간의 과정이 시작되었습니다.</p>
<p>6주 간의 과정은 기초 학습(4주)과 실전 학습(2주)로 이루어져 있습니다.</p>
<p>기초 학습의 경우는, 매일 텍스트로 주어진 알고리즘 내용이 주어지며, 온라인 강의들을 시청하고 관련 문제 풀이를 해야합니다. 문제의 수는 매일 달랐고, 난이도도 다양했기 때문에 생각보다 시간이 오래 걸렸습니다.</p>
<p>실전 학습의 경우는, 삼성 검정 시험 B형 (Pro) 수준의 문제들을 풀게 됩니다. 높은 난이도이기에 하루에 한 문제를 풀고, 해설 강의를 보는 방식으로 진행되었습니다. 그리고 B형을 풀기 위한 팁을 알려주는 시간도 있었는데, 실전 문제를 풀 때 배운 팁을 활용해 실전인 것처럼 풀이하는 것이 도움이 되었습니다.</p>
<hr>
<h1 id="삼성-sw-검정-시험-b형">삼성 SW 검정 시험 B형</h1>
<p>교육생들에게는 실제 B형 시험을 볼 수 있는 기회 2번이 주어졌습니다.
용인 인재개발원에 가서 시험을 봤는데, 시설이 너~무 좋아서 기분이 좋았던..🤣🤣
하지만 시험은 역시 정말 어려웠답니다...ㅠㅠ (결국 통과 못했다는 얘기) </p>
<p>아 그리고 저는 검정 시험 2번 다 &#39;공부하고 제일 마지막 날짜에 봐야지&#39; 했다가 가장 마지막 시험 날짜에 다른 기업 코테와 일정이 겹쳐서
결국 1번만 응시하게 된 점은 많이 아쉬움이 남습니다.
만약 저처럼 취업 준비를 하실 예정이라면 일정을 잡는 데 참고하시면 좋을 것 같습니다 :)</p>
<p><del>이후 추가 응시 여부 메일이 왔으나 다른 코테랑 또 겹쳐버렸다...ㅠㅠ</del></p>
<hr>
<h1 id="후기--팁">후기 &amp; 팁</h1>
<p>위의 과정들은 6주 간 매일 매일 이뤄지기에 솔직하게 시간적으로 여유가 많이 없었습니다.
그래도 한 마디로 얘기하면 방학 동안 알고리즘 올인한 느낌 !</p>
<p>또한 위의 교육을 듣는 과정이, 매일 매일 여기까지 하세요-가 아니라,
본인 일정에 맞게 공부해서 교육 마감일 전까지 수강 완료 하세요! 이기 때문에, 
스스로 시간을 잘 관리하고 자기주도적으로 학습해야 합니다.
하지만 나의 일정에 맞춰 공부할 수 있다는 점이 아주 큰 장점이죠 !!
그러나 한 번 밀리면.. 죽음 뿐...
_TMI) 실제로 부산 여행 갔다와서 밀린 내용 공부 하려다가 힘들어서 죽는 줄 알았답니다..🤣 _</p>
<p>하지만 알고리즘을 카테고리별로 공부하고, 양질의 자료들을 많이 제공해주기 때문에
본격적으로 코딩테스트를 준비하기 전 기초를 다지는데 도움이 많이 되어서
매<del>우</del> 만족스러웠습니다 !
그리고 이 일정을 모두 소화하고 수료증을 받았을 때의 뿌듯함은 보너스 ㅎㅎ
(수료증은 메일로 pdf 파일을 받았습니다!)</p>
<hr>
<p>결국 결론은,
힘들었지만 수료해서 정말 기쁘고요 ☺️
코테 등 문제를 풀기 전에도 무작정 풀기보다는 다각도로 고민하면서 문제를 푸는 연습이 되었습니다.
알고리즘 이론을 잘 다지고 싶고 심도있게 공부하고 싶은 대학생분들에게는 매우 추천합니다 ! :)</p>
]]></description>
        </item>
    </channel>
</rss>