<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>0n1dev's LOG</title>
    <link>https://0n1dev.tistory.com/</link>
    <description>0n1dev's LOG</description>
    <language>ko</language>
    <pubDate>Thu, 13 Aug 2026 22:02:29 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>0n1dev</managingEditor>
    <image>
      <title>0n1dev's LOG</title>
      <url>https://tistory1.daumcdn.net/tistory/4790037/attach/bb09bd6202f649edbc713bb6c53caedd</url>
      <link>https://0n1dev.tistory.com</link>
    </image>
    <item>
      <title>취소와 타임아웃 #3</title>
      <link>https://0n1dev.tistory.com/48</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;취소와&amp;nbsp;타임아웃&amp;nbsp;#3&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Job에 대해 취소&lt;/h3&gt;
&lt;pre id=&quot;code_1650023507695&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job1 = launch {
        println(&quot;launch1: ${Thread.currentThread().name}&quot;)
        delay(1000L)
        println(&quot;3!&quot;)
    }

    val job2 = launch {
        println(&quot;launch2: ${Thread.currentThread().name}&quot;)
        println(&quot;1!&quot;)
    }

    val job3 = launch {
        println(&quot;launch3: ${Thread.currentThread().name}&quot;)
        delay(500L)
        println(&quot;2!&quot;)
    }

    delay(800L)
    job1.cancel()
    job2.cancel()
    job3.cancel()
    println(&quot;4!&quot;)
}

fun main() = runBlocking {
    doOneTwoThree()
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;5!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;job1은 1000ms 동안 지연 되기 때문에 3!은 출력이 안됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취소 불가능한 Job&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch(Dispatchers.Default)는 다른 스레드에서 수행 됨.&lt;/p&gt;
&lt;pre id=&quot;code_1650023803196&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L

        while (i &amp;lt;= 10) {
            val currentTime = System.currentTimeMillis()
            if (currentTime &amp;gt;= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }

    delay(200L)
    job1.cancel()
    println(&quot;doCount Done!&quot;)
}

fun main() = runBlocking {
    doCount()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;doCount Done!이 작업이 끝나기 전에 출력이 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 job1이 취소가 되지 않음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cancel과&amp;nbsp;join&lt;/h3&gt;
&lt;pre id=&quot;code_1650024550013&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L

        while (i &amp;lt;= 10) {
            val currentTime = System.currentTimeMillis()
            if (currentTime &amp;gt;= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
    
    delay(200L)
    job1.cancel()
    job1.join()
    println(&quot;doCount Done!&quot;)
}

fun main() = runBlocking {
    doCount()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel 이후에 join을 넣으면 doCount가 끝날 때 doCount Done!가 출력 됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cancelAndJoin&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel을 하고 join을 하는 일은 자주 일어나는 일이기 때문에 한번에 하는 cancelAndJoin이 준비되어 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1650024500606&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L

        while (i &amp;lt;= 10) {
            val currentTime = System.currentTimeMillis()
            if (currentTime &amp;gt;= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
    
    delay(200L)
    job1.cancelAndJoin()
    println(&quot;doCount Done!&quot;)
}

fun main() = runBlocking {
    doCount()
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;isActive&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isActive를 호출하면 해당 코루틴이 여전히 활성화된지 확인이 가능.&lt;/p&gt;
&lt;pre id=&quot;code_1650024670909&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L

        while (i &amp;lt;= 10 &amp;amp;&amp;amp; isActive) {
            val currentTime = System.currentTimeMillis()
            if (currentTime &amp;gt;= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }

    delay(200L)
    job1.cancelAndJoin()
    println(&quot;doCount Done!&quot;)
}

fun main() = runBlocking {
    doCount()
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;finally를&amp;nbsp;같이&amp;nbsp;사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;suspend 함수들은 JobCancellationException를 발생하기 때문에 표준 try catch finally로 대응할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1650025015437&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job1 = launch {
        try {
            println(&quot;launch1: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;3!&quot;)
        } finally {
            println(&quot;job1 is finishing!&quot;)
        }
    }

    val job2 = launch {
        try {
            println(&quot;launch2: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;1!&quot;)
        } finally {
            println(&quot;job2 is finishing!&quot;)
        }
    }

    val job3 = launch {
        try {
            println(&quot;launch3: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;2!&quot;)
        } finally {
            println(&quot;job3 is finishing!&quot;)
        }
    }

    delay(800L)
    job1.cancel()
    job2.cancel()
    job3.cancel()
    println(&quot;4!&quot;)
}

fun main() = runBlocking {
    doOneTwoThree()
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;5!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취소 불가능한 블록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 코드는 취소가 불가능해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withContext(Noncancellable) 을 이용하면 취소 불가능한 블록을 만들 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1650025148772&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job1 = launch {
        withContext(NonCancellable) {
            println(&quot;launch1: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;3!&quot;)
        }
        delay(1000L)
        print(&quot;job1: end&quot;)
    }

    val job2 = launch {
        withContext(NonCancellable) {
            println(&quot;launch1: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;1!&quot;)
        }
        delay(1000L)
        print(&quot;job2: end&quot;)
    }

    val job3 = launch {
        withContext(NonCancellable) {
            println(&quot;launch1: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;2!&quot;)
        }
        delay(1000L)
        print(&quot;job3: end&quot;)
    }

    delay(800L)
    job1.cancel()
    job2.cancel()
    job3.cancel()
    println(&quot;4!&quot;)
}

fun main() = runBlocking {
    doOneTwoThree()
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;5!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타임 아웃&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 시간이 끝난 후 종료하고 싶으면 withTimeout을 사용.&lt;/p&gt;
&lt;pre id=&quot;code_1650026114986&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L

        while (i &amp;lt;= 10 &amp;amp;&amp;amp; isActive) {
            val currentTime = System.currentTimeMillis()
            if (currentTime &amp;gt;= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
}

fun main() = runBlocking {
    withTimeout(500L) {
        doCount()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취소가 되면 TimeoutCancellationException 예외가 발생&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;withTimeoutOrNull&lt;/h3&gt;
&lt;pre id=&quot;code_1650026181467&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L

        while (i &amp;lt;= 10 &amp;amp;&amp;amp; isActive) {
            val currentTime = System.currentTimeMillis()
            if (currentTime &amp;gt;= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
}

fun main() = runBlocking {
    val result = withTimeoutOrNull(500L) {
        doCount()
        true
    } ?: false
    println(result)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공할 경우 withTimeoutOrNull의 마지막에서 true를 리턴하고, 실패했을 경우 null을 반환&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/FastCampus</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/48</guid>
      <comments>https://0n1dev.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 15 Apr 2022 21:37:54 +0900</pubDate>
    </item>
    <item>
      <title>스코프 빌더, 잡 #2</title>
      <link>https://0n1dev.tistory.com/47</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;스코프&amp;nbsp;빌더,&amp;nbsp;잡&amp;nbsp;#2&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;suspend 함수에서 코루틴 빌더 호출&lt;/h3&gt;
&lt;pre id=&quot;code_1650020724149&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doOneTwoThree() {
    launch {
        println(&quot;launch1: ${Thread.currentThread().name}&quot;)
        delay(1000L)
        println(&quot;3!&quot;)
    }

    launch {
        println(&quot;launch2: ${Thread.currentThread().name}&quot;)
        println(&quot;1!&quot;)
    }

    launch {
        println(&quot;launch3: ${Thread.currentThread().name}&quot;)
        delay(500L)
        println(&quot;2!&quot;)  
    }
    println(&quot;4!&quot;)
}

fun main() = runBlocking {
    doOneTwoThree()
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;5!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 오류가 발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking을 제외한 코루틴 빌더는 코루틴 스코프 내에서 호출 되어야 함.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코루틴 스코프&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 스코프 만드는 다른 방법은 스코프 빌더(coroutineScope)를 이용하는 것.&lt;/p&gt;
&lt;pre id=&quot;code_1650021235164&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    launch {
        println(&quot;launch1: ${Thread.currentThread().name}&quot;)
        delay(1000L)
        println(&quot;3!&quot;)
    }

    launch {
        println(&quot;launch2: ${Thread.currentThread().name}&quot;)
        println(&quot;1!&quot;)
    }

    launch {
        println(&quot;launch3: ${Thread.currentThread().name}&quot;)
        delay(500L)
        println(&quot;2!&quot;)  
    }
    println(&quot;4!&quot;)
}

fun main() = runBlocking {
    doOneTwoThree()
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;5!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking은 현재 스레드를 멈추게 만들고 기다리지만, coroutineScope는 현재 스레드를 멈추게 하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출한 쪽이 suspend 되고 시간이 되면 다시 활동하게 됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Job을 이용한 제어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 빌더 launch 는 Job 객체를 반환하며 이를 통해 종료될 때까지 기다릴 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1650022329866&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job = launch {
        println(&quot;launch1: ${Thread.currentThread().name}&quot;)
        delay(1000L)
        println(&quot;3!&quot;)
    }
    job.join()

    launch {
        println(&quot;launch2: ${Thread.currentThread().name}&quot;)
        println(&quot;1!&quot;)
    }

    launch {
        println(&quot;launch3: ${Thread.currentThread().name}&quot;)
        delay(500L)
        println(&quot;2!&quot;)
    }
    println(&quot;4!&quot;)
}

fun main() = runBlocking {
    doOneTwoThree()
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;5!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/FastCampus</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/47</guid>
      <comments>https://0n1dev.tistory.com/47#entry47comment</comments>
      <pubDate>Fri, 15 Apr 2022 20:37:48 +0900</pubDate>
    </item>
    <item>
      <title>스코프 빌더 #1</title>
      <link>https://0n1dev.tistory.com/46</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;스코프&amp;nbsp;빌더&amp;nbsp;#1&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;runBlocking&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴을 만드는 가장 간단한 함수. (코루틴 빌더라고 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴을 만들고 코드 블록의 수행이 끝날 때까지 runBlocking 다음 코드를 수행하지 못하게 막음.&lt;/p&gt;
&lt;pre id=&quot;code_1649940852708&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    println(Thread.currentThread().name)
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코루틴 빌더의 수신 객체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking안에서 this를 수행하면 코루틴이 수신 객체(Receiver)인 것을 알 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1649941413987&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    println(this)
    println(Thread.currentThread().name)
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;BlockingCoroutine{Active}@2ff5659e&lt;/span&gt; 이런 형태의 결과가 나옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BlockingCoroutine은 CoroutineScope의 자식이다. 코틀린 코루틴을 쓰는 모든 곳에는 코루틴 스코프가 있다고 생각하면 됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코루틴 컨텍스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 스코프는 코루틴을 제대로 처리하기 위한 정보, 코루틴 컨텍스트를 가지고 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1649942291689&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    println(coroutineContext)
    println(Thread.currentThread().name)
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;launch 코루틴 빌더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch는 코루틴 빌더임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch는 &quot;할 수 있다면 다른 코루틴 코드를 같이 수행&quot;시키는 코루틴 빌더.&lt;/p&gt;
&lt;pre id=&quot;code_1649945103894&quot; class=&quot;isbl&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        println(&quot;launch: ${Thread.currentThread().name}&quot;)
        println(&quot;World!&quot;)
    }
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch 안에 있는 내용이 runBlocking 안에 있는 내용 보다 늦게 수행 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 스레드를 다 사용할 때 까지 launch의 코드 블록이 기다림.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;delay 함수&lt;/h3&gt;
&lt;pre id=&quot;code_1649945879850&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        println(&quot;launch: ${Thread.currentThread().name}&quot;)
        println(&quot;World!&quot;)
    }
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    delay(500L)
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;delay가 호출이 되면 runBlocking의 코드가 쉬어가면서 500ms 동안 launch의 코드 블록이 먼저 수행 됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코루틴 내에서 sleep&lt;/h3&gt;
&lt;pre id=&quot;code_1650019059680&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        println(&quot;launch: ${Thread.currentThread().name}&quot;)
        println(&quot;World!&quot;)
    }
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    Thread.sleep(500)
    println(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread.sleep 은 delay 스레드가 중단되기 때문에 스레드를 양보하지 않음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한번에 여러 launch&lt;/h3&gt;
&lt;pre id=&quot;code_1650019294562&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        println(&quot;launch1: ${Thread.currentThread().name}&quot;)
        delay(1000L)
        println(&quot;3!&quot;)
    }
    launch {
        println(&quot;launch2: ${Thread.currentThread().name}&quot;)
        println(&quot;1!&quot;)
    }
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    delay(500L)
    println(&quot;2!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중단 이후 깨어나는 순서에 따라 결과가 달라짐.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상위 코루틴은 하위 코루틴을 끝까지 책임짐&lt;/h3&gt;
&lt;pre id=&quot;code_1650019440183&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() {
    runBlocking {
        launch {
            println(&quot;launch1: ${Thread.currentThread().name}&quot;)
            delay(1000L)
            println(&quot;3!&quot;)
        }
        launch {
            println(&quot;launch2: ${Thread.currentThread().name}&quot;)
            println(&quot;1!&quot;)
        }
        println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
        delay(500L)
        println(&quot;2!&quot;)
    }
    print(&quot;4!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking 안에 두 launch 가 속해 있는데 계층화 되어 있어 구조적.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch가 모두 끝나기 전까지 종료되지 않음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;suspend 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 일부를 함수로 분리할 때 suspend 키워드를 붙여서 함수를 생성하면 됨.&lt;/p&gt;
&lt;pre id=&quot;code_1650019778190&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

suspend fun doThree() {
    println(&quot;launch1: ${Thread.currentThread().name}&quot;)
    delay(1000L)
    println(&quot;3!&quot;)
}

suspend fun doOne() {
    println(&quot;launch1: ${Thread.currentThread().name}&quot;)
    println(&quot;1!&quot;)
}

suspend fun doTwo() {
    println(&quot;runBlocking: ${Thread.currentThread().name}&quot;)
    delay(500L)
    println(&quot;2!&quot;)
}

fun main() = runBlocking {
    launch {
        doThree()
    }
    launch {
        doOne()
    }
    doTwo()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;doOne 함수는 delay와 같은 suspend 함수를 사용하지 않았기 때문에 suspend 생략 가능.&lt;/p&gt;
&lt;pre id=&quot;code_1650019816158&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public suspend fun delay(timeMillis: Long) {
    if (timeMillis &amp;lt;= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation&amp;lt;Unit&amp;gt; -&amp;gt;
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis &amp;lt; Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Book &amp;amp; Lecture/FastCampus</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/46</guid>
      <comments>https://0n1dev.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 14 Apr 2022 23:29:47 +0900</pubDate>
    </item>
    <item>
      <title>코루틴 등장 #0</title>
      <link>https://0n1dev.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;코루틴&amp;nbsp;등장&amp;nbsp;#0&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코루틴 등장&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성&lt;/li&gt;
&lt;li&gt;병렬성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동시성&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동시성 없이는 사용자가 불편&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운로드 중에는 다운로드만
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운로드 시간이 오래걸릴수록 아무것도 할 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로그램을 다운받다 잘못되면 그 디스크의 처음부터 다시 다운로드&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동시성 없이는, 하드웨어 리소스도 낭비&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초당 수백만개 처리가능한 리소스가 있을 때, 동시성 없이는 사용중인 리소스 외 다른 리소스를 사용할 수 없음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;병렬성&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;여러 CPU가 개별 캐쉬를 사용하게 되면서 문제 발생&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유하고 있는 캐쉬의 데이터 불일치가 발생할 수 있음.&lt;/li&gt;
&lt;li&gt;락이나 메모리 베리어가 필요할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 이러한 문제들을 콜백과 RxJava가 해결해 주었지만 아직도 많은 문제들이 있음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;images_zhd5379_post_39c3e923-a999-4978-8071-309a9a051b0b_image.png&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tBxQN/btrzjHUNJ09/PnwfR3u1pjThpFiWnCInIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tBxQN/btrzjHUNJ09/PnwfR3u1pjThpFiWnCInIK/img.png&quot; data-alt=&quot;콜백 코드 지옥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tBxQN/btrzjHUNJ09/PnwfR3u1pjThpFiWnCInIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtBxQN%2FbtrzjHUNJ09%2FPnwfR3u1pjThpFiWnCInIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;407&quot; data-filename=&quot;images_zhd5379_post_39c3e923-a999-4978-8071-309a9a051b0b_image.png&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;콜백 코드 지옥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/FastCampus</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/45</guid>
      <comments>https://0n1dev.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 14 Apr 2022 21:30:09 +0900</pubDate>
    </item>
    <item>
      <title>#0. 자료구조란</title>
      <link>https://0n1dev.tistory.com/44</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;#0.&amp;nbsp;자료구조란&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 자료구조는 데이터를 저장, 조직, 관리하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조는 자료(데이터)에 효율적으로 접근하고 수정할 수 있도록 저장, 조직, 관리하는 방법에 관한 이론.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 자료구조는 문제 해결에 사용할 부품&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결 작업을 수행하기 위해 입력을 받아 원하는 출력을 만들어내는 과정을 기술한 것을 알고리즘이라 하는데 자료구조는 이 과정에서 부품 같은 역할을 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조를 몰라도 프로그램을 작성할 수 있지만 이는 재료의 속성이나 구조물의 구성 방법을 모르고 건축묵을 만드는 것과 같음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 자료구조는 생각하는 방법을 훈련하는 도구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조를 구현하는 과정, 자료구조들을 이용해서 문제를 해결하는 과정, 문제를 해결하는 과정에서 논리의 골격이 구성되는 방법 또는 스타일 등(생각하는 방법)이 중요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 자료구조의 종류와 자바의 컬렉션 패키지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조는 종류가 다양하지만 사향과 목적에 맞게 적절한 자료구조를 선택함으로써 효율적인 데이터 관리가 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 경우 자료구조 관련 클래스를 모아둔 컬렉션 패키지가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 이 책에서 다루는 자료구조&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트&lt;/li&gt;
&lt;li&gt;스택&lt;/li&gt;
&lt;li&gt;큐&lt;/li&gt;
&lt;li&gt;검색 트리&lt;/li&gt;
&lt;li&gt;힙&lt;/li&gt;
&lt;li&gt;해시 테이블&lt;/li&gt;
&lt;li&gt;그래프&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/Algorithm</category>
      <category>쉽게 배우는 자료구조</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/44</guid>
      <comments>https://0n1dev.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 6 Apr 2022 22:01:19 +0900</pubDate>
    </item>
    <item>
      <title>F-Lab 멘토링 회고</title>
      <link>https://0n1dev.tistory.com/43</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;F-Lab 멘토링 회고&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며...&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;내돈내산.png&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UGBqL/btrviIwEtY1/vspHW17Cnqd3ftyswip6EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UGBqL/btrviIwEtY1/vspHW17Cnqd3ftyswip6EK/img.png&quot; data-alt=&quot;내돈 내산... 금액은 가려놓겠다. (수강료에 변동이 있는 듯)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UGBqL/btrviIwEtY1/vspHW17Cnqd3ftyswip6EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUGBqL%2FbtrviIwEtY1%2FvspHW17Cnqd3ftyswip6EK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;989&quot; height=&quot;424&quot; data-filename=&quot;내돈내산.png&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내돈 내산... 금액은 가려놓겠다. (수강료에 변동이 있는 듯)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 광고도 아니며 내돈내산해서 F-LAB이라는 멘토링을 진행하고 남기는 후기겸 회고이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 언어에 대한 고민이 있거나 F-LAB을 수강을 할까 고민하는 취준생 혹은 이직러들의 고민의 해답을 찾을 수 있는 글이 되었으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이직에 대한 고민 그리고 언어 전환...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 직장에서 1년동안 PHP를 사용하였다. 단순하게 개발이 좋아서 개발자가 되었고, 그 당시에는 국내 개발 시장에서 PHP의 선호도가 그렇게 낮은 줄 몰랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사 후 첫 회식에서 사수분께서 말씀하시길 &quot;신입일 때 PHP로 시작하는건 별로다...&quot; 입사하자마자 개발 커리어에 대한 고민이 시작되지 않았나 싶다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스파게티.jpeg&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eytI2G/btrvoda3AeC/fdwoKxHBMCKwNaILk0YcT0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eytI2G/btrvoda3AeC/fdwoKxHBMCKwNaILk0YcT0/img.jpg&quot; data-alt=&quot;코드를 보니 스파게티가 먹고 싶다...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eytI2G/btrvoda3AeC/fdwoKxHBMCKwNaILk0YcT0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeytI2G%2Fbtrvoda3AeC%2FfdwoKxHBMCKwNaILk0YcT0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;353&quot; data-filename=&quot;스파게티.jpeg&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드를 보니 스파게티가 먹고 싶다...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PHP로 개발한지 1년이 지난 시점 내가 개발한 서비스를 돌아 보니 단순한 이벤트성 페이지로 가득 채워졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 단순 이벤트성 페이지만 개발한건 아니다. 하지만 조금 큰 서비스 프로젝트를 보면 PHP 언어 자체의 문제점은 아닐거라 생각이 들지만 정말 로직을 파악하는것 자체가 힘들정도로 코드들이 꼬여있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맡은 일을 열심히 하다 보니 운이 좋게 좀 더 중요 서비스를 담당하게 되었는데 외주사에서 개발한 프로젝트를 기반으로 보다 더 고도화 시키고 신규개발을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외주사에서 진행한 프로젝트는 Java로 개발이 되어있었고 협업 툴을 활용해 보다 체계적인 개발 문화를 보여주고 있었는데 나는 정말 충격이었다. (첫 직장에서는 3개의 본부에서 한 레포에 붙어서 개발하고 있었다... 실화인가?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 입사한 주변 동기들도 1년만에 더 좋은 개발 문화를 찾아서 떠났다. 나 또한 더 좋은 개발 문화를 원했고 새롭게 담당한 프로젝트 언어에 대한 공부 또한 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 공부를 해야했으니 이직을 위한 공부를 시작하기로 마음을 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;F-LAB을 선택한 이유...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷을 찾아보면 정말 수많은 강의 후기를 볼 수 있다. 항상 느끼지만 인터넷에는 너무 과한 정보들이 많기 때문에 질 좋은 정보를 얻는것도 하나의 능력이라 생각이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 F-LAB을 선택한 이유는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공부는 어차피 스스로 해야한다. 얼마나 효율적인 방향성을 제시해 주나?&lt;/li&gt;
&lt;li&gt;회사에는 코드 리뷰라던가 동료 개발자로 부터 피드백을 받기 어려운 개발 문화다. 내가 무심코 작성한 코드가 정말 최선일지 같이 고민을 해주나?&lt;/li&gt;
&lt;li&gt;이직을 위해서는 회사 프로젝트 외에 추가적인 프로젝트가 필요했다. 수강 기간중 프로젝트가 진행이 되는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-08 오전 11.15.46.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DbkXW/btrvnVPTlwf/X9v3kyqwFgetaoboLYzPEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DbkXW/btrvnVPTlwf/X9v3kyqwFgetaoboLYzPEk/img.png&quot; data-alt=&quot;실제 멘토링 중 코드 리뷰&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DbkXW/btrvnVPTlwf/X9v3kyqwFgetaoboLYzPEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDbkXW%2FbtrvnVPTlwf%2FX9v3kyqwFgetaoboLYzPEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;757&quot; data-filename=&quot;스크린샷 2022-03-08 오전 11.15.46.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;757&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 멘토링 중 코드 리뷰&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F-LAB에서는 직접 코드 리뷰가 어떤식으로 진행되는지 GitHub로 공개되어 있어서 레포 하나하나 확인해 보고 신중하게 결정할 수 있었고, 또 상담 신청을 하면 1:1로 많은 대화를 얻을 수 있는데 상담만 받아도 많은걸 되돌아 볼 수 있었고 더 고민하는 것 보다 빠르게 시작해서 열심히 하는게 좋을것 같다는 생각이 들어 바로 카드를 긁었다... (할부의 노예 시작...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6개월이라는 시간동안...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링은 멘토님이 정해준 책을 읽어와서 멘토링 시간동안 기술면접 보듯 질문을 주신다. 나는 그 질문에 대해 답변을 드리면 점점 꼬리 물기로 질문을 주시는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문이 다 끝나면 피드백과 추가로 보면 좋을만한 부분을 알려주신다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 회사와 병행하며 멘토링을 소화하려니 정말 많이 힘들고 정신이 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 중간 회사에서 장애도 있었고... 순수하게 멘토링에만 집중할 수 있는 상황은 아니었다. (이 부분에 대해 아직도 아쉬움이 남는다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 고민을 멘토님께 말씀 드렸고 보다 효율적으로 시간 관리하며 공부하는 방법에 대해 제시해 주셔서 보다 준비 된 멘토링 시간을 가질수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링 기간동안 계속해서 이직과 좀 더 경력을 쌓는것에 대한 고민이 있었는데 더 큰 기업의 서비스를 개발하고 더 나은 개발 문화를 경험하고 싶다는 생각에 이직을 결심하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이직 성공 후 퇴사...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이직을 위해 지원한 회사는 카카오, 네이버, 넥슨 3곳이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은것을 경험하기 위해서는 스타트업도 좋다는 멘토님의 말씀도 있었지만 보다 안정감을 줄수있고 다양한 개발자를 만나며 큰 서비스를 경험해 보고 싶은 욕심이 있었기에 큰 서비스를 개발하는 IT회사 중 흥미가 있는 도메인을 골라 지원하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 ...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 - 코테 탈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 - 최종 탈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넥슨 - 최종 합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합격을 받은 순간까지 정말 많은 고민을 했다. 내가 과연 이직하는게 옳은 생각일까? 하지만 계속 머물러 있기 보다는 흐르는 물이 되고 싶었기 때문에 이직을 선택하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10월 22일 부로 퇴사를 하게 되었는데 퇴사하는 날 자리 정리 후 마지막으로 집으로 돌아가는데 정말 말 그대로 &quot;시원 섭섭&quot;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멘토링을 끝내며...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링을 하는 당시에는 정말 내가 적지 않은 돈을 쓰면서 내 인생에 긍정적인 변화가 있을까? 하는 많은 걱정이 있었지만 수료를 하면서 이직도 성공하게 되어 힘들었지만 보람찬 6개월이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 단순하게 지식만 얻고 끝난게 아닌 내가 앞으로 어떤식으로 나아가야 할 지 남은 개발 인생의 방향성을 어느정도 잡게 되었다. (아직 많이 부족하지만...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-08 오후 2.58.39.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IQAYy/btrvr9GAqih/ETZmAMn0galg9NoC9dkHrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IQAYy/btrvr9GAqih/ETZmAMn0galg9NoC9dkHrk/img.png&quot; data-alt=&quot;개발자 커뮤니티&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IQAYy/btrvr9GAqih/ETZmAMn0galg9NoC9dkHrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIQAYy%2Fbtrvr9GAqih%2FETZmAMn0galg9NoC9dkHrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;640&quot; data-filename=&quot;스크린샷 2022-03-08 오후 2.58.39.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발자 커뮤니티&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 F-LAB의 장점중 하나가 수료를 하고나서 그냥 끝나는게 아닌 커뮤니티에 가입이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 수료생 혹은 멘토분들과 교류를하며 계속해서 긍정적인 영향을 공유하는 모임이라 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 자격만 된다면 커뮤니티에 합류도 가능하니 신청해보면 좋을것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://intro.f-lab.kr/?utm_source=graduate&amp;amp;utm_medium=blog&amp;amp;utm_campaign=event&amp;amp;utm_content=15&quot;&gt;https://intro.f-lab.kr/?utm_source=graduate&amp;amp;utm_medium=blog&amp;amp;utm_campaign=event&amp;amp;utm_content=15&lt;/a&gt;&lt;/p&gt;</description>
      <category>Log/회고</category>
      <category>F-LAB</category>
      <category>flab</category>
      <category>개발자 이직</category>
      <category>개발자 취업</category>
      <category>멘토링 후기</category>
      <category>부트캠프</category>
      <category>에프랩</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/43</guid>
      <comments>https://0n1dev.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 8 Mar 2022 15:00:32 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot 기반으로 개발하는 Spring Security #1</title>
      <link>https://0n1dev.tistory.com/42</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring&amp;nbsp;Boot&amp;nbsp;기반으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Security&amp;nbsp;#1&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Logout 처리, LogoutFilter&lt;/h3&gt;
&lt;pre id=&quot;code_1643204727443&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        http.logout() // 로그아웃 처리
                .logoutUrl(&quot;/logout&quot;) // 로그아웃 처리 URL
                .logoutSuccessUrl(&quot;/login&quot;) // 로그아웃 성공 후 이동 페이지
                .deleteCookies(&quot;JESSIONID&quot;, &quot;remember-me&quot;) // 로그아웃 후 쿠키 삭제
                .addLogoutHandler() // 로그아웃 핸들러
                .logoutSuccessHandler() // 로그아웃 성공 후 핸들러러&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그아웃은 기본적으로 POST 방식으로 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Remember Me 인증&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션이 만료되고 웹 브라우저가 종료된 후에도 애플리케이션이 사용자를 기억하는 기능.&lt;/li&gt;
&lt;li&gt;Remember-Me 쿠키에 대한 Http 요청 을 확인한 후 토큰 기반 인증을 사용해 유효성을 검사하고 토큰이 검증되면 사용자는 로그인 됨.&lt;/li&gt;
&lt;li&gt;사용자 라이프 사이클
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증 성공&lt;/li&gt;
&lt;li&gt;인증 실패&lt;/li&gt;
&lt;li&gt;로그아웃&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1643206511334&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        http
            .rememberMe() // rememberMe 기능이 작동
                .rememberMeParameter(&quot;remember&quot;) // 기본 파라미터명은 remember-me
                .tokenValiditySeconds(3600) // Default는 14일
//                .alwaysRemember(true) // 리멤버 미 기능이 활성화되지 않아도 항상 실행
                .userDetailsService(userDetailsService); // 리멤버 미 인증할 때 필요한 서비스스&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RememberMeAuthenticationFilter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>스프링 시큐리티</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/42</guid>
      <comments>https://0n1dev.tistory.com/42#entry42comment</comments>
      <pubDate>Thu, 27 Jan 2022 00:34:08 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot 기반으로 개발하는 Spring Security #0</title>
      <link>https://0n1dev.tistory.com/41</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring&amp;nbsp;Boot&amp;nbsp;기반으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Security&amp;nbsp;#0&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 시큐리티의 의존성 추가 시 일어나는 일&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어짐&lt;/li&gt;
&lt;li&gt;별도의 설정이나 구현을 하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 요청은 인증이 되어야 자원에 접근 가능.&lt;/li&gt;
&lt;li&gt;인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공&lt;/li&gt;
&lt;li&gt;기본 로그인 페이지 제공&lt;/li&gt;
&lt;li&gt;기본 계정 한 개를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계정 추가, 권한 추가, DB 연동 등.&lt;/li&gt;
&lt;li&gt;기본적인 보안 기능 외에 시스템에서 필요로 하는 더 세부적이고 추가적인 보안기능이 필요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 정의 보안 기능 구현&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebSecurityConfigurerAdapter 클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 시큐리티의 웹 보안 기능 초기화 및 설정. (가장 기본적인 초기 설정)&lt;/li&gt;
&lt;li&gt;HttpSecurity 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HttpSecurity 클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세부적인 보안 기능을 설정할 수 있는 API 제공.&lt;/li&gt;
&lt;li&gt;인증 API
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http.formLogin()&lt;/li&gt;
&lt;li&gt;http.logout()&lt;/li&gt;
&lt;li&gt;http.csrf()&lt;/li&gt;
&lt;li&gt;http.httpBasic()&lt;/li&gt;
&lt;li&gt;http.SessionManagement()&lt;/li&gt;
&lt;li&gt;http.RememberMe()&lt;/li&gt;
&lt;li&gt;http.ExceptionHandling()&lt;/li&gt;
&lt;li&gt;http.addFilter()&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인가 API
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http.authorizeRequests()&lt;/li&gt;
&lt;li&gt;http.antMatchers(&quot;/admin&quot;)&lt;/li&gt;
&lt;li&gt;http.hasRole(USER)&lt;/li&gt;
&lt;li&gt;http.permitAll()&lt;/li&gt;
&lt;li&gt;http.authenticated()&lt;/li&gt;
&lt;li&gt;http.fullyAuthentication()&lt;/li&gt;
&lt;li&gt;http.access(hasRole(USER))&lt;/li&gt;
&lt;li&gt;http.denyAll()&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 시큐리티에서 제공되는 기본 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1643191259282&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	private void applyDefaultConfiguration(HttpSecurity http) throws Exception {
		http.csrf();
		http.addFilter(new WebAsyncManagerIntegrationFilter());
		http.exceptionHandling();
		http.headers();
		http.sessionManagement();
		http.securityContext();
		http.requestCache();
		http.anonymous();
		http.servletApi();
		http.apply(new DefaultLoginPageConfigurer&amp;lt;&amp;gt;());
		http.logout();
	}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Form Login 인증&lt;/h3&gt;
&lt;pre id=&quot;code_1643193469876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        http.formLogin() // Form 로그인 인증 기능 작동
                .loginPage() // 사용자 정의 로그인 페이지
                .defaultSuccessUrl() // 로그인 성공 후 이동 페이지
                .failureUrl() // 로그인 실패 후 이동 페이지
                .usernameParameter() // 아이디 파라메터명 설정
                .passwordParameter() // 패스워드 파라메터명 설정
                .loginProcessingUrl() // 로그인 Form Action Url
                .successHandler() // 로그인 성공 핸들러
                .failureHandler() // 로그인 실패 핸들러&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>스프링 시큐리티</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/41</guid>
      <comments>https://0n1dev.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 26 Jan 2022 22:40:26 +0900</pubDate>
    </item>
    <item>
      <title>Reactive Programming #1. Reactive Streams</title>
      <link>https://0n1dev.tistory.com/40</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reactive&amp;nbsp;Programming&amp;nbsp;#1.&amp;nbsp;Reactive&amp;nbsp;Streams&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reactive Streams란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non-Blocking Back pressure로 비동기적 스트림 처리를 제공하는 표준.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/40</guid>
      <comments>https://0n1dev.tistory.com/40#entry40comment</comments>
      <pubDate>Fri, 21 Jan 2022 23:15:14 +0900</pubDate>
    </item>
    <item>
      <title>Reactive Programming #0. 옵저버 패턴</title>
      <link>https://0n1dev.tistory.com/39</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reactive&amp;nbsp;Programming&amp;nbsp;#0.&amp;nbsp;옵저버&amp;nbsp;패턴&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵저버 패턴이란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떠한것들을 감지하고, 상태의 변경을 지켜보는 디자인 패턴.&lt;/li&gt;
&lt;li&gt;여러개의 객체가 어떠한 특정 상태의 변경을 감지하고 반응을 해야하는 경우 적용.&lt;/li&gt;
&lt;li&gt;Pub/Sub 패턴을 쉽게 구현 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;채팅 서버/클라이언트 기반 예시 (옵저버 패턴 적용 전)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ChatServer 클래스&lt;/h4&gt;
&lt;pre id=&quot;code_1642344396402&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ChatServer(
    private val messages: MutableMap&amp;lt;String, MutableList&amp;lt;String&amp;gt;&amp;gt; = mutableMapOf()
) {

    fun add(subject: String, message: String) {
        messages[subject]?.let {
            it.add(message)
        }?: kotlin.run {
            val messageList = mutableListOf&amp;lt;String&amp;gt;()
            messageList.add(message)
            messages.put(subject, messageList)
        }
    }

    fun getMessage(subject: String): MutableList&amp;lt;String&amp;gt;? {
        return messages[subject]
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;User 클래스&lt;/h4&gt;
&lt;pre id=&quot;code_1642344438522&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(
    private val chatServer: ChatServer
) {

    fun sendMessage(subject: String, message: String) {
        chatServer.add(subject, message)
    }

    fun getMessage(subject: String): MutableList&amp;lt;String&amp;gt;? {
        return chatServer.getMessage(subject)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Client 메인 함수&lt;/h4&gt;
&lt;pre id=&quot;code_1642345816129&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val chatServer = ChatServer()

    val user1 = User(chatServer)
    user1.sendMessage(&quot;디자인패턴&quot;, &quot;이번엔 옵저버 패턴입니다.&quot;)
    user1.sendMessage(&quot;롤드컵2021&quot;, &quot;LCK 화이팅!&quot;)

    val user2 = User(chatServer)
    println(user2.getMessage(&quot;디자인패턴&quot;))

    user1.sendMessage(&quot;디자인패턴&quot;, &quot;예제 코드 보는 중..&quot;)
    println(user2.getMessage(&quot;디자인패턴&quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user1이 메세지를 보냈을때 user2는 계속해서 메세지를 가져와야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 경우에는 user2가 메세지를 받으려고 계속해서 요청을 보내는 형태. (Calling 매커니즘)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 메세지가 존재하면 좋겠지만 메세지가 존재하지 않을 경우도 있기 때문에 해당 애플리케이션에 맞지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이럴때 옵저버 패턴을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵저버 패턴 좀 더 자세하게!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 객체가 특정 객체 상태 변화를 감지하고 알림 받는 패턴.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blP7Ql/btrqNfd95iZ/HOyRr0simbSMyA4uvWxPKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blP7Ql/btrqNfd95iZ/HOyRr0simbSMyA4uvWxPKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blP7Ql/btrqNfd95iZ/HOyRr0simbSMyA4uvWxPKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblP7Ql%2FbtrqNfd95iZ%2FHOyRr0simbSMyA4uvWxPKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1348&quot; height=&quot;694&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Subject&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 옵저버들을 등록하거나 해지 할 수 있는 기능을 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subject의 상태가 변경되면 자신에게 등록 된 모든 옵저버를 순회하며 옵저버가 제공하는 모든 메서드를 호출.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;채팅 서버/클라이언트 기반 예시 (옵저버 패턴 적용 후)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ChatServer 클래스 (Subject 역할)&lt;/h4&gt;
&lt;pre id=&quot;code_1642347910438&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ChatServer(
    private val _subscribers: MutableMap&amp;lt;String, MutableList&amp;lt;Subscriber&amp;gt;&amp;gt; = mutableMapOf()
) {

    fun register(subject: String, subscriber: Subscriber) {
        _subscribers[subject]?.let {
            it.add(subscriber)
        }?: kotlin.run {
            val subscribers = mutableListOf&amp;lt;Subscriber&amp;gt;()

            subscribers.add(subscriber)
            _subscribers.put(subject, subscribers)
        }
    }

    fun unregister(subject: String, subscriber: Subscriber) {
        _subscribers[subject]?.let {
            it.remove(subscriber)
        }
    }

    fun sendMessage(user: User, subject: String, message: String) {
        _subscribers[subject]?.forEach {
            val receiver = it as? User

            it.handleMessage(&quot;${user.name} -&amp;gt; ${receiver?.name} : $message&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Subscriber 인터페이스 (Observer 인터페이스 역할)&lt;/h4&gt;
&lt;pre id=&quot;code_1642347960879&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Subscriber {

    fun handleMessage(message: String)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;User 클래스 (ConcreteObserver 역할)&lt;/h4&gt;
&lt;pre id=&quot;code_1642348013814&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(
    val name: String
): Subscriber {

    override fun handleMessage(message: String) {
        println(message)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Client 메인 함수&lt;/h4&gt;
&lt;pre id=&quot;code_1642348088974&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {

    val chatServer = ChatServer()

    val user1 = User(&quot;u1&quot;)
    val user2 = User(&quot;u2&quot;)

    chatServer.register(&quot;코틀린&quot;, user1)
    chatServer.register(&quot;코틀린&quot;, user2)

    chatServer.register(&quot;RX&quot;, user1)

    chatServer.sendMessage(user1, &quot;코틀린&quot;, &quot;코틀린 꿀잼?&quot;)
    chatServer.sendMessage(user1, &quot;RX&quot;, &quot;리액티브 스트림 어려워?&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵저버 패턴의 장/단점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subscriber)의 관계를 느슨하게 유지할 수 있음.&lt;/li&gt;
&lt;li&gt;Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있음.&lt;/li&gt;
&lt;li&gt;런타임에 옵저버를 추가하거나 제거할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡도가 증가.&lt;/li&gt;
&lt;li&gt;다수의 Observer 객체를 등록 이후 해지 하지 않으면 Memory Leak이 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자바에 적용된 옵저버 패턴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 9 이전
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observer 인터페이스 (Java 9 부터 사용 권장하지 않음.)&lt;/li&gt;
&lt;li&gt;Observable 클래스&amp;nbsp;(Java 9 부터 사용 권장하지 않음.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Java 9 이후
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java.beans
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PropertyChangeListener&lt;/li&gt;
&lt;li&gt;PropertyChangeSupport&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flow API (Reactive Stream을 구현할 수 있도록 제공되는 인터페이스)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subscription&lt;/li&gt;
&lt;li&gt;Subscriber&lt;/li&gt;
&lt;li&gt;Publisher&lt;/li&gt;
&lt;li&gt;해당 인터페이스 만으로는 동기적으로 작동함. 비동기적으로 사용하려면 SubmissionPublisher와 같이 사용해야 함.&lt;/li&gt;
&lt;li&gt;구독을 하는쪽에서 백프레셔를 제어 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>reactive programming</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/39</guid>
      <comments>https://0n1dev.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 17 Jan 2022 00:49:53 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin으로 개발하는 Spring Boot Web MVC #5</title>
      <link>https://0n1dev.tistory.com/38</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Web&amp;nbsp;MVC&amp;nbsp;#5&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;JDK 11&lt;/li&gt;
&lt;li&gt;Spring Boot 2.6.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;https://github.com/0n1dev/kotlin-spring-practice&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642328631180&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&quot; data-og-description=&quot;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbnfgT/hyM6AzcszX/oBcwHURkqIV3kVAS4GmXxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbnfgT/hyM6AzcszX/oBcwHURkqIV3kVAS4GmXxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JUnit&lt;/h3&gt;
&lt;pre id=&quot;code_1642328645291&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebMvcTest
@AutoConfigureMockMvc
internal class ExceptionApiControllerTest {

    @Autowired
    lateinit var mockMvc: MockMvc

    @Test
    fun indexOutOfBoundTest() {
        mockMvc.perform(
            get(&quot;/api/exception/index-out-of-bound&quot;)
        ).andExpect(
            status().isOk
        ).andDo(print())
    }

    @Test
    fun getTest() {
        val queryParams = LinkedMultiValueMap&amp;lt;String, String&amp;gt;()

        queryParams.add(&quot;name&quot;, &quot;0n1dev&quot;)
        queryParams.add(&quot;age&quot;, &quot;20&quot;)

        mockMvc.perform(
            get(&quot;/api/exception&quot;)
                .queryParams(queryParams)
        ).andExpect(
            status().isOk
        ).andExpect(
            content().string(&quot;0n1dev 20&quot;)
        ).andDo(print())
    }

    @Test
    fun getFailTest() {
        val queryParams = LinkedMultiValueMap&amp;lt;String, String&amp;gt;()

        queryParams.add(&quot;name&quot;, &quot;0n1dev&quot;)
        queryParams.add(&quot;age&quot;, &quot;1&quot;)

        mockMvc.perform(
            get(&quot;/api/exception&quot;)
                .queryParams(queryParams)
        ).andExpect(
            status().isBadRequest
        ).andExpect(
            content().contentType(&quot;application/json&quot;)
        ).andExpect(
            jsonPath(&quot;\$.result_code&quot;).value(&quot;Fail&quot;)
        ).andDo(print())
    }

    @Test
    fun postTest() {
        val userRequest = UserRequest().apply {
            this.name = &quot;0n1dev&quot;
            this.age = 20
            this.phoneNumber = &quot;010-3333-3333&quot;
            this.address = &quot;fawefawf&quot;
            this.email = &quot;test@naver.com&quot;
            this.createdAt = &quot;2022-01-16 18:50:00&quot;
        }

        val json = jacksonObjectMapper().writeValueAsString(userRequest)

        mockMvc.perform(
            post(&quot;/api/exception&quot;)
                .content(json)
                .contentType(&quot;application/json&quot;)
                .accept(&quot;application/json&quot;)
        ).andExpect(
            status().`is`(200)
        ).andDo(print())
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>Kotlin</category>
      <category>Spring</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/38</guid>
      <comments>https://0n1dev.tistory.com/38#entry38comment</comments>
      <pubDate>Sun, 16 Jan 2022 19:24:25 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin으로 개발하는 Spring Boot Web MVC #4</title>
      <link>https://0n1dev.tistory.com/37</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Web&amp;nbsp;MVC&amp;nbsp;#4&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;JDK 11&lt;/li&gt;
&lt;li&gt;Spring Boot 2.6.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;https://github.com/0n1dev/kotlin-spring-practice&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642323453565&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&quot; data-og-description=&quot;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbnfgT/hyM6AzcszX/oBcwHURkqIV3kVAS4GmXxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbnfgT/hyM6AzcszX/oBcwHURkqIV3kVAS4GmXxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ControllerAdvice&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Global 에외 처리 및 특정 Package / Controller 예외 처리&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ExceptionHandler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 Controller의 예외처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 실습코드는 깃허브에 있습니다.&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>Kotlin</category>
      <category>Spring</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/37</guid>
      <comments>https://0n1dev.tistory.com/37#entry37comment</comments>
      <pubDate>Sun, 16 Jan 2022 17:59:03 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin으로 개발하는 Spring Boot Web MVC #3</title>
      <link>https://0n1dev.tistory.com/36</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Web&amp;nbsp;MVC&amp;nbsp;#3&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;JDK 11&lt;/li&gt;
&lt;li&gt;Spring Boot 2.6.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;https://github.com/0n1dev/kotlin-spring-practice&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642315021678&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&quot; data-og-description=&quot;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbnfgT/hyM6AzcszX/oBcwHURkqIV3kVAS4GmXxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbnfgT/hyM6AzcszX/oBcwHURkqIV3kVAS4GmXxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 id=&quot;Spring_Boot_Validation&quot; data-ke-size=&quot;size23&quot;&gt;Spring Boot Validation&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Custom Validation Annotation 만들어서 사용하기&lt;/h4&gt;
&lt;pre id=&quot;code_1642314940400&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Constraint(validatedBy = [StringFormatDateTimeValidator::class])
@Target(
    AnnotationTarget.FIELD,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class StringFormatDateTime(
    val pattern: String = &quot;yyyy-MM-dd HH:mm:ss&quot;,
    val message: String = &quot;시간 형식이 유효하지 않습니다.&quot;,
    val groups: Array&amp;lt;KClass&amp;lt;*&amp;gt;&amp;gt; = [],
    val payload: Array&amp;lt;KClass&amp;lt;out Payload&amp;gt;&amp;gt; = []
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1642314951056&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class StringFormatDateTimeValidator: ConstraintValidator&amp;lt;StringFormatDateTime, String&amp;gt; {

    private var pattern: String? = null

    override fun initialize(constraintAnnotation: StringFormatDateTime?) {
        this.pattern = constraintAnnotation?.pattern
    }

    // 정상이면 true, 비정상이면 false
    override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
        return try {
            LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern))
            true
        } catch (e: Exception) {
            false
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>Kotlin</category>
      <category>Spring</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/36</guid>
      <comments>https://0n1dev.tistory.com/36#entry36comment</comments>
      <pubDate>Sun, 16 Jan 2022 15:43:46 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin으로 개발하는 Spring Boot Web MVC #2</title>
      <link>https://0n1dev.tistory.com/35</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Web&amp;nbsp;MVC&amp;nbsp;#2&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;JDK 11&lt;/li&gt;
&lt;li&gt;Spring Boot 2.6.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;https://github.com/0n1dev/kotlin-spring-practice&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642258576483&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&quot; data-og-description=&quot;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnNTts/hyM6AelTeo/eyUhnDaJDPhPsvqX95JEJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnNTts/hyM6AelTeo/eyUhnDaJDPhPsvqX95JEJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Boot Validation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://beanvalidation.org/2.0-jsr380/spec/#builtinconstraints&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://beanvalidation.org/2.0-jsr380/spec/#builtinconstraints&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642259103339&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Bean Validation specification&quot; data-og-description=&quot;BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and&quot; data-og-host=&quot;beanvalidation.org&quot; data-og-source-url=&quot;https://beanvalidation.org/2.0-jsr380/spec/#builtinconstraints&quot; data-og-url=&quot;https://beanvalidation.org/2.0-jsr380/spec/#builtinconstraints&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://beanvalidation.org/2.0-jsr380/spec/#builtinconstraints&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://beanvalidation.org/2.0-jsr380/spec/#builtinconstraints&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Bean Validation specification&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;beanvalidation.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 DeleteApiController 수정&lt;/h4&gt;
&lt;pre id=&quot;code_1642259130657&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api&quot;)
@Validated
class DeleteApiController {

    // 1. path variable
    // 2. request param

    @DeleteMapping(path = [&quot;/delete-mapping/name/{name}/age/{age}&quot;])
    fun deleteMappingPath(
        @PathVariable
        @NotNull
        @Size(min = 1, max = 10)
        name: String,

        @NotNull(message = &quot;age 값이 누락되었습니다.&quot;)
        @Min(value = 20, message = &quot;age는 20보다 커야 합니다.&quot;)
        @PathVariable
        age: Int
    ): String {
        return &quot;$name $age&quot;
    }

    @DeleteMapping(path = [&quot;/delete-mapping&quot;])
    fun deleteMapping(
        @RequestParam name: String,
        @RequestParam age: Int
    ): String {
        return &quot;$name $age&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 Data Class 수정&lt;/h4&gt;
&lt;pre id=&quot;code_1642259445985&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
// @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) PropertyNamingStrategy Class Deprecated
data class UserRequest(
    // @Size(min = 2, max = 8) // 자바에서 사용하는 방식
    @field:NotEmpty
    @field:Size(min = 2, max = 8) // 코틀린에서 Data Class 필드에 어노테이션을 붙일 때
    var name: String?=null,

    @field:PositiveOrZero // 0 이상의 숫자를 검증
    var age: Int?=null,

    @field:Email // email 양식
    var email: String?=null,

    @field:NotBlank // 공백을 검증
    var address: String?=null,

    @field:Pattern(regexp = &quot;&quot;&quot;^\d{2,3}-\d{3,4}-\d{4}$&quot;&quot;&quot;) // 정규식 검증
    // @JsonProperty(&quot;phone_number&quot;) Json에서는 스네이크케이스를 주로 사용해서 서로 매핑되도록 지정.
    var phoneNumber: String?=null // 코틀린에서는 기본적으로 카멜케이스 사용.
)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BindingResult 활용&lt;/h4&gt;
&lt;pre id=&quot;code_1642259938113&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api&quot;)
class PutApiController {

    @PutMapping(&quot;/put-mapping&quot;)
    fun putMapping(): String {
        return &quot;put-mapping&quot;
    }

    @RequestMapping(method = [RequestMethod.PUT], path = [&quot;/request-mapping&quot;])
    fun requestMapping(): String {
        return &quot;put-mapping&quot;
    }

    @PutMapping(path = [&quot;/put-mapping/object&quot;])
    fun putMappingObject(@Valid @RequestBody userRequest: UserRequest,
                         bindingResult: BindingResult): ResponseEntity&amp;lt;String&amp;gt; {

        if (bindingResult.hasErrors()) {

            val msg = StringBuilder()
            bindingResult.allErrors.forEach {
                val field = it as FieldError
                val message = it.defaultMessage

                msg.append(&quot;${field.field} $message\n&quot;)
            }

            return ResponseEntity.badRequest().body(msg.toString())
        }

//        // 0. Response
//        return UserResponse().apply {
//
//            // 1. result
//            Result().apply {
//                this.resultCode = &quot;OK&quot;
//                this.resultMessage = &quot;성공&quot;
//            }
//
//
//        }.apply {
//
//            // 2. description
//            this.description = &quot;@@@@@@&quot;
//        }.apply {
//
//            // 3. user mutable list
//            val userList = mutableListOf&amp;lt;UserRequest&amp;gt;()
//
//            userList.add(userRequest)
//            userList.add(UserRequest().apply {
//                this.name = &quot;a&quot;
//                this.age = 11
//                this.email = &quot;test@naver.com&quot;
//                this.address = &quot;dd&quot;
//                this.phoneNumber = &quot;01018838303&quot;
//            })
//
//            this.userRequest = userList
//        }
        return ResponseEntity.ok(&quot;&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>Kotlin</category>
      <category>Spring</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/35</guid>
      <comments>https://0n1dev.tistory.com/35#entry35comment</comments>
      <pubDate>Sun, 16 Jan 2022 00:24:40 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin으로 개발하는 Spring Boot Web MVC #1</title>
      <link>https://0n1dev.tistory.com/34</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Web&amp;nbsp;MVC&amp;nbsp;#1&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 환경&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;JDK 11&lt;/li&gt;
&lt;li&gt;Spring Boot 2.6.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/0n1dev/kotlin-spring-practice&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1642248742866&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&quot; data-og-description=&quot;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnNTts/hyM6AelTeo/eyUhnDaJDPhPsvqX95JEJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/0n1dev/kotlin-spring-practice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnNTts/hyM6AelTeo/eyUhnDaJDPhPsvqX95JEJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 0n1dev/kotlin-spring-practice: 인프런 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인프런 강의. Contribute to 0n1dev/kotlin-spring-practice development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GET&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 GET Mapping&lt;/li&gt;
&lt;li&gt;PathVariable&lt;/li&gt;
&lt;li&gt;QueryParam
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체로 받는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;POST&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 POST Mapping&lt;/li&gt;
&lt;li&gt;RequestBody&lt;/li&gt;
&lt;li&gt;JsonNaming 컨벤션&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PUT&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 PUT Mapping&lt;/li&gt;
&lt;li&gt;RequestBody&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DELETE&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PathVariable&lt;/li&gt;
&lt;li&gt;QueryParam&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ResponseEntity&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상황별 HttpStatus 코드 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 모든 코드는 실습 코드 깃헙에 업로드 되어있습니다.&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>Kotlin</category>
      <category>Spring</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/34</guid>
      <comments>https://0n1dev.tistory.com/34#entry34comment</comments>
      <pubDate>Sat, 15 Jan 2022 22:25:56 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin으로 개발하는 Spring Boot Web MVC #0</title>
      <link>https://0n1dev.tistory.com/32</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin으로&amp;nbsp;개발하는&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;Web&amp;nbsp;MVC&amp;nbsp;#0&lt;/h2&gt;
&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Why Kotlin?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM 언어 / Java와 100% 호환.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린 &amp;lt;-&amp;gt; 자바 서로 임포트해서 사용할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;현대 언어 지향&lt;/li&gt;
&lt;li&gt;코틀린의 Elvis Operation을 통해 안정적으로 NULL Point 대응 가능.&lt;/li&gt;
&lt;li&gt;Java에 비해 간결한 코드.&lt;/li&gt;
&lt;li&gt;코틀린만의 표준 함수.&lt;/li&gt;
&lt;li&gt;타입추론의 편리함 (Java10에서는 지원)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Boot&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 실행되며, 프로덕션 레벨의 애플리케이션을 쉽게 만들 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework의 구성 설정이 거의 필요 하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java -jar로 실행하는 Java 애플리케이션을 만들 수 있음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Web 개론&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Web이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;World Wide Web, WW, W3이라 불리는데 인터넷에 연결된 컴퓨터를 통해 사람들이 정보를 공유할 수 있는 전 세계적인 정보 공간을 말함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Web의 용도를 우리가 제일 많이 접하는 부분으로 정리.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Web Site (Google, Naver, Daum ...)&lt;/li&gt;
&lt;li&gt;User Interface (Chrome, Safari, Explorer ...)&lt;/li&gt;
&lt;li&gt;API (Application Programming Interface)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Web의 기반&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP (Hypertext Transfer Protocol)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 컨트롤&lt;/li&gt;
&lt;li&gt;Get, Post, Put ... 등의 메서드 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;URI (Uniform Resource Identifier)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 식별자&lt;/li&gt;
&lt;li&gt;특정 사이트, 특정 쇼핑 목록 ...&lt;/li&gt;
&lt;li&gt;모든 정보에 접근 할 수 있는 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTML (Hyper Text Markup Language)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하이퍼미디어 포맷&lt;/li&gt;
&lt;li&gt;XML을 바탕으로한 범용 문서 포맷&lt;/li&gt;
&lt;li&gt;웹 브라우저에서 사용자가 알아보기 쉬운 형태로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;REST&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST (Representational State Transfer, 자원의 상태 전달)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 아키텍처 원리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client, Server: 클라이언트와 서버가 서로 독립적으로 분리되어져 있어야 함.&lt;/li&gt;
&lt;li&gt;Stateless: 요청에 대해서 클라이언트의 상태가 서버에 저장하지 않음.&lt;/li&gt;
&lt;li&gt;캐시: 클라이언트는 서버의 응담을 캐시 할 수 있어야 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시를 통해 응답을 재사용할 수 있어야 하며, 이를 통해 서버의 부하를 낮춤.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;계층화: 서버와 클라이언트 사이에, 방화벽, 게이트웨이, Proxy 등 다계층 형태를 구성할 수 있어야 하며, 확장 할 수 있어야 함.&lt;/li&gt;
&lt;li&gt;인터페이스 일관성: 아키텍처를 단순화시키고 작은 단위로 분리하여서, 클라이언트와 서버가 독립적으로 개선될 수 있어야 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자원 식별: 웹 기반의 REST에서는 리소스 접근을 URI를 사용.&lt;/li&gt;
&lt;li&gt;메시지를 통한 리소스 조작: 리소스 타입을 알려주기 위해 header 부분에 content-type을 통해서 어떠한 타입인지 명시 가능.&lt;/li&gt;
&lt;li&gt;자기서술적 메시지: 요청하는 데이터가 어떻게 처리 되어야 하는지 충분한 데이터를 포함 할 수 있어야 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 기반의 REST에서는 HTTP Method와 Header의 정보로 이를 표현.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션 상태에 대한 엔진으로서 하이펑미디어: HATOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Code On Demand (Optional): 자바 애플릿, 자바스크립트, 플래시 등 특정기능을 서버가 클라이언트에 코드를 전달하여 실행 할 수 있어야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;URI&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URI (Uniform Resource Identifier)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷에서 특정 자원을 나타내는 주소값. 해당 값은 유일.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;URL (Uniform Resource Locator)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 상에서의 자원, 특정 파일이 어디에 위치하는지 식별 하는 주소&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; URL은 URI의 하위 개념.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;URI 설계원칙 (RFC-3986)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슬래시 구분자(/)는 계층 관계를 나타내는 데 사용.&lt;/li&gt;
&lt;li&gt;URI 마지막 문자로(/)는 포함하지 않음.&lt;/li&gt;
&lt;li&gt;하이픈(-)은 URI의 가독성을 높이는데 사용.&lt;/li&gt;
&lt;li&gt;밑줄(_)은 사용하지 않음.&lt;/li&gt;
&lt;li&gt;URI 경로에는 소문자가 적합.&lt;/li&gt;
&lt;li&gt;파일 확장자는 URI에 포함하지 않음.&lt;/li&gt;
&lt;li&gt;프로그래밍 언어에 의존적인 확장자를 사용하지 않음.&lt;/li&gt;
&lt;li&gt;구현에 의존적인 경로를 사용하지 않음.&lt;/li&gt;
&lt;li&gt;세션 ID를 포함하지 않음.&lt;/li&gt;
&lt;li&gt;프로그래밍 언어의 메서드명을 이용하지 않음.&lt;/li&gt;
&lt;li&gt;명사에 단수형 보다는 복수형을 사용해야 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬렉션에 대한 표현은 복수로 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨트롤러 이름으로는 동사나 동사구를 사용.&lt;/li&gt;
&lt;li&gt;경로 부분 중 변하는 부분은 유일한 값으로 대체.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;https://kotlin.example.co.kr/vehicles/suv/q7/{car-id}/users/{user_id}/release&lt;/li&gt;
&lt;li&gt;https://kotlin.example.co.kr/vehicles/suv/q7/117/users/0n1dev/release&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CRUD 기능을 나타내는것은 URI에 사용하지 않음.&lt;/li&gt;
&lt;li&gt;URI Query Parameter 디자인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URI 쿼리 부분으로 컬렉션 결과에 대해서 필터링 할 수 있음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;https://kotlin.example.co.kr/vehicles/suv?model=q7&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;URI 쿼리는 컬렉션의 결과를 페이지로 구분하여 나타내는데 사용.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;https://kotlin.example.co.kr/vehicles/suv?page=0&amp;amp;size=10&amp;amp;sort=asc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API에 있어서 서브 도메인은 일관성 있게 사용해야 함.&lt;/li&gt;
&lt;li&gt;클라이언트 개발자 포탈 서브 도메인은 일관성 있게 만듬.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HTTP (추후 깊게 다시 공부)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hyper Text Transfer Protocol 로 RFC-2616에서 규정된 Web에서 데이터를 주고 받는 프로토콜.&lt;/li&gt;
&lt;li&gt;이름에는 하이퍼 텍스트 전송용 프로토콜로 정의되어 있지만 실제로는 HTML, XML, JSON, Image 등 다양한 형태의 데이터를 전송 할 수 있음.&lt;/li&gt;
&lt;li&gt;HTTP는 TCP를 기반으로한 REST의 특징을 모두 구현하고 있는 Web 기반의 프로토콜 (HTTP 3.0 부터는 UDP... 추후 공부)&lt;/li&gt;
&lt;li&gt;HTTP는 메세지를 주고 (Request) 받는 (Response) 의 형태의 통신 방식.&lt;/li&gt;
&lt;li&gt;HTTP Method
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 취득&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;POST
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 생성, 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PUT
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 갱신, 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PATCH
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DELETE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HEAD
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;헤더 데이터 취득&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OPTIONS
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지원하는 메서드 취득&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TRACE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 메세지 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CONNECT
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 동작의 터널 접속으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP Status Code
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1XX
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처리중&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2XX
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3XX
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리다이렉트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4XX
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5XX
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>Kotlin</category>
      <category>Spring</category>
      <category>springboot</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/32</guid>
      <comments>https://0n1dev.tistory.com/32#entry32comment</comments>
      <pubDate>Sat, 15 Jan 2022 19:43:33 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin In Action #5</title>
      <link>https://0n1dev.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin In Action #5&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스,&amp;nbsp;객체,&amp;nbsp;인터페이스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;object 키워드: 클래스 선언과 인스턴스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서 object 키워드를 다양한 상황에서 사용하지만 모든 경우 클래스를 정의하면서 동시에 인스턴스(객체)를 생성한다는 공통점이 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 선언은 싱글턴을 정의하는 방법 중 하나.&lt;/li&gt;
&lt;li&gt;동반 객체는 인스턴스 메서드는 아니지만 어떤 클래스와 관련 있는 메서드와 팩터리 메서드를 담을 때 사용.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동반 객체 메서드에 접근할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체 식은 자바의 무명 내부 클래스 대신 쓰임.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 선언: 싱글턴 쉽게 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 클래스의 생성자를 private로 제한하고, 정적 필드에 그 클래스의 유일한 객체를 저장하는 형태로 구현.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 객체 선언 기능을 통해 싱글턴을 언어에서 기본 지원.&lt;/p&gt;
&lt;pre id=&quot;code_1641814313324&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object Payroll {
    
    val allEmployees = arrayListOf&amp;lt;Person&amp;gt;()
    
    fun calculateSalary() {
        for (person in allEmployees) {
            ...
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 한 문장으로 처리.&lt;/li&gt;
&lt;li&gt;클래스와 마찬가지로 객체 선언 안에도 프로퍼티, 메서드, 초기화 블록 등이 들어갈 수 있음.&lt;/li&gt;
&lt;li&gt;생성자는 객체 선언에 쓸 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641814596619&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.File

object CaseInsensitiveFileComparator: Comparator&amp;lt;File&amp;gt; {

    override fun compare(o1: File?, o2: File?): Int {
        return o1!!.path.compareTo(o2!!.path, ignoreCase = false)
    }
}


println(CaseInsensitiveFileComparator.compare(File(&quot;/User&quot;), File(&quot;/user&quot;)))&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동반 객체: 팩토리 메서드와 정적 멤버가 들어갈 장소&lt;/h3&gt;
&lt;pre id=&quot;code_1641816333003&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class A {
    companion object {
        fun bar() {
            println(&quot;Companion Object called&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린 클래스 안에는 정적인 멤버가 없음.&lt;/li&gt;
&lt;li&gt;대신 코틀린에서는 패키지 수준의 최상위 함수와 객체 선언을 활용.&lt;/li&gt;
&lt;li&gt;클래스 안에 정의된 객체 중 하나에 companion 이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641816313320&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User private constructor(val nickname: String) {
    companion object {
        fun newSubscribingUser(email: String) = User(email.substringBefore('@'))
        
        fun newFaceBookUser(accountId: Int) = User(getFacebookName(accountId))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 확장해야 하는 경우 동반 객체 멤버를 하위 클래스에서 오버라이드할 수 없으므로 여러 생성자를 사용하는 편이 더 나은 방법.&lt;/p&gt;</description>
      <category>Programming/Kotlin</category>
      <category>KotlinInAction</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/31</guid>
      <comments>https://0n1dev.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 10 Jan 2022 21:10:16 +0900</pubDate>
    </item>
    <item>
      <title>[당근마켓 밋업 정리] - Kotlin Coroutines 톺아보기</title>
      <link>https://0n1dev.tistory.com/30</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;[당근마켓&amp;nbsp;밋업&amp;nbsp;정리]&amp;nbsp;-&amp;nbsp;Kotlin&amp;nbsp;Coroutines&amp;nbsp;톺아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 자료에 대한 내용은 당근마켓 유튜브를 참고하여 정리한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료 : &lt;a href=&quot;https://youtu.be/eJF60hcz3EU&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://youtu.be/eJF60hcz3EU&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=eJF60hcz3EU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bs85vT/hyM1B6CfCa/dFEmCm2Dw2AHieaWXIngr1/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/eJF60hcz3EU&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 개념&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Synchronous (동기)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청과 결과가 동시에 일어난다는 약속.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청을 하면 System Call이 끝날때까지 기다렸다 결과물을 가져옴.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;설계가 매우 간단하고 직관적&lt;/span&gt;이지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;결과가 주어질 때까지 어무것도 못하고 대기&lt;/span&gt;해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Asynchronous (비동기)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청과 결과가 동시에 일어나지 않음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청한 그 자리에서 결과가 주어지지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청을 하면 System Call이 완료되지 않아도 나중에 완료가 되면 그때 결과물을 가져옴.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주로 Callback 함수를 통해 결과물을 가져옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;동기에 비해 코드가 복잡하고 추적과 에러 핸들링이 어렵&lt;/span&gt;지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;결과가 주어지는데 시간이 걸리더라도 다른 작업을 할 수 있어 자원을 효율적으로 사용&lt;/span&gt;할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비동기에 대한 고민&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다양한 비동기 라이브러리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링에서는 Spring Reactor&lt;/li&gt;
&lt;li&gt;일부에서는 Java8의 CompletableFuture&lt;/li&gt;
&lt;li&gt;Hibernate Reactive의 MUTINY&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤걸 사용해야 하고 어떻게 혼용해서 써야 할까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Coroutine이 해결사?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우수한 가독성&lt;/li&gt;
&lt;li&gt;에러 핸들링&lt;/li&gt;
&lt;li&gt;동시성 처리&lt;/li&gt;
&lt;li&gt;Flow&lt;/li&gt;
&lt;li&gt;Channel&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Coroutine 소개&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주문 생성 동기 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1641712648218&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun execute(inputValues: InputValues): Order {
    val (userId, productIds) = inputValues

    // 1. 구매자 조회
    val buyer = userRepository.findUserByIdSync(userId)

    // 2. 주소 조회 및 유효성 체크
    val address = addressRepository.findAddressByUserSync(buyer).last()
    checkValidRegion(address)
    
    // 3. 상품들 조회
    val products = productRepository.findAllProductsByIdsSync(productIds)
    check(products.isNotEmpty())
    
    // 4. 스토어 조회
    val stores = storeRepository.findStoresByProductsSync(products)
    check(stores.isNotEmpty())
    
    // 5. 주문 생성
    val order = orderRepository.createOrderSync(buyer, products, stores, address)
    
    return order
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;비동기 코드로&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RxJava3의 Maybe&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Maybe는 Observable의 또 다른 형태.&lt;/li&gt;
&lt;li&gt;최대 데이터 하나를 발행할 수 있으며 추가로 데이터 없이도 완료할 수도 있음.&lt;/li&gt;
&lt;li&gt;0 .. 1, Error&lt;/li&gt;
&lt;li&gt;총 3가지 알림을 보냄.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;onSuccess: 데이터 하나를 발행함과 동시에 종료&lt;/li&gt;
&lt;li&gt;onError: 에러가 발생했음을 알림&lt;/li&gt;
&lt;li&gt;onComplete: 데이터 발행이 완료됐음을 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641713290664&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface UserAsyncRepository {
    fun findUserByIdMaybe(userId: String): Maybe&amp;lt;User&amp;gt;
}

class UserRxRepository: UserAsyncRepository {
    
    override fun findUserByIdMaybe(userId: String): Maybe&amp;lt;User&amp;gt; {
        val user = prepareUser(userId)
        return Maybe.just(user)
            .delay(TIME_DELAY_MS, TimeUnit.MILLISECONDS)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK9 ReactiveStream (구현 생략)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Non-Blocking과 BackPressure을 이용하여 비동기 서비스를 할 때 기본이되는 스펙.&lt;/li&gt;
&lt;li&gt;정해진 개수 만큼 Publish 하고나면 complete 이벤트로 Flow 종료.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641713559631&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface AddressAsyncRepository {
    fun findAddressByUserAsPublisher(user: User): Flow.Publisher&amp;lt;Address&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactor의 Flux (구현 생략)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 .. n, Error&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641713696223&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ProductAsyncRepository {
    fun findAllProductsByIdsAsFlux(productIds: List&amp;lt;String&amp;gt;): Flux&amp;lt;Product&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mutiny의 Multi (구현 생략)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 .. n, Error&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641713852230&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface StoreAsyncRepository {
    fun findStoresByProductsAsMulti(products: List&amp;lt;Product&amp;gt;): Multi&amp;lt;Store&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK8 CompletableFuture&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;complete 되는 시점에 결과 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641713966766&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface OrderAsyncRepository {
    fun createOrderAsFuture(
        buyer: User,
        products: List&amp;lt;Product&amp;gt;,
        stores: List&amp;lt;Store&amp;gt;,
        address: Address0
    ): CompletableFuture&amp;lt;Order&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위의 비동기 라이브러리를 혼합해서 사용하면 어떤일이 벌어질까?&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;callback-hell.gif&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6gYwh/btrp7R5Erxh/Re2tax3tyFsHlu0ntXcZG1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6gYwh/btrp7R5Erxh/Re2tax3tyFsHlu0ntXcZG1/img.gif&quot; data-alt=&quot;Callback 지옥... 이런 느낌이 아닐까...?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6gYwh/btrp7R5Erxh/Re2tax3tyFsHlu0ntXcZG1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b6gYwh/btrp7R5Erxh/Re2tax3tyFsHlu0ntXcZG1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1042&quot; height=&quot;362&quot; data-filename=&quot;callback-hell.gif&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Callback 지옥... 이런 느낌이 아닐까...?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscribe 지옥...&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;subscribe는 결과를 얻은 시점에 주어진 subscriber (consumer)를 실행하는 일종의 callback.&lt;/li&gt;
&lt;li&gt;반환값들이 아래에서 계속 필요하여 subscribe가 중첩.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Coroutine&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴화 시킬 수 있는 코드는 suspend라는 키워드가 붙은 함수들에 한 해서 가능.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Maybe&amp;lt;T&amp;gt;.awaitSingle&lt;/li&gt;
&lt;li&gt;Publisher&amp;lt;T&amp;gt;.awaitLast&lt;/li&gt;
&lt;li&gt;Flow&amp;lt;T&amp;gt;.toList&lt;/li&gt;
&lt;li&gt;CompletableFuture&amp;lt;T&amp;gt;.await&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 함수들은 모두 확장 함수에 포함됨.&lt;/p&gt;
&lt;pre id=&quot;code_1641714671332&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun execute(inputValues: InputValues): Order {
    val (userId, productIds) = inputValues

    // 1. 구매자 조회
    val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()

    // 2. 주소 조회 및 유효성 체크
    val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast()
    checkValidRegion(address)

    // 3. 상품들 조회
    val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow.toList()
    check(products.isNotEmpty())

    // 4. 스토어 조회
    val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow.toList()
    check(stores.isNotEmpty())

    // 5. 주문 생성
    val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await()

    return order
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Kotlin</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/30</guid>
      <comments>https://0n1dev.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 9 Jan 2022 18:46:30 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin In Action #4</title>
      <link>https://0n1dev.tistory.com/29</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin In Action #4&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스,&amp;nbsp;객체,&amp;nbsp;인터페이스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 생성자를 하나 이상 선언할 수 있고, 코틀린도 비슷하지만 주 생성자와 부 생성자를 구분함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 초기화 블록을 통해 초기화 로직을 추가할 수 있음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클래스 초기화: 주 생성자와 초기화 블록&lt;/h4&gt;
&lt;pre id=&quot;code_1641310020395&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val nickname: String)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스에서 중괄호가 아닌 괄호로 둘러싸인 코드를 주 생성자라 함.&lt;/li&gt;
&lt;li&gt;주 생성자는 생성자 파라메터를 지정하고, 생성자 파라메터에 의해 초기화되는 프로퍼티를 정의하는 두 가지 목적에 사용 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 명시적인 선언으로 풀어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1641310243856&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User constructor(_nickname: String) {

    val nickname: String
    
    init {
        nickname = _nickname
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;constructor 키워드는 주 생성자나 부 생성자 정의를 시작할 때 사용.&lt;/li&gt;
&lt;li&gt;init 키워드는 초기화 블록을 시작.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기화 블록에는 클래스의 객체가 만들어질 때 실행될 초기화 코드가 들어감.&lt;/li&gt;
&lt;li&gt;주로 초기화 블록은 주 생성자와 함께 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성자 파라메터 _nickname에서 밑줄(_)은 프로퍼티와 생성자 파라메터를 구분할때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641360257171&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val nickname: String, 
            val isSubscribed: Boolean = true) // 디폴트 값 제공
            
fun main() {
    val user = User(&quot;0n1dev&quot;)
    println(user.isSubscribed)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기반 클래스가 있다면 주 생성자에서 기반 클래스의 생성자를 호출해야 할 필요가 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1641360628703&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class User(val nickname: String,
                   val isSubscribed: Boolean = true)

class TwitterUser(nickname: String): User(nickname) {}


fun main() {
    val user = TwitterUser(&quot;0n1dev&quot;)
    println(user.isSubscribed)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유일한 주 생성자의 변경자로 private를 붙이면, 외부에서 인스턴스화할 수 없음.&lt;/p&gt;
&lt;pre id=&quot;code_1641360767870&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Secretive private constructor()&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부 생성자: 상위 클래스를 다른 방식으로 초기화&lt;/h4&gt;
&lt;pre id=&quot;code_1641360943969&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class View {
    constructor(ctx: Context) {
        
    }
    
    constructor(ctx: Context, attr: AttributeSet) {
        
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;super나 this를 사용하여 다른 생성자에게 위임 할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1641361368728&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class View {
    constructor(ctx: Context) {

    }

    constructor(ctx: Context, attr: AttributeSet) {

    }
}

class MyButton: View {
    constructor(ctx: Context): this(ctx, AttributeSet.NameAttribute as AttributeSet) {

    }

    constructor(ctx: Context, attr: AttributeSet): super(ctx, attr) {

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인터페이스에 선언된 프로퍼티 구현&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에 추상 프로퍼티 선언을 넣을 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1641464950951&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
    val nickname: String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 User 인터페이스를 구현하는 클래스는 nickname의 값을 얻을 수 있는 방법을 제공해야 한다는 뜻.&lt;/p&gt;
&lt;pre id=&quot;code_1641465245520&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PrivateUser(override val nickname: String) : User

class SubscribingUser(val email: String) : User{
    override val nickname: String
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: Int) : User{
    override val nickname: String
        get() = getFacebookName(accountId)

    private fun getFacebookName(accountId: Int): String {
        TODO(&quot;Not yet implemented&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에는 추상 프로퍼티뿐 아니라 게터와 세터가 있는 프로퍼티를 선언할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1641465437255&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
    val email: String
    val nickname: String
        get() = email.substringBefore('@')
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 프로퍼티 email은 반드시 오버라이드해야 하며, nickname은 오버라이드하지 않고 상속 가능.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;게터와 세터에서 뒷받침하는 필드에 접근&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세터에서 뒷받침하는 필드 접근하기&lt;/p&gt;
&lt;pre id=&quot;code_1641465721991&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val name: String) {
    var address: String = &quot;unspecified&quot;
        set(value: String) {
            println(&quot;&quot;&quot;
               Address was changed for $name:
               &quot;$field&quot; -&amp;gt; &quot;$value&quot;
            &quot;&quot;&quot;.trimIndent()) // 뒷받침하는 필드 값 읽기
            field = value // 뒷받침하는 필드 값 변경
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;접근자의 가시성 변경&lt;/h4&gt;
&lt;pre id=&quot;code_1641466022454&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LengthCounter {
    var counter: Int = 0
        private set
    fun addWord(word: String) {
        counter += word.length
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일러가 생성한 메서드: 데이터 클래스와 클래스 위임&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 필수 메서드(equals, hashCode, toString 등)로 인한 잡음 없이 코드를 깔끔하게 유지할 수 있음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모든 클래스가 정의해야 하는 메서드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client 클래스의 초기 정의&lt;/p&gt;
&lt;pre id=&quot;code_1641469481828&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Client(val name: String, val postalCode: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문자열 표현: toString()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1641469663092&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Client(val name: String, val postalCode: Int) {
    override fun toString(): String = &quot;Client(name='$name', postalCode=$postalCode)&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체의 동등성: equals()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 간주.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;자바에서 ==는 원시 타입과 참조 타입을 비교할 때 사용.&lt;br /&gt;원시 타입의 경우 두 피연산자의 값이 같은지 비교. (동등성)&lt;br /&gt;참조 타입의 경우 두 피연산자의 주소가 같은지 비교. (동일성)&lt;br /&gt;자바에서 동등성을 알려면 equals를 호출해야 함.&lt;br /&gt;&lt;br /&gt;코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법.&lt;br /&gt;==는 내부적으로 equals를 호출해서 객체를 비교.&lt;br /&gt;참조 비교를 위해서는 === 연산자를 사용.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1641470236572&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client) {
            return false
        }
        
        return name == other.name &amp;amp;&amp;amp;
                postalCode == other.postalCode
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 복잡한 작업을 수행해보면 제대로 작동하지 않는 경우가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashCode 정의를 빠뜨려서 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해시 컨테이너: hashCode()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드해야 함.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;JVM 언어에서는 equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 함.&lt;br /&gt;원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시 코드를 비교하기 때문.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1641470703244&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client) {
            return false
        }

        return name == other.name &amp;amp;&amp;amp;
                postalCode == other.postalCode
    }

    override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 클래스: 모든 클래스가 정의해야 하는 메서드 자동 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 클래스가 데이터를 저장하는 역할만을 수행한다면 toString, equals, hashCode를 반드시 오버라이드해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 data라는 변경자를 클래스 앞에 붙이면 필요한 메서드를 컴파일러가 자동으로 만들어줌.&lt;/p&gt;
&lt;pre id=&quot;code_1641470958211&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Client(val name: String, val postalCode: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 클래스는 아래 요구사항의 메서드를 포함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인스턴스 간 비교를 위한 equals&lt;/li&gt;
&lt;li&gt;HashMap과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode&lt;/li&gt;
&lt;li&gt;클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 세개 말고 몇 가지 유용한 메서드를 추가로 생성해줌.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 클래스와 불변성: copy() 메서드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 클래스의 프로퍼티가 꼭 val일 필요는 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 copy 메서드를 제공하면서 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만들며 일부 프로퍼티를 바꿀수 있게 해줌.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클래스 위임: by 키워드 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속을 허용하지 않는 클래스에 새로운 동작을 추가해야 할 때가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 사용하는 일반적인 방법이 데코레이터 패턴.&lt;/p&gt;
&lt;pre id=&quot;code_1641813103014&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DelegatingCollection&amp;lt;T&amp;gt;: Collection&amp;lt;T&amp;gt; {

    private val innerList = arrayListOf&amp;lt;T&amp;gt;();
    
    override val size: Int
        get() = innerList.size

    override fun contains(element: T): Boolean = innerList.contains(element)

    override fun containsAll(elements: Collection&amp;lt;T&amp;gt;): Boolean = containsAll(elements)

    override fun isEmpty(): Boolean = isEmpty()

    override fun iterator(): Iterator&amp;lt;T&amp;gt; = iterator()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데코레이터 패턴을 구현하는데 있어서 위와 같이 단순한 인터페이스를 구현하는것 조차 복잡한 코드를 작성해야 함.&lt;/li&gt;
&lt;li&gt;이러한 위임을 언어가 제공하는 일급 시민 기능으로 지원하는것이 코틀린의 장점.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641813298322&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DelegatingCollection&amp;lt;T&amp;gt;(
    innerList: Collection&amp;lt;T&amp;gt; = ArrayList&amp;lt;T&amp;gt;()
): Collection&amp;lt;T&amp;gt; by innerList&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1641813534964&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CountingSet&amp;lt;T&amp;gt;(
    val innerSet: MutableCollection&amp;lt;T&amp;gt; = HashSet&amp;lt;T&amp;gt;()
): MutableCollection&amp;lt;T&amp;gt; by innerSet {
    
    var objectsAdded = 0

    override fun add(element: T): Boolean {
        objectsAdded++
        return innerSet.add(element)
    }

    override fun addAll(elements: Collection&amp;lt;T&amp;gt;): Boolean {
        objectsAdded += elements.size
        return innerSet.addAll(elements)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Kotlin</category>
      <category>KotlinInAction</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/29</guid>
      <comments>https://0n1dev.tistory.com/29#entry29comment</comments>
      <pubDate>Wed, 5 Jan 2022 00:33:55 +0900</pubDate>
    </item>
    <item>
      <title>2021년 마무리 회고</title>
      <link>https://0n1dev.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;2021년 마무리 회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021년에는 크게 3가지의 인생 전환점이 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공채 동기들의 이직.&lt;/li&gt;
&lt;li&gt;F-Lab 유료 멘토링 수강.&lt;/li&gt;
&lt;li&gt;이직 준비 ... 그리고 성공.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 와서 생각해보면 이러한 전환점들이 있었기 때문에 나를 발전시키고 있는 게 아닐까라는 생각이 든다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공채 동기들의 이직&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 2020년 첫 회사를 신입 공채로 입사하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사일에 보면 신입으로 같은 팀으로 들어간 공채 동기가 나 포함 4명이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늘 항상 혼자 개발을 하다가 (심지어 개발자 친구도 없었음...) 개발에 관심이 많은 동기들과 함께 개발에 대한 얘기를 하다 보면 나는 많은 반성을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공채 동기들과 대화를 하며 개발에 대한 방향성을 잡으며 1년이란 시간을 보냈더니 동기들 2명이 좋은 곳으로 이직하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 대한 욕심이 많은 동기들의 이직 소식은 나 또한 이직을 하고 싶다는 욕심을 만들어주었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;F-Lab 유료 멘토링 수강&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 사용하게 될 언어가 Java로 바뀌면서 빠르고 깊게 공부하기 위한 방법을 찾아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사를 다니면서도 선배 개발자의 조언을 얻을만한 기회가 크게 많지가 않아 고민하던 도중 흔히 말하는 네카라쿠배에서도 큰 성과를 만들어낸 선배 개발자분들의 멘토링 F-Lab에 대해서 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 공부를 시작하기로 마음먹은 거 비싼 금액이지만 투자를 해보는 것도 좋을 것이라 생각되어 바로 결단을 내렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F-Lab에서는 자바의 기본 내용부터 스프링까지 모든 내용을 다뤘으며, 공부의 방향성 및 방법 그리고 앞으로 나아가야 할 자세를 배울 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 공부한 내용에 대해서 멘토님께서 질문을 해주시는데 항상 질문을 받을 때마다 좀 더 깊게 공부하지 않았던 부분에 대해 반성하는 시간들이 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이직&amp;nbsp; 준비.. 그리고 성공&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링도 들었고 공부도 했다고 생각이들어 무작정 이직하고 싶다는 마음에 세 곳에 지원을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 말하는 노란색 회사, 초록색 회사 그리고 지금 회사다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 내가 회사를 결정한 부분은 아래 요소와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 회사보다 큰 회사
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 중견 규모의 회사로 서비스의 TPS만 봤을 때는 국내에서 손에 꼽히는 회사다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;나의 고민을 같이 고민해줄 선배 개발자 유무
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 회사에서는 시니어 개발자는 물론 Java 개발자가 없어서 항상 고민이 생기면 조언을 구할 수가 없었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;흥미가 생기는 도메인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노란색 회사는 코테에서 떨어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 다 풀었다고 생각이 들었는데 예외 케이스를 통과를 못한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 코딩 테스트를 제대로 준비해본 경험이 없다. 약간 코딩 테스트라고 하면 구현을 하면 된다는 안일한 생각을 가지고 있었기 때문에 떨어졌다고 생각이 들어&amp;nbsp;이러한 부분에서 2022년의 목표는 알고리즘을 제대로 한번 공부해 보자는 반성의 시간을 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초록색 회사는 최종 면접에서 떨어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 최종 면접이라고 하면 인성 면접을 생각할 텐데 심화 기술 면접이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 면접 때 나의 부족함에 정말 많은 자괴감이 들었고... 반성하고 다음에 다시 지원하게 된다면 붙을 수 있을 것 같다는 희망도 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 회사는 최종 합격이다. (그러니깐 다니고 있지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코테에서 조금 당황했다. 평소 생각하던 코테와 다른 유형의 문제들이 출제되었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 풀 수는 있었다. 한 문제 정도에서 테스트 케이스를 통과 못했던 것으로 기억난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌 됐던 세 가지 회사에 지원을 하였고 한 곳에 합격하여 지금 회사를 다니고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 정도 만족스러운 결과를 얻었지만 이번에 이직하면서 느낀 부분은 이직 생각이 없어도 항상 준비를 해두어야 언제든지 이직을 할 수 있다고 생각이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 늘 공부를 하고 있다고 생각을 했다. 하지만 여러 개발자의 공부하면서 남아있는 기록을 보면 정말 열심히 하는 것 같다 나는 그것에 비해 게을르고 그렇게 많이 공부를 하는것 같지가 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새해에는 조금 더 부지런하게 살아야겠다.&lt;/p&gt;</description>
      <category>Log/회고</category>
      <category>회고</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/28</guid>
      <comments>https://0n1dev.tistory.com/28#entry28comment</comments>
      <pubDate>Tue, 4 Jan 2022 10:16:11 +0900</pubDate>
    </item>
    <item>
      <title>2022년 목표</title>
      <link>https://0n1dev.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;2022년 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021년 마무리 목표는 지켜지지가 않았다.. 정신차리고 해야하는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021년을 너무 정신없이 지내다보니 의욕이 살짝 떨어진것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 정신 차려서 진행해야겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; 코틀린 제대로 시작해보기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린 공부.&lt;/li&gt;
&lt;li&gt;코틀린 기반으로 Spring 진영의 모듈 다시 정리해보기.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 개발 방법론 공부하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DDD&lt;/li&gt;
&lt;li&gt;클린 아키텍처&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 파이썬 기반 알고리즘 공부&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자료구조 부터 주 3회 이상 시작하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 회고 꾸준히 작성하기&lt;/h3&gt;</description>
      <category>Log/목표</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/27</guid>
      <comments>https://0n1dev.tistory.com/27#entry27comment</comments>
      <pubDate>Tue, 4 Jan 2022 09:41:08 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin In Action #3</title>
      <link>https://0n1dev.tistory.com/26</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin&amp;nbsp;In&amp;nbsp;Action&amp;nbsp;#3&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스, 객체, 인터페이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 클래스와 인터페이슨느 자바의 클래스, 인터페이스와 약간 다름.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 달리 코틀린 선언은 기본적으로 public, final 임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 중첩 클래스는 기본적으로 내부 클래스가 아님.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스 계층 정의&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코틀린 인터페이스&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린은 자바 8 인터페이스와 비슷.&lt;/li&gt;
&lt;li&gt;코틀린 인터페이스 안에는 추상 메서드뿐 아니라 구현 메서드도 정의 가능. (자바 8의 디폴트 메서드와 비슷)&lt;/li&gt;
&lt;li&gt;인터페이스에 아무런 필드가 들어갈 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1640181107993&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Clickable {
    fun click()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1640181122122&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Button: Clickable {
    override fun click() = println(&quot;Button Click&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서는 extends와 implements 키워드를 사용하지만, 코틀린에서는 클래스 이름 뒤에 콜론(:)을 사용해 클래스 확장과 인터페이스 구현을 모두 처리.&lt;/li&gt;
&lt;li&gt;자바와 동일하게 인터페이스는 개수 제한 없이 구현 가능하지만, 클래스는 오직 하나만 확장 가능.&lt;/li&gt;
&lt;li&gt;override 변경자는 자바의 @Override 어노테이션과 비슷하지만 필수적으로 사용해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1640181325657&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Clickable {
    fun click()
    fun showOff() = println(&quot;Show Off&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 다르게 메서드에 디폴트 구현을 할 때 특별한 키워드가 필요 없음.&lt;/p&gt;
&lt;pre id=&quot;code_1640182825514&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Focusable {
    fun setFocus(b: Boolean) = println(&quot;I ${if (b) &quot;got&quot; else &quot;lost&quot;} focus.&quot;)
    fun showOff() = println(&quot;Focusable&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1640182812066&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Button: Clickable, Focusable {
    override fun click() = println(&quot;Button Click&quot;)
    override fun showOff() {
        super&amp;lt;Clickable&amp;gt;.showOff()
        super&amp;lt;Focusable&amp;gt;.showOff()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름과 시그니처가 동일한 멤버 메서드에 대해 둘 이상의 디폴트 구현이 있는 경우 하위 클래스에서 명시적으로 새로운 구현을 제공해야 함.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;open, final, abstract 변경자: 기본적으로 final&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 final로 명시적으로 상속을 금지하지 않은 모든 클래스는 다른 클래스가 상속 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 상속이 가능하면 편리한 경우도 있지만 문제가 생기는 경우도 많음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취약한 기반 클래스라는 문제는 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반 클래스를 변경함으로써 깨져버린 경우.&lt;/li&gt;
&lt;li&gt;코틀린은 클래스와 메서드는 기본적으로 final임.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열린 메서드를 포함하는 열린 클래스 정의&lt;/p&gt;
&lt;pre id=&quot;code_1641209078764&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class RichButton : Clickable { // 해당 클래스는 다른 클래스가 상속 가능

    fun disable() {} // final 함수, 하위 클래스가 오버라이드할 수 없음

    open fun animate() {} // 열려있는 함수, 하위 클래스에서 오버라이드 가능

    override fun click() {} // 오버라이드한 함수는 기본적으로 열려있음
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오버라이드한 함수는 기본적으로 열려있음. 이를 방지하려면 앞에 final을 붙여줘야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 클래스 정의&lt;/p&gt;
&lt;pre id=&quot;code_1641209861197&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class Animated { // 추상 클래스는 인스턴스를 만들 수 없음. (구현이 없는 추상 멤버가 있기 때문)

    abstract fun animate() // 추상 함수는 기본적으로 open

    open fun stopAnimating() {} // 추상 클래스에 속했더라도 비추상 함수는 기본적으로 final

    fun animateTwice() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가시성 변경자: 기본적으로 공개&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가시성 변경자는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어함.&lt;/li&gt;
&lt;li&gt;특정 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부 코드를 깨지 않고도 클래스 내부 구현을 변경할 수 잇음.&lt;/li&gt;
&lt;li&gt;가시성 변경자 종류
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;public (default)&lt;/li&gt;
&lt;li&gt;protected&lt;/li&gt;
&lt;li&gt;private&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코틀린은 패키지를 네임스페이스를 관리하기 위한 용도로만 사용하여 package-private (자바의 default)를 사용하지 않음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패키지 전용 가시성에 대한 대안으로 internal이라는 새로운 가시성 변경자를 도입.&lt;/li&gt;
&lt;li&gt;internal은 &quot;모듈 내부에서만 볼 수 있음&quot;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈은 한 번에 컴파일되는 코틀린 파일들을 의미.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;변경자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;클래스 멤버&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;최상위 선언&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;public (default)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;모든 곳에서 볼 수 있음.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;모든 곳에서 볼 수 있음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;internal&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;같은 모듈 안에서만 볼 수 있음.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;같은 모듈 안에서만 볼 수 있음.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;protected&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;하위 클래스 안에서만 볼 수 있음.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;최상위 선언에 적용할 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;private&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;같은 클래스 안에서만 볼 수 있음.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;같은 파일 안에서만 볼 수 있음.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1641302495470&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;internal open class TalkativeButton : Focusable {

    private fun yell() = println(&quot;Hey!&quot;)
    protected fun whisper() = println(&quot;Let's talk!&quot;)
}

fun TalkativeButton.giveSpeech() { // &quot;public&quot; 멤버가 자신의 &quot;internal&quot; 수신 타입인 &quot;TalkativeButton&quot;을 노출함
    yell() // &quot;yell&quot;에 접근할 수 없음.
    whisper() // &quot;whisper&quot;에 접근할 수 없음.
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;public 함수 안에서 그보다 가시성이 더 낮은 타입을 참조하지 못하게 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;giveSpeech 함수는 public / TalkativeButton 클래스는 internal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가시성은 같거나 더 높아야 한다는 일반적인 규칙이 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;코틀린의 가시성 변경자와 자바&lt;br /&gt;코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트 코드 안에서도 그대로 유지 됨.&lt;br /&gt;유일한 예외는 자바에서는 클래스를 private로 만들 수 없으므로 내부적으로 코틀린의 private 클래스를 패키지 전용 클래스로 컴파일 함.&lt;br /&gt;internal 변경자는 자바에 딱 맞는 가시성이 없음. 바이트 코드상으로는 public이 됨.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린과 자바 가시성 규칙의 다른 차이는 코틀린에서는 외부 클래스가 내부 클래스나 중첩 클래스의 private 멤버에 접근할 수 없음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 중첩 클래스는 자바와 다르게 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직렬화할 수 있는 상태가 있는 뷰 선언&lt;/p&gt;
&lt;pre id=&quot;code_1641306919931&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.Serializable

interface State: Serializable

interface View {
    fun getCurrentState() : State
    fun restoreState(state: State) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 내부 클래스를 사용해 View 구현&lt;/p&gt;
&lt;pre id=&quot;code_1641307083570&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.jetbrains.annotations.NotNull;

public class Button implements View{
    @NotNull
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }

    @Override
    public void restoreState(@NotNull State state) {
        /* ... */
    }
    
    public class ButtonState implements State {
        /* ... */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State 인터페이스를 구현한 ButtonState 클래스를 정의하여 Button에 대한 구체적인 정보를 저장.&lt;/li&gt;
&lt;li&gt;버튼의 상태를 직렬화하면 NotSerializableException: Button 발생.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서는 다른 클래스 안에 정의한 클래스는 자동으로 내부 클래스가 됨.&lt;/li&gt;
&lt;li&gt;ButtonState 클래스는 바깥쪽 Button 클래스에 대한 참조를 묵시적으로 포함.&lt;/li&gt;
&lt;li&gt;해결하기 위해서는 ButtonState를 static 클래스로 선언해야 함. (묵시적인 참조가 사라짐)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩 클래스를 사용해 코틀린에서 View 구현하기&lt;/p&gt;
&lt;pre id=&quot;code_1641307441666&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Button : View {
    override fun getCurrentState(): State = ButtonState()

    override fun restoreState(state: State) {
        /* ... */
    }

    class ButtonState :State {
        /* ... */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린 중첩 클래스에는 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같음.&lt;/li&gt;
&lt;li&gt;반대로 바깥족 클래스에 대한 참조를 포함하게 만들고 싶으면 inner 변경자를 붙여야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inner 클래스에서 Outer 클래스의 참조에 접근&lt;/p&gt;
&lt;pre id=&quot;code_1641307748067&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Outer {
    val a = 1;
    
    inner class Inner {
        fun getOuterReference()  : Outer = this@Outer
        fun getValue() : Int = a
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 구현을 통한 식 표현하기&lt;/p&gt;
&lt;pre id=&quot;code_1641308960553&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Expr
class Num(val value: Int): Expr
class Sum(val left: Expr, val right: Expr): Expr

fun eval(e: Expr): Int = 
    when (e) {
        is Num -&amp;gt; e.value
        is Sum -&amp;gt; eval(e.right) + eval(e.left)
        else -&amp;gt;
            throw IllegalArgumentException(&quot;Unknown expression&quot;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린은 when을 사용하여 Expr 타입의 값을 검사할 때 꼭 디폴트 분기인 else 분기를 덧붙이게 강제함.&lt;/li&gt;
&lt;li&gt;새로운 하위 클래스를 추가했을때 컴파일러가 when이 모든 경우를 처리하는지 제대로 검사가 불가능.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sealed 변경자를 붙이면 하위 클래스 정의를 제한할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1641309214825&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class Expr { // sealed로 봉인
    class Num(val value: Int): Expr() // 기반 클래스의 모든 하위 클래스를 중첩 클래스로 나열
    class Sum(val left: Expr, val right: Expr): Expr()
}

fun eval(e: Expr): Int =
    when (e) { // when 식이 모든 하위 클래스를 검사하므로 별도의 else 분기 필요 없음.
        is Expr.Num -&amp;gt; e.value
        is Expr.Sum -&amp;gt; eval(e.right) + eval(e.left)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sealed로 표시된 클래스는 자동으로 open 변경자.&lt;/li&gt;
&lt;li&gt;내부적으로 Expr 클래스는 private 생성자를 가짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;코틀린 1.0에서는 하위 클래스는 중첩 클래스여야 하고, 데이터 클래스로 sealed 클래스를 상속할 수 없음.&lt;br /&gt;1.1 부터는 해당 제한이 완화 됨.&lt;/blockquote&gt;</description>
      <category>Programming/Kotlin</category>
      <category>KotlinInAction</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/26</guid>
      <comments>https://0n1dev.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 22 Dec 2021 00:00:02 +0900</pubDate>
    </item>
    <item>
      <title>Raspberry pi4를 활용한 Kubernetes Cluster #1</title>
      <link>https://0n1dev.tistory.com/25</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Raspberry&amp;nbsp;pi4를&amp;nbsp;활용한&amp;nbsp;Kubernetes&amp;nbsp;Cluster&amp;nbsp;#1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS 환경은 Ubuntu 20.04 64bit 라즈베리용에서 진행됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;호스트네임 수정&lt;/h3&gt;
&lt;pre id=&quot;code_1639576131219&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi /etc/cloud/cloud.cfg

preserve_hostname = true

sudo hostnamectl set-hostname 호스트이름

sudo reboot&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아이피 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1639576240914&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi /etc/netplan/50-cloud-init.yaml

network:
    ethernets:
        eth0:
            dhcp4: false
            # optional: true
            addresses: [172.30.1.1/24]
            gateway4: 172.30.1.254
            nameservers:
                addresses: [168.126.63.1, 168.126.63.2]
    version: 2
    
sudo netplan apply&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;패키지 업데이트&lt;/h3&gt;
&lt;pre id=&quot;code_1639576335699&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update
sudo apt -y upgrade &amp;amp;&amp;amp; sudo systemctl reboot&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kubelet, Kubeadm, Kubectl 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1639576607457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update
sudo apt -y install curl apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo &quot;deb https://apt.kubernetes.io/ kubernetes-xenial main&quot; | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt update
sudo apt -y install vim git curl wget kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cgroup 메모리 구성&lt;/h3&gt;
&lt;pre id=&quot;code_1639576718482&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cgroup=&quot;$(head -n1 /boot/firmware/cmdline.txt) cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1&quot;
echo $cgroup | sudo tee /boot/firmware/cmdline.txt&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 스왑 비활성화&lt;/h3&gt;
&lt;pre id=&quot;code_1639576752289&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
sudo swapoff -a&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커널 모듈 활성화&lt;/h3&gt;
&lt;pre id=&quot;code_1639576862561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo modprobe overlay
sudo modprobe br_netfilter

sudo tee /etc/sysctl.d/kubernetes.conf&amp;lt;&amp;lt;EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Kubernetes</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/25</guid>
      <comments>https://0n1dev.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 15 Dec 2021 23:01:36 +0900</pubDate>
    </item>
    <item>
      <title>Raspberry pi4를 활용한 Kubernetes Cluster #0</title>
      <link>https://0n1dev.tistory.com/24</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Raspberry&amp;nbsp;pi4를&amp;nbsp;활용한&amp;nbsp;Kubernetes&amp;nbsp;Cluster&amp;nbsp;#0&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재료
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Raspberry pi4 8GB 3개
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마스터 노드 1개&lt;/li&gt;
&lt;li&gt;워커 노드 2개 (추후 1개 늘려줄 예정)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;iUniker Raspberry Pi Cluster Case (아마존 직구 배송중)&lt;/li&gt;
&lt;li&gt;MicroSD 삼성 32GB 3개&lt;/li&gt;
&lt;li&gt;액토 고속 충전기&lt;/li&gt;
&lt;li&gt;C타입 고속충전 케이블 0.2m&lt;/li&gt;
&lt;li&gt;IPTIME H6005&lt;/li&gt;
&lt;li&gt;랜케이블 짧은거로 3개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/Kubernetes</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/24</guid>
      <comments>https://0n1dev.tistory.com/24#entry24comment</comments>
      <pubDate>Tue, 14 Dec 2021 23:42:16 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin In Action #2</title>
      <link>https://0n1dev.tistory.com/23</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin In Action #2&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수 정의와 호출&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 프로그램에서 핵심이라 할 수 있는 함수 정의와 호출 기능을 코틀린이 어떻게 개선했는지 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 확장 함수와 프로퍼티를 사용해 자바 라이브러리를 코틀린 스타일로 적용하는 방법을 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코틀린에서 컬렉션 만들기&lt;/h3&gt;
&lt;pre id=&quot;code_1639459475286&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val set = hashSetOf(1, 7, 53)
    val list = arrayListOf(1, 7, 53)
    val map = hashMapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map에서 사용된 to는 언어가 제공하는 키워드가 아닌 일반 함수.&lt;/p&gt;
&lt;pre id=&quot;code_1639460214839&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val set = hashSetOf(1, 7, 53)
    val list = arrayListOf(1, 7, 53)
    val map = hashMapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)

    println(set.javaClass)
    println(list.javaClass)
    println(map.javaClass)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 실행해 보면 아래와 같은 결과를 얻을수 있음.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;java.util.HashSet&lt;br /&gt;class&amp;nbsp;java.util.ArrayList&lt;br /&gt;class&amp;nbsp;java.util.HashMap&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자신만의 컬렉션 기능을 제공하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린이 자체 컬렉션을 제공하지 않는 이유는 표준 자바 컬렉션을 활용하면 자바 코드와 상호작용하기 훨씬 쉽기 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바보다 더 많은 기능을 제공.&lt;/p&gt;
&lt;pre id=&quot;code_1639460615207&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    println(set.last())
    println(list.maxOrNull())&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 호출하기 쉽게 만들기&lt;/h3&gt;
&lt;pre id=&quot;code_1639461433968&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; joinToString(
    collection: Collection&amp;lt;T&amp;gt;,
    separator: String,
    prefix: String,
    postfix: String
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in collection.withIndex()) {
        if (index &amp;gt; 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 함수는 제네릭 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 함수를 계속해서 사용하기에는 매번 인자 4개를 모두 전달해야 해서 번잡함이 있다. 이를 해결해보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이름 붙인 인자&lt;/h4&gt;
&lt;pre id=&quot;code_1639461658651&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;joinToString(list, separator = &quot;; &quot;, prefix =  &quot;(&quot;, postfix = &quot;)&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE 도움을 받아 시그니처를 표시할 수 있겠지만, 코드 자체가 모호하기 때문에 인자의 이름을 명시 할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자의 이름을 꼭 명시해야 함.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;디폴트 파라미터 값&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 일부 클래스에서 오버로딩한 메서드가 너무 많아진다는 문제가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 사용해 오버로드 중 상당수를 피할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1639461893913&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; joinToString(
    collection: Collection&amp;lt;T&amp;gt;,
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 코드를 클래스의 메소드로 작성해야 함.&lt;/li&gt;
&lt;li&gt;어느 한 클래스에 포함시키기 어려운 코드가 많음.&lt;/li&gt;
&lt;li&gt;정적 메소드만 모아두는 역할을 담당하는 특별한 상태나 인스턴스가 없는 클래스가 생겨남.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코틀린
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무의미한 클래스가 필요 없음.&lt;/li&gt;
&lt;li&gt;함수를 직접 소스 파일의 최상위 수준, 모든 다른 클래스의 밖에 위치 시키면 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최상위 프로퍼티&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1640011049745&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var opCount: Int = 0

fun performOperation() {
    opCount++
}

fun reportOperationCount() {
    println(&quot;Operation performed $opCount times&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 프로퍼티 값은 정적 필드에 저장됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 프로퍼티를 활용해 상수를 추가할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1640011156241&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const val UNIX_LINE_SEPARATOR = &quot;\n&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 val만 사용하면 게터가 생김.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const를 붙이면 public static final 필드로 컴파일.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메서드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 라이브러리 기반 혹은 자바 프로젝트와 통합하는 경우 코틀린으로 직접 변환할 수 없거나 미처 변환하지 않은 기존 자바 코드를 처리할 수 있어야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 있게 해주는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;확장 함수&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640011976593&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun String.lastChar(): Char = this.get(this.length - 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수를 만들려면 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 String은 수신 객체 타입이고, this는 수신 객체.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신 객체 멤버에 this없이 접근 가능.&lt;/p&gt;
&lt;pre id=&quot;code_1640012079913&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun String.lastChar(): Char = get(length - 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수가 캡슐화를 깨지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 안에서 정의한 메서드와 달리 확장 함수 안에서는 클래스 내부에서만 사용 가능한 private 혹은 protected 멤버를 사용할 수 없음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;임포트와 확장 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야 사용 가능.&lt;/p&gt;
&lt;pre id=&quot;code_1640012428802&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import strings.lastChar

fun main(args: Array&amp;lt;String&amp;gt;) {
    println(&quot;Kotlin&quot;.lastChar())
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1640012440082&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import strings.*

fun main(args: Array&amp;lt;String&amp;gt;) {
    println(&quot;Kotlin&quot;.lastChar())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;as 키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 부를 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 패키지의 동일한 함수를 가져와야 하면 충돌을 막기 위해 사용.&lt;/p&gt;
&lt;pre id=&quot;code_1640012456754&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import strings.lastChar as last

fun main(args: Array&amp;lt;String&amp;gt;) {
    println(&quot;Kotlin&quot;.last())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자바에서 확장 함수 호출&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lastChar() 함수의 파일명을 LastKt로 했으면 아래와 같이 사용하면 됨.&lt;/p&gt;
&lt;pre id=&quot;code_1640050516612&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;char c = LastKt.lastChar(&quot;dddd&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;joinToString() 함수 확장 함수로 정의&lt;/h4&gt;
&lt;pre id=&quot;code_1640050817002&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package strings

fun &amp;lt;T&amp;gt; Collection&amp;lt;T&amp;gt;.joinToString(
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index &amp;gt; 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확장 함수는 오버라이드할 수 없다&lt;/h4&gt;
&lt;pre id=&quot;code_1640051546313&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class View {
    open fun click() = println(&quot;View&quot;)
}

class Button: View() {
    override fun click() = println(&quot;Button&quot;)
}

fun main(args: Array&amp;lt;String&amp;gt;) {
    val v: View = Button()
    v.click()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Button이 View의 하위 타입이기 때문에 View 타입 변수를 선언해도&amp;nbsp; Button 타입 변수 대입 가능.&lt;/li&gt;
&lt;li&gt;Button 클래스가 click 함수를 오버라이드했다면 Button이 오버라이드한 click 함수가 호출 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1640052105406&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun View.showOff() = println(&quot;View2&quot;)
fun Button.showOff() = println(&quot;Button2&quot;)

fun main(args: Array&amp;lt;String&amp;gt;) {
    val v: View = Button()
    v.showOff()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 함수는 정적으로 결정되기 때문에 View 클래스의 showOff 함수가 호출 됨.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확장 프로퍼티&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 저장할 적절한 방법이 없기 때문에 확장 프로퍼티는 아무 상태도 가질 수 없음.&lt;/p&gt;
&lt;pre id=&quot;code_1640053276383&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val String.lastChar: Char
    get() = get(length - 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 가능한 프로퍼티&lt;/p&gt;
&lt;pre id=&quot;code_1640053500893&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var StringBuilder.lastChar: Char
    get() = get(length - 1)
    
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vararg 키워드를 사용하면 호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있음.&lt;/li&gt;
&lt;li&gt;중위 함수 호출 구문을 사용하면 인자가 하나뿐인 메서드를 간편하게 호출할 수 있음.&lt;/li&gt;
&lt;li&gt;구조 분해 선언을 사용하면 복합적인 값을 분해해서 여러 변수에 나눠 담을 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자바 컬렉션 API 확장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서의 컬렉션은 자바와 같은 클래스를 사용하지만 더 확장된 API를 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시로 last나 maxOrNull 함수가 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1640091634093&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val list = listOf&amp;lt;String&amp;gt;(&quot;test1&quot;, &quot;test2&quot;, &quot;test3&quot;)
    println(list.last())

    val numbers = setOf&amp;lt;Int&amp;gt;(9, 1, 10, 5, 6)
    println(numbers.maxOrNull())
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1640091701102&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public fun &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt;.last(): T {
    if (isEmpty())
        throw NoSuchElementException(&quot;List is empty.&quot;)
    return this[lastIndex]
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1640091781110&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public fun &amp;lt;T : Comparable&amp;lt;T&amp;gt;&amp;gt; Iterable&amp;lt;T&amp;gt;.maxOrNull(): T? {
    val iterator = iterator()
    if (!iterator.hasNext()) return null
    var max = iterator.next()
    while (iterator.hasNext()) {
        val e = iterator.next()
        if (max &amp;lt; e) max = e
    }
    return max
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;last나 maxOrNull은 확장 함수라는 것.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트를 생성하는 listOf 함수를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1640092398334&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public fun &amp;lt;T&amp;gt; listOf(vararg elements: T): List&amp;lt;T&amp;gt; = if (elements.size &amp;gt; 0) elements.asList() else emptyList()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가변 길이 인자는 메서드를 호출할 때 원하는 개수만큼 값을 인자로 넘기면 컴파일러가 배열에 그 값들을 넣어주는 기능.&lt;/li&gt;
&lt;li&gt;자바의 ...과 비슷. 코틀린에서는 vararg 변경자를 쓰면 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1640093406831&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val list = listOf(&quot;args : &quot;, *args)
    println(list)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서는 가변 길이 인자를 넘길 때 배열을 사용함.&lt;/li&gt;
&lt;li&gt;코틀린에서는 배열을 명시적으로 풀어 배열의 각 원소가 인자로 전달되게 해야 함. (스프레드 연선자)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;값의 쌍 다루기: 중위 호출과 구조 분해 선언&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중위 호출 시 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣음.&lt;/p&gt;
&lt;pre id=&quot;code_1640093948175&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val map = mapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)
    
    1.to(&quot;one&quot;)
    1 to &quot;one&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1.to(&quot;one&quot;) &amp;lt;- &quot;to&quot; 메서드를 일반적인 방식으로 호출&lt;/li&gt;
&lt;li&gt;1 to &quot;one&quot; &amp;lt;- &quot;to&quot; 메서드를 중위 호출 방식으로 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1640094576639&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public infix fun &amp;lt;A, B&amp;gt; A.to(that: B): Pair&amp;lt;A, B&amp;gt; = Pair(this, that)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 중위 호출에 사용하게 허용하고 싶으면 infix 변경자를 함수 선언 앞에 추가.&lt;/li&gt;
&lt;li&gt;to 함수는 Pair의 인스턴스를 반환.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pair는 코틀린 표준 라이브러리 클래스로, 두 원소로 이루어진 순서쌍을 표현.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1640094904176&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val (number, name) = 1 to &quot;one&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pair 클래스를 사용하여 두 변수를 즉시 초기화.&lt;/li&gt;
&lt;li&gt;해당 기능을 구조 분해 선언이라 부름.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열과 정규식 다루기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린 코드가 만들어낸 문자열을 아무 자바 메서드에 넘겨도 되고, 자바 코드에서 받은 문자열을 아무 코틀린 표준 라이브러리 함수에 전달해도 문제 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별한 변환과 자바 문자열을 감싸는 별도의 래퍼가 필요 없음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문자열 나누기&lt;/h4&gt;
&lt;pre id=&quot;code_1640095940107&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println(&quot;324.23gerag23.23549.234gw-ew&quot;.split(&quot;\\.|-&quot;.toRegex())) // 정규식을 명시적으로 만듬.

println(&quot;324.23gerag23.23549.234gw-ew&quot;.split(&quot;.&quot;, &quot;-&quot;)) // 여러 구분 문자열을 지정.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정규식과 3중 따옴표로 묶은 문자열&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String 확장 함수를 사용해 경로 파싱&lt;/p&gt;
&lt;pre id=&quot;code_1640096439569&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun String.parsePath(): String {
    val directory = this.substringBeforeLast(&quot;/&quot;)
    val fullName = this.substringAfterLast(&quot;/&quot;)
    val fileName = fullName.substringBeforeLast(&quot;.&quot;)
    val extension = fullName.substringAfterLast(&quot;.&quot;)

    return &quot;Dir: $directory, name: $fileName, ext: $extension&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식 사용해 경로 파싱&lt;/p&gt;
&lt;pre id=&quot;code_1640096759073&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun String.parsePath(): String {
    val regex = &quot;&quot;&quot;(.+)/(.+)\.(.+)&quot;&quot;&quot;.toRegex()
    val matchResult = regex.matchEntire(this)
    if (matchResult != null) {
        val (directory, fileName, extension) = matchResult.destructured
        return &quot;Dir: $directory, name: $fileName, ext: $extension&quot;
    }

    return &quot;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3중 따옴표 문자열에서는 역슬래시(\)를 포함한 어떤 문자도 이스케이프할 필요가 없음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;여러 줄 3중 따옴표 문자열&lt;/h4&gt;
&lt;pre id=&quot;code_1640097623834&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val text = &quot;&quot;&quot;|ddd
        .|ddddfeafe
        .|wefw
    &quot;&quot;&quot;

    println(text.trimMargin(&quot;.&quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이스케이프를 피하기 위해서만 아닌 줄 바꿈을 쉽게 표현하기 위해서도 사용.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 다듬기: 로컬 함수와 확장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 잇음.&lt;/p&gt;
&lt;pre id=&quot;code_1640098372075&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(
    val id: Int,
    val name: String,
    val address: String
)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            &quot;Can't save user ${user.id}: empty Name&quot;
        )
    }

    if (user.address.isEmpty()) {
        throw IllegalArgumentException(
            &quot;Can't save user ${user.address} empty Address&quot;
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 함수를 사용해 중복 코드 줄이기&lt;/p&gt;
&lt;pre id=&quot;code_1640098649587&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(
    val id: Int,
    val name: String,
    val address: String
)

fun saveUser(user: User) {
    fun validate(value: String,
                 fieldName: String
    ) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                &quot;Can't save user ${user.name}: empty $fieldName&quot;
            )
        }
    }
    
    validate(user.name, &quot;Name&quot;)
    validate(user.address, &quot;Address&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있음.&lt;/p&gt;</description>
      <category>Programming/Kotlin</category>
      <category>KotlinInAction</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/23</guid>
      <comments>https://0n1dev.tistory.com/23#entry23comment</comments>
      <pubDate>Sat, 11 Dec 2021 23:01:10 +0900</pubDate>
    </item>
    <item>
      <title>6.0 데이터 압축</title>
      <link>https://0n1dev.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/21</guid>
      <comments>https://0n1dev.tistory.com/21#entry21comment</comments>
      <pubDate>Wed, 8 Dec 2021 00:24:55 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin In Action #1</title>
      <link>https://0n1dev.tistory.com/20</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin&amp;nbsp;In&amp;nbsp;Action&amp;nbsp;#1&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본요소: 함수와 변수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Hello, World!&lt;/h4&gt;
&lt;pre id=&quot;code_1638798018875&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    println(&quot;Hello World!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 선언할 때 fun 키워드 사용.&lt;/li&gt;
&lt;li&gt;파라메터 이름 뒤에 그 파라메터의 타입을 작성.&lt;/li&gt;
&lt;li&gt;함수를 최상위 수준에 정의. (자바는 클래스 안에 메서드를 넣음.)&lt;/li&gt;
&lt;li&gt;코틀린 표준 라이브러리는 여러 표준 자바 라이브러리의 메서드를 간결하게 사용할 수 있게 감싼 레퍼를 제공.&lt;/li&gt;
&lt;li&gt;세미콜론을 붙이지 않아도 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;반환 값이 있는 함수&lt;/h4&gt;
&lt;pre id=&quot;code_1638798307320&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun max(a: Int, b: Int): Int {
    return if (a &amp;gt; b) a else b
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서의 if는 식(expression)이지 문(statement)가 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;식은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음.&lt;/li&gt;
&lt;li&gt;문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;식이 본문인 함수&lt;/h4&gt;
&lt;pre id=&quot;code_1638798478046&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun max(a: Int, b: Int): Int = if (a &amp;gt; b) a else b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reaturn을 제거하면서 등호(=)를 식 앞에 붙이면 더 간결한 함수 표현이 가능.&lt;/p&gt;
&lt;pre id=&quot;code_1638798636723&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun max(a: Int, b: Int) = if (a &amp;gt; b) a else b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식이 본문인 함수의 경우 굳이 사용자가 반환 타입을 적지 않아도 됨. (타입 추론)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 다르게 타입 지정을 생략하거나, 변수 이름 뒤에 타입을 명시.&lt;/p&gt;
&lt;pre id=&quot;code_1638798799510&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val question = &quot;가나다라마바사&quot;
    val answer = 42
    val answer2: Int = 43

    println(question)
    println(answer)
    println(answer2)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 초기화 식을 사용하지 않는다면 반드시 타입을 지정해야 함&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경 가능한 변수와 변경 불가능한 변수&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val(value) - 변경 불가능한 참조를 저장하는 변수. 자바로 따지면 final 키워드가 붙은 변수.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조가 가리키는 객체의 내부 값은 변경될 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;var(variable) - 변경 가능한 참조. 값이 바뀔 수 있음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입은 고정되어 바뀌지 않음. (&quot;Error: type mismatch&quot; 오류 발생)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;더 쉽게 문자열 형식 지정: 문자열 템플릿&lt;/h4&gt;
&lt;pre id=&quot;code_1639021802796&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    var name = if  (args.size &amp;gt; 0) args[0] else &quot;Kotlin&quot;
    println(&quot;Hello, $name&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 리터럴의 필요한 곳에 $ 키워드와 함께 변수를 넣어 사용 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 식은 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;${} 중괄호를 사용&lt;/span&gt;해서 넣을 수 있음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스와 프로퍼티&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서의 Person 클래스&lt;/p&gt;
&lt;pre id=&quot;code_1639022098765&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Person {

    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서의 Person클래스&lt;/p&gt;
&lt;pre id=&quot;code_1639022166228&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person (val name: String)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 유형의 클래스(코드가 없이 데이터만 저장하는 클래스)를 값 객체라 함.&lt;/li&gt;
&lt;li&gt;코틀린에서의 기본 접근 제어자는 public이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로퍼티&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 필드와 접근자를 묶은것을 프로퍼티라 부르고, 해당 개념을 활용하는 프레임워크가 많음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 프로퍼티를 언어 기본 기능으로 제공하며, 자바의 필드와 접근자 메서드를 완전히 대신함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val로 선언한 프로퍼티는 읽기 전용. (게터만 만듬)&lt;/li&gt;
&lt;li&gt;var로 선언한 프로퍼티는 변경도 가능. (게터, 세터 둘 다 만듬)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1639029472222&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person (
    val name: String,
    var isMarried: Boolean
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1639029592835&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val person = Person(&quot;0n1dev&quot;, true)

    println(person.name)
    println(person.isMarried)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커스텀 접근자&lt;/h4&gt;
&lt;pre id=&quot;code_1639029883292&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Rectangle(val height: Int, val width: Int) {

    val isSquare: Boolean
        get() {
            return height == width
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isSquare 프로퍼티에는 자체 구현을 제공한는 게터만 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 블록문 대신 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;get() = height == width&lt;/span&gt;로 작성해도 됨.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코틀린 소스코드 구조: 디렉터리와 패키지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 경우 모든 클래스를 패키지 단위로 관리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에도 비슷한 개념의 패키지가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 다른점은 자바에서는 패키지와 디렉터리 계층 구조가 동일해야 함, 코틀린은 디렉터리와 상관없이 원하는 대로 소스코드 구성이 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 자바와 같이 패키지별로 디랙터리를 구성하는게 좋음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선택 표현과 처리: enum과 when&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;enum 클래스 정의&lt;/h4&gt;
&lt;pre id=&quot;code_1639031868763&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum class Color {
    RED, ORANGE, BLUE, GREEN, YELLOW
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서의 enum은 소프트 키워드라 부르는 존재.&lt;/li&gt;
&lt;li&gt;class 앞에 있을 때는 특별한 의미를 지니지만 다른 곳에서 이름에 사용할 수 있음.&lt;/li&gt;
&lt;li&gt;반면 class는 키워드.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1639032471952&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum class Color(
    val r: Int, // 프로퍼티
    val g: Int,
    val b: Int
) {
    RED(255, 0, 0), // 상수 생성시 프로퍼티 값 지정
    ORANGE(255, 165, 0),
    BLUE(0, 0, 255),
    GREEN(0, 255, 0),
    YELLOW(255, 255, 0); // 여기서는 세미콜론 필수

    fun rgb() = (r * 256 + g) * 256 + b // enum 클래스 내부의 메서드 정의
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;when으로 enum 클래스 다루기&lt;/h4&gt;
&lt;pre id=&quot;code_1639032756221&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -&amp;gt; &quot;RED&quot;
        Color.BLUE -&amp;gt; &quot;BLUE&quot;
        Color.GREEN -&amp;gt; &quot;GREEN&quot;
        Color.YELLOW -&amp;gt; &quot;YELLOW&quot;
        Color.ORANGE -&amp;gt; &quot;ORANGE&quot;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바와 다르게 각 분기의 끝에 break를 넣지 않아도 됨.&lt;/li&gt;
&lt;li&gt;한 분기 안에서 여러 값을 매치 패턴으로 적용할 수 있음. (콤마로 분리)&lt;/li&gt;
&lt;li&gt;해당 enum class를 임포트 시키면 클래스 수식자 없으 사용 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;when과 임의의 객체를 함께 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch와 다르게 분기 조건에 상수만 사용할 수 있는게 아닌 임의의 객체를 허용.&lt;/p&gt;
&lt;pre id=&quot;code_1639033481468&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun mix(color1: Color, color2: Color) =
    when (setOf(color1, color2)) {
        setOf(Color.RED, Color.YELLOW) -&amp;gt; Color.ORANGE
        else -&amp;gt; throw Exception(&quot;Dirth Color&quot;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 when의 분기 조건 부분에 식을 넣을 수 있기 때문에 코드를 더 간결하고 아름답게 작성할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해당 함수는 호출될 때마다 함수 인자로 주어진 두 색이 when의 분기 조건에 있는 다른 두 색과 같은지 비교하기 위해 여러 Set 인스턴스를 생성. (불필요한 가비지 객체가 늘어남.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인자 없는 when 사용&lt;/h4&gt;
&lt;pre id=&quot;code_1639223084309&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun mixOptimized(color1: Color, color2: Color) =
    when {
        (color1 == RED &amp;amp;&amp;amp; color2 == YELLOW) ||
                (color1 == YELLOW &amp;amp;&amp;amp; color2 == RED) -&amp;gt;
            ORANGE
        (color1 == YELLOW &amp;amp;&amp;amp; color2 == BLUE) ||
                (color1 == BLUE &amp;amp;&amp;amp; color2 == YELLOW) -&amp;gt;
            GREEN
        else -&amp;gt; throw Exception(&quot;Dirty Color&quot;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 객체를 생성하지 않는다는 장점이 있지만 가독성이 떨어지는 단점이 있음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스마트 캐스트: 타입 검사와 타입 캐스트를 조합&lt;/h4&gt;
&lt;pre id=&quot;code_1639223269974&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Expr
class Num(val value: Int): Expr // value라는 프로퍼티만 존재하는 클래스
class Sum(val left: Expr, val right: Expr): Expr // Expr 타입의 객체라면 어떤 것이나 Sum 연산자의 인자가 될 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1639223458292&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}

fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num
        return n.value
    }

    if (e is Sum) {
        return eval(e.left) + eval(e.right)
    }

    throw IllegalArgumentException(&quot;Unknown expression&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;is는 자바의 instanceof와 비슷함.&lt;/li&gt;
&lt;li&gt;자바 경우 타입을 확인 후 해당 타입에 속한 멤버에 접근하기 위해서는 명시적으로 타입을 캐스팅해야 함.&lt;/li&gt;
&lt;li&gt;코틀린에서는 프로그래머 대신 컴파일러가 캐스팅 해줌.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;리팩토링: if를 when으로 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차 리팩토링: return문과 중괄호 제거&lt;/p&gt;
&lt;pre id=&quot;code_1639223765764&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun eval(e: Expr): Int =
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.left) + eval(e.right)
    } else {
        throw IllegalArgumentException(&quot;Unknown expression&quot;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차 리팩토링: when으로 변경&lt;/p&gt;
&lt;pre id=&quot;code_1639223789380&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun eval(e: Expr): Int =
    when (e) {
        is Num -&amp;gt; {
            e.value
        }
        is Sum -&amp;gt; {
            eval(e.left) + eval(e.right)
        }
        else -&amp;gt; {
            throw IllegalArgumentException(&quot;Unknown expression&quot;)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대상을 이터레이션: while과 for 루프&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;while은 자바와 동일.&lt;/li&gt;
&lt;li&gt;for은 자바의 for-each 루프에 해당하는 형태만 존재.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수에 대한 이터레이션: 범위와 수열&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서는 초기값, 증가 값, 최종 값을 사용한 루프가 아닌 범위를 사용.&lt;/li&gt;
&lt;li&gt;범위는 기본적으로 두 값으로 이뤄진 구간이며, 보통 정수 등의 숫자 타입의 값 .. 연산자로 시작 값과 끝 값을 연결해서 범위를 만듬.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1639225114987&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val oneToTen = 1..10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수 범위로 수행할 수 있는 가장 단순한 작업은 범위에 속한 모든 값에 대한 이터레이션.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 범위에 속한 값을 일정한 순서로 이터레이션하는 경우를 수열이라 함.&lt;/p&gt;
&lt;pre id=&quot;code_1639225386971&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val oneToTen = 1..10

    for (i in oneToTen) {
        println(clapGame(i))
    }
}

fun clapGame(i: Int) =
    when {
        i % 3 == 0 -&amp;gt; &quot;짝&quot;
        i % 6 == 0 -&amp;gt; &quot;짝&quot;
        i % 9 == 0 -&amp;gt; &quot;짝&quot;
        else -&amp;gt; &quot;$i&quot;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증가 값을 갖고 범위 이터레이션&lt;/p&gt;
&lt;pre id=&quot;code_1639229475937&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    for (i in 100 downTo 1 step 2) {
        println(clapGame(i))
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;100 dwonTo 1은 역방향 수열.&lt;/li&gt;
&lt;li&gt;step을 붙이면 증가 값이 변경 됨.&lt;/li&gt;
&lt;li&gt;그 외에 until도 있는데 뒤에서 배울 예정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맵에 대한 이터레이션&lt;/h4&gt;
&lt;pre id=&quot;code_1639230162470&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*

fun main(args: Array&amp;lt;String&amp;gt;) {
    val binaryReps = TreeMap&amp;lt;Char, String&amp;gt;()

    for (c in 'A'..'Z') {
        val binary = Integer.toBinaryString(c.code)
        binaryReps[c] = binary
    }

    for ((letter, binary) in binaryReps) {
        println(&quot;$letter = $binary&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서는 Integer.toBinaryString(c.toInt())로 나와있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Char.toInt()는 Kotlin 1.5부터 Deprecated 되었기 때문에 Char.code로 작성해야 함.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;in으로 컬렉션이나 범위의 원소 검사&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1639230500032&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    println(isLetter('q'))
    println(isNotDigit('x'))
}

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;when에서 in 사용하기&lt;/p&gt;
&lt;pre id=&quot;code_1639230706383&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun recognize(c: Char) = when (c) {
    in '0'..'9' -&amp;gt; &quot;It's digit!&quot;
    in 'a'..'z', in 'A'..'Z' -&amp;gt; &quot;It's a letter!&quot;
    else -&amp;gt; &quot;I don't know..&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코틀린의 예외 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 예외 처리는 자바나 다른 언어의 예외 처리와 비슷.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서의 예외는 자바와 비슷하지만, new를 붙일 필요가 없음.&lt;/p&gt;
&lt;pre id=&quot;code_1639230859183&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val percentage = 1000
    
    if (percentage !in 0..100) {
        throw IllegalArgumentException(&quot;수치를 벗어남.&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;try, catch, finally&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 동일하게 try, catch, finally 절을 함께 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 다르게 함수가 던질 수 있는 예외를 명시할 필요가 없음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;try를 식으로 사용&lt;/h4&gt;
&lt;pre id=&quot;code_1639231082071&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun readNumber(reader: BufferedReader) {
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        null
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Kotlin</category>
      <category>KotlinInAction</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/20</guid>
      <comments>https://0n1dev.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 6 Dec 2021 22:17:36 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin In Action #0</title>
      <link>https://0n1dev.tistory.com/19</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin&amp;nbsp;In&amp;nbsp;Action&amp;nbsp;#0&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코틀린이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바 플랫폼에서 돌아가는 새로운 프로그래밍 언어.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간결하고 실용적.&lt;/li&gt;
&lt;li&gt;자바 코드와 상호운용적.&lt;/li&gt;
&lt;li&gt;정적 타입 지정 언어.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 구성 요소의 타입을 컴파일 시점에 알 수 있음.&lt;/li&gt;
&lt;li&gt;객체의 필드나 메서드를 사용할 때 컴파일러가 타입을 검증.&lt;/li&gt;
&lt;li&gt;자바와 다르게 타입을 추론해주기 때문에 변수의 타입을 프로그래머가 직접 명시할 필요 없음.&lt;/li&gt;
&lt;li&gt;정적 타입의 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 시점에 어떤 메서드를 호출할지 알아내는 과정이 필요 없으므로 메서드 호출이 더 빠름.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;신뢰성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일러가 프로그램의 정확성을 검증하기 때문에 실행 시 프로그램이 오류로 중단될 가능성이 적음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유지 보수성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드에서 다루는 객체가 어떤 타입에 속하는지 알 수 있기 때문에 처음 보는 코드를 다룰 때도 쉬움.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도구 지원
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적 타입 지정을 활용하면 더 안전하게 리팩토링 할 수 있고, 도구는 더 정확한 코드 완성 기능을 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Null이 될 수 있는 타입을 지원.&lt;/li&gt;
&lt;li&gt;함수 타입.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바를 사용중인 곳이라면 거의 대부분 코틀린 사용이 가능.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수형 프로그래밍과 객체지향 프로그래밍&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일급 객체(First-Class Citizen) 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 일반 값처럼 다룰 수 있음.&lt;/li&gt;
&lt;li&gt;함수를 변수에 저장할 수 있음.&lt;/li&gt;
&lt;li&gt;함수를 인자로 다른 함수에 전달할 수 있으며, 새로운 함수를 만들어서 반환할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;불변성(Immutability)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수형 프로그래밍에서는 일단 만들어지고 나면 내부 상태가 절대로 바뀌지 않는 불볍 객체를 사용해 프로그램을 작성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부수 효과(Side Effect) 없음&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력이 같으면 항상 같은 결과를 내놓는 순수 함수를 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코틀린의 철학&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실용성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대규모 시스템을 개발해본 다년간의 IT업계의 경험을 바탕으로 이루어짐.&lt;/li&gt;
&lt;li&gt;수많은 개발자들의 사용에 잘 들어맞을 수 있게 주의 깊게 언어 특성을 선택.&lt;/li&gt;
&lt;li&gt;어느 특정 프로그래밍 스타일이나 패러다임을 사용할 것을 강제로 요구하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;간결성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 간결할수록 내용을 파악하기 쉬움.&lt;/li&gt;
&lt;li&gt;설계와 각 부분의 역할을 잘 표현해주는 적절한 이름도 중요하지만 해당 언어가 얼마나 간결한 언어인지 중요.&lt;/li&gt;
&lt;li&gt;자바의 게터, 세터, 생성자 파라메터를 필드에 대입하기 위한 로직 등을 코틀린은 묵시적으로 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;안전성&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;절대적인 오류 방지는 아님. (모든 오류를 막을 수는 없음)&lt;/li&gt;
&lt;li&gt;JVM을 사용한다는 것 자체가 상당한 안전성을 보장한다는 의미.&lt;/li&gt;
&lt;li&gt;코틀린은 NPE를 없애기 위해 노력.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상호운용성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 기존 라이브러리를 그대로 사용 할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/Kotlin</category>
      <category>KotlinInAction</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/19</guid>
      <comments>https://0n1dev.tistory.com/19#entry19comment</comments>
      <pubDate>Sun, 5 Dec 2021 22:12:39 +0900</pubDate>
    </item>
    <item>
      <title>실전! Querydsl #1</title>
      <link>https://0n1dev.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전!&amp;nbsp;Querydsl&amp;nbsp;#1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryDSL 은 프로젝트 내의 @Entity 어노테이션을 선언한 클래스를 탐색하고 JPAAnnotationProcessor을 이용하여 Q클래스를 생성함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q클래스를 활용하면 자바 언어가 가지는 정적코드의 장점을 활용하여 안전한 쿼리문을 작성할 수 있음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 환경설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의에서는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;com.ewerk.gradle.plugins.querydsl&lt;/span&gt; 플러그인을 활용하지만, 플러그인 방식만 있는것이 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 실무에서 프로젝트의 버전들을 올리는 마이그레이션 작업을 진행했는데 QueryDSL로 인해 문제가 발생한적이 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플러그인 방식&lt;/li&gt;
&lt;li&gt;Annotation Processor 방식&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <category>querydsl</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/18</guid>
      <comments>https://0n1dev.tistory.com/18#entry18comment</comments>
      <pubDate>Sat, 4 Dec 2021 19:04:05 +0900</pubDate>
    </item>
    <item>
      <title>실전! Querydsl #0</title>
      <link>https://0n1dev.tistory.com/17</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전! Querydsl #0&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 자바 백엔드 기술은 주로 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;스프링 부터 + 스프링 데이터 JPA&lt;/span&gt;를 조합으로 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기술로 해결이 불가능한 부분이 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 쿼리&lt;/li&gt;
&lt;li&gt;동적 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하는것을 도와주는게 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;QueryDSL&lt;/span&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;QueryDSL&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리를 자바 코드로 작성.&lt;/li&gt;
&lt;li&gt;문법 오류를 컴파일 시점에 체크.&lt;/li&gt;
&lt;li&gt;동적 쿼리를 깔끔하게 해결.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPQL vs QueryDSL&lt;/h3&gt;
&lt;pre id=&quot;code_1638534782239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    void jpql() {
        String username = &quot;Shin&quot;;
        String query = &quot;select m from Member m &quot; +
                &quot;where m.username = :username&quot;;
        List&amp;lt;Member&amp;gt; result = em.createQuery(query, Member.class)
                .getResultList();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1638610990229&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    void querydsl() {
        String username = &quot;Shin&quot;;

        jpaQueryFactory
                .select(member)
                .from(member)
                .where(member.username.eq(username))
                .fetch();
    }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Book &amp;amp; Lecture/Inflearn</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/17</guid>
      <comments>https://0n1dev.tistory.com/17#entry17comment</comments>
      <pubDate>Fri, 3 Dec 2021 21:23:00 +0900</pubDate>
    </item>
    <item>
      <title>5.4 MySQL의 격리 수준</title>
      <link>https://0n1dev.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.4 MySQL의 격리 수준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 격리 수준이란? 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격리 수준이 높아질수록 격리 정도가 높아지며, 동시 처리 성능이 떨어짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; SERIALZABLE 격리 숮준이 아니라면 크게 성능의 개선이나 저하가 발생하지 않음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;격리 수준&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;READ UNCOMMITTED
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DIRTY READ, NON-REPEATABLE READ, PHANTOM READ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;READ COMMITTED
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NON_REPEATABLE READ, PHANTOM READ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;REPEATABLE READ
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PHANTOM READ(InnoDB는 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SERIALIZABLE&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부정합&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DIRTY READ&lt;/li&gt;
&lt;li&gt;NON_REPEATABLE READ&lt;/li&gt;
&lt;li&gt;PHANTOM READ&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4.1 READ UNCOMMITTED&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COMMIT이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 보임.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A, B 트랜잭션이 존재한다고 했을때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 트랜잭션이 0n1dev, 5000 이라는 값을 삽입 (아직 COMMIT 안함)&lt;/li&gt;
&lt;li&gt;B 트랜잭션이 5000을 조회하면 0n1dev, 5000이 조회 됨&lt;/li&gt;
&lt;li&gt;A 트랜잭션 COMMIT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, B 트랜잭션이 조회 후 A 트랜잭션에서 예외가 발생 했을때 ROLLBACK이 되어도 B 트랜잭션에서 0n1dev, 5000이 정상적인 데이터라고 판단.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 어떤 트랜잭션에서 처리한 작업이 완료되지 않았는데 다른 트랜잭션에서 볼 수 있는 현상을 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;더티 리드&lt;/span&gt;라 함.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4.2 READ COMMITTED&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클 DBMS에서 기본으로 사용되는 격리 수준.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더티 리드는 발생하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COMMIT이 완료된 데이터만 다른 트랜잭션에서 조회 가능.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A, B 트랜잭션이 존재한다고 했을때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 트랜잭션이 0n1dev, 5000 이라는 값을 0n1dev, 7000으로 업데이트 (아직 COMMIT 안함)&lt;/li&gt;
&lt;li&gt;B 트랜잭션이 0n1dev를 조회하면 0n1dev, 5000이 조회 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경 된 값은 테이블에 기록되고, 이전 값은 언두 영역으로 백업 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A 트랜잭션 COMMIT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;NON-REPEATABLE READ&lt;/span&gt;가 발생 함. 하나의 트랜잭션 내에서 동일한 SELECT 쿼리를 실행하면 항상 같은 결과를 가져와야 함. (정합성에 어긋남)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4.3 REPEATABLE READ&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 InnoDB 스토리지 엔진에서 기본으로 사용되는 격리 수준.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더티 리드와 NON-REPEATABLE READ가 발생하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;READ COMMITTED와 동일하게 MVCC를 이용해 COMMIT 전 데이터를 보여줌. (언두 영역에 백업된 레코드의 여러 버전 중 몇 번째 이전 버전까지 찾아 들어가야 하느냐가 다름.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A, B 트랜잭션이 존재한다고 했을때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;B 트랜잭션이 0n1dev, 5000 조회&lt;/li&gt;
&lt;li&gt;A 트랜잭션이 0n1dev, 5000을 0n1dev, 7000으로 업데이트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언두 로그에 변경 전 데이터 0n1dev, 5000이 남아 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A 트랜잭션 COMMIT&lt;/li&gt;
&lt;li&gt;B 트랜잭션 0n1dev 조회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언두 로그를 통해서 0n1dev, 5000을 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; PHANTOM READ가 발생.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4.4 SERIALIZABLE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 격리 수준이면서 동시에 가장 엄격한 격리 수준.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 처리 성능이 다른 트랜잭션의 격리 수준보다 떨어짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진에서는 갭 락과 넥스트 키 락 덕분에 REPEATABLE READ 격리 수준에서도 PHANTOM READ가 발생하지 않음.&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <category>Real MySQL8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/16</guid>
      <comments>https://0n1dev.tistory.com/16#entry16comment</comments>
      <pubDate>Wed, 1 Dec 2021 23:12:14 +0900</pubDate>
    </item>
    <item>
      <title>대용량 서비스를 위한 아키텍처 with Redis #0</title>
      <link>https://0n1dev.tistory.com/15</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Chapter 0. 대규모 서비스의 특성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대규모 서비스란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;많은 사용자 트래픽&lt;/li&gt;
&lt;li&gt;많은 사용자 데이터&lt;/li&gt;
&lt;li&gt;사용자 트래픽/데이터의 빠른 증가 (&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;서비스 증가&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스 이용량 증가에 따른 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트 업에서 서비스의 인기가 좋아, 트래픽이 감당할 수 없을 정도로 늘어난다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로인해 몇일 동안 서비스가 안된다면 어떤 일이 벌어질까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만드는 서비스는 트래픽/데이터의 증가에도 묹제없이 서비스가 가능할까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대규모 서비스가 가져야 할 특성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성 (Elastic)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스의 가용용량을 늘이고 줄이는 것이 쉬워야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장애 회복성 (Resiliency)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스에 장애가 일어났을 때 가능한 메뉴얼한 처리 없이 자동적으로 회복되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자동화 (Automation)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포부터 장애처리 등 대부분 자동화 되어서 버튼 클릭 한 두번 정도로 진행되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모니터링 (Monitoring)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 상태는 항상 모니터링 되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확장성 (Elastic)&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;트래픽이 늘어나서 서버를 추가해야 한다면?&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추가가 되어야 하는 서버의 종류는?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Web Server&lt;/li&gt;
&lt;li&gt;DB Server&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이런 종류의 서버들이 쉽게 추가 가능할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;트래픽이 줄어서 서버를 줄여야 한다면?&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제거가 되어야 하는 서버의 종류는?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잘 안쓰는 서버&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이런 종류의 서버들이 쉽게 제거될 수 있을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장애 회복성 (Resiliency)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FailOver&lt;/li&gt;
&lt;li&gt;Replication&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;서비스 중인 API서버 한, 두대가 장애가 나면 서비스는 계속 지속될 수 있을까?&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;서비스 데이터를 저장하는 DB서버가 장애가 나면 서비스는 계속 지속될 수 있을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애 회복성을 위해서는 SPOF(Single Point of Failure)가 없어져야 함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 지점에 장애가 발생하면 전체 서비스에 장애를 낼 수 있는 부분
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 서버가 장애가 발생하고, 해당 서버를 대체할 서버가 없으면 서비스의 동작에 문제가 생김&lt;/li&gt;
&lt;li&gt;인증 서버에 장애가 발생하면 인증이 불가능하여 서비스 이용이 불가능&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;한대의 물리 서버는 무조건 SPOF가 존재&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SPOF를 제거 하려면 모든 부분에서 최소 이중화가 되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화 (Automation)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;서비스 운영의 많은 부분이 자동화가 되어야 함&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람이 손으로 직접 건드리는 것은 도리어 새로운 장애의 씨앗이 됨 (Human Error)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;IaC(Infrastructure as code)가 중요함&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/FastCampus</category>
      <category>대용량 서비스를 위한 아키텍처 with Redis</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/15</guid>
      <comments>https://0n1dev.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 29 Nov 2021 23:00:46 +0900</pubDate>
    </item>
    <item>
      <title>5.3 InnoDB 스토리지 엔진 잠금</title>
      <link>https://0n1dev.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.3&amp;nbsp;InnoDB&amp;nbsp;스토리지&amp;nbsp;엔진&amp;nbsp;잠금&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 제공하는 잠금과 별개로 스토리지 엔진 내부에서 레코드 기반의 잠금 방식을 탑재하고 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 MyISAM보다 훨씬 뛰어난 동시성 처리를 제공할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 버전에서 InnoDB의 트랜잭션과 잠금, 그리고 잠금 대기 중인 트랜잭션의 목록을 조회할 수 있는 방법이 도입.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;information_schema 데이터베이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;INNODB_TRX&lt;/li&gt;
&lt;li&gt;INNODB_LOCKS&lt;/li&gt;
&lt;li&gt;INNODB_LOCK_WAITS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB의 잠금에 대한 모니터링도 강화되면서 Performance Schema를 이용해 InnoDB 스토리지 엔진의 내부 잠금(세마포어)에 대한 모니터링 방법도 추가 됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.3.1 InnoDB 스토리지 엔진의 잠금&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 상용 DBMS와는 조금 다르게 InnoDB 스토리지 엔진에서는 레코드 락뿐 아니라 레코드와 레코드 사이의 간격을 잠그는 갭(GAP) 락이라는 것이 존재하는데, InnoDB 스토리지 엔진의 레코드 락과 레코드 간의 간격을 잠그는 갭 락을 보여 줌.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.3.1.1 레코드 락&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레코드 자체만을 잠그는 것&lt;/li&gt;
&lt;li&gt;다른 상용 DBMS의 레코드 락과 동일한 역할을 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InnoDB 스토리지 엔진은 레코드 자체가 아닌 인덱스의 레코드를 잠금&lt;/li&gt;
&lt;li&gt;인덱스가 하나도 없어도 내부적으로 자동 생성된 클러스터 인덱스를 이용해 잠금을 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대부분의 보조 인덱스를 이용한 변경 작업은 넥스트 락 or 갭 락 사용&lt;/li&gt;
&lt;li&gt;프라이머리 키 or 유니크 인덱스에 의한 변경 작업에서는 갭에 대해서는 잠그지 않고 레코드 자체에 대해서만 락을 걸음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.3.1.2 갭 락&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레코드와 바로 인접한 레코드 사이의 간격만 잠그는 것을 의미&lt;/li&gt;
&lt;li&gt;레코드와 레코드 사이의 간격에 새로운 레코드가 생성(INSERT)되는 것을 제어하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.3.1.3 넥스트 키 락&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레코드 락과 갭 락을 합쳐 놓은 형태의 잠금&lt;/li&gt;
&lt;li&gt;STATEMENT 포맷의 바이너리 로그를 사용하는 MySQL 서버에서는 REPEATABLE READ 격리 수준을 사용&lt;/li&gt;
&lt;li&gt;innodb_locks_unsafe_for_binlog 시스템 변수가 비활성되면(0으로 설정) 변경을 위해 검색하는 레코드에 넥스트 키 락 방식으로 잠금 걸림&lt;/li&gt;
&lt;li&gt;InnoDB의 갭 락이나 넥스트 키 락은 바이너리 로그에 기록되는 쿼리가 레플리카 서버에서 실행될 때 소스 서버에서 만들어 낸 결과와 동일한 결과를 만들어내도록 보장하는 것이 주 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.3.1.4 자동 증가 락&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서는 자동 증가하는 숫자 값을 추출하기 위해 AUTO_INCREMENT라는 칼럼 속성을 제공 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AUTO_INCREMENT 칼럼이 사용된 테이블에 동시에 여러 레코드가 INSERT되는 경우, 저장되는 각 레코드는 중복되지 않고 저장된 순서대로 증가하는 일련번호 값을 가져야 함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;innodb_autoinc_lock_mode = 0
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 5.0과 동일한 잠금 방식으로 모든 INSERT 문장은 자동 증가 락을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;innodb_autoinc_lock_mode = 1
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 건의 레코드를 INSERT하는 SQL 중 MySQL 서버가 INSERT되는 레코드의 건수를 정확히 예측할 수 있을 때 자동 증가 락을 사용하지 않고, 훨씬 가볍고 빠른 래치를 이용해서 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;innodb_autoinc_lock_mode = 2
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InnoDB 스토리지 엔진은 절대 자동 증가 락을 걸지 않고 경량화 된 래치를 사용.&lt;/li&gt;
&lt;li&gt;이 설정에서는 하나의 INSERT 문장으로 INSERT되는 레코드라고 하더라도 연속된 자동 증가 값을 보장하지는 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.3.2 인덱스와 잠금&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB의 잠금과 인덱스는 상당히 중요한 연관 관계가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB의 잠금은 레코드를 잠그는 것이 아닌 인덱스를 잠그는 방식으로 처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경해야 할 레코드를 찾기 위해 검색한 인덱스의 레코드를 모두 락을 걸어야 함.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.3.3 레코드 수준의 잠금 확인 및 해제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진을 사용하는 테이블의 레코드 수준 잠근은 테이블 수준의 잠금보다는 조금 더 복잡함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 잠금에서는 잠금의 대상이 테이블 자체이므로 쉽게 문제의 원인이 발견되고 해결될 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드 수준의 잠금은 테이블의 레코드 각각에 잠금이 걸리므로 그 레코드가 자주 사용되지 않는다면 오랜 시간 동안 잠겨진 상태로 남아 있어도 잘 발견되지 않음.&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <category>RealMySQL8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/14</guid>
      <comments>https://0n1dev.tistory.com/14#entry14comment</comments>
      <pubDate>Sun, 28 Nov 2021 23:28:10 +0900</pubDate>
    </item>
    <item>
      <title>0. 시간 복잡도 &amp;amp; 공간 복잡도</title>
      <link>https://0n1dev.tistory.com/13</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;0.&amp;nbsp;시간&amp;nbsp;복잡도&amp;nbsp;&amp;amp;&amp;nbsp;공간&amp;nbsp;복잡도&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시간 복잡도&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 연산의 실행 횟수로 수행 시간을 평가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 연산&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 입출력&lt;/li&gt;
&lt;li&gt;산술 연산&lt;/li&gt;
&lt;li&gt;제어 연산&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Best Case
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빅 오메가 표기&lt;/li&gt;
&lt;li&gt;최선의 시나리오&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Worst Case
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빅 오 표기&lt;/li&gt;
&lt;li&gt;최악의 시나리오&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Average Case
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빅 세타 표기&lt;/li&gt;
&lt;li&gt;평균 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 알고리즘은 &lt;code&gt;최악의 경우&lt;/code&gt;로 성능 파악. 즉 빅 오 표기인 &lt;code&gt;Worst Case&lt;/code&gt;로 파악&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빅 오 표기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;O(1) - Constant&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터 크기와 상관없이 항상 일정한 처리 시간이 걸림&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;    public void printNumber(int n) {
        System.out.println(n);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;O(log₂n) - Logarithmic&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터 크기가 커질수록 연산 횟수가 log₂n에 비례해서 처리 시간 증가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        for (int i = 0; i &amp;lt; n; i *= 2) {
            ...
        }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;O(n) - Linear&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터 크기와 비례해서 처리 시간 증가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;        for (int i = 0; i &amp;lt; n; i++) {
            ...
        }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;O(nlog₂n) - Linear-Logarithmic&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터 크기가 커질수록 처리 시간이 로그 배 만큼 증가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Merge Sort&lt;/li&gt;
&lt;li&gt;Heap Sort&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;O(n&amp;sup2;) - Quadratic&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터 크기가 커질수록 처리 시간이 제곱으로 증가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;        for (int i = 0; i &amp;lt; n; i++) {
            for (int j = 0; j &amp;lt; n; j++) {
                ...
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;O(2ⁿ) - Exponential&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터 크기가 커질수록 처리 시간이 2ⁿ으로 증가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;    public int printNumber(int n) {
        if (n &amp;lt;= 1) {
            return n;
        }

        return printNumber(n - 1) + printNumber(n - 2);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 복잡도&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘에서 사용하는 메모리의 양&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;    public int printNumber(int n) {
        int a = n;
        int[] b = new int[n];

        for (int i = 0; i &amp;lt; n; i++) {
            b[i] = i;
        }

        return b[a];
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a = 4byte&lt;/li&gt;
&lt;li&gt;b = 4byte * n&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 4n + 4 =&amp;gt; 데이터 크기가 증가할수록 선형적으로 증가하기 때문에 공간 복잡도는 &lt;code&gt;O(n)&lt;/code&gt;다.&lt;/p&gt;</description>
      <category>Programming/Algorithm</category>
      <category>알고리즘</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/13</guid>
      <comments>https://0n1dev.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 25 Nov 2021 23:04:48 +0900</pubDate>
    </item>
    <item>
      <title>5.2 MySQL 엔진의 잠금</title>
      <link>https://0n1dev.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.2 MySQL 엔진의 잠금&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 사용되는 잠금은 크게 스토리지 엔진 레벨과 MySQL 엔진 레벨로 나눌 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지 엔진 레벨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지 엔진 간 상호 영향을 미치지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MySQL 엔진 레벨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지 엔진을 제외한 나머지 부분&lt;/li&gt;
&lt;li&gt;모든 스토리지 엔진에 영향을 미침&lt;/li&gt;
&lt;li&gt;테이블락 (테이블 데이터 동기화)&lt;/li&gt;
&lt;li&gt;메타데이터 락 (테이블 구조를 잠금)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2.1 글로벌 락&lt;/h3&gt;
&lt;pre id=&quot;code_1637845454276&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FLUSH TABLES WITH READ LOCK&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서 제공하는 잠금 가운데 가장 큰 범위.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 세션에서 글로벌 락을 휙득하면 다른 세션에서 SELECT를 제외한 대부분의 DDL 문장이나 DML 문장을 실행하는 경우 글로벌 락이 해제될 때까지 해당 문장이 대기 상태로 남음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영향을 미치는 범위는 MySQL 서버 전체. (테이블 뿐만 아니라 데이터베이스가 다르더라도 영향.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MySQL 8.0 부터 바뀐점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB가 기본 스토리지 엔진으로 채택되면서 조금 더 가벼운 글로벌 락이 생김. (백업 락)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB 스토리지 엔진은 트랜잭션을 지원하기 때문에 일관된 데이터 상태를 위해 모든 데이터 변경 작업을 멈출 필요가 없음.&lt;/p&gt;
&lt;pre id=&quot;code_1637846254674&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LOCK INSTANCE FOR BACKUP;
--// 백업 실행
UNLOCK INSTANCE;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 세션에서 백업 락을 휙득하면 모든 세션에서 다음과 같은 일을 학 수 없음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 및 테이블 등 모든 객체 생성 및 변경, 삭제&lt;/li&gt;
&lt;li&gt;REPAIR TABLE (특정 테이블 복구)과 OPTIMIZE TABLE (특정 테이블 최적화) 명령&lt;/li&gt;
&lt;li&gt;사용자 관리 및 비밀번호 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 테이블의 데이터 변경은 허용, 백업의 실패를 막기 위해 DDL 명령이 실행되면 복제를 일시 중지 함&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2.2 테이블 락&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 테이블 단위로 설정되는 잠금. 명시적 or 묵시적으로 특정 테이블의 락을 획득할 수 있음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;명시적&lt;/h4&gt;
&lt;pre id=&quot;code_1637846853337&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LOCK TABLES table_name [ READ | WRITE ]
UNLOCK TABLES&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 글로벌 락과 동일하게 온라인 작업에 상당한 영향을 미치기 때문에 특별한 상황 아니면 사용 X&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;묵시적&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyISAM이나 MEMORY 테이블에 데이터를 변경하는 쿼리를 실행하면 발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 변경되는 테이블을 잠금 설정하고 데이터를 변경한 후, 즉시 잠금을 해제하는 형태로 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB는 스토리지 엔진 차원에서 레코드 기반의 잠금을 제공하기 때문에 단순 데이터 변경 쿼리로 인해 묵시적인 테이블 락이 설정되지는 않음. (DDL이 변경 될 때만 테이블 락이 설정)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2.3 네임드 락&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET_LOCK() 함수를 이용해 임의의 문자열에 대해 잠금을 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 사용자가 지정한 문자열(String)에 대해 획득하고 반납(해제)하는 잠금.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;네임드 락을 사용하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 서버 1대에 5대의 웹 서버가 접속해서 서비스하는 상황에 5대의 웹 서버가 어떤 정보를 동기화해야 하는 상황이면 여러 클라이언트가 상호 동기화를 처리해야 할 때 네임드 락을 이용.&lt;/p&gt;
&lt;pre id=&quot;code_1637847920190&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- // &quot;mylock&quot;이라는 문자열에 대해 잠금 획득 2초 동안
SELECT GET_LOCK('mylock',2);
-- // 잠금 설정이 되어 있는지 확인 (잠금이면 0 아니면 1)
SELECT IS_FREE_LOCK('mylock');
-- // 잠금 해제 (해제한 경우 1, 아닌 경우 1 or NULL)
SELECT RELEASE_LOCK('mylock');
-- // 잠금 설정이 되어 있는지 확인 (잠금이면 0 아니면 1)
SELECT IS_FREE_LOCK('mylock');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치와 같은 프로그램에서 한번에 많은 레코드를 변경하는 쿼리가 데드락의 원인이 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램의 실행 시간을 분산하거나 프로그램의 코드를 수정해서 데드락을 최소화할 수 있지만, 간단하지 않고 완전한 해결책이 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일 데이터를 변경하거나 참조하는 프로그램끼리 분류하여 네임드 락을 걸고 쿼리를 실행하면 아주 간단히 해결.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MySQL 8.0부터 바뀐점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네임드 락을 중첩해서 사용 가능&lt;/li&gt;
&lt;li&gt;현재 세션에서 획득한 네임드 락을 한번에 모두 해제 가능 (SELECT RELEASE_ALL_LOCKS();)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2.4 메타데이터 락&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 객체(대표적으로 테이블이나 뷰 등)의 이름이나 구조를 변경하는 경우 획득하는 잠금.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시적으로 획득하거나 해제하는 것이 아닌 &quot;RENAME TABLE tab_a TO tab_b&quot;같이 테이블의 이름을 변경하는 경우 자동으로 획득하는 잠금.&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <category>RealMySQL8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/11</guid>
      <comments>https://0n1dev.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 24 Nov 2021 21:37:07 +0900</pubDate>
    </item>
    <item>
      <title>5.1 트랜잭션</title>
      <link>https://0n1dev.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.1 트랜잭션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션을 지원하지 않는 MyISAM과 트랜잭션을 지원하는 InnoDB의 처리 방식 차이를 살펴보자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1.1 MySQL에서의 트랜잭션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 꼭 여러 개의 변경 작업을 수행하는 쿼리가 조합됐을 때만 의미 있는 개념이 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 논리적인 작업 셋에 쿼리의 갯수와 상관없이 논리적인 작업 셋 자체가 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;100% COMMIT 되거나 오류가 있을 때 ROLLBACK 되는 것을 보장&lt;/span&gt;해주는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MyISAM 엔진 환경 구성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1637587474524&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE tab_myisam (
    fdpk INT NOT NULL,
    PRIMARY KEY (fdpk)
) ENGINE=MyISAM;

INSERT INTO tab_myisam (fdpk) VALUES (3);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MyISAM 엔진 쿼리 실행&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1637587752614&quot; class=&quot;sql&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;real_mysql_test&amp;gt; INSERT INTO tab_myisam VALUES (1), (2), (3)
[2021-11-22 22:27:10] [23000][1062] Duplicate entry '3' for key 'tab_myisam.PRIMARY'&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MyISAM 엔진 결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1637587805914&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM tab_myisam;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-11-22 223027.png&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpRl75/btrlV0LejoT/qNbup9YoKKkxLlv0ZOIwK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpRl75/btrlV0LejoT/qNbup9YoKKkxLlv0ZOIwK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpRl75/btrlV0LejoT/qNbup9YoKKkxLlv0ZOIwK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpRl75%2FbtrlV0LejoT%2FqNbup9YoKKkxLlv0ZOIwK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;170&quot; data-filename=&quot;화면 캡처 2021-11-22 223027.png&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;InnoDB 엔진 환경 구성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1637587592819&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE tab_innodb (
    fdpk INT NOT NULL,
    PRIMARY KEY (fdpk)
) ENGINE=INNODB;

INSERT INTO tab_innodb (fdpk) VALUES (3);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;InnoDB 엔진 쿼리 실행&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1637587895594&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;real_mysql_test&amp;gt; INSERT INTO tab_innodb VALUES (1), (2), (3)
[2021-11-22 22:31:16] [23000][1062] Duplicate entry '3' for key 'tab_innodb.PRIMARY'&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;InnoDB 엔진 결과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1637587925818&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM tab_innodb;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-11-22 223228.png&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLaUmG/btrlIlRvZoe/bGJcg4J1fFpoM6udLvb0Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLaUmG/btrlIlRvZoe/bGJcg4J1fFpoM6udLvb0Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLaUmG/btrlIlRvZoe/bGJcg4J1fFpoM6udLvb0Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLaUmG%2FbtrlIlRvZoe%2FbGJcg4J1fFpoM6udLvb0Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;185&quot; data-filename=&quot;화면 캡처 2021-11-22 223228.png&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 엔진 모두 Primary Key 중복 오류로 쿼리가 실패.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyISAM 엔진은 오류에도 불구하고 '1'과 '2'가 삽입 됨, InnoDB 엔진은 트랜잭션의 원칙대로 원 상태로 ROLLBACK.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;MyISAM 처럼 트랜잭션을 지원하지 않으면 테이블 데이터의 정합성을 맞추는데 상당히 어려운 문제를 만듬.&lt;/span&gt;&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1.2 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 또한 DBMS의 커넥션과 동일하게 꼭 필요한 최소의 코드에만 적용하는 것이 좋음. (프로그램 코드에서 트랜잭션의 범위를 최소화하라는 의미.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터베이스 커넥션을 가지고 있는 범위와 트랜잭션이 활성화돼 있는 프로그램의 범위를 최소화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크크 작업이 있는 경우 반드시 트랜잭션에서 배제&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <category>RealMySQL8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/10</guid>
      <comments>https://0n1dev.tistory.com/10#entry10comment</comments>
      <pubDate>Mon, 22 Nov 2021 22:47:26 +0900</pubDate>
    </item>
    <item>
      <title>5. 트랜잭션과 잠금</title>
      <link>https://0n1dev.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.&amp;nbsp;트랜잭션과&amp;nbsp;잠금&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 작업의 완전성을 보장해주는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 논리적인 작업 셋을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;모두 완벽하게 처리&lt;/span&gt;하거나, 처리하지 못할 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;원 상태로 복구&lt;/span&gt;해서 작업의 일부만 적용되는 현상(Partial update)이 발생하지 않게 만들어주는 기능.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;잠금(Lock)과 트랜잭션은 서로 비슷한 개념 같지만 사실 잠금은 동시성을 제어하기 위한 기능이고 트랜잭션은 데이터의 정합성을 보장하기 위한 기능&lt;/blockquote&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <category>RealMySQL8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/9</guid>
      <comments>https://0n1dev.tistory.com/9#entry9comment</comments>
      <pubDate>Mon, 22 Nov 2021 22:46:37 +0900</pubDate>
    </item>
    <item>
      <title>0. 실습환경</title>
      <link>https://0n1dev.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 Real MySQL 8.0 책을 보고 요약한 내용입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0.&amp;nbsp;실습환경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Docker&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MySQL 8.0.27&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DataGrip&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0.1 도커 이미지 다운로드 및 컨테이너 실행&lt;/h3&gt;
&lt;pre id=&quot;code_1637587070251&quot; class=&quot;shell&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=xptmxm -d mysql:8.0.27&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0.2 DataGrip으로 Connection&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-11-22 221936.png&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwEObz/btrlUPwpiaF/B3HOJlDfqbrvfKGLI4cfKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwEObz/btrlUPwpiaF/B3HOJlDfqbrvfKGLI4cfKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwEObz/btrlUPwpiaF/B3HOJlDfqbrvfKGLI4cfKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwEObz%2FbtrlUPwpiaF%2FB3HOJlDfqbrvfKGLI4cfKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;213&quot; data-filename=&quot;화면 캡처 2021-11-22 221936.png&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Book &amp;amp; Lecture/Real MySQL 8.0</category>
      <category>RealMySQL8.0</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/8</guid>
      <comments>https://0n1dev.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 22 Nov 2021 22:21:03 +0900</pubDate>
    </item>
    <item>
      <title>Github Profile에 Tistory RSS Feed 연결하기</title>
      <link>https://0n1dev.tistory.com/7</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github&amp;nbsp;Profile에&amp;nbsp;Tistory&amp;nbsp;RSS&amp;nbsp;Feed&amp;nbsp;연결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글을 보면 Github와 Tistory의 연동을 고민을 하다가 Python으로 작성한 코드를 Github Actions을 통해 지속적으로 업데이트 해주는 방법을 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.koo.gg/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2021.11.19 - [Log/이것저것] - 2021.11.19 - 블로그 시작&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637494739962&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;2021.11.19 - 블로그 시작&quot; data-og-description=&quot;2021.11.19&amp;nbsp;-&amp;nbsp;블로그&amp;nbsp;시작 블로그 시작부터 큰 난관에 봉착했다. 블로그 서비스를 제공하는 플랫폼의 다양성 때문이다. 솔직하게 말하면 블로그를 시작하려한게 이번이 처음이 아니다. 네이버 블&quot; data-og-host=&quot;blog.koo.gg&quot; data-og-source-url=&quot;https://blog.koo.gg/2&quot; data-og-url=&quot;https://blog.koo.gg/2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bSqR45/hyMq2iJuax/0zx9T8hASUewoP2OGvdNxK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cV7AzL/hyMqX2MybF/hAcFDKBKlapLAdNXrmLgP1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bx6qAl/hyMq4nktVn/ru38TpDvYipokg51dEkiy1/img.jpg?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600&quot;&gt;&lt;a href=&quot;https://blog.koo.gg/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.koo.gg/2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bSqR45/hyMq2iJuax/0zx9T8hASUewoP2OGvdNxK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cV7AzL/hyMqX2MybF/hAcFDKBKlapLAdNXrmLgP1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bx6qAl/hyMq4nktVn/ru38TpDvYipokg51dEkiy1/img.jpg?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;2021.11.19 - 블로그 시작&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2021.11.19&amp;nbsp;-&amp;nbsp;블로그&amp;nbsp;시작 블로그 시작부터 큰 난관에 봉착했다. 블로그 서비스를 제공하는 플랫폼의 다양성 때문이다. 솔직하게 말하면 블로그를 시작하려한게 이번이 처음이 아니다. 네이버 블&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.koo.gg&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Python을 Java로 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익숙하지 않은 Python을 그대로 가져다 쓰는것 보다는 주력 언어인 Java로 변경하여 자유롭게 커스터마이징 하는게 좋을것이라 판단하여 Java로 새로 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/0n1dev&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/0n1dev/0n1dev&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637494875358&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 0n1dev/0n1dev&quot; data-og-description=&quot;Contribute to 0n1dev/0n1dev development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/0n1dev/0n1dev&quot; data-og-url=&quot;https://github.com/0n1dev/0n1dev&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cYQbZ8/hyMq3hD0g4/aXhTfoQd0pwbxkoiRmo9m1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/0n1dev/0n1dev&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/0n1dev/0n1dev&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cYQbZ8/hyMq3hD0g4/aXhTfoQd0pwbxkoiRmo9m1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 0n1dev/0n1dev&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to 0n1dev/0n1dev development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 Python 코드의 불편한 점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 자체가 익숙하지 않아 수정이 어렵다. -&amp;gt; Java로 컨버팅하여 해결&lt;/li&gt;
&lt;li&gt;원본 텍스트가 하드코딩 되어있다. -&amp;gt; Profile.md 파일을 읽어오는 로직으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 그냥 내가 편하게 바꾼것 뿐이다. 요즘 블로그 글 작성에 빠져있다. 하지만 1일 1커밋이라는 목표도 있어서 어떻게 하면 두마리의 토끼를 다 잡을수 있을까라는 고민이 있었지만 의미 없는 커밋 보다는 이렇게 블로그로 글을 남기고 Github와 연동 시키는것도 좋은 결과라고 생각이된다.&lt;/p&gt;</description>
      <category>Log/이것저것</category>
      <category>깃허브</category>
      <category>블로그</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/7</guid>
      <comments>https://0n1dev.tistory.com/7#entry7comment</comments>
      <pubDate>Sun, 21 Nov 2021 20:47:38 +0900</pubDate>
    </item>
    <item>
      <title>이직 성공... 그리고 회고 - 1</title>
      <link>https://0n1dev.tistory.com/6</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이직&amp;nbsp;성공...&amp;nbsp;그리고&amp;nbsp;회고&amp;nbsp;-&amp;nbsp;1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 작성하는 시점에는 이미 다른 회사를 다니고있다. 전 직장에서의 생각을 기반으로 작성해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이직을 생각하는 주니어 개발자 혹은 본인 커리어에 대한 고민이 있다면 읽어봐도 좋을것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 글은 정답이 아니다... 경험이 부족하기에 틀린 부분이 많을것이다. 하지만 누군가 도움이 된다면 좋겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이직의 결심&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 6개월 정도 다니는 시점 커리어에 대해 많은 고민이 되기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 멘토링을 듣고 있던 상황인데 멘토님과 고민에 대해 대화를 해본 결과 &quot;이직을 해야겠다.&quot;는 생각에 확신이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이직에 대해 고민할 때 고려한 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 내가 회사에서 아직 할 수 있는게 남아있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 아직 경험이 부족하고, 내가 큰 기업의 문화를 평가할 수준이 아니라 생각은 들지만 나에게는 개발에 대한 열정이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 기존 레거시 코드 대부분은 PHP 코드였다. 그런말이 떠오른다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;장인은 도구를 탓하지 않는다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 나도 공감한다. PHP가 나쁜 언어라고는 생각하지 않는다. 그치만 분명 문제점도 존재한다고 생각된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 자체가 어려운 언어는 아니라 생각한다. 또, 개발자에게 높은 자유도를 준다고 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이것은 양날의 검이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 개발 커뮤니티에서 본 기억이 있는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;PHP는 나쁜 언어가 아니에요... PHP로 개발하는 개발자가 나쁜거지...&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반화 시키면 안되는 부분이지만 어느정도 공감되는 부분이 있다. 러닝커브가 낮기 때문에 나 또한 취업전에 NodeJS로 개발하다가 취업 후 바로 실무에 투입이 가능했다고 생각이된다. (회사 입장에서는 어느 누가 와도 빠르게 알려주고 일을 시키면 이득...?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 만큼 언어에 대한 이해도가 낮은 상태로 개발을 시작하니깐 개발을 하는건지 조금 의구심이 들기 시작했다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 최신 버전은 다르다고 하겠지만 회사에서는 낮은 버전의 PHP를 사용했기 때문에 Loose Typing으로 인해 흔히 말하는 Human Error가 자주 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 두 가지 문제 모두 PHP의 문제는 아니라 생각한다. 개발 문화의 문제일것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 다른 언어인 Java를 사용하고 싶은 이유는 먼저 국내 시장의 커뮤니티다. Java와 PHP 두 진영의 커뮤니티를 비교해보면 Java 진영의 커뮤니티가 더 활발하다고 느껴진다. (PHP도 잘되어 있는곳도 있겠지만 점점 시들시들 해지는것 같다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 투표 사진을 보면 전 세계적으로 볼 때 PHP 사용자 보다 Java 사용자가 더 많을것이라 추측이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-11-20 214228.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brZUN4/btrlIdYD28Y/cuEtdnX8HNoQRIIkpDdWNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brZUN4/btrlIdYD28Y/cuEtdnX8HNoQRIIkpDdWNK/img.png&quot; data-alt=&quot;Stack Overflow&amp;amp;amp;amp;nbsp;most-popular-technologies-language-prof&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brZUN4/btrlIdYD28Y/cuEtdnX8HNoQRIIkpDdWNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrZUN4%2FbtrlIdYD28Y%2FcuEtdnX8HNoQRIIkpDdWNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1462&quot; height=&quot;669&quot; data-filename=&quot;화면 캡처 2021-11-20 214228.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Stack Overflow&amp;amp;amp;nbsp;most-popular-technologies-language-prof&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 두번째는 PHP 코드를 보면 프론트와 백의 경계가 무너진듯한 코드가 많다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 확실하게 구분하여 코드를 작성하면 되겠지만.... 그러면 또 PHP의 그나마 있는 장점을 버리고 쓰는게 아닌가...싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 글을 남기면 분명 태클이 들어올것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접에서도 Java를 그렇게 쓰고 싶으면 직접 컨버팅하면 되지 않냐고 질문이 들어올것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 난 말할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이미 컨버팅했다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 컨버팅 했는데 왜 이직을 하냐고 물어보면 회사에서 웹 개발자 중 95% 이상이 PHP 개발자라 생각이된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 개발한 부분에 대해 같이 고민해줄 선배 개발자의 필요성을 절실하게 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 개발 문화의 한계점을 알게되어 이직을 결심하게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글은 시리즈다.&lt;/p&gt;</description>
      <category>Log/회고</category>
      <category>이직</category>
      <category>회고</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/6</guid>
      <comments>https://0n1dev.tistory.com/6#entry6comment</comments>
      <pubDate>Sat, 20 Nov 2021 21:55:47 +0900</pubDate>
    </item>
    <item>
      <title>카카오브레인 대외활동 코테 회고</title>
      <link>https://0n1dev.tistory.com/5</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;카카오브레인&amp;nbsp;대외활동&amp;nbsp;코테&amp;nbsp;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전부터 대외활동에 참여하고 싶었고, 이전에 기업과 관계없이 진행되는 대외활동을 지원했었는데 아쉽게 기회가 없었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우연히 카카오브레인에서 진행하는 대외활동 패스파인더에 지원하였고, 코딩테스트를 진행하고 회고를 남겨본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전형의 시작 서류&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;202110270924161944_l.jpg&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7bk7M/btrlEn9go3y/3tI1U2A6svx4KHBxJ2dKS0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7bk7M/btrlEn9go3y/3tI1U2A6svx4KHBxJ2dKS0/img.jpg&quot; data-alt=&quot;카카오브레인 패스파인더 포스터?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7bk7M/btrlEn9go3y/3tI1U2A6svx4KHBxJ2dKS0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7bk7M%2FbtrlEn9go3y%2F3tI1U2A6svx4KHBxJ2dKS0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;424&quot; data-filename=&quot;202110270924161944_l.jpg&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카카오브레인 패스파인더 포스터?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원 할 당시 대외활동이라는 키워드와 간접적으로 카카오 계열 특유의 개발 문화를 간접적으로 접해볼 기회라 생각했기에 채용연계형이라는 문구는 크게 신경써서 보질 않았다... (이게 후 폭풍이 올까...?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 우수 활동자에게는 정규직 제의가 있을수 있다는 문구는 봤는데 이직 준비하는 직장인이 아니라면 채용연계형이 크게 눈에 들어올까...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 나는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;소속 무관하게 활동&lt;/span&gt;이 가능하다는 문구를 보고 솔깃한 마음에 부랴부랴 서류를 작성하여 제출하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서류 경쟁률 100대 1?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;술마시고 있는데 문자로 서류 결과가 나왔다고 연락왔다. 두근 거리는 마음으로 메일을 확인한 결과 서류 합격이라는 기쁜 소식을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합격 메일에는 설문조사를 진행하는 폼을 같이 전달 받았는데 폼을 통해 들어가보면 아래와 같은 문구를 볼 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-11-20 205357.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XfxWS/btrlFKvR5Ya/Jw4bot0Tkcr0xQh9mrcno1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XfxWS/btrlFKvR5Ya/Jw4bot0Tkcr0xQh9mrcno1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XfxWS/btrlFKvR5Ya/Jw4bot0Tkcr0xQh9mrcno1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXfxWS%2FbtrlFKvR5Ya%2FJw4bot0Tkcr0xQh9mrcno1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;206&quot; data-filename=&quot;화면 캡처 2021-11-20 205357.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대외활동이라 서류에서 많은 사람들이 갈릴것 같지는 않았다. (솔직히 개발자를 판단하는건 대부분 코테라 생각하고 있었기 때문에 기분 좋으라는 문구라고 생각했다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;같이 지원한 사람들에게 확인해보니 나 빼고 다 불합격...?&lt;/span&gt;&quot; 구직활동을 위한 전형도 아닌데 괜히 긴장되는 순간이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대망의 코딩테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 패스파인더는 1기를 시작으로 계속해서 진행될 예정이라한다. (나는 이번이 마지막 기회다.... 경력 2년 이상은 지원 불가능...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해당 포스트에 문제 관련한 내용이 궁금해서 찾아보았다면, 뒤로가기를 눌러주길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안내 메일에 나와있듯 법적 책임을 받는다고한다... (무섭다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제풀이는 해커랭크에서 진행되었으며 5문제를 5시간동안 푸는거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해커랭크에서 이전에 코딩테스트 진행한 경험이 있었기 때문에 대략적으로 어떤식으로 진행될지 느낌이 있었다. (해커랭크 특유의 감성이 있다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 문제를 풀때 1번 문제를 생각없이 풀다가 효율성에서 통과를 못하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문제를 풀고 4번 문제에서 효율성이랑 싸우고 있다가 시간이 없어서 다시 1번 문제를 봤더니 애초에 잘못 접근해서 문제를 풀었다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 &quot;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;3문제 통과, 1문제 테스트케이스(12/15), 1문제 못 품&lt;/span&gt;&quot; 부분 점수가 있으면 3.5...? 3.8...? 정도 받은것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;느낀점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대외활동이 하고싶어서 지원하게 되었지만 꼭 구직활동을 하는게 아니더라도 코딩테스트나 면접은 한번씩 보는것도 나쁘지는 않을것 같다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 자신이 지금 어떤 상황에 놓여있는지 판단하는 기준이 될 것이다.&lt;/p&gt;</description>
      <category>Log/회고</category>
      <category>대외활동</category>
      <category>카카오브레인</category>
      <category>코테</category>
      <category>회고</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/5</guid>
      <comments>https://0n1dev.tistory.com/5#entry5comment</comments>
      <pubDate>Sat, 20 Nov 2021 21:14:56 +0900</pubDate>
    </item>
    <item>
      <title>2020년 마무리 목표</title>
      <link>https://0n1dev.tistory.com/4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- 주 2~3회 알고리즘 문제 풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Real MySQL 1, 2권 완독&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- QueryDSL 강의 완독&lt;/p&gt;</description>
      <category>Log/목표</category>
      <category>2020년목표</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/4</guid>
      <comments>https://0n1dev.tistory.com/4#entry4comment</comments>
      <pubDate>Fri, 19 Nov 2021 22:15:16 +0900</pubDate>
    </item>
    <item>
      <title>첫 직장 아프리카 TV의 늦은 회고</title>
      <link>https://0n1dev.tistory.com/3</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫&amp;nbsp;직장&amp;nbsp;아프리카&amp;nbsp;TV의&amp;nbsp;늦은&amp;nbsp;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 늦은 회고다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2020년 1월에 입사하여 전 직장이라는 타이틀로 남겨진 회사지만 인생의 전환점을 가져다 준 회사가 아닐까 하는 생각이 들었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인생을 바꾼 도전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늦은 회고지만 아직도 기억이 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2019년 말 처음에 가산 디지털단지에 있는 모 게임 회사에 면접을 보고 최종 합격하여 출근 일을 기다리고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무것도 모르는 상태로 지원하여 커리어와 연봉에 대해 크게 느껴지는게 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출근 3일 전 아프리카 TV로 부터 서류 합격 메일을 받았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pylsu/btrlr6mtBpk/cRkjRgAIa2GGMvu6uZnpMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pylsu/btrlr6mtBpk/cRkjRgAIa2GGMvu6uZnpMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pylsu/btrlr6mtBpk/cRkjRgAIa2GGMvu6uZnpMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpylsu%2Fbtrlr6mtBpk%2FcRkjRgAIa2GGMvu6uZnpMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;742&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회고를 쓰다가 생각나서 메일을 찾아보니 역시나 합격 안내 메일을 찾아 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 전형의 시작인 서류합격이라 기존에 합격한 회사를 두고 면접을 보러가기 겁났지만 도전해볼 가치가 있다고 생각 되어 기존 회사는 입사를 포기하게 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;포기랑 바꾼 기쁨&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서류 이후 전형은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 테스트 + 1차 면접 -&amp;gt; BtoB (신입 공채라 개인 방송 함) -&amp;gt; 2차 면접&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순조롭게 2차 면접까지 진행 되었고, 결국 최종 합격을 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 결과가 12월 31일에 나와서 가족들이 전형 결과가 나올때까지 계속 걱정하고 있었던게 아직도 기억이 난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhf65X/btrlxfb0jgE/915NcjDBOSgIav3m4gPoqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhf65X/btrlxfb0jgE/915NcjDBOSgIav3m4gPoqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhf65X/btrlxfb0jgE/915NcjDBOSgIav3m4gPoqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhf65X%2Fbtrlxfb0jgE%2F915NcjDBOSgIav3m4gPoqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;586&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;얻은 것과 실망한 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 개발자로서 중요한 것을 얻은 회사다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 앞으로 어떤 방향으로 성장해 나가야 될 지 알게 해준 동료들과 개발에 대한 열정을 좋게 봐주셔서 기회를 만들어주신 팀장님이 계셨기 때문에 크게 성장 할 수 있었다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 결국 퇴사를 했다는 것은 내부에 불만족스러운 부분이 있었기 때문에 이직을 생각하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 회사를 가던 외부에서 바라본 시선과 내부에서 바라보는 시선이 같을수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 어떤 상황에서도 내가 얻을 수 있는걸 얻어 내는것이 중요하다고 생각이 된다.&lt;/p&gt;</description>
      <category>Log/회고</category>
      <category>아프리카tv</category>
      <category>직장</category>
      <category>회고</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/3</guid>
      <comments>https://0n1dev.tistory.com/3#entry3comment</comments>
      <pubDate>Fri, 19 Nov 2021 00:54:23 +0900</pubDate>
    </item>
    <item>
      <title>2021.11.19 - 블로그 시작</title>
      <link>https://0n1dev.tistory.com/2</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;2021.11.19&amp;nbsp;-&amp;nbsp;블로그&amp;nbsp;시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 시작부터 큰 난관에 봉착했다. 블로그 서비스를 제공하는 플랫폼의 다양성 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직하게 말하면 블로그를 시작하려한게 이번이 처음이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 블로그, 티스토리, Github 블로그 등 다양한 플랫폼에서 시도를 하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나에게 맞는 플랫폼은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;나에게 맞는 플랫폼은?&quot; 이게 가장 큰 난관인듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 블로그를 시작할 때 꾸준하게 하려고 하는데 늘 실패하는 듯 하여 실패의 원인을 찾아 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 블로그의 이상향&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자유도 보장 (내가 원하는 스킨을 사용할 수 있어야 함)&lt;/li&gt;
&lt;li&gt;글 작성 및 이미지 업로드가 편리해야 함&lt;/li&gt;
&lt;li&gt;독자가 쉽게 접근이 가능해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세가지 정도를 충족 시키는 플랫폼은 티스토리지만 한 가지 더 욕심이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그건 바로 Github의 1일 1커밋이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조졸두님 블로그에서 Github 글을 Tistory에 발행하는 것을 본적이 있었는데 어디갔는지 잘 모르겠다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 역발상으로 티스토리 글을 하루 한번씩 Github로 커밋도 가능하지 않을까? 하는 생각을 가지고 있다가 방법을 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1637249347217&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Python+GitHub Actions로 GitHub 프로필에 티스토리 최근 글 목록 연동하기&quot; data-og-description=&quot;개요 얼마 전 GitHub에 username/username repo를 만들고 README.md에 원하는 내용을 작성하면 원하는 내용을 본인의 프로필 페이지에 표시할 수 있게 되었습니다. 티스토리 활동과 GitHub 활동 모두를 하고 &quot; data-og-host=&quot;blog.stdio.dev&quot; data-og-source-url=&quot;https://blog.stdio.dev/59&quot; data-og-url=&quot;https://blog.stdio.dev/59&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oFPUU/hyMoof0lh6/PIP5vMSKr0EkAVqmvJtWO1/img.png?width=800&amp;amp;height=637&amp;amp;face=0_0_800_637,https://scrap.kakaocdn.net/dn/eX8hN/hyModFvcwe/WjgwpKUSQXowxkptI7dUeK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cRxjcD/hyMon9dFsd/nK3WtEytxnYgbKCDy0axmk/img.png?width=800&amp;amp;height=637&amp;amp;face=0_0_800_637&quot;&gt;&lt;a href=&quot;https://blog.stdio.dev/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.stdio.dev/59&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oFPUU/hyMoof0lh6/PIP5vMSKr0EkAVqmvJtWO1/img.png?width=800&amp;amp;height=637&amp;amp;face=0_0_800_637,https://scrap.kakaocdn.net/dn/eX8hN/hyModFvcwe/WjgwpKUSQXowxkptI7dUeK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cRxjcD/hyMon9dFsd/nK3WtEytxnYgbKCDy0axmk/img.png?width=800&amp;amp;height=637&amp;amp;face=0_0_800_637');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Python+GitHub Actions로 GitHub 프로필에 티스토리 최근 글 목록 연동하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요 얼마 전 GitHub에 username/username repo를 만들고 README.md에 원하는 내용을 작성하면 원하는 내용을 본인의 프로필 페이지에 표시할 수 있게 되었습니다. 티스토리 활동과 GitHub 활동 모두를 하고&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.stdio.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python과 Github Action을 사용하여 스케쥴링을 돌리는 것 같다.&lt;/p&gt;</description>
      <category>Log/이것저것</category>
      <category>블로그시작</category>
      <category>티스토리</category>
      <author>0n1dev</author>
      <guid isPermaLink="true">https://0n1dev.tistory.com/2</guid>
      <comments>https://0n1dev.tistory.com/2#entry2comment</comments>
      <pubDate>Fri, 19 Nov 2021 00:32:43 +0900</pubDate>
    </item>
  </channel>
</rss>