<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>soonduck-dreams.log</title>
        <link>https://velog.io/</link>
        <description>soonduck dreams</description>
        <lastBuildDate>Sat, 26 Apr 2025 11:02:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>soonduck-dreams.log</title>
            <url>https://velog.velcdn.com/images/soonduck-dreams/profile/894f706e-eb23-4f9f-8821-d2277087a16b/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. soonduck-dreams.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/soonduck-dreams" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[PostgreSQL JSONB, Enum Array를 JPA Entity에 매핑하기: 트러블슈팅 기록]]></title>
            <link>https://velog.io/@soonduck-dreams/PostgreSQL-JSONB-Enum-Array%EB%A5%BC-JPA-Entity%EC%97%90-%EB%A7%A4%ED%95%91%ED%95%98%EA%B8%B0-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@soonduck-dreams/PostgreSQL-JSONB-Enum-Array%EB%A5%BC-JPA-Entity%EC%97%90-%EB%A7%A4%ED%95%91%ED%95%98%EA%B8%B0-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Sat, 26 Apr 2025 11:02:16 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-상황">문제 상황</h2>
<p>최근 진행 중인 프로젝트에서 비즈니스 뷰를 구축하게 되었다.
이 뷰에는 도감 데이터를 하나의 테이블로 집약해 관리할 필요가 있었는데, 이 과정에서 자연스럽게 RDBMS의 전통적인 &quot;정형 데이터&quot;만 다루는 방식을 넘어, JSON이나 Array 같은 타입도 함께 사용하게 되었다.</p>
<p>특히 PostgreSQL은 <code>jsonb</code> 타입과 배열(<code>array</code>) 타입을 강력하게 지원한다.<br>이번 작업에서는</p>
<ul>
<li><strong>jsonb</strong> 타입으로 객체 리스트를 저장하고,</li>
<li><strong>enum array</strong> 타입으로 여러 열거형 값을 저장하는 방식을 적용했다.<br>(※ enum 타입은 <code>habitat_type_enum</code>이라는 PostgreSQL enum을 미리 정의해두었다.)</li>
</ul>
<p>문제는, 이걸 JPA Entity에 어떻게 매핑할 것인가였다. jsonb나 enum array는 RDB스러운 정형화된 데이터 형식이 아니므로, 단순히 <code>@Column</code>만으로 매핑하긴 어렵다.</p>
<h2 id="해결-과정">해결 과정</h2>
<h3 id="1-기본-검색">1. 기본 검색</h3>
<p>처음 검색을 통해 다음 사실을 알게 되었다:</p>
<ul>
<li>Hibernate 6부터는 <code>@JdbcTypeCode(SqlTypes.JSON)</code>을 통해 <code>jsonb</code> 매핑이 표준 지원된다.</li>
<li>PostgreSQL의 enum array는 별도로 지원되지 않기 때문에, <strong>hypersistence-utils</strong>라는 오픈소스 라이브러리를 사용하는 방법이 소개되어 있었다.</li>
</ul>
<blockquote>
<p>참고 문서:<br><a href="https://vladmihalcea.com/map-postgresql-enum-array-jpa-entity-property-hibernate/">How to map a PostgreSQL Enum ARRAY to a JPA entity property using Hibernate</a></p>
</blockquote>
<p>이 문서에서는 다음과 같은 방식이 제시되었다.</p>
<pre><code class="language-java">@Type(
    value = EnumArrayType.class,
    parameters = @Parameter(
        name = AbstractArrayType.SQL_ARRAY_TYPE,
        value = &quot;sensor_state&quot;
    )
)
@Column(
    name = &quot;sensor_states&quot;,
    columnDefinition = &quot;sensor_state[]&quot;
)
private SensorState[] sensorStates;</code></pre>
<p>이를 참고해 <code>habitats</code> 필드에 적용해봤다.</p>
<pre><code class="language-java">@Type(
    value = EnumArrayType.class,
    parameters = @Parameter(
        name = AbstractArrayType.SQL_ARRAY_TYPE,
        value = &quot;habitat_type_enum&quot;
    )
)
@Column(
    name = &quot;habitats&quot;,
    columnDefinition = &quot;habitat_type_enum[]&quot;
)
private HabitatType[] habitats;</code></pre>
<h3 id="2-삽질과-문제">2. 삽질과 문제</h3>
<p>하지만 적용 과정에서 여러 에러가 발생했다.<br>(※ 정확한 원인은 알 수 없었지만, Hibernate 버전이나 hypersistence-utils와의 호환성 문제로 추정된다.)</p>
<p>결국, 문서 방식을 포기하고 직접 실험을 시작했다.<br>그 결과, 굳이 <code>@Type</code>을 쓰지 않고, 단순히 <code>@JdbcTypeCode(SqlTypes.ARRAY)</code>만 붙이면 정상 동작한다는 것을 발견했다!</p>
<pre><code class="language-java">@JdbcTypeCode(SqlTypes.ARRAY)
@Column(name = &quot;habitats&quot;)
private List&lt;HabitatType&gt; habitats;</code></pre>
<p><strong>정리:</strong>  </p>
<ul>
<li><code>jsonb</code> → <code>@JdbcTypeCode(SqlTypes.JSON)</code></li>
<li><code>enum[]</code> → <code>@JdbcTypeCode(SqlTypes.ARRAY)</code></li>
</ul>
<p>두 경우 모두 <code>@JdbcTypeCode</code>로 심플하게 해결할 수 있었다. Hibernate 5 버전까지는 hypersistence-utils 같은 오픈소스 라이브러리를 쓸 수밖에 없던 상황으로 보이나, 6부터는 이렇게 Hibernate가 표준으로 지원하는 <code>@JdbcTypeCode</code>를 적극 활용하는 게 좋겠다.</p>
<h3 id="3-json-key-매칭-문제">3. JSON Key 매칭 문제</h3>
<p><code>jsonb</code> 타입 매핑을 할 때 또 하나 고려해야 할 점이 있었다.<br>바로 JSON Key와 Java 필드명 매칭 문제였다.</p>
<p>PostgreSQL에 저장된 JSON 구조는 Snake Case(<code>s3_url</code>, <code>original_url</code> 등)였는데,<br>Java에서는 Camel Case(<code>s3Url</code>, <code>originalUrl</code>)를 쓰고 싶었다.</p>
<p>처음에는 그냥 맞춰주기 위해 Java 필드명도 Snake Case로 작성했다.</p>
<pre><code class="language-java">private String s3_url;
private String original_url;</code></pre>
<p>하지만 가독성 문제와 코딩 스타일 불일치가 거슬렸다.<br>그래서 다른 방법을 찾다가 <strong><code>@JsonProperty</code> 어노테이션</strong>을 발견했다.</p>
<pre><code class="language-java">@Data
public static class Image {
    @JsonProperty(&quot;s3_url&quot;)
    private String s3Url;

    @JsonProperty(&quot;original_url&quot;)
    private String originalUrl;

    @JsonProperty(&quot;is_thumb&quot;)
    private Boolean isThumb;

    @JsonProperty(&quot;order_index&quot;)
    private Integer orderIndex;
}</code></pre>
<p>이렇게 설정해주니,</p>
<ul>
<li>데이터베이스에서는 그대로 Snake Case를 유지하고</li>
<li>Java 코드에서는 자연스럽게 Camel Case를 사용할 수 있게 되었다.</li>
</ul>
<p>그리고, 매핑 오류 없이 정상적으로 동작했다.</p>
<h2 id="최종-결과">최종 결과</h2>
<p>최종적으로 Entity 클래스는 다음과 같은 구조가 되었다.</p>
<pre><code class="language-java">@Entity
@Immutable
@Table(name = &quot;bird_profile_mv&quot;)
public class BirdProfile {

    @Id
    private Long id;

    (... 중략 ...)

    @JdbcTypeCode(SqlTypes.ARRAY)
    @Column(name = &quot;habitats&quot;)
    private List&lt;HabitatType&gt; habitats;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = &quot;seasons_with_rarity&quot;)
    private List&lt;SeasonWithRarity&gt; seasonsWithRarity;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = &quot;images&quot;)
    private List&lt;Image&gt; images;

    @Data
    public static class SeasonWithRarity {

        @JsonProperty(&quot;rarity&quot;)
        private String rarity;

        @JsonProperty(&quot;season&quot;)
        private String season;

        @JsonProperty(&quot;priority&quot;)
        private Integer priority;
    }

    @Data
    public static class Image {

        @JsonProperty(&quot;s3_url&quot;)
        private String s3Url;

        @JsonProperty(&quot;original_url&quot;)
        private String originalUrl;

        @JsonProperty(&quot;is_thumb&quot;)
        private Boolean isThumb;

        @JsonProperty(&quot;order_index&quot;)
        private Integer orderIndex;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[PostgreSQL Trigger로 본 테이블 updated_at 갱신 자동화하기]]></title>
            <link>https://velog.io/@soonduck-dreams/PostgreSQL-Trigger%EB%A1%9C-%EB%B3%B8-%ED%85%8C%EC%9D%B4%EB%B8%94-updatedat-%EA%B0%B1%EC%8B%A0-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@soonduck-dreams/PostgreSQL-Trigger%EB%A1%9C-%EB%B3%B8-%ED%85%8C%EC%9D%B4%EB%B8%94-updatedat-%EA%B0%B1%EC%8B%A0-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 23 Apr 2025 13:22:10 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-인식">문제 인식</h1>
<p>현재 진행중인 학회 프로젝트에서, 앱 개발 쪽 요구사항이 새로 들어왔다.</p>
<blockquote>
<p>앱에서는 도감 데이터를 일괄적으로 서버로부터 제공받아 캐싱해 사용하고, 주기적으로 해당 데이터에서 변경된 부분만 확인해 받아오려고 합니다.</p>
</blockquote>
<p>그럼 서버 쪽에서는</p>
<ol>
<li>도감 데이터를 전체 제공하는 API와, </li>
<li>특정 시각 이후 갱신된 데이터만 확인해 제공하는 API
를 제공할 필요가 있다.</li>
</ol>
<p>이때 문제는 2번, 특정 시각 이후 갱신된 데이터를 확인하는 것이다.
언뜻 보면 &quot;updated_at 필드만 보면 되지 않을까?&quot; 라고 생각할 수 있지만, 문제는 관련 데이터가 여러 테이블에 걸쳐 있다는 것이다.</p>
<p>구체적으로는 본 테이블(bird)에 bird_habitat, bird_image, bird_residency라는 서브 테이블들이 연결된 상황.</p>
<h1 id="해결-방안-탐색">해결 방안 탐색</h1>
<h2 id="❓-처음-떠올린-방안-서브-테이블들-join해서-확인해야-하나">❓ 처음 떠올린 방안: 서브 테이블들 JOIN해서 확인해야 하나?</h2>
<p>이 방법은 일단 어렵다고 판단했다. 이유는 첫번째, 각 테이블마다 created_at, updated_at, deleted_at 존재 여부가 서로 다르고, 이 문제를 해결하기 위해서 일괄적으로 그것들을 테이블마다 붙이는 것은 배보다 배꼽이 더 큰 격이라고 생각했다. 이거 하나 때문에 모든 관련 테이블을 Soft Delete로 처리하라니! 현실적으로 비효율적이다.
두번째, 성능적으로도 비효율적이다. 서브 테이블들은 모두 본 테이블에 대해 1:N으로 연결되어 있다. 이들 각각 JOIN시켜서 하나하나 확인하고 있자니, 그것도 모든 도감 데이터에 대해서!
아무래도 다른 방법이 필요했다.</p>
<h2 id="💡-2번째-방안-본-테이블의-updated_at-하나가-관련-테이블들의-생성수정삭제를-전부-반영할-수-있다면">💡 2번째 방안: 본 테이블의 updated_at 하나가 관련 테이블들의 생성/수정/삭제를 전부 반영할 수 있다면?</h2>
<p>이거 괜찮았다. 예를 들어 bird_habitat 테이블에서 관련 데이터가 생성/수정/삭제될 경우마다 bird.updated_at을 갱신하는 것이다. 그러면 쿼리도 간단해지고 효율적이다.</p>
<blockquote>
<p>🤔 하지만 어떻게 하지?</p>
</blockquote>
<h2 id="✅-이럴-때-쓰는-게-trigger">✅ 이럴 때 쓰는 게 Trigger</h2>
<p>ChatGPT와의 대화를 통해 Trigger 개념에 대해 학습했다.
대화 전체는 <a href="https://chatgpt.com/share/6808e480-5318-800c-ae35-bb871e507872">여기</a>서 볼 수 있다.</p>
<p>이 대화를 통해 배운 내용들을 요약하면:</p>
<blockquote>
<ul>
<li>트리거는 특정 DML 이벤트(INSERT, UPDATE, DELETE)에 반응해 자동 실행된다.  </li>
</ul>
</blockquote>
<ul>
<li>PostgreSQL 트리거는 함수 형태로 작성되며 <code>plpgsql</code> 언어를 사용한다.  </li>
<li><code>BEFORE</code> 트리거는 DML 실행 전, <code>AFTER</code> 트리거는 실행 직후 (단, 커밋 전) 실행된다.  </li>
<li><code>NEW</code> 객체는 INSERT와 UPDATE 시 사용 가능하며, 변경 후 값을 담고 있다.  </li>
<li><code>OLD</code> 객체는 UPDATE와 DELETE 시 사용 가능하며, 변경 전 값을 담고 있다.  </li>
<li>트리거는 같은 테이블 내 필드뿐 아니라 다른 테이블도 명시적 쿼리로 수정할 수 있다.  </li>
<li>관계된 테이블의 변경으로 본 테이블의 <code>updated_at</code>을 갱신하려면 트리거 함수 내 UPDATE 쿼리를 써야 한다.  </li>
<li><code>INSERT OR UPDATE</code>는 <code>AFTER</code> 트리거로 처리하는 것이 일반적이다.  </li>
<li><code>DELETE</code>는 <code>OLD</code>를 사용해야 하므로 별도의 트리거로 관리하는 것이 명확하다.  </li>
<li>트리거는 트랜잭션 내부에서 실행되며, 커밋 전에 효과가 반영된다.</li>
</ul>
<h1 id="문제-해결">문제 해결</h1>
<p><code>bird_habitat</code>, <code>bird_image</code>, <code>bird_residency</code> 각각에 대해 다음 트리거들을 설정했다:</p>
<ul>
<li><code>AFTER INSERT OR UPDATE</code>: <code>NEW.bird_id</code>로 해당 <code>bird</code>의 <code>updated_at</code> 갱신</li>
<li><code>AFTER DELETE</code>: <code>OLD.bird_id</code>로 갱신</li>
</ul>
<p>이제 도감 데이터 갱신 API는 단순히 bird.updated_at이 지난번 fetch한 시각보다 큰지만 확인하면 된다.</p>
<p>결과적으로, Trigger를 통해 관련 테이블의 변경을 본 테이블의 updated_at 하나로 일괄 추적할 수 있게 되었고, 클라이언트는 단순한 조건 비교만으로 변경된 데이터를 손쉽게 가져올 수 있게 되었다. 복잡한 연관 구조를 단순하게 관리하는 실용적인 해결책이었다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀원이 Git 처음 쓴대서 고민했던 Git 전략 설계기(記)]]></title>
            <link>https://velog.io/@soonduck-dreams/%ED%8C%80%EC%9B%90%EC%9D%B4-Git-%EC%B2%98%EC%9D%8C-%EC%93%B4%EB%8C%80%EC%84%9C-%EA%B3%A0%EB%AF%BC%ED%96%88%EB%8D%98-Git-%EC%A0%84%EB%9E%B5-%EC%84%A4%EA%B3%84%EA%B8%B0%E8%A8%98</link>
            <guid>https://velog.io/@soonduck-dreams/%ED%8C%80%EC%9B%90%EC%9D%B4-Git-%EC%B2%98%EC%9D%8C-%EC%93%B4%EB%8C%80%EC%84%9C-%EA%B3%A0%EB%AF%BC%ED%96%88%EB%8D%98-Git-%EC%A0%84%EB%9E%B5-%EC%84%A4%EA%B3%84%EA%B8%B0%E8%A8%98</guid>
            <pubDate>Mon, 31 Mar 2025 06:51:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>최근 학회 프로젝트에서 백엔드 2명이서 협업하면서 git 전략에 대해 깊이 고민할 일이 생겼다. 이 글은 그 과정을 기록한 것이다. merge, rebase, squash에 대한 개념 정리와 함꼐, 내가 어떤 기준으로 전략을 선택했는지도 담겨 있다.</p>
</blockquote>
<h3 id="1-어떤-문제인가--문제-정의">1. 어떤 문제인가? :: 문제 정의</h3>
<p><strong>1-1. 마주한 문제상황을 명확히 정의하시오.</strong></p>
<hr>
<p>이번 프로젝트에서 두 명이서 백엔드 개발을 하게 되었다. 팀원 분이 git을 이용해 협업하시는 것은 이번이 처음이다.</p>
<ol>
<li>효과적인 협업</li>
<li>깔끔하고 의미 있게 git history 관리</li>
</ol>
<p>이 2가지를 충족하고 싶고, 이를 위해 git 사용에 대한 전반적인 고민이 필요하다.</p>
<p><strong>1-2. 이 문제를 해결하는 데 있어 내가 현재까지 알고 있는 것을 정리하시오.</strong> </p>
<hr>
<p>git으로 전에 협업한 적이 있고 기본적인 사용법을 알고 있다.</p>
<p>rebase나 squash 같은 거 잘 쓰면 git history 깔끔하게 할 수 있다고 들었는데 정확히 모름 </p>
<p><strong>1-3. 문제를 해결하기 위해 새로 배워야 할 지식을 정하고, 그 이유를 서술하시오.</strong></p>
<hr>
<p>git merge의 3가지 전략 개념 (merge vs. squash merge vs. rebase)</p>
<p>git flow vs. github flow 뭐가 지금 우리에게 나은 선택인가?</p>
<p>얘네들을 알고 왜 언제 쓰는 건지 이해해야 지금 나의 고민을 해결할 실마리를 잡을 수 있다.</p>
<p><strong>1-4. 학습 및 탐구 실행 계획을 구체적으로 작성하시오.</strong></p>
<hr>
<p>새로 배우기로 한 지식을 유튜브, 블로그, ChatGPT와의 문답 등을 통해 배운다.</p>
<p>연습용 git repo 파서 git merge 실습한다.</p>
<p>다른 사람들의 git 사용 전략에 대한 의견을 유튜브 댓글을 통해 탐색한다.</p>
<h3 id="2-어떻게-해결했는가--문제-해결-과정">2. 어떻게 해결했는가? :: 문제 해결 과정</h3>
<p><strong>2-1. 탐구 실행 계획을 통해 새롭게 알게 된 내용을 정리하시오.</strong></p>
<hr>
<h2 id="merge-3가지-전략의-차이점을-이해하게-되었다">merge 3가지 전략의 차이점을 이해하게 되었다</h2>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/773500bf-8e22-4b4b-b6dc-b228b7b17149/image.png" alt=""></p>
<p>내가 만든 feature branch를 main 브랜치에 PR merge하고 싶다고 상상하자.</p>
<p>이때 3가지 선택지가 있다.</p>
<h3 id="그냥-merge">그냥 merge</h3>
<p>내가 알고 있었던 일반 merge는 맨 오른쪽 그림처럼 내 feature branch를 그대로 놔두고 main 브랜치에 내 feature branch 내용을 합친다. 제일 직관적이고, 브랜치 흐름을 파악할 수 있다는 장점이 있으나, </p>
<p>main 브랜치에서 갈라졌다 나오는 수많은 브랜치들의 흔적이 얽히고설켜 git history 가독성 및 아름다움이 떨어진다.</p>
<h3 id="squash-merge">squash merge</h3>
<p>내 feature branch의 여러 커밋에 걸친 변경사항들을 하나의 커밋으로 뭉뚱그려서(squash해서) main 브랜치에 갖다 붙인다. 말하자면, 갈라져 나온 나뭇가지를 똑 떼서, 공 모양으로 찌끄러뜨려서 원래 큰 줄기 끝에 갖다 붙이는 것이다.</p>
<h3 id="rebase">rebase</h3>
<p>일단 이름의 의미를 곱씹을 필요가 있다. rebase라는 건 “base를 새로 설정한다는 것”. base란 뭐냐? 이 feature branch가 갈라져 나온 “근본 지점” = base. 우리가 이 rebase를 사용하고 싶을 땐 이런 때다. “내가 지금까지 작업한 feature branch의 내역들이, 마치 원래 main 브랜치에서 쭉 작업하고 있었던 것처럼 보이게 옮겨 놓고 싶다.” 말하자면 원래 줄기에서 갈라져 나온 나뭇가지를 똑 떼서 다시 원래 줄기 끝에 갖다 붙여서 깔끔하게 만들고 싶다는 것. 그게 rebase다.</p>
<h2 id="rebase를-사용하는-2가지-케이스를-알게-되었다">rebase를 사용하는 2가지 케이스를 알게 되었다</h2>
<p>rebase란 개념을 처음 배우면 괜히 헷갈리기만 하고 이런 걸 언제 사용하는지 감이 안 잡힌다. 그래서 전에 한 번 배운 적이 있지만 헷갈리고 까먹고 흐지부지되었던 기억이 있다.</p>
<p>이번에 학습하면서 rebase를 사용할 수 있는 2가지 케이스를 알게 되었다.</p>
<ol>
<li>PR merge하고 싶을 때</li>
<li>feature branch를 최신화하고 싶을 때</li>
</ol>
<p>사실 이중에 PR merge할 때보다 feature branch를 최신화할 때 rebase를 사용하는 게 더 좋겠단 생각이 들었다. 이 경우에는 말하자면 그런 상황이다. feature branch에서 기능 개발 열심히 하고 있는데, 이 와중에 main 브랜치에는 다른 사람들이 만든 새 기능들이 속속 업데이트되면서, 내 feature branch의 근본은 main 브랜치의 끄트머리가 아니라 중간 지점이 되어버렸다. 그러니까 내가 작업하고 있는 feature branch는 현재 최신 main 브랜치의 새로 추가된 기능들을 반영하고 있지 않다.</p>
<p>이런 경우에 나는 중간중간에 main 브랜치를 내 feature 브랜치에 일반적인 merge를 하는 방식을 쓴 적이 있는데 이게 또 git history를 더럽게 만들어서 보기 영 좋지 않았다. 말하자면 큰 줄기랑 갈라진 나뭇가지가 서로 엉겨붙어 있는 것 같이.</p>
<p>이럴 때 rebase를 하면, 일종의 속임수를 쓸 수 있다. 마치 방금, main 브랜치의 끄트머리에서 갓 갈라져 나온 파릇파릇한 feature branch인 척을 할 수 있다. 내 작업 중인 feature branch를 똑 뗴어서, main 브랜치 끄트머리에 갖다 붙이는 것이다. 하지만 이 경우는 main 브랜치를 갱신하기 위한 게 아니고 내 feature branch에 main 브랜치 최신사항을 반영해 conflict를 미리 해결하면서도, 예쁘게 해결하기 위한 것이라 할 수 있다.</p>
<h2 id="rebase로-내-feature-branch를-최신화하고-squash로-pr-merge하는-방법이-git-history를-깔끔하고-의미-있게-유지할-수-있는-방법이라는-걸-발견했다">rebase로 내 feature branch를 최신화하고, squash로 PR merge하는 방법이 git history를 깔끔하고 의미 있게 유지할 수 있는 방법이라는 걸 발견했다</h2>
<p><a href="https://www.youtube.com/watch?v=0chZFIZLR_0">이 영상</a> 댓글을 보다가 맘에 쏙 드는 방법을 발견했다.</p>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/ba019222-55e4-4851-b334-e87782bde796/image.png" alt=""></p>
<p>기능 개발 중엔 rebase로 main 브랜치 최신 사항 반영하고, 개발 완료 시에는 squash merge하면,</p>
<p>중요한 브랜치를 깔끔하고 의미 있게 관리할 수 있다.</p>
<ol>
<li>깔끔하다는 건, 심미적으로 보기 좋다는 것이다.</li>
<li>의미 있다는 건, 커밋 내용들이 의미 있다는 것이다.</li>
</ol>
<p>squash merge는 내가 기능 개발하면서 만든 자잘한 commit들을 하나로 합치기 때문에, commit message에 핵심을 요약해 정리할 수 있다. 만약 PR merge할 때 rebase를 쓴다면, 자잘한 commit들이 중요한 develop이나 main 브랜치에 그대로 흘러들어오기 때문에, 나중에 commit history를 봤을 때 큰 개발 흐름을 보기가 어렵다. (오타 수정, 버그 수정, 사소한 변경 등으로 범벅되어 있는 히스토리..)</p>
<p>이 방법이 맘에 들었으니 이번에 도입을 해보고 싶다.</p>
<h2 id="git-flow가-무조건-더-좋은-방법은-아니라는-것을-인식했다">git flow가 무조건 더 좋은 방법은 아니라는 것을 인식했다</h2>
<p>git flow가 보기엔 더 멋져 보이고 고수 냄새가 나는 건 사실이다.</p>
<p>하지만 화려하고 큰 게 무조건 더 좋은 것은 아니다. 우리 백엔드 팀은 2명이라는 소규모이고, 팀원 분이 git 사용이 처음이고, 쉽고 효과적인 협업이 필요하다. 그렇기 때문에 github flow라는 단순하지만 효과적인 방법을 사용하기로 결정했다.</p>
<p><strong>2-2. 문제 해결 과정에서 추가로 얻게 된 관련 지식을 정리하시오.</strong></p>
<hr>
<p>IntelliJ에서 사용할 수 있는 유용한 git 관련 플러그인 중 “Git Dev Tools”라는 걸 알게 되었다. 이 플러그인을 설치하면, 코드의 각 줄마다 누가 언제 어떤 커밋에서 변경한 내용인지를 바로바로 표시해준다. 내 탓 네 탓을 쉽고 간편하게 할 수 있다.</p>
<p><strong>2-3. 문제 해결 과정에서 가장 중요했던 정보를 출처와 함께 정리하시오.</strong></p>
<hr>
<p>merge 3가지 전략 학습한 영상: <a href="https://www.youtube.com/watch?v=0chZFIZLR_0">https://www.youtube.com/watch?v=0chZFIZLR_0</a></p>
<p>이 영상에 있던 댓글:</p>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/1b7539a1-e032-4430-acee-c9c6b7809e33/image.png" alt=""></p>
<p>git flow vs. github flow: <a href="https://velog.io/@gmlstjq123/Git-Flow-VS-Github-Flow">https://velog.io/@gmlstjq123/Git-Flow-VS-Github-Flow</a></p>
<h3 id="3-마무리">3. 마무리</h3>
<p><strong>3-1. 한 주간 WIL을 통해 성장한 점, 느낀 점을 자유롭게 적어주세요.</strong></p>
<hr>
<p>한동안 안 해서 까먹고 있던 git을 이번에 다시 익숙하게 해서 몸이 좀 풀렸다.</p>
<p>거기에 그동안 미뤘던 rebase, squash 개념 복습을 다시 했고, 얘네들을 어떻게 쓸지, git 전략 어떻게 할지에 대한 고민도 이번에 해결할 수 있었다.</p>
<p><strong>3-2. 문제 상황과 관련된 후속 학습 계획이 있다면 자유롭게 적어주세요.</strong></p>
<p>내가 도입하고자 하는 이러한 방법을 팀원과 함께 컨벤션으로 정하고 그걸 강제적으로 모두가 지킬 수 있게 하는 방법에 대해 탐색하고 싶다. (GitHub ruleset 이용해서 컨벤션에 맞지 않는 행위를 원천 방지할 수 있나? 문서화는 어떻게 하는 게 좋을까?)</p>
<p>아직 Git Flow는 내 상황에선 오버스펙이라 느껴졌지만, 추후 팀 규모가 커지거나 릴리즈 관리가 필요해질 때 다시 도입을 검토할 예정이다.</p>
<blockquote>
<p>혹시 더 나은 전략이나 경험이 있다면 댓글로 공유해 주세요! 읽어주셔서 감사합니다</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[ImportError: DLL load failed while importing _imaging: 지정된 모듈을 찾을 수 없습니다]]></title>
            <link>https://velog.io/@soonduck-dreams/ImportError-DLL-load-failed-while-importing-imaging-%EC%A7%80%EC%A0%95%EB%90%9C-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@soonduck-dreams/ImportError-DLL-load-failed-while-importing-imaging-%EC%A7%80%EC%A0%95%EB%90%9C-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Wed, 03 Jul 2024 12:22:42 GMT</pubDate>
            <description><![CDATA[<h1 id="원인">원인</h1>
<p>pillow 패키지의 버전 문제였습니다.</p>
<h1 id="해결">해결</h1>
<p>사용하고 있는 conda 가상환경에서 다음 명령어를 실행하여 버전을 바꿔주었더니 문제가 해결되었습니다.</p>
<pre><code>conda install --channel conda-forge pillow=10.1.0</code></pre><p><a href="https://github.com/python-pillow/Pillow/issues/7494">이곳</a>을 참고하여 해결했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[VSCode에서 현재 열린 파일 내용을 boilerplate로 자동으로 덮어씌워보자]]></title>
            <link>https://velog.io/@soonduck-dreams/VSCode%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%EC%97%B4%EB%A6%B0-%ED%8C%8C%EC%9D%BC-%EB%82%B4%EC%9A%A9%EC%9D%84-boilerplate%EB%A1%9C-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%8D%AE%EC%96%B4%EC%94%8C%EC%9B%8C%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@soonduck-dreams/VSCode%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%EC%97%B4%EB%A6%B0-%ED%8C%8C%EC%9D%BC-%EB%82%B4%EC%9A%A9%EC%9D%84-boilerplate%EB%A1%9C-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%8D%AE%EC%96%B4%EC%94%8C%EC%9B%8C%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 29 Feb 2024 10:04:29 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p>평소 백준 문제를 풀 때마다 VSCode로 <em>hellocpp.cpp</em>를 열고 이전에 풀었던 문제의 소스코드를 지우고 보일러플레이트 코드를 적는다. 항상 똑같은 보일러플레이트를 쓰는데, 매번 일일이 지웠다 쓰는 게 여간 귀찮은 일이 아니다.</p>
<h2 id="문제-정의">문제 정의</h2>
<p>VSCode에서 현재 열린 파일 내용을 boilerplate로 수동으로 수정하는 것이 번거롭다.</p>
<h2 id="문제-해결">문제 해결</h2>
<p><strong>shell script와 tasks.json을 이용해 자동화한다.</strong></p>
<ol>
<li>초기 상태 확인</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/2b6e1664-ca8e-4ec2-9b33-98c749676950/image.png" alt="">
cpp 소스 파일, exe 실행 파일, 그리고 <code>.vscode</code> 폴더에 <code>tasks.json</code>이 있는 상태 (<code>.vscode</code> 폴더 내 <code>tasks.json</code>이 없다면 만들어줄 것)</p>
<ol start="2">
<li>shell script 작성</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/a9e6ce47-399e-417f-80ec-08a136db4e67/image.png" alt="">
<code>scripts</code> 폴더 내에 <code>boilerplate.sh</code> 파일을 만들고 다음 내용을 붙여넣는다.</p>
<pre><code class="language-bash">#!/bin/bash

# Check if the file path is provided as an argument
if [ $# -eq 0 ]; then
    echo &quot;Please provide the file path as an argument.&quot;
    read -n 1
    exit 1
fi

# Get the file path from the argument
file_path=$1

# Check if the file exists
if [ ! -f &quot;$file_path&quot; ]; then
    echo &quot;File does not exist.&quot;
    exit 1
fi

# Reset the content of the file with the boilerplate
cat &lt;&lt; EOF &gt; &quot;$file_path&quot;
#define FASTIO ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
#include &lt;iostream&gt;
#include &lt;vector&gt;

using namespace std;

int main() {
  FASTIO;

  return 0;
}
EOF</code></pre>
<ol start="3">
<li>tasks.json에 task 추가
다음 내용을 기존 <code>tasks.json</code>에 붙여넣는다.<pre><code class="language-json">{
&quot;label&quot;: &quot;Overwrite with Baekjoon C++ Boilerplate&quot;,
&quot;type&quot;: &quot;shell&quot;,
&quot;command&quot;: &quot;./scripts/boilerplate.sh ${file}&quot;,
&quot;group&quot;: &quot;test&quot;,
&quot;presentation&quot;: {
 &quot;reveal&quot;: &quot;always&quot;,
 &quot;panel&quot;: &quot;new&quot;
}
}</code></pre>
</li>
</ol>
<p>붙여넣은 후의 <code>tasks.json</code>의 모습은 다음과 같을 것이다.</p>
<pre><code class="language-json">{
  &quot;version&quot;: &quot;2.0.0&quot;,
  &quot;tasks&quot;: [
    (... 기존 tasks ...)
    {
      &quot;label&quot;: &quot;Overwrite with Baekjoon C++ Boilerplate&quot;,
      &quot;type&quot;: &quot;shell&quot;,
      &quot;command&quot;: &quot;./scripts/boilerplate.sh ${file}&quot;,
      &quot;group&quot;: &quot;test&quot;,
      &quot;presentation&quot;: {
        &quot;reveal&quot;: &quot;always&quot;,
        &quot;panel&quot;: &quot;new&quot;
      }
    }
  ]
}</code></pre>
<p>이전에 만든 <code>boilerplate.sh</code> 파일을 현재 열린 파일 경로를 매개변수로 넘겨주면서 실행한다.
이걸로 자동화 작업이 끝났다!</p>
<ol start="4">
<li>테스트</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/fcb0b6c5-841b-415c-9de4-43a4c7f09cc8/image.png" alt=""></p>
<p><code>hellocpp.cpp</code>를 열고 <code>Ctrl + Shift + P</code>를 눌러 <code>Tasks: Run Task</code> 커맨드를 선택한다.</p>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/b18f6aa8-8354-42a8-9226-796cead10344/image.png" alt=""></p>
<p>방금 우리가 만든 task의 이름인 <code>Overwrite with Baekjoon C++ Boilerplate</code>을 선택한다. (task 이름이 마음에 안 들면 <code>tasks.json</code>으로 가서 <code>label</code> 값을 자유롭게 바꾸자)</p>
<blockquote>
<p><strong>.sh 파일을 실행하는 기본 프로그램을 선택하라는 창이 뜰 경우</strong>
Windows의 경우 task를 처음 실행할 때 .sh 파일을 실행할 기본 프로그램을 선택하는 창이 뜰 수 있다. 나는 이미 설치되어 있던 Git Bash를 선택해 주었다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/8f716b90-8f0d-462c-8652-89a2079107d9/image.gif" alt=""></p>
<p>잘 된다!</p>
<h2 id="후기">후기</h2>
<p>문제를 해결하는 과정이 다음과 같은 것들을 학습하는 계기가 되었다.</p>
<ol>
<li>shell script: bash, shebang, here document, echo, cat, ...</li>
<li>VSCode의 <code>task.json</code></li>
</ol>
<p>이제 편하게 보일러플레이트로 리셋할 수 있다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Prettier & ESLint 개념, 설치, 사용 방법]]></title>
            <link>https://velog.io/@soonduck-dreams/Prettier-ESLint</link>
            <guid>https://velog.io/@soonduck-dreams/Prettier-ESLint</guid>
            <pubDate>Wed, 03 Jan 2024 07:53:05 GMT</pubDate>
            <description><![CDATA[<h1 id="🔭-overview">🔭 Overview</h1>
<h2 id="prettier는-format을-알아서-해준다">Prettier는 Format을 알아서 해준다</h2>
<p>코드의 시각적인 스타일을 설정하는 것을 <strong>Format</strong>이라고 한다.</p>
<pre><code class="language-jsx">let a = true;
if (a) {
  console.log(&#39;Hola!&#39;);
}</code></pre>
<pre><code class="language-jsx">let a=true;
if(a)
{
      console.log(&quot;Hola!&quot;)
}</code></pre>
<p>위 두 코드는 내용은 같지만 스타일이 서로 다르다. 사람마다 선호하는 스타일이 다를 수 있다. 그러나 팀으로 일할 때는 하나의 통일된 스타일을 정해야 한다. 여기서 논쟁이 발생한다. 이게 더 좋다 저게 더 좋다…… 이런 format, code style에 관한 논쟁에 시간을 낭비하고 싶지 않다면 Prettier를 사용하면 된다.</p>
<p><strong>Prettier는 모든 코드가 일관된 스타일을 유지할 수 있도록 알아서 format해주는 도구이다.</strong></p>
<p>Prettier는 JavaScript뿐 아니라 JSON, HTML, CSS, TypeScript 등의 formatting도 지원한다.</p>
<h2 id="eslint는-잠재적인-버그를-잡아주고-코드의-질-향상을-도와준다">ESLint는 잠재적인 버그를 잡아주고 코드의 질 향상을 도와준다</h2>
<pre><code class="language-jsx">let a = 1;
let b = 2;

console.log(a, c);</code></pre>
<p>위 코드에는 문제가 있다.</p>
<ul>
<li>b라는 변수를 정의했지만 사용하지 않았다. 당장은 문제가 안 되지만 나중에 버그의 원인이 될 수도 있다.</li>
<li>c라는 정의되지 않은 변수를 사용하려고 한다. 런타임 에러를 일으킨다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/bbd2bb15-2a45-4222-98cf-4f0d0ae347f2/image.png" alt=""></p>
<p><strong>ESLint는 JavaScript 코드에서 이런 버그, 잠재적 버그를 사전에 파악해서 에러로 띄워주는 도구이다.</strong> ESLint 같은 도구들을 Linter라고 하고, ESLint는 요즘 잘 나가는 JavaScript Linter이다.</p>
<h1 id="🛠️-설치하고-사용해보자">🛠️ 설치하고 사용해보자</h1>
<h2 id="prettier">Prettier</h2>
<ol>
<li><strong>VSCode Extensions에서 Prettier를 검색해서 설치한다.</strong></li>
</ol>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/fb130dc8-2935-4ffb-ab6f-a341c4df17b7/image.png" alt=""></p>
<ol start="2">
<li><strong>npm으로 prettier를 설치한다.</strong></li>
</ol>
<pre><code class="language-bash">$ npm init -y
$ npm install prettier -D --save-exact</code></pre>
<blockquote>
<p>💡 <code>-D</code> == <code>--save-dev</code>
해당 패키지를 devDependency로 설치하겠다는 뜻이다.</p>
</blockquote>
<blockquote>
<p>💡 <code>--save-exact</code>
<code>--save-exact</code>로 정확한 버전을 설치해서 협업 시 팀원 별로 사용하는 prettier 버전이 달라지는 것을 예방할 수 있다. 실제로 package.json을 확인해 보면 <code>--save-exact</code>로 설치하지 않은 패키지의 버전은 <code>^8.56.0</code>과 같이 앞에 ^가 붙는데 이는 “8.56.0과 호환되는 버전을 사용하라”는 뜻이다. <code>--save-exact</code>로 설치하면 ^가 붙지 않고 정확히 그 버전을 사용하게 한다.</p>
</blockquote>
<ol start="3">
<li><strong><code>Ctrl + ,</code>로 설정을 열어 Default Formatter를 Prettier로 설정한다.</strong></li>
</ol>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/56799c34-932d-4c08-8047-c5e8b14c659a/image.png" alt=""></p>
<ol start="4">
<li><p><strong><code>.prettierrc.json</code> 파일을 추가하고 아래 내용을 붙여넣는다.</strong></p>
<p> <code>.prettierrc.json</code> 은 prettier의 formatting configuration file이고, VSCode에게 Prettier를 사용하고 있음을 알려준다.</p>
<p> 아래 내용은 하나의 예시 설정이다.</p>
<p> (반드시 <code>.prettierrc.json</code>을 써야 하는 건 아니다. <a href="https://prettier.io/docs/en/configuration">자세히</a>)</p>
</li>
</ol>
<pre><code class="language-json">{
  &quot;singleQuote&quot;: true,
  &quot;semi&quot;: true,
  &quot;useTabs&quot;: false,
  &quot;tabWidth&quot;: 2,
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;printWidth&quot;: 80
}</code></pre>
<ol start="5">
<li><p><strong><code>.prettierignore</code> 파일을 추가한다.</strong></p>
<p> 이 파일에 명시된 파일이나 디렉터리는 prettier의 formatting 대상에서 제외된다.</p>
</li>
</ol>
<hr>
<p>Prettier를 사용해보자.</p>
<ul>
<li><p><code>Shift + Alt + F</code> 또는 [우클릭] - [Format Document]</p>
<ul>
<li>Prettier가 해당 파일의 코드를 Format해준다.</li>
</ul>
</li>
<li><p><code>npx prettier --write .</code></p>
<ul>
<li>해당 프로젝트의 <code>.prettierignore</code>을 제외한 모든 파일을 Format해준다.</li>
<li>npx로 로컬에 설치된 prettier 패키지를 사용하고 있다. <a href="https://velog.io/@gth1123/npx-%EB%9E%80">npx란?</a></li>
</ul>
</li>
<li><p><code>npx prettier --check .</code></p>
<ul>
<li><p>얘는 Format을 직접 해주는 건 아니고 Prettier의 Formatting Configuration에 어긋난 파일들을 찾아준다.</p>
<pre><code class="language-bash">$ npx prettier --check .
Checking formatting...
[warn] index.js
[warn] Code style issues found in the above file. Run Prettier to fix.</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 <strong>Format on Save</strong>
설정에서 Format on Save를 true로 설정하면 파일을 저장할 때마다 자동으로 Format할 수 있다.
<img src="https://velog.velcdn.com/images/soonduck-dreams/post/69beae5a-9fe9-4e31-95da-a91fd2b01e08/image.png" alt=""></p>
</blockquote>
<h2 id="eslint">ESLint</h2>
<ol>
<li><strong>VSCode Extensions에서 ESLint를 검색해서 설치한다.</strong></li>
</ol>
<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/7cd7a7e7-29a7-4c1c-9562-5d7a85a42095/image.png" alt=""></p>
<ol start="2">
<li><strong>npm으로 eslint를 설치한다.</strong></li>
</ol>
<pre><code class="language-bash">$ npm init @eslint/config</code></pre>
<ol start="3">
<li><strong>터미널에 eslint의 configuration file 설정을 위한 질문들이 나오는데, 프로젝트 상황에 맞게 적당히 답변하고 (설정 파일 확장자 질문에서 JSON을 선택했다면) <code>.eslintrc.json</code> 파일이 만들어진다.</strong></li>
</ol>
<pre><code class="language-json">// .eslintrc.json

{
  &quot;env&quot;: {
    &quot;commonjs&quot;: true,
    &quot;es2021&quot;: true
  },
  &quot;extends&quot;: &quot;eslint:recommended&quot;,
  &quot;parserOptions&quot;: {
    &quot;ecmaVersion&quot;: &quot;latest&quot;
  },
  &quot;rules&quot;: {}
}</code></pre>
<p>이 파일에서 가장 많이 건드리게 될 부분은 <code>extends</code>와 <code>rules</code>이다.</p>
<ul>
<li><code>extends</code><ul>
<li>어떤 Lint 설정을 확장하여 사용할 것인지 명시 (기본값은 <code>eslint:recommended</code>)</li>
<li>잘 쓰이는 설정으로는 airbnb에서 만든 <code>eslint-config-airbnb</code>가 알려져 있다.</li>
</ul>
</li>
<li><code>rules</code><ul>
<li>자신의 프로젝트에 적용할 개별 규칙을 정의할 때 사용한다.</li>
<li><a href="https://eslint.org/docs/latest/rules/">ESLint Rules Reference</a></li>
</ul>
</li>
</ul>
<ol start="4">
<li><strong><code>.eslintignore</code> 파일을 추가한다.</strong></li>
</ol>
<p>이 파일에 명시된 파일이나 디렉터리는 ESLint의 Lint 대상에서 제외된다.</p>
<hr>
<p>ESLint를 사용해보자. 에디터에서 빨간 줄로 ESLint 에러를 띄워주는 것은 물론, 다음과 같은 터미널 명령어를 사용할 수 있다.</p>
<ul>
<li><code>npx eslint index.js</code><ul>
<li>index.js 파일을 검사해서 ESLint 에러를 알려준다.</li>
</ul>
</li>
<li><code>npx eslint .</code><ul>
<li><code>.eslintignore</code>을 제외한 모든 파일을 검사해서 ESLint 에러를 알려준다.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">$ npx eslint .      

C:\yourpath\index.js
  2:1  error  &#39;a&#39; is constant                         no-const-assign
  2:1  error  &#39;a&#39; is assigned a value but never used  no-unused-vars

✖ 2 problems (2 errors, 0 warnings)</code></pre>
<ul>
<li><code>npx eslint . --fix</code><ul>
<li>에러를 알려주는 것은 물론, 자동으로 교정할 수 있는 것은 교정까지 해준다.</li>
</ul>
</li>
<li>package.json에 ESLint 실행하는 npm 스크립트 추가하기<ul>
<li>package.json의 scripts에 아래와 같이 스크립트를 추가하면 <code>npm run lint</code>를 입력하는 것으로 <code>npx eslint .</code>를 대신할 수 있다.</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">// package.json

&quot;scripts&quot;: {
  &quot;lint&quot;: &quot;eslint .&quot;
},</code></pre>
<h1 id="☯️-prettier와-eslint를-함께-사용하기">☯️ Prettier와 ESLint를 함께 사용하기</h1>
<h2 id="서로-충돌하지-않도록-추가-작업을-해주자-eslint-config-prettier">서로 충돌하지 않도록 추가 작업을 해주자 (<code>eslint-config-prettier</code>)</h2>
<p>ESLint와 같은 Linter들은 주로 버그를 잡아주는 규칙들을 제공하지만, Formatting에 관한 규칙을 제공하기도 한다. Prettier랑 ESLint를 함께 사용한다면 Prettier의 스타일링 규칙과 ESLint의 스타일링 규칙이 겹쳐서 문제가 될 수 있다. Prettier로 format했더니 ESLint에서 그렇게 하면 안 된다고 오류를 띄우는 것이다.</p>
<p>이 문제를 해결하기 위해 Prettier 공식 문서에서 권장하는 방법은 eslint configuration file을 수정하여 Prettier와 충돌을 일으키는 ESLint의 설정을 끄는 것이다.</p>
<p>그걸 해주는 설정이 <code>eslint-config-prettier</code>이다.</p>
<ol>
<li><code>eslint-config-prettier</code>를 설치한다.</li>
</ol>
<pre><code class="language-bash">npm install -D eslint-config-prettier</code></pre>
<ol start="2">
<li>ESLint configuration file에 <code>eslint-config-prettier</code>를 적용하기 위해 다음과 같이 수정한다. <code>prettier</code>라는 항목이 추가되었다. <code>prettier</code>가 반드시 배열 마지막에 있어야 하는데, 그래야 그 앞의 config를 덮어쓸 수 있기 때문이다.</li>
</ol>
<pre><code class="language-json">// .eslintrc.json

&quot;extends&quot;: [&quot;eslint:recommended&quot;, &quot;prettier&quot;],</code></pre>
<ol start="3">
<li>직접 설정한 ESLint <code>rules</code>가 Prettier와 충돌하는지를 검사할 수도 있는데, 여기서는 <code>rules</code>를 따로 설정하지 않았기 때문에 생략한다. <a href="https://github.com/prettier/eslint-config-prettier?tab=readme-ov-file">자세히</a></li>
</ol>
<blockquote>
<p>ℹ️ <code>eslint-plugin-prettier</code>라는 패키지를 추가로 이용하는 방법도 알려져 있는데, 이것은 Prettier의 규칙을 ESLint 규칙에 포함시켜서 ESLint 에러 형식으로 띄워주는 것이다. <a href="https://prettier.io/docs/en/integrating-with-linters">Prettier 공식 문서</a>에서는 이 방법을 권장하지 않는다.</p>
</blockquote>
<blockquote>
<p>💡 소스 코드를 git commit할 때 ESLint를 강제로 실행하도록 자동화할 수 있다. 자세히는 <a href="https://www.daleseo.com/js-eslint/">이곳</a>의 “ESLint 강제하기” 문단 참조</p>
</blockquote>
<h1 id="📚-참고할-만한-자료">📚 참고할 만한 자료</h1>
<p><a href="https://prettier.io/docs/en/comparison">Prettier vs. Linters · Prettier</a></p>
<p><a href="https://www.daleseo.com/js-eslint/">ESLint로 소스 코드에 숨어있는 문제 찾기</a></p>
<p><a href="https://prettier.io/docs/en/integrating-with-linters">Integrating with Linters · Prettier</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹] 자리바꾸기 나라]]></title>
            <link>https://velog.io/@soonduck-dreams/jarinara</link>
            <guid>https://velog.io/@soonduck-dreams/jarinara</guid>
            <pubDate>Thu, 23 Feb 2023 10:37:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/3a07610e-c93a-49d4-befd-8b8817acac24/image.png" alt=""></p>
<p><a href="https://jarinara.netlify.app/">웹사이트 바로가기</a></p>
<p>자리바꾸기를 도와주는 웹사이트를 만들었다.
순수 HTML, CSS, JavaScript에 더해 Konva라는 2d canvas 조작 라이브러리를 사용했다.</p>
<p>생활코딩에서 제공하는 웹 개발 입문 강의를 떼고서 뭐라도 만들어봐야겠단 맘으로 고민하고 있을 때, 고등학교 시절 플래시로 만들었던 자리바꾸기 프로그램이 떠올랐다. 플래시는 내가 일찍이 진로에 대한 소신을 갖도록 해준 소중한 친구이지만 이제는 구시대의 유물. 요즘 웬만한 서비스는 웹이나 앱으로 제공된다. 따라서 이 서비스를 웹사이트로 다시 만들어보겠다는 결심이 섰다.</p>
<p>조금씩 만들어서 일주일 가량 걸렸다. 내가 만들어 배포하는 첫 웹사이트라 뿌듯하고, learning by building이 중요하다는 것을 새삼 느낀다. 아무쪼록 이 웹사이트가 유용하게 쓰이면 기쁘겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[게임] Ropable]]></title>
            <link>https://velog.io/@soonduck-dreams/ropable</link>
            <guid>https://velog.io/@soonduck-dreams/ropable</guid>
            <pubDate>Thu, 23 Feb 2023 08:40:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/soonduck-dreams/post/2b9377ef-c58a-46d4-b02d-c97c630a3331/image.png" alt=""></p>
<p><a href="https://dancydangames.itch.io/ropable">게임 소개 페이지</a></p>
<p>Unity + C#을 사용해 만든 PC용 게임. 밧줄로 캐릭터를 움직여 스테이지를 클리어하는 2D 플랫포머. 무료로 다운로드할 수 있다.
22년 11월부터 23년 1월까지 약 두 달 간 작업했다.</p>
<p>총 10스테이지 분량. 실은 더 많이 만들고 싶었는데 레벨 디자인 하다가 진이 빠지고, 게임 말고 다른 것을 만들어보고 싶다는 생각이 들어서 이렇게 갈무리했다. 나중에 다시 건드릴지도?</p>
<p>기획도 하고 개발도 하고 도트도 찍고 음악도 만든 나 자신에게 박수! 협업이 왜 필요한지를 절실히 느꼈다..</p>
<p>만들면서 재미있었다. &#39;할 수 있을까?&#39;라고 생각한 것들을 할 수 있었을 때, 노트에 낙서한 기획이 화면 위에 실제로 작동하고 있을 때 기분이 좋았다. 디자인 패턴이란 걸 살짝 맛보았는데, 디자인 패턴에 대한 본격적인 공부는 프로덕트를 만드는 경험 자체에 더 익숙해진 뒤에 시작해도 알맞겠다는 생각이 들었다.</p>
]]></description>
        </item>
    </channel>
</rss>