<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>bo-like-chicken.log</title>
        <link>https://velog.io/</link>
        <description>Time waits for no one</description>
        <lastBuildDate>Fri, 31 Jan 2025 17:31:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>bo-like-chicken.log</title>
            <url>https://images.velog.io/images/bo-like-chicken/profile/1ec2256d-222b-41e8-be15-63371f6aafc6/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. bo-like-chicken.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bo-like-chicken" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[erasableSyntaxOnly를
알아보며 되짚어본 TypeScript]]></title>
            <link>https://velog.io/@bo-like-chicken/erasableSyntaxOnly%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%A9%B0-%EB%90%98%EC%A7%9A%EC%96%B4%EB%B3%B8-TypeScript</link>
            <guid>https://velog.io/@bo-like-chicken/erasableSyntaxOnly%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%A9%B0-%EB%90%98%EC%A7%9A%EC%96%B4%EB%B3%B8-TypeScript</guid>
            <pubDate>Fri, 31 Jan 2025 17:31:45 GMT</pubDate>
            <description><![CDATA[<h2 id="글을-시작하며">글을 시작하며</h2>
<p>이번 글에서는 <code>erasableSyntaxOnly</code>라는 실험적인 옵션을 살펴보면서, TypeScript 안에서 <strong>“순수 타입”</strong>과 <strong>“런타임 코드”</strong>가 어떻게 뒤섞여 있는지 되짚어보려고 합니다.</p>
<p>타입스크립트에는 꽤 오랫동안 이어져 온 논쟁이 있습니다. 바로 <strong><code>enum</code>을 써도 되는가, 아니면 써서는 안되는가?</strong> 하는 문제죠.</p>
<p>저도 이 논쟁에 대해 생각해보던 중, 최근 토스에서 공개한 <code>frontend-fundamentals</code>라는 자료에서 <strong>enum vs as const</strong> 이야기가 오가는 모습을 발견했습니다. 그 토론 속에서 <code>erasableSyntaxOnly</code>라는 흥미로운 옵션도 보이더군요. “이게 뭘까?” 싶어 살펴보고, 제가 생각한 바를 간단히 정리해보았습니다.</p>
<h2 id="타입스크립트의-역할">타입스크립트의 역할</h2>
<p>우선, 타입스크립트가 어떤 역할을 하는지 공식 홈페이지 핸드북의 문구를 통해 다시 한 번 떠올려보겠습니다.</p>
<blockquote>
<p><strong>TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.</strong><br>- 타입스크립트 공식 홈페이지 핸드북</p>
</blockquote>
<p>결국 타입스크립트는 <strong>“JavaScript에 타입 기능을 추가한 상위 언어(슈퍼셋)”</strong>이라는 것 입니다. 이런 이유로, 우리가 작성한 타입스크립트 코드는 컴파일만 거치면 <strong>완전히 평범한 JavaScript</strong>가 됩니다. 즉, <strong>런타임 레벨에서는 보통은 “타입 정보”가 다 제거된</strong> 상태로 실행된다는 뜻이죠.</p>
<p>이러한 특징 덕분에 예전 자바스크립트 코드를 차근차근 타입스크립트로 작성해나가거나, 혹은 타입스크립트 코드를 작성하고 “타입만 걷어내서” 순수 자바스크립트 코드로 사용할 수 있습니다.</p>
<h2 id="js로-트랜스파일될-때도-남아있는-구문들">JS로 트랜스파일될 때도 남아있는 구문들</h2>
<p><code>type</code>, <code>interface</code>, <code>generic</code>처럼 <strong>정적 타입 체크에만 쓰이는 요소</strong>들은 컴파일 결과에서 모두 사라집니다. 그러나 그와 달리, <strong>일부 TypeScript 전용 문법</strong>들은 트랜스파일 후에도 <strong>런타임 동작을 하는 코드</strong>가 남습니다. 대표적으로 다음과 같은 것들이 있습니다.</p>
<h3 id="1-enum">1. <code>enum</code></h3>
<pre><code class="language-ts">enum Colors {
  Red = &quot;RED&quot;,
  Green = &quot;GREEN&quot;,
  Blue = &quot;BLUE&quot;,
}

function printColor(color: Colors) {
  console.log(color);
}

printColor(Colors.Red);</code></pre>
<p>위 코드를 컴파일(tsc)하면, 다음과 같이 변환됩니다.</p>
<pre><code class="language-js">&quot;use strict&quot;;
var Colors;
(function (Colors) {
    Colors[&quot;Red&quot;] = &quot;RED&quot;;
    Colors[&quot;Green&quot;] = &quot;GREEN&quot;;
    Colors[&quot;Blue&quot;] = &quot;BLUE&quot;;
})(Colors || (Colors = {}));
function printColor(color) {
    console.log(color);
}
printColor(Colors.Red);</code></pre>
<p>결과물에서 Colors라는 실제 객체가 생성되고, Colors.Red는 &quot;RED&quot;라는 값을 갖습니다.
즉, <strong>“타입만”</strong> 추가하려던 의도와 달리 런타임에 새로운 객체가 남는 형태가 되는 셈이죠.</p>
<p>이 때문에 &quot;enum&quot; 대신 <strong>as const</strong>를 쓰고, 타입 체커가 알아서 추론하게 만든 뒤, 실제로는 단순 객체나 상수로만 구성하는 방식을 선호하는 분들도 많습니다.</p>
<h3 id="2-namespace">2. namespace</h3>
<p><code>namespace</code>는 자바스크립트에서 모듈 시스템이 표준화되기 전, <strong>하나의 “네임스페이스”</strong>를 만들어 전역 공간 오염을 줄이려는 목적으로 도입된 개념입니다.</p>
<pre><code class="language-ts">namespace Utils {
  export function greet(name: string) {
    console.log(`Hello, ${name}`);
  }
}

Utils.greet(&quot;Alice&quot;);</code></pre>
<p>위 코드를 컴파일하면, 다음과 같이 변환됩니다.</p>
<pre><code class="language-js">&quot;use strict&quot;;
var Utils;
(function (Utils) {
    function greet(name) {
        console.log(`Hello, ${name}`);
    }
    Utils.greet = greet;
})(Utils || (Utils = {}));

Utils.greet(&quot;Alice&quot;);</code></pre>
<p>보시다시피 <strong>즉시 실행 함수(IIFE)와 객체</strong>가 만들어지며, 결국 런타임에서도 Utils라는 객체가 살아있게 됩니다. enum과 마찬가지로 런타임에서 객체를 생성하니, <strong>&quot;타입만&quot;</strong> 사용한다고 보기에는 어렵습니다.</p>
<h3 id="3-클래스의-매개변수-속성parameter-properties">3. 클래스의 매개변수 속성(Parameter Properties)</h3>
<p>매개변수 속성이란, 생성자(constructor) 매개변수에 public, private, readonly 등을 붙여서 한 번에 필드를 선언하고 초기화할 수 있는 문법입니다.</p>
<pre><code class="language-ts">class Person {
  constructor(
    public name: string,
    private age: number
  ) {}

  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
}</code></pre>
<p>컴파일 결과를 보면, <code>public name: string</code> 같은 타입스크립트 전용 구문이 사라지고, 대신 <code>this.name = name;</code>이라는 실제 프로퍼티 할당이 남습니다.</p>
<pre><code class="language-js">&quot;use strict&quot;;
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}</code></pre>
<p>그렇다 보니, 런타임에도 동작하는 구문이 생성되는 셈입니다. 따라서 이 역시 <strong>“타입만 추가”</strong>하는 것이 아니라, 컴파일 후에도 남는 코드를 만들어낸다고 볼 수 있습니다.</p>
<h2 id="erasablesyntaxonly">erasableSyntaxOnly</h2>
<p>앞서 살펴본 문법들은 모두 일정 부분 런타임 동작을 만들어냅니다.</p>
<p>하지만 저를 포함한 일부 사람들은 <strong>“정말로 타입 체크만을 원할 뿐, 컴파일된 JS 코드에는 추가 구문이 남지 않길 바란다”</strong>는 요구를 갖기도 합니다.</p>
<p>이런 요구를 의식한 듯, TypeScript 5.8 Beta에서 <strong>--erasableSyntaxOnly</strong>라는 옵션이 공개됐습니다.</p>
<h3 id="erasablesyntaxonly-등장-배경">erasableSyntaxOnly 등장 배경</h3>
<p>이 기능은 2024년 8월경 <strong>TypeScript GitHub 이슈</strong> <a href="https://github.com/microsoft/TypeScript/issues/59601">Tsconfig option to disallow features requiring transformations which are not supported by Node.js&#39; --strip-types (#59601)</a>을 통해 구체화되었습니다.</p>
<ul>
<li>Node.js는 실험적 기능으로 <code>--strip-types</code> 플래그를 도입했습니다. 이는 <strong>TypeScript의 타입 주석을 제거</strong>할 수 있게 해주는 기능입니다.</li>
<li>하지만 Node.js의 <code>--strip-types</code>는 순수한 타입 주석만 제거할 수 있고, JavaScript 코드로 변환이 필요한 TypeScript 기능들은 지원하지 않습니다.<ul>
<li>enum</li>
<li>experimentalDecorators</li>
<li>namespaces</li>
<li>parameter properties 등</li>
</ul>
</li>
<li>이로 인해 개발자들이 Node.js의 <code>--strip-types</code>와 호환되는 TypeScript 코드를 작성하기 위해서는 여러 설정을 수동으로 관리해야 하는 불편함이 있었습니다.</li>
</ul>
<p>이에 대한 해결책으로 <code>erasableSyntaxOnly</code>가 등장하게 되었습니다.</p>
<p><code>erasableSyntaxOnly</code> 옵션은 <strong>TypeScript 5.8 Beta</strong>에서 도입된 <strong>실험적</strong> 기능으로 다음과 같은 역할을 합니다.</p>
<ol>
<li>순수하게 타입 제거만으로 실행 가능한 코드만 허용합니다.</li>
<li>JavaScript로의 변환이 필요한 TypeScript 기능들을 사용하지 못하게 합니다.</li>
<li>Node.js의 --strip-types와 완벽하게 호환되는 코드를 작성할 수 있게 해줍니다.</li>
</ol>
<h3 id="erasablesyntaxonly-살펴보기">erasableSyntaxOnly 살펴보기</h3>
<p>위에서 살펴 본 예시와 함께 이 옵션이 어떻게 방지해주는지 알아보겠습니다.</p>
<p>먼저 5.8.0-beta 버전 혹은 그 이상의 버전의 <a href="https://www.typescriptlang.org/play/?ts=5.8.0-beta">Typescript Playground</a>로 이동합니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/f4d5cec3-3288-482a-8495-7cdee387eac7/image.png" alt=""></p>
<p>이후 TS Config 설정에서 erasableSyntaxOnly 속성을 활성화 해줍니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/2f07eff4-2707-429f-9e8e-19696c2c99ba/image.png" alt=""></p>
<p>이전의 캡쳐에서 옵션이 설명된 것처럼 ECMAScript의 일부가 아닌 runtime constructs가 허용되지 않기 때문에 다음과 같이 에러가 나는 모습을 볼 수 있습니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/10720e3f-f822-4f3d-beff-6a7be623918f/image.png" alt=""></p>
<ul>
<li>erasableSyntaxOnly 설정을 true로 설정하면, 앞서 예로 든 enum, namespace, 클래서 매개변수 속성 등 런타임 변환이 필요한 TS 구문을 전부 막아주게 됩니다.</li>
<li>해당 구문들이 등장하면 컴파일 에러가 발생하므로, 순수한 타입 주석만 사용하는 코드만이 허용됩니다.</li>
</ul>
<h2 id="글을-마치며">글을 마치며</h2>
<p>정리해보자면,</p>
<ol>
<li><strong>TypeScript</strong>에는 <code>enum</code>, <code>namespace</code>, 클래스 매개변수 속성처럼 <strong>런타임 코드</strong>가 남는 기능들도 포함되어 있어, “단순히 타입만 추가하는 언어”라고 말하기엔 다소 무리가 있습니다.  </li>
<li>이러한 문법들은 Node.js의 <code>--strip-types</code>처럼 <strong>“타입만 제거하는 방식”</strong>과 충돌이 생길 수 있고, 그 때문에 제안된 옵션이 바로 <strong><code>erasableSyntaxOnly</code></strong>입니다.</li>
</ol>
<p>추가로, 개인적인 추측을 덧붙이자면, 이 옵션을 활용하면 별도의 변환 과정을 생략해 <strong>컴파일 효율</strong>을 높이고, 결과물과 소스 코드가 거의 동일해져 <strong>디버깅</strong>에도 이점이 있을 것으로 보입니다. 다만 이는 어디까지나 제 예상이며, 아직 베타 기능이니 만큼 실제 적용 전에는 충분한 검토와 테스트가 필요합니다.</p>
<p>한편, 저는 이러한 옵션들이 <strong>“타입과 JavaScript 코드를 완전히 분리”</strong>하고자 하는 타입스크립트의 미래 방향성을 보여주는 것이 아닐까 하는 생각도 듭니다. 만약 향후 <strong>ECMAScript 표준</strong>에서 타입 문법을 본격적으로 지원하게 된다거나 타입스크립트에서 erasable한 값만 남기게 된다면, 미리 이와 같은 분리 방식을 염두에 두고 코드를 작성해두었을 때 <strong>마이그레이션 시점에서 발생할 수 있는 어려움</strong>을 덜 수 있겠죠.</p>
<p>물론 <strong>정답은 없습니다.</strong>  </p>
<p>어떤 팀은 <code>enum</code>이나 클래스 매개변수 속성을 적극 활용해 생산성을 높일 수 있고, 다른 팀은 “정적 타입 체크만을 원한다”며 런타임 구문을 최소화하는 방식을 선호할 수 있습니다. 중요한 건 <strong>프로젝트의 목표와 팀의 합의</strong>에 맞춰, TypeScript가 제공하는 여러 옵션들을 적절히 선택하는 것입니다. </p>
<p><code>erasableSyntaxOnly</code> 역시 그런 선택지 중 하나로 보이는 만큼, 앞으로 이 기능이 더 다듬어지는 과정을 관심 있게 지켜봐도 좋겠습니다. </p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[tsconfig 짧게 정리]]></title>
            <link>https://velog.io/@bo-like-chicken/tsconfig-%EC%A7%A7%EA%B2%8C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@bo-like-chicken/tsconfig-%EC%A7%A7%EA%B2%8C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 18 Jan 2025 16:41:29 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p>TypeScript를 사용하면서 반드시 마주치는 파일이 바로 <code>tsconfig.json</code>입니다. 이 설정 파일은 TypeScript 컴파일러(tsc)가 어떤 방식으로 동작할지를 결정합니다. 최근에 다뤘던 프로젝트별로 요구 사항이 다르다 보니, 매번 어떤 옵션을 켜고 끌지 고민하게 되었습니다. 이 글에서는 꼭 알아야 하는 옵션, 반드시 켜두면 좋은 옵션, 그리고 특수한 상황에서 유용한 옵션들을 짧게 정리해 보았습니다.</p>
<h2 id="꼭-알아야-하는-옵션">꼭 알아야 하는 옵션</h2>
<p>TypeScript를 사용하는 개발자라면 반드시 알아야하는 대표적인 항목들입니다. 프로젝트 환경이나 요구사항에 따라 적절히 값을 조정하는 것이 좋습니다.</p>
<h2 id="target">target</h2>
<p>어떤 버전의 JavaScript로 트랜스파일할지를 결정합니다. 예: ES5, ESNext, ES2020 등.</p>
<h3 id="용도">용도</h3>
<ul>
<li>브라우저 호환성 또는 Node.js 버전에 따라 달라집니다.</li>
<li>최신 기능 사용이 가능하면 ESNext로 설정해도 좋고, 레거시 지원이 필요하다면 ES5나 ES6를 선택할 수 있습니다.</li>
<li>예를 들어, 오래된 브라우저까지 지원해야 할 경우 ES5가 적합하지만, 최신 환경에서 빠르고 간단히 개발하고 싶다면 ESNext가 권장됩니다.</li>
</ul>
<h3 id="예제">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES5&quot;
  }
}
// ES5 문법( function, var 등 )으로 트랜스파일됨</code></pre>
<h2 id="module">module</h2>
<p>모듈 시스템을 결정합니다. commonjs, ESNext 등을 설정할 수 있습니다.</p>
<h3 id="용도-1">용도</h3>
<ul>
<li>Node.js(구버전) 환경에서는 보통 commonjs를 사용합니다. require, module.exports 형태로 동작합니다.</li>
<li>모던 번들러나 브라우저 기반 ESM 사용 시 ESNext를 권장합니다. import, export 문법이 그대로 유지되어 트리 쉐이킹이나 최적화에 유리할 수 있습니다.</li>
</ul>
<h3 id="예제-1">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;ESNext&quot;
  }
}
// import/export가 유지된 상태로 JS가 생성됨</code></pre>
<h2 id="outdir-rootdir-include-exclude">outDir, rootDir, include, exclude</h2>
<ul>
<li>rootDir: 소스 코드의 루트 디렉터리</li>
<li>outDir: 컴파일된 JS, .d.ts 등을 어디에 생성할지</li>
<li>include, exclude: 어떤 파일(혹은 폴더)을 빌드에 포함/제외할지</li>
</ul>
<h3 id="용도-2">용도</h3>
<p>빌드 결과물이 섞이지 않도록 디렉터리를 분리하고, 필요 없는 파일을 빌드 대상에서 제거합니다.</p>
<p>예를 들어 node_modules나 테스트 파일(.test.ts)을 제외하면 빌드 속도를 높이고 불필요한 산출물을 줄일 수 있습니다.</p>
<h3 id="예제-2">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;rootDir&quot;: &quot;src&quot;,
    &quot;outDir&quot;: &quot;dist&quot;
  },
  &quot;include&quot;: [&quot;src/**/*&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;, &quot;**/*.test.ts&quot;]
}</code></pre>
<h2 id="lib">lib</h2>
<p>전역 객체등에 대한 타입 정의를 어떤 환경으로 가정할지 지정합니다.</p>
<h3 id="용도-3">용도</h3>
<ul>
<li>브라우저인지, Node.js인지, DOM API를 사용하는지 등에 따라 달라집니다.</li>
<li>&quot;lib&quot;: [&quot;DOM&quot;]는 브라우저 환경에서 DOM 관련 API를 사용함을 의미하고, &quot;ESNext&quot;로 설정하면 최신 ECMAScript 문법에 대한 타입도 사용할 수 있습니다.</li>
</ul>
<h3 id="예제-3">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;lib&quot;: [&quot;DOM&quot;, &quot;ESNext&quot;]
  }
}</code></pre>
<h2 id="jsx">jsx</h2>
<p>React 등 JSX/TSX를 다룰 때, 어떤 방식으로 JSX를 트랜스파일할지 결정합니다.</p>
<ul>
<li><p>&quot;react&quot;: React 16 이하 버전 사용 시 주로 사용되며, JSX를 createElement 형태로 변환합니다.</p>
</li>
<li><p>&quot;react-jsx&quot;: React 17+ 버전의 새 JSX 트랜스폼을 사용하며, import React from &quot;react&quot; 없이도 JSX를 사용 가능하게 해줍니다.</p>
</li>
<li><p>&quot;preserve&quot;: JSX를 그대로 남겨두고, 추가적인 변환은 Babel 등 별도 도구에서 처리하도록 합니다.</p>
</li>
<li><p>&quot;react-native&quot;: React Native 환경을 위한 트랜스폼.</p>
</li>
<li><p>값 예시: &quot;react&quot;, &quot;react-jsx&quot;, &quot;preserve&quot;, &quot;react-native&quot;.</p>
</li>
</ul>
<h3 id="예제-4">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;jsx&quot;: &quot;preserve&quot;
  }
}</code></pre>
<h1 id="꼭-켜야-하는-옵션">꼭 켜야 하는 옵션</h1>
<p>꼭 켜야 하는 옵션들은 TypeScript의 <strong>타입을 엄격히 다루고 개발 편의성을 늘리기 위해서 매우 중요한 설정</strong>입니다.
프로젝트 규모가 커질수록, 타입 안정성과 코드 일관성을 위해 아래 옵션들을 <strong>기본적으로 켜두는 것을 강력히 권장</strong>합니다.</p>
<h2 id="strict">strict</h2>
<p><strong>TypeScript의 모든 엄격 모드를 활성화</strong>합니다.</p>
<h3 id="이점">이점</h3>
<ul>
<li>암시적인 any를 허용하지 않으므로, 타입 선언을 까먹거나 실수했을 때 컴파일러가 즉시 경고를 줍니다.</li>
<li>예) <code>strictNullChecks</code>가 활성화되어 <code>null</code>, <code>undefined</code>를 안전하게 처리해야 합니다.</li>
<li>함수의 매개변수 타입이나 리턴 타입 등이 더 엄격하게 검사되어, 런타임 오류를 크게 줄일 수 있습니다.</li>
</ul>
<h3 id="예제-5">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: false
  }
}</code></pre>
<pre><code class="language-ts">// example.ts
function greet(name) {
  // name에 타입이 명시되지 않았지만 에러 발생 X
  return `Hello, ${name.toUpperCase()}`;
}
// 런타임에서 name이 undefined면 에러!</code></pre>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true
  }
}</code></pre>
<pre><code class="language-ts">// example.ts
function greet(name: string) {
  return `Hello, ${name.toUpperCase()}`;
}
// 컴파일 시 정확한 타입 검사 가능</code></pre>
<h2 id="skiplibcheck">skipLibCheck</h2>
<p><code>node_modules</code> 내부의 <code>.d.ts</code> 파일들에 대한 타입 검사 스킵</p>
<ul>
<li>이점: 외부 라이브러리의 사소한 타입 에러로 인한 빌드 중단을 막고, 빌드 속도를 크게 개선할 수 있습니다.</li>
</ul>
<h3 id="예제-6">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;skipLibCheck&quot;: true
  }
}</code></pre>
<h2 id="forceconsistentcasinginfilenames">forceConsistentCasingInFileNames</h2>
<p>파일 import 시 대소문자가 일관되지 않으면 에러 처리</p>
<ul>
<li>이점: 파일명 대소문자 불일치 이슈를 사전에 막아줍니다.</li>
</ul>
<h3 id="예제-7">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;forceConsistentCasingInFileNames&quot;: true
  }
}
// import { X } from &#39;./MyFile.ts&#39; vs &#39;./myFile.ts&#39;
// 이런 식으로 대소문자 불일치를 에러로 잡아줌</code></pre>
<h2 id="esmoduleinterop">esModuleInterop</h2>
<p>CommonJS 모듈을 import할 때 default import를 허용하도록 보정</p>
<ul>
<li>이점: CJS 기반 라이브러리를 import React from &#39;react&#39;처럼 편하게 가져올 수 있습니다.</li>
</ul>
<h3 id="예제-8">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;esModuleInterop&quot;: false
  }
}
// import \* as React from &#39;react&#39;;
// 반드시 namespace import 형태를 써야 하는 경우 발생</code></pre>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;esModuleInterop&quot;: true
  }
}
// import React from &#39;react&#39;;
// default import 형태를 사용 가능</code></pre>
<h2 id="isolatedmodules">isolatedModules</h2>
<p>각 파일을 독립적인 모듈 단위로 처리 (Vite, Next.js 등 단일 파일 단위 트랜스파일 시 필수)</p>
<ul>
<li>이점: 파일별로 독립적인 스코프를 두기 때문에, 선언적/모듈식 코드를 더 엄격하게 작성하게 됩니다.</li>
<li>주의: 모든 파일에 최소 하나의 import나 export가 있어야 함</li>
</ul>
<h3 id="예제-9">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;isolatedModules&quot;: false
  }
}
// import, export가 없더라도 그냥 컴파일됨</code></pre>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;isolatedModules&quot;: true
  }
}
// import/export 없는 파일 -&gt; 에러: TS1208
// &quot;Cannot be compiled under --isolatedModules&quot;</code></pre>
<h3 id="주의할-점">주의할 점</h3>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/a13eeaf2-40e4-43f4-afd6-8e3bc37d9079/image.png" alt="">출처: <a href="https://ko.vite.dev/guide/features#isolatedmodules">https://ko.vite.dev/guide/features#isolatedmodules</a></p>
<h2 id="noemit">noEmit</h2>
<p>TypeScript 컴파일 시 실제 JS 파일을 생성하지 않습니다.</p>
<ul>
<li>이점: Vite, Webpack, Next.js처럼 자체 번들 파이프라인이 있는 경우, TS 단계에서 굳이 JS 산출물을 만들 필요가 없습니다. 순수하게 타입 검사만 수행하고, 나머지 번들링은 다른 도구에 맡기는 형태를 취할 수 있습니다.</li>
</ul>
<h3 id="예제-10">예제</h3>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;noEmit&quot;: false,
    &quot;outDir&quot;: &quot;dist&quot;
  }
}
// 빌드 시 dist 폴더에 JS 파일들 생성</code></pre>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;noEmit&quot;: true
  }
}
// 빌드해도 JS 결과물이 생성되지 않음</code></pre>
<h1 id="특수한-상황에서의-옵션들">특수한 상황에서의 옵션들</h1>
<p>프로젝트마다 필요한 설정이 다르므로, 아래와 같은 상황에서는 추가로 고려할 옵션들이 있습니다.</p>
<h2 id="nextjs">Next.js</h2>
<ul>
<li><p>noEmit: true</p>
</li>
<li><p>isolatedModules: true</p>
</li>
<li><p>jsx: &quot;preserve&quot; || &quot;react-jsx&quot;</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/de49df41-0d59-47ea-ae3f-025c8e7b32a2/image.png" alt="">출처: <a href="https://nextjs.org/docs/pages/api-reference/config/typescript#incremental-type-checking">https://nextjs.org/docs/pages/api-reference/config/typescript#incremental-type-checking</a></p>
</li>
</ul>
<h3 id="vite">Vite</h3>
<ul>
<li><p>module: &quot;ESNext&quot;</p>
</li>
<li><p>isolatedModules: true</p>
</li>
<li><p>noEmit: true</p>
</li>
<li><p>useDefineForClassFields: true</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/fdc1f422-eee0-4c44-a882-794ff6054d75/image.png" alt="">출처: <a href="https://vite.dev/guide/features#usedefineforclassfields">https://vite.dev/guide/features#usedefineforclassfields</a></p>
</li>
</ul>
<h2 id="javasript와-typescript가-함께-사용되는-프로젝트">JavaSript와 TypeScript가 함께 사용되는 프로젝트</h2>
<h3 id="allowjs-true">allowJs: true</h3>
<ul>
<li>.js 파일도 TS 컴파일 파이프라인에서 처리<h3 id="checkjs-truefalse">checkJs: true/false</h3>
</li>
<li>.js 파일에 대해 타입 검사 여부</li>
</ul>
<h2 id="라이브러리-개발-시-고려할-옵션들">라이브러리 개발 시 고려할 옵션들</h2>
<p>라이브러리를 배포하기 위해서는 타입 정의 파일(.d.ts) 생성이 매우 중요합니다. 또한, 모듈 시스템(CJS/ESM)을 어떻게 제공할지도 고려해야 합니다.</p>
<h3 id="1-declaration-true">1. declaration: true</h3>
<ul>
<li>TS 컴파일 시 .d.ts 타입 선언 파일을 생성</li>
<li>라이브러리를 사용하는 소비자에게 타입 정보를 제공합니다.</li>
</ul>
<h3 id="2-emitdeclarationonly-true">2. emitDeclarationOnly: true</h3>
<ul>
<li>JS 코드 대신 타입 선언 파일만 배출할 때 유용</li>
<li>Babel 등 다른 빌드 도구가 JS를 변환하도록 하고, TS는 타입만 추출.</li>
</ul>
<h3 id="3-declarationmap-true">3. declarationMap: true</h3>
<ul>
<li>.d.ts.map 파일을 생성하여, 타입 정의에 대한 소스 맵을 제공합니다.</li>
</ul>
<h3 id="4-declarationdir-types">4. declarationDir: &quot;types&quot;</h3>
<ul>
<li>.d.ts 파일을 별도 디렉토리에 모아두고 싶다면 설정</li>
<li>예: &quot;declarationDir&quot;: &quot;dist/types&quot;</li>
</ul>
<h3 id="5-composite-incremental">5. composite, incremental</h3>
<ul>
<li>대규모 프로젝트나 모노레포 환경에서 빌드 속도를 개선하고, 프로젝트 간 의존성을 관리할 때 사용.</li>
<li>composite: true가 설정되면, declaration도 자동으로 true가 됩니다.</li>
</ul>
<p>실제 배포 시에는 Rollup/Vite/Webpack 등을 추가로 구성하여 CJS/ESM 번들을 동시에 제공하는 경우가 많습니다. 만약 타입 선언만 추출하고 싶다면 &quot;emitDeclarationOnly&quot;: true로 설정하고, JS 빌드는 Babel/ESBuild 등을 통해 처리할 수 있습니다.</p>
<h1 id="글을-마치며">글을 마치며</h1>
<p><code>tsconfig.json</code>은 TypeScript 프로젝트의 필수 설정으로, 구성을 어떻게 하느냐에 따라 개발 생산성과 코드 안정성이 크게 달라집니다. 꼭 알아야 하는 옵션을 통해 트랜스파일 환경과 파일 구조를 잡고, 반드시 켜두길 권장하는 옵션으로 타입 안전성과 개발 편의성을 높인 뒤, 특수한 상황에 맞춰 추가 옵션들을 세부 조정하면 됩니다.</p>
<p>궁극적으로는 프로젝트 목적(앱, 라이브러리, 레거시 전환 등)에 따라 설정을 다르게 가져가야 하므로, 위의 예시들과 문서들에서 가이드하는 방법을 토대로 <strong>상황에 맞는 최적의 tsconfig.json</strong>을 구성해보시길 바랍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이상적인 아키텍처의 특징 - 클린 아키텍처를 통해 바라본]]></title>
            <link>https://velog.io/@bo-like-chicken/%EC%9D%B4%EC%83%81%EC%A0%81%EC%9D%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%ED%8A%B9%EC%A7%95-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%B0%94%EB%9D%BC%EB%B3%B8</link>
            <guid>https://velog.io/@bo-like-chicken/%EC%9D%B4%EC%83%81%EC%A0%81%EC%9D%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%ED%8A%B9%EC%A7%95-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%B0%94%EB%9D%BC%EB%B3%B8</guid>
            <pubDate>Sun, 05 Jan 2025 13:19:29 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<blockquote>
<p>좋은 아키텍처란 무엇일까요?</p>
</blockquote>
<p>예전에는 이 질문에 대한 대답을 굉장히 추상적으로 할 수 밖에 없었습니다. &quot;좋은 아키텍처란 무엇인가?&quot;에 대해서 깊게 고민해보지 않았기 때문에 질문의 본질적인 의미를 이해하기 어려웠고 <strong>결국에는 유명한 아키텍처를 적절하게 차용한 그럴싸한 폴더 구조를 찾아 설명하는 것이 최선</strong>이었습니다. </p>
<h2 id="좋은-아키텍처에-대한-필요성을-느낀-순간">좋은 아키텍처에 대한 필요성을 느낀 순간</h2>
<p>개인적으로 &#39;좋은 아키텍처&#39;를 진심으로 고민하게 된 계기는 불분명한 기준으로 설계된 프로젝트가 커지면서 예기치 못한 문제가 발생하는 때였습니다. 간단한 수정임에도 불구하고 여러 서비스에서 사이드이펙트를 염려해야하거나, 작은 기능 하나 추가하려고 수 많은 파일을 수정해야 할 때가 되어서야 비로소 <strong>&quot;더 좋은 아키텍처로 설계했더라면 어땠을까?&quot;</strong>라는 후회가 들었습니다.</p>
<h2 id="클린-아키텍처">클린 아키텍처</h2>
<p>이러한 어려움을 겪어본 사람이라면 자연스럽게 <strong>“좋은 아키텍처란 무엇인가?”</strong>라는 질문에 고민이 깊어질 것입니다. 저 역시 이런 문제들을 해결할 수 있을 것이라는 기대를 가지고 로버트 C. 마틴의 클린 아키텍처를 읽었습니다. 처음에는 혼자 책을 읽고 “클린 아키텍처란 무엇인가?“에 대해 고민했지만, 책에서 설명하는 개념과 컨셉을 온전히 이해하기는 쉽지 않았습니다.</p>
<p>그러던 와중 스터디에 참여해 함께 책을 읽고 의견을 나눌 기회가 생겼습니다. 스터디 멤버들도 저와 비슷한 필요성을 느끼고 책을 읽었지만, 결론적으로는 <strong>지금의 프런트엔드에 클린 아키텍처에서 제시하는 구조를 그대로 적용하기에는 어려움이 있다는 공감대가 형성</strong>됐습니다. 저 역시 <strong>클린 아키텍처가 모든 문제를 해결하는 해답은 아니다</strong>라는 생각에 도달하게 되었습니다.</p>
<h2 id="이상적인-아키텍처">이상적인 아키텍처</h2>
<p>그렇다면 이상적인 아키텍처란 과연 존재할까요? 최근 주목 받는 <a href="https://feature-sliced.design">FSD(Feature-Sliced Design)</a>같은 방식이 해답이 되어줄 수 있을까요? </p>
<p>저는 <strong>이상적인 아키텍처란 존재하지 않는다</strong>고 생각합니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/850e7568-29e3-45cf-9e00-d31fbeb43b67/image.png" alt="이상적인-스키야키">
출처: <a href="https://www.youtube.com/watch?v=MORCRNvOKnc">제로스애니리그</a></p>
<blockquote>
<p>단, 스키야키에는 명확한 법칙은 존재하지 않는다. 관동풍, 관서풍의 차이 뿐만 아니라 재료의 종류, 배치, 순서까지... 가정의 개수만큼 스키야키의 조리법이 존재한다. 이건 어디까지나 내가 최고로 생각하는 방법. 나에게 있어서 이상적인 스키야키다.</p>
</blockquote>
<p>제가 좋아하는 영상 &#39;<a href="https://youtu.be/MORCRNvOKnc?si=0M-2Z5EMGyAKlRrJ">이상적인 스키야키</a>&#39;의 한 대목입니다.</p>
<p>아키텍처를 공부하면서 깨달은 것은, <strong>스키야키 조리법만큼이나 아키텍처도 프로젝트 규모, 도메인, 조직 문화 등에 따라 “이상적”이라는 개념이 달라질 수밖에 없다는 점</strong>입니다. 결국, 상황마다 ‘가장 적합한 아키텍처’를 찾아 적용해야 하고, 그 과정에서 통용될 수 있는 ‘이상적인 아키텍처’는 존재하지 않습니다.</p>
<p>그럼에도 불구하고 널리 알려진 아키텍처를 공부하는 것은 가치가 있습니다. 그 안에 녹아 있는 공통적인 원칙과 이유를 이해하면, 이를 우리의 프로젝트에 맞게 응용할 수 있기 때문입니다.</p>
<p>이 글에서는 클린 아키텍처를 통해 바라본 ‘이상적인 아키텍처’가 가지는 특징에 대한 제 생각을 정리해 보고자 합니다.</p>
<h1 id="클린-아키텍처의-특징">클린 아키텍처의 특징</h1>
<blockquote>
<p>소프트웨어를 계층으로 분리하고 의존성 규칙을 준수한다면 본질적으로 테스트하기 쉬운 시스템을 만들게 될 것이며, 그에 다른 이점을 누릴 수 있다. - 로버트 C. 마틴, 『클린 아키텍처』 중에서</p>
</blockquote>
<p>이 문장은 클린 아키텍처의 핵심을 명확히 보여줍니다. <strong>“계층 분리”와 “의존성 규칙 준수”</strong>는 클린 아키텍처의 근간이며, 이 두 가지를 통해 궁극적으로 예측 가능하고 유지보수하기 쉬운 시스템을 만들 수 있습니다.</p>
<h2 id="첫-번째-특징-잘-분리된-계층">첫 번째 특징, 잘 분리된 계층</h2>
<p>소프트웨어 아키텍처의 <strong>첫 번째 목표는 역할(Role)과 책임(Responsibility)을 분리해, 코드가 논리적으로 정리되도록 만드는 것</strong>입니다. 이를 통해 각 계층의 변경이 다른 계층에 영향을 주지 않도록 설계함으로써, 유지보수와 확장이 용이한 시스템을 구축할 수 있습니다.</p>
<h3 id="경계-긋기">경계 긋기</h3>
<blockquote>
<p>소프트웨어 아키텍처는 선을 긋는 기술이며, 나는 이러한 선을 경계(boundary)라고 부른다. - 로버트 C. 마틴, 『클린 아키텍처』 중에서</p>
</blockquote>
<p>클린 아키텍처에서 말하는 “계층 분리”를 이해하려면 먼저 <strong>경계(boundary)</strong>라는 개념을 알아야 합니다. 책에서도 “소프트웨어 아키텍처는 선을 긋는 기술”이라고 소개하면서, 이 선을 ‘경계’라고 부릅니다.</p>
<h4 id="외부-라이브러리와의-경계-긋기">외부 라이브러리와의 경계 긋기</h4>
<pre><code class="language-tsx">// RegisterDate.tsx
import moment from &#39;moment&#39;;

const RegisterDate = (registerDate) =&gt; {
    return &lt;Text&gt;{moment(registerDate).format(&#39;YYYY-MM-DD&#39;)}&lt;/Text&gt;
}</code></pre>
<p><code>&lt;RegisterDate/&gt;</code> 컴퍼넌트는 가입일을 <code>YYYY-MM-DD</code> 형식으로 표시해주는 단순한 컴퍼넌트입니다. 읽기에도 어렵지 않고, 그 의미가 충분히 잘 전달된다고 생각이 들 수 있습니다. 프로젝트가 점차 커지면 날짜 포맷 처리 관련 로직을 비슷하게 중복 사용하게 되고, 프로젝트가 커지면 이 로직을 사용하는 컴퍼넌트가 수 없이 많아지게 됩니다.</p>
<p>이때 팀의 규칙 변화로 “moment 대신 dayjs로 교체하자”와 같은 결정이 내려지면, <strong>모든 호출부를 한꺼번에 수정해야 하는 문제</strong>가 생길 수 있습니다.</p>
<p>이러한 문제의 원인은, <strong>라이브러리 의존성을 “경계 없이” 컴포넌트 내부에 직접 두었기 때문</strong>입니다.</p>
<pre><code class="language-tsx">// lib/date
import moment from &#39;moment&#39;;

export function formatDate(date, { format }) {
  return moment(date).format(format);
}

// RegisterDate.tsx
import formatDate from &#39;@lib/date&#39;;

const RegisterDate = (date) =&gt; {
    return &lt;Text&gt;{formatDate(date,{format:&#39;YYYY-MM-DD&#39;})}&lt;/Text&gt;
}</code></pre>
<p>만약 <code>moment</code>를 <code>formatDate</code> 함수에 래핑하여 경계를 두었다면,<code>moment</code> 대신 <code>dayjs</code>나 <code>date-fns</code> 같은 라이브러리로 교체한다고 해도 <code>formatDate</code> 함수 내부만 수정하면 되므로, 수정 범위가 매우 줄어듭니다. 또한, <code>formatDate</code>를 가져와서 사용하는 컴퍼넌트는 내부적으로 어떤 라이브러리를 사용하는지 알 필요가 없어집니다. 즉, <code>&lt;RegisterDate/&gt;</code>는 <code>formatDate</code>에만 의존할 뿐 외부 라이브러리에 직접 의존하지 않게 됩니다.</p>
<h3 id="책임-분리">책임 분리</h3>
<p>경계를 세웠다면, 이제 책임 또한 명확히 분리해야 합니다.</p>
<h4 id="책임responsibility이란">책임(Responsibility)이란?</h4>
<p>아키텍처에서 말하는 책임은 <strong>단일 책임 원칙(Single Responsibility Principle)</strong>과 밀접한 관련이 있습니다. 클린 아키텍처에서는 단일 책임 원칙을 다음과 같이 설명합니다.</p>
<blockquote>
<p>헷갈리지 말라. 단 하나의 일만 해야 한다는 원칙은 사실 따로 있다. 그것은 바로 함수는 반드시 하나의 일만 해야한다는 원칙이다. 이 원칙은 커다란 함수를 작은 함수들로 리팩터링하는 더 저수준에서 사용된다. - 로버트 C. 마틴, 『클린 아키텍처』 중에서</p>
</blockquote>
<blockquote>
<p>역사적으로 SRP는 아래와 같이 기술되어 왔다.
단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다. - 로버트 C. 마틴, 『클린 아키텍처』 중에서</p>
</blockquote>
<blockquote>
<p>하나의 모듈은 하나의, 오직 하나의 사용자 또는 이해관계자에 대해서만 책임져야 한다. - 로버트 C. 마틴, 『클린 아키텍처』 중에서</p>
</blockquote>
<blockquote>
<p>하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다. - 로버트 C. 마틴, 『클린 아키텍처』 중에서</p>
</blockquote>
<p>즉, <strong>“하나의 모듈은 변경 이유가 오직 하나여야 한다”</strong>는 것입니다. <strong>여러 목적과 책임이 한곳에 섞여 있으면 변경 요인이 많아지고, 결국 유지보수가 어려워집니다.</strong></p>
<p>좋은 아키텍처가 되기 위해서는 아키텍처를 구성하는 모듈을 각각 하나의 책임을 가지도록 설계하는 것이 중요합니다. <strong>여러 책임을 가지고 있는 모듈로 이루어진 아키텍처라면 그만큼 변경될 이유가 많기 때문에 좋은 아키텍처라고 할 수 없습니다.</strong> 하나의 레이어에서 하나의 역할을 담당하기 위해서는 모듈이나 코드도 이러한 취지에 맞게 나눠지는 것이 중요할 것 입니다.</p>
<h4 id="책임이-뒤섞여-있는-예시">책임이 뒤섞여 있는 예시</h4>
<pre><code class="language-ts">// apis.ts
import { toast } from &#39;react-toastify&#39;;
import axios from &#39;axios&#39;;

axios.interceptors.response.use(
  response =&gt; response,
  error =&gt; {
    const { message } = error;
    toast.error(message);
    return Promise.reject(error.originalError);
  }
);</code></pre>
<p>여기서는 <strong>네트워크 요청 코드(axios)와 UI 표시 코드(toast)가 한 파일에 섞여 있습니다.</strong> 만약 토스트 대신 모달이나 다른 방식으로 에러를 표시하고 싶다면, 이 파일도 수정해야 합니다. 즉, <strong>“네트워크 요청”이라는 책임과 “UI 표시”라는 책임이 함께 뒤섞여 있어, 변경이 발생했을 때 서로 얽히게 됩니다.</strong></p>
<h4 id="에러-처리에-대한-책임을-분리해-보기">에러 처리에 대한 책임을 분리해 보기</h4>
<pre><code class="language-ts">// apis.ts
import axios from &#39;axios&#39;;
import { handleGlobalError } from &#39;./handleGlobalError&#39;;

axios.interceptors.response.use(
  response =&gt; response,
  error =&gt; {
    handleGlobalError(error);
  }
);

// handleGlobalError.ts
import { toast } from &#39;react-toastify&#39;;
import type { AxiosError } from &#39;axios&#39;;

export const handleGlobalError = (error: AxiosError) =&gt; {
  const { message } = error;
  toast.error(message);
  return Promise.reject(error.originalError);
};</code></pre>
<p>에러 처리 함수가 분리되어 책임 분리에 한 걸음 가까워졌지만, 여기서는 여전히 <code>handleGlobalError</code>가 UI 표시(토스트)에 직접 의존합니다. 특정 기능에서 에러 표시를 모달로 표시해달라는 요청이 들어오거나, UI로 표시가 아닌 처리방식으로 요청이 들어왔을 때 이 로직이 복잡해질 우려가 있습니다.</p>
<p>더 나은 접근 방법은 <strong>“에러를 전파하는 역할”</strong>과 <strong>“UI에 표시하는 역할”</strong>을 나누는 것입니다. 전역 에러 핸들러는 “에러 객체를 넘겨받아 전역에서 처리할 수 있도록 만드는 책임”까지만 맡고, 실제 UI 표시 로직은 가장 끝단(예: ErrorBoundary, 최상위 컴포넌트 등)에서 수행하도록 분리할 수 있습니다.</p>
<pre><code class="language-tsx">// apis.ts
import { toast } from &#39;react-toastify&#39;;
import axios from &#39;axios&#39;;

const handleGlobalError = (error: Error) =&gt; {
  // ... UI와 관련 없는 에러 로직 처리
  Promise.reject(error);
};

axios.interceptors.response.use(
  response =&gt; response,
  error =&gt; handleGlobalError(error)
);

// ProductDetail.tsx
const ProductDetail = ({ id }: { id: string }) =&gt; {
  const { data } = useSuspenseQuery({
    queryKey: [&#39;product&#39;, id],
    queryFn: () =&gt; axios.get(`/product/${id}`),
  });

  return &lt;div&gt;{data.name}&lt;/div&gt;;
};

// ParentComponent.tsx
const handleLocalError = (error: Error) =&gt; {
// 여기서만 Toast 등 UI 표시 로직 처리
  toast.error(error.message);
};

const ParentComponent = ({ id }: { id: string }) =&gt; {
  return (
    &lt;ErrorBoundary
      fallback={&lt;div&gt;Something went wrong&lt;/div&gt;}
      onError={handleLocalError}
      &gt;
      &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
        &lt;ProductDetail id={id} /&gt;
      &lt;/Suspense&gt;
    &lt;/ErrorBoundary&gt;
  );
};</code></pre>
<p>이 방식이면, 상위 컴포넌트 혹은 <code>ErrorBoundary</code> 쪽에서 “에러를 어떤 방식으로 표현할지” 결정하므로, <code>apis.ts</code> 같은 네트워크 계층을 굳이 수정할 필요가 없어집니다. <strong>책임이 명확히 분리</strong>되면 이런 유연함을 얻을 수 있습니다.</p>
<h3 id="계층-나누기">계층 나누기</h3>
<p>프로젝트가 커질수록, 코드가 흩어지며 유지보수가 어려워집니다. 그래서 ‘<strong>비슷한 역할끼리 모으는 계층 구조’</strong>가 중요해집니다. <strong>역할(Role)을 기준으로 코드를 나누는 것이 계층 분리의 핵심</strong>입니다. 역할을 명확히 정의하면 코드가 어디에 위치해야 하는지 명확해지고, 변경사항에 내성이 생깁니다.</p>
<p>프로젝트 규모가 커질수록, “역할이 뒤섞인” 코드가 유지보수를 어렵게 만듭니다. 여기서는 UI/도메인/인프라로 단순화한 예시를 들어 보겠습니다. 앞서 다뤘던 에러 처리 코드를 이 레이어 구조에 대입해보면, 각 계층이 어떤 역할을 맡아야 하는지 보다 쉽게 이해할 수 있을 것 입니다.</p>
<blockquote>
<p> 주의: 로버트 C. 마틴이 책에서 말하는 4단계(Entities, Use Cases, Interface Adapters, Frameworks &amp; Drivers)와는 용어와 범위가 조금 다를 수 있습니다. 이 글에서는 UI/도메인/인프라로 나눠서 사용해봤을 때 어떠한 이점을 얻었는지 예시를 보여주기 위함입니다.</p>
</blockquote>
<p><strong>1. UI 레이어 (User Interface Layer)</strong></p>
<ul>
<li>역할: 사용자와의 상호작용을 담당합니다. 화면 표시, 이벤트 처리, 에러 메시지 표시 등을 맡습니다.</li>
</ul>
<p><strong>2. 도메인 레이어 (Domain/Application Layer)</strong></p>
<ul>
<li>역할: 비즈니스 로직을 담당합니다. 예컨대 “로그인 오류면 어떤 에러를 발생시킬 것인가?” “재고가 0개면 어떻게 처리할 것인가?” 같은 규칙을 결정하고, 필요하다면 에러를 ‘분류’하여 상위 레벨에 전달합니다.</li>
</ul>
<p><strong>3. 인프라 레이어 (Infrastructure Layer)</strong></p>
<ul>
<li>역할: HTTP 통신, DB 접근, 외부 라이브러리/시스템 연동 등 환경 의존 로직을 담당합니다.</li>
</ul>
<h4 id="예시로-살펴보기">예시로 살펴보기</h4>
<p>에러 처리 코드를 레이어에 맞게 재구성해보면 아래와 같이 나눌 수 있습니다.</p>
<p><strong>1. 인프라 레이어</strong>
어떤 HTTP 에러가 발생했는지 도메인 계층으로 넘겨주는 역할을 합니다.</p>
<pre><code class="language-ts">// infra/apiClient.ts
import axios from &#39;axios&#39;;
import { classifyError } from &#39;@domain/errorClassifier&#39;;

const apiClient = axios.create({
  baseURL: &#39;/api&#39;,
});

apiClient.interceptors.response.use(
  response =&gt; response,
  error =&gt; {
    // 서버에서 받은 에러를 도메인 계층이 이해할 수 있는 형태로 변환
    const domainError = classifyError(error.response?.data || error);
    // 분류된 에러를 UI에서 처리할 수 있도록 throw
    return Promise.reject(domainError);
  }
);

export default apiClient;</code></pre>
<p><strong>2. 도메인 레이어</strong></p>
<ul>
<li>에러가 발생했을 때, 이를 어떻게 분류(또는 해석)할지 결정합니다.</li>
<li>UI에 대한 구체적 의존은 없으며, AUTH_ERROR, PERMISSION_ERROR 같은 도메인에 특화된 에러 타입을 정의하거나 반환합니다.</li>
</ul>
<pre><code class="language-ts">// domain/errorClassifier.ts
export type DomainErrorType = &#39;AUTH_ERROR&#39; | &#39;PERMISSION_ERROR&#39; | &#39;SERVER_ERROR&#39; | &#39;UNKNOWN_ERROR&#39;;

export interface DomainError {
  type: DomainErrorType;
  message: string;
}

export function classifyError(errorData: any): DomainError {
  if (errorData?.status === 401) {
    return {
      type: &#39;AUTH_ERROR&#39;,
      message: &#39;로그인이 필요합니다.&#39;,
    };
  }

  if (errorData?.status === 403) {
    return {
      type: &#39;PERMISSION_ERROR&#39;,
      message: &#39;권한이 없습니다.&#39;,
    };
  }

  if (errorData?.status &gt;= 500) {
    return {
      type: &#39;SERVER_ERROR&#39;,
      message: &#39;서버 오류가 발생했습니다.&#39;,
    };
  }

  return {
    type: &#39;UNKNOWN_ERROR&#39;,
    message: errorData?.message || &#39;알 수 없는 오류가 발생했습니다.&#39;,
  };
}</code></pre>
<p><strong>3. UI 레이어</strong></p>
<ul>
<li>도메인에서 전달된 에러(= DomainError)를 해석해, 화면 상에 나타내는 방식을 결정합니다.</li>
<li>Toast, 모달, 팝업 등 구체적인 UI를 선택하는 것은 UI 레이어의 책임입니다.<pre><code class="language-tsx">// ui/ErrorBoundary.tsx
import { ErrorBoundary } from &#39;react-error-boundary&#39;;
import { toast } from &#39;react-toastify&#39;;
import type { DomainError } from &#39;@domain/errorClassifier&#39;;
</code></pre>
</li>
</ul>
<p>function handleDomainError(error: DomainError) {
  switch (error.type) {
    case &#39;AUTH_ERROR&#39;:
      // 이미 로그인 화면으로 이동하는 로직이 UI 책임으로 분류되었다고 가정
      navigate(&#39;/login&#39;);
      break;
    case &#39;PERMISSION_ERROR&#39;:
      toast.error(&#39;권한이 없습니다.&#39;);
      break;
    case &#39;SERVER_ERROR&#39;:
      toast.error(error.message);
      break;
    default:
      toast.error(error.message);
  }
}</p>
<p>export function MyErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    &lt;ErrorBoundary
      fallback={<div>Something went wrong</div>}
      onError={(error: DomainError) =&gt; {
        handleDomainError(error);
      }}
    &gt;
      {children}
    </ErrorBoundary>
  );
}</p>
<pre><code>
#### 계층 나누기의 효과
이처럼 에러 처리 코드를 역할에 따라 분리하고 레이어 구조에 대입하면 다음과 같은 장점이 있습니다.

**1. UI와 로직 분리**
- UI 레이어는 에러를 어떻게 보여줄지(Toast, 모달 등)만 결정하고, 에러의 세부 분류는 도메인 레이어에서 합니다.
**2. 도메인 로직 독립성**
- 비즈니스 규칙이나 에러 분류 로직이 바뀌더라도, UI는 큰 영향을 받지 않습니다(반대로 UI를 바꿔도 도메인은 수정 없이 그대로 둡니다).
**3. 수정 범위 최소화**
- UI 방식을 교체하거나(Toast → 모달) 새로운 요구사항이 생겨도, 인프라나 도메인 레이어를 건드릴 필요가 없습니다.
**4. 확장성**
- 새로운 에러 유형이 추가되거나, 인프라(HTTP 라이브러리)를 교체하더라도 각 레이어가 독립적으로 확장 가능합니다.

이렇듯 “계층 분리”와 “책임 분리”가 만나면, 코드 구조가 훨씬 명확해지고 유지보수가 쉬워집니다.

## 두 번째 특징, 잘 준수된 의존성 규칙

클린 아키텍처에서 말하는 **의존성 규칙**은, 의존성이 항상 “안쪽(도메인)” 계층으로만 흐르도록 설계해야 한다는 원칙입니다. 즉, UI나 인프라 레이어가 도메인 레이어를 참조할 수 있지만, **도메인 레이어가 UI나 인프라를 역으로 참조해서는 안 됩니다.**

이 규칙을 지키지 않으면 코드가 잘못된 방식으로 결합되어 유지보수와 확장이 어려워질 위험이 있습니다. 앞서 살펴본 경계 설정 예시를 통해 의존성 규칙의 필요성과 효과를 구체적으로 알아보겠습니다.

#### 의존성 규칙을 지키지 않을 때의 문제

```ts
// apis.ts
import { toast } from &#39;react-toastify&#39;;
import axios from &#39;axios&#39;;

axios.interceptors.response.use(
  response =&gt; response,
  error =&gt; {
    const { message } = error;
    toast.error(message);
    return Promise.reject(error);
  }
);</code></pre><p>위 코드에서는 인프라 레이어(axios 설정)가 UI 라이브러리인 toast에 직접 의존합니다. 만약 에러 표시 방식을 토스트에서 모달로 바꾸거나, react-toastify 대신 다른 라이브러리를 쓰기로 하면 이 코드 역시 수정해야 합니다. 이는 <strong>인프라 로직과 UI가 강하게 결합되어 있다는 신호이며, 수정 범위를 넓히고 테스트 복잡도를 높입니다.</strong></p>
<h4 id="의존성-규칙을-지켰을-때의-개선">의존성 규칙을 지켰을 때의 개선</h4>
<p>의존성 규칙을 준수하려면, 도메인 계층에서는 에러 처리 결과만 반환하고, 실제 UI 표시 로직은 UI 레이어에서 처리하도록 분리해야 합니다.</p>
<p><strong>1. 인프라 레이어</strong>
외부 연동(HTTP 호출)만 책임지며, 에러가 발생하면 도메인 형태로 변환해 throw합니다.</p>
<pre><code class="language-ts">// infra/apiClient.ts
import axios from &#39;axios&#39;;
import { classifyError } from &#39;@domain/errorClassifier&#39;;

const apiClient = axios.create({ baseURL: &#39;/api&#39; });

apiClient.interceptors.response.use(
  response =&gt; response,
  error =&gt; {
    const domainError = classifyError(error.response?.data || error);
    return Promise.reject(domainError);
  }
);

export default apiClient;</code></pre>
<p><strong>네트워크 요청 중 발생한 에러를 포착해 도메인 계층으로 전달</strong>합니다.</p>
<p><strong>2. 도메인 레이어</strong>
에러를 어떻게 해석(분류)할지만 결정하고, UI나 인프라에 직접 의존하지 않습니다.</p>
<pre><code class="language-ts">// domain/errorClassifier.ts
export function classifyError(errorData): DomainError {
  // ...
}</code></pre>
<p><strong>3. UI 레이어</strong>
도메인 에러를 구체적으로 어떻게 표시할지 담당합니다. (Toast, 모달, 라우팅 등)</p>
<pre><code class="language-tsx">// ui/ErrorBoundary.tsx
const handleLocalError = (error: CustomError) =&gt; {
  if (error.action === &#39;notify&#39;) {
    toast.error(error.message); // UI 레이어에서 에러 표시
  } else if (error.action === &#39;redirect&#39;) {
    navigate(error.path);
  }
};

&lt;ErrorBoundary
  fallback={&lt;div&gt;Something went wrong&lt;/div&gt;}
  onError={handleLocalError}
&gt;
  &lt;YourComponent /&gt;
&lt;/ErrorBoundary&gt;;</code></pre>
<p>의존성 규칙을 조금 더 명확히 표현하면, 아래와 같은 흐름을 가진다고 볼 수 있습니다.</p>
<ul>
<li>UI 레이어는 화면 표시와 사용자 입력(이벤트)에 집중하며, 내부 동작(도메인 로직)에는 의존하지만 역방향으로 영향받지는 않습니다.</li>
<li>도메인 레이어는 비즈니스 로직을 담당하며, UI가 어떻게 구현되든 상관없이(=UI 레이어에 의존하지 않고) 필요한 액션만 정합니다.</li>
<li>인프라 레이어는 DB나 외부 API, 라이브러리 등에 대한 실제 연동을 처리하며, 도메인 로직을 응용하려면(=도메인 레이어를 사용하려면) 해당 규칙을 통해 통신할 뿐, 인프라 레이어가 도메인 레이어를 거꾸로 의존해서는 안 됩니다.</li>
</ul>
<p>즉, 의존성이 한쪽 방향(안쪽 계층)으로만 흐르도록 하면 UI가 바뀌어도 도메인 레이어는 건드릴 필요가 없고, 도메인 로직이 바뀌어도(예: 에러 처리 로직 변경) UI에는 최소한의 영향만 미치도록 설계할 수 있습니다. 이처럼 <strong>“어느 한쪽이 변경되더라도 다른 한쪽에 대한 영향이 최소화되는 ‘내성’”</strong>을 확보할 수 있다는 점이 가장 큰 장점입니다.</p>
<p>결과적으로, 첫 번째 특징인 <strong>“계층 분리”</strong>와 두 번째 특징인 <strong>“의존성 규칙 준수”</strong>가 결합되면, 유지보수성과 확장성이 뛰어난 시스템을 구축할 수 있습니다.</p>
<h2 id="세-번째-특징-테스트에-용이한가">세 번째 특징, 테스트에 용이한가?</h2>
<p><strong>좋은 아키텍처는 테스트 코드 작성이 쉽고</strong>, 코드 수정 후에도 비교적 쉽게 리팩터링할 수 있다는 특징을 가지고 있습니다. 왜냐하면, <strong>“테스트하기 어려운 구조”</strong>는 그 자체로 응집도가 낮거나 결합도가 높다는 신호일 수 있기 때문입니다.</p>
<h3 id="리팩터링-내성을-높여주는-테스트">리팩터링 내성을 높여주는 테스트</h3>
<p>아래 코드를 다시 보겠습니다.</p>
<pre><code class="language-tsx">// lib/date
import moment from &#39;moment&#39;;

export function formatDate(date, format) {
  return moment(date).format(format);
}

// Date.tsx
const Date = (date) =&gt; {
    return &lt;Text&gt;{formatDate(date,&#39;YYYY-MM-DD&#39;)}&lt;/Text&gt;
}</code></pre>
<pre><code class="language-ts"> it(&quot;should format a general date string to the given format&quot;, () =&gt; {
    const inputDate = &quot;01/02/2025&quot;;
    const format = &quot;YYYY-MM-DD&quot;;
    const expectedOutput = &quot;2025-01-02&quot;;

    const result = formatDate(inputDate, format);
    expect(result).toBe(expectedOutput);
  });</code></pre>
<p>경계에서 언급했던 예시에 테스트코드를 추가해 가져왔습니다. 여기서 <code>moment</code> 대신 <code>dayjs</code> 등 다른 라이브러리로 교체하여 세부구현을 바꾸더라도, 입출력(파라미터와 반환값)이 동일하게 유지되면 테스트가 그대로 통과합니다.</p>
<p>즉, <strong>“경계를 둔 유틸 함수”에 대한 단위 테스트만 통과하면, 컴포넌트를 전부 바꾸지 않아도 됩니다.</strong> 이것이 “경계를 두는 것”과 “테스트 코드”가 결합했을 때 얻을 수 있는 가장 큰 이점입니다.</p>
<h3 id="결합도가-높은-컴포넌트-테스트의-어려움">결합도가 높은 컴포넌트 테스트의 어려움</h3>
<p>반면, 한 컴포넌트에 지나치게 많은 의존성이 몰려 있으면 테스트가 매우 까다로워집니다. 예를 들어, 아래처럼 하나의 컴포넌트가 Redux 상태, 네트워크 로직, UI 로직, 도메인 로직 등 다양한 책임을 동시에 안고 있다고 해봅시다.</p>
<pre><code class="language-tsx">// ComplexComponent.tsx
export function ComplexComponent() {
  // 1. 리덕스 상태와 UI 상태를 혼합
  const [localState, setLocalState] = useState(false);
  const reduxValue = useSelector((state) =&gt; state.value);

  // 2. 네트워크 요청 포함
  useEffect(() =&gt; {
    axios.get(&#39;/api/something&#39;).then((res) =&gt; {
      // 네트워크 응답 처리
    });
  }, []);

  // 3. 여러 레이어 섞임: 도메인 로직 + UI 로직 + 네트워크 로직 등
  const handleClick = async () =&gt; {
    await axios.post(&#39;/api/action&#39;, { value: reduxValue });
    setLocalState(true);
  };

  return (
    &lt;div onClick={handleClick}&gt;
      {localState ? &#39;Clicked&#39; : &#39;Not clicked yet&#39;}
    &lt;/div&gt;
  );
}</code></pre>
<p>위 컴포넌트의 테스트 코드를 작성하려고 하면, 아래와 같은 문제가 발생합니다:</p>
<ul>
<li><code>store</code>, <code>QueryClientProvider</code>, <code>BrowserRouter</code> 등 여러 의존성이 모두 필요하고, 어느 한 군데만 바뀌어도 테스트가 깨질 수 있습니다.</li>
<li>서버 요청을 한다면 Mock 서버 설정이나 목 데이터를 준비해야 하고, 테스트 범위가 불필요하게 커집니다.</li>
<li>결국 하나의 테스트에서 너무 많은 부분을 목킹해야 하며, 테스트 작성 자체가 복잡해질 뿐만 아니라 유지보수에도 부담이 커집니다.</li>
</ul>
<p>이는 이미 <strong>“해당 컴포넌트가 너무 많은 책임을 안고 있다”</strong>는 신호입니다. 즉, 아키텍처적 관점에서 봤을 때 결합도가 높은 구조가 숨어 있을 가능성이 높습니다.</p>
<p>결국, 테스트가 용이한 구조란 의존성 방향을 잘 지키고, 각 계층이 독립적으로 동작하도록 만들어진 구조를 의미합니다. 특정 컴포넌트나 모듈에 외부 의존성이 몰려 있다면 테스트가 힘들어지며, 이 또한 좋은 아키텍처와 거리가 멀어지는 지름길입니다.</p>
<h1 id="글을-마치며">글을 마치며</h1>
<p>정리하자면, 클린 아키텍처가 말하는 <strong>계층 분리와 의존성 규칙을 제대로 지키면, 결국 테스트하기 편한 구조가 만들어지고 예측 가능한 아키텍처를 쟁취</strong>할 수 있습니다.</p>
<p>클린 아키텍처에서 제시하는 레이어를 학습한 후 그대로 적용해야 한다는 의미는 아닙니다. 이 레이어들이 어떤 기준으로 구분되었는지 이해하고, 자신의 프로젝트와 팀 문화에 맞춰 재설정하는 과정이 필요합니다. 작은 규모의 프로젝트라면 이러한 레이어링이 오버엔지니어링이 될 수도 있으므로, 늘 균형감 있게 적용해야 합니다.</p>
<p>여전히 아키텍처 고민을 하다 보면, “이상적인 스키야키” 영상을 떠올리곤 합니다. 서로 다른 재료와 조리법이 있듯, 우리도 서로 다른 조직, 기술 스택, 프로젝트 규모가 있습니다. 스키야키 영상 속에선 가족들이 각자 다른 방식을 고집하다가, 결국 서로의 문화를 받아들이고 이해하며 <strong>‘나름의 스키야키’</strong>를 완성해 갑니다.</p>
<p>마치 아키텍처 설계도 그렇게 여러 방식을 시도해보고 때로는 충돌도 일으키며, 결국엔 우리 팀에 맞는 <strong>‘또다른 스키야키’</strong>를 만들어가는 과정이 아닐까 싶습니다.</p>
<p>영상에서 스키야키를 즐기는 서로의 방식을 이해하고 받아들이는 모습처럼, 우리 역시 “이상적인 아키텍처”라는 하나의 정답이 아니라 <strong>서로 다른 방식을 열어두고, 함께 발전시키며 결국엔 우리만의 스키야키, 우리만의 아키텍처를 완성나가는 것</strong>이 아닐까 생각해 봅니다.</p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tidy First? - 관리]]></title>
            <link>https://velog.io/@bo-like-chicken/Tidy-First-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@bo-like-chicken/Tidy-First-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sat, 23 Nov 2024 04:25:32 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p>지난 달 <a href="https://velog.io/@bo-like-chicken/tidy-first-%EC%BD%94%EB%93%9C-%EC%A0%95%EB%A6%AC%EB%B2%95"><strong>Tidy First?를 읽고 코드 레벨에서의 코드 정리법</strong></a>에 대해 간단하게 작성해 보았습니다. 몇 가지 유용한 코드 정리 방법을 익혔지만, 실제로 코드 정리가 어려운 이유는 단순히 방법을 모르기 때문이 아니라, 지속적으로 새로운 코드가 코드베이스에 추가되고 기존 코드에 덧붙여지는 과정에서 <strong>어떻게 코드 정리를 계획하고 관리해야 하는지에 대한 어려움 때문</strong>이었습니다.</p>
<p>실제로 개발을 할 때는 새로운 기능 추가와 버그 수정이 끊임없이 이루어지며, 그 과정에서 코드베이스는 복잡해지고 유지보수가 어려워집니다. 이러한 상황에서 효율적으로 코드 정리를 수행하기 위해서는 <strong>체계적인 계획과 관리</strong>가 필수적입니다.</p>
<p>이번 글에서는 &#39;Tidy First?&#39;의 두 번째 파트인 &#39;관리&#39;를 읽고, 코드 정리 과정을 어떻게 계획하고 관리할 수 있는지에 대해 제 의견을 더해 정리해 보았습니다. 또한 이를 실천하기 위한 몇 가지 액션 아이템도 함께 소개하려 합니다.</p>
<h1 id="코드-정리를-배우는-사람들이-거쳐가는-단계">코드 정리를 배우는 사람들이 거쳐가는 단계</h1>
<p>코드 정리를 처음 배우는 개발자들은 종종 <strong>코드 정리와 기능 추가를 동시에 진행</strong>하다가 예상치 못한 어려움에 부딪히곤 합니다. 이러한 상황에서 코드 정리를 어떻게 계획하고 실행해야 하는지, 구체적인 예시를 통해 단계별로 알아보겠습니다.</p>
<h2 id="문제-상황">문제 상황</h2>
<p>약 300줄에 달하는 코드가 있다고 가정해 봅시다. 이 코드는 <code>openapi-generator-cli</code>를 사용하여 OpenAPI 명세를 기반으로 TypeScript 클라이언트를 생성하는 스크립트이며, 여러 기능이 하나의 파일에 복잡하게 얽혀 있습니다. 여기에 <strong><code>msw-auto-mock</code></strong>을 추가하여 API 모킹 기능을 구현하려고 합니다.</p>
<h3 id="기존-코드">기존 코드</h3>
<pre><code class="language-javascript">// OpenAPI 명세를 기반으로 TypeScript 클라이언트를 생성하는 스크립트

const { exec } = require(&#39;child_process&#39;);
const fs = require(&#39;fs&#39;);

function main() {
  let specUrl = &#39;https://api.example.com/openapi.yaml&#39;;
  let outputDir = &#39;./generated-client&#39;;

  // 폴더를 만드는 로직
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir);
  }

  // OpenAPI 명세를 가져오는 로직
  exec(`curl -o openapi.yaml ${specUrl}`, (error, stdout, stderr) =&gt; {
    if (error) {
      console.error(`Error fetching OpenAPI spec: ${error}`);
      return;
    }

    // 클라이언트 코드 생성하는 로직
    exec(
      `openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o ${outputDir}`,
      (error, stdout, stderr) =&gt; {
        if (error) {
          console.error(`Error generating client code: ${error}`);
          return;
        }

        // 파일 입력 처리를 하는 로직
        fs.copyFileSync(&#39;./custom-config.ts&#39;, `${outputDir}/config.ts`);

        // 더는 필요 없는 파일을 삭제하는 로직
        fs.unlinkSync(&#39;openapi.yaml&#39;);

        // 기타 방어 처리들이 혼합되어 있음
        console.log(&#39;TypeScript client generated successfully.&#39;);
      }
    );
  });

  // 에러 처리가 부족하고 코드가 중첩되어 있음
}

main();</code></pre>
<h3 id="문제점">문제점:</h3>
<ul>
<li><strong>모든 로직이 하나의 함수에 들어있어 가독성이 떨어집니다.</strong></li>
<li><strong>콜백 지옥으로 인해 코드의 흐름이 복잡합니다.</strong></li>
<li><strong>에러 처리가 미흡합니다</strong> (예: 에러 메시지가 구체적이지 않음).</li>
<li><strong>하드코딩된 값들이 존재합니다</strong> (<code>specUrl</code>, <code>outputDir</code> 등).</li>
<li>따라서 <strong>새로운 기능을 추가하기 어려운 구조입니다</strong> (예: <code>msw-auto-mock</code> 기능 추가).</li>
</ul>
<p>이러한 상태에서 <code>msw-auto-mock</code> 기능을 추가하려고 하니 코드가 더욱 복잡해지기 때문에 <strong>기능 추가와 코드 정리를 동시에 진행하고 싶은 욕심</strong>에 사로잡히게 됩니다.</p>
<h3 id="코드-정리와-기능-추가를-동시에-진행한-예시">코드 정리와 기능 추가를 동시에 진행한 예시</h3>
<pre><code class="language-javascript">// OpenAPI 명세를 기반으로 TypeScript 클라이언트를 생성하고 MSW 모킹 핸들러를 생성하는 스크립트

const { exec } = require(&#39;child_process&#39;);
const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);

const SPEC_URL = &#39;https://api.example.com/openapi.yaml&#39;;
const OUTPUT_DIR = &#39;./generated-client&#39;;

async function main() {
  try {
    createOutputDirectory(OUTPUT_DIR);
    await fetchOpenAPISpec(SPEC_URL);
    await generateClientCode(&#39;openapi.yaml&#39;, OUTPUT_DIR);
    await generateMswMocks(&#39;openapi.yaml&#39;, &#39;./msw-mocks&#39;); // 새로운 기능 추가
    copyCustomConfig(&#39;./custom-config.ts&#39;, `${OUTPUT_DIR}/config.ts`);
    cleanUpFiles([&#39;openapi.yaml&#39;]);
    console.log(&#39;TypeScript client and MSW mocks generated successfully.&#39;);
  } catch (error) {
    console.error(&#39;Error:&#39;, error);
  }
}

function createOutputDirectory(dir) {
  // ...
}

function fetchOpenAPISpec(url) {
  // ...
}

function generateClientCode(specFile, outputDir) {
  // ...
}

function copyCustomConfig(src, dest) {
  // ...
}

function cleanUpFiles(files) {
  // ...
}

function generateMswMocks(specFile, outputDir) {
  // ...
}

main();</code></pre>
<p>겉보기에는 코드가 멀쩡해 보이지만, 코드 변경 내역(Diff)을 살펴보면 <strong>리뷰어가 변경 사항의 맥락을 파악하기 어렵다는 문제</strong>가 있습니다.</p>
<p>결과적으로 코드가 개선되었지만, 리뷰어들은 변경 사항의 의도를 파악하기 어려워집니다.</p>
<h3 id="리뷰어-입장에서의-어려움">리뷰어 입장에서의 어려움</h3>
<p>아래의 코드 변경 내역(Diff)을 보면, <strong>코드의 변경 범위가 광범위하고, 여러 종류의 변경 사항이 한꺼번에 이루어져 리뷰어가 어떤 부분이 코드 정리이고, 어떤 부분이 기능 추가인지 구분하기 어렵습니다</strong>.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/59c673fa-a1b5-4819-9c44-f64c18e26d0d/image.png" alt="코드 변경 사항 1"><img src="https://velog.velcdn.com/images/bo-like-chicken/post/ba843945-9cba-41af-a440-7fd93f88a452/image.png" alt="코드 변경 사항 2"></p>
<ul>
<li><strong>방대한 변경 사항</strong>: 코드의 구조와 로직이 크게 변경되어 <strong>변경된 파일의 줄 수가 많고, Diff가 복잡</strong>합니다.</li>
<li><strong>의도 파악의 어려움</strong>: 코드 정리인지, 기능 추가인지, 버그 수정인지 <strong>의도를 명확히 알기 어렵습니다</strong>.</li>
<li><strong>검토 시간 증가</strong>: 변경 사항을 이해하고 검토하는 데 <strong>많은 시간이 소요</strong>되며, 이는 개발 프로세스의 지연으로 이어집니다.</li>
</ul>
<p>이로 인해 리뷰어들은 이 PR을 빠르게 검토하지 못하고, 코드 병합이 지연될 수 있습니다. 더 꼼꼼한 리뷰어들은 PR을 다시 올리라고 요청하거나(Re-request), 심지어는 PR을 닫아버리는 경우도 발생할 수 있습니다.</p>
<p>그렇다면 이러한 PR을 어떻게 다루는 것이 좋을까요? 효율적인 코드 정리와 기능 추가를 위해서는 어떤 접근 방법이 필요할까요?</p>
<h2 id="올바른-접근-방법">올바른 접근 방법</h2>
<p>효율적인 코드 정리와 기능 추가를 위해서는 <strong>변경 사항을 명확히 구분하고, 올바른 순서로 진행하며, 작은 단위로 관리하는 것</strong>이 중요합니다. 이를 통해 코드베이스의 품질을 높이고, 팀의 생산성을 향상시킬 수 있습니다.</p>
<h3 id="코드-정리와-기능-추가를-분리하기">코드 정리와 기능 추가를 분리하기</h3>
<p>먼저 <strong>코드 정리를 선행</strong>하여 코드의 구조를 개선한 후, 새로운 기능을 추가합니다. 이렇게 하면 리뷰어들이 각 변경 사항의 의도와 목적을 명확히 이해할 수 있으며, 검토 과정도 훨씬 수월해집니다.</p>
<h3 id="작은-단위로-변경-사항을-관리하기">작은 단위로 변경 사항을 관리하기</h3>
<p>변경 사항을 작은 단위로 나누어 <strong>PR을 분리</strong>하면, 리뷰어들이 변경의 의도를 명확히 이해할 수 있습니다. 이는 코드 리뷰의 효율성을 높이고, 피드백 반영도 용이하게 합니다.</p>
<h3 id="1-코드-정리를-선행한-pr">1. 코드 정리를 선행한 PR</h3>
<p>먼저 코드 정리를 선행하여 코드의 구조를 개선한 코드입니다. 이렇게 하면 기능 추가 전 리뷰어들이 변경 사항의 의도와 목적을 명확히 이해할 수 있으며, 검토 과정도 훨씬 수월해집니다.</p>
<pre><code class="language-javascript">// 코드 정리만 수행한 개선된 코드 예시

const { exec } = require(&#39;child_process&#39;);
const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);

const SPEC_URL = &#39;https://api.example.com/openapi.yaml&#39;;
const OUTPUT_DIR = &#39;./generated-client&#39;;

async function main() {
  try {
    createOutputDirectory(OUTPUT_DIR);
    await fetchOpenAPISpec(SPEC_URL);
    await generateClientCode(&#39;openapi.yaml&#39;, OUTPUT_DIR);
    copyCustomConfig(&#39;./custom-config.ts&#39;, `${OUTPUT_DIR}/config.ts`);
    cleanUpFiles([&#39;openapi.yaml&#39;]);
    console.log(&#39;TypeScript client generated successfully.&#39;);
  } catch (error) {
    console.error(&#39;Error:&#39;, error);
  }
}

function createOutputDirectory(dir) {
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir);
  }
}

function fetchOpenAPISpec(url) {
  return new Promise((resolve, reject) =&gt; {
    exec(`curl -o openapi.yaml ${url}`, (error) =&gt; {
      if (error) {
        reject(`Error fetching OpenAPI spec: ${error.message}`);
      } else {
        resolve();
      }
    });
  });
}

function generateClientCode(specFile, outputDir) {
  return new Promise((resolve, reject) =&gt; {
    exec(
      `openapi-generator-cli generate -i ${specFile} -g typescript-axios -o ${outputDir}`,
      (error) =&gt; {
        if (error) {
          reject(`Error generating client code: ${error.message}`);
        } else {
          resolve();
        }
      }
    );
  });
}

function copyCustomConfig(src, dest) {
  fs.copyFileSync(src, dest);
}

function cleanUpFiles(files) {
  files.forEach((file) =&gt; {
    if (fs.existsSync(file)) {
      fs.unlinkSync(file);
    }
  });
}

main();</code></pre>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/39f78895-fbb2-4671-b564-10a9cefd37ab/image.png" alt="코드 정리 PR의 변경 사항"><img src="https://velog.velcdn.com/images/bo-like-chicken/post/dc034386-6e91-4d59-a551-7885e05b30e5/image.png" alt="코드 정리 PR의 변경 사항 2"></p>
<h3 id="2-기능-추가를-위한-pr">2. 기능 추가를 위한 PR</h3>
<p>미리 정리해 둔 코드베이스에 <strong>함수 하나와 <code>main</code> 함수에 한 줄만 추가</strong>하면 됩니다.</p>
<pre><code class="language-javascript">async function main() {
  try {
    createOutputDirectory(OUTPUT_DIR);
    await fetchOpenAPISpec(SPEC_URL);
    await generateClientCode(&#39;openapi.yaml&#39;, OUTPUT_DIR);
    await generateMswMocks(&#39;openapi.yaml&#39;, &#39;./msw-mocks&#39;); // 새로운 기능 추가
    copyCustomConfig(&#39;./custom-config.ts&#39;, `${OUTPUT_DIR}/config.ts`);
    cleanUpFiles([&#39;openapi.yaml&#39;]);
    console.log(&#39;TypeScript client and MSW mocks generated successfully.&#39;);
  } catch (error) {
    console.error(&#39;Error:&#39;, error);
  }
}

// ...

function generateMswMocks(specFile, outputDir) {
  return new Promise((resolve, reject) =&gt; {
    exec(
      `msw-auto-mock -i ${specFile} -o ${outputDir}`,
      (error) =&gt; {
        if (error) {
          reject(`Error generating MSW mocks: ${error.message}`);
        } else {
          resolve();
        }
      }
    );
  });
}</code></pre>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/04d82c0d-6703-443f-888c-9dfd9b52714a/image.png" alt="기능 추가 PR의 변경 사항"></p>
<p>이렇게 코드 정리와 기능 추가를 <strong>분리하여 진행</strong>함으로써, 리뷰어들은 변경 사항의 의도를 명확히 파악할 수 있고, 검토 과정도 훨씬 효율적으로 만들 수 있습니다.</p>
<p>코드 정리와 기능 추가를 동시에 진행하면 일시적으로 개발 속도가 빨라지는 것처럼 보일 수 있지만, 장기적으로는 <strong>유지보수성과 코드 품질에 악영향</strong>을 미칩니다. 특히, 팀원들과의 협업 환경에서는 이러한 방식이 <strong>커뮤니케이션 비용 증가</strong>와 <strong>프로젝트 지연</strong>으로 이어질 수 있습니다.</p>
<p>따라서 위와 같은 예시를 통해 <strong>변경 사항을 명확히 구분하고, 올바른 순서로 진행하며, 작은 단위로 관리하는 것</strong>의 중요성을 체감할 수 있습니다. 이를 통해 코드베이스의 품질을 높이고, 팀의 생산성을 향상시킬 수 있습니다.</p>
<h2 id="action-item">Action Item</h2>
<p>이러한 원칙을 잘 지키기 위해 도움이 되는 다음과 같은 액션 아이템을 제안합니다.</p>
<ol>
<li><strong>Tidy 라벨을 활용하여 코드 정리 PR을 명확히 구분하기</strong><ul>
<li>코드 정리 PR에 <code>tidy</code> 라벨을 추가하여 <strong>리팩터링보다 작은 규모의 코드 정리</strong>임을 명시합니다. 이를 통해 리뷰어들이 PR의 목적을 빠르게 이해할 수 있고, 코드 정리에 대한 의사소통 비용을 줄일 수 있습니다.</li>
<li>실제로 실무에서도 이렇게 제안하고 적용하여 사용하고 있습니다. 예를 들어, 저희 팀에서는 코드 정리 PR에 <code>tidy</code> 라벨을 붙여 관리하고 있으며, 이를 통해 리뷰어들이 변경 사항의 의도를 빠르게 파악하고 있습니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/72a67975-4d96-4c4e-bf7d-7837556ea6d2/image.png" alt=""></li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>변경 사항의 종류를 명확히 구분하기</strong></p>
<ul>
<li><strong>기능 추가</strong>, <strong>버그 수정</strong>, <strong>코드 정리</strong> 등의 변경 사항을 명확히 구분하여 각각 별도의 PR로 관리합니다. 이를 통해 리뷰어들이 변경의 의도를 명확히 파악할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>작은 단위로 PR을 만들기</strong></p>
<ul>
<li>변경 사항을 가능한 작은 단위로 나누어 PR을 작성합니다. 작은 PR은 리뷰하기 쉽고, 피드백 반영도 용이하여 개발 효율성을 높입니다.</li>
</ul>
</li>
<li><p><strong>팀 내에서 합의된 코드 정리 규칙을 수립하기</strong></p>
<ul>
<li>코드 정리에 대한 팀의 합의를 통해 <strong>코딩 스타일</strong>, <strong>비동기 처리 방식</strong>, <strong>에러 처리 방식</strong> 등을 통일합니다. 이를 통해 코드의 일관성을 유지하고, 협업 효율을 높일 수 있습니다.</li>
</ul>
</li>
<li><p><strong>코드 정리와 기능 추가의 순서를 전략적으로 결정하기</strong></p>
<ul>
<li>상황에 따라 <strong>코드 정리를 선행</strong>할지, <strong>동작 변경 후에 코드 정리</strong>를 할지 판단합니다. 코드 정리를 선행하면 새로운 기능 추가가 용이해지고, 동작 변경 후에 코드 정리를 하면 최근에 수정된 코드를 정돈하여 유지보수성을 높일 수 있습니다.</li>
</ul>
</li>
<li><p><strong>코드 정리 목록을 관리하기</strong></p>
<ul>
<li>지금 당장 코드 정리를 할 수 없는 경우, <strong>&quot;재미 목록&quot;</strong>이나 <strong>백로그</strong>에 정리할 코드를 기록해 둡니다. 이를 통해 나중에 코드 정리를 체계적으로 진행할 수 있습니다.</li>
</ul>
</li>
</ol>
<h1 id="글을-마치며">글을 마치며</h1>
<p><strong>&#39;Tidy First?&#39;</strong>의 두 번째 파트인 &#39;관리&#39;를 읽고, 코드 정리를 어떻게 계획하고 관리할 수 있는지에 대해 제 의견과 경험을 더해 정리해 보았습니다. 책에서 제시한 원칙들을 실제 개발 현장에서 적용해 보니, 코드 정리와 기능 추가를 명확히 구분하고 작은 단위로 관리하는 것이 얼마나 중요한지 다시 한 번 깨닫게 되었습니다.</p>
<p>코드 정리는 단순히 코드를 깔끔하게 만드는 것을 넘어, <strong>팀의 생산성을 높이고 소프트웨어의 품질을 향상시키는 핵심적인 과정</strong>입니다. 그러나 코드 정리와 기능 추가를 동시에 진행하면 혼란을 야기하고 협업에 어려움을 초래할 수 있습니다.</p>
<p>이번 글에서는 <strong>코드 정리와 기능 추가를 명확히 구분하고, 올바른 순서로 진행하며, 작은 단위로 관리하는 것</strong>의 중요성을 살펴보았습니다. 구체적인 예시를 통해 코드 정리를 선행하고 기능 추가를 별도의 PR로 분리함으로써 리뷰어들이 변경 사항의 의도를 명확히 파악하고, 검토 과정을 원활하게 진행할 수 있음을 보여드렸습니다.</p>
<p>또한, 팀 내에서 코드 정리에 대한 <strong>공통된 규칙과 절차를 수립</strong>하고, 코드 정리의 <strong>시점을 전략적으로 결정</strong>하는 것이 중요하다는 것을 강조했습니다. 상황에 따라 코드 정리를 선행할지, 동작 변경 후에 진행할지 판단하여 효율적인 개발 프로세스를 구축할 수 있습니다.</p>
<p>앞으로 코드베이스를 관리할 때, 이 글에서 소개한 원칙과 방법들을 적용해 보시길 권장합니다. 코드 정리를 <strong>체계적으로 계획하고 관리함으로써</strong>, 더 효율적이고 건강한 개발 문화를 만들어갈 수 있을 것입니다.</p>
<p>코드에 새로운 기능을 추가하거나 버그를 수정할 때, <strong>이번에 소개한 방법으로 코드 정리를 관리해보면 어떨까요?</strong></p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tidy First? - 코드 정리법]]></title>
            <link>https://velog.io/@bo-like-chicken/tidy-first-%EC%BD%94%EB%93%9C-%EC%A0%95%EB%A6%AC%EB%B2%95</link>
            <guid>https://velog.io/@bo-like-chicken/tidy-first-%EC%BD%94%EB%93%9C-%EC%A0%95%EB%A6%AC%EB%B2%95</guid>
            <pubDate>Sun, 13 Oct 2024 14:29:56 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p>지난 여름 처음 Kent Beck의 <strong>Tidy First?</strong>를 접했을 때, 그 단순하면서도 실용적인 내용에 크게 공감되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/66412c8f-32f4-42cf-89ed-7d0c53f71ad5/image.jpg" alt=""></p>
<p>최근 지인들과 이 책을 다시 읽으면서 처음 놓쳤던 부분들도 새롭게 발견하고, 개발자들이 꼭 한 번쯤은 읽어봐야 할 책이라는 것을 다시금 느꼈습니다. 하지만 모든 개발 서적이 그렇듯이, 이 책도 프런트엔드에서 바로 이해하고 적용하기에는 조금 어려운 부분이 있었습니다.</p>
<p>그래서 <strong>Tidy First?</strong>의 핵심 개념들을 프런트엔드에서 어떻게 적용할 수 있을지 <strong>정리한 글과 예시, 그리고 몇 가지 액션 아이템</strong>을 준비했습니다. </p>
<p>이번 글은 첫 번째 파트인 코드 정리를 다룹니다.</p>
<h1 id="tidy-first">Tidy First?</h1>
<p><a href="https://x.com/kentbeck">Kent Beck</a>의 <strong>Tidy First?</strong>는 코드를 정리하고 리팩터링하는 과정을 통해 시스템의 복잡성을 줄이고, 코드를 더 명료하게 만드는 것을 목표로 합니다. 이 책에서 가장 강조되는 점은 <strong>기능을 추가하기 전에 먼저 코드를 깔끔하게 정리하라는 원칙</strong>입니다. 복잡한 코드베이스에 새로운 기능을 추가하려다 보면, 코드의 구조가 더 엉망이 되고, 유지보수성이 떨어지는 경우가 많습니다. 이를 방지하기 위해서는 코드를 먼저 정리한 후에 기능을 추가함으로써, 코드의 일관성을 유지하고 더 나은 품질의 소프트웨어를 만들 수 있습니다.</p>
<p>또한 이 책은 작은 리팩터링 단위의 중요성도 강조합니다. 코드 <strong>정리는 작은 단계에서 시작해야 하며, 큰 변화를 한꺼번에 시도하기보다는 일관된 방식으로 점진적으로 개선해야 한다</strong>는 것이 핵심입니다. 이렇게 해야 팀원들이 변경사항을 이해하기 쉽고, 버그가 발생할 가능성도 줄일 수 있습니다.</p>
<blockquote>
<p>&quot;코드 정리를 리팩터링의 부분집합으로도 볼 수 있습니다. 코드 정리는 작은 리팩터링으로 누구도 싫어할 수 없을 정도로 사랑스럽고 포근합니다.&quot;</p>
</blockquote>
<h1 id="보호구문">보호구문</h1>
<p>보호구문이라는 표현은 한 번쯤 들어보았을 법하지만, 정확히 무엇을 의미하는지 설명하기는 막상 쉽지 않습니다. 책에서는 보호구문을 적용한 예시를 통해 보호구문을 다음과 같이 설명하고 있습니다:</p>
<blockquote>
<p>&quot;코드의 세부 사항을 살펴보기 전에 염두에 두어야 할 몇 가지 전제 조건이 있습니다&quot;라고 말하는 것처럼 보입니다.</p>
</blockquote>
<p><strong>즉, 보호구문은 특정한 조건이 만족되지 않을 경우, 더 이상의 처리가 필요하지 않다는 것을 명시하는 방식</strong>입니다. 이를 통해 코드의 흐름을 명확하게 하고, 불필요한 깊이를 줄여 코드의 가독성을 높일 수 있습니다.</p>
<p>그럼 예제를 보며 보호구문을 이해해 보겠습니다.</p>
<h2 id="보호구문을-사용하지-않는-경우">보호구문을 사용하지 않는 경우</h2>
<p>보호구문을 사용하지 않고 작성된 코드는 다음과 같습니다. 이 경우 중첩된 조건문들이 계속해서 깊어지고, 읽기 어려워집니다.</p>
<pre><code class="language-ts">function processOrder(order) {
  if (order) {
    if (order.items &amp;&amp; order.items.length &gt; 0) {
      if (order.paymentInfo) {
        if (order.shippingAddress) {
          // 주문 처리 로직 실행
          console.log(&quot;주문을 처리합니다.&quot;);
        } else {
          console.log(&quot;배송 주소가 없습니다.&quot;);
        }
      } else {
        console.log(&quot;결제 정보가 없습니다.&quot;);
      }
    } else {
      console.log(&quot;주문 항목이 없습니다.&quot;);
    }
  } else {
    console.log(&quot;주문 정보가 없습니다.&quot;);
  }
}</code></pre>
<h2 id="보호구문을-사용하는-경우">보호구문을 사용하는 경우</h2>
<p>보호구문을 적용한 코드는 조건을 미리 확인하고, 조건이 만족되지 않는 경우에는 일찍 반환하여 코드의 깊이를 줄이고 가독성을 높입니다. 이렇게 하면 조건이 맞지 않을 때 더 이상 불필요한 처리를 하지 않게 되어 코드가 간결해집니다. 아래 예제는 보호구문을 적용한 코드입니다.</p>
<pre><code class="language-ts">function processOrder(order) {
if (!order) {
    console.log(&quot;주문 정보가 없습니다.&quot;);
    return;
  }

  if (!order.items || order.items.length === 0) {
    console.log(&quot;주문 항목이 없습니다.&quot;);
    return;
  }

  if (!order.paymentInfo) {
    console.log(&quot;결제 정보가 없습니다.&quot;);
    return;
  }

  if (!order.shippingAddress) {
    console.log(&quot;배송 주소가 없습니다.&quot;);
    return;
  }

  // 주문 처리 로직 실행
  console.log(&quot;주문을 처리합니다.&quot;);
}</code></pre>
<p>책에서 언급한 것처럼, 몇 가지 전제 조건을 미리 체크하여, 조건이 충족되지 않는 경우에는 바로 반환하는 방식입니다. 이를 통해 코드를 한 눈에 이해하기 쉽고, 로직 흐름도 간결하게 정리됩니다.</p>
<h2 id="보호구문이-주는-이점">보호구문이 주는 이점</h2>
<p>보호구문을 사용함으로써 얻을 수 있는 가장 큰 이점은 코드의 <strong>가독성과 유지보수성</strong>입니다. 복잡한 중첩 조건을 사용하면 코드의 흐름을 파악하기 어렵고, 어디에서 어떤 로직이 실행되는지 혼란스럽기 쉽습니다. <strong>보호구문을 사용하면, 코드를 읽는 사람은 불필요한 로직을 생략하고 중요한 로직에만 집중</strong>할 수 있습니다.</p>
<p>또한, <strong>보호구문은 코드의 예측 가능성을 높입니다.</strong> 코드의 흐름이 명확해지기 때문에, 특정 조건이 충족되지 않는 경우에는 즉시 반환되어야 함을 쉽게 알 수 있습니다. 이로 인해 코드가 더 단순해지고, 디버깅도 쉬워집니다. 특히, 조건이 복잡하거나 여러 개일 때, 중복된 코드 없이 명확하게 로직을 표현할 수 있습니다.</p>
<h2 id="주의해야-할-것">주의해야 할 것</h2>
<p>그러나 보호구문을 남용해서는 안 됩니다. <strong>지나치게 많은 보호구문이 사용된 코드는 가독성을 높이려다 오히려 코드가 너무 잘게 쪼개지고 복잡해질 수 있습니다.</strong> 보호구문은 주로 필수적인 조건을 미리 확인하고, 코드의 흐름을 단순화하는 데 사용해야 하며, 너무 많은 조건문이 포함된 코드라면 다시 한 번 코드의 구조를 고민해볼 필요가 있습니다.</p>
<h2 id="action-item">Action Item</h2>
<ol>
<li><strong>조건문은 보호구문으로 정리하자</strong>
코드의 실행을 결정하는 조건문은 보호구문을 사용해 로직을 간결하게 만들어 코드의 흐름을 명확히 하자.</li>
<li><strong>불필요한 조건 중첩을 피하자</strong>
중첩된 조건문을 줄이고, 불필요한 로직을 미리 걸러내어 코드의 가독성과 유지보수성을 높이자.</li>
<li><strong>필요할 때만 보호구문을 사용하자</strong>
남용하지 않고, 필수적인 전제 조건만 보호구문으로 처리해 코드가 지나치게 세분화되지 않도록 하자.</li>
</ol>
<h2 id="참고">참고</h2>
<p>참고로, Swift에서는 이를 위해 특별한 문법인 guard문을 제공합니다. guard문은 조건이 충족되지 않을 때 특정 동작을 수행하고, 조건이 충족될 때만 계속해서 코드를 실행할 수 있게 도와줍니다. Swift의 guard문을 사용한 예시는 다음과 같습니다.</p>
<pre><code class="language-swift">func processOrder(order: Order?) {
    guard let order = order else {
        print(&quot;주문 정보가 없습니다.&quot;)
        return
    }

    guard let items = order.items, !items.isEmpty else {
        print(&quot;주문 항목이 없습니다.&quot;)
        return
    }

    guard let paymentInfo = order.paymentInfo else {
        print(&quot;결제 정보가 없습니다.&quot;)
        return
    }

    guard let shippingAddress = order.shippingAddress else {
        print(&quot;배송 주소가 없습니다.&quot;)
        return
    }

    // 주문 처리 로직 실행
    print(&quot;주문을 처리합니다.&quot;)
}</code></pre>
<h1 id="대칭으로-맞추기">대칭으로 맞추기</h1>
<p>코드는 유기체처럼 성장합니다. 시간이 지남에 따라 새로운 기능과 요구 사항에 맞추어 변화하고 확장되며, 이러한 과정에서 대칭성을 유지하는 것은 유지보수성과 가독성을 높이는 중요한 요소가 됩니다. <strong>코드의 대칭성은 코드의 일관성을 유지하고, 코드를 읽는 사람에게 직관적인 구조를 제공하여 효율적인 개발을 가능</strong>하게 합니다.</p>
<p><strong>대칭적인 코드란, 동일한 동작을 하는 코드를 여러 가지 방식으로 작성하기보다는, 하나의 일관된 방식으로 통일하여 작성하는 것</strong>을 의미합니다. 이로 인해 코드는 읽기 쉬워지고, 확장하기 용이해지며, 팀원 간의 협업 효율성도 높아집니다.</p>
<h2 id="예시-사용자의-나이에-따라-다른-메시지를-표시하는-기능">예시: 사용자의 나이에 따라 다른 메시지를 표시하는 기능</h2>
<p>다양한 방식으로 동일한 동작을 구현할 수 있지만, 코드 스타일의 일관성은 코드의 품질에 직결됩니다. 아래 예시는 사용자의 나이에 따라 다른 메시지를 출력하는 코드의 여러 구현 방식을 보여줍니다.</p>
<h3 id="1-ifelse-문을-사용한-구현">1. if…else 문을 사용한 구현</h3>
<pre><code class="language-ts">function getUserCategory(age) {
  if (age &gt;= 18) {
    return &quot;성인입니다.&quot;;
  } else if (age &gt;= 13) {
    return &quot;청소년입니다.&quot;;
  } else {
    return &quot;어린이입니다.&quot;;
  }
}

console.log(getUserCategory(20)); // 출력: 성인입니다.</code></pre>
<h3 id="2-삼항-연산자를-사용한-구현">2. 삼항 연산자를 사용한 구현</h3>
<pre><code class="language-ts">function getUserCategory(age) {
  return age &gt;= 18
    ? &quot;성인입니다.&quot;
    : age &gt;= 13
    ? &quot;청소년입니다.&quot;
    : &quot;어린이입니다.&quot;;
}

console.log(getUserCategory(20)); // 출력: 성인입니다.</code></pre>
<h3 id="3-switch-문을-사용한-구현">3. switch 문을 사용한 구현</h3>
<pre><code class="language-ts">function getUserCategory(age) {
  switch (true) {
    case age &gt;= 18:
      return &quot;성인입니다.&quot;;
    case age &gt;= 13:
      return &quot;청소년입니다.&quot;;
    default:
      return &quot;어린이입니다.&quot;;
  }
}

console.log(getUserCategory(20)); // 출력: 성인입니다.</code></pre>
<p>위 세 가지 예시는 모두 동일한 결과를 반환하지만, 각각 다른 방식으로 구현되어 있습니다. 대칭적으로 맞춘 코드는 이러한 다양한 구현 방식 중 하나를 선택하여 일관된 코드 스타일을 유지하는 것을 목표로 합니다.</p>
<h2 id="대칭적인-코드가-중요한-이유">대칭적인 코드가 중요한 이유</h2>
<p>대칭적인 코드 작성은 코드베이스에서 중요한 역할을 합니다. 코드의 대칭성은 코드의 일관성을 높여 유지보수가 쉬워지고, 확장성이 커지며, 협업 시 불필요한 충돌을 줄입니다.</p>
<ol>
<li><strong>가독성 향상</strong>: 대칭적인 코드는 일정한 패턴을 따르므로, 코드가 어떻게 동작할지 쉽게 예측할 수 있습니다. 이는 코드를 빠르게 이해하는 데 도움이 됩니다.</li>
<li><strong>유지보수성 향상</strong>: 일관된 코드 구조는 수정이나 기능 추가 시 혼란을 줄이고, 코드의 흐름을 유지하는 데 도움이 됩니다. 대칭적으로 작성된 코드는 한눈에 파악할 수 있기 때문에 버그 발생 가능성도 줄어듭니다.</li>
<li><strong>팀 생산성 증가</strong>: 팀 내에서 합의된 코드 스타일을 따르는 것은 협업의 효율성을 높입니다. 서로 다른 방식으로 작성된 코드는 이해하는 데 시간이 걸리지만, 대칭적으로 작성된 코드는 모든 팀원이 동일한 규칙을 적용하므로 작업 속도가 빨라집니다.</li>
</ol>
<h2 id="action-item-1">Action Item</h2>
<ol>
<li><strong>일관된 코드 스타일을 유지하자</strong>
코드 작성 시, 여러 가지 구현 방식을 혼합하지 않고 팀 내에서 합의된 스타일 가이드를 따르자. 이를 통해 코드가 일관된 구조를 유지하고, 가독성을 높이자.</li>
<li><strong>동일한 로직은 하나의 방식으로 통일하자</strong>
같은 기능을 수행하는 코드가 여러 방식으로 작성되어 있을 때, 한 가지 일관된 방식을 선택해 통일하자. 특히 조건문이나 반복문에서 사용할 패턴을 미리 정의해 두는 것이 중요하다.</li>
<li><strong>확장성과 가독성을 고려한 선택을 하자</strong>
간결하면서도 확장성 있는 코드 작성 방식을 선택하여, 코드의 가독성과 유지보수성을 높이자. 복잡한 로직에서도 간단한 구문을 사용해 복잡도를 낮추자.</li>
<li><strong>ESLint와 Prettier로 코드 스타일을 강제하자</strong>
대칭성을 유지하려면 ESLint와 Prettier 같은 도구를 활용하여 자동으로 코드 스타일을 일관되게 맞추자. 팀 내에서 설정된 규칙에 맞춰 ESLint를 설정하고, Prettier로 코드 형식을 일관되게 적용하여 대칭성을 유지하자.</li>
<li><strong>코드 리뷰를 통해 일관성을 체크하자</strong>
코드 리뷰는 대칭적인 코드 스타일을 유지하는 데 중요한 역할을 한다. 팀원들이 작성한 코드가 스타일 가이드와 일치하는지 확인하고, 일관성을 유지할 수 있도록 피드백을 주고받자.</li>
</ol>
<h1 id="읽는-순서">읽는 순서</h1>
<h2 id="읽기-좋은-순서로-다시-정렬하기">읽기 좋은 순서로 다시 정렬하기</h2>
<p>코드를 작성할 때 읽기 좋은 순서로 정렬하는 것은 매우 중요합니다. <strong>코드를 읽는 사람이 논리적인 흐름에 따라 코드를 쉽게 이해할 수 있기 때문</strong>입니다. 하지만 <strong>주의할 점은 순서를 정렬하면서 함수의 동작이나 세부 구현을 변경하지 않아야 한다는 것</strong>입니다. 코드를 정리할 때는 코드의 기능을 그대로 유지하면서 순서만 변경해야 합니다.</p>
<h2 id="예시-유효성-검사-순서-정렬">예시: 유효성 검사 순서 정렬</h2>
<p>차례대로 이름, 이메일, 주소에 대한 정보를 수집하는 <code>form</code>이 있다고 가정해 보겠습니다. 각 필드에 대해 유효성 검사를 수행하는 코드가 있을 때, 입력 순서에 맞춰 코드를 정리하는 것이 가독성에 좋습니다.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/14043767-f171-4df0-bc13-fd023db586cf/image.jpeg" alt=""></p>
<h3 id="순서가-맞지-않는-코드">순서가 맞지 않는 코드</h3>
<pre><code class="language-ts">const isEmailValid = validateEmail(email);
const isAddressValid = validateAddress(address);
const isNameValid = validateName(name);

// 유효성 검사 로직
if (isEmailValid) {
  console.log(&quot;이메일이 유효합니다.&quot;);
}

if (isAddressValid) {
  console.log(&quot;주소가 유효합니다.&quot;);
}

if (isNameValid) {
  console.log(&quot;이름이 유효합니다.&quot;);
}</code></pre>
<h4 id="문제점">문제점:</h4>
<ul>
<li>입력받는 순서는 이름 → 이메일 → 주소인데, 유효성 검사 순서는 이메일 → 주소 → 이름으로 코드가 작성되어 있습니다.</li>
<li>입력 순서와 유효성 검사 순서가 맞지 않으면, 코드를 읽는 사람이 혼란스러워질 수 있습니다.</li>
</ul>
<h3 id="순서를-맞춘-코드">순서를 맞춘 코드</h3>
<pre><code class="language-ts">const isNameValid = validateName(name);
const isEmailValid = validateEmail(email);
const isAddressValid = validateAddress(address);

// 유효성 검사 로직
if (isNameValid) {
  console.log(&quot;이름이 유효합니다.&quot;);
}

if (isEmailValid) {
  console.log(&quot;이메일이 유효합니다.&quot;);
}

if (isAddressValid) {
  console.log(&quot;주소가 유효합니다.&quot;);
}</code></pre>
<h4 id="개선점">개선점:</h4>
<ul>
<li>입력 순서와 코드의 순서를 일치시켜 가독성을 높였습니다.</li>
<li>이름 → 이메일 → 주소의 순서대로 유효성 검사를 수행하여 코드의 흐름이 더 자연스러워졌습니다.</li>
<li>동작은 그대로 유지하면서 코드의 논리적인 흐름을 명확하게 정리했습니다.</li>
</ul>
<h3 id="함수-정의-순서-맞추기">함수 정의 순서 맞추기</h3>
<p>추가로, 각 필드에 대한 유효성 검사를 함수로 정의한다면, 함수 정의 순서도 입력 순서에 맞추는 것이 좋습니다. 함수의 정의 순서가 입력 흐름과 일치하면, 전체 코드의 일관성이 더해져 읽기 쉽고 직관적인 코드가 됩니다.</p>
<pre><code class="language-ts">function validateName(name: string): boolean {
  // 이름 유효성 검사 로직
  return name.length &gt; 0;
}

function validateEmail(email: string): boolean {
  // 이메일 유효성 검사 로직
  const emailRegex = /\S+@\S+\.\S+/;
  return emailRegex.test(email);
}

function validateAddress(address: string): boolean {
  // 주소 유효성 검사 로직
  return address.length &gt; 5;
}</code></pre>
<h2 id="주의해야-할-것-1">주의해야 할 것</h2>
<p>코드를 보기 좋은 순서로 바꾸는 것이 중요하지만, 순서에 민감한 코드의 동작을 변경해 사이드 이펙트가 발생하지 않도록 주의해야 합니다. 코드의 기능은 그대로 유지하고 순서만 정리하는 것이 핵심입니다.</p>
<p>이 과정에서 코드의 정리를 지나치게 복잡하게 하거나 순서를 맞추려는 욕심 때문에 기존 코드의 동작을 바꾸는 실수를 범하지 않도록 해야 합니다. <strong>Tidy First?</strong>에서 강조하는 정리의 원칙은, 코드의 동작을 변경하지 않고 일관된 기준에 따라 구조를 정돈하는 것입니다. 따라서 본인의 기준을 명확히 정하고 그 기준에 따라 일관되게 정리하는 것이 중요합니다.</p>
<h2 id="action-item-2">Action Item</h2>
<ol>
<li><strong>코드를 읽기 좋은 순서로 정렬하자</strong>
입력 순서나 처리 순서와 일치하도록 코드를 정렬하여 가독성을 높이자. 논리적인 흐름에 맞게 코드를 작성하면 유지보수성과 협업 효율이 향상됩니다.</li>
<li><strong>코드를 정리할 때 함수의 동작은 변경하지 말자</strong>
순서를 정렬하면서 코드의 기능이 변경되지 않도록 주의하자. 순서는 정리하되, 동작은 그대로 유지해야 합니다.</li>
<li><strong>함수 정의 순서도 일관성을 맞추자</strong>
함수의 정의 순서도 입력과 처리 흐름에 맞춰 정리하여, 전체적인 코드의 일관성을 유지하자. 코드를 읽는 사람이 쉽게 흐름을 파악할 수 있도록 코드를 구성하자.</li>
<li><strong>사이드 이펙트를 일으키지 않도록 주의하자</strong>
순서 정리 중 코드 동작을 바꾸지 말고, 불필요한 변화가 발생하지 않도록 해야 합니다. 정리의 핵심은 순서만 변경하는 것이지, 코드를 새로 작성하는 것이 아닙니다.</li>
</ol>
<h1 id="설명하는-변수와-상수">설명하는 변수와 상수</h1>
<p>코드를 더 명확하고 가독성 좋게 만들기 위해서는 설명하는 변수와 상징적인 상수를 사용하는 것이 중요합니다. 이는 코드의 의도를 더 명확하게 표현하여 유지보수성을 높이고, 코드 수정 시에도 쉽게 이해할 수 있도록 돕습니다. 이 과정에서 표현식의 의도와 리터럴 값을 설명하는 이름으로 치환하는 것이 핵심입니다.</p>
<h2 id="설명하는-변수">설명하는 변수</h2>
<p>설명하는 변수란, 복잡하거나 의도가 분명하지 않은 표현식을 변수로 추출하여 이름을 통해 그 목적을 명확히 하는 것을 의미합니다. 코드가 복잡해질수록 설명하는 변수는 가독성을 크게 향상시킵니다.</p>
<h3 id="예시">예시</h3>
<p>다음 코드는 <code>query</code>가 비어있는지를 확인하는 로직입니다. 하지만 코드만으로는 그 의도를 명확히 파악하기 어렵습니다. 이 복잡한 표현을 설명하는 변수로 치환해봅시다.</p>
<h4 id="의도가-드러나지-않는-코드">의도가 드러나지 않는 코드</h4>
<pre><code class="language-ts">useEffect(() =&gt; {
  if (Object.keys(query).length === 0) {
    return;
  }

  foo();
}, [query]);</code></pre>
<h4 id="의도가-드러나는-코드">의도가 드러나는 코드</h4>
<pre><code class="language-ts">useEffect(() =&gt; {
  const isInitialPageLoad = Object.keys(query).length === 0;

  if (isInitialPageLoad) {
    return;
  }

  foo();
}, [query]);</code></pre>
<h3 id="변경으로-인한-개선점">변경으로 인한 개선점</h3>
<ol>
<li><strong>가독성 향상:</strong>
<code>isInitialPageLoad</code>라는 변수명을 사용하여 <code>query</code>가 비어있는 상태가 페이지의 첫 진입을 의미한다는 의도를 명확히 했습니다. 이를 통해 코드를 읽는 사람은 이 조건이 페이지 첫 로드를 확인하는 것임을 쉽게 파악할 수 있습니다.</li>
<li><strong>유지보수성:</strong>
코드가 더 복잡해지거나 조건이 변경되어야 할 때, 표현식 대신 <code>isInitialPageLoad</code> 변수만 수정하면 되므로 변경이 더 쉬워집니다.</li>
<li><strong>의미 전달:</strong>
복잡한 조건식을 설명하는 변수로 분리함으로써 코드의 의도를 명확하게 전달할 수 있습니다.</li>
</ol>
<h2 id="설명하는-상수">설명하는 상수</h2>
<p>설명하는 상수는 숫자나 문자열과 같은 리터럴 값을 의미를 명확하게 드러내는 상수로 변환하는 것을 말합니다. 리터럴 값을 설명하는 상수로 바꾸면 가독성과 유지보수성이 크게 향상되며, 코드 곳곳에서 같은 값을 반복적으로 사용할 때 발생하는 오류도 방지할 수 있습니다.</p>
<h3 id="사례">사례</h3>
<p>리터럴 상수인 <code>4_800</code>과 <code>6_240</code>을 설명하는 상수로 변경하여 코드의 의도를 명확히 드러내 보겠습니다.</p>
<h4 id="의도가-드러나지-않는-코드-1">의도가 드러나지 않는 코드</h4>
<pre><code class="language-ts">function calculateTaxiFare(isNight: boolean): number {
  if (isNight) {
    return 6_240;
  }

  return 4_800;
}</code></pre>
<h4 id="의도가-드러나는-코드-1">의도가 드러나는 코드</h4>
<pre><code class="language-ts">const TAXI_BASE_FARE = 4_800;
const TAXI_NIGHT_SURCHARGE_FARE = 6_240;

function calculateTaxiFare(isNight: boolean): number {
  if (isNight) {
    return TAXI_NIGHT_SURCHARGE_FARE;
  }

  return TAXI_BASE_FARE;
}</code></pre>
<h3 id="변경으로-인한-개선점-1">변경으로 인한 개선점</h3>
<ol>
<li><strong>가독성 향상:</strong>
상수명을 통해 숫자 값이 의미하는 바를 명확히 알 수 있습니다. <code>TAXI_BASE_FARE</code>와 <code>TAXI_NIGHT_SURCHARGE_FARE</code>는 각각 기본 요금과 야간 할증 요금임을 바로 파악할 수 있습니다.</li>
<li><strong>유지보수성:</strong>
요금이 변경되어야 할 경우, 상수만 수정하면 되므로 여러 곳의 코드를 변경할 필요 없이 유지보수가 수월해집니다.</li>
<li><strong>의미 전달:</strong>
상수를 통해 코드가 택시 요금을 계산하고 있음을 명확히 전달할 수 있습니다. 상수화된 값은 설명적이기 때문에 코드의 의도를 쉽게 파악할 수 있습니다.</li>
</ol>
<h3 id="상수화의-한계">상수화의 한계</h3>
<p>모든 값을 상수화하는 것이 좋은 것은 아닙니다. 예를 들어, <strong><code>const ONE = 1;</code>과 같은 상수는 의미 전달에 전혀 도움이 되지 않으므로</strong> 지양해야 합니다. 상수화는 코드에서 의미를 명확하게 전달할 수 있을 때만 적용해야 하며, 단순히 리터럴 값을 치환하는 것이 상수화의 목적이 되어서는 안 됩니다.</p>
<p>또한, 관련된 상수는 한 곳에 모아서 관리하는 것도 고려해보면 좋습니다. 이렇게 정리한다면 같이 변경되거나 이해해야 할 상수들을 쉽게 관리하고 유지보수할 수 있도록 도와줍니다.</p>
<h2 id="action-item-3">Action Item</h2>
<ol>
<li><strong>설명하는 변수를 적극적으로 사용하자</strong>
복잡한 조건식이나 표현식은 설명하는 변수로 추출하여 코드의 의도를 명확히 하고 가독성을 높이자.</li>
<li><strong>리터럴 상수는 상징적인 상수로 바꾸자</strong>
코드에 자주 등장하는 리터럴 값들은 의미를 명확하게 설명하는 상수로 변환하여 유지보수성과 가독성을 향상시키자.</li>
<li><strong>코드 정리와 동작 변경은 별도로 관리하자</strong>
코드 정리에 대한 커밋과 동작 변경에 대한 커밋을 명확히 분리하여 관리하자. 이를 통해 변경 사항을 추적하고 코드 리뷰를 효율적으로 진행할 수 있다.</li>
<li><strong>관련된 상수를 한곳에 모으자</strong>
같이 변경되거나 관련된 상수는 한곳에 모아 관리하여, 나중에 수정할 때 코드 전체에 미치는 영향을 최소화하자.</li>
</ol>
<h1 id="도우미-함수-추출">도우미 함수 추출</h1>
<p>코드를 더 깔끔하고 목적에 맞게 작성하려면 도우미 함수를 추출하는 것이 중요합니다. <strong>도우미 함수는 목적이 명확하고, 다른 코드와 상호작용이 적은 코드 블록을 함수로 분리하는 방법</strong>입니다. 중요한 점은 도우미 함수의 이름은 작동 방식이 아니라 그 목적을 설명하는 이름으로 짓는 것입니다. 이를 통해 코드의 가독성이 향상되고, 유지보수와 코드 재사용이 용이해집니다.</p>
<p>이 작업은 리팩터링의 기본적인 원칙인 메서드 추출과 유사하며, 코드의 복잡성을 줄이고 변경에 유연한 설계를 가능하게 합니다.</p>
<h2 id="예시-시간적-결합을-표현하는-코드">예시: 시간적 결합을 표현하는 코드</h2>
<p><strong>시간적 결합이란, 특정 로직이 특정 순서로 실행되어야 할 때 나타나는 상황을 의미</strong>합니다. 이러한 로직은 순서가 매우 중요하기 때문에 코드를 도우미 함수로 추출하여 그 순서 의도를 명확하게 표현하는 것이 좋습니다.</p>
<h3 id="흐름이-명확하게-드러나지-않는-코드">흐름이 명확하게 드러나지 않는 코드</h3>
<pre><code class="language-ts">function handleFileUpload(file) {
  const isValid = validateFile(file);

  if (!isValid) {
    throw new Error(&quot;Invalid file&quot;);
  }

  uploadFile(file)
    .then(() =&gt; {
      console.log(&quot;File uploaded successfully&quot;);
      finalizeUpload();
    })
    .catch((error) =&gt; {
      console.error(&quot;Upload failed&quot;, error);
    });
}

function validateFile(file) {
  // 파일 유효성 검사 로직
  return file.size &lt; 5000000; // 5MB 미만 파일만 허용
}

function uploadFile(file) {
  // 파일 업로드 로직
  return new Promise((resolve, reject) =&gt; {
    // 비동기 업로드 시뮬레이션
    setTimeout(() =&gt; {
      resolve();
    }, 2000);
  });
}

function finalizeUpload() {
  console.log(&quot;Upload finalized&quot;);
}</code></pre>
<h3 id="흐름이-드러나는-코드">흐름이 드러나는 코드</h3>
<pre><code class="language-ts">async function handleFileUpload(file) {
  try {
    if (!isFileValid(file)) {
      throw new Error(&quot;Invalid file&quot;);
    }

    await processFileUpload(file);
    onFileUploadSuccess();
  } catch (error) {
    onFileUploadFailure(error);
  }
}

function isFileValid(file) {
  return file.size &lt; 5000000; // 5MB 미만 파일만 허용
}

async function processFileUpload(file) {
  await uploadFile(file);
}

function uploadFile(file) {
  return new Promise((resolve, reject) =&gt; {
    // 비동기 업로드 시뮬레이션
    setTimeout(() =&gt; {
      resolve();
    }, 2000);
  });
}

function onFileUploadSuccess() {
  console.log(&quot;File uploaded successfully&quot;);
  finalizeUpload();
}

function onFileUploadFailure(error) {
  console.error(&quot;Upload failed&quot;, error);
}

function finalizeUpload() {
  console.log(&quot;Upload finalized&quot;);
}</code></pre>
<h3 id="변경으로-인한-개선점-2">변경으로 인한 개선점</h3>
<ol>
<li><strong>시간적 결합 표현:</strong>
파일 검증 → 파일 업로드 → 성공 또는 실패 처리 → 완료 과정이 순차적으로 진행되어 <strong>시간적 결합의 흐름</strong>이 더 분명하게 드러납니다.</li>
</ol>
<h2 id="예시-복잡한-메인-로직을-헬퍼-함수로-분리하는-경우">예시: 복잡한 메인 로직을 헬퍼 함수로 분리하는 경우</h2>
<p>도우미 함수는 거대한 함수에서 복잡한 로직을 분리해 함수의 가독성과 유지보수성을 크게 향상시킬 수 있습니다. 거대한 함수에 여러 로직이 혼합되어 있으면 함수의 흐름을 파악하기 어려워지는데, 이를 같은 추상화 레벨로 맞춘 헬퍼 함수로 분리하면 함수가 훨씬 명확해집니다.</p>
<h3 id="복잡하고-가독성이-낮은-메인-함수">복잡하고 가독성이 낮은 메인 함수</h3>
<pre><code class="language-ts">function processOrder(order) {
  // 1. 유효성 검사
  if (order.items.length === 0) {
    throw new Error(&quot;Empty order&quot;);
  }

  // 2. 주문 처리
  console.log(&quot;Processing order:&quot;, order);

  // 3. 주문 완료 처리
  console.log(&quot;Order completed&quot;);
}</code></pre>
<h3 id="명확하고-가독성이-높은-메인-함수">명확하고 가독성이 높은 메인 함수</h3>
<pre><code class="language-ts">function processOrder(order) {
  validateOrder(order);
  handleOrderProcessing(order);
  completeOrder();
}

function validateOrder(order) {
  if (order.items.length === 0) {
    throw new Error(&quot;Empty order&quot;);
  }
}

function handleOrderProcessing(order) {
  console.log(&quot;Processing order:&quot;, order);
}

function completeOrder() {
  console.log(&quot;Order completed&quot;);
}</code></pre>
<h3 id="변경으로-인한-개선점-3">변경으로 인한 개선점</h3>
<ol>
<li><strong>가독성 향상:</strong>
<code>processOrder</code> 함수는 이제 세부 구현을 신경 쓰지 않고 주문 처리 흐름을 쉽게 이해할 수 있습니다. 각 로직을 헬퍼 함수로 분리해 메인 함수가 간결해졌습니다.</li>
<li><strong>유지보수성:</strong>
<code>validateOrder</code>, <code>handleOrderProcessing</code>, <code>completeOrder</code>와 같은 헬퍼 함수로 분리하여 각 부분을 독립적으로 수정할 수 있습니다. 예를 들어, 유효성 검사 로직만 변경해야 할 경우 <code>validateOrder</code> 함수만 수정하면 됩니다.</li>
<li><strong>메인 로직 가독성 개선:</strong>
헬퍼 함수로 복잡한 로직을 분리하면 메인 함수 상단에 메인 로직을 배치할 수 있습니다. 이렇게 하면 메인 로직이 바로 드러나고, 헬퍼 함수들은 아래에 배치되어 있어 파일을 열었을 때 로직의 흐름을 쉽게 파악할 수 있습니다. 큰 프로젝트일수록 이 방식은 메인 로직을 빠르게 이해하는 데 유용합니다.</li>
</ol>
<h2 id="도우미-함수의-장점">도우미 함수의 장점</h2>
<ol>
<li><strong>목적이 명확한 함수 추출:</strong>
도우미 함수는 특정 코드 블록을 분리하여 그 목적에 맞는 이름을 부여함으로써 코드의 의도를 명확히 드러냅니다. 이를 통해 코드를 읽는 사람은 각 함수가 무엇을 위해 존재하는지를 쉽게 이해할 수 있습니다.</li>
<li><strong>코드의 유연성 향상:</strong>
도우미 함수는 특정 로직을 분리해 두었기 때문에, 변경이 필요할 때 해당 함수만 수정하면 됩니다. 이를 통해 코드 전체에 영향을 미치지 않고 필요한 부분만 유연하게 수정할 수 있습니다.</li>
<li><strong>시간적 결합 표현:</strong>
실행 순서가 중요한 경우, 도우미 함수로 로직을 추출하면 실행 순서를 더 명확히 표현할 수 있습니다.</li>
</ol>
<h2 id="action-item-4">Action Item</h2>
<ol>
<li><strong>코드 블록을 목적에 따라 함수로 추출하자</strong>
코드가 복잡해지거나 반복적으로 사용되는 블록은 도우미 함수로 추출하여 가독성을 높이고 유지보수를 쉽게 하자.</li>
<li><strong>도우미 함수는 작동 방식이 아닌 목적에 맞는 이름을 짓자</strong>
함수의 이름은 작동 방식이 아니라 무엇을 하는지에 따라 명확히 지어야 합니다. 이를 통해 코드의 의도가 더 명확하게 드러납니다.</li>
<li><strong>시간적 결합이 필요한 코드에는 도우미 함수로 추출하자</strong>
실행 순서가 중요한 코드 블록은 순서의 의도를 명확히 하기 위해 도우미 함수로 추출하여 코드를 정리하자.</li>
</ol>
<h1 id="설명하는-주석과-불필요한-주석-지우기">설명하는 주석과 불필요한 주석 지우기</h1>
<p>코드를 작성할 때 주석을 적절하게 사용하는 것은 매우 중요합니다. 주석은 명확하지 않은 부분을 설명하거나 특별한 맥락을 기록하기 위해 사용되지만, 불필요한 주석은 오히려 혼란을 초래할 수 있습니다. 주석을 잘못 사용하면, 코드와 주석이 따로 놀게 되어 유지보수에 어려움을 겪을 수 있습니다.</p>
<h2 id="설명하는-주석">설명하는 주석</h2>
<p>설명하는 주석은 코드에서 의도가 명확하지 않은 부분이나 특별한 사정을 설명하는 데 사용됩니다. 코드를 읽는 사람이 해당 주석을 통해 코드의 의도나 맥락을 쉽게 이해할 수 있도록 돕는 것이 목적입니다.</p>
<h3 id="예시-1">예시</h3>
<pre><code class="language-ts">// antd에서는 명시적 `undefined` 할당이 필요하므로 `undefined`를 할당해주는 로직이 존재합니다
const someValue = condition ? undefined : value;</code></pre>
<p>위 주석은 특별한 맥락을 설명하고 있으며, 코드를 이해하는 데 중요한 정보를 제공합니다. 이런 경우 설명하는 주석이 코드의 가독성을 높이는 데 기여할 수 있습니다.</p>
<h2 id="불필요한-주석-지우기">불필요한 주석 지우기</h2>
<p><strong>불필요한 주석은 시간이 흐르면서 코드와 주석이 맞지 않게 되어 오히려 혼란을 야기</strong>할 수 있습니다. 주석은 코드의 변경에 따라 계속 업데이트되어야 하는데, 이를 놓치면 주석이 잘못된 정보를 전달할 수 있습니다.</p>
<h3 id="주석과-코드-불일치">주석과 코드 불일치</h3>
<pre><code class="language-ts">/**
 * @function calculateTotal
 * @description 세금과 선택적인 할인 금액을 포함한 총 금액을 반환합니다.
 * @param {number} price - 상품의 기본 가격.
 * @param {number} taxRate - 적용할 세율.
 * @param {number} discount - 적용할 할인 금액.
 * @returns {number} 세금과 할인을 적용한 후의 총 금액.
 */
function calculateTotal(
  price: number,
  taxRate: number,
  discount: number | string = 0
): number {
  const discountValue =
    typeof discount === &quot;string&quot; ? parseFloat(discount) : discount;
  return price * (1 + taxRate) - discountValue;
}</code></pre>
<h4 id="문제점-1">문제점:</h4>
<ul>
<li><strong>주석과 코드의 불일치:</strong> 함수의 <code>discount</code> 파라미터는 <code>number | string</code> 타입을 수용하도록 변경되었지만, tsdoc 주석에서는 여전히 <code>@param {number} discount</code>로 타입을 <code>number</code>로만 명시하고 있습니다.</li>
<li><strong>타입 중복 및 혼란:</strong> TypeScript 코드에 이미 타입 정보가 있는데, tsdoc 주석에 타입을 다시 명시하면 타입 변경 시 주석을 업데이트해야 하는 번거로움이 생기며, 업데이트를 놓치면 혼란을 야기합니다.</li>
</ul>
<h3 id="불필요한-주석-제거하여-주석과-코드를-일치">불필요한 주석 제거하여 주석과 코드를 일치</h3>
<p>주석이 코드와 일치하지 않거나 불필요한 경우, 주석을 삭제하거나 수정하는 것이 좋습니다. 특히, TypeScript의 타입 시스템이 이미 명확한 정보를 제공하는 경우라면 주석에서 타입 정보를 불필요하게 명시할 필요가 없습니다.</p>
<pre><code class="language-ts">/**
 * @function calculateTotal
 * @description 세금과 선택적인 할인 금액을 포함한 총 금액을 반환합니다.
 * @param price - 상품의 기본 가격.
 * @param taxRate - 적용할 세율.
 * @param discount - 선택적인 할인 금액.
 * @returns 세금과 할인을 적용한 후의 총 금액.
 */
function calculateTotal(
  price: number,
  taxRate: number,
  discount: number | string = 0
): number {
  const discountValue =
    typeof discount === &quot;string&quot; ? parseFloat(discount) : discount;
  return price * (1 + taxRate) - discountValue;
}</code></pre>
<ul>
<li><strong>주석에서 타입 정보 제거:</strong> tsdoc 주석에서 <code>{number}</code>와 같은 타입 정보를 제거하여, 코드와 주석 간의 타입 불일치 문제를 해결했습니다.</li>
<li><strong>TypeScript 타입 시스템 활용:</strong> 코드에 이미 타입 정보가 명시되어 있으므로, 주석에서는 타입을 반복할 필요가 없습니다. 이를 통해 타입 변경 시 주석을 따로 수정할 필요가 없어 유지보수가 용이해집니다.</li>
</ul>
<h2 id="action-item-5">Action Item</h2>
<ol>
<li><strong>불필요한 주석은 제거하자</strong>
코드가 명확하게 의도를 드러내는 경우, 불필요한 주석은 피하고 코드 자체로 설명될 수 있도록 하자. 주석이 오래되거나 코드와 맞지 않는 경우 삭제하는 것이 좋습니다.</li>
<li><strong>주석은 언제나 최신 상태로 유지하자</strong>
코드가 변경되면, 주석도 반드시 수정되어야 한다. 특히 타입스크립트와 같은 언어에서는 타입 정보를 주석에 굳이 명시하지 말고, 코드 자체에 의존하자.</li>
<li><strong>설명하는 주석을 사용하자</strong>
주석은 코드를 설명하기 위해 존재하므로, 명확하지 않은 부분만 설명하고, 코드 리뷰에서 질문을 받은 부분을 선제적으로 주석으로 남기는 습관을 들이자.</li>
<li><strong>TODO를 남겨 결함을 추적하자</strong>
코드를 수정해야 하는 부분을 발견했을 때, 주석에 TODO를 남겨 나중에 다시 돌아와 수정할 수 있도록 기록하자. 이를 통해 팀원들과도 작업 우선순위를 공유할 수 있다.</li>
</ol>
<h1 id="글을-마치며">글을 마치며</h1>
<p>코드 정리에 대한 개념들은 겉으로는 당연해 보일 수 있지만, 실제로 왜 이러한 정리가 필요한지 명확하게 설명하기는 쉽지 않습니다. <strong>Tidy First?</strong>를 읽으면서 코드 정리의 중요성을 깊이 이해하게 되었고, 이를 통해 동료 개발자들에게도 정리의 필요성을 조금 더 효과적으로 전달할 수 있게 되었습니다.</p>
<p>이 글에서는 <strong>보호구문, 대칭으로 맞추기, 읽는 순서, 설명하는 변수와 상수, 도우미 함수 추출, 그리고 설명하는 주석과 불필요한 주석 지우기</strong>와 같은 개념들을 살펴보았습니다. 각각의 개념은 코드의 가독성과 유지보수성을 향상시키는 데 큰 도움이 되며, 작은 변화로도 코드 품질에 큰 영향을 미칠 수 있습니다.</p>
<p>하지만 이 글에서 다루지 않은 내용들도 많습니다. 예를 들어, 선언과 초기화를 옮기기와 같은 주제는 프런트엔드 입장에서 공감하기 쉽지 않았습니다. 또한, 응집도 향상과 같은 주제들은 범위가 넓어 이번에는 포함하지 않았습니다. 이러한 내용들은 <strong>Tidy First?</strong>에서 자세히 다루고 있으니, 직접 책을 구매하여 읽어보시길 강력하게 추천드립니다. 책을 통해 더 깊이 있는 내용을 얻으실 수 있을 것입니다.</p>
<p>마지막으로, 앞으로 새로운 코드를 작성하거나 기존의 레거시 코드를 마주할 때, 이 글에서 소개한 액션 아이템들을 떠올리시는 순간이 있다면 좋겠습니다. 그리고 작은 정리의 습관이 모여 코드베이스 전체의 품질을 향상시키고, 팀의 생산성을 높일 수 있다면 더더욱 좋겠습니다. 코드 정리는 단순히 예쁘게 보이기 위한 것이 아니라, 더 나은 소프트웨어를 만들기 위한 중요한 과정이니까요.</p>
<p>내일은 코드에 새로운 기능을 추가하기 전에 <strong>“정리가 먼저인가?”</strong>를 떠올려보면 어떨까요?</p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미 후기] React Query / TanStack Query : React로 서버 상태 관리하기]]></title>
            <link>https://velog.io/@bo-like-chicken/%EC%9C%A0%EB%8D%B0%EB%AF%B8-%ED%9B%84%EA%B8%B0-React-Query-TanStack-Query-React%EB%A1%9C-%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@bo-like-chicken/%EC%9C%A0%EB%8D%B0%EB%AF%B8-%ED%9B%84%EA%B8%B0-React-Query-TanStack-Query-React%EB%A1%9C-%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 28 Apr 2024 02:42:30 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p>지난해 합류한 글또라는 멋진 동아리에서 제공받은 제휴 혜택 중 하나인  유데미 강의 제공으로 수강할 수 있게 된 <a href="https://www.udemy.com/course/best-javascript-data-structures/learn/quiz/5338781#overview">React Query / TanStack Query : React로 서버 상태 관리하기</a>의 후기입니다.</p>
<p>강의를 처음들어갔을 때는 <code>React-Query</code> <strong>v4</strong>의 내용을 다루고 있어 아쉬운 부분이 있었지만, 2024년 1월에 <strong>v5</strong>의 내용을 다루게 업데이트 되어 늦게나마 후기를 작성하게 되었습니다.</p>
<h1 id="강의-내용">강의 내용</h1>
<p>강의에서는 <code>React-Query</code>를 사용한 정말 다양한 활용법을 알려주고 있습니다. 조금 과장해서 <code>React-Query</code>로 할 수 있는 모든 것들을 다루고 있다고 표현해도 될 정도 입니다.</p>
<p>강의에서는 <code>Section</code>별로 미리 작성된 코드를 제공하고 그 코드를 기반을 설명하며 강의를 진행합니다. 따라서, 불필요하게 클론 코딩처럼 강의를 따라서 타이핑하지 않아도 되며 온전히 강의에 집중할 수 있습니다.</p>
<p>그리고 각 <code>Section</code>이 끝나면 코드 퀴즈와 요약으로 한번 더 개념을 확실하게 습득했는지를 확인하고 복습할 수 있습니다.</p>
<p>이어서 간단하게 &quot;React Query / TanStack Query : React로 서버 상태 관리하기&quot; 강의의 장단점을 소개해 드리겠습니다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>강의 형태로 들을 수 있는 React-Query 컨텐츠 중 가장 좋았습니다.</li>
<li>헷갈리는 개념들이나, 빈번하게 사용되는 응용 방법이 소개되어 있습니다.</li>
<li>제공되는 코드가 있기 때문에 불필요하게 타이핑에 시간을 쏟거나, 집중이 분산되지 않는 것이 좋았습니다.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li><code>React-Query</code>를 사용해 본 유저들에게는 다소 익숙한 내용이 주가 될 수 있습니다.</li>
<li><code>Suspense</code>를 활용한 예시가 없습니다.</li>
</ul>
<h1 id="글을-마치며">글을 마치며</h1>
<p><strong>정리하자면 이런 분들에게 이 강의를 추천드립니다.</strong></p>
<ul>
<li><code>React-Query</code>의 도입을 고려하시는 분</li>
<li><code>React-Query</code>를 사용하고 있으나 v4 버전에 머물고 계신 분</li>
<li><code>React-Query</code>를 사용하지만 단순 데이터 페칭으로 용도로 사용하고 있지만 조금 더 응용 개념을 배워보고 싶으신 분들</li>
</ul>
<p>글이 작성된 2024년 4월 28일 기준으로는 정가 99,000원에서 약 81% 할인된 19,000원으로 수강 가능하니 할인쿠폰 등을 적극적으로 노려서 구매해 보시기를 추천드립니다. </p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/6c6ffef2-ea26-443b-9dd2-17e94000e68d/image.png" alt="할인가격 이미지"></p>
<p>다만, React-Query v5의 사용에 익숙하신 분들이라면 아쉬운 내용일 수 있으니 커리큘럼을 잘 읽어보시고 수강하시는 것을 추천드립니다.</p>
<p>하지만, 커리큘럼에 처음보는 개념이 등장하거나, 커뮤니티에 많은 질문이 올라오는 <code>isLoading</code>과 <code>isFetching</code>의 차이, <code>statleTime</code>과 <code>gcTime</code>의 차이 등 중요한 개념들을 명확히 모른다면 할인 가격에 수강하는 것은 꽤나 괜찮은 것 같습니다.</p>
<blockquote>
<p>글또를 통해서 유데미의 강의를 수강할 수 있는 기회를 얻게 되어 작성하게 된 글입니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[작고 소중한 Visual Studio Code 확장프로그램 만들기]]></title>
            <link>https://velog.io/@bo-like-chicken/%EC%9E%91%EA%B3%A0-%EC%86%8C%EC%A4%91%ED%95%9C-Viusal-Studio-Code-%ED%99%95%EC%9E%A5%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@bo-like-chicken/%EC%9E%91%EA%B3%A0-%EC%86%8C%EC%A4%91%ED%95%9C-Viusal-Studio-Code-%ED%99%95%EC%9E%A5%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 13 Apr 2024 09:20:44 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<blockquote>
<p>이거 너무 반복되는 일 같은데?</p>
</blockquote>
<p>개발자라면 누구나 보일러플레이트를 줄이고 DRY 원칙에 따라 코드를 작성하려고 노력합니다. 그러나 개발 전체 프로세스를 고려했을 때, 단순히 코드 작성뿐만 아니라 반복되는 다른 작업도 줄이는 것이 중요함을 깨닫습니다. 가장 대표적인 예로, 반복되는 Pull Request에 사용될 템플릿을 미리 만들어 반복을 방지할 수 있습니다.</p>
<p>오늘은 개발 과정에서 느낀 불편함을 해소하기 위해 확장 프로그램을 만든 경험과 그 과정에서 얻은 교훈을 간략하게 공유해 보고자 합니다.</p>
<h1 id="문제">문제</h1>
<p>프로젝트를 진행하면서 컴포넌트를 관리하기 위해 다음과 같이 모듈화된 구조를 사용하곤 합니다:</p>
<pre><code class="language-bash">│── Button
│   ├── Button.tsx
│   ├── index.ts
│   └── Button.styles.ts</code></pre>
<p>이 구조를 사용하면 최소 네 번의 폴더 또는 파일 생성이 필요하며, 파일명을 잘못 입력하는 실수가 발생할 경우 수고롭게 이름을 수정해야 했습니다. 이런 문제로 인해, 급하게 기능을 추가할 때 이상적인 폴더 구조가 제대로 지켜지지 않는 경우가 종종 있었습니다.</p>
<p>이러한 불편함을 해결하기 위해 Visual Studio Code 확장 프로그램을 만들기로 결정했습니다.</p>
<h1 id="과정">과정</h1>
<h2 id="프로젝트-생성">프로젝트 생성</h2>
<p>Visual Studio에서는 확장 프로그램의 생성을 아래와 같이 쉽게 안내합니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/97366daf-01b6-4268-b7c3-7656312ec1c5/image.png" alt="Your First Extension Page Capture"></p>
<p><code>yo code</code>를 실행하면 다음과 같은 텍스트 기반 사용자 인터페이스(TUI) 창을 통해 설정할 수 있습니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/817a8a5c-0b4f-4188-ba93-5b1efea61e6c/image.png" alt="yo code in terminal"></p>
<p>이후 물어보는 몇 가지의 질문에 답하면 프로젝트가 성공적으로 만들어지게 됩니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/a4f426d7-31e3-413d-bc68-db114fbfd4db/image.png" alt="project example"></p>
<p>프로젝트 생성 후, <code>vsc-extension-quickstart.md</code> 문서를 참조하면 프로젝트의 구성요소와 디버깅 방법을 충분히 이해할 수 있습니다.</p>
<h2 id="기능-구현">기능 구현</h2>
<p>확장프로그램의 기능은 <code>extension.ts</code>에서 구현할 수 있습니다. 저는 다음과 같이 기능을 정의하고 개발했습니다.</p>
<p><strong>1. 사용자가 파일이나 폴더에서 보조 클릭을 할 경우, 커맨드를 실행할 수 있는 메뉴가 보인다.</strong></p>
<pre><code class="language-json">  // package.json

  &quot;contributes&quot;: {
      &quot;commands&quot;: [
        {
          &quot;command&quot;: &quot;dryfs.helloWorld&quot;,
          &quot;title&quot;: &quot;Hello World&quot;
        },
        {
          &quot;command&quot;: &quot;dryfs.makeMyFolder&quot;,
          &quot;title&quot;: &quot;Make My Folder&quot;
        }
      ],
      &quot;menus&quot;: {
        &quot;explorer/context&quot;: [
          {
            &quot;command&quot;: &quot;dryfs.makeMyFolder&quot;,
            &quot;when&quot;: &quot;explorerViewletVisible&quot;
          }
        ]
      },</code></pre>
<p><strong>2. 메뉴를 클릭시 폴더명을 유저에게 입력받을 수 있는 창이 나온다.</strong></p>
<pre><code class="language-ts">  // extension.ts
  const folderName = await vscode.window.showInputBox({
          prompt: &quot;Enter the folder name&quot;,
    });</code></pre>
<p><strong>3. 입력받은 폴더명에 따라 생성된 폴더 내에  ${입력된 값}.tsx, index.ts, styles.ts, 그리고 테스트 파일 *.test.tsx을 추가한다.</strong></p>
<pre><code class="language-ts">      // extension.ts
const newFolderPath = path.join(folderPath, folderName);
    if (!fs.existsSync(newFolderPath)) {
        fs.mkdirSync(newFolderPath);
        }

    const componentTemplate = isIncludeReactImport
              ? generateImportReactComponentTemplate(folderName)
              : generateComponentTemplate(folderName);

    const indexTemplate = generateIndexTemplate(folderName);

    fs.writeFileSync(
              path.join(newFolderPath, `${folderName}.tsx`),
              componentTemplate
    );
    fs.writeFileSync(path.join(newFolderPath, &quot;index.ts&quot;), indexTemplate);
    fs.writeFileSync(path.join(newFolderPath, &quot;styles.ts&quot;), &quot;&quot;);
    fs.writeFileSync(path.join(newFolderPath, `${folderName}.test.tsx`), &quot;&quot;);</code></pre>
<p><strong>4. 생성완료 후 메시지가 보여진다.</strong></p>
<pre><code class="language-ts">  vscode.window.showInformationMessage(
          &quot;Folder and files created successfully!&quot;
    );</code></pre>
<p>이후, 트러블 슈팅 과정을 통해 폴더에서 보조클릭 했을 경우, 파일에서 보조클릭을 클릭 했을 경우등에 대한 디테일을 추가하며 확장프로그램의 구현을 마쳤습니다.</p>
<h2 id="확장프로그램-사용-및-출시">확장프로그램 사용 및 출시</h2>
<p>먼저, <code>vsce package</code> 명령어로 확장 프로그램을 빌드합니다. 이 명령어 실행 후, 프로젝트 내에 [확장 프로그램][버전].vsix 파일이 생성됩니다.</p>
<p>준비된 <code>.visx</code> 파일로 확장프로그램을 사용하는 방법은 크게 두 가지로 나뉘어 집니다.</p>
<h3 id="1-vscode에-직접-설치">1. VSCode에 직접 설치</h3>
<p>VSCode의 확장 프로그램 탭(<code>command+shift+x</code>)에서 <code>install from VSIX...</code>를 선택하여 <code>.vsix</code> 파일을 설치합니다. <img src="https://velog.velcdn.com/images/bo-like-chicken/post/987b6e0f-6ad6-4390-9b01-0c863cfc86b8/image.png" alt="Intall from VSIX"></p>
<h3 id="2-market-place에-출시">2. Market Place에 출시</h3>
<p> <a href="https://marketplace.visualstudio.com/manage/createpublisher">여기</a>에서 Publisher 등록을 합니다.<img src="https://velog.velcdn.com/images/bo-like-chicken/post/841cd272-0f45-4e26-8285-541b84c04b98/image.png" alt="https://marketplace.visualstudio.com/manage/createpublisher"></p>
<p>등록된 Publisher 페이지에서 <code>.vsix</code> 파일을 업로드하여 출시할 수 있게 됩니다. 단, <code>package.json</code>에 <code>publisher</code> 정보가 포함되어 있어야 합니다.</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;dryfs&quot;,
  &quot;publisher&quot;: &quot;dryfs&quot;,
  ...
}</code></pre>
<p> 이후, 등록한 이메일을 통해 정상적으로 출시되었다는 안내를 받은 이후부터 다음과 같이 확장프로그램을 다운로드 받아 사용할 수 있습니다.
 <img src="https://velog.velcdn.com/images/bo-like-chicken/post/20596a80-bb3f-4ca7-baa5-89e7559b5dca/image.png" alt="Dont Repeat Your Folder Structure"></p>
<h1 id="글을-마치며">글을 마치며</h1>
<p>이 확장 프로그램을 개발함으로써 반복적인 클릭을 통해 폴더 구조를 만드는 번거로움을 한 번의 클릭으로 간소화할 수 있게 되었습니다. 또한, 개발 과정에서 다음과 같은 중요한 경험도 하게 되었습니다:</p>
<ol>
<li><p><strong>시멘틱 버저닝으로 라이브러리를 관리하는 경험:</strong>
시멘틱 버저닝의 개념은 익숙했지만, 확장 프로그램 개발을 통해 버전 관리의 실질적 중요성을 깊이 느꼈습니다. 이 경험은 향후 업데이트의 타이밍과 범위를 효과적으로 결정하는 데 크게 도움이 될 것입니다.</p>
</li>
<li><p><strong>시멘틱 버저닝을 <code>git tags</code>로 관리하는 경험:</strong>
<code>git tags</code>를 통한 버저닝 관리는 프로젝트의 각 버전을 명확히 식별하고 이력을 추적할 수 있게 해주었습니다. 이는 확장 프로그램의 버전 관리를 용이하게 하며, 앞으로 다른 프로젝트에서도 유용하게 사용할 수 있을 것 같습니다.</p>
</li>
<li><p><strong>’git tags’로 GitHub에서 릴리스를 출시하는 경험:</strong>
GitHub에서 직접 릴리스를 생성하면서, 각 릴리스에 자세한 설명과 문서를 첨부하는 것의 중요성을 깨달았습니다. 이는 다른 라이브러리의 릴리스 내용을 더욱 효과적으로 이해하고 파악할 수 있게 되었습니다.</p>
</li>
</ol>
<p>앞으로 확장 프로그램을 고도화하면서 다음과 같은 부분에 기대를 하고 있습니다:</p>
<ol>
<li><strong>기능 고도화:</strong>
사용자 경험을 개선하기 위해 다양한 기능을 추가할 계획입니다.</li>
</ol>
<ol start="2">
<li><strong>오픈소스 커뮤니티 창출:</strong>
세계 각지의 개발자들과 협력하여 오픈소스 프로젝트를 성장시킬 계획입니다.</li>
</ol>
<p>이 확장 프로그램이 문제를 겪고 계신 분들에게 도움이 되기를 바라며, 같은 수고스러움을 느껴보셨다면 한 번쯤 사용해 보셨으면 좋겠습니다. 그리고, 사용 중 개선할 점이 보인다면 GitHub를 통해 이슈를 등록해 주시거나 PR을 만들어 주심으로써 여러분과 협업할 기회를 얻을 수 있다면 더할 나위 없이 좋을 것 같습니다.</p>
<p>마지막으로, 확장 프로그램 개발이 생각보다 간단할 수 있다는 것을 알려드리고 싶습니다. 저 역시 처음 접하는 분야였지만, GPT와 같은 AI 도구를 활용하여 신속하게 MVP를 개발할 수 있었습니다. 여러분도 직접 확장 프로그램을 만들어보시면 개발 중 겪었던 불편함을 해결하고 새로운 경험을 얻을 수 있을 것입니다.</p>
<p>감사합니다.</p>
<h1 id="비고">비고</h1>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://code.visualstudio.com/api/get-started/your-first-extension">https://code.visualstudio.com/api/get-started/your-first-extension</a></p>
<h2 id="저장소">저장소</h2>
<p><a href="https://github.com/BO-LIKE-CHICKEN/do-not-repeat-your-folder-structure">https://github.com/BO-LIKE-CHICKEN/do-not-repeat-your-folder-structure</a></p>
<h2 id="확장프로그램">확장프로그램</h2>
<p><a href="https://marketplace.visualstudio.com/items?itemName=dryfs.dryfs">Dont Rpeat Your Folder Structure</a>
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/2cd58eaf-a5cd-45e8-875a-b843d03a269a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미 후기] JavaScript 알고리즘 & 자료구조 마스터클래스]]></title>
            <link>https://velog.io/@bo-like-chicken/%EC%9C%A0%EB%8D%B0%EB%AF%B8-%ED%9B%84%EA%B8%B0-JavaScript-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@bo-like-chicken/%EC%9C%A0%EB%8D%B0%EB%AF%B8-%ED%9B%84%EA%B8%B0-JavaScript-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Sat, 30 Mar 2024 17:27:11 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p>지난해 감사하게도 글또라는 멋진 동아리에 합류하여 여러 활동들을 할 수 있었습니다. 프런트엔드 직군이 아닌 개발자분들도 만나 뵙고 재직 당시에는 이야기 나누기 어려웠던 데이터 관련 업무를 보시는 분들과도 이야기 나눌 기회가 무척 많았었습니다. 그리고 다양한 제휴 혜택도 주어졌는데요. 오늘은 그 제휴 혜택 중 하나인 유데미 강의 제공으로 수강할 수 있게 된 <a href="https://www.udemy.com/course/best-javascript-data-structures/learn/quiz/5338781#overview">JavaScript 알고리즘 &amp; 자료구조 마스터클래스</a>입니다.</p>
<p>알고리즘과 자료구조에 대해서 한번 더 복습하고 싶었던차에 후보 중에 있던 강의였고 적절하게 맞아서 수강하게 되었습니다.</p>
<h1 id="강의-내용">강의 내용</h1>
<p>강의에서는 JavaScript를 기반으로 한 알고리즘이나 자료구조에 대해서 총 소개하고 있습니다. </p>
<p>강의 Section 별로 나누어져있으며, 꼭 순서대로 수강하지 않아도 되는 구조입니다. 다만, 아래의 이미지에서 보이는 것처럼 각 Section에는 선수 학습이라는 부분이 있습니다. 선수 학습에서는 해당하는 Section을 듣기 위해 필수적인 Section과 권장되는 Section이 소개되니 해당 가이드라인을 따라서 수강하게 된다면 강의를 더 효율적으로 들으실 수 있습니다. </p>
<blockquote>
<p>예) 이중 연결 리스트 강의를 듣기 전에는 단일 연결 리스트 강의를 듣는 것을 권장합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/e1140614-a4ce-4fa3-9162-0b31f4464132/image.png" alt="목차 예시"></p>
<p>그럼 간단하게 &quot;JavaScript 알고리즘 &amp; 자료구조 마스터클래스&quot; 강의의 장단점을 소개해 드리겠습니다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>기본적인 자료구조나 알고리즘에 대한 이론만 설명하는 것이 아닌 JavaScript에서 자료구조나 알고리즘이 어떻게 구현되어 있고, 어떤 시간 복잡도를 가지는지를 설명해 주며 마침내 프런트엔드 개발자인 왜 우리가 자료구조와 알고리즘을 반드시 공부해야만 하는지를 직접 깨닫게 해줍니다.</li>
<li>깔끔한 사운드와 강의자료가 무척 마음에 들었습니다.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li>IDE처럼 사용하는 브라우저의 콘솔 창이 너무 밝아서 가끔 눈이 아플 때가 있었습니다.</li>
<li>강의 중 슬라이드가 앞뒤로 빠르게 왔다 갔다 하는 경우가 있는데 이 경우 조금 산만하게 느껴졌습니다.</li>
</ul>
<h1 id="글을-마치며">글을 마치며</h1>
<p>정리하자면 이런 분들에게 이 강의를 추천드립니다. </p>
<ul>
<li>JavaScript에 대한 기초적인 지식이 있는 상태이지만 알고리즘이나 자료구조에 대한 학습이 필요하신 분</li>
<li>다른 언어로 알고리즘이나 자료구조에 대해서는 어느 정도 학습했지만 JavaScript의 다양한 내장 메서드들의 자료구조나 알고리즘에 대한 학습이 필요하신 분</li>
</ul>
<p>글이 작성된 2024년 3월 31일 기준으로는 정가 99,000원에서 81% 할인된 19,000원으로 수강 가능하니 할인쿠폰 등을 적극적으로 노려서 구매해 보시기를 추천드립니다. 다만, 개인적으로는 정가에 구매하더라도 아깝지 않을 강의였습니다.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/2dd07a7c-1d4f-4332-b128-94538db97b60/image.png" alt="할인가격 이미지"></p>
<blockquote>
<p>글또를 통해서 유데미의 강의를 수강할 수 있는 기회를 얻게 되어 작성하게 된 글입니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Query Keys를 관리하는 기준과 방법]]></title>
            <link>https://velog.io/@bo-like-chicken/Query-Keys%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EA%B8%B0%EC%A4%80%EA%B3%BC-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@bo-like-chicken/Query-Keys%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EA%B8%B0%EC%A4%80%EA%B3%BC-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 17 Mar 2024 14:25:39 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<blockquote>
<p><em>Powerful asynchronous state management for TS/JS, React, Solid, Vue, Svelte and Angular</em>
<br/><a href="https://tanstack.com">https://tanstack.com</a></p>
</blockquote>
<p>강력한 비동기 상태관리 도구 <code>Tanstacak-Query</code>는 작성일 기준 npm에서 300만회 이상 다운로드 될 정도로 많은 프런트엔드 개발자들에게 사랑받고 있습니다. <img src="https://velog.velcdn.com/images/bo-like-chicken/post/4e83cc40-d7e3-4b93-847d-34104023863d/image.png" alt="gdgd"></p>
<p>저도 마찬가지로 <code>Tanstack-Query</code>가 제공하는 다양한 API들을 유용하게 사용하고 있습니다. 하지만, <code>Tanstack-Query</code>가 제공하는 기능들을 제대로 사용하기 위해서는 필수적으로 이해해야 하는 개념들이 몇 가지 존재합니다. 오늘 다뤄볼 주제는 그 핵심 개념들 중 하나인 <code>Query Keys</code>를 관리하는 방법입니다.</p>
<p>공식문서에서는 다음과 같이 <code>Query Keys</code>를 소개하고 있습니다.</p>
<blockquote>
<p><em>At its core, TanStack Query manages query caching for you based on query keys. Query keys have to be an Array at the top level, and can be as simple as an Array with a single string, or as complex as an array of many strings and nested objects. As long as the query key is serializable, and unique to the query&#39;s data, you can use it!</em>
<br/><a href="https://tanstack.com/query/latest/docs/framework/react/guides/query-keys">https://tanstack.com/query/latest/docs/framework/react/guides/query-keys</a></p>
</blockquote>
<p>Query Keys의 관리가 제대로 이루어지지 않을 경우, 예상치 못한 문제가 발생할 수 있음을 알 수 있습니다. 많은 조직에서는 이러한 문제를 방지하기 위해 Query Keys를 어떻게 효율적으로 관리할지에 대해 심도 깊게 고민하고 있습니다.</p>
<p>이 글에서는 <code>Tanstck-Query</code> 공식문서에서 다음과 같이 소개하는 두 가지 읽을거리와 비교적 최신에 작성된 <strong><a href="https://tkdodo.eu/blog/the-query-options-api">The Query Options API</a></strong>를 읽고 어떻게 <code>Query Keys</code>를 관리하면 좋을지에 대한 생각을 정리해보았습니다.</p>
<blockquote>
<p><em>For tips on organizing Query Keys in larger applications, have a look at <strong><a href="https://tkdodo.eu/blog/effective-react-query-keys">Effective React Query Keys</a></strong> and check the <strong><a href="https://github.com/lukemorales/query-key-factory">Query Key Factory Package</a></strong> from the Community Resources.</em>
<br/><a href="https://tanstack.com/query/latest/docs/framework/react/guides/query-keys#further-reading">https://tanstack.com/query/latest/docs/framework/react/guides/query-keys#further-reading</a></p>
</blockquote>
<h1 id="query-keys를-관리하는-방법들">Query Keys를 관리하는 방법들</h1>
<h2 id="effective-react-query-keys">Effective React Query Keys</h2>
<p><code>Query Keys</code>를 효과적으로 관리하는 것은 Tanstack Query를 사용하는 애플리케이션의 성능과 유지보수성에 큰 영향을 미칩니다. <strong>Effective React Query Keys</strong>에서는 <code>Query Keys</code>를 생성할 때 고려해야 할 중요한 점들을 다루고 있으며, 이를 통해 보다 체계적이고 효율적인 데이터 캐싱 전략을 구축할 수 있습니다. <code>Query Keys</code>를 생성할 때 반드시 고려해야 할 유용한 내용들이 많이 담겨있으므로, 아직 읽지 않으신 분들은 꼭 읽어보시길 권장드립니다.</p>
<h3 id="배치-전략">배치 전략</h3>
<p>먼저, <code>Query Keys</code>를 포함한 Tanstack-Query의 기능들을 어디에 배치할지에 대한 고민입니다. 글에서는 기능 단위로 Query Keys를 배치하는 것을 권장합니다. 이러한 접근은 코드의 모듈성과 가독성을 높이며, 관련 로직을 쉽게 찾을 수 있도록 도와줍니다.</p>
<pre><code>-src
  - features
    - Profile
      - index.tsx
      - queries.ts
    - Todos
      - index.tsx
      - queries.ts</code></pre><p><code>queries.ts</code> 에서는 <code>Tanstack-Query</code>에서 제공하는 API를 활용하는 커스텀 훅을 내보내며, 실제 쿼리 함수와 쿼리 키는 이 파일 안에 위치시킵니다.</p>
<h3 id="구조-전략">구조 전략</h3>
<p>다음으로, <code>Query Keys</code>의 구조화에 대한 고려입니다. 가장 일반적인 요소에서 시작하여 가장 구체적인 요소로 끝나는 구조를 권장하며, 이는 적절한 세분성을 제공합니다. 아래 예시를 통해 이 개념을 보다 명확히 이해할 수 있습니다.</p>
<pre><code class="language-ts">[&#39;todos&#39;, &#39;list&#39;, { filters: &#39;all&#39; }]
[&#39;todos&#39;, &#39;list&#39;, { filters: &#39;done&#39; }][&#39;todos&#39;, &#39;detail&#39;, 1]
[&#39;todos&#39;, &#39;detail&#39;, 2]</code></pre>
<p>이렇게 작성하게 된다면 <code>[&#39;todos&#39;]</code>가 <code>Query Key</code>로 들어간 모든 작업을 한번에 관리할 수 있는 장점이 있습니다. 가령, <code>[&#39;todos&#39;]</code>가 <code>Query Keys</code>로 들어간 모든 <code>Query</code>를 <code>invalidate</code> 시킨다던가 말이죠. 아래 예시를 통해 이 개념을 보다 명확히 이해할 수 있습니다.</p>
<pre><code class="language-ts">function useUpdateTitle() {
  return useMutation({
    mutationFn: updateTitle,
    onSuccess: (newTodo) =&gt; {
      queryClient.setQueryData(
        [&#39;todos&#39;, &#39;detail&#39;, newTodo.id],
        newTodo
      )

      // ✅ just invalidate all the lists
      queryClient.invalidateQueries({
        queryKey: [&#39;todos&#39;, &#39;list&#39;]
      })
    },
  })
}</code></pre>
<p><code>invalidateQueries</code>의 <code>queryKey</code>에 <code>[&#39;todos&#39;, &#39;list&#39;]</code>를 전달해줌으로서 <code>[&#39;todos&#39;, &#39;list&#39;, { filters: &#39;all&#39; }]</code>에 해당하는 쿼리와 
<code>[&#39;todos&#39;, &#39;list&#39;, { filters: &#39;done&#39; }]</code>에 해당하는 쿼리를 한번에 무효화할 수 있었습니다.</p>
<p>이러한 구조적 접근은 <code>Query Keys</code>를 체계적으로 관리하며, 관련 쿼리들에 대한 일관된 처리를 가능하게 합니다.</p>
<h3 id="query-key-factories를-사용">Query Key factories를 사용</h3>
<p>하지만, 이대로 계속해서 수동으로 <code>Query Keys</code>를 작성한다면, 오류가 발생하기 쉬울 뿐더러 유지보수가 어려워집니다. 특히, Query Keys에 세분성을 추가하거나 변경이 필요한 경우, 관련된 모든 <code>Query Keys</code>를 일일이 수정하는 번거로움이 있습니다.</p>
<p>이에 대한 해결책으로, 글쓴이는 각 기능 단위별로 <strong>Query Key Factories</strong>를 구성하는 방법을 제안합니다. 다음은 앞서 언급된 예시들을 변환한 코드 예시입니다.</p>
<pre><code class="language-ts">// query-key-factory

const todoKeys = {
  all: [&#39;todos&#39;] as const,
  lists: () =&gt; [...todoKeys.all, &#39;list&#39;] as const,
  list: (filters: string) =&gt; [...todoKeys.lists(), { filters }] as const,
  details: () =&gt; [...todoKeys.all, &#39;detail&#39;] as const,
  detail: (id: number) =&gt; [...todoKeys.details(), id] as const,
};</code></pre>
<br/>
이러한 접근 방식을 사용함으로써, 다음과 같은 작업들을 보다 유연하게 수행할 수 있습니다:

<pre><code class="language-ts">// 🕺 remove everything related
// to the todos feature
queryClient.removeQueries({
  queryKey: todoKeys.all
})

// 🚀 invalidate all the lists
queryClient.invalidateQueries({
  queryKey: todoKeys.lists()
})

// 🙌 prefetch a single todo
queryClient.prefetchQueries({  // ⚠️ (작성자) 이 부분에서 왜 prefetchQueries를 사용했을까요?
  queryKey: todoKeys.detail(id),
  queryFn: () =&gt; fetchTodo(id),
})</code></pre>
<p>이러한 <strong>Query Key Factories</strong> 접근 방식을 통해, 초기에 수동으로 <code>Query Keys</code>를 작성할 때보다 훨씬 효율적으로 쿼리 키를 관리할 수 있습니다.</p>
<h2 id="query-key-factory">Query Key Factory</h2>
<p><code>Query Key Factory</code>는 <code>Query Keys</code>의 관리를 간소화하고, 번거로운 기억 작업 없이 효율적으로 처리할 수 있도록 돕는 라이브러리입니다. 이 도구는 <code>Query Keys</code>를 체계적으로 구성하고, 관련된 쿼리 함수와 함께 관리함으로써 개발 과정에서 발생할 수 있는 혼란과 오류를 줄여줍니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/95786621-d8ed-452c-95f4-a8d7fe020b74/image.png" alt=""></p>
<p>최근 우아콘에서 진행된 <strong><a href="https://youtu.be/nkXIpGjVxWU?t=1185">프론트엔드 상태관리 실전 편 with React Query &amp; Zustand</a></strong>에서도 <code>Query Keys</code>의 명명에 있어 발생하는 고민과 이로 인한 개발 비용의 증가를 지적하며, 잘 설계된 라이브러리의 도입 필요성을 강조했습니다. <code>Query Key Factory</code>는 이러한 문제를 해결하기 위한 방안으로 <code>Tanstack Query</code>의 공식 문서에서도 소개되었습니다.</p>
<h3 id="사용-방법">사용 방법</h3>
<pre><code class="language-ts"> import { createQueryKeys, mergeQueryKeys } from &quot;@lukemorales/query-key-factory&quot;;

// queries/users.ts
export const users = createQueryKeys(&#39;users&#39;, {
  all: null,
  detail: (userId: string) =&gt; ({
    queryKey: [userId],
    queryFn: () =&gt; api.getUser(userId),
  }),
});

// queries/todos.ts
export const todos = createQueryKeys(&#39;todos&#39;, {
  detail: (todoId: string) =&gt; [todoId],
  list: (filters: TodoFilters) =&gt; ({
    queryKey: [{ filters }],
    queryFn: (ctx) =&gt; api.getTodos({ filters, page: ctx.pageParam }),
    contextQueries: {
      search: (query: string, limit = 15) =&gt; ({
        queryKey: [query, limit],
        queryFn: (ctx) =&gt; api.getSearchTodos({
          page: ctx.pageParam,
          filters,
          limit,
          query,
        }),
      }),
    },
  }),
});

// queries/index.ts
export const queries = mergeQueryKeys(users, todos);</code></pre>
<p>이 접근법은 코드의 관리를 용이하게 하고, 각 기능별로 쿼리 키와 함수를 명확히 구분함으로써 관심사의 분리를 잘 달성합니다. <strong>Effective React Query Keys</strong>에서 제시한 기능 단위의 <code>queries.ts</code> 배치 방식을 더 발전시켜, <code>createQueryKeys</code>를 사용하여 각 기능별로 쿼리 키와 함수를 정의하고, <code>mergeQueryKeys</code>를 통해 이들을 병합하여 하나의 <code>queries</code> 객체로 구성했습니다. 이 방식은 프로젝트의 구조를 더욱 명확하게 하고, 쿼리 관리의 효율성을 극대화합니다.</p>
<p>만들어진 <code>queries</code> 객체를 활용하는 방식은 쿼리를 더 효율적이고 명확하게 관리할 수 있게 해줍니다.</p>
<pre><code class="language-ts">import { queries } from &#39;../queries&#39;;

export function useTodos(filters: TodoFilters) {
  return useQuery(queries.todos.list(filters));
};

export function useSearchTodos(filters: TodoFilters, query: string, limit = 15) {
  return useQuery({
    ...queries.todos.list(filters)._ctx.search(query, limit),
    enabled: Boolean(query),
  });
};

export function useUpdateTodo() {
  const queryClient = useQueryClient();

  return useMutation(updateTodo, {
    onSuccess(newTodo) {
      queryClient.setQueryData(queries.todos.detail(newTodo.id).queryKey, newTodo);

      // invalidate all the list queries
      queryClient.invalidateQueries({
        queryKey: queries.todos.list._def,
        refetchActive: false,
      });
    },
  });
};</code></pre>
<p>그러나, 코드에서 등장하는 _ctx와 _def와 같은 용어들에 대한 추가적인 이해가 필요해 보입니다. 이러한 용어들은 <code>@lukemorales/query-key-factory</code>에서 제공하는 기능을 깊이 이해하는 데 중요한 역할을 합니다. 따라서 <code>@lukemorales/query-key-factory</code>에서 제공하는 주요 함수들을 하나하나 살펴보겠습니다.</p>
<h3 id="기능">기능</h3>
<p><code>createQueryKeys</code>의 간단한 예제입니다.</p>
<pre><code class="language-ts">export const todos = createQueryKeys(&#39;todos&#39;, {
  detail: (todoId: string) =&gt; [todoId],
  list: (filters: TodoFilters) =&gt; ({
    queryKey: [{ filters }],
  }),
});

// =&gt; createQueryKeys output:
// {
//   _def: [&#39;todos&#39;],
//   detail: (todoId: string) =&gt; {
//     queryKey: [&#39;todos&#39;, &#39;detail&#39;, todoId],
//   },
//   list: (filters: TodoFilters) =&gt; {
//     queryKey: [&#39;todos&#39;, &#39;list&#39;, { filters }],
//   },
// }

// query-key-factory

// const todoKeys = {
//   all: [&#39;todos&#39;] as const,
//   lists: () =&gt; [...todoKeys.all, &#39;list&#39;] as const,
//   list: (filters: string) =&gt; [...todoKeys.lists(), { filters }] as const,
//   details: () =&gt; [...todoKeys.all, &#39;detail&#39;] as const,
//   detail: (id: number) =&gt; [...todoKeys.details(), id] as const,
// };</code></pre>
<p>아래 첨부한 <code>todoKeys</code>는 위에서 소개해드렸던 <code>Effective React Query Keys</code>의 예제를 가져와보았습니다. <code>all</code>에 해당하는 <code>[&#39;todos&#39;]</code>는 <code>createQueryKeys</code>의 첫 번째 인자로 들어가고, 반환되는 객체의 <code>_def</code> 값이 됩니다. 그리고 <code>detail</code>과 <code>list</code>의 경우는 key 값이 <code>queryKey</code> 배열의 두 번째 요소, 그리고 의존되는 값들을 파라미터로 받아 이후의 배열을 채우게 됩니다.</p>
<p>이처럼 <code>@lukemorales/query-key-factory</code>는 해당 공식문서에서 다음과 같이 언급하는것 처럼 <code>@tanstack/query</code>의 컨벤션을 따르며 키를 생성합니다.</p>
<blockquote>
<p>All keys generated follow the @tanstack/query convention of being an array at top level, including keys with serializable objects:<br/>
<a href="https://github.com/lukemorales/query-key-factory/blob/main/README.md#standardized-keys">https://github.com/lukemorales/query-key-factory/blob/main/README.md#standardized-keys</a></p>
</blockquote>
<p>그리고 다음과 같이 queryKey와 함께 <code>useQuery</code>에서 사용되기를 원하는 옵션들(예: <code>queryFn</code>)을 선언할 수도 있습니다.</p>
<pre><code class="language-ts">export const users = createQueryKeys(&#39;users&#39;, {
  detail: (userId: string) =&gt; ({
    queryKey: [userId],
    queryFn: () =&gt; api.getUser(userId),
  }),
});

// =&gt; createQueryKeys output:
// {
//   _def: [&#39;users&#39;],
//   detail: (userId: string) =&gt; {
//     queryKey: [&#39;users&#39;, &#39;detail&#39;, userId],
//     queryFn: (ctx: QueryFunctionContext) =&gt; api.getUser(userId),
//   },
// }&#39;</code></pre>
<p><br/>또한, 다른 쿼리의 컨텍스트 또는 상황에 의존적이거나 관련되어 있을 때 사용하는 <code>contextQueries</code>도 존재합니다. 이 방식을 통해 주요 쿼리와 연관된 추가적인 쿼리들을 명확하게 구분하고, 주요 쿼리의 결과에 기반한 추가 데이터 요청을 구조화하여 관리할 수 있습니다. 이때 <code>contextQueries</code>에 작성한 의존된 쿼리는 반환되는 객체의 <code>_ctx</code>에서 찾아볼 수 있습니다.</p>
<pre><code class="language-ts">// Declare queries that are dependent or related to a parent context (e.g.: all likes from a user):
export const users = createQueryKeys(&#39;users&#39;, {
  detail: (userId: string) =&gt; ({
    queryKey: [userId],
    queryFn: () =&gt; api.getUser(userId),
    contextQueries: {
      likes: {
        queryKey: null,
        queryFn: () =&gt; api.getUserLikes(userId),
      },
    },
  }),
});

// =&gt; createQueryKeys output:
// {
//   _def: [&#39;users&#39;],
//   detail: (userId: string) =&gt; {
//     queryKey: [&#39;users&#39;, &#39;detail&#39;, userId],
//     queryFn: (ctx: QueryFunctionContext) =&gt; api.getUser(userId),
//     _ctx: {
//       likes: {
//         queryKey: [&#39;users&#39;, &#39;detail&#39;, userId, &#39;likes&#39;],
//         queryFn: (ctx: QueryFunctionContext) =&gt; api.getUserLikes(userId),
//       },
//     },
//   },
// }

export function useUserLikes(userId: string) {
  return useQuery(users.detail(userId)._ctx.likes);
};


export function useUserDetail(id: string) {
  return useQuery(users.detail(id));
};</code></pre>
<p><br/>또한, 특정 범주나 컨텍스트에 대한 캐시를 쉽게 관리할 수 있도록 <code>queryKey</code>나 <code>_def</code>를 사용합니다. <code>queryKey</code>는 특정 데이터 요청을 식별하는 데 사용되며, <code>_def</code>는 특정 범주의 쿼리들에 대한 작업을 용이하게 합니다.</p>
<pre><code class="language-ts">users.detail(userId).queryKey; // =&gt; [&#39;users&#39;, &#39;detail&#39;, userId]
users.detail._def; // =&gt; [&#39;users&#39;, &#39;detail&#39;]</code></pre>
<p><code>Query Key Factories</code>의 사용은 <strong>Effective React Query Keys</strong>에서 소개된 <strong>Query Key factories</strong> 방식을 한층 발전시켜, <code>Query Keys</code>의 관리를 더욱 효율적이고 체계적으로 만듭니다. 이는 학습 곡선이 낮고, 실제 프로젝트에 적용하기 쉬운 라이브러리입니다.</p>
<h2 id="the-query-options-api">The Query Options API</h2>
<p><strong>Effective React Query Keys</strong>를 작성한 <em>tkdodo</em>가 다룬 또 다른 주제, <strong>The Query Options API</strong>는 <code>Query Keys</code>를 다루는 방법의 진화를 다룹니다. V5 버전 업데이트를 통해 <code>Tanstack Query</code>는 함수 인수를 단일 <code>Query Options</code> 객체로 받는 방식으로 변경되었습니다. 이 변화는 쿼리의 인터페이스를 추상화하는 과정을 더욱 우아하게 만들었습니다.</p>
<pre><code class="language-ts">const todosQuery = {
  queryKey: [&#39;todos&#39;],
  queryFn: fetchTodos,
  staleTime: 5000,
}

// ✅ works
useQuery(todosQuery)

// 🤝 sure
queryClient.prefetchQuery(todosQuery)

// 🙌 oh yeah
useSuspenseQuery(todosQuery)

// 🎉 absolutely
useQueries([{
  queries: [todosQuery]
}]</code></pre>
<p>다만, TypeScript 환경에서는 이러한 작성방식에 문제점이 존재했습니다. </p>
<pre><code class="language-ts">useQuery({
  queryKey: [&#39;todos&#39;],
  queryFn: fetchTodos,
  stallTime: 5000,
})

// Object literal may only specify known properties, but &#39;stallTime&#39; does not exist in type
//&#39;UseQueryOptions&lt;Todo[], Error, Todo[], string[]&gt;&#39;. Did you mean to write &#39;staleTime&#39;?(2769)</code></pre>
<p>TypeScript는 객체 리터럴을 직접 함수의 인자로 인라인으로 전달할 때, 이 객체 리터럴에 대해 <strong>&quot;과잉 프로퍼티 검사&quot;(excess property checking)</strong>를 수행합니다. 따라서, TypeScript는 우리에게 <code>stallTime</code>은 존재하지 않는 프로퍼티라고 알려줍니다.</p>
<p>하지만 객체를 변수에 할당하는 경우, 구조적 타이핑의 특성으로 인해 TypeScript는 같은 수준의 엄격한 검사를 수행하지 않습니다.</p>
<pre><code class="language-ts">const todosQuery = {
  queryKey: [&#39;todos&#39;],
  queryFn: fetchTodos,
  stallTime: 5000,
}

useQuery(todosQuery)</code></pre>
<p>이 경우에도 런타임 에러는 발생하지 않지만, 의도하지 않은 방식으로 코드가 작동할 수 있으며, 문제를 발견하기 어려울 수 있습니다.</p>
<h3 id="queryoption">queryOption</h3>
<p><code>queryOptions</code> 함수는 이러한 문제를 해결하는 데 유용한 도구입니다. <code>Tanstack Query</code>의 공식 문서에서는 <code>queryOptions</code>를 사용하여 여러 위치에서 <code>queryKey</code>와 <code>queryFn</code>을 공유하면서도 서로 밀접하게 관리할 수 있는 방법 중 하나로 소개합니다.</p>
<blockquote>
<p>One of the best ways to share queryKey and queryFn between multiple places, yet keep them co-located to one another, is to use the queryOptions helper. At runtime, this helper just returns whatever you pass into it, but it has a lot of advantages when using it with TypeScript. You can define all possible options for a query in one place, and you&#39;ll also get type inference and type safety for all of them.</p>
</blockquote>
<pre><code class="language-ts">import { queryOptions } from &#39;@tanstack/react-query&#39;

function groupOptions(id: number) {
  return queryOptions({
    queryKey: [&#39;groups&#39;, id],
    queryFn: () =&gt; fetchGroups(id),
    staleTime: 5 * 1000,
  })
}

// usage:

useQuery(groupOptions(1))
useSuspenseQuery(groupOptions(5))
useQueries({
  queries: [groupOptions(1), groupOptions(2)],
})
queryClient.prefetchQuery(groupOptions(23))
queryClient.setQueryData(groupOptions(42).queryKey, newGroups)</code></pre>
<p>위의 코드처럼 queryOptions를 사용하면, TypeScript의 타입 추론과 안전성이 모든 옵션에 대해 제공됩니다. 실수로 잘못된 프로퍼티 이름을 사용하더라도 TypeScript는 즉각적으로 이를 알려주어 빠르게 수정할 수 있게 돕습니다.</p>
<pre><code class="language-ts">import { queryOptions } from &#39;@tanstack/react-query&#39;

function groupOptions(id: number) {
  return queryOptions({
    queryKey: [&#39;groups&#39;, id],
    queryFn: () =&gt; fetchGroups(id),
    stalTime: 5 * 1000,
  })
}

// usage:

useQuery(groupOptions(1)) // error! 🚨
// Object literal may only specify known properties, but &#39;stallTime&#39; does not exist in type
//&#39;DefinedInitialDataOptions&lt;Todo[], Error, Todo[], string[]&gt;&#39;. Did you mean to write &#39;staleTime&#39;?(2769)</code></pre>
<p>결과적으로, <code>queryOptions</code> 함수는 <code>Tanstack Query</code>를 사용하는 프로젝트에서 <code>Query Keys</code>와 관련된 옵션을 효율적으로 관리하고, 타입 안전성을 높이는 데 큰 도움을 줍니다. 이는 개발자가 보다 신뢰할 수 있는 코드를 작성하도록 지원하며, 잠재적인 실수를 사전에 방지할 수 있게 합니다.</p>
<h2 id="query-factories">Query Factories</h2>
<blockquote>
<p>거의 다 왔습니다! 조금만 더 집중해봅시다! 💪🏼</p>
</blockquote>
<p>그래서 새롭게 제안된 패턴은 <strong>Query key Factories</strong>가 아닌 <strong>Query Factories</strong>로 이 접근법은 <code>Query Keys</code>만이 아닌 <code>Query</code> 자체의 추상화를 더욱 진전시키는 방법입니다. 이 방식은 <code>queryOptions</code>를 활용하여 <code>Query Keys</code>와 <code>Query Function</code>을 통합 관리합니다.</p>
<p>글 초반에 언급했듯이, <code>queries.ts</code>는 <code>Tanstack Query</code>의 API를 래핑한 커스텀 훅을 내보내며 실제 쿼리 함수와 쿼리 키를 포함하고 있습니다. 하지만 Query들을 집합으로부터 직접 사용하게 되면, 커스텀 훅은 아래와 같이 간소화됩니다.</p>
<pre><code class="language-ts">const useTodos = () =&gt; useQuery(todosQuery)</code></pre>
<p>이러한 변화는 커스텀 훅을 통한 추상화의 가치를 다소 감소시키며, 따라서 <strong>Query Factories</strong> 패턴을 사용함으로써, 커스텀 훅에 과도하게 의존하는 상황을 줄일 수 있게 됩니다. 이는 <strong>Query Key Factories</strong>에서 한 단계 더 발전한 모델로, <code>Query Keys</code>와 <code>QueryFn</code>을 분리 배치했던 초기 접근의 한계를 극복하고, 타입 안정성, 코드의 위치 선정 및 개발 경험을 동시에 개선하는 전략을 제안합니다.</p>
<pre><code class="language-ts">const todoQueries = {
  all: () =&gt; [&#39;todos&#39;],
  lists: () =&gt; [...todoQueries.all(), &#39;list&#39;],
  list: (filters: string) =&gt;
    queryOptions({
      queryKey: [...todoQueries.lists(), filters],
      queryFn: () =&gt; fetchTodos(filters),
    }),
  details: () =&gt; [...todoQueries.all(), &#39;detail&#39;],
  detail: (id: number) =&gt;
    queryOptions({
      queryKey: [...todoQueries.details(), id],
      queryFn: () =&gt; fetchTodo(id),
      staleTime: 5000,
    }),
}</code></pre>
<p><strong>Query Factories</strong> 접근법은 <code>queryOptions</code>를 활용하여 <code>Query Keys</code>뿐만 아니라 <code>queryFn</code>과 <code>staleTime</code> 등 추가적인 옵션까지 함께 관리함으로써 코드의 응집도가 향상됩니다. 이는 <code>Query Keys</code>와 관련 함수들의 밀접한 연결을 통해 코드의 가독성을 향상시키고, 관련 로직을 보다 직관적으로 표현할 수 있게 도와줍니다. 이러한 방식은 타입 안정성을 강화하며, 개발자의 코드 작성 경험을 더욱 쾌적하게 만들어 줍니다.</p>
<h1 id="결론">결론</h1>
<p>먼저, <strong>Effective React Query Keys</strong>를 통해 <code>Query Keys</code>의 배치 및 구조화 방법에 대해 고민해보았습니다. 이 과정에서 <code>Query Keys</code>의 관리를 용이하게 하기 위한 <strong>Query Key Factories</strong>라는 개념을 탐색했습니다.</p>
<p>프로젝트 규모가 커짐에 따라 신경 써야 하는 부분이 늘어나고, 표준화된 키 작성이나 의존적인 쿼리 작성 시 발생하는 문제점들을 최소화하기 위해 공식 문서에서 소개된 <code>@lukemorales/query-key-factory</code>를 살펴보았습니다.</p>
<p><code>Query Keys</code>를 관리하는 방법에 대한 개인적인 판단 기준은 다음과 같습니다:</p>
<ul>
<li>라이브러리에 의존하지 않고 독자적인 규격과 구조를 만들어나갈 수 있는 환경이 있는 경우:
<code>Query</code>의 추상화 방식을 결정하고, 팀원 모두가 동의하는 구조로 <code>Query Factories</code>를 구축하는 방향으로 진행할 수 있습니다. 이 경우, <code>queryOptions</code>를 활용하여 자체적인 컨벤션과 구조를 개발하고 유지할 수 있습니다.<ul>
<li>고민하는 비용을 아끼고, 일관된 방식의 구조로 <code>Query Keys</code>를 관리하고 싶은 경우:
<code>@lukemorales/query-key-factory</code>와 같이 잘 설계된 라이브러리를 활용하여, <code>Query Keys</code> 관리의 복잡성을 줄이고 일관된 구조를 유지할 수 있습니다.</li>
</ul>
</li>
</ul>
<p><strong>결국 모든 선택에는 trade-off가 존재합니다.</strong> 독자적인 규격을 개발했을 경우, <code>Tanstack Query</code>의 스펙 변경에 따라 해당 규격을 수정해야 할 필요가 있을 수 있으며, 반대로 <code>@lukemorales/query-key-factory를</code> 사용할 경우, 해당 라이브러리의 업데이트가 프로젝트의 요구사항을 충족시키지 못하는 상황도 고려해야 합니다. <strong>가장 중요한 것은 Tanstack Query의 지침을 따라 Query Keys를 만들고, 이들을 어떻게 구조화할지에 대한 분명한 기준을 마련하는 것입니다.</strong></p>
<p>긴 글 읽어주셔서 감사합니다.</p>
<h1 id="비고">비고</h1>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><p>TkDodo&#39;s blog에 작성된 다음의 글을 참고했습니다.</p>
<ul>
<li><strong><a href="https://tkdodo.eu/blog/effective-react-query-keys">Effective React Query Keys</a></strong></li>
<li><strong><a href="https://tkdodo.eu/blog/the-query-options-api">The Query Options API</a></strong></li>
</ul>
</li>
<li><p>TkDodo&#39;s blog에 연결된 다음의 한글 번역글을 참고했습니다.</p>
<ul>
<li><strong><a href="https://highjoon-dev.vercel.app/blogs/8-effective-react-query-keys">Effective React Query Keys</a></strong></li>
<li><strong><a href="https://velog.io/@cnsrn1874/the-query-options-api">The Query Options API</a></strong></li>
</ul>
</li>
<li><p>라이브러리의 공식문서를 참고했습니다.</p>
<ul>
<li><strong><a href="https://github.com/lukemorales/query-key-factory">@lukemorales/query-key-factory</a></strong></li>
<li><strong><a href="https://tanstack.com/query/latest/docs/framework/react/guides/query-keys">Tanstack Query</a></strong></li>
</ul>
</li>
</ul>
<h2 id="관련하여-공부해보면-좋을법한-것들">관련하여 공부해보면 좋을법한 것들</h2>
<ul>
<li>구조적 타이핑</li>
<li>타입 신선도(Freshness)</li>
</ul>
<h2 id="기타">기타</h2>
<ul>
<li><strong>Effective React Query Keys</strong>에서 &#39;Always use Array Keys&#39;에 대한 내용은 생략했습니다.</li>
<li>글에 부족한 점이나 오류가 있다면 언제라도 알려주세요. 🙂</li>
<li>주제와 관련된 이야기 및 커피챗도 언제나 환영입니다. 🙌🏻</li>
</ul>
<h2 id="수정기록">수정기록</h2>
<ul>
<li>2024.03.17 첫 포스팅</li>
<li>2024.03.24 <code>Query Keys</code> 스타일 통일, 어색한 문장 일부 교정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023년, 나를 성장시킨 글]]></title>
            <link>https://velog.io/@bo-like-chicken/2023%EB%85%84-%EB%82%98%EB%A5%BC-%EC%84%B1%EC%9E%A5%EC%8B%9C%ED%82%A8-%EA%B8%80</link>
            <guid>https://velog.io/@bo-like-chicken/2023%EB%85%84-%EB%82%98%EB%A5%BC-%EC%84%B1%EC%9E%A5%EC%8B%9C%ED%82%A8-%EA%B8%80</guid>
            <pubDate>Sun, 07 Jan 2024 14:04:28 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p>한 해를 마무리하는 회고록으로 각종 커뮤니티가 붐비는 계절이 다가왔습니다. 저도 2023년에는 참 많은일이 있어서 기록으로 남겨둘까 라는 생각을 했는데요. 아무래도 사적인 감상과 감정이 많이 들어가는 글이다보니 올해의 소감은 작게 사내에서 나누는 시간을 가지고 <strong>2023년, 나를 성장시킨글</strong>들을 되돌아보며 한번 더 동기부여를 하고자 합니다. 🔥</p>
<p>그리고 이 블로그를 읽는분들이 아직 접해보지 못한 글이 소개된다면 개발 분야에 상관없이 꼭 한번 읽어보셨으면 합니다. 어쩌면 그 글이 <strong>여러분의 개발 커리어를 바꿀정도의 깨달음을 줄 수 있을것</strong>이라는 생각도 들어요. 🫢</p>
<p>그럼 지금부터 개발자인 저에게 큰 영향을 준 글들을 소개해드리겠습니다.</p>
<br/>

<h1 id="1-프론트엔드-엔지니어-커리어-로드맵-주니어를-위한-3가지-전문성-트랙">1. <a href="https://steady-study.super.site/frontend-engineer-career-roadmap">프론트엔드 엔지니어 커리어 로드맵: 주니어를 위한 3가지 전문성 트랙</a></h1>
<p>구독 중인 뉴스레터에서 소개되어 처음 읽게 되었습니다. 이후 2023년 인프콘에서도 해당 주제로 발표를 진행해주셔서 많은 분들이 접해보셨을거에요.</p>
<h2 id="소개">소개</h2>
<blockquote>
<p>3년차 프론트엔드 엔지니어입니다. 이제 뭘 더 공부해야 할까요?</p>
</blockquote>
<p>위와 같은 고민을 하는 프론트엔드 엔지니어들, 그리고 이들을 어떻게 고민하는 리드 혹은 CTO를 생각하며 글을 작성하셨다고 합니다. 저도 마침 3년 차에 접어든 개발자라서 더 와닿는 부분이 많았던 글이었습니다. 글에서는 &#39;탁월한 시니어 프론트 엔지니어 되기의 과정&#39;을 다음과 같이 크게 3가지 주제로 나누어 다루고 있습니다.</p>
<p><strong>1) 탁월한 엔지니어 되기</strong>
탁월한 엔지니어가 되기 위해 필요한 기본적인 역량을 강조합니다. 해당 파트에서는 탁월한 엔지니어의 5가지 주요 역량에 대하여 소개하며 덧붙여 Prompt Engineering에 대한 중요성도 함께 강조합니다.</p>
<p><strong>2) 탁월한 프론트엔드 엔지니어 되기</strong>
프론트엔드 엔지니어로서의 전문성을 쌓기 위한 구체적인 방향을 제시합니다. 크게 웹 특화, 제품 특화, 운영 특화 등 세 가지 트랙으로 나뉘며, 각각의 트랙에 대한 자세한 설명과 이를 통해 달성할 수 있는 커리어 목표가 제시됩니다.</p>
<p><strong>3) 탁월한 시니어 엔지니어 되기</strong>
시니어 엔지니어로서 필요한 역량과 태도에 대해 설명합니다. 이는 기본에 충실함, 리더십 발휘, 그리고 상황에 따른 큰 임팩트 창출을 포함합니다.</p>
<p>이 글을 통해 저와 같은 프론트엔드 개발자들이 자신의 커리어 방향을 명확히 하는 데 도움이 되기를 바랍니다.</p>
<h2 id="나에게-와닿았던-내용">나에게 와닿았던 내용</h2>
<blockquote>
<h3 id="2-탁월한-프론트엔드-엔지니어-되기">2) 탁월한 프론트엔드 엔지니어 되기</h3>
</blockquote>
<p>&quot;탁월한 프론트엔지니어가 되기 위해서는 어떤 기술을 배워야 할까?&quot;라는 생각에 사로잡혀있던 제 자신에게 큰 깨달음을 주었던 부분이었습니다. 소프트웨어 엔지니어도 어느 분야에 특화되었는지에 따라 프론트엔드 엔지니어, 백엔드 엔지니어로 나뉘는데 그 안에서 더 나뉠 수 있을것이라는 생각을 이전에는 왜 하지 못했을까요? 이러한 트랙들을 이해하고 나니, 나의 현재 위치와 앞으로 나아가야 할 방향이 명확해졌습니다.</p>
<blockquote>
<p>제품 전문가는 아직 시장으로부터 충분한 반응을 얻지 못한 초기 스타트업에서 가장 환영받는 유형의 엔지니어다. 적절한 가설을 설정하고 빈번하게 실험하면서 초기 제품에 유의미한 변화를 만들어내려면 높은 수준의 기술보다는 고객/시장에 대한 이해와 프로덕 센스가 더 중요한데, 제품 특화 프론트엔드 엔지니어가 바로 그런 센스를 갖춘 사람들이기 때문이다. </p>
</blockquote>
<p>처음 글을 읽고나서는 커리어에 대한 고민을 많이하며 위의 인용에서 소개하는 것처럼 제품 특화가 된 Product Engineer에 가장 가깝다는 생각을 했어요. 더불어 이 특화가 시장에서 가장 수요가 많고 가치있는 길이라고 생각하기도 했죠. 그래서 탁월한 Product Enginner가 될 수 있는 스킬들을 연마하며 커리어를 꾸려나가는 기간을 거치고 있었는데요. 그 와중에 바로 다음 소개해드릴 글을 읽게되며 생각이 바뀌어 한 단계 더 발전해 &quot;환경에 적합한 탁월한 프론트엔드 엔지니어&quot;가 되도록 노력하는 중 입니다.</p>
<h2 id="action-item">Action Item</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/11d3ac88-4d5d-42eb-ace1-7b98ec471e90/image.jpeg" alt=""></p>
<p>웹 특화, 제품 특화, 운영 특화 중 어느 쪽에 가장 가까운 개발자인지 고민해본 경험이 없다면 깊게 생각해보고 나의 커리어를 구체화해나가자!
<br/></p>
<h1 id="2-미국가서-중국어-공부하지-않기">2. <a href="https://jojoldu.tistory.com/733">미국가서 중국어 공부하지 않기</a></h1>
<p>회사에서 개발문화의 개선이나 기술부채를 해결하기 위해 고민이 많았던 시기에 동료 서버개발자로 부터 추천받아 읽게 되었어요.</p>
<h2 id="소개-1">소개</h2>
<blockquote>
<p>빅테크 회사분들의 시니어분들과 사석에서 만날때와 스타트업으로 이직하신 시니어분들을 만날때 서로 대화의 주제가 다름을 체감한다.
예를 들면, 우리팀의 주니어가 평균 레이턴시 1초인 API를 0.1초로 개선했다 를 이야기한다고 하면
여전히 빅테크에 계신 분들은 그 주니어가 기술적으로 뛰어난 사람임을 이야기하고, 어떻게 개선했는지가 주력이라고 한다면, 
스타트업으로 이직하신 시니어분들은 &quot;1초인데 왜 개선하지…?&quot; 라는 이야기를 한다는 것이다.
두 그룹의 시니어분들 다 몇년 전까지는 비슷한 규모의 회사를 다녔음에도 말이다.</p>
</blockquote>
<p>빅테크와 스타트업 사이에는 자원적 차이에서 비롯된 의사결정의 차이가 있고, 이런 의사결정이 어떻게 진행되는지에 따라 엔지니어의 역할이 달라지게 된다고 합니다. 그리고 그 역할에서 얻어 가야 할 경험은 분명 차이가 있다는 내용을 다루고 있습니다.</p>
<blockquote>
<p>미국 유학가서 중국어 공부하고 있다면 얼마나 이상한가.</p>
</blockquote>
<p>단 한 문장으로도 글에서 전달하고 싶은 바를 알 수 있는 이 글은 개발자들에게 많은 존경을 받고 있는 향로님의 글입니다. 많은 분들이 알고 계시듯 스타트업부터 빅테크까지 모든 환경에서 다양한 역할을 수행하신 분이 쓰신 글이기에 더욱 설득력 있게 다가오는 글 입니다.</p>
<p>대부분의 빅테크 기업에서 일하지 않는 개발자분들이 빅테크 기업의 흉내를 내려하지 않고 한정된 자원에서만 얻을 수 있는 최고의 경험을 얻는데 집중하는데 이 글이 도움이 되기를 바랍니다.</p>
<h2 id="나에게-와닿았던-내용-1">나에게 와닿았던 내용</h2>
<p>돌이켜보면, 빠른 주기로 MVP를 출시하고 시장의 반응에 따라 제품을 조정하는 과정은 이 단계에서만 경험할 수 있는 귀중한 것이었습니다. 이 과정을 통해 제품의 안정화와 성장을 위한 중요한 교훈을 얻었죠. 이러한 경험 중에, 회사와 관련 없는 기술이나 최신 기술에 맹목적으로 집착하는 것이, 현재 단계에서 얻을 수 있는 소중한 교훈을 놓칠 수 있다는 사실을 깨달았습니다.</p>
<p>실제로, 한 동료는 개인적으로 새로운 프레임워크를 배우는 데 많은 시간을 투자했는데, 이 프레임워크는 빅테크에서 사용하지만 당장 우리 프로젝트에는 적절치 못한 기술이었습니다. 결국, 이러한 학습은 제품의 성장이나 본인의 개발에 크게 기여하지 못했습니다.</p>
<p>이 상황에서, 저는 그 동료에게 &#39;현재의 프로젝트와 환경에서 얻을 수 있는 깊은 이해와 경험에 집중하는 것의 중요성&#39;에 대해 이야기하고 싶었습니다. 향후 이직을 고려한다 해도, 현재의 경험을 충실히 쌓는 것이 더 가치 있고 인정받는 경력으로 이어질 것임을 강조하고 싶었죠. 이는 마치 미국에서 중국어를 배우는 것보다 영어를 배우는 것이 당연하게도 더 좋은 경험이 될 것이라는 것을 말이죠.</p>
<h2 id="action-item-1">Action Item</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/6af476b0-c8d9-49c0-af0e-a3dbf32354d4/image.jpg" alt=""></p>
<blockquote>
<p>마찬가지로 내가 속한 회사가 어느 단계에 있는지를 보고, 그 단계에 맞는 경험에 집중하는 것이 좋다. 
소규모 스타트업에 있으면서 빅테크에서의 경험을 시도하려고 하거나 빅테크에 있으면서 소규모 스타트업에서의 경험을 시도하려고 한다면 경험치 효율이 좋지 못할 것이다.</p>
</blockquote>
<p>나의 환경에 가장 적절한 경험치 효율이 좋은 그런 경험을 하자. 그리고 주변의 동료들도 헤메이지 않도록 마일스톤을 깔아주자.</p>
<br/>

<h1 id="3-협업을-잘하는-개발자가-되어보자---프로그래머가-아니라-문제-해결사가-되자">3. <a href="https://velog.io/@teo/collaboration">협업을 잘하는 개발자가 되어보자 - 프로그래머가 아니라 문제 해결사가 되자!</a></h1>
<h2 id="소개-2">소개</h2>
<blockquote>
<p>이 글에서는 1) 왜 협업을 잘 해야 하는지, 2) 그래서 어떠한 태도와 관점을 가져야 하는지, 4) 그리고 어떻게 하면 협업을 더 잘 할 수 있는지에 대해서 개인적인 생각을 이야기 드리려고 합니다.</p>
</blockquote>
<p>개발자로서 떼어낼 수 없는 협업을 잘하는 방법을 구체적인 경험과 이해하기 쉬운 예시, 그리고 가이드라인을 소개하며 친절히 알려주는 글입니다.
소개 드렸던 글 중에 가장 길어서 간추린 소개보다는 직접 읽어보시는 것을 추천드립니다. 프론트엔드 개발자라면 모두 알고 있을만한 테오님의 글입니다.</p>
<h2 id="나에게-와닿았던-내용-2">나에게 와닿았던 내용</h2>
<p>앞에서 소개한 게시글을 통해서 제가 어떤 태도와 마음가짐으로 일하고 있는지에 대해서 정리해 본다면 다음과 같을 것 같아요.</p>
<ul>
<li>제품의 성공에 초점을 두는 Product Developer로서 고객과 시장을 이해하는 Product Sence를 가지는 개발자가 되자.</li>
<li>한정된 자원에 가장 적합한 의사결정을 내리고 이 단계에서만 할 수 있는 경험을 좇는 개발자가 되자.</li>
</ul>
<p>그리고 이 글을 읽으며 한 가지가 추가되었어요.</p>
<ul>
<li>협업을 잘하는 개발자가 되자. 가치에 대한 생각을 바꾸는 것으로 시작해서 주위의 행동을 바꾸는 개발자가 되자.</li>
</ul>
<p>글을 읽기 전까지는 코드의 품질과 제품의 성공이나 일정에 관련해서는 고민이 참 많았었는데 글을 읽고 나니 그 사이에서 빈번하게 벌어지는 협업의 과정에서도 많은 것을 고치고 개선해 나가야겠다는 생각을 들게 해준 글이었어요. 다행히도 글에서 제시하는 가이드라인 중 잘 지키고 있는 것도 있었지만</p>
<blockquote>
<p>&#39;심리적 안전감&#39; 만들어주기 - 마음의 치안</p>
</blockquote>
<p>라는 단락에서는 심장이 철렁할 정도로 반성을 하게 되었어요. 코드의 품질을 지켜나가면서 제품을 성공적으로 만들어나가는 협업의 과정에 나는 얼마나 많은 심리적 안전감을 모든 이들에게 주었을까? 하면서 말이죠. 그래서 이 글을 읽은 후로는 &#39;심리적 안전감&#39;을 주기 위해서 가장 가까운 동료들부터 시작해 천천히 유대관계를 만들어나가는 일을 해오고 있어요. 저는 이런 사례를 포함하여 지금은 글에서 알려주는 다양한 가이드라인을 토대로 개발자를 넘어 문제 해결사가 되기 위해 노력하고 있습니다. </p>
<h2 id="action-item-2">Action Item</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/98fa4188-5235-4315-8759-4d35a684e682/image.jpeg" alt=""></p>
<blockquote>
<p>내 가치를 높이는 과정이 코드 밖에도 있다는 것을 알게되면 훨씬 더 일을 하는 과정에서 주체적으로 일을 할 수 있게 됩니다. 일정의 마감을 지켜줘야 하고 구현을 해줘야 하는 것이 아니라 우리의 문제를 함께 논의하며 풀어나가는 것입니다. 이러한 마인드는 일 자체와 모든 과정을 다 즐겁게 할 수 있게 만들어 줄 것입니다.<br/>
당장의 현실은 그렇지 않게 느껴지더라도 생각이 바뀌면 관점이 바뀌고 관점은 태도를 바꿀 수 있습니다. 내 태도는 주위에 반드시 영향을 주게 됩니다.</p>
</blockquote>
<p>협업을 잘하는 개발자가 되어보자! 그러기 위해 생각을 바꾸고 관점을 바꾸고 태도를 바꿔나가자.
<br/></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[힘들어하는 주니어들을 위하여]]></title>
            <link>https://velog.io/@bo-like-chicken/%ED%9E%98%EB%93%A4%EC%96%B4%ED%95%98%EB%8A%94-%EC%A3%BC%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%84-%EC%9C%84%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@bo-like-chicken/%ED%9E%98%EB%93%A4%EC%96%B4%ED%95%98%EB%8A%94-%EC%A3%BC%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%84-%EC%9C%84%ED%95%98%EC%97%AC</guid>
            <pubDate>Sun, 03 Dec 2023 16:43:14 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며</h1>
<p> 지난 <a href="https://www.teoconf.com">Teo Conf</a>2기에서 <strong>&quot;황폐화된 개발 환경을 기름진 개발 환경으로 만들기&quot;</strong>라는 주제로 발표를 했습니다.</p>
<p>첫 발표라 걱정도 많이 하고 긴장도 많이 되었는데요, 걱정과는 다르게 많은 분들이 공감도 해주시고 응원의 메시지도 보내주셔서 올해 했던 일 중 가장 뿌듯한 일이 되었어요. 아마 개발자 커리어를 쌓아나가는 과정에 있어서도 오래오래 기억에 남을 추억이 되지 않을까 싶어요. </p>
<p>발표를 마치고 바로 회고를 해보려고 했지만, 회사일과 건강이슈(코로나)가 동시에 터지면서 이제서야 글을 작성하게 되네요. 발표를 준비하는 과정부터 그 이후에 대한 소감 위주로 정말 회고록처럼 다뤄볼까를 생각했는데 아무래도 발표 내용에 대해서 생각보다 많은 공감대를 얻게 되어 이대로 휘발시키기는 아까워 발표 내용 위주로 저의 경험을 공유해 보려고 해요.</p>
<p><strong>과거의 저처럼 혹은 저보다 더 힘든 시기를 보내고 계시는 주니어들에게 조금의 위로와 격려가 되기를 바라며 글을 시작</strong>합니다.</p>
<h1 id="스피커로-발표를-하게-된-계기">스피커로 발표를 하게 된 계기</h1>
<h2 id="주니어-리드-개발자">주니어 리드 개발자</h2>
<p> <img src="https://velog.velcdn.com/images/bo-like-chicken/post/acfb9e41-2130-4603-b3ac-62208e153b62/image.jpeg" alt="">
저는 회사에서 프론트엔드 팀을 리드하고 있어요. 팀의 리드라고 하니까 연차가 많은 시니어 개발자라는 오해를 많이 겪곤 했지만 사실 저도 이제 개발을 직업으로 삼은지는 3년이 조금 안되는 주니어 개발자랍니다.</p>
<h2 id="이직-제안을-받다">이직 제안을 받다</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/06b4226e-0ae5-462e-8ce6-5ea73034eb94/image.jpeg" alt="">
첫 직장에서 퇴사를 결정하고 분주하게 이직 준비를 할 때 이직 제안을 받았어요.</p>
<h2 id="회사에서-내가-필요했던-이유">회사에서 내가 필요했던 이유</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/bea6736a-ef14-4b6f-9270-1bc43127eb28/image.png" alt="">
회사의 기존 비즈니스 모델은 고객에게 알맞은(가장 유리한) 대출 상품을 소개해 주고 수수료로 수익을 내는 방식이었어요. 저에게 이직을 제안했을 당시에는 법이 개정되어서 고객과 대면이나 유선상으로 직접 접촉할 수 없게 되었고, <strong>회사는 기존의 비즈니스 모델을 IT화 시키고 핀테크 플랫폼으로 전환</strong>하고자 했어요. 그리고 그 과정에서 전 직장에서 인연이 닿았던 저의 합류를 희망했어요.</p>
<h2 id="내가-회사에-합류하게-된-이유">내가 회사에 합류하게 된 이유</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/86966316-0d53-459e-8a2c-c3ffae369453/image.png" alt="">
당시의 저는 <strong>주니어 레벨에서 경험하기 힘든 일들을 많이 겪어볼 수 있다는 기회</strong>라고 생각했어요. 저의 첫 직장은 SI 업체였기에 만드는 서비스에 대해 애정을 가지지 못하고 떠나보내야 하는 경우가 많았어요. 그래서 그 부분이 몹시 아쉬웠는데 제품의 출시부터 운영까지 어쩌면 제품의 마지막까지도 겪어볼 수 있다는 생각이 들었어요. 그리고 한편으로는 조금 더 연차가 쌓이게 되면 주력 도메인을 바꾸기도 주저될 테고, 새로운 도전을 해볼 기회가 줄어들 텐데 지금 연차에 도전해 볼 수 있는 마지막 기회라는 생각이 들었어요.</p>
<p><strong>그렇게 저는 이직을 결정했어요.</strong></p>
<h2 id="각오했던-것보다-더-처참했던-현실">각오했던 것보다 더 처참했던 현실</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/bf268215-08f3-41b6-b903-39a884bcabda/image.png" alt="">
그러나 막상 이직을 하니 <strong>현실은 각오했던 것보다 훨씬 더 처참</strong>했어요. 아무것도 갖춰지지 않은 회사에 주니어 개발자가 가게 되면서 기술적으로 맞닥뜨릴 걱정은 많았지만 실제로 제가 겪게 되었던 문제와는 결이 많이 달랐어요. </p>
<h2 id="베테랑이었던-내가-지금은-주니어">베테랑이었던 내가, 지금은 주니어??</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/0a97daef-85e2-438a-8762-2e4fde7267f5/image.png" alt=""><img src="https://velog.velcdn.com/images/bo-like-chicken/post/350da8c3-a985-407b-8f55-d3fb2dbcd32b/image.png" alt="">
<strong>비즈니스 모델이 바뀐다는 것은 기존 비즈니스 모델에서 필요했던 직무가 없어진다는 말</strong>이었어요. 그 말인즉슨, 기존에 이 분야에서 베테랑이었던 직원들이 낯선 업무에 새롭게 배치되어 일을 배워나가야 한다는 얘기였죠. 저의 이직과 동시에 기존 직원분들의 직무도 완전히 바뀌게 되었어요. 그리고 저는 <strong>IT 관련 업무를 처음 맡으신 분들과 여러 갈등</strong>을 겪는 것이 몹시 힘들었어요. 그리고 프로덕트를 만들어나가면서 IT를 조금 더 아는 사람(개발자)으로서 남들보다 더 많은 부분을 신경 쓰고 대응해야 했어요.</p>
<h2 id="이해도의-문제로-겪게된-갈등들">이해도의 문제로 겪게된 갈등들</h2>
<p>다음은 실제로 제가 초기에 맞닥뜨렸던 문제를 조금 각색하여 다룬 내용이에요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/6434e5f5-eca2-4abd-a869-692afc53456b/image.png" alt=""><img src="https://velog.velcdn.com/images/bo-like-chicken/post/377dea25-75db-46b1-9969-06967dcd8239/image.png" alt=""></p>
<h2 id="도망칠까">도망칠까?</h2>
<p>이런 종류의 갈등을 계속해서 겪다보니 생각보다 빠르게 지쳐버렸어요. 그래서 한참동안 고민했어요. </p>
<blockquote>
<p>&quot;퇴사하고 도망칠까? 아니면 회사는 적당히 다니면서 이직 준비를 할까?&quot;</p>
</blockquote>
<p>하지만 퇴사하거나 이직하는 것도 쉬운 결정은 아니었어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/4d31c998-a089-47e3-a676-b129696d4246/image.png" alt="">경제적인 부담도 있었고, 짧은 이직 주기가 제 커리어에 나쁜 영향을 끼치지 않을까라는 고민도 있었어요. 하지만 제일 저를 힘들게 했던 고민은 <strong>&quot;이직하면 정말 다를까?&quot;</strong> 라는 고민이었어요.</p>
<p>그러다가 개발자 행사에 참가해서 소위 &quot;네카라쿠배&quot;라고 불리는 빅 테크 기업의 시니어 개발자와 이야기 나눌 기회를 얻었어요. 그래서 저는 이렇게 물어봤어요.</p>
<blockquote>
<p>나: 제가 지금 이런 갈등과 어려움을 겪고 있는데 조금 더 큰 회사로 가게 된다면 이런 일을 더 이상 겪지 않아도 될까요?</p>
</blockquote>
<p>그리고 제가 예상했던 대답과 전혀 다른 답변이 돌아왔어요.</p>
<blockquote>
<p>시니어 개발자: 여기도 다 똑같아요.</p>
</blockquote>
<p>라면서 말이죠. 제가 겪고 있는 일들이 레벨의 차이는 있겠지만 개발자로서 반드시 겪어야 하는 일이며 어느 기업을 가더라도 쉽게 피할 수 없다는 얘기를 해주었어요. 그래서 저는 조금 생각을 바꿔보기로 했어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/e1aa5094-7e22-49fe-86e6-c58764e316e1/image.png" alt="">
오히려 주체적으로 해결할 수 있는 기회를 맞이한 것은 아닐까? 그리고 여기서라면 (당시 수준이 확연히 낮았기 때문에) 더 많은 변화를 이루어낼 수 있겠다는 생각을 하게 되었어요. 그리고 이 과정의 끝에 분명히 나에게 남는 것이 있을 것이라 생각했어요.</p>
<p><strong>그렇게 저는 개발과 관련된 문화라고는 아무것도 없는 황무지에서 새롭게 시작하는 것을 받아들였어요.</strong></p>
<p>그래서 지금부터 제가 들려드릴 이야기는 지금보다 더 주니어 개발자 였던 제가 어떻게 발버둥쳐서 이 환경을 개척하고, 무엇을 바꿨고, 어떤 게 남게 되었는지에 관한 이야기예요. 그럼 지금부터 제가 어떻게 발버둥 쳤는지에 대한 이야기를 들려드릴게요.</p>
<h1 id="소통-채널부터-하나로-만들자">소통 채널부터 하나로 만들자</h1>
<h2 id="우리-slack으로-메신저를-통일해요">우리 Slack으로 메신저를 통일해요!</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/0f34c93e-9a10-486f-854e-41acb5bd531a/image.png" alt="">
여러분의 회사에서는 어떤 메신저를 사용하고 계시나요? 합류 당시 회사에서는 놀랍게도 메신저를 3개나 사용했어요! 외부 활동이 필요한 부서는 카카오톡을 사용하고, 전사용으로는 사내 메신저를 사용했고, 개발팀은 Slack을 사용하고 있었어요. 이거 뭔가 잘못되었다고 생각이 들어서 처음에는 무턱대고 이렇게 주장했어요!</p>
<blockquote>
<p>개발팀이 슬랙을 사용하는데 엄청 편리해요! 자동화할 수 있는 부분도 많고, 기능도 다채로워요 그러니 전사 메신저를 슬랙으로 통일했으면 좋겠어요! </p>
</blockquote>
<h2 id="모든-설득에는-근거가-필요하더라">모든 설득에는 근거가 필요하더라</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/d1eab59e-28f3-41d5-bdbc-ce916a2c4abe/image.png" alt=""> 당연하게도 바로 회사가 수긍하기는 어려운 주장이었어요. 회사에서는 이미 3개의 메신저로 충분하다고 생각했는데 왜 가장 비싼 슬랙을 사용해서 일을 해야 하는지에 대해서 이해하지 못했어요. 저는 이때 회사에 “당연하다고 생각하는 무언가”를 바꾸기 위해 제안을 할 때도 &quot;상황과 이해관계에 맞는 설득”이 필요하다는 것을 깨달았어요. 그리고 다음과 같이 설득할 자료를 준비해 기회를 달라는 요청을 드렸어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/91e4d818-ea28-4dd2-a51c-5ee2e3663a57/image.png" alt=""> 설득하는 내용에는 Slack으로 메신저를 통일했을 때 어떤 이점을 우리 조직에 가져다 줄지에 대해서 담았어요. </p>
<p><strong>1) 공유가 되지 않는다.</strong>
사용되는 메신저가 3개니 주로 사용되는 메신저 범위 내에 국한되어서 다른 메신저를 주로 사용하는 부서와 내용 공유가 힘들다는 문제점을 지적하고, Slack의 채널 생성 및 태그 등의 기능을 소개해서 구성원끼리 공유해야 할 내용을 빠르게 공유하는 것을 장점으로 소개했어요.</p>
<p><strong>2) 기록이 남지 않는다</strong>
아무래도 개발을 하다 보면 히스토리 관리가 필수적이잖아요? 하지만 메신저를 3개로 쓰다 보니 히스토리도 산발적이고 심지어 앞에서 예시를 들었던 것처럼 톡 서랍등의 유료 결제도 이뤄지고 있지 않다 보니 저희가 나누었던 생산적인 대화들이 모두 휘발되는 것을 막을 수 있다고 어필했어요. 그래서 직접 슬랙에서 검색하는 방식을 시연하기도 하고 기존 메신저에 비해서 얼마나 더 편리하게 기록을 찾아볼 수 있는지에 대해서 담았어요.</p>
<p><strong>3) 불필요한 과정을 자동화 할 수 있다</strong>
저희 회사는 지금도 그렇지만 당시에는 더더욱 빠르게 프로덕트를 만들어서 정책에 대응해야 하는 일이 많았어요. 그래서 조금이라도 더 개발하고 검수하고 출시하는 과정에 있어서 군더더기가 없어야 했어요. 그래서 Slack을 도입했을 때 자동화나 봇을 사용해서 불필요한 액션 등을 줄이고 그 시간에 더 생산적인 일을 할 수 있다는 주장을 했어요. 특히 배포의 시작과 끝, 혹은 배포 과정이 잘못되었을 때를 알려주는 배포 봇이 큰 호응을 얻었어요.</p>
<p>이렇게 <strong>3가지의 큰 주제로 Slack의 도입을 주장</strong>했어요.</p>
<h2 id="우리-slack으로-메신저를-통일했어요">우리 Slack으로 메신저를 통일했어요</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/3831bc2c-a955-4d3d-bbd9-a3318a5af9a6/image.png" alt=""> 그 결과 모든 직원이 Slack을 통해서 하나의 채널에서 다 함께 소통할 수 있게 되었어요. </p>
<h1 id="같이-잘하기">같이 잘하기</h1>
<p>이제 소통의 수단을 하나로 만들었으니 이를 바탕으로 어떻게 다른 직군과 소통하면서 일을 더 잘하기 위해 노력했는지 말씀해 드릴게요.</p>
<h2 id="소통의-채널을-통일했으니-단어도-통일하자">소통의 채널을 통일했으니 단어도 통일하자</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/57488635-3e2e-4931-bc00-d7ec55cfd286/image.png" alt="">편가르기를 할 때 손바닥을 위아래로 뒤집는 이 행위.
여러분은 뭐라고 부르셨나요? 데덴찌, 젠디 제엔~디, 뺀다뺀다 또뺀다, 소라메치기 등 다양한 단어로 불리는데요.
<strong>놀랍게도 회사에서 일을 할 때도 이런 현상이 벌어지고 있었어요!</strong>
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/4a6d4632-174a-454c-bac1-7061c747e024/image.png" alt="">화살표가 가리키는 UI 요소를 어떤 사람은 드롭 다운, 다른 사람은 드롭박스이라고 부르고 개발자들은 셀렉트, 심지어 필터라고 부르기도 했어요. <strong>같은 요소를 보고 서로 너무 다르게 표현한 거죠!</strong>
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/8612590b-da92-4c48-b656-e0bb94e5aa10/image.png" alt="">
물론, <strong>프론트엔드 개발자인 저는 이것을 &quot;셀렉트&quot;라고 부르는 것이 익숙하지만 그게 정답이라고 할 수 있을까요? 우리도 간혹 복잡한 요구사항의 셀렉트를 구현하면서 “셀렉트” 태그를 쓰지 않곤 하니까요.</strong> 그래서 저는 누구의 잘못도 아니라는 생각이 들어서 우리끼리 소통할 때 혼선을 만들지 않기 위해 용어를 먼저 맞춰나가자고 마음을 먹었어요. 기껏 Slack으로 소통의 채널을 하나로 만들어놨어도 서로 다른 언어를 쓰면 무의미하기 때문이었죠.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/46daab2b-7e65-44d2-9d6e-afd0a877867f/image.jpeg" alt=""> 그래서 저는 <strong>어떻게 통일하지? 한참 고민을 하다가 우리 서비스에서 사용되는 라이브러리이면서 비 개발자도 보기 어렵지 않은 Antd라는 컴포넌트 라이브러리의 사이트</strong>를 떠올렸어요. 사이트에는 기획자나 디자이너가 쉽게 볼 수 있을 만큼 예시가 잘 나와있어서 따로 용어에 대한 정리를 할 필요도 없었고 심지어 Admin을 개발할 때 추상적으로 전달되던 기능 명세도 Antd에서 제공하는 기능을 바탕으로 전달하도록 했어요. 용어도 정리되면서 라이브러리에 있는 기본 기능을 활용한 기획으로 생산성까지 조금 더 챙길 수 있게 된 거죠!
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/360a8447-ffb2-48f3-b879-6ee9a33f1846/image.png" alt=""> 이후에도 UI 요소뿐만 아니라 개발자가 아니라면 헷갈릴 수 있을만한 용어들을 모아서 간혹 세미나도 진행하며 서로 같은 용어로 소통할 수 있도록 했어요.</p>
<h2 id="일하는-방식도-맞춰나가보자">일하는 방식도 맞춰나가보자</h2>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/d628fe33-fa9d-4012-84a8-67450418b43f/image.png" alt=""> 그렇게 기획자, 디자이너와 용어를 맞춰나가다 보니 이제 <strong>&quot;단어를 통일&quot; 하는 영역을 넘어서 일하는 방식을 맞춰보고 더 우아하게 일을 해봐야겠다</strong>는 생각이 자연스럽게 들었어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/761b4ad1-f240-46d2-b319-411678b3ce00/image.png" alt=""> 먼저, <strong>개발자가 기획자와 디자이너를 업무를 이해하는 것보다 다른 직군이 개발자의 업무와 사고방식을 이해하는 것이 더 어렵다고 판단</strong>했어요. 왜냐면 개발이라는 게 굉장히 생소한 분야잖아요. 맨날 안된다고 하고, 조금만 바꿔달라는 데 일주일이 걸린다고 하고, 반대로 생각해 보면 도통 이해하기 어려운 일이에요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/2fe0c352-ee05-455b-8962-bc73e32c8980/image.png" alt=""> 그래서, 조금 간접적으로 저희를 이해시키기 위해 작전을 세웠어요. 당시 기획, 디자인 파트를 관리하고 있던 리더분에게 몰래 책을 추천한 거죠. 개발자가 저 책을 직접 추천한다면 타 직군분들은 거부감을 느낄 수 있을 것이라는 생각 때문이었어요. 그렇게 리더분에게 메시지를 보내고 조마조마하고 있었는데요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/d34075b2-a238-4018-829c-9e1f72a48cad/image.png" alt=""> 다행히 다른 직군도 제가 느꼈던 것처럼 더 나은 소통에 대한 갈증을 느끼고 있었다며 수용해 주셨고, 심지어 당시 회사에서 꽤 높으신 직책을 가지고 계셨던 분이 저렇게 몸소 인증샷까지 보내주셨답니다.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/b6fcddc0-fcdd-4175-9e8d-564bd3773ff7/image.png" alt=""> 저는 여기서 더 동기부여가 되어서 개발자보다는 기획자나 디자이너들이 더 관심 있어할 주제들도 가져와서 공개된 채널에 역으로 제안해 보기도 했어요. <strong>맨 처음에는 위에서처럼 반응도 적고 댓글도 없었어요.</strong>
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/5c18affd-fa32-4965-bf64-baa05f66f369/image.png" alt=""> <strong>하지만, 여러 번 반복하다 보니 지금은 무척이나 반응도 많아지고 올라온 글을 주제로 다양한 부서에서 서로 스몰토크를 진행하는 문화가 생겼어요!</strong> 그러다 보니 조금 더 욕심이 나서 더 열정 있는 직원들, 관심사가 비슷한 직원들과 함께 성장을 하고 싶어졌어요. 제가 이끌어주는 성장에 그칠 게 아니라 서로가 성장을 북돋아 줄 수 있는 관계가 되기를 바랐어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/8bb41b5a-e3e3-49c9-aa66-bcd69ec04d70/image.png" alt=""> 그래서 각자 개선하고 싶은 일들 그리고 관심 있는 영역들, 그리고 공부에 대한 의지가 얼마나 있는지를 조사해서 비슷한 곳을 바라고 달리는 열정 있는 희망자에 한 해 스터디를 진행하기로 계획하고 절찬리에 진행 중에 있어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/dcce45cf-b512-42ab-836d-8a192a49ba2c/image.png" alt="">
<strong>그렇게 우리 조직이 같이 잘하며, 함께 자라도록 만들었어요.</strong></p>
<h1 id="너-내-동료가-되라">너! 내 동료가 되라</h1>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/0545a149-f779-4baf-b0bd-9d1b70dbcace/image.png" alt=""><strong>“개발 문화를 근사하게 바꿔나고 있구나!”라고 생각이 들 때쯤, 미뤄두었던 기술 부채가 눈에 들어오기 시작했어요.</strong> 그리고 더 이상 저 혼자서는 이 기술 부채를 해결하기는 버겁다는 생각이 들었어요. 그래서 회사에 채용이 필요하다는 의견을 내었고, 다행히 회사에서도 채용에 대해 긍정적인 답변을 주셨어요. 그래서 채용공고를 내고 지원자를 받기 시작했어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/461a50a1-481f-45b4-a705-e7c062369400/image.png" alt="">하지만, 기대와는 달리 지원자가 너무 적었어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/a957c61b-90e3-4c28-84f0-fbc5438970d1/image.png" alt="">왜 지원을 하지 않을까? 고민하다가 대수롭지 않게 생각한 채용공고 내용을 다시 보았어요. 제가 지원자라고 하더라도 우리 회사에 큰 지원 동기가 생기지 않겠더라고요. 그저 추상적인 자격요건들을 나열해두었고, 선택이 아닌 필수가 되어버린 <code>Typescript</code>에 관한 이야기도 일절 없었으니 말이죠.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/79f00125-4d94-40e8-987b-557992cec30e/image.png" alt="">그래서 다른 회사의 채용공고를 한참 들여다봤어요. 그러면서 내가 지원하고 싶은 마음이 드는 회사들을 모아보니, 우리 채용공고에도 이런 내용을 추가하고 싶다는 생각이 들었어요! 그런데 없는걸 쓸 수는 없잖아요? 그래서 한참을 고민하다가
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/db0602c9-59ef-4c87-a605-6780fd025971/image.png" alt="">“채용공고를 채우기 위해서 내가 더 성장해서 채워 넣자!”라는 마음가짐으로 새로운 도전을 하게 되었어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/b3a5667e-0c26-4493-9560-ccef1d36e14a/image.png" alt=""> 요즘 개발 스택 중 필수로 알아야 된다고 생각이 드는 것들을 위주로 먼저 공부하고, 그중 회사에 필요하기도 한 기술들은 야근을 해서라도 최대한 빠르게 적용시켰어요. 그렇게 적용된 기술들은 &quot;자격요건&quot;란에 기입할 수 있었어요. 그리고 당장 적용은 못하지만 앞으로 회사에서 적용할 예정이 있거나 합류해서 함께 연구해 나가고 싶은 기술들은 &quot;우대사항&quot;에 기술했어요. </p>
<p>마지막으로 저와 함께 개발 문화를 개선해 나갈 사람들을 원했기에</p>
<blockquote>
<p>&quot;개발 문화 발전에 기꺼이 함께해 주실 분&quot;</p>
</blockquote>
<p>이라는 문구를 우대사항에 마지막으로 넣었어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/dfa33dff-d423-4ff8-8ae7-df8494653b4b/image.png" alt="">그렇게 수정한 채용공고를 등록했더니 <strong>10명 정도였던 지원자의 수가 150명 넘게 지원</strong>하는 수준으로 급상승했어요. 쏟아지는 이력서를 보느라 업무에 차질이 생겨서 예정보다 빠르게 채용공고를 내리는 경우도 발생했어요. </p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/a14fea7f-86e9-4193-ad52-752cae6661ba/image.png" alt="">그리하여 함께 개발 환경을 일궈나갈 멋진 동료들을 얻을 수 있었어요!</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/96b0b76a-f6d2-4f87-9604-828309f6ebff/image.png" alt=""><strong>근데 또 남은 게 동료뿐만이 아니었어요. 놀랍게도 채용공고를 채우기 위해 발버둥 치고 밤을 지새우며 노력했던 경험이 저에게는 큰 지식이라는 부산물을 남기게 되었어요.</strong> 기술의 부채를 해결하고자 동료를 모집하는 과정에서 더 나은 동료를 얻기 위해 제 스스로 더 나은 개발자가 된 것이죠.</p>
<h1 id="주니어-리드-개발자-1">주니어 리드 개발자</h1>
<p>그렇게 아무것도 없던 땅에 홀로 떨어졌던 주니어 개발자는 졸지에 비슷한 연차의 동료들, 그리고 팀을 이끄는 리드 개발자의 역할을 해야 했어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/600db411-325a-4c71-8b9c-5cac79ea04a4/image.png" alt="">새로운 동료들이 오면서 <code>Typescript</code>도 도입하고, <code>Husky</code>로 자동화도 하고, 나 혼자서는 당연히 할 수 없었던 코드 리뷰도 하면서 정말 많은 부분이 좋아지고 있었어요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/6ad39381-f011-445f-aed3-74f7ac390677/image.gif" alt=""> 그런데 한편으로는 제 스스로 위기감을 느꼈어요. 회사 입장에서 제가 아닌 비슷한 연차의 다른 개발자라는 비교의 대상이 생겼으니까요!
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/32f01fe9-7100-4c47-a10c-f015ceea8803/image.png" alt="">그렇게 위기감을 느꼈던 순간, 부끄럽게도 아주 잠깐 자만했던 시기가 있었어요. &quot;나는 스타트 멤버고, 개발의 모든 히스토리를 알고, 조직 문화를 얼마나 많이 개선했는데?&quot;라고 생각을 했었어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/05e0e082-b842-46cb-8171-20c2cde8c044/image.png" alt="">그러던 찰나에 문득 이런 생각이 들더라고요. 이런 배경 없이 다른 곳으로 갔을때, 그게 내 실력이라고 할 수 있을까? 개발자로서 내가 평가받을 때 부끄럽지 않을 수 있을까?</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/0027c0cc-df10-4b2e-a4f3-e8f4828a0fd2/image.png" alt="">그래서 &quot;이제는 주니어에서 벗어날 때가 된 것 같다!&quot;라고 생각하며 리더로서 해야 할일을 찾기 시작했어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/635b7b84-6af2-49f2-94be-3f21558707b8/image.png" alt="">먼저, 정신을 차리고 리더로서 채용한 실력 있는 동료들에게 당당하기 위해서, 내 자신이 주니어에서 벗어나기 위한 개인의 성장을 일궈내는 기간을 가졌어요. 회사가 끝나면 스터디도 하고, 미뤄두었던 개발 관련 서적도 읽고, 인터넷 강의도 출퇴근 시간에 보고, 사이드 프로젝트도 진행하며 저 자신을 혹독하게 성장시켰어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/28cb1da0-9780-42ae-868f-308c4f3d28bf/image.png" alt=""> 그리고 리더로서 팀원들이 더 윤택하기 일하기 위한 환경 만들어줘야겠다 라고 생각하고, 회사에 적극적으로 어필하여 노션 결제를 승낙 받았어요. 그리고 노션을 도입하면서 저도, 동료들도 더 개발에 집중할 수 있게끔 여러 가지 시스템을 만들었어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/95eefba7-a5b5-46b3-adcf-4791fa63fa76/image.png" alt=""> 먼저, 파편화되었거나 코드 리뷰상에만 남아있던 저희 팀의 컨벤션을 문서화했어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/09029591-216f-4491-aa40-ce65e10ba40b/image.png" alt="">다음으로, 아이디어라는 제도를 만들었어요. 아이디어는 평일에 업무를 보면서 나누고 싶던 이야기들을 칸반 보드 형태로 남겨 매주 금요일 오전에 토론하는 시간을 가지는 제도에요. 아이디어 제도를 통해서 업무에 집중해야 할 때는 흐름을 그대로 가져가고 논의하고 싶던 이야기는 칸반 보드로 남긴 후 정해진 시간에 서로의 아이디어에 공유하며 우리 개발 환경을 일궈나가는 추진력이 되었어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/292aabe7-896c-43e4-9f52-b5f333871c90/image.png" alt=""> 그리고 KPT 형식의 주간 회고록도 진행하기 시작했어요. 매주 서로가 어떤 문제(개발 외적인 문제를 포함)를 맞이하고, 어떻게 풀어나갔는지 그리고 어떻게 더 발전해나갈지 공유하며 서로를 격려하고 북돋아 주었어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/435a3bdc-dd01-4112-afdc-56d3d2f7ad6c/image.png" alt="">
그리고 개발에만 몰두할 수 있도록 외부에서 오는 노이즈를 최대한 차단시키도록 다음과 같은 노력을 했었어요. 다른 부서와 함께 수많은 논의를 거쳐서 프로젝트를 노션으로 관리하기 시작했어요. 노션으로 전환하며 기획팀 주도(PM이나 PO가 따로 없던 상황)로 일정관리를 시작하게 함으로써, 개발팀이 복잡한 일정관리에서 벗어나서 조금 더 개발에만 신경 쓸 수 있도록 유도했어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/9e29ba56-3ab7-4e8c-857a-d08feaf24576/image.png" alt="">오류 접수 양식도 개발에만 집중하도록 변화시킨 방법 중 하나인데요. 오류를 접수 시에 비 개발자도 충분히 기입할 수 있는 항목들의 양식을 만들어서 우리 팀원들이 불필요한 QA로 시간을 허비하는 것을 막았어요.</p>
<p>그렇게 저는 기술적인 성장을 통해서 제 자신에게 당당하고 더 나은 업무환경을 동료들에게 만들어주며 동료들에게 인정받는 주니어 리드 개발자가 되었어요.</p>
<h1 id="앞으로-더-가꿔나갈-것들">앞으로 더 가꿔나갈 것들</h1>
<p>그리고 우리 팀은 앞으로도 멈추지 않고 새로운 도전들을 해보려고 해요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/2fa240ee-8503-4a11-b4ac-7cc20e4eeed9/image.png" alt=""> 먼저 올해 안에 테스트 코드를 도입해서 안정성을 높히고, 다양한 라이브러리들의 버전업 등을 통해서 성능 최적화에 도전해 보려고 해요.
<img src="https://velog.velcdn.com/images/bo-like-chicken/post/da3b7898-c576-4e9d-971c-288ac1af0ad2/image.png" alt="">그리고 내년 중으로 우리가 얻었던 지식들과 경험을 남들에게 공유할 수 있을 만큼 멋지게 정리하여 기술 블로그를 게시하겠다는 야망을 가지고 있어요.</p>
<h1 id="글을-마치며">글을 마치며</h1>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/6fdccfc8-7269-4a88-a491-60cf34145532/image.png" alt="">여기까지 다뤘던 이야기는 제가 어떻게 개발 환경을 가꾸어왔는지 그리고 앞으로 어떻게 더 가꾸어 나갈지에 대한 내용이었어요. 제가 컨퍼런스를 준비하면서 돌이켜보니 <strong>이러한 고난들도 결국 “개발”이라는 큰 흐름 속에서 필연적으로 겪게 되는 하나의 과정</strong>이더라고요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/4587b569-0f45-4d33-9892-2f29e9a17f90/image.png" alt="">기획자와 디자이너와 함께 소통하는 것도, 채용을 통해서 동료를 찾고 성장하는 것도, 더 나은 환경을 만드는 것에 대해 끊임없이 고민했던 것도 <strong>언젠가는 개발자라면 누구나 맞이하게 될 한 과정일 뿐</strong>이었어요. 그리고 저는 <strong>그 과정을 남들보다 낮은 연차에 일찍 겪게 된 것뿐</strong>이었죠.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/f125b000-1047-42a1-ab46-a36e6d2657f2/image.png" alt=""> 그래서 저는 이 글을 읽은 여러분에게 이런 이야기를 해주고 싶어요. <strong>“이상적인 개발 환경”이라는 환상을 좇지 마시라</strong>고요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/5218838d-d7b9-4669-8943-5ad1e285a1fd/image.png" alt="">소위 네카라쿠배라고 불리는 기업들의 경우에도 “이상적인” 개발 환경, 확실한 체계와 프로세스가 과연 존재할까요? 물론 정도의 차이는 있겠지만 그런 <strong>우리가 익히 알고 있는 기업들조차도 체계가 부족하고 비즈니스 일정을 맞추기 위해서 많은 부분에서 애를 먹고 포기하는 경우가 있다</strong>고 해요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/c336601e-986b-4bd1-834c-5316c96663a2/image.png" alt=""> 완벽한 프로세스가 없는 곳에 새로 합류하게 된, 연차가 적은 주니어 개발자분들의 경우에 상상했던 개발과정과는 다른 현실을 맞이하며 “이상과 현실에 대한 괴리감”을 느끼고 그 “괴리감 속에서 실망감”을 느끼시더라고요. 그리고 더 한 경우에는 “아 왜 내가 조금 더 노력해서 더 좋은 곳으로 가지 못했지?”라면서 자책하며 힘들어하시는 경우도 있어요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/55a8d4b3-10c7-41c6-870c-085666b27467/image.png" alt="">테오의 프론트엔드 방이나 개발 커뮤니티를 보다 보면 옛날의 저와 비슷하거나 혹은 더 힘든 환경에서 시작하는 주니어 개발분들도 많은 것 같아요. 그리고 간혹 생각했던 “이상과는 다른 현실&quot;에 속상함을 토로하는 분들도 많이 뵙게 되는 것 같아요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/200a7da5-4d34-496b-9c75-a595550b0803/image.png" alt="">하지만 여러분들을 위해서 이렇게 생각해 보면 어떨까요? &quot;지금의 환경이 이상적이지 못하다는 것은 그만큼 내 손으로 바꿔나갈 수 있는 부분이 많다는 얘기&quot;라고 말이에요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/760746ef-5922-46a5-8bd1-aac24bf581fc/image.png" alt="">멋지게 표현한다면 그만큼 내가 성장할 수 있는 기회가 주어졌다는 것이고, 우리끼리 조금 더 솔직하게 표현한다면 실적을 낼 부분이 많고, 여러분이 이력서에 기술할 수 있는 부분이 남들보다 훨씬! 많다는 것이에요. 그리고 이 경험은 여러분의 커리어에 분명 긍정적인 영향을 미칠 거에요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/80e7ce9e-967e-424a-8ec8-698592052d6f/image.png" alt="">그래서 오늘 저의 이야기가 여러분에게 조금의 동기부여가 되었다면 좋겠다고 생각했어요. 제가 말씀드린 이야기가 정답이라는 것은 절대 아니에요. 저의 경험들보다 더 심각하고, 더 황폐화된 환경에서 일을 하고 있는 분들도 분명 계실 거예요. 다만, 여러분은 황폐화된 개발 환경에서 조금 더 기름진 개발 환경으로 가꾸어 나가기 위해 발버둥 쳤던 저의 이야기를 듣고 현실에 대한 도피가 아닌 조금의 위로와 조금 더 나은 환경으로 바꿔보려는 용기를 얻어 가기를 바랄 뿐이에요.</p>
<p><img src="https://velog.velcdn.com/images/bo-like-chicken/post/ac687a97-7727-4ccd-a94c-4c0e2cf6af72/image.png" alt=""> 온라인상에서도 커피 챗을 하다가도 개발자 행사에서 만나더라도 언제 어느 공간에서 만나더라도, 서로를 위로하고 북돋아 주기를 바라면서 글을 마칩니다.</p>
<h1 id="이상과-현실의-괴리감속에-힘들어하는-주니어-여러분이-조금의-위로와-용기를-얻기를-바라며">이상과 현실의 괴리감속에 힘들어하는 주니어 여러분이 조금의 위로와 용기를 얻기를 바라며</h1>
<hr>
<p><strong>추신 1)</strong> 저의 이야기를 나눌 수 있는 기회를 만들어 준 테오, 그리고 발표를 만들어 나가기 위해 많은 피드백과 도움을 주었던 스피커(허브, 오웬, 준, 달리)에게 다시 한번 감사를 표합니다.</p>
<p><strong>추신 2)</strong> 다른 곳보다 갖춰지지 않은 환경에 합류해 적극적으로 개선하기 위해 애쓰고 있는 우리 프론트엔드 팀원들에게 항상 진심으로 고맙고 또 어디서 이런 좋은 동료들과 일할 수 있을까라는 생각만 자주 하게 되는 요즘이네요 감사합니다.</p>
<p><strong>추신 3)</strong> 발표가 끝난 직후, 그리고 그 이후로 이메일과 링크드인으로 인사주시고 제 경험을 토대로 시도해 보거나 잊고 있었던 열정을 다시 한번 느끼게 되었다고 응원의 메시지를 보내주신 분들 모두 감사합니다. 정말 큰 동기부여가 되어 이후로 제가 더 좋은 사람이 되고자 많은 노력을 하고 있습니다. 연락 주신 모든 분들의 앞날에 좋은 결과가 있기를 바랍니다 :)</p>
<hr>
<p><a href="https://www.linkedin.com/in/bosung-baek-a22a42287">링크드인</a>
이메일: <a href="mailto:teabs1125@gmail.com">teabs1125@gmail.com</a></p>
]]></description>
        </item>
    </channel>
</rss>