<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>woohoo.log</title>
        <link>https://velog.io/</link>
        <description>Hongik CE</description>
        <lastBuildDate>Thu, 27 Jan 2022 11:28:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>woohoo.log</title>
            <url>https://images.velog.io/images/woo0_hooo/profile/8b0a121b-7ef2-43ad-a221-1866b19615ba/FCA5DD5A-050E-4381-AA18-D4A4534EE1FD-12868-000005C908BB3506.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. woohoo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/woo0_hooo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[kotlin] 3장 함수의 정의와 호출 - 2]]></title>
            <link>https://velog.io/@woo0_hooo/kotlin-3%EC%9E%A5-%ED%95%A8%EC%88%98%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-%ED%98%B8%EC%B6%9C-2</link>
            <guid>https://velog.io/@woo0_hooo/kotlin-3%EC%9E%A5-%ED%95%A8%EC%88%98%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-%ED%98%B8%EC%B6%9C-2</guid>
            <pubDate>Thu, 27 Jan 2022 11:28:06 GMT</pubDate>
            <description><![CDATA[<h1 id="34-컬렉션-처리">3.4 컬렉션 처리</h1>
<h2 id="코틀린-언어-특성">코틀린 언어 특성</h2>
<ul>
<li>vararg : 호출 시 인자 개수가 달라질 수 있는 함수를 정의할 수 있음</li>
<li>중위 함수 호출 구문: 인자가 하나인 메소드를 편하게</li>
<li>구조 분해 선언: 복합적인 값을 분해하고 여러 변수에 나눠 담을 수 있음</li>
</ul>
<h2 id="자바-컬렉션-api의-확장">자바 컬렉션 API의 확장</h2>
<h3 id="last">last()</h3>
<pre><code class="language-kotlin">val strings: List&lt;String&gt; = listOf(&quot;first&quot;, &quot;second&quot;, &quot;last&quot;)
println(strings.last())

&gt;&gt;&gt; last</code></pre>
<p>실제 구현은 다음과 같이 List의 확장함수로 구현되어 있다.
<img src="https://images.velog.io/images/woo0_hooo/post/563886ae-7409-4a6b-be58-b45543581c16/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2010.47.20.png" alt=""></p>
<h3 id="max">max()</h3>
<pre><code class="language-kotlin">val numbers: Collection&lt;Int&gt; =setOf(1, 14, 2)
println(numbers.max())</code></pre>
<p>→ 코틀린 1.4 버전 이후로 deprecated되었고, maxOfNull()을 사용한다.
<img src="https://images.velog.io/images/woo0_hooo/post/8083c318-14f8-410c-9388-53974da30079/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2010.51.03.png" alt=""></p>
<h2 id="가변-인자-함수">가변 인자 함수</h2>
<p>위에서 사용한 listOf()는 다음과 같이 정의되어 있다.</p>
<pre><code class="language-kotlin">public fun &lt;T&gt; listOf(vararg elements: T): List&lt;T&gt; = if (elements.size &gt; 0) elements.asList() else emptyList()</code></pre>
<ul>
<li>자바의 가변 길이 인자 : <code>...</code></li>
<li>코틀린의 가변 길이 인자: 파라미터 앞에 <code>vararg</code></li>
</ul>
<p><strong>이미 배열인 원소를 가변 길이 인자로 넘기기</strong></p>
<pre><code class="language-kotlin">fun main(args: Array&lt;String&gt;) {
    val list = listOf(&quot;args: &quot;, *args)
    println(list)
}</code></pre>
<ul>
<li><p>스프레드 연산자 * 사용</p>
<p>  → 자바에서는 사용할 수 없는 기능</p>
</li>
</ul>
<h2 id="중위-호출과-구조-분해-선언">중위 호출과 구조 분해 선언</h2>
<pre><code class="language-kotlin">val map = mapOf(1 to  &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)</code></pre>
<p>와 같이 map을 만들때, to는 <strong>중위호출</strong>로 to라는 일반 메소드를 호출한다.</p>
<h3 id="중위-호출">중위 호출</h3>
<ul>
<li>인자가 하나뿐인 일반 메소드/확장 함수에 사용 가능</li>
<li>사용 방법<ul>
<li>infix 변경자를 함수 선언 앞에 추가
  <img src="https://images.velog.io/images/woo0_hooo/post/3becff84-3dd5-42a9-81b1-cd3b361fe9ae/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.49.51.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li>to를 일반 방법으로 호출 : 1.to(&quot;one&quot;)</li>
<li>to를 중위 호출로 호출 : 1 to(&quot;one&quot;)</li>
</ul>
<h3 id="구조-분해-선언">구조 분해 선언</h3>
<pre><code class="language-kotlin">infix fun Any.to(other: Any) = Pair(this, other)</code></pre>
<p>위와 같이 선언된 to()는 Pair의 구조 분해 선언으로 두 변수를 즉시 초기화 한다.</p>
<pre><code class="language-kotlin">val (number, name) = 1 to &quot;one&quot;</code></pre>
<ul>
<li><p>활용: Pair 뿐만 아니라 다른 객체에도 적용 가능함</p>
<pre><code class="language-kotlin">  for ((index, element) in collection.withIndex()) {
      println(&quot;$index: $element&quot;)
  }</code></pre>
</li>
</ul>
<h2 id="문자열과-정규식-다루기">문자열과 정규식 다루기</h2>
<h3 id="문자열-나누기">문자열 나누기</h3>
<ul>
<li><p>자바의 split : separator가 정규식임 → .는 모든 문자를 나타내는 정규식으로 해석됨</p>
</li>
<li><p>코틀린에서의 문자열 나누기</p>
<ul>
<li><p>파라미터로 Regex 타입을 받는다.</p>
<pre><code class="language-kotlin">println(&quot;12.345-6.A&quot;.split(&quot;\\.|-&quot;.toRegex()))

&gt;&gt;&gt; [12, 345, 6, A]</code></pre>
<p>→ 코틀린 정규식 문법은 자바와 같음</p>
</li>
<li><p>자바 split 오버로딩</p>
<pre><code class="language-kotlin">println(&quot;12.345-6.A&quot;.split(&quot;.&quot;, &quot;-&quot;))

&gt;&gt;&gt; [12, 345, 6, A]</code></pre>
<p>→ 여러 separator 사용 가능</p>
</li>
</ul>
</li>
</ul>
<h3 id="정규식과-3중-따옴표로-묶은-문자열">정규식과 3중 따옴표로 묶은 문자열</h3>
<p>파일 path를 디렉토리, 파일명, 확장자로 구분하기</p>
<ul>
<li>방법 1) 코틀린 라이브러리 사용</li>
</ul>
<pre><code class="language-kotlin">fun parsePath(path: String) {
    val directory = path.substringBeforeLast(&quot;/&quot;)
    val fullName = path.substringAfterLast(&quot;/&quot;)
    val fileName = fullName.substringBeforeLast(&quot;.&quot;)
    val extension = fullName.substringAfterLast(&quot;/&quot;)

    println(&quot;Dir: $directory, name: $fileName, ext: $extension&quot;)
}

fun main() {
    parsePath(&quot;/Users/yole/kotlin-book/chapter.doc&quot;)
}
</code></pre>
<ul>
<li>방법 2) 정규식 사용</li>
</ul>
<pre><code class="language-kotlin">fun parsePath(path: String) {
    val regex = &quot;&quot;&quot;(.+)/(.+)\.(.+)&quot;&quot;&quot;.toRegex()
    val matchResult = regex.matchEntire(path)

    if (matchResult != null) {
        val (directory, filename, extension) = matchResult.destructured
        println(&quot;Dir: $directory, name: $filename, ext: $extension&quot;)
    }
}

fun main() {
    parsePath(&quot;/Users/yole/kotlin-book/chapter.doc&quot;)
}</code></pre>
<p>→ &quot;&quot;&quot;&quot;&quot; : 삼중 따옴표 문자열에서는 어떤 문자도 이스케이프 할 필요가 x</p>
<h3 id="여러-줄-3중-따옴표-문자열">여러 줄 3중 따옴표 문자열</h3>
<pre><code class="language-kotlin">fun main() {
    val kotlinLogo = &quot;&quot;&quot;| //
        .| //
        .|/\&quot;&quot;&quot;
    println(kotlinLogo.trimMargin(&quot;.&quot;))
}

&gt;&gt;&gt; | //
&gt;&gt;&gt; | //
&gt;&gt;&gt; |/\</code></pre>
<p>→ 와 같이 문자열이 텍스트가 포함해도 쉽게 문자열로 변환할 수 있음</p>
<h1 id="로컬-함수-확장">로컬 함수 확장</h1>
<ul>
<li><p>추출한 함수를 원래 함수 내부에 중첩시키기</p>
<p>  : 일반적으로 한단계만 중첩시키라고 권장</p>
</li>
</ul>
<pre><code class="language-kotlin">import java.lang.IllegalArgumentException

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(&quot;name null&quot;)
    }

    if (user.address.isEmpty()) {
        throw IllegalArgumentException(&quot;address null&quot;)
    }
}

fun main() {
    saveUser(User(1, &quot;&quot;, &quot;&quot;))
}</code></pre>
<p>→ 함수가 복잡하지는 않지만, 검증하는 코드가 반복된다</p>
<pre><code class="language-kotlin">import java.lang.IllegalArgumentException

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    fun validate(user: User, value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(&quot;$[user.id]: null $fieldName&quot;)
        }
    }
    validate(user, user.name, &quot;Name&quot;)
    validate(user, user.address, &quot;Address&quot;)
}

fun main() {
    saveUser(User(1, &quot;&quot;, &quot;&quot;))
}</code></pre>
<p>→ 중복되는 검증 부분을 메소드로 추출하고, 원래 함수 내부에 중첩</p>
<p>→ user 객체를 로컬 함수에 하나하나 전달해야 된다</p>
<pre><code class="language-kotlin">import java.lang.IllegalArgumentException

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;$[user.id]: null $fieldName&quot;)
        }
    }
    validate(user.name, &quot;Name&quot;)
    validate(user.address, &quot;Address&quot;)
}

fun main() {
    saveUser(User(1, &quot;&quot;, &quot;&quot;))
}</code></pre>
<p>→ 로컬 함수가 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있음을 이용</p>
<pre><code class="language-kotlin">import java.lang.IllegalArgumentException

class User(val id: Int, val name: String, val address: String)

fun User.validateBeforeSave(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(&quot;$[user.id]: null $fieldName&quot;)
        }
    }
    validate(name, &quot;Name&quot;)
    validate(address, &quot;Address&quot;)
}

fun saveUser(user: User) {
    user.validateBeforeSave(user)
}

fun main() {
    saveUser(User(1, &quot;&quot;, &quot;&quot;))
}</code></pre>
<p>→ 확장 함수로 추출</p>
<p>: 이 검증 로직이 User 외에 다른 데에서는 쓰이지 않아서 포함시키고 싶지 않은 경우</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[kotlin] 5장 람다로 프로그래밍]]></title>
            <link>https://velog.io/@woo0_hooo/kotlin-5%EC%9E%A5-%EB%9E%8C%EB%8B%A4%EB%A1%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@woo0_hooo/kotlin-5%EC%9E%A5-%EB%9E%8C%EB%8B%A4%EB%A1%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Thu, 27 Jan 2022 11:23:33 GMT</pubDate>
            <description><![CDATA[<aside>
💡 람다식이란? 메소드를 하나의 식으로 표현 → 1) 메소드의 매개변수로 전달, 2) 메소드의 결과로 반환

</aside>

<ul>
<li>함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행가능한 함수</li>
<li>공통 코드 구조를 라이브러리 함수로 쉽게 뽑아낼 수 있음</li>
<li>코틀린 표준 라이브러리에서 많이 사용</li>
</ul>
<h1 id="51-람다-식과-멤버-참조">5.1 람다 식과 멤버 참조</h1>
<h2 id="511-코드-블록을-인자로-넘기기">5.1.1 코드 블록을 인자로 넘기기</h2>
<h3 id="람다-도입-이전">람다 도입 이전</h3>
<p>: 내부 익명 클래스를 사용</p>
<pre><code class="language-java">button.setOnClickListner(new OnClickListener() {
    @Override
    public void onClick(View view) {
        //
    }
}</code></pre>
<p>→ 코드가 번잡스럽고, 이런 작업을 여러번 수행해야 하는 경우엔..</p>
<p>→ 일회용 클래스</p>
<h3 id="람다-도입-이후">람다 도입 이후</h3>
<p>: 함수를 값처럼 다루자!</p>
<pre><code class="language-java">button.setOnClickListner{ // }</code></pre>
<h2 id="512-람다와-컬렉션">5.1.2 람다와 컬렉션</h2>
<h3 id="람다-없이">람다 없이</h3>
<pre><code class="language-java">data class Person(val name: String, val age: Int) {
    companion object {}
}

fun findTheOldest(people: List&lt;Person&gt;) {
    var maxAge = 0
    var theOldest: Person? = null

    for (person in people) {
        if (person.age &gt; maxAge) {
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}

fun main() {
    val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 24), Person(&quot;Carol&quot;, 39))
    findTheOldest(people)
}</code></pre>
<h3 id="람다-활용하기">람다 활용하기</h3>
<pre><code class="language-kotlin">data class Person(val name: String, val age: Int) {
    companion object {}
}

fun main() {
    val people = listOf(Person(&quot;Alice&quot;, 29), Person(&quot;Bob&quot;, 24), Person(&quot;Carol&quot;, 39))
    println(people.maxByOrNull{ it.age })
    println(people.maxByOrNull(Person::age))
}</code></pre>
<p><em>→ 책에는 maxBy()로 나오는데 이건 deprecated</em></p>
<p>→ 람다나 멤버 참조를 사용해서 더 짧고 가독성 좋게 개선</p>
<h2 id="513-람다-식의-문법">5.1.3 람다 식의 문법</h2>
<pre><code class="language-kotlin">val sum = { x: Int, y: Int -&gt; x + y }
println(sum(1, 2))</code></pre>
<ol>
<li>항상 중괄호 사이에 위치한다</li>
<li>인자 목록에 괄호 필요 x</li>
<li>람다식을 변수에 저장할 수 있음 → runCatching에서 onSuccess, onFailure</li>
</ol>
<h3 id="run">run</h3>
<p>: 코드의 일부분을 블록으로 둘러싸서 실행이 필요한 경우</p>
<pre><code class="language-kotlin">val num = 42
num.run { println(this) }</code></pre>
<p><em>→ 기본 람다를 언제 사용해야 할지?</em></p>
<p><a href="https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%9D%98-apply-with-let-also-run-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-4a517292df29">https://medium.com/@limgyumin/코틀린-의-apply-with-let-also-run-은-언제-사용하는가-4a517292df29</a></p>
<p><a href="https://kotlinlang.org/docs/scope-functions.html#function-selection">https://kotlinlang.org/docs/scope-functions.html#function-selection</a></p>
<h3 id="위의-코드-정식으로-람다-활용하기">위의 코드 정식으로 람다 활용하기</h3>
<pre><code class="language-kotlin">people.maxByOrNull({ p: Person -&gt; p.age })
people.maxByOrNull(){ p: Person -&gt; p.age }
people.maxByOrNull { p: Person -&gt; p.age }
people.maxByOrNull{ it.age }  // 제일 익숙한 형태</code></pre>
<p>→ 코틀린에서 맨 뒤의 인자가 람다면 밖으로 뺄 수 있고, 다른 인자가 없으면 () 생략 가능</p>
<p>를 활용해보자</p>
<pre><code class="language-kotlin">val name = people.joinToString(separator = &quot; &quot;, transform = {p-&gt;p.name})
val name = people.joinToString(&quot; &quot;){ p-&gt;p.name }</code></pre>
<h3 id="파라미터-타입-추론이-필요할-때">파라미터 타입 추론이 필요할 때</h3>
<p>코틀린 컴파일러가 자동으로 파라미터 타입을 추론하여 굳이 명시하지 않아도 되지만, 변수에 저장할때는 타입 추론 문맥이 없어서 명시해야됨</p>
<pre><code class="language-kotlin">val getAge = { p: Person -&gt; p.age }</code></pre>
<h2 id="514-현재-영역에-있는-변수-접근">5.1.4 현재 영역에 있는 변수 접근</h2>
<pre><code class="language-kotlin">fun printMessageWithPrefix(responses: Collection&lt;String&gt;, prefix: String) {
    var clientErrors = 0
    var serverErrors = 0

    responses.forEach {
        if (it.startsWith(&quot;4&quot;)) clientErrors++
        else if (it.startsWith(&quot;5&quot;)) serverErrors++
    }
    println(&quot;$prefix $clientErrors client errors, $prefix  $serverErrors server errors&quot;)
}

fun main() {
    val errors = listOf(&quot;401 Unauthorized&quot;, &quot;404 Not Found&quot;, &quot;200 Ok&quot;, &quot;500 Internal Server Error&quot;)
    printMessageWithPrefix(errors, &quot;Errors:&quot;)
}</code></pre>
<h3 id="포획-변수-captured-variable">포획 변수 (captured variable)</h3>
<ul>
<li><p>위 코드에서 prefix, clientErrors, serverErrors와 같이 람다 안에서 사용되는 외부 변수</p>
</li>
<li><p>포획 변수의 생명 주기</p>
<p>  : 일반적으로 함수 안의 로컬 변수는 함수가 반환되면 생명 주기가 끝나지만, 함수가 해<strong>당 로컬 변수를 포획한 람다를 반환</strong>하거나 변수에 <strong>저장</strong>하면 함수가 끝난 뒤에도 여전히 포획 변수를 사용할 수 있음</p>
</li>
</ul>
<pre><code class="language-kotlin">class Button {
    fun onClick(function: () -&gt; Int) {
        function()
    }
}

fun tryToCountButtonClicks(button: Button): Int {
    var clicks = 0
    button.onClick{ clicks++ }
    return clicks
}

fun main() {
    val button = Button()
    val clickNum = tryToCountButtonClicks(button)
    println(clickNum)
}</code></pre>
<p><em>→ 책에서의 설명(</em>tryToCountButtonClicks가 clicks를 반환 한 다음 tryToCountButtonClicks이 호출되어 항상 0이 반환된다고 하는데 실제로 디버깅하면 반환 전에 onClick()이 먼저 호출된 다음 반환이 된다. 따라서 위와 같이 실행하면 1이 출력되는데 요건 번역 오류인지 무슨 의미인지 잘 모르겠다</p>
<h2 id="515-멤버-참조">5.1.5 멤버 참조</h2>
<pre><code class="language-kotlin">people.maxByOrNull{ it.age }</code></pre>
<p>→ 최상위에 선언된 함수나 프로퍼티도 참조 가능하다 </p>
<pre><code class="language-kotlin">fun salute() = println(&quot;salute!&quot;)

fun main() {
    run(::salute)
}</code></pre>
<h3 id="인자가-여러개인-경우">인자가 여러개인 경우</h3>
<p>: 직접 위임 함수에 대한 참조 제공하면 편함</p>
<pre><code class="language-kotlin">fun sendEmail(person: Person, message: String) {
    println(&quot;${person.name} sent $message&quot;)
}

fun main() {
    val action1 = {
            person: Person, message: String -&gt; sendEmail(person, message)
    }
    val action2 = ::sendEmail //람다 대신 멤버 참조 사용

    action1(Person(&quot;leean&quot;, 25), &quot;hi&quot;)
    action2(Person(&quot;leean&quot;, 25), &quot;hi&quot;)
}</code></pre>
<h1 id="52-컬렉션-함수형-api">5.2 컬렉션 함수형 API</h1>
<h2 id="521-filter와-map">5.2.1 filter와 map</h2>
<h3 id="filter">filter</h3>
<pre><code class="language-kotlin">val list = listOf(1, 2, 3, 4)
println(list.filter { it%2 == 0 })</code></pre>
<p>→ 컬렉션에서 원하는 원소만 필터해서 새 컬렉션 반환</p>
<h3 id="map">map</h3>
<pre><code class="language-kotlin">val list = listOf(1, 2, 3, 4)
println(list.filter { it%2 == 0 })</code></pre>
<p>→ 컬렉션에서 주워진 람다를 각 컬렉션에 적용한 결과를 새 컬렉션으로 반환</p>
<pre><code class="language-kotlin">// 위의 코드에서 불필요한 연산 제거하기
val maxAge = people.maxBy(Person::age)!!.age
people.filter{ it.age == maxAge }</code></pre>
<h2 id="522-all-any-find">5.2.2 all, any, find</h2>
<ul>
<li>count: 조건을 만족하는 원소의 개수 반환</li>
<li>find: 조건을 만족하는 첫번째 원소 반환</li>
<li>all: 모든 원소가 만족하는지 여부 체크</li>
<li>any: 하나라도 만족하는지 여부 체크</li>
</ul>
<pre><code class="language-kotlin">val canBeInClub27 = { p:Person -&gt; p.age &lt;= 27 }
val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 25), Person(&quot;Carol&quot;, 37))

println(people.all(canBeInClub27))
println(people.any(canBeInClub27))
println(people.count(canBeInClub27))
println(people.find(canBeInClub27))

&gt;&gt;&gt; false
&gt;&gt;&gt; true
&gt;&gt;&gt; 2
&gt;&gt;&gt; Person(name=Alice, age=27)</code></pre>
<h3 id="523-groupby">5.2.3 groupBy</h3>
<p>원소를 특성에 따라 그룹으로 나누기</p>
<pre><code class="language-kotlin">val people = listOf(Person(&quot;Alice&quot;, 27), Person(&quot;Bob&quot;, 25), Person(&quot;Carol&quot;, 27))
println(people.groupBy { it.age })

&gt;&gt;&gt; {27=[Person(name=Alice, age=27), Person(name=Carol, age=27)], 25=[Person(name=Bob, age=25)]}</code></pre>
<p>→ 결과: Map&lt;Int, List<Person>&gt;
  → db에서 인덱스 타더라도 groupBy/aggregate 때문에 slow가 발생하는경우가 왕왕 있다 → 메모리에 올려놔도 상관없는 경우에 app단에서 groupBy를 사용하는게 더 빠른 경우가 있다</p>
<h3 id="524-flapmap-flatten">5.2.4 flapMap, flatten</h3>
<h3 id="flatmap">flatMap</h3>
<p>인자로 주어진 람다를 컬렉션 모든 객체에 매핑</p>
<pre><code class="language-kotlin">data class Book(val title: String, val author: List&lt;String&gt;) {
    companion object
}

fun main() {
    val books = listOf(
        Book(&quot;kotlin in action&quot;, listOf(&quot;rein&quot;, &quot;moka&quot;, &quot;leean&quot;)),
        Book(&quot;modern java in action&quot;, listOf(&quot;jace&quot;, &quot;dante&quot;, &quot;moka&quot;))
    )

    println(books.flatMap { it.author }.toSet())
}</code></pre>
<h3 id="flatten">flatten</h3>
<p>특별히 매핑이 필요 없는 경우</p>
<p>외에 더 많은 API는 <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/#kotlin.collections.Collection">https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/#kotlin.collections.Collection</a> 를 참고하자~!</p>
<p>→ 람다 안에 뭔가 로직이 복잡하다,,? 중첩된다? 싶으면 바로 검색</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[kotlin] 3장 함수 정의와 호출 - 1]]></title>
            <link>https://velog.io/@woo0_hooo/kotlin</link>
            <guid>https://velog.io/@woo0_hooo/kotlin</guid>
            <pubDate>Wed, 08 Dec 2021 16:17:57 GMT</pubDate>
            <description><![CDATA[<h1 id="31-코틀린의-컬렉션">3.1 코틀린의 컬렉션</h1>
<pre><code class="language-kotlin">val setExample = hashSetOf(1, 7, 53)
val listExample = arrayListOf(1, 7, 53)
val mapExample = hashMapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)

println(setExample.javaClass)
println(listExample.javaClass)
println(mapExample.javaClass)</code></pre>
<p><strong>결과</strong>
<img src="https://images.velog.io/images/woo0_hooo/post/4cf2172b-399f-4ba2-853f-90fbe93b652c/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.59.44.png" alt=""></p>
<p>: 코틀린은 자신만의 컬렉션을 제공하지 않고, 기존 자바 컬렉션을 활용한다.
→ 왜? 자바 코드와의 <strong>상호작용</strong>을 위해서 굳이 컬렉션을 서로 변환할 필요가 없게 한다. </p>
<pre><code class="language-kotlin">val strings = listOf(&quot;first&quot;, &quot;second&quot;, &quot;fourteenth&quot;)
println(strings.last())</code></pre>
<p>:코틀린은 기존 자바 컬렉션을 활용하지만, 자바보다 더 많은 기능을 쓸 수 있다.</p>
<h2 id="32-함수를-호출하게-쉽게-만들기">3.2 함수를 호출하게 쉽게 만들기</h2>
<h3 id="자바-컬렉션의-tosring">자바 컬렉션의 toSring()</h3>
<ul>
<li>디폴트 toString()</li>
</ul>
<pre><code class="language-kotlin">import java.util.HashSet;

class Main {  
  public static void main(String args[]) { 
    HashSet set = new HashSet();
    set.add(&quot;abc&quot;);
    set.add(&quot;efg&quot;);
    set.add(&quot;hij&quot;);
    System.out.println(set.toString());
  } 
}

&gt;&gt;&gt; [abc, efg, hij]</code></pre>
<p>→ 원하는 형식으로 <a href="https://stackoverflow.com/questions/395401/printing-java-collections-nicely-tostring-doesnt-return-pretty-output">toString()을 재정의</a> 하려면?
: 구아바, 아파치 커먼즈 같은 라이브러리 혹은 직접 관련 로직 구현해야 한다 → 번거롭다</p>
<h3 id="코틀린에서의-tostring">코틀린에서의 toString()</h3>
<p><strong>방법 1) 기본 호출</strong></p>
<pre><code class="language-kotlin">fun &lt;T&gt; joinToString(
    collection: Collection&lt;T&gt;,
    separator: String,
    prefix: String,
    postfix: String
): String {
    val result = StringBuilder(prefix)

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

    result.append(postfix)
    return result.toString()
}</code></pre>
<p><strong>호출 결과</strong></p>
<pre><code class="language-kotlin">&gt;&gt;&gt; val list = listOf(1, 2, 4)
&gt;&gt;&gt; println(joinToString(list, &quot;:&quot;, &quot;(&quot;, &quot;)&quot;))
&gt;&gt;&gt; (1:2:4)</code></pre>
<p>: 의도대로 잘 작동한다. 하지만? 호출 부분이 번거롭다
→ 인자로 전달한 separator, prefix, postfix가 각각 어떤 역할하는지 구분이 어렵고, 매번 함수의 시그니처를 찾아봐야함</p>
<p><strong>방법2) 이름 붙힌 인자</strong></p>
<pre><code class="language-kotlin">&gt;&gt;&gt; println(joinToString(list, separator = &quot;:&quot;, prefix = &quot;(&quot;, postfix = &quot;)&quot;))</code></pre>
<p>: 전달하는 인자 중 일부/전부의 이름을 명시할 수 있다.
<em>→ 인자 중 하나라도 이름을 명시하면 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다??</em> 
<img src="https://images.velog.io/images/woo0_hooo/post/14a4438f-0a5c-4f70-b611-536027c85619/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.00.06.png" alt=""></p>
<p>명시하지 않아도 잘 컴파일이 된다 ..?</p>
<p><strong>방법 3) 디폴트 파라미터 값</strong></p>
<ul>
<li><p>자바의 문제점<img src="https://images.velog.io/images/woo0_hooo/post/75187659-902f-4865-8f6a-5449855a29a2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-09%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.00.31.png" alt=""></p>
<p>  : java.Lang.Thread의 생성자는 8개나 정의되어 있다.
  → 파라미터 이름과 타입이 계속 반복되고, 인자 중 일부가 생략된 오버로드 함수를 호출하면 어떤 함수를 호출할지 모호하다.</p>
</li>
</ul>
<ul>
<li><p>코틀린에서의 디폴트 파라미터
  : 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있다.</p>
<pre><code class="language-kotlin">fun &lt;T&gt; joinToString(
    collection: Collection&lt;T&gt;,
    separator: String = &quot;,&quot;,
    prefix: String = &quot;(&quot;,
    postfix: String = &quot;)&quot;
): String {
    val result = StringBuilder(prefix)

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

    result.append(postfix)
    return result.toString()
}

&gt;&gt;&gt; println(joinToString(list)
&gt;&gt;&gt; println(joinToString(list, &quot;:&quot;) // 일부를 생략하면 맨 뒤에 n개가 생략된다.</code></pre>
</li>
</ul>
<pre><code>- 디폴트 파라미터가 설정된 코틀린 함수를 자바에서 호출하면?
    : 자바에는 디폴트 파라미터 개념이 없어서 디폴트 파라미터가 설정된 코틀린 함수더라도 모든 인자를 명시해야 된다.
    → `@JvmOverloads` : 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로 부터 하나씩 생략한, 오버로딩된 자바 메소드를 추가함 
    → joinToString위에 JvmOverloads 어노테이션을 추가하면?

    ```kotlin
    String joinToString(Collection&lt;T&gt; collection, String seperator, String prefix, String postfix); 
    String joinToString(Collection&lt;T&gt; collection, String seperator, String prefix); 
    String joinToString(Collection&lt;T&gt; collection, String seperator); 
    String joinToString(Collection&lt;T&gt; collection);
    ```

    와 같은 네개의 오버로딩한 함수가 만들어진다. 생략된 파라미터는 코틀린 디폴트 파라미터 값을 사용</code></pre><h3 id="정적인-유틸리티-클래스-없애기">정적인 유틸리티 클래스 없애기</h3>
<ul>
<li><p>정적 유틸리티 클래스?
  : 다양한 정적 메소드를 모아두는 역할로 특별한 상태나 인스턴스 메소드가 없는 클래스</p>
</li>
<li><p>코틀린에서의 정적 유틸리티 클래스
  : 코틀린 파일의 모든 최상위 함수는 해당 클래스의 정적 메소드가 된다.</p>
</li>
</ul>
<pre><code class="language-kotlin">package strings

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

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

    result.append(postfix)
    return result.toString()
}</code></pre>
<p>→ 이 함수의 실행 과정
: 컴파일러가 join.kt를 컴파일할때 새로운 클래스 정의 → JVM에서 호출할때는 </p>
<pre><code class="language-kotlin">pakage strings;

public class JoinKt {
    public static String joinToString(...) {...}
}</code></pre>
<p>와 같이 컴파일된다.</p>
<ul>
<li>파일에 대응하는 클래스 이름 변경하기
  : 코틀린 최상위 함수가 포함되는 클래스의 이름 바꿀때는 <code>@JvmNames</code> 어노테이션을 사용한다.
  <em>→ 어떤 경우에 클래스 이름이 바뀌어야 하는지 잘 이해가 안간다..</em></li>
</ul>
<ul>
<li>최상위 프로퍼티</li>
</ul>
<pre><code class="language-kotlin">var opCount = 0

fun performOperation() {
    opCount++
}

fun reportOperationCount() {
    println(&quot;Operation performed $opCount times&quot;)
}

fun main() {
    performOperation()
    performOperation()
    performOperation()
    reportOperationCount()
}

&gt;&gt;&gt; Operation performed 3 times</code></pre>
<p>: 최상위 프로퍼티를 활용해서 코드에 상수를 추가할 수 있음</p>
<h3 id="확장-함수와-확장-프로퍼티">확장 함수와 확장 프로퍼티</h3>
<p><strong>확장 함수</strong></p>
<ul>
<li>기존 자바 API를 재작성 하지 않고도 코틀린 제공 기능 활용하는 방법</li>
<li>추가하려는 함수 이름 앞에 확장할 클래스 이름 덧붙히기</li>
</ul>
<pre><code class="language-kotlin">fun String.lastChar(): Char = this.get(this.length - 1)
// fun String.lastChar(): Char = get(length - 1) 
// this 생략 가능

fun main() {
    println(&quot;Kotlin&quot;.lastChar())
}

&gt;&gt;&gt; n</code></pre>
<p>→ String: 수신 객체 타입 → 확장할 클래스
→ this: 수신 객체 → 확장 함수가 호출되는 대상이 되는 값</p>
<ul>
<li>왜 사용하는가? 확장할 클래스(예제에서는 String)을 직접 작성한게 아니어도 해당 클래스에 <strong>새로운 메소드를 추가하는 것과 같게</strong> 동작한다.</li>
<li>캡슐화를 깨지 않는다
  : 수신 객체 타입의 private이나 protected 멤버는 사용할 수 없다!</li>
</ul>
<h3 id="임포트와-확장-함수">임포트와 확장 함수</h3>
<ul>
<li>클래스 임포트할 때와 같이 개별 함수 임포트 가능</li>
</ul>
<pre><code class="language-kotlin">import strings.lastChar

fun main() {
    val c = &quot;Kotlin&quot;.lastChar()
    println(c)
}</code></pre>
<ul>
<li><em>코틀린 문법 상 확장 함수는 반드시 짧은 이름을 써야 한다 ??</em></li>
</ul>
<h3 id="자바에서-확장-함수-호출하기">자바에서 확장 함수 호출하기</h3>
<p>: 정적 메소드를 호출하면서 첫번째 인자로 수신 객체 넘기기</p>
<pre><code class="language-kotlin">char c = StringUtilsKt.lastChar(&quot;Java&quot;);</code></pre>
<h3 id="확장-함수로-유틸리티-함수-정의하기">확장 함수로 유틸리티 함수 정의하기</h3>
<pre><code class="language-kotlin">fun main() {
    val list1 = listOf(1,2,3)
    println(list1.joinToString(separator = &quot;;&quot;, prefix = &quot;[&quot;, postfix = &quot;]&quot;))

    val list2 = arrayListOf(1,2,3)
    println(list2.joinToString(separator = &quot;;&quot;, prefix = &quot;[&quot;, postfix = &quot;]&quot;))
}</code></pre>
<p>→ joinToString을 마치 클래스 멤버인거처럼 호출이 가능</p>
<h3 id="확장-함수는-오버라이드할-수-없다">확장 함수는 오버라이드할 수 없다</h3>
<p><strong>오버라이딩</strong></p>
<pre><code class="language-kotlin">open class View {
    open fun click() = println(&quot;View clicked&quot;)
}

// Button은 View를 확장한다.
class Button: View() {
    override fun click() = println(&quot;Button clicked&quot;)
}

fun main() {
    val view: View = Button()
    view.click()
}

&gt;&gt;&gt; Button clicked</code></pre>
<p><strong>확장 함수의 오버라이딩</strong></p>
<ul>
<li><p>확장 함수는 이렇게 작동하지 x</p>
<p>  → 클래스 밖에 선언되므로 확장 함수를 호출할 때 <strong>수신객체로 지정한 변수의 정적 타입에 의해</strong> 어떤 확장 함수가 호출될지 결정된다.</p>
</li>
<li><p>코틀린은 호출될 확장 함수를 정적으로 결정한다</p>
</li>
</ul>
<pre><code class="language-kotlin">open class View {
    open fun click() = println(&quot;View clicked&quot;)
}

// Button은 View를 확장한다.
class Button: View() {
    override fun click() = println(&quot;Button clicked&quot;)
}

fun View.showOff() = println(&quot;I&#39;m a view&quot;)
fun Button.showOff() = println(&quot;I&#39;m a button&quot;)

fun main() {
    val view: View = Button()
    view.showOff()
}

&gt;&gt;&gt; I&#39;m a view</code></pre>
<p>→ view가 가리키는 객체의 실제 타입은 Button이지만, view의 type은 View이므로 View.showOff()가 호출된다.</p>
<p>💡 확장 함수와 멤버 함수의 이름이 같으면? 멤버 함수의 우선 순위가 더 높아서 멤버 함수가 호출된다.</p>
<pre><code class="language-kotlin">fun FunctionUtil.hello() {
    println(&quot;hello-extend&quot;)
}

class FunctionUtil {
    constructor()

    fun hello() {
        println(&quot;hello-class&quot;)
    }
}

fun main() {
    val func: FunctionUtil = FunctionUtil()
    func.hello()
}

&gt;&gt;&gt; hello-class</code></pre>
<h3 id="확장-프로퍼티">확장 프로퍼티</h3>
<ul>
<li>프로퍼티 형식 구문으로 사용 가능</li>
<li>이름은 프로퍼티지만 상태 저장할 방법은 x</li>
</ul>
<pre><code class="language-kotlin">var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }


fun main() {
    val sb = StringBuilder(&quot;Kotlin?&quot;)
    sb.lastChar = &#39;!&#39;
    println(sb)
}
&gt;&gt;&gt; Kotlin!</code></pre>
<ul>
<li>일반적인 프로퍼티와 같은데 수신 객체 클래스만 추가됨</li>
<li>자바에서 사용하고 싶다면? 게터&amp;세터를 명시적으로 호출해야됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ] 2306 - 유전자]]></title>
            <link>https://velog.io/@woo0_hooo/BOJ-2306-%EC%9C%A0%EC%A0%84%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/BOJ-2306-%EC%9C%A0%EC%A0%84%EC%9E%90</guid>
            <pubDate>Sun, 20 Jun 2021 03:35:48 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크">문제 링크</h1>
<p><a href="https://www.acmicpc.net/problem/2306">2306 - 유전자</a></p>
<h1 id="문제-설명">문제 설명</h1>
<p>DNA는 a,c,g,t로 이루어진 문자열이다. </p>
<p>KOI 유전자는</p>
<ol>
<li>at와 gt는 가장 짧은 길이의 KOI 유전자</li>
<li>X가 KOI 유전자이면 aXt와 gXc도 KOI 유전자이다.</li>
<li>X와 Y가 KOI 유전자이면 XY도 KOI 유전자이다.</li>
</ol>
<p>KOI 유전자가 DNA 서열의 임의의 0개 이상 문자를 삭제해서 얻어지는 부분서열이라고 할때, 길이가 최대가 되는 KOI 유전자를 구하시오</p>
<h1 id="문제-풀이">문제 풀이</h1>
<p>dp에서 유명한 유형인 DNA 서열,,, 3학년 알골시간 중간고사에도 나왔던 걸로 기억 ,, 하지만 DP는 역시 쉽지않내요,,</p>
<p>Top-Down 방식으로 풀었다.
주어진 DNA 서열에 대해서</p>
<ol>
<li>(앞,뒤)의 쌍이 (&#39;a&#39;,&#39;t&#39;)이거나 (&#39;g&#39;,&#39;c&#39;)이면 앞+1, 뒤-1로 재귀</li>
<li>1 검사하고 주어진 서열 길이만큼 (앞,i), (i+1,뒤)가 KOI 서열을 만족하는지 재귀를 돈다.</li>
</ol>
<pre><code class="language-python">import sys

input = sys.stdin.readline

def findKOI(start, end):
    isKOI = dp[start][end]
    if isKOI != -1:
        return isKOI

    isKOI = 0
    # 조건 2: aXt, gXc 인지? 
    if((dna[start] == &#39;a&#39; and dna[end] == &#39;t&#39;) or (dna[start] == &#39;g&#39; and dna[end] == &#39;c&#39;)):
        isKOI = findKOI(start+1, end-1) + 2;

    for i in range(start, end):
        isKOI = max(isKOI, findKOI(start, i) + findKOI(i+1, end))

    dp[start][end] = isKOI
    return isKOI

dna = input()
dp = [[-1 for _ in range(len(dna))] for _ in range(len(dna))]
print(findKOI(0, len(dna)-1))</code></pre>
<h1 id="참고">참고</h1>
<p><a href="https://www.crocus.co.kr/964">https://www.crocus.co.kr/964</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ] 3649 - 로봇 프로젝트]]></title>
            <link>https://velog.io/@woo0_hooo/BOJ-3649-%EB%A1%9C%EB%B4%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@woo0_hooo/BOJ-3649-%EB%A1%9C%EB%B4%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Sun, 20 Jun 2021 03:07:26 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크">문제 링크</h1>
<p><a href="https://www.acmicpc.net/problem/3649">3649 - 로봇 프로젝트</a></p>
<h1 id="문제-설명">문제 설명</h1>
<p>x 센치의 너비를 가진 구멍을 채우기 위해서 길이의 합이 구멍의 너비와 정확하게 일치하는 두 개의 레고 조각이 필요하다. 
각 테스트 케이스마다</p>
<ul>
<li>구멍의 크기</li>
<li>레고조각의 개수</li>
<li>각 조각마다의 길이</li>
</ul>
<p>가 주어질때, 구멍을 완벽하게 막을 수 있는 두 조각을 구하시오.</p>
<h1 id="문제-풀이">문제 풀이</h1>
<p>EZ한 투포인터 문제였다,,~ 근데 테스트케이스가 몇개인지 주어지지 않기 때문에 try except를 이용해서 풀어야 한다. </p>
<p>각 테스트 케이스마다</p>
<ol>
<li>레고 조각의 길이를 정렬</li>
<li>투포인터를 사용해서 구멍과 일치하는 두조각 찾기
로 풀면 된다.<pre><code class="language-python">import sys 
</code></pre>
</li>
</ol>
<p>input = sys.stdin.readline</p>
<p>while True:
    try:
        x = int(input())* pow(10,7)
        n = int(input())
        lego = []
        for _ in range(n):
            lego.append(int(input()))</p>
<pre><code>    # 1. 정렬하기
    lego.sort()

    # 2. 투포인터로 탐색
    left, right = 0, len(lego)-1
    flag = False

    while left &lt; right :
        if lego[left] + lego[right] == x:
            flag = True
            print(&quot;yes&quot;, str(lego[left]), str(lego[right]))
            break
        elif lego[left] + lego[right] &gt; x:
            right -= 1
        else:
            left += 1

    if flag == False:
        print(&quot;danger&quot;)
except:
    break</code></pre><pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] HTTP 상태코드에 대해 알아보자]]></title>
            <link>https://velog.io/@woo0_hooo/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 03 Jun 2021 13:22:34 GMT</pubDate>
            <description><![CDATA[<h2 id="1xx--처리중">1xx : 처리중</h2>
<p>거의 사용하지 않는다고 알고 있습니다</p>
<h2 id="2xx--요청의-정상-처리">2xx : 요청의 정상 처리</h2>
<ul>
<li>200 OK 성공</li>
<li>201 리소스의 생성시 사용 → 헤더 필드에 리소스의 주소가 들어있다.</li>
<li>202 accessed 접수는 됐는데 아직 처리는 안된 경우 → 배치 처리에서 사용한다.</li>
<li>204 no content → 정상 요청이지만 payload 본문에 데이터가 필요없는 경우</li>
</ul>
<h2 id="3xx--리다이렉션">3xx : 리다이렉션</h2>
<p>요청 완료를 위해 추가 행동이 필요한 경우이다</p>
<h3 id="리다이렉션">리다이렉션?</h3>
<p>Location header의 위치로 다시 이동하는 것</p>
<h3 id="영구-리다이렉션">영구 리다이렉션</h3>
<p>: 특정 리소스의 URI가 영구적으로 이동한 경우</p>
<ul>
<li>301 Moved permanantly → 리다이렉트시 요청이 GET 본문은 제거 (May)</li>
<li>308 Permanent Redirect 위에꺼랑 기능은 같은데 요청과 본문은 유지</li>
</ul>
<h3 id="일시-리다이렉션">일시 리다이렉션</h3>
<ul>
<li>302 Found → 리다이렉트시 요청은 GET, 본문은 제거 (May)</li>
<li>307 Temporary Redirect 위에꺼랑 기능은 같은데 요청과 본문은 유지</li>
<li>303 See Other 리다이렉트시 요청 메소드가 무조건 GET으로</li>
</ul>
<p>→ 는 언제 사용할까? PRG에서!</p>
<p>PRG란 POST→ REDIRECT→ GET의 약자이다. </p>
<p>Post로 주문 후에 웹 브라우저를 그대로 새로고침하면 중복 주문의 오류가 발생이 가능하다. POST로 주문 후 GET으로 리다이렉트 하도록 200ok가 아닌 302나 303으로 리다이렉트 하도록 한다.</p>
<h3 id="특수-리다이렉션">특수 리다이렉션</h3>
<p>: 결과 대신 캐시를 사용하는 방법</p>
<h2 id="4xx--클라이언트의-오류">4xx : 클라이언트의 오류</h2>
<p>: 오류의 원인이 클라이언트에게 있는 경우이다. 뭔가 잘못된 문법이 있거나, 요청한 파라미터가 잘못된 경우가 그 예이다. 여러번 재시도해도 똑같이 실패한다.</p>
<ul>
<li>400 Bad Request 잘못된 요청시 e.g. 요청 parameter가 잘못될때</li>
<li>401 unauthorized → 인증이 되지 않은 경우</li>
<li>403 forbidden → 서버가 요청은 정상적으로 이해했는데 승인이 거부된 경우</li>
<li>404 not found → 요청한 리소스가 없는 경우</li>
</ul>
<h2 id="5xx--서버의-오류">5xx : 서버의 오류</h2>
<p>→ 서버가 정상 처리되지 않은 경우 → 이때는 재시도시 성공할 수도 있음</p>
<ul>
<li>500 Internal Server Error 애매하면 대부분 이 에러</li>
<li>503 서비스 이용 불가 → 서비스가 과부하 또는 점검으로 일시적으로 다운된 경우</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] HTTP Method에 대해서 알아보자]]></title>
            <link>https://velog.io/@woo0_hooo/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-Method%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-HTTP-Method%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 03 Jun 2021 12:29:15 GMT</pubDate>
            <description><![CDATA[<h2 id="http-method">HTTP Method</h2>
<p>클라이언트가 서버에게 요청할때 기대하는것입니다.</p>
<p>자주 사용하는 HTTP Method에는 크게 GET, POST, PUT, PATH, DELETE가 있습니다.</p>
<h3 id="get">GET</h3>
<p>조회하는데 사용합니다. 쿼리 파라미터를 통해서 조회의 대상을 전달합니다. 바디를 통해서도 전달이 가능하지만, 지원하지 않는 서버가 많다고 알고 있습니다.</p>
<h3 id="post">POST</h3>
<p>요청한 data에 대한 처리를 하는 Method이고, 주로 등록하는데 많이 사용합니다. 바디를 통해서 요청할 데이터를 전달하고, 서버는 요청한 데이터를 처리합니다. 포스트 요청에 대해서는 리소스별로 각자 액션을 정의해줘야합니다. </p>
<p>역할은 크게</p>
<ol>
<li>새 리소스의 생성</li>
<li>요청한 데이터의 처리 → 단순히 값의 변경이 아닌, process의 상태가 변경되는 경우에 사용합니다.</li>
<li>다른 메소드로 처리하기 애매한 경우</li>
</ol>
<p>가 있습니다. </p>
<h3 id="put">PUT</h3>
<p>폴더에 파일을 넣는다고 생각하면 이해가 쉬운데, 이미 해당 리소스가 있으면 완전히 대체, 없으면 새로 작성하는 메소드입니다. &quot;수정&quot;이 아닌 덮어쓰기의 개념입니다. 클라이언트가 리소스를 식별하여 구체적인 리소스의 URL을 알고 있습니다.</p>
<h3 id="patch">PATCH</h3>
<p>리소스를 부분 변경하는 메소드입니다. 해당 필드만 부분 변경이 가능하고, 일부 지원되지 않는 서버의 경우에는 POST로 사용합니다.</p>
<h3 id="delete">DELETE</h3>
<p>리소스를 삭제할때 사용합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크] 인터넷 프로토콜에 대해 알아보자]]></title>
            <link>https://velog.io/@woo0_hooo/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%B8%ED%84%B0%EB%84%B7-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%B8%ED%84%B0%EB%84%B7-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 03 Jun 2021 09:04:27 GMT</pubDate>
            <description><![CDATA[<h1 id="ip">IP</h1>
<p>IP 프로토콜은 지정한 주소로 packet을 전달하는 프로토콜입니다.</p>
<h2 id="특징">특징</h2>
<ul>
<li>비연결성 : 패킷을 받을 대상이 없거나 불능이어도 알 수 없음</li>
<li>비신뢰성 : 중간에 패킷이 사라지거나, packet이 순서대로 오지 않아도 알 수 없음</li>
</ul>
<p>-&gt; 이런 IP의 한계를 해결하자~</p>
<h1 id="tcp와-udp">TCP와 UDP</h1>
<p>이런 IP의 한계를 해결하기 위해서 Transport 계층에서 사용하는 프로토콜이 TCP와 UDP입니다.</p>
<h2 id="tcp">TCP</h2>
<ul>
<li>연결 지향형 : 3 way handshake</li>
<li><blockquote>
<p>수신자가 SYN를 보내면 송신자는 잘 받았단 뜻의 ACK + SYN, 수신자는 SYN에 대해 ACK를 보내고, 이로서 통신이 시작됩니다.</p>
</blockquote>
</li>
<li>데이터 전달 보증: 패킷이 누락되면 알 수 있습니다.</li>
<li>순서보장: packet의 순서가 보장됩니다.</li>
</ul>
<p>-&gt; 따라서 TCP는 신뢰가능한 프로토콜!</p>
<h2 id="udp">UDP</h2>
<ul>
<li>연결지향x, 순서보장x, 데이터 전달 보장 x인 프로토콜입니다. IP와 거의 같지만, 차이점은 Port 번호 정보와 checksum을 포함한다는 것입니다.</li>
<li>TCP보다 속도가 빠르다는 장점이 있습니다.</li>
<li>요즘은 UDP가 각광받고 있는데, HTTP3에서 최적화를 위해 UDP를 사용하기 떄문입니다. <h3 id="port">port?</h3>
한 IP에서 프로세스를 구분하기 위해 사용하는 주소</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Jenkins] Pipeline을 만들어보자 ~]]></title>
            <link>https://velog.io/@woo0_hooo/Jenkins-Pipeline%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Jenkins-Pipeline%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 01 Jun 2021 18:34:50 GMT</pubDate>
            <description><![CDATA[<h1 id="pipeline-만들기">Pipeline 만들기</h1>
<p>pipeline의 시나리오는 다음과 같습니다.</p>
<ul>
<li>git checkout</li>
<li>gcc hello.c; ./a.out &gt; index.html</li>
<li>docker build image</li>
<li>docker push image</li>
<li>docker run image</li>
</ul>
<p>New Item에서 Pipeline을 만들어 줍니다.</p>
<p><img src="https://images.velog.io/images/woo0_hooo/post/1995d78e-77eb-44b6-ad15-eee8942cd051/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.15.11.png" alt="">
스크립트는 <a href="http://3.37.87.174:8080/job/demo/pipeline-syntax/">http://3.37.87.174:8080/job/demo/pipeline-syntax/</a> 를 참고할 수 있습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/56cccada-0d04-4a0e-8043-951f71dbfa33/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.22.15.png" alt="">
credentials는 public repo라 없어도 되지만 한번 설정해보았습니다. 연결하려는 github의 계정정보를 입력하면 됩니다.
여기서 만든 pipeline script를 checkout stage에 추가해줍니다.
<img src="https://images.velog.io/images/woo0_hooo/post/a42da681-e7f3-4d1a-900d-6f1cb7efb3ff/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.24.35.png" alt="">
<img src="https://images.velog.io/images/woo0_hooo/post/83f69304-f1af-4800-9b40-17c7a2229aaa/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.26.16.png" alt=""></p>
<p>master instance에서 확인했을때 맞게 checkout됐음을 알 수 있습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/cd4a803b-4048-41f6-97fc-51ed4f66573e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.28.49.png" alt=""></p>
<p>docker pipeline을 사용하기 위한 plugin을 설치합니다.
<img src="https://images.velog.io/images/woo0_hooo/post/bd21a434-8198-40de-b522-c04a3994e603/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.38.00.png" alt="">
master node에 docker-jenkins 권한을 설정해줍니다.</p>
<pre><code>sudo usermod -aG docker jenkins
sudo chown root:jenkins /var/run/docker.sock</code></pre><p>dockerhub에 로그인 하기 위한 credentials를 설정합니다. 명령어 자체를 입력해서 비밀번호를 하드코딩 해서 넣을 수도 있지만, 비밀번호 노출을 막기 위해 credentials binfing plugin을 활용합니다.
위의 github credentials 설정과 같이 <a href="http://3.37.87.174:8080/job/demo/pipeline-syntax/">http://3.37.87.174:8080/job/demo/pipeline-syntax/</a> 에서 script를 작성합니다.
<img src="https://images.velog.io/images/woo0_hooo/post/e39d21ff-4d03-4df1-bdaa-26f4ee05be13/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.51.01.png" alt=""></p>
<p>에서 Username and password를 선택합니다. 두개중에 아무거나 골라도 상관 무,, 저는 seperated를 골랐습니당
<img src="https://images.velog.io/images/woo0_hooo/post/f5b438ae-ad13-44fe-8290-6a0b3c1747dc/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%203.52.22.png" alt="">
도커 계정 정보를 입력합니다. 여기서 generate한 스크립트를 다음 스테이지에 넣어줍니당</p>
<p>전체 시나리오에 대한 pipeline scipt은 다음과 같습니다.</p>
<pre><code class="language-jenkins">node {
    stage(&#39;git checkout&#39;){
        git branch: &#39;master&#39;, credentialsId: &#39;gitpasswd&#39;, url: &#39;https://github.com/wookyoungkim/CICD&#39;
    }
    stage(&#39;compile hello.c and produce index.html&#39;){
        sh &#39;gcc hello.c&#39;
        sh &#39;./a.out &gt; index.html&#39;
    }
    stage(&#39;build docker image&#39;){
        sh &#39;docker build -t viliketh1s/myweb:1.1.0 .&#39;
    }
    stage(&#39;push docker image&#39;){
        withCredentials([usernamePassword(credentialsId: &#39;docker&#39;, passwordVariable: &#39;dockerpwd&#39;, usernameVariable: &#39;dockeruser&#39;)]) {
            sh &quot;docker login -u ${dockeruser} -p ${dockerpwd}&quot;
        }
        sh &#39;docker push viliketh1s/myweb:1.1.0&#39;
    }
    stage(&#39;deploy demo container&#39;){
        sh &#39;docker run -itd -p 5000:80 --name my-app viliketh1s/myweb:1.1.0&#39;
    }
}</code></pre>
<p><img src="https://images.velog.io/images/woo0_hooo/post/5431078a-2770-42c1-b3c2-58a0b69940c9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%204.04.29.png" alt="">
으음 ,,, ^ ^낫 이지 ^_ㅠ 졸프에서 빌드 자동화툴로 써먹어야지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Jenkins] GitHub에 push 되면 자동 빌드해보자 ]]></title>
            <link>https://velog.io/@woo0_hooo/Jenkins-GitHub%EC%97%90-push-%EB%90%98%EB%A9%B4-%EC%9E%90%EB%8F%99-%EB%B9%8C%EB%93%9C%ED%95%98%EB%8A%94-Pipeline%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Jenkins-GitHub%EC%97%90-push-%EB%90%98%EB%A9%B4-%EC%9E%90%EB%8F%99-%EB%B9%8C%EB%93%9C%ED%95%98%EB%8A%94-Pipeline%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 01 Jun 2021 17:42:24 GMT</pubDate>
            <description><![CDATA[<p>간단하게 GitHub에 push 되면 자동 빌드하도록 project를 구성해보겠습니다.</p>
<h1 id="첫번째-project-만들기">첫번째 project 만들기</h1>
<p>New Item 클릭</p>
<p><img src="https://images.velog.io/images/woo0_hooo/post/c68a5a41-7f94-4c38-83e1-66ade88d2ccc/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.43.58.png" alt=""></p>
<p>이 프로젝트가 build node에서만 실행될 수 있도록 설정해줍니다.
<img src="https://images.velog.io/images/woo0_hooo/post/1a55cc33-0f41-4b8a-8a40-dd909844b95b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.45.13.png" alt=""></p>
<p>연결할 github의 repository 주소를 입력해줍니다. 브랜치는 기본 Master로 설정해주었습니다. 브랜치 안만들거라,,
<img src="https://images.velog.io/images/woo0_hooo/post/42984f58-bffb-42d4-bfda-f8ec74323c9b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.45.56.png" alt=""></p>
<p>Build Trigger는 GitHub hook trigger for GITScm polling 를 선택! 말 그대로 GitHub 의 hook trigger 를 받으면 빌드를 하겠다는 뜻임</p>
<p>지금은 build툴을 사용하지 않을것이므로 build는 따로 설정해주지 않았습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/1c79a725-0007-4c25-b1f7-9d0f3dbfb654/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.47.28.png" alt="">
까지 설정하고 save~</p>
<p>repository에 새 commit을 올려서 제대로 반영이 되는지 확인해봅시다.<img src="https://images.velog.io/images/woo0_hooo/post/7b9021e3-8b04-43d4-a9d8-9470d497564a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.52.35.png" alt=""></p>
<p>만든 프로젝트를 run 해봅니다.
<img src="https://images.velog.io/images/woo0_hooo/post/3134e0f8-0e9c-4ab0-92f1-35d0c9dfe3a2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.18.36.png" alt="">
빌드 히스토리를 보면 맞게 빌드됨을 확인할 수 있습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/96d364df-f4fc-4046-8ef0-6635d8ed3f71/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.19.52.png" alt=""></p>
<h2 id="두번째-프로젝트-만들기">두번째 프로젝트 만들기</h2>
<p>첫번째 프로젝트에서 pull받아온 코드를 컴파일하고 빌드하는 프로젝트를 만들어보겠습니다.
아까와 같이 new items를 클릭하여 새 프로젝트를 만듭니다. 대상을 아까처럼 build agent로 설정해주고, git은 사용하지 않을 것이므로 선택하지 않습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/f00c246e-6d58-4fe7-9aa0-6d6a78d393e0/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.23.14.png" alt=""></p>
<p>빌드는 쉘을 사용해서 다음과 같은 스크립트로 컴파일-&gt;빌드하려고 합니다.
<img src="https://images.velog.io/images/woo0_hooo/post/05d39a12-ee9c-4883-9507-4cbb601e92fd/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.23.55.png" alt=""></p>
<p>빌드에 사용하기 위한 gcc와 docker를 Agent instance에 설치합니다.</p>
<pre><code>sudo apt-get install docker.io
sudo apt-get install gcc</code></pre><p>설치 후 두번째 프로젝트를 빌드합니다.</p>
<p><img src="https://images.velog.io/images/woo0_hooo/post/66224698-ab69-4da8-8f4b-51236e3ba356/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.40.19.png" alt="">
성공 😎</p>
<p>Agent Instance의 public IP주소에서 접근하여 맞게 빌드됐는지 확인해봅시당 .<img src="https://images.velog.io/images/woo0_hooo/post/2dc01d0e-adaa-4603-8f5f-9f7fe5939bae/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.41.15.png" alt=""></p>
<h2 id="두-프로젝트를-엮어보자">두 프로젝트를 엮어보자</h2>
<p>지금은 수동으로 Build Now를 클릭해서 빌드하였는데, 첫번째 프로젝트가 문제없이 빌드되면 두번째 프로젝트가 수행되도록 해보겠습니다.</p>
<p>첫번째 프로젝트의 configure를 수정합시다.
<img src="https://images.velog.io/images/woo0_hooo/post/4030c318-7502-405c-92ad-4d2ae8027b80/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.45.48.png" alt="">
빌드 후 다른 프로젝트를 빌드하도록 설정!
<img src="https://images.velog.io/images/woo0_hooo/post/bcc84fd7-ce6d-4642-a6ce-a8f7f37fde81/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.46.25.png" alt="">
<img src="https://images.velog.io/images/woo0_hooo/post/88909211-f9f0-4d68-825a-e6e7e2da2382/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.46.56.png" alt=""></p>
<p>Github에서 WebHook을 추가합니다.
<img src="https://images.velog.io/images/woo0_hooo/post/f556069c-6792-4151-9f6b-6bc73683bbc3/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.48.15.png" alt="">
Jenkins가 돌고있는 인스턴스의 publicIP:8080으로 주소를 설정해줍니다.
<img src="https://images.velog.io/images/woo0_hooo/post/876d07b4-795d-4078-9ddc-cb1e8ab33fba/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.51.52.png" alt=""></p>
<p>repo에 새 commit을 하여 자동화되었는지 확인해봅시다.!
첫번째 pull-code가 실행되고, 두번째인 compile-build를 트리거했다는 로그를 볼 수 있습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/a263f32f-0a4b-4d87-a92b-2512f5dbd073/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.55.01.png" alt="">
compile-build의 로그에서 pull-code에 의해 실행되었고, 설정한 스크립트대로 수행되었음을 볼 수 있습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/aa322c14-a626-4441-97c4-b0a4fb0253d9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.56.27.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Jenkins] Jenkins를 설치부터 연결해보자 ~]]></title>
            <link>https://velog.io/@woo0_hooo/Jenkins-Jenkins%EB%A5%BC-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Jenkins-Jenkins%EB%A5%BC-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 01 Jun 2021 16:39:05 GMT</pubDate>
            <description><![CDATA[<h1 id="jenkins">Jenkins</h1>
<p>가장 널리 쓰이는 DevOps Integrity 도구</p>
<ul>
<li>오픈소스이다</li>
<li>어떤 SW DevOps도 자동화가 가능하다</li>
<li>Java로 작성되어 있음</li>
</ul>
<h1 id="설치하기">설치하기</h1>
<p><a href="https://linuxize.com/post/how-to-install-jenkins-on-ubuntu-18-04/">https://linuxize.com/post/how-to-install-jenkins-on-ubuntu-18-04/</a> 와  <a href="https://pkg.jenkins.io/debian-stable">https://pkg.jenkins.io/debian-stable</a> 를 참고해서 설치했습니다.
이해를 위해 흰 배경은 Master, 회색 배경은 Agent로 사용하겠습니다.</p>
<h2 id="새-인스턴스-생성">새 인스턴스 생성</h2>
<p>Master로 쓸 인스턴스 하나와 Agent로 쓸 인스턴스 하나를 생성합니다.
<img src="https://images.velog.io/images/woo0_hooo/post/dd96cbd9-1eba-4725-b917-39b6bd63f017/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.27.09.png" alt=""></p>
<p>Master와 Agent 사이의 통신을 위해 다음과 같이 보안 규칙을 수정해줍니다. 특정 포트로 통신할 수도 있지만 간단한 실습을 위한 것이니 그냥 어디서든 접근 가능하게 수정해줍니다.
<img src="https://images.velog.io/images/woo0_hooo/post/b686513f-853b-4198-82ea-52793d4ec991/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.26.33.png" alt=""></p>
<h2 id="인스턴스에-자바-설치하기">인스턴스에 자바 설치하기</h2>
<p>Jenkins는 자바로 작성된 프로그램이므로 인스턴스에 자바를 설치해줍니다.!
Master Instance
<code>sudo apt install openjdk-8-jdk</code>
자바 8을 사용해야 동작하므로 맞는 버전을 설치해줍니다.
BuildAgent Instance에서도 마찬가지로 자바를 설치해줍니다.</p>
<h2 id="master에-jenkins-설치">Master에 Jenkins 설치</h2>
<p><a href="https://pkg.jenkins.io/debian-stable">https://pkg.jenkins.io/debian-stable</a> 를 참고하여 Master Instance에 젠킨스를 설치합니다.</p>
<p>Master Node에서 sudo 권한으로 바꿉니다. 
-&gt; 해당 위치는 일반 권한으로 접근이 안되기 떄문에~
<code>sudo su</code>
<code>/etc/apt/sources.list</code>     파일의 맨 윗줄에 다음 명령어를 추가합니다.
<code>deb https://pkg.jenkins.io/debian-stable binary/</code>
<img src="https://images.velog.io/images/woo0_hooo/post/7b80c6f5-9c23-458f-8f6a-0fe38b9d3b66/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.42.01.png" alt=""></p>
<p>저장하고, 루트권한을 종료시킨뒤 젠킨스를 설치합니다.
<code>sudo apt-get update</code>
<code>sudo apt-get install jenkins</code></p>
<p>맞게 설치되어 동작하는지 확인합니다~
<img src="https://images.velog.io/images/woo0_hooo/post/67850fe5-6b93-48a1-9290-4853ec552208/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.50.08.png" alt=""></p>
<h1 id="젠킨스-접속하기">젠킨스 접속하기</h1>
<p>Master Instance의 퍼블릭IPv4:8080으로 접속하면 다음과 같은 화면을 볼 수 있습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/6e4ddbea-ccd7-45f4-b1dc-c61369f4f244/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.51.35.png" alt="">
페이지에 안내되어있는 경로를 접속하여 비밀번호를 찾고, 해당 비밀번호를 입력합니당.
<code>sudo cat /var/lib/jenkins/secrets/initialAdminPassword</code>
<img src="https://images.velog.io/images/woo0_hooo/post/9f132131-66c8-4f90-b382-01aecdd94e8b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.53.54.png" alt="">
전 왼쪽의 suggested plugins를 선택하여 설치했습니다.
설치가 오래 걸린다,,</p>
<p>적절한 유저네임, 패스워드를 설정한다음 Continue!    
<img src="https://images.velog.io/images/woo0_hooo/post/b74660dc-53d0-4aa1-b3e2-2e418fcde251/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.05.12.png" alt=""></p>
<p><img src="https://images.velog.io/images/woo0_hooo/post/162c8601-5747-4904-8f56-db911b823c15/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.05.46.png" alt=""></p>
<p>Agent 기계와 연결될 수 있도록 설정합니다.
<img src="https://images.velog.io/images/woo0_hooo/post/c64d84ee-574a-4ba2-9c0d-a685f7f4947d/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.07.03.png" alt="">
Random port로 클릭!
<img src="https://images.velog.io/images/woo0_hooo/post/b28fc630-b4f7-49e7-a7fb-3188d79b8960/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.07.47.png" alt="">
<img src="https://images.velog.io/images/woo0_hooo/post/10ecca14-bca1-4bd3-a051-47e7dfb22d3a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.08.30.png" alt="">
Agent로 사용할 New Node를 추가합니다.<img src="https://images.velog.io/images/woo0_hooo/post/47e50d26-5ab7-41eb-8972-77593d74a394/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.10.11.png" alt="">
적절한 이름을 생성해주고, 아까 Master Node에 생성한 jenkins 폴더의 경로를 Labels에 입력, Launch Method는 connecting to the master로 설정해줍니다.
<img src="https://images.velog.io/images/woo0_hooo/post/c605ae90-a80d-4ecb-9a5a-715a8f76cb2b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.11.08.png" alt=""></p>
<h1 id="master와-agent-연결하기">Master와 Agent 연결하기</h1>
<p>build node 클릭!
<img src="https://images.velog.io/images/woo0_hooo/post/d45127ee-ff95-4b5f-9387-df5e142ed5e9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.14.30.png" alt="">
Launch를 클릭해서 jnlp파일을 다운받고, agent.jar 파일을 다운받습니다.
<img src="https://images.velog.io/images/woo0_hooo/post/2be84f48-3844-43ab-a08a-bc28089dbb69/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.14.48.png" alt=""></p>
<p>깃헙 레포지토리를 하나 파고, 아까 다운받은 두 파일을 업로드 해줍니다.<img src="https://images.velog.io/images/woo0_hooo/post/8783251d-b713-4154-92d0-5522f8698c28/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.19.33.png" alt=""></p>
<h2 id="agent에-git-설치">Agent에 Git 설치</h2>
<p>Agent Instance에 git을 설치합니다.
<code>sudo apt-get install -y git</code>
<img src="https://images.velog.io/images/woo0_hooo/post/cdd09607-0d83-486f-9ce1-c1044c1c4afe/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.49.23.png" alt="">
Agent에 jenkins 디렉토리를 만들고 git init &amp; 아까 만든 레포지토리를 origin으로 설정해줍니다. 그리고 풀받아오기 !</p>
<pre><code>git init
git remote add origin {레포지토리주소}
git pull origin master</code></pre><p><img src="https://images.velog.io/images/woo0_hooo/post/8eda50a8-c63c-42ea-b13d-839f26800c0a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.31.16.png" alt="">
<img src="https://images.velog.io/images/woo0_hooo/post/06cd53b6-ce9c-44f0-8ddc-70af81035c41/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.32.08.png" alt=""></p>
<p>그리고 아까 Jenkins에서 안내해준 cmd를 입력합니다.</p>
<pre><code>java -jar agent.jar -jnlpUrl http://3.37.87.174:8080/computer/build/jenkins-agent.jnlp -secret 48bccd95d36f1ba45658657a1d88557b94814bca39070eaff9873e14e537552f -workDir &quot;/home/ubuntu/jenkins&quot;</code></pre><p>-&gt; <img src="https://images.velog.io/images/woo0_hooo/post/5bdc1b83-e78b-4d34-830d-37e4c798b020/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-06-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%201.35.26.png" alt="">
연결 완 료 @~!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] JPA에 대해 알아보자~]]></title>
            <link>https://velog.io/@woo0_hooo/Spring-JPA%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Spring-JPA%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 31 May 2021 05:56:24 GMT</pubDate>
            <description><![CDATA[<h2 id="jpa란">JPA란</h2>
<p>자바의 ORM 기술 표준으로 인터페이스의 모음이다. → 이를 실제로 구현한 구현체가 하이버네이트</p>
<ul>
<li><a href="https://velog.io/@woo0_hooo/Spring-Persistence%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90#orm">ORM?</a>
  Object Oriented Mapping: RDB 테이블을 객체지향적으로 사용하기 위한 기술
<img src="https://images.velog.io/images/woo0_hooo/post/9ada388e-a78e-4076-a99e-3ba2916524b3/Untitled.png" alt=""></li>
</ul>
<p>Java 어플리케이션과 JDBC 사이에서 동작한다.</p>
<p>→ 개발자가 JPA를 사용하면 JPA 내부에서 JDBC API를 사용하여 SQL을 호출하고, DB와 통신한다.</p>
<h2 id="spring-data-jpa">Spring Data JPA</h2>
<p>JPA를 한단계 더 추상화시킨 Repository 인터페이스를 제공 </p>
<p>→ 정해진 규칙대로 메소드를 입력하면, 해당 메소드에 맞는 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>반복된 코드를 제거하므로 생산성이 크게 향상</li>
<li>SQL을 직접 실행함으로서 객체 중심의 설계가 가능 → SQL 중심 개발? 코드 반복이 많음, 모델링 과정에서 문제 발생</li>
<li>기본적인 CRUD를 제공한다. 개발자가 핵심 비즈니스 로직에만 집중할 수 있게 해준다.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li><p>복잡한 쿼리보다는 실시간 처리용 쿼리에 최적화되어있다.</p>
<p>  : 통계와 같이 미세하게 쿼리작업이 필요하면 Mybatis등의 다른 Mapper 방식을 사용해야한다</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Persistence에 대해 알아보자]]></title>
            <link>https://velog.io/@woo0_hooo/Spring-Persistence%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Spring-Persistence%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 31 May 2021 05:54:11 GMT</pubDate>
            <description><![CDATA[<h1 id="persistence">Persistence</h1>
<p>데이터 생성 프로그램이 종료되어도 데이터는 사라지지 않는 특성
→ 파일시스템, RDB 등을 사용해서 객체에 영속성을 부여한다.</p>
<h2 id="persistence-framework">Persistence Framework</h2>
<h3 id="jdbc">JDBC</h3>
<p>자바에서 DB에 접근하기 위해 사용하는 API
→ 모든 Persistence Framework는 내부적으로 JDBC API를 이용한다. </p>
<p>이러한 Persistence를 부여하는 Framework에는 두가지 종류가 있다.</p>
<h3 id="sql-mapper">SQL Mapper</h3>
<p>SQL과 객체를 매핑 → 직접 SQL을 다뤄서 매핑한다.
→ MyBatis, JDBCTemplate</p>
<ul>
<li>MyBatis : SQL, procedure 등을 지원하는 SQL Mapper이다. SQL에 대한 모든 control이 가능하다.</li>
<li>JDBCTemplate : 스프링의 가장 기본적인 Data Access 템플릿으로 쿼리 기반으로 데이터 베이스의 접근 가능</li>
</ul>
<h3 id="orm">ORM</h3>
<p>Object Oriented Mapping의 약자로 RDB과 객체를 매핑하는 기술이다. 프로그래머가 SQL을 직접 다룰 필요 없이, SQL을 자동 생성해준다.
→ JPA</p>
<p><strong>장점</strong> </p>
<ul>
<li>객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있게 도와준다.</li>
<li>재사용 및 유지보수의 편리성이 증가한다.</li>
<li>DBMS에 대한 종속성이 줄어든다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>완벽한 ORM 으로만 서비스를 구현하기가 어렵다.</li>
<li>프로시저가 많은 시스템에선 ORM의 객체 지향적인 장점을 활용하기 어렵다.</li>
</ul>
<h1 id="출처">출처</h1>
<p><a href="https://gmlwjd9405.github.io/2018/12/25/difference-jdbc-jpa-mybatis.html">https://gmlwjd9405.github.io/2018/12/25/difference-jdbc-jpa-mybatis.html</a>
<a href="https://github.com/WeareSoft/tech-interview/blob/master/contents/db.md#orm%EC%9D%B4%EB%9E%80">https://github.com/WeareSoft/tech-interview/blob/master/contents/db.md#orm%EC%9D%B4%EB%9E%80</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 의 Garbage Collection에 대해 알아보자]]></title>
            <link>https://velog.io/@woo0_hooo/Java-%EC%9D%98-Garbage-Collection%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Java-%EC%9D%98-Garbage-Collection%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 28 May 2021 07:02:41 GMT</pubDate>
            <description><![CDATA[<h1 id="garbage란">Garbage란?</h1>
<ul>
<li>주소를 잃어버려서 사용할 수 없는 정리되지 않은 메모리</li>
<li>앞으로 사용하지 않고 메모리를 가지고 있는 객체</li>
</ul>
<h1 id="garbage-collector">Garbage Collector</h1>
<ul>
<li><p>이러한 가비지들을 메모리에서 정리하여 다른 용도로 사용할 수 있게 해주는 프로그램</p>
</li>
<li><p>자바는 개발자가 명시적으로 메모리 해제를 수행하지 않고, 가비지 컬렉터가 대신 해줌
  → C에서는 free()를 이용해서 직접 해제</p>
</li>
<li><p>GC의 대상 : 객체가 NULL 인 경우, 블록 안에서 객체가 생성되고 블록의 실행이 끝난 경우</p>
</li>
</ul>
<h1 id="gc의-동작">GC의 동작</h1>
<h2 id="minor-gc와-major-gc">Minor GC와 Major GC</h2>
<p>GC는 두가지 가정 하에 동작한다.</p>
<ol>
<li>대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.</li>
<li>오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.</li>
</ol>
<p>이 가설에 기반하여, 영역을 young 영역, old 영역 두가지로 나눈다.</p>
<p>→ young 영역에서의 GC가 Minor GC, old 영역에서의 GC가 Major GC이다.</p>
<h3 id="minor-cg">Minor CG</h3>
<ul>
<li>young 영역 : eden영역 + 2*survivor 영역</li>
</ul>
<ol>
<li>새롭게 생성되는 객체는 eden 영역에 생성</li>
<li>eden 영역이 가득차게 된다면 minor GC → 살아남은 객체들을 survivor영역 중 하나로</li>
<li>survivor 영역이 가득차게 되면 살아남은 객체를 다른 survivor 영역으로 </li>
<li>aging이 되고 정해진 threshold를 넘으면, old 영역으로</li>
</ol>
<h3 id="major-gc">Major GC</h3>
<p>Old 영역의 메모리가 부족해지면 발생하게 된다.</p>
<p>→ Old 영역은 Young 영역보다 크고, Young 영역을 참조할 수도 있기 때문에 일반적으로 Minor GC보다 오래걸림</p>
<h2 id="동작방식">동작방식</h2>
<h3 id="stop-the-world">stop-the-world</h3>
<ul>
<li><p>GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것</p>
<p>  → GC 관련 스레드를 제외한 모든 스레드의 동작을 중단</p>
<p>  → GC 튜닝? 이 stop-the-world 시간을 줄이는 것</p>
</li>
</ul>
<p><strong>Mark and Sweep</strong></p>
<ul>
<li><p>Mark: 힙(heap) 내의 객체 중에서 가비지를 식별하는 작업</p>
<p>  → reachability라는 개념을 사용 : 어떤 객체에 유효한 참조가 있으면 &#39;reachable&#39;로, 없으면 &#39;unreachable&#39;로 구별하고, unreachable 객체를 가비지로 간주해 GC를 수행</p>
</li>
<li><p>Sweep: Mark 단계에서 찾은 가비지를 처리해서 메모리를 회수하는 작업</p>
</li>
</ul>
<h1 id="출처">출처</h1>
<p><a href="https://d2.naver.com/helloworld/329631">https://d2.naver.com/helloworld/329631</a>
<a href="https://d2.naver.com/helloworld/1329">https://d2.naver.com/helloworld/1329</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[REST API] URL 생성 규칙]]></title>
            <link>https://velog.io/@woo0_hooo/REST-API-URL-%EC%83%9D%EC%84%B1-%EA%B7%9C%EC%B9%99</link>
            <guid>https://velog.io/@woo0_hooo/REST-API-URL-%EC%83%9D%EC%84%B1-%EA%B7%9C%EC%B9%99</guid>
            <pubDate>Thu, 27 May 2021 02:17:38 GMT</pubDate>
            <description><![CDATA[<p>컨트롤러에서 URL 정의내릴때마다 햇갈려서 정리해봤습니다아</p>
<h1 id="기본규칙">기본규칙</h1>
<ol>
<li>URL은 자원에 대한 정보를 포함한다.</li>
<li>자원에 대한 행위는 HTTP Method로 표현한다.</li>
</ol>
<h1 id="restful-url-작성하기">RESTful URL 작성하기</h1>
<ol>
<li>소문자로 !</li>
<li>동사보다는 명사를 사용한다.
: 단순 명사가 아니라 복수 명사를 사용한다.</li>
<li>언더바대신 하이픈을 사용한다.
: 하이픈도 사용하지 않는게 좋다고 함</li>
<li>행위는 포함하지 않는다.</li>
<li><code>/</code>는 계층을 나타낸다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[디자인패턴] 싱글톤 패턴이란?]]></title>
            <link>https://velog.io/@woo0_hooo/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@woo0_hooo/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Tue, 25 May 2021 12:32:08 GMT</pubDate>
            <description><![CDATA[<h1 id="싱글톤-패턴이란">싱글톤 패턴이란</h1>
<blockquote>
<p>생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.
-위키피디아-</p>
</blockquote>
<p><strong>객체를 하나만</strong> 생성하고, 생성된 객체를 어디서든 참조가능하게 하여 프로그램 전반에서 <strong>하나의 인스턴스만 사용</strong>하게 하는 패턴이다. </p>
<h2 id="자바의-싱글톤-패턴">자바의 싱글톤 패턴</h2>
<ul>
<li><code>private</code> 사용 : 생성자를 <code>private</code>으로 선언하여 외부에서의 호출과 상속이 불가능하게 지정</li>
</ul>
<h1 id="장점">장점</h1>
<ul>
<li>메모리 낭비 방지 : 한번의 new로 인스턴스를 사용하므로 메모리 낭비를 방지할 수 있다.</li>
<li>데이터의 공유가 용이함 : 전역 인스턴스이므로 다른 클래서의 인스턴스들과 데이터 공유가 쉬움</li>
<li>주로 공통된 객체를 여러개 생성해서 사용하는 DBCP(DataBase Connection Pool)와 같은 상황에서 많이 사용
<em>-&gt; DBCP란? 데이터베이스와 애플리케이션을 효율적으로 연결하는 커넥션 풀 라이브러리</em></li>
</ul>
<h1 id="문제점">문제점</h1>
<ul>
<li>동시성 문제 고려 : 멀티쓰레드 환경에서 제대로 동기화 하지 않으면 race condition이 발생하여 인스턴스가 두개 생성되는 경우가 발생 가능 </li>
<li>개방 폐쇄 원칙 위배 가능 : 각 객체간의 결합도가 높아지고, 변경에 유연하게 대처할 수 없다.</li>
</ul>
<h1 id="동시성-문제-해결-방법">동시성 문제 해결 방법</h1>
<h2 id="방법-1-정적-변수에-인스턴스를-만들어-바로-초기화하기">방법 1: 정적 변수에 인스턴스를 만들어 바로 초기화하기</h2>
<pre><code class="language-java">private static Printer printer = new Printer();</code></pre>
<p>와 같이 static 변수에 바로 인스턴트를 만들어서 초기화시킨다.</p>
<pre><code>
-&gt; static 변수란? 객체가 생성되기 전 클래스가 메모리에 로딩될 때 만들어져 초기화가 한 번만 실행된다.
프로그램 시작~종료까지 없어지지 않고 메모리에 계속 상주하며 클래스에서 생성된 모든 객체에서 참조할 수 있다.</code></pre><h2 id="방법-2-메소드에-동기화-블록을-지정하기">방법 2: 메소드에 동기화 블록을 지정하기</h2>
<p>인스턴스를 만드는 메소드에 <code>synchronized</code>를 지정하여 해당 메소드를 임계구역으로 지정한다.
-&gt; 다중 스레드 환경에서 여러 스레드가 동시에 접근하는 것을 방지한다.</p>
<h1 id="출처">출처</h1>
<p><a href="https://ko.wikipedia.org/wiki/%EC%8B%B1%EA%B8%80%ED%84%B4_%ED%8C%A8%ED%84%B4">https://ko.wikipedia.org/wiki/%EC%8B%B1%EA%B8%80%ED%84%B4_%ED%8C%A8%ED%84%B4</a>
<a href="https://gmlwjd9405.github.io/2018/07/06/singleton-pattern.html">https://gmlwjd9405.github.io/2018/07/06/singleton-pattern.html</a>
<a href="https://velog.io/@kyle/%EC%9E%90%EB%B0%94-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4-Singleton-Pattern">https://velog.io/@kyle/%EC%9E%90%EB%B0%94-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4-Singleton-Pattern</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ] 10942 - 펠린드롬?]]></title>
            <link>https://velog.io/@woo0_hooo/BOJ-10942-%ED%8E%A0%EB%A6%B0%EB%93%9C%EB%A1%AC</link>
            <guid>https://velog.io/@woo0_hooo/BOJ-10942-%ED%8E%A0%EB%A6%B0%EB%93%9C%EB%A1%AC</guid>
            <pubDate>Sun, 23 May 2021 16:09:38 GMT</pubDate>
            <description><![CDATA[<h1 id="문제링크">문제링크</h1>
<p><a href="https://www.acmicpc.net/problem/10942">10942 - 펠린드롬?</a></p>
<h1 id="문제설명">문제설명</h1>
<p>N개의 자연수에 대해서 M번의 질문을 받는다.
각 질문은 두 정수 S와 E(1 ≤ S ≤ E ≤ N)로 나타낼 수 있으며, S번째 수부터 E번째 까지 수가 팰린드롬을 이루는지 물어본다. 펠린드롬이면 1을, 아니면 0을 반환한다.</p>
<h1 id="문제풀이">문제풀이</h1>
<p>DP로 풀이하는 문제이다. 
<code>nums[S:E+1]</code>에 대해서 해당 문자가 펠린드롬이기 위해서는 
-&gt; <code>nums[S] == nums[E]</code>이고, <code>nums[S-1:E]</code>가 펠린드롬이어야 한다.
여기서 <code>nums[S-1:E]</code>의 부분을 DP로 처리한다.</p>
<p>크게 세가지 경우로 나누어서 풀이했다. </p>
<ol>
<li>문자열의 길이가 1인 경우
이때는 무조건 팰린드롬이다.</li>
<li>문자열의 길이가 2인 경우 (E=S+1)
<code>nums[S]==nums[S+1]</code>이면 팰린드롬이다.</li>
<li>문자열의 길이가 3인 경우
<code>nums[S] == nums[E]</code>이고, <code>nums[S-1:E]</code></li>
</ol>
<pre><code class="language-python">import sys

input = sys.stdin.readline

N = int(input())
nums = list(map(int, input().split()))
M = int(input())
questions = []
dp = [[0 for _ in range(N)] for _ in range(N)]

for i in range(N):
    dp[i][i] = 1

for i in range(N-1):
    if nums[i] == nums[i+1]:
        dp[i][i+1] = 1

for l in range(2, N):
    # 길이가 3 이상
    for i in range(N-l):
        # 처음 문자 == 마지막 문자 &amp;&amp; dp[처음+1][마지막-1]
        if nums[i] == nums[i+l] and dp[i+1][i+l-1] == 1:
            dp[i][i+l] = 1


for m in range(M):
    S, E = map(int, input().split())
    print(dp[S-1][E-1])</code></pre>
<p>dp는 참 한번에 떠올리기 어려움,,</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ] 1261 - 알고스팟]]></title>
            <link>https://velog.io/@woo0_hooo/BOJ-1261-%EC%95%8C%EA%B3%A0%EC%8A%A4%ED%8C%9F</link>
            <guid>https://velog.io/@woo0_hooo/BOJ-1261-%EC%95%8C%EA%B3%A0%EC%8A%A4%ED%8C%9F</guid>
            <pubDate>Sun, 23 May 2021 16:03:58 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-링크">문제 링크</h1>
<p><a href="https://www.acmicpc.net/problem/1261">1261 - 알고스팟</a></p>
<h1 id="문제-설명">문제 설명</h1>
<p>N*M크기의 미로는 1*1크기의 방으로 이루어져 있다. 각 칸은 빈 방 또는 벽으로 이루어져 있고, 벽은 부수지 않으면 이동할 수 없다. 
이동은 상하좌우 인접한 칸으로 가능하다. 
현재 (1, 1)에서 (N, M)으로 이동하려면 벽을 최소 몇 개 부수어야 하는지 구하는 프로그램을 작성하시오.</p>
<h1 id="문제-풀이">문제 풀이</h1>
<p>BFS + 우선순위 큐로 풀이하면 된다.</p>
<p>[부신 벽의 개수,시작 좌표]를 힙에 넣고, 부신 벽의 개수가 작은 순서대로 차례대로 pop하면서 이동가능한 칸에 대해 다시 append해주면 된다.</p>
<pre><code class="language-python">import sys
from collections import deque
import heapq

input = sys.stdin.readline
dx = [-1, 0, 1, 0]
dy = [0, -1, 0, 1]

def in_bound(x, y):
    if x in range(N) and y in range(M):
        return True
    return False

M, N = map(int, input().split())
board = []
for _ in range(N):
    board.append(list(map(int, list(input().strip()))))

queue = []
heapq.heappush(queue, [0,0,0])
visited = [[False for _ in range(M)] for _ in range(N)]
visited[0][0] = 0

while queue:
    count, x, y = heapq.heappop(queue)

    if x == N-1 and y == M-1:
        print(count)
        break

    for i in range(4):
        nx, ny = x+dx[i], y+dy[i]
        if in_bound(nx, ny) and visited[nx][ny] == False:
            if board[nx][ny] == 1:
                heapq.heappush(queue, [count+1, nx,ny])
            else:
                heapq.heappush(queue, [count, nx,ny])
            visited[nx][ny] = True</code></pre>
<p>BFS+우선순위큐 조합의 문제는 처음 풀어보는데 담에 이런 문제 풀때 바로 생각 날라나 ,, </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ] 5430 - AC ]]></title>
            <link>https://velog.io/@woo0_hooo/BOJ-5430-AC</link>
            <guid>https://velog.io/@woo0_hooo/BOJ-5430-AC</guid>
            <pubDate>Fri, 21 May 2021 08:52:58 GMT</pubDate>
            <description><![CDATA[<h1 id="문제링크">문제링크</h1>
<p><a href="https://www.acmicpc.net/problem/5430">5430 - AC </a></p>
<h1 id="문제-설명">문제 설명</h1>
<p>AC는 정수 배열 연산을 위한 언어다. 이 언어에는 두 가지 함수 R(뒤집기)과 D(버리기)가 있다.</p>
<p>함수 R은 배열에 있는 숫자의 순서를 뒤집는 함수이고, D는 첫 번째 숫자를 버리는 함수이다. 배열이 비어있는데 D를 사용한 경우에는 에러가 발생한다.</p>
<p>배열의 초기값과 수행할 함수가 주어졌을 때, 최종 결과는?</p>
<h1 id="문제-풀이">문제 풀이</h1>
<p>deque를 이용해서 푸는 문제이다. </p>
<h2 id="입력">입력</h2>
<p>입력이 좀 까다롭다고 느껴졌다. 연산할 정수 배열이 &quot;배열&quot;형태로 주어지기 때문에 앞뒤로 붙은 []와 ,를 파싱해야 한다. 
python에서 제공되는 strip으로 []와 뒤에 붙는 개행 문자를 제거하고, split을 이용해서 ,를 기준으로 나누었다. 빈 배열이 들어오는 경우를 처리하기 위해 <code>if nums[0] != &#39;&#39;:</code>를 통해 처리했다. 이렇게 파싱한 결과를 deque에 저장한다.</p>
<pre><code class="language-python">nums = list(input().strip(&quot;[]\n&quot;).split(&quot;,&quot;))
if nums[0] != &#39;&#39;:
    nums = list(map(int, nums))
    nums = deque(nums)
else:
    nums = []</code></pre>
<h2 id="연산의-수행">연산의 수행</h2>
<p>R 연산의 경우 매번 reverse를 하게되면 시간 초과가 발생한다. 따라서 R 연산을 짝수번 수행했는지, 홀수번 수행했는지 저장하는 플래그를 이용했다. </p>
<p>D 연산의 경우에 R을 짝수번 수행한 경우 맨 앞의 원소를 pop, 홀수번 수행한 경우에는 맨 뒤의 원소를 pop한다. 위에서 구한 플래그에 따라 연산해주면 된다. </p>
<pre><code class="language-python">    count_reverse = 1 # 1: 원상태 2: 한번 reverse된 상태

    for f in funcs:
        if f == &#39;R&#39;:
            count_reverse *= -1
        elif f == &#39;D&#39;:
            if len(nums) == 0:
                flag = False
                print(&quot;error&quot;)
                break
            elif count_reverse == 1:
                nums.popleft()
            else:
                nums.pop()</code></pre>
<p>결과의 출력에 약간의 낚시가,, 있는데 파이썬의 리스트 그대로 출력하면 안된다. 정답의 출력 형태는 <code>[1,2,3,5,8]</code>와 같이 원소를 나누는 , 뒤에 띄어쓰기가 없기 때문,,</p>
<pre><code class="language-python">    print(&quot;[&quot;, end=&#39;&#39;)

      if len(nums) &gt; 0:
          if count_reverse == 1:
              for i in range(len(nums)-1):
                  print(nums[i], end=&#39;,&#39;)
              print(nums[-1], end=&#39;&#39;)
          else:
              for i in range(len(nums)-1, 0, -1):
                  print(nums[i], end=&#39;,&#39;)
              print(nums[0], end=&#39;&#39;)
      print(&quot;]&quot;)</code></pre>
<p>따라서 위와 같이 처리해주었다.</p>
<h2 id="전체-코드">전체 코드</h2>
<pre><code class="language-python">import sys
from collections import deque

input = sys.stdin.readline

T = int(input())
answer = []

for t in range(T):
    funcs = list(input().strip())
    N = int(input())
    nums = list(input().strip(&quot;[]\n&quot;).split(&quot;,&quot;))
    if nums[0] != &#39;&#39;:
        nums = list(map(int, nums))
        nums = deque(nums)
    else:
        nums = []

    flag = True

    count_reverse = 1 # 1: 원상태 2: 한번 reverse된 상태

    for f in funcs:
        if f == &#39;R&#39;:
            count_reverse *= -1
        elif f == &#39;D&#39;:
            if len(nums) == 0:
                flag = False
                print(&quot;error&quot;)
                break
            elif count_reverse == 1:
                nums.popleft()
            else:
                nums.pop()

    if flag:
        print(&quot;[&quot;, end=&#39;&#39;)

        if len(nums) &gt; 0:
            if count_reverse == 1:
                for i in range(len(nums)-1):
                    print(nums[i], end=&#39;,&#39;)
                print(nums[-1], end=&#39;&#39;)
            else:
                for i in range(len(nums)-1, 0, -1):
                    print(nums[i], end=&#39;,&#39;)
                print(nums[0], end=&#39;&#39;)
        print(&quot;]&quot;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Selenium] Selenium Python에 대해 알아보자]]></title>
            <link>https://velog.io/@woo0_hooo/Selenium-Selenium-Python%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@woo0_hooo/Selenium-Selenium-Python%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 20 May 2021 13:38:42 GMT</pubDate>
            <description><![CDATA[<h1 id="1-python과-pip-설치">1. Python과 pip 설치</h1>
<p><a href="https://www.python.org/downloads/">Download Python</a>에서 OS에 맞게 설치한다.</p>
<p>-&gt; pip란? package installer for python: python의 여러 패키지를 쉽게 설치하고 관리하게 해주는 역할
-&gt; mac의 brew나 ubuntu의 apt-get과 같은 패키지 관리 툴임</p>
<h1 id="2-selenium-라이브러리-설치">2. selenium 라이브러리 설치</h1>
<p><code>pip3 install -U selenium</code>
<img src="https://images.velog.io/images/woo0_hooo/post/f5c2dc6a-7b22-4fa4-a604-182301b918b0/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-05-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.04.25.png" alt=""></p>
<h1 id="3-chrome-webdriver-설치">3. Chrome Webdriver 설치</h1>
<p><a href="https://sites.google.com/a/chromium.org/chromedriver/downloadse">chromedriver.chromium.org</a>에서 OS에 맞게 다운로드한다.</p>
<h1 id="4-webdriver-사용-실습">4. webdriver 사용 실습</h1>
<p>webdriver를 간단히 실습해보았다.
<img src="https://images.velog.io/images/woo0_hooo/post/c8cc949f-44fe-4823-b3d4-1ef4e468b35b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-05-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.31.09.png" alt="">
다운받은 드라이버를 <code>/drivers</code>안에 넣어주고, 다음과 같은 코드를 실행한다.</p>
<pre><code class="language-python">from selenium import webdriver
import time

driver = webdriver.Chrome(&quot;/Users/kimwookyoung/Desktop/2021/2021-1/소공/selenium-demo/drivers/chromedriver&quot;)
driver.implicitly_wait(10)
driver.get(&quot;http://google.com&quot;)
driver.find_element_by_name(&quot;q&quot;).send_keys(&quot;Hongik University&quot;)
driver.find_element_by_name(&quot;btnK&quot;).click()
driver.maximize_window()
driver.refresh()
time.sleep(2)
driver.quit()
print(&quot;Test Completed&quot;)</code></pre>
<h1 id="5-unit-test-실습">5. Unit Test 실습</h1>
<pre><code class="language-python">from selenium import webdriver
import HtmlTestRunner
import unittest
import time


class DemoUnitTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome(&quot;/Users/kimwookyoung/Desktop/2021/2021-1/소공/selenium-demo/drivers/chromedriver&quot;)
        cls.driver.implicitly_wait(10)
        cls.driver.maximize_window()

    def test_GoogleSearch_Hongik(self):
        self.driver.get(&quot;https://google.com&quot;)
        self.driver.find_element_by_name(&quot;q&quot;).send_keys(&quot;HongikUniversity&quot;)
        self.driver.find_element_by_name(&quot;btnK&quot;).click()

    @classmethod
    def tearDownClass(cls):
        time.sleep(3)
        cls.driver.close()
        cls.driver.quit()

    print(&quot;Test Done Successfully!&quot;)


if __name__ == &#39;__main__&#39;:
    unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output=&#39;/Users/kimwookyoung/Desktop/2021/2021-1/소공/selenium-demo/reports&#39;))
</code></pre>
]]></description>
        </item>
    </channel>
</rss>