<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Fe</title>
        <link>https://velog.io/</link>
        <description>하고 싶은 게 많은 사람</description>
        <lastBuildDate>Tue, 30 Dec 2025 16:13:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Fe</title>
            <url>https://velog.velcdn.com/images/its_fe/profile/b24af9bc-bdf0-46da-9a9d-19ebc29cf29e/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Fe. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/its_fe" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2025년 돌아보기]]></title>
            <link>https://velog.io/@its_fe/2025%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/2025%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 30 Dec 2025 16:13:41 GMT</pubDate>
            <description><![CDATA[<p>2025년은 정말로 빨리 지나간 느낌이다. 벌써 연말 회고를 할 시즌이라니.. 올해는 쉴 새 없이 달려온 나에게 칭찬해주고 싶다.</p>
<h1 id="1학기">1학기</h1>
<p>1학기는 한 마디로 표현하자면 <strong>현생 복귀, 졸업 예정자가 되기 위한 몸부림😞</strong>이다. </p>
<h3 id="캡스톤">캡스톤</h3>
<p>우테코 6기를 수료하고 학교로 돌아왔다. 이때 기준으로 학교는 세 학기가 남았지만, 우리 학교의 캡스톤은 1년 단위라 올해 마무리를 해야 했다. 학과에 좀 늦게 문의한 바람에 방학 시즌에 급하게 캡스톤 조를 찾아다녔고, 다행히 사람을 구하고 있는 조를 찾아 합류하게 되었다. 주제는 <strong>웹, 화이트박스 테스트 기반의 코드 취약점 분석 및 솔루션 제공 서비스</strong>를 개발하는 것이었다. 나는 프론트엔드 개발을 담당했고, 서비스는 복잡도가 그리 높지 않아서 개발 부담은 없었다. 백엔드 개발을 처음 해본 조원들이 있었기 때문에 문서화나 개발 방향성 설정 등 내가 더 기여할 수 있는 부분들을 찾아 나섰다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/5399d452-52c2-484f-9258-504dd4c400be/image.png" alt=""></p>
<p>한 학기 동안 캡스톤에서 총 4번 발표해야 했고, 조원들이 한 번씩 돌아가면서 발표했다. 캡스톤 강의 교수님의 날카로운 질문에 대처하기 위해 전날 밤늦게까지 발표 준비를 했던 것이 기억에 남는다.</p>
<h3 id="6전공">6전공</h3>
<p>1학기엔 캡스톤을 포함해서 6전공을 들었다. 빡센 이론 과목들도 있었고, docker와 k8s를 배우는 과목도 있었고, 라즈베리파이 뚝딱거리면서 IoT 만드는 과목도 있었다. 2025년 1학기는 학교 공부와 캡스톤만으로도 굉장히 바빴다. </p>
<p>복학하면 정신을 차리고(?) 공부를 열심히 한다고 했던가... 두 번째 복학이긴 했지만 과제와 시험 공부도 치열하게 해서 좋은 성적을 받았다. 하지만 4학년이다 보니 4.5가 수두룩해서 등수는 좀 많이 밀려났다.. 그래도 숫자 자체가 가져다주는 기쁨은 있었다!</p>
<h3 id="스노우링크-합류">스노우링크 합류</h3>
<p>1학기가 끝나기 전, 친구가 학생 창업 팀에서 같이 개발하자고 제안했다. 스키 도메인이고, 예비창업패키지 1차에 합격해서 정부 지원을 받고 있다는 것이었다. 나는 스키를 살면서 한 번 타봤을 정도로 도메인에 대한 관심은 거의 없었지만, 지원금도 받으면서 실제 수익을 목표로 하는 서비스를 만들 수 있겠다는 기대감에 함께하게 되었다. 지금까지 몇 번 팀 프로젝트는 해왔지만 수익과 직결되는 프로젝트는 처음이었다.</p>
<p>6월까지는 학교 공부를 하고 앞으로의 개발 방향을 설계하기로 했고, 7월부터 본격적으로 B2B 매칭 앱을 개발하기로 했다.
(이때까지만 해도 2025년을 적당히 바쁘게 보낼 수 있을 거라 생각했는데...)</p>
<h1 id="여름방학">여름방학</h1>
<p>여름방학은 <strong>먹고 자고 개발하고의 반복</strong>이었다. </p>
<h3 id="스노우링크">스노우링크</h3>
<p>7월부터 스노우링크 크로스플랫폼 앱을 개발했다. 스노우링크는 <strong>프리랜서 스키 강사와 강습업체를 매칭하는 플랫폼</strong>이다. 우리 팀은 정부 지원금이 사업 유지에 반드시 필요했기 때문에 예비창업패키지 2차에 붙는 것이 최우선 과제가 되었다. 심사 기간에 맞추어 빠른 개발이 필요했다. 7월부터 개발 시작해서, 처음에는 10월 중순쯤 심사를 예상하고 개발 일정을 잡았는데 9월 중순으로 결정이 나면서 개발 일정이 중간에 일주일 단축되기도 했다. </p>
<p>백엔드 친구랑 거의 매일 만나서 카페에서 작업했다. 특히 앱 목표 출시 기간이 가까워지면서는 이런저런 문제가 많이 생겨서 저녁을 거르고 새벽까지 카페에 있기도 했다. 그도 그럴 것이, 개발자가 두 명밖에 없었고 둘 다 앱 개발은 처음이었다. 도메인이 복잡해서 개발 자체의 난이도도 높은 편이었고, 앱에 결제 기능을 붙이기 위해 카드사 심사 과정을 담당하는 등 다양한 업무를 처리했다.</p>
<p>그렇게 8월 말, 핵심 기능을 담은 MVP 개발을 끝내고 소중한 앱이 스토어에 출시되었다! 7주 동안 밤낮으로 개발한 결과물이 출시되고 나니 너무나 뿌듯했다. 자신감도 많이 얻을 수 있었다. 보통의 팀 프로젝트에서는 하기 힘든 경험을 많이 한 것이 여름방학에 얻어간 점이 아닌가 싶다. 실 결제 연동, 카드사 심사, 플레이스토어/앱스토어 법인 계정 처리 및 심사 요청 등...</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/66d7499d-7571-4751-abfc-dbebeea744be/image.png" alt="">
<img src="https://velog.velcdn.com/images/its_fe/post/94d92969-f1ba-4cf9-9cb0-286bdf8a5ae6/image.png" alt=""></p>
<h3 id="정보처리기사-필기">정보처리기사 필기</h3>
<p>올해는 다들 많이 취득하는 정보처리기사 자격증에도 도전해 보았다. 미래를 위한 최소한의 경쟁력이라고 생각했다. 문제는 스노우링크 개발이 워낙 바빠서, 거의 공부할 시간이 없었다는 것이다.</p>
<p>온라인 CBT에서 최근 기출 문제만 몇 번씩 풀어보고 시험을 보았는데, 다행히 합격했다. CS를 공부하면서 혹은 전공 강의를 들으면서 알게 된 내용도 조금씩 있어서 보다 수월했던 것 같다.</p>
<h1 id="2학기">2학기</h1>
<p>여름방학의 연장선상으로, 2학기에는 학교 공부가 더해져서 그야말로 몸이 한 개로는 부족했다. 몸이 3개쯤 있으면 너무 좋을 것 같다😋</p>
<h3 id="스노우링크-continue">스노우링크 continue</h3>
<p>여름방학에 MVP를 출시하고, 2학기에는 남은 기능들을 마저 개발했다. 가장 중요한 매칭 기능과 프로필 관련 기능 등은 이전에 개발을 마쳤는데, 채팅과 알림 기능이 남아있었다. 채팅 기능은 이전에 살짝 다뤄본 적은 있었지만 깊이는 없었기 때문에 2학기에서의 경험이 만족스럽다. 읽음 처리는 어떻게 구현할 것인지, 이미지나 동영상 전송할 때 썸네일은 어떻게 보여줄 것인지 등 백엔드 친구와 열심히 공부하고 토론했다. 그 과정 자체가 너무 재밌었다. 개발하는 것도 재밌지만, 그에 앞서 어떻게 개발할 건지 논의하고 공부하는 과정도 재밌다.</p>
<p>알림 기능은 이번에 처음 해본 것인데, Expo 프레임워크에서 제공해 주는 라이브러리를 사용하니 어렵지 않게 할 수 있었다.</p>
<p>중간에 실제 강사로 활동하고 있는 분들부터 우리 도메인과는 관련 없는 분들까지 초대해서 사용성 테스트도 진행했다. 반드시 우리 앱의 타겟이 아니더라도 간단한 설명이 있으니 앱 자체의 사용성을 판단하는 데는 큰 무리가 없다고 판단했다. 그동안은 개발자 시선에만 갇혀 있다가 사용자들과 이야기를 나눠보니 내가 미처 신경 쓰지 못한 부분까지 발견하게 되어 굉장히 신선했다.</p>
<p>그렇게 11월이 되고, 여전히 개발할 기능들은 남아있지만 우선 순위를 고려해서 스노우링크는 운영 단계로 두었다.</p>
<h3 id="스노우파트너스">스노우파트너스</h3>
<p>스노우링크가 가고 스노우파트너스가 왔다. 강습업체용 CRM 웹 서비스이다. 몇 달 동안 React Native를 쓰다가 React로 돌아오니 고향에 돌아온 기분이었다. 매번 빌드해서 확인해야 했던 앱과 다르게 브라우저에서 수정 결과를 직접 볼 수 있다는 것이 너무 감사했다.</p>
<p>스노우파트너스는 스노우링크를 한 번 경험해 보기도 했고, 상대적으로 규모가 작은 서비스라 비교적 수월하게 개발할 수 있었다.</p>
<h3 id="정보처리기사-실기">정보처리기사 실기</h3>
<p>필기 때도 그랬지만, 실기 때도 마찬가지로 개발과 시험이 겹쳐서 공부할 시간이 정말 없었다. CBT에서 최근 기출 문제들을 집중적으로 살펴볼 시간밖에 남지 않았다. 급한 대로 최근 기출만이라도 익숙해질 정도로 풀었고, 결국 최종 합격했다!</p>
<p>코드를 보고 출력 결과를 쓰는 문제가 많았는데, 아무래도 그런 문제들은 익숙하기 때문에 좋은 결과가 나오지 않았나 싶다. </p>
<h3 id="opic">OPIc</h3>
<p>모 기업의 우대 사항으로 영어 공인 성적이 적혀 있어서 급하게 취득했다(결국 지원 자격 미달로 지원은 못 했지만...). 오픽노잼 유튜브에서 모의고사만 정리된 게 있어서 몇 번 풀어보고, 질문을 들은 뒤 빠르게 나의 과거 경험을 떠올리는 연습을 하면서 시험을 대비했다. 중간에 더듬기도 했고 살짝 공백도 있었지만 AL 등급을 받았다! 받고도 깜짝 놀랐다..</p>
<h3 id="7학기-수료">7학기 수료!</h3>
<p>캡스톤 발표까지 마무리하고 이제 졸업까지 한 학기를 남겨두었다. 엇학기가 불러온 눈덩이가 너무 크게 불어나버려서 지금까지 스트레스도 많이 받았지만 이제는 진짜 졸업 예정자가 되었다. 2학기에 가장 아쉬운 건 성적이 기대에 못 미쳤다는 것이다. 바쁜 개발 일정이 겹쳐 시간 관리와 집중력 관리가 제대로 되지 않았던 것 같다. 막학기에는 다시 집중해봐야겠다!</p>
<hr>
<p><img src="https://velog.velcdn.com/images/its_fe/post/0ed69f0d-b8e3-4c61-9dfa-56d9af69dd77/image.png" alt=""></p>
<p>(올해 커밋 개수는 역대급이다)</p>
<p>쓰고 보니 할 말은 많지만 정리는 안 된 느낌이 가득한 것 같다.. 사실 올해는 한 것만 나열해도 상당히 길어서, 절제할 부분은 과감하게 쳐내고 내 생각을 써보려 노력했다.</p>
<p>올해 개발적으로도 경험적으로도 성장한 것 같아 좋다. 2026년에는 이 경험을 온전히 내 것으로 만들기 위해 다시 노력해야겠다.
2025년 회고 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 백준 19539 - 사과나무]]></title>
            <link>https://velog.io/@its_fe/Algorithm-%EB%B0%B1%EC%A4%80-19539-%EC%82%AC%EA%B3%BC%EB%82%98%EB%AC%B4</link>
            <guid>https://velog.io/@its_fe/Algorithm-%EB%B0%B1%EC%A4%80-19539-%EC%82%AC%EA%B3%BC%EB%82%98%EB%AC%B4</guid>
            <pubDate>Tue, 20 May 2025 07:27:41 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/19539">https://www.acmicpc.net/problem/19539</a>
▲ 문제 바로가기</p>
<p>물뿌리개를 한 번 사용할 때마다</p>
<ul>
<li>서로 다른 두 나무에 각각 1, 2만큼 사용</li>
<li>한 나무에 1+2만큼 사용</li>
</ul>
<p>시킬 수 있다. 결국 1과 2를 동시에 사용하는 조합만으로 입력의 형태를 만들 수 있는지를 물어보는 문제이다.</p>
<h1 id="풀이">풀이</h1>
<h3 id="early-return">early return</h3>
<p>먼저 물뿌리개를 한 번 사용할 때마다 전체 나무는 3만큼 성장한다는 것을 알 수 있다. 따라서 <strong>입력의 합이 3의 배수가 아니면 <code>NO</code>를 출력하고 종료해야 한다.</strong></p>
<p>처음에는 쉽게 생각나지 않아서 몇 가지 예시를 들어 동작의 특징들을 살펴보았다.</p>
<h3 id="example-1">example 1</h3>
<p><code>1 1 1 3 3</code></p>
<p><strong>편의상 1, 2씩 빼면서 모든 수를 0으로 만들어보겠다.</strong> </p>
<p><code>1 1 1 3 3</code> -&gt; <code>0 1 1 1 3</code> -&gt; <code>0 0 1 1 1</code> -&gt; <code>NO</code></p>
<p><strong>1을 없애려면 2가 반드시 필요하다.</strong> 하지만 3의 경우 두 물뿌리개를 동시에 사용하면 본인만 사라지고, 2짜리 물뿌리개를 사용하면 다시 1이 남게 된다. </p>
<p>따라서 1과 짝이 되어 없어질 수 있는 2의 개수를 세어야겠다고 생각했다.</p>
<p>위의 예시 <code>1 1 1 3 3</code>의 경우, 2의 개수는 두 개다. 각 숫자를 2로 나눈 몫의 합이다. 반면 1의 개수는 다섯 개다. 1이 세 개 있고, 3을 2로 나눈 나머지가 1이기 때문에 두 개가 더해진다.</p>
<p>정리해 보면,</p>
<ul>
<li><code>1</code>이 5개</li>
<li><code>2</code>가 2개</li>
</ul>
<p>두 개씩은 짝을 맞추어 없어지지만, <code>1</code>이 세 개 남기 때문에 이 경우 <code>NO</code>가 되는 것이다.</p>
<h3 id="example-2">example 2</h3>
<p><code>1 1 4</code>의 경우는 어떨까? </p>
<p><code>1 1 4</code> -&gt; <code>0 1 2</code> -&gt; <code>0 0 0</code> -&gt; <code>YES</code></p>
<p>1과 2의 개수를 세어 보면,</p>
<ul>
<li><code>1</code>이 2개</li>
<li><code>2</code>가 2개</li>
</ul>
<p>짝이 맞춰져 모두 없어지게 된다. 남는 것이 없기 때문에 <code>YES</code>가 된다.</p>
<h3 id="example-3">example 3</h3>
<p><code>1 4 4</code>의 경우는 어떨까?</p>
<p><code>1 4 4</code> -&gt; <code>0 2 4</code> -&gt; <code>0 1 2</code> -&gt; <code>0 0 0</code> -&gt; <code>YES</code></p>
<p>1과 2의 개수를 세어 보면,</p>
<ul>
<li><code>1</code>이 1개</li>
<li><code>2</code>가 4개</li>
</ul>
<p>하나의 짝이 만들어져 없어지고, <code>2</code>가 세 개 남게 된다. 그런데 어떻게 YES가 될까?</p>
<p>남은 세 개의 <code>2</code> 중에 하나를 <code>1</code> 두 개로 생각할 수 있다. 그렇게 되면 다시 <code>1</code>이 두 개, <code>2</code>가 두 개가 되어 짝이 맞추어 사라지게 된다. 실제로 위 과정에서 보면 (1, 2)씩 세 번 물뿌리개를 사용한다.</p>
<h3 id="한-가지-의심">한 가지 의심</h3>
<p>여기서 의심이 들었다. example 3의 경우 <code>2</code>가 세 개 남았기 때문에 그 중 하나를 <code>1</code>로 변환해서 짝을 맞추었다. </p>
<p><strong>하지만 <code>2</code>가 한 개 또는 두 개 남으면?</strong></p>
<p>사실 이 부분은 간단하다. 짝을 맞추어 없어진 것들은 모두 3의 배수이다. 여기에 <code>2</code>가 한 개 또는 두 개 남게 되면 입력값의 합이 $3k+1$ 또는 $3k+2$ ($k$는 음이 아닌 정수)라는 뜻이 된다. 따라서 이런 경우는 <strong>early return</strong>에서 걸러지게 된다.</p>
<h3 id="결론">결론</h3>
<p>위의 예시 이외에도 여러 예시를 들어보면서 내린 결론은 다음과 같다.</p>
<ul>
<li><strong>1을 없애기 위해 충분한 2가 있어야 한다.</strong></li>
</ul>
<ol>
<li>입력의 각 숫자들을 2로 나눈 몫의 합을 <strong>2의 개수</strong>로 둔다.</li>
<li>입력의 각 숫자들을 2로 나눈 나머지가 있는 경우 <strong>1의 개수</strong>로 둔다.</li>
<li><strong>2의 개수</strong>가 <strong>1의 개수</strong>보다 <em>크거나 같으면</em> <code>YES</code>, 아니면 <code>NO</code>를 출력한다.</li>
</ol>
<hr>
<h1 id="다른-풀이의-비교식">다른 풀이의 비교식</h1>
<p>정답을 받고 나서 다른 풀이들을 살펴보았다. 많은 풀이들이 2의 개수를 구하는 것까지는 동일하지만, 그 비교 대상이 <strong>1의 개수가 아니라 (입력값의 합)/3</strong>으로 달랐다. 입력값의 합을 3으로 나눈다는 것은 물뿌리개를 사용하는 전체 횟수가 된다. 2짜리 물뿌리개를 전체 사용 횟수 이상으로 사용할 수 있다는 조건도 직관적이라 생각한다.</p>
<p>얼핏 보면 내 논리와 달라 보이는데, 두 비교 모두 정답 처리를 받았다. 그래서 두 비교가 같은 비교인지를 수식으로 보이고 싶었다.</p>
<h1 id="수식으로-증명하기">수식으로 증명하기</h1>
<ul>
<li>2의 개수 $t$</li>
<li>1의 개수 $o$</li>
<li>입력값의 합 $sum$</li>
<li>물뿌리개를 사용하는 전체 횟수 $D$ ($sum/3=D$)</li>
</ul>
<p>라고 하자.</p>
<p>1, 2의 개수와 입력값의 합의 관계를 식으로 쓰면
$$sum = 2t+o$$</p>
<p>$D$를 $t$, $o$에 관한 식으로 쓰면
$$3D=2t+o$$, $$D=\frac{2t+o}{3}$$</p>
<p><strong>내 풀이의 비교식 -&gt; 다른 풀이의 비교식</strong> 
$$t\ge o \Leftrightarrow 3t\ge 2t+o$$
            $$\space\space\space\space\space\space\space\space\space\space\Leftrightarrow t\ge \frac{2t+o}{3}=D$$</p>
<p>따라서, $$t\ge o$$와 $$t\ge D$$은 같은 비교식이다.</p>
<p>식을 적절히 변형해서 같은 식임을 증명할 수 있었다. </p>
<h1 id="마무리">마무리</h1>
<p><strong>1의 개수</strong>와 <strong>물뿌리개의 전체 사용 횟수</strong>는 얼핏 보면 다르게 보였지만, 수식으로 두 비교식이 같은 것임을 보였다. 많은 사람들이 작성한 풀이의 로직도 함께 알아두어야겠다.</p>
<p>틀린 부분이 있다면 언제든 피드백 남겨주세요 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 전체 화면 캡처해서 이미지로 다운로드하기]]></title>
            <link>https://velog.io/@its_fe/React-%EC%A0%84%EC%B2%B4-%ED%99%94%EB%A9%B4-%EC%BA%A1%EC%B2%98%ED%95%B4%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A1%9C-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/React-%EC%A0%84%EC%B2%B4-%ED%99%94%EB%A9%B4-%EC%BA%A1%EC%B2%98%ED%95%B4%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A1%9C-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 09 May 2025 16:30:41 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<p>캡스톤 프로젝트로 &#39;웹 서비스 기반 코드 보안 취약점 분석&#39;을 하고 있는데, 그 중 취약점 분석 결과를 요약 레포트 형식으로 제공하는 페이지가 있다. 여기서 요약 레포트 페이지를 이미지로 다운로드 하는 기능을 구현해야 했다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/2d667132-de49-4b56-9209-9f9f0d36db0f/image.png" alt=""></p>
<p>요약 레포트는 위와 같이 생겼다. 취약점 목록은 길어질 경우 스크롤로 내릴 수 있는데, <strong>캡처 이미지는 이 스크롤된 목록을 전부 펴서 보여주고 싶었다.</strong></p>
<h1 id="라이브러리-선택">라이브러리 선택</h1>
<p>DOM 요소를 이미지로 만들어주는 라이브러리를 찾아보니 <code>html2canvas</code>와 <code>dom-to-image</code>에 관련된 글들이 보였다. 어떤 라이브러리가 더 적합할지 비교를 해보았다. 참고로 두 라이브러리 모두 오픈소스로 공개되어 있었다.</p>
<h2 id="html2canvas">html2canvas</h2>
<ul>
<li><strong>svg, canvas 요소 등 다양한 요소에 대한 캡처 기능을 지원한다.</strong></li>
<li><code>dom-to-image</code>에 비해 번들 크기가 조금 더 크다.</li>
<li>개인적으로 오픈소스 라이브러리를 선택할 때 최근까지도 유지보수 및 개발이 이루어지는지를 함께 보는데, 약 3년 전에 마지막으로 커밋 기록이 있다.</li>
<li>몇몇 CSS 속성이 지원되지 않는다. <a href="https://html2canvas.hertzen.com/features">여기</a>에 나와있는데, 이 중 사용하고 있는 속성은 없어서 문제되지는 않았다.</li>
</ul>
<h2 id="dom-to-image">dom-to-image</h2>
<ul>
<li>직접 사용해 보지는 않았지만, 다른 블로그에서 img 요소가 깨진다는 이야기를 보았다. <code>html2canvas</code>에 비해 지원 범위가 좁은 것 같다.</li>
<li><code>html2canvas</code>에 비해 번들 크기가 작고 빠르다.</li>
<li>마지막 커밋이 약 8년 전이다.</li>
</ul>
<hr>
<h2 id="선택">선택</h2>
<p>요약 레포트 페이지에서는 svg로 구현된 도넛 차트가 있었고, 지원 범위 및 커밋 기록 등으로 살펴 보아 <code>html2canvas</code>가 기능 구현에 좀 더 적합하다고 판단했다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/7e8ce3e9-65b5-4d0e-a5c7-8cc16bf9baf4/image.png" alt=""></p>
<p>npm 다운로드 수를 비교해봤을 때도 <code>html2canvas</code>가 압도적으로 많은 것을 확인할 수 있다. 또한 관련 자료도 <code>html2canvas</code>가 훨씬 많이 보였다.</p>
<h1 id="구현--트러블슈팅">구현 &amp; 트러블슈팅</h1>
<h2 id="파일-저장-호환성">파일 저장 호환성</h2>
<p>구현 코드를 보기 전에 앞서, <code>html2canvas</code>로 만든 이미지를 다운로드 할 때는 <code>a</code> 태그를 임시로 만들어서 다운로드 하는 방식을 많이 사용한다. PC에서는 잘 동작했지만, 갤럭시 스마트폰에서 자동 다운로드 차단 기능 때문에 다운로드가 정상적으로 되지 않는 문제가 있었다.</p>
<p>클라이언트에서 브라우저나 디바이스 호환성을 위해 <code>file-saver</code> 라는 파일 저장 라이브러리를 사용했다. 이 라이브러리는 다양한 브라우저에서 Blob이나 URI 형식으로 파일을 저장할 수 있도록 한다. (<a href="https://www.npmjs.com/package/file-saver">라이브러리 링크</a>)</p>
<h2 id="구현-코드">구현 코드</h2>
<p><code>file-saver</code>를 사용하여 처음 구현한 코드는 다음과 같다.</p>
<pre><code class="language-typescript">const handleDownloadReportImage = async () =&gt; {
    if (!reportRef.current) return;

    try {
      const report = reportRef.current;
      const canvas = await html2canvas(report, { scale: 2 }); // scale: 이미지 사이즈를 2배로 키워 해상도 개선

      canvas.toBlob((blob) =&gt; {
        if (blob) saveAs(blob, ${scanId}_summary_report.png);
      });
    } catch (error) {
      console.error(&#39;Error downloading report image:&#39;, error);
    }
  };</code></pre>
<p><code>html2canvas</code>의 <code>scale</code> 속성을 사용하여 이미지 해상도를 개선하기도 했다.</p>
<p>useRef를 사용하여 지정한 요소를 canvas로 바꾸어 저장한다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/99dc1c95-3e18-4c6a-b0b3-eac7799f0866/image.png" alt=""></p>
<p>다운로드 버튼을 누르니 위와 같이 캡처가 잘 되었다! 하지만 뭔가 이상하지 않은가? 화면을 벗어나는 부분이 잘려서 저장된다. 그리고 배경색도 반영되지 않았다.</p>
<p><strong><code>html2canvas</code>는 기본적으로 눈에 보이는 영역만을 캡처하기 때문이다.</strong></p>
<p>일종의 트릭을 사용하여 이 문제를 해결했다.</p>
<h2 id="전체-화면을-캡처하는-트릭">전체 화면을 캡처하는 트릭</h2>
<p>눈에 보이는 부분만을 캡처하기 때문에 스크롤을 없애고, 펼쳐서 캡처해야 한다. 따라서 원본 요소를 복제하고, CSS 스타일링을 통해 모든 요소를 펼친 상태로 캡처한 뒤, 복제한 요소를 지우는 방식으로 처리했다.</p>
<p><strong>&lt;과정&gt;</strong></p>
<ol>
<li>원본 요소를 복제한다.</li>
<li>복제한 요소에 <code>overflow: visible</code>, <code>height: auto</code> 속성과 <code>background-color</code>을 포함하여 스타일링한다.</li>
<li>화면에 보이지 않는 곳에 복제한 요소를 잠시 옮겨둔다.</li>
<li>복제한 요소를 캡처한다.</li>
<li>복제한 요소를 제거한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/its_fe/post/9d4feacd-8e62-46cc-a983-cd81576a1a67/image.png" alt=""></p>
<p>위 방법을 통해 보이는 화면은 그대로 두고, 보이지 않는 곳에서 스타일만 잠시 바꾸어 캡처할 수 있다. 화면에 보이지 않을 만큼만 위치를 이동하면 되는데, 혹시나 모를 상황에 안전하게 처리하기 위해 <code>-9999px</code>와 같은 큰 값을 설정했다.</p>
<p>수정한 코드는 다음과 같다.</p>
<pre><code class="language-typescript">const handleDownloadReportImage = async () =&gt; {
    const original = reportRef.current;
    if (!original) return;

    try {
      // 원본 div 클론
      const clone = original.cloneNode(true) as HTMLElement;

      // 화면에 보이지 않는 곳에 잠시 붙이기
      clone.style.position = &#39;absolute&#39;;
      clone.style.top = &#39;-9999px&#39;;
      clone.style.left = &#39;-9999px&#39;;
      clone.style.width = `${original.scrollWidth}px`;
      clone.style.height = &#39;auto&#39;;
      clone.style.overflow = &#39;visible&#39;;
      clone.style.backgroundColor = &#39;#f3f4f6&#39;;

      // 페이지 body에 붙이기
      document.body.appendChild(clone);

      // 클론 캡처
      const canvas = await html2canvas(clone, { scale: 2 });

      // Blob 생성 및 다운로드
      canvas.toBlob((blob) =&gt; {
        if (blob) saveAs(blob, `${scanId}_summary_report.png`);
      });

      // 클론 제거
      document.body.removeChild(clone);
    } catch (error) {
      console.error(&#39;Error downloading full report image:&#39;, error);
    }
  };</code></pre>
<p><img src="https://velog.velcdn.com/images/its_fe/post/ecdb1274-faa0-4d8a-8224-0693581df22e/image.png" alt=""></p>
<p>수정하고 나니, 위와 같이 전체 화면이 모두 잘 캡처되었다. </p>
<hr>
<h1 id="마치며">마치며</h1>
<p>DOM 요소를 이미지로 캡처하여 다운로드 하는 기능은 종종 유용하게 사용되는 것 같다. 특히, 스크롤이 포함된 화면의 경우 전체 화면을 캡처하는 것이 필요한데 위와 같이 적절한 트릭을 사용하여 문제를 해결할 수 있었다. </p>
<p>비교적 단순한 스타일과 단순한 요소들로만 이루어져 있어 다른 문제는 발생하지 않았지만, 복잡한 CSS 속성이 포함되어 있는 경우가 있다면 다른 방법도 알아봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[외장 SSD에서 VSCode EPERM 오류 해결하기]]></title>
            <link>https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD%EC%97%90%EC%84%9C-VSCode-EPERM-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD%EC%97%90%EC%84%9C-VSCode-EPERM-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 06 May 2025 18:35:16 GMT</pubDate>
            <description><![CDATA[<p>VSCode에서 개발을 하다 보면 종종 <code>Error: EPERM: operation not permitted...</code> 오류가 발생한다. 그러면 VSCode를 관리자 권한으로 실행하거나 npm 캐시를 제거하는 등의 해결책을 많이들 제시한다. 나의 경우, 라이브러리를 설치할 때마다 EPERM 오류가 발생해서 해결을 하고 넘어가야겠다고 생각했다.</p>
<h1 id="eperm">EPERM?</h1>
<p>EPERM은 <code>Error PERMission</code>의 약자로, <strong>운영체제 수준에서 허가되지 않은 작업을 시도하는 경우</strong> 반환되는 표준 에러 코드(errno 1)이다. 즉, 권한 문제로 발생하는 문제이다.</p>
<p>주로 언제 발생할까?</p>
<ol>
<li>파일이나 디렉토리의 권한이 부족한 경우 (일반 사용자 계정으로 시스템 폴더를 수정한다거나...)</li>
<li>파일 시스템 제약이 있는 경우</li>
</ol>
<p>권한 문제가 파일 시스템과도 연관이 있는 것을 확인하고, 외장 SSD를 사용하고 있다는 점을 고려해서 이 부분을 살펴보았다.</p>
<h1 id="파일-시스템-exfat-vs-ntfs">파일 시스템, exFAT vs NTFS</h1>
<p>나는 삼성의 외장 SSD인 <code>Samsung Portable SSD T7 Shield</code>를 사용하고 있다 (광고 아님). 그리고 초기 파일 시스템은 <code>exFAT</code>으로 설정되어 있었다. </p>
<h2 id="exfat">exFAT</h2>
<ul>
<li>Extended File Allocation Table</li>
<li>exFAT은 Microsoft가 FAT32 파일 시스템의 파일 크기/볼륨 제약을 해결하기 위해(파일 하나당 최대 4GB까지만 복사/이동이 가능) 등장했다.</li>
<li>Windows, MacOS, Linux 등 다양한 OS와 디바이스에 호환된다는 장점이 있다. 그래서 기본 파일 시스템으로 채택되지 않았나 싶다. </li>
<li><strong>하지만</strong> 파일 무결성을 보장하지 않고, NTFS에 비해 상대적으로 보안과 안정성이 낮다는 문제가 있다.</li>
<li>또한, <strong>세밀한 권한 관리가 가능한 ACL이 현재 Windows에서는 지원되지 않는다.</strong> <ul>
<li>이 부분은 <a href="https://en.wikipedia.org/wiki/ExFAT">여기</a>에서 확인할 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="ntfs">NTFS</h2>
<ul>
<li>New Technology File System</li>
<li>Microsoft Windows NT 계열부터 채택된 기본 파일 시스템이다. 서버, 데스크톱 환경을 염두에 두고 고급 기능을 제공한다.</li>
<li>데이터의 무결성을 보장하며, <strong>ACL을 지원</strong>한다. 또한 강력한 보안을 자랑한다.</li>
<li>기본적으로 Windows 전용으로 지원되고, MacOS에서는 읽기만 가능하며 쓰기 시 드라이버가 필요하다. 리눅스는 드라이버가 필요하다.</li>
</ul>
<p>Windows에서 ACL 미지원 때문에 VSCode에서 패키지 매니저가 파일을 쓰거나 조작할 때 권한 문제가 발생한 것으로 보인다.</p>
<h1 id="해결">해결</h1>
<p>나는 데스크탑과 노트북 환경을 왔다 갔다 하면서 작업하기 때문에 외장 SSD 사용이 필요하다. 또한, <strong>윈도우만 사용하기 때문에</strong> NTFS 파일 시스템을 사용해도 호환성에 문제가 생길 일은 없었다. </p>
<p><strong>exFAT 기반의 SSD를 NTFS 파일 시스템으로 포맷하기로 결정했다!</strong></p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/e7c63b58-aabd-40a7-abb3-4c2367a8e76c/image.png" alt=""></p>
<p>열심히 백업을 해준다. 이때 용량이 꽤 많기 때문에 SSD를 사용하는 C 드라이브로 백업했다. </p>
<p>포맷하면 삼성에서 제공하는 <code>Portable SSD Software</code>가 함께 날아갈 것이기 때문에 이를 다운로드 하려 했는데 찾을 수 없었다. 알고 보니 외장 SSD 전용 소프트웨어는 없어지고 기기의 모든 메모리를 한 번에 관리할 수 있는 Magician 소프트웨어가 새로 나왔다.</p>
<p>그래서 마음 놓고 포맷했다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/2c0125a9-9382-4a91-80ae-e7776e591d6c/image.png" alt="">
포맷 후 잘 인식되고 잠금 설정도 되는 것을 확인했다.</p>
<h1 id="성과">성과</h1>
<p>테스트 삼아서 VSCode에 라이브러리를 설치하고 삭제해 보았다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/a880dcc6-f8ec-4f98-a7ca-899726d0150e/image.png" alt="">
(TMI: <code>fe-custom-modal</code>은 내가 직접 만든 모달 라이브러리이다)</p>
<p>이전에는 계속 발생했던 <strong>EPERM 오류가 뜨지 않았다</strong>. 검증 차원에서 2번 정도 더 설치 삭제를 반복했는데도 안 떴다. 성공이다😊</p>
<p>EPERM 뿐만 아니라 라이브러리 설치/삭제 시 <code>Linking dependencies</code> 단계에서 <strong>시간이 너무 오래 걸리는 문제</strong>도 있었다. 간단한 라이브러리 하나 설치하는데도 2분 가까이 걸렸는데, 이번에 SSD의 파일 시스템을 바꾸고 나니 <strong>약 19초로 시간도 대폭 단축</strong>되었다.</p>
<p>삭제할 때도 약 2초만에 끝났다.</p>
<hr>
<p>개발 도중에 계속 문제를 일으켜서 작업 피로감도 상당했는데, 이 문제가 해결되어 너무 기쁘다. 별 것 아닌 작업이지만 개발 환경을 개선해서 뿌듯하다. Windows 환경에서 외장 SSD를 사용해서 개발하는 사람이라면 한 번쯤 겪을 수 있는 문제인데, 이 글이 도움이 될 수 있으면 좋겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] std::sort와 std::priority_queue에서의 비교 함수]]></title>
            <link>https://velog.io/@its_fe/C-stdsort%EC%99%80-stdpriorityqueue%EC%97%90%EC%84%9C%EC%9D%98-%EB%B9%84%EA%B5%90-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@its_fe/C-stdsort%EC%99%80-stdpriorityqueue%EC%97%90%EC%84%9C%EC%9D%98-%EB%B9%84%EA%B5%90-%ED%95%A8%EC%88%98</guid>
            <pubDate>Tue, 15 Apr 2025 15:22:56 GMT</pubDate>
            <description><![CDATA[<p>백준 문제를 풀다 보면 기본적인 오름차순 또는 내림차순 정렬 방식 이외에 커스텀 비교 함수를 사용해서 정렬하는 경우가 많다. <a href="https://boj.ma/1445">오늘 푼 문제</a>에서는 priority_queue를 사용했는데, <code>pair&lt;pair&lt;int, int&gt;, pair&lt;int, int&gt;&gt;</code> 타입의 값을 사용했기 때문에 커스텀 정렬 함수가 필요했다. 편의상 위 타입을 간단하게 <code>piipii</code>라고 부르겠다.</p>
<p><code>piipii</code>에 들어가는 값은, <code>pair&lt;pair&lt;통과한 쓰레기 개수, 쓰레기 옆을 지나가는 칸의 개수&gt;, pair&lt;x좌표, y좌표&gt;&gt;</code>였다.</p>
<p>내가 원했던 정렬 방식은 다음과 같았다.</p>
<ol>
<li>통과한 쓰레기 개수가 더 적은 것을 앞으로(<code>top()</code>으로) 보낸다.</li>
<li>만약 통과한 쓰레기 개수가 같다면 쓰레기 옆을 지나가는 칸의 개수가 더 적은 것을 앞으로 보낸다.</li>
</ol>
<p>이를 구현하기 전에 priority_queue에 대해 간단히 살펴보자.</p>
<h2 id="priority_queue">priority_queue</h2>
<p><code>std::priority_queue</code>는 내부적으로 힙을 사용하여 요소를 관리한다. 
<img src="https://velog.velcdn.com/images/its_fe/post/70e0bab2-6441-451c-b539-90a57c1c2858/image.png" alt="">
이때 세 번째 인자인 Compare에 비교 연산에 사용할 비교 함수 객체를 지정할 수 있다. 지정하면 우선순위 큐를 만들거나 갱신할 때 두 요소를 비교하기 위해 <code>comp(a, b)</code>와 같이 호출된다(comp를 지정했다면).</p>
<p>이때 <code>함수 객체</code>가 무엇인지 알아야 한다. 함수 객체는 객체를 함수처럼 쓸 수 있는 것으로, <code>()</code> 연산자를 오버로딩하면 사용 가능하다.</p>
<p><code>priority_queue</code>는 설계상 기본적으로 저장된 비교 객체를 직접 호출해서 요소들 간의 우선순위를 결정한다. 이때 호출 연산자인 <code>()</code>가 정의되어 있지 않으면 comp 객체를 함수처럼 사용할 수 없기 때문에 연산자 오버로딩이 필요하다.</p>
<pre><code class="language-cpp">struct Comp {
    bool operator ()(int a, int b) {
        return a &gt; b;
    }
}

priority_queue&lt;int, vector&lt;int&gt;, Comp&lt;int&gt;&gt; pq;</code></pre>
<p>▲ 위와 같이 연산자를 오버로딩할 수 있다.</p>
<p>세 번째 인자의 기본값은 <code>less</code>로, 가장 큰 값이 top으로 가서 점점 작아진다. 만약 가장 작은 값을 우선하고 싶다면 비교 함수를 <code>greater</code>로 지정할 수 있다. </p>
<h2 id="sort">sort</h2>
<p>이 글을 쓰게 된 이유가 바로 이 <code>std::sort</code>의 비교 함수와 헷갈렸기 때문이다. </p>
<p><code>sort</code>는 배열이나 vector 컨테이너를 한 번에 정렬하는 함수로, <code>&lt;algorithm&gt;</code> 헤더에 정의되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/f34facad-bb3b-4a7e-8821-6d42bd531721/image.png" alt=""></p>
<p><code>sort</code> 함수의 세 번째 인자에는 비교 함수를 지정할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/6f4542b8-2dad-4f64-be7f-1270d9ecf1de/image.png" alt="">
파라미터에 대한 설명을 좀 더 읽어보면, <code>comp(a, b)가 true를 반환할 경우 a가 b보다 앞에 와야 한다</code>는 것을 알 수 있다.</p>
<p>만약 배열을 오름차순 정렬하고 싶다면</p>
<pre><code class="language-cpp">bool comp(int a, int b){
    return a &lt; b;
}</code></pre>
<p>과 같이 쓸 수 있다. a가 b보다 작다면 위 함수가 <code>true</code>를 반환하기 때문에 a가 b보다 앞에 온다. 즉, 오름차순으로 정렬된다. 만약 부등호 방향을 반대로 한다면? a가 더 클 때 <code>true</code>를 반환하기 때문에 내림차순으로 정렬된다.</p>
<h2 id="처음-구현한-방식">처음 구현한 방식</h2>
<p>위에서 priority_queue와 sort의 비교 함수를 간단하게 살펴보았다. 다시 맨 처음으로 돌아가서,</p>
<ol>
<li>통과한 쓰레기 개수가 더 적은 것을 앞으로(<code>top()</code>으로) 보낸다.</li>
<li>만약 통과한 쓰레기 개수가 같다면 쓰레기 옆을 지나가는 칸의 개수가 더 적은 것을 앞으로 보낸다.</li>
</ol>
<p>를 구현하는 연산자를 오버로딩했다.</p>
<pre><code class="language-cpp">#define piipii pair&lt;pair&lt;int, int&gt;, pair&lt;int, int&gt;&gt;

// ...

struct comp {
    bool operator()(piipii&amp; a, piipii&amp; b){
        if (a.first.first == b.first.first){
            return a.first.second &lt; b.first.second;
        } else {
            return a.first.first &lt; b.first.first;
        }
    }
};</code></pre>
<p>sort의 비교 함수와 마찬가지로 <code>&lt;</code> 부등호를 사용했으니 더 작은 값이 top으로 가겠지! 라고 생각했다.</p>
<h3 id="문제점">문제점</h3>
<p>하지만 코드를 제출해 보니 시간 초과가 발생했다. priority_queue를 사용해서 다익스트라 알고리즘을 구현했기 때문에 시간은 전혀 문제되지 않을 것이라 생각했다. 콘솔에 값들을 찍으며 디버깅을 반복하다가, <strong>priority_queue의 top에 가장 작은 값이 아닌 가장 큰 값이 오고 있다는 것</strong>을 알게 되었다.</p>
<p>다익스트라 알고리즘에서 비용이 작은 상태부터 꺼내면 처리하는 값의 개수를 줄일 수 있는데, 반대로 <strong>비용이 큰 상태부터 꺼내고 있었기 때문에 전체 연산량이 크게 늘어 시간 초과가 발생</strong>한 것이다.</p>
<p>비교 함수를 잘 작성했다고 생각했는데, 이 부분에 문제가 있었다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/7af7721d-4773-4c41-a448-d2f28e5da3cd/image.png" alt=""></p>
<p>priority_queue의 파라미터 설명을 읽어보자. <code>comp(a, b)가 true를 반환하면 a는 b보다 우선순위가 낮다</code>는 것을 알 수 있었다.
즉, <strong>a가 b보다 앞에 온다는 개념이 우선순위 큐에서는 우선순위가 낮다는 것이 되어버렸다.</strong> </p>
<p>따라서 원래 구현하고 싶던 비교 함수를 구현하기 위해서는 위 코드에서 부등호 방향을 반대로 바꾸어야 한다.</p>
<h2 id="수정한-방식">수정한 방식</h2>
<pre><code class="language-cpp">struct comp{
    bool operator()(piipii&amp; a, piipii&amp; b){
        if(a.first.first==b.first.first){
            return a.first.second&gt;b.first.second;
        } else{
            return a.first.first&gt;b.first.first;
        }
    }
};</code></pre>
<p>▲ 올바른 비교 함수</p>
<hr>
<p>이전에도 한 번 부등호 방향 때문에 문제를 겪은 경험이 있었는데, 그때는 크게 생각하지 않고 답을 맞히는 데 급급했던 것 같다. 하지만 이번 기회에 <code>sort</code>와 <code>priority_queue</code>의 비교 함수에서 부등호 방향이 의미하는 것을 확실하게 알게 되었다.</p>
<p>앞으로는 부등호 방향 때문에 문제될 일이 없길 바란다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[외장 SSD가 인식 됐다 안 됐다 하는 문제 해결]]></title>
            <link>https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD%EA%B0%80-%EC%9D%B8%EC%8B%9D-%EB%90%90%EB%8B%A4-%EC%95%88-%EB%90%90%EB%8B%A4-%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD%EA%B0%80-%EC%9D%B8%EC%8B%9D-%EB%90%90%EB%8B%A4-%EC%95%88-%EB%90%90%EB%8B%A4-%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 04 Mar 2025 15:30:16 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD-GitHub-Desktop%EC%9C%BC%EB%A1%9C-%EC%97%AC%EB%9F%AC-%EA%B8%B0%EA%B8%B0%EC%97%90%EC%84%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0">이전 게시물</a>에서 외장 SSD를 사용하는 작업 환경을 정돈하고... 오늘 다시 노트북으로 작업하는 도중 다른 이슈가 생겼다.</p>
<p>외장 SSD를 노트북에 직접 연결한 채로 작업하고 있는데, 파일 탐색기가 자꾸만 열렸다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/301de6ef-17a3-4713-8bf6-79d4fbcca3c9/image.png" alt="">
그리고, vscode에서 작업하다가 저장할 때 종종 위와 같이 파일을 저장할 수 없다는 에러 메시지가 떴다. 재시도하니 해결되긴 했지만, 문제를 완전히 해결하고 싶었다.</p>
<h1 id="분석">분석</h1>
<p><strong>환경</strong></p>
<ul>
<li>노트북에서 작업 중이다.</li>
<li>배터리가 얼마 안 남아 절전 모드가 켜져 있었다.</li>
</ul>
<p>파일 탐색기가 계속 뜨는 이유는 외장 SSD가 연결 해제되었다가 다시 연결되기 때문이고, 절전 모드가 그 원인인 것으로 판단했다.</p>
<h1 id="해결">해결</h1>
<h3 id="전원-옵션-설정">전원 옵션 설정</h3>
<p><img src="https://velog.velcdn.com/images/its_fe/post/6c9287dc-4335-486b-845b-e82cf2e57dd6/image.png" alt=""></p>
<p>노트북의 <code>전원 관리 옵션 설정 편집</code>에서 <code>고급 전원 관리 옵션 설정 변경</code>으로 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/41162d30-fe14-4d54-bca4-4691c20d4058/image.png" alt="">
<code>다음 시간 이후에 하드 디스크 끄기</code>가 모두 1분으로 설정되어 있었다. 이 때문에 1분마다 외장 SSD가 꺼졌다가 다시 켜지면서 탐색기가 활성화된 것이다.
모두 사용 안 함으로 바꿔주었다.</p>
<h3 id="장치-관리자의-usb-포트-설정">장치 관리자의 USB 포트 설정</h3>
<p><img src="https://velog.velcdn.com/images/its_fe/post/ebb81ee5-3224-4484-bdcd-9aaa3f631006/image.png" alt=""></p>
<p>노트북의 <code>장치 관리자</code>에서 <code>범용 직렬 버스 컨트롤러</code>에 들어간다. 그 중 <code>USB 루트 허브(USB 3.0)</code>의 속성으로 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/997e8511-8516-4dec-937f-0a23595a022a/image.png" alt=""></p>
<p><code>전원 관리</code> 탭에서 &#39;전원을 절약하기 위해 컴퓨터가 이 장치를 끌 수 있음&#39;을 선택 해제한다. 이 항목이 선택되어 있어서, 전원 절약을 위해 포트에 연결된 외장 SSD를 끄는 일이 일어날 수 있다. </p>
<p>이 두 가지 설정을 마치고 나니 더 이상 문제가 발생하지 않았다. 오늘도 문제를 해결했다😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[외장 SSD + GitHub Desktop으로 여러 기기에서 개발하기]]></title>
            <link>https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD-GitHub-Desktop%EC%9C%BC%EB%A1%9C-%EC%97%AC%EB%9F%AC-%EA%B8%B0%EA%B8%B0%EC%97%90%EC%84%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/%EC%99%B8%EC%9E%A5-SSD-GitHub-Desktop%EC%9C%BC%EB%A1%9C-%EC%97%AC%EB%9F%AC-%EA%B8%B0%EA%B8%B0%EC%97%90%EC%84%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 03 Mar 2025 13:38:15 GMT</pubDate>
            <description><![CDATA[<p>외장 SSD + GitHub Desktop을 사용해서 데스크탑, 노트북 환경에서 개발하다가 하루 종일 작업한 파일을 날릴 뻔한 이야기를 담았다. 작업 환경을 잘 설정해야 한다는 깨달음을 얻고...</p>
<h1 id="배경">배경</h1>
<p>집에서 개발할 때는 데스크탑을 사용하고, 밖에서 개발할 때는 노트북을 사용한다. 그러다 보니 프로젝트 파일들을 한 기기에만 저장해둘 수 없고 기기간 공유가 가능해야 했다. </p>
<p>처음에는 OneDrive를 사용했다. 학교 계정으로 쭉 사용하다가 어느 순간 학교 계정의 스토리지 용량이 확 줄고 나서는 개인 계정을 사용했다. OneDrive는 안전하게 데이터를 저장할 수 있다는 장점이 있지만, 속도가 느리다는 단점이 있다. 파일을 옮기는 데도 한참 걸리고, 특히 프로젝트에서 빌드하고 개발 서버를 열었을 때 필요한 파일들을 모두 다운로드 할 때까지 기다려야 한다는 점이 답답했다. </p>
<p>어느 날, 일부 파일이 OneDrive에서 동기화가 안 되는지 vscode 상에서 계속 오류가 났다. 폴더를 삭제하고 다시 clone해도 같은 문제가 반복되어 이대로는 안 되겠다 싶어서 방치했던 외장 SSD를 꺼내들었다.</p>
<h1 id="사건">사건</h1>
<p>데스크탑과 노트북의 GitHub Desktop에서 외장 SSD의 폴더에 프로젝트를 clone했다. 사실 이 부분은 정확히 기억이 안 나는데, 데스크탑에서 clone했으면 노트북에서는 Add Existing Repository 방식으로 접근했을 것이다. </p>
<p>오늘 사건이 터졌다. 어제 하루 종일 데스크탑에서 작업하고, 로컬에만 commit을 해두었다. 그리고 카페에 가서 노트북으로 작업을 이어서 하려고 했는데, 외장 SSD를 연결하자 어제 작업한 게 보이지 않았다. 며칠 전에 rebase 과정에서 꼬인 파일들만 남아있을 뿐, 작업한 것은 온데간데 없었다. 다른 폴더를 보고 있는 건가 싶어서 아무 생각 없이 GitHub Desktop에서 레포지토리를 삭제했다. 여기서 아차 싶었는데😅, 로컬에서만 작업한 것이었기 때문에 다시 clone했을 때는 당연히 작업한 파일들이 없었다. </p>
<p>정말 다행히도 레포지토리를 삭제할 때 휴지통으로 폴더를 버리는 옵션을 선택해서 삭제한 폴더는 휴지통에 남아있었다. </p>
<p>급하게 집으로 돌아와 데스크탑을 켜고... 휴지통에서 레포지토리를 복구했다. 그리고 커밋 기록을 되찾았다. 🫠</p>
<h1 id="분석">분석</h1>
<blockquote>
<p><strong>데스크탑은 C, D 드라이브가 모두 있기 때문에 외장 SSD가 E 드라이브로 인식되고 있었다. 하지만 노트북은 C 드라이브만 있기 때문에 외장 SSD가 D 드라이브로 인식되고 있었다.</strong></p>
</blockquote>
<p>노트북에서 삭제한 레포지토리를 복구했더니 데스크탑에서 외장 SSD(E 드라이브)가 아닌 기존의 D 드라이브로 복구되었다. 외장 SSD가 환경에 따라 드라이브 문자가 다르기 때문에 이런 현상이 일어났다. </p>
<p>노트북에서 작업하다가 데스크탑으로 옮겨왔을 때, E 드라이브가 아닌 D 드라이브에서 작업이 이어졌다면 데스크탑에서 작업한 것은 외장 SSD에 저장되는 것이 아닌, 데스크탑에 저장되었을 것이다. 이 때문에 동기화가 제대로 되지 않은 것으로 파악했다. </p>
<hr>
<h2 id="또-다른-문제점-파악">또 다른 문제점 파악</h2>
<p>문제점을 파악하다가 위의 문제와 별개로 이전에 종종 겪었던 문제의 이유도 찾게 되었다. <strong>노트북, 데스크탑에서 번갈아가며 작업하던 레포지토리에서 종종 파일 충돌이 일어나곤 했다.</strong> 로컬에서 나 혼자 작업했는데, 파일 충돌이라니 뜬금없다. 당시에는 작업 자체에 문제가 없었기 때문에 그냥 수정하고 넘겼지만, 이번 기회에 같이 해결해야겠다고 마음 먹었다. </p>
<p>그것은 바로 데스크탑과 노트북에서의 Git 로컬 설정이 달랐기 때문이다. 커밋 기록이 다르게 남기 때문에 다른 사람이 파일을 수정한 것으로 인식한 것이다.<br><code>git config --global user.name</code>과 <code>git config --global user.email</code> 명령어를 통해 설정된 사용자의 name과 email을 확인할 수 있다.</p>
<p>확인해 보니, 데스크탑에서는 name이 <code>chysis</code>로 설정되어 있었고, 노트북에서는 <code>Fe</code>로 설정되어 있었다. 최근에 노트북을 바꾸면서 다르게 설정된 것 같은데, 이것이 문제가 되었다.</p>
<h1 id="해결">해결</h1>
<blockquote>
<p><strong>1. 데스크탑, 노트북에서 인식하는 외장 SSD의 드라이브 문자를 통일</strong></p>
</blockquote>
<p>먼저, 동기화 문제를 해결하기 위해 데스크탑과 노트북에서 인식하는 외장 SSD의 드라이브 문자를 통일했다. </p>
<p><code>Win + R</code>에서 <code>diskmgmt.msc</code>를 입력하면 디스크 관리 메뉴로 들어갈 수 있다. 거기서 외장 SSD의 드라이브 문자를 T로 수정했다. 다른 USB 기기가 연결될 경우 E, F까지는 충분히 할당될 수 있다고 판단하여, 아예 관련 없는 문자로 설정했다.
<img src="https://velog.velcdn.com/images/its_fe/post/8f69f2be-99c4-42a3-8638-673a4882d713/image.png" alt=""></p>
<p>노트북에서도 마찬가지로 외장 SSD를 연결한 뒤에 드라이브 문자를 수정했다.</p>
<blockquote>
<p><strong>2. Git 로컬 설정 통일</strong></p>
</blockquote>
<p>다음으로는 커밋 충돌을 방지하기 위해 데스크탑과 노트북의 Git 로컬 설정을 통일했다.</p>
<p><code>git config --global user.name chysis</code> 명령어를 통해 두 기기에서 <code>user.name</code>을 모두 chysis로 통일했다. 이제 충돌 문제는 발생하지 않을 것이다. </p>
<blockquote>
<p><strong>3. GitHub Desktop에서 clone하지 않고 수동으로 clone 후, GitHub Desktop에서는 Add Existing Repository만 하기</strong></p>
</blockquote>
<p>외장 SSD의 폴더 경로를 확실히 인식할 수 있도록 하는, 좀 더 안정적인 방법을 선택했다. GitHub Desktop에서 clone할 경우 간편하지만 혹시라도 경로가 변경될 가능성이 있기 때문이다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/85fb1ebb-cf3a-4ab3-a4de-94c72cef77da/image.png" alt=""></p>
<p>PowerShell에서 수동으로 clone했다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/bf0dc7d4-9ded-4a05-89a8-a5336dc6be3b/image.png" alt="">
이후 GitHub Desktop에서는 Add local repository만 했다. 이렇게 되면 경로는 유지한 채로 GitHub Desktop에서는 GUI만 편하게 이용할 수 있게 된다. </p>
<hr>
<p>여러 기기에서 작업하는 만큼, 환경을 동일하게 설정해서 안전하게 작업할 수 있도록 노력해야겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024년 돌아보기]]></title>
            <link>https://velog.io/@its_fe/2024%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/2024%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 31 Dec 2024 14:26:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/its_fe/post/b0cbed0a-7c58-4725-960c-05f9dc0efea6/image.JPG" alt="">
이번에도 직접 찍은 사진으로 시작하며...</p>
<hr>
<p>2024년이 가기 전에 한 해를 돌아보려 한다. 올해는 정말이지 폭풍 같은 해였다. 좋은 사람들도 많이 만나고, 살면서 제일 많이 아팠던 1년일만큼 잠까지 줄여가며 개발에 몰입했다😊 또 그만큼 열심히 놀기도 했다.</p>
<p><a href="https://velog.io/@its_fe/2023%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0">2023년 돌아보기</a>에서는 시간 순으로 회고를 작성했다면, 이번에는 키워드를 중심으로 회고해볼까 한다.</p>
<h1 id="우아한테크코스-6기">우아한테크코스 6기</h1>
<p>2024년을 한 마디로 요약하자면, <strong>우테코</strong>라 할 수 있다. 2월부터 11월 말까지 함께했다. 진로와 더불어 어떻게 개발 공부를 하면 좋을지 고민이 깊어지던 찰나에 우테코 6기에 최종 합격했다.</p>
<h2 id="몰입">#몰입</h2>
<p>10개월 내내 할 게 정말 많았다. <code>이걸 다 할 수 있을까?</code> 싶었지만 결국 모두 해낸 내 자신을 보며 뿌듯했다. 미션이나 프로젝트 모두 최소한의 요구 사항만 있었지만 매번 거기서 그치지 않고 스스로 더 발전시킬 수 있는 부분을 찾았다. 주변 크루들 모두가 개발에 진심이었기 때문에 가능했다고 생각한다😊</p>
<p>레벨 1, 레벨 2 기간 동안에는 9~10시까지 자주 남아서 개발했다. 몇 번은 왔다감에도 등장했다. 거기에 왕복 3시간에 달하는 출퇴근길이 더해지니 정신적으로 지쳤던 것 같다. 그래서 프로젝트를 시작하면서는 필요한 상황이 아니고서는 일찍 집에 돌아왔다. 그리고 편한 환경에서 작업을 이어나갔다. 하지만 집에만 일찍 돌아왔을 뿐, 매일 잠을 줄여 개발하면서 면역력도 급속도로 떨어졌다. 🫠 한 번도 걸린 적 없던 병까지 얻고 나서야 컨디션 관리를 열심히 했다. 그치만 후회는 없다. 살면서 이렇게까지 개발에 몰입했던 적이 있을까 싶다. 또, 체력 관리를 어떻게 해야 하는지 그 노하우도 생겼다.</p>
<h2 id="나무심기">#나무심기</h2>
<p>나는 2023년 3월부터 매일 백준 문제를 풀고 있다. 코딩 테스트도 대비하고 문제 푸는 습관을 꾸준히 들이기 위해 매일 한 문제씩 풀고 인증하는 <code>나무심기</code> 스터디를 운영했다. 안 풀면 하루에 1500원의 벌금도 있다.</p>
<p>프로젝트 중간쯤부터는 다들 너무 바쁘기도 했고, 스터디가 원래 취지대로 돌아가고 있지 않는 것 같아 자율제로 바꾸었다.
<img src="https://velog.velcdn.com/images/its_fe/post/0b6adab8-3953-4344-b63a-c8e4583ce8d0/image.png" alt="">
▲ 지금까지도 묵묵히 인증방을 지키고 있는 고독한 에프이</p>
<p>나는 지금까지도 스트릭을 유지하고 있다. 사실 이게 큰 의미는 없다. 실력과는 더더욱 관계가 없다. 하지만 이왕 한 거, 최선을 다하자는 생각으로 계속하고 있다. 고등학생 때 수학 문제푸는 것과 비슷하게, 관성이 아주 중요하다고 생각하기 때문에 감을 잃지 않기 위해 노력하는 것이다. 꾸준함을 나의 무기로 삼고 싶다.</p>
<p>나무심기 말고도 레벨 1 크론조와 함께 <code>리액트 스터디</code>와 <code>토론 스터디</code>도 했다. 여러 사람 앞에서 발표하는 경험도 쌓을 수 있었고, CS 지식과 함께 리액트의 동작 원리를 깊이 있게 학습한 귀중한 경험이었다.</p>
<h2 id="리뷰미">#리뷰미</h2>
<p><code>리뷰미</code>는 우테코 기간의 절반 정도를 함께한 우리 팀의 서비스 이름이다. 협업한 동료들에게 피드백을 받아서 나를 파악하고 표현하는 데 도움을 주는 서비스이다. 이 글을 읽고 있는 여러분들도 <strong>프로젝트에서 내 모습이 어땠는지 궁금하다면</strong>, <strong>나는 무엇을 잘하는 개발자인지 궁금하다면</strong> 리뷰미를 사용해볼 것을 추천한다!!🔥😊</p>
<h3 id="🔗리뷰미-바로가기"><a href="https://review-me.page">🔗리뷰미 바로가기</a></h3>
<p><img src="https://velog.velcdn.com/images/its_fe/post/33672b79-3b3d-4844-8074-64729914b88e/image.png" alt="">
▲ 리뷰미 서비스 화면 (실제로 받은 리뷰 모아보기)</p>
<p>8명이 모여서 4달 동안 하나의 프로젝트를 한 만큼, 진한 협업 경험을 쌓을 수 있었다. 이전에는 제대로 된 프로젝트 경험이 없었는데 기획부터 개발, 유저 테스트, 실사용자 모집, 유지보수까지 모든 과정에 참여하면서 배운 것이 정말 많았다. 코드 리뷰를 하고 의견을 주고 받으면서 다른 크루들의 코드로 공부할 수 있었다. 유저 테스트와 데모데이를 진행하면서는 사용자의 생각은 개발자의 생각과 꽤 다르다는 것을 알 수 있었다. 팀 회의를 거듭해서 사용자의 생각을 추측하기보다는 빠르게 개발하고 피드백을 받으면서 발전시키는 것이 더 효율적이라는 것을 느꼈다.</p>
<p>우테코 수료 이후, 지금까지도 프로젝트를 이어가고 있다. 리뷰미를 통해 최대한 많은 경험을 해보고 싶다.</p>
<hr>
<p>우테코를 세 가지 태그로 요약하기에는 너무나 많은 일들이 있었다. 그래도 가장 주도적으로 참여했고 열정적이었던 세 가지를 적어보았다😊</p>
<h1 id="새로운-도전">새로운 도전</h1>
<p>우테코 수료 이후, <strong>2025 스마일게이트 DEV CAMP</strong>에 지원했다. 12월 말부터 2월 말까지, 방학 시즌 동안 프로젝트에 몰입할 수 있는 개발 캠프이다. </p>
<p>1차 서류는 합격했다! 그리고 2차 면접은 <strong>팀 면접 + 개인 면접</strong>으로 진행됐다. 팀 프로젝트 면접은 지금까지 경험해본 적이 없었다. 2시간 동안 주어진 상황에서 프로젝트를 기획하고, 발표하는 형식이었다. 처음 본 사람들과 그 자리에서 바로 협업하는 것이 쉬운 일은 아니었지만, 신선하고 재밌었다. </p>
<p>팀 면접에서 프로젝트 기획 발표를 마친 뒤에는 개별적으로 그동안의 경험과 본인의 기술 역량을 드러낼 수 있는 질문 타임이 이어졌다. 여기서 나의 이야기를 충분히 하지 못했다. 서류 지원 당시 선택 사항이었던 포트폴리오를 제출하지 않았는데, 이것이 나에게 꽤 큰 타격이었던 것 같다. 면접 당시 이 사실을 나에게 재차 확인하셨고, 내가 너무 오만했다는 생각이 들었다. </p>
<p>팀 면접에서의 아쉬움을 조금이나마 벗어내기 위해 개인 면접에서 최선을 다했다. 주로 인성 질문이었고, 준비한 것은 모두 이야기하고 왔기 때문에 후회는 없었다.</p>
<p>아쉽게도 면접에서 불합격했지만, 앞으로 내가 경쟁력 있는 개발자가 되기 위해 무엇을 어떻게 준비해야 할지 배울 수 있는 경험이 되었다. </p>
<h1 id="그리고-복학-준비">그리고.. 복학 준비</h1>
<p>나는 아직 학교를 세 학기 더 다녀야 한다. 엇복학 때문에 학사 일정이 꼬인 상태에서 졸업 요건을 채우려다 보니 너무 골치 아팠다. 가장 큰 문제는 졸업 작품이었다. 우리 학교는 졸업 작품을 1학기에 시작해서 2학기에 발표해야 인정되기 때문에, 여름 졸업자의 경우 그 전년도에 끝내야 했다. 내가 그런 상황이었고, 26년 8월 졸업을 위해서는 내년까지 졸업 작품을 모두 끝내야 했다.</p>
<p>졸업 작품 참가 신청 기간을 하루 남기고 이 사실을 알게 되어 급하게 팀을 구하게 되었다. 다행히 팀을 구했고, 열심히 참여할 일만 남았다. 졸업이 걸린 일이라 스트레스를 많이 받았는데 다행히 첫 단추가 잘 꿰어져서 한 시름 놓았다. 🫠</p>
<p>1년 동안 학교에서 벗어나 우테코에서 야생 학습을 하다 보니 이제는 학교가 굉장히 어색하게 느껴진다. 하지만 돌아가서는 이전보다 더 열심히 살아갈 것 같은 확신이 든다. <code>더한 것도 해냈는데!</code> 이런 생각으로 말이다😅</p>
<h2 id="내년-계획">내년 계획?</h2>
<p>내년 1학기까지는 우선 공부(학교, 개발 모두)와 졸업 작품에 집중하면서 적응하는 것을 목표로 하고 있다. 물론 장학금을 목표로 할 것이다😎</p>
<p>2학기부터는 졸업 작품 발표와 함께 외부 활동하는 것을 목표로 하고 있다. 취업 준비를 병행하지 않을까 싶다.</p>
<hr>
<p>글을 쓰고 나니 2024년이 30분 남았다. 2023년에 비해 정말 많이 성장한 한 해였고, 2025년에는 올해보다도 더욱 성장하고 싶다. 블로그도 열심히 쓰면서 말이다. 내년에도 파이팅!🎊🎉</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 백준 2285 - 우체국]]></title>
            <link>https://velog.io/@its_fe/Algorithm-%EB%B0%B1%EC%A4%80-2285-%EC%9A%B0%EC%B2%B4%EA%B5%AD</link>
            <guid>https://velog.io/@its_fe/Algorithm-%EB%B0%B1%EC%A4%80-2285-%EC%9A%B0%EC%B2%B4%EA%B5%AD</guid>
            <pubDate>Mon, 09 Dec 2024 16:56:37 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2285">https://www.acmicpc.net/problem/2285</a>
▲ 문제 바로가기</p>
<p>수직선상에 마을이 분포해 있고, 각 마을에는 사람들이 살고 있다. 그리고 각 사람들까지의 거리의 합이 최소가 되는 위치에 우체국을 세우는 문제이다.</p>
<h1 id="approach-1-naive">Approach 1: Naive</h1>
<p>가장 쉽게 생각할 수 있는 방법은 범위 내의 모든 점에 대해 각 사람들까지의 거리의 합을 직접 구하는 것이다. 하지만 범위의 길이가 최대 20억, 사람 수는 10억이기 때문에 엄청나게 큰 수가 나오고, 오버플로우가 발생한다.</p>
<p>좌표의 모든 점이 아닌, 입력으로 들어온 최대 10만 개의 마을에 우체국을 세운다고 해도 여전히 $20억 * 10억 * 10만$을 계산해야 한다. <code>long long</code> 타입을 사용해도 담을 수 없는 큰 수이다.</p>
<h1 id="approach-2-가중-평균">Approach 2: 가중 평균</h1>
<p>그 다음으로 생각한 방법은 가중평균을 구하는 것이다. 입력으로 들어온 마을 좌표와 사람 수를 곱해서 더한 뒤, 총 사람 수로 나눈다. 하지만 이 방법도 수식을 활용하는 것 뿐, $10억 * 10억 * 10만$을 계산해야 하기 때문에 구할 수 없다.</p>
<p><strong>사실 가중평균은 <code>거리의 제곱의 합</code>을 최소화하기 때문에, <code>절댓값 거리의 합</code>을 최소화해야 하는 이 문제에서는 잘못된 사용이다.</strong></p>
<h1 id="approach-3-그리디">Approach 3: 그리디</h1>
<p>불현듯 이전에 어디선가 봤던 방법이 떠올랐다. 마을까지의 거리의 합이 아닌 각 사람들까지의 거리의 합을 최소화해야 한다. 우체국을 기준으로 왼쪽에 사는 사람들의 합과 오른쪽에 사는 사람들의 합이 같다면 되지 않을까?로 접근했다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/e0b91f52-624e-4959-9415-1311118213f1/image.png" alt=""></p>
<p>위와 같은 상황을 생각해보았다. -99~99 사이 어느 곳에 우체국이 있어도 거리의 합은 200으로 같다. 이보다 더 작을 수는 없다.</p>
<p>그렇다면 사람이 조금 더 분포해 있어도 이 추측이 들어 맞을까? </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/30609254-d140-44db-bce6-9cd5cd5f5691/image.png" alt=""></p>
<p>위와 같은 경우에도 들어 맞는 것을 확인할 수 있었다.</p>
<p>하지만 이 문제의 예제를 보면, 좌우에 사는 사람의 수가 같아지는 점이 마을이 있는 좌표라는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/8e929014-b2ca-4bab-8f9f-e2f668d69642/image.png" alt=""></p>
<p>우체국이 2에 있다고 가정했을 때 좌우에 3명씩 있기 때문에 우체국이 2에 있으면 된다. 하지만 입력으로 주어지는 분포가 대칭형이라는 보장은 없다.</p>
<h2 id="언제-최소가-될까">언제 최소가 될까?</h2>
<p>우체국을 기준으로 좌우에 사는 사람의 수가 같으면 최소가 된다는 것을 몇 가지 예제를 통해 확인했다. 하지만 이것은 특수한 경우고, 최소를 만들기 위해서는 <strong>우체국 기준 좌우에 사는 사람의 수를 최대한 비슷하게</strong> 우체국의 위치를 잡아야 한다.</p>
<p>그리고 <strong>우체국이 세워진 곳에 사람이 많이 살고 있으면</strong> 유리하다. 그 사람들은 우체국까지의 거리가 0이 되기 때문이다.</p>
<h2 id="구현">구현</h2>
<ol>
<li>먼저 입력으로 들어온 모든 사람의 수의 합을 구한다.</li>
<li>입력을 좌표 순으로 오름차순 정렬한다. 문제의 조건에서 거리의 합이 최소가 되는 점이 여러 개일 경우 그 중 작은 값을 출력해야 하기 때문이다.</li>
<li>배열을 순회하면서 사람 수의 누적 합을 계산한다. 만약 그 값이 <strong>전체 사람 수의 절반 이상</strong>이라면 그때의 좌표를 출력하고 순회를 멈춘다.</li>
</ol>
<p>위 그림처럼 직접 계산해 보면서 관찰한 내용을 토대로 코드를 작성했고, 정답 처리되었다.</p>
<p>문제는 풀었지만 개운한 느낌이 들지 않았다. 왜 우체국이 사람 수의 절반쯤 되는 지점에 있으면 거리의 합이 최소가 되는 걸까? 수식으로 좀 더 명확히 이해하고 싶었다.</p>
<h1 id="수식으로-이해하기">수식으로 이해하기</h1>
<h2 id="수식-정의">수식 정의</h2>
<p>거리의 합을 다음과 같이 정의할 수 있다.
<img src="https://velog.velcdn.com/images/its_fe/post/9424b726-6fe9-45d1-9cb4-26d31e4e1ce1/image.png" alt=""></p>
<p>문제의 목표는 $D(x_k)$를 최소화하는 것이고, 그러기 위해서는 $x_k$에 따라 변하는 $D(x_k)$의 변화량을 관찰해보자.</p>
<h2 id="변화량-관찰">변화량 관찰</h2>
<p>먼저 위 수식에서 절댓값을 풀어보자.
<img src="https://velog.velcdn.com/images/its_fe/post/a0d64bde-16d9-4a30-a0f1-7f26599cd4bc/image.png" alt=""></p>
<p>$D(x_k)$의 변화량인 $\Delta D(x_k)$는 다음과 같다.
<img src="https://velog.velcdn.com/images/its_fe/post/dc6d47b8-613d-4c57-9147-b997f627bead/image.png" alt="">
절댓값을 푼 수식에서 $x_k$가 커짐에 따라 증가하는 부분은 <code>+</code>의 부호만 가지게 되고, 감소하는 부분은 <code>-</code>의 부호만 가지게 된다. </p>
<p>변화량의 각 항을 이해해보자!
첫 번째 항은 우체국보다 왼쪽에 있는 사람 수의 합이다. 두 번째 항은 우체국보다 오른쪽에 있는 사람 수의 합이다. 그 두 항의 차가 곧 변화량이다.</p>
<p>$x_k$는 가장 작은 값(입력 중 가장 작은 좌표)에서부터 점점 커진다. 처음에는 변화량도 가장 작은 값을 갖는다. 그때 두 번째 항의 값이 가장 크기 때문이다.
<img src="https://velog.velcdn.com/images/its_fe/post/ef5fcdb0-4a97-4422-937e-21315c292ffa/image.png" alt=""></p>
<p>$x_k$가 커지면서 변화량도 함께 증가하고, 그러다가 어느 순간 변화량이 0 이상이 되는 순간이 생긴다. <strong>그 때 거리의 합은 최소가 된다.</strong></p>
<p>$i$는 정수이기 때문에 위에서 정의한 <strong>거리의 합과 변화량 모두 실수에서 연속인 함수가 아니다.</strong> 따라서 변화량이 정확히 0이 되는 점이 없을 수 있기 때문에 <strong>처음으로 0 이상인 점</strong>을 찾아야 한다.</p>
<h2 id="결론">결론</h2>
<p><img src="https://velog.velcdn.com/images/its_fe/post/cdb4f0ea-11a0-4b4a-87ed-51405751dcdf/image.png" alt="">
변화량이 처음으로 0 이상이 되는 점, <strong>왼쪽 사람 수가 오른쪽 사람 수보다 같거나 많아지는 순간</strong>을 찾아야 한다는 것을 수식을 통해 이해할 수 있다.</p>
<h1 id="마무리">마무리</h1>
<p><code>이러면 되지 않을까?</code> 감으로 해결한 문제여서 풀고 나서도 찝찝함이 남아있었다. 하지만 수식을 통해 실제로도 그럴 수밖에 없다는 것을 증명해 내니 남아있던 찝찝함이 해소되었다. </p>
<p>입력으로 분포가 주어지고, 그 중 하나의 점을 잡았을 때 거리의 합이 최소가 되는 점을 찾는 유형. 꽤 많이 보이는 것 같다. 증명을 했으니 앞으로 비슷한 상황이 왔을 때 이 테크닉을 생각해낼 수 있도록 연습해야겠다.😊</p>
<p>틀린 부분이 있다면 언제든 피드백 남겨주세요 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우테코] 리뷰미 인프라 이관]]></title>
            <link>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A6%AC%EB%B7%B0%EB%AF%B8-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%9D%B4%EA%B4%80</link>
            <guid>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A6%AC%EB%B7%B0%EB%AF%B8-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%9D%B4%EA%B4%80</guid>
            <pubDate>Fri, 06 Dec 2024 09:54:59 GMT</pubDate>
            <description><![CDATA[<h1 id="🔍리뷰미가-뭐예요">🔍리뷰미가 뭐예요?</h1>
<p><img src="https://velog.velcdn.com/images/its_fe/post/d05c0fd0-0077-4629-b4c9-1a79be8b50d0/image.png" alt="">
<strong>(<del>광고</del>) 리뷰미를 통해 함께 일한 동료로부터 피드백을 받아보세요!</strong></p>
<p><a href="https://review-me.page" target="_blank"> <strong>리뷰미 바로가기</strong> </a></p>
<p>리뷰미는 우테코 레벨 3, 4 프로젝트 기간 동안 피땀 흘려 열심히 기획하고 개발한 서비스이다. <strong>동료로부터 피드백을 받아 자신이 어떤 개발자인지 파악하고, 표현하는 데 도움을 주는 서비스</strong>이다. </p>
<blockquote>
<p>🤔 나는 <strong>무엇을 잘하는 개발자</strong>일까?
📚 어떤 점을 보완하면 내가 더 <strong>성장</strong>할 수 있을까?
🫂 우리 팀원은 <strong>나를 어떻게 생각할까?</strong></p>
</blockquote>
<p>개발자라면 한 번쯤은 이런 고민들을 했을 것이다! 
우테코에서는 크루들끼리 서로 피드백 해주는 문화가 활성화되어 있는데, 피드백을 통해 성장하는 데 도움이 되었다. 이 경험을 살려 위의 질문들에 답하는 데 도움을 주고자 리뷰미가 세상에 나오게 되었다.</p>
<h1 id="이관하게-된-배경">이관하게 된 배경</h1>
<p>우테코에서는 레벨 3부터 수료할 때까지 AWS 리소스를 제공해준다. 리뷰미 프론트엔드는 AWS CodeBuild와 CodePipeline으로 CD를 구축했고, S3와 Cloudfront를 사용해 배포했다.</p>
<p>하지만 수료식과 동시에 AWS 지원이 끊어지기 때문에 서비스를 계속 유지하기 위해서는 인프라를 이관해야 했다. 레벨 5 기간 동안 두 차례 인프라 회의를 통해 어디로 이관할지와 어떤 서비스를 사용할지 세부 전략을 세웠다.</p>
<blockquote>
<p><strong>인프라 선정 기준</strong></p>
<ol>
<li>비용의 대부분은 서버에서 발생하기 때문에 백엔드 팀원들의 의견을 더 듣는다.</li>
<li>비용 부담은 최소화한다.</li>
</ol>
</blockquote>
<p>Microsoft Azure, Amazon AWS, Naver Cloud, Oracle Cloud 등 여러 후보가 있었다. 그 중 무료 요금제로 4대의 인스턴스를 제공하고 객체 스토리지도 제공하는 <strong>Oracle cloud</strong>로 결정했다. 관련 자료가 많지 않다는 점이 걸렸지만, 우선은 백엔드와 프론트엔드 모두 오라클 클라우드로 이관하기로 했다.</p>
<p>OCI(Oracle Cloud Infrastructure)는 기존에 사용하던 AWS와 사용하는 용어가 많이 달랐지만, CodePipeline이나 CodeBuild 등과 대응하는 서비스들은 모두 제공하고 있었다.</p>
<h1 id="oci-순탄하지-않았다">OCI, 순탄하지 않았다</h1>
<p><strong>OCI를 사용해 보면서 삽질을 통해 많은 공부가 되었다.</strong></p>
<p>가장 먼저 어떤 것을 이관할 것인지 정리했다.</p>
<blockquote>
<p><strong>스토리지</strong>: AWS S3 → OCI Object Storage
<strong>CDN</strong>: Cloudflare
<strong>CI</strong>: 기존의 GitHub Actions 유지
<strong>CD</strong>: AWS CodeBuild, CodePipeline → OCI DevOps</p>
</blockquote>
<p>OCI에도 AWS S3와 유사한 객체 스토리지를 제공하고 있었다. CDN의 경우엔 OCI에서 자체적으로 제공하지 않기 때문에 외부 서비스인 Cloudflare를 사용하기로 했다. 이는 관련 자료를 발견했기 때문에 결정할 수 있었다.
CI는 기존의 방식을 유지하고, CD는 빌드부터 파이프라인까지 한 번에 관리할 수 있는 OCI의 DevOps 서비스를 사용하기로 했다.</p>
<h2 id="oci-devops-도전하기">OCI DevOps 도전하기</h2>
<p>DevOps 프로젝트를 생성한 뒤에, 리뷰미 레포지토리를 연동하려 했다. DevOps 메뉴 중 <code>Code Repository</code>와 <code>External Connection</code>를 사용해 보았다.</p>
<p><code>Code Repository</code>에서는 HTTPS 방식이나 SSH 방식으로 레포지토리 클론이 가능했다. 이 과정에서 SSH 키를 생성하고, OCI에 공개 키를 저장해두고 config 파일도 만들어보았다. 뭔가 이상했다. 깃허브 레포지토리를 클론하는 것으로 이해했는데 안내해주는 Git 명령어는 그렇지 않았다.</p>
<p>공식문서를 자세히 읽어보면서 Code Repository에 대해 찾아보았더니, 해당 서비스는 깃허브 레포지토리 연동이 아니라 <strong>DevOps 전용 Git 레포지토리</strong>를 만드는 것이었다.</p>
<p>다음으로는 <code>External Connection</code>로 외부(깃허브) 레포지토리를 연동하려 했다. 이를 위해서는 OCI에 깃허브 PAT를 담은 Secret을 만들어서 <code>validate</code>하는 과정이 필요했다. 하지만 리뷰미 레포지토리는 우테코의 Organization 하위에 있었기 때문에 내 개인 계정에서 발급한 PAT로는 인증하지 못했다.</p>
<p>추측하기로는 우테코 계정에서 PAT를 생성해서 공유해주어야 하는데, 현실적으로 이는 불가능했다.</p>
<h2 id="devops-없이-cd-구축하기">DevOps 없이 CD 구축하기</h2>
<p>백엔드 팀원 <code>아루</code>의 도움을 받아 우테코 계정의 권한을 사용하지 않는 방식으로 우회하는 방법을 생각해냈다.</p>
<p>OCI Object Storage에서는 정적 웹 사이트 호스팅 서비스인 OCI API Gateway를 제공하고 있다. 여기에 GitHub Actions와 OCI CLI를 함께 사용하는 접근 방식이다.</p>
<p>구체적인 흐름은 다음과 같다.</p>
<blockquote>
<p><strong>1. GitHub Actions Workflow 설정</strong>
    - 특정 브랜치에 머지되면 작동하도록
<strong>2. OCI CLI 설치 및 인증 설정</strong>
    - OCI Auth Token을 발급받고, 이를 사용해서 CLI에 접근할 수 있도록
<strong>3. 소스 빌드</strong>
    - GitHub Actions에서 프로젝트 빌드
<strong>4. Object Storage에 업로드</strong>
    - 빌드 결과물을 CLI를 통해 OCI Object Storage에 업로드
<strong>5. API Gateway 서비스를 통해 호스팅</strong></p>
</blockquote>
<p>이렇게 되면 PAT를 발급받지 않고도 CD를 구축할 수 있게 된다. </p>
<p>OCI Docs에 가이드가 있어 참고해서 API Gateway를 설정했지만 다음과 같은 오류를 마주했다.
<img src="https://velog.velcdn.com/images/its_fe/post/c129c722-6500-4157-8e27-ff6a7c2b11ee/image.png" alt=""></p>
<p>배포를 생성할 때 설정을 잘못했는지, 혹은 접근 권한이 없어서 그런지는 파악하지 못했다. 문제를 해결하려 했지만 시간은 어느덧 수료식 전날이 되었다.</p>
<p>당장 다음 날이면 지원이 종료되어 서비스를 사용할 수 없게 될 텐데, 아직 OCI로 이관된 것이 없었다.</p>
<p><strong>사용자들이 끊김없이 서비스를 사용하는 것이 가장 중요</strong>했기 때문에 우선은 빠르게 배포할 수 있는 서비스로 임시 이관하기로 했다.</p>
<h1 id="cloudflare-pages로-정착">Cloudflare Pages로 정착</h1>
<p>Cloudflare Pages는 정적 웹사이트를 손쉽게 배포하고 호스팅할 수 있는 플랫폼이다. GitHub와 연동해서 코드가 변경되면 자동으로 빌드하고 배포해주기 때문에 별도로 CD를 구축할 필요가 없다. 글로벌 CDN을 통해 배포하기 때문에 AWS의 Cloudfront를 대체할 수 있다.</p>
<p>다른 사이드 프로젝트에서 Cloudflare Pages를 사용해서 배포하고 있는데, 굉장히 쉬워서 사용하기로 했다. 그리고 무료 요금제는 월 500번까지 무료로 빌드할 수 있는데, 현재 우리 서비스는 그 정도로도 충분했다.</p>
<p>간단하게 target branch, 환경 변수 등만 설정해주면 되기 때문에 빠르게 배포할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/f7566cf5-850f-4382-80bc-478a14c66691/image.png" alt=""></p>
<p>현재 develop과 release 두 가지로 배포되고 있는 모습이다.</p>
<p>급한 불을 끄기 위해 임시로 선택했던 Cloudflare Pages였지만, 사실 기본적으로 제공하는 기능만으로도 충분했다. </p>
<p>Cloudflare Pages를 사용하면서 AWS에서 사용하는 기능들을 완전히 이관할 수 있었다.</p>
<ul>
<li><strong>AWS S3</strong> 스토리지는 <strong>Cloudflare Pages 전용 스토리지</strong>로 대체되었다.</li>
<li><strong>Cloudfront</strong>는 <strong>Cloudflare Global Network</strong>로 대체되어 전세계에 배포된다.</li>
<li><strong>AWS CodeBuild, CodePipeline</strong>은 <strong>Cloudflare Pages의 빌드 서비스</strong>로 대체되었다.</li>
</ul>
<p>그밖에 캐시나 SSL 인증 등을 모두 지원하기 때문에 우리 서비스를 배포하는 데 문제가 없다고 판단했다. 또한, OCI의 API Gateway는 무료 요금제여도 월 3달러의 요금이 부과된다. 최대한 비용을 아끼는 방향으로 작업하기로 했기 때문에 Cloudflare Pages도 괜찮은 선택지였다. 처음에는 OCI로의 최종 이관을 목표로 했다가, 팀원들과의 상의를 통해 Cloudflare Pages에 정착하는 것으로 결정했다.</p>
<p>추후에 Cloudflare Pages에서 지원하지 않는 기능을 필요로 하거나, 배포할 때 세밀한 설정이 필요한 경우에는 다른 방법을 다시 고려할 것 같다.</p>
<h1 id="마치며">마치며</h1>
<p>OCI는 결국 사용되지 않았지만 직접 몸으로 부딪혀 보면서 얻어가는 것이 분명 있었다. 인프라를 구축하는 방법은 여러 가지가 될 수 있고, 상황에 따라 사용할 수 있는 자원들을 잘 조합해야겠다는 것. 유연한 사고가 중요하다는 것을 다시끔 깨달았다🫠</p>
<p><strong>리뷰미 인프라 이관 끝!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] unordered_set과 set 탐구하기]]></title>
            <link>https://velog.io/@its_fe/C-unorderedset%EA%B3%BC-set-%ED%83%90%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/C-unorderedset%EA%B3%BC-set-%ED%83%90%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 26 Nov 2024 09:33:43 GMT</pubDate>
            <description><![CDATA[<p>매일 알고리즘 문제 풀기 611일차이다. 오늘 푼 문제는 27111번, 출입 기록이다. 문제를 풀면서 탐구했던 내용에 대해 다뤄보려 한다.</p>
<p><a href="https://www.acmicpc.net/problem/27111">https://www.acmicpc.net/problem/27111</a>
▲ 문제 링크</p>
<p>bool 타입의 <code>visited</code> 배열로 각 번호의 사람이 부대 내에 있는지를 관리한다. 출입 기록이 끝난 뒤에 visited를 순회하면서 부대 내에 있는 사람이 있는지 확인하고, 부대 내에 있으면 해당 사람의 기록이 누락되었다고 판단한다.</p>
<p>이번 글에서 탐구할 것은 <strong>출입 기록이 끝난 뒤에 부대에 남아 있는 사람이 있는지 어떻게 효율적으로 확인하는가?</strong> 이다.</p>
<h1 id="v1-배열-접근-1최댓값까지-순회">v1: 배열 접근, 1~최댓값까지 순회</h1>
<p>처음 작성했던 v1의 코드이다. v1 코드는 <strong>1부터 (등장한 사람의 번호 중 최댓값)까지 for문으로 순회한다.</strong></p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;

bool visited[200001];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);

    int N, ans=0, mx=-1;
    cin&gt;&gt;N;
    for(int i=0; i&lt;N; i++){
        int A, B;
        cin&gt;&gt;A&gt;&gt;B;
        mx=max(mx, A);
        if(B==1){
            if(visited[A]) ans++;
            else visited[A]=true;
        } else{
            if(!visited[A]) ans++;
            else visited[A]=false;
        }
    }

    // 1부터 등장한 번호 중 최댓값까지 순회
    for(int i=1; i&lt;=mx; i++){
        if(visited[i]) ans++;
    }

    cout&lt;&lt;ans;
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/its_fe/post/bdf67ef4-921a-42a0-a095-8f66b3c2822e/image.png" alt="">
▲ <strong>메모리 2216KB, 시간 36ms</strong></p>
<p>정답 코드지만, 마음에 들지 않았다. 만약 1개의 기록이 있었는데, 그 기록의 사람 번호가 <code>200,000</code>이라면? for문으로 20만 번 순회하면서 등장하지도 않은 번호를 199,999번 확인해야 한다. 즉, A가 sparse(희소)하게 분포되어 있고 최댓값이 큰 경우 비효율적이다. 좀 더 효율적으로 코드를 작성할 수는 없을지 고민하다가 v2 코드를 작성하게 되었다.</p>
<h1 id="v2-set-사용">v2: set 사용</h1>
<p><code>set</code> 자료구조를 사용해서 기록에 등장한 사람의 번호만 기억해두었다가 확인하는 방법을 떠올렸다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;set&gt;
#include &lt;algorithm&gt;
using namespace std;

bool visited[200001];
set&lt;int&gt; s;

int main()
{
    // ...
    int N, ans=0;
    cin&gt;&gt;N;
    for(int i=0; i&lt;N; i++){
        int A, B;
        cin&gt;&gt;A&gt;&gt;B;
        s.insert(A);

        // ...
    }

    // set의 원소를 순회
    for(auto a: s){
        if(visited[a]) ans++;
    }

    cout&lt;&lt;ans;
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/its_fe/post/90f87cff-96a8-47cf-9cbc-a5b733928054/image.png" alt="">
▲ <strong>메모리와 시간 모두 v1보다 크게 증가했다</strong></p>
<p>시간이 줄어들길 기대했지만 오히려 늘어났다. 왜 성능이 나빠졌을까?</p>
<h2 id="set-vs-배열-접근">set vs 배열 접근</h2>
<h3 id="연산의-시간-복잡도">연산의 시간 복잡도</h3>
<p>C++의 <code>set</code> 자료구조는 노드 기반 컨테이너로, <strong>균형 이진트리(Balanced Binary Tree)</strong>로 구현되어 있다. 원소 값을 기준으로 정렬 상태를 유지한다.</p>
<p>삽입, 삭제, 탐색 모두 $O(log N)$의 시간이 소요된다. 그렇기 때문에 $O(1)$인 배열에 비해 느리다.</p>
<h3 id="입력-데이터의-특성">입력 데이터의 특성</h3>
<p>입력 데이터가 sparse한 경우에는 set이 효율적일 수 있다. 하지만 dense(밀집)한 경우에는 삽입하는 데 시간이 많이 걸리기 때문에 이득을 볼 수 없다. </p>
<p>위 코드에서 시간 복잡도를 비교해보자. 총 N개의 입력에 대한 시간이다.
<code>K</code>는 set에 저장된 고유한 값의 개수이다.</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>삽입</th>
<th>순회</th>
<th>전체 시간 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td>set 사용</td>
<td>$O(Nlog N)$</td>
<td>$O(K)$</td>
<td>$O(NlogN+K)$</td>
</tr>
<tr>
<td>배열 접근</td>
<td>$O(N)$</td>
<td>$O(N)$</td>
<td>$O(N)$</td>
</tr>
</tbody></table>
<p>따라서 set을 사용하는 것은 일반적인 경우에 이득을 볼 수 없다는 결론을 얻을 수 있었다. 그렇다면 해시 테이블로 구현된 <code>unordered_set</code>을 사용하면 어떨까? 그렇게 v3 코드를 작성하게 되었다.</p>
<h1 id="v3-unordered_set-사용">v3: unordered_set 사용</h1>
<p>세 번째로 <code>unordered_set</code> 자료구조를 사용해 보았다. <code>unordered_set</code>은 <code>set</code>과 다르게 원소들이 정렬되어 있지 않다. 그리고 <strong>해시 테이블</strong>로 구현되어 있다.</p>
<p>삽입, 삭제, 탐색 모두 평균 $O(1)$에 가능하다. 하지만 최악의 경우(충돌이 많이 발생할 경우)에는 $O(N)$의 시간이 걸린다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;unordered_set&gt;
#include &lt;algorithm&gt;
using namespace std;

bool visited[200001];
unordered_set&lt;int&gt; s;

int main()
{
    // ...
    int N, ans=0;
    cin&gt;&gt;N;
    for(int i=0; i&lt;N; i++){
        int A, B;
        cin&gt;&gt;A&gt;&gt;B;
        s.insert(A);

        // ...
    }

    for(auto a: s){
        if(visited[a]) ans++;
    }

    cout&lt;&lt;ans;
}</code></pre>
<p>v2의 코드와 모두 똑같고, <code>unordered_set</code>을 사용한다는 것만 달라졌다. </p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/052017ba-fadf-478a-b827-c1de40663eac/image.png" alt="">
▲ <strong><code>set</code>을 사용할 때보다 시간이 절반 정도로 줄었다.</strong></p>
<p>그럼에도 여전히 배열을 사용하는 것보다는 시간이 더 많이 걸린다. 그 이유를 다음과 같이 정리할 수 있겠다.</p>
<h2 id="unordered_set-vs-배열-접근">unordered_set vs 배열 접근</h2>
<ul>
<li>배열에 값을 쓰거나 읽을 때는 단순히 메모리 주소 계산만 필요하다. 하지만 <code>unordered_set</code>은 해시 함수 계산과 충돌 처리를 해야 하기 때문에 같은 $O(1)$이라 해도 추가적인 비용이 발생할 수 있다. 특히 충돌이 많이 발생할수록 $O(K)$에 가까워진다.</li>
<li>배열은 데이터가 메모리 공간에서 연속적으로 위치하기 때문에 캐시 친화적이다. 하지만 해시 테이블은 버킷에 저장하기 때문에 메모리 공간에서 불연속적으로 배치된다. 캐시 친화적인 경우 순회하는 데 드는 비용이 줄어든다. </li>
</ul>
<p>지금까지는 배열 접근 코드가 가장 빠른 시간을 보여주었다. 그렇다면 조금 더 효율적인 코드를 위해 순회 범위를 줄여볼 수 있지 않을까?</p>
<h1 id="v4-배열-접근-최솟값최댓값-순회">v4: 배열 접근, 최솟값~최댓값 순회</h1>
<p>v4의 코드는 첫 번째 버전과 아주 유사하지만 배열을 순회할 때 1부터 순회하는 것이 아닌, 입력 값들 중 최솟값부터 순회한다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;

#define INF 987654321
using namespace std;

bool visited[200001];

int main()
{
    // ...
    int N, ans=0, mn=INF, mx=-1;
    cin&gt;&gt;N;
    for(int i=0; i&lt;N; i++){
        int A, B;
        cin&gt;&gt;A&gt;&gt;B;
        mx=max(mx, A);
        mn=min(mn, A);

        //...
    }

    // 최솟값~최댓값 순회
    for(int i=mn; i&lt;=mx; i++){
        if(visited[i]) ans++;
    }

    cout&lt;&lt;ans;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/its_fe/post/d539fe55-6177-4413-89c3-0da2e6f80da7/image.png" alt="">
▲ <strong>v1 코드보다 4ms 줄어들었다.</strong></p>
<p>4ms 정도는 서버의 오차 범위로 볼 수 있기 때문에 같은 코드로 총 3번 채점해보았다. 세 번의 결과가 모두 같았기 때문에 코드의 효율성이 조금이나마 개선되었다고 판단했다.</p>
<hr>
<p>배열 접근하는 것이 <code>unordered_set</code>을 사용하는 것보다 더 적은 시간이 걸린다는 것을 확인할 수 있었다.
<code>unordered_set</code>을 사용하더라도 <strong>입력 데이터의 특성에 따라서 배열 접근하는 것보다 효율적일 수 있지 않을까?</strong> 하는 궁금증이 생겼다. </p>
<p>단순히 생각해 보면 데이터가 dense한 경우에는 배열이 효율적이고, sparse한 경우에는 <code>unordered_set</code>이 효율적일 것 같다. 하지만 구체적인 기준을 계산해 보고 싶었다.</p>
<h1 id="언제-누가-더-효율적일까">언제, 누가 더 효율적일까?</h1>
<p>누가 더 효율적인지 계산하기 위해서는 <strong>데이터의 분포</strong>, <strong>탐색 범위</strong>, <strong>고유한 원소 개수</strong>를 고려해야 한다. 이를 바탕으로 확률 모델을 만들어 계산해 보자.</p>
<h2 id="1-모델-정의">1. 모델 정의</h2>
<p><strong>&lt;변수&gt;</strong></p>
<ul>
<li>$N$: 입력 데이터 개수 (여기서는 20만)</li>
<li>$mx$: 탐색 범위의 최댓값</li>
<li>$K$: 고유한 원소 개수</li>
</ul>
<p><strong>&lt;시간 복잡도&gt;</strong></p>
<p>배열 접근의 전체 시간 복잡도: $O(mx)$
<code>unordered_set</code>의 시간 복잡도: $O(N+K)$
위의 두 시간 복잡도를 비교하면 된다.</p>
<p><strong>&lt;가정&gt;</strong>
다음을 가정하자.</p>
<ol>
<li>1부터 $mx$까지 탐색하고, 위에서 언급한 탐색 범위는 그 최댓값이다.</li>
<li>배열 원소는 [1, $mx$] 범위에서 균등하게 분포되어 있다.</li>
<li>각 원소가 고유할 확률은 $K/mx$이다.</li>
</ol>
<h2 id="2-고유-원소-개수-k-계산">2. 고유 원소 개수 K 계산</h2>
<p>두 방식의 시간 복잡도를 비교할 때 mx, N는 고정되어 있고, 효율성을 결정하는 데 <code>K</code>가 큰 영향을 미친다. 따라서 K의 기댓값을 구해보자.</p>
<p><strong>입력의 $i$번째 원소가 고유 원소일 확률</strong>
기댓값을 구하기 앞서 $i$번째 원소가 고유 원소인 확률을 구해보자. 
$i$번째 원소가 고유하려면 이전의 $(i-1)$개의 원소에 $i$번째 원소가 포함되지 않아야 한다.
따라서 가능한 값의 전체 개수 $mx$ 개에서 이미 사용된 $(i-1)$개를 제외한 값들 중에서 $i$번째 원소를 선택하면 된다.</p>
<ul>
<li>$P(고유) = \frac{mx-(i-1)}{mx}$</li>
</ul>
<p>따라서 $K$의 기댓값 $E(K)$는 </p>
<ul>
<li>$E(K) = \sum_{i=1}^{N} {\frac{mx-(i-1)}{mx}} = mx \space\cdot\space(1-\frac{mx-N}{mx}\space\cdot\space\frac{1}{2})=\frac{N+mx}{2}$</li>
</ul>
<h2 id="3-시간-복잡도-비교">3. 시간 복잡도 비교</h2>
<p>$K$의 기댓값을 구했으므로 위의 시간 복잡도에 대입하자.</p>
<p><strong>배열 접근</strong>: $O(mx)$
<strong><code>unordered_set</code></strong>:$O(N+K)=O(N+\frac{N+mx}{2})$</p>
<h2 id="4-효율성-비교">4. 효율성 비교</h2>
<h3 id="배열이-더-효율적일-조건">배열이 더 효율적일 조건</h3>
<p>$O(mx)&lt; O(N+\frac{N+mx}{2})$일 때이다.
즉 $mx&lt;N+\frac{N+mx}{2}$</p>
<p>정리하면 $mx&lt;3N$이다.
즉, <strong>탐색 범위 $mx$가 $N$의 약 3배 미만일 경우</strong> 배열 접근이 더 효율적이다.</p>
<h3 id="unordered_set이-더-효율적일-조건">unordered_set이 더 효율적일 조건</h3>
<p>반대로 $mx \ge 3N$인 경우에 <code>unordered_set</code>이 더 효율적이다.</p>
<h2 id="5-문제에-적용">5. 문제에 적용</h2>
<p><img src="https://velog.velcdn.com/images/its_fe/post/4bcb1745-cd0b-45bc-9dd2-e013865209c4/image.png" alt=""></p>
<p>위에서 얻은 결론을 문제에 적용해보자. $N$과 $mx$ 모두 20만 이하라는 조건에서 배열이 더 효율적일 확률이 크기 때문에 이 문제에서는 배열로 접근하는 것이 일반적으로 더 효율적이라는 결론을 얻을 수 있다. </p>
<p>하지만 $N=1, mx=200,000$과 같이 일부 극단적인 케이스에서는 <code>unordered_set</code>이 효율적일 수 있다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>문제 자체는 쉬운 문제이지만, 좀 더 효율적인 코드를 작성하는 과정에서 <code>set</code>, <code>unordered_set</code>에 대해 자세히 학습할 수 있었다. 또한 확률 모델을 설계해서 언제 어떤 자료구조를 사용하는 것이 효율적일지 계산할 수 있었다.</p>
<p>틀린 부분이 있다면 언제든 피드백 남겨주세요 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] prev_permutation의 동작 원리]]></title>
            <link>https://velog.io/@its_fe/C-prevpermutation%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@its_fe/C-prevpermutation%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Fri, 22 Nov 2024 08:51:36 GMT</pubDate>
            <description><![CDATA[<p>C++의 <code>algorithm</code> 헤더에는 <code>prev_permutation</code>이라는 함수가 정의되어 있다. 범위를 인자로 받아서 이전 순열로 바꿔주는 함수이다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/cee22a07-0211-42cb-b8e9-d2ec95942c06/image.png" alt=""></p>
<p><a href="https://en.cppreference.com/w/cpp/algorithm/prev_permutation">https://en.cppreference.com/w/cpp/algorithm/prev_permutation</a>
자세한 스펙은 위 문서에 소개되어 있다.</p>
<h2 id="이전-순열이란">이전 순열이란?</h2>
<p><a href="https://www.acmicpc.net/problem/10973">https://www.acmicpc.net/problem/10973</a>
백준 10973 - 이전 순열</p>
<p>위 문제는 <code>prev_permutation</code> 함수를 사용하면 아주 쉽게 해결할 수 있다. 하지만 함수 없이 그 원리를 파악해 보며 직접 이전 순열을 구해보기로 했다. 위의 Cpp 공식문서를 참고했다.</p>
<p>먼저, 이전 순열이란 무엇일까? 어떤 순열이 있을 때, <strong>사전순으로 바로 이전에 오는 순열</strong>을 뜻한다.</p>
<p>예를 들어 길이 3짜리 모든 순열을 사전순으로 나열하면 다음과 같다.</p>
<ul>
<li>1, 2, 3</li>
<li>1, 3, 2</li>
<li>2, 1, 3</li>
<li>2, 3, 1</li>
<li>3, 1, 2</li>
<li>3, 2, 1</li>
</ul>
<p>이제 거꾸로 한 단계 돌아가면 이전 순열이 된다. <code>3, 2, 1</code>의 이전 순열은 <code>3, 1, 2</code>가 된다.</p>
<p><code>1, 2, 3</code>의 경우 이전 순열이 없는데, <code>prev_permutation</code> 함수에서는 다시 마지막 순열(<code>3, 2, 1</code>)을 가리키도록 구현되어 있다. 하지만 위 문제에서는 이전 순열이 없으면 <code>-1</code>을 출력하면 된다. 직접 구현할 때에는 필요에 따라 적절히 예외 처리를 해주면 되겠다.</p>
<h2 id="과정">과정</h2>
<p><img src="https://velog.velcdn.com/images/its_fe/post/03889471-cd0c-435f-9490-12830f29c9bb/image.png" alt="">
<code>3, 4, 1, 2</code>라는 배열을 예로 들어 이전 순열을 찾아보자.</p>
<blockquote>
<ol>
<li>오른쪽-&gt;왼쪽으로 탐색하며 교환 기준점 <code>k</code>를 찾는다.</li>
<li>교환 대상 <code>m</code>을 찾는다.</li>
<li><code>k-1</code>번째 값과 <code>m</code>번째 값을 교환한다.</li>
<li><code>k</code>번째 이후의 배열을 뒤집는다.</li>
</ol>
</blockquote>
<h3 id="1-pivot-찾기">1. pivot 찾기</h3>
<p>먼저 배열의 오른쪽부터 왼쪽으로 탐색하면서 오름차순이 처음으로 깨지는 위치를 찾는다. 즉, 처음으로 <code>arr[k-1] &gt; arr[k]</code>이 되는 <code>k</code>를 찾는다. 이 위치를 <code>pivot</code>이라 한다.</p>
<ul>
<li><code>k=3</code>: <code>2&gt;1</code>은 <code>true</code>이므로 <code>k</code> 감소</li>
<li><code>k=2</code>: <code>1&gt;4</code>은 <code>false</code>이므로 <code>pivot</code>은 2이다.</li>
</ul>
<p><code>pivot</code>부터는 오름차순 정렬되어 있다는 것을 알 수 있다(1, 2).</p>
<p>만약 <code>pivot</code>이 0이라면 배열 전체가 오름차순 정렬되어 있다는 뜻이므로, 이전 순열이 존재하지 않는다.</p>
<h3 id="2-교환-대상-찾기">2. 교환 대상 찾기</h3>
<p>다음으로 교환 대상 <code>m</code>을 찾는다. 이 값은 이전 순열을 만들기 위해 교환해야 할 적당한 값이다. 적당한 값이란 무엇일까?</p>
<p><code>k-1</code>번째 값보다 작은 값들 중 가장 오른쪽에 위치한 값을 의미한다. 따라서 <code>m</code>도 마찬가지로 배열의 오른쪽부터 탐색한다.</p>
<ul>
<li><code>m=3</code>: 2는 4보다 작고 가장 오른쪽에 있기 때문에 성립한다. 교환 대상은 3번째 인덱스가 된다.</li>
</ul>
<h3 id="3-arrk-1과-arrm-교환">3. arr[k-1]과 arr[m] 교환</h3>
<p>현재 <code>k=2</code>, <code>m=3</code>이다. <code>arr[k-1]과 arr[m]</code> 두 값을 교환하자.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/5c1307c4-f36d-4d5d-ae45-07546b8c2e88/image.png" alt=""></p>
<p>사전 순으로 보았을 때 <code>3, 4, 1, 2</code>에서 <code>3, 2, 1, 4</code>가 되었고, <strong>&quot;조금 더 작은&quot;</strong> 순열이 되었다. 하지만 바로 이전 순열은 아니다. <code>pivot</code>부터는 여전히 오름차순 정렬되어 있기 때문이다. <code>3, 2, 1, 4</code>의 다음 순열은 <code>3, 2, 4, 1</code>이다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/385ca74b-5424-499f-bdfb-2e12415f64dd/image.png" alt=""></p>
<p>따라서 우리는 <code>3, 2</code>로 시작하면서 가장 사전순으로 큰 순열을 만들어주어야 한다.</p>
<h3 id="4-arrk이후-배열-뒤집기">4. arr[k]이후 배열 뒤집기</h3>
<p><img src="https://velog.velcdn.com/images/its_fe/post/ea03b993-0470-4928-97b7-f180f9b04b64/image.png" alt=""></p>
<p><code>pivot</code> 이전까지의 배열은 교환 과정을 거치면서 안정화된 상태이다. 하지만 <code>pivot</code>부터는 사전순으로 더 증가할 가능성이 있기 때문에 내림차순으로 정렬해야 한다.</p>
<hr>
<p>지금까지의 과정을 코드로 옮겨보았다. </p>
<h2 id="소스-코드">소스 코드</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;

int arr[10001];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);

    int N;
    cin&gt;&gt;N;
    for(int i=0; i&lt;N; i++){
        cin&gt;&gt;arr[i];
    }

    int k=N-1, m=N-1;
    while(k&gt;0 &amp;&amp; arr[k-1]&lt;arr[k]) k--;

    if(k==0) {
        // 이전 순열이 없는 경우 적절한 처리
    }

    while(arr[k-1]&lt;arr[m]) m--;

    swap(arr[k-1], arr[m]);

    reverse(arr+k, arr+N);

    for(int i=0; i&lt;N; i++){
        cout&lt;&lt;arr[i]&lt;&lt;&quot; &quot;;
    }
}</code></pre>
<h2 id="덧붙이는-말">덧붙이는 말</h2>
<ul>
<li><code>pivot</code>을 오름차순이 처음으로 깨지는 두 값 중 작은 값으로 두어도 된다. 현재는 둘 중 큰 값으로 두었기 때문에 교환하는 과정에서 <code>k</code>번째가 아닌 <code>k-1</code>번째와 교환하고 있다. 하지만 이는 구현 차이일 뿐, 전체적인 흐름은 같다.</li>
<li>다음 순열을 구하는 <code>next_permutation</code> 함수는 <code>prev_permutation</code>과 반대로 구현하면 된다.</li>
<li><code>swap</code>, <code>reverse</code>는 <code>algorithm</code> 헤더에 정의되어 있다.</li>
<li>추상적인 생각을 구체적인 단어로 옮기는 것이 정말 어려운 것 같다. 최대한 흐름을 따라가기 쉽게끔 나만의 언어와 그림으로 정리해보았다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우테코] FE 레벨1 - 커피챗 회고]]></title>
            <link>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-FE-%EB%A0%88%EB%B2%A81-%EC%BB%A4%ED%94%BC%EC%B1%97-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-FE-%EB%A0%88%EB%B2%A81-%EC%BB%A4%ED%94%BC%EC%B1%97-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 10 Apr 2024 06:33:41 GMT</pubDate>
            <description><![CDATA[<p>크론과의 커피챗 회고!
원래는 3월 중순쯤에 신청하려고 했지만 신청 폼을 열심히 쓰다가 선착순을 놓쳐버렸는데... 3월 29일에 자리가 나서 그 날 커피챗을 할 수 있었다.</p>
<h2 id="커피챗을-왜-신청했는가">커피챗을 왜 신청했는가?</h2>
<p>고민을 해결하고 용기를 얻기 위해서다😎</p>
<p>나는 고민거리나 걱정이 있을 때 다른 사람에게 얘기한다. 털어놓는다는 행동 자체만으로 마음이 편안해진다. 그리고 이야기하는 과정 속에서 반드시 해결책이 나오지 않더라도 스스로 해결할 수 있는 용기를 얻는다.</p>
<p>그러니까, 크게 두 가지 이유로 간추려보면</p>
<ol>
<li>앞으로의 공부 방향 설정</li>
<li>고민 해결
(스몰 토킹은 덤)</li>
</ol>
<p>레벨1을 바쁘게 보내다 보니 고민거리들이 하나둘 생겼고, 중간에 꼭 한 번 해결하고 넘어가야겠다는 생각이 들었다.</p>
<h2 id="시간-관리">시간 관리</h2>
<p>시간은 레벨1 내내 나를 괴롭혔다. 로또 미션 step2를 진행하는 동안 집에 일이 생겨서 늦게 마무리하게 되었다. 그때부터 이전 미션과 다음 미션을 병행하게 되었다. 점심 뭐 먹지를 하면서 로또를 해야 했고, 영화 리뷰를 하면서 점심 뭐 먹지를 해야 했다. 미션 하나만 제대로 하기에도 충분하지 않은 시간인데, 두 개를 같이 하다 보니 심적으로 압박을 많이 받았다.</p>
<p>그리고 나의 성격도 한몫했다. 계획적+완벽주의 성향 때문에 어디까지 끝내겠다 마음 먹으면 잠을 줄여서라도 끝내고 잤다. 만약 결과물이 마음에 안 들면 시간을 더 투자한다. 특히 점심 뭐 먹지 step2부터 그랬던 것 같다. 하루 평균 4~5시간만 자고 일어나서 출근하다 보니 온종일 피곤했고, 집중력도 떨어졌다. 지금 생각해 보면 머리가 안 굴러간다는 얘기를 달고 살았다🤕</p>
<p>출근길 지하철에 앉아서 30~40분 정도 눈을 붙이긴 하지만 이건 말 그대로 응급처치였다. 만약 내내 서서 가야 했다면 이 글이 세상 밖으로 나올 수 있었을까? 🤔</p>
<p>크론은 <strong>그 어느 때보다도 강력한 목소리</strong>로 처방전을 내려주었다.💊
<code>잠은 무조건 충분히 잘 것</code></p>
<p>잠을 위해 미션을 포기할 수는 없으니 주말 시간을 미션에 더 투자하고, 평상시에는 잠을 충분히 자는 것이다. 잠이 부족하면 30분만에 끝날 것도 3시간이 걸리기도 한다. 근본적으로 학습과 작업의 효율성을 높이기 위해서는 잠을 충분히 자야 한다. 항상 머리로는 알고 있었지만 지키지 못했다.</p>
<p>방학인 지금, 잠을 충분히 자면서 열심히 체력 보충을 하고 있다. 레벨2가 시작되면 정말로 충분한 수면 시간을 지키도록 노력해야겠다.</p>
<h2 id="공부-방법">공부 방법</h2>
<p>지금까지 프론트엔드를 제대로 공부해본 적이 없다. 당장 귀찮거나 급하면 제대로 모르는 내용이라도 그냥 넘어가기도 했다. 그러다 보니 지식 중간중간 구멍이 뚫렸다. 동아리에서 프로젝트를 해본 경험이 있지만 흐지부지되거나 기술력 부족으로 제대로 완성한 적이 없다. 그러다가 우테코에 합류하게 되었고, 의식적으로 제대로 공부하는 건 이번이 처음이다.</p>
<p>크론과 공부 방법에 대해 이야기를 나누었고, 이를 참고해서 앞으로 어떻게 공부할지를 간단하게 정리해보았다.</p>
<h3 id="1-필요성-따져보기">1. 필요성 따져보기</h3>
<p>어떤 개념을 공부할 때 <code>왜 공부하는지</code>를 아는 것이 굉장히 중요한 것 같다. 나에게 꼭 필요한 것인지를 판단해서 <code>왜 공부하는지</code>에 먼저 답변할 것이다.</p>
<h3 id="2-근본적인-물음-던지기">2. 근본적인 물음 던지기</h3>
<p>공부의 필요성을 느꼈다면 이제 본격적으로 공부를 시작한다. 레벨1 강의에서도 <code>근본적인 물음 던지기</code>를 자주 경험했는데, 혼자 공부할 때도 이를 똑같이 적용할 것이다.</p>
<p>한 가지 예시로 크론이 내가 이전에 경험했던 프로젝트의 기술 스택과 관련해서 근본적인 물음을 주었다. </p>
<ul>
<li><code>recoil</code>이 무엇인지?</li>
<li>그렇다면 <code>상태 관리</code>를 왜 해야 하는지?</li>
<li>(기타 등등.. 기억이 제대로 안 남🥲)</li>
</ul>
<p>와 같이 계속해서 꼬리물기식으로 질문을 받다 보니 제대로 답변하지 못하는 부분을 금방 찾을 수 있었다. 단순히 많이 사용된다는 이유로, 깊은 고민 없이 기술을 사용해왔던 스스로를 반성하게 되었다.</p>
<p>그렇다면 이런 근본적인 물음을 어떻게 잘 던질 수 있을까? 
<code>아주 깐깐한 사람과 대화하듯이</code> 해보라는 조언을 들었다. 나를 모든 것을 걸고 넘어지는 사람이라 생각하고 <code>이건 왜 쓸까?</code>, <code>이건 왜 해야 할까?</code>와 같은 질문을 스스로에게 하는 것이다. </p>
<h3 id="3-경험과-생각이-드러난-글쓰기">3. 경험과 생각이 드러난 글쓰기</h3>
<p>공부하고 나서는 나의 생각이 드러난 글쓰기를 할 것이다. 생각이 드러난 글을 통해 내가 어떤 사람인지 알릴 수 있다. 레벨1을 예시로 들면 미션 피드백을 받고 나서 리뷰어와 의견을 주고 받은 경험을 글로 녹일 수 있을 것이다. </p>
<p>이전까지는 기술적인 내용들로 블로그를 채워야겠다는 생각을 많이 했었는데 <code>기술적인 내용은 다른 블로그에도 많다</code>는 크론의 말을 듣고 생각이 바뀌었다. 물론 기술적인 내용을 아예 쓰지 않는다는 의미가 아니고 나의 경험과 생각이 주가 되는 글을 쓰겠다는 것이다.</p>
<p>실제로도 구글링해보면 잘 정리된 블로그가 굉장히 많이 보인다. 단순히 기술적인 내용들을 나열한 포스팅보다는 그것을 바탕으로 고민했던 흔적을 글로 담아보려 한다.</p>
<h2 id="나만의-위키">나만의 위키</h2>
<p>레벨1에서 학습한 내용들을 바탕으로 나만의 위키를 만들고 있다. 학습한 개념에 경험했던 내용들과 나의 생각, 그리고 크루들과 나눈 이야기를 덧붙여 문서화하는 것이다. 어디서부터 어디까지, 어떻게 정리해야 할지 고민이 많이 되었지만 위에서 언급한 <code>근본적인 물음</code>에 답하는 방식으로 정리하니 약간은 감이 잡힌다.</p>
<hr>
<p>커피챗을 하고 나니 마음이 편안해졌다. 용기를 잔뜩 얻었다. 레벨2는 더 열심히 해야겠다...😊</p>
<p>마무리는 최근 찍은 벚꽃 사진
<img src="https://velog.velcdn.com/images/its_fe/post/47087cff-dfb2-4e19-8c5b-a0a43d88d45d/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우테코] FE 레벨1 - 점심 뭐 먹지? 메타인지 말하기 회고]]></title>
            <link>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-FE-%EB%A0%88%EB%B2%A81-%EC%A0%90%EC%8B%AC-%EB%AD%90-%EB%A8%B9%EC%A7%80-%EB%A9%94%ED%83%80%EC%9D%B8%EC%A7%80-%EB%A7%90%ED%95%98%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-FE-%EB%A0%88%EB%B2%A81-%EC%A0%90%EC%8B%AC-%EB%AD%90-%EB%A8%B9%EC%A7%80-%EB%A9%94%ED%83%80%EC%9D%B8%EC%A7%80-%EB%A7%90%ED%95%98%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 18 Mar 2024 17:31:38 GMT</pubDate>
            <description><![CDATA[<p>1달만에 쓰는 포스팅이다. 매주 회고를 하는 것이 목적이었지만 집에 일이 있기도 했고, 생각보다 미션을 수행하는 게 오래 걸리다 보니 쓸 것들만 잔뜩 메모해두고 못 썼다. <del>(사실 이것도 핑계겠지만?🤔)</del></p>
<p>점심 뭐 먹지 미션이 마무리되면서 메타인지 말하기 활동을 했다. 지난 로또 미션 때는 활동을 하지 못했기 때문에 이번이 처음이다.</p>
<p>내 페어는 버건디였고, 평소에 고민했던 내용들에 대해 생각을 물어보았는데 좋은 의견을 내주어서 어느 정도 생각 정리를 할 수 있었다.</p>
<h2 id="메타인지">메타인지?</h2>
<ul>
<li>메타인지는 내가 무엇을 알고 무엇을 모르는지를 아는 것이다. </li>
<li>내가 인지하는 것보다 한 차원 높은 곳에서 잘 인지하고 있는지를 돌아보는 것이 메타인지라 생각한다.</li>
</ul>
<p>메타인지 말하기 활동을 통해 내가 학습하고 새롭게 알게된 내용을 상대방에게 이야기하면서 내가 잘 알고 있는 게 맞는지 확인할 수 있다.</p>
<h2 id="메타인지-말하기">메타인지 말하기</h2>
<p>2분 30초 동안 내가 이번 미션에서 배운 것들을 정리해서 이야기하는데, 처음이라 그런지 2분 30초가 얼마나 짧은 시간인지 감이 없었다. 세부 주제를 잡지 않고 학습한 과정의 흐름을 전부 설명하다 보니 시간이 금방 지나갔고, 아쉬움이 많이 남았다.</p>
<p>짧은 시간 동안 내가 학습을 잘했다는 것을 보여주기 위해서는 <code>주제 좁히기</code>가 중요한 것 같다. 작은 주제를 한 가지만 골라보고, 살을 붙이는 방식으로 주어진 시간 내에 &#39;중심이 있는 말하기&#39;를 할 수 있을 것 같다. 이건 다음 미션이 끝난 뒤에 시도해보겠다.</p>
<hr>
<p>여기부터는 페어와 이야기하면서 생각을 정리해본 것이다.</p>
<h2 id="좋은-질문하는-법">좋은 질문하는 법</h2>
<p><code>질문이라기보다는 퀴즈에 가까운 느낌</code>
<code>왜 그런 고민을 했는지가 전혀 나와있지 않아서 아쉬웠다</code></p>
<p>이번 미션 동안 했던 질문들에 대한 답변을 일부 발췌했다. 많은 고민을 하게 해주신 리뷰어님께 감사드린다는 말씀.. 꼭 하고 싶다.
시간에 쫓기면서 미션을 하고 PR을 제출하다 보니 이전에 궁금했던 사항들이 채 다듬어지지 못한 채로 그대로 올라갔다. <strong>질문 방법을 되돌아봐야 했다.</strong> </p>
<p>내가 했던 질문들을 다시 읽어보기도 하고, 다른 사람들이 하는 질문들도 읽어보고. 좋은 질문하는 방법들도 찾아보면서 스스로 지켜야겠다고 생각되는 몇 가지 방법을 정의해보았다.
비단 미션 리뷰 뿐 아니라 살면서 누군가에게 질문할 일들이 많을 텐데, 그럴 때마다 필요한 내용이라 생각한다.</p>
<ol>
<li><p><strong>질문하는 구체적인 상황을 제시하자</strong></p>
<ul>
<li>구체적인 상황 없이 추상적인 단어들의 나열로 질문을 한다면 질문자의 의도를 파악하기 힘들 것이다.</li>
<li>상황을 먼저 제시한 후 질문하자.</li>
</ul>
</li>
<li><p><strong>고민하게 된 계기를 생각하자</strong></p>
<ul>
<li>질문을 매끄럽게 만들기 위해서는 궁금하게 된 이유를 구체적으로 밝히는 것이 좋다.</li>
<li>계기에서부터 이후 고민한 내용들을 정리하는 방식으로 질문한다면 훨씬 매끄러워질 것이다.</li>
</ul>
</li>
<li><p><strong>질문 글을 쓴 뒤에 다시 읽어보자</strong></p>
<ul>
<li>글을 쓴 뒤에 보완해야 하는 부분은 없는지 다시 읽어볼 필요가 있다. 심호흡을 하고 다시 읽어보면 어떨까?</li>
</ul>
</li>
<li><p><strong>충분히 먼저 찾아보자</strong></p>
<ul>
<li>내가 A만 알고 있는 상황에서 A에 대한 의견을 묻는 질문 방법 -&gt; 성의가 너무 없는 것 아닐까?</li>
<li>A와 비교군을 형성하는 다른 개념들을 먼저 찾아보고 비교해보면서 나의 주관을 만들자. 그 과정에서 추가적인 공부가 이루어질 것이고, 그럼에도 궁금한 부분을 이후에 질문하자.</li>
</ul>
</li>
<li><p><strong>내가 아는 것과 모르는 것 구분하기</strong></p>
<ul>
<li>결국 메타인지와 연결된다. 4번 방법 물론 질문하기 전에 반드시 선행되어야 하는 방법이지만 때로는 비교 대상이 되는 다른 개념들이 무엇인지조차 모르는 경우도 있는 것 같다.</li>
<li>이런 경우 내가 어디까지 아는지를 명확하게 판단해서 함께 언급한다면 답변해주는 사람도 답변하기 수월할 것 같다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="블로그-포스팅">블로그 포스팅</h2>
<p><code>블로그 써야지</code>, 하고 자주 다짐한다. 그런데 이를 나중으로 미루는 순간 글을 거의 안 쓰게 된다.
좋은 질문하는 방법과 함께, 회고하고 이를 블로그에 기록하는 것과 관련해서도 고민했다.</p>
<ul>
<li>글을 써야겠다는 생각이 들면, 나중으로 미루지 말자.</li>
<li>써야겠다는 생각이 들 당시의 생생한 감정이 시간이 지나면 잊히기 마련이고, 나중에 보면 하려던 이야기가 생각이 나지 않게 된다.</li>
<li>(페어의 생각을 인용함) 모든 포스팅은 저마다 그 시기가 정해져 있는 것 같다.<ul>
<li>소프트 스킬과 관련된 포스팅이라면 상관없겠지만, 기술적인 내용을 다루는 글의 경우 더욱 효과적인 학습을 위해 적절한 시기에 포스팅하는 것이 중요한 것 같다.</li>
<li>우테코가 거의 끝나가는 시점에 점심 뭐 먹지 미션을 회고할 수는 없으니까.  </li>
</ul>
</li>
<li>약간의 강제성을 가지고 주말을 활용해서 내 생각과 학습 내용을 정리하자.</li>
</ul>
<hr>
<p>미션을 열심히 하는 것도 중요하지만 바쁘게 치이다 보니 나아가는 방향이 흐트러지는 것 같다. 올바른 방향으로 나아갈 수 있도록 위와 같은 고민들을 꾸준히 해야겠다.😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우테코] FE 레벨1 - 1주차]]></title>
            <link>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-FE-%EB%A0%88%EB%B2%A81-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its_fe/%EC%9A%B0%ED%85%8C%EC%BD%94-FE-%EB%A0%88%EB%B2%A81-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 19 Feb 2024 16:00:46 GMT</pubDate>
            <description><![CDATA[<p>우테코 6기 레벨1을 시작하고 벌써 일주일이 지났다. 그 어느 때보다 바쁜 일주일이 아니었나 싶다.</p>
<p>회고를 매일 하는 게 가장 좋겠지만 바빠서 그러지는 못하겠고... 1주일에 한 번씩은 한 주를 정리하고 새로운 주를 맞이하기 위해 간단하게 회고할 수 있도록 노력해야겠다.</p>
<h2 id="ot">OT</h2>
<p>약간은 어색한 공기가 흐르는 교육장이 아직도 생각난다. 그래서 그런가? 일주일밖에 안 지났는데 잘 기억이 안 난다. OT를 진행하면서 코치님들 소개도 듣고, 여러 안내사항도 들으면서 진짜 우테코에 왔음을 실감했다.</p>
<p>그리고 첫 날부터 주어진 &quot;연극조&quot;와 나의 페어... 친해진 뒤에 연극을 하는 게 아니라 연극을 준비하면서 친해지라는 느낌이었다. 우테코의 시작을 함께 할 연극조 동료들과 돈까스 먹으러 간 게 지금도 생생하다.🤣 리안, 시모, 마루, 제이드 모두 잘 부탁해요</p>
<p>OT를 들으면서 느낀 점은, 여긴 진짜 자유롭다는 것이었다. 자유롭다는 건 다르게 말하면 나 스스로 노력하지 않으면 뒤쳐지게 될 것이라는 뜻이기도 하기에, 앞으로 열심히 해야겠다 다짐했다.</p>
<h2 id="첫-미션">첫 미션</h2>
<p>우테코의 미션은 페어 프로그래밍으로 진행된다. 내 페어 제이드와 함께 노트북 한 대에서 번갈아가며 키보드를 잡고 미션을 진행했다. 프리코스에서 했던 주제라 어렵지 않게 해나갈 수 있었다. 다만 최종 합격 이후에 JS에서 손을 뗐던 터라... 너무 감이 떨어져 있었는데 페어가 스트레스 받지는 않았을지 걱정도 된다. </p>
<p>테스트 방식이나 아키텍처 구조 등 잘 모르는 개념들이 많이 나와서 열심히 공부했다. 모르는 건 페어에게 물어보기도 하고, 리뷰어와 의견을 주고 받으면서 시야가 조금은 넓어진 것 같다. 페어 프로그래밍을 하면서 새롭게 알게 된 개념들과, 피드백을 받으면서 개선해야 할 점들 모두 잘 기록해둬야겠다. 온전히 내 것으로 만드는 게 레벨1의 목표이다.</p>
<h2 id="연극">연극</h2>
<p>처음에 연극의 요구 사항을 봤을 때 굉장히 막막했다.
<code>이걸 어떻게 하지?</code>
중, 고등학교 때도 연극을 했던 기억이 없는 것 같은데, 갑자기 우테코에서 연극이라니. 그것도 준비 기간이 얼마 주어지지 않았고, 미션 제출과도 겹쳤다. 연극 당일까지 매일 우리는 저녁에 남아서 연극을 준비했다. 함께 연극 준비하면서 이제는 다들 꽤 친해졌다.</p>
<p>&#39;mbti별 우테코 생활기&#39;를 주제로 정하기까지 시간이 꽤 오래 걸렸다. 주제를 정하고 나서 대본을 쓰는 건 생각보다 금방 했다. 1시간 회의를 했으면 50분 고민하다가 마지막 10분에 엄청난 아이디어들이 쏟아져 나오면서 휘리릭 만들어졌다. 다들 아이디어 짱👍🤣</p>
<p>오늘 연극 공연 당일이었는데, 우리 조 크루들 모두 준비했던 것보다 더 잘해주었다.👍👍 연극이 끝나고 나니 굉장히 후련하다는 생각이 가장 먼저 들었다.</p>
<h2 id="레벨1-목표">레벨1 목표</h2>
<ul>
<li>코어 자바스크립트 완독하기</li>
<li>타입스크립트 책 한 권 완독하기</li>
<li>매주 회고록 쓰기</li>
<li>피드백 받은 내용들, 새로 알게 된 내용들 잘 기록해두기</li>
</ul>
<p>(다 할 수 있겠지?!)</p>
<hr>
<p>위에 쓴 것 말고도 데일리 미팅 등등 더 많은 일들이 있었지만 이만 총총</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023년 돌아보기]]></title>
            <link>https://velog.io/@its_fe/2023%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@its_fe/2023%EB%85%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 03 Jan 2024 17:40:06 GMT</pubDate>
            <description><![CDATA[<hr>
<p>지금까지는 따로 회고록을 작성해 본 기억이 없다. 2024년에는 작년보다 좀 더 나은 사람이 되고자!!! 회고록을 열심히 써보려 한다. 
기록이 쌓이면 동기 부여도 확실히 될 것이고, 전에 했던 실수를 반복하지 않을 테고..😗 회고하는 습관을 들이면서 좋은 점을 나중에 회고해 보겠다.</p>
<p>2024년을 본격적으로 시작해 보기 전에 2023년을 돌아보려 한다. 어쩌면 이건 일기에 가까울지도 모르겠네..</p>
<h2 id="2023년-1학기">2023년 1학기</h2>
<p>2023년 2월에 전역해서, 바로 복학했다. 다행인 건 코로나로 못 쓴 휴가를 몰아서 써서 12월부터 집에 있었다. 그래서 마음의 준비는 충분히 했던 것 같다😐</p>
<p>1, 2학년 때 동아리 활동을 안 해봐서 올해는 꼭 해야지! 해서 들어간 게 바로 멋쟁이사자처럼 이다!! 웹 개발 연합 동아리이고, 오랜만에 본 면접에 살짝은 떨리기도 했다🤣🤣
멋사에서 이런저런 활동을 되게 많이 했다. 처음에 진행한 프로젝트 조원들이랑은 지금도 거의 매일 연락한다 :) 중간에 PS 스터디도 참가해서 백준도 열심히 풀었고(지금도 매일 하나씩 풀고 있다!) 아이디어톤과 연합 해커톤도 경험해 보았다. 우리 팀 서비스는 완성되지는 않았지만 참가 자체에 의의를 두고... 다음번에 해커톤에 참여하면 더 열심히 해야겠다고 생각했던 것 같다.</p>
<p>1학기에는 팀플 전공 수업도 하나 있었다. 소켓 프로그래밍을 사용해서 윈폼 기반 온라인 그림판을 만들었는데, 최종 발표 전날에 학교에서 밤새면서 개발하고 발표 준비했던 게 아직도 생생하다... 무슨 정신으로 발표까지 했던 걸까🫠</p>
<p>복학한 뒤 첫 학기는 공백기를 감안했을 때 꽤 괜찮았다. 전공 수업도 열심히 따라갔고, 동아리 활동도 열심히 했다. 중간에 운동도 꾸준히 하면서 정말 건강하게 살았다...</p>
<h2 id="여름방학">여름방학</h2>
<p>여름방학에 카페 알바를 시작하게 되면서 정신없이 바쁘게 살았다. 지금 돌이켜 보면 좀 아쉬운데, 카페 일에 열심히 적응하다 보니 생각보다 개인 공부를 많이 하지 못했다. 좀 더 부지런했으면 둘 다 할 수 있었는데...!! 
그래도 알바에 완벽 적응해서 방학 이후에는 밸런스를 되찾을 수 있게 되었다. </p>
<p>(중간에 연합 해커톤도 참여했다!)</p>
<h2 id="2023년-2학기">2023년 2학기</h2>
<p>1학기에도 열심히 살았지만, 분명히 아쉬웠던 점들이 있었기 때문에 2학기에는 진짜로 바쁘게 살아보자 다짐했다. 실제로 2학기는 진짜 바빴고, 얻은 것도 많지만 잃은 것도 많았다.</p>
<p>먼저 학기가 시작되고 나서 얼마 되지 않아 운동을 하던 중 허리를 다치게 되었다😞 다 낫지 않았는데도 객기를 부려서 운동을 하다가 더 안 좋아져서 결국 운동을 쉬면서 꾸준히 관리해야 하는 몸이 되었다..... 시간을 돌릴 수 있다면 2학기 시작할 때쯤으로 돌아가고 싶다🤕🤕
제일 중요한 건강을 잃어버린 시점이다.</p>
<p>2학기에는 한 게 너무나 많다.</p>
<ul>
<li>전공 공부</li>
<li>해커톤 동아리</li>
<li>KT 멘토링</li>
<li>우테코 프리코스</li>
<li>알바</li>
<li>등등</li>
</ul>
<p>이걸 과연 내가 다 해낼 수 있을까 생각했고, 걱정을 많이 했다. 여러 마리의 토끼를 잡으려다 한 마리도 못 잡을 수도 있었으니까.</p>
<p>KT 멘토링은 강원도 교육청과 KT가 공동 주관하는 사업인데, 나는 중학생 2명의 멘토가 되었다. 이것도 사연이 좀 많았는데, 여기서는 과감하게 생략하겠다. </p>
<p>해커톤 동아리는 바로 구름톤 1기였다! 약 1달 동안 팀 빌딩과 네트워킹, 그리고 5일 동안의 개발 기간을 거쳐서 하나의 서비스를 만들었다. 잘하는 사람이 워낙 많았고, 내 실력이 부족해서 팀원들을 따라가기 위해 최선을 다했다. 
발표 전날 판교에 모여서 마무리 개발을 하고 발표 준비를 하면서 밤을 새웠다. 우리 팀은 결국 발표 당일까지 목표한 기능을 다 구현하지 못했는데, 이때 개발자로서 성장하고 싶다는 생각을 정말 많이 했다. 최선을 다하긴 했지만 만족스러운 결과를 내지 못했다는 것이 스트레스였다. </p>
<p>사실 해커톤 활동할 때 우테코(우아한 테크코스) 프리코스도 동시에 진행되고 있었다. 우테코는 우아한 형제들에서 주관하는 개발자 교육 프로그램인데, 합격한다면 엄청난 성장의 기회가 될 것 같아서 고민하지 않고 지원했다.</p>
<p>프리코스는 본 코스에서 배우는 내용들을 미리 체험해 볼 수 있는 코스이다. 4주 동안 매주 하나의 과제를 수행하는 방식인데, 요구 사항도 많고 지켜야 하는 규칙들이 굉장히 많아서 처음에는 막막했다. 하지만 이것도 하나하나 따라가다 보니 나름대로의 재미를 느낄 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/0e24a27d-dc21-4c02-8eff-e123f7f533eb/image.png" alt=""></p>
<p>그런데 웬걸?! 큰 기대를 하지 않았던 우테코에 1차 합격했다. 지금도 자소서를 잘 봐주신 게 아닌가 생각이 든다. 1차 합격 메일을 받고 최종 코딩 테스트까지는 시간이 꽤 많이 남았지만 시험 기간이 다가오고 있었다.</p>
<p>시험과 과제를 챙기면서, 우테코에도 꼭 합격하고 싶었기 때문에 3주 정도는 거의 폐관 수련을 했다. 시험이 어느 정도 끝난 뒤 코테를 준비할 수 있었고, 코테 당일에는 &quot;돌아가는 쓰레기&quot;를 만드는 데 성공했다. </p>
<p>코테가 끝나고 나서도 아직 시험과 과제가 남아있었고, 속으로 엉엉 울면서 마무리했다🤔</p>
<p><img src="https://velog.velcdn.com/images/its_fe/post/f88e9523-aff1-4c0c-a86c-175e2eb57bff/image.png" alt=""></p>
<p>종강하고 며칠 뒤, 행복한 메일 하나가 왔다. 보자마자 방방 뛰고 싶었지만 그러지는 못했고 대신 정말 안도했다. 성장할 수 있는 기회를 얻게 되어 기쁘다 :D</p>
<p>당장 다음 달부터 우테코 활동을 시작하면서 다시 바빠질 예정이다. 힘들게 얻은 기회인 만큼 열심히 공부해서 실력 있는 개발자가 되는 것이 올해 목표이다. 그 과정에서 배운 것들, 생각한 것들을 정리해서 포스팅하는 것도 올해 목표이다.</p>
<p>2023년 돌아보기 끝! 2024년 시작!😎</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Core JavaScript] 3. This]]></title>
            <link>https://velog.io/@its_fe/Core-JavaScript-3.-This</link>
            <guid>https://velog.io/@its_fe/Core-JavaScript-3.-This</guid>
            <pubDate>Sat, 24 Sep 2022 07:23:49 GMT</pubDate>
            <description><![CDATA[<h3 id="본문에-앞서서">본문에 앞서서</h3>
<p>자바스크립트를 공부하면서 본 코드 중에 this가 들어가지 않은 코드를 찾을 수가 없을 정도로 this는 필수적이고 아주 중요한 개념이다. 하지만 그만큼 혼란스러운 개념이기도 한데, this는 아무데서나 쓰일 수 있기 때문이다. 어느 상황에서 this를 사용했는지에 따라 this가 가리키는 대상이 달라지기 때문에 잘 공부해서 기억해야겠다.</p>
<h1 id="this">this</h1>
<p>자바스크립트에서 <code>this</code>는 실행 컨텍스트가 생성될 때, 즉 <code>함수를 호출할 때 결정된다</code>. 실행 컨텍스트는 이전 글에서 살펴봤듯이 함수를 호출할 때 생성되기 때문이다. </p>
<h2 id="전역-공간에서-this">전역 공간에서 this</h2>
<p>전역 공간에서 <code>this</code>는 전역 객체를 가리킨다. 전역 컨텍스트를 생성하는 것이 전역 객체이기 때문이다. 전역 객체는 런타임 환경에 따라 다른 이름을 가지고 있는데, 브라우저 환경에서 <code>window</code>이고, Node.js 환경에서는 <code>global</code>이다.</p>
<p>각 환경에서 this를 입력해보면 전역 객체에 대한 정보를 출력한다.</p>
<h3 id="전역-공간-여담">전역 공간 여담</h3>
<p>여담으로 전역 공간에서만 발생하는 특이한 성질을 보고 가자. 자바스크립트에서 <code>전역 변수</code>란, 변수이면서 동시에 전역객체의 프로퍼티이기도 하다.</p>
<pre><code class="language-javascript">var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1</code></pre>
<p>Node.js 환경의 경우 <code>window.a</code> 대신 <code>global.a</code>를 사용해도 같은 결과를 얻는다. </p>
<blockquote>
<p>우리는 a에 1을 할당했을 뿐인데 어떻게 window.a와 this.a가 모두 1을 출력하는 것일까?</p>
</blockquote>
<p><strong>자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작한다.</strong> 이 때문에 위의 세 문장이 모두 1을 출력하는 것이다. <code>var</code> 연산자를 이용해 변수를 선언하더라도 자바스크립트 엔진은 어떤 특정 객체의 프로퍼티로 인식한다. 여기서 <strong>특정 객체</strong>란 <code>실행 컨텍스트의 LexicalEnvironment</code>이다. 실행 컨텍스트는 변수를 수집해서 <code>LexicalEnvironment</code>의 프로퍼티로 저장한다. 이후 변수를 호출하면 <code>LexicalEnvironment</code>를 조회해서 일치하는 프로퍼티가 있는 경우 그 값을 반환하게 된다. 전역 컨텍스트의 경우에는 <code>LexicalEnvironment</code>가 전역 객체를 그대로 참조한다.</p>
<p>특정 객체의 프로퍼티로 동작한다 했으니 <code>window.a</code>와 <code>this.a</code>의 결과가 1이 나오는 것은 당연해졌다.</p>
<blockquote>
<p>그렇다면 <code>a</code>만 출력했을 때는 어떻게 1이 나올까?</p>
</blockquote>
<p>어차피 a에 접근하려 하면 스코프 체인에서 a를 검색하다가 마지막에 도달하는 <code>전역 스코프의 LexicalEnvironment(전역 객체가 된다)</code>에서 a를 발견하고 그 값을 반환하기 때문이다. 간단하게 <code>window.</code>를 생략한 채 변수 이름만 써도 접근이 가능하다는 것을 알아두면 된다. 사실 원리를 알기 전에도 당연하게 받아들였을 것이다.</p>
<p>여기서 한 가지 의문이 생긴다.</p>
<blockquote>
<p><code>var b = 2</code>와 <code>window.b = 2</code>는 같을까?</p>
</blockquote>
<p>일반적으로는 그렇지만 삭제 명령(delete)에 대해서는 전혀 다르다.</p>
<pre><code class="language-javascript">var a = 1;
delete window.a; // false
console.log(a, window.a, this.a); // 1 1 1</code></pre>
<pre><code class="language-javascript">var b = 2;
delete b; // false
console.log(b, window.b, this.b); // 2 2 2</code></pre>
<pre><code class="language-javascript">window.c = 3;
delete window.c; // true
console.log(c, window.c, this.c); // Uncaught ReferenceError : c is not defined</code></pre>
<pre><code class="language-javascript">window.d = 4;
delete d; // true
console.log(d, window.d, this.d); // Uncaught ReferenceError : d is not defined</code></pre>
<p>전역객체의 프로퍼티로 할당한 경우에는 삭제가 되었지만 전역변수로 할당한 경우에는 삭제가 되지 않았다. 전역변수로 선언하면 자바스크립트 엔진이 자동으로 전역객체의 프로퍼티로 할당하면서 해당 프로퍼티의 <code>configurable 속성(변경 및 삭제 가능성)</code>을 <code>false</code>로 정의한다.</p>
<p><strong>결론: var로 선언한 전역변수와 전역객체의 프로퍼티는 호이스팅 여부 및 configurable 여부에서 차이를 보인다.</strong></p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/81db9bc4-0606-4e03-a62a-a95d85bcfa37/image.png"></p>

<h2 id="메서드로서-호출할-때-그-메서드-내부에서의-this">메서드로서 호출할 때 그 메서드 내부에서의 this</h2>
<h3 id="함수-vs-메서드">함수 vs 메서드</h3>
<p>먼저 함수와 메서드의 차이를 짚고 넘어가자. 둘을 구분하는 차이는 <strong>독립성</strong>이다.</p>
<p><code>함수</code>: 그 자체로 독립적인 기능 수행
<code>메서드</code>: 자신을 호출한 대상 객체에 관한 기능 수행</p>
<p>하지만 어떤 함수를 객체의 프로퍼티에 저장했다고 해서 항상 메서드가 되는 것은 아니다. 객체의 메서드로서 호출한 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.</p>
<pre><code class="language-javascript">var func = function (x) {
  console.log(this, x);
};
func(1); // Window {...} 1

var obj = {
  method: func
};
obj.method(2); // { method: f } 2</code></pre>
<p><code>func(1)</code>을 호출했을 때는 <code>this</code>가 전역객체를 가리킨다. 반면 <code>obj.method(2)</code>를 호출했을 때는 <code>this</code>가 obj 객체를 가리킨다. func의 값과 obj의 프로퍼티 method의 값은 모두 첫 번째 줄의 함수를 참조한다.</p>
<p><strong>결론: 함수를 변수에 담아 호출하는 경우와 객체의 프로퍼티에 할당해서 호출하는 경우에 this는 달라진다.</strong></p>
<h3 id="함수로서-호출-vs-메서드로서-호출-구분하기">함수로서 호출 vs 메서드로서 호출 구분하기</h3>
<p>구분 방법은 간단하다. 메서드로 호출하는 방법은 두 가지가 있다.</p>
<ol>
<li>함수 앞에 <code>.</code>이 있으면 메서드이다. (점 표기법)</li>
<li>대괄호 표기법으로 호출했으면 메서드이다.</li>
</ol>
<pre><code class="language-javascript">var obj = {
  method: function (x) {console.log(this, x);}
};
obj.method(1); // { method: f } 1
obj[&#39;method&#39;](2); // { method: f } 2</code></pre>
<p>모두 메서드로서 호출이다.</p>
<h3 id="메서드-내부에서의-this">메서드 내부에서의 this</h3>
<p>메서드로서 호출하는 방법까지 알아보았으니 이제 이 상황에서 this가 무엇을 가리키는지만 알면 된다.</p>
<pre><code class="language-javascript">var obj = {
  methodA: function () {console.log(this);}
  inner: {
      methodB: function () {console.log(this);}
  }
};
// 이 둘은 this가 obj를 가리킨다.
obj.methodA();               // { methodA: f, inner: {...} }
obj[&#39;methodA&#39;]();            // { methodA: f, inner: {...} }

// 이 넷은 this가 obj.inner를 가리킨다.
obj.inner.methodB();         // { methodB: f }
obj.inner[&#39;methodB&#39;]();      // { methodB: f }
obj[&#39;inner&#39;].methodB();      // { methodB: f } 
obj[&#39;inner&#39;][&#39;methodB&#39;]();   // { methodB: f }</code></pre>
<p>함수를 메서드로서 호출하는 경우 호출 주체는 함수명(프로퍼티명) 앞의 객체이다.
점 표기법을 사용하는 경우, 점이 아무리 많아도 마지막 점 앞까지의 객체가 this가 된다.
대괄호 표기법도 마찬가지로 마지막 함수명(프로퍼티명) 앞까지의 객체가 this가 된다.</p>
<h2 id="함수로서-호출할-때-그-함수-내부에서의-this">함수로서 호출할 때 그 함수 내부에서의 this</h2>
<p><code>this</code>에는 호출한 주체가 담긴다. 하지만 함수로 호출한다는 것은 호출 주체를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이다. 따라서 <code>this</code>는 지정되지 않는다.</p>
<p>2장에서 거의 그냥 넘어가다시피 했던 내용 중 실행 컨텍스트의 ThisBinding이 있었다. <code>this</code>가 바라봐야 할 대상을 저장하는데, 지정되지 않은 경우 전역 객체가 지정된다. <strong>따라서 함수로서 호출할 때 그 내부에서의 this는 <code>전역 객체</code>를 가리킨다.</strong> </p>
<pre><code class="language-javascript">var obj1 = {
  outer: function() {
    console.log(this);
    var innerFunc = function() {
      console.log(this);
    }
    innerFunc(); // (2)

    var obj2 = {
      innerMethod: innerFunc
    };
    obj2.innerMethod(); // (3)
  }
};
obj1.outer(); // (1)</code></pre>
<p>위 코드에서 <code>obj1.outer()</code>, <code>innerFunc()</code>, <code>obj2.innterMethod()</code>를 호출했을 때의 결과를 맞혀보자.</p>
<ul>
<li><code>obj1.outer()</code> - 3번째 줄에서 this가 출력된다. 이 문장은 점 표기법을 이용한 메서드로서의 호출이다. 따라서 출력 결과는 <code>obj1</code>이다.</li>
<li><code>innerFunc()</code> - 바로 위의 innerFunc 내부에서 this가 출력된다. 점이나 대괄호가 없는 함수로서의 호출이다. 따라서 출력 결과는 전역 객체이다.</li>
<li><code>obj2.innerMethod()</code> - 바로 위의 obj2 내부에서 innerFunc이 실행된다. 얼핏 보면 <code>innerFunc()</code>과 같지만 점 표기법을 이용한 메서드로서의 호출이다. 따라서 출력 결과는 <code>obj2</code>이다.</li>
</ul>
<p>따라서 (1): obj1, (2): 전역 객체, (3): obj2이다.</p>
<p>같은 함수여도 함수로서 호출하느냐, 메서드로서 호출하느냐에 따라 바인딩되는 this의 대상이 서로 달라진다. <strong>이것을 결정하는 요소로 오직 함수 호출 구문에 점이나 대괄호 표기가 있는지만 보면 된다.</strong></p>
<h2 id="메서드의-내부-함수에서-this-우회하기">메서드의 내부 함수에서 this 우회하기</h2>
<p>분명 다른 함수 내부에서 함수를 실행하는데, this는 전역 객체를 가리키고 있다면 참 아이러니한 상황이다. 호출 주체가 없을 때(함수로서 실행할 때) 자동으로 전역 객체를 가리키지 않고 주변 환경에 따라 this를 사용할 수 있으면 좋겠다. ES5까지는 자체적으로 내부함수에 this를 상속할 방법이 없었다. 따라서 이 경우 변수를 활용하는 방법을 사용했다.</p>
<pre><code class="language-javascript">var obj = {
  outer: function() {
    console.log(this); // { outer: f }
    var innerFunc1 = function() {
      console.log(this); // Window { ... }
    };
    innerFunc1();

    var self = this;
    var innerFunc2 = function() {
      console.log(self); // { outer: f }
    };
    innerFunc2();
  }
};
obj.outer();</code></pre>
<p>innerFunc2()는 함수로서의 호출이라 전역 객체를 가리켜야 하지만 obj를 가리키고 있다. 변수를 하나 만들어서 this를 할당하고, 그 변수를 출력했기 때문에 <code>this</code>가 우회되었다. 상위 스코프의 <code>this</code>를 저장해서 내부함수에서 활용했을 뿐이다.</p>
<h2 id="this를-바인딩하지-않는-함수">this를 바인딩하지 않는 함수</h2>
<p>ES5까지는 변수를 이용해서 <code>this</code>를 우회했다면 ES6에는 화살표 함수가 도입되어 우회할 수 있게 되었다. 화살표 함수는 <code>this</code>를 바인딩하는 과정이 없어서 상위 스코프의 <code>this</code>를 그대로 활용할 수 있다.</p>
<pre><code class="language-javascript">var obj = {
  outer: function() {
    console.log(this); // { outer: f }
    var innerFunc = () =&gt; {
      console.log(this); // { outer: f }
    };
    innerFunc();
  }
};
obj.outer();</code></pre>
<h2 id="콜백-함수-호출-시-그-함수-내부에서의-this">콜백 함수 호출 시 그 함수 내부에서의 this</h2>
<p>함수 A의 제어권을 다른 함수(메서드) B에게 넘겨주는 경우 함수 A를 <code>콜백 함수</code>라고 한다. 콜백 함수도 함수이기 때문에 기본적으로 <code>this</code>가 전역 객체를 참조하지만 제어권을 받은 함수에서 별도로 <code>this</code>의 대상을 지정할 수 있다. </p>
<pre><code class="language-javascript">setTimeout(function () {console.log(this); }, 300);

[1, 2, 3, 4, 5].forEach(function (x) {
  console.log(this, x);
});

document.body.innerHTML += &#39;&lt;button id=&quot;a&quot;&gt;클릭&lt;/button&gt;&#39;;
document.body.querySelector(&#39;#a&#39;)
    .addEventListener(&#39;click&#39;, function (e) {
          console.log(this, e);
    });</code></pre>
<p><code>setTimeout</code> 함수와 <code>forEach</code> 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 <code>this</code>를 지정하지 않는다. 따라서 전역 객체를 참조한다.
한편 <code>addEventListener</code> 메서드는 콜백 함수를 호출할 때 자신의 <code>this</code>를 상속하도록 정의돼 있다. 여기서는 메서드명의 <code>.</code> 앞부분이 <code>this</code>가 된다.</p>
<p><strong>결론: 콜백 함수에서는 <code>this</code>를 하나로 정의할 수 없다. 제어권을 가지는 함수가 콜백 함수에서 <code>this</code>를 무엇으로 할지 결정하고, 특별히 정의하지 않았다면 전역 객체를 가리킨다.</strong></p>
<h2 id="생성자-함수-내부에서의-this">생성자 함수 내부에서의 this</h2>
<p><code>생성자 함수</code>는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다. 객체지향에서 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라 한다. 생성자는 구체적인 인스턴스를 만드는 일종의 <strong>틀</strong>이다.</p>
<p>자바스크립트에서 <code>new</code> 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작한다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우 <code>this</code>는 새로 만들 <strong>인스턴스 자신</strong>이 된다.</p>
<pre><code class="language-javascript">var Cat = function (name, age) {
  this.bark = &#39;야옹&#39;;
  this.name = name;
  this.age = age;
};
var choco = new Cat(&#39;초코&#39;, 7);
var nabi = new Cat(&#39;나비&#39;, 5);
console.log(choco, nabi);

// 결과
// Cat { bark: &#39;야옹&#39;, name: &#39;초코&#39;, age: 7 }
// Cat { bark: &#39;야옹&#39;, name: &#39;나비&#39;, age: 5 }</code></pre>
<p><code>new</code> 키워드와 함께 만든 두 개의 인스턴스에서 <code>this</code>는 각각 <code>choco</code>와 <code>nabi</code>를 가리키고 있다.</p>
<hr>
<p>앞에서 다룬 화살표 함수 이외에도 명시적으로 <code>this</code>를 바인딩하는 방법으로 세 가지 메서드가 있다. 앞에서 배운 규칙에 부합하지 않는다면 명시적으로 바인딩했다고 추측할 수 있다. 이 부분은 따로 포스팅하려 한다.</p>
<h1 id="정리">정리</h1>
<ol>
<li><code>전역 공간</code>에서 <code>this</code>는 <code>전역 객체(window, global)</code>를 가리킨다.</li>
<li><code>어떤 함수를 함수로서 호출</code>했을 때 <code>this</code>는 <code>전역 객체</code>를 가리킨다.</li>
<li><code>어떤 함수를 메서드로서 호출(점 표기법, 대괄호 표기법)</code>했을 때 <code>this</code>는 <code>함수명(프로퍼티명) 앞의 객체</code>를 가리킨다.</li>
<li><code>콜백 함수 내부</code>에서 <code>this</code>는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 <code>전역 객체</code>를 가리킨다.</li>
<li><code>생성자 함수</code>에서 <code>this</code>는 <code>생성될 인스턴스</code>를 가리킨다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Core JavaScript] 2. 실행 컨텍스트]]></title>
            <link>https://velog.io/@its_fe/Core-JavaScript-2.-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@its_fe/Core-JavaScript-2.-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 09 Sep 2022 13:38:48 GMT</pubDate>
            <description><![CDATA[<h3 id="본문에-앞서서">본문에 앞서서</h3>
<p> 실행 컨텍스트가 뭔지 이해하고 나니 자바스크립트만의 매력을 더 느낄 수 있었다. 공부하면서 초반에는 어색하고 한 눈에 내용이 들어오지 않았지만, 천천히 읽다 보니 실행 컨텍스트를 이해하는 것이 이 개념과 유기적으로 연결된 다른 핵심 개념들을 이해하는 데 굉장히 도움이 된다는 사실을 깨닫게 되었다. 꼭 이해하고 넘어가는 것이 좋을 것 같다.</p>
<hr>
<h1 id="실행-컨텍스트">실행 컨텍스트</h1>
<p> <code>실행 컨텍스트(Execution context)</code>는 <strong>실행할 코드에 제공할 환경 정보들을 모아놓은 객체</strong>이다. _동일한 환경_의 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 <code>콜 스택(call stack)</code>에 쌓아올린다. 콜 스택의 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드부터 실행하여 전체 코드의 순서를 보장한다. </p>
<blockquote>
<p>동일한 환경은 어떻게 구성할까?</p>
</blockquote>
<p> 하나의 컨텍스트가 되면 그 구성 정보들은 모두 동일한 환경이며, 이를 구성하기 위해서는 <code>전역공간</code>, <code>eval()함수</code>, <code>함수</code>등을 이용하면 된다. 그 중 우리가 흔히 구성하는 방법으로 <strong>함수를 실행</strong>하면 된다.</p>
<pre><code class="language-javascript">// ---------------------(1)
var a = 1;
function outer() {
  function inner() {
    console.log(a); // undefined
    var a = 3;
  }
  inner(); // ----------(2)
  console.log(a); // 1
}
outer(); // ------------(3)
console.log(a); // 1</code></pre>
<p>위 코드에서 실행 컨텍스트가 콜 스택에 다음 순서로 쌓이게 된다.</p>
<ol>
<li>코드 실행 시 브라우저가 자동으로 전역 컨텍스트를 콜 스택에 담는다.</li>
<li>(3)에서 <code>outer</code>함수를 호출하면 전역 컨텍스트 관련 코드 실행이 잠시 중단되고 <code>outer</code>함수가 그 위에 담긴다. 그리고 <code>outer</code> 내부의 코드를 실행한다.</li>
<li>코드를 실행하다가 (2)에서 <code>inner</code>함수를 호출하면 <code>outer</code> 컨텍스트 관련 코드 실행이 잠시 중단되고 <code>inner</code>함수가 그 위에 담긴다. 그리고 <code>inner</code> 내부의 코드를 실행한다.</li>
<li>a에 3을 할당하고 <code>inner</code>함수는 종료된다. 이때 <code>inner</code> 실행 컨텍스트가 콜 스택에서 삭제된다.</li>
<li>콜 스택에 <code>outer</code>가 맨 위에 있는 상태이다. (2) 다음 줄부터 이어서 실행된다. a가 출력되고 <code>outer</code> 함수는 종료된다. <code>outer</code> 실행 컨텍스트가 콜 스택에서 삭제된다.</li>
<li>콜 스택에는 전역 컨텍스트만 남았다. (3) 다음 줄부터 이어서 실행된다. a가 출력되고 더 이상 실행할 코드가 없으므로 전역 컨텍스트가 콜 스택에서 삭제된다.</li>
<li>콜 스택에 아무것도 남지 않고 실행이 종료된다.</li>
</ol>
<p>규칙은 간단하다. 코드를 읽으면서 함수가 호출되는 순간에 실행 컨텍스트를 생성해서 콜 스택에 쌓아올리고, 모든 코드를 읽은 다음에는 콜 스택의 맨 위에 있는 실행 컨텍스트 관련 코드를 실행한다. 다 실행했으면 삭제된다.</p>
<blockquote>
<p>그럼 이 객체는 어떻게 생겼을까?</p>
</blockquote>
<p>사실 이 객체는 JS 엔진이 활용할 목적으로 생성할 뿐 개발자는 코드로 확인할 수 없다. 객체에 담기는 정보는 다음과 같다.</p>
<ul>
<li><p><code>VariableEnvironment</code>: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 <code>LexicalEnvironment</code>의 스냅샷으로, 변경 사항은 반영되지 않는다.</p>
</li>
<li><p><code>LexicalEnvironment</code>: 처음에는 <code>VariableEnvironment</code>와 같지만 변경 사항이 실시간으로 반영된다.</p>
</li>
<li><p><code>ThisBinding</code>: this 식별자가 바라봐야 할 대상 객체. 실행 컨텍스트 활성화 당시 this가 지정되지 않은 경우 전역 객체가 저장된다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/d1209dcc-6a28-4caf-917a-63699ea21802/image.png"></p>


</li>
</ul>
<h2 id="variableenvironment">VariableEnvironment</h2>
<p>실행 컨텍스트가 처음 만들어질 때 <code>VariableEnvironment</code>가 먼저 만들어지고 이를 복사해서 <code>LexicalEnvironment</code>를 만들고, 이후에는 <code>LexicalEnvironment</code>를 주로 활용한다. 변경 사항을 실시간으로 반영하기 때문이다.</p>
<p>초기 상태에서는 <code>VariableEnvironment</code>과 <code>LexicalEnvironment</code>가 완전히 동일하지만 이후에 달라지게 된다.</p>
<h2 id="lexicalenvironment">LexicalEnvironment</h2>
<p><code>LexicalEnvironment</code>는 &quot;현재 컨텍스트 내부에는 a, b, c 식별자들이 있고, 그 외부 정보는 D를 참조하도록 구성되어 있다&quot;와 같이 컨텍스트를 구성하는 환경 정보를 <strong>사전식</strong>으로 모아놓은 것이다. <code>Lexical</code>을 한국어로 번역해서 이해하려 하면 도통 이해가 되지 않는다.</p>
<p><code>environmentRecord</code>와 <code>outerEnvironmentReference</code>로 구성되어 있다.</p>
<hr>
<h2 id="호이스팅">호이스팅</h2>
<p><code>LexicalEnvironment</code>의 <code>environmentRecord</code>에는 <strong>현재 컨텍스트와 관련된 코드의 식별자 정보</strong>가 저장된다. 여기서 식별자란?</p>
<ul>
<li>컨텍스트 구성 함수에 지정된 매개변수 식별자</li>
<li>선언한 함수가 있을 경우 그 함수 자체</li>
<li>var로 선언된 변수의 식별자
컨텍스트 내부를 쭉 훑으면서 <strong>순서대로</strong> 수집한다.</li>
</ul>
<p>이렇게 되면 자바스크립트 엔진은 코드를 실행하지도 않았는데 각 환경에 속한 코드의 변수명을 다 알고 있는 것이 된다. 그렇다면 식별자들만 맨 위로 끌어올린 뒤 코드를 실행한다고 생각해도 문제가 없게 되는데, 이를 <code>호이스팅(hoisting)</code>이라 한다. 끌어올린다는 의미로, <em><strong>엔진이 실제로 끌어올리지는 않지만 편의상 그렇게 간주하도록 한다</strong></em>.</p>
<h3 id="매개변수와-변수에-대한-호이스팅">매개변수와 변수에 대한 호이스팅</h3>
<p><code>environmentRecord</code>에 담기는 식별자들은 매개변수/변수와 함수 자체로 나눌 수 있는데, 전자를 먼저 살펴보자.</p>
<pre><code class="language-javascript">function a (x) { // 수집 대상 1
  console.log(x); // (1)
  var x; // 수집 대상 2
  console.log(x); // (2)
  var x = 2; // 수집 대상 3
  console.log(x); // (3)
}
a(1);</code></pre>
<p>그냥 코드를 딱 봤을 때 (1), (2), (3)의 결과로 1, undefined, 2가 출력될 것 같다. 하지만 여기서 호이스팅의 개념을 적용해 보면?</p>
<pre><code class="language-javascript">function a () {
  var x = 1; // 수집 대상 1
  console.log(x);
  var x; // 수집 대상 2
  console.log(x);
  var x = 2; // 수집 대상 3
  console.log(x);
}
a();</code></pre>
<p>첫 번째 코드와 두 번째 코드는 함수의 argument에 전달된 인자를 담는 것을 제외하고는 다른 것이 없다. a를 호출할 때 1을 전달하므로 두 번째 코드에서는 첫 줄에 1을 할당해준 것이다. </p>
<p><code>environmentRecord</code>는 현재 실행될 컨텍스트의 대상 코드 내에 <strong>어떤 식별자가 있는지</strong>에만 관심이 있다. 거기에 어떤 값이 할당되는지는 관심이 없다. 따라서 우리는 호이스팅 과정에서 변수명만 위로 올리고 값을 할당하는 부분은 그냥 두면 된다. 그러면 다음과 같이 바뀐다.(<strong>실제로 바뀌는 것은 아니다!</strong>)</p>
<pre><code class="language-javascript">function a () {
  var x;
  var x;
  var x;

  x = 1;
  console.log(x);
  console.log(x);
  x = 2;
  console.log(x);
}
a(1);</code></pre>
<p>두 번째 줄에서 x를 선언하고 메모리에 공간을 확보해서 그 공간의 주솟값을 x에 연결한다. 하지만 이어서 x를 다시 선언하는데, 이미 선언된 것이 있으므로 무시된다. 그리고 나서 값을 할당하고 출력하므로 우리는 각각의 출력 결과가 1, 1, 2라는 것을 알게 된다. 처음에 예측한 1, undefined, 2와는 전혀 다른 결과인데, 호이스팅 개념을 알지 못한다면 맞추기 어렵다.</p>
<h3 id="함수-선언의-호이스팅">함수 선언의 호이스팅</h3>
<p>이번에는 함수 선언을 했을 때의 경우이다. <code>environmentRecord</code>는 <em><strong>함수를 만났을 때 통채로 끌어올린다</strong></em>. </p>
<pre><code class="language-javascript">function a () {
  console.log(b);
  var b = &#39;bbb&#39;; // 수집 대상 1
  console.log(b);
  function b() { } // 수집 대상 2
  console.log(b);
}
a();</code></pre>
<p>이번에도 이 코드만 보고 결과를 예측해보자면, undefined, &#39;bbb&#39;, function b가 출력될 것 같다. </p>
<p>a 함수를 실행하는 순간 a의 실행 컨텍스트가 생성된다. 그리고 변수명과 함수 선언 정보를 위로 끌어올린다. 이때 변수는 할당부는 그냥 두고 선언부만 끌어올리고, <em><strong>함수 선언은 전체를 끌어올린다</strong></em>. 끌어올리면 다음과 같이 바뀐다.</p>
<pre><code class="language-javascript">function a () {
  var b;
  function b () { }

  console.log(b);
  b = &#39;bbb&#39;;
  console.log(b);
  console.log(b);
}
a();</code></pre>
<p>호이스팅이 끝난 뒤에 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있다.</p>
<pre><code class="language-javascript">function a () {
  var b;
  var b = function b () { }

  console.log(b);
  b = &#39;bbb&#39;;
  console.log(b);
  console.log(b);
}
a();</code></pre>
<p>이렇게 말이다. 처음에 b를 선언하고 그 다음 줄에서 b는 함수를 가리키게 된다. 그리고 출력을 하게 되므로 우리가 예상했던 결과와 다르게 function b, &#39;bbb&#39;, &#39;bbb&#39;가 출력된다.</p>
<hr>
<h2 id="함수-선언문과-함수-표현식">함수 선언문과 함수 표현식</h2>
<pre><code class="language-javascript">function a () {/*...*/} // 함수 선언문
a(); // ok

var b = function () {/*...*/} // 익명 함수 표현식
b(); // ok

var c = function d () {/*...*/} // 기명 함수 표현식
c(); // ok
d(); // Error</code></pre>
<p>코드로 보는 것이 가장 확실하다.
*<code>함수 선언문</code>: function 정의부만 존재하고 별도의 할당 명령이 없다. 반드시 함수명이 정의돼 있어야 한다.
*<code>함수 표현식</code>: 정의한 function을 별도의 변수에 값으로 할당한다. 함수명이 있다면 기명 함수 표현식이고, 함수명이 없다면 익명 함수 표현식이다.</p>
<p>기명 함수 표현식은 한 가지 주의해야 할 점이 있다. 함수 외부에서는 함수명으로 함수를 호출할 수 없다. 할당한 변수명으로 호출해야만 한다. 하지만 함수 내부에서는 함수명으로 호출할 수 있다.</p>
<p>함수 선언문과 함수 표현식에 호이스팅을 적용하면 어떻게 될까?</p>
<pre><code class="language-javascript">console.log(sum(1, 2));
console.log(multiply(3, 4));

function sum (a, b) { // 함수 선언문
  return a + b;
}

var multiply = function (a, b) { // 함수 표현식
  return a * b;
}</code></pre>
<p>위는 초기 코드이다. 보면 함수들이 선언되기도 전에 함수를 호출하고 있다. 에러가 날 것 같이 생겼다. 호이스팅을 적용해 보자.</p>
<p>함수 선언은 통채로 끌어올리되 함수명과 같은 변수에 값으로 할당한 것처럼 취급할 수 있고, 변수는 선언부만 위로 끌어올리면 다음과 같이 변한다.</p>
<pre><code class="language-javascript">var sum = function sum (a, b) {
  return a + b;
}
var multiply;

console.log(sum(1, 2));
console.log(multiply(3, 4));
multiply = function (a, b) {
  return a * b;
}</code></pre>
<p>함수도 하나의 값으로 취급할 수 있다는 것이 이런 것이다. 함수 표현식이 함수를 다른 변수에 값으로써 할당한 것이다. 호이스팅된 결과를 보니 함수를 선언 전에 호출해도 아무런 문제가 없다. 하지만 이것은 혼란을 줄 수 있으므로 다른 언어에서 그랬던 것처럼 <strong>함수를 선언한 뒤에 호출</strong>하는 것이 좋을 것 같다.</p>
<h3 id="함수-선언문은-위험하다">함수 선언문은 위험하다</h3>
<p>제목 그대로 함수 선언문이 위험할 수 있는 경우가 있다. 다음 코드를 보자.</p>
<pre><code class="language-javascript">...
console.log(sum(3, 4));
...
function sum (x, y) {
  return x + y;
}
...
var a = sum(1, 2); // 여기까지 개발자 A가 작성하고 사용
...
function sum (x, y) { // 여기부터 개발자 B가 작성하고 사용
  return x + &#39; + &#39; + y + &#39; = &#39; + (x + y);
}
...
var c = sum(1, 2);
console.log(c);
...</code></pre>
<p>개발자 A가 먼저 두 수를 더하는 함수 sum을 함수 선언문으로 선언하고 사용했다. 그러다가 개발자 B가 같은 파일의 한참 밑에서 sum 함수를 새로 선언한다. B는 자신의 sum 함수가 자신이 작성한 부분에서만 영향을 줄 것이라 생각했다.</p>
<p>A는 당연히 자신이 작성한 부분에서는 두 수를 더한 값을 반환할 것이라 생각했지만 갑자기 뜬금 없는 문자열이 출력되는 것을 보게 된다. <strong>동일한 변수명에 서로 다른 값을 할당하면 override된다.</strong> 즉, 값이 덮어씌워진다. 따라서 실행 중에 호출되는 함수는 A가 작성한 함수가 아닌 B가 작성한 함수가 된다. 물론 코드에 문제는 없으므로 에러가 발생하지도 않는다. 이런 경우 디버깅에 정말 애를 먹을 것 같다.</p>
<p>A와 B가 모두 함수를 표현식으로 선언했다면 어떻게 됐을까?</p>
<pre><code class="language-javascript">...
console.log(sum(3, 4));
...
var sum = function (x, y) {
  return x + y;
}
...
var a = sum(1, 2);
...
var sum = function (x, y) {
  return x + &#39; + &#39; + y + &#39; = &#39; + (x + y);
};
...
var c = sum(1, 2);
console.log(c);
...</code></pre>
<p>함수 표현식으로 선언하면 함수가 담기는 변수만 호이스팅되고 함수의 몸체는 원래 자리에 그대로 있으므로 override되지 않고 각 개발자의 의도대로 본인이 작성한 부분에만 함수가 적용되었을 것이다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/c9c870f4-d1e3-4fec-9216-1d3ca43e0d8b/image.png"></p>
또한 sum 함수가 처음 선언되기 이전에 함수를 호출한다면 위 사진과 같이 에러가 난다. 호이스팅 되면 sum을 호출하는 시점에서 sum은 선언만 됐을 뿐 아무런 값도 할당되지 않은 상태이기 때문이다.

<p>이런 점에서 함수를 선언할 때는 <strong>함수 표현식</strong>을 사용하는 것이 보다 안전하다.</p>
<hr>
<h2 id="스코프-스코프-체인-outerenvironmentreference">스코프, 스코프 체인, outerEnvironmentReference</h2>
<p><code>스코프(Scope)</code>는 식별자에 대한 유효범위이다. 해당 식별자를 사용할 수 있는 공간을 뜻하는데, 어떤 경계 A의 밖에서 선언한 변수는 A의 안팎에서 모두 접근이 가능하지만, A의 안에서 선언한 변수는 안에서만 접근할 수 있다. 포함 관계인 두 집합의 밴 다이어그램을 생각하면 편하다. ES5까지는 전역공간을 제외하면 <strong>오직 함수에 의해서만</strong> 스코프가 생성되었지만 ES6부터는 블록에 의해서도 스코프가 생성된다.(다만 var로 선언한 변수가 아닌 let, const, class, strict mode에서 선언한 함수에 대해서만 생성된다)</p>
<h3 id="스코프-체인">스코프 체인</h3>
<p>스코프 체인을 이해하기 전에 짚고 넘어갈 것이 있다.
<code>outerEnvironmentReference</code>는 현재 호출된 함수가 선언될 당시의 <code>LexicalEnvironment</code>를 참조한다. 여기서 &#39;선언될 당시&#39;는 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태를 뜻한다. 한 가지 예시를 보자.</p>
<pre><code class="language-javascript">var a = function () {
  var b = function () {
    var c = function () {
      // ...
    };
  };
};</code></pre>
<p>함수 a 안에 함수 b를 선언하고, 다시 b 안에 함수 c를 선언한 모습이다. 함수 c는 b일 때 선언되었으므로 함수 c의 <code>outerEnvironmentReference</code>는 함수 b의 <code>LexicalEnvironment</code>를 참조한다. 같은 이유로 함수 b의 <code>outerEnvironmentReference</code>는 함수 a의 <code>LexicalEnvironment</code>를 참조한다. 이처럼 <code>outerEnvironmentReference</code>는 연결리스트의 형태를 띈다. 선언 시점의 <code>LexicalEnvironment</code>를 찾아 올라가다 보면 마지막에는 전역 컨텍스트의 <code>LexicalEnvironment</code>가 있게 된다. 또한 각각의 <code>outerEnvironmentReference</code>는 자신이 선언된 시점의 <code>LexicalEnvironment</code>만 참조하고 있기 때문에 가장 가까운 요소만 접근이 가능하다. 이렇듯 <code>스코프 체인</code>은 각각의 스코프들이 어떻게 연결되어 있는지를 나타내는 일종의 연결리스트이다. 스코프 체인의 구조 특성상 여러 스코프에서 동일한 식별자를 선언하는 경우에는 <strong>스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능</strong>하다. </p>
<pre><code class="language-javascript">var a = 1;
var outer = function() {
  var inner = function() {
    console.log(a);
    var a = 3;
  };
  inner();
  console.log(a);
};
outer();
console.log(a);</code></pre>
<p>스코프 체인을 살펴보자.</p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/9806a8f2-f063-431f-930b-2635b6309cb6/image.png">L.E: LexicalEnvironment / e: environmentRecord / o: outerEnvironmentReference</p>

<p>e는 현재 컨텍스트에 있는 식별자들을 저장하고, o는 현재 호출된 함수가 선언될 당시의 L.E를 저장한다. 전역 컨텍스트에서 outer 컨텍스트, inner 컨텍스트로 갈수록 규모는 작아지지만 스코프 체인을 타고 접근 가능한 변수는 늘어난다. inner 함수 내부에서는 inner, outer, 전역 스코프 모두 접근 가능하지만 outer 함수 내부에서는 outer, 전역 스코프에는 접근할 수 있지만 inner에는 접근할 수 없다. </p>
<p>위의 코드를 보면 전역공간과 inner 함수에서 모두 a라는 식별자를 선언하고 있다.</p>
<blockquote>
<p>inner 함수 내부에서 a에 접근한다면?</p>
</blockquote>
<p>스코프 체인 상의 첫 번째 인자인 inner 스코프의 L.E부터 검색을 시작한다. inner의 L.E에 a가 존재하므로 스코프 체인 검색이 종료된다. 그리고 inner의 L.E 상에 있는 a를 반환한다.
inner에서 a를 선언했기 때문에 inner 내부에서는 전역 공간에서 선언한 a에 접근할 수가 없게 된다. 이를 <code>변수 은닉화(variable shadowing)</code>라고 한다.</p>
<h2 id="전역변수와-지역변수">전역변수와 지역변수</h2>
<p>마지막으로 살펴볼 것은 전역변수(global variable)와 지역변수(local variable)이다. 앞선 예제에서 전역변수는 전역공간에서 선언한 a와 outer이고, 지역변수는 outer 함수에서 선언한 inner와 inner 함수에서 선언한 a이다. 전역 공간에서 선언한 변수를 전역변수라 하고, 함수 내부에서 선언한 변수는 지역변수이다.
위에서 다뤘던 &#39;함수 선언문은 위험하다&#39;에서 전역변수를 사용했기 때문에 문제가 되었는데, 좀 더 안전한 방법으로 소개한 함수 표현식보다도 사실은 <strong>지역변수를 사용하는 것이 훨씬 안전하다</strong>. 지역변수로 사용하기 위해서는 sum 함수 외부를 함수 X로 감싸야 한다. X 함수 내부에서 선언한 sum 함수의 경우 X 내부에서만 호출할 수 있기 때문에 전역 공간에서는 이에 접근할 수 없게 된다. </p>
<p>전역변수의 사용을 가급적 자제하고 지역변수로 사용하는 습관을 들여야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Core JavaScript] 1. 데이터 타입]]></title>
            <link>https://velog.io/@its_fe/Core-JavaScript-1.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@its_fe/Core-JavaScript-1.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</guid>
            <pubDate>Sat, 27 Aug 2022 08:04:13 GMT</pubDate>
            <description><![CDATA[<p>코어 자바스크립트를 읽고 공부하면서 이해하고 정리한 내용들을 기록으로 남기려 한다.</p>
<h1 id="이-책을-고른-이유">이 책을 고른 이유</h1>
<ol>
<li>ES6에서 새롭게 등장한 내용뿐 아니라 ES5이하에서 중요한 내용들도 함께 설명한다.</li>
<li>동작 원리에 입각해서 깊이 있게 설명한다.</li>
<li>책의 분량이 부담스러울 정도로 많지 않다.</li>
</ol>
<p>여러 책으로 자바스크립트를 공부하다 보니 책의 분량이 상당히 중요하다는 것을 깨닫게 되었다. 아무리 양질의 내용이어도 너무 많은 양을 담고 있다면 중간에 그만 둘 확률이 높다. 그런 점에서 이 책은 내용도 괜찮고 양도 적당하다. velog에서도 코어 자바스크립트 관련 스터디 글을 여럿 보았다.</p>
<hr>
<h1 id="데이터-타입">데이터 타입</h1>
<p><img src="https://velog.velcdn.com/images/its_fe/post/ee1f8555-70b5-4147-9eef-09ce3a2af449/image.png" alt="">
크게 두 가지로 분류된다. <code>기본형(원시형, primitive type)</code>과 <code>참조형(reference type)</code>으로 분류되며 참조형은 객체(Object)가 있으며 나머지는 객체의 하위 분류에 속한다.</p>
<blockquote>
<p>그렇다면 무슨 기준으로 이 둘을 나눈 것일까?</p>
</blockquote>
<p><strong>할당이나 연산 시,</strong> </p>
<ul>
<li>값이 담긴 주소값을 바로 복제한다: <strong>기본형</strong></li>
<li>값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제한다: <strong>참조형</strong></li>
</ul>
<p>이런 차이가 있으며, 기본형은 <code>불변성(immutability)</code>을 띈다. 이는 아래에서 살펴볼 것이다.</p>
<hr>
<h1 id="관련-배경지식">관련 배경지식</h1>
<p>C/C++, Java 등의 정적 타입 언어는 메모리 낭비를 최소화하기 위해 데이터 타입 별로 할당되는 메모리 크기를 정해놓았다. (char, int, float, long 등)
하지만 자바스크립트는 이들에 비해 메모리 관리에 대한 압박으로부터 자유롭다. 숫자의 경우 정수형과 부동소수형을 구분하지 않고 8비트를 할당한다.</p>
<p>이런 모든 데이터는 메모리 주소값을 통해 서로 구분되고 연결된다.</p>
<h2 id="변수-vs-식별자">변수 vs 식별자</h2>
<p>변수와 식별자를 혼동해서 쓰는 경우가 많다. 
<code>변수</code>: 변경 가능한 데이터가 담길 수 있는 공간이나 그릇
<code>식별자</code>: 어떤 데이터를 식별하는 데 사용하는 이름(즉, <strong>변수명</strong>)</p>
<p>단어 자체의 뜻을 생각하면 쉽게 이해할 수 있다. 사람들이 이름으로 서로를 식별할 수 있는 것처럼 변수도 변수명으로 데이터를 식별한다. </p>
<hr>
<h1 id="데이터-할당">데이터 할당</h1>
<p>컴퓨터가 명령을 받아 메모리 영역에서 어떤 작업을 수행하는지 알아보자.</p>
<h2 id="변수-선언과-접근">변수 선언과 접근</h2>
<pre><code class="language-javascript">var a;</code></pre>
<p>이를 말로 풀어서 쓰면, <code>변할 수 있는 데이터를 만든다. 데이터의 식별자는 a로 한다.</code>가 된다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/2733b29a-928c-442e-a6a3-68733a4c764c/image.png"></p>

<ol>
<li>메모리에서 비어있는 공간을 하나 확보한다.</li>
<li>공간의 이름을 a라고 지정한다.</li>
</ol>
<p>여기까지가 변수 선언 과정이다.</p>
<p>만약 a에 접근하려고 한다면, 컴퓨터는 메모리에서 a라는 이름을 가진 주소를 검색해 해당 공간에 담긴 데이터를 반환할 것이다.</p>
<h2 id="변수에-데이터-할당">변수에 데이터 할당</h2>
<pre><code class="language-javascript">var a;
a = &#39;abc&#39;;

var a = &#39;abc&#39;;</code></pre>
<p>두 방법 모두 a라는 이름을 가진 주소를 검색해 그곳에 문자열 &#39;abc&#39;를 할당하는 코드이다.</p>
<p><strong>하지만 실제로는 a 이름의 주소에 데이터가 직접 저장되지는 않는다.</strong> 데이터를 저장하는 별도의 공간을 확보해서 그곳에 데이터를 저장한다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/66bc6747-6a65-4578-ba3c-94c110471a8f/image.png"></p>

<p>변수 선언 과정 이후에</p>
<ol start="3">
<li>데이터 영역의 빈 공간(@5004)에 문자열 &#39;abc&#39;를 저장한다.</li>
<li>변수 영역에서 a 식별자를 검색한다.</li>
<li>저장한 문자열의 주소 @5004를 @1003의 값에 대입한다.</li>
</ol>
<p>과정까지가 데이터 할당 과정이다.</p>
<blockquote>
<p><strong>그렇다면 왜 데이터만을 저장하는 공간을 따로 두는 것일까?</strong></p>
</blockquote>
<p>이유는 다음과 같다.</p>
<ul>
<li>데이터 변환을 자유롭게 할 수 있다.</li>
<li>메모리를 더욱 효과적으로 관리할 수 있다.</li>
</ul>
<p>자바스크립트의 문자열은 8바이트로 정해진 숫자형과 다르게 길이가 가변적이므로 미리 확보한 공간 내에서만 데이터 변환이 가능하다면 불필요한 연산이 늘어난다. 가령 메모리 공간 중간에 있는 데이터를 늘려야 한다면 해당 공간보다 뒤쪽에 있는 모든 데이터를 한 칸씩 미루고, 다시 주소값을 연결시켜야 한다. 이와 같은 일을 방지할 수 있기 때문에 변수와 데이터를 별도로 저장하는 것이 효율적이다.</p>
<h2 id="변수의-데이터-변경">변수의 데이터 변경</h2>
<p>지금 저장되어 있는 &#39;abc&#39; 뒤에 &#39;def&#39;를 덧붙이고 싶거나, 마지막 c를 제거해서 &#39;ab&#39;를 만들고 싶을 때가 있을 것이다. 이럴 때 자바스크립트는 &#39;abc&#39;가 저장되어 있는 공간에 바뀐 값을 할당하는 대신 &#39;abcdef&#39;, &#39;ab&#39;라는 문자열을 <strong>새로</strong> 만들어 데이터 영역에 저장하고 그 주소를 변수 공간에 대입한다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/04037af4-f462-4218-9b01-6edb338de53f/image.png"></p>

<p>기존 문자열을 어떻게 바꾸든 상관없이 항상 새로운 문자열을 만들어서 별도의 영역에 저장한다.
@1003은 이제 더이상 @5004를 가리키고 있지 않다. @5004 입장에서 자신의 주소를 저장하는 변수가 하나도 없게 되면 가비지 컬렉터(Garbage Collector)의 수거 대상이 된다.</p>
<p>한 가지 예시로 500개의 변수에 5를 할당하는 상황을 들어보자. 만약 변수 영역과 데이터 영역이 분리되어 있지 않다면, 8바이트 공간을 500개 확보해야 한다. 이 경우 4000바이트가 필요하다.
만약 분리되어 있다면, 5를 한 번만 저장하고 그 주소값을 500번 가져다 사용하면 된다. 주소값이 2바이트라고 한다면 8+500<em>2=1008바이트만 필요하다. 이렇듯 *</em>변수 영역과 데이터 영역이 분리되면 중복 데이터에 대한 처리 효율이 높아진다.**</p>
<hr>
<h1 id="기본형-데이터와-참조형-데이터">기본형 데이터와 참조형 데이터</h1>
<h2 id="불변값">불변값</h2>
<p>앞에서 기본형 데이터는 불변성을 띄는 불변값이라고 언급했다. 불변값? 변하지 않는 값이라는 뜻이기에 우리가 아는 상수(constant)와 불변값이 같은 개념이라고 생각할 수 있다. <strong>그렇지 않다.</strong> 
변수와 상수를 구분하는 성질은 &#39;변경 가능성&#39;이다. 변수와 상수를 구분하는 변경 가능성은 <code>변수 영역 메모리</code>를 대상으로 한다. 반면에 불변성 여부를 구분하는 변경 가능성은 <code>데이터 영역 메모리</code>를 대상으로 한다.</p>
<p>기본형 데이터에는 숫자, 문자열, boolean, null, undefined, Symbol이 있는데, 이들은 모두 불변값이다.</p>
<pre><code class="language-javascript">var a = &#39;abc&#39;;
a = a + &#39;def&#39;;

var b = 5;
var c = 5;
b = 7;</code></pre>
<p>위 코드의 1~2번째 줄에서는 바로 위 &#39;변수의 데이터 변경&#39;에서 설명했듯 데이터 영역에 저장되어 있는 &#39;abc&#39;가 &#39;abcdef&#39;로 바뀌는 것이 아니라 새로 만들어서 그 주소를 변수 a에 저장했다. 따라서 &#39;abc&#39;와 &#39;abcdef&#39;는 완전히 다른 데이터가 된다.</p>
<p>4번째 줄의 동작 과정은 다음과 같다.</p>
<ol>
<li>b를 할당한다.</li>
<li>데이터 영역에서 5를 찾고 없으므로 데이터 공간을 하나 만들어 5를 저장한다.</li>
<li>5를 저장한 주소를 b에 대입한다.</li>
</ol>
<p>5번째 줄은 다음처럼 동작한다.</p>
<ol>
<li>c를 할당한다.</li>
<li>데이터 영역에서 5를 찾는데 윗 줄에서 저장한 5가 있으므로 그 주소를 재활용한다.</li>
</ol>
<p>6번째 줄은 b의 값을 7로 바꾸는 과정이다.</p>
<ol>
<li>데이터 영역에서 7을 찾는데 없으므로 데이터 공간을 하나 만들어 7을 저장한다.</li>
<li>7을 저장한 주소를 b에 대입한다.</li>
</ol>
<p>이렇듯 한 번 만든 값은 다른 값으로 변경되지 않는다. 변경은 새로 만드는 과정을 통해서만 이루어진다. 변수의 값을 바꾸더라도 데이터 영역 어딘가에는 이전에 할당했던 값이 그대로 남아있다. </p>
<h2 id="가변값">가변값</h2>
<h3 id="참조형-데이터를-할당하는-경우">참조형 데이터를 할당하는 경우</h3>
<pre><code class="language-javascript">var obj1 = {
  a: 1,
  b: &#39;bbb&#39;
};</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/03f00767-ac96-4262-b1ee-58fe5a47325c/image.png"></p>


<ol>
<li>변수 영역의 빈 공간 @1002를 확보하고 이름을 obj1로 지정한다.</li>
<li>데이터 영역에 데이터를 저장하려고 하는데 여러 개의 프로퍼티로 이루어진 데이터 그룹이다. 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고 그 주소(@7103~?)를 @5001에 저장한다.</li>
<li>@7103과 @7104에는 각각 이름으로 a, b가 지정된다.</li>
<li>데이터 영역에서 1을 검색하고 없으므로 임의로 @5003에 저장한 뒤 이 주소를 @7103에 저장한다. &#39;bbb&#39; 역시 데이터 영역에 없으므로 @5004에 저장한 뒤 이 주소를 @7104에 저장한다.</li>
</ol>
<p>기본형 데이터와의 차이는 <strong>객체의 변수(프로퍼티) 영역이 별도로 존재</strong>한다는 점이다. 하지만 위 그림을 보면 객체가 별도로 둔 영역은 데이터 영역이 아니라 변수 영역이다. 데이터 영역은 기존의 공간을 그대로 쓰고 있다. 데이터 영역의 값들은 모두 불변값이다. 하지만 객체의 변수 영역에는 다른 값을 얼마든지 대입(주소를 저장)할 수 있다. <em>이 때문에 참조형 데이터는 가변값이라고 하는 것이다.</em></p>
<h3 id="참조형-데이터의-프로퍼티를-재할당하는-경우">참조형 데이터의 프로퍼티를 재할당하는 경우</h3>
<pre><code class="language-javascript">var obj1 = {
  a: 1,
  b: &#39;bbb&#39;
};
obj1.a = 2;</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/ab88629a-d9ba-4afd-97e1-cce65580114f/image.png"></p>
마지막 줄을 살펴보자. 데이터 영역에서 2를 검색하는데 없으므로 @5005에 새로 만들고 그 주소를@7103에 저장한다. 이때 obj1이 가리키고 있는 주소는 @5001로 변하지 않았다. 따라서 새로운 객체가 만들어진 것이 아닌 기존의 객체 내부의 값만 바뀐 것이라는 것을 알 수 있다.

<h3 id="중첩-객체의-프로퍼티를-할당하는-경우">중첩 객체의 프로퍼티를 할당하는 경우</h3>
<p><code>중첩 객체(nested object)</code>란 참조형 데이터의 프로퍼티에 다시 참조형 데이터를 할당하는 경우를 말한다. </p>
<pre><code class="language-javascript">var obj = {
  x: 3,
  arr: [3, 4, 5]
};</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/38fa8213-4cfa-48ea-9ab9-ca4b15672652/image.png"></p>

<p>할당 과정은 앞과 다른 것이 없다. </p>
<ul>
<li>저장해야 하는 값이 데이터 그룹이라면 별도의 변수 영역을 마련하고 그 영역의 주소를 데이터 영역에 저장한다. </li>
<li>그리고 프로퍼티의 개수만큼 객체의 변수 영역에 할당해주고, 값은 데이터 영역에 저장한 뒤 그 주소값을 객체의 변수 영역에 저장한다.</li>
<li>값이 데이터 영역에 있다면 주소를 재활용하고, 없다면 새로 만들어서 주소값을 가져온다.</li>
</ul>
<p>위 코드의 경우 데이터 그룹이 2개(obj, arr)이므로 객체의 변수 영역이 2개 만들어진다. 데이터 그룹이 더 많아져도 원리는 같다.</p>
<p><code>obj.arr[1]</code>을 검색하려고 하면 다음 과정을 거친다.
<code>@1002 -&gt; @5001 -&gt; (@7103~?) -&gt; @7104 -&gt; @5003 -&gt; (@8104~?) -&gt; @8105 -&gt; @5004 -&gt; 4</code></p>
<blockquote>
<p>여기서 값을 재할당한다면?</p>
</blockquote>
<pre><code class="language-javascript">obj.arr = &#39;str&#39;;</code></pre>
<p> 데이터 영역에 &#39;str&#39;이 없으므로 @5006에 &#39;str&#39;를 저장하고 그 주소 @5006을 @7104에 저장한다. 그러면 @5103은 더 이상 자신을 참조하는 변수가 하나도 없게 된다. <strong>어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수</strong>를 <code>참조 카운트</code>라고 한다. @5103은 참조 카운트가 1이었다가 &#39;str&#39;로 재할당하는 순간 0이 되는데, 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다. 특정 시점이나 메모리 사용량이 포화에 임박하면 자동으로 수거된다. 수거된 메모리는 다시 빈 공간이 된다.
@5103이 가리키고 있는 @8104~@8106도 수거 대상이 되어 함께 사라질 것이다. </p>
<h2 id="변수-복사-비교">변수 복사 비교</h2>
<p>이번엔 변수를 복사할 때 기본형 데이터와 참조형 데이터의 차이를 확인해보자.</p>
<pre><code class="language-javascript">var a = 10;
var b = a;

var obj1 = {c: 10, d: &#39;ddd&#39;};
var obj2 = obj1;</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/its_fe/post/775b6f48-2bf6-4aad-a003-b829542f5730/image.png"></p>

<p>변수를 복사할 때 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보게 되는 점에서는 동일하다. 하지만 데이터 할당 과정이 애초에 다르기 때문에 이후의 동작에서 큰 차이가 발생한다.</p>
<h3 id="객체의-프로퍼티를-변경하는-경우">객체의 프로퍼티를 변경하는 경우</h3>
<pre><code class="language-javascript">b = 15;
obj2.c = 20;</code></pre>
<p>객체의 프로퍼티를 변경하는 경우이다. 
먼저 데이터 영역에 15가 없으므로 @5004에 저장하고, 그 주소를 들고 변수 영역에서 b를 찾는다. @1002의 값이 @5004로 바뀐다.
다음으로 데이터 영역에 20이 없으므로 @5005에 저장하고, 주소를 들고 obj2를 찾고 obj2의 값인 @5002가 가리키는 변수 영역에서 c를 찾아 그곳에 @5005를 저장한다. 주소를 따라가다 보면 전혀 어렵지 않다. </p>
<p>결과적으로 a와 b가 가리키는 주소는 각각 @5001, @5004로 달라졌지만, obj1과 obj2가 가리키는 주소는 @5002로 달라지지 않았다. 즉, <code>a!==b, obj1===obj2</code>이다.
obj2.c는 내부 프로퍼티를 바꿨고 b는 변수 자체를 바꿨으므로 어떻게 보면 당연한 일이기도 하다. 이번에는 같은 조건 하에서 비교해보자.</p>
<h3 id="객체-자체를-변경하는-경우">객체 자체를 변경하는 경우</h3>
<pre><code class="language-javascript"> b = 15;
 obj2 = {c: 20, d:&#39;ddd&#39;}</code></pre>
<p>이번에는 obj2 자체를 바꿔보자. 데이터 그룹을 만나는 상황이므로 데이터 영역의 새 공간에 새 객체가 저장되고 바뀐 주소가 obj2에 저장될 것이다. 
이번 경우에서는 a와 b뿐 아니라 obj1과 obj2도 서로 가리키는 주소가 달라졌다. </p>
<p>여기서 결론! <code>참조형 데이터가 &#39;가변&#39;값이라 하는 것은 객체 자체를 변경하는 것이 아닌 객체 내부의 프로퍼티를 변경할 때에만 해당된다.</code></p>
<hr>
<h1 id="불변-객체-만들기">불변 객체 만들기</h1>
<p>다시 정리를 하고 넘어가자.</p>
<ul>
<li>기본형 데이터는 불변값이다. -&gt; <code>불변</code></li>
<li>객체의 내부 프로퍼티 값을 변경할 때에는 변수에 다른 값을 넣는다. -&gt; <code>가변</code></li>
<li>객체 자체를 변경하려 하면 변수의 값을 바꾸는 것이 아닌 데이터 그룹을 새로운 공간에 할당하고 결국 객체가 가리키는 주소가 바뀌는 것이기 때문에 기존 데이터는 변하지 않는다. -&gt; <code>불변</code></li>
</ul>
<p>그렇다면 내부 프로퍼티를 변경하는 경우만 불변하도록 만들면 우리는 불변 객체를 사용할 수 있다.</p>
<blockquote>
<p>어떻게 불변성을 확보할 수 있을까?</p>
</blockquote>
<ul>
<li>내부 프로퍼티 변경 시에도 매번 새로운 객체를 만들기로 규칙을 정한다.</li>
<li><code>immutable.js</code>, <code>immer.js</code> 등의 자동으로 새로운 객체를 만드는 라이브러리를 활용한다.</li>
</ul>
<p>아니면 불변성이 필요할 때에만 불변 객체로 취급해주는 방법도 있다. 이번에는 매번 새로운 객체를 만드는 방법을 살펴보자.</p>
<p>다음 코드는 객체의 가변성 때문에 문제가 생긴다.</p>
<pre><code class="language-javascript">var user = {
  name: &#39;Jaenam&#39;,
  gender: &#39;male&#39;
};

var changeName = function (user, newName) {
  var newUser = user;
  newUser.name = newName;
  return newUser;
};

var user2 = changeName(user, &#39;Jung&#39;);

if(user !== user2) {
  console.log(&#39;유저 정보가 변경되었습니다.&#39;);
}
console.log(user.name, user2.name); // Jung Jung
console.log(user === user2);        // true</code></pre>
<p>이름이 바뀌면 유저 정보가 변경되었다는 알림을 주고 싶었는데, 그 부분은 출력되지 않는다. 맨 아래 두 줄에서 확인해본 것처럼 user과 user2는 서로 같은 이름을 가리키고 있다. <code>changeName</code> 함수가 <code>user</code> 객체의 프로퍼티 값을 바꾼 것을 알 수 있다.(같은 주소값을 가리키는 상태.) </p>
<p>이 예시처럼 정보가 바뀔 때 알림을 보내야 하거나, 변경 이전과 이후를 함께 보여줘야 하는 경우가 있을 수 있으므로 ,<code>changeName</code> 함수가 기존 객체를 건드리지 않고 새로운 객체를 만들도록 코드를 바꿔보자.</p>
<pre><code class="language-javascript">var user = {
  name: &#39;Jaenam&#39;,
  gender: &#39;male&#39;
};

var changeName = function (user, newName) {
  return {
    name: newName,
    gender: user.gender
  };
};

var user2 = changeName(user, &#39;Jung&#39;);

if(user !== user2) {
  console.log(&#39;유저 정보가 변경되었습니다.&#39;); // 유저 정보가 변경되었습니다.
}
console.log(user.name, user2.name); // Jaenam Jung
console.log(user === user2);        // false</code></pre>
<p>함수 부분만 코드를 바꿨고, 나머지는 그대로다. 정보가 제대로 변경되었음을 확인했다.</p>
<p>하지만 이름만 바뀌는 것인데, <code>gender</code>를 설정하는 부분은 일일이 입력했다. 프로퍼티가 아주 많아진다면 전부 코딩하는 것은 시간 낭비다. 모든 프로퍼티를 복사하는 함수를 만들어보자.</p>
<h2 id="얕은-복사-함수">얕은 복사 함수</h2>
<pre><code class="language-javascript">var copyObject = function (target) {
  var result = {};
  for (var prop in target) {
    result[prop] = target[prop];
  }
  return result;
}</code></pre>
<p><code>copyObject</code>는 target 객체의 프로퍼티들을 result에 복사하는 함수이다. <code>for-in</code> 문법으로 모든 프로퍼티를 순회한다.</p>
<p>위 코드에서 <code>var user2 = changeName(user, &#39;Jung&#39;)</code> 대신에</p>
<pre><code class="language-javascript">var user2 = copyObject(user);
user2.name = &#39;Jung&#39;</code></pre>
<p>과 같은 방식으로 객체를 복사, 수정할 수 있다.</p>
<p>하지만 곧 문제가 생겼다. 중첩 객체의 경우에는 얕은 복사 함수로 완벽하게 복사되지 않는 것이다! </p>
<pre><code class="language-javascript">var user = {
  name: &#39;Jaenam&#39;,
  urls: {
    portfolio: &#39;http://github.com/abc&#39;,
    blog: &#39;http://blog.com&#39;,
    facebook: &#39;http://facebook.com/abc&#39;
  }
};
var user2 = copyObject(user);

user2.name = &#39;Jung&#39;;
console.log(user.name === user2.name); // false

user.urls.portfolio = &#39;http://portfolio.com&#39;;
console.log(user.urls.portfolio === user2.urls.portfolio); // true

user2.urls.blog = &#39;&#39;;
console.log(user.urls.blog === user2.urls.blog) // true</code></pre>
<p>내부 프로퍼티(blog, facebook)의 경우에는 원본 객체를 그대로 참조하고 있다. <code>urls</code>객체도 따로 복사를 해줘야 한다. 이것을 한 번에 할 수 있는 깊은 복사 함수를 살펴보자.</p>
<h2 id="깊은-복사-함수">깊은 복사 함수</h2>
<pre><code class="language-javascript">var copyObjectDeep = function(target) {
  var result = {};
  if (typeof target === &#39;object&#39; &amp;&amp; target !== null) {
    for (var prop in target) {
      result[prop] = copyObjectDeep(target[prop]);
    }
  } else {
    result = target;
  }
  return result;
};</code></pre>
<p>몇 개의 객체가 중첩되어 있든 상관 없이, 재귀 호출을 이용해서 내부 프로퍼티를 모두 순회한다. 객체거나, null(typeof 결과가 object로 나오기 때문)인 경우엔 재귀 호출하고, 기본값 프로퍼티의 경우 그대로 지정한다. 이렇게 하면 원본 객체와 복사된 객체는 서로 완전히 다르게 된다.</p>
<hr>
<h1 id="undefined와-null">undefined와 null</h1>
<h2 id="undefined">undefined</h2>
<p><code>undefined</code>는 사용자가 명시적으로 지정할 수도 있지만 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여하기도 한다. 다음 경우에 undefined가 부여된다.</p>
<ul>
<li>값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때</li>
<li>객체 내부의 존재하지 않는 프로퍼티에 접근하려 할 때</li>
<li>return 문이 없거나 호출되지 않는 함수의 실행 결과</li>
</ul>
<pre><code class="language-javascript">var a;
console.log(a); // undefined: 값을 대입하지 않은 변수에 접근

var obj = {a: 1};
console.log(obj.a); // 1
console.log(obj.b); // undefined: 존재하지 않는 프로퍼티에 접근
console.log(b); // ReferenceError: b is not defined

var func = function() { };
var c = func(); // return 값이 없으면 undefined를 반환
console.log(c); // undefined</code></pre>
<p>하지만 값을 대입하지 않은 <code>배열</code>의 경우에는 조금 다르다.</p>
<pre><code class="language-javascript">var arr1 = [];
arr1.length = 3;
console.log(arr1); // [empty * 3]

var arr2 = new Array(3);
console.log(arr2); // [empty * 3]

var arr3 = [undefined, undefined, undefined];
console.log(arr3); // [undefined, undefined, undefined]</code></pre>
<p>빈 배열을 만들고 크기를 3이라고 하자 <code>undefined</code> 조차도 할당되지 않고 empty로 보인다.</p>
<p><code>new</code> 연산자와 <code>Array</code> 생성자 함수로 만든 배열 인스턴스에서도 아무것도 할당되어 있지 않다.</p>
<p>마지막으로 사용자가 직접 배열에 undefined를 넣은 경우에는 그것이 값이 된다.</p>
<p>이것이 <code>비어있는 요소</code>와 <code>undefined</code>의 차이점이다.&#39;</p>
<blockquote>
<p>빈 요소가 있는 배열을 순회할 때는 어떻게 될까?</p>
</blockquote>
<pre><code class="language-javascript">var arr1 = [undefined, 1];
var arr2 = [];
arr2[1] = 1;

arr1.forEach((v, i) =&gt; {console.log(v, i)}); // undefined 0 / 1 1
arr2.forEach((v, i) =&gt; {console.log(v, i)}); // 1 1

arr1.map((v, i) =&gt; v + i); // [NaN, 2]
arr2.map((v, i) =&gt; v + i); // [empty, 2]

arr1.filter(v =&gt; !v); // [undefined]
arr2.filter(v =&gt; !v); // []

arr1.reduce((p, c, i) =&gt; p + c + i); // undefined011
arr2.reduce((p, c, i) =&gt; p + c + i); // 11</code></pre>
<p>코드를 적고 나니 배열을 순회하는 <code>forEach</code>, <code>map</code>, <code>filter</code>, <code>reduce</code> 함수를 따로 정리해서 공부해봐야겠다.</p>
<p>본론으로 다시 돌아와서, arr1에 대해서는 배열의 모든 요소를 순회하지만 arr2에 대해서는 빈 요소에 대해 어떤 일도 하지 않고 그냥 넘어갔다는 것을 알 수 있다. 이것으로 다시 한 번 사용자가 할당하는 <code>undefined</code>와 자바스크립트 엔진이 자동으로 반환하는 <code>undefined</code>는 다르다는 것을 확인했다.</p>
<p>엔진에서 자동으로 <code>undefined</code>를 반환하므로 우리는 혼란을 피하기 위해 값으로 <code>undefined</code>를 할당하지 않으면 된다. <strong>&#39;값이 없다&#39;를 표현하고 싶다면 <code>undefined</code>가 아닌 애초에 그런 목적으로 만들어진 <code>null</code>을 쓰도록 하자.</strong></p>
<h2 id="null">null</h2>
<p><code>null</code>은 <code>비어있음</code>을 명시적으로 나타내준다. 하지만 버그가 하나 있는데, <code>typeof null === object</code> 이다. 따라서 어떤 값이 <code>null</code>인지 확인하고 싶을 때 <code>typeof</code>로 확인할 수 없다. </p>
<pre><code class="language-javascript">var n = null;
console.log(typeof n); // object

console.log(n == undefined); // true
console.log(n == null);      // true

console.log(n === undefined); // false
console.log(n === null);      // true</code></pre>
<p>동등 연산자 <code>==</code>로 비교하면 <code>null</code>과 <code>undefined</code>는 같다고 판단된다. 일치 연산자 <code>===</code>를 사용해서 정확히 판별하자.</p>
]]></description>
        </item>
    </channel>
</rss>