<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yjkim.log</title>
        <link>https://velog.io/</link>
        <description>We may throw the dice, But the Lord determines how they fall.</description>
        <lastBuildDate>Wed, 12 Aug 2026 13:47:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. yjkim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/manofmen_yj" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Java] 리플렉션이란 무엇인가]]></title>
            <link>https://velog.io/@manofmen_yj/Java-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-fdk12rd2</link>
            <guid>https://velog.io/@manofmen_yj/Java-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-fdk12rd2</guid>
            <pubDate>Wed, 12 Aug 2026 13:47:19 GMT</pubDate>
            <description><![CDATA[<h2 id="리플렉션이란">리플렉션이란?</h2>
<p>자바 리플렉션을 검색해보면 &quot;객체가 원본이고, JVM 메모리에 로드된 클래스 정보가 거울에 비친 상(reflection)이다&quot; 같은 비유를 자주 보게 됩니다.</p>
<p>틀린 설명은 아닙니다. 다만 저는 이걸로 &quot;그래서 그게 왜 필요한데?&quot;라는 의문이 풀리지 않았습니다. 비유는 리플렉션이 <strong>무엇인지</strong>는 알려주지만, <strong>왜 존재하는지</strong>는 알려주지 않으니까요.</p>
<p>그래서 이해 자체를 목표로, 제 방식대로 다시 정리해보기로 했습니다.</p>
<h3 id="한-줄-정의">한 줄 정의</h3>
<blockquote>
<p>컴파일 시점에 어떤 클래스인지 몰라도, 런타임에 클래스·필드·메서드를 알아내고 조작하는 기능.</p>
</blockquote>
<p>핵심은 &quot;컴파일 시점에 몰라도&quot;입니다. 이 조건이 왜 중요한지가 이 글의 전부입니다.</p>
<h3 id="컴파일-타임에-아는-코드-vs-모르는-코드">컴파일 타임에 아는 코드 vs 모르는 코드</h3>
<p>우리가 평소에 쓰는 코드입니다.</p>
<pre><code class="language-java">UserRepository repo = new UserRepository();
repo.findName();</code></pre>
<p>컴파일러는 이 시점에 &quot;이건 <code>UserRepository</code>다&quot;라는 사실을 이미 알고 있습니다. 그래서 <code>findName()</code>이 실제로 존재하는지도 컴파일 시점에 검증됩니다. 없는 메서드를 호출하면 컴파일 자체가 되지 않습니다.</p>
<p>같은 일을 리플렉션으로 하면 이렇게 됩니다.</p>
<pre><code class="language-java">Class&lt;?&gt; clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
Method m = clazz.getMethod(&quot;findName&quot;);
m.invoke(obj);</code></pre>
<p>훨씬 장황하고, 타입 안전성도 없고, 오타가 나도 컴파일러가 잡아주지 않습니다.</p>
<p>여기서 눈여겨볼 건 <code>className</code>이 <strong>String</strong>이라는 점입니다. 클래스 이름이 문자열이라는 건, 그 값을 설정 파일에서 읽어오든 스캔 결과에서 가져오든 상관없다는 뜻입니다. 즉 <strong>코드를 작성하는 시점에 그 클래스가 세상에 존재하지 않아도 된다</strong>는 겁니다.</p>
<h3 id="이렇게-위험한-기능을-대체-왜-쓰는걸까요">이렇게 위험한 기능을 대체 왜 쓰는걸까요?</h3>
<p>익숙한 Spring 코드를 봅시다.</p>
<pre><code class="language-java">@Component
public class UserRepository {
    public String findName() { return &quot;hello&quot;; }
}

@Component
public class UserService {
    @Autowired
    private UserRepository userRepository;   // ← 여기를 꽂아줘야 함

    public void print() {
        System.out.println(userRepository.findName());
    }
}</code></pre>
<p>만약 리플렉션이 없다면 이 코드는 어떻게 될까요?</p>
<p>컴파일은 됩니다. <code>@Component</code>도 <code>@Autowired</code>도 어노테이션일 뿐이라 컴파일러는 아무 불평도 하지 않습니다. 실행도 됩니다. 대신 이렇게 터집니다.</p>
<pre><code>NullPointerException: Cannot invoke &quot;UserRepository.findName()&quot;
                      because &quot;this.userRepository&quot; is null</code></pre><p>당연합니다. 우리는 <code>userRepository</code>에 값을 넣는 코드를 어디에도 쓴 적이 없으니까요. <code>new UserRepository()</code>를 호출한 줄이 우리 프로젝트에 단 한 줄도 없습니다. <strong><code>@Autowired</code>라는 글자만 적어놨을 뿐입니다.</strong></p>
<p>그런데 이 코드를 Spring 위에서 돌리면 콘솔에 &quot;hello&quot;가 찍힙니다.</p>
<h3 id="어떻게-가능한걸까">어떻게 가능한걸까?</h3>
<p><code>@Autowired</code>는 마법이 걸린 키워드가 아닙니다. 컴파일 결과물에 남는 <strong>메타데이터 딱지</strong>입니다. 딱지는 그 자체로 아무 일도 하지 않습니다. 누군가 읽어야 의미가 생깁니다.</p>
<h4 id="그-누군가가-spring이고-읽는-도구가-리플렉션입니다">그 &quot;누군가&quot;가 Spring이고, 읽는 도구가 리플렉션입니다.</h4>
<p>애플리케이션이 뜰 때 Spring은 이 일을 순서대로 합니다.</p>
<ol>
<li>클래스패스를 뒤져서 <code>@Component</code>가 붙은 클래스를 찾는다 → <code>UserRepository</code>, <code>UserService</code> 발견</li>
<li>찾은 클래스를 리플렉션으로 생성한다 → <code>clazz.getDeclaredConstructor().newInstance()</code></li>
<li>각 객체의 필드를 훑어서 <code>@Autowired</code>가 붙은 걸 찾는다 → <code>UserService.userRepository</code> 발견</li>
<li>필드 타입(<code>UserRepository</code>)에 맞는 객체를 찾아서 꽂는다</li>
</ol>
<p>코드로 줄여보면 이 정도입니다. 먼저 <strong>1~2번</strong>, <code>@Component</code>를 읽고 객체를 만드는 부분입니다.</p>
<pre><code class="language-java">Map&lt;Class&lt;?&gt;, Object&gt; container = new HashMap&lt;&gt;();

for (Class&lt;?&gt; clazz : classes) {                       // 클래스패스에서 긁어온 목록
    if (clazz.isAnnotationPresent(Component.class)) {  // ← @Component 딱지 확인
        Object bean = clazz.getDeclaredConstructor().newInstance();
        container.put(clazz, bean);                    // 타입 → 객체로 보관
    }
}</code></pre>
<p>이 시점에 <code>UserRepository</code>와 <code>UserService</code> 객체가 만들어져 컨테이너에 들어갑니다. 다만 <code>UserService</code>의 필드는 아직 null입니다.</p>
<p>이어서 <strong>3~4번</strong>, <code>@Autowired</code>를 읽고 값을 꽂는 부분입니다.</p>
<pre><code class="language-java">for (Object bean : container.values()) {
    for (Field field : bean.getClass().getDeclaredFields()) {
        if (field.isAnnotationPresent(Autowired.class)) {    // ← @Autowired 딱지 확인
            field.set(bean, container.get(field.getType())); // ← 타입으로 찾아서 꽂는다
        }
    }
}</code></pre>
<p>이제 꺼내 쓰면 됩니다.</p>
<pre><code class="language-java">UserService service = (UserService) container.get(UserService.class);
service.print();   // &quot;hello&quot;</code></pre>
<p>생성과 주입이 두 단계로 나뉜 이유가 있습니다. <code>UserService</code>에 값을 꽂으려면 <code>UserRepository</code> 객체가 <strong>이미 만들어져 있어야</strong> 하기 때문입니다. 그래서 일단 전부 만들어놓고, 그다음에 서로 연결합니다. Spring도 같은 순서로 움직입니다.</p>
<p>우리가 쓰지 않은 <code>new UserRepository()</code>를, 우리가 붙여둔 딱지를 보고 Spring이 대신 실행해준 겁니다.</p>
<p>여기서 어노테이션의 정체가 정리됩니다.</p>
<blockquote>
<p><strong>어노테이션 = 표식, 리플렉션 = 그 표식을 읽고 실제로 뭔가 하는 손</strong></p>
</blockquote>
<p><code>@Transactional</code>, <code>@Entity</code>, <code>@GetMapping</code> 전부 같은 구조입니다. 우리가 표시를 남기고, 프레임워크가 리플렉션으로 그 표시를 찾아 동작합니다.</p>
<h3 id="왜-굳이-런타임에">왜 굳이 런타임에?</h3>
<p>한 가지 의문이 남습니다. 왜 굳이 런타임에 클래스패스를 뒤져야 할까요? 컴파일 타임에 하면 타입 안전성도 챙기고 오타로 인한 오류도 막을 수 있을 텐데요. Spring 소스 어딘가에 그냥 이렇게 적어두면 안 되는 걸까요?</p>
<pre><code class="language-java">UserService service = new UserService();   // spring-context 안에?</code></pre>
<p>결론부터 말함녀 <strong>불가능합니다</strong>. 이 코드가 컴파일되려면 컴파일러가 <code>UserService</code> 타입을 알아야 하는데, <code>spring-context</code> jar가 빌드되던 순간 우리 클래스는 세상에 없었습니다. 프레임워크는 언제나 응용 코드보다 먼저 컴파일되니까요.</p>
<p>즉 Spring은 자기가 모르는 타입을 다뤄야 하는 처지입니다. 이름조차 모르는 클래스를 찾아내고, 생성자가 뭘 요구하는지 알아내고, 인스턴스를 만들어야 합니다. 컴파일 타임에 존재하지 않던 정보를 실행 시점에 알아내는 것 — 이게 정확히 리플렉션이 하는 일입니다.</p>
<h3 id="그래서-이-거래는-남는-장사일까">그래서 이 거래는 남는 장사일까</h3>
<p>리플렉션은 공짜가 아닙니다. <strong>정적 타입 시스템의 안전망을 반납하고 유연성을 사는 거래</strong>입니다.</p>
<p>반납하는 쪽은 꽤 구체적입니다. <code>getMethod(&quot;getNmae&quot;)</code> 같은 오타를 컴파일러는 잡아주지 않습니다. IDE의 &quot;사용처 찾기&quot;에도 걸리지 않으니, 아무도 안 쓰는 줄 알고 지운 메서드가 운영에서 사고를 냅니다. 필드 이름을 리팩터링해도 문자열은 따라오지 않습니다. 컴파일러가 지켜주던 것들이 전부 실행 시점으로 미뤄지는 겁니다.</p>
<p>그런데도 프레임워크가 이 거래를 하는 이유는, <strong>프레임워크에겐 그 유연성이 곧 존재 이유이기 때문</strong>입니다. 모르는 타입을 다루지 못하는 프레임워크는 프레임워크가 아닙니다. 반대로 우리가 짜는 애플리케이션 코드에서는 <strong>대부분 손해입니다</strong>. 잃는 안전성에 비해 얻는 게 없으니까요. 서비스 코드 한복판에 <code>getDeclaredField</code>가 등장한다면, 대개는 설계를 다시 볼 신호입니다.</p>
<h3 id="마무리">마무리</h3>
<p>처음의 비유로 돌아가봅시다. 객체가 원본이고, 클래스 정보가 거울에 비친 상이라는 설명.</p>
<p>이제 보면 그 비유는 틀린 게 아니라 <strong>다른 질문에 답하고 있었습니다.</strong> &quot;리플렉션이 무엇을 보여주는가&quot;를 설명할 뿐, &quot;그걸 왜 봐야 하는가&quot;는 말해주지 않았던 거죠.</p>
<p>우리가 필요했던 답은 이겁니다. <strong>컴파일러가 모르는 것을 다루기 위해서.</strong></p>
<p>우리 코드는 프레임워크보다 나중에 태어납니다. 먼저 컴파일된 쪽은 나중에 올 클래스의 이름을 적어둘 수 없습니다. 그래서 이름 대신 <strong>약속</strong>을 남겨둡니다. <code>@Component</code>를 붙여두면 찾아주겠다, <code>@Autowired</code>를 붙여두면 꽂아주겠다는 약속. 그 약속을 실행 시점에 이행하는 도구가 리플렉션입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 코드 시대, 다 읽는 건 불가능하니 검증이라도 잘해볼까요? - Playwright E2E 테스트 도입기]]></title>
            <link>https://velog.io/@manofmen_yj/AI-%EC%BD%94%EB%93%9C-%EC%8B%9C%EB%8C%80-%EB%8B%A4-%EC%9D%BD%EB%8A%94-%EA%B1%B4-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%98%EB%8B%88-%EA%B2%80%EC%A6%9D%EC%9D%B4%EB%9D%BC%EB%8F%84-%EC%9E%98%ED%95%B4%EB%B3%BC%EA%B9%8C%EC%9A%94-Playwright-E2E-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@manofmen_yj/AI-%EC%BD%94%EB%93%9C-%EC%8B%9C%EB%8C%80-%EB%8B%A4-%EC%9D%BD%EB%8A%94-%EA%B1%B4-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%98%EB%8B%88-%EA%B2%80%EC%A6%9D%EC%9D%B4%EB%9D%BC%EB%8F%84-%EC%9E%98%ED%95%B4%EB%B3%BC%EA%B9%8C%EC%9A%94-Playwright-E2E-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Sat, 01 Aug 2026 05:26:36 GMT</pubDate>
            <description><![CDATA[<h2 id="커밋-60개-중에-내가-진짜-읽은-건-몇-줄일까"><em>커밋 60개 중에 내가 진짜 읽은 건 몇 줄일까?</em></h2>
<p>최근에 기존 외부 CS 서비스를 대체하기 위해 AI 기반 사내 CS 응대 자동화 봇을 직접 구축하는 프로젝트를 진행했습니다. 고객이 채팅을 보내면 Claude가 상담 초안을 생성하고, 상담원이 승인하면 고객에게 발송되는 구조입니다. 처음으로 프론트엔드까지 직접 맡아 전체 서비스를 구축한 프로젝트였습니다.</p>
<p>그런데 실제 커밋 로그는 60개 남짓이지만, 그 안의 코드를 한 줄씩 읽고 &quot;이게 맞다&quot;고 판단하며 넘긴 비율은 솔직히 <strong>절반도 되지 않습니다.</strong> 웹소켓 재연결 로직이나 훅의 의존성 배열, 백오프 계산 같은 것들은 코드를 완전히 이해했다기보다 실제로 동작하는 것을 확인하고 넘어간 경우도 많았습니다.</p>
<p>이것이 요즘 코드를 작성하는 방식의 실제 모습이라고 생각합니다. AI를 활용하면 구현 속도는 크게 빨라지지만, <strong>그만큼 작성된 코드의 모든 동작을 완전히 이해하는 것은 더 어려워지는 게 현실입니다.</strong></p>
<blockquote>
<p>그렇다면 AI를 사용하는 것을 지양해야 할까요?</p>
</blockquote>
<p><strong>저는 절대 그렇게 생각하지 않습니다.</strong> 이미 AI는 개발 과정에서 빼놓기 어려운 도구가 되었고, 굳이 그 흐름을 거스를 이유도 없습니다. 저 또한 AI가 없었다면 백엔드뿐 아니라 프론트엔드까지 포함한 전체 서비스를 기한 내에 혼자 구축한다는 것은 시도조차 하지 못했을 것입니다. 아마 아직도 개발 중이었을 것이고, CS 응대 역시 여전히 휴먼 리소스에 의존하고 있었겠죠.</p>
<p>이처럼 현재 소프트웨어 엔지니어링에서 AI가 가져다주는 생산성은 AI가 가진 단점들을 상쇄시킬 만큼 압도적입니다. 그렇기에 결국 중요한 것은 AI를 사용하지 않는 것이 아니라, <strong>AI가 만들어낸 코드를 어떻게 다룰 것인가 에 존재합니다.</strong></p>
<p>그래서 태도를 바꿨습니다. <strong>AI가 만들어준 코드를 모두 이해하려고 하기보다, 이해하지 못한 코드가 포함되어 있더라도 실제로 문제가 발생했을 때 확실하게 검증할 수 있는 방법을 만드는 것</strong>에 집중하기로 했습니다.</p>
<h3 id="1-무엇을-검증할-것인가--코드가-아니라-동작">1. 무엇을 검증할 것인가 — 코드가 아니라 동작</h3>
<blockquote>
<p><strong><em>소프트웨어가 제대로 동작하는지 판단해야 할 때, 가장 큰 확신을 주는 것이 바로 E2E 테스트 입니다.</em></strong>
— Ham Vocke, &quot;The Practical Test Pyramid&quot;, martinfowler.com (2018)</p>
</blockquote>
<p>코드를 읽는 것만으로는 판단할 수 없는 것들이 있습니다. 아래 코드는 실제로 저희 프로젝트에 있던 웹소켓 재연결 부분입니다.</p>
<pre><code class="language-ts">// 지터가 없으면 서버가 살아날 때 모든 클라이언트가 같은 순간에 몰린다
const wait = Math.min(1000 * 2 ** attempt++, BACKOFF_MAX_MS) * (0.8 + Math.random() * 0.4);</code></pre>
<p>솔직히 말씀드리면 이 줄은 처음에 읽고도 무슨 말인지 몰랐습니다. 지터가 무엇인지, 백오프가 무엇인지도 몰랐습니다. 그러나 이 코드를 이해하기 위해 관련 개념을 찾아보는 과정에서 생각보다 많은 것을 배웠는데, 이 이야기는 글의 마지막에서 다시 하겠습니다.</p>
<p>의도는 주석 덕분에 짐작할 수 있었지만, 코드만 봐서는 <strong>그게 실제로 지켜지는지는 읽어서 알 수 없었습니다.</strong> 또 다른 예시로는 다음과 같은 것들이 있습니다.</p>
<ul>
<li>재연결이 실제로 몇 초 뒤에 일어나는가</li>
<li>브라우저 탭이 백그라운드에 있다가 돌아왔을 때, 소켓이 정상적으로 복구되는가</li>
</ul>
<p>이건 코드가 아니라 브라우저가 대답할 문제입니다.</p>
<blockquote>
<p><em><strong>못 읽은 코드가 절반인 상태에서 &quot;이게 맞다&quot;고 말하려면, 그 근거는 코드가 아니라 실제 동작에서 나와야 했습니다</strong></em>.</p>
</blockquote>
<p>즉, 유닛 테스트를 먼저 작성하기보다 <strong>E2E 테스트에 우선순위</strong>를 두었습니다. 제가 확인하고 싶었던 것은 개별 함수가 예상대로 동작하는지가 아니라, 여러 시스템이 연결된 상태에서도 서비스가 브라우저 상에서 제대로 동작하는지였기 때문입니다. <strong>개별 함수를 아무리 꼼꼼히 검증해도, 시스템 간 연결 지점에서 생기는 문제는 유닛 테스트만으로 잡아내기 어렵습니다.</strong></p>
<p>테스트를 진행하기 위해 가장 먼저 한 일은 검증 범위부터 좁히는 것이었습니다. 실제 위험 구간은 아래 네 지점이 맞물리는 곳에 있습니다.</p>
<p><code>Lambda</code> ↔ <code>DynamoDB</code> ↔ <code>API Gateway WebSocket</code> ↔ <code>React</code></p>
<p>위의 네 가지 컴포넌트가 정상적으로 맞물려 동작하는지는 다음과 같은 질문 하나로 검증할 수 있었습니다.</p>
<blockquote>
<p>고객이 보낸 메시지가 <strong>빠짐없이</strong> 상담원 화면에 <strong>순서대로</strong> 뜨는가?</p>
</blockquote>
<p>검증할 것은 그 외에도 많았지만, 첫 번째 E2E 테스트는 이 질문 하나면 충분했습니다.</p>
<h3 id="2-어떻게-검증할-것인가--실제-사용자-흐름을-재현하기">2. 어떻게 검증할 것인가 — 실제 사용자 흐름을 재현하기</h3>
<p>검증 대상이 브라우저에서 발생하는 실제 동작까지 포함하고 있었기 때문에 <strong>Playwright</strong>를 사용했습니다.</p>
<p><strong>API를 직접 호출해서 응답이 정상인지 확인하는 것만으로는 부족했습니다.</strong> 제가 확인하고 싶은 것은 고객의 메시지가 서버를 거쳐 WebSocket으로 전달되고, 최종적으로 상담원 화면에 표시되는 전체 흐름이었기 때문입니다.</p>
<p>테스트의 시작점은 개별 함수가 아니라 <strong>사용자의 행동</strong>입니다. 검증해야 하는 흐름 자체는 단순했습니다.</p>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/ec9c70d6-9bf4-46fc-898a-a7dafe3fbbe3/image.png" alt=""></p>
<p>고객이 메시지를 보내고, 서버에서 이를 처리한 뒤, WebSocket을 통해 상담원 화면에 전달합니다. 그리고 마지막으로 실제 브라우저에서 해당 메시지가 화면에 나타났는지를 확인합니다.</p>
<p>이렇게 하면 함수 하나가 정상적으로 실행됐는지만 확인하는 것이 아니라, <code>Lambda</code> ↔ <code>DynamoDB</code> ↔ <code>API Gateway WebSocket</code> ↔ <code>React</code>까지 여러 컴포넌트가 연결된 상태에서 사용자가 기대하는 결과가 실제로 발생했는지를 검증할 수 있습니다.</p>
<p>여기서 중요한 원칙이 하나 있습니다.</p>
<blockquote>
<p>** 테스트는 내부 구현을 몰라도 됩니다. 사용자가 보는 결과만 알면 됩니다. **</p>
</blockquote>
<p>예를 들어 이런 것들은 테스트가 알 필요가 없습니다.</p>
<ul>
<li>메시지를 전달하는 과정에서 내부적으로 어떤 함수를 호출하는가</li>
<li>React에서 어떤 상태(state)를 업데이트하는가</li>
</ul>
<p>테스트가 확인할 것은 하나뿐이었습니다.</p>
<blockquote>
<p>고객이 보낸 메시지가 <strong>빠짐없이</strong> 상담원 화면에 <strong>순서대로</strong> 뜨는가?</p>
</blockquote>
<p>검증 대상을 이렇게 <strong>사용자 행동과 결과</strong>로 제한했습니다.</p>
<p><strong>그리고 실제로 테스트를 실행해보니, 코드만 읽었을 때는 보이지 않았던 문제들이 하나씩 드러나기 시작했습니다.</strong></p>
<h3 id="3-문제는-코드에-있지-않았습니다">3. 문제는 코드에 있지 않았습니다</h3>
<p>첫 번째 E2E 테스트를 실행했습니다.</p>
<p>고객이 메시지를 여러 개 빠르게 보내자 처음에는 상담원 화면에 정상적으로 표시됐습니다.
<img src="https://velog.velcdn.com/images/manofmen_yj/post/5fefb3a6-0ef6-48de-a983-7ded7d26b1d1/image.png" alt=""></p>
<p>그런데 페이지를 새로고침하자 결과가 달라졌습니다.
<img src="https://velog.velcdn.com/images/manofmen_yj/post/aa8d4dad-7424-4507-946c-9106bfdddb82/image.png" alt=""></p>
<p>처음에는 프론트엔드에서 메시지 순서를 잘못 처리하고 있는 문제라고 생각했습니다. 그런데 새로고침 전후의 데이터를 비교해보니, 화면이 임의로 순서를 바꾸고 있는 것이 아니었습니다.</p>
<p>새로고침하면 서버에서 메시지를 다시 받아오고 있었고, 그때 전달되는 메시지 자체의 순서가 처음 화면과 달랐습니다.</p>
<p><strong>원인을 따라가 보니 고객이 메시지를 보낸 순서와 서버가 메시지를 처리하는 순서가 달라질 수 있다는 것이 문제였습니다.</strong></p>
<p>서버리스 환경에서는 여러 요청이 각각의 Lambda에서 병렬로 처리될 수 있습니다. 따라서 고객이 msg1 → msg2 → msg3 순서로 메시지를 보내더라도, 서버에서는 다른 순서로 처리될 수 있었던 것입니다.</p>
<p><strong>결국 낙관적 UI에서는 클라이언트가 보낸 순서대로 보이지만, 새로고침하면 서버가 처리한 순서가 다시 나타나면서 같은 대화인데도 메시지 순서가 달라지는 문제가 발생한 것이었습니다.</strong></p>
<p><strong>코드를 읽는 것만으로는 쉽게 발견하기 어려운 문제였습니다.</strong> 실제 사용자처럼 메시지를 빠르게 여러 개 보내고 페이지를 새로고침해보면서 비로소 확인할 수 있었습니다.</p>
<h3 id="4-해결과-재검증">4. 해결과 재검증</h3>
<p>문제의 원인은 고객이 여러 메시지를 빠르게 보내면 서버에서 요청이 병렬로 처리될 수 있다는 것이었습니다.</p>
<p><strong>그렇다면 클라이언트에서부터 메시지를 순차적으로 보내면 됩니다.</strong> 그래서 메시지 전송 큐를 도입했습니다.</p>
<p>여러 메시지를 빠르게 입력하더라도 서버에는 한 번에 하나씩 전송하고, 앞선 메시지의 처리가 끝난 뒤 다음 메시지를 전송하도록 변경했습니다.</p>
<pre><code>msg1 → 처리 완료
          ↓
       msg2 → 처리 완료
                  ↓
               msg3 → 처리 완료</code></pre><p>이제 고객이 msg1 → msg2 → msg3 순서로 메시지를 보내면 서버에서도 같은 순서로 처리할 수 있습니다.</p>
<p>수정 후 동일한 E2E 테스트를 다시 실행했습니다.</p>
<p>메시지를 빠르게 여러 개 보내고 페이지를 새로고침하는 상황까지 다시 재현했습니다. 이번에는 새로고침 이후에도 메시지가 처음과 동일한 순서로 표시됐습니다.</p>
<p><strong>문제를 발견한 테스트를 그대로 다시 실행해 문제가 사라졌는지 확인하는 것.</strong></p>
<p>제가 생각한 E2E 테스트의 가장 중요한 역할은 바로 이것이었습니다.</p>
<h3 id="5-검증만으로는-부족한-것">5. 검증만으로는 부족한 것</h3>
<p>1장에서 인용했던 웹소켓 재연결 코드로 다시 돌아가 보겠습니다.</p>
<pre><code class="language-ts">// 지터가 없으면 서버가 살아날 때 모든 클라이언트가 같은 순간에 몰린다
const wait = Math.min(1000 * 2 ** attempt++, BACKOFF_MAX_MS) * (0.8 + Math.random() * 0.4);</code></pre>
<p>저 한 줄을 에이전트에게 설명해 달라고 했더니 이런 답이 왔습니다.</p>
<blockquote>
<p>지터가 없으면 백오프만으로는 재시도가 동기화되어…</p>
</blockquote>
<p>앞에서 말씀드렸듯 저는 지터도, 백오프도 모르는 단어였습니다. 그래서 다시 물었습니다.</p>
<blockquote>
<p>백오프가 무엇인가.</p>
</blockquote>
<p>답을 받았는데, 그 안에 또 모르는 단어가 있었습니다.</p>
<blockquote>
<p>지터가 무엇인가.</p>
</blockquote>
<p>여기서 조금 이상하다는 생각이 들었습니다.</p>
<p><strong>모르는 상태에서 질문하면 질문이 줄어드는 게 아니라 오히려 갈라집니다.</strong> 답 하나에 모르는 단어가 두 개 들어 있으면 질문도 두 개가 됩니다. 아는 것이 적을수록 이 과정은 끝나지 않고, 답은 계속 받는데 정작 이해한 것은 많지 않은 상태가 쌓입니다.</p>
<p>그런데 진짜 문제는 그다음이었습니다.</p>
<p>지터가 무엇인지 이해하고 나서야 이런 질문이 생겼습니다.</p>
<blockquote>
<p><em><strong>그런데 지터라는 거, 정말 우리 서비스에 필요한 것 일까?</strong></em></p>
</blockquote>
<p>저희 서비스에 CS 문의를 남기는 고객은 많아야 하루 10명 정도입니다. 같은 시간대에 CS 채널에 접속해 있는 클라이언트는 많아봤자 한두 명이 전부입니다. </p>
<p>지터는 수많은 클라이언트의 재시도 시점을 분산시켜 특정 순간에 요청이 몰리는 것을 완화하기 위한 기법입니다. 코드 자체가 틀린 것은 아니지만, 이 규모에서는 분산시킬 몰림 자체가 생기지 않습니다.</p>
<p>이건 에이전트가 먼저 말해주지 않았습니다. 말해줄 수도 없습니다. 동시 접속자가 몇 명인지, 트래픽이 어느 정도인지 같은 정보는 코드가 아니라 <strong>서비스의 맥락</strong>에 있기 때문입니다.</p>
<p>그리고 앞에서 그렇게 공들여 만든 E2E 테스트도 이 문제는 잡아내지 못합니다. 지터가 있든 없든 웹소켓 재연결이라는 동작 자체는 정상적으로 이루어지기 때문입니다.</p>
<p>결국 AI가 만든 코드에는 서로 다른 종류의 문제가 섞여 들어옵니다.</p>
<p><strong>동작이 깨지는 문제는 E2E 테스트로 잡을 수 있습니다.</strong> 이 글 앞부분에서 발견했던 메시지 순서 문제가 그랬습니다.
<strong>반면 동작은 정상인데 우리 상황에 맞지 않는 문제는 검증만으로 잡기 어렵습니다.</strong> 결국 사람이 읽고 판단해야 합니다. 그리고 읽으려면 그 코드가 무엇을 의미하는지도 알아야 합니다.</p>
<p><strong>AI가 대신 쳐주는 건 코드이지, 판단이 아닙니다.</strong></p>
<p>그래서 AI 시대에도 학습은 필요하다고 생각합니다. AI를 잘 활용하기 위해서라도, AI가 만들어낸 코드를 이해하고 그게 우리 서비스에 정말 필요한지 판단할 수 있을 만큼은 계속 배워야 합니다.</p>
<h3 id="마치며">마치며</h3>
<p>아직 추가해야 할 테스트도 많습니다. 이번에 발견한 문제 외에도 실제 사용자 흐름에서 발생할 수 있는 예외 상황들을 계속 찾아보고, 필요한 테스트를 하나씩 추가해나갈 예정입니다.</p>
<p>결론은 처음 마음먹은 그대로입니다.</p>
<p><strong>AI가 쓴 코드를 다 읽는 것은 이제 현실적으로 어렵습니다.</strong> 이것을 인정하지 않으면 &quot;리뷰했다&quot;는 말 자체가 거짓말이 될 수 있습니다.</p>
<p><strong>대신 바꿀 수 있는 것은 검증의 단위입니다.</strong> 리뷰의 단위를 diff에서 관측 가능한 동작으로 옮기는 것입니다.</p>
<p>코드 수백 줄을 읽고 &quot;이게 맞다&quot;고 판단하는 대신, 그 코드가 만들어낸 동작을 통해 <strong>&quot;고객 10명분의 메시지가 상담원 화면에 빠짐없이 표시된다&quot;</strong>고 확인할 수 있다면, 읽지 못한 수천 줄에 대해서도 적어도 그 동작만큼은 확실하게 말할 수 있습니다.</p>
<p>물론 이것만으로 충분한 것은 아닙니다. 어떤 부분을 검증해야 하는지, 무엇을 테스트해야 의미가 있는지, 그리고 테스트로 잡히지 않는 것은 무엇인지 판단하려면 결국 사람이 알아야 합니다.</p>
<p>AI가 코드를 대신 작성해주는 시대일수록, 개발자의 역할은 코드를 직접 작성하는 것에서 끝나지 않습니다. 그 코드가 무엇을 의미하는지 이해하고, 필요한 것을 판단하고, 결과를 검증할 수 있어야 합니다.</p>
<p><strong>그래서 다 읽는 것은 포기하되, 적어도 어디를 읽어야 할지는 스스로 판단할 수 있는 개발자가 되려고 합니다.</strong></p>
<br>
참고 영상

<p>NAVER D2 - AI 에이전트를 위한 Playwright E2E 테스트 하네스 구축하기 <a href="https://youtu.be/wo0Rsh9hlTo?si=yi8hSl5UcFD1PtGJ">https://youtu.be/wo0Rsh9hlTo?si=yi8hSl5UcFD1PtGJ</a>
미드나잇 로그 Midnight Log - 노션 개발자는 코드를 이렇게 읽습니다
<a href="https://youtu.be/iv60GIHpijE?si=Pr1GgLpBsQaCd6la">https://youtu.be/iv60GIHpijE?si=Pr1GgLpBsQaCd6la</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI가 짜준 코드, 읽어야 할까?]]></title>
            <link>https://velog.io/@manofmen_yj/AI%EA%B0%80-%EC%A7%9C%EC%A4%80-%EC%BD%94%EB%93%9C-%EC%9D%BD%EC%96%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@manofmen_yj/AI%EA%B0%80-%EC%A7%9C%EC%A4%80-%EC%BD%94%EB%93%9C-%EC%9D%BD%EC%96%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sat, 01 Aug 2026 02:08:43 GMT</pubDate>
            <description><![CDATA[<p>ㅇㅁㄴㅇㅁ
브라우저 직접 조작
느림
비결정적</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[애플리케이션 재배포 및 DB 조회 없이 변경된 외부의 값을 가져와서 코드에 반영하는 법]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%9E%AC%EB%B0%B0%ED%8F%AC-%EB%B0%8F-DB-%EC%A1%B0%ED%9A%8C-%EC%97%86%EC%9D%B4-%EB%B3%80%EA%B2%BD%EB%90%9C-%EC%99%B8%EB%B6%80%EC%9D%98-%EA%B0%92%EC%9D%84-%EA%B0%80%EC%A0%B8%EC%99%80%EC%84%9C-%EC%BD%94%EB%93%9C%EC%97%90-%EB%B0%98%EC%98%81%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@manofmen_yj/%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%9E%AC%EB%B0%B0%ED%8F%AC-%EB%B0%8F-DB-%EC%A1%B0%ED%9A%8C-%EC%97%86%EC%9D%B4-%EB%B3%80%EA%B2%BD%EB%90%9C-%EC%99%B8%EB%B6%80%EC%9D%98-%EA%B0%92%EC%9D%84-%EA%B0%80%EC%A0%B8%EC%99%80%EC%84%9C-%EC%BD%94%EB%93%9C%EC%97%90-%EB%B0%98%EC%98%81%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Tue, 17 Dec 2024 15:26:02 GMT</pubDate>
            <description><![CDATA[<p>Spring Cloud Config
Amazon Systems Manager
Redis
그리고 AWS S3도 써도 가능할 것 같음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소공 - 객체지향]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%86%8C%EA%B3%B5-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5</link>
            <guid>https://velog.io/@manofmen_yj/%EC%86%8C%EA%B3%B5-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5</guid>
            <pubDate>Wed, 27 Nov 2024 15:43:46 GMT</pubDate>
            <description><![CDATA[<p>d</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TCB]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%99%80-%EB%82%98-TCB%EB%9D%BC%EB%8A%94%EA%B1%B0-%EC%B2%A8-%EB%93%A4%EC%96%B4%EB%B4%84</link>
            <guid>https://velog.io/@manofmen_yj/%EC%99%80-%EB%82%98-TCB%EB%9D%BC%EB%8A%94%EA%B1%B0-%EC%B2%A8-%EB%93%A4%EC%96%B4%EB%B4%84</guid>
            <pubDate>Thu, 14 Nov 2024 17:25:22 GMT</pubDate>
            <description><![CDATA[<p>면접 준비로 운영체제를 공부하던 중에 갑자기 몇가지 궁금한 점이 생겨버림</p>
<blockquote>
<ol>
<li>아니 PCB는 Process의 상태를 저장하고 복원하는데 쓰이는 자료구조인데, 만약에 같은 Process에 존재하는 Thread간에 Context Switching이 발생하는 경우에는 어떻게 상태를 추적할 것인가?<br></li>
<li>실행되는 작업의 최소 단위는 Process가 아니라 Thread라는 것을 보면 Context Switching의 실질적인 기준이 되는 것도 Thread 여야 되는 것 아닌가? <br></li>
<li>근데 PCB가 이러한 것들을 다 감당하는 자료구조인가?</li>
</ol>
</blockquote>
<p><strong>그래서 검색해본 결과 TCB라는 새로운 자료구조를 알게되었음</strong></p>
<h4 id="tcb---thread-control-block">TCB - Thread Control Block</h4>
<p>Thread의 상태를 저장하는 자료구조
다음과 같이 PCB에 연결된 리스트에 존재함
<img src="https://velog.velcdn.com/images/manofmen_yj/post/f60e4c8e-ecc1-466b-a6dc-82613926df94/image.png" alt=""></p>
<p>Thread Context Switching이 발생하면</p>
<blockquote>
<p>Thread A가 CPU 사용중
Thread A의 상태 정보를 TCB A에 저장
Thread A가 속한 Process A의 상태 정보 PCB A에 저장
Thread B가 속한 Process B의 상태 정보 PCB B 로드
Thread B의 TCB B 로드
Thread B가 CPU 사용</p>
</blockquote>
<p>과 같은 과정이 실행된다고 한다 그리고 gpt한테 물어봤는데 Context Switching의 실질적인 기준이 되는것도 Thread가 맞다 함 아 근데 먼가 확실ㄹ하지 않고 애매한거 같아서 더 찾아보고 공부해바야 될듯</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비트마스킹]]></title>
            <link>https://velog.io/@manofmen_yj/%EB%B9%84%ED%8A%B8%EB%A7%88%EC%8A%A4%ED%82%B9</link>
            <guid>https://velog.io/@manofmen_yj/%EB%B9%84%ED%8A%B8%EB%A7%88%EC%8A%A4%ED%82%B9</guid>
            <pubDate>Thu, 31 Oct 2024 15:35:12 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-py">from collections import deque
n = int(input())
graph=[]
for i in range(n):
    graph.append(list(map(int , list(input()))))

q = deque()

visited_mask = 1

visited_dict={}
visited_dict[1]=1
q.append([0,visited_mask,1,0])
answer= 0

while q:
    cur = q.pop()
    seller = cur[0]
    mask = cur[1]
    count = cur[2]
    amount = cur[3]
    answer = max(answer,count)
    for i in range(n):
        if graph[seller][i]&gt;=amount and mask &amp; (1&lt;&lt;i)==0:
            new_mask = mask | (1&lt;&lt;i)
            q.append([i,new_mask,count+1,graph[seller][i]])

print(answer)
</code></pre>
<p>방문 상태를 저장 할 경우 배열로 선언하는 것보다 비트마스킹 형태의 정수로 저장하면 공간복잡도 측면에서 효율적으로 코드 작성 가능</p>
<ul>
<li>추가 
파이썬에서 딕셔너리의 특정 키 존재 유무 파악할 때 그냥 in 연산자 적어주면 된다. 시간복잡도 o(1)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[딕셔너리로 비트스트림 구현]]></title>
            <link>https://velog.io/@manofmen_yj/%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC%EB%A1%9C-%EB%B9%84%ED%8A%B8%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@manofmen_yj/%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC%EB%A1%9C-%EB%B9%84%ED%8A%B8%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Tue, 22 Oct 2024 07:44:56 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-py">arr =[[{k:0 for k in range(3)} for i in range(3)] for j in range(3)]
</code></pre>
<p>비트스트림에서 i번째 인덱스의 값을 딕셔너리의 키가 i일때 value 값을 구하는 방식으로 구현</p>
<p>훨씬 직관적인듯</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소공]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%86%8C%EA%B3%B5</link>
            <guid>https://velog.io/@manofmen_yj/%EC%86%8C%EA%B3%B5</guid>
            <pubDate>Mon, 21 Oct 2024 02:37:09 GMT</pubDate>
            <description><![CDATA[<h4 id="가용도">가용도</h4>
<p>mtbf = 고장률의 역수</p>
<ul>
<li>고장률 = 고장건수 / 총가동시간</li>
</ul>
<p>mttf = 총가동시간 / 고장시간</p>
<p>mttr = 총고장시간 / 고장건수</p>
<p>가용도 = mttf / mtbf</p>
<h4 id="cpm-네트워크">cpm 네트워크</h4>
<h4 id="타당성-3가지">타당성 3가지</h4>
<p>기술적 타당성 = 프로젝트가 기술적으로 가능한지
경제적 타당성 = 프로젝트가 경제적으로 가능한지 (이익을 창출할 수 있을지)
운영적 타당성 = 프로젝트가 실제로 사용 가능한지 (조직과 잘 맞음? 클라이언트가 사용 가능?)</p>
<h4 id="유스케이스-다이어그램">유스케이스 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/212382b1-b1fc-456a-80c9-97a9f1898f42/image.png" alt=""></p>
<h4 id="통신-다이어그램">통신 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/9a820af2-86b1-4c44-bfd8-c866d9513f7b/image.png" alt=""></p>
<ul>
<li>직사각형 : 객체</li>
<li>선 : 관계</li>
<li>화살표 : 메세지가 흐르는 방향</li>
<li>번호 : 메세지 전송 순서</li>
</ul>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/5268d768-5726-4019-912f-d51bab5009e9/image.png" alt=""></p>
<h4 id="순차-다이어그램">순차 다이어그램</h4>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/85a30600-eb3a-4e42-bf9a-32d0c4cd4177/image.png" alt=""></p>
<h4 id="비용-산정-기법">비용 산정 기법</h4>
<ul>
<li>상향식<ul>
<li>loc = 라인 / (생산성 * 프로그래머)</li>
<li>cocomo<ul>
<li>조직형 = 조직형 (소규모 소프트웨어, 비즈니스 로직)</li>
<li>반분리형 = 30만 라인 이하</li>
<li>내장형 = 30만 라인 이상 (운영체제, 대규모 트랜잭션)</li>
</ul>
<hr>
<ul>
<li>기본형 = 라인 수와 개발 유형 사용해서 비용 산정</li>
<li>중간형 = 제품, 컴퓨터, 개발자 특성 고려해 비용 산정</li>
<li>발전형 = 개발 공정별로 더욱 자세하게 비용 산정</li>
</ul>
<hr>
<ul>
<li>보정 후 기능 점수 = 기본 점수 * 보정 인자</li>
</ul>
</li>
</ul>
</li>
<li>하향식 (전문가들)</li>
</ul>
<h4 id="dfd">dfd</h4>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/a18242f2-127a-488f-8668-29268da69bfa/image.png" alt=""></p>
<ul>
<li>data store = db테이블</li>
<li>terminator = 대상</li>
<li>process = 수행</li>
<li>data flow = 데이터 이동</li>
</ul>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/2a324679-ea32-4af3-9f2c-13674980fa2f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인터뷰 키워드 - 운영체제]]></title>
            <link>https://velog.io/@manofmen_yj/cs-%EB%A9%B4%EC%A0%91-%EB%8C%80%EB%B9%84-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C</link>
            <guid>https://velog.io/@manofmen_yj/cs-%EB%A9%B4%EC%A0%91-%EB%8C%80%EB%B9%84-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C</guid>
            <pubDate>Wed, 16 Oct 2024 16:02:44 GMT</pubDate>
            <description><![CDATA[<p>프로세스
프로세스 제어블록
코드 데이터 힙 스택
스레드
멀티 프로세스
문맥교환
IPC
멀티 스레드
동기화
공유 자원과 경쟁조건</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬된 배열의 대소 비교를 할 시 이분탐색]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%A0%95%EB%A0%AC%EB%90%9C-%EB%B0%B0%EC%97%B4%EC%9D%98-%EB%8C%80%EC%86%8C-%EB%B9%84%EA%B5%90%EB%A5%BC-%ED%95%A0-%EC%8B%9C-%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@manofmen_yj/%EC%A0%95%EB%A0%AC%EB%90%9C-%EB%B0%B0%EC%97%B4%EC%9D%98-%EB%8C%80%EC%86%8C-%EB%B9%84%EA%B5%90%EB%A5%BC-%ED%95%A0-%EC%8B%9C-%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89</guid>
            <pubDate>Fri, 11 Oct 2024 11:41:46 GMT</pubDate>
            <description><![CDATA[<p>a 배열의 특정 값이 b 배열의 값들 중 몇개의 값보다 더 큰 지 알고싶을때 이분탐색 ㄱ
그러면 특정 값에서 break가 걸리는 순간에, 해당 값의 index를 읽어주면 됨</p>
<p>bisect 라이브러리 추가 공부 ㄱ</p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/258709">https://school.programmers.co.kr/learn/courses/30/lessons/258709</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Node.js는 어떻게 비동기 작업을 하는거임]]></title>
            <link>https://velog.io/@manofmen_yj/Node.js%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%ED%95%98%EB%8A%94%EA%B1%B0%EC%9E%84</link>
            <guid>https://velog.io/@manofmen_yj/Node.js%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%ED%95%98%EB%8A%94%EA%B1%B0%EC%9E%84</guid>
            <pubDate>Wed, 25 Sep 2024 20:19:27 GMT</pubDate>
            <description><![CDATA[<p>Node.js는 싱글스레드 기반으로 동작함. 싱글스레드로 동작한다는 소리는 한 번에 하나의 작업만 실행할 수 있다는 것을 의미함. 하지만 async/await 구문과 promise 객체의 존재를 보면 알 수 있듯이 우리의 javascript 친구는 비동기작업을 아주 기가막히게 처리해줌. 대체 어떻게 하는걸까?</p>
<h3 id="이벤트-루프">이벤트 루프</h3>
<p>자바 스크립트 코드 실행은 스레드에서 담당하고 비동기작업은 이벤트 루프에 등록되어 백그라운드엣에서 실행됨 이벤트 루프는 요청을 처리하면서 블로킹 작업을 피하고 (non-blocking), 작업이 완료될 때까지 기다리는 대신 다른 작업을 계속 진행할 수 있게 함. 콜 스택 과 태스크 큐를 사용</p>
<blockquote>
<h4 id="콜-스택-call-stack">콜 스택 (call stack)</h4>
<p>자바스크립트 코드가 실행되는 공간. 코드가 동기적이면 콜 스택에서 순차적으로 처리됨</p>
<h4 id="태스크-큐-task-queue">태스크 큐 (task queue)</h4>
<p>비동기 작업(예: 네트워크 요청, 파일 읽기, 데이터베이스 접근 등)은 콜 스택에서 바로 처리되지 않고 백그라운드에서 실행. 백그라운드에서 실행된 작업이 완료되면 그 결과가 태스크 큐에 담김
*<em>태스크 큐에 쌓인 작업은 콜 스택이 비어 있을 때 이벤트 루프가 이를 콜 스택으로 다시 가져와 실행함
*</em></p>
</blockquote>
<br>
<br>
<br>
습관적으로 사용하던 것의 원리를 개념적으로 이해하게 되는 순간은 언제나 즐거운 것 같습니다.
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 통신 vs Socket 통신]]></title>
            <link>https://velog.io/@manofmen_yj/HTTP-%ED%86%B5%EC%8B%A0-vs-Socket-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@manofmen_yj/HTTP-%ED%86%B5%EC%8B%A0-vs-Socket-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Sat, 07 Sep 2024 16:51:37 GMT</pubDate>
            <description><![CDATA[<p>운영체제 공부하던 중 다음과 같은 내용을 알게됨 </p>
<blockquote>
<p>소켓 통신은 서로 다른 컴퓨터에 있는 프로세스 사이에 데이터를 주고 받기위해 사용되는 기술이다. 소켓 통신은 클라이언트와 서버가 실시간으로 데이터를 주고 받을 수 있는 양방향 통신으로 채팅, 실시간 스트리밍 등에 사용된다. 소켓 통신은 IP와 포트번호를 통해 어떤 컴퓨터의 어떤 프로세스와 통신할지 구별한다</p>
</blockquote>
<p>근데 갑자기 다음과 같은 궁금증이 생겨버림</p>
<blockquote>
<p><em>아니 근데 내가 알기로는 HTTP 통신도 서버랑 클라이언트가 실시간으로 데이터 주고 받는 기술이고, HTTP 통신도 IP와 포트번호를 통해 어떤 컴퓨터의 어떤 프로세스와 통신할지 구별하는데, 둘이 같은거 아님?</em></p>
</blockquote>
<p>그래서 찾아보니까 HTTP 통신은 소켓 통신이 맞음. TCP 계층에 올라간 HTTP 또한 소켓 통신과 같은 방식을 사용함. 즉 HTTP 통신은 소켓 통신에 일종이라고 볼 수 있으나, 소켓 통신은 HTTP 통신이 아님</p>
<h4 id="추가">추가</h4>
<ul>
<li>HTTP 통신은 클라이언트의 요청이 들어왔을 때 서버가 응답하는 단방향 통신, 서버는 응답하면 클라이언트와 커넥션을 끊어버림. 즉 *<em>실시간 통신이 아님 *</em> -&gt; 그러나 현재는 Keep Alive 옵션을 통해 커넥션 유지 가능 즉 근본자체는 실시간 통신이 아니기 때문에 내가 기존에 알고있던 내용은 틀린 내용</li>
<li>초기에는 HTML 파일만 전송하였으나, 현재는 JSON, 이미지 파일 등도 전송 가능</li>
</ul>
<br>
<br>

<p>아 퇴사하고 오랜만에 공부하니까 개재밌네 ㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 구현 대비 달력]]></title>
            <link>https://velog.io/@manofmen_yj/%EB%B0%B1%EC%A4%80-%EA%B5%AC%ED%98%84-%EB%8C%80%EB%B9%84-%EB%8B%AC%EB%A0%A5</link>
            <guid>https://velog.io/@manofmen_yj/%EB%B0%B1%EC%A4%80-%EA%B5%AC%ED%98%84-%EB%8C%80%EB%B9%84-%EB%8B%AC%EB%A0%A5</guid>
            <pubDate>Fri, 27 Oct 2023 07:58:15 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-py">cal=[[0 for i in range(367)] for i in range(1000)]
dict={}
for i in range(2,14):
  dict[i]=0

# 여기에는 놓일 위치
work=[]
n=int(input())
for i in range(n):
  s,e=map(int, input().split())
  work.append([s,e])

work.sort(key=lambda x:(x[0],-x[1]))
for w in work:
  s,e=w[0],w[1]
  count=0
  for j in range(s,e+1):
    while cal[count][j]==1:
      count+=1
  for j in range(s,e+1):
    cal[count][j]=1

answer=0
ga,se=0,0
for j in range(367):
  flag=True
  count=0
  for i in range(1000):
    if cal[i][j]==1:
      flag=False
      count=i
  se=max(count,se)
  if flag:
    answer+=ga*(se+1)
    ga,se=0,0
  else:
    ga+=1


print(answer)





</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 연습문제 아이템줍기]]></title>
            <link>https://velog.io/@manofmen_yj/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C-%EC%95%84%EC%9D%B4%ED%85%9C%EC%A4%8D%EA%B8%B0</link>
            <guid>https://velog.io/@manofmen_yj/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C-%EC%95%84%EC%9D%B4%ED%85%9C%EC%A4%8D%EA%B8%B0</guid>
            <pubDate>Mon, 23 Oct 2023 09:30:51 GMT</pubDate>
            <description><![CDATA[<p>문제 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/87694">https://school.programmers.co.kr/learn/courses/30/lessons/87694</a></p>
<h3 id="접근">접근</h3>
<p>사각형을 하나씩 순회하면서 모서리 부분은 1, 안쪽 부분이거나 다른 사각형의 안쪽부분이라면 0으로 그래프 초기화</p>
<h3 id="코드">코드</h3>
<pre><code class="language-py">from collections import deque


def solution(rectangle, characterX, characterY, itemX, itemY):

    leng=max(list(map(max, rectangle)))*2
    graph=[[5 for i in range(leng+1)] for j in range(leng+1)]
    visited=[[0 for i in range(leng+1)] for j in range(leng+1)]
    answer=10000
    movelist=[[1,0],[-1,0],[0,-1],[0,1]]
    for re in rectangle:
        re=list(map(lambda x:x*2,re))
        for i in range(re[1],re[3]+1):
            for j in range(re[0],re[2]+1):
                if graph[i][j]!=0 and (i==re[1] or i==re[3] or j==re[0] or j==re[2]):
                    graph[i][j]=1
                else:
                    graph[i][j]=0

    curx,cury=characterX*2, characterY*2
    visited[cury][curx]=1
    itemx,itemy=itemX*2, itemY*2
    queue=deque()
    queue.append([curx,cury,0])

    while queue:
        curx,cury,curd=queue.popleft()
        if curx==itemx and cury==itemy:
            answer=min(answer,curd//2)

        for mo in movelist:
            nx,ny=curx+mo[0],cury+mo[1]
            if 0&lt;=nx&lt;leng+1 and 0&lt;=ny&lt;leng+1 and visited[ny][nx]==0 and graph[ny][nx]==1:
                queue.append([nx,ny,curd+1])
                visited[ny][nx]=1

    return answer</code></pre>
<h3 id="참고">참고</h3>
<p>실제 문제의 좌표평면을 그래프상의 숫자 0과1로 옮길떄 주의해야 할 점은 다음과 같다</p>
<p>좌표평면의 ㄷ자 모양과 ㅁ자 모양을 그래프로 옮길 경우 두 그래프의 형태 모두
1 1
1 1
로 표현된다. 그렇기 떄문에 위와같은 문제를 해결할 떄는 그래프의 좌표를 2배로 늘려주는 방식 또한 고려해야한다. 만약 위 경우를 그래프를 두배로 늘렸을 경우로 표현한다면 </p>
<p>1 1 1
0 0 1
0 0 0 과
1 1 1
1 1 1
1 1 1로 정상적으로 구별이 가능해진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인프로젝트 1차 리팩토링 ]]></title>
            <link>https://velog.io/@manofmen_yj/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%B0%A8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@manofmen_yj/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%B0%A8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Tue, 17 Oct 2023 15:13:03 GMT</pubDate>
            <description><![CDATA[<h2 id="config">Config</h2>
<h3 id="security-config">Security Config</h3>
<h4 id="success-handler-추가">Success handler 추가</h4>
<h4 id="session-management">Session Management</h4>
<ul>
<li>최대 로그인 세션 1개로 제한 -&gt; 중복 로그인 제한<h4 id="formlogin">formLogin</h4>
</li>
<li>defaultsuccessurl 방식에서 successHandler 방식으로 변경, 
이게 인증,인가 다룰때 더 좋은듯<h4 id="oauth-login-속성-추가">oauth login 속성 추가</h4>
</li>
<li>사실 이번 리팩토링의 가장 큰 이유인 부분. 구글 로그인 연동 기능 추가 , -&gt;DB에도 정상적으로 연동된것 확인<h4 id="logout-handler-추가">logout handler 추가</h4>
</li>
</ul>
<h2 id="controller">Controller</h2>
<h4 id="main">Main</h4>
<ul>
<li>메인 컨트롤러에서 authenticated 또는 authorized된사용자 정보 1차적으로 불러오게끔 변경</li>
</ul>
<h4 id="member">Member</h4>
<ul>
<li>authenticated 유무에 따라서 redirect 되는 결과 변경</li>
</ul>
<h4 id="route">route</h4>
<ul>
<li>이번 리팩토링의 핵심 2, 이제 매 실행마다 이전 데이터 clear하고 실행되기 때문에 연속된 서비스 실행 가능</li>
</ul>
<h2 id="dto">DTO</h2>
<ul>
<li>이건 별거 없음 그냥 oauth 종속성 추가함에 따라 기존 DTO에 column 추가하고 oauth속성이랑 세션멤버 dto 생성한거?</li>
</ul>
<h2 id="entity">Entity</h2>
<ul>
<li>이것도 마찬가지 </li>
</ul>
<h2 id="service">Service</h2>
<ul>
<li>이거 진짜 개 큰일날뻔 한게 전체 프로젝트의 가장 중요한 부분 담당하는 service 두개가 bean으로 등록이 안되어있었음... 바로 어노테이션 추가함...지금까지 어떻게 정상적으로 실행되었는지는 잘 모르겠으나 되게 기특하다ㅠ</li>
</ul>
<h3 id="추가">추가</h3>
<p>이번에는 감사하게도 로컬상에서 커밋한 내용 그대로 배포단에서 정상적으로 실행이 됐다.. 잠깐 구글oauth 측에서 redirect uri mismatch 에러가 나긴 했는데 이것도 리디렉션 uri 조금 조물조물 거리니까 되었음. 아마 자바스크립트 원본에 ec2주소 안넣고 배포 주소 넣어서 그런듯?</p>
<p><br><br><br><br>
프론트는 변경한거 작성안함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[authentication instanceof OAuth2AuthenticationToken 그리고 authentication instanceof UsernamePasswordAuthenticationToken ]]></title>
            <link>https://velog.io/@manofmen_yj/authentication-instanceof-OAuth2AuthenticationToken-%EC%99%80authentication-instanceof-OAuth2AuthenticationToken</link>
            <guid>https://velog.io/@manofmen_yj/authentication-instanceof-OAuth2AuthenticationToken-%EC%99%80authentication-instanceof-OAuth2AuthenticationToken</guid>
            <pubDate>Mon, 16 Oct 2023 11:13:37 GMT</pubDate>
            <description><![CDATA[<p>ㅎㅇ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오 기출 - 기둥과 보 설치]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B8%B0%EC%B6%9C-%EA%B8%B0%EB%91%A5%EA%B3%BC-%EB%B3%B4-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@manofmen_yj/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B8%B0%EC%B6%9C-%EA%B8%B0%EB%91%A5%EA%B3%BC-%EB%B3%B4-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Mon, 16 Oct 2023 06:48:01 GMT</pubDate>
            <description><![CDATA[<p>문제 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/60061">https://school.programmers.co.kr/learn/courses/30/lessons/60061</a></p>
<h3 id="접근">접근</h3>
<p>기둥배열과 보 배열을 각 각 따로 이차원 배열로 생성해주었고. 기둥과 보의 시작점과 끝점을 각각 1로 설정해준후 구현하였음. </p>
<h3 id="1차-수정">1차 수정</h3>
<p>0ㅡ0ㅡ0 보가 이렇게 지어져있을 때나,
0ㅡxㅡ0 이렇게 지어져있을 때나 이차원 배열의 값은 같기 떄문에 구별이 안된다. 그리하여 시작점만 1로 설정해주는 식으로 변경하였다</p>
<h3 id="처음-코드">처음 코드</h3>
<pre><code class="language-py">def solution(n, build_frame):
    # 기둥은 바닥위에 있거나 보의 한 쪽 끝 부분 위에 있거나 다른 기둥위
    # 보는 한쪽 끝이 기둥 위 or 양쪽 끝부분이 다른 보
    gi=[[0 for i in range(n+1)] for j in range(n+1)]
    bo=[[0 for i in range(n+1)] for j in range(n+1)]

    for bu in build_frame:
        x,y,a,b=bu
        # x y는 교차점 좌표고 교차점 기준으로 보는 오른쪽 기둥은 위쪽
        if a==0:
            # 기둥
            if b==0:
                flag=True
                # first 위에 기둥
                if gi[y+1][x]==1:
                    if not (bo[y+1][x]==1 or bo[y+1][x-1]==1):
                        flag=False

                # second 오른쪽에 보
                if bo[y+1][x]==1:
                    if not(gi[y][x+1]==1 or (bo[y+1][x-1]==1 and bo[y+1][x+1]==1)):
                        flag=False

                # third 왼쪽에 보
                if bo[y+1][x-1]==1:
                    if not(gi[y][x]==1 or (bo[y+1][x-2]==1 and bo[y+1][x]==1)):
                        flag=False

                if flag:
                    gi[y][x]=0

            else:
                # 설치
                if y==0 or gi[y-1][x]==1 or bo[y][x]==1 or bo[y][x-1]==1:
                    # 바닥이거나 기둥위거나 보 위에
                    gi[y][x]=1
        else:
            if b==0:
                flag=True
                # 삭제
                # first 왼쪽에 기둥
                if gi[y][x]==1:
                    if not(bo[y][x]==1 or bo[y][x-1]==1 or y==0):
                        flag=False

                # second 오른쪽에 기둥
                if gi[y][x+1]==1:
                    if not(bo[y][x+1]==1 or bo[y][x]==1 or y==0):
                        flag=False

                # third 오른쪽에 보
                if bo[y][x+1]==1:
                    if not(gi[y-1][x+1]==1 or gi[y-1][x+2]==1):
                        flag=False

                # fourth 왼쪽에 보
                if bo[y][x-1]==1:
                    if not(gi[y-1][x-1]==1 or gi[y-1][x]==1):
                        flag=False
                if flag:
                    bo[y][x]=0
            else:
                if (bo[y][x+1]==1 and bo[y][x-1]==1) or gi[y-1][x+1]==1 or gi[y-1][x]==1:
                    # 다른 기둥의 한 쪽 위거나 양쪽이 보
                    bo[y][x]=1



    answer = []

    for i in  range(n+1):
        for j in range(n+1):
            if gi[i][j]==1:
                answer.append([j,i,0])
            if bo[i][j]==1:
                answer.append([j,i,1])
    answer.sort()
    return answer</code></pre>
<p>예제는 모두 통과하였으나 실 테스트 케이스는 반타작</p>
<h3 id="2차-수정">2차 수정</h3>
<p>초기코드에는 기둥 혹은 보를 삭제해줄때 해당 보 혹은 기둥많이 영향을 끼치는 부분만 고려하여 코드를 구현하였으나, 이부분에서 문제가 있었다.</p>
<p>인접 보와 기둥은 유지가 가능하더라도, 연쇄적으로 확장해봤을때 조건에 부합하지 않는 보와 기둥이 생길 수 도 있기때문에, 삭제 연산이 들어올 때마다 전체 기둥과 보의 적합성을 판단해야함</p>
<h3 id="수정된-코드">수정된 코드</h3>
<pre><code class="language-py">def delete(i,j,gb,gi,bo,n):
    if gb==0:
        # 기둥
        gi[i][j]=0
    else:
        # 보
        bo[i][j]=0
    count=0
    for y in range(n+1):
        for x in range(n+1):
            if gi[y][x]==1:
                if not(y==0 or gi[y-1][x]==1 or bo[y][x]==1 or bo[y][x-1]==1):
                    return False
            if bo[y][x]==1:
                if not((bo[y][x+1]==1 and bo[y][x-1]==1) or gi[y-1][x+1]==1 or gi[y-1][x]==1):
                    return False


    return True



def solution(n, build_frame):
    # 기둥은 바닥위에 있거나 보의 한 쪽 끝 부분 위에 있거나 다른 기둥위
    # 보는 한쪽 끝이 기둥 위 or 양쪽 끝부분이 다른 보
    gi=[[0 for i in range(n+1)] for j in range(n+1)]
    bo=[[0 for i in range(n+1)] for j in range(n+1)]

    for bu in build_frame:
        x,y,a,b=bu
        # x y는 교차점 좌표고 교차점 기준으로 보는 오른쪽 기둥은 위쪽
        if a==0:
            # 기둥
            if b==0:
                # 기둥 삭제
                if not delete(y,x,a,gi,bo,n):
                    gi[y][x]=1

            else:
                # 설치
                if y==0 or gi[y-1][x]==1 or bo[y][x]==1 or bo[y][x-1]==1:
                    # 바닥이거나 기둥위거나 보 위에
                    gi[y][x]=1
        else:
            if b==0:
                if not delete(y,x,a,gi,bo,n):
                    bo[y][x]=1
            else:
                if (bo[y][x+1]==1 and bo[y][x-1]==1) or gi[y-1][x+1]==1 or gi[y-1][x]==1:
                    # 다른 기둥의 한 쪽 위거나 양쪽이 보
                    bo[y][x]=1



    answer = []

    for i in  range(n+1):
        for j in range(n+1):
            if gi[i][j]==1:
                answer.append([j,i,0])
            if bo[i][j]==1:
                answer.append([j,i,1])
    answer.sort()
    return answer</code></pre>
<p><img src="https://velog.velcdn.com/images/manofmen_yj/post/702d2df3-81b4-408a-8da0-d1215317a21e/image.png" alt=""></p>
<h3 id="참고">참고</h3>
<p>사고 확장하기
데이터 그래프로 표현할때는 정확하게 되었는지 확인</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠키 세션 캐시가뭐임]]></title>
            <link>https://velog.io/@manofmen_yj/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%EC%BA%90%EC%8B%9C-%ED%86%A0%ED%81%B0%EC%9D%B4%EB%AD%90%EC%9E%84</link>
            <guid>https://velog.io/@manofmen_yj/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%EC%BA%90%EC%8B%9C-%ED%86%A0%ED%81%B0%EC%9D%B4%EB%AD%90%EC%9E%84</guid>
            <pubDate>Sat, 14 Oct 2023 11:42:12 GMT</pubDate>
            <description><![CDATA[<p>JWT에 대해서 알아보고자 깔짝깔짝 구글링을 하던 중 쿠키/세션/캐시에 대한 개념을 알아두면 좋을 것 같아서 정리함</p>
<h3 id="이것들-왜씀">이것들 왜씀?</h3>
<p>http는 기본적으로 통신이 완료되면 클라이언트와 관계를 냅다 끊어버림. 즉 데이터가 유지되지 않는다. 그래서 웹에서 뭐 작성하다 새로고침하면 다 날라가는거임ㅋㅋ.</p>
<blockquote>
<p> 그러나, 어떤 데이터들은 유지되어야 할 필요가 있다.</p>
</blockquote>
<p>예를들어, 우리가 특정 사이트에 로그인을 했는데, 새로고침 한번 했다고 로그아웃이 된다면 굉장히 어처구니가 없을것</p>
<p>그래서 이러한 데이터들을 브라우저등에 &quot;임시로 저장&quot; 하기 위해 사용되는 것이 바로 쿠키/세션/캐시/토큰이라고 할 수 있다~</p>
<h3 id="쿠키">쿠키</h3>
<blockquote>
<p>쿠키는 웹사이트 접속시 내 컴퓨터 혹은 웹 브라우저에 저장되는 작은 텍스트 조각이다.
(크롬일 경우 브라우저에, IE일 경우 내컴퓨터에 저장됨)</p>
</blockquote>
<p>쿠키에는 서버에서 유저를 식별할 수 있는 데이터들이 저장되며, 지정된 만료일이 지나면 삭제된다.</p>
<p>쿠키는 사용자가 수정및 삭제할 수 있고, 제 3자가 조회할 수 있기 때문에 가벼운 정보들이 저장되며 보안상 민감한 정보는 저장되지 않는다. 예를 들어 검색내역, 장바구니 내역등은 제3자에게 보여져도 보안상 큰 타격이 없지만, 사용자의 개인정보 내용등은 타인에게 보여지면 안된다.</p>
<h3 id="캐시">캐시</h3>
<blockquote>
<p>캐시란 자주 사용하는 데이터나 값을 미리 복사해놓는 임시 장소</p>
</blockquote>
<ul>
<li>(캐시는 인터넷 환경 뿐만 아니라 다양한 곳에서 사용되는 개념. 컴퓨터 하드웨어 안에서도 정보를 더 빨리 가져오기 위한 cpu캐시등이 있음)</li>
</ul>
<p>데이터를 서버에 요청하는 과정에는 시간 및 자원이 소모된다. 그러므로 반복적으로 쓰이는 데이터는 매번 서버에 요청하는 것 보다 저장해놨다가 쓰는 것이 효율적. 이때 쓰이는 것이 캐시. 예를 들어 프로필 사진같은 경우는 짧은 시간 단위로 변화하지 않기 때문에, (여기서 짧은 시간은 밀리초 혹은 초 단위) 매번 서버에 요청하는 것 보다는 캐시에서 가져오는 것이 더 효율적임. 그래서 가끔 프로필 사진 바꿔도 새로고침 광클해야 변하는 경우 있는데 그게 프로필 사진이 캐싱돼서 그런거임 ㅇㅇ</p>
<h3 id="세션">세션</h3>
<blockquote>
<p>세션은 서버가 유저를 구분하기 위해 사용되는 방법이다.</p>
</blockquote>
<p>우리가 특정 사이트에 접속하여 글을 쓰기 위해 로그인을 하였다고 가정하자. 그러나 기본적으로 사이트는 로그인을 하기 이전의 사용자와 로그인을 한 이후의 사용자가 동일 인물이라는 것을 알지 못한다. </p>
<p>그렇기 때문에 새로은 글을 작성할 때마다 로그인을 해야한다. 하지만 실제로 우리가 사이트를 이용할때 그런 번거로운 과정을 다 거치지 않는다. 그이유가 바로 &quot;세션&quot;때문이다.</p>
<p>유저가 로그인에 성공하면 서버는 세션 아이디를 사용자에게 전달하고, 사용자는 세션아이디를 쿠키에 저장한다.(그래서 크롬 종료해도 로그인 유지되는거) 서버가 유저로부터 요청을 받을 때 마다 이 세션아이디를 통해 누구의 계정인지 확인하고 요청을 수행함. (영화관에서 티켓내면 보관용만 찢어서 다시 주는거처럼)</p>
<h3 id="토큰">토큰</h3>
<p>이건 나중에 JWT하면서 ㄱ</p>
<h3 id="참고">참고</h3>
<p><a href="https://hongong.hanbit.co.kr/">https://hongong.hanbit.co.kr/</a>
<a href="https://devbirdfeet.tistory.com/203">https://devbirdfeet.tistory.com/203</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2020 카카오 기출 - 외벽점검]]></title>
            <link>https://velog.io/@manofmen_yj/2020-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B8%B0%EC%B6%9C-%EC%99%B8%EB%B2%BD%EC%A0%90%EA%B2%80</link>
            <guid>https://velog.io/@manofmen_yj/2020-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B8%B0%EC%B6%9C-%EC%99%B8%EB%B2%BD%EC%A0%90%EA%B2%80</guid>
            <pubDate>Fri, 13 Oct 2023 10:39:13 GMT</pubDate>
            <description><![CDATA[<p>문제 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/60062">https://school.programmers.co.kr/learn/courses/30/lessons/60062</a></p>
<h3 id="접근">접근</h3>
<p>각각의 데이터의 크기가 크지 않아 완전탐색 알고리즘으로 접근하였다. n짜리의 원형의 외벽을 크기 2n짜리 배열을 생성하고, range(0,n)까의 반복문을 돌면서 i~i+n부분을 파싱하고 dist배열을 permutaations 한 값을 하나씩 순회하면서 최소갯수를 갱신해주었으나 몇몇 테스트 케이스에서 시간초과가 발생하였다.</p>
<h3 id="수정">수정</h3>
<p>외벽이 원형의 형태로 주어졌기 때문에 이를 두배로 늘려 배열 형태로 변환하는 것은 맞다. 그러나 기준이 잘못되었음.</p>
<p>굳이 크기가 2n짜리인 배열을 선언하는 것이 아니라, weak의 크기 *2 만큼만 선언하여도 충분하다. 어차피 중요한것은 weak의 위치 보다는, 각 weak사이의 거리가 더 중요하기 때문에,</p>
<pre><code class="language-py">    for we in weak:
        newweak.append(we)
        newweak.append(we+n)
    newweak.sort()
</code></pre>
<h3 id="전체-코드">전체 코드</h3>
<pre><code class="language-py">from itertools import permutations
from collections import deque
def solution(n, weak, dist):
    minlength=100000
    newweak=[]
    check=len(weak)
    for we in weak:
        newweak.append(we)
        newweak.append(we+n)
    newweak.sort()
    dist_permu=list(permutations(dist,len(dist)))

    for i in range(check):
        for dist in dist_permu:
            cur_newweak=deque(newweak[i:i+check])
            dist=deque(dist)
            count=1
            curweak=cur_newweak.popleft()
            curdist=dist.popleft()
            flag=False
            while len(cur_newweak)&gt;0:
                if cur_newweak[0]-curweak&lt;=curdist:
                    cur_newweak.popleft()
                else:
                    curweak=cur_newweak.popleft()
                    if dist:
                        curdist=dist.popleft()
                        count+=1
                    else:
                        flag=True
                        break
            if flag:
                continue
            else:
                minlength=min(minlength,count)
    if minlength==100000:
        return -1
    return minlength
</code></pre>
<h3 id="참고">참고</h3>
<p>원형은 배열로 변환, 데이터 적으면 완탐 먼저 생각</p>
]]></description>
        </item>
    </channel>
</rss>