<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yeo-li.log</title>
        <link>https://velog.io/</link>
        <description>안녕하세요:) 아키텍트가 되고 싶은 백엔드 개발자 지망생입니다.</description>
        <lastBuildDate>Mon, 23 Feb 2026 10:27:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yeo-li.log</title>
            <url>https://velog.velcdn.com/images/yeo___li/profile/750cba31-5120-4e94-917a-6aafff8c9f0e/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yeo-li.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yeo___li" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[AI를 이용해 문서로만 개발해본 후기]]></title>
            <link>https://velog.io/@yeo___li/AI%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%AC%B8%EC%84%9C%EB%A1%9C%EB%A7%8C-%EA%B0%9C%EB%B0%9C%ED%95%B4%EB%B3%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@yeo___li/AI%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%AC%B8%EC%84%9C%EB%A1%9C%EB%A7%8C-%EA%B0%9C%EB%B0%9C%ED%95%B4%EB%B3%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 23 Feb 2026 10:27:29 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<p><strong>AI를 이용해 어떻게 하면 생산성을 높일 수 있을까?</strong> </p>
<p>오늘 집에 가던 도중 이런 생각이 들었습니다. 이 고민은 사실 저 뿐만 아니라 전세계 모든 개발자들이 고민하고 있는 주제가 아닐까 싶습니다. 인터넷의 칼럼과 유튜브를 보면 AI를 사용해 개발 생산성을 10배 혹은 100배를 끌어올린다고 합니다. </p>
<p>그리고 저는 아직 주니어 개발자에도 진입하지 못한 대학생(🥺)이지만, 정말 이런 일이 가능한지 정말 궁금했습니다. 생산성을 높이기 위해 시도를 해보았고 그 작은 시도를 통한 저의 부족한 생각을 간단하게 적어보려합니다. </p>
<br>

<h1 id="1-ai-도입을-통해-그들이-기대하는-것">1. AI 도입을 통해 그들이 기대하는 것</h1>
<p>AI를 도입하려는 여러 개발자 혹은 대표님들의 기대는 <strong>AI를 통한 괄목할 만한 생산성 향상</strong>일 것입니다. 과연 개발에서 생산성이란 무엇일까요? 제가 현업에 있다면 더 현명한 답을 내놓을 수 있을 것이지만, 아직 현업 개발을 못해보아서.. 우선 제가 지금까지의 개발을 돌아보며 생산성을 정의해보겠습니다.</p>
<blockquote>
<p><strong>개발에서의 생산성이란?</strong></p>
<p><strong>time</strong>: 현재와 같은 코드 질을 유지하며 개발 시간을 단축 시키는 것
<br> <strong>quality</strong>: 같은 시간동안 더 좋은 질의 코드를 생산하는 것</p>
</blockquote>
<p>저는 개발에서의 생산성을 시간과 질을 기준으로 정의했습니다. 이 정의 외에 새로운 정의가 추가될 순 있지만(협업 속도 등), 위 정의 대해 이견이 있기엔 어려울 것 같습니다. 그렇다면 AI를 이용해서 현재 글에서 정의한 생산성을 향상시키는 방법론은 무엇이 있을까요?</p>
<br>

<h1 id="2-ai를-바라보는-개발자의-태도">2. AI를 바라보는 개발자의 태도</h1>
<ol>
<li><strong>도구 측면</strong>: AI는 개발자의 도구일 뿐, 결국 인간이 필요하다.</li>
<li><strong>책임자 측면</strong>: AI도 이제 웬만한 개발자보다 낫다. 생산물에 대해 신뢰하며 개발하자.</li>
</ol>
<p>저는 개발자들이 AI를 대하는 2가지 다른 태도에 따라 생산성을 높이는 방법이 달라질 것이라고 생각했습니다. </p>
<p>첫 번째는 AI를 사람이 개발하는데 필요한 도구로 바라보는 것입니다. 이 태도를 가진 사람은 본인의 개발 사이클에 AI를 적용하려는 사람들입니다. </p>
<p>두 번째는 AI를 개발 책임자로 바라보는 것입니다. 이들은 본인의 개발 사이클을 만들지 않고, AI의 개발 사이클을 만드는 사람입니다.</p>
<p>저는 아직 책임자 측면이 아닌 도구 측면의 입장입니다. AI를 책임자로 내세우게 되면 결국 개발자들은 필요없는 세상이 될 것입니다. </p>
<p>AI가 인간을 아득히 뛰어넘게 되는 날이 오더라도, 저는 기술의 흐름과 AI가 생산해내는 것들을 이해하는 사람이 되고싶습니다. 그렇기에 생산성을 높이는 방법론도 AI를 도구 측면에서 사용하는 입장에서 제시해보겠습니다.</p>
<br>

<h1 id="3-ai를-이용해-개발-생산성을-높이는-방법론">3. AI를 이용해 개발 생산성을 높이는 방법론</h1>
<p>AI를 도구로 이용해 개발 생산성을 높이기 위해선 AI에게 좋은 결과가 나오길 기대해선 안됩니다. 지금까지 저를 비롯한 초보 개발자들은 AI를 이용할 때 지금보다 더 좋은 UX, 더 좋은 코드를 달라고 했습니다.</p>
<p>이는 개발자가 스스로 개발하는 것이 아니라, AI에게 모든 것을 의탁하는 행위입니다. 생산성은 A를 투입했을때 B라는 결과를 보장할 수 있을때 높아집니다. 하지만 AI의 생산물을 예측하려 하지 않고 결과물을 기대하며 개발하는 행위는 투입물에 대한 결과를 개발자 스스로 예측할 수 없게합니다. </p>
<p>따라서 <strong>AI의 결과물을 예측가능하게 통제하는 것</strong>이 생산성 향상의 핵심일 것입니다. 이런 이유로 TDD가 다시금 각광받지 않을까 싶습니다. 테스트 코드는 AI의 생산물을 통제할 수 있는 좋은 수단이기 때문입니다.</p>
<h3 id="방법-ai를-이용해-기술-명세를-내가-아는-코드로-번역하도록-한다">방법: AI를 이용해 기술 명세를 내가 아는 코드로 번역하도록 한다.</h3>
<p>AI는 자연어로 구성된 명세를 이용해 코드로 빠르게 번역(완성)해줍니다. 이 방법이 실제로 작동하고 잘 적용하면, 적은 양의 문서만을 통해 개발을 할 수 있을 것입니다. 하지만 문제는 AI는 개발자가 원하는대로 짜주지 않는다는 것입니다. </p>
<p>따라서 이를 위해선 모호한 설계를 해선 안됩니다. 명세에는 문서엔 기술의 사용과 성능과 구현 방안, 유효성을 검증할 테스트 케이스가 구체적이고 명확히 적혀있어야 합니다.</p>
<h3 id="주의-구체적인-명세는-코딩과-다를-바-없다">주의: 구체적인 명세는 코딩과 다를 바 없다.</h3>
<p>하지만 여기에도 문제가 있습니다. AI가 예측 가능한 설계 문서를 작성하는 시간과 코딩을 하는 시간이 같아질 수도 있다는 문제입니다. </p>
<p>또한 기술적 명세의 표현과 코드가 일대일 매칭된다고 가정하다면, 결국 기술 명세의 표현 양과 코드의 표현 양이 같게되어 명세 작성과 코드를 작성하는 시간이 같아질 수도 있습니다. </p>
<p>따라서 이런 문제점을 극복하기 위해선 적은 양의 설계 명세를 이용한 AI 코딩 시스템을 개발해야 할 필요성이 있습니다.</p>
<h3 id="추가-해결-과제-높은-문맥을-압축-혹은-빠르게-짚어낼-수-있는-방안은">추가 해결 과제: 높은 문맥을 압축 혹은 빠르게 짚어낼 수 있는 방안은?</h3>
<p>AI가 발전하는 만큼 사람도 AI가 생산하는 생산물의 문맥을 따라가거나 통제할 수 있어야 합니다. 이를 실현할 수 있다면 개발자는 AI라는 거인의 어깨에 올라 더 큰 규모의 시스템을 구축할 수 있을 것입니다.</p>
<br>

<h1 id="4-문서를-이용해-코딩해보자">4. 문서를 이용해 코딩해보자</h1>
<p>방법론을 했다면 이제 시도해보아야겠죠? 아직 배포되진 않았지만 <a href="https://blog.yeo-li.com/">개인브로그</a>에 댓글 기능을 만들고 있는데, 그중 하나의 API를 코드 하나 없이 문서만을 이용해 코딩해보았습니다.</p>
<p>제가 적용한 방법은 간단합니다. 우선 디렉토리는 <code>action-log</code>와 <code>master-action-log</code>가 있습니다. <code>action-log</code>는 API나 하나의 작업 단위를 문서로 작성한 md 파일의 묶음이 있습니다. 각각은 독립적으로 존재합니다. </p>
<p><code>master-action-log</code>는 독립적인 action-log 파일들을 유기적으로 배치하는 문서입니다. AI는 master-action-log의 현재 branch의 md 파일을 보고 작업 순서대로 action-log에 들어가 개발을 진행합니다. 그리고 코딩을 완료하면 문서에 상태를 업데이트합니다.</p>
<h3 id="action-log-일부">action-log 일부</h3>
<p><img src="http://api.yeo-li.com/api/v1/images/fc076e760d6c4f99a9dde3235dc3b24d.png" alt="">
<img src="http://api.yeo-li.com/api/v1/images/444f0caedf324bb9874e8017a01138bb.png" alt=""></p>
<h3 id="master-action-log-일부">master-action-log 일부</h3>
<p><img src="http://api.yeo-li.com/api/v1/images/055aba87b9a14f5f969be75fc567a7a4.png" alt="">
<img src="http://api.yeo-li.com/api/v1/images/c7e6bc63412841fba52b9ffe6de7cc24.png" alt=""></p>
<br>

<h1 id="5-문서로-개발해본-후기">5. 문서로 개발해본 후기</h1>
<p>저의 후기는 <strong>그냥 내가 만드는게 더 빠를듯?</strong> 이었습니다. 구현한 양보다 문서를 작성한 양이 더 많아서 제가 todo 적고 개발하는게 아직은 더 빨랐던 것 같습니다. </p>
<p>하지만, 여러 파일을 돌아다지니 않고 하나의 문서로만 개발 내용을 적어 편리했고, 문서를 통해 실제로 예상한 코드 산출물을 만들 수 있었습니다. </p>
<p>이제 문서의 양을 조금씩 줄이고, 지금 방법을 더 체계화 한다면 설계하는 시간만 들이면 코드를 자동으로 원하는 코드가 생성되지 않을까 싶습니다!</p>
<br>

<h1 id="마치며">마치며</h1>
<p>지금까지 방법론부터 적용해본 후기를 적어보았는데요~ 다른 팀 프로젝트였다면 시도해보지 못했겠지만, 제 개인 프로젝트이기에 이런 실험을 해볼 수 있었던 것 같습니다ㅎㅎ </p>
<p>앞으로도 이런 실험적인 도전들 많이 해보겠습니다! 그리고 직접 해보셨던 AI 개발 방법도 이야기해주시면 좋을 것 같아요! 이번주도 부족한 글 읽어주셔서 감사합니다! 2월 마지막 주도 파이팅이에요~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[하나의 실패가 전체를 망가뜨리지 않도록]]></title>
            <link>https://velog.io/@yeo___li/yeolpost-%ED%95%98%EB%82%98%EC%9D%98-%EC%8B%A4%ED%8C%A8%EA%B0%80-%EC%A0%84%EC%B2%B4%EB%A5%BC-%EB%A7%9D%EA%B0%80%EB%9C%A8%EB%A6%AC%EC%A7%80-%EC%95%8A%EB%8F%84%EB%A1%9D</link>
            <guid>https://velog.io/@yeo___li/yeolpost-%ED%95%98%EB%82%98%EC%9D%98-%EC%8B%A4%ED%8C%A8%EA%B0%80-%EC%A0%84%EC%B2%B4%EB%A5%BC-%EB%A7%9D%EA%B0%80%EB%9C%A8%EB%A6%AC%EC%A7%80-%EC%95%8A%EB%8F%84%EB%A1%9D</guid>
            <pubDate>Thu, 12 Feb 2026 06:35:04 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<p><a href="https://blog.yeo-li.com/">운영 중인 블로그</a>에 기능들이 추가되며 하나 둘 기존 구조에 대한 문제가 보이기 시작했습니다. 성능 측면에서는 아직 블로그 접속자 및 게시물의 양이 많지 않아 최적화의 필요성은 못 느끼고 있지만, 이번에 개발 서버에서 테스트를 하면서 앞으로 개발에 있어 심각해 보이는 기술 부채를 마주했습니다.</p>
<p>개발서버는 운영 서버와 다르게 평소에 실행되어있지 않기 때문에, 스트릭 스케쥴링이 되지 않았습니다. 이에 따라 게시물 발행 로직이 실행 될 때 스트릭 데이터가 유효하지 않아 예외가 발생했고, 이 예외의 연쇄작용으로 게시물 발행 메일이 구독자들에게 전송되지 않았습니다.</p>
<p>이번 글에서는 이 문제의 원인을 찾아보고 어떻게 해결할지를 적어보려고 합니다.</p>
<br>

<hr>
<h1 id="1-원인-분석">1. 원인 분석</h1>
<p>이 문제는 구독 기능이 만들어지며 눈에 보이기 시작했습니다. 현재 게시물을 출간하면 스트릭 도메인에서 이를 감지하여 스트릭 관련 비지니스 로지을 처리한 뒤 게시물을 구독자에게 메일을 발송합니다. 이때 스트릭에서 예외 혹은 오류가 발생하게 되면, 뒤에 있는 구독 메일 발송도 실행되지 않습니다. </p>
<h3 id="문제의-코드">문제의 코드</h3>
<pre><code class="language-java">public void createPost(PostCreateCommand command) { // 게시물 발행 메서드
    // 게시물 DB 저장
    List&lt;Tag&gt; tags = tagService.findOrCreateAll(command.tags());
    Post post = postRepository.save(command.toEntity());
    postTagService.createPostTag(post, tags);

    // 스트릭 업데이트
    streakService.addStreakCount(LocalDateTime.now());

    // 구독자에게 메일 발송
    if (post.getIsPublished()) {
        newsLetterService.sendPublishedPostMails(subscriptionService.getSubscribedEmail(),
            postCommandFactory.createPostMailCommand(post));
    }
}</code></pre>
<p>게시물 발행 로직을 도식화 하면 아래와 같은 그림이 됩니다.</p>
<h3 id="게시물-발행-로직">게시물 발행 로직</h3>
<p><img src="http://api.yeo-li.com/api/v1/images/1cf2f92b589643f991488f5a1eb996d7.png" alt=""></p>
<p>게시물 발행 로직은 총 3단계로 나누어집니다. 이때 하나의 단계라도 실패하면 게시물 발행 로직 전체가 실패합니다. 저는 게시물 발행 로직에서 가장 핵심은 게시물을 DB에 저장하는 것이라고 생각합니다. 스트릭 업데이트와 메일 발송은 게시물 발행에 따른 부수적인 기능으로 이 두 개가 모두 실패했다고 해서 게시물 발행이 실패한 것은 아닙니다. 따라서 문제의 원인은 게시물 발행 로직에 스트릭 업데이트와 메일 발송 로직이 직렬로 연결되어 있는것입니다.</p>
<p>다음은 이를 해결하기 위한 게시물 발행 로직을 재설계 해보겠습니다.</p>
<br>

<hr>
<h1 id="2-해결-방안">2. 해결 방안</h1>
<p>아래는 재설계한 게시물 발행 로직입니다. 게시물 DB에 저장하면 성공으로 판단하고, 스트릭 업데이트와 메일 발송을 외부에서 진행합니다. 게시물 발행 성공 단계에서는 스트릭, 메일기능을 호출만 하고 사용자에게 응답을 내려줍니다.</p>
<p><img src="http://api.yeo-li.com/api/v1/images/d37257b8593f426cb558409e9660f369.png" alt=""></p>
<p>위처럼 설계를 진행했을때 장점이 있습니다. </p>
<p>첫 번째는 사용자 응답 시간이 단축된다는 것입니다. 메일 발송 기능은 아직 최적화가 되지 않아 약 3-5초 정도 소요 시간이 걸립니다. 기존 로직에선 메일 발송이 완료될 때 까지 응답이 내려오지 않았습니다. 하지만 이젠 기능을 호출만 하고 응답을 내려주니, 응답 시간이 상당히 줄어들 것으로 기대됩니다.</p>
<p>두 번째는 스트릭 기능과 메일 발송 기능이 독립적으로 실행되기 때문에 둘 중 하나가 실패하더라도 다른 기능에 영향을 주지 않습니다.</p>
<br>

<hr>
<h1 id="3-구현---spring-event를-사용하자">3. 구현 - Spring Event를 사용하자</h1>
<blockquote>
<h3 id="spring-event"><strong>Spring Event?</strong></h3>
<p>애플리케이션 내부에서 발생한 사실을 다른 컴포넌트에게 알리는 메커니즘</p>
</blockquote>
<p>이를 더욱 쉽게 설명하면 한 도메인에서 어떤 이벤트(사실)가 발생하면 이 이벤트를 구독하고 있는 다른 도메인에서 특정 일을 수행하는 것을 의미합니다. 그리고 이는 옵저버 패턴과 같습니다.</p>
<p>게시물 발행 로직을 예시로 들면, 게시물 발행에 성공하게 되면 &quot;게시물 발행됨&quot; 이라는 이벤트를 생성합니다. 그럼 이 생성된 이벤트를 스트릭 서비스와 메일 발송 서비스가 핸들링하여 각각 스트릭을 업데이트하고 메일을 발송합니다.</p>
<h3 id="구현-코드">구현 코드</h3>
<h4 id="게시물-발행-메서드">게시물 발행 메서드</h4>
<pre><code class="language-java">@Transactional
public void createPost(PostCreateCommand command) {
    // 게시물 저장
    List&lt;Tag&gt; tags = tagService.findOrCreateAll(command.tags());
    Post post = postRepository.save(command.toEntity());
    postTagService.createPostTag(post, tags);

    // 게시물 발행 이벤트 발행
    publisher.publishEvent(
        new PostPublishedEvent(post.getId(), post.getTitle(), post.getSummary(),
            post.getPublishedAt())
    );
}</code></pre>
<h4 id="스트릭-이벤트-핸들러">스트릭 이벤트 핸들러</h4>
<pre><code class="language-java">@Slf4j
@Component
@RequiredArgsConstructor
public class StreakEventHandler {

    private final StreakService streakService;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(PostPublishedEvent event) {
        try {
            streakService.addStreakCount(event.publishedAt());
        } catch (Exception e) {
            log.error(&quot;Streak 업데이트 실패&quot;, e);
        }
    }
}</code></pre>
<h4 id="메일-발송-이벤트-핸들러">메일 발송 이벤트 핸들러</h4>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class SubscriptionEventHandler {

    private final SubscriptionService subscriptionService;
    private final NewsLetterService newsLetterService;

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(PostPublishedEvent event) {
        newsLetterService.sendPublishedPostMails(subscriptionService.getSubscribedEmail(),
            new PostMailCommand(event.postId(), event.title(), event.summary()));
    }
}</code></pre>
<h3 id="코드-도식화">코드 도식화</h3>
<p><img src="http://api.yeo-li.com/api/v1/images/c989f3802dcc4fc493e0b3ff398a0d70.png" alt=""></p>
<p>위 코드를 도식화 해보았습니다. 게시물 발행에 성공하면 게시물 발행됨 이벤트를 발행합니다. 그럼 그 이벤트를 스트릭 이벤트 핸들러와 메일 발송 이벤트 핸들러가 핸들링합니다. 이때, 원래는 비동기로 처리하려고 했지만, 스트릭 업데이트는 DB에 직접 기록하기도 하고 단순한 insert 로직이기 때문에 한 스레드 안에서 동기적으로 처리하도록 했습니다. 하지만 메일 발송은 시간이 오래걸리기도 하고, 외부 메일 서버를 사용하기 때문에 한 스레드에서 처리할 필요가 없습니다. 따라서 게시물 발행 응답 속도 개선을 위해 비동기로 처리했습니다.</p>
<br>

<hr>
<h1 id="4-성능-개선-결과">4. 성능 개선 결과</h1>
<p>결과는.. 재미있었습니다..</p>
<p>각각 20번씩 게시물 발행을 요청하여 최소, 평균, 최대 응답 시간을 계산해보았습니다.</p>
<table>
<thead>
<tr>
<th>응답 시간</th>
<th>개선 전</th>
<th>개선 후</th>
<th>응답 시간 감소율</th>
</tr>
</thead>
<tbody><tr>
<td>평균</td>
<td>2909ms</td>
<td>12.10ms</td>
<td>99.58%</td>
</tr>
<tr>
<td>최소</td>
<td>2737ms</td>
<td>9.21ms</td>
<td>99.66%</td>
</tr>
<tr>
<td>최대</td>
<td>3262ms</td>
<td>25.33ms</td>
<td>99.22%</td>
</tr>
</tbody></table>
<p>응답 시간 감소율 약 99%를 찍으며.. 이는 성능의 문제가 아니라 개선 전 어플리케이션 아키텍쳐 설계의 고질적 병목 문제임을 알 수 있었습니다. 여러분 설계 잘하세용</p>
<br>

<hr>
<h1 id="5-구조적-위험---이벤트로-실행된-비지니스-로직이-실패하면">5. 구조적 위험 - 이벤트로 실행된 비지니스 로직이 실패하면?</h1>
<p>이벤트와 비동기 처리를 이용해 사이드 비지니스 로직 실패로 인한 발행 실패 문제를 해결하고 게시물 발행 응답 속도를 개선했습니다. 하지만 이 구조의 근본적인 문제가 있습니다.</p>
<p>바로 비지니스 로직에서 예외가 발생하여 실패하는 경우 입니다. 스트릭 핸들러에선 예외가 발생하면 try-catch문으로 예외를 로깅만 하도록 했고, 메일 발송은 아예 다른 스레드이므로 예외가 발생해도 이벤트 발행 스레드에선 예외를 받지 않습니다.</p>
<p>이런 경우, 스트릭 저장에 실패했을때 데이터 신뢰도를 복구할 수 없을 수도 있고, 게시물이 발행 되었는데도 메일이 발송되지 않는 경우가 발생할 수도 있습니다. 따라서 이번 문제를 해결하기 위해 이벤트를 단순 메모리 기반 콜백이 아닌 영속화된 이벤트 저장 구조로 전환하기로 했습니다. 이는 outbox 패턴이라고도 부르는데, 이를 적용하여 더 안전한 블로그 운영을 하고싶습니다. 아직은 이에 대해 잘 모르기 때문에 추후 학습하여 블로그에 작성하도록 하갰습니다.</p>
<br>

<hr>
<h1 id="6-새롭게-배운-내용">6. 새롭게 배운 내용</h1>
<h3 id="이벤트는-느슨한-동기-호출이다이벤트는-만능이-아니다">이벤트는 느슨한 동기 호출이다(이벤트는 만능이 아니다)</h3>
<p>저는 개념적으로 이벤트를 알았기에 이벤트를 던지면 핸들러들이 비밀스러운 매커니즘으로 이벤트를 핸들링해서 동시에(비동기적으로) 비지니스 로직을 수행한다고 생각했습니다. </p>
<p>하지만 이번에 스프링 이벤트에 대해 공부해보니 단순히 이벤트가 발행되면 구독하고 있는 핸들러들을 for문으로 돌려 각각 동기적으로 수행한다는 것을 알았습니다. 따라서 이는 한 트랜젝션 안에서 수행하는 강한 동기적 수행 로직을 트랜젝션을 분리할 수 있는 느슨한 동기적 수행 로직으로 변환 시켜주는 것임을 알았습니다.</p>
<h3 id="비동기는-어렵다">비동기는 어렵다</h3>
<p>비동기를 처리하기 어렵다는 생각을 했습니다. 메일 발송 같은 외부 서버를 사용하는 서비스 로직 같은 경우엔 현재 비지니스 로직과 관련성이 없기 때문에 비동기 처리를 하여 다른 스레드에서 수행해도 괜찮았지만, 만약 같은 서비스(DB를 공유하는)에서 비동기로 처리하게 된다면 고려해야 할 사항이 많아져 신뢰도를 관리하기 어렵다는 생각이 들었습니다.</p>
<br>

<hr>
<h1 id="마치며">마치며</h1>
<p>어찌보면 단순한 문제였지만 나름 깊이 고민을 해보며 여러 고민을 해본 것 같아 즐거웠던 시간이었습니다! 그리고 트랜젝션과 비동기의 개념이 필요해지면서 머리가 많이 복잡했습니다.. 그래서 이 개념도 확실히 공부해야겠습니당 긴 글 읽어주셔서 감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우아한테크코스 8기 최종 합격을 돌아보며]]></title>
            <link>https://velog.io/@yeo___li/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-8%EA%B8%B0-%EC%B5%9C%EC%A2%85-%ED%95%A9%EA%B2%A9%EC%9D%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</link>
            <guid>https://velog.io/@yeo___li/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-8%EA%B8%B0-%EC%B5%9C%EC%A2%85-%ED%95%A9%EA%B2%A9%EC%9D%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</guid>
            <pubDate>Wed, 04 Feb 2026 06:24:31 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<p><img src="http://api.yeo-li.com/api/v1/images/f8d57662b5e2428b900295878bf5f4da.png" alt=""></p>
<p>우아한테크코스 8기 최종 합격했습니다! 1월 23일 오후 3시에 메일로 발송이 되었는데, 경주여행을 마치고 돌아오는 기차에서 합격 메일을 보고 소리지를 뻔 했습니다..ㅎㅎ </p>
<p>이번 글은 다음 우테코를 도전하시는 분들께 작은 도움과 참고가 되고자 이렇게 합격 수기를 적어보려고 합니다!</p>
<br>

<h1 id="1-24년-우테코-7기-도전-결과는">1. 24년 우테코 7기 도전, 결과는?</h1>
<p>저는 사실 24년도에 우테코를 지원했었습니다. 결과는...</p>
<p><img src="http://api.yeo-li.com/api/v1/images/56ed53e4267540e18301c408c2e00222.png" alt=""></p>
<p>1차 심사에서 광탈했습니다.. 그래도 이때 프리코스에서 스터디를 주관했었는데, 그때 만났던 분들이 좋은 인연이 되어 요즘도 가끔 연락하는 사이가 되었습니다ㅎㅎ 떨어지고 많이 느꼈던 점은 저는 아직 많이 부족하다는 것이었습니다. </p>
<p>24년 당시에는 제대로된 개발을 해본적이 없었기 때문에, 프리코스에서 커밋 방법, 단위에 대해 공부하고 스터디원분들과 코드리뷰를 통해 더 좋은 개발 방법에 대해 많이 배울 수 있었습니다.(제가 제일 못했슴..ㅠ) 그리고 스터디원 모두 우테코를 포함한 유명한 부트캠프에 합격하게되어 저에게도 좋은 자극이 되었습니다.</p>
<br>

<h1 id="2-개발-연합-동아리-프로젝트와-개인-프로젝트">2. 개발 연합 동아리 프로젝트와 개인 프로젝트</h1>
<p>프리코스 이후 개발 능력이 많이 부족하단 생각이 들어 25년 초엔 개발 연합 동아리 Seeds에 들어가 약 10개월간 프로젝트를 했습니다. 비록 한이음엔 떨어졌지만, 상반기에 기획을 완료하고 여름 방학때 본격적으로 개발을 시작하게 됩니다. 그리고 동아리 팀 내에서 개발 블로그를 하기 위한 여러가지 방법을 고안 했던 때가 있었습니다. 개발 블로그는 시간상 문제로 무산 되었지만, 저는 이때를 계기로 개인 개발 블로그를 만들고 싶어졌습니다. 그리고 팀 프로젝트에서 하지 못하는 크고 작은 것들을 도입할 목적으로 개인 개발 블로그 프로젝트를 시작했습니다. 방학때는 이 두 개에만 몰두하며 2달을 보냈습니다.</p>
<br>

<h1 id="3-우테코-재도전">3. 우테코 재도전</h1>
<p>2학기 개강을 하고 우테코 지원 시즌이 왔습니다.</p>
<p>사실 저때 학업+동아리 프로젝트때문에 너무 정신이 없었어서 우테코 지원은 안하려고 했습니다. 하지만 하다가 힘들면 중도 하차하면 되지만, 시작도 안하면 도중에 후회해도 못할 것이라는 생각이 들어 추석 연휴에 자소서를 써서 제출했습니다. 그리고 동아리 프로젝트 팀원들과 함께 지원하게 되어 조금 더 든든하게 지원할 수 있었습니다.</p>
<p><img src="http://api.yeo-li.com/api/v1/images/82a7f3af984e4b79a023eb411c965cad.png" alt=""></p>
<p>우테코 지원 자소서를 작성할때는 여름방학때 만들었던 개인 개발 블로그 프로젝트를 소재로 약 5000자 정도 적었습니다!</p>
<br>

<h1 id="4-프리코스-시작">4. 프리코스 시작!</h1>
<p>저는 작년에 프리코스를 해본적이 있었기 때문에, 우선 저번 프리코스에서 아쉬웠던 점을 되짚어 보았습니다. 가장 아쉬웠던 점은 매주 미션 후 감상문과 다짐을 적었는데, 이를 제대로 다음 미션에 정리하지 못했던 것이었습니다. 따라서 이번 프리코스에선 하기로 했던 것을 제대로 하기 위해 노력했습니다. </p>
<p>그리고 저번과 같이 이번 프리코스에서도 스터디를 모집했습니다. 하지만 생각보다 바쁜 일정 때문에 적극적으로 참여하지 못해 아쉬웠습니다. 그래도 함께한다는 동질감이 있어 프리코스에서 흔들리지 않고 끝까지 완주할 수 있었던 것 같습니다!</p>
<p>1주차, 2주차, 3주차 미션은 저번과 동일하게 나왔습니다. 저는 그때의 경험과 지금까지 프로젝트를 했던 경험을 적용하여 구현을 했습니다. 그리고 매주 감상문에 이번에 아쉬웠던 점과 다음주에 개선할 방안과 어떤것을 할지 미리 적어 다음 미션에 적었습니다. 궁금하신 분들을 위해 아래 링크로 달아두겠습니다. (3주차는 소감문을 복사해둔다는걸 까먹고 못해서 존재하지 않습니다ㅠㅠ)</p>
<ul>
<li><a href="https://blog.yeo-li.com/posts/55">[우테코] 1주차 미션 회고: 나를 알아가는 시간</a></li>
<li><a href="https://blog.yeo-li.com/posts/56">[우테코] 2주차 미션 회고: TDD는 원칙이 아니라 사고방식이다</a></li>
</ul>
<br>

<h1 id="5-오픈미션이-뭐에요">5. 오픈미션이 뭐에요..?</h1>
<p>4주차부턴 오픈미션이라는 새로운 미션이었습니다. 오픈미션에 대해서는 프리코스에 참여하셨던 팡일님의 게시물을 참고하시면 좋을 것 같아 링크걸어두겠습니다!</p>
<ul>
<li><a href="https://pangil-log.tistory.com/566">[우테코] 6편 : 8기 프리코스 오픈미션 웨비나</a></li>
</ul>
<p>저는 오픈미션에서 IDE에서 개발과 기록을 함께 할 수 있는 인텔리제이 플러그인을 제작했습니다. 프리코스에서 개발을 할때 어떤게 어려웠는지, 어떤걸 배웠는지 노션이나 노트에 정리하곤 했는데, 항상 코드와 매칭이 잘 되지 않는 문제가 있었고, 이를 해결하기 위한 목적이었습니다.</p>
<ul>
<li><a href="https://blog.yeo-li.com/posts/62">[우테코][DevLog] IntelliJ 플러그인 개발 일지 1</a></li>
<li><a href="https://blog.yeo-li.com/posts/65">[우테코][DevLog] IntelliJ 플러그인 개발 일지 2</a></li>
<li><a href="https://blog.yeo-li.com/posts/67">[DevLog] IntelliJ 플러그인 개발 일지 3</a></li>
</ul>
<p>3편은 프리코스가 종료된 후 추가로 진행했던 부분이라 평가에 반영되지 않았습니다! 이 부분 참고해주세요!</p>
<p>다음 기수에서도 오픈미션을 할 지는 미지수이지만, 만약 하게 된다면 프리코스와 관련된 도전 중 스스로 재미있게 몰입해서 할 수 있거나, 꼭 해결하고 싶은 문제에 도전하는 것을 추천 드리고 싶습니다. 이유는 8기 오픈미션은 3주동안 진행되었기 때문에 그 호흡이 상당히 길었습니다. 따라서 3주동안 의미있는 결과물을 만들어 내기 위해선 정말 많은 고민과 시간을 투자해야하고 스스로도 이 도전이 의미있어야 그 호흡을 유지할 수 있습니다. 저는 참고로 프리코스를 하면서 마주했던 문제를 해결하기 위한 프로젝트를 오픈미션 제출물로 제출했습니다!</p>
<br>

<h1 id="6-지극히-개인적인-프리코스-꿀팁">6. 지극히 개인적인 프리코스 꿀팁</h1>
<p>프리코스를 진행하면서 가장 추천드리는 활동은 매주 미션마다 개인적인 방향성을 설정한 뒤 미션을 진행하는 것입니다. 프리코스에서 미션의 구현 난이도는 높지 않습니다. 따라서 단순 미션 구현으로만 진행하면, 감상문을 적을때 스스로 무엇을 했는지 그때부터 고민해야 합니다. 하지만 미션 전에 스스로 무엇을 할지 생각하고 이를 달성하기 위한 여러 방안들을 구체적으로 생각해본 후, 이를 적용하면서 미션을 하면 감상문을 작성하실 때 더욱 풍성하고 차별점 있는 감상문이 되지 않을까 싶습니다. 실패해도 괜찮습니다. 실패에서 무엇을 배웠는지 적으면 되니까 말이죠!</p>
<br>

<h1 id="7-최종-코딩-테스트">7. 최종 코딩 테스트</h1>
<p><img src="http://api.yeo-li.com/api/v1/images/11a44676f3e0479d82ba3e1671f99de3.png" alt=""></p>
<p>25년 12월 29일 오후 3시에 1차 심사 합격 메일을 받았습니다! 그리고 이를 어떻게 준비했는지는 이전에 적었던 게시물이 있어 첨부하겠습니다!</p>
<ul>
<li><a href="https://blog.yeo-li.com/posts/68">[우테코] 8기 최종 코딩테스트 회고</a></li>
</ul>
<p>최종 코딩 테스트는 철저하게 준비하는게 좋다고 생각합니다. 시험날은 실제보다 긴장이 많이 되기 때문에 어떤 상황이 와도 흔들리지 않게 준비해야 합니다. 따라서 우테코에서 제공된 이전기수 문제들과 이번 기수 프리코스 문제를 시간을 빡빡하게 설정하여 구현 및 리팩토링 하는 연습을 하시면 좋을 것 같습니다! 물론 시간이 끝났다고 해서 멈추는게 아니라, 계속 시간을 측정하며 끝까지 완성해야 합니다. 그리고 템플릿을 많이 가져가더라도 실제 코테에선 시간 압박때문에 쓰기 어렵습니다. 그래서 많이 준비해 가기 보단, 입출력 파싱과 관련된 메서드만 준비해가셔도 충분히 대비가 되실 것 같습니다! + 소감문 미리 써가세요!</p>
<br>

<h1 id="8-최종-합격-정말-기뻤지만-한편으론">8. 최종 합격, 정말 기뻤지만 한편으론</h1>
<p>최종 코딩 테스트 이후 2주의 시간동안 시간이 흐르지 않았습니다.. 마음을 다잡으려고 해도 아무것도 잡히지 않아서 그냥 쉬었습니다ㅠ 다음엔 중요한 일을 기다릴땐 무언갈 하려고 하기 보단, 일정이 나온 이후의 최소한의 계획을 짜고 푹 쉬는게 나을 것 같단 생각이 들었습니다.</p>
<p><img src="http://api.yeo-li.com/api/v1/images/f8d57662b5e2428b900295878bf5f4da.png" alt=""></p>
<p>그리고 합격 당일이 되었습니다. 저는 처음 합격 메일을 보고 나서는 정말 기뻤습니다. 열심히 준비하고 간절히 붙고 싶었기 때문에, 합격 메일을 보았을 때의 기쁨은 대학교 합격보다도 기뻤던 것 같습니다.</p>
<p>8기 준비 단체 톡방에서 붙으신 분들도 계셨고, 안타깝게 불합격하신 분들도 계셨습니다. 실제로 만나뵙진 못했지만, 모두들 열심히 했을텐데 누군간 붙고 누군간 떨어진다는 사실이 당연하지만 마음이 좋지 않았습니다. 따라서 이번에 얻은 기회를 가볍게 생각하지 않고, 그 어떤 시기보다 열심히 임해야 겠다고 생각했습니다.</p>
<br>

<h1 id="마치며">마치며</h1>
<p>우테코 8기 합격을 돌아보니 7기 광탈부터 동아리 프로젝트, 개인프로젝트 모든 과정이 있었기에 가능했던 것 같습니다. 중간중간에 저의 방향성에 대해 의문이 들기도 하고 미래에 대한 걱정도 되었지만, 결국 그런 시도가 쌓여 합격이라는 결과를 만든 것 같습니다. 이 글을 보시는 모든 분들께서도 의미없는 시도는 없다는 확신을 가지고 현재가 혼란스럽더라도 잘 헤쳐나가시길 응원하겠습니다🍀</p>
<p>그리고 다음 우테코 글은 24일 OT 이후 우테코에서의 다짐 및 계획으로 돌아오겠습니다! 부족한 글 읽어주셔서 감사합니다!☀️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[남들은 취준 2트 할 때 우테코 2트한 후기]]></title>
            <link>https://velog.io/@yeo___li/%EB%82%A8%EB%93%A4%EC%9D%80-%EC%B7%A8%EC%A4%80-2%ED%8A%B8-%ED%95%A0-%EB%95%8C-%EC%9A%B0%ED%85%8C%EC%BD%94-2%ED%8A%B8%ED%95%9C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@yeo___li/%EB%82%A8%EB%93%A4%EC%9D%80-%EC%B7%A8%EC%A4%80-2%ED%8A%B8-%ED%95%A0-%EB%95%8C-%EC%9A%B0%ED%85%8C%EC%BD%94-2%ED%8A%B8%ED%95%9C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 14 Jan 2026 09:00:13 GMT</pubDate>
            <description><![CDATA[<h2 id="다사다난했던-최종-코테-준비-과정">다사다난했던 최종 코테 준비 과정</h2>
<p>프리코스가 끝나고 나서는 그동안 못했던 학교 시험 공부를 했습니다. 그리고 종강 후에는 교내 해커톤에 출전해 수상도 하고, 1년동안 진행한 동아리 프로젝트에서도 최우수상을 수상하며 나름 바쁜 나날들을 보내고 있었습니다. </p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/32744ec3-3df0-420d-bc26-05b185cdf0be/image.png" alt=""></p>
<p>그러던 25년 12월 29일 오후 3시 9분 우아한테크코스 8기 1차 심사 합격 메일을 받았습니다. 프리코스 기간동안에 정말 열심히 참여했지만, 다른 분들도 모두 열심히 하셨기 때문에 저는 붙을거란 기대가 없었습니다. 그런데 1차 심사 합격 메일을 받고는 정말 기뻤습니다. 기회가 주어지면 최선을 다해야 한다는 마음가짐으로, 1차 합격 메일을 받자마자 최종 코데 준비 계획을 세웠습니다. 하지만 다음날에 원인 모를 몸살에 걸려 중요한 기간에 2-3일을 아무것도 못하고 앓아 눕게 됩니다.. 첫 날부터 계획에 차질이 생겼었지만, 원래 계획대로 안되는게 삶 아니겠습니까 다시 준비를 시작했습니다. 저는 몸살 이후 어떤 마음가짐으로 연습을 했는지, 그리고 그 과정에서 어떤걸 배우게 되었는지 적어보려고 합니다.</p>
<hr>
<h2 id="어떻게-연습했어">어떻게 연습했어?</h2>
<p>저의 연습 목표는 <strong>지긋지긋하게 연습하기</strong>였습니다. 대부분 시험장에서 떨리는 이유는 결과가 좋아야 한다는 마음에서 비롯되는 것 같습니다. 따라서 이런 떨림을 없애는 가장 좋은 방법은 결과가 중요해지지 않을 정도로 힘들게 연습하는 것이라고 생각했습니다.</p>
<p>2차 코딩테스트 준비의 목표인 힘들게 연습하기 위한 요소를 3가지로 나누었습니다. 첫 번째 시간 감축은 원래 시간인 5시간이 아닌 4시간만 배분해 시간 압박을 늘렸습니다. 두 번째 장소 변화는 소음이 없는 독서실부터 시끄러운 카페까지 다양한 장소에서 미션을 풀도록 하여 낯선 장소에서 저만의 페이스를 찾는 연습을 했습니다. 세 번째는 미션 구현 정도의 선택입니다. 실제 최종 코딩 테스트는 어떤 형식으로 나올지 모르기 때문에, 클린코드를 최대로 끌어올리는 클린코드 연습과 복잡한 구현을 빠르게 하는 방식 두 개를 섞어가며 연습했습니다.  추가로 시간 안에 구현을 하지 못한 경우, 추가 시간을 재며 끝까지 구현하고 리팩토링하는 연습을 했습니다. 그리고 마지막으로 AI를 이용한 채점을 하여 고려하지 못한 비지니스 오류나 자주하는 실수들을 정리했습니다.</p>
<p>위와 같은 연습을 하면서 시간안에 구현을 하지 못하는 경우도 많았고, 심각한 비지니스 오류를 발견하지 못하는 경우도 많았습니다. 이런 연습을 하면서 시험장에서도 이런 실수를 하게 될까 두려웠지만, 오늘 두렵지 않았다면 시험 당일에 반드시 두려운 일이 일어날 것이라 생각을 했습니다. 그렇기에 저는 연습할때 최대한 많이 저의 약점을 파고들고 스스로 무너지기 위해 집요하게 노력했던 것 같습니다.</p>
<hr>
<h2 id="코드에-어떤걸-담기-위해-노력했어">코드에 어떤걸 담기 위해 노력했어?</h2>
<p>저는 실수를 금방 발견할 수 있는 코드를 짜기 위해 노력했습니다. 실전에서도 분명 실수를 할텐데, 그렇다면 실수한걸 알았을 때 빠른 시간 안에 실수를 찾아야 할 것입니다. 실수를 빠르게 찾는 방법은 무작정 빠르게 개발하는 것이 아니라, 클린 코드를 지향하며 개발해야 합니다. 그러나 코드 품질을 한 없이 높히기만 하면 시간 안에 요구사항을 모두 완료하지 못할 것입니다. 그래서 최종 코딩테스트에서의 클린 코드란 무엇인지 생각했습니다. 최종 코딩테스트에서의 클린 코드는 문제가 되는 코드가 어디에 있는지 아는 것이라고 정의했습니다. 그리고 이를 위해 도메인 주도 개발과 MVC 패턴을 어떻게 녹여낼지 고민했고, MVC 패턴으로 가되 도메인의 역할과 협력을 서비스 계층을 두어 협력하는 방식을 채택했습니다.</p>
<p>이 두 방법을 합친 이유는, 도메인 주도 개발을 하게 되면 새로운 미션마다 지나치게 달라지는 도메인 때문에 초반 설계 시간이 지나치게 많이 소요 됩니다. 반대로 MVC 패턴으로만 개발하게 되면 역할이 분명히 나누어져 있기에 설계는 일관되고 빠르게 할 수 있지만, 지나치게 추상화된 계층 때문에 서비스나 컨트롤러에 코드가 집약될 수 있습니다. 따라서 MVC 패턴을 주도로 하며 초반에 빠른 설계를 열어두고, 서비스 계층에서 도메인 협력을 하여 코드가 집약되는 것을 방지하기 위해 노력했습니다.</p>
<hr>
<h2 id="최종-코딩-테스트-당일---시험-전">최종 코딩 테스트 당일 - 시험 전</h2>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/1bf62b75-b3a5-465a-998c-f536b910f214/image.jpeg" alt=""></p>
<p>아침부터 긴장 되었지만, 잘 보려던 생각보단 그냥 연습때처럼만 하자는 생각으로 잠실로 갔습니다. 잠실에서 프리코스때 스터디를 같이 했던 분과 만나 가볍게 이야기 하고 최종 코테 장소가 달라 각자 시험장으로 갔습니다. 그리고 우연히 최종 코딩테스트 스터디를 하셨던 분과 스타벅스에서 만났어요! 짧았지만 반가웠습니다ㅎㅎ</p>
<p>저는 삼성생명 잠실빌딩이었습니다. 들어오자마자 신분증 검사를 했는데, 포비님께서 신분증 검사를 해주셔서 내적 친밀감이 있었어요. 다른 분들처럼 저도 싸인을 받고싶었는데, 약간 부끄러워서 멀리서 인사 드렸습니다(꾸벅)</p>
<p>시험장 분위기는 편안했습니다. 시험 자리는 조금 협소했지만, 시간지 지나니 크게 불편함으로 와닿진 않았습니다.</p>
<hr>
<h2 id="최종-코딩-테스트-당일---시험-시작">최종 코딩 테스트 당일 - 시험 시작</h2>
<p>1시가 되고 시험이 시작되었습니다. 최종 코테 문제를 보자마자 이전 기수들과 다른 형식이라 당황했습니다. 지금까지는 1시부터 6시까지 어려운 문제를 푸는 형식이었지만, 이번엔 프리코스 3주차와 비슷하거나 혹은 더 쉬운 수준이었습니다. 그리고 <code>InputView</code>와 <code>OutputView</code>가 모두 구현 되어있어 저희는 추가 유효성 검증과 비지니스 로직만 구현하면 됐습니다. 하지만 코딩 테스트 시간은 1시부터 5시까지고, 5시부터 6시까지 회고를 작성하는 시간을 주었습니다. </p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/87404e32-cb50-44ec-92c6-1f131160cba2/image.png" alt=""></p>
<p>그리고 이번에 새롭게 추가된 내용은 도전과제였습니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/7f18e920-fde6-4b90-95f2-42fe0442c07a/image.png" alt=""></p>
<p>이번 <strong>우아한테크코스의 핵심 비전은 도전</strong>이었기에, 최종 코딩테스트에서도 목적에 맞게 리팩터링과 기능 확장 중 하나의 도전 과제를 선택하여 수행하는 것이었습니다. </p>
<p>둘 중 어느것을 할지 고민을 많이 했습니다. 이 자리에서는 완전히 새로운 도전을 하기 보단, 지금까지 도전했던 것을 녹여내는 자리라고 생각했습니다. 저는 오픈 미션을 할때 주제에 대해 고민하는 시간이 정말 오래 걸렸습니다. 그렇다고 해서 새롭게 기획하고 도전했던 내용을 최종 코테에서 녹여내기는 조금 어렵겠다는 생각이 들었습니다. 그와 반대로 리팩터링은 1주차부터 3주차때 연습했던 TDD와 클린 코드를 위해 했던 도전들, 그리고 오픈미션을 도전하면서 느꼈던 리팩터링의 중요성을 경험했기에 프리코스에서 했던 도전을 잘 녹일 수 있을 것 같았습니다. 그래서 저는 리팩터링, 클린코드를 목적으로 개발을 시작했습니다.</p>
<hr>
<h2 id="최종-코딩-테스트-당일---시험중">최종 코딩 테스트 당일 - 시험중</h2>
<p>구현은 2시 40분 쯤에 완료했습니다. 도전과제 성공의 전제 조건은 요구사항을 만족하는 것이었기에, 우선 그동안 연습했던 방식으로 구현을 완료 했습니다. 하지만 TDD를 추구하며 개발했기에 평소보다는 조금 오래걸렸던 것 같습니다. 구현도 중요하지만 도전 과제도 중요하기에 약 20분 동안 도전과제를 어떻게 수행할지 리드미에 정리했습니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/dde1f3e7-1c14-49d6-8e20-36e0ddb7d0c7/image.png" alt=""></p>
<p>(다시 보니까 리팩토링을 래픽터링으로 오타를 냈네요..ㅎㅎ 머쓱)</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/1ed1b7f5-32ba-4fdb-a4e6-abf37cdeacfc/image.jpeg" alt=""></p>
<p>작성을 완료할 때 쯤, 3시부터 10분간 일괄 휴식시간을 가졌습니다. 모두 노트북을 닫고 쉬는 시간을 가졌는데, 이때 중간에 포비님께서 좋은 말씀들을 해주셨습니다. 그리고 자주 하는 실수들에 대해서도 이야기 해주셨는데, 바로 레포지토리를 포크하지 않고 바로 클론해서 push가 안되는 경우였습니다. 사실 제가 시험 시작때 할 뻔한 실수라 공감이 되었습니다ㅎㅎ</p>
<p>10분 쉬고 다시 시작하니 확실히 이전보다 집중이 잘되었습니다. 남은 1시간 50분 동안 무한 리팩터링 천국? 지옥?을 하면서 계속 리팩터링 했습니다. 그리고 프리코스, 최종 코테 연습에서 TDD와 리팩터링을 끝까지 해보아서 다행이란 생각이 들었습니다. 4시 58분까지 리팩터링을 하고, 5시부턴 왜 리팩터링을 이렇게 했는지에 대해 하고 싶은 이야기들을 적으며 회고했습니다. 아래는 회고문 일부입니다!</p>
<hr>
<h3 id="어떤-도전을-선택했고-왜-그-선택이-나에게-중요해">어떤 도전을 선택했고, 왜 그 선택이 나에게 중요해?</h3>
<p>저는 리팩터링 도전을 선택했습니다. 이 선택이 저에게 중요한 이유는 오픈미션에서 리팩터링을 제대로 하지 못해서 코드의 맥락 파악과 관리를 하지 못했던 경험이 있기 때문이었습니다. 따라서 오픈미션과 최종 코딩 테스트에서 연습했던 리팩터링의 필요성과 저만의 기준을 이용해 조금은 급하게 짠 코드의 품질을 높히는 도전을 했습니다.</p>
<h3 id="구현-과정에서-끝까지-지키려-한-원칙과-그것을-위해-내려놓은-건-뭐야">구현 과정에서 끝까지 지키려 한 원칙과 그것을 위해 내려놓은 건 뭐야?</h3>
<p>프리코스에서 연습한 TDD를  끝까지 적용하기 위해 노력했습니다. 개발을 할 때는 두 가지 뇌의 스위칭이 필요합니다. 첫 번째는 기능이 안전하게 돌아가는지 확인하는 뇌, 두 번째는 기능을 효율적으로 구현하는 뇌입니다. 개인적으로 기능을 효율적으로 구현하고 나면, 기능이 안전하게 돌아가는지를 제가 짠 로직에 근거하여 테스트 하게 됩니다. 따라서 이를 방지하고 더 안전한 기능을 만들기 위해 저는 테스트 코드 -&gt; 기능 개발 -&gt; 리팩토링을 하는 TDD를 차용했습니다. 연습을 하면서 TDD가 장점이 됨을 느꼈던 순간은 사용자의 입력값 유효성을 검증할때 였습니다. 사용자 입력값은 상당히 자유롭기 때문에, 다양한 값에 대해서 유효성을 검증 해야 합니다. 하지만 이를 테스트 코드 없이 먼저 개발하게 되면 무조건 실수가 일어나는 경험을 했습니다. 그리고 유효성 검증 메서드의 테스트 코드를 먼저 짜고 개발을 하니 예외를 잘 검증하는지 확인하는 시간도 대폭 줄일 수 있었고 실수 또한 줄었습니다.</p>
<p>이처럼 TDD가 가져다주는 장점도 있지만, 아쉬운 부분도 있었습니다. 바로 private 메서드를 테스트 하기 위한 public 전환 입니다. 저는 이번 행성 로또의 등수를 계산할 때, calculateRank() 라는 메서드를 사용했습니다. 하지만, 이는 calculateResult()에서만 이용하기 때문에 public으로 둘 필요가 없었습니다. 하지만 제가 calculateRank()를 public으로 둔 이유는, 행성 로또 프로그램에서 핵심이 되는 로직이었기 때문입니다. 등수는 실행 결과에 심각한 영향을 줄 수 있기 때문에, calculateRank() 메서드의 동작 확인을 테스트 하는 테스트 코드가 있어야 했습니다. 따라서 TDD를 지향하기 위해 외부에서 사용하지 않는 메서드의 테스트를 위해 public으로 변경했습니다.</p>
<h3 id="프리코스에서-얻은-교훈-중-오늘-실제로-적용한-것은-뭐야">프리코스에서 얻은 교훈 중 오늘 실제로 적용한 것은 뭐야?</h3>
<p>저는 프리코스 2주차 미션을 하면서, 도메인 주도 개발과 MVC 패턴의 충돌 경험을 했습니다. 그리고 이런 경험은 최종 코딩테스트 연습을 할 때 적극적으로 반영되었습니다. 위에서 언급한 바와 같이, MVC패턴과 도메인 주도 개발 사이의 저만의 합의점을 찾아내었습니다. 그리고 이번 코드에서 MVC 패턴의 장점이라고 생각하는 계층 끼리의 책임 분리와, 로또 도메인을 활용하여 적절한 비지니스 로직을 만들었습니다.</p>
<h3 id="정책-상수를-분리해서-정책에-유연한-프로그램을-만들자">정책 상수를 분리해서 정책에 유연한 프로그램을 만들자</h3>
<p>리팩토링을 하면서 또 노력했던 것은 바로 정책에 유연한 프로그램을 만드는 것이었습니다. 프리코스 3주차 미션에서는 로또 1장의 가격은 1000원, 로또 번호의 범위는 1에서 45, 로또 번호의 개수는 6개, 로또 등수 책정 방법도 행성 로또와는 달랐습니다. 저는 이처럼 같은 프로그램이더라도, 정책이 달라질 수 있겠다는 생각이 들었습니다. 따라서 로또 1장의 가격, 로또 번호의 숫자 범위, 로또 번호의 개수를 PolicyConstant로 분리했고, 등수는, Rank enum으로 분리하고 of()메서드를 활용하여 로또가 당첨 번호와 몇 개가 맞는지와 보너스 번호 일치 유무를 가지고 Rank를 반환하도록 했습니다. 이렇게 분리 하면, 이제 정책을 변경하고 싶을 때 프로그램을 수정할 필요 없이 PolicyConstant와 Rank의 of만 수정하면 됩니다. 이렇게 되면 정책 변경을 할 때 발생하는 비용을 줄일 수 있어 효율적이라고 생각합니다. 추가로 ErrorMessage에서도 정책값을 사용하고 있어 이에 맞게 에러 메세지도 변경될 수 있도록 리팩터링 했습니다.</p>
<h3 id="마지막으로-하지-못했던-리팩터링">마지막으로 하지 못했던 리팩터링</h3>
<p>저의 리드미 파일의 리팩터링 리스트를 보시면, &quot;기본 메서드를 제외한 모든 메서드 15줄 이하로 리팩터링하기&quot;를 완료하지 못했습니다. 완료를 하지 못했던 부분은 Rank에 있는 of() 메서드였습니다. 저는 &quot;기본 메서드를 제외한 모든 메서드 15줄 이하로 리팩터링하기&quot;의 목적은 메서드는 하나의 기능만 하도록 하는 것과 더 나은 가독성을 위함이라고 생각했습니다. 하지만 of() 메서드는 메서드의 줄이 15줄을 넘어가지만, 하나의 기능만 할 뿐 아니라 가독성 또한 충분히 좋습니다. 오히려 15줄이라는 맹목적 규칙에 맞게 리팩터링을 하게 되면 직관적이지 않은 메서드가 될 것이라 생각하여 이를 남겨두었습니다.</p>
<hr>
<h2 id="최종-코딩테스트-끝">최종 코딩테스트 끝</h2>
<p>5시 50분 쯤 소감문 작성을 완료하고 6시까지 내용 검토 후에 시험이 끝났습니다. </p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/c9b13238-d365-4332-a868-5d74d51affc9/image.png" alt=""></p>
<p>최종 코테가 끝나고 그동안 했던 활동과 그때의 기억들이 주마등처럼 스쳐지나갔습니다. 학교 중간고사가 끝날때 시작하여 종강을 하고 해가 바뀌는 동안 상당히 긴 기간동안 프리코스를 하면서 그동안 생각해보지 못했던 것에 대해 고민해보고 디스코드에서 다양한 사람들의 글을 읽으며 저 또한 많이 성장한 것 같습니다.</p>
<p>마지막으로 우아한테크코스의 프리코스와 최종 코딩테스트를 준비하면서 저는 참 많은 <strong>도전과 성취</strong>를 한 것 같습니다. 무언갈 간절히 바랐던 것도, 바라던 목표를 이루었던 것도, 간절히 바라던 목표를 이루고 싶어 열심히 연습했던 것도 저에겐 모두 값진 경험이었습니다. 오늘부로 최종 코딩테스트도 모두 마무리 되었지만, 앞으로 또 다른 이루고 싶은 목표가 생긴다면 지금 우아한테크코스를 준비했던 마음과 태도를 가지고 도전할 준비를 할 것 같습니다.</p>
<hr>
<h2 id="최종-결과-발표까지-뭐할거야">최종 결과 발표까지 뭐할거야?</h2>
<p>일단 10월부터 대학교 공부와 동아리 프로젝트, 프리코스를 병행하면서 정말 바쁜 나날을 보냈습니다. 종강 후에도 제대로 휴식이란걸 하지 못한 것 같아 며칠은 푹 쉴 것 같습니다. 그리고 결과에 상관없이 해야할 일들이 있기에 이제 그 일들을 할 것 같습니다.</p>
<hr>
<h2 id="우아한테크코스-8기의-모든-지원-과정을-마치며">우아한테크코스 8기의 모든 지원 과정을 마치며</h2>
<p><strong>이번에도 역시 합격을 기대하지 않습니다.</strong> 사실 합격을 기대하지 않기 위해 노력중입니다. 결과에 흔들리지 않기 위해 최선을 다해 노력했지만, 노력했기에 기대를 하게 됩니다. 그래도 남은 기간 동안 이런 마음과 생각을 잘 다스리는 것도 앞으로 수없이 해야 할 중요한 도전에 좋은 경험이 될  것 같습니다.</p>
<p>정말 마지막으로 다들 수고하셨습니다:) 그리고 부족한 글 읽어주셔서 감사합니다!</p>
<hr>
<blockquote>
<p><strong>26.01.23 - 최종 합격 했습니다! 최종 합격 회고로 뵙겠습니다☺️</strong>
<img src="https://velog.velcdn.com/images/yeo___li/post/00a587da-475c-4e4e-bc90-4cf6751cdf6b/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[아직도 IntelliJ 그냥 쓰시나요?]]></title>
            <link>https://velog.io/@yeo___li/DevLog-JetBrains-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B0%9C%EB%B0%9C%EA%B3%BC-%EA%B8%B0%EB%A1%9D%EC%9D%84-%ED%95%A8%EA%BB%98%ED%95%B4%EB%B3%B4%EC%84%B8%EC%9A%94-fllbbjnv</link>
            <guid>https://velog.io/@yeo___li/DevLog-JetBrains-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B0%9C%EB%B0%9C%EA%B3%BC-%EA%B8%B0%EB%A1%9D%EC%9D%84-%ED%95%A8%EA%BB%98%ED%95%B4%EB%B3%B4%EC%84%B8%EC%9A%94-fllbbjnv</guid>
            <pubDate>Sat, 29 Nov 2025 18:30:29 GMT</pubDate>
            <description><![CDATA[<h1 id="0-들어가며">0. 들어가며</h1>
<p>안녕하세요! 서여입니다:) 오늘은 여러분들께 직접만든 플러그인을 소개드리고자 이렇게 글을 씁니다! 이번 프로젝트는 우아한테크코스 8기 프리코스에서 이번 기수에 새롭게 추가된 오픈미션 과정에서 만든 플러그인입니다. </p>
<p>오픈미션은 이름처럼 정말 각자 자유로운 주제를 3주동안 하는 과정이었는데요. 저는 프리코스를 진행하면서 평소에 제가 불편하게 느꼈던 부분을 개선하기 위한 프로젝트를 진행했습니다.</p>
<p>이제 어떤 가치를 주는 플러그인인지 간단하게 소개를 해드린 뒤, 설치 방법 까지 모두 전달해드릴 예정입니다:) </p>
<p>추가로 오픈미션에서는 마켓플레이스 등록까지 진행하지 못했지만, 2025년 11월 28일 마켓플레이스 심사 승인이 완료되어 현재는 마켓플레이스에서 누구나 다운로드 받으실 수 있어요!</p>
<p>혹시라도 사용하시는 분들에 한해서 플러그인의 후기를 남겨주신다면 정말 감사할 것 같습니다☺️</p>
<p>사용해보고 싶으신 분들은 <a href="#4-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%EB%B0%A9%EB%B2%95">여기</a>를 클릭해주세요!</p>
<hr>
<h1 id="1-devlog가-주는-가치">1. DevLog가 주는 가치</h1>
<blockquote>
<p><strong>IDE 내부에서 개발과 기록을 함께 할 수 있는 편리함</strong></p>
</blockquote>
<p>요즘 많은 개발자 분들께서 개발 일지나 회고를 많이 적으시는데요! 저같은 경우에는 노트나 메모장에 어떤 내용으로 적을지 저장해놓고 막상 회고를 적을때는 까먹어서 다시 코드나 커밋을 보면서 적거나, 더듬더듬 메모를 찾아 다시 기억을 꺼내는 작업을 하면서 회고를 했습니다. </p>
<p>그렇게 하다보니 메모를 보더라도 어떤 의도로 이 글을 적었는지 기억이 안날때도 있고, 메모를 할 때에도 코드와 같이 인사이트나 감상을 적고 싶은데 그렇제 못하니 어려울때가 많았습니다. 이런 문제점들 때문에 개발과 회고가 완벽히 분리되어 일관성이 떨어지는 경험을 자주 했었어요.</p>
<p>그리고 저는 주로 백엔드를 개발을 할때, java를 이용하기 때문에 intellij 라는 IDE를 사용합니다. 아마 대다수 분들께서도 이 IDE를 사용하고 계셔서 익숙하실 것 같습니다:) 저는 인텔리제이 안에서 개발 기록도 하면 좋겠단 생각이 들었습니다. </p>
<p>인텔리제이에서는 마켓플레이스를 통해 다양한 기능이 있는 플러그인을 다운받아 IDE 내부에서 사용할 수 있습니다. 플러그인을 생각하는 순간 개발과 기록을 함께라는 가치를 사용자에게 어떻게 전달할지 명확해졌습니다. 개발은 주로 IDE를 통해서 하므로, 기록을 IDE 안에서 할 수 있도록 플러그인을 제공하는 것입니다.</p>
<p>이렇게 <strong>IDE 내부에서 개발과 기록을 함께하여 개발 중 흐름을 깨지 않으면서 기록을 할 수 있는 것</strong>이 DevLog의 핵심 가치입니다!</p>
<hr>
<h1 id="2-이런-분들께-추천해요">2. 이런 분들께 추천해요</h1>
<p>DevLog는 코드를 선택하고 메모를 하고 추출하는 기능을 제공합니다! 따라서 아래와 같은 분들께 DevLog는 좋은 선택이 될 것 같습니다:)</p>
<ol>
<li><strong>개발일지/회고를 작성</strong>하며 그때그때 떠오르는 생각을 코드와 함께 기록하고 싶으신 분</li>
<li>개발을 하면서 git처럼 모든 코드가 아니라 <strong>일부 코드를 잠깐 백업</strong>하고 싶으신 분</li>
<li>나만 보기 위한 <strong>개발 상황 및 코드 리팩토링 사항을 정리</strong>하고 싶으신 분</li>
<li>코드에서 헷갈렸던 부분을 선택하여 <strong>지식을 아카이빙</strong>하고 싶으신 분</li>
</ol>
<hr>
<h1 id="3-기능-및-화면-소개">3. 기능 및 화면 소개</h1>
<p>이번엔 핵심 가치를 사용자분들께 전달하기 위해 어떤 기능을 제공하는지 보여드리는 챕터입니다! </p>
<p>큰 설명 없이 직접 사용해보시면서도 플러그인의 기능을 충분히 아실 수 있지만, 더 명확한 사용 방법을 제공하고자 이렇게 글로 다시 소개드립니다:) </p>
<p>추가로 모든 정보는 로컬에만 저장되기 때문에 데이터 유출은 걱정하지 않으셔도 됩니다!</p>
<hr>
<h2 id="31-초기-화면">3.1 초기 화면</h2>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/dc33b6f5-92c6-4aea-8c9b-1283d6ec2481/image.png" alt="최초 설치 화면"></p>
<p>마켓플레이스를 통해 가장 먼저 다운로드 받으시면, 왼쪽 툴바에 단추모양 혹은 사진과 같은 메모 모양의 아이콘이 생깁니다. 이 아이콘을 클릭하면 제일 먼저 작성한 메모를 확인할 수 있는 메모 목록 화면과 아래 새로운 메모를 입력할 수 있는 메모 입력칸이 있습니다.</p>
<hr>
<h2 id="32-상단-버튼">3.2 상단 버튼</h2>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/459a7957-4a26-405b-ba90-57099138acc8/image.png" alt="상단 버튼 4개"></p>
<p>다음은 상단 4개 버튼에 대해 소개해드리겠습니다. 사용자가 쉽게 사용할 수 있도록 직관적으로 만들기 위해 노력했습니다.</p>
<hr>
<h3 id="321-메모-목록노트-전환-버튼">3.2.1 메모 목록/노트 전환 버튼</h3>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/a11ef5fc-f7e6-45c6-9bbb-996fecaf95d5/image.png" alt=""></p>
<p>버튼을 누르면 메모가 생성 순으로 뜨는 메모 목록 화면과 하나의 노트처럼 사용할 수 있는 노트 화면으로 전환 됩니다.</p>
<blockquote>
<h4 id="메모-목록-화면">메모 목록 화면</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/52c2ce7e-cf99-4948-ae15-ef4d4b8c89d2/image.png" alt=""></p>
</blockquote>
<blockquote>
<h4 id="노트-화면">노트 화면</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/a926eceb-dcb1-40f8-b3c2-1b4a647e1edd/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="322-메모-전체-선택해제-버튼">3.2.2 메모 전체 선택/해제 버튼</h3>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/59d732bd-ddb4-49f1-bce4-98f09483aecc/image.png" alt=""></p>
<p>메모를 전체 선택하거나 선택 해제할 수 있습니다. 선택한 메모는 일괄 삭제하거나 추출할 수 있습니다.</p>
<blockquote>
<h4 id="메모-전체-선택-화면">메모 전체 선택 화면</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/0153cacd-5ec7-4a02-8aa0-6645be8d9d0a/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="323-메모-추출-버튼">3.2.3 메모 추출 버튼</h3>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/358a1af3-e8ec-4036-9ff5-2e7a3be0b41c/image.png" alt=""></p>
<p>선택한 메모를 txt 파일로 추출할 수 있습니다. 텍스트 파일 제목은 <code>devlog-{프로젝트명}-{YYYYDDMM}-{HHTTSS}</code> 입니다.</p>
<blockquote>
<h4 id="메모-추출-화면">메모 추출 화면</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/b0c9563d-49f9-48f3-b5b5-f796b3424d40/image.png" alt=""></p>
</blockquote>
<blockquote>
<h4 id="추출한-메모">추출한 메모</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/3658de67-f229-4bd7-90b0-f31c06c5ceb5/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="324-메모-삭제-버튼">3.2.4 메모 삭제 버튼</h3>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/e7f76c08-03ef-422e-a3f4-a82bf9e4e1c4/image.png" alt=""></p>
<p>선택한 메모를 삭제할 수 있습니다.</p>
<blockquote>
<h4 id="메모-삭제-화면">메모 삭제 화면</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/e1463514-9738-4fc1-856a-4a4f5e70eef0/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="33-메모-입력-화면">3.3 메모 입력 화면</h2>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/c73972f7-a75c-4566-a8bb-d266922f296f/image.png" alt=""></p>
<p>메모를 입력할 수 있습니다. 코드를 드래그 하면 어떤 파일을 드래그 했는지 <code>No code selected.</code> 컴포넌트에 표시됩니다.</p>
<p>메모를 입력한 뒤, <code>Save Log</code> 버튼 또는 macOS에서는 <code>⌘ + Enter</code>, Windows에서는 <code>Ctrl + Enter</code>를 눌러 저장할 수 있습니다. 저장을 할 때, 코드가 드래그 되어있으면 드래그된 코드와 메모가 함께 저장 됩니다.</p>
<blockquote>
<h4 id="코드가-선택되었을-때-화면selected-memokt">코드가 선택되었을 때 화면(Selected Memo.kt)</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/5c974ca2-5178-4239-8daa-91a528df2237/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="34-메모-목록-화면">3.4 메모 목록 화면</h2>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/1b9667ed-462b-48f5-8101-4550493f7f8a/image.png" alt=""></p>
<p>메모 목록 화면은 메모를 작성한 날짜와 시간순으로 정렬 됩니다. 코드가 포함되어있는지 유무에 따라 각 메모들은 다르게 표시됩니다.</p>
<p>코드가 포함되어 있는 메모는 시간 옆에 돋보기 모양이 뜹니다.</p>
<blockquote>
<h4 id="코드가-포함되어-있는-메모">코드가 포함되어 있는 메모</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/198ad4fc-9c21-4cc8-adbc-767514cb3811/image.png" alt=""></p>
</blockquote>
<blockquote>
<h4 id="코드가-포함되어-있지-않은-메모">코드가 포함되어 있지 않은 메모</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/773d8a83-a0b4-4f88-905d-6c5f27faa399/image.png" alt=""></p>
</blockquote>
<p>코드가 포함되어있는 메모를 더블클릭하면 전체 메모 내용과 드래그했던 코드가 녹색으로 하이라이트 되어 표시됩니다. </p>
<p>이때 생성되는 파일은 실제 파일이 아니며, DevLog_{파일명}으로 read only 형식으로 출력됩니다. </p>
<p>따라서 스냅샷을 찍었던 코드가 삭제/변경 되어도 기록하던 시점의 코드를 볼 수 있습니다. 파일 최상단에는 메모의 메타 데이터가 주석으로 포함됩니다.</p>
<blockquote>
<h4 id="코드와-함께-저장한-메모-화면">코드와 함께 저장한 메모 화면</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/b7150550-24a7-4733-a63d-6ba85e0818b7/image.png" alt=""></p>
</blockquote>
<p>코드가 없는 메모를 더블클릭 했을 때는 위 화면에서 코드 스냅샷이 뜨지 않고, 메모 전체 내용만 표시됩니다.</p>
<hr>
<h2 id="35-노트-화면">3.5 노트 화면</h2>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/97f5a313-8aa6-49e9-b0de-c749285acfa8/image.png" alt=""></p>
<p>개요를 적거나 현재 작업 내용을 정리해서 적을 수 있는 노트 화면입니다. </p>
<p>작성 후 자동으로 저장되며, 메모와 마찬가지로 macOS에서는 <code>⌘ + Enter</code>, Windows에서는 <code>Ctrl + Enter</code> 를 통해 저장할 수 있습니다.</p>
<hr>
<h1 id="4-다운로드-방법">4. 다운로드 방법</h1>
<p>플러그인을 다운로드 하는 방법은 정말 단순합니다! 아래 과정을 따라해주세요!</p>
<ol>
<li>JetBrains 어플리케이션 실행(IntelliJ, WebStorm 등)</li>
<li>상단 메뉴의 <code>settings → Plugins</code> 들어가기</li>
<li>Plugins의 중앙 상단에 있는 <code>Marketplace</code> 클릭 후 <strong>dev-log</strong> 검색하여 최상단 dev-log 어플리케이션 <code>Install</code> 버튼 누르기
<img src="https://velog.velcdn.com/images/yeo___li/post/003cc75b-9bef-4f5c-a6cc-dd104d8f3579/image.png" alt=""></li>
<li>우측 하단의 <code>Apply</code> 누르고 <code>OK</code> 누르기
<img src="https://velog.velcdn.com/images/yeo___li/post/ab9b508b-0996-473b-b6eb-10e7463079c3/image.png" alt=""></li>
<li>인텔리제이 종료 후 재시작 하면 우측 또는 좌측에 메모장(아니면 단추모양) 아이콘으로 뜨게 되어요!
<img src="https://velog.velcdn.com/images/yeo___li/post/8312afe3-5771-4b29-836b-f7fdc6944f62/image.png" alt=""></li>
</ol>
<hr>
<h1 id="5-마치며">5. 마치며</h1>
<p>이것으로 JetBrains 플러그인인 DevLog 소개를 마치도록 하겠습니다! 플러그인을 처음 만들면서 어려웠던 부분도 많았습니다. 하지만 프로그램을 만들고 현재도 직접 사용하면서 불편이 해소가 되는 부분들이 실제로 있어서 의미있는 프로젝트였던 것 같아요☺️ </p>
<p>마켓플레이스에도 등록한 만큼, 사용자가 한 명이라도 있다면 앞으로 부족한 부분을 꾸준히 보완하여 보다 더 좋은 플러그인을 만들겠습니다! </p>
<p>부족하고 긴 글 읽어주셔서 감사합니다👍🏻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit, 왜 그리고 어떻게 써야돼?]]></title>
            <link>https://velog.io/@yeo___li/JUnit-%EC%99%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8D%A8%EC%95%BC%EB%8F%BC</link>
            <guid>https://velog.io/@yeo___li/JUnit-%EC%99%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8D%A8%EC%95%BC%EB%8F%BC</guid>
            <pubDate>Sat, 24 May 2025 09:47:13 GMT</pubDate>
            <description><![CDATA[<h2 id="1-junit-이란">1. JUnit 이란?</h2>
<p>JUnit은 Java에서 <strong>단위 테스트</strong> 코드를 작성할 수 있게 도와주는 프레임워크입니다.</p>
<h3 id="11-단위테스트란">1.1 단위테스트란?</h3>
<p>소스코드의 특정 모듈(프로그램 내 하나의 기능을 부르는 말)이 의도된 대로 정확히 작동하는지 검증하는 절차이며,</p>
<p>함수, 메서드, 개별 코드 같은 작은 단위에 대해 테스트 케이스(Test Case)로 분리하고 테스트 코드를 작성하여 테스트하는 것을 의미합니다.</p>
<p>외부 API와의 연동이 필수라든가 DB 데이터 접근 등 외부 리소스를 직접 사용해야 하는 테스트라면 단위 테스트가 아닙니다. 단위 테스트에서 외부와의 연동이 필요하다면 테스트 대역(Test Double)을 사용하면 됩니다.</p>
<h3 id="12-junit을-사용하는-이유는">1.2 JUnit을 사용하는 이유는?</h3>
<p><strong>1. Java 진영의 사실상 표준 테스트 프레임워크</strong></p>
<ul>
<li>Spring과 Gradle, Maven, IntelliJ, Jekins 등 주요 툴이 Junit을 지원하고 있기 때문입니다.</li>
</ul>
<p><strong>2. 코드와 테스트를 같은 언어(Java)로 작성 가능</strong></p>
<ul>
<li>테스트 전용 문법(DSL) 없이 순수 자바 문법으로 테스트가 가능하기 때문입니다.</li>
</ul>
<p><strong>3. 테스트 자동화와 CI/CD 연동이 쉬움</strong></p>
<ul>
<li>GitHub Actions, Jenkins, Travies CI등에서 JUnit 테스트 결과를 자동으로 분석해주기 때문입니다.</li>
</ul>
<hr>
<h2 id="2-junit-기초">2. JUnit 기초</h2>
<h3 id="21-기본-annotation">2.1 기본 Annotation</h3>
<h4 id="211-test">2.1.1 @Test</h4>
<p><code>@Test</code>는 테스트 메서드라고 선언해주는 어노테이션입니다.</p>
<pre><code class="language-java">@Test
void testSum() {
    int result = 2 + 3;
    assertEquals(5, result);
}</code></pre>
<h4 id="212-beforeeachaftereach">2.1.2 @BeforeEach/@AfterEach</h4>
<ul>
<li><code>@BeforeEach</code>는 각 테스트 메서드 실행 전, 실행되는 메서드를 선언해주는 어노테이션입니다.</li>
<li><code>@AfterEach</code>는 각 테스트 메서드 실행 후, 실행되는 메서드를 선언해주는 어노테이션입니다.</li>
</ul>
<pre><code class="language-java">@BeforeEach
void setup() {
    System.out.println(&quot;테스트 시작 전 준비!&quot;);
}

@AfterEach
void teardown() {
    System.out.println(&quot;테스트 끝난 후 정리!&quot;);
}</code></pre>
<h4 id="213-beforeallafterall">2.1.3 @BeforeAll/@AfterAll</h4>
<ul>
<li><code>@BeforeAll</code>은 테스트 클래스를 초기화 할 때 딱 한 번 수행되는 메서드를 선언해주는 어노테이션입니다.</li>
<li><code>@AfterAll</code>은 테스트 클래스 내의 테스트 메서드를 모두 실행시킨 후 딱 한 번 수행되는 메서드를 선언해주는 어노테이션 입니다.</li>
<li><code>@BeforeAll</code>/<code>@AfterAll</code>을 사용할 메서드는 static으로 선언해야 합니다.</li>
</ul>
<pre><code class="language-java">@BeforeAll
static void beforeAll() {
    System.out.println(&quot;테스트 시작 전 최초 1회 수행!&quot;);
}

@AfterAll
static void afterAll() {
    System.out.println(&quot;테스트 종료 후 1회 수행!&quot;);
}</code></pre>
<h4 id="214-displayname">2.1.4 @DisplayName</h4>
<p><code>@DisplayName(&quot;&quot;)</code>은 테스트 이름을 사람이 읽기 쉽게 지정해주는 어노테이션입니다.</p>
<pre><code class="language-java">@DisplayName(&quot;더하기 기능 테스트&quot;)
@Test
void testAddition() {
    assertEquals(4, 2 + 2);
}</code></pre>
<h4 id="215-disabled">2.1.5 @Disabled</h4>
<p><code>@Disabled</code>은 무시하고 싶은 테스트 메서드를 지정하는 어노테이션입니다.</p>
<pre><code class="language-java">@Disabled
@Test
void testAddition() {
    System.out.println(&quot;이 메서드는 실행되지 않습니다!&quot;)
}</code></pre>
<h3 id="22-assertions-종류">2.2 Assertions 종류</h3>
<h4 id="221-assertequalsexpected-actual">2.2.1 assertEquals(expected, actual)</h4>
<p><code>assertEquals()</code>는 기대값과 실제값이 같은지 아닌지를 테스트 할 때 사용합니다.</p>
<pre><code class="language-java">// then
assertEquals(10, 5 + 5);</code></pre>
<h4 id="222-asserttruecondition">2.2.2 assertTrue(Condition)</h4>
<p><code>assertTrue()</code>는 조건식이 참인지 아닌지를 테스트 할 때 사용합니다.</p>
<pre><code class="language-java">// then
assertTrue(5 &gt; 3);</code></pre>
<h4 id="223-assertthat--assertj">2.2.3 assertThat() + AssertJ</h4>
<p><code>assertThat()</code>은 AssertJ를 사용하여 테스트에서 값을 좀 더 사람처럼 표현하면서 비교할 수 있게 도와주는 방식입니다.</p>
<blockquote>
<p><strong>AssertJ 란?</strong>
테스트 코드 안에서 값을 검증할 때 사용하는 &quot;Assertion 라이브러리&quot;
<a href="https://assertj.github.io/doc/">AssertJ 공식 문서 사이트</a></p>
</blockquote>
<p>JUnit에서도 지원하는 기본 Assertion 라이브러리가 있지만, 더 다양한 기능을 사용하기 위해 대부분 JUnit과 AssertJ를 함께 사용합니다.</p>
<blockquote>
<p><strong>JUnit vs AssertJ 차이점</strong></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>JUnit</th>
<th>AssertJ</th>
</tr>
</thead>
<tbody><tr>
<td>종류</td>
<td>테스트 프레임워크</td>
<td>Assertion 라이브러리</td>
</tr>
<tr>
<td>핵심 역할</td>
<td>테스트 실행, 구조 제공</td>
<td>테스트 내부 검증 표현</td>
</tr>
<tr>
<td>테스트 어노테이션</td>
<td>@Test, @BeforeEach, ...</td>
<td>없음(단독 실행 불가)</td>
</tr>
<tr>
<td>Assertion 기능</td>
<td>기본 제공</td>
<td>확장 제공</td>
</tr>
</tbody></table>
</blockquote>
<p>이제 assertThat의 문법의 예시 대해 알아보겠습니다.</p>
<p><strong>1. 숫자 테스트 코드 예시</strong></p>
<pre><code class="language-java">int result = 10;

assertThat(result)
    .isEqualTo(10)
    .isGreaterThan(5)
    .isLessThanOrEqualTo(10)
    .isPositive()
    .isNotZero();</code></pre>
<p><strong>2. boolean 테스트 코드 예시</strong></p>
<pre><code class="language-java">boolean flag = true;

assertThat(flag)
    .isTrue();</code></pre>
<p><strong>3. 문자열 테스트 코드 예시</strong></p>
<pre><code class="language-java">String name = &quot;yeoli&quot;;

assertThat(name)
    .isNotEmpty()
    .startsWith(&quot;ye&quot;)
    .endsWith(&quot;li&quot;)
    .contains(&quot;eo&quot;)
    .hasSize(5);</code></pre>
<p><strong>4. List 테스트 코드 예시</strong></p>
<pre><code class="language-java">List&lt;String&gt; users = List.of(&quot;yeoli&quot;, &quot;minwoo&quot;, &quot;junsuh&quot;);

assertThat(users)
    .hasSize(3)
    .contains(&quot;yeoli&quot;, &quot;junsuh&quot;)
    .doesNotContain(&quot;hacker&quot;);</code></pre>
<p><strong>5. Map 테스트 코드 예시</strong></p>
<pre><code class="language-java">Map&lt;String, Integer&gt; scores = Map.of(&quot;yeoli&quot;, 90, &quot;minwoo&quot;, 80);

assertThat(scores)
    .containsKey(&quot;yeoli&quot;)
    .containsEntry(&quot;minwoo&quot;, 80)
    .doesNotContainKey(&quot;hacker&quot;);</code></pre>
<p><strong>6. 객체 테스트 코드 예시</strong></p>
<pre><code class="language-java">User user = new User(&quot;yeoli&quot;, 25);

assertThat(user)
    .isNotNull()
    .extracting(&quot;name&quot;, &quot;age&quot;) // 특정 필드를 추출하여 검증 가능
    .containsExactly(&quot;yeoli&quot;, 25);</code></pre>
<p><strong>7. Optional 테스트 코드 예시</strong></p>
<pre><code class="language-java">Optional&lt;String&gt; result = Optional.of(&quot;yeoli&quot;);

assertThat(result)
    .isPresent()
    .contains(&quot;yeoli&quot;)
    .hasValueSatisfying(name -&gt; assertThat(name).startsWith(&quot;ye&quot;));</code></pre>
<p><strong>8. 예외 테스트 코드 예시</strong></p>
<pre><code class="language-java">assertThatThrownBy(() -&gt; {
    // 검증하고 싶은 메서드 입력
    throw new IllegalArgumentException(&quot;잘못된 입력입니다!&quot;);
})
    .isInstanceOf(IllegalArgumentException.class)
    .hasMessageContaining(&quot;잘못된&quot;);</code></pre>
<p><strong>9. 날짜 &amp; 시간 테스트 코드 예시</strong></p>
<pre><code class="language-java">LocalDate today = LocalDate.now();

assertThat(today)
    .isBeforeOrEqualTo(LocalDate.now())
    .isAfter(LocalDate.of(2020, 1, 1));</code></pre>
<h4 id="224-nested-테스트">2.2.4 @Nested 테스트</h4>
<p><code>@Nested</code>는 테스트 집합을 계층 구조로 그룹화 할 수 있는 어노테이션 입니다.</p>
<pre><code class="language-java">class LoginTest {

    @Test
    @Nested
    @DisplayName(&quot;성공&quot;)
    class Scucess {
        @Test
        @DisplayName(&quot;성공 시 200 반환&quot;)
        public void testExample() {...}

        @Test
        @DisplayName(&quot;~시 ~를 해야한다.&quot;)
        public void testExample2() {...}
    }

    @Test
    @Nested
    @DisplayName(&quot;실패&quot;)
    class Failure {
        @Test
        @DisplayName(&quot;실패 시 400 반환&quot;)
        public void testExample() {...}

        @Test
        @DisplayName(&quot;~시 ~를 해야한다.&quot;)
        public void testExample2() {...}
    }
}</code></pre>
<blockquote>
<p><strong>테스트 실행 결과</strong></p>
<pre><code>✅성공
   ✅성공 시 200 반환
   ✅~시 ~를 해야한다.
✅실패
   ✅실패 시 400 반환
   ✅~시 ~를 해야한다.</code></pre></blockquote>
<h4 id="225-parameterizedtest---반복-테스트">2.2.5 @ParameterizedTest - 반복 테스트</h4>
<p><code>@ParameterizedTest</code>는 한 테스트 메서드에 대해 다른 값으로 여러번 테스트 할 수 있도록 하는 어노테이션 입니다.</p>
<p><strong>1. 기본 테스트 구조</strong>
<code>@ValueSource()</code>를 사용하여 테스트 값을 입력합니다.</p>
<pre><code class="language-java">import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4}) // ValueSource를 이용하여 여러 값 입력
void isPositive(int number) {
    assertTrue(number &gt; 0);
}</code></pre>
<p><strong>2. 매개변수가 2개 이상일 때 테스트</strong>
<code>@CsvSource()</code>를 사용하여 테스트 값을 입력합니다.</p>
<pre><code class="language-java">@ParameterizedTest
@CsvSource({
    &quot;yeoli, 25&quot;,
    &quot;minwoo, 20&quot;,
    &quot;junsuh, 30&quot;
})
void testNameAndAge(String name, int age) {
    assertThat(name).isNotEmpty();
    assertThat(age).isGreaterThan(0);
}</code></pre>
<p><strong>3. 컬렉션 및 커스텀 객체 테스트</strong>
<code>@MethodSource</code>를 사용하여 테스트 값을 입력합니다.
<code>@MethodSource</code> 메서드는 static이고 Stream&lt;...&gt;을 리턴해야 합니다.</p>
<ul>
<li>컬렉션 입력 방법<pre><code class="language-java">static Stream&lt;List&lt;String&gt;&gt; provideNameLists() {
  return Stream.of(
      List.of(&quot;yeoli&quot;, &quot;minwoo&quot;),
      List.of(&quot;junsuh&quot;, &quot;hacker&quot;)
  );
}
</code></pre>
</li>
</ul>
<p>@ParameterizedTest
@MethodSource(&quot;provideNameLists&quot;)
void testNameList(List<String> names) {
    assertThat(names).isNotEmpty();
    assertThat(names).allSatisfy(name -&gt; assertThat(name).isNotBlank());
}</p>
<pre><code>
- 커스텀 객체 입력 방법
```java
record User(String name, int age) {}

static Stream&lt;User&gt; provideUsers() {
    return Stream.of(
        new User(&quot;yeoli&quot;, 25),
        new User(&quot;minwoo&quot;, 22)
    );
}

@ParameterizedTest
@MethodSource(&quot;provideUsers&quot;)
void testUser(User user) {
    assertThat(user.name()).isNotEmpty();
    assertThat(user.age()).isGreaterThan(0);
}</code></pre><p><strong>4. Enum 값 테스트</strong>
<code>@EnumSource</code>를 이용하여 테스트 값을 입력합니다.</p>
<pre><code class="language-java">enum Status { ACTIVE, INACTIVE, PENDING }

@ParameterizedTest
@EnumSource(Status.class)
void testEnum(Status status) {
    assertThat(status).isNotNull();
}</code></pre>
<hr>
<p>지금까지 JUnit과 AssertJ를 이용한 단위 테스트 방법에 대해 알아보았습니다.
감사합니다!</p>
<blockquote>
<p><strong>참조</strong>
<a href="https://velog.io/@jhbae0420/Nested%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B3%84%EC%B8%B5-%EA%B5%AC%EC%A1%B0%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0">@Nested를 활용하여 계층 구조의 테스트 코드 작성하기</a>
<a href="https://hseungyeon.tistory.com/328">[JAVA] UnitTest에서 사용하는 AssertJ의 AssertThat이란?</a>
<a href="https://beststar-1.tistory.com/27">JUnit의 개념과 장점, 단위 테스트의 개념과 장점, 단위/통합/기능 테스트</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[초보자를 위한 DTO 기본 틀]]></title>
            <link>https://velog.io/@yeo___li/DTO-%EA%B0%88%ED%94%BC-%EC%9E%A1%EA%B8%B0</link>
            <guid>https://velog.io/@yeo___li/DTO-%EA%B0%88%ED%94%BC-%EC%9E%A1%EA%B8%B0</guid>
            <pubDate>Tue, 29 Apr 2025 07:38:17 GMT</pubDate>
            <description><![CDATA[<p>백엔드 개발을 하다보면 dto에 대해 자주 보게 된다. 하지만 나같은 개발 초보에겐 dto는 정확히 무엇인지, 어떻게, 어디에 사용해야할지 막막해서 결국 받아들이기를 포기하는 것 같다. 그래서 개발 초보 입장에서 바라보고 나름 공부해본 dto에 대해서 정리해보겠다.</p>
<hr>
<h1 id="dto란">DTO란?</h1>
<p>DTO란 Data Tranfer Object로 프로세스 간에 데이터를 전달하는 객체를 의미한다. </p>
<p>프로그래밍을 하면서 한 메서드에서 다른 메서드로 결과 혹은 값을 전달해야하는 경우가 빈번하게 발생한다. 이때, 전달하는 값들이 어떤 집합이고 어떤 의미를 가지고 있는지 DTO를 이용해 묶어주면 코드의 가독성이 높아지게 된다.</p>
<p>DTO를 잘 사용하기 위해선 DTO의 목적에 대해 잘 생각해야 한다고 생각한다. </p>
<p>DTO의 목적은 프로세스 간에 데이터를 전달하는 것이다. 그렇다면 A프로세스에서 B프로세스로 넘어갈 때, 넘겨주어야 할 데이터를 DTO로 묶어서 보내면 된다. </p>
<p>변수를 직접 파라미터로 넘겨줘도 되지만, 각 데이터의 집합이 무엇을 의미하는지 모호해질 수도 있고, 메서드에 다른 변수가 추가되었을 때 수정해야 할 범위가 늘어난다. 따라서 변수가 한 개일지라도 DTO로 감싸 확장에 대비하는 것이 좋다.</p>
<hr>
<h1 id="dto는-언제어떻게-사용해야돼">DTO는 언제/어떻게 사용해야돼?</h1>
<p>DTO는 읽기 및 쓰기 프로세스로 나누어 사용 방법을 정할 수 있다.</p>
<h2 id="1-읽기-프로세스">1. 읽기 프로세스</h2>
<p>읽기 프로세스의 핵심은 사용자에게 값을 반환해야한다는 것이다.
읽기 프로세스는 요청 → 처리 → 응답 순서이므로 각 과정마다 DTO로 담아서 보내면 유지보수성 및 가독성이 좋아진다.</p>
<ul>
<li><strong>Request DTO</strong>: 클라이언트가 서버로 보낼 때 사용. 사용자가 보낸 정보를 파싱해서 Request DTO에 담으면 된다.</li>
<li><strong>Param DTO</strong>: 중간 로직(Service → Repository)에서 필요한 값을 전달할 때 사용. </li>
<li><strong>Response DTO</strong>: 서버가 클라이언트로 응답할 때 사용.</li>
</ul>
<p>Request → Param → Entity → Response 순서로 데이터는 이동하고 각각이 DTO가 된다.</p>
<h2 id="2-쓰기-프로세스">2. 쓰기 프로세스</h2>
<p>쓰기 프로세스의 핵심은 사용자의 값을 DB에 저장 혹은 삭제 해야한다는 것이다.
읽기 프로세스는 요청 → 처리 → 저장/삭제 순서이므로 각 과정마다 DTO로 담아서 보내면 좋다.</p>
<ul>
<li><strong>Request DTO</strong>: 외부 요청을 받을 때 사용.</li>
<li><strong>Command DTO</strong>: 비지니스 로직을 거칠 때 사용.</li>
<li><strong>Entity</strong>: DB 저장용 도메인 모델.</li>
</ul>
<p>DTO를 사용하기 모호할땐, 위처럼 읽기/쓰기 프로세스의 구조를 기반으로 DTO를 사용하면 훨씬 더 명확하게 사용이 가능하다.</p>
<hr>
<h1 id="일관성-있는-api를-위한-dto-사용법">일관성 있는 API를 위한 DTO 사용법</h1>
<p>백엔드는 프론트에게 JSON 형식으로 데이터를 보내준다. 이때 통일된 응답으로 프론트에게 보내주면 가독성 및 협업에 더 좋다.</p>
<p>형식은 다음과 같다.</p>
<p><code>CommonResponse&lt;T&gt;</code> DTO는 아래와 같은 정보를 가진다.</p>
<pre><code class="language-json">{
    &quot;status&quot;: 200,
      &quot;message&quot;: &quot;성공&quot;,
      &quot;data&quot;: {...}
}</code></pre>
<p>controller의 반환값은 <code>CommonResponse&lt;T&gt;</code>로 통일하게 되면, 일관성 있는 API를 만들어 사용할 수 있다.</p>
<p>다음은 Controller 예시이다.</p>
<pre><code class="language-java">package com.yeoli.yeolpost.controller;

@RestController
@RequiredArgsConstructor
public class PostController {

  private final PostService postService;

  @PostMapping(value = &quot;api/posts&quot;)
  // CommonResponse&lt;T&gt; 사용하여 API 형식 통일
  public CommonResponse&lt;Void&gt; createPost(@RequestBody @Valid PostCreateRequest request) {
  // 쓰기 프로세스 이므로, Request - Command - Response 구조 사용
    postService.savePost(request.toCommand());
    return new CommonResponse&lt;&gt;(200, &quot;게시글 작성 성공&quot;, null);
  }

  @GetMapping(value = &quot;/api/posts&quot;)
  // CommonResponse&lt;T&gt; 사용하여 API 형식 통일
  public CommonResponse&lt;PostSummaryListResponse&gt; getAllPosts() {
    return new CommonResponse&lt;&gt;(200, &quot;게시글 조회 성공&quot;, postService.getAllPosts());
  }

  @GetMapping(value = &quot;/api/posts&quot;, params = &quot;title&quot;)
  // CommonResponse&lt;T&gt; 사용하여 API 형식 통일
  public CommonResponse&lt;PostListResponse&gt; getPosts(
      @RequestParam(&quot;title&quot;) @Valid PostSearchRequest request) {
     // 읽기 프로세스이므로, Request - Param - Response 구조 사용, 하지만 간단한 프로세스이므로 Param DTO 생략
    return new CommonResponse&lt;&gt;(200, &quot;게시글 검색 성공&quot;, postService.getPostsByTitle(request));
  }
}
</code></pre>
<hr>
<h1 id="좋은-dto-네이밍-규칙">좋은 DTO 네이밍 규칙</h1>
<p>DTO의 네이밍 방법에 대해서 알아보겠다. DTO를 사용하기로 마음먹었지만 개념도 추상적이라 이름을 짓기가 더 어려울 수 있다. 따라서 DTO의 네이밍 규칙에 대해서 정리해보았다.</p>
<p>우선 DTO의 이름을 보고 알 수 있어야하는 정보는 역할과 목적이다. 따라서 아래 두 가지 원칙을 준수하여 네이밍하면 더 좋은 DTO 명이 될 것이다.</p>
<h2 id="1-역할에-따라-접미사를-붙여라">1. &#39;역할&#39;에 따라 접미사를 붙여라</h2>
<p>아까 프로세스에 따라 나누었듯이, 요청(Request), 응답(Response), 내부 전달(Command, Param), 응답(Response)를 접미사로 붙이면 어느 과정에서 필요한 DTO인지 바로 파악이 가능하다.</p>
<h2 id="2-무엇을-위한-dto인지를-붙여라">2. &#39;무엇&#39;을 위한 DTO인지를 붙여라</h2>
<p>Request DTO를 예로 들면, 게시물을 생성하기 위한 Request일수도, 또는 게시물을 단건으로 조회하기 위한 Request일수도, 게시물 목록 전체를 조회하기 위한 Request일 수도 있다. 따라서 무엇을 위한 DTO인지도 구별해주기 위해 아래와 같은 네이밍을 이용하면 좋다.</p>
<pre><code class="language-java">CreatePostRequest   // 생성용
UpdatePostRequest   // 수정용
PostDetailResponse  // 단건 조회용
PostListResponse    // 목록 응답용</code></pre>
<p>추가로 <code>List&lt;단일 조회 Response dto&gt;</code>로 결과를 반환한다면, 이는 <code>PostListResponse</code>라는 DTO로 한 번 더 감싼 뒤 반환하면 가독성과 확장가능성이 높은 코드를 만들 수 있다.</p>
<hr>
<p>글쓰기 너무 어렵다... 이번 글도 두서없이 적었지만 그래도 어쨌든 완성해서 뿌듯하다. 글쓰기 실력이 늘 수 있을까🥺</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Embedded SQL, JDBC, JPA의 개념 및 차이점]]></title>
            <link>https://velog.io/@yeo___li/Embedded-SQL-JDBC-JPA%EC%9D%98-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@yeo___li/Embedded-SQL-JDBC-JPA%EC%9D%98-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Mon, 10 Mar 2025 10:10:44 GMT</pubDate>
            <description><![CDATA[<p>최근 학교에서 강의를 들으면서 Embedded SQL에 대해서 알았습니다. 그런데 내장 SQL과 JDBC가 유사해 보였고, 더 나아가서 JPA와의 차이점 또한 궁금해졌습니다. 그래서 Embedded SQL, JDBC, JPA의 개념과 차이점을 간단히 알아보았습니다.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#1-embedded-sql%EC%9D%B4%EB%9E%80">1. Embedded SQL이란?</a>  <ul>
<li><a href="#11-embedded-sql%EC%9D%98-%EA%B0%9C%EB%85%90">1.1. Embedded SQL의 개념</a>  </li>
<li><a href="#12-embedded-sql%EC%9D%98-%ED%8A%B9%EC%A7%95-5%EA%B0%80%EC%A7%80">1.2. Embedded SQL의 특징 5가지</a>  </li>
</ul>
</li>
<li><a href="#2-jdbc%EB%9E%80">2. JDBC란?</a>  <ul>
<li><a href="#21-jdbc%EC%9D%98-%EA%B0%9C%EB%85%90">2.1. JDBC의 개념</a>  </li>
<li><a href="#22-jdbc%EC%9D%98-%ED%8A%B9%EC%A7%95-4%EA%B0%80%EC%A7%80">2.2. JDBC의 특징 4가지</a>    </li>
</ul>
</li>
<li><a href="#3-jpa%EB%9E%80">3. JPA란?</a>  <ul>
<li><a href="#31-jpa%EC%9D%98-%EA%B0%9C%EB%85%90">3.1. JPA의 개념</a>  </li>
<li><a href="#32-jpa%EC%9D%98-%ED%8A%B9%EC%A7%95-4%EA%B0%80%EC%A7%80">3.2. JPA의 특징 4가지</a>  </li>
</ul>
</li>
<li><a href="#4-%EC%B0%A8%EC%9D%B4%EC%A0%90">4. 차이점</a>  <ul>
<li><a href="#41-embedded-sql-vs-jdbc">4.1. Embedded SQL vs JDBC</a>  </li>
<li><a href="#42-jdbc-vs-jpa">4.2. JDBC vs JPA</a>  </li>
</ul>
</li>
</ul>
<h2 id="1-embedded-sql이란">1. Embedded SQL이란?</h2>
<hr>
<h3 id="11-embedded-sql의-개념">1.1. Embedded SQL의 개념</h3>
<p>Embedded SQL(내장 SQL)은 일반 프로그래밍 언어(호스트 언어)에 SQL 문을 포함하여 사용할 수 있도록 한 기술입니다. 즉, 데이터베이스와 상호작용하기 위해 프로그래밍 언어 내부에서 직접 SQL 쿼리를 실행하는 방식입니다.</p>
<h3 id="12-embedded-sql의-특징-5가지">1.2. Embedded SQL의 특징 5가지</h3>
<ol>
<li><p><strong>정적 SQL</strong></p>
<ul>
<li>내장 SQL은 정적 쿼리를 사용하므로 실행 계획이 컴파일 시점에 고정됩니다. 따라서 런타임에 쿼리를 분석하고 최적화 하는 오버헤드를 줄여줍니다.</li>
</ul>
</li>
<li><p><strong>프리컴파일러(Precompiler) 필요</strong></p>
<ul>
<li>내장 SQLdms 전처리기를 통해 SQL 문장을 분리하고 컴파일 해야하므로, 추가적인 빌드 단계가 필요합니다.</li>
</ul>
</li>
<li><p><strong>SQL 실행 결과 관리 가능</strong></p>
<ul>
<li>내장 SQL은 <code>SQLCODE</code> 또는 <code>SQLSTATE</code>를 통해 쿼리 실행 결과를 관리할 수 있어, 오류 처리 및 상태 점검이 용이합니다.</li>
</ul>
</li>
<li><p><strong>단일 튜플만 반환 가능</strong></p>
<ul>
<li>내장 SQL의 결과는 단일 튜플만 반환 가능합니다. 따라서 다중 행 데이터를 처리하기 위해선 커서를 사용해야 합니다. 이는 코드 복잡성을 증가시킬 수 있습니다.</li>
</ul>
</li>
<li><p><strong>호스트 언어와 통합 가능</strong></p>
<ul>
<li>내장 SQL은 호스트 언어와 통합되어 있어, 데이터베이스와 애플리케이션 간의 상호작용이 원활합니다.</li>
</ul>
</li>
</ol>
<h2 id="2-jdbc란">2. JDBC란?</h2>
<hr>
<h3 id="21-jdbc의-개념">2.1. JDBC의 개념</h3>
<p>JDBC는 Java Database Connectivity의 약자로, <strong>Java 애플리케이션에서 데이터베이스와 통신하기 위한 표준 API</strong>입니다. 이를 통해 Java 프로그램은 데이터베이스에 연결하고 데이터를 조회, 삽입, 수정, 삭제하는 작업을 수행할 수 있습니다. JDBC는 데이터베이스의 종류와 상관없이 일관된 방식으로 접근할 수 있습니다.</p>
<h3 id="22-jdbc의-특징-4가지">2.2. JDBC의 특징 4가지</h3>
<ol>
<li><p><strong>정적, 동적 SQL 모두 가능</strong></p>
<ul>
<li>동정 SQL이 가능하기 때문에 유연성과 코드 재사용성이 증가합니다. 또한 런타임에서 실행되기 때문에 복잡한 비지니스 로직을 구현하기에 용이합니다.</li>
</ul>
</li>
<li><p><strong>데이터베이스 독립성 보장</strong></p>
<ul>
<li>JDBC는 데이터베이스와 애플리케이션 간의 통신을 표준화하여, 특정 DBMS에 종속되지 않고 다양한 데이터베이스를 지원합니다.</li>
</ul>
</li>
<li><p><strong>표준 인터페이스 제공</strong></p>
<ul>
<li>추상화된 표준 API를 제공합니다.</li>
</ul>
</li>
<li><p><strong>영속성 보장</strong></p>
<ul>
<li>애플리케이션 데이터를 데이터베이스에 안전하게 저장하고 관리하여 영속성을 보장합니다.</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>동적 SQL과 정적 SQL 비교</strong></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>정적 SQL</th>
<th>동적 SQL</th>
</tr>
</thead>
<tbody><tr>
<td><strong>쿼리 생성 시점</strong></td>
<td>컴파일 시점</td>
<td>실행 시점</td>
</tr>
<tr>
<td><strong>유연성</strong></td>
<td>고정된 구조로 유연성이 낮음</td>
<td>조건에 따라 자유롭게 변경 가능</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>실행 계획 재사용 가능</td>
<td>매번 새로운 실행 계획 작성 필요</td>
</tr>
<tr>
<td><strong>보안</strong></td>
<td>상대적으로안전</td>
<td>SQL Injection에 취약</td>
</tr>
<tr>
<td><strong>코드 복잡성</strong></td>
<td>간단하고 명료</td>
<td>복잡한 로직으로 인해 가독성이 떨어질 수 있음</td>
</tr>
<tr>
<td><strong>사용 사례</strong></td>
<td>고정된 조건의 반복 작업</td>
<td>사용자 입력 기반의 동적인 조건 처리</td>
</tr>
</tbody></table>
</blockquote>
<h2 id="3-jpa란">3. JPA란?</h2>
<hr>
<h3 id="31-jpa의-개념">3.1. JPA의 개념</h3>
<p>JPA는 Java Persistence API의 약자로, <strong>자바 애플리케이션에서 관계형 데이터베이스와 상호작용하기 위한 ORM(Object-Relational Mapping) 기술 표준</strong>입니다. JPA는 객체 지향 프로그래밍(OOP)과 관계형 데이터베이스(RDBMS) 간의 패러다임 불일치 문제를 해결하기 위해 설계된 기술입니다.</p>
<blockquote>
<p><strong>ORM이란?</strong>
ORM은 객체와 관계형 데이터를 매핑하여, 객체 지향 프로그래밍 언어가 관계형 데이터베이스를 쉽게 사용할 수 있도록 도와주는 것입니다.</p>
</blockquote>
<h3 id="32-jpa의-특징-4가지">3.2. JPA의 특징 4가지</h3>
<ol>
<li><p><strong>애플리케이션과 JDBC 사이에서 동작</strong></p>
<ul>
<li>개발자가 JPQL로 코딩을 하면, JPA는 이를 SQL로 변환하고, 변환된 SQL은 최종적으로 JDBC를 통해 데이터베이스와 통신합니다.</li>
</ul>
</li>
<li><p><strong>데이터베이스 독립성 보장</strong></p>
<ul>
<li>JDBC와 마찬가지로 다양한 데이터베이스를 지원합니다.</li>
</ul>
</li>
<li><p><strong>객체 지향적 설계 가능</strong></p>
<ul>
<li>JPA는 데이터베이스의 테이블을 자바 객체(Entity)로 매핑하므로 자바 객체를 다루는 방식으로 데이터를 처리할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>JAVA ORM 기술 표준</strong></p>
<ul>
<li>JPA는 JAVA의 ORM 기술을 위한 API 표준 명세로, 특정 구현체에 의존하지 ㅇ낳고 다양한 ORM 프레임워크(Hibernate, EclipseLink 등)를 사용할 수 있습니다.</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>JPA와 QueryDSL의 차이</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>항목</th>
<th>JPQL(JPA)</th>
<th>QueryDSL</th>
</tr>
</thead>
<tbody><tr>
<td><strong>쿼리 작성 방식</strong></td>
<td>문자열 기반의 JPQL 작성</td>
<td>자바 코드 기반으로 쿼리 작성</td>
</tr>
<tr>
<td><strong>컴파일 시점 검증</strong></td>
<td>런타임 시점에 문법 오류 발생 가능</td>
<td>컴파일 시점에서 문법 오류 검증 가능</td>
</tr>
<tr>
<td><strong>동적 쿼리 작성</strong></td>
<td>복잡한 동적 쿼리 작성이 어려움</td>
<td>메서드 체이닝으로 동적 쿼리 작성이 쉬움</td>
</tr>
<tr>
<td><strong>IDE 지원</strong></td>
<td>문자열 기반이라 IDE 자동 완성 기능 제공 불가</td>
<td>IDE 자동 완성 지원</td>
</tr>
<tr>
<td><strong>가독성 및 유지보수성</strong></td>
<td>문자열로 인해 가독성이 떨어질 수 있음</td>
<td>코드 기반이라 가독성과 유지보수성이 높음</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>JPA 표준 구현체를 사용하므로 성능은 동일</td>
<td>JPA 위에서 동작하므로 성능은 동일</td>
</tr>
</tbody></table>
<h2 id="4-차이점">4. 차이점</h2>
<hr>
<h3 id="41-embedded-sql-vs-jdbc">4.1. Embedded SQL vs JDBC</h3>
<p>많은 차이점들이 있지만, 내장 SQL과 JDBC의 대표적인 차이점 2가지를 정리해보았습니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Embedded SQL</th>
<th>JDBC</th>
</tr>
</thead>
<tbody><tr>
<td><strong>데이터베이스 독립성</strong></td>
<td>특정 DBMS에 종속됨. DBMS마다 SQL 문법이 다를 수 있어, 다른 DBMS로 변경 시 수정이 필요함.</td>
<td>Java 표준 API로, 다양한 DBMS에서 동일한 코드로 동작 가능. DB독립성이 뛰어남.</td>
</tr>
<tr>
<td><strong>동적 SQL 지원</strong></td>
<td>정적 SQL만 지원. 컴파일 타임에 SQL이 확정되며, 런타임에서 변경할 수 없음.</td>
<td>동적 SQL 지원. 실행 중 SQL을 생성하여 동적으로 실행 가능.</td>
</tr>
</tbody></table>
<h3 id="42-jdbc-vs-jpa">4.2. JDBC vs JPA</h3>
<p>JDBC와 JPA는 근복적인 목적이 다릅니다. JDBC는 DBMS와 정보를 주고받기 위한 API이고, JPA는 JDBC 위에서 동작하는 ORM 프레임워크 입니다. 따라서 JPA → JDBC → DBMS 순서대로 동작합니다. 따라서 둘은 같은 선상에서 비교하는 것은 바람직하지 않습니다.</p>
<blockquote>
<h2 id="감사합니다☺️">감사합니다☺️</h2>
</blockquote>
<blockquote>
<p><strong>참조</strong>
[참조 사이트] <a href="https://velog.io/@dnjscksdn98/Database-ORM%EC%9D%B4%EB%9E%80">https://velog.io/@dnjscksdn98/Database-ORM%EC%9D%B4%EB%9E%80</a>
<a href="https://subsay.tistory.com/36">https://subsay.tistory.com/36</a>
<a href="https://f-lab.kr/insight/spring-data-jdbc-vs-jpa">https://f-lab.kr/insight/spring-data-jdbc-vs-jpa</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 설계 5원칙]]></title>
            <link>https://velog.io/@yeo___li/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84-5%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@yeo___li/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84-5%EC%9B%90%EC%B9%99</guid>
            <pubDate>Thu, 06 Mar 2025 02:40:57 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#1-solid%EB%9E%80">SOLID란?</a></li>
<li><a href="#2-srp---%EB%8B%A8%EC%9D%BC-%EC%B1%85%EC%9E%84-%EC%9B%90%EC%B9%99">SRP - 단일 책임 원칙</a></li>
<li><a href="#3-ocp---%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99">OCP - 개방 폐쇄 원칙</a></li>
<li><a href="#4-lsp---%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84-%EC%B9%98%ED%99%98-%EC%9B%90%EC%B9%99">LSP - 리스코프 치환 원칙</a></li>
<li><a href="#5-isp---%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EC%9B%90%EC%B9%99">ISP - 인터페이스 분리 원칙</a></li>
<li><a href="#6-dip---%EC%9D%98%EC%A1%B4-%EC%97%AD%EC%A0%84-%EC%9B%90%EC%B9%99">DIP - 의존 역전 원칙</a></li>
<li><a href="#7-solid-%EC%A0%95%EB%A6%AC">SOLID 정리</a></li>
</ol>
<h2 id="1-solid란">1. SOLID란?</h2>
<hr>
<p>객체지향 언어의 역사는 50년 이상의 세월이 흘렀다. 이런 많은 세월 동안 수많은 시행착오와 베스트 프랙티스 속에서 객체지향 설계(OOD: Object Oriented Design)의 정수라고 할 수 있는 5원칙이 집대성 되었는데, 이것이 바로 SOLID이다. SOLID는 아래 5가지 원칙의 앞 머리 알파벳을 따서 부르는 이름이다.</p>
<ul>
<li>SRP(Single Responsibility Prinple): 단일 책임 원칙</li>
<li>OCP(Open Closed Principle): 개방 폐쇄 원칙</li>
<li>LSP(Liskov Substitution Principle): 리스코프 치환 원칙</li>
<li>ISP(Interface Segregation Principle): 인터페이스 분리 원칙</li>
<li>DIP(Dependency Inversion Principle): 의존 역전 원칙</li>
</ul>
<p>이 원칙들은 <strong>응집도를 높이고 결합도를 낮추는</strong> 고전 원칙을 객체 지향의 관점에서 재정립 한 것이라고 할 수 있다.</p>
<blockquote>
<p><strong>결합도(coupling)와 응집도(cohesion)</strong></p>
<p>결합도: 모듈(클래스) 간의 상호 의존 정도.
응집도: 하나의 모듈 내부에 존재하는 구성 요소들의 기능적 관련성.</p>
</blockquote>
<p>SOLID 원칙은 물리적으로 존재하는 것이 아니라 개념적으로 존재하는 것이다. 따라서 사람마다 다르게 해석될 수 있다.</p>
<h2 id="2-srp---단일-책임-원칙">2. SRP - 단일 책임 원칙</h2>
<hr>
<p>단일 책임 원칙(Single Responsibility Principle)이란 클래스는 하나의 책임만 담당해야 한다는 원칙이다. 이 원칙이 지켜지지 않은 예시들을 보면서 더 자세히 설명해보겠다.</p>
<blockquote>
<p><strong>나쁜 예시1</strong></p>
</blockquote>
<pre><code class="language-java">class 사람 {
    String 군번;
    ...
}
&gt;
...
&gt;
사람 로미오 = new 사람();
사람 줄리엣 new 사람();
&gt;
줄리엣.군번 = &quot;70000784&quot;; // 이건?</code></pre>
<blockquote>
</blockquote>
<p>줄리엣은 여자라 군대를 가지 않았다. 따라서 군번이 존재해선 안되는 것이다. 따라서 사람 클래스를 여자, 남자 클래스로 분할하고, 남자 클래스에만 군번 속성을 갖게 리팩토링 하면 SRP 원칙을 잘 준수하는 코드를 만들 수 있다. 그리고 이때 여자와 남자의 공통점은 사람이라는 클래스에 만들고 이를 상속 받으면 된다.</p>
<blockquote>
<p><strong>나쁜 예시2</strong></p>
</blockquote>
<pre><code class="language-java">class 강아지 {
    final static Boolean 수컷 = true;
    final static Boolean 암컷 = false;
    Boolean 성별;
&gt;    
    void 소변보다() {
        if(this.성별 == 수컷) {
            // 한쪽 다리를 들고 소변을 본다.
        } else {
            // 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
        }
    }
}</code></pre>
<p>강아지 클래스는 강아지가 수컷이냐 암컷이냐에 따라 <code>소변보다()</code> 메서드에서 분기 처리가 진행되는 것을 볼 수 있다. 즉, 강아지 클래스는 수컷, 암컷 강아지의 메서드를 모두 구현하고 있다. 이는 단일 책임 원칙에 위배하고 있다는 것이다.</p>
<blockquote>
<p><strong>좋은 예시 2</strong></p>
</blockquote>
<pre><code class="language-java">abstract class 강아지 {
    abstract void 소변보다();
}
&gt;
class 수컷강아지 extends 강아지 {
    void 소변보다() {
        // 한쪽 다리를 들고 소변을 본다.
    }
}
&gt;
class 암컷강아지 extends 강아지 {
    void 소변보다() {
        // 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
    }
}</code></pre>
<p>이렇게 나누어준다면 단일 책임 원칙을 준수할 수 있다.</p>
<h2 id="3-ocp---개방-폐쇄-원칙">3. OCP - 개방 폐쇄 원칙</h2>
<hr>
<p>개방 폐쇄 원칙(Open Closed Principle)이란, <strong>&quot;소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다.&quot;</strong> 라는 원칙이다. 이 원칙을 조금 더 간단하게 의역해보면 <strong>&quot;자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.&quot;</strong> 라는 의미이다. 현실 세계에서의 OCP 원칙 적용 사례를 보면 좀 더 쉽게 이해할 수 있을 것이다. </p>
<blockquote>
<p><strong>현실 세계의 예시 1</strong>
운전자(소프트웨어 엔티티)는 기존에 몰던 자동차의 차종이 바뀌어도(확장됨) 운전을할 수 있다.
즉, 자동차는 항상 운전자에게 맞춰진다.</p>
</blockquote>
<blockquote>
<p><strong>현실 세계의 예시 2</strong>
JAVA로 만든 소프트웨어(소프트웨어 엔티티)는 운영체제의 종류와 상관없이(확장됨) JVM을 통해 실행할 수 있다.
즉, JVM은 항상 JAVA 소프트웨어에게 맞춰진다.</p>
</blockquote>
<blockquote>
<p><strong>현실 세계의 예시 3</strong>
손님(소프트웨어 엔티티)은 편의점 직원이 바뀌어도(확장됨) 물건을 구매할 수 있다.
즉, 편의점 직원은 항상 손님에게 맞춰진다.</p>
</blockquote>
<p>현실 세계의 예시들을 객체지향적으로 구현하기 위해서는 소프트웨어 엔티티 사이에 인터페이스가 존재해야 한다. </p>
<p>예시1도 운전자와 자동차 사이에 <code>운전하다()</code> 라는 인터페이스를 만들어 어떤 운전자도 <code>운전하다()</code> 라는 것만 익히면 앞으로 <code>운전하다()</code> 라는 인터페이스를 상속한 어떤 자동차든 운전할 수 있게 된다. 이렇게 되면 자동차 입장에선 개방에는 열려있고, 운전자 입장에선 주변 변화에 대해서 폐쇄되어 있게 된다.</p>
<h2 id="4-lsp---리스코프-치환-원칙">4. LSP - 리스코프 치환 원칙</h2>
<hr>
<p>리스코프 치환 원칙(Liskov Substitution Principle)이란 <strong>&quot;서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다.&quot;</strong> 는 의미이다.</p>
<p>상속에 대해 설명하면서 객체 지향에서의 상속은 조직도나 계층도가 아닌 분류도가 돼야 한다고 했다. 객체 지향의 상속은 다음의 조건을 만족해야 한다. </p>
<ul>
<li>하위 클래스 is a kind of 상위 클래스 - 하위 분류는 상위 분류의 한 종류이다.</li>
<li>구현 클래스 is a kind of 인터페이스 - 구현 분류는 인터페이스할 수 있어야 한다.</li>
</ul>
<p>위 두 개의 문장대로 구현된 프로그램이라면 이미 리스코프 치환 원칙을 잘 준수하고 있다. 하지만 상속이 조직도나 계층도 형태로 구축되었다면 이는 LSP 원칙을 잘 준수하지 못하는 코드이다.</p>
<blockquote>
<p><strong>&quot;인터페이스하다&quot;는 다음을 의미한다.</strong></p>
</blockquote>
<ul>
<li>AudoCloseable - 자동으로 닫힐 수 있어야 한다.</li>
<li>Appendable - 덧붙일 수 있어야 한다.</li>
<li>Cloneable - 복제할 수 있어야 한다.</li>
<li>Runnable - 실행할 수 있어야 한다.</li>
</ul>
<p>다음은 LSP 원칙의 예시이다.</p>
<blockquote>
<p><strong>좋은 예시 1) 동물(기반 타입) ← 펭귄(서브 타입) 상속 관계</strong></p>
</blockquote>
<pre><code class="language-java">동물 뽀로로 = new 펭귄();</code></pre>
<blockquote>
<p><strong>나쁜 예시 1) 아버지(기반 타입) ← 딸(서브 타입) 상속 관계</strong></p>
</blockquote>
<pre><code class="language-java">아버지 춘향이 = new 딸(); // ??</code></pre>
<p>우선 동물-펭귄 상속 관계는 전혀 문제가 없으므로 LSP 원칙을 잘 준수하고 있다. 하지만 아버지-딸 관계는 문제가 발생한다. 춘향이는 딸인데 아버지의 역할을 시킬 수 있단 말인가? 따라서 이는 LSP 원칙에 위배되는 코드이다.</p>
<p>LSP 원칙은 계층도/조직도의 경우가 아닌, 분류도에서 지켜질 수 있다.</p>
<h2 id="5-isp---인터페이스-분리-원칙">5. ISP - 인터페이스 분리 원칙</h2>
<hr>
<p>인터페이스 분리 원칙(Interface Segregation Principle)이란 <strong>&quot;클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.&quot;</strong> 라는 의미이다.</p>
<p>인터페이스 분리 원칙은 다른 말로 인터페이스 최소주의 원칙이다. 인터페이스를 제공할 땐 최소한의 메서드만 제공해야 한다는 것을 의미한다다. 그리고 최소주의에 기반하여 상위 클래스는 풍성할수록 좋고, 인터페이스는 작을수록 좋다.</p>
<blockquote>
<p><strong>빈약한 상위 클래스 예시</strong></p>
</blockquote>
<pre><code class="language-java">// 빈약한 상위 클래스
public class 사람 {
    String 이름;
 &gt;
    public void 먹다() {...}
}
&gt;
// 풍성한 하위 인터페이스들
public class 학생 extends 사람 {
    String 생일;
    String 주민등록번호;
    String 학번;
&gt;    
    public void 자다() {...}
    public void 소개하다() {...}
      public void 공부하다() {...}
}
&gt;
public class 군인 extends 사람 {
    String 생일;
    String 주민등록번호;
    String 군번;
&gt;
    public void 자다() {...}
    public void 소개하다() {...}
      public void 훈련하다() {...}
}</code></pre>
<blockquote>
<p><strong>풍성한 상위 클래스 예시</strong></p>
</blockquote>
<pre><code class="language-java">// 풍성한 상위 클래스
public class 사람 {
    String 이름;
    String 생일;
    String 주민등록번호;
&gt;
    public void 먹다() {...}
    public void 자다() {...}
    public void 소개하다() {...}
}
&gt;
// 빈약한 하위 인터페이스들
public class 학생 extends 사람 {
    String 학번;
&gt;    
      public void 공부하다() {...}
}
&gt;
public class 군인 extends 사람 {
    String 군번;
&gt;
      public void 훈련하다() {...}
}</code></pre>
<p>이처럼 인터페이스는 &quot;~할 수 있는(is able to)&quot;이라는 기준으로 만드는 것이 정석이다.</p>
<p>마지막으로 ISP 원칙과 SRP 원칙은 같은 문제에 대한 두 가지 다른 해결 방법이다. 책임과 역할을 분리하여 유지보수성과 가독성을 높이는 전략이고, 이 두 가지 방법 모두 해결이 가능하다. 하지만 특별한 경우가 아니라면 SRP 원칙을 사용하는 것이 더 좋은 해결책이다.</p>
<h2 id="6-dip---의존-역전-원칙">6. DIP - 의존 역전 원칙</h2>
<hr>
<p>의존 역전 원칙(Dependency Inversion Principle)이란 <strong>&quot;자신보다 변하기 쉬운 곳에 의존하지 마라.&quot;</strong> 라는 의미입니다. 예시를 들어보겠습니다.</p>
<h3 id="자동차와-스노우타이어">자동차와 스노우타이어</h3>
<p>자동차는 스노우타이어에 의존합니다. 하지만 스노우타이어는 겨울이 끝나면 일반 타이어로 교체해야 한다. 이때, 자동차는 그 영향에 그대로 노출되어 있다. 이는 외부 환경에 부서지기 쉬운 나쁜 코드다. </p>
<p>이를 해결하기 위해서 자동차와 스노우타이어 사이에 <strong>타이어</strong> 라는 인터페이스를 두고 자동차는 타이어 클래스를, 스노우타이어도 타이어 인터페이스를 의존하도록 합니다. 이렇게 되면 스노우타이어는 타이어 인터페이스에 역으로 의존하게 된다. 이를 DIP 원칙, 의존 역전 원칙 이라고 부른다.</p>
<p>이때 OCP 원칙과 상당히 유사한 부분이 있는데, 객체 지향 4대 특성(추상화, 상속, 다형성, 캡슐화)을 활용하기 때문에 결국 비슷비슷한 코드의 모습으로 구현될 수 밖에 없다.</p>
<h2 id="7-solid-정리">7. SOLID 정리</h2>
<hr>
<p>SOLID를 이야기 할 때 빼놓을 수 없는 것이 SoC다. SoC는 관심사의 분리(Separation Of Concerns)의 머리글자이다. 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모으고, 관심이 다른 것은 가능한 한 따로 떨어져 서로 영향을 주지 않도록 분리하라는 것이다. 하나의 속성, 하나의 메서드, 하나의 클래스, 하나의 모듈, 또는 하나의 패키지에는 하나의 관심사만 들어 있어야 한다는 것이다. Soc를 적용하면 자연스럽게 SOLID 하게 코딩을 하게 된다.</p>
<p>다음은 SOLID 원칙을 다시 한 번 정리해보겠습니다.</p>
<ul>
<li>SRP(단일 책임 원칙): 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.</li>
<li>OCP(개방 폐쇄 원칙): 자신이 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.</li>
<li>LSP(리스코프 치환 원칙): 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.</li>
<li>DIP(의존 역전 원칙): 자신보다 변하기 쉬운 것에 의존하지 마라.</li>
</ul>
<p>SOLID 원칙을 적용하면 소스파일의 개수는 더 많아지지만 이를 통해 얻는 이점이 더 크다.</p>
<blockquote>
<p><strong>참조</strong>
[도서] 스프링 입문을 위한 자바 객체지향의 원리와 이해 5장</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Union-Find 알고리즘]]></title>
            <link>https://velog.io/@yeo___li/Union-Find-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@yeo___li/Union-Find-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Tue, 04 Mar 2025 13:05:38 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ul>
<li><a href="#%EC%84%9C%EB%A1%9C%EC%86%8C-%EC%A7%91%ED%95%A9disjoint-set">서로소 집합(Disjoint Set)</a></li>
<li><a href="#union-find%EB%9E%80">Union-Find란?</a></li>
<li><a href="#union-find-%EC%97%B0%EC%82%B0">Union-Find 연산</a><ul>
<li><a href="#findx-%EC%97%B0%EC%82%B0">Find(x) 연산</a></li>
<li><a href="#unionx-y-%EC%97%B0%EC%82%B0">Union(x, y) 연산</a></li>
</ul>
</li>
<li><a href="#union-find-%EC%97%B0%EC%82%B0-%EC%B5%9C%EC%A0%81%ED%99%94">Union-Find 연산 최적화</a><ul>
<li><a href="#1-%EA%B2%BD%EB%A1%9C-%EC%95%95%EC%B6%95path-compression">1. 경로 압축(Path Compression)</a></li>
<li><a href="#2-%EB%9E%AD%ED%81%ACrank-%EC%B5%9C%EC%A0%81%ED%99%94">2. 랭크(Rank) 최적화</a></li>
</ul>
</li>
<li><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ul>
<h2 id="서로소-집합disjoint-set">서로소 집합(Disjoint Set)</h2>
<hr>
<p>서로소 집합(Disjoint Set)은 <strong>겹치는 원소가 없는 여러 개의 집합</strong>을 의미합니다.</p>
<blockquote>
<p><strong>서로소 집합 예시</strong>
{1, 2, 3}, {4, 5, 6}</p>
</blockquote>
<h2 id="union-find란">Union-Find란?</h2>
<hr>
<p>Union-Find는 서로소의 집합을 유지하면서, 여러 개의 집합을 하나로 합치거나(Union), 특정 원소가 속한 집합을 찾는(Find) 연산을 빠르게 수행하는 알고리즘 입니다.</p>
<h2 id="union-find-연산">Union-Find 연산</h2>
<hr>
<h3 id="findx-연산">Find(x) 연산</h3>
<ul>
<li>x가 속한 집합의 대표(루트) 노드를 찾는 연산</li>
<li>해당 노드가 속한 트리의 루트 노드를 찾는 과정임.</li>
</ul>
<h3 id="unionx-y-연산">Union(x, y) 연산</h3>
<ul>
<li>x가 속한 집합과 y가 속한 집합을 하나로 합치는 연산</li>
<li>한 집합의 루트 노드를 다른 집합의 루트 노드에 연결하는 방식으로 수행</li>
</ul>
<h2 id="union-find-연산-최적화">Union-Find 연산 최적화</h2>
<hr>
<p>기본적인 구현은 Find 연산이 최악의 경우 <code>O(N)</code>까지 걸릴 수 있습니다. 따라서 이를 최적화 하기 위해 경로 압축(Path Compression)과 랭크 최적화 기법을 사용합니다.</p>
<h3 id="1-경로-압축path-compression">1. 경로 압축(Path Compression)</h3>
<p>경로 압축 기법은 find(x) 호출 시, 재귀적으로 부모를 갱신하여 최상위 루트를 저장하는 방식입니다. 이를 적용하면 트리의 높이가 줄어들어, find 연산이 평균적으로 <code>O(1)</code>에 가까워집니다.</p>
<pre><code class="language-java">public int find(int x) {
    if(parent[x] != x) {
        parent[x] = find(parent[x]);
    }
    return parent[x];
}</code></pre>
<h3 id="2-랭크rank-최적화">2. 랭크(Rank) 최적화</h3>
<p>두 집합을 합칠 때, 항상 작은 트리를 큰 트리에 붙이는 기법입니다. 이를 적용하면 트리의 높이가 불필요하게 커지는 것을 방지할 수 있습니다.</p>
<pre><code class="language-java">private int[] rank;

public UnionFind(int size) {
    parent = new int[size];
    rank = new int[size]; // 각 집합의 깊이를 저장
    for (int i = 0; i &lt; size; i++) {
        parent[i] = i;
        rank[i] = 1;
    }
}

public void union(int x, int y) {
    int rootX = find(x);
    int rootY = find(y);

    if (rootX != rootY) {
        if(rank[rootX])
    }
}</code></pre>
<h2 id="마무리">마무리</h2>
<hr>
<p>Union-Find는 그래프의 사이클을 찾을때 아주 빠른 시간내로 찾을 수 있습니다. 따라서 꼭 익혀야 할 중요한 알고리즘인 것 같습니다. 그리고 앞서 유니온 파인드를 설명할 때 최적화 방법까지 설명 드렸는데, 그냥 처음부터 최적화된 방법으로 이해해주시면 앞으로 코딩 인생에 더 좋지 않을까 생각됩니다ㅎㅎ 설명이 필요하시다면 언제든 댓글 달아주시면 감사하겠습니다☺️ 다들 수고하셨습니다:)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[최소 신장 트리(MST)의 개념과 프림 알고리즘]]></title>
            <link>https://velog.io/@yeo___li/%EC%B5%9C%EC%86%8C-%EC%8B%A0%EC%9E%A5-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-Prim-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@yeo___li/%EC%B5%9C%EC%86%8C-%EC%8B%A0%EC%9E%A5-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-Prim-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Fri, 28 Feb 2025 13:19:58 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#1-spanning-tree%EC%8B%A0%EC%9E%A5-%ED%8A%B8%EB%A6%AC%EB%9E%80">Spanning Tree(신장 트리)란?</a>  </li>
<li><a href="#2-minimum-spanning-tree%EC%B5%9C%EC%86%8C-%EC%8B%A0%EC%9E%A5-%ED%8A%B8%EB%A6%AC%EB%9E%80">Minimum Spanning Tree(최소 신장 트리)란?</a>  </li>
<li><a href="#3-mst%EC%9D%98-%EC%82%AC%EC%9A%A9-%EC%98%88%EC%8B%9C">MST의 사용 예시</a>  </li>
<li><a href="#4-mst%EB%A5%BC-%EA%B5%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95">MST를 구하는 방법</a>  <ul>
<li><a href="#41-%ED%94%84%EB%A6%BC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">프림 알고리즘</a>  </li>
</ul>
</li>
</ol>
<h2 id="1-spanning-tree신장-트리란">1. Spanning Tree(신장 트리)란?</h2>
<hr>
<p>spanning tree(신장 트리)는 <strong>모든 정점이 간선으로 연결</strong>되어 있고 <strong>간선의 개수가 정점 개수보다 하나 적은 그래프</strong>를 의미합니다.</p>
<blockquote>
<p><strong>신장 트리의 조건</strong></p>
<ol>
<li>모든 정점이 간선으로 연결 되어 있어야 한다.</li>
<li>간선의 개수는 정점 개수 - 1 이어야 한다.</li>
</ol>
</blockquote>
<blockquote>
<p><strong>Spanning Tree 예시</strong>
<img src="https://velog.velcdn.com/images/yeo___li/post/b15ff4ec-1dea-4708-b549-a9fd706d9e54/image.png" alt=""></p>
</blockquote>
<h2 id="2-minimum-spanning-tree최소-신장-트리란">2. Minimum Spanning Tree(최소 신장 트리)란?</h2>
<hr>
<p>Minimum Spanning Tree(최소 신장 트리)는 그래프에서 만들 수 있는 신장 트리 중 간선의 가중치의 합이 최소가 되는 트리를 의미합니다. 최소 신장 트리는 주로 MST로 부릅니다.</p>
<blockquote>
<p><strong>MST의 조건</strong></p>
<ol>
<li>신장 트리의 조건을 만족해야 한다.</li>
<li>간선 가중치의 합이 최소가 되어야 한다.</li>
</ol>
</blockquote>
<blockquote>
<p><strong>MST 예시</strong>
<img src="https://velog.velcdn.com/images/yeo___li/post/bf73cffc-5f97-419c-80bd-19250a0d1272/image.png" alt=""></p>
</blockquote>
<h2 id="3-mst의-사용-예시">3. MST의 사용 예시</h2>
<hr>
<p>MST는 도시 간 도로망 최적화, 통신 네트워크 설계, 전력망 최적화, 물류 네트워크 설계 등 다양한 분야에서 사용 됩니다.</p>
<h2 id="4-mst를-구하는-방법">4. MST를 구하는 방법</h2>
<hr>
<p>이제 간선의 가중치가 주어진 그래프에서 MST를 구하는 방법에 대해 소개하겠습니다. MST를 구하는 알고리즘은 프림 알고리즘과 크루스칼 알고리즘이 있습니다.</p>
<h3 id="41-프림-알고리즘">4.1 프림 알고리즘</h3>
<p>프림 알고리즘(Prim&#39;s Algorithm)의 단계는 다음과 같습니다.</p>
<blockquote>
<p><strong>프림 알고리즘에서 MST를 구하는 과정</strong></p>
<ol>
<li>임의의 정점 하나를 선택해서 MST에 추가합니다.</li>
<li>최소 신장 트리와 연결되어 있는 정점 중 가장 가중치가 적은 정점을 MST에 추가합니다. 단, 순환을 형성하지 않는 정점을 추가해야 합니다.</li>
<li><code>과정 2</code>를 신장 트리 조건에 만족할 때 까지 반복합니다.</li>
</ol>
</blockquote>
<p>프림 알고리즘의 과정을 시각화 해보겠습니다.</p>
<p><strong>프림 과정 시각화</strong></p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/0d5fcf74-5a60-42cd-938f-a421858efbc5/image.png" alt=""></p>
<p>정점 2번을 임의로 선택한 후, MST 목록에 추가했습니다. 그 다음 MST에 있는 간선들 중 가장 작은 가중치가 있는 간선으로 이동합니다. 파란색과 빨간색 간선은 MST에 연결된 간선입니다. 그 중 빨간선은 MST의 간선으로 연결된 간선입니다. 즉, 가중치가 최소인 간선입니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/eaa1f5fe-c4cd-486d-b024-d4d7ca8707c8/image.png" alt=""></p>
<p>MST가 1, 2번 정점으로 확장되었습니다. MST의 간선의 가중치는 2, 3, 4, 5, 6이 있습니다. 그 중 2번은 이미 연결되어 있기 때문에, 그 다음 최소 가중치인 3인 간선이 연결됩니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/22c7cb3f-5654-448c-bfa9-219e1efc788f/image.png" alt=""></p>
<p>MST의 정점이 1, 2, 3으로 늘어났으므로 이에 따른 간선도 추가해줍니다. 그리고 가중치가 가장 낮으면서 사이클을 만들지 않는 간선을 선택합니다. 이제 그림만 나열하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/bc674b0f-6c73-4e61-93ef-8c02c7fd7ab0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/37975950-8784-46df-b681-d1f6ceadea21/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/e15b7eb8-74ab-4775-ba48-32c934061e44/image.png" alt=""></p>
<p>이렇게 MST가 완성되었습니다. 부분 MST의 간선을 확장할 땐 반드시 이미 포함된 간선은 아닌지 확인한 후 확장해야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LCS의 개념과 LCS 알고리즘 소개]]></title>
            <link>https://velog.io/@yeo___li/LCS%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-LCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@yeo___li/LCS%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-LCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Thu, 27 Feb 2025 08:58:54 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#1-%EC%B5%9C%EC%9E%A5-%EA%B3%B5%ED%86%B5-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4lcs%EC%9D%B4%EB%9E%80">최장 공통 부분 수열(LCS)이란?</a></li>
<li><a href="#2-lcs-%EA%B8%B8%EC%9D%B4-%EA%B5%AC%ED%95%98%EA%B8%B0">LCS 길이 구하기</a></li>
<li><a href="#3-lcs-%EA%B5%AC%ED%95%98%EA%B8%B0">LCS 구하기</a></li>
<li><a href="#4-%EA%B5%AC%ED%98%84-%EC%BD%94%EB%93%9C">구현 코드</a></li>
<li><a href="#5-%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ol>
<h2 id="1-최장-공통-부분-수열lcs이란">1. 최장 공통 부분 수열(LCS)이란?</h2>
<hr>
<p>최장 공통 부분 수열(Longest common subsequence, 이하 LCS)은 <strong>수열 A, B가 주어졌을 때, A, B에서 공통적으로 발견될 수 있는 가장 긴 수열</strong>을 의미합니다. 이때 수열은 &#39;특정 순서로 숫자를 나열한 것&#39;을 의미합니다. 따라서 대부분 LCS 문제들은 문자열로 수열을 입력을 받습니다.</p>
<p>LCS의 간단한 예시를 드리겠습니다.</p>
<blockquote>
<p><strong>예시 1)</strong>
수열 A: A<strong>C</strong>
수열 B: <strong>C</strong></p>
</blockquote>
<p>수열 A, B의 LCS는 <strong>C</strong> 입니다. A와 B 모두 <strong>C</strong>를 가지고 있기 때문입니다. 여기서 알 수 있는건 <strong>수열 A, B의 길이는 서로 달라도 된다</strong>는걸 알 수 있습니다.</p>
<p>그 다음 예시를 알아보겠습니다.</p>
<blockquote>
<p><strong>예시 2)</strong>
수열 A: A<strong>BC</strong>
수열 B: <strong>B</strong>A<strong>C</strong></p>
</blockquote>
<p>수열 A, B의 LCS는 <strong>BC</strong> 입니다. 이처럼 <strong>중간에 다른 수가 있더라도 순서만 지켜진다면 LCS</strong>라고 부를 수 있습니다.</p>
<p>마지막 예시입니다.</p>
<blockquote>
<p><strong>예시 3)</strong>
수열 A: <strong>AC</strong>DB<strong>E</strong>
수열 B: <strong>A</strong>B<strong>C</strong>D<strong>E</strong></p>
</blockquote>
<blockquote>
<p><strong>예시 3)</strong>
수열 A: <strong>A</strong>CD<strong>BE</strong>
수열 B: <strong>AB</strong>CD<strong>E</strong></p>
</blockquote>
<p>수열 A, B의 LCS는 <strong>ACE</strong> 이거나 <strong>ABE</strong>일 수 있습니다. 이처럼 <strong>LCS의 값은 하나가 아닐 수도</strong> 있습니다.</p>
<p>위의 세 예시를 곱씹다보면 LCS의 정의와 고려해야 할 점에 대해 알 수 있습니다.</p>
<h2 id="2-lcs-길이-구하기">2. LCS 길이 구하기</h2>
<hr>
<p>이제 본격적으로 LCS의 길이를 구해보겠습니다. 구하기에 앞서 길이를 구해야 하는 이유는 문제를 풀기 위해서... 입니다..^^ 이거 풀면 티어 많이 오릅니다. 다들 파이텡</p>
<p>사실 LCS는 유전자 서열 분석, 버전 관리 시스템, 텍스트 유사도 검사 등 다양한 분야에서 사용되고 있으니 알고 계시면 좋습니다.</p>
<p>LCS는 기본적으로 DP를 이용하여 해결할 수 있습니다.  <a href="https://www.acmicpc.net/problem/9252"><strong>백준 9252번 LCS 2</strong></a> 문제를 고민하고 오시면 더 와닿으실 것 같습니다.</p>
<p>LCS의 길이를 구하기 앞서, 임의의 메서드를 정의하겠습니다.</p>
<blockquote>
<ul>
<li><strong>LCS(i, j)</strong> = A[1 ... i]와 B[1 ... j]의 LCS의 길이</li>
</ul>
</blockquote>
<p>LCS(i, j)의 값을 계산하기 위해 고려해야 할 점이 있습니다. 바로 <strong>A[i], B[j]의 값의 동일성 유무</strong>입니다.</p>
<p><strong>A[i]와 B[j]의 값이 같을 때</strong>는 단순히 A[1 ... i - 1], B[1 ... j - 1] 까지의 수열에 A[i]와 B[j]가 추가된 것이므로, <strong>LCS(i-1, j-1)에 1을 더해</strong>주면 됩니다.</p>
<p>이때 다른 LCS()를 고려하지 않는 이유는 A[i]와 B[j]보다 작은 수열 중 가장 큰 LCS()의 값은 LCS(i - 1, j - 1) 하나 밖에 없기 때문입니다. LCS(i - 1, j)나 LCS(i, j - 1)의 값을 고려하게 된다면 상황이 중복으로 카운트 되어 정확한 값이 나오지 않습니다. (제가 이렇게 해서 틀렸습니다..)</p>
<p> 다음은 <strong>A[i]와 B[j]의 값이 다를 때</strong>의 경우입니다. 이 경우에는 A[1 ... i]와 B[1 ... j - 1]의 LCS의 길이, A[1 ... i - 1]와 B[1 ... j]의 LCS의 길이 중 더 큰 값을 넣어야 합니다. </p>
<p> 이유는 A[i]와 B[j]의 값이 같지 않기 때문에, 다른 곳에서 LCS의 길이를 가져와야 하는데, 가져올 명분이 있는 LCS()는 A[i] 혹은 B[i]를 포함하고 있는 수열이어야 합니다. 따라서 이를 만족하는 LCS()는 <strong>LCS(i, j-1), LCS(i-1, j) 이므로 이 두 값 중 최대값을 LCS(i, j)에 넣으면 됩</strong>니다.</p>
<p>지금까지 설명한걸 간단히 정리해보겠습니다.</p>
<blockquote>
<p><strong>LCS의 길이 점화식</strong></p>
<ul>
<li>LCS(0, 0) = 0</li>
<li><strong>A[i] == B[j]</strong> → <code>LCS(i, j) = LCS(i-1, j-1) + 1</code></li>
<li><strong>A[i] != B[j]</strong> → <code>LCS(i, j) = max(LCS(i-1, j), LCS(i, j-1))</code></li>
</ul>
</blockquote>
<h2 id="3-lcs-구하기">3. LCS 구하기</h2>
<p>LCS의 길이를 구했지만 아직 LCS를 구하진 못했습니다. LCS를 구하는 방법은 LCS(i, j)의 결과를 역추적 하는 것입니다. <a href="https://www.acmicpc.net/problem/9252">백준 9252번</a>의 예제를 가지고 설명하겠습니다.</p>
<blockquote>
<p><strong>백준 9252 예제)</strong>
수열 A: ACAYKP
수열 B: CAPCAK</p>
</blockquote>
<p>아래는 예제의 LCS() 결과표입니다. <code>-</code> 표시는 수열의 길이가 0일때 입니다.
<img src="https://velog.velcdn.com/images/yeo___li/post/0f8d10ff-79d9-4453-b1c9-5c16736dc26f/image.png" alt=""></p>
<p>역추적은 LCS(A.length, B.length)부터 시작합니다. 역추적의 방식은 LCS의 길이를 구했던 경우처럼 A[i]와 B[j]의 동일성 유무로 나누어 추적할 수 있습니다.</p>
<p>A[i]와 B[j]의 값이 같다면 A[i]의 값을 저장하고, LCS(i-1, j-1)로 이동하면 됩니다.</p>
<p>A[i]와 B[j]의 값이 다르다면 LCS(i - 1, j)와 LCS(i, j - 1) 중 더 큰 값으로 이동하면 됩니다. 만약 두 값이 같다면 아무데나 가도 괜찮습니다.</p>
<p>LCS의 길이 점화식을 역으로 추적하는 것이기 때문에 복잡하게 생각하지 않으셔도 됩니다. </p>
<p>아래는 역추적의 경로입니다.</p>
<p><strong>초록색은 A[i] != B[j]</strong> 일때, <strong>빨간색은 A[i] == B[j]</strong> 일때 입니다. 
그리고 <strong>회색은 초록색일때(A[i] != B[j] 일때) 더 작거나 선택받지 못한 값</strong>입니다. </p>
<p>역추적의 종료 조건은 <code>i</code> 또는 <code>j</code>가 0이 되었을 때 입니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/7d3eec06-1ed2-410f-9c14-c0f1feb51831/image.png" alt=""></p>
<p>지금까지 설명한 걸 간단히 정리해보겠습니다.</p>
<blockquote>
<p><strong>LCS 역추적 점화식</strong></p>
<ul>
<li><strong>시작 위치</strong>: <code>lcs = &quot;&quot;(빈 문자열), LCS(A.length, B.length)</code></li>
<li><strong>종료 조건</strong>: <code>i &lt;= 0 || j &lt;= 0</code></li>
<li><strong>A[i] == B[j]</strong> → <code>lcs = A[i](B[j]여도 됨) + lcs, LCS(i-1, j-1)로 이동</code></li>
<li><strong>A[i] != B[j]</strong> → <code>max(LCS(i-1, j), LCS(i, j-1))로 이동</code></li>
</ul>
</blockquote>
<h2 id="4-구현-코드">4. 구현 코드</h2>
<hr>
<ul>
<li><p>LCS(i, j)의 값을 저장하는 2차원 배열</p>
<pre><code class="language-java">int[] LCS = new int[A.length + 1][B.length + 1];</code></pre>
</li>
<li><p>LCS 길이 구하기 코드</p>
<pre><code class="language-java">for (i = 1; i &lt;= A.length; i++) {
  for (j = 1; j &lt;= B.length; j++) {
      if (A[i - 1] == B[j - 1]) {
          LCS[i][j] = LCS[i - 1][j - 1] + 1;
      } else {
          LCS[i][j] += Math.max(LCS[i - 1][j], LCS[i][j - 1]);
      }
  }
}</code></pre>
</li>
<li><p>LCS 구하기 코드</p>
<pre><code class="language-java">String lcs = &quot;&quot;;
i = A.length;
j = B.length;
while (i &gt; 0 &amp;&amp; j &gt; 0) {
  if (A[i - 1] == B[j - 1]) {
      lcs = A[i - 1] + lcs;
      i--;
      j--;
  } else {
      if (LCS[i - 1][j] &gt; LCS[i][j - 1]) {
          i--;
      } else {
          j--;
      }
  }
}</code></pre>
</li>
</ul>
<h2 id="5-마무리">5. 마무리</h2>
<p>솔직히 DP 진짜 어렵다. 원래 예시를 들면서 자연스럽게 왜 DP를 써야하는지와 점화식 도출 과정을 설명하려고 했는데 처참하게 실패했다.(누가 나한테 해줬으면..) DP 문제는 동적 계획법을 통해 문제 해결 방법을 떠올리기 어렵다. 따라서 점화식 <strong>도출 과정</strong>을 이해하려고 노력하기 보다, <strong>점화식</strong>을 이해하려고 노력하는게 더 좋은 접근일 것 같다. </p>
<h1 id="감사합니다☺️">감사합니다☺️</h1>
<hr>
<blockquote>
<p><strong>참조</strong></p>
<ul>
<li>[도서] 코딩 테스트 완전 정복</li>
<li>[문제] <a href="https://www.acmicpc.net/problem/9252">https://www.acmicpc.net/problem/9252</a></li>
<li>[사이트] <a href="https://mojing.tistory.com/entry/Algorithm-Longest-Common-SubsequenceLCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B0%84-%EC%B5%9C%EC%9E%A5-%EA%B3%B5%ED%86%B5-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%EC%B0%BE%EA%B8%B0">https://mojing.tistory.com/entry/Algorithm-Longest-Common-SubsequenceLCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B0%84-%EC%B5%9C%EC%9E%A5-%EA%B3%B5%ED%86%B5-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%EC%B0%BE%EA%B8%B0</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[SWOT 분석 기법]]></title>
            <link>https://velog.io/@yeo___li/SWOT-%EB%B6%84%EC%84%9D-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@yeo___li/SWOT-%EB%B6%84%EC%84%9D-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Wed, 26 Feb 2025 00:29:45 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#1-swot-%EB%B6%84%EC%84%9D">SWOT 분석</a></li>
<li><a href="#2-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0">사용 이유</a></li>
<li><a href="#3-%EC%82%AC%EC%9A%A9-%EC%8B%9C%EA%B8%B0">사용 시기</a></li>
<li><a href="#4-%EC%9A%94%EC%86%8C%EB%B3%84-%EC%9D%98%EB%AF%B8">요소별 의미</a></li>
<li><a href="#5-%EC%82%AC%EC%9A%A9-%EC%98%88%EC%8B%9C">사용 예시</a>
   - <a href="#%EA%B8%B0%EC%97%85-%EC%98%88%EC%8B%9C">기업 예시</a>
   - <a href="#%EA%B0%9C%EC%9D%B8-%EC%98%88%EC%8B%9C">개인 예시</a></li>
<li><a href="#6-swot%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%A0%84%EB%9E%B5">SWOT를 사용한 문제 해결 전략</a>
   - <a href="#so-%EC%A0%84%EB%9E%B5strengths---opportunities">SO 전략(Strengths - Opportunities)</a>
   - <a href="#st-%EC%A0%84%EB%9E%B5strengths---threats">ST 전략(Strengths - Threats)</a>
   - <a href="#wo-%EC%A0%84%EB%9E%B5weaknesses---opportunities">WO 전략(Weaknesses - Opportunities)</a>
   - <a href="#wt-%EC%A0%84%EB%9E%B5weaknesses---threats">WT 전략(Weaknesses - Threats)</a><h2 id="1-swot-분석">1. SWOT 분석?</h2>
</li>
</ol>
<hr>
<p>SWOT는 강점(Strength), 약점(Weakness), 기회(Opportunity), 위협(Threat)을 식별하기 위해 사용하는 분석 기법 입니다.</p>
<h2 id="2-사용-이유">2. 사용 이유</h2>
<hr>
<p>SWOT 분석을 사용하는 이유는 <strong>간단하면서도 개선을 위해 경쟁 기회를 식별하는데 도움</strong>이 되는 강력한 도구이기 때문입니다.</p>
<h2 id="3-사용-시기">3. 사용 시기</h2>
<hr>
<ol>
<li><p><strong>전략적 계획 수립</strong></p>
<ul>
<li>기업이나 개인이 목표를 설정할 때, SWOT 분석을 통해 현재의 위치를 정확히 파악할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>문제 해결</strong></p>
<ul>
<li>어떤 문제를 해결하려고 할 때, 문제의 원인과 해결 방향을 파악할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>개인 경력 개발</strong></p>
<ul>
<li>개인이 직업이나 경력을 발전시키고자 할 때, 자신의 강점과 약점을 파악하고 시장의 기회와 위협을 고려해서 커리어 목표를 설정할 수 있습니다.</li>
</ul>
</li>
</ol>
<h2 id="4-요소별-의미">4. 요소별 의미</h2>
<hr>
<h3 id="strength강점">Strength(강점)</h3>
<ul>
<li>내부적 요소로, <strong>조직이나 개인이 잘하고 있는 점과 경쟁 우위 등을 의미</strong>합니다.<h3 id="weakness약점">Weakness(약점)</h3>
</li>
<li>내부적 요소로, <strong>조직이나 개인이 개선해야 하는 점과 부족한 부분을 의미</strong>합니다.<h3 id="기회opportunity">기회(Opportunity)</h3>
</li>
<li>외부적 요소로, <strong>향후 긍정적인 영향을 줄 수 있는 기회를 의미</strong>합니다.<h3 id="위협threat">위협(Threat)</h3>
</li>
<li>외부적 요소로, <strong>위험하거나 불리한 상황을 의미</strong>합니다.</li>
</ul>
<h2 id="5-사용-예시">5. 사용 예시</h2>
<hr>
<h3 id="기업-예시">기업 예시</h3>
<p>스마트 홈 제품 개발 스타트업 SWOT 분석</p>
<ul>
<li><strong>강점</strong>: 혁신적인 기술, 팀 내 전문가들이 많다.</li>
<li><strong>약점</strong>: 마케팅 부족, 브랜드 인지도가 낮다.</li>
<li><strong>기회</strong>: 스마트 홈 시장의 급성장, 환경 친화적인 제품에 대한 수요 증가.</li>
<li><strong>위협</strong>: 경쟁사의 가격 인하, 기술 표준화 문제.</li>
<li><strong>분석결과</strong>: 이 정보를 바탕으로 스타트업은 마케팅을 강화하고, 환경 친화적인 제품을 강조하며 경쟁사의 가격 인하에 대응하기 위한 전략을 세운다.</li>
</ul>
<h3 id="개인-예시">개인 예시</h3>
<p>개발자 경력 개발 고민</p>
<ul>
<li><strong>강점</strong>: 문제 해결 능력, 코드 작성 속도가 빠르다</li>
<li><strong>약점</strong>: 발표 능력 부족, 최신 기술에 대한 지식이 부족하다</li>
<li><strong>기회</strong>: 인공지능 분야가 성장하고 있으며, 해당 분야에 관련된 교육이 많다</li>
<li><strong>위협</strong>: 많은 개발자가 AI 분야로 진출하고 경쟁이 치열하다</li>
<li><strong>해결방안</strong>: 발표 능력을 개선하고 최신 AI 기술을 배우기 위한 교육을 듣는다. </li>
</ul>
<h2 id="6-swot를-사용한-문제-해결-전략">6. SWOT를 사용한 문제 해결 전략</h2>
<hr>
<p>SWOT 분석은 각 항목을 조합해서 문제를 해결할 수 있는 전략을 도출할 수 있습니다. 조합 방식은 내부, 외부적 요인을 조합하여 SO, WO, ST, WT 전략으로 나눌 수 있습니다.</p>
<h3 id="so-전략strengths---opportunities">SO 전략(Strengths - Opportunities)</h3>
<ul>
<li><strong>기회를 최대화 하는 전략</strong>
강점과 기회를 결합한 전략으로, <strong>현재의 강점을 활용해서 외부 기회를 최대한 잘 활용하는 방법</strong>입니다. 즉, 자신이 잘하는 것을 기반으로 외부에서 찾아낸 유리한 기회를 포착하는 전략입니다.</li>
</ul>
<h3 id="st-전략strengths---threats">ST 전략(Strengths - Threats)</h3>
<ul>
<li><strong>위협을 최소화 하는 전략</strong>
강점과 위협을 결합한 전략으로, <strong>자신의 강점을 이용해 외부 위협에 대응하는 방법</strong>입니다. 즉, 자신이 가진 장점을 이용해 위협 요소에 미리 대비하거나 극복하는 전략입니다.</li>
</ul>
<h3 id="wo-전략weaknesses---opportunities">WO 전략(Weaknesses - Opportunities)</h3>
<ul>
<li><strong>약점을 보완하는 전략</strong>
약점과 기회를 결합한 전략으로, <strong>자신의 약점을 보완하면서 외부 기회를 활용하는 방법</strong>입니다. 즉, 약점을 보완하거나 개선할 수 있는 기회를 찾아 활용하는 전략입니다.</li>
</ul>
<h3 id="wt-전략weaknesses---threats">WT 전략(Weaknesses - Threats)</h3>
<ul>
<li><strong>위협과 약점 모두 해결하는데 중점을 두는 전략</strong>
약점과 위협을 결합한 전략으로, <strong>자신의 약점과 외부 위협을 동시에 해결하기 위한 방안을 찾는 방법</strong>입니다. 즉, 약점을 해결하거나 최소화 하는 방법을 찾아 위협에 대응하는 전략입니다.</li>
</ul>
<hr>
<blockquote>
<p><strong>참조</strong>
<a href="https://asana.com/ko/resources/swot-analysis">https://asana.com/ko/resources/swot-analysis</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료 구조]]></title>
            <link>https://velog.io/@yeo___li/5.-%EC%9E%90%EB%A3%8C-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@yeo___li/5.-%EC%9E%90%EB%A3%8C-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 15 Feb 2025 15:55:29 GMT</pubDate>
            <description><![CDATA[<p>자료구조(data structrue)는 효율적으로 데이터를 관리하고 수정, 삭제, 탐색, 저장할 수 있는 데이터 집합을 이야기함.</p>
<h2 id="51-복잡도">5.1 복잡도</h2>
<hr>
<p>복잡도는 <strong>시간 복잡도</strong>와 <strong>공간 복잡도</strong>로 나뉨.</p>
<h3 id="511-시간-복잡도">5.1.1 시간 복잡도</h3>
<p>시간 복잡도란 <strong>문제를 해결하는 데 걸리는 시간과 입력의 함수 관계</strong>를 의미함.
시간 복잡도의 존재 이유는 효율적인 코드로 개선하는 데 쓰이는 척도가 되기 때문임.</p>
<h4 id="빅오-표기법">빅오 표기법</h4>
<p>&#39;가장 많이 영향을 많이 끼치는&#39; 항의 상수 인자를 빼고 나머지 항을 없앤 것임.</p>
<h3 id="512-공간-복잡도">5.1.2 공간 복잡도</h3>
<p>공간 복잡도란 프로그램을 실행시켰을 때 필요로 하는 자원 공간의 양을 말함.
정적 변수 뿐 만 아니라 동적으로 재귀적인 함수로 인해 공간을 계속 해서 필요오 할 경우도 포함임.</p>
<h3 id="513-자료-구조에서의-시간-복잡도">5.1.3 자료 구조에서의 시간 복잡도</h3>
<h4 id="자료구조-평균-시간-복잡도">자료구조 평균 시간 복잡도</h4>
<table>
<thead>
<tr>
<th>자료구조</th>
<th>접근</th>
<th>탐색</th>
<th>삽입</th>
<th>삭제</th>
</tr>
</thead>
<tbody><tr>
<td>배열(array)</td>
<td>O(1)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>스택(stack)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>큐(queue)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>이중 연결 리스트(doubly linked list)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>해시 테이블(hash table)</td>
<td>O(1)</td>
<td>O(1)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>이진 탐색 트리(BST)</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
</tr>
<tr>
<td>AVL 트리</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
</tr>
<tr>
<td>레드 블랙 트리</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
</tr>
</tbody></table>
<h4 id="자료구조-최악-시간-복잡도">자료구조 최악 시간 복잡도</h4>
<table>
<thead>
<tr>
<th>자료구조</th>
<th>접근</th>
<th>탐색</th>
<th>삽입</th>
<th>삭제</th>
</tr>
</thead>
<tbody><tr>
<td>배열(array)</td>
<td>O(1)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>스택(stack)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>큐(queue)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>이중 연결 리스트(doubly linked list)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>해시 테이블(hash table)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>이진 탐색 트리(BST)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>AVL 트리</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
</tr>
<tr>
<td>레드 블랙 트리</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
<td>O(logn)</td>
</tr>
</tbody></table>
<h2 id="52-선형-자료-구조">5.2 선형 자료 구조</h2>
<hr>
<p>선형 자료 구조란 요소가 일렬로 나열되어 있는 자료구조를 의미함.</p>
<h3 id="521-연결-리스트">5.2.1 연결 리스트</h3>
<p>연결리스트는 데이터를 감싼 노드를 포인터로 연결해서 공간적인 효율성을 극대화 시킨 자료구조임.
삽입과 삭제가 O(1)이 걸리며 탐색에는 O(n)이 걸림.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/5db65e3b-de33-4b7f-8837-f125ea58ec90/image.png" alt=""></p>
<p>prev 포인터와 next포인터로 앞과 뒤의 노드를 연결시킨 것이 연결리스트임.
연결리스트는 싱글 연결 리스트, 이중 연결 리스트, 원형 이중 연결리스트가 있음.</p>
<ul>
<li>싱글 연결 리스트: next 포인터만 가짐</li>
<li>이중 연결 리스트: next 포인터와 prev 포인터를 가짐</li>
<li>원형 이중 연결 리스트: 이중 연결 리스트와 같지만 마지막 노드의 next 포인터가 헤드 노드를 가리키는 것을 말함.</li>
</ul>
<h3 id="522-배열">5.2.2 배열</h3>
<p>배열은 같은 타입의 변수들로 이루어져 있고, 크기가 정해져 있으며, 인접한 메모리 위치에 있는 데이터를 모아놓은 집합임. 중복을 허용하고 순서가 있음.</p>
<p>탐색에 O(1)이 걸리기 때문에 랜덤 접근(random access)가 가능함.</p>
<h4 id="랜덤-접근과-순차적-접근">랜덤 접근과 순차적 접근</h4>
<p>랜덤 접근은 동일한 시간에 배열과 같은 순차적인 데이터가 있을 때 임의의 인덱스에 해당하는 데이터에 접근할 수 있는 기능임. 이를 직접 접근이라고도 함. 이는 데이터를 저장된 순서대로 검색해야 하는 순차적 접근과는 반대임.</p>
<h4 id="배열과-연결리스트-비교">배열과 연결리스트 비교</h4>
<ul>
<li>배열은 랜덤 접근이 가능하고, 연결 리스트는 불가능함.</li>
<li>배열은 데이터 추가가 느리지만, 연결리스트는 빠름.</li>
</ul>
<h3 id="523-벡터">5.2.3 벡터</h3>
<p>벡터(vertor)는 동적으로 요소를 할당할 수 있는 동적 배열임. 컴파일 시점에 개수를 모른다면 벡터를 써야함. 중복을 허용하고 순서가 있고 랜덤 접근이 가능함. 탐색과 맨 뒤의 요소를 삭제하거나 삽입하는 데 O(1)이 걸리며, 맨 뒤나 맨 앞이 아닌 요소를 삭제하고 삽입하는 데 O(n)의 시간이 걸림.</p>
<p>벡터는 push_back()을 한다고 해서 매번 크기가 증가하는 것이 아니라, 2의 제곱승 + 1 마다 크기를 2배로 늘림.</p>
<p>메서드는 push_back(), pop_back(), erase(), find(), clear() 가 있음.</p>
<h3 id="524-스택">5.2.4 스택</h3>
<p>스택은 가장 마지막으로 들어간 데이터가 가장 첫 번째로 나오는 성질(LIFO, Last In First Out)을 가진 자료 구조임. 재귀적인 함수, 알고리즘에 사용되며 웹 브라우저 방문 기록 등에 쓰임. 삽입 및 삭제에 O(1), 탐색에 O(n)이 소요됨.</p>
<h3 id="525-큐">5.2.5 큐</h3>
<p>큐(queue)는 먼저 집어넣은 데이터가 먼저 나오는 성질(FIFO, First In First Out)을 지닌 자료 구조이며, 나중에 집어넣은 데이터가 먼저 나오는 스택과는 반대되는 개념을 가짐. 삽입 및 삭제에 O(1), 탐색에 O(n)이 걸림. CPU 작업을 기다리는 프로세스, 스레드 행렬 또는 네트워크 접속을 기다리는 행렬, 너비 우선 탐색, 캐시 등에 사용됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영체제]]></title>
            <link>https://velog.io/@yeo___li/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C</link>
            <guid>https://velog.io/@yeo___li/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C</guid>
            <pubDate>Sun, 02 Feb 2025 03:27:16 GMT</pubDate>
            <description><![CDATA[<p>운영체제(OS, Operating System)는 사용자가 컴퓨터를 쉽게 다루게 해주는 인터페이스임. 참고로 운영체제와 유사하지만 소프트웨어를 추가로 설치할 수 없는 것을 펌웨어(firmware)라고 함.</p>
<hr>
<h2 id="1-운영체제와-컴퓨터">1. 운영체제와 컴퓨터</h2>
<h3 id="311-운영체제의-역할과-구조">3.1.1 운영체제의 역할과 구조</h3>
<h4 id="운영체제의-역할">운영체제의 역할</h4>
<ol>
<li><strong>CPU 스케줄링과 프로세스 관리</strong> : CPU 소유권을 어떤 프로세스로 할당할지, 프로세스의 생성과 삭제, 자원 할당 및 반환을 관리</li>
<li><strong>메모리 관리</strong> : 한정된 메모리를 어떤 프로세스에 얼마큼 할당해야 하는지 관리</li>
<li><strong>디스크 파일 관리</strong> : 디스크 파일을 어떠한 방법으로 보관할지 관리</li>
<li><strong>I/O 디바이스 관리</strong> : I/O 디바이스들인 마우스, 키보드와 컴퓨터 간에 데이터를 주고받는 것을 관리</li>
</ol>
<h4 id="운영체제의-구조">운영체제의 구조</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/91952b42-9fa9-4923-a32d-dfe72a6e20ea/image.png" alt=""></p>
<p>이때 GUI 가 없고 CUI만 있는 리눅스 서버도 있음.</p>
<h5 id="gui">GUI</h5>
<p>사용자가 전자장치와 상호 작용할 수 있도록 하는 사용자 인터페이스의 한 형태, 단순 명령어 창이 아닌 아이콘을 마우스로 클릭하는 단순한 동작으로 컴퓨터와 상호 작용할 수 있도록 해줌.</p>
<blockquote>
<p>드라이버?
하드웨어를 제어하기 위한 소프트웨어
CUI?
그래픽이 아닌 명령어로 처리하는 인터페이스</p>
</blockquote>
<h5 id="시스템콜">시스템콜</h5>
<p>시스템콜이란 운영체제가 커널에 접근하기 위한 인터페이스. 유저 프로그램이 운영체제의 서비스를 받기 위해 커널 함수를 호출할 때 사용함.
유저 프로그램이 I/O 요청으로 트랩(trap)을 발동하면 올바른 I/O 요청인지 확인한 후 유저 모드가 시스템콜을 통해 커널모드로 변환되어 실행됨.
유저모드에서 파일을 읽지 않고 커널 모드로 변환되어 파일을 읽고 다시 유저 모드로 돌아가기 때문에, 컴퓨터 자원에 대한 직접 접근을 차단할 수 있고 프로그램을 다른 프로그램으로부터 보호할 수 있음.</p>
<blockquote>
<p>I/O 요청
입출력 함수, 데이터베이스, 네트워크, 파일 접근 등에 관한 일
드라이버</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/afb26c42-f1f7-4faf-b054-d1292a7557b9/image.png" alt=""></p>
<p>위의 그림처럼 프로세스나 스레드에서 운영체제로 어떠한 요청을 할 때 시스템콜이라는 인터페이스와 커널을 거쳐 운영체제에 전달됨.
이 시스템콜은 하나의 추상화 계층임. 따라서 이를 통해 네트워크 통신이나 데이터베이스와 같은 낮은 단계의 영역 처리에 대한 부분을 많이 신경 쓰지 않고 프로그램을 구현할 수 있음.</p>
<blockquote>
<p>modebit
시스템콜이 작동될 때 modebit을 참고해서 유저 모드와 커널 모드를 구분함. modebit은 1, 0의 값을 가지는 플래그 변수임. 카메라, 키보드 등 I/O 디바이스는 운영체제를 통해서만 작동해야함. 그렇지 않으면 사용하자 의도하지 않은 동작이 컴퓨터에서 수행될 수 있음. modebit의 0은 커널 모드, 1은 유저모드임. 유저모드일 경우에는 시스템콜을 못하게 막아서 한정된 일만 할 수 있게함.</p>
</blockquote>
<blockquote>
<p>용어
커널
운영체제의 핵심 부분이자 시스템콜 인터페이스를 제공하며 보안, 메모리, 프로세스, 파일 시스템, I/O 디바이스, I/O 요청 관리 등 운영체제의 중추적인 역할을 함</p>
</blockquote>
<h3 id="312-컴퓨터의-요소">3.1.2 컴퓨터의 요소</h3>
<p>컴퓨터는 CPU, DMA 컨트롤러, 메모리, 타이머, 디바이스 컨트롤러 등으로 이루어져 있음.</p>
<h4 id="cpucentral-processing-unit">CPU(Central Processing Unit)</h4>
<p>산술논리연산장치, 제어장치, 레지스터로 구성되어 있는 컴퓨터 장치를 말하며, 인터럽드에 의해 단순히 메모리에 존재하는 명령어를 해석해서 실행하는 일꾼임. 관리자 역할을 하는 운영체제의 커널이 프로그램을 메모리에 올려 프로세스로 만들면 일꾼인 CPU가 이를 처리함.</p>
<h5 id="제어장치cu-control-unit">제어장치(CU, Control Unit)</h5>
<p>프로세스 조작을 지시하는 CPU의 한 부품임. 입출력장치 간 통신을 제어하고 명령어들을 읽고 해석하며 데이터 처리를 위한 순서를 결정함.</p>
<h5 id="레지스터">레지스터</h5>
<p>CPU 안에 있는 매우 빠른 임시기억장치를 가르킴. CPU와 직접 연결되어 있으므로 연산 속도가 메모리보다 수십 배에서 수백 배까지 빠름. 레지스터를 거쳐 데이터를 전달함.</p>
<h5 id="산술논리연산장치alu-arithmetic-logic-unit">산술논리연산장치(ALU, Arithmetic Logic Unit)</h5>
<p>덧셈, 뺄셈 같은 두 숫자의 산술 연산과 배타적 논리합, 논리곱 같은 논리 연산을 계산하는 디지털 회로임.</p>
<h4 id="cpu의-연산-처리">CPU의 연산 처리</h4>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/12d9c440-8706-4ba4-a5d7-9b03f5ba66fb/image.png" alt=""></p>
<ol>
<li>제어장치가 메모리에 계산할 값을 로드함. 또한 레지스터에도 로드함.</li>
<li>제어장치가 레지스터에 있는 값을 계산하라고 산술논리연산장치에 명령함.</li>
<li>제어장치가 계산된 값을 다시 &#39;레지스터에서 메모리로&#39; 계산한 값을 저장함.</li>
</ol>
<h4 id="인터럽트">인터럽트</h4>
<p>인터럽트는 어떤 신호가 들어왔을 때 CPU를 잠깐 정지시키는 것을 말함. 키보드, 마우스 등 I/O 디바이스로 인한 인터럽트, 0으로 숫자를 나누는 산술 연산에서의 인터럽트, 프로세스 오류 등으로 발생함.</p>
<p>인터럽트가 발생되면 핸들러 함수가 모여 있는 인터럽트 벡터로 가서 인터럽트 핸들러 함수가 실행됨. 인터럽트 간에는 우선순위가 있고 우선순위에 따라 실행되며 인터럽트는 하드웨어 인터럽트, 소프트웨어 인터럽트 두 가지로 나뉨</p>
<h5 id="하드웨어-인터럽트">하드웨어 인터럽트</h5>
<p>키보드를 연결하거나 마우스를 연결하는 일 등의 IO 디바이스에서 발생하는 인터럽트를 말함.</p>
<h5 id="소프트웨어-인터럽트">소프트웨어 인터럽트</h5>
<p>trap(트랩)이라고도 함. 프로세스 오류 등으로 프로세스가 시스템콜을 호출할 때 발동함.</p>
<h4 id="dma-컨트롤러">DMA 컨트롤러</h4>
<p>I/O 디바이스가 메모리에 직접 접근할 수 있도록 하는 하드웨어 장치를 뜻함. CPU에만 너무 많은 인터럽트 요청이 들어오기 때문에 CPU 부하를 아주며 CPU의 일을 보조하는 일꾼임. </p>
<h5 id="메모리memory">메모리(memory)</h5>
<p>전자회로에서 데이터나 상태, 명령어 등을 기록하는 장치를 말함. 보통 RAM(Random Access Memory)을 일컬어 메모리라고 함. 이름과 같이 기억을 담당함. CPU가 일꾼이라면, 메모리는 작업장. 메모리가 클수록 더 많은 일을 빠르게 할 수 있음.</p>
<h4 id="타이머timer">타이머(timer)</h4>
<p>몇 초 안에는 작업이 끝나야 한다는 것을 정하고 특정 프로그램에 시간 제한을 다는 역할을 함. 시간이 많이 걸리는 프로그램이 작동할 때 제한을 걸기 위해 존재함.</p>
<h4 id="디바이스-컨트롤러divice-controller">디바이스 컨트롤러(divice controller)</h4>
<p>컴퓨터와 연결되어 있는 IO 디바이스들의 작은 CPU를 말함.</p>
<hr>
<h2 id="2-메모리">2. 메모리</h2>
<h3 id="321-메모리-계층">3.2.1 메모리 계층</h3>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/a37ed137-cd08-4c31-a79b-02adf09d9d61/image.png" alt=""></p>
<p>메모리 계층은 레지스터, 캐시, 메모리, 저장장치로 구성되어 있음. 
이런 계층이 있는 이유는 경제성과 캐시 때문임.</p>
<ul>
<li>레지스터: CPU 안에 있는 작은 메모리, 휘발성, 속도 가장 빠름, 기억 용량이 가장 적음.</li>
<li>캐시: L1, L2 캐시를 지칭함. 휘발성, 속도 빠름, 기억 용량이 적음. 참고로 L3 캐시도 있음.</li>
<li>주기억장치: RAM. 휘발성, 속도 보통, 기억 용량이 보통임.</li>
<li>보조기억장치: HDD, SDD를 일컬으며 휘발성, 속도 낮음, 기억 용량이 많음.</li>
</ul>
<h4 id="캐시cache">캐시(cache)</h4>
<p>데이터를 미리 복사해 놓는 임시 저장소이자 빠른 장치와 느린 장치에서 속도 차이에 따른 병목 현상을 줄이기 위한 메모리를 의미함. 이를 통해 데이터를 접근하는 시간이 오래 걸리는 경우를 해결하고 무언가를 다시 계산하는 시간을 절약할 수 있음.
실제로 메모리와 CPU 속도 차이가 너무 크기 때문에 그 중간에 레지스터 계층을 둬서 속도 차이를 해결함. 이렇게 속도 차이를 해결하기 위해 계층과 계층 사이에 있는 계층을 캐싱 계층이라고 함.</p>
<h4 id="지역성의-원리">지역성의 원리</h4>
<p>캐시를 직접 설정할땐 자주 사용하는 데이터를 기반으로 설정해야함. 그리고 그 근거가 바로 지역성임.
지역성은 시간 지역성, 공간 지역성으로 나뉨.</p>
<h5 id="시간-지역성">시간 지역성</h5>
<p>최근에 사용한 데이터에 다시 접근하려는 특성을 말함.</p>
<h5 id="공간-지역성">공간 지역성</h5>
<p>최근 접근한 데이터를 이루고 있는 공간이나 그 가까운 공간에 접근하는 특성을 말함.</p>
<h4 id="캐시히트와-캐시미스">캐시히트와 캐시미스</h4>
<p>캐시에서 원하는 데이터를 찾았다면 캐시히트, 해당 데이터가 캐시에 없다면 주 메모리로 가서 데이터를 찾아오는 것을 캐시미스라고 함.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/8f3aaba2-5a28-471c-b227-dc65eb984fda/image.png" alt=""></p>
<h4 id="캐시매핑">캐시매핑</h4>
<p>캐시매핑이란 캐시가 히트되기 위해 매핑하는 방법을 말하며 CPU의 레지스터와 주 메모리(RAM) 간에 데이터를 주고 받을 때를 기반으로 설명함.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/55e7e3b0-7a94-4125-94d8-7fb0fa46509a/image.png" alt=""></p>
<h4 id="웹-브라우저의-캐시">웹 브라우저의 캐시</h4>
<p>쿠키, 로컬 스토리지, 세션 스토리지가 있음. 이들은 인증 모듈 관련 사항들을 웹 브라우저에 저장해서 추후 서버에 요청할 때 자신을 나타내는 아이덴테테나 중복 요청 방지를 위해 쓰임.</p>
<h5 id="쿠키">쿠키</h5>
<p>쿠키는 만료기한이 있는 키-값 저장소임. 최대 4KB까지 저장할 수 있고, 만료기간을 정할 수 있음.</p>
<h5 id="로컬-스토리지">로컬 스토리지</h5>
<p>만료기한이 없는 키-값 저장소임. 10MB까지 저장할 수 있고, 웹 브라우저를 닫아도 유지됨. 추가로 HTML5에서 지원함.</p>
<h5 id="세션-스토리지">세션 스토리지</h5>
<p>만료기한이 없는 키-값 저장소임. 탭단위로 세션 스토리지를 생성, 탭을 닫을 때 해당 데이트 삭제됨. 5MB 까지 저장 가능하며, HTML5에서 지원함.</p>
<h3 id="322-메모리-관리">3.2.2 메모리 관리</h3>
<h4 id="가상-메모리virtual-memory">가상 메모리(virtual memory)</h4>
<p>메모리 관리 기법의 하나로 컴퓨터가 실제로 이용 가능한 메모리 자원을 추상화 하여 이를 사용하는 사용자들에게 매우 큰 메모리로 보이게 만드는 것을 말함.</p>
<p>이때 가상적으로 주어진 주소를 가상 주소(logical address)라고 하며, 실제 메모리상에 있는 주소를 실제 주소(physical adress)라고 함. 가상 주소는 메모리관리 장치(MMU)에 의해 실제 주소로 변환됨. 이때 프로세스의 주소 정보가 들어있는 &#39;페이지 테이블&#39;로 관리하는데, 속도 향상을 위해 TLB를 사용함.</p>
<blockquote>
<p>TLB
메모리와 CPU 사이에 있는 주소 변환을 위한 캐시임.</p>
</blockquote>
<h4 id="스와핑">스와핑</h4>
<p>만약 가상메모리에는 존재하지만 실제 메모리인 RAM에는 현재 없는 데이터나 코드에 접근할 경우 페이지 폴트가 발생함. 이를 방지하기 위해 당장 사용하지 않는 영역을 하드디스크로 옮겨 필요할 때 다시 RAM으로 불러와 올리고, 사용하지 않으면 다시 하드디스크로 내림을 반복하여 RAM으로 불러와 올리고, 사용하지 않으면 다시 하드디스크로 내림을 반복하여 RAM을 효과적으로 관리하는 것임.</p>
<h4 id="페이지-폴트">페이지 폴트</h4>
<p>프로세스의 주소 공간에는 존재하지만 지금 이 컴퓨터의 RAM에는 없는 데이터에 접근했을 때 발생함.</p>
<p>페이지 폴트가 발생했을 때 단계</p>
<ol>
<li>CPU는 물리 메모리를 확인하여 해당 페이지가 없으면 트랩을 발생해서 운영 체제에 알립니다.</li>
<li>운영체제는 CPU의 동작을 잠시 멈춥니다.</li>
<li>운영체제는 페이지 테이블을 확인하여 가상 메모리에 페이지가 존재하는지 확 인하고, 없으면 프로세스를 중단하고 현재 물리 메모리에 비어 있는 프레임이 있는지 찾습니다. 물리 메모리에도 없다면 스와핑이 발동됩니다.</li>
<li>비어 있는 프레임에 해당 페이지를 로드하고, 페이지 테이블을 최신화합니다.</li>
<li>중단되었던 CPU를 다시 시작합니다.</li>
</ol>
<blockquote>
<p>페이지
가상 메모리를 사용하는 최소 크기 단위
프레임
실제 미모리를 사용하는 최소 크기 단위</p>
</blockquote>
<h4 id="스레싱thrashing">스레싱(thrashing)</h4>
<p>메모리의 페이지 폴트율이 높은 것을 의미함. 컴퓨터의 심각한 성능 저하를 초래함.이를 해결하기 위해선 메모리를 늘리거나 SDD바꾸는 방법이 있음. 운영체제에서 해결할 수 있는 방법은 작업세트와 PFF가 있음.</p>
<p>작업세트(working set)는 프로세스의 과거 사용 이력인 지역성(locality)을 통해 결정된 페이지 집합을 만들어서 미리 메모리에 로드하는 것임.</p>
<p>PFF(Page Fault Frequency)는 페이지 폴트 빈도를 조절하는 방법으로 상한선과 하한선을 만드는 방법임.</p>
<h4 id="메모리-할당">메모리 할당</h4>
<p>메모리에 프로그램을 할당할 때는 시작 메모리 위치, 메모리의 할당 크기를 기반으로 할당함. 이때 연속 할당과 불연속 할당으로 나뉨.</p>
<h5 id="연속-할당">연속 할당</h5>
<p>메모리에 연속적으로 공간을 할당하는 것. 
메모리를 미리 나누어 관리하는 고정 분할식 방식과 매 시점 프로그램의 크기에 맞게 메모리를 분할하여 사용하는 가변 분할 방식이 있음.</p>
<h5 id="고정-분할-방식fixed-partition-allocation">고정 분할 방식(fixed partition allocation)</h5>
<p>메모리를 미리 나누어 관리하는 방식. 내부 단편화가 발생함.</p>
<h5 id="가변-분할-방식variable-partition-allocation">가변 분할 방식(variable partition allocation)</h5>
<p>매 시점 프로그램의 크기에 맞게 동적으로 메모리를 나눠 사용함. 외부 단편화가 발생함.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/4bc87671-3ebb-4c25-b979-4ea7e2f6de2e/image.png" alt=""></p>
<blockquote>
<p>내부 단편화
메모리를 나눈 크기보다 프로그램이 작아서 들어가지 못하는 공간이 많이 발생하는 현상
외부 단편화
메모리를 나눈 크기부다 프로그램이 커서 들어가지 못하는 공간이 많이 발생하는 현상
홀
할당할 수 있는 비어있는 메모리 공간.</p>
</blockquote>
<h5 id="불연속-할당">불연속 할당</h5>
<p>메모리를 연속적으로 할당하지 않는 불연속 할당은 현대 운영체제가 쓰는 방법으로 페이징 기법이 있음. 메모리를 동일한 크기의 페이지로 나누고 프로그램마다 페이지 테이블을 두어 이를 통해 메모리에 프로그램을 할당하는 것임.</p>
<h5 id="페이징paging">페이징(paging)</h5>
<p>동일한 크기의 페이지 단위로 나누어 메모리의 서로 다른 위치에 프로세스를 할당함. 홀의 크기가 균일하지 않은 문제가 없어지지만 주소 변환이 복잡해짐.</p>
<h5 id="세그멘테이션segmentation">세그멘테이션(segmentation)</h5>
<p>페이지 단위가 아닌 의미 단위인 세그면트로 나누는 방식임. 프로세스는 코드, 데이터, 스택, 힙 등으로 이루어지는데, 코드와 데이터 등 이를 기반으로 나눌 수도 있으며 함수 단위로 나눌 수 있음을 의미합니다. 공유, 보안 측명에서 좋으며 홀 크기가 균일하지 않은 문제 발생.</p>
<h5 id="페이지드-세그멘테이션paged-segmentation">페이지드 세그멘테이션(paged segmentation)</h5>
<p>공유나 보안을 의미 단위의 세그먼트로 나누고, 물리적 메모리는 페이지로 나누는 것을 말함.</p>
<h4 id="페이지-교체-알고리즘">페이지 교체 알고리즘</h4>
<p>메모리는 한정되어 있기 때문에 스와핑이 많이 일어남. 스와핑이 많이 일어나지 않도록 설계되어야 하며, 이는 페이지 교체 알고리즘을 기반으로 스와핑이 일어남.</p>
<h5 id="오프라인-알고리즘offline-algorithm">오프라인 알고리즘(offline algorithm)</h5>
<p>먼 미래에 참조되는 페이지와 현재 할당하는 페이지를 바꾸는 알고리즘. 물론 미래는 알 수 없기에 사용할 수 없는 알고리즘이지만 성능비교에 대한 기준을 제공함.</p>
<h4 id="fifo">FIFO</h4>
<p>아시죠?</p>
<h4 id="lruleast-recentle-used">LRU(Least Recentle Used)</h4>
<p>참조가 가장 오래된 페이지를 바꿈. 오래된것을 파악하기 위해 각 페이지마다 계수기, 스택을 두어야 하는 문제점이 있음.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/da48d89d-758d-4ae7-9c65-9c7fd7a99c8a/image.png" alt=""></p>
<p>LRU는 보통 해시 테이블과 이중연결 리스트를 사용함. 해시테이블은 빠르게 찾아서 쓸 수 있고, 이중 연결 리스트는 한정된 메모리를 나타냄.</p>
<h4 id="nurnot-used-recently">NUR(Not Used Recently)</h4>
<p>clock 알고리즘이라고도 하며 먼저 0과 1을 가진 비트를 둠. 1은 최근에 참조되었고 0은 참조되지 않음을 의미함. 시계 방향으로 돌면서 0을 찾고 0을 찾은 순간 해당 프로세스를 교체하고 해당 부분을 1로 바꾸는 알고리즘임.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/8b2fcda2-308b-4ae7-a38f-ac6631c8c4b9/image.png" alt=""></p>
<h4 id="lfuleast-frequently-used">LFU(Least Frequently Used)</h4>
<p>가장 참조 횟수가 적은 페이지를 교체함. </p>
<h1 id="thank-you☺️"><strong>thank you</strong>☺️</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴과 프로그래밍 패러다임]]></title>
            <link>https://velog.io/@yeo___li/%EB%A9%B4%EC%A0%91%EC%9D%84-%EC%9C%84%ED%95%9C-CS-%EC%A0%84%EA%B3%B5-%EC%A7%80%EC%8B%9D-%EB%85%B8%ED%8A%B8-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@yeo___li/%EB%A9%B4%EC%A0%91%EC%9D%84-%EC%9C%84%ED%95%9C-CS-%EC%A0%84%EA%B3%B5-%EC%A7%80%EC%8B%9D-%EB%85%B8%ED%8A%B8-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 15 Jan 2025 05:40:08 GMT</pubDate>
            <description><![CDATA[<h2 id="11-디자인-패턴">1.1 디자인 패턴</h2>
<p>디자인 패턴이란 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 &#39;규약&#39; 형태로 만드렁 놓은 것을 의미함.</p>
<h3 id="111-싱글톤-패턴singleton-pattern">1.1.1 싱글톤 패턴(singleton pattern)</h3>
<p>오직 하나의 인스턴스만 가지는 패턴. 주로 데이터베이스 연결 모듈에 많이 사용됨.</p>
<pre><code class="language-java">class Singleton {
    private static class singleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return singleInstanceHolder.INSTANCE;
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Singleton a = Singleton.getInstance(); // 새로 할당하지 않고 get 메서드 사용
        Singleton b = Singleton.getInstance();
        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        if(a == b) {
            System.out.pritnln(true);
        }
    }
}
</code></pre>
<h4 id="장점">장점</h4>
<p>하나의 인스턴스를 만들어 놓고, 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스 생성 비용이 줄어듦.</p>
<h4 id="단점">단점</h4>
<p>의존성이 높아짐. TDD를 할 때 걸림돌이 됨. TDD를 할 때, 단위 테스트를 주로 하는데, 단위 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 함. 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 테스트마다 독립적인 인스턴스 만들기 어려움.</p>
<h4 id="단점-해결-방안">단점 해결 방안</h4>
<p>의존성 주입(Dependency Injection)을 통해 해결할 수 있음. </p>
<hr>
<h3 id="112-팩토리-패턴factory-pattern">1.1.2 팩토리 패턴(factory pattern)</h3>
<p>객체를 사용하는 코드에서 <strong>객체 생성 부분을 떼어내 추상화한 패턴</strong>이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴임.</p>
<pre><code class="language-java">    // 객체 생성 부분 추상화
    abstract class Coffee {
        public abstract int getPrice();

        @Override
        public String toString() {
            return &quot;Hi this coffee is &quot; + this.getPrice();
        }
    }

    // 팩토리 패턴
    class CoffeeFactory {
        public Coffee getCoffee(String type, int price) {
            if(&quot;Latte&quot;.equalsIgnoreCase(type)) return new Latte(price);
            else if(&quot;Americano&quot;.equalsIgnoreCase(type)) return new Americano(price);
            else return new DefaultCoffee();
        }
    }

    // 하위 클래스에서 구체적인 내용 결정
    class DefaultCoffee extends Coffee {
        private int price;

        public DefaultCoffee() {
            this.price = -1;
        }

        @Override
        public int getPrice() {
            return this.price;
        }
    }

    class Latte extends Coffee {
        private int price;

        public Latte(int price) {
            this.price = price;
        }

        @Override
        public int getPrice() {
            return this.price;
        }
    }

    class Americano extends Coffee {
        private int price;

        public Americano(int price) {
            this.price = price;
        }

        @Override
        public int getPrice() {
            return this.price;
        }
    }</code></pre>
<h3 id="113-전략-패턴strategy-pattern-policy-pattern">1.1.3 전략 패턴(strategy pattern, policy pattern)</h3>
<p>객체의 행위를 바꾸고 싶은 경우 &#39;직접&#39; 수정하지 않고 전략이라고 부르는 &#39;캡슐화한 알고리즘&#39;을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴임.</p>
<pre><code class="language-java">package org.example;

import java.util.ArrayList;
import java.util.List;

public class Main {
    interface PaymentStrategy {
        public void pay(int amount);
    }

    static class KAKAOCardStragtegy implements PaymentStrategy {
        private String name;
        private String cardNumber;
        private String cvv;
        private String dateOfExpiry;

        public KAKAOCardStragtegy(String nm, String ccNum, String cvv, String expiryDate) {
            this.name = nm;
            this.cardNumber = ccNum;
            this.cvv = cvv;
            this.dateOfExpiry = expiryDate;
        }

        @Override
        public void pay(int amount) {
            System.out.println(amount + &quot; paid using KAKAOCard.&quot;);
        }
    }

    static class LUNACardStrategy implements PaymentStrategy {
        private String emailId;
        private String password;

        public LUNACardStrategy(String email, String pwd) {
            this.emailId = email;
            this.password = pwd;
        }

        @Override
        public void pay(int amount) {
            System.out.println(amount + &quot; paid using LUNACard.&quot;);
        }
    }

    static class Item {
        private String name;
        private int price;
        public Item(String name, int cost) {
            this.name = name;
            this.price = cost;
        }

        public String getName() {
            return this.name;
        }

        public int getPrice() {
            return price;
        }
    }

    static class ShoppingCart {
        List&lt;Item&gt; items;

        public ShoppingCart() {
            this.items = new ArrayList&lt;&gt;();
        }

        public void addItem(Item item) {
            this.items.add(item);
        }

        public void removeItem(Item item) {
            this.items.remove(item);
        }

        public int calculateTotal() {
            int sum = 0;
            for(Item item : items) {
                sum += item.getPrice();
            }
            return sum;
        }

        public void pay(PaymentStrategy paymentStrategy) {
            int amount = calculateTotal();
            paymentStrategy.pay(amount);
        }
    }

    public static void main(String[] args) {
       ShoppingCart cart = new ShoppingCart();

       Item A = new Item(&quot;MacBookM3&quot;, 100);
       Item B = new Item(&quot;MacBookM2&quot;, 300);

       cart.addItem(A);
       cart.addItem(B);

       cart.pay(new LUNACardStrategy(&quot;yeoli@naver.com&quot;, &quot;yeolipw&quot;));
       cart.pay(new KAKAOCardStragtegy(&quot;Park Seong Yeol&quot;, &quot;141313515&quot;, &quot;123&quot;, &quot;01/13&quot;));
    }
}</code></pre>
<h3 id="114-옵저버-패턴observer-pattern">1.1.4 옵저버 패턴(observer pattern)</h3>
<p>주체가 어떤 객체의 상태 변화를 관찰하여 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴임.</p>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.list;

interface Subject {
    public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate(Observer obj);
}

interface Observer {
    public void update();
}

class Topic implements Subject {
    private List&lt;Observer&gt; observers;
    private String message;

    public Topic() {
        this.observers = new ArrayList&lt;&gt;();
        this.message = &quot;&quot;;
    }

    @Override
    public void register(Observer obj) {
        if (!observers.contains(obj)) observers.add(obj);
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj);
    }

    @Override
    public void notifyObservers() {
        this.observers.forEach(Observer::update);
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }

    public void postMessage(String msg) {
        System.out.println(&quot;Message sended to Topic:&quot; + msg);
        this.message = msg;
        notifyObservers(); // 여기서 옵저버에게 알려줌
    }
}

class TopicSubscriber implements Observer {
    private String name;
    private Subject topic;

    public TopicSubscriber(String name, Subject topic) {
        this.name = name;
        this.topic = topic;
    }

    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this);
        System.out.println(name + &quot;:: got message &gt;&gt; &quot; + msg);
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Topic topic = new Topic();
        Observer a = new TopicSubscriber(&quot;a&quot;, topic);
        Observer b = new TopicSubscriber(&quot;b&quot;, topic);
        Observer c = new TopicSubscriber(&quot;c&quot;, topic);
        topic.register(a);
        topic.register(b);
        topic.register(c);

        topic.postMessage(&quot;amumu is op champion!!&quot;);
    }
}</code></pre>
<h4 id="상속과-구현의-차이">상속과 구현의 차이</h4>
<ul>
<li><p>상속(extends)</p>
<ul>
<li>자식 클래스가 부모 클래스의 메서드 등을 상속 받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있는 것을 말함.</li>
</ul>
</li>
<li><p>구현(implements)</p>
<ul>
<li>부모 인터페이스(interface)를 자식 클래스에서 재정의하여 구현하는 것을 말하며, 상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야함.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="115-프록시-패턴과-프록시-서버">1.1.5 프록시 패턴과 프록시 서버</h3>
<p>프록시 패턴(procy patter)은 대상 객체(subject)에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴임.</p>
<hr>
<h3 id="116-이터레이터-패턴">1.1.6 이터레이터 패턴</h3>
<p>이터레이터 패턴(iterator pattern)은 이터레이터(iterator)를 사용하여 컬렉션(collection)의 요소들에 접근하는 디자인 패턴임. </p>
<hr>
<h3 id="117-노출모듈-패턴">1.1.7 노출모듈 패턴</h3>
<p>노출모듈 패턴(revealing module pattern)은 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴을 말함.
-&gt; 자바스크립트 내용이라 별로 안 중요한듯</p>
<hr>
<h3 id="118-mvc-패턴">1.1.8 MVC 패턴</h3>
<p>MVC 패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 디자인 패턴임.</p>
<ul>
<li><p>모델
모델은 애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 의미함</p>
</li>
<li><p>뷰
뷰는 inputbox, checkbox, textarea 등 사용자 인터페이스 요소를 나타냄.</p>
</li>
<li><p>컨트롤러
컨트롤러는 하나 이사의 모델과 하나 이상의 뷰를 잇는 다리 역할을 하며 이벤트 등 메인 로직을 담당함.</p>
</li>
<li><p>MVC 패턴 예시
Spring</p>
</li>
</ul>
<hr>
<h3 id="119-mvp-패턴">1.1.9 MVP 패턴</h3>
<p>MVP 패턴은 MVC 패턴으로부터 파생되었으며 MVC에서 C에 해당하는 컨트롤러가 프레젠터(presenter)로 교체된 패턴임. MVC 패턴과의 차이점은 뷰와 프레젠터는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 지닌 디자인 패턴임.</p>
<hr>
<h3 id="1110-mvvm-패턴">1.1.10. MVVM 패턴</h3>
<p>MVVM 패턴은 MVC의 C의 해당하는 컨트롤러가 뷰모델(View Model)로 바뀐 패턴임. 뷰모델은 뷰를 더 추상화한 계층이며, MVVM 패턴은 MVC 패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징임.</p>
<hr>
<hr>
<h2 id="12-프로그래밍-패러다임">1.2 프로그래밍 패러다임</h2>
<p>프로그래밍 패러다임(programming paradigm)은 프로그래머에게 프로그래밍의 관점을 갖게 해주는 역할을 하는 개발 방법론임.</p>
<hr>
<h3 id="121-선언형과-함수형-프로그래밍">1.2.1 선언형과 함수형 프로그래밍</h3>
<p>선언형 프로그래밍(declarative programing)이란 <strong>&#39;무엇을&#39;</strong> 풀어내는가에 집중하는 패러다임이며, <strong>&quot;프로그램은 함수로 이루어진 것이다.&quot;</strong> 라는 명제가 담겨 있는 패러다임의 일종임. 함수형 프로그래밍(functional programming)은 선언형 패러다임의 일종임.</p>
<ul>
<li><p>함수형 프로그래밍(functional programming)
작은 &#39;순수 함수&#39;들을 블록처럼 쌓아 룆ㄱ을 구현하고 &#39;고차 함수&#39;를 통해 재사용성을 높인 프로그래밍 패러다임임.</p>
</li>
<li><blockquote>
<p>자바스크립트에서 선호함</p>
</blockquote>
</li>
<li><p>순수 함수
출력이 입력에만 의존하는 함수를 의미함.</p>
</li>
<li><p>고차 함수
함수가 함수를 값처럼 매개변수로 받아 로직을 생성할 수 있는 것을 의미함.</p>
</li>
</ul>
<hr>
<h3 id="122-객체지향-프로그래밍">1.2.2 객체지향 프로그래밍</h3>
<p>객체지향 프로그래밍(OOP, Object-Oriented Programming)은 객체들의 집합으로 프로그램의 상호 작용을 표현하며 데이터를 객체로 취급하여 객체 내부에 선언된 메서드를 활용하는 방식을 의미함. 설계에 많은 시간이 소요되며 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느림.</p>
<h4 id="객체지향-프로그래밍의-특징">객체지향 프로그래밍의 특징</h4>
<ul>
<li><p>추상화(abstraction)
추상화란 복잡한 시스템으로부터 핵심적인 개념 또는 기능을 간추려내는 것을 의미함.</p>
</li>
<li><p>캡슐화(encapsulation)
캡슐화는 객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감추어 은닉하는 것을 의미함.</p>
</li>
<li><p>상속성(inheritance)
상속성은 상위 클래스의 특성을 하위 클래스가 이어받아서 재사용하거나 추가, 확장하는 것을 의미함.</p>
</li>
<li><p>다형성(polymorphism)
다형성은 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것을 의미함.</p>
</li>
</ul>
<h4 id="오버로딩-오버라이딩">오버로딩, 오버라이딩</h4>
<ul>
<li><p>오버로딩(overlading)
같은 이름을 가진 메서드를 여러개 두는 것을 의미함.</p>
</li>
<li><p>오버라이딩(overriding)
주로 메서드 오버리아딩(method overridig)을 말하며, 상위 클래스로부터 상속받은 메서드를 하위 클래서가 재정의하는 것을 의미함.</p>
</li>
</ul>
<h4 id="설계원칙---solid">설계원칙 - SOLID</h4>
<ul>
<li><p>단일 책임 원칙(SRP, Responsibility Principle)
모든 클래스는 각각 하나의 책임만 가져야 한다는 원칙임.</p>
</li>
<li><p>개방-페쇄 원칙(OCP, Open Closed Principle)
유지 보수 사항이 생긴다면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀 있어야 한다는 원칙임. 즉, 기존의 코드는 잘 변경하지 않으면서 확장은 쉽게 할 수 있어야 함.</p>
</li>
<li><p>리스코프 치환 원칙(LSP, Liskov Substitution Principle)
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 하는 것을 의미함. 즉, 부모 객체에 자식 객체를 넣어도 시스템이 문제없이 돌아가게 만드는 것을 말함.</p>
</li>
<li><p>인터페이스 분리 원칙(ISP, Interface Segregation Principle)
하나의 일반적인 인터페이스보다 구체적인 여러 개의 인터페이스를 만들어야 하는 원칙을 의미함.</p>
</li>
<li><p>의존 역전 원칙(DIP, Dependency Inversion Principle) 
자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 원칙을 의미함.</p>
</li>
</ul>
<hr>
<h3 id="123-절차형-프로그래밍">1.2.3 절차형 프로그래밍</h3>
<p>절차형 프로그래밍은 로직이 수행되어야 할 연속적인 계산 과정으로 이루어져 있음. 장점은 코드의 가독성이 좋고, 실행 속도가 빠름. 단점은 모듈화하기 어렵고 유지 보수성이 떨어짐.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[nginx] 원하는 도메인 HTTPS 인증 받고 원하는 포트로 리버스 프록시 하기]]></title>
            <link>https://velog.io/@yeo___li/nginx-%EC%9B%90%ED%95%98%EB%8A%94-%EB%8F%84%EB%A9%94%EC%9D%B8-HTTPS-%EC%9D%B8%EC%A6%9D-%EB%B0%9B%EA%B3%A0-%EC%9B%90%ED%95%98%EB%8A%94-%ED%8F%AC%ED%8A%B8%EB%A1%9C-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yeo___li/nginx-%EC%9B%90%ED%95%98%EB%8A%94-%EB%8F%84%EB%A9%94%EC%9D%B8-HTTPS-%EC%9D%B8%EC%A6%9D-%EB%B0%9B%EA%B3%A0-%EC%9B%90%ED%95%98%EB%8A%94-%ED%8F%AC%ED%8A%B8%EB%A1%9C-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Dec 2024 12:55:04 GMT</pubDate>
            <description><![CDATA[<p><a href="#1-%EB%8F%84%EB%A9%94%EC%9D%B8-https-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0">1. 도메인 HTTPS 인증 받기</a>
<a href="#2-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%9B%90%ED%95%98%EB%8A%94-%ED%8F%AC%ED%8A%B8%EC%97%90-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0">2. 도메인 원하는 포트에 리버스 프록시 설정하기</a>
<a href="#3-nginxconf-%ED%8C%8C%EC%9D%BC-%EC%88%98%EC%A0%95-%ED%9B%84-%EC%84%A4%EC%A0%95-%EC%A0%81%EC%9A%A9-%EC%A0%88%EC%B0%A8">3. nginx 설정 적용 하기</a></p>
<hr>
<h2 id="1-도메인-https-인증하기">1. 도메인 https 인증하기</h2>
<h3 id="a-certbot-으로-인증서-발급-받기">a. Certbot 으로 인증서 발급 받기</h3>
<pre><code>sudo certbot --nginx -d {YOUR_DOMAIN}</code></pre><h3 id="b-nginx-설정-적용">b. nginx 설정 적용</h3>
<p>설정 적용을 다시 해주는 이유는 <code>certbot</code>이 <code>nginx의 설정 파일</code>을 직접 수정했기 때문입니다. 아래 코드를 입력해주세요.</p>
<pre><code>sudo nginx -t # 설정 파일 문법 확인
sudo systemctl reload nginx # nginx 리로드</code></pre><h3 id="c-자동-갱신-설정">c. 자동 갱신 설정</h3>
<p>https 인증은 90일마다 재인증을 해야합니다. 따라서 이를 자동으로 해줄 수 있게 하도록 아래 코드를 입력해주세요.</p>
<pre><code>sudo certbot renew --dry-run</code></pre><hr>
<h2 id="2-도메인-원하는-포트에-리버스-프록시-설정하기">2. 도메인 원하는 포트에 리버스 프록시 설정하기</h2>
<h3 id="a-nginx-설정-파일-열기">a. Nginx 설정 파일 열기</h3>
<pre><code>sudo {nano, vim} /etc/nginx/sites-available/{YOUR_DOMAIN}</code></pre><h4 id="잠깐-짚고-넘어가기">잠깐 짚고 넘어가기</h4>
<p><code>설정 파일명</code>을 <code>도메인명</code>으로 짓는 이유는 나중에 설정 관리를 편리하게 하기 위해서입니다.
실제로는 <code>/etc/nginx/sites-available</code> 디렉토리 안에 있는 활성화 된 아무 폴더에 이 코드를 작성해도 오류가 나지않습니다.</p>
<p>추가로 활성화와 비활성화 정책에 대해서도 간단히 짚고 가겠습니다.
<code>/etc/nginx/sites-available</code> 이 디렉토리 안에 있는 모든 정책은 비활성화 되어 있습니다.
따라서 <code>/etc/nginx/sites-enabled</code> 디렉토리에 심볼릭 링크를 만들어 정책을 활성화 할 수 있습니다.</p>
<p>심볼릭 링크를 만드는 방법은 다음과 같습니다.</p>
<pre><code># 문법
sudo ln -s {심볼릭 링크 할 파일의 경로} {심볼릭 링크를 담고싶은 디렉토리 경로}

# 예시
sudo ln -s /etc/nginx/sites-available/{YOUR_DOMAIN} /etc/nginx/sites-enabled/</code></pre><p>위의 예시와 같이 정책 파일을 활성화 시킬 수 있습니다.</p>
<p>삭제하는 방법은 즉, 비활성화 하는 방법은 아래와 같습니다.</p>
<pre><code># 문법
sudo rm -s {삭제할 파일 경로}

# 예시
sudo rm -s /etc/nginx/sites-enabled/{YOUR_DOMAIN}</code></pre><h3 id="b-설정-파일에-http-→-https로-리다이렉트-설정-후-리버스-프록시-설정-코드-작성">b. 설정 파일에 http → https로 리다이렉트 설정 후 리버스 프록시 설정 코드 작성</h3>
<pre><code>server {
    listen 80;
    server_name {YOUR_DOMAIN};
    return 301 https://$host$request_uri;  # HTTP -&gt; HTTPS 리디렉션
}

server {
    listen 443 ssl;
    server_name {YOUR_DOMAIN};

    ssl_certificate /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem;

    # 리버스 프록시 설정
    location / {
        proxy_pass http://localhost:{YOUR_PORT_NUMBER};  
        # {YOUR_PORT_NUMBER} 포트로 리버스 프록시
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
</code></pre><p>위 코드 타이핑 후 파일 저장</p>
<h3 id="c-개방되어-있는-포트번호-확인하고-your_port_number가-닫혀있다면-포트-개방하기">c. 개방되어 있는 포트번호 확인하고, {YOUR_PORT_NUMBER}가 닫혀있다면 포트 개방하기</h3>
<p>{YOUR_PORT_NUMBER}가 방화벽에 차단되어있는지 확인
(80이나 443 포트도 리스트에 allow 되어있지 않다면 똑같이 차단 풀어주세요!)</p>
<pre><code>sudo ufw status</code></pre><p>차단되어 있다면 포트 개방하기</p>
<pre><code>sudo ufw allow {YOUR_PORT_NUMBER}</code></pre><h3 id="d-설정-적용하기">d. 설정 적용하기</h3>
<pre><code>sudo nginx -t # 오류난다면 오류 해결하기
sudo systemctl reload nginx</code></pre><hr>
<h2 id="3-nginxconf-파일-수정-후-설정-적용-절차">3. nginx.conf 파일 수정 후 설정 적용 절차</h2>
<h3 id="a-설정-파일-문법-확인">a. 설정 파일 문법 확인</h3>
<pre><code>sudo nginx -t # 설정 파일 문법 확인</code></pre><p>성공할 때 까지 반복</p>
<h3 id="b-설정-적용-재로드-또는-재시작-둘-중-하나-적용">b. 설정 적용 (재로드 또는 재시작 둘 중 하나 적용)</h3>
<pre><code>sudo systemctl reload nginx # 재로드 (추천) 연결이 끊기지 않고 설정 변경 사항이 적용됨
sudo systemctl restart nginx # 재시작</code></pre><h3 id="c-서비스-상태-확인">c. 서비스 상태 확인</h3>
<pre><code>sudo systemctl status nginx # 정상 작동 시 Active: active (running)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[yeolchat] 채팅 프로젝트]]></title>
            <link>https://velog.io/@yeo___li/yeolchat-%EC%B1%84%ED%8C%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@yeo___li/yeolchat-%EC%B1%84%ED%8C%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Sun, 01 Dec 2024 06:51:54 GMT</pubDate>
            <description><![CDATA[<p>채팅 프로젝트를 하기로 마음 먹었다. 이유는 재미있을 것 같다. </p>
<hr>
<h2 id="기능-설명">기능 설명</h2>
<p>닉네임을 입력하고 입장하면 현재 접속중인 사람과 대화할 수 있는 시스템이다.</p>
<ul>
<li><p>닉네임 저장 기능</p>
<ul>
<li>접속하고 있는 사람들 중에선 겹치는 닉네임이 없어야 한다.</li>
</ul>
</li>
<li><p>채팅 기능</p>
<ul>
<li>접속중인 사람들과 채팅을 할 수 있어야 한다.</li>
<li>내가 보낸 메세지는 오른쪽에, 다른 사람이 보낸 메세지는 왼쪽에 떠야한다.</li>
<li>메세지는 내용과 닉네임, 보낸 날짜와 시각이 떠야 한다.</li>
</ul>
</li>
</ul>
<p>이게 전부이다.</p>
<hr>
<h2 id="사용-기술">사용 기술</h2>
<ol>
<li>SpringBoot</li>
<li>nginx</li>
<li>websocket</li>
<li>Spring WebSocket</li>
<li>thymeleaf</li>
</ol>
<hr>
<p>시작해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP 웹 기본 지식] 인터넷 네트워크]]></title>
            <link>https://velog.io/@yeo___li/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-%EC%9D%B8%ED%84%B0%EB%84%B7-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@yeo___li/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-%EC%9D%B8%ED%84%B0%EB%84%B7-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Wed, 20 Nov 2024 16:01:36 GMT</pubDate>
            <description><![CDATA[<ol>
<li><a href="#1-%EC%9D%B8%ED%84%B0%EB%84%B7-%ED%86%B5%EC%8B%A0">인터넷 통신</a></li>
<li><a href="#2-ip%EC%9D%B8%ED%84%B0%EB%84%B7-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C">IP(인터넷 프로토콜)</a></li>
<li><a href="#3-tcp%EC%99%80-udp">TCP와 UDP</a></li>
<li><a href="#4-port">PORT</a></li>
<li><a href="#5-dns">DNS</a></li>
</ol>
<hr>
<h2 id="1-인터넷-통신">1. 인터넷 통신</h2>
<p>컴퓨터 둘은 수많은 노드(node) 들로 연결되어있는 인터넷에 연결하여 서로 통신을 주고 받습니다.
여기서 노드(node)란 네트워크에 연결되어 있는 1개 1개의 기계를 의미합니다. 전 세계적으로 수 많은 노드들이 연결되어있는 인터넷 세상에서 서로 떨어져있는 두 컴퓨터는 어떻게 통신을 주고 받을 수 있을까요?</p>
<hr>
<h2 id="2-ip인터넷-프로토콜">2. IP(인터넷 프로토콜)</h2>
<p>우선 IP(Internet Protocol)의 개념은 아래와 같습니다.</p>
<h3 id="ip-역할">IP 역할</h3>
<ul>
<li>지정한 <code>IP 주소(IP Adress)</code>에 데이터 전달</li>
<li><code>패킷(Packet)</code>이라는 통신 단위로 데이터 전달</li>
</ul>
<h3 id="패킷packet-이란">패킷(Packet) 이란</h3>
<ul>
<li><code>Package</code>과 <code>bucket</code>의 합성어</li>
<li>정보를 보낼 때 특정 형태를 맞추어 보내는 것.(데이터 블록 혹은 조각)</li>
</ul>
<h3 id="ip-패킷-이란">IP 패킷 이란</h3>
<ul>
<li><p>출발지IP, 목적지 IP 등 IP와 관련된 정보들이 들어있는 패킷</p>
<p>현실세계로부터 생각을 해보면 좋을 것 같습니다. A가 B에게 편지를 보내려고 합니다. A가 편지를 보내기 위해선 B의 집 주소를 알아야 합니다. 이처럼 인터넷 세상에서도 각 장치에게 주소를 부여합니다. 그리고 이를 IP 주소(IP Address)라고 합니다.</p>
<p>그리고 컴퓨터가 다른 컴퓨터에게 데이터를 보낼땐 데이터를 <code>IP Packet</code>으로 감싼 뒤 보냅니다.(수 많은 노드들은 목적지 IP를 확인하며 다른 노드에게 패킷을 보냅니다.)</p>
</li>
</ul>
<p>주소만 알면 모든것이 가능 할 것이라고 생각 할 수도 있지만, B가 이사를 가버리거나 그 집이 사라졌다면... 편지를 보내더라도 답장을 받을 수 없을 것입니다. 혹은 배달하던 트럭이 도둑(?) 맞아서 편지가 통째로 사라지거나 할 수도 있습니다.</p>
<p>인터넷 세상도 마찬가지 입니다. 이를 조금 전문적인 용어로 바꿔보겠습니다.</p>
<h3 id="ip-프로토콜의-한계">IP 프로토콜의 한계</h3>
<ul>
<li>비연결성<ul>
<li>패킷을 받을 대상이 없거나 서비스 불능 상태여도 패킷 전송</li>
</ul>
</li>
<li>비신뢰성<ul>
<li>중간에 패킷이 사라지면?</li>
<li>패킷이 순서대로 안오면?</li>
</ul>
</li>
<li>프로그램 구분<ul>
<li>같은 IP를 사용하는 서버에서 통신하는 애플리케이션이 둘 이상이라면?</li>
</ul>
</li>
</ul>
<p>이처럼 IP 프로토콜은 위와 같은 한계를 가지고 있습니다. 그렇다면 위의 한계를 해결하기 위해선 무엇이 필요할까요?</p>
<hr>
<h2 id="3-tcp와-udp">3. TCP와 UDP</h2>
<p>IP의 한계를 극복하기 위해선 TCP와 UDP가 필요합니다. 이를 이해하기 전에 필요한 선지식부터 학습해보도록 하겠습니다.</p>
<h3 id="인터넷-프로토콜-스택의-4계층">인터넷 프로토콜 스택의 4계층</h3>
<ul>
<li>4계층: 애플리케이션 계층 - HTTP, FTP</li>
<li>3계층: 전송 계층 - TCP, UDP</li>
<li>2계층: 인터넷 계층 - IP</li>
<li>1계층: 네트워크 인터페이스 계층</li>
</ul>
<h3 id="프로토콜-계층">프로토콜 계층</h3>
<ul>
<li>애플리케이션 계층: 웹브라우저, 네트워크 게임, 채팅 프로그램, SOCKET 라이브러리...</li>
<li>OS 계층: TCP, UDP, IP</li>
<li>네트워크 인터페이스 : LAN 드라이버, LAN 장비</li>
</ul>
<p>위는 우선 인터넷 프로토콜의 계층에 관한 설명입니다. 보시다시피 4계층으로 이루어져있고, 4계층에서 1계층으로 내려간 뒤 인터넷을 이용해 통신을 합니다.</p>
<p>애플리케이션 계층에서 &#39;Hello, world!&#39; 라는 메세지를 작성한 뒤, 다른 PC에 보낸다고 가정하겠습니다. 이때 정보가 전환되는 과정은 아래외 같습니다.</p>
<h3 id="계층별-정보-변환-과정">계층별 정보 변환 과정</h3>
<ol>
<li>프로그램이 &#39;Hello, World!&#39; 메시지 생성</li>
<li>SOCKET 라이브러리를 통해 전달</li>
<li><code>TCP 정보 생성, 메시지 데이터 포함</code></li>
<li><code>IP패킷 생성, TCP 데이터 포함</code></li>
<li>슝~</li>
</ol>
<p>위에서 IP 패킷으로 데이터를 한번 감싼다고 했습니다. 그리고 TCP는 IP 패킷으로 감싸기 전에 TCP 정보를 미리 생성하여 메시지를 먼저 감쌉니다. 따라서 IP 패킷은 TCP 데이터와 메시지 데이터를 합친 데이터를 감싸는 것입니다.
<img src="https://velog.velcdn.com/images/yeo___li/post/cf39e0f0-3bc2-411d-a9c9-692daa1e4fce/image.png" alt=""></p>
<p>그렇다면 TCP란 무엇이길래 IP의 한계를 극복시켜준다는 것일까요?</p>
<h3 id="tcptransmission-control-protocol란">TCP(Transmission Control Protocol)란?</h3>
<ul>
<li>전송 제어 프로토콜</li>
<li>신뢰할 수 있는 프로토콜</li>
</ul>
<p>TCP(Transmission Control Protocol)는 <code>전송 제어 프로토콜</code>입니다. 풀어 설명하면, 데이터를 전달할 때 전송을 <code>신뢰할 수 있게</code> 해주는 약속입니다. 이런 약속을 지키기 위해서 TCP는 다음과 같은 특징을 같습니다.</p>
<h3 id="tcp의-특징">TCP의 특징</h3>
<ul>
<li>연결지향 - TCP 3 way handshake(가상 연결)</li>
<li>데이터 전달 보증</li>
<li>순서 보장</li>
</ul>
<h4 id="연결지향---tcp-3-way-handshake">연결지향 - TCP 3 way handshake</h4>
<p>TCP는 데이터를 보내기 전에 우선적으로 데이터를 보내려는 IP주소가 유효한지 확인하는 과정을 거칩니다. 그리고 이를 <code>TCP 3 way handshake</code> 라고 부릅니다. handshake의 과정은 아래처럼 진행됩니다.</p>
<ol>
<li>A컴퓨터가 B컴퓨터에게 접속 요청인 <code>SYN</code>를 보낸다.</li>
<li>B컴퓨터는 A컴퓨터에게 요청 수락인 <code>ACK</code>와 <code>SYN</code>를 보낸다.</li>
<li>A컴퓨터는 B컴퓨터에게 요청 수락인 <code>ACK</code>를 보낸다.</li>
</ol>
<p>두 컴퓨터가 접속 요청(<code>SYN</code>)을 받고 요청 수락(<code>ACK</code>)를 받았기에 두 컴퓨터는 논리적으로 연결되어 있다고 확신할 수 있는 상태가 됩니다. 그리고 이를 통해서 IP 프로토콜의 비연결성 문제를 해결할 수 있습니다. </p>
<h4 id="데이터-전달-보증">데이터 전달 보증</h4>
<p>A컴퓨터와 B컴퓨터가 연결되고 난 후, 데이터 전송을 진행합니다. 그리고 B컴퓨터가 데이터를 받을 때 마다 A컴퓨터에게 데이터를 잘 받았다고 메세지를 보내줍니다. 그리고 데이터를 받았다는 메시지가 안온다면 A컴퓨터는 해당 데이터를 다시 보내줍니다. 이렇게 해서 TCP는 IP 프로토콜의 비신뢰성 중 데이터 소실 문제를 해결할 수 있습니다. </p>
<h4 id="순서-보장">순서 보장</h4>
<p>데이터를 한 번만 보낸다면 순서가 상관 없겠지만, 동영상 같이 용량이 큰 정보를 보낼땐 하나의 데이터를 여러개의 패킷으로 나누어 전송합니다. 이때 발생할 수 있는 문제는 모든 패킷이 일정한 노드를 타고 가는것이 아니라, 서로 다른 노드들을 거쳐서 가기 때문에 순서가 각각 다르게 B컴퓨터로 도착할 수 있습니다. 이를 해결하기 위해서 TCP는 만약 패킷1, 패킷3, 패킷2 순서대로 도착을 했다면 패킷2부터 A컴퓨터에게 다시 요청합니다. 이렇게 해서 IP 프로토콜의 비신뢰성 중 패킷 순서 문제를 해결할 수 있습니다.</p>
<p>TCP는 위와같은 특징으로 신뢰성을 보장받고 현재 대부분 TCP를 사용하고 있습니다. 하지만 TCP를 위해선 handshake도 해야하고 데이터도 보내달라 해야해서 때에 따라서 필요한 스팩보다 과하게 데이터를 선송하는 경우가 될 수도 있습니다. 그래서 상황에 맞춰서 직접 커스텀 할 수 있는 프로토콜인 <code>UDP</code>가 있습니다.</p>
<h3 id="udp-특징">UDP 특징</h3>
<ul>
<li>하얀 도화지에 비유(기능이 거의 없음)</li>
<li>연결 지향X</li>
<li>데이터 전달 보증X</li>
<li>순서 보장X</li>
<li>데이터 전달 및 순서가 보장되지 않지만, 단순하고 빠름</li>
<li>정리<ul>
<li>IP와 거의 같다. + PORT + 체크섬 정도만 추가</li>
<li>애플리케이션에서 추가 작업 필요</li>
</ul>
</li>
</ul>
<p>어? 여기서 나온 PORT는 무엇이죠?? 그리고 아직 IP의 한계인 프로그램 구분 문제가 해결 되지 않았습니다... 이어서 쭉 가보겠습니다.</p>
<hr>
<h2 id="4-port">4. PORT</h2>
<p> port란 영어로 항구란 뜻입니다. 인터넷은 거대한 바다입니다. 그 거대한 바다를 타고 온 배들은 항구에 정박하여 컨테이너를 각각 필요한 곳으로 옮깁니다. port도 이와 같은 역할을 합니다. 컴퓨터 하나엔 여러 프로그램이 실행되고 있습니다. 모든 애플리케이션들은 모두 하나의 IP를 타고와서 TCP/UDP를 이용하여 데이터를 받고 있을것입니다. 그렇다면 각 애플리케이션들의 데이터들이 섞이지 않을까요? 예상하셨겠지만 port 덕분에 그런 일은 발생하지 않고 우리가 유튜브로 음악을 들으며 카카오톡을 할 수 있는 것입니다.
 프로그램들은 각각 하나의 포트를 사용하고 있습니다. 그리고 TCP 안에는 출발 port 번호와 도착 port 번호가 들어있습니다. 따라서 같은 IP를 타고 들어오더라도 TCP 계층에서 port 번호에 따라 데이터가 분류 됩니다.</p>
<p> 위의 말을 정리하자면, IP 주소는 컴퓨터를 찾을 때 필요한 주소이고, Port는 컴퓨터 안에서 프로그램을 찾을 때 사용합니다.</p>
<p> Port 번호는 0~65535 까지 있습니다. http는 80포트를 사용하고 https는 443을 사용합니다. 그리고 테스트 서버? 는 주로 8080을 많이 사용합니다.</p>
<p>이렇게 해서 IP주소의 한계를 모두 타파할 수 있었습니다. 그렇다면 정말 남은 딱 하나의 문제는 무엇일까요?</p>
<h2 id="5-dns">5. DNS</h2>
<p>(호흡이 길어서 쓰기 조금 힘드네요... 후딱 쓰겠습니다 ㅎ)</p>
<p>IP주소의 최대 단점은... <code>사용자가 읽기 어렵다는 것</code>입니다. 게다가 서버의 IP주소가 바뀐다면 백날 알고있던 IP 주소로 접속을 시도해도 들어가지지 않을 것입니다. 이를 해결하기 위해서 사람들은 IP 주소에 이름을 달아주었습니다. 그리고 IP 주소의 이름을 <code>domain</code>이라고 부릅니다.</p>
<h3 id="domain">domain?</h3>
<ul>
<li>인터넷에 연결된 컴퓨터를 사람이 쉽게 기억하고 입력할 수 있도록 문자(영문, 한글 등)로 만든 인터넷주소</li>
</ul>
<p>도메인의 예시론 &#39;naver.com&#39;이나 &#39;google.com&#39;이 있습니다. 인터넷 검색창에 naver.com을 검색하면 늘 네이버 페이지로 이동합니다. 이게 가능한 이유는 <code>DNS</code> 덕분입니다. DNS란 <code>도메인 네임 시스템(Domain Name System)</code>입니다. 쉽게 말해서 도메인 명을 IP주소로 변환해주는 시스템 입니다. 따라서 서버의 IP 주소가 변하더라도 DNS서버에 바뀐 IP 주소를 입력해주면 사용자는 항상 같은 도메인 주소로도 바뀐 IP 주소에 접근할 수 있게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/yeo___li/post/f856704b-6cc6-452c-b6c1-b8bf9c250004/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 회고록 | 모른다는 걸 정말 알고 계신가요?]]></title>
            <link>https://velog.io/@yeo___li/%EC%9A%B0%ED%85%8C%EC%BD%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-%EB%AA%A8%EB%A5%B8%EB%8B%A4%EB%8A%94-%EA%B1%B8-%EC%A0%95%EB%A7%90-%EC%95%8C%EA%B3%A0-%EA%B3%84%EC%8B%A0%EA%B0%80%EC%9A%94</link>
            <guid>https://velog.io/@yeo___li/%EC%9A%B0%ED%85%8C%EC%BD%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-%EB%AA%A8%EB%A5%B8%EB%8B%A4%EB%8A%94-%EA%B1%B8-%EC%A0%95%EB%A7%90-%EC%95%8C%EA%B3%A0-%EA%B3%84%EC%8B%A0%EA%B0%80%EC%9A%94</guid>
            <pubDate>Thu, 31 Oct 2024 09:50:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yeo___li/post/f82c15a6-3668-49ff-888e-b9955becf28e/image.jpg" alt="달수"></p>
<p>프리코스가 시작된 지 어느덧 2주가 넘게 흘렀습니다. 프리코스를 진행 중인 여러분은 1, 2주 차 미션 모두 잘 마치셨나요? 개인적인 이야기론 10월 초에 전역한 저는 사회에 익숙해지기도 전에 프리코스에 뛰어들어 그 물살과 파도를 열심히 즐기는 중입니다.
 특히 이번 프리코스에서 인생 처음으로 스터디를 만들어 보고 좋은 분들과 함께 이야기하면서 정말 많은 것에 대해 배우고 성장하는 시간이 되고 있습니다. 그래서 저는 이번에 프리코스와 스터디를 하면서 어떤 걸 알았는지, 그리고 부족한 부분이 무엇인지 회고하는 시간을 가져보려고 합니다. 편하게 읽어주세요!</p>
<p> 그리고 저의 글은 기술적 회고보단, 프리코스에서 배운 사고와 태도에 관해서 이야기를 주로 해볼 예정입니다.</p>
<h2 id="모른다는-걸-정말-알고-계신가요">모른다는 걸 정말 알고 계신가요?</h2>
<p> 여러분들은 모른다는 걸 어디까지 알고 있으신가요? 저는 프리코스를 2주 차까지 진행하면서 제가 무엇을 모르고 있는지 더 자세하게 알게 된 것 같아요. 저는 이전엔 방향성이 없는 사람이었습니다. 내가 무언갈 하고 있지만 왜, 그리고 무얼 위해서 하고 있는지 모르는 것에서 오는 막막함은 아마 지금 읽고 있으신 분들도 자주 느껴보셨을 감정일 겁니다.
 인간에게 있어서 최고의 저주는 어떤 걸 모르는지조차도 모르는 게 아닐까 싶습니다. 내가 어떤 걸 모르는지 알고 있다면 나의 부족한 부분을 채우거나 혹은 한계를 명확히 하고 다른 시도를 할 수 있기 때문입니다. 그렇다면 모르는 걸 알기 위한 방법은 무엇이 있을까요?</p>
<h3 id="회고를-시작하며">회고를 시작하며</h3>
<p> 우아한테크코스에선 회고록을 추천하고 있습니다. 회고록을 작성하는 것도 좋은 방법이라고 생각하지만, 저는 회고록 작성 전에 더 중요한 과정이 있다고 생각합니다. 바로 자신 능력과 사고의 한계를 경험하는 것입니다. 그리고 저는 프리코스와 스터디를 진행하면서 저의 한계가 어디까지였지 알아가는 시간이었던 것 같습니다. </p>
<h3 id="프리코스에서-어떤-한계를-경험했어">프리코스에서 어떤 한계를 경험했어?</h3>
<p> 프리코스에선 매주 미션에서 제약사항을 줍니다. 1주 차는 커밋 메시지의 형식과 단위의 제약이었고, 2주 차는 코드의 <code>indent</code>가 3을 넘지 말라는 제약 사항과 함수가 한 가지 일만 하도록 최대한 작게 만드라는 제약사항이 가장 기억에 남습니다. 제약사항을 지키기 위해 더 신중히 코딩하고, 기존에 가지고 있던 사고에 한계를 경험했습니다. 예전엔 커밋도 아무 때나 하기도 하고 메세지도 마음대로 적었지만, 1주 차의 제약사항을 지키려고 노력하다 보니 지금은 커밋 단위도 많이 깔끔해지고 메세지도 이전보다 더 명확하게 적을 수 있게 되었습니다. 그리고 2주 차의 제약사항을 지키기 위해 너무 길어지고 깊어진 코드는 쪼개기 시작했고, 이전보다 더 읽기 좋은 코드가 되었습니다. 제약사항을 통해 생각하는 방식의 한계를 경험하고 그 한계를 넘어서기 위해 다양한 시도를 하면서 지금까지 저의 방식이 어떻게 잘못되었는지도 알 수 있었습니다.</p>
<h3 id="스터디에선-어떤-한계를-경험했어">스터디에선 어떤 한계를 경험했어?</h3>
<p> 개인적으로 프리코스의 꽃은 스터디라고 생각합니다. 사람들과 같은 미션에 대해 토의하며 소프트 스킬도 성장할 수 있고, 한 번도 생각지도 못한 방법을 타인에게 배우며 성장할 수 있는 유일무이한 시간이기 때문입니다. 저는 이번 프리코스 첫날에 스터디를 모집했습니다. 정말 감사하게도 많은 분께서 지원해 주셨고, 성공적으로 스터디를 결성했습니다. 
 가장 기억에 남는 경험은 1주 차 미션이 끝나고 코드 리뷰를 할 때였습니다. 다른 스터디원분들의 코드를 보고 감탄만 연발했습니다. 부족한 부분은 보이지 않았고, 제가 제출한 미션이 한없이 작아지는 기분이었습니다. 스터디장이 되어서 도움을 못 준다는 생각으로 미안한 마음도 들었습니다. 하지만 스터디원분들께서 많이 부둥부둥(?) 해주셔서 지금은 많이 배우고 질문도 열심히 하고 있습니다.
 코드 리뷰를 하면서 알았던 점은 다른 사람의 코드를 보는건 기능을 구현하는 것만큼 중요한 일 같습니다. 스터디에서 코드 리뷰를 하기 전엔 문제가 없고 한계가 없을 것 같던 저의 코드도, 코드 리뷰 후엔 한계와 부족한 부분이 명확히 보였습니다. 그리고 한계와 부족한 부분이 보인다는 건, 어떤 걸 모르고 있는지 알게 될 수도 있다는 겁니다.</p>
<h3 id="한계를-어떻게-경험해">한계를 어떻게 경험해?</h3>
<p> 처음에 무언가에 도전할 땐, 방향이 없는 수많은 점을 무작위로 찍는 과정이 필요한 것 같습니다. 점이 모이면 선이 되고 방향성과 추세가 생겨납니다. 이처럼 정말 아무것도 모르겠다면, 일단 많이 도전해 보는 게 중요한 것 같습니다. 그런 과정에서 만나는 한계는 내부에서 스스로 해결되기도 하고, 때로는 외부의 피드백으로 그 한계를 알아차리곤 합니다. 그리고 저에게 프리코스는 끝없는 도전이었습니다. 우아한테크코스에 신청한 것도 도전이었고, 미션을 제출하는 것도 도전이었고, 스터디를 모집하는 것도 도전이었습니다. (심지어 회고록을 쓰는 것 또한 저에겐 또다른 도전입니다.) 도전을 통해 실패와 한계를 맛보고 이를 통해 무엇이 부족하고 모르는지 알 수 있는 메타인지를 기를 수 있다고 생각합니다.</p>
<h3 id="회고를-마치며">회고를 마치며</h3>
<p>3주 차 미션도 저에겐 쉽지 않아 보입니다. 하지만 최선을 다할 것이고, 이번 주도 한계에 부딪히며 제가 어디까지 모르는지, 그리고 어떻게 극복할 것인지 알아갈 것입니다. 지금 프리코스를 진행하고 있는 모든 분께서도 남은 2주도 모두 각자의 목표를 항해 노력하고 많은 걸 배워갈 수 있는 프리코스가 되길 응원하겠습니다! </p>
]]></description>
        </item>
    </channel>
</rss>