<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>prussian-1to9</title>
        <link>https://velog.io/</link>
        <description>코딩하는 햄스터</description>
        <lastBuildDate>Wed, 09 Apr 2025 06:40:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>prussian-1to9</title>
            <url>https://velog.velcdn.com/images/prussian-1to9/profile/1f98383d-3649-4338-84b7-bac8816a2a95/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. prussian-1to9. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/prussian-1to9" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[브라우저 저장소 총정리 (with 크롬 개발자도구)]]></title>
            <link>https://velog.io/@prussian-1to9/browser-storages</link>
            <guid>https://velog.io/@prussian-1to9/browser-storages</guid>
            <pubDate>Wed, 09 Apr 2025 06:40:54 GMT</pubDate>
            <description><![CDATA[<h2 id="개요--크롬-개발자도구를-통해-종류-파악하기">개요 : 크롬 개발자도구를 통해 종류 파악하기</h2>
<ul>
<li><p>크롬 개발자도구 &gt; <code>Application</code> 탭에 들어가면 사진과 같이 여러가지 브라우저 저장소를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/prussian-1to9/post/96301259-7068-4a8c-a8f4-c86a53885ae4/image.png" alt="chrome devtool"></p>
</li>
<li><p>해당 게시글은 이 <code>Application</code> 탭에서 확인할 수 있는 주요 저장소인 <strong>Local storage</strong>, <strong>Session storage</strong>, <strong>Cookie</strong>, <strong>Cache</strong>, <strong>IndexedDB</strong>의 각 특징과 쓰임새에 대해서 살펴보고자 한다. (HTML5 미만의 구형 브라우저에 대해서는 생각하지 않음)</p>
</li>
<li><p>Extension storage : 외부 저장공간이 아닌 Chrome <strong>확장 프로그램 저장용량</strong>을 뜻한다.</p>
</li>
<li><p>이는 <strong><em>모두 XSS 등에 의한 데이터 탈취 가능성이 존재</em></strong>하기 때문에, 민감한 정보는 BE단에서 처리하는 것이 좋다.</p>
</li>
</ul>
<blockquote>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#web-storage-api"><strong>Web Storage API</strong></a><ul>
<li><a href="#-local-storage">로컬 스토리지(Local storage)</a></li>
<li><a href="#-session-storage">세션 스토리지(Session storage)</a></li>
</ul>
</li>
<li><a href="#-indexeddb"><strong>IndexedDB</strong></a></li>
<li><a href="#-cookie"><strong>쿠키(Cookie)</strong></a></li>
<li><a href="#-cache"><strong>캐시(Cache)</strong></a></li>
<li><a href="#summary--%EB%AA%A8%EC%95%84%EB%B3%B4%EA%B8%B0">Summary</a></li>
<li><a href="#%EB%A7%88%EC%B9%98%EB%A9%B0--%ED%86%A0%EB%A7%89%EC%A7%80%EC%8B%9D">마치며</a></li>
</ul>
</blockquote>
<hr>
<h2 id="web-storage-api">Web Storage API</h2>
<p>브라우저에 데이터를 간단히 저장하는 JS 기반 저장소 API</p>
<h3 id="특징">특징</h3>
<ul>
<li><a href="#-cookie"><strong>쿠키(Cookie)</strong></a>와 유사하게 <strong><code>key-value</code> 쌍</strong>을 이루어 브라우저 메모리에 데이터를 저장하나, <strong>저장방식 자체는 쿠키와 상이</strong>하다.</li>
<li><strong>JS 객체를 통한 쉬운 접근</strong>이 가능하여 Web Storage <strong>API</strong>로 명명된다.</li>
<li>Local, Session storage 각 <strong>scope에 따른 쓰임에 차이</strong>가 있다.</li>
</ul>
<h3 id="📓-local-storage">📓 Local storage</h3>
<ul>
<li>브라우저 탭 삭제시에도 데이터가 삭제되지 않고, 특정 <strong>유효기간도 존재하지 않는</strong> 저장소</li>
<li>테마 및 언어 설정 등의 <strong>반영구적</strong>이고, <strong>민감정보가 아닌</strong> 데이터 저장 시 유용하다.</li>
</ul>
<h3 id="📝-session-storage">📝 Session storage</h3>
<ul>
<li>각 origin 별, 브라우저 탭 별로 귀속되어, <strong>브라우저나 탭을 삭제하면 데이터도 함께 삭제</strong>된다. (새로고침은 OK)</li>
</ul>
<blockquote>
<ul>
<li><strong>💡 같은 origin</strong>의 <strong>여러 브라우저 탭</strong>이 존재할 경우<ul>
<li>각 탭은 서로 다른 session storage를 사용한다.</li>
<li>session storage의 scope는 탭에 귀속된다.</li>
</ul>
</li>
</ul>
</blockquote>
<h3 id="핸들링">핸들링</h3>
<ul>
<li>JS를 코드를 통한 접근 : <code>localStorage</code>, <code>sessionStorage</code> 변수 사용</li>
<li><strong>사용자</strong>의 수동 조작 (권장X) : 개발자 도구를 통한 접근 등</li>
</ul>
<hr>
<h2 id="📅-indexeddb">📅 IndexedDB</h2>
<p><strong>브라우저 상의 noSQL</strong> 데이터베이스로 이해하면 쉽다.</p>
<h3 id="특징-1">특징</h3>
<ul>
<li>일반적인 noSQL처럼 데이터베이스를 생성하여 사용하고, 마찬가지로 <code>key-value</code> 쌍의 데이터를 저장한다.</li>
<li><strong>트랜잭션</strong> 단위 요청, 여러가지의 <strong>자료구조가 저장 가능</strong>하며, <strong>고용량</strong>의 저장공간을 지원한다.</li>
<li>모든 조작이 <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology"><strong>비동기 요청</strong>과 <strong>콜백 기반</strong> 구조</a>다.</li>
<li>JS를 통한 핸들링이 가능하나, Web Storage API에 비해 <strong>코드 구조가 복잡</strong>해질 수 있다.<ul>
<li>보통 <code>idb</code>, <code>dexie</code>등의 라이브러리를 함께 사용한다.</li>
</ul>
</li>
</ul>
<h3 id="핸들링-1">핸들링</h3>
<ul>
<li>JS를 통한 접근 : <code>window.indexedDB</code> 변수 사용 (브라우저별 변수명 상이)</li>
<li><strong>사용자</strong>의 수동 조작 (권장X) : 개발자 도구를 통한 접근 등 (데이터 조회, 삭제만 가능)</li>
</ul>
<hr>
<h2 id="🍪-cookie">🍪 Cookie</h2>
<p>현재는 Web Storage API의 등장으로 쿠키 사용 빈도가 많이 줄어들었으나, 특정 케이스에서 쿠키 사용이 최적인 경우가 존재한다.</p>
<h3 id="특징-2">특징</h3>
<ul>
<li><p>모든 <strong>HTTP request와 함께 전송</strong>된다.</p>
<ul>
<li><strong>성능 저하</strong>가 발생할 수 있지만 <strong>서버에서의 제어가 가능</strong>하다.</li>
<li>사용자 행동 <strong>트래킹</strong> 등 서버 데이터에 따라 클라이언트 데이터가 동기화 되어야 하는 경우 유리하다.</li>
</ul>
</li>
<li><p>해당 도메인의 <strong>전역적인 설정</strong>과 관련된 데이터 설정 시 유리할 수 있다.</p>
<ul>
<li>테마, 언어 설정 등 <strong>개인화 옵션</strong></li>
<li><strong>세션 관리</strong>에 필요한 자원 등</li>
</ul>
</li>
</ul>
<h3 id="핸들링-2">핸들링</h3>
<p><code>HttpOnly</code> 플래그가 설정된 쿠키 값은 <strong>JS를 통한 접근이 불가</strong>능하다.</p>
<ul>
<li><strong>JS</strong> <code>document.cookie</code>를 통한 접근</li>
<li><strong>서버 측</strong>에서의 만료시간, 보안 플래그(<code>Secure</code>, <code>HttpOnly</code>) 등을 설정하여 제어 (언어 / 환경 별 방법 상이)</li>
<li><code>curl</code>, <code>postman</code> 등 <strong>HTTP 클라이언트</strong>를 통한 접근</li>
<li><strong>사용자</strong>의 설정 및 조작 : 브라우저 설정, 확장 프로그램, 개발자 도구(<code>Network</code>) 조작 등</li>
</ul>
<hr>
<h2 id="📦-cache">📦 Cache</h2>
<p>브라우저에서 자동으로 <strong>정적(static)인 컨텐츠를 저장</strong>하는 것을 의미한다.</p>
<h3 id="특징-3">특징</h3>
<ul>
<li>페이지에서 불러오는 정적 컨텐츠가 많을 경우, 캐시 메모리를 사용하면 로딩시간을 줄일 수 있다.</li>
<li>지정한 캐시 만료/갱신시간보다 정적 컨텐츠 업로드 주기가 빠를 경우, <strong>최신버전이 반영되지 않을 수</strong> 있다.<ul>
<li>이 경우 캐시 갱신에 대한 핸들링이 필요하다.</li>
</ul>
</li>
</ul>
<blockquote>
<ul>
<li><p><strong>💡 정적 컨텐츠</strong></p>
<ul>
<li>WAS(Web Application Server)와 WEB서버가 분리되어 있을 때, WEB서버에서 원활하게 구동되는 것들이다.</li>
<li>e.g. HTML, 이미지, JS(script) / CSS(style sheet) 등</li>
</ul>
</li>
</ul>
</blockquote>
<h3 id="핸들링-3">핸들링</h3>
<ol>
<li><p>WEB 서버 혹은 response의 <strong>헤더 설정</strong>을 통한 <code>cache-control</code>(캐시 만료기간) 옵션 설정</p>
</li>
<li><p>정적 컨텐츠 로드 시 조작 : <strong>해시값</strong>(fingerprint) 사용, 쿼리스트링(<code>ver=</code>) 사용 (수기, 권장X)</p>
<pre><code class="language-bash">일반 CSS 로드 예시 : style.css
해시값 사용 예시 : style.4dgjf63.css # 번들러로 자동 생성 가능</code></pre>
</li>
<li><p>서비스 워커를 통한 캐시 제어 : <strong>PWA</strong>(Progressive Web Application) 환경이거나 <strong>오프라인 기동이 필요한 경우</strong>에만 추천</p>
</li>
<li><p><strong>사용자</strong>의 캐시 갱신 (권장X) : 브라우저 캐시 제거, 개발자 도구 사용 등</p>
</li>
<li><p><strong>사용자</strong>의 캐시 최대값 설정 (권장X) : 브라우저 바로가기에 <code>--disk-cache-size=${cache_size}</code> 커맨드 설정(크롬 기준)</p>
</li>
</ol>
<hr>
<h2 id="summary--모아보기">Summary : 모아보기</h2>
<p>각 목적과 특징에 맞는 브라우저 저장소를 이용하자.</p>
<table>
<thead>
<tr>
<th align="center">storage type</th>
<th align="center">저장형태</th>
<th align="center">(자동) 만료시간</th>
<th align="center">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>Local storage</strong></td>
<td align="center"><code>key-value(string)</code></td>
<td align="center">X</td>
<td align="center">도메인 공유</td>
</tr>
<tr>
<td align="center"><strong>Session storage</strong></td>
<td align="center"><code>key-value(string)</code></td>
<td align="center">브라우저 <code>탭</code> 유지 동안</td>
<td align="center">도메인 공유</td>
</tr>
<tr>
<td align="center"><strong>IndexedDB</strong></td>
<td align="center">다양한 자료구조</td>
<td align="center">X</td>
<td align="center">트랜잭션, 고용량</td>
</tr>
<tr>
<td align="center"><strong>Cookie</strong></td>
<td align="center"><code>key=value</code></td>
<td align="center"><code>Expires</code> || <code>Max-Age</code> || X(세션쿠키)</td>
<td align="center"><code>HttpOnly</code> 설정 가능</td>
</tr>
<tr>
<td align="center"><strong>Cache</strong></td>
<td align="center">정적 컨텐츠 자체</td>
<td align="center">브라우저 자동 설정</td>
<td align="center">적절한 활용 시 성능 향상</td>
</tr>
</tbody></table>
<h3 id="마치며--토막지식">마치며 : 토막지식</h3>
<ul>
<li><strong>사생활보호 &amp; 시크릿 모드</strong> : cookie 저장소 자체를 사용하지 않거나, Web Storage API의 저장용량을 0바이트로 할당하는 일부 브라우저도 존재한다고 한다.</li>
<li><strong>Web Storage의 저장소 크기는 접속 환경별로 다르다</strong> : 서드파티 쿠키 활성화 여부, 접속 브라우저 등에 따라 Web storage에 접근 가능한 크기는 상이할 수 있다.</li>
</ul>
<blockquote>
<h4 id="참고-자료">참고 자료</h4>
<ul>
<li><strong>MDN</strong><ul>
<li>Web Storage API (<a href="https://developer.mozilla.org/ko/docs/Web/API/Web_Storage_API">ko</a>)</li>
<li>IndexedDB API(<a href="https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API">ko</a> / <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology">en</a>)</li>
<li>IndexedDB key characteristics and basic terminology (<a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology">en</a>)</li>
<li>HTTP Cookie (<a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Cookies">ko</a>)</li>
<li>Set-Cookie (<a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Reference/Headers/Set-Cookie">ko</a>)</li>
<li>Service Worker(<a href="https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API">en</a>)</li>
</ul>
</li>
<li><strong>기타 기술 블로그</strong><ul>
<li>WHATWG - <a href="https://blog.whatwg.org/tag/localstorage">local storage</a></li>
<li>imgix docs - <a href="https://docs.imgix.com/en-US/getting-started/best-practices/fingerprinting-images">Fingerprinting Images to Improve Page Load Speed</a></li>
</ul>
</li>
<li><strong>stack overflow</strong><ul>
<li><a href="https://stackoverflow.com/questions/77314647/what-is-the-maximum-amount-of-data-that-a-browser-for-example-chrome-can-stor">What is the maximum amount of data that a browser (for example: Chrome) can store in cache?</a></li>
</ul>
</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[01. API 서버 마이그레이션의 결심]]></title>
            <link>https://velog.io/@prussian-1to9/API-%EC%84%9C%EB%B2%84-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98%EC%9D%98-%EA%B2%B0%EC%8B%AC</link>
            <guid>https://velog.io/@prussian-1to9/API-%EC%84%9C%EB%B2%84-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98%EC%9D%98-%EA%B2%B0%EC%8B%AC</guid>
            <pubDate>Fri, 02 Aug 2024 01:27:41 GMT</pubDate>
            <description><![CDATA[<h1 id="목차">목차</h1>
<blockquote>
<ul>
<li><a href="#%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%84%ED%96%89-%EB%B0%B0%EA%B2%BD">프로젝트 진행 배경</a></li>
<li><a href="#%EC%82%AC%EC%9A%A9-%EC%8A%A4%ED%83%9D">사용 스택</a><ul>
<li><a href="#%EC%84%A0%EC%A0%95-%EC%9D%B4%EC%9C%A0">선정 이유</a></li>
<li><a href="#%ED%83%88%EB%9D%BD%ED%96%88%EB%8D%98-%ED%9B%84%EB%B3%B4%EA%B5%B0">탈락했던 후보군</a></li>
<li><a href="#PHP-%EA%B8%B0%EB%B0%98-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EC%9D%98-%EB%B0%98%EB%A0%A4">PHP 기반 프레임워크의 반려</a></li>
</ul>
</li>
<li>[앞으로의 계획](#앞으로의 계획)</li>
</ul>
</blockquote>
<h2 id="프로젝트-진행-배경">프로젝트 진행 배경</h2>
<ul>
<li>pure PHP 코드로 인해 <strong>뒤엉켜있는 FE / BE 코드</strong></li>
<li><strong>$n(n &gt; 15)$년 간의 코드 누적</strong> : 미사용 코드 다수, 실제 사용되는 로직은 극히 일부<ul>
<li><strong>수기처리 코드</strong> : 공통로직을 찾아내 최대한 모듈화, 객체화를 하여도 기존 수기처리로 인해 이전 코드와 큰 차이가 없었다.</li>
<li><strong>몇 만 줄의 한 파일</strong> : 한 파일 당 한 기능을 하지 않고, 기본적인 절차/객체지향의 코드 형태를 따르지도 않았다.</li>
</ul>
</li>
</ul>
<p>따라서 부분적으로 마이그레이션을 하는 것이 가장 현실적이고 실현 가능한 방법이 아닐까, 하는 생각이 들었다.</p>
<h2 id="초기-사용-스택">초기 사용 스택</h2>
<p>최대한 오래 갈 수 있는 스택을 선택했으며, 다른 모듈이나 라이브러리도 많지만 일단은 초기 구성을 최소화 하고 <strong>추후 고도화 시킬 수 있는 방향</strong>으로 셋팅했다.</p>
<ul>
<li>node.js 20.10.0 LTS + express 4.19.2 (+ dotenv등 기타 npm 모듈)</li>
<li>sentry</li>
<li>typescript 5.5.3</li>
<li>기존 DB : My SQL, MS SQL (SQL Server), Oracle 모두 사용</li>
</ul>
<h3 id="선정-이유">선정 이유</h3>
<p>기재해둔 내용이 상당히 적다. <strong>compact한 틀을 만드는 것이 첫번째 목표</strong>였기 때문이다.
그 외에도 아래의 목표들이 해당 스택 선정의 이유가 되었다.</p>
<ol>
<li><strong>범용성</strong>, <strong>커뮤니티 규모</strong> : 프레임워크를 사용하지 않고 pure PHP를 사용하던 팀원들이 접할 수 있는 자료가 충분한가?</li>
<li><strong>정규화</strong> : DB에서 정규화가 되어있는 편이 아니다. API단에서 이를 보완할 수 있는가?</li>
<li><strong>확장성</strong> : 기존 서비스는 많은 기능이 동작 중이다. 내가 알지 못하는 영역이 분명히 존재할 것이기 때문에 <strong>초기엔 최대한 컴팩트</strong>하게, 그리고 <strong>이후 기능 추가는 쉬워야</strong> 한다.</li>
<li><strong>빌드 용이</strong> : CI/CD, 미러링이 구축된 환경이 아니기 때문에 빌드가 하나의 변수로 작용할 수 있다.</li>
<li><strong>영구성</strong> : 오래 유지/사용할 수 있는 버전or환경인가?</li>
<li><strong>HTTP2 프로토콜 사용</strong></li>
</ol>
<h3 id="탈락했던-후보군">탈락했던 후보군</h3>
<ul>
<li><p>java 계열 : Spring boot + kotlin</p>
<ul>
<li>장점<ul>
<li>정규화, 객체/인스턴스 관리가 용이하며 강제성을 띄며, 이에 대한 자료도 많다.</li>
<li>annotation의 활용 : 타 언어에 비해 annotaion의 역할과 명시성이 확실하다.</li>
</ul>
</li>
<li>반려 사유<ul>
<li>기존 인력의 낮은 친숙도 : 정적 언어의 특성, Java / Gradle syntax를 알고 있어야 접근성이 좋다.</li>
<li>빌드</li>
</ul>
</li>
</ul>
</li>
<li><p>python : Django rest framework</p>
<ul>
<li>장점 : PHP와 가장 비슷한 언어적 특성, 친절한 템플릿</li>
<li>반려 사유 : RESTful 할 수 있지만 이후 설명할 정규화가 흠이 되었다.</li>
</ul>
</li>
<li><p>javascript : Next.js API Routes</p>
<ul>
<li>반려 사유 : 빠르게 변화하는 프레임워크, 파일 구조 기반 url라는 리스크, 비교적 적은 자료(14), 검색엔진을 따로 사용하지 않는 환경</li>
<li>결정적인 반려 사유 : FE / BE 서버가 분리되지 않은 입장이라면 유용하겠지만, 분리 목적으로 넥스트를 바라보니 <strong>장점이 떠오르지 않았다</strong>.</li>
<li><strong>그 외에도 JS기반 프레임워크는 수 없이 존재</strong>하지만 가장 자료가 많고, 확장성이 큰 express를 선정하게 되었다.</li>
</ul>
</li>
</ul>
<h3 id="php-기반-프레임워크의-반려">PHP 기반 프레임워크의 반려</h3>
<p>php is dead 밈이 나올 정도로 수십년간 건재한 PHP.. 때문에 PHP라는 언어 자체를 피하고 싶다는 강박은 없었다. (오히려 앞서 언급한 &#39;영구성&#39;에 가장 잘 부합하는 것이 PHP가 아닐까 싶다.)
<img src="https://velog.velcdn.com/images/prussian-1to9/post/dd90b44e-1bce-4735-bd0f-3b48c45e29ae/image.png" alt="tellme_yes"></p>
<p>다만 PHP를 기반으로 하는 Laravel과 Composer, CodeIgniter 등은 아래의 이유로 배제하였다.</p>
<ol>
<li><p><strong>범용성</strong></p>
<ul>
<li><strong>압도적으로 적은 자료 수</strong> : 자료 자체의 수가 타 프레임워크 (spring 계열, node.js 계열)과 차이가 컸다.</li>
<li><strong>PHP 버전 종속성</strong> : 찾은 자료가 있어도 PHP가 버전에 민감하다 보니, syntax 지원이 안돼 페이지가 먹통이 되는 일이 잦았다.</li>
</ul>
</li>
<li><p><strong>동적 언어 PHP</strong></p>
<ul>
<li>입출력 / 배열 관리를 중점해 처리할 수 있는 환경이 아니었다.</li>
<li>한 페이지에서 <strong>여러 형태의 데이터 입출력</strong>이 처리되는 경우가 많음</li>
<li>모든 페이지에서 일정한 데이터를 사용하지 않음 : 비슷한 형태는 많지만, 동일한 형태는 희소함</li>
</ul>
</li>
</ol>
<h2 id="앞으로의-계획">앞으로의 계획</h2>
<p>스크래치부터 시작하고자 한다. 개발환경 구축부터 </p>
<ol>
<li>VS Code 환경 구축하기 : express + eslint + dotenv 환경 구축</li>
<li>docker 환경 구축 : node 설치, git 연결</li>
<li>alias, HTTP2 설정 : localhost에서 테스트</li>
<li>sentry 로깅 설정</li>
<li>auth 관련 구현 : 이 부분은 벨로그에 기재하지 않을 예정이다.</li>
<li>DI 이용하여 커넥션 / 모델 구현 및 테스트</li>
</ol>
<p>여태까지 겪은 pure PHP 스파게티 코드들에게 안녕을 고할 수 있기를 바라며! 글을 마친다.</p>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/ef4f46cb-d532-486c-a883-b1304e1dbe51/image.png" alt="PHP_still_alive"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Reflow & Repaint]]></title>
            <link>https://velog.io/@prussian-1to9/Reflow-Repaint</link>
            <guid>https://velog.io/@prussian-1to9/Reflow-Repaint</guid>
            <pubDate>Thu, 04 Jul 2024 06:41:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>목차</strong></p>
<ul>
<li><a href="#%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A7%A4%EC%BB%A4%EB%8B%88%EC%A6%98">브라우저 매커니즘</a></li>
<li><a href="#%EC%BD%94%EB%93%9C-%EC%83%81%EC%97%90%EC%84%9C%EC%9D%98-%EB%B0%9C%EC%83%9D">코드 상에서의 발생</a></li>
<li><a href="#%EA%B0%9C%EB%85%90%EC%9D%84-%EC%95%8C%EA%B3%A0-%EC%9E%88%EC%96%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">개념을 알고 있어야 하는 이유</a><ul>
<li><a href="#%EB%B0%9C%EC%83%9D-%EB%B9%88%EB%8F%84%EB%A5%BC-%EC%A4%84%EC%9D%B4%EB%A0%A4%EB%A9%B4">발생 빈도를 줄이려면</a></li>
</ul>
</li>
</ul>
</blockquote>
<h1 id="브라우저-매커니즘">브라우저 매커니즘</h1>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/9c3b9c4e-9f9f-4cb3-b2b6-33e5d4d86047/image.png" alt="HTML_CSS_parsing"></p>
<ol>
<li><strong>DOM(Document Object Model) Tree 생성</strong></li>
</ol>
<ul>
<li>Server-&gt;Client : URL 입력, HTML 코드등 <strong>데이터 수신</strong></li>
<li>Browser parsing<ul>
<li>HTML source -&gt; Token : <code>TagName</code>, <code>Attribute</code>, <code>AttributeValue</code></li>
<li><strong>lexing</strong> : Token -&gt; DOM Tree nodes -&gt; logical tree (DOM)</li>
</ul>
</li>
</ul>
<ol start="2">
<li><p><strong>CSSOM(CSS Object Model) Tree 생성</strong> : CSS 규칙에 따라 생성</p>
<ul>
<li>logical tree인 DOM과 달리, JS를 통한 CSS 접근이 가능하다.<ul>
<li>CSS에 대한 DOM개념</li>
</ul>
</li>
<li>사용자의 CSS read / write를 가능하게 한다.</li>
</ul>
</li>
<li><p>Browser rendering : merge to <strong>Render Tree(DOM Tree + CSSOM Tree)</strong></p>
</li>
</ol>
<ul>
<li>DOM Tree root node부터 시작, element들이 visible한지, computed style이 무엇인지 도출</li>
<li><strong>not visible한 element</strong> : <strong>무시</strong>. <code>&lt;meta&gt;</code>, <code>&lt;script&gt;</code>, <code>&lt;link&gt;</code>, <code>display: none;</code> 등의 요소는 제외하고 구성됨</li>
<li><strong>visible한 노드</strong> : CSSOM에 맞춰 Rendering Tree에 구성 됨</li>
</ul>
<ol start="4">
<li><strong>Reflow</strong> : visible한 node들에 대한 <strong>레이아웃을 계산</strong>한다.</li>
</ol>
<ul>
<li>ex) <code>width</code>, <code>position</code>, <code>float</code> 등</li>
</ul>
<ol start="5">
<li><strong>Repaint</strong> : 병합된 Render Tree의 노드들을 화면에 <strong>픽셀로 표시</strong></li>
</ol>
<ul>
<li>ex) <code>opacity</code>, <code>color</code>, <code>background-color</code>, <code>visibility</code> 등</li>
</ul>
<blockquote>
<p>💡 <strong>computed style?</strong></p>
<ul>
<li>CSS는 Cascading style sheet로, 선언 방식or위치에 따라 <strong>우선순위가 나뉜다</strong>.</li>
<li>우선순위(inline &gt; internal &gt; external)에 따라 <strong>적용된 최종 style</strong>을 computed style이라고 한다.</li>
</ul>
</blockquote>
<hr>
<h1 id="코드-상에서의-발생">코드 상에서의 발생</h1>
<p>이러한 reflow, repaint는 <strong>이벤트가 일어나 화면이 변경되었을 때에도 발생</strong>한다.<br>코드와 함께 확인해보자.</p>
<pre><code class="language-javascript">let elStyle = document.body.style;

// dimension 상 변화 O + 화면상 변화 O =&gt; reflow + repaint
elStyle.padding = &#39;15px 10px&#39;;
elStyle.border = &#39;1px solid red&#39;;
elStyle.fontSize = &#39;30px&#39;;

// dimension 상 변화 X + 화면상 변화 O =&gt; repaint only
elStyle.color = &#39;white&#39;;
elStyle.backgroundColor = &#39;black&#39;;

// element append : dimension상 변화 O + 화면상 변화 O =&gt; reflow + repaint
document.body.appendChild(document.createElement(&#39;H1&#39;))</code></pre>
<p>이와같이, DOM element의 구조적인 <strong>dimension 상 변화가 있다면 reflow</strong>, 화면상의 <strong>픽셀값이 변화하면 repaint</strong>가 일어나는 것을 확인할 수 있다.  </p>
<p>그 외에도 JS에서 reflow/repaint <strong>관련 syntax를 사용할 때에도 발생</strong>한다.</p>
<ul>
<li>ex) <code>getComputedStyle()</code>, <code>scroll()</code>, <code>.focus()</code>, <code>.style</code></li>
</ul>
<hr>
<h1 id="개념을-알고-있어야-하는-이유">개념을 알고 있어야 하는 이유</h1>
<p>reflow, repaint의 발생 빈도를 줄이면 아래의 것들이 개선될 수 있다.</p>
<ul>
<li>read/write 감소하며 <strong>최적화에 유리</strong>해짐</li>
<li><strong>웹 성능</strong>이 떨어질 경우, 발생 요소를 고려하여 문제를 해결할 수 있음</li>
<li>클린 코드, 언어적인 활용과도 연관이 있기 때문에 코드 유지보수에도 일정 정도 도움이 될 것으로 예상</li>
</ul>
<h2 id="발생-빈도를-줄이려면">발생 빈도를 줄이려면</h2>
<h3 id="1-가상-메모리--캐싱-활용하기-js-코드-요소-활용하기">1. 가상 메모리 / 캐싱 활용하기 (JS 코드 요소 활용하기)</h3>
<h4 id="1-1-fragment-노드를-사용">1-1. fragment 노드를 사용</h4>
<ul>
<li>더 유연한 코드 작성이 가능하다. (실제로 DOM에 추가된 element가 아니기 때문에)</li>
<li>속도 면에서는 접속 환경에 따른 차이가 발생할 수 있다, fragment 사용 시 효과는 미미한것 같다. (오히려 느릴 때도 있다)<pre><code class="language-javascript">const root = document.querySelector(&#39;#root&#39;);</code></pre>
</li>
</ul>
<pre><code class="language-javascript">// ex1. documentFragment 객체 사용하기
const fragment = new documentFragment();

fragment.append(document.createElement(&#39;h1&#39;));
fragment.append(document.createElement(&#39;h2&#39;));
fragment.append(document.createElement(&#39;h3&#39;));

root.append(fragment);</code></pre>
<p>두번째 예시는 ex1과 선언 방식만 다르고 거의 동일하다.</p>
<pre><code class="language-javascript">// ex2. createElement(&#39;fragment&#39;) 컴포넌트 활용하기
const fragment = document.createElement(&#39;fragment&#39;);

// ex1과 동일
fragment.append(document.createElement(&#39;h1&#39;));
fragment.append(document.createElement(&#39;h2&#39;));
fragment.append(document.createElement(&#39;h3&#39;));

root.append(fragment); // ex1과 동일</code></pre>
<p>세번째 예시는 ex1, ex2 비슷하지만, append할 <strong>요소들을 포괄하는 컴포넌트가 같이 추가된다</strong>는 차이점이 있다. </p>
<pre><code class="language-javascript">// ex3. fragment 사용 없이 가상 메모리 이용하기
const divEl = document.createElement(&#39;Div&#39;);

// ex1, ex2와 변수명만 다름
divEl.append(document.createElement(&#39;h1&#39;));
divEl.append(document.createElement(&#39;h2&#39;));
divEl.append(document.createElement(&#39;h3&#39;));

root.append(divEl); // ex1, ex2와 변수명만 다름</code></pre>
<h4 id="1-2-캐싱하기">1-2. 캐싱하기</h4>
<p>같은 맥락으로, <strong>변경사항이 여러번 발생</strong>하는 element라면 <strong>변수에 캐싱</strong>하는 것도 도움이 될것 같다.</p>
<ul>
<li><code>class</code>, <code>cssText</code> 사용하기</li>
</ul>
<h4 id="1-3-주의할-점">1-3. 주의할 점</h4>
<p>해당 게시글이 참고한 <a href="https://gist.github.com/faressoft/36cdd64faae21ed22948b458e6bf04d5">github documnet</a>에 따르면, 다음과 같은 유의점이 기재되어있다.</p>
<ul>
<li>jQuery(<code>${CSS_selector}</code>) : <code>:even</code>, <code>:has</code>, <code>:gt</code>, <code>:eq</code> 피하기<ul>
<li><strong>기피해야하는 이유?</strong> : 본인으로서는 <em>정확히</em> 알 수 없으나, 이하의 이유들로 추론된다.<ul>
<li>현존하는 Vanilla JS가 이미 충분히 직관적임 : <code>querySelectorAll</code>, <code>Array.prototype.filter</code> 등</li>
<li>jQuery라는 라이브러리를 사용 : 즉각적인 코드 실행이 아님</li>
<li>명확하지 않은 선택범위 : 해당 관심사(concern)에 속하지 않는 범위의 파악이 어려움</li>
<li>러프하게 이야기 하자면, <strong>&#39;있는거 쓰지 뭐하러 굳이 직관적이지도 더 빠르지도 않는걸 쓰냐&#39;</strong>는 느낌이지 않나 싶다.</li>
</ul>
</li>
</ul>
</li>
<li>코드 상의 <strong>관심사(concern)는 최소 범위로</strong> 잡을 것</li>
</ul>
<h3 id="2-가시적인-element-숨기기">2. 가시적인 element 숨기기</h3>
<p>Render Tree 구성은 <strong>보여지는 노드들을 대상</strong>인 점을 이용한다.<br>기본적으로 <strong>보여지지 않는 element들</strong>에 대해서는 미리 <code>display: none;</code>처리를 하거나, visible한 케이스를 분리해 주는 것도 도움이 될 것이다.  </p>
<h3 id="3-css-수정">3. CSS 수정</h3>
<ul>
<li><strong>20개 미만</strong>의 사항이라면 css 파일보다는 <code>&lt;style&gt;</code> 태그 사용하기</li>
<li>inline style 피하기</li>
<li><code>position: absolute</code>, <code>position: fixed</code> 등을 활용하기 : 리소스 감소에 도움을 준다.</li>
</ul>
<blockquote>
<ul>
<li><strong>참고자료</strong> (참고 비중순 정렬)</li>
<li><a href="https://gist.github.com/faressoft/36cdd64faae21ed22948b458e6bf04d5">github: dom_performance_reflow_repaint.md</a></li>
<li><a href="https://www.hi-interactive.com/blog/avoid-reflow-repaint">Avoid Reflow &amp; Repaint</a></li>
<li><a href="https://www.edwith.org/htmlcss/lecture/16612?isDesc=false">edwith : 캐스케이딩 - computed style이 결정되는 방식</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/CSS_Object_Model">MDN : CSSOM</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model">MDN : DOM</a></li>
<li><a href="https://velog.io/@oneook/DocumentFragment-%EA%B0%9D%EC%B2%B4%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%EC%8D%A8%EC%95%BC%ED%95%A0%EA%B9%8C">velog : documentFragment 객체는 무엇이며 왜 써야할까?</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 크레인 인형뽑기 게임 (파이썬)]]></title>
            <link>https://velog.io/@prussian-1to9/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%81%AC%EB%A0%88%EC%9D%B8-%EC%9D%B8%ED%98%95%EB%BD%91%EA%B8%B0-%EA%B2%8C%EC%9E%84-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@prussian-1to9/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%81%AC%EB%A0%88%EC%9D%B8-%EC%9D%B8%ED%98%95%EB%BD%91%EA%B8%B0-%EA%B2%8C%EC%9E%84-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Sun, 23 Apr 2023 16:35:30 GMT</pubDate>
            <description><![CDATA[<p><code>print()</code>로 정답을 제출했던 백준과 달리, 프로그래머스는 함수형 (<code>return</code>)으로 정답을 반환해야 한다.</p>
<p>간단히 문제 정리를 해 보았다. 
▶ <a href="https://school.programmers.co.kr/learn/courses/30/lessons/64061">문제 링크</a></p>
<blockquote>
<h3 id="함수-명세">함수 명세</h3>
<p>기계 내부가 되는 격자 형태는 <code>N</code>칸 X <code>N</code>칸의 형태이다.</p>
</blockquote>
<ul>
<li><code>board</code><ul>
<li><code>N</code>개의 element들로 이루어진 list <code>N</code>개가 argument로 입력된다.</li>
<li>행렬을 떠올리면 쉬울것 같다. 처음 입력된 list부터 가장 상위에 있는 list</li>
<li><code>숫자 == 인형의 종류</code> (0은 빈칸)</li>
</ul>
</li>
<li><code>moves</code> : 인형을 꺼내올 열 번호 (index 값과 상이하게, <strong>첫 열이</strong> 0이 아니라 <strong>1</strong>이다.)</li>
<li><code>return</code> 값 : 사라진 인형의 개수</li>
</ul>
<blockquote>
<h3 id="코드-처리">코드 처리</h3>
</blockquote>
<ul>
<li>board에서 moves에 담긴 열의 <strong>맨 위 인형</strong>을 꺼내 별도의 바구니에 담는다.</li>
<li>별도의 바구니 속 <code>최상단 인형의 종류 == 새로 담는 인형의 종류</code>일 경우 두 인형은 사라진다.</li>
<li>사라진 인형의 개수를 반환할 것.</li>
</ul>
<p>일단 한번 함수를 작성해 보았다.</p>
<pre><code class="language-python">def solution(board, moves):
    result = 0
    stack = [ ] # 크레인으로 꺼낸 인형을 담음.

    for col_idx in moves:
        try:
            col_idx -= 1 # 열 값이니 하나 빼줘야 index 계산 가능

            # 뽑을 인형이 위치한 좌표 찾기
            row_idx = [el for el in board if el[col_idx] != 0] # col_idx에 유효한 값이 있는 곳
            row_idx = board.index(row_idx[0]) # 맨 위에 있는 list의 index

            # 인형 뽑기 처리
            popped_el = board[row_idx][col_idx] # 젤 위에거, 해당 index의 인형
            board[row_idx][col_idx] = 0 # 빈칸 처리

            # 바구니에 담기 전 처리
            if len(stack) &gt; 0: # 빈 바구니가 아닐 경우

                if stack[-1] == popped_el: # 맨 위 인형이 같으면
                    stack.pop(-1) # 삭제
                    result += 2 # 터진 인형 : 새로 뽑은 거 + 맨 위에 있던 거

                else: # 맨 위 인형이 다른 종류 : stack에 쌓기
                    stack.append(popped_el)

            else: # 빈 바구니 : stack에 쌓기
                stack.append(popped_el)

        # moves에 있는 열 번호에 인형이 없는 경우
        except IndexError:
            continue

    return result</code></pre>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/9de9c845-c740-4931-9812-62e3baab22dd/image.png" alt="">
일단 테스트 코드는 통과했다. 채점버튼을 눌러봤다.</p>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/a6fa5081-f0a5-47c8-ab21-b4eac25a3a92/image.png" alt=""></p>
<p>이럴수가. 한번에 정답.
사실 코드를 몇 번 고쳤다.</p>
<h4 id="첫-번째-코드에서의-접근">첫 번째 코드에서의 접근</h4>
<p><code>board</code>를 조작하지 않고, <code>list_to_pop</code> 변수를 만들었다.
<code>col_idx</code>가 0이 아니며, 맨 위에 인형이 위치한 row의 list 변수였다.
그렇지만 이렇게 변수를 따로 빼게 될 경우, <code>board</code>에 접근이 어렵기 때문에
for문이 한번 더 돌때 <strong>인형을 뺐음에도 그 전과 같은 <code>board</code> 상태</strong>일 것이라 판단했다.</p>
<h4 id="indexerror-예외를-뺀-이유">IndexError 예외를 뺀 이유</h4>
<p>그리고 프로그래머스 예시 출력을 봤을 때, 1열에 <strong>2개의 인형</strong>이 있음에도 불구하고 3번을 빼길래 넣었다.</p>
<hr>
<h2 id="번외--chatgpt를-이용해-코드-줄이기">번외 : ChatGPT를 이용해 코드 줄이기</h2>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/7b5b9eca-cc35-4346-9449-71b331f8f132/image.png" alt=""></p>
<p>코드가 좀 긴 감이 있어 한번 줄여볼까 싶은 마음이 들었다.
위의 내 코드를 주며 축약을 부탁했고, 주석을 좀 정리해서 코드를 다시 써봤다.</p>
<pre><code class="language-python">def solution(board, moves):
    result = 0
    stack = [ ] # 크레인으로 꺼낸 인형을 담음.

    for col_idx in [col - 1 for col in moves]: # col_idx를 미리 빼 준다.

        # row_idx를 구하는 식은 한줄로 축약이 됐다.
        row_idx = next((row_idx for row_idx, row in enumerate(board) if row[col_idx] != 0), None)

        # 코드를 풀어보면
        #   for row_idx, row in enumerate(board):
        #       if row[col_idx] != 0:
        #           return row_idx
        # iterable 반환 -&gt; next(iterable, None) 으로 젤 처음값만 도출
        # -&gt; 가장 위쪽에 있는 인형을 뽑을 행(row_idx)이 됨.

        # 뺄 수 있는 인형이 없는 경우
        if row_idx is None: # 예외처리로 빼지 않고 좌표를 도출하기 전 row_idx로 검사.
            continue

        # 빼야할 인형의 좌표는 별도의 축약이 없었다.
        popped_el = board[row_idx][col_idx]
        board[row_idx][col_idx] = 0

        if stack and stack[-1] == popped_el: # stack의 None검사가 합쳐졌다.
            stack.pop()
            result += 2
        else:
            stack.append(popped_el)

    return result</code></pre>
<p>코드가 확실히 짧아졌다. 속도 차이도 있을까 싶어 채점버튼도 다시 한번 눌러봤다.</p>
<p>ChatGPT 코드 ▼
<img src="https://velog.velcdn.com/images/prussian-1to9/post/8f3730eb-bbf0-42ee-a514-a20c72e1a42b/image.png" alt=""></p>
<p>위에서 작성한 내 코드 ▽
<img src="https://velog.velcdn.com/images/prussian-1to9/post/03166a9b-76e9-48f2-82ae-b7a925a73943/image.png" alt=""></p>
<p>작은 양으로 추정되는 데이터가 들어왔을 땐 상관 없지만,
많은 양으로 추정되는 데이터가 들어왔을 땐 대략 <strong>0.2 ~ 0.45ms의 속도 차이</strong>를 보인다.
아마 무시할 수는 없는 수치라 예상된다.</p>
<p>그리고 주석을 지우면서 나름대로 아쉬웠던 점을 적어봤다.</p>
<blockquote>
<h3 id="chatgpt-코드를-참고했을-때-생각되어지는-개선점">chatGPT 코드를 참고했을 때 생각되어지는 개선점</h3>
</blockquote>
<ol>
<li><code>enumerate()</code> 활용<ul>
<li>어차피 <code>board.index()</code>로 <code>row_idx</code>를 빼올거<br>  enumerate를 돌리면 확실히 빠르고 직관적이였을 것 같다.</li>
</ul>
</li>
<li>내장 함수에 대한 이해 : <code>next()</code> <ul>
<li><code>next(iterable[, default])</code> 의 기본 값 설정 부분을 모르고 있었다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 9012 : 괄호 (파이썬)]]></title>
            <link>https://velog.io/@prussian-1to9/%EB%B0%B1%EC%A4%80-9012-%EA%B4%84%ED%98%B8-Python</link>
            <guid>https://velog.io/@prussian-1to9/%EB%B0%B1%EC%A4%80-9012-%EA%B4%84%ED%98%B8-Python</guid>
            <pubDate>Thu, 20 Apr 2023 18:11:14 GMT</pubDate>
            <description><![CDATA[<p>시작에 앞서 본인은 코딩 테스트는 거의 보지 않았기에, (해봐도 프로그래머스로 연습문제 몇개 정도였다.)
어떤 방식으로 답안을 제출해야 하는지 부터 확인해야 했다.</p>
<p>백준은 입력값을 split하는 방식으로 구분하고, 결과값을 print로 출력하는 방식인 것 같았다.</p>
<p>▶ <a href="https://www.acmicpc.net/problem/9012">문제 링크</a></p>
<p>먼저 첫 코드</p>
<pre><code class="language-python">str = input().split()
parenthesis_cnt = 0 # 괄호 쌍 개수

for s in str:
    if s == &#39;(&#39;:
        parenthesis_cnt += 1

    elif s == &#39;)&#39;:
        parenthesis_cnt -= 1

    else:
        parenthesis_cnt = -1
        break

print(&#39;NO&#39; if parenthesis_cnt &lt; 0 else &#39;YES&#39;)</code></pre>
<p>프로그래머스처럼 출력값을 따로 볼 수 없었기에 더 어렵게 느껴졌다.
예시 출력값들을 위의 로직에 맞춰 숫자를 계산해봤다.</p>
<blockquote>
</blockquote>
<p>(())())    NO : -1 [-1]에서 break
(((()())()    NO : 2
(()())((()))    YES : 0
((()()(()))(((())))()    NO : 1
()()()()(()()())()    YES : 0
(()((())()( NO : 3
(( NO : 2
)) NO : [0]에서 break
())(() NO : [2]에서 break</p>
<p>위에 적은 코드들은 닫는 괄호 <code>)</code> 만 필터링하지, 여는 괄호 <code>(</code>가 더 많은 경우는 필터링하지 않는다는 것을 깨달았다.</p>
<p>그래서 맨 마지막 구문을 아래와 같이 고쳐줬는데...</p>
<pre><code class="language-python">print(&#39;NO&#39; if parenthesis_cnt != 0 else &#39;YES&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/fe00782c-03c0-4a1b-bef4-cdac6290b36c/image.png" alt="">
처참히 실패. 그래서 아예 stack으로 구현해봤다.</p>
<pre><code class="language-python">str = input().split()
li = [ ]

for s in str:
    try:
        if s == &#39;(&#39;:
            li.append(s)
        elif s == &#39;)&#39;:
            li.pop(-1)
    except:
        print(&#39;NO&#39;)
        break

print(&#39;NO&#39; if len(li) &gt; 0 else &#39;YES&#39;)</code></pre>
<p>코드가 짧아진 만큼 빨간 <span style="color:red; font-weight:bold;">실패</span> 글자도 더 빠르게 나타났다.
답답해진 나머지 IDE에서 따로 돌려보니, 문제는 두가지였다.</p>
<ol>
<li><p><code>split()</code>의 기본값을 고려하지 않음
split의 기본값이 공백 <code></code>인데, 공백 없이 입력되는 문자열을 받으니 list에 들어갈 리가 없었다.</p>
</li>
<li><p><code>try</code> 문의 위치
어찌 저찌 split이 실행 됐다 해도, for문 안에서 <code>IndexError</code>를 잡고, NO를 출력한 뒤에
또 <code>if</code>문이 들어간 print 함수를 실행해 총 두 번 출력했다.</p>
</li>
</ol>
<p>그렇게 다시 고쳐진 코드.</p>
<pre><code class="language-python">li = [ ]    
str = input()[::1]

try:
    for s in str:
        if s == &#39;(&#39;:
            li.append(s)
        elif s == &#39;)&#39;:
            li.pop(-1)
    print(&#39;YES&#39; if len(li) == 0 else &#39;NO&#39;)
except IndexError:
    print(&#39;NO&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/1cee818b-172f-4250-b747-c996968d4e56/image.png" alt="">
예시로 나온 문자열들을 넣어보니 이번엔 꽤 성공적이였다. 그러나 이번에도 실패해, 최종병기 구글링을 꺼냈다.</p>
<p>.. 알고보니 예시 입력에 나온것들은 따로 따로 입력되는 것이 아니라 한번에 입력되는 것이였고
입력된 문자열 중 젤 처음의 정수, <code>T</code> 개의 문자열이 들어온다고 했다.
<strong>문제 자체를 잘못 이해한 것이였다!</strong></p>
<p>이해한 대로 코드를 고쳐줬다.</p>
<pre><code class="language-python">for i in range(0, int(input())):
    li = [ ]
    str = input()[::1]

    try:
        for s in str:
            if s == &#39;(&#39;:
                li.append(s)
            elif s == &#39;)&#39;:
                li.pop()
        print(&#39;YES&#39; if len(li) == 0 else &#39;NO&#39;)
    except IndexError:
        print(&#39;NO&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/eaabead4-45e8-47ba-a568-cefec8d1fbf5/image.png" alt=""></p>
<p>허무하게도 한번에 통과했다.
다사다난 첫 파이썬 백준 문제 풀이였다. 두번째 부터는 좀 잘 할수 있기를.</p>
<p>문제를 잘 읽는것이 시작의 반 이상이라는 걸 깨닫는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jupyter lab 테마 & 글꼴 변경]]></title>
            <link>https://velog.io/@prussian-1to9/Jupyter-lab-%ED%85%8C%EB%A7%88-%EA%B8%80%EA%BC%B4-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@prussian-1to9/Jupyter-lab-%ED%85%8C%EB%A7%88-%EA%B8%80%EA%BC%B4-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Fri, 31 Mar 2023 09:39:30 GMT</pubDate>
            <description><![CDATA[<p>해당 포스트는 <a href="https://velog.io/@prussian-1to9/Jupyter-notebook-%ED%85%8C%EB%A7%88-%EA%B8%80%EA%BC%B4-%EB%B3%80%EA%B2%BD">Jupyter notebook 테마 &amp; 글꼴 변경</a>의  
stylefx.py 수정, .ttf 파일 복붙 후를 전제로 작성했습니다.</p>
<hr>
<h3 id="0-jupyter-notebook-기동">0. Jupyter notebook 기동</h3>
<h3 id="1-1-settings---theme-에서-테마-설정">1-1. Settings - Theme 에서 테마 설정</h3>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/e67ad17e-86b1-4940-a23c-46a12ff85dc6/image.png" alt=""></p>
<p style="font-size:11pt">Light / Dark 밖에 없지만 일단 나는 Dark로 설정했다.</p>

<h3 id="1-2-상단-settings---advanced-settings-editor---우측-상단-체크-박스-클릭">1-2. 상단 Settings - Advanced Settings Editor - 우측 상단 체크 박스 클릭</h3>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/6624d4a6-49e4-4309-9ce2-c9daf6cd0383/image.png" alt=""></p>
<h3 id="2-좌측-system-defaults-참고해-커스텀-하고-싶은-변수명-복사해두기">2. 좌측 System Defaults 참고해 커스텀 하고 싶은 변수명 복사해두기</h3>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/00115152-b663-4b48-9f32-190d0f9bb7a7/image.png" alt=""></p>
<p>주석이 꽤 자세히 되어있으니 참고하면 좋다.<br><code>System defaults</code> 부분에 직접 입력은 불가능하니 변수명만 복사해 두자.</p>
<ul>
<li>codeCellConfig : 코드 셀 설정</li>
<li>markdownCellConfig : 마크다운 셀 설정</li>
<li>rawCellConfig : raw NBConvert 셀 설정  </li>
</ul>
<h3 id="3-우측-user-config에-들여쓰기-똑같이-지켜서-변경-값-넣어두기">3. 우측 User Config에 들여쓰기 똑같이 지켜서 변경 값 넣어두기</h3>
<p>내 경우 jupyter notebook때와 같이 D2Coding, Consolas 폰트를 넣어주었다.
<img src="https://velog.velcdn.com/images/prussian-1to9/post/490aa921-29b0-4d75-b9b8-ae290702150f/image.png" alt=""></p>
<p>당연하지만 구문 오류가가 있으면 실행되지 않는다.
혹시나 싶어 첨부하는 구문 오류 검사기 : <a href="https://ko.rakko.tools/tools/63/">https://ko.rakko.tools/tools/63/</a></p>
<h4 id="4-우상단-저장버튼을-눌러주면-적용-완료">4. 우상단 저장버튼을 눌러주면 적용 완료.</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jupyter notebook 테마 & 글꼴 변경]]></title>
            <link>https://velog.io/@prussian-1to9/Jupyter-notebook-%ED%85%8C%EB%A7%88-%EA%B8%80%EA%BC%B4-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@prussian-1to9/Jupyter-notebook-%ED%85%8C%EB%A7%88-%EA%B8%80%EA%BC%B4-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Fri, 31 Mar 2023 09:19:49 GMT</pubDate>
            <description><![CDATA[<p><span style="color:#FCB056; font-weight: bold;">(anaconda prompt에서 실행)</span></p>
<hr>
<h3 id="0-최초-실행-시-테마-패키지-설치-테마-리스트-확인">0. (최초 실행 시) 테마 패키지 설치, 테마 리스트 확인</h3>
<pre><code class="language-shell">pip install jupyterthemes
jt -l # 테마 리스트</code></pre>
<h3 id="1-원하는-테마-폰트-입력">1. 원하는 테마, 폰트 입력</h3>
<pre><code class="language-shell">jt -t ${theme_name} -f ${monospace_code_font} -nf $notebook_font} -tf ${textcell_font}</code></pre>
<p>폰트 사이즈까지 설정하고 싶은 경우 각각 <code>-fs</code>, <code>-nfs</code>, <code>-tfs</code> 로 설정해 주면 된다.<br><span style="font-size:11pt">(<code>jt -h</code> 명령어를 입력하면 더 자세한 설명을 볼 수 있다.)</span></p>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/642354e0-ee1f-42c4-b78c-02d68985405f/image.png" alt="">
이렇게 <strong>stylefx.py</strong>에서 설정이 안됐다는 경고창이 뜨게 되어 따로 설정해 주어야 한다.  </p>
<h3 id="2-해당-경로-stylefxpy에-폰트명-추가해주기">2. 해당 경로 stylefx.py에 폰트명 추가해주기</h3>
<p><code>stored_font_dicts</code> 라는 함수 내 <code>mono</code>, <code>sans</code>, <code>serif</code>에 사용할 폰트 명을 적어준다.<br><span style="font-size:11pt">(필자는 D2Coding 폰트와 Consolas 폰트를 추가했다.)</span>
<img src="https://velog.velcdn.com/images/prussian-1to9/post/d90f90b3-fba8-4297-8327-ba1f41b9ba21/image.png" alt=""></p>
<h3 id="3-다시-1번-명령문-을-실행해보자">3. 다시 <a href="#1-%EC%9B%90%ED%95%98%EB%8A%94-%ED%85%8C%EB%A7%88-%ED%8F%B0%ED%8A%B8-%EC%9E%85%EB%A0%A5">1번 명령문</a> 을 실행해보자.</h3>
<p>또 지정된 경로를 찾을 수 없다는 에러가 뜨는데, 해당 디렉토리에 ttf 파일만 옮겨주면 끝난다.
<img src="https://velog.velcdn.com/images/prussian-1to9/post/e064232b-b974-46c1-9ac8-29b06d55277f/image.png" alt=""></p>
<details>
  <summary>폰트 경로 확인하기</summary>
  <div markdonwn="1">
    Windows 키 - 검색 - 글꼴 or font - '글꼴 설정'
    - 원하는 폰트 선택 <br>
    할 경우 아래의 결과가 뜬다. <span style="font-size:11pt">(경로는 지웠음)</span>
    <img src="https://velog.velcdn.com/images/prussian-1to9/post/3b9bba17-02af-4886-a570-586d6ab08f93/image.png">
    이 경로로 들어가 디렉토리 째로 복사해준다. <br>
    <b>단, ttc 파일은 읽지 못하니 ttf 파일만 가져와야 한다.</b>
  </div>
</details>

<h3 id="4-1번의-명령문-재실행">4. <a href="#1-%EC%9B%90%ED%95%98%EB%8A%94-%ED%85%8C%EB%A7%88-%ED%8F%B0%ED%8A%B8-%EC%9E%85%EB%A0%A5">1번의 명령문</a> 재실행</h3>
<p><img src="https://velog.velcdn.com/images/prussian-1to9/post/a62177f9-91a0-4ad2-bc9a-9b0fe418e29c/image.png" alt="">
이렇게 깜빡였다 다시 입력창이 뜨면 성공적으로 적용 완료.</p>
]]></description>
        </item>
    </channel>
</rss>