<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by 이문기 on Medium]]></title>
        <description><![CDATA[Stories by 이문기 on Medium]]></description>
        <link>https://medium.com/@junep?source=rss-7f8ab6539497------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*C5Ost4hDQuKCsEJP8Qn3hg.jpeg</url>
            <title>Stories by 이문기 on Medium</title>
            <link>https://medium.com/@junep?source=rss-7f8ab6539497------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 09 Apr 2026 18:27:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@junep/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[toodoori와 AI]]></title>
            <link>https://medium.com/@junep/toodoori%EC%99%80-ai-42d951fcb034?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/42d951fcb034</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[kanban]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Sun, 08 Feb 2026 13:12:17 GMT</pubDate>
            <atom:updated>2026-02-08T13:12:17.236Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YrxYBZM8hQEVoZVEVUx7HA.png" /></figure><h3>퍼스널 칸반, 투두리(toodoori)</h3><p>AI를 사용하면서 ‘언젠가 퍼스널 칸반 애플리케이션’을 꼭 만들어야겠다는 다짐을 했습니다. 일을 조금이라도 더 잘하기 위해 아등바등하며 사용하기 시작한 퍼스널 칸반의 여정은 제 커리어와 항상 함께였습니다.</p><p>할 일을 관리하기 위해 처음으로 사용한 애플리케이션은 Things와 TickTick이었고, 할 일 앱의 한계를 느끼고 Notion, Kanban Tool 등을 사용했습니다. 그 중 가장 오래 사용한 건 <a href="https://trello.com/">Trello</a>입니다. 그리고 가장 많은 영향을 받은 건 <a href="https://kanbantool.com/">Kanban Tool</a>입니다. 그래도 항상 불편하고 갇혀있는 느낌이 들었습니다.</p><p>왜 그런지 생각해보면, 대부분의 칸반 애플리케이션은 팀을 위한 제품이기 때문이었습니다. 이 제품들은 개인을 위한 특별한 기능보다 팀 단위 작업을 주로 지원했습니다. 그리고 용어들도 개인을 위하기보다 팀을 위한 용어들이 사용됐습니다. ‘담당자’, ‘내용’, ‘답글’ 등이 그랬습니다. 그리고 우선순위 관리나 필터, 보관, 삭제 기능 등이 종종 신경 쓰이기도 했습니다.</p><p>그렇게 2025년 10월, Gemini와 Claude, ChatGPT를 사용하여(종종 copilot도 활용했습니다.) 개발을 시작했습니다. 모든 프로젝트의 난관인 회원가입과 로그인은 1시간도 채 걸리지 않아 완성되었습니다.</p><p>‘이 프로젝트, 금방 끝나겠는데?’</p><p>그렇게 시작한 퍼스널 칸반 프로젝트는 2026년 1월 18일에 첫 사용자를 받았습니다. (네, 접니다.) 프로젝트 이름은 ‘투두리(toodoori)’로 정했습니다. 개인이 많이 사용하는 할 일(Todo List) 관리 앱처럼 개인을 위한 칸반 앱이라는 의미를 주기 위해서였습니다.</p><p>이번 글에서는 AI를 활용해 투두리 구현 과정을 소개하고, 이 프로젝트를 통해 경험한 내용을 소개합니다.</p><h3>이제 중요한 건 구현이 아닌 방향</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*x1g20dm4WX5dkzf1" /><figcaption>Photo by <a href="https://unsplash.com/@overlyawesome?utm_source=medium&amp;utm_medium=referral">Daniel Gonzalez</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>AI와 함께 투두리를 구현하며 가장 어려웠던 문제는 디자인, 그다음은 기획입니다. 서비스의 색상 팔레트를 구성한다고 해보겠습니다. 빨간색이 좋을까요, 파란색이 좋을까요? 처음으로 프로젝트를 시작해보면, 이게 그리 간단한 문제가 아니라는 걸 깨닫게 됩니다. 예를 들어, 회색은 전문성을 느끼도록 하고 노란색은 활기를 느끼도록 합니다.</p><p>즉, 색상을 정하기 전에 만드려고 하는 서비스가 어떤 철학, 가치관, 방향을 가지고 있는지 먼저 정해야 합니다. 그러지 않으면 AI는 그때그때 조사한 결과를 혼란스럽게 반영합니다. 빨간색에 어울리는 색상은 다양합니다. 그렇게 각 페이지와 요소들은 빨간색과 어울리는 다양한 색상이 들어가기 시작하고, 서비스 전반적으로 혼란스럽게 느껴질 수 있습니다.</p><blockquote>AI 뿐만 아니라 사람 역시 속도보단 방향이 중요합니다.</blockquote><p>기획을 할 때에도 서비스의 방향성은 매우 중요합니다. 일반적인 칸반 서비스는 작업별로 담당자 속성이 필수입니다. 하지만 투두리는 담당자 속성이 필요 없습니다. 답글은 다른 사람이 쓴 글에 대해 덧붙이는 글입니다. 하지만 투두리는 내 작업만 관리합니다. 그렇기 때문에 답글이라는 단어는 서비스의 방향성과 어울리지 않습니다. 그래서 투두리는 답글 대신 노트라는 단어를 사용합니다.</p><p>이 외에도 개인화를 지원하기 위해 작업 그룹과 스테이지에 색상을 부여할 수 있고, 작업 상세의 레이아웃과 도구 사용을 설정할 수 있도록 했습니다. 이들 역시 일반적인 칸반에서는 어울리지 않는 기능이지만, 투두리에게는 매우 중요한 기능입니다.</p><p>이 과정은 생각보다 매우 어려운 과정입니다. 그래서 프로젝트를 시작하고 2~3주 정도는 Gemini, ChatGPT와 함께 서비스의 철학과 방향성을 설정했습니다. 반드시 필요한 기능과 그렇지 않은 기능을 정의하고, 칸반 보드에서는 어떤 기능을 보여줘야 할지, 보드의 배치는 어떻게 해야 할지 등 매우 어려운 문제를 함께 고민했습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lYAsIcNoX_RSoaopjo4BLQ.png" /><figcaption>2026년 2월 8일 투두리의 모습</figcaption></figure><p>너무 오랜 시간 고민하느라 구체적인 실행을 하지 못하면 안 되기 때문에, 계속해서 ‘이제 시작해도 되는지’ 점검했습니다.</p><p>이렇게 준비한 내용은 AI가 참고할 수 있도록 모두 문서화했습니다. 그러고 나니 구현은 정말 빠르게 진행되었습니다. 기능에 대해 고민이 될 때면 준비해둔 서비스 철학이 답을 주었습니다.</p><blockquote>모든 문서는 사람보다 LLM이 이해하기에 좋은 형태로 작성해줘.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HPKu-js-jUdgnGILfAgU7w.png" /><figcaption>투두리의 철학</figcaption></figure><p>UI/UX의 결과가 서툴게 느껴질 때도 있었지만, 뚱딴지 같은 결과를 내뱉거나 하지 않았습니다. 즉, 결과물의 예측 가능성이 높았습니다.</p><h3>각 AI 모델의 역할</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hkRuKQrBqiQFLyG1" /><figcaption>Photo by <a href="https://unsplash.com/@polarmermaid?utm_source=medium&amp;utm_medium=referral">Anne Nygård</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>단순 기능 구현이 아닌 서비스를 내보내려면 신경 써야 할 것이 많았습니다. 그중 가장 어려운 문제는 로고와 법률이었습니다.</p><p>UI/UX 디자인이 아무리 어렵다고 하더라도 결국 구현은 코드이기 때문에 접근 자체가 막막하게 느껴지지 않았습니다. 하지만 로고는 그렇지 않았습니다. 로고는 디자인으로 끝나는 결과물입니다. 막막함이 느껴졌고, 유튜브와 블로그 등을 다수 참고했습니다.</p><p>먼저 서비스 철학을 제공하고 Gemini와 ChatGPT를 사용해 로고를 위한 아이디어를 조금씩 구체화했습니다. 생각보다 더 많은 시간이 로고를 만드는 데 들었습니다. 최종 아이디어는 ChatGPT를 통해 이미지로 구현하고 Figma를 사용해 벡터 이미지로 생성하여 구체화되었습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E27DHaETSZG_blvh1dwxlw.png" /><figcaption>투두리 로고 디자인</figcaption></figure><p>웹에 배포하기 직전, 그 다음으로 준비해야 할 것은 개인정보처리방침과 이용약관이었습니다. 법률에 대해 아는 바가 없기 때문에, 이 역시 AI의 도움을 받아야 했습니다. Claude와 Gemini를 사용해 기능을 명세하고 Gemini에게 법률 검토와 최종 문서를 준비하도록 요청했습니다. 그리고 바로 적용하기 전, 새로운 AI 세션을 통해 반복적인 검토를 진행했습니다. 최종적으로 문제 없음을 확인하고 배포를 했습니다.</p><p>일련의 과정을 거치며 알게 된 건 모델마다 잘 하는 게 분명히 다르다는 것입니다. 코딩 작업을 제외하고 가장 많이 활용한 모델은 Gemini입니다. 서비스 철학, 브랜딩, 로고, 법률 검토 등에서 Gemini의 결과물은 상당히 만족스러웠습니다. Gemini가 부진할 때는 의외로 ChatGPT가 역할을 해주었습니다. 특히 로고를 이미지화하는 데에는 ChatGPT가 압도적인 성능을 보여줬습니다.</p><h3>구현과 AI</h3><p>프로젝트 초기, AI의 성능은 가히 압도적이었습니다. 코드 구현에 있어서 개입할 일은 존재하지 않았습니다. 하지만 시간이 지날수록 개입해야 하는 일이 늘어나기 시작했습니다. 구체적으로 요청을 하더라도 버그가 다수 발생했습니다. 그렇게 트러블슈팅 문서가 점점 늘어났습니다. 몇몇 문제의 경우 AI도 힘들어하는 거 같아 신기하기도 했습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2G0ZO5EckKEuQEegkTPCCA.png" /><figcaption>Claude가 고생한 문제 중 하나. 결국 답은 ‘엉뚱한 방법으로 답을 찾아봐’였다.</figcaption></figure><blockquote>아무리 AI라도 예상치 못한 문제는 있기 마련이었다.</blockquote><p>시간이 지날수록 모든 구현에 개입하는 수준까지 다다랐습니다. 특히 예상과 다른 지점이 있었는데, 그것은 아키텍처와 관련이 있습니다.</p><p>AI는 컴포넌트나 함수 등을 분리하기보다는 하나의 파일 안에 모아두는 습성이 있었습니다. 어떤 페이지의 경우 2,000 라인에 가깝기도 했습니다. 사실 AI가 완벽하게 구현한다면 문제가 없지만, AI와 함께 구현 내용을 파악해야 하는 경우, 이 과정은 그리 유쾌하지 않았습니다. 또한 한 구현체의 라인수가 길다 보니, 코드를 파악하는 데 들어가는 시간과 토큰의 양이 늘어나는 현상도 발생했습니다.</p><p>결국 프로젝트를 시작한지 얼마 지나지 않아, 프로젝트의 아키텍처는 다시 사람이 이해하기에 좋은 구조로 잡아야 했습니다.</p><blockquote>AI에게 가장 많이 요청한 건 ‘비즈니스 규칙은 주석이 아니라 테스트로 강제되어야 해’, ‘진실의 원천이 하나인지 검토해줘’입니다.</blockquote><p>프로젝트의 구조는 FE, BE, Infra로 잡았습니다. 평소 협업하는 구조를 그대로 유지하여 익숙한 작업 흐름을 가져가기 위해서입니다. 각 프로젝트는 마치 담당자가 있는 것처럼 동작했습니다. 새로운 기능을 BE에서 구현하면 FE에서 참고하기 위한 명세를 BE가 작성하도록 했습니다. 그리고 FE는 해당 문서를 참고하여 개발을 진행했습니다. 버그가 발생하면 FE에서 먼저 파악하고, FE의 문제가 아니라면 BE에 해당 내용을 전달하기 위한 문서를 작성하도록 FE에 요청했습니다. 인프라를 구축하기 전에 FE와 BE에게 운영에 필요한 인프라 자원을 제출하도록 했고, 그 결과를 Infra에게 그대로 전달하여 구현했습니다.</p><p>이 과정을 통해 예기치 않게 평소 협업 방식을 돌아보는 기회도 생겼습니다.</p><h3>검증과 AI</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*I4yGYwJOqTEcdQRP" /><figcaption>Photo by <a href="https://unsplash.com/@amstram?utm_source=medium&amp;utm_medium=referral">Scott Graham</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>이번 프로젝트에서 생각만 해오던 프로세스를 구현하게 되어 매우 신나기도 했습니다. 그건 BE의 검증 과정입니다. BE는 구현된 결과를 다음과 같은 방식으로 검증합니다.</p><ul><li>단위 테스트 : 비즈니스 로직, 불변성 등을 주로 검증</li><li>E2E 테스트 : 모든 API에 대한 검증</li><li>마이그레이션 테스트 : 마이그레이션이 정상적으로 진행될지 검증</li></ul><pre>기능 개발 흐름<br><br>1. 기능 구현 → 라우트 + Swagger 스키마 + 서비스 로직 + 타입 정의<br>2. DB 스키마 변경 (해당 시) → database-migration 스킬 참조<br>3. 테스트 → yarn test + yarn test:e2e:full<br>4. 문서화 → API 명세 (docs/api-specs/)<br>5. 검증 → yarn lint &amp;&amp; yarn type-check &amp;&amp; yarn format</pre><p>특히, E2E 테스트의 경우 실행과 동시에 도커 컨테이너를 활용하여 테스트용 Postgresql과 MinIO를 띄우고, 테스트 종료와 함께 제거했습니다. MinIO는 S3와 인터페이스 호환이 되어서, 테스트 환경에서의 코드가 바로 운영 환경에서 사용될 수 있습니다. 또한 Postgresql의 스키마 기능을 사용하여 독립된 테스트가 돌 수 있도록 했습니다. 이렇게 함으로써 각 테스트 스위트 별로 간섭 없이 독립된 환경에서 테스트가 실행되고, 아무리 테스트 스위트가 많아도 제한된 시간 내에 테스트가 모두 실행되었습니다.</p><p>마이그레이션의 경우 AI와 함께 3시간을 고생한 경험이 있어서, 이 역시 로컬에서 검증 과정을 거치고 운영 데이터베이스에 적용하는 흐름을 적용했습니다. 마찬가지로 컨테이너에 현재 버전의 데이터베이스를 띄우고 마이그레이션 적용부터 롤백까지 검증을 진행했습니다.</p><pre>1. 기존 테스트 컨테이너 정리 (docker down -v)<br>2. 마이그레이션 전용 DB 시작 (포트 5434)<br>3. init-db.sql 적용 → &quot;운영 DB 현재 상태&quot; 시뮬레이션<br>4. migrations/.existing 목록을 pgmigrations에 fake 등록<br>5. Dry-run → SQL 문법/구조 검증 (실제 실행 없음)<br>6. 새 마이그레이션 실제 실행<br>7. seed-test-data.sql 적용 → NOT NULL 등 제약조건 검증<br>8. 스키마 무결성 검증 (필수 테이블, 컬럼 존재 여부)<br>9. 롤백 테스트 ( - with-rollback 옵션 시)<br>10. 컨테이너 정리</pre><h3>실무와 AI</h3><p>이 모든 과정을 지나면서 얻은 가장 의외의 소득은 AI 경험, 그 자체입니다. 마치 AI를 스터디하는 것과 같았고, 이 경험은 고스란히 실무에 닿았습니다. 구성원들의 AI 활용에 대해 피드백을 하기도 하고, 긍정적이었던 경험을 공유하기도 했습니다. 실제로 몇몇 중요한 프로젝트에서 중요한 기여로 이어지기도 했습니다.</p><ul><li>에이전트를 활용한 설계. 전체 일정의 절반 이상을 전문 에이전트와 설계 이후 빠른 구현 및 피드백</li><li>코드 리뷰를 학습하고 컨벤션을 관리하는 에이전트. 낯선 코드 환경에 빠르게 적응하도록 도와줌</li><li>UI/UX 에이전트를 통한 어드민 경험 증진</li><li>프로젝트별 CLAUDE.md, skills, agents, rules 설계</li></ul><h3>남은 과제</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vv6NlL-pQkUsutq1" /><figcaption>Photo by <a href="https://unsplash.com/@anniespratt?utm_source=medium&amp;utm_medium=referral">Annie Spratt</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>투두리는 제게 새로운 기회와 경험을 선물해주고 있습니다. 이제는 단순한 애플리케이션이 아닙니다. 그 어느곳에서도 느끼지 못한 퍼스널 칸반의 경험을 주고 있으며, AI와 함께 커리어를 발전시키는 데 있어서 뛰어난 교보재 역할도 해주고 있습니다. 뿐만 아니라, 애플리케이션 하나를 통째로 개발하면서 쌓아가는 지식은 한 분야에만 국한되는 경향이 있는 커리어의 폭을 넓혀주었습니다.</p><p>이젠 새로운 기능을 하나씩 추가하면서 사회적인 기회를 찾아보려고 합니다. 그리고 그 과정에서 경험하기 어려웠던 다수의 기술 경험을 해볼 것입니다. 여기에 더해 AI는 제게 새로운 과제를 주고 있습니다. ‘들쑥날쑥한 성능을 어디까지 신뢰할 수 있는가.’, ‘AI 활용에서의 빈부격차를 어떻게 극복할 것인가’, ‘AI가 닿을 수 없는 영역은 무엇인가’ 등등</p><p>지금까지의 경험이 그렇듯, 앞으로 얼마나 많은 경험을 하게 될지 기대가 됩니다.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=42d951fcb034" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[리더십: 리더가 있는 팀과 없는 팀의 차이는 무엇일까]]></title>
            <link>https://medium.com/@junep/%EB%A6%AC%EB%8D%94%EC%8B%AD-%EB%A6%AC%EB%8D%94%EA%B0%80-%EC%9E%88%EB%8A%94-%ED%8C%80%EA%B3%BC-%EC%97%86%EB%8A%94-%ED%8C%80%EC%9D%98-%EC%B0%A8%EC%9D%B4%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-0ee7f7639e06?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/0ee7f7639e06</guid>
            <category><![CDATA[leadership]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Sun, 30 Nov 2025 08:57:33 GMT</pubDate>
            <atom:updated>2025-12-01T01:12:59.739Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*JFzURlUsiDlN_gqvK9FaGw.jpeg" /><figcaption><a href="https://kr.pinterest.com/pin/866520784529403285/">https://kr.pinterest.com/pin/866520784529403285/</a></figcaption></figure><p>25년 6월, 처음으로 ‘리드’라는 단어가 직무에 붙었습니다.</p><p>항상 <a href="https://medium.com/@junep/%EC%9E%90%EA%B8%B0%EA%B2%BD%EC%98%81-22cecca0fc21">리더십에 대해 관심을 가져왔지만</a>, 나 자신이 아닌 다른 사람을 향한 리더십은 처음이었기 때문에 막막함이 앞섰습니다. 이러한 막막함은 곧 여러 가지 질문을 낳았습니다. 이번 글에서는 처음으로 프론트엔드 리드 직무를 맡은 이후 해온 질문과 답을 구하는 과정을 공유합니다.</p><h3>리더가 있는 팀과 없는 팀의 차이는 무엇일까</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4xc_EwY1yed7MVDbFLVREA.jpeg" /><figcaption><a href="https://operations.nfl.com/inside-football-ops/players-legends/nfl-player-engagement/bill-walsh-diversity-coaching-fellowship/">https://operations.nfl.com/inside-football-ops/players-legends/nfl-player-engagement/bill-walsh-diversity-coaching-fellowship/</a></figcaption></figure><p>프론트엔드 리드 직무를 맡기 전부터 지금까지 계속 이어지는 질문은 <em>‘리더가 있는 팀과 없는 팀의 차이는 무엇일까?’</em>입니다. <em>‘나’</em>라는 존재가 이 조직에 어떤 영향을 주는지 고민하기 전에, <em>‘리더’</em>의 존재 여부가 조직에 어떤 영향을 줘야 하는지 따져보는 이 질문은 리더가 <strong>이 조직</strong>에서 어떤 역할을 해내야 하는지 답을 내리기에 적절해 보였습니다.</p><p>이제 리더가 조직에 어떤 영향을 줘야 하는지에 대한 답을 얻으려면 정보가 필요합니다. 그리고 이 정보는 직접 경험하지 않고는 얻을 수 없습니다.</p><p>그래서 입사 후, 정보를 얻기 위해 몇 가지 방법을 의식적으로 사용했습니다.</p><ul><li>업무 진행 흐름을 온몸으로 느껴보기</li><li>나에게 무엇을 물어보는지 관찰하기</li><li>팀원에게 무엇을 물어보는지, 어떻게 답변하는지 관찰하기</li><li>가깝게 협업하는 분들과 다양한 이야기 나누기</li></ul><p>이 방법들을 통해 얻고자 하는 정보는<em> ‘누가 이랬으면 좋겠고’</em>, <em>‘일이 이렇게 진행됐으면 좋겠고’</em>와 같이 업무와 직접적으로 연관 있는 것만을 포함하지 않았습니다. 여기에는 <em>‘구성원들이 프론트엔드 엔지니어와 협업할 때 어떤 감정을 느끼는지’</em>와 같은 정보도 포함되어 있었습니다. 왜냐하면 모두가 공통으로 느끼는 감정은 말로 표현하기 어려운 중요한 정보를 포함하고 있을거라 생각했기 때문입니다. 여기에 더해 여러 관계 속에서 프론트엔드 엔지니어가 어떻게 지내고 반응하는지 관찰하고 파악했습니다.</p><p>그렇게 입사 후 세 달이 되어갈 때, 어설프지만 처음으로 프론트엔드 엔지니어들이 모여 대화를 나누었습니다. 이 시간에 (카리스마는 부족했지만)처음으로 <em>‘이렇게 일하면 좋겠습니다.’</em>와 같은 방향을 제시했습니다. 두 번째 모임에서는 조금 더 구체적인 방향을 제시했고, 그 다음에는 더욱 구체적인 방향을 제시했습니다.</p><p>이 과정을 통해 <em>‘리더가 있는 팀과 없는 팀의 차이’</em>에 대해 어렴풋하게나마 다음과 비슷한 문장을 만들 수 있게 되었습니다.</p><blockquote>구성원 개개인으로 이해되던 팀은 리더의 존재로 인해 팀이라는 <strong>단일</strong>하고 추상화된 주체로서 이해되기 시작한다.</blockquote><p>즉, 리더로 인해 팀 외부의 구성원과 내부의 팀원이 서로를 단순하고 분명하게 파악할 수 있게 됩니다. 팀 외부와 내부가 리더를 상대의 대표로서 활용할 수 있게 되는 것입니다. 이는 특히 <strong>팀 외부에서 쏟아지는 다양하고 막연한 기대를 분명하게 정의</strong>하여, 팀원이 조직의 기대를 수월하게 파악하여 성과를 내도록 합니다. 그리고 팀원은 불안감을 줄이고 해야할 일과 자신의 욕구를 충족시키는 데 집중할 수 있게 됩니다.</p><h3>질문의 가치</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*0ai9aNv5BdTSYxSt3-VVvA.jpeg" /><figcaption><a href="https://kr.pinterest.com/pin/351491945940188745/">https://kr.pinterest.com/pin/351491945940188745/</a></figcaption></figure><p>이 질문은 <em>‘AI의 활용은 리더의 역할을 축소할까?’</em>, <em>‘프론트엔드 리드에게 필요한 역량 중 엔지니어링을 제외하면 무엇이 있을까?’</em>와 같은 질문에 답을 할수 있는 아이디어를 제공합니다.</p><p>또한 <em>‘나’</em>라는 사람을 중요하게 만들지 않고 <em>‘리더’</em>에 집중하게 함으로써, 사람이 바뀌더라도 계속해서 적응하고 성장하는 조직의 모습을 추구하게 합니다.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0ee7f7639e06" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[코드에 예민해지기: 커링(Currying)]]></title>
            <link>https://medium.com/@junep/%EC%BD%94%EB%93%9C%EC%97%90-%EC%98%88%EB%AF%BC%ED%95%B4%EC%A7%80%EA%B8%B0-%EC%BB%A4%EB%A7%81-currying-99e169e2c6a8?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/99e169e2c6a8</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Sat, 08 Nov 2025 15:40:03 GMT</pubDate>
            <atom:updated>2025-11-08T15:46:50.046Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*MM3SO2mfjtUDNm9xQACbVQ.jpeg" /><figcaption><a href="https://pin.it/53RH95lLU">https://pin.it/53RH95lLU</a></figcaption></figure><p>코드를 어떻게 작성해야 하는지 공부하다 보면 아키텍처와 같이 크고 근사하고 어려운 주제에 관심이 갑니다. 하지만 이러한 주제는 현실에서 다룰 일이 많지 않고, 다루더라도 코드를 다루며 발생하는 어려움만큼 또는 그 이상으로 협업하는 과정이 더 어렵습니다.</p><p>반면, 작은 문제를 해결하기 위한 코드를 작성하는 방법은 관심이 덜 가고, 개인의 습관으로 여겨지곤 합니다. 그래서 작은 코드에 대한 피드백은 종종 바쁜 와중에 불필요한 참견처럼 느껴질 때도 있습니다.</p><p>하지만 작은 문제를 해결하는 코드는 가볍거나 번거롭게 느껴질 때가 있더라도 매우 중요합니다. 왜냐하면 버그는 큰 구조가 아닌 작은 코드에서 예상치 못하게 발생하기 때문입니다. 그리고 중대한 문제는 결국 작은 코드가 모이고 모여 거대한 덩어리가 되고 수정이 쉽지 않을 때 발생하기 때문입니다.</p><p>이번 글에선 최근 작은 코드를 다루며 얻은 인사이트를 다루려고 합니다. 이 인사이트는 코드의 복잡함, 중복 등을 해소하기 위해 커링을 어떻게 활용하는지 보여줍니다.</p><h3>커링 패턴(Currying Pattern)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*A4klE77FMTgHOn2eWTscTw.jpeg" /><figcaption>커링 패턴하면 ‘환경 설정&#39;이 떠오릅니다.</figcaption></figure><p>커링은 둘 이상의 매개변수를 갖는 함수에 대해 특정 매개변수에 매번 같은 값을 전달하는 경우, 반복 또는 중복을 줄이기 위해 사용하는 방법입니다.</p><pre>function createSumCalculator(base) {<br>    return function (value) {<br>        return base + value;<br>    }<br>}<br><br>const add10 = createSumCalculator(10);<br><br>add10(5); // 10 + 5<br>add10(-12); // 10 - 12</pre><p>예를 들어, 쿠폰이 적용되었을 때와 멤버십이 적용되었을 때에 따라 서로 다른 상품 가격을 보여준다고 해보겠습니다. 그 값을 구하기 위한 코드는 다음과 같습니다.</p><p>(이 예시는 예시를 위한 코드입니다. 더 간단한 해결 방법이 많이 있습니다!)</p><pre>// 10% 할인 쿠폰이라면 coupon.value는 0.1입니다.<br>const discountedByCoupon = product.price - (product.price * coupon.value);<br>// 멤버십을 가지고 있으면 1,000원을 할인합니다.<br>const discountedByMembership = product.price - membership.discount</pre><p>이제 상품 옵션 기능이 추가 되어서, 할인을 적용할 때 상품 가격 뿐만 아니라 상품 옵션 가격에도 적용해야 한다고 해보겠습니다. 즉, `product.price`를 `product.price + selectedOption.price`로 바꿔야 합니다.</p><p>이때 커링을 활용하면 반복적으로 사용되는 `product.price + selectedOption.price`를 미리 설정하여, 최종 가격을 구할 때 사용되는 상품 가격에 대한 진실의 원천을 한 곳에서 관리할 수 있습니다.</p><pre>function createPriceCalculator(basePrice) {<br>    return {<br>        // 고정 값 더하기<br>        add: (amount) =&gt; basePrice + amount,<br>        // 할인 적용하기<br>        applyDiscountRate: (rate) =&gt; basePrice * (1 - rate),<br>    };<br>}</pre><p>이제 커링을 활용한 `createPriceCalculator` 함수를 사용하면, 기본 가격(`basePrice`)이 설정된 함수를 사용할 수 있습니다.</p><pre>const calculator = createPriceCalculator(product.price);<br><br>// 10% 할인 쿠폰 적용<br>const discountedByCoupon = calculator.applyDiscountRate(0.1);<br>// 1,000원 멤버십 할인 적용<br>const discountedByMembership = calculator.add(-1000);</pre><p>이제 상품 옵션 가격을 반영해야 한다면 `createPriceCalculator`에 넘겨주는 기본 가격만 변경해주면 됩니다.</p><pre>const calculator = createPriceCalculator(product.price + selectedOption.price);<br><br>const discountedByCoupon = calculator.applyDiscountRate(0.1);<br>const discountedByMembership = calculator.add(-1000);</pre><p>`calculator`를 사용하는 쪽의 코드는 `basePrice`가 어떻게 계산되는지 전혀 알 필요 없이, 일관된 방법으로 가격을 계산할 수 있습니다.</p><h3>Panda CSS에서</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/746/1*zXcj4XG6nx099FD5c5cl-w.png" /><figcaption><a href="https://panda-css.com/">https://panda-css.com/</a></figcaption></figure><p>최근 Panda CSS를 사용할 일이 있었습니다. Panda CSS는 <a href="https://panda-css.com/docs/customization/utilities">유틸리티(Utility)라는 API</a>를 제공합니다. 복잡한 계산 과정을 거쳐 스타일 값을 구한다면 유틸리티를 통해 간결하게 만들 수 있습니다. 이때 계산하는 과정을 함수로 전달해야 합니다.¹</p><pre>// 스타일 설정<br>export default defineConfig({<br>  utilities: {<br>    extend: {<br>      br: {<br>        className: &#39;rounded&#39;,<br>        values: &#39;radii&#39;,<br>        transform(value) {<br>          return { borderRadius: value }<br>        }<br>      }<br>    }<br>  }<br>});<br><br>// 컴포넌트<br>import { css } from &#39;../styled-system/css&#39;<br><br>function App() {<br>  return &lt;div className={css({ br: &#39;sm&#39; })} /&gt;<br>}</pre><p>제 경우 반응형 스타일을 위해 `clamp`를 적용해야 하고, 반응형을 적용하는 최대 값과 최소 값을 활용하는 등 복잡한 계산이 필요합니다. 하지만 유틸리티의 `transform`에서 사용하는 값은 하나이기 때문에 활용되는 다른 값을 커링을 통해 조달합니다.</p><pre>function curryTransform(styleKey, designBase) {<br>  // 커링을 통해 `value`외에 `styleKey`와 `designBase`를 설정합니다.<br><br>  return function (value) {<br>    // `designBase`와 `value`를 사용하여 복잡한 계산을 하고 resultValue를 만듭니다.<br>    return {<br>      [styleKey]: resultValue<br>    };<br>  }<br>}</pre><p>이때 스코프에 따라 수행 회수가 다르다는 점을 활용하는 것도 좋은 방법입니다.</p><pre>function curryTransform(styleKey, designBase) {<br>  // curryTransform을 호출할 때 수행<br><br>  return function (value) {<br>    // 매번 실행<br>    return {<br>      [styleKey]: resultValue<br>    };<br>  }<br>}</pre><h3>안정적인 컴포넌트에서</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/626/1*j4FvaQ4kYOlrnffV6JwT9A.jpeg" /><figcaption><a href="https://pin.it/7kGmDKTEO">https://pin.it/7kGmDKTEO</a></figcaption></figure><p>안정적인 모듈은 여러 모듈이 의존하는 모듈을 의미합니다. 쉽게 말하자면 여러곳에서 사용하는 모듈을 말합니다. 여러곳에서 사용하는 만큼 수정하면 영향이 광범위하기 때문에 수정이 쉽지 않습니다. 따라서 수정이 적게 발생하고 그만큼 안정적입니다.</p><p>최근 작업 도중 안정적인 컴포넌트를 수정할 일이 있었습니다. 이 컴포넌트의 특징은 다양한 기능을 담고 있어서 `props`의 개수가 많다는 것입니다.</p><pre>function StableComponent({<br>  prop1,<br>  prop2,<br>  prop3,<br>  ...<br>  propN,<br>}) {<br>  return (<br>    &lt;div&gt;<br>      {/* ... */}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>이렇게 활용되는 `props`가 많으면 사용하기 위해 매번 `props`를 참고하기도 어렵고 설정하는 것도 번거롭습니다.</p><pre>// Page1.tsx에서 사용되는 StableComponent<br>&lt;StableComponent<br>  prop5={...}<br>  prop8={...}<br>  prop9={...}<br>  ...<br>/&gt;<br><br>// Page2.tsx에서 사용되는 StableComponent<br>&lt;StableComponent<br>  prop1={...}<br>  prop2={...}<br>  ...<br>/&gt;</pre><p>이럴 때 커링 패턴의 아이디어를 활용하면 복잡함과 중복을 줄일 수 있습니다.</p><pre>function StableComponentForPage1({ prop5, prop8, ...props }) {<br>  // `prop5`와 `prop8`는 전달 받더라도 `StableComponent`에 전달하지 않습니다.<br><br>  // `StableComponentForPage1`은 미리 정해진 값을 `prop5`와 `prop8`에 전달합니다.<br>  return &lt;StableComponent<br>    prop5={...}<br>    prop8={...}<br>    {...props}<br>  /&gt;<br>}<br><br>// Page1.tsx<br>&lt;StableComponentForPage1<br>  prop9={...}<br>  ...<br>/&gt;</pre><p>물론 정확히 커링처럼 동작하는 건 아니지만, 반복적으로 전달하는 값(`prop5`, `prop8`)을 외부 함수(`StableComponentForPage1`)를 활용해 전달하고, 나머지만 설정하도록 하는 아이디어는 커링과 같습니다.</p><p>이렇게 함으로써 `StableComponent` 컴포넌트의 구조와 `props`가 아무리 복잡해지더라도, 변경에 대응해야 하는 범위를 줄일 수 있습니다. 또한 복잡한 컴포넌트를 문맥에 맞게 이해하는 데에도 많은 도움이 됩니다.</p><h3>커링이 주는 인사이트</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/626/1*hyE4AWiHv_yIplBma-schg.jpeg" /><figcaption><a href="https://pin.it/2QGLexOYt">https://pin.it/2QGLexOYt</a></figcaption></figure><p>커링 패턴이 주는 인사이트는 자바스크립트와 같은 특정 언어나 구체적인 동작 원리에 한정되지 않고 반복, 중복, 복잡함을 줄이고 결과적으로 <strong>실수를 줄이는 데</strong>에 있습니다. 이점을 확장해보면 팩토리 패턴(Factory Pattern) 역시 비슷한 효과를 제공합니다. 왜냐하면 복잡한 과정을 거쳐야 만들어지는 객체를 최대한 단순한 방법으로 만들수 있도록 하기 때문입니다.</p><pre>const ProductFactory = {<br>  dummy(count) {<br>    const result = Array(count).fill(null).map((_, index) =&gt; ({<br>      id: index,<br>      name: ...,<br>      price: ...,<br>      ...<br>    }));<br>  }<br>};<br><br>const dummyProducts = ProductFactory.dummy(20);</pre><p>그리고 지금까지 봤듯이 그 방법은 거창하지 않고 매우 간단합니다.</p><p>¹: <a href="https://panda-css.com/docs/customization/utilities">https://panda-css.com/docs/customization/utilities</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99e169e2c6a8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[자기경영]]></title>
            <link>https://medium.com/@junep/%EC%9E%90%EA%B8%B0%EA%B2%BD%EC%98%81-22cecca0fc21?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/22cecca0fc21</guid>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Fri, 13 Dec 2024 15:31:11 GMT</pubDate>
            <atom:updated>2024-12-13T15:54:53.917Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8cBCKgbt8q-2TAYx" /><figcaption>Photo by <a href="https://unsplash.com/@pickawood?utm_source=medium&amp;utm_medium=referral">Pickawood</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>얼마전 리드라는 직책에 대해 이야기를 나눌 계기가 있었습니다. 집으로 돌아오는 길에 ‘이 정도 연차가 되니까 대화주제가 자연스럽게 리더십이나 매니징으로 흘러 가는 구나’라는 생각이 들었습니다. 그리고 곰곰이 생각해보니 이 주제에 대해 그 어떤 확실한 느낌도 잡을 수가 없었습니다. 스스로 물어봐도 분명하게 대답할 수 있는 게 단 하나도 없어서 ‘밖에 있는 사람들은 내게 리드의 자격을 묻는데, 난 진지하게 생각조차 안 하고 있었다니, 대답 하나 분명하게 하지 못하다니…’하며 부끄럽고 초라하게 느껴졌습니다.</p><p>그래서 상당 시간 고민하고 메모해서 리딩, 매니징 등에 대해 떠오르는 생각들을 글로 적고 정리해서 공유하려고 합니다.</p><p>지금까지 리드, 즉 이끄는 일을 해 본 적이 없기 때문에 이 글은 우리가 일반적으로 이해하는 ‘리더’에 대한 내용을 다루지 않습니다. 오히려 이 글은 ‘자기경영’에 대한 글입니다. 어떻게 스스로를 경영해왔는지 돌아보고, 그리고 앞으로 어떨지 보다 분명하게 중간 점검을 하기 위해 썼습니다.</p><p>여기에 더해, 일을 하는 환경이 좋지 않거나, 오랜 시간 뛰어난 팔로어로서 일해 오며 미래를 걱정하는 등 불안한 분들에게 이 짧은 글이 작은 도움이 되었으면 좋겠습니다.</p><h3>자기경영</h3><blockquote>경영학 책들은 대체로 사람들을 관리하는 방법을 다룬다. 이 책의 주제는 목표 달성 능력을 높이는 자기관리 방법이다. 개인이 다른 사람들을 올바르게 관리할 수 있는지 입증된 적은 없다. 그렇지만 인간은 자신을 관리할 수는 있다. … 자신뿐만 아니라 다른 사람들의 성과에 책임지는 경영자로, 또는 자기 자신의 성과만 책임지는 개별 전문가로 공헌해도 마찬가지다.<br><br>&lt;피터 드러커 자기경영노트&gt;, 피터 드러커, 들어가는 말</blockquote><p>리더십이나 경영에 대한 책을 읽고 있으면 가끔 제가 관련 직무를 수행하고 있는지 물어오곤 합니다. 그러면 전 ‘관심이 있어서 읽는 거지 리드는 아닙니다.’라고 답합니다. 그러면 ‘그런데 왜 경영서적을 읽으시나요’라고 물어옵니다.</p><p>제가 리더십, 리딩, 매니징, 경영 등에 대해 관심이 있는 이유는 ‘자기경영’ 때문입니다.</p><p>누구나 그렇듯 왜 늦잠을 자지 않고 일어나서 공부를 하러 가야하는지, 왜 시험기간에 공부를 해야 하는지, 잘 한 건지, 못 한 건지, 더 좋은 결과를 내려면 어떻게 해야 하는지 등 스스로에게 설명하고 동기를 부여하고 평가 해왔습니다. 하지만 이 과정은 절대 쉽지 않습니다. 왜냐하면 설명이 충분하지 않으면 하기가 싫고, 힘들어도 하기 싫고, 현실이 마땅치 않으면 불안하고, 결과는 좋아지지 않는 등 마주한 문제가 한 둘이 아니기 때문입니다.</p><p>이럴 땐 어떻게 하면 좋은 결과를 낼 수 있을지 경영에서 답을 구하곤 합니다. 그렇기 때문에 대규모 조직을 운영하거나 회계를 들여다보는 등의 내용이 아닌 시간 관리, 동기부여, 성과평가 등에 대해 주로 찾아봅니다.</p><p>어떻게 하면 내가 더 좋은 결과를 낼 수 있을지 고민하고 시도하는 자기경영의 결과는 오랜시간에 걸쳐 천천히 드러났습니다. 그 결과 중 하나가 바로 ‘퍼스널 칸반’입니다. 이전엔 목표나 성과를 특정하기조차 어려웠다면, 칸반을 사용하고 부터 평가 대상을 특정할 수 있게 되었습니다.</p><p>또 다른 결과는 스스로 성과 또는 목표를 정의하고, 달성하기 위해 노력하고, 피드백 하고 다시 수정하는 과정입니다. ‘더 나은 삶’이라는 중요하지만 모호한 주제로 현실을 평가할 때와 달리, ‘더 나은 삶’을 위한 조건 중 하나인 ‘노동과 수입’의 측면에서 ‘생산성’을 높이기 위해 ‘목표’와 ‘성과’와 관련된 지식을 수집하고 실험하고 적용하게 되었습니다.</p><h3>훈육과 돌봄</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*J7EuK9w3STJtoYRD" /><figcaption>Photo by <a href="https://unsplash.com/@adroman?utm_source=medium&amp;utm_medium=referral">Aditya Romansa</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>사실 자기경영을 하는 이유는 스스로를 잘 돌보기 위함입니다.</p><p>살다보면 스스로 너무 지쳐있고 위로받지 못하는 외로운 감정에 사무칠 때가 있습니다. 그러면 모든게 무작정 미워지고 원망스럽기도 합니다. 이 해결할 수 없는 감정 속을 헤매다보면 그 손이 스스로를 향하기도 합니다.</p><p>‘이럴 때가 아니야, 정신 차리자.’<br>‘너무 힘들다, 이걸 어떻게 해결해야 하지.’</p><p>그러다보면 영화에서 죽을 위기에 처한 주인공이 살 방법을 찾기 위해 주변을 뒤집어대듯 방황하기 시작합니다. 술을 마시기도 하고 드라마나 영화, 정신과 의사나 심리 상담사의 컨텐츠를 찾아보기도 합니다. 하지만 갑자기 숨통이 트이는 것 같다가도 진통제의 약효가 떨어지듯 다시 고통스러워집니다. 휴식과 돌봄이 필요하지만, 그러면 직업을 포기할 것만 같아서 그리고 이렇게 열심히 해도 불안한데 쉬다보면 그만둘 상황에 내몰릴 것만 같아 더욱 불안합니다.¹</p><p>불면증을 치료하는 인지적 방법 중 하나는 공간 분리입니다. 수면을 하는 공간과 그렇지 않은 공간이 분리되어 있지 않으면 뇌는 자려고 할 때에도 잠을 자지 않고 다른 걸 합니다. 그래서 공간을 분리해 수면을 위한 공간에 있으면 습관적으로 잠을 잘 수 있도록 하는 게 중요합니다.²</p><p>비슷한 이유로 생산을 위한 것과 나를 돌보는 걸 모든 측면에서 최대한 분리하기 위해 자기경영을 더욱 분명하게 합니다. 예를 들어, ‘생산성’이라는 단어는 나에게 사용하지 않고 ‘생산을 해야 하는 상황’에 사용합니다. 즉, 쉬면서 책을 보는데 생산성이라는 개념을 사용해 ‘요즘 책을 읽는 양이 준거 같아’ 또는 ‘운동을 해야 하는데 자꾸 하질 못하네’라고 ‘어디에선가 정한 기준에 따라 나도 모르게 평가’하지 않습니다. 또는 ‘일을 잘 하려면 평소에 이런저런 걸 해야 하는데’라며 생산성을 높이기 위해 일상을 평가하지 않습니다.</p><p>이럴 땐 나 스스로를 돌보고 훈육하는 입장이 되려고 합니다. 즉, 시간을 들이고 적극적인 관찰을 합니다.</p><blockquote>제대로 훈육하기 위해서는 시간을 들여야 한다. 자녀에게 줄 시간이 없거나 시간을 들일 마음이 없으면 가까이에서 아이들을 제대로 관찰하지 못한다. … 아이에게 시간을 투자하는 부모는 아이가 확실히 잘못을 저지르지 않았더라도 아이를 훈육해야 할 미묘한 순간도 알아차리고 애정과 배려로 부드럽게 타이르거나 야단치거나 방법을 알려주거나 칭찬을 한다.<br><br>&lt;아직도 가야 할 길&gt;, M. 스캇 펙, 30쪽</blockquote><p>수시로 감시하는 게 아니라, 스스로 믿고 행동하고 돌아보고 책임을 가집니다. 예를 들어 책을 읽지 못했다면, 그럴 만한 이유가 있다고 믿고, 책을 봐야 하는 이유를 다시 한 번 생각해보고, 스스로 왜 그런 생각을 갖는지 돌아보고, 마음 상태도 돌아보고, 책을 정말 봐야 하는 상황이 됐을 땐 책임감을 가지고 읽습니다.</p><p>방법이 있는 것도 아니고, 나를 지금보다 더 잘 알아야 하고 가깝기도 멀기도 해야 합니다. 너무 어렵고, 시간도 많이 걸립니다. 그래서 이렇게 하지 않아 서서히 망가지거나 무너지고 있었는지 모릅니다.</p><p>이럴 때 자기경영은 정해진 시간과 환경에서 더 나은 결과를 낼 수 있는 방법과 목표를 달성할 수 있는 방법 등 모든 걸 정의 합니다. 이렇게 정의함으로써 그렇지 않은 다른 것들을 더욱 분명하게 분리할 수 있습니다. 그리고 이렇게 분리된 시간, 개념³, 장소에서 나를 조금 더 잘 돌볼 수 있습니다.</p><h3>원칙</h3><p>원칙은 복잡한 상황에서 결정을 내려야 할 때 단순하고 명확한 기준이 됩니다.⁴ 이전에 원칙에 대해 정리했지만 시간이 지나며 어떤 건 없어지고, 어떤 건 생기고, 어떤 건 더욱 다져졌습니다. 자기경영의 입장, 즉 일을 하면서 사용하는 원칙들에 대해 정리해보려고 합니다.</p><h4>성과</h4><blockquote>달성해야 할 구체적인 목표나 일을 잘했는지 못했는지 판가름할 수 있는 성과 척도를 제시한 답변은 아예 없었다.<br><br>&lt;슬로우 워크&gt;, 칼 뉴포트, ‘생산성’이란 무엇을 의미할까?</blockquote><p>‘성과’란 무엇인지 조직에서 나를 평가하는 사람과 대화하며 정의하고, 그럼에도 불분명하고 모호하다면 막연하고 높아보이는 게 아닌, 나에게 필요한 성과가 무엇인지 분명하게 정의해야 한다. 그리고 가능하다면 함께 일하는 사람들과 성과가 무엇인지 이야기를 나누고 기준을 다듬어 가는 것도 좋은 방법이다.</p><p>‘성과’가 무엇인지 모호할 수록 자기 착취는 심해지고 겉으로 아무 일도 없지만 어느새 탈진해 버리고 만다.</p><h4>뭐든 시간이 걸린다</h4><p>나 뿐만 아니라 함께하는 동료를 볼 때에도 급하게 판단하지 않는다. 무언가 못하는가? 시간이 필요하다. 왜 못하냐며 비난하지 않는다. 그보다 게으르지 않은지 관찰하는 게 더 좋은 방법이다. 게으른게 아니라면 방향을 살펴보고 방향을 점검하고 믿는 것, 여기까지가 지금 할 수 있는 것이다. 그 외엔 시간이 필요하다.</p><p>그렇기 때문에 성과 또는 목표를 정할 때 미래에 기대되는 역량이 아닌, 지금 역량을 기반으로 정해야 한다. 그외에 부족한 건 시스템으로 받쳐주고 그게 아니라면 받아들이고 지원한다.</p><h4>선택과 집중</h4><p>모든 걸 다 잘 할 수는 없다. 모든 걸 다 이룰 순 없다. 포기하는 게 두려워서, 할 수 없다고 말하는 게 두려워서, 인정 받지 못할까 두려워서 해냈을 때의 성취감과 가능성을 미리 느끼고 스스로를 착취해선 안 된다.</p><p>지금 필요한 건 무엇이든 가능하다는 헛된 희망을 품는 게 아니라, 마음 아프더라도 희생하고 선택한 걸 올곧게 해야 한다.⁵</p><h4>친절해야 한다</h4><p>친절을 역량, 성과, 성격 등에 따라 정하지 않는다. 나 스스로가 아무리 한심해 보이고 싫어도 친절함을 포기해선 안 된다. 함께 하는 동료가 나를 아무리 싫어하고, 서로 갈등이 있고, 일을 못하거나 반대로 못한다고 평가 받더라도 친절함을 포기해선 안 된다.</p><p>나를 진심으로 믿어줄 수 있는 사람은 나 뿐이라는 말이 있다. 나에게 친절하지 않다면, 그래서 포기하는 게 점점 많아지면 안 된다. 동료에게 친절하지 않아서, 그래서 동료가 더이상 함께하지 않기로 한다면 그건 단순히 일을 잘 하고 못 하고의 문제가 아니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YsvEmr3vc7xKv8lToO1g9g.png" /><figcaption><a href="https://story.inflab.com/2024-mobile/">인프런 모바일셀을 만나다 | 더 나은 앱을 위한 끝없는 도전</a></figcaption></figure><h4>‘할 줄 안다’가 아닌 ‘해야 한다’</h4><p>평가 받기에 가장 좋은 경험, 가장 뛰어난 지식, 가장 좋은 포지션 등 ‘할 줄 압니다.’라고 말 할 수 있는 것을 고집하지 않는다. 어떤 걸 하든 미래에 내가 원하는 무언가를 얻기엔 항상 부족 할 수 밖에 없다. 지금 내가 해내고 있는 모든 것들이 여기에 있기 전에 준비되어 있지 않았다. 미래에도 그럴 가능성이 높다.</p><p>오히려 지금 ‘해야 하는 것’에 집중 한다. 무엇을 해야 하는지 탐색하고 해내는 과정과 경험은 과거 뿐만 아니라 지금 그리고 미래에도 항상 존재하기 때문이다.</p><p>인공지능의 활용도가 높아지고 있는 지금, 이 태도는 더욱 중요하다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6AYpsY1rz0VfewlW" /><figcaption>Photo by <a href="https://unsplash.com/@shivelycreative?utm_source=medium&amp;utm_medium=referral">Nathan Shively</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>마무리</h3><p>전에 썼던 메모, 글을 보면 정말 민망하고 부끄러울 때가 있습니다. 생각이 너무 어리게 느껴지고, 모순되고, 논리적이지 않고, 단편적으로 보입니다. 하지만 다시 생각해보면 이렇게 느낄 정도로 지나온 시간 동안 변한 것입니다.</p><p>이 글은 또 삶의 어느 한 시점을 남겨 놓고, 미래의 내가 스스로를 돌아볼 때 사용할 표식이 될 것입니다.</p><p>지금 처해있는 환경이 마음에 들지 않거나, 지금 내 상태가 부끄럽거나 마음에 들지 않을 수 있습니다. 저 역시 대부분의 시간을 그런 상태로 보냅니다. 그러다 잠깐 숨돌릴 틈이 생기면 이렇게 돌아보고 마음을 다시 다잡곤 합니다.</p><blockquote>왜냐하면 어떻게든 아픔을 견디다 보면 아픔이 조금은 수그러드는 때가 반드시 왔기 때문이다. 고통이 24시간 내내 똑같은 강도로 지속되는 것은 아니다. 고통과 고통 사이에 조금은 덜 아픈 시간이 분명 있다. 그래서 나는 그 시간을 기다렸다. 고통이 조금 수그러드는 시간을 기다리고, 약을 먹어서 움직일 수 있는 상태가 되기를 기다렸다. 그리고 아픔이 덜해 움직일 수 있거나 약 기운으로 걸어 다닐 수 있을 때는 그 시간에 할 수 있는 일들을 했다. 밥을 먹고, 운동을 하고, 산책을 나가고, 장을 보러 가기도 하고, 친구와 수다도 떨면서 말이다.</blockquote><blockquote>누구나 힘든 시간을 견디고 있을 때는 언제 이 고통이 끝날지 몰라 절망하게 된다. 하지만 언젠가 힘든 시간들이 지나가고 좋은 시절이 찾아온다고 생각하면 오늘 하루를 다르게 보낼 수 있다. 그러니 인생의 겨울을 지나고 있다면 기억해 두기 바란다. 당신에게도 봄은 꼭 올 것이다.<br><br>&lt;만일 내가 인생을 다시 산다면&gt;, 김혜남</blockquote><p>이번에도 글의 마무리는 횡설수설입니다.</p><p>1: 『엘리트 세습』 (세종서적, 2020) 5장. 엘리트 교육과 신분 세습. 기회의 종말<br>2: <a href="https://www.amc.seoul.kr/asan/healthinfo/disease/diseaseDetail.do?contentId=31586">https://www.amc.seoul.kr/asan/healthinfo/disease/diseaseDetail.do?contentId=31586</a><br>3: 예를 들어 경영은 목표 지향적이고 그 과정의 모든 걸 도구화 하지만, 훈육과 돌봄은 대상 그 자체가 목적이고 목적 지향적입니다.<br>4: 『원칙』 (한빛비즈, 2018) 들어가는 글<br>5: <a href="https://youtu.be/o6bNZiX-zWA?si=IyjRGTNVZ3ZsQU-x">https://youtu.be/o6bNZiX-zWA?si=IyjRGTNVZ3ZsQU-x</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22cecca0fc21" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[프론트엔드의 트리거(Trigger)]]></title>
            <link>https://medium.com/@junep/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%9D%98-%ED%8A%B8%EB%A6%AC%EA%B1%B0-trigger-a67027d84e9d?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/a67027d84e9d</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Fri, 01 Nov 2024 15:59:06 GMT</pubDate>
            <atom:updated>2024-11-02T00:01:26.402Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aE7rBOumJIBywoPJ" /><figcaption>Photo by <a href="https://unsplash.com/@kaleidico?utm_source=medium&amp;utm_medium=referral">Kaleidico</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>이상한 느낌</h3><p>바닐라(VanillaJS)와 JQuery로 웹 개발을 하다가 처음 뷰(Vue.js) 그리고 리액트(React)를 접했을 때 그 느낌을 잊을 수 없습니다. 지금은 익숙하지만, HTML 요소를 동적으로 업데이트 할 때 번거롭게 또는 장황하게 코드를 작성하지 않아도 된다는 건 정말 멋지고 세련된 방법이었습니다. 특히, 요소의 상태와 요소를 강하게 결합(bind)하여 누가 개발을 하더라도 실수하지 않도록 머리를 싸맬 일이 확연히 줄었습니다. 예를 들어 상품 리스트에서 장바구니에 담기위해 상품을 선택할 수 있고, 이 때 장바구니 버튼이 활성화된다고 해보겠습니다. 이 기능을 바닐라로 구현하려면 아래와 같습니다.</p><pre>const selectedProductIds = new Set();<br><br>document.querySelector(&#39;#products&#39;).addEventListener(&#39;change&#39;, event =&gt; {<br>  // 버블링 또는 캡쳐링을 사용해 상위 요소에 이벤트를 하나만 추가합니다.<br>  // 그리고 이벤트 객체를 통해 선택한 상품의 아이디를 가져옵니다.<br>  const productId = event.target.value;<br> <br>  // selectedProductIds에 productId를 추가하거나<br>  // selectedProductIds로부터 productId를 제거함<br><br>  if (selectedProductIds.length &gt; 0) {<br>    document.querySelector(&#39;#buttonCart&#39;).disabled = false;<br>  } else {<br>    document.querySelector(&#39;#buttonCart&#39;).disabled = true;<br>  }<br>});</pre><p>여기에선 상품을 선택하거나 해제했을 때 선택한 상품의 정보를 관리하는 것 뿐만 아니라, 장바구니 버튼의 활성화 또는 비활성화를 결정하는 과정을 포함하고 있습니다. 즉, 기획 의도에 따르면 선택한 상품의 정보가 장바구니 버튼의 비활성화 여부에 결합되어 있습니다. 만약 상품 선택을 하는 다른 방법이 추가된다면 거기에도 동일한 로직을 파악하고 넣어줘야 합니다.</p><pre>document.querySelector(&#39;#buttonSelectEventProduct&#39;).addEventListener(&#39;click&#39;, () =&gt; {<br>  // 상품 리스트엔 없는 &#39;이벤트 상품 선택&#39; 버튼 클릭 이벤트<br><br>  // 이벤트 상품의 아이디를 selectedProductIds에 담습니다.<br><br>  // 상품을 담기 때문에 장바구니 버튼의 비활성화를 해제합니다.<br>  document.querySelector(&#39;#buttonCart&#39;).disabled = false;<br>});</pre><p>이 과정은 번거롭기도 하지만 무엇보다 실수할 가능성이 많습니다. 기획 의도에 대한 공유가 안 되었거나 단순히 정신이 없었다는 이유로 문제가 발생할 수 있습니다.</p><p>하지만 뷰나 리액트는 그렇지 않습니다. 리액트로 예를 들면 이 과정은 아래와 같이 단순해집니다.</p><pre>function ProductsPage() {<br>  // 상품 리스트는 API로 초기화 합니다.<br>  const [products, setProducts] = useState([]);<br>  // 선택한 상품 리스트<br>  const [selectedProducts, setSelectedProducts] = useState([]);<br><br>  return (<br>    &lt;&gt;<br>      &lt;ul&gt;<br>        {products.map((product) =&gt; {<br>          return (<br>            &lt;li key={product.id}&gt;<br>              &lt;input<br>                type=&quot;checkbox&quot;<br>                name=&quot;selectedProducts&quot;<br>                value={product.id}<br>                onChange={() =&gt; {<br>                  // setSelectedProducts를 사용하여 선택한 상품 리스트 상태 초기화<br>                }}<br>              &gt;<br>              {/* ... */}<br>            &lt;/li&gt;<br>          );<br>        })}<br>      &lt;ul&gt;<br>      &lt;button<br>        type=&quot;button&quot;<br>        disabled={selectedProducts.length &gt; 0}<br>      &gt;<br>        장바구니에 담기<br>      &lt;/button&gt;<br>    &lt;/&gt;<br>  );<br>}</pre><p>그리고 이벤트 상품 선택 버튼이 추가되었을 때 이전과 달리 실수할 여지는 사라집니다. 아래의 리액트 코드는 이벤트 상품 선택 버튼을 추가했을 때 신경 쓸 부분만 남겼습니다.</p><pre>function ProductsPage() {<br>  // 선택한 상품 리스트<br>  const [selectedProducts, setSelectedProducts] = useState([]);<br><br>  return (<br>    &lt;&gt;<br>      &lt;button<br>        type=&quot;button&quot;<br>        onClick={() =&gt; {<br>          // setSelectedProducts를 사용하여 선택한 상품 리스트 상태 초기화<br>        }}<br>      &gt;    <br>        이벤트 상품 선택<br>      &lt;/button&gt;<br>      {/* 기존의 코드들은 신경쓰지 않아도 됩니다. */}<br>    &lt;/&gt;<br>  );<br>}</pre><p>이 코드는 ‘장바구니에 담기’ 버튼의 비활성화 여부가 selectedProducts에 결합되어 있기 때문에, selectedProducts를 다루는 그 어떤 로직도 ‘장바구니에 담기’ 버튼의 비활성화에 대해 신경쓰지 않아도 된다는 걸 보여줍니다.</p><p>이외에도 장점은 많고, 그렇기 때문에 많은 웹 페이지가 이러한 방법을 활용해 만들어지고 있습니다.</p><p>하지만 언제 부터인가 한 가지 의문이 들기 시작했습니다. 서비스 초반에는 뷰나 리액트와 같은 프레임워크를 사용하며 생산성, 실수 발생 가능성 등에서 큰 개선을 경험했습니다. 그러나 시간이 지나며 컴포넌트 간의 상태 관리가 복잡해지고, 상태 간의 의존성이 증가하면서 개발 난이도가 올라가기 시작했습니다. 이로 인해 생산성은 감소하고, 실수 발생 빈도수는 점점 이전으로 회귀하는 듯했습니다.</p><p>무슨 일인지, 왜 그런 건지 의문은 계속 됐습니다. 그러다 상태(변수)와 UI(HTML 요소)의 강한 결합(bind)이 눈에 들어오기 시작했습니다. 그리고 프론트엔드 개발에 익숙하지 않은 개발자를 만났을 때 듣는 “변수에 값을 넣었는데 알아서 바뀌더라고. 난 이게 적응이 안 돼.”와 같은 의견들도 들었습니다.</p><p>‘알아서 바뀌’는 과정이 문제일까, 그렇다면 구체적으로 뭐가 문제일까, 하는 의문이 들었습니다. 예시를 만들어보기도 하고, 실제 코드를 통해 고민도 해봤지만 구체적으로 ‘이런 문제가 있으니, 이런식으로 개선해야 한다.’는 방법을 찾기는 더욱 어려웠습니다. 특히 설명을 위한 예시는 현실의 코드 처럼 복잡하지 않았고, ‘다른 사람은 다 잘 쓰고 있어. 너만 어렵게 생각하는 거야. 봐, 단순하고 관리하기 쉽게 다룰 수 있잖아. 평소에 코드를 엉망으로 써서 그런거야.’라며 다시 원점으로 돌아오곤 했습니다.</p><h3>트리거</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XfqR0zeh0_wp2Prn" /><figcaption>Photo by <a href="https://unsplash.com/@pietrozj?utm_source=medium&amp;utm_medium=referral">Pietro Jeng</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>개발자로서 경력을 시작했을 때 기억에 남는 경험 중 하나는 데이터베이스를 다루는 것입니다. 대학에서 이론으로만 배웠던 SQL을 다루는 건 기대 이상으로 흥미로운 경험이었습니다. 그 중 하나는 트리거(Trigger)를 다루는 것이었습니다.</p><p>트리거는 데이터베이스의 특정 이벤트가 발생할 때 자동으로 실행되는 작업입니다¹. 예를 들어, 고객이 주문을 제출(데이터 삽입)하면 자동으로 재고가 감소(데이터 업데이트)하거나, 일정 조건이 만족될 때 로그를 기록하는 것 처럼 데이터베이스에 데이터를 삽입하거나 업데이트할 때 특정 작업을 자동으로 실행하도록 설정할 수 있습니다. 이 때 작업은 함수와 유사한 저장 프로시저와 같은 복잡한 작업을 포함할 수 있습니다².</p><p>트리거를 사용할 때 가장 큰 장점은 데이터를 조작하는 행동이 필요한 다른 행동을 알아서 수행(trigger)해준다는 것입니다. 주문 데이터를 추가하거나 주문한 제품의 수량을 변경했을 때 발생한 제품 수량의 변동을 재고 데이터에 반영해야 한다고 가정해보겠습니다. 주문 데이터를 다루는 방법이 다양하고, 앞으로도 더 늘어날 수 있다고 가정할 때 매번 재고를 함께 다뤄야 한다는 규칙은 매번 지키기 어려울 수 있습니다. 서버 코드에서 주문 데이터를 다루는 메서드에 항상 재고를 함께 다루도록 하면 문제를 해결할 수 있지만 트리거 역시 이러한 문제를 해결하는 방법일 수 있습니다.</p><p>서비스 규모가 작은 초반엔 서버 코드를 여러 줄 작성하는 것 보다 트리거를 만드는 게 더 매력적으로 느껴질 수 있고, 서버에서 어떤 방식으로 주문 데이터를 건드리든 정해진 규칙을 지킬 수 있도록 보장하기 때문에 더욱 그렇습니다.</p><p>하지만 세상에 무조건 좋기만 한 건 없듯 트리거 역시 그렇습니다. 가장 먼저 데이터를 다루는 입장에선 주문 데이터를 삽입하거나 업데이트 했다고 재고 데이터까지 변할거라 기대하지 않습니다. 따라서 이와 관련된 문서가 준비되어 있어야 합니다. 또한 어떤 경우엔 잘못된 데이터를 수정하기 위해 주문 데이터만 바꾸면 되는데 재고 데이터까지 갱신되어 문제가 더욱 커질 수 있습니다. 또는 대부분의 경우 불필요한 작업이 트리거에 포함되어 있어서 성능 문제를 야기할 수 있습니다. 그리고 트리거는 디버깅하기 어렵습니다. 데이터를 변경했을 때 어떤 트리거가 실행되는지, 이 트리거는 어떤 트리거를 실행하는지 파고들어가는 과정은 고통스러울 수 있습니다³ ⁴.</p><p>트리거에 대한 이 내용, 어딘가 익숙하지 않으신가요?</p><pre>const [state, setState] = useState(...);<br><br>useEffect(() =&gt; {<br>  // state가 업데이트 되면 수행되어야 하는 작업<br>}, [state]);</pre><p>이 내용은 뷰나 리액트와 같이 상태를 UI와 결합하여 상태를 변경했을 때 UI가 업데이트되는 과정과 매우 유사합니다. 그리고 이 내용은 프론트엔드 생태계와 달리 비교적 오래동안 다뤄지고 연구된 내용입니다. 따라서 어떻게 하면 복잡한 프론트엔드 코드를 개선할 수 있을지 힌트를 얻을 수 있습니다.</p><h3>개선하기</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EfeSVd5xOHLmFQS-" /><figcaption>Photo by <a href="https://unsplash.com/@ochimaxstudio?utm_source=medium&amp;utm_medium=referral">ochimax studio</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>그렇다면 트리거를 잘 다루는 방법을 통해 얻을 수 있는 힌트는 무엇이 있는지 살펴보겠습니다.</p><h4>설명하기</h4><p>먼저 UI와 결합되어 있는 상태의 이름이 적절해야 합니다. 즉, 어느 UI와 결합되어 있는지, UI의 어떤 상태를 다루는지 이해할 수 있어야 합니다.</p><p>UI와 결합하지 않았지만 값을 업데이트 했을 때 컴포넌트를 리렌더링 해야 한다면 주석 또는 문서를 통해 적절한 설명이 있으면 좋습니다. 예를 들어, <em>useEffect</em>를 사용해 상태가 변경될 때 API를 호출 하거나 로깅, 로컬 스토리지 등을 관리하는 경우가 있을 수 있습니다.</p><h4>반드시 필요한 작업만 넣기</h4><p>상태를 업데이트 할 때 수행해야 하는 필요한 작업이 처음엔 적을 것입니다. 그렇기 때문에 매우 편리합니다⁵. 하지만 시간이 지나다보면 필요한 작업의 종류가 늘어날 수 있습니다. 이 때 주의해야 할 것은 ‘정말 이 상태와 관련해 직접적으로 필요한 작업인지, 아니면 연속적으로 실행시키기에 편리한 것인지’ 따져보는 것입니다.</p><p>만약 해당 상태를 다루는 다른 이벤트가 생기고, 이 경우엔 실행되는 작업 중 일부만 필요하다면 다른 작업은 단순히 불필요하거나, 실행되어선 안 되거나, 성능에 문제를 일으키거나 디버깅을 어렵게 만들 수 있습니다.</p><h4>신중하게 연결하기</h4><p>상태를 업데이트 할 때 수행되어야 하는 작업이 있어서 훅을 추가하고, 여기에서 상태를 다시 업데이트 하거나 다른 상태를 업데이트 하고, 여기에 훅이 또 추가되는 등 필요 작업을 계속해서 연결하는 경우가 종종 생깁니다. 이럴 땐 신중할 필요가 있습니다.</p><p>왜냐하면 디버깅이 어렵기 때문입니다. 트리거 처럼 데이터를 다룰 때 부수효과로 작업을 수행하는 건 코드의 흐름을 파악하기 어렵게 만듭니다.</p><p>우리는 코드의 흐름을 파악할 때 데이터가 아닌 함수의 콜 스택을 쫓곤 합니다⁶. 왜냐하면 코드에서 함수는 ‘행동’을 의미하고 문제가 발생했다면 무얼 ‘해서’ 문제가 발생했는지 찾기 때문입니다. 반면, 변수는 값을 담고 값을 확인 하기 위해 사용하고 행동을 기대하지 않습니다.</p><h4>순수한 컴포넌트</h4><p>사실 트리거를 다루거나 관리하기 어렵다면 사용하지 않는 것도 좋은 방법입니다. 데이터베이스에 데이터 삽입을 요청했다면 데이터가 들어가고 끝나면 됩니다. 컴포넌트 역시 그렇습니다. 상태를 변경했을 때 결합된 UI가 변하면 됩니다. 이러한 컴포넌트를 우린 부수효과를 일으키지 않고 순수하다(pure)고 합니다. 그리고 리액트에선 아래와 같이 권장합니다.</p><blockquote>If you’ve exhausted all other options and can’t find the right event handler for your side effect, you can still attach it to your returned JSX with a <a href="https://react.dev/reference/react/useEffect">useEffect</a> call in your component. This tells React to execute it later, after rendering, when side effects are allowed. <strong>However, this approach should be your last resort.⁷</strong></blockquote><h3>여담</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MCR7xQDUxeJxx3nv" /><figcaption>Photo by <a href="https://unsplash.com/@trevormbrown7?utm_source=medium&amp;utm_medium=referral">Trevor Brown</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>여담이지만, 트리거와 <em>useEffect</em> 모두 사용하기 편합니다. 그리고 사용하기에 편한 또 다른 친구가 있습니다. 바로 HTML <em>div</em> 요소 입니다. 웹 개발을 시작할 때, 값을 입력받는 걸 제외하고 뭐든 가능한데 다른 요소는 왜 필요한지 정말 순수한 마음에 궁금했을 정도였죠.</p><p>HTML div 요소에 대해 명세는 이렇게 설명합니다.</p><blockquote><strong>Authors are strongly encouraged to view the </strong><a href="https://html.spec.whatwg.org/#the-div-element"><strong>div</strong></a><strong> element as an element of last resort</strong>, for when no other element is suitable. Use of more appropriate elements instead of the <a href="https://html.spec.whatwg.org/#the-div-element">div</a> element leads to better accessibility for readers and easier maintainability for authors⁸.</blockquote><p>빠르게 적용해야 할 때, 애플리케이션 초기에 개발을 시작할 때, 이제 막 공부를 시작할 때 편리함은 좋은 도구입니다. 하지만 편리함은 눈에 보이지 않는 부작용을 가져오곤 합니다. 새로운 무언가 편리하다면, 한 번쯤 의심해보는 게 개발 뿐 아니라 삶의 다양한 면에서 도움이 될지 모르겠습니다.</p><h3>이유</h3><p><em>“변수에 값을 넣었는데 알아서 바뀌더라고. 난 이게 적응이 안 돼”</em></p><p>왜 적응이 안 되는지, 왜 프론트엔드 코드가 누군가에겐 낯설게 느껴지는지 이유를 알기 어려웠습니다. 오히려 값을 바꿨을 때 UI가 업데이트 되면 이렇게 편한데, 왜 한줄한줄 고생하며 코드를 쓰고 있을까 의문이 들기도 했습니다. 리액트를 다루는 노하우에 관한 글을 보며 모두 다른 소리로 느껴지고, 뭐가 중요한지 알기 어렵고, 동료와 공유하거나 내용을 설명하는 데 정직하지 못한 느낌이 들기도 했습니다.</p><p>이 비어있는 간극이 찝찝하던 어느날, 옛날에 머리를 싸매며 다루던 트리거가 떠올랐습니다. 이유는 오래전부터 설명되어 오고 있었고, 역시 하늘 아래 새로운 것은 없었습니다.</p><p>[¹]: <a href="https://www.ibm.com/docs/ko/db2/11.1?topic=plsql-create-trigger-statement">https://www.ibm.com/docs/ko/db2/11.1?topic=plsql-create-trigger-statement</a><br>[²]: <a href="https://learn.microsoft.com/ko-kr/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#b-using-a-dml-trigger-with-a-reminder-e-mail-message">https://learn.microsoft.com/ko-kr/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#b-using-a-dml-trigger-with-a-reminder-e-mail-message</a><br>[³]: <a href="https://www.mssqltips.com/sqlservertutorial/9329/pros-and-cons-of-triggers-in-sql-server/">https://www.mssqltips.com/sqlservertutorial/9329/pros-and-cons-of-triggers-in-sql-server/</a><br>[⁴]: 트리거를 다룰 때면 ‘안개 속을 헤매는 것 같은 느낌이 든다’는 말을 많이 하곤 했습니다.<br>[⁵]: 3번 참조엔 트리거의 장점으로 <strong><em>‘Triggers are easy to code.’</em></strong>가 있습니다.<br>[⁶]: 『프로그래머의 뇌』(제이펍, 2022) 90쪽<br>[⁷]: <a href="https://react.dev/learn/keeping-components-pure#where-you-_can_-cause-side-effects">https://react.dev/learn/keeping-components-pure#where-you-_can_-cause-side-effects</a><br>[⁸]: <a href="https://html.spec.whatwg.org/#the-div-element">https://html.spec.whatwg.org/#the-div-element</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a67027d84e9d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[준비, 하고 있나요]]></title>
            <link>https://medium.com/@junep/%EC%A4%80%EB%B9%84-%ED%95%98%EA%B3%A0-%EC%9E%88%EB%82%98%EC%9A%94-1b98d9ff09c7?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b98d9ff09c7</guid>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Fri, 28 Jun 2024 08:33:20 GMT</pubDate>
            <atom:updated>2024-06-28T08:33:20.300Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wjj0TlS9VLC7NZeC" /><figcaption>Photo by <a href="https://unsplash.com/@christianlue?utm_source=medium&amp;utm_medium=referral">Christian Lue</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>트위터, 링크드인 뿐만 아니라 오래전 멘티 등으로 부터 절대 가볍지 않은 고민들을 듣곤 합니다. 그리고 저 역시 해결이 쉽지 않은 고민을 갖고 있고, 고민 없이 커리어를 만들어 온 적은 없습니다.</p><p>이번 글에선 여러 고민들을 보고, 듣고 경험하며 그 자리에서 하지 못한 말, 생각을 정리합니다. (횡설수설 하는 것 같다면, 그게 정답입니다.)</p><h3>준비, 하고 있나요</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Vuo4JUdMTzXo03xwuUwZUA.png" /><figcaption><a href="https://youtu.be/xEAXuHvzjao?si=WJ5sfd3EZAG_Uww3">Auburn University Spring 2010 Commencement Speaker Tim Cook</a></figcaption></figure><p>어떤 고민을 하든 가장 먼저 하는 생각은 ‘섣불리 결정하지 말자&#39;입니다. 삶은 신기하게도 쉽고 편해보이는 결정을 반기지 않는 것 같습니다. 그렇기에 무작정 좋은 결정은 많지 않습니다.</p><p>‘그렇지만 똑같이 마음에 안 드는 사람이 있더라도, 더 좋은 연봉, 더 좋은 환경에서 일할 수 있잖아요. 그럼 모든 조건에서 더 좋은 선택 아닌가요?’</p><p>하지만 이런 선택을 하려면 보통 준비(행동)가 되어 있어야 합니다. 사람과의 이별도 그렇습니다. 마음에 안 드는 면을 발견하고 헤어질 결심까지는 보통 준비가 필요합니다. 용기가 없다면 관계를 거절할 수 있는 용기가, 가치관에 맞지 않는다면 관계에 있어 내 가치관은 무엇인지 준비가 되어 있어야 합니다.</p><blockquote>결국 제대로 된 기회를 알아보고 잡기 위해서는 그에 맞는 태도를 지니고 있어야 한다.</blockquote><blockquote>나 역시 지금껏 수많은 기회를 놓치거나 제대로 잡지 못한 적이 많았다. 그 당시에는 타이밍이 좋지 않았다거나, 운이 없었다거나, 다른 핑곗거리를 찾아 합리화했었는데, 시간이 지나고 깨달았다. 그 기회를 잡을 준비가 되어 있지 않았다는 것을 말이다.</blockquote><blockquote>&lt;이해인, 감정은 사라져도 결과는 남는다&gt;</blockquote><p>지금, 다른 회사를 선택할 수 있는 준비가 되어 있나요? 그렇지 않다면 준비가 될 때까지 지금 시간을 견딜 수 있어야 합니다. 그리고 지금 만족하고 있다고 하더라도 언제 찾아올지 모를 기회, 불행을 맞이하기 위해선 역시 준비해야 합니다.</p><blockquote>사람들은 종종 직관을 운이나 운명과 혼동합니다. 직관은 당신이 열어야 할 인생의 문을 알려줍니다. 하지만, 그 문 너머는 항상 대비해야 합니다. 링컨은 이런 말을 남겼습니다. “준비한다면 언젠가 기회는 온다”고 말이죠.</blockquote><blockquote>&lt;<a href="https://youtu.be/xEAXuHvzjao?si=WOPCFoh20taF-GA4&amp;t=649">팀쿡 AUBURN 대학교 졸업 연설</a>&gt;</blockquote><p>준비한다는 건 내가 그리는 미래를, 내가 그리는 미래의 내 모습을 기다리지 않고 지금 또는 근시일에 주도적으로 만들어 보는 것입니다.</p><p>더 좋은 직장은 보통 더 많은 사람이 가고자하고, 채용 담당자는 많은 사람 중 제한된 인원을 선택해야 하기 때문에 점점더 까다로운 기준을 적용합니다. 이건 그 조직에서 뛰어난 기술을 쓰지 않더라도 그렇습니다. 왜냐하면 사람을 선착순으로 채용할 순 없기 때문이죠. 그렇기 때문에 지금보다 더 나은 역량을 갖추기 위해, 그리고 그렇게 준비해온 사람들이 일하는 곳에 적응하고, 그걸 넘어 영향력을 끼치기 위해서 준비해야 합니다.</p><p>만약 좋아하는 다른 일을 해보고 싶다면, 지금 그 좋아하는 일이 무엇인지 탐색하고 시도해보며 준비해야 합니다. (<a href="https://markmanson.net/screw-finding-your-passion">마크 맨슨의 글</a>은 좋아하는 일을 찾아 헤매다 지쳤을 때 도움이 되곤 합니다.)</p><p>예전에 ‘난 나보다 똑똑한 사람을 만나고 싶어. 그런데 쉽지가 않아.’라는 말을 들었습니다. 그래서 전 이렇게 말했습니다.</p><p>‘똑똑한 사람을 만나고 싶으면, 친구한테 소개받는 것도 좋지만 직접 대학 도서관을 가보는 것도 좋지 않을까?’</p><h3>잘 견디고 여유 갖기</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*13zk8Jvnui86Qr4V" /><figcaption>Photo by <a href="https://unsplash.com/@jdelchico?utm_source=medium&amp;utm_medium=referral">James Elchico</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>준비를 하기 위해선 그만큼 여유가 있어야 합니다. 여유는 시간적인 것도 있지만 심적인 것도 있습니다. 눈코뜰새 없이 바빠도 마음에 여유가 있다면 생각과 시야의 범위가 더 넓어집니다.</p><blockquote>옛사람들은 어떻게 이렇게 대범하게 초탈할 수 있었고, 생사존망의 역사적 전환점에서도 침착하고 냉정할 수 있었을까요? 그들은 일의 성취를 추구하는 동시에 그 성취 바깥으로 벗어날 수 있었기 때문입니다. 그들은 눈앞에 던져진 사유의 틀에서 벗어나 문제의 바깥으로 나갈 수 있었습니다.</blockquote><blockquote>&lt;팡차오후이, 나를 지켜낸다는 것&gt;</blockquote><p>하지만 녹록지 않은 현실에 여유는 사치일 수도 있습니다. 저 또한 그랬고 종종 그렇습니다. 그럴 땐 견디는 힘이 중요합니다. 온갖 압박과 스트레스에서 여유를 가질 수 있으려면, 여유에 앞서 견딜 수 있어야 합니다. 견디지 못한다면 이성은 멈추고 머릿속에 온갖 부정적인 생각과 분노, 혐오, 질투, 시기, 불평이 가득차고 몸과 마음은 빠르게 지치기 시작합니다. 왜냐하면 이러한 부정적인 시각, 감정, 긴장 모두 많은 에너지를 요구하기 때문입니다.</p><blockquote>회사에서 상사와 잘 맞지 않아 느닷없이 인도로 떠난 후배가 있다. 당시 후배가 겪은 문제는 충분히 극복 가능한 일이었다. 하루 이틀 일한 사람도 아니고, 그 정도 갈등은 직장에서 충분히 예상 가능한 강도의 어려움이었다. 하지만 그는 극복하지 못했다. 아니 더 정확히 말하자면 극복하고 싶어 하지 않았던 것 같다. 마치 이미 충분히 지쳐버린 노새가 작은 소금 한 가마니에 털썩 주저앉아 더 이상 움직이기를 거부하듯이… 그동안의 상처가 채 아물기도 전에 날아온 잽 한 방에 맥없이 스르륵 쓰러진 것이다.</blockquote><blockquote>&lt;최명화, 나답게 일한다는 것&gt;</blockquote><p>그렇기 때문에 잘 견디려면 이러한 부정적인 감정, 즉 세상에 대한 대응 방법을 잘 다뤄야 합니다. 그러기 위해선 세상보다 나 스스로에게 집중하는 게 정말 중요합니다. 왜냐하면 세상, 조직, 다른 사람을 바꾸는 건 가능하기도 하지만 불가능에 가깝고, 나를 바꾸고 내 시야에 변화를 주는 건 충분히 가능하기 때문입니다.</p><blockquote>이런 의미에서 진정한 이기주의자는 자신의 삶을 과시하거나 타인의 박수를 받는 데 골몰하지 않는다. 타인을 위해 나의 삶을 희생시키고 있다는 피해의식에 사로잡혀 스스로를 괴롭히지도 않는다. 남과의 ‘비교’라는 못된 습성에서 자유롭다. 관심의 대상은 오직 ‘나’ 자신이다. 건강한 공동체를 위해 노력하는 것도 나를 확장하는 것이기에 ‘나의 희생’을 담보로 하지 않는다.</blockquote><blockquote>&lt;최명화, 나답게 일한다는 것&gt;</blockquote><p>물론 모든 감정을 잘 다룰 순 없고 어느정도 또는 그 이상의 부정적인 감정과 힘듦을 정말 ‘견뎌내야 합니다.’ 니체의 세 가지 변화는 왜 자유를 갖고 아이처럼 즐기고 긍정하며 창조하기 전 인내를 먼저 길러야 하는지 알려주고 견디는 현재를 긍정할 수 있도록 해줍니다.</p><blockquote>외경심이 깃들여 있는 강하고 인내력 있는 정신은 많은 무거운 짐을 지고 있다. 정신의 억센 힘은 무거운 짐, 가장 무거운 짐을 요구한다.</blockquote><blockquote>무엇이 무거운가? 인내력 있는 정신은 이렇게 묻고 낙타처럼 무릎을 꿇어 짐을 충분히 싣고자 한다.</blockquote><blockquote>영웅들이여, 내가 짊어지고 나의 억센 힘에 기쁨을 느끼게 될 가장 무거운 짐은 무엇인가? 인내력 있는 정신은 이렇게 묻는다.</blockquote><blockquote>&lt;니체, 황문수 역, 차라투스트라는 이렇게 말했다&gt;</blockquote><p>그리고 오랜시간 잘 견디기 위해선 쉼도 중요합니다. 그래서 전 역설적이지만 먼저 여유를 가지려고 합니다. 중간중간 심호흡을 하고 몸에 긴장을 풉니다. 그리고 이렇게 주문을 외웁니다. ‘숨을 쉬는 바로 지금 이 순간은 아무런 부족함 없이 완벽하다.&#39;</p><blockquote>삶의 상황을 개선하고자 노력하는 것은 잘못이 아닙니다. 우리는 삶의 상황을 개선할 수 있습니다. 하지만 삶을 개선할 수는 없습니다. 삶은 근원적입니다. 삶은 우리의 가장 깊은 내면에 존재합니다. 그것은 이미 완전하고 완벽합니다.</blockquote><blockquote>&lt;에크하르트 톨레, 지금 이 순간을 살아라&gt;</blockquote><p>그리고 집에오면 몇 시간이든 또는 몇 분이든 아무것도 하지 않으면서 걱정하는 스스로를 위로하는 시간을 보냅니다. ‘이거저거 해야 하는 거 아니야?’라고 걱정하고 불안해하는 게 느껴진다면 ‘에이 괜찮아’라며 벌러덩 누워버립니다. 신기하게 그 순간 마음이 편해지는 걸 느낍니다. 그렇게 내일 다시 견딜 수 있는 에너지가 채워집니다.</p><h3>준비, 역지사지</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lU3lfke6S_BQWwZN" /><figcaption>Photo by <a href="https://unsplash.com/@rruprrup?utm_source=medium&amp;utm_medium=referral">Aleksandra Sapozhnikova</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>이런 경험 해보신적 있나요? ‘난 저런 사람 되지 말아야지’라고 생각했지만 막상 그 사람이 처한 상황이 되어보면 그게 그리 쉽지 않았던 경험.</p><p>이제 막 이등병이 되었을 때였습니다. 당시 속했던 부대는 후임에게 가혹한 얼차려가 주어지는 것은 물론이고 후임에게 순한맛으로 지적을 하면 오히려 내가 매운맛으로 혼나는 구조였습니다. 온갖 심하고 모욕적인 말은 기본이고 폭력과 심한 얼차려가 일상이었습니다. 그래서 이등병 때부터 ‘난 저런 선임이 되지 말아야지’라고 생각했습니다. 하지만 막상 후임이 점점 늘어나면서 순한맛 선임은 절대 쉬운 일이 아니라는 걸 알았습니다. 임무를 잘 해내지 못했을 때 잘 타이르는 건 효과가 없어 보였고, 내 빨래는 내가 하고 생활관 밀대질을 내가 하는 건 이상하게도 억울하게 느껴졌습니다. (결국 ‘저런 선임’과 같은 사람은 되지 않았다고 자부하는 군생활을 해냈습니다.)</p><p>이 경험을 떠올리며 조직에 있으면서 다른 사람과 관계가 틀어지거나, 조직 관리자의 결정이 마음에 들지 않을 때면 역지사지를 하곤합니다. ‘저 사람은 왜 저런 결정을 했을까?’</p><p>그리고 다른 방법의 역지사지를 하기도 합니다. ‘만약 나 같은 사람을 관리해야하는 관리자라면, 지금 조직의 관리자가 된다면, 난 어떻게 해야하지?’ 그리고 곧 깨닫습니다. 절대 쉽지 않다고.</p><p>동료와 함께 일하는 것, 그리고 조직의 관리자가 된다는 건 정말 쉽지 않은 일입니다. 특히 잘 해내는 건 더욱더욱 어렵습니다.</p><p>지금 조직의 결정과 조직의 문화가 맘에 들지 않는다면, ‘나라면 어떻게 했을까?’라는 생각으로 리더십을 준비해보는 건 어떨까요?</p><p>왜냐하면 회사의 사장이 되지 않는 이상 누군가와 상하 관계가 나뉜 조직에 있을 것이고, 그건 지금의 상황과 크게 다르지 않을 것이기 때문입니다. 단 한 명과의 관계도 잘 해내지 못한다면, 미래의 내가 맡은 조직은 지금의 조직보다 더 불행해질지 모를 일입니다.</p><blockquote>경영학 책들은 대체로 사람들을 관리하는 방법을 다룬다. 이 책의 주제는 목표 달성 능력을 높이는 자기관리 방법이다. 개인이 다른 사람들을 올바르게 관리할 수 있는지 입증된 적은 없다. 그렇지만 인간은 자신을 관리할 수는 있다. 사실 목표를 달성해야 하는 경영자가 자신도 관리하지 못하면서 부하나 동료들을 관리하기란 어려운 일이다. 경영은 대부분 본보기다. 자기 일과 회사 일에서 자신의 목표를 달성하지 못하는 경영자는 잘못된 본보기가 되는 것이다.</blockquote><blockquote>목표를 달성하는 경영자는 목표를 달성할 수 있는 능력을 갖추어야 한다. 그런 후에는 목표 달성 능력이 습관이 될 때까지 실천해야 한다. 그렇게 목표를 달성하는 경영자가 되도록 노력한 사람들만이 성공한다. 목표 달성 능력은 배울 수 있어야 한다.</blockquote><blockquote>&lt;피터 드러커, 피터 드러커 자기경영노트&gt;</blockquote><h3>유쾌하고 명랑하게</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cFkTVTqtctOWbGZAdLw5sg.png" /><figcaption><a href="https://youtu.be/vRRjmqlI0Ds?si=YcAMXK2eNlSEuCQk&amp;t=738">유퀴즈, 최화정님 편</a></figcaption></figure><p>전 너무 오랜시간 부정적인 감정, 우울한 상태인적이 많아 온몸에 부정이 새겨져있습니다. 마음이 긍정적이고 밝아도 다른 사람과 함께 있으면 어느새 부정적인 기운이 그 사람에게 전달되고 있는 듯한 느낌이 들 정도 입니다. 확실히 삶의 태도는 쉽게 바뀌지 않는구나, 그래서 나이가 들수록 그 사람 얼굴만 봐도 어떤 삶을 살아왔는지 보인다고 하는구나, 라고 생각하곤 합니다.</p><p>하지만 포기할 생각은 없습니다. 왜냐하면 조금이라도 더 삶은 밝고 명랑해야 하기 때문입니다. 그러기 위해선 부조리한 세상에서도 ‘유쾌하고 명랑하자’라고 주문을 외워야 합니다.</p><p>불과 얼마전까지만해도 사람을 만나면 항상 부정적인 이야기만 했습니다. 뭐가 힘들고, 누구 때문에 힘들고, 저게 싫고, 이게 싫고. 그러다 문득 이런 생각이 들었습니다. ‘너도 힘드니, 나도 힘들어 힘내자, 라는 생각으로 풀어내던 이야기들이 나도 너도 힘들게 했구나.’</p><p>‘유쾌하고 명랑한 마음가짐과 태도’로 나와 상대를 위로하면 어땠을까, 하는 생각이 드는 요즘입니다.</p><blockquote>어린애는 순결이며 망각이고 하나의 새로운 출발, 하나의 유희, 스스로 굴러가는 수레바퀴, 최초의 운동, 신성한 긍정이다.</blockquote><blockquote>그렇다, 나의 형제들이여, 창조라는 유희를 위해서는 신성한 긍정이 필요하다. 이제 정신은 자신의 의지를 의욕하고 세계를 상실한 자는 자신의 세계를 획득한다.</blockquote><blockquote>&lt;니체, 황문수 역, 차라투스트라는 이렇게 말했다&gt;</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b98d9ff09c7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[새로운 도전 돌아보기]]></title>
            <link>https://medium.com/@junep/%EC%83%88%EB%A1%9C%EC%9A%B4-%EB%8F%84%EC%A0%84%EC%97%90-%EB%8C%80%ED%95%9C-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0-99d0c8218e25?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/99d0c8218e25</guid>
            <category><![CDATA[work]]></category>
            <category><![CDATA[retrospectives]]></category>
            <category><![CDATA[gpt]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Sat, 15 Jun 2024 14:32:13 GMT</pubDate>
            <atom:updated>2024-06-17T03:15:17.733Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gC0C12sAEjoGL0sr" /><figcaption>Photo by <a href="https://unsplash.com/@kitera?utm_source=medium&amp;utm_medium=referral">Kitera Dent</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>이 글은 2024년 상반기(1월 ~ 6월) 동안 경험 그리고 결과, 과정을 몇 가지 주제로 공유합니다.</p><h3>부정이 아닌 긍정으로의 전환</h3><p>이 기간 동안 있었던 가장 값진 경험은 안드로이드 개발입니다. React Native로 앱을 개발하기로 했을 때 앱의 몇몇 부분을 직접 Native로 개발하기로 결정했습니다. 그러면서 안드로이드 개발을 시작하게 됐습니다. 지난 만 7년 정도되는 개발 경력 동안 낯설 정도의 경험은 초반 1~2년을 제외하곤 없었습니다. 새로운 기술이라도 현재 사용하는 기술의 확장이었기 때문에 낯설 정도까진 아니었습니다.</p><p>하지만 안드로이드는 낯선 기술이었습니다. 언어(Java, Kotlin)부터 View를 다루는 방법 등 모두 새로 파악하면서 해야 했습니다. 특히 순수하게 안드로이드만 개발하는 게 아니라 React Native와 함께 다뤄야 하다보니 낯섦은 더욱 심했습니다.</p><p>이런 낯섦은 생각 이상으로 많은 영향을 끼쳤습니다. 특히 함께하는 사람들과의 신뢰에 많은 영향을 줬습니다. 처음 다루는 기술을 기반으로 프로젝트를 진행하다보니 예상 일정을 공유하기가 어려웠습니다. 그러다보니 프로젝트 플래닝에선 ‘예상 일정은 일주일 정도입니다.’와 같은 방식이 아닌 ‘일주일 안에 마무리도록 하고 특이사항이 발생하면 공유하겠습니다.’와 같은 방식이 주를 이뤘습니다. 그렇기 때문에 불확실성을 최소화하고 개발 방향 설정 자체가 문제가 되지 않기 위해선 그 기간 동안 발생하는 문제를 최소화 해야 했습니다.</p><p>그래서 이 기간 동안 가장 크게 신경쓴 건 ‘공유’였습니다.</p><ul><li>개발을 진행하며 예상치 못한 문제가 발생하고 프로젝트에 영향을 줄거라 판단되면 공유했습니다. 이전에도 일정에 영향을 줄 수 있는 문제가 발생하면 빠르게 공유하기 위해 노력했지만, 이번엔 더욱 신경을 썼습니다.</li><li>반드시 해결해야 하는 문제인데 오랜 시간이 걸리면 그 과정을 문서화했습니다. 왜냐하면 외부에서 볼 땐 문제 ‘하나&#39;를 ‘오랫동안 붙잡고&#39;있는 걸로 보이기 때문입니다. 문서엔 문제가 무엇이고, 어떤 시도들을 했으며, 어떻게 해결했는지를 적었습니다. 이 문서를 통해 지금 구체적으로 무엇을 하고 있는지 어떤 결정을 어떤 근거로 내리고 있는지 공유하려고 했습니다. (사실 엄청 고생하고 있으니 조금만 이해해달라는 의도가 가장 강했습니다.)</li><li>새로운 기술로 제품을 만든다는 건 나 뿐만 아니라 합류할 동료에게도 마찬가지라고 판단했습니다. 그래서 문제 해결 뿐만 아니라 다른 이슈에 대해서도 문서화하기 위해 노력했습니다.</li><li>놓치는 작업이 없도록 노력했습니다. 하기로 했다면 작업을 생성하고 관리했습니다. 그와 동시에 알 수 없는 정보를 줄였습니다. ‘저도 잘 모르겠습니다.’, ‘깜빡했습니다.’, ‘언제 시작할 수 있을지 모르겠습니다.’ 등과 같이 정보가 누락된 공유가 아닌 ‘지금 진행하고 있는 작업으로 인해 지연되고 있습니다. 일정을 앞으로 당기기 위해 우선순위를 높이는 건 어떨까요?’, ‘현재 진행 중입니다.’, ‘혹시 이 작업은 논의 된 적이 없는 거 같은데, 예정대로 진행하면 될까요?’와 같이 정보를 확인하고 갱신하도록 했습니다.</li></ul><p>이 과정을 통해 ‘낯섦&#39;이라는 상황에 몰입하지 않고 ‘내가 할 수 있는 걸 찾고, 해낸다&#39;는, 부정이 아닌 긍정으로의 전환을 경험할 수 있었습니다.</p><p>부정적인 상황과 감정을 긍정적인 방향으로 흐르게 하는 건 정말 멋진 경험입니다.</p><h3>뛰어난 동료와 함께 하기</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/0*BSynR-1vqfE-BLN8" /><figcaption><a href="https://www.usatoday.com/story/sports/soccer/worldcup/2023/12/18/lionel-messi-celebrates-argentinas-world-cup-anniversary-instagram/71959355007/">Lionel Messi celebrates Argentina’s World Cup anniversary on Instagram</a></figcaption></figure><p>뛰어난 동료와 함께하는 상황은 항상 있었습니다. 특히 지금 조직에 합류하고 부터 보다 뛰어난 동료와 일하는 기회는 더욱 늘었습니다.</p><p>하지만 이번엔 그전과 달랐습니다. 전 사실상 앱 개발이 거의 처음이고 함께하는 동료는 그렇지 않았습니다. 게다가 개발 경험을 향상 시키기 위해 틈틈이 노력하고, 함께 하는 동료들과 적극적으로 논의하는 모습은 헐레벌떡 겨우 밥값하는 제 모습과 너무 대비되었습니다.</p><p>오래전부터 이런 시간이 지속되고 이번 기간 집중적으로 이런 상황에 처하다보니, 심적으로 힘든 순간이 더욱 많았습니다. 프로젝트의 중심이 한쪽으로 쏠리는 건 개발이 아닌 다른 직무, 일이 아닌 스포츠에서도 그리 좋은 현상은 아닙니다. 마치 스타 선수가 있는 축구 팀의 수비수1과 같은 느낌입니다. 제대로 해내고 있는 게 맞는지, 내가 짐이 되진 않는지, 내가 아니어도 상관없는 게 아닌지 등등 수많은 심리적 압박감을 느끼곤 했습니다.</p><p>그럴 때면 감정과 행동을 구분하기 위해 애썼습니다. 감정은 주변 상황에 대해 생존하기 위해 자동으로 발생하는 반작용이라고 생각했습니다. 나쁜 게 아니기 때문에 부정하지 않으려 했습니다. 그리고 어떻게 행동해야 할지 집중했습니다. 그러한 행동을 이끌어 내는 데 근거로 세운 문장은 이렇습니다.</p><ul><li>뛰어난 동료, 빛나는 사람과 함께 있다는 건 그렇지 않은 상황보다 분명 더 좋은 환경이다. 그 사람으로 부터 배울 수 있고, 내 부족한 점을 파악할 수 있다. 하지만 쉽지 않다.</li><li>뛰어난 동료가 자신의 역량을 온전히 발휘 할 수 있도록 해야 한다. 왜냐하면 모두 팀 게임이기 때문이다.</li><li>심리적 압박감이 상대로부터 결점을 찾으려고 한다면, 오히려 그 사람의 강점에 집중한다.</li><li>앞으로 뛰어난 사람과 일할 기회는 더욱 많아지고, 피할 수 없다. 그리고 그래야 한다. 왜냐하면 나 혼자 모든 걸 잘 할 수 없고, 내가 더 뛰어난 사람이 된다는 건 내가 속한 곳에 나보다 더 뛰어난 사람이 많다는 걸 의미하기 때문이다.</li></ul><p>덕분에 뛰어난 사람과 함께 일하는 방법을 경험하고 준비하는 좋은 시간을 보낼 수 있었습니다.</p><blockquote>선하고 건강한 사람들과 함께 지내는 일이 쉬울 것 같지만, 사실은 그렇지 않다. 오히려 문제 많고 질 나쁜 사람들과 지내는 것보다 더 어렵다. 몸과 마음이 모두 건강한 사람은 그야말로 이상적이다. 그런 사람과 가까이 지내려면 강인한 의지와 꾸준한 노력이 필요하다. 겸손해야 하고, 용기가 있어야 한다. 모든 걸 스스로 판단해야 하고, 조건 없는 동정과 연민도 경계해야 한다. 그럼에도 나는 당신에게 이렇게 말해 주고 싶다.</blockquote><blockquote>당신에게 최고의 모습을 기대하는 사람만 만나라.</blockquote><blockquote>- &lt;12가지 인생의 법칙&gt;, 조던 피터슨</blockquote><h3>문제 해결</h3><p>“처음 마주하는 환경의 처음 만나는 문제를 어떻게 해결하는지 파악할 수 있는 너무 좋은 기회”</p><p>이전엔 ‘경험&#39;이 많은 문제를 해결해줬습니다. 그래서 상대적으로 적은 경험을 한 동료와 있으면 심리적 안정감을 느끼곤 했습니다. 그들에 비해 더 많은 무기를 들고 있다고 무의식적으로 느껴서 그럴지도 모릅니다.</p><p>하지만 낯선 환경에 처하니 경험은 다시 제로가 되고, 다른 분야에서의 많은 경험은 오히려 부담이 되었습니다. 이전과 반대로 ‘경력이 그렇게 많은데 -’로 시작하는 모든 문장이 저를 향하고 있었습니다.</p><p>그래서 경력과 상관 없이 역량을 파악할 수 있는 좋은 기회라고 생각하고 오히려 문제 해결에 더욱 집중하기 위해 노력했습니다. 아래는 이 과정을 통해 만들어진 문제해결 과정의 흐름입니다. (이 훈련을 위해 <a href="https://product.kyobobook.co.kr/detail/S000002749977">HOW TO SOLVE IT — Paolya, George , Conway, John H.</a>를 자주 참고했습니다.)</p><ul><li>원하는 결과 정의하기: 원하는 결과를 정의하는 건 목표를 분명히 하는 것과 같아서, 중간에 길을 잃어도 다시 돌아와 문제에 집중하게 합니다. 또한 원하는 결과를 정의하다 보면 종종 한 가지 문제가 사실 여러 문제로 구성되어 있다는 걸 알게 합니다. 그리고 문제가 여럿이라면 해결해야 하는 순서, 조건이 존재하게 됩니다. ‘원하는 결과’는 ‘정상적으로 동작하는 경우’를 포함합니다.</li><li>정확히 무엇이 문제인지 파악하기: 보통 무엇이 문제인지 나열하다보면 ‘문제&#39;와 크게 상관없는 상황이 들어가있곤 합니다. 즉, ‘소음’이 끼어있습니다. 그렇기 때문에 소음을 제거하고 문제를 분명히 하는 과정이 필요합니다.</li><li>알고 있는 정보와 모르는 정보를 파악하기: 문제 해결을 위해 알고 있는 정보와 모르는 정보를 파악합니다. 이 과정에서 로깅, 디버깅, 조사 등을 합니다. 그리고 모은 정보를 활용하여 문제를 해결합니다. 여기엔 기존 경험과 직관 등을 사용합니다.</li><li>돌아보기: 문제 해결 과정을 돌아봅니다. 더 좋은 방법은 없었는지, 전과 달리 더 좋아진 점은 없는지 확인합니다. 이 과정을 통해 새로운 문제 해결 과정은 ‘경험&#39;이 되고, 다음 문제를 해결할 때 사용할 ‘직관’을 키워 더 빠르고 정확하게 문제를 해결할 수 있게 합니다.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xujL3Z42IDFJsCqZjkGqQA.png" /><figcaption>이 기간 동안 회고한 이슈들</figcaption></figure><p>그리고 이번 경험을 통해 다시 한 번 알게된 몇 가지를 적습니다.</p><ul><li>신체 상태 그리고 쉼은 정말 중요하다. 쉽게 해결할 수 있는 문제임에도 지쳐있어서 또는 컨디션이 좋지 않아서 해결하지 못하는 경우가 정말 많다.</li><li>멋지고 어렵게 해결하는 건 차선이다. 단순하고 쉽고 명확하게 해결하는 게 최선이다.</li></ul><p>얼마전에 웃으라고 적혀있는 말이었는데, 왠지 다가오는 문장이 있어 공유합니다.</p><blockquote>대학 교재에 답이 없는 이유는, 문제를 풀었다면 답이 없어도 그게 답이라는 걸 알기 때문이다.</blockquote><h3>ChatGPT</h3><p>이 기간 동안 ChatGPT를 정말 많이 사용했습니다. 만약 ChatGPT가 없었다면 결과를 만들어낼 수 있었을까 싶을 정도로 많은 도움을 받았습니다. 그리고 ChatGPT를 사용하기 전에 비해 활용하는 노하우도 늘어나서 이전보다 더욱 잘 사용하게 됐습니다. 이전 정말 AI와 함께 일하는 게 선택이 아닌 필수라는 걸 실감하고 있습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CP00XZGKxj5tpRcmpTlu7A.png" /><figcaption>OpenAI의 ChatGPT 메인 화면</figcaption></figure><p>하지만 분명 주의해야 하는 점도 절실하게 느끼고 있습니다.</p><p>하루는 <a href="https://status.openai.com/incidents/qvp3rhvc3vwk">ChatGPT에 문제</a>가 생겨서 몇 시간 정도 사용하지 못할 때가 있었습니다. 그 순간 마치 업무도 멈춘 것 같은 느낌이 들었습니다. ‘어떻게 해야 하지?’</p><p>확실히 ChatGPT에 의존성이 커질 수록 문제를 해결하는 능력에 좋지 않은 영향을 주고 있다는 사실을 확인하는 순간이었습니다. 이런 느낌을 느낀적이 또 있었는데, 그건 ChatGPT와 돌고 도는 질답을 수시간 반복하다가 직접 문제 해결을 결심하고 너무 쉽게 문제를 해결할 때 입니다.</p><p>그렇기 때문에 인공지능을 잘 활용하여 생산성을 높이는 것과 문제 해결 능력의 기초 체력을 키우는, 다소 상반된 두 영역을 잘 훈련해야 할 필요를 느꼈습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zj6JR5jttZza18E-7cEBsg.png" /><figcaption>인공지능의 활용과 문제 해결의 기초 체력 모두 소홀히 하지 않아야 겠습니다.</figcaption></figure><h3>여유</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4UwtkRypA0gVFJ90" /><figcaption>Photo by <a href="https://unsplash.com/@aaronburden?utm_source=medium&amp;utm_medium=referral">Aaron Burden</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>이 기간을 통해 이전보다 더 나아지기 위해 노력하는 데 필요한 많은 요소 중 몇 가지를 놓치고 있었다는 사실을 알게 됐습니다.</p><p>그건 여유와 이유입니다. 꽤 오래전 부터, 지금도 계속 꼬리를 물며 답을 구하려고 하는 건 ‘왜 더 나아져야 하는가?’ 입니다. 사실 더 나아져야 할 이유 없이 그냥 해도 됩니다. 하지만 깨달음이 있는 사람이 아니고선 더 이상 그 무엇도 할 힘이 없을 때, 더 해야할 그 어떤 이유도 찾지 못할 때, ‘그럼에도 더 나아지기 위해 희생하고 노력해야 돼&#39;라고 스스로를 설득하려면 찾아 둔 이유가 있어야 합니다.</p><p>그리고 평소에 불필요해보이는 이유를 찾으려면 여유가 있어야 합니다.</p><p>그리고 사실 그 무엇을 하더라도 여유는 반드시 있어야 합니다. 몸이 아무리 바쁘고 지치더라도 마음과 생각엔 여유가 있어야 합니다.</p><blockquote>하지만 사안은 군대를 파견만 하고 친히 전선으로 나가 군사들을 독려하지 않고 가족과 친구를 데리고 건강성에서 20리 떨어진 동산에서 바둑을 두고 있었습니다. 마침내 전선의 첩보가 전해졌을 때 그는 첩보를 슬쩍 옆에 놔두고 계속 바둑을 두었습니다. 그러자 주위 사람들이 참지 못하고 물었습니다. “지금 전방의 전투는 도대체 어떻게 되고 있습니까?” 사안은 가볍게 “애들이 이겼나 봅니다.”라고 말하고는 계속해서 바둑을 두었다고 합니다.</blockquote><blockquote>옛사람들은 어떻게 이렇게 대범하게 초탈할 수 있었고, 생사존망의 역사적 전환점에서도 침착하고 냉정할 수 있었을까요? 그들은 일의 성취를 추구하는 동시에 그 성취 바깥으로 벗어날 수 있었기 때문입니다.<br><br>- &lt;나를 지켜낸다는 것&gt;, 팡차오후이</blockquote><p>여유가 있어야 예상치 못한 일이 생겨도 처리할 수 있는 틈이 있고, 여유가 있어야 해결한 문제를 더욱 다듬을 수 있습니다. 여유가 있을 때 생기는 좋은 효과는 너무 많습니다. 사실 여유가 있어서 나쁜 이유를 찾는 게 어렵거나 불가능할 정도 입니다.</p><p>하지만 그 무엇보다도 여유가 있어야 나를 지킬 수 있습니다.</p><h3>마무리</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UBmLVjFMYV-d3JFC" /><figcaption>Photo by <a href="https://unsplash.com/@calebjshaver?utm_source=medium&amp;utm_medium=referral">CALEB SHAVER</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>힘든 시간을 보냈지만 그만큼 또는 그 이상으로 많은 걸 얻은 시간이었습니다. 하지만 이게 끝은 아닙니다. 앞으로 또 어떤 시간이 기다릴지 모를 일입니다.</p><p>줄곧 공유하곤 하는 퍼스널 칸반에서 가장 중요하다고 생각하는 건 ‘내가 나에게 보상&#39;을 주는 것입니다. 눈코뜰새 없이 지나가는 시간을 보내고 나면 어떤 날들을 보냈는지 기억조차 나지 않습니다. 그럴 때 내가 해냈던 완료된 작업들은 나에게 ‘대단한데? 고생했어&#39;라고 해주는 ‘보상&#39;입니다.</p><p>이 글 역시 그 누가 ‘준프 정말 고생했어요. 대단한 일을 했는데요?’라고 인정하고 말해주길 기다리는 게 아니라, 외부의 평가에 연연하지 않고 나 스스로 보상을 주기 위한 글입니다.</p><p>그리고 이 글을 읽는 분이 힘들게 지나온 길을 돌아보며 스스로를 토닥이고 응원할 수 있기를 바랍니다 :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99d0c8218e25" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[계획적인 삶을 위해 Part 5. 퍼스널 칸반과 히스토리 관리]]></title>
            <link>https://medium.com/@junep/%EA%B3%84%ED%9A%8D%EC%A0%81%EC%9D%B8-%EC%82%B6%EC%9D%84-%EC%9C%84%ED%95%B4-part-5-%ED%8D%BC%EC%8A%A4%EB%84%90-%EC%B9%B8%EB%B0%98%EA%B3%BC-%ED%9E%88%EC%8A%A4%ED%86%A0%EB%A6%AC-%EA%B4%80%EB%A6%AC-0c5868c859cd?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/0c5868c859cd</guid>
            <category><![CDATA[kanban]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Thu, 09 May 2024 12:49:09 GMT</pubDate>
            <atom:updated>2024-05-22T01:56:16.199Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3K0T-fDVLEG_UDY1" /><figcaption>Photo by <a href="https://unsplash.com/@marcospradobr?utm_source=medium&amp;utm_medium=referral">Marcos Paulo Prado</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>칸반, 특히 퍼스널 칸반을 사용하다보면 히스토리 관리와 관련해 몇 가지 좋은 점을 활용할 수 있습니다. 이번 글에서는 칸반의 작업(Task) 히스토리 관리 방법과 활용 방법을 살펴보려고 합니다.</p><h3>댓글을 통한 히스토리 관리</h3><p><a href="https://medium.com/@junep/계획적인-삶을-위해-part-4-퍼스널-칸반의-깊은-이야기-그리고-회고-55a97f2347eb">이전 글</a>에서 공유했던 것 처럼 칸반 소프트웨어는 작업과 관련된 구체적인 정보를 두 군데에서 다룰 수 있습니다. 하나는 설명(Description)이고 하나는 댓글(Comment)입니다. 제가 주로 사용하는 Trello는 설명과 활동(Activity)이지만 주요 역할은 동일합니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*shJcVPqP5LGb0j6YuQGxxg.png" /></figure><p>이 두 영역의 역할은 다음과 같이 나눌 수 있습니다.</p><ul><li><strong>설명</strong>: 설명엔 작업과 관련된 기획, 명세 등 문서를 추가합니다. 즉, 작업과 관련된 주요 정보입니다.</li><li><strong>댓글</strong>: 댓글엔 작업과 관련된 히스토리를 기록합니다. 예를 들어, 설명에 있는 초기 기획이 중간에 변경됐거나 구두로 논의한 내용을 적을 수 있습니다. 또한 작업을 하며 참고한 문서, 작업을 수행하며 발생한 이슈, 문제 해결 과정 등, 말 그대로 작업과 관련된 모든 히스토리를 기록할 수 있습니다.</li></ul><p>아래 이미지는 제가 진행했던 작업 히스토리를 기록한 몇몇 댓글(또는 활동)입니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mkzoujZZIyrj0fxmVFOiVg.png" /></figure><p>히스토리는 형식 없이 기록하고 작업을 하는 데 필요한 내용이라면 뭐든 적습니다. 여기에 더해 작업을 하며 GPT와 나눴던 대화도 적기도 합니다.<br>이렇게 히스토리를 쌓기 시작하자 예상하지 못했던 여러가지 좋은 점이 발견되었습니다.</p><h4>문제 해결의 흐름을 파악할 수 있다</h4><p>우리는 문제를 해결할 때 좋은 방향으로만 해결하진 않습니다. 가장 좋은 방법으로 해결하기도 하지만 많은 경우 ‘우당탕탕’ 해결하곤 합니다. 이때 얼마나 우당탕탕 헤매느냐가 작업 진행 시간, 난이도를 결정합니다. 얼마나 검색을 했는지, 문제를 해결하는 어떤 지점에서 동료와 대화를 하는지, 내가 관련 지식을 얼마나 갖고 있는지 등이 우당탕탕에 관여를 합니다.</p><p>저 같은 경우 문제를 해결할 때 종종 다른 길로 빠지곤 합니다. 문제를 해결하려고 이런 저런 정보를 획득하다보면 정보 사이에 끼어있는 흥미로운 주제가 있고, 관련 주제를 알아보고, 연계된 다른 정보를 알아보고 하다보면 문제 해결과는 거리거 멀어져있곤 합니다. 또는 급한 마음에 어찌저찌 검색하고 우다다다 적용하다보면 문제가 해결되어 있습니다. 마치 문제를 풀기위해 알고 있는 공식을 무작정 때려박는 느낌입니다. 그래서 구체적으로 어떤 과정을 거쳐 해결했는지 알기 어렵습니다. 그래서 다른 사람에게 공유하기도 민망합니다.</p><p>작업 히스토리는 ‘내가 어디까지 문제를 해결했고 어떤 시도를 했는지’ 보여줍니다. 그래서 한참을 헤매도 다시 방향을 잡는데 많은 도움을 줍니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YCl5-Uu4kHOuZ4rIa5hlZA.png" /><figcaption>정신 차려야지!</figcaption></figure><p>또한 이런 과정 역시 고스란히 히스토리로 남아있어서 회고를 하며 문제 해결에 문제는 없었는지 보다 구체적으로 파악하고 개선할 수 있습니다.</p><h4>작업 과정을 구체적으로 공유할 수 있다</h4><p>작업을 하다보면 ‘왜 예상 일정보다 늦어지는지’, ‘어떤 이유로 빨리 끝났는지’, ‘왜 해결 불가능한지’, ‘어떤 방법을 시도했는지’ 등에 대한 정보를 요구받을 때가 있습니다.</p><p>히스토리를 기록하면 이런 요구사항에 대응하기가 수월해집니다. 쌓아둔 히스토리 덕분에 가장 크게 좋아진 점은 ‘예상 일정에서 벗어났을 때 설명’과 ‘시도한 방법 공유’입니다.</p><p>예상 일정에서 벗어난 경우에 원인 분석을 하고 대비 할 수 있는 부분이 있다면 대비하는 과정을 거칩니다. 예를 들어, 협업에 있어 서로 일정이 맞물리지 않아 문제가 있었거나 선택한 기술에 문제가 있었다면, 앞으로 협업을 진행할 땐 서로 일정을 먼저 조율하는 과정을 거치거나 기술 선택 전에 발생했던 문제 등을 사전에 검토할 수 있습니다. 하지만 구체적으로 왜 늦어졌는지 알 수 없다면 막연히 ‘일정을 지키도록 해야겠습니다.’와 같이 모호한 개선점이 세워지기 마련입니다. 특히 작업량이 많고 바쁜 기간엔 문제 발생의 구체적인 원인을 파악하기 어려운 경우가 더 많습니다.</p><p>시도한 방법의 경우도 그렇습니다. 문제 해결과 관련해 개발자와 대화를 하다보면 ‘이런 방법은 해보셨나요?’ 또는 ‘이 방법이 더 좋을 거 같은데 어떠신가요?’와 같은 질문을 많이 주고받습니다. 그런데 예상외로 ‘해봤는데 이유는 잘 떠오르지 않고 잘 안 되더라구요’와 같이 다소 모호한 답변을 하는 경우가 종종 있습니다. 그러면 질문한 사람 입장에선 ‘그럴 거 같지 않은데, 다시 해보시라고 요청해볼까’와 같이 생각할 수 있습니다. 이때 히스토리를 기록해두면 어떤 방법을 사용해봤고 어떤 문제가 있어서 문제 해결에 도움이 되지 않았는지 구체적으로 파악할 수 있습니다.</p><p>또한 장애가 발생하거나 해결하기 어려운 문제를 맞닥뜨리는 등 히스토리를 팀 내에 문서로 공유해야할 때 가장 빛을 발합니다.</p><h4>어떻게 진행했는지 참고할 수 있다</h4><p>작업을 하다보면 이전에 진행했던 작업 또는 해결했던 문제와 비슷한 상황에 처할 수 있습니다. 그런데 기억이 잘 나지 않는다면 이때만큼 답답할 때가 없습니다.</p><p>히스토리를 남겨두면 이런 상황에 도움이 됩니다. 특히 정말 정신이 없을 때 주말은 작업했던 기억이 증발하기에 가장 좋은 시간입니다. 월요일에 출근해서 금요일에 했던 작업을 보면 ‘내가 왜 이렇게 했지, 이제 뭘 해야 하지?’와 같은 상황을 종종 겪곤 합니다. 이외에도 히스토리가 쌓인 작업이 많아지면서 나 뿐만 아니라 동료와 함께 참고하는 정보가 발생하기도 합니다. 그래서 히스토리를 남길 땐 참고했던 문서도 남겨두곤 합니다.</p><h4>어필할 수 있다</h4><p>회고를 하거나 이력서를 정비하는 등 내가 작업한 내용을 돌아봐야 할 때면 기억이 가장 문제입니다. 분명 A라는 작업을 했는데 무슨 과정을 통해 해냈는지 알기 어려우면 돌아보기도 개선하기도 어필하기도 힘듭니다.</p><p>특히 어려운 문제를 해결했고 어필하면 좋을 만한 업적(?)인데, 과정이 기억나지 않으면 극단적인 경우 다른 사람이 보기에 내가 한 일이 아닐 수도 있습니다. 또한 주기적으로 내가 해낸 작업 중 눈여겨볼만 한 걸 공유해야 하는 상황이 있는데 기억이 나지 않는다면 매번 소설을 쓰듯 설명해야 합니다. 대표적으로 기술 블로그 작성이 있습니다.</p><p>아무리 작은 작업이라도 히스토리를 쌓아두면 어필하기에 좋은 내용을 찾을 수 있는 대상이 많아지기 때문에 이전보다 그 과정이 훨씬 수월해집니다.</p><p>그래서 전 히스토리를 잘 적어둔 작업 카드에는 ‘참 잘했어요’ 라벨을 붙이고 종종 검색하곤 합니다. 왜냐하면 스스로 뿌듯한 감정을 느끼면 다른 사람에게 어필(자랑)하고자 하는 마음이 더 잘 생기기 때문입니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/938/1*xF2d1wmPsX2vzxBr4CXRPw.png" /><figcaption>참 잘했어요</figcaption></figure><h3>작업 히스토리를 기록할 때 몇 가지 규칙</h3><p>히스토리를 기록할 때 좋은 점이 많지만 지켜야 할 규칙이 있습니다. 바로 ‘<strong>기록이 귀찮고 어려워선 안 된다</strong>’ 입니다.</p><p>그래서 히스토리는 처음부터 기록하는 게 아니라 작업을 진행하다보니 히스토리를 남겨야겠다는 필요성을 느낄 때 남기곤 합니다.</p><p>또한 히스토리를 남길 때 형식을 따지지 않습니다. 그렇기 때문에 스스로에게 남기는 글이라 생각하고 반말로 적고 대부분은 복붙을 합니다. 코드가 필요하다면 내가 핵심을 알기에 적당한 양의 코드를 가져옵니다.</p><p>즉, 최소한의 비용으로 히스토리를 기록합니다.</p><h3>카드 정리를 통한 히스토리 관리</h3><p>저를 포함한 현대의 많은 사람이 겪는 문제 중 하나는 FOMO(Fear Of Missing Out)입니다. 그래서 나보다 뛰어난 사람 또는 경쟁자를 보며 ‘나도 저거 해야지’하며 할 일 목록을 늘려나가곤 합니다. 그렇게 기억 저편에 쌓이고 쌓인 목록은 ‘뭔지 모르겠지만 뭔가 해야돼, 이럴 시간이 없어’라며 스스로 압박을 합니다. 그리고 실제로 자리에 앉아 뭘 해야하는지 적어보면 기억 저편에 쌓여있던 목록(히스토리)이 술술 나옵니다. 일종의 자이가르닉 효과(Zeigarnic effect)입니다.</p><p>퍼스널 칸반을 통해 해야 할(하고싶은) 일을 무작위로 쌓다보면 카드 리스트는 생각했던 것 이상으로 길어집니다. 이 많은 할 일이 내 마음을 누르고 있었다고 생각하니 더 무겁게 느껴집니다. 이 때 중압감을 느끼고 끝내는 게 아니라, 해야 한다고 생각해 남겨뒀던 할 일 목록(과거 내가 해야겠다고 생각한 일의 히스토리)을 정리합니다.</p><p>정리하는 과정은 이렇습니다. 백로그엔 목표 일정이 없어도 되지만 대기(할 일) 목록은 목표 일정이 있는 작업을 할당합니다. 백로그는 많이 쌓여 있을 때 정리를 하고 대기는 묙표 일정이 지났을 때 정리를 시작합니다. 정리를 할 땐 하기로 한 일을 ‘정말’ 할 건지 결정합니다. 만약 하기로 했다면 남겨두고 일정을 연장합니다. 그렇지 않다면 과감히 삭제합니다. (전 ‘파기’라는 목록이 있어서 먼 미래에 한 번 더 기회를 주곤 합니다.) 그리고 계속해서 연장되는 카드가 있다면 ‘정말’ 할 건지 다시 한 번 고민합니다. ‘이렇게 계속 연장만 되는데 정말 할 거 같아?’라는 질문을 던집니다.</p><p>이 과정을 한 달, 두 달 거치다보면 하고자 했던 일의 대부분은 할 여력도 없고 할 필요가 없다는 걸 알게 됩니다. 세상에 하면 좋은 일은 많지만 모두 다 할 수 없다는 사실을 시각적으로 반복해서 확인하게 됩니다. 그러다보면 ‘해야 할 거 같은’ 카드를 추가하려고 할 때 ‘정말’ 할 수 있을지 생각하게 되고 목록에 쌓는 것 자체를 줄이게 됩니다. 그리고 더 시간이 지나면 ‘해야할 것 같은’ 불안감 또는 욕망이 줄어듭니다.</p><h3>마무리</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*waPTZhrWmVvqzNpm" /><figcaption>Photo by <a href="https://unsplash.com/@jacquiemunguia?utm_source=medium&amp;utm_medium=referral">Jacqueline Munguía</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>퍼스널 칸반을 유용하게 쓰면서 몇몇 글을 쓰고 주변 동료에게 공유하기도 했습니다. 퍼스널 칸반과 관련해선 더이상 새로울 것도 없고 계속해서 잘 활용하자라는 생각이 들곤 했습니다. 그러다 최근 이전에 경험하지 못했던 좋은 점 몇 가지를 경험하게 되었고 이렇게 공유하게 됐습니다.</p><p>사실 이 경험 모두 칸반 카드에 적혀있던 것입니다. 불현듯 아이디어가 떠올랐고 ‘칸반과 관련해 아이디어 정리 및 글쓰기’란 카드로 만들었습니다. 그리고 댓글에 이 글의 아이디어 몇 가지를 적었습니다. 그렇게 목표로 했던 일정이 몇 번 연장된 후 ‘정말’ 해야겠다는 생각이 들어 이렇게 정리해서 공유합니다.</p><p>이 경험들이 어떤 형태로든 도움이 되면 좋겠습니다 :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0c5868c859cd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[코드에 예민해지기]]></title>
            <link>https://medium.com/@junep/%EC%BD%94%EB%93%9C%EC%97%90-%EC%98%88%EB%AF%BC%ED%95%B4%EC%A7%80%EA%B8%B0-aa6bf8864ce4?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/aa6bf8864ce4</guid>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Wed, 08 May 2024 10:06:21 GMT</pubDate>
            <atom:updated>2024-05-08T10:06:21.421Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cWvsUekJj8763dmk" /><figcaption>Photo by <a href="https://unsplash.com/@creativemomentsphotography09?utm_source=medium&amp;utm_medium=referral">Vinit Vispute</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>무언가 반복하다 보면 점점 예민해지곤 합니다. 어깨 부상으로 팔굽혀펴기를 못할 때가 있었습니다. 그때부터 ‘어떻게 하면 어깨를 다치지 않고 팔굽혀펴기를 할 수 있을까’ 하는 고민을 하고 연습을 오래 해왔습니다. (오랜기간 해온 것이지 자주 하는 건 아닙니다.) 그리고 처음과 달리 호흡이나 팔의 각도, 힘이 들어가는 근육들이 점점 더 선명하게 느껴지곤 합니다. 달리기와 명상도 반복 할수록 더 작은 부분에 집중할 수 있게 되었습니다.</p><blockquote>달리기에 숙련된 사람은 주의를 기울이고 조정해나가는 법을 배우게 된다. 컨디션이 좋아지고 호흡도 안정적으로 자리를 잡자 셸비도 바로 이 방법을 터득했다. 속도를 조절하고, 새로운 목표를 떠올리면서 다른 주자들을 따라잡은 것이다. 운동선수들은 자신의 운동감각을 인지하는 능력이 서서히 축적되어 시간이 갈수록 몸 상태를 더욱 예민하게 파악한다. 달리기 선수의 경우 특정 근육의 수축과 호흡 패턴, 심박 수, 발의 착지 방식, 보폭, 보속(분당 걸음 수) 등을 세세하게 인지한다. 몸 상태에 귀 기울이는 법을 알면 좀 더 수월하게 달리고, 목표를 달성하는 데 도움이 되는 요소와 그렇지 않은 요소를 구분할 수 있다.¹</blockquote><p>그래서 숙련된다는 건 예민해지는 걸 포함하는 게 아닐까 합니다. 코드를 작성하는 것도 그렇습니다. 이전 같으면 신경쓰지 않았던 부분을 더 예민하게 느끼고 더 신경을 쓰게 됩니다. 그렇다고 시간이 더 드는 게 아닙니다. 평소 알게 모르게 했던 고민의 결과가 자연스레 코드에 녹아들고, 집중 할 곳과 아닌 곳을 선택하는 예민함도 더 늘어납니다.</p><p>이번 글에선 코드를 작성하면서 ‘언제부터 이런 걸 신경썼지? 신기하네’ 했던 경험, 전과 달리 예민하게 다가오는 코드를 공유하려고 합니다.</p><ul><li><strong>setter에 대해</strong></li><li><strong>긍정이 부정보다 이해하기 쉽다.</strong></li><li><strong>함수의 인터페이스</strong></li></ul><h3>setter에 대해</h3><p>여기에서 setter는 함수를 통해 변수, 상태 등의 값을 바꾸는 걸 말합니다.</p><pre>// setValue는 모두 setter<br><br>const [value, setValue] = useState(1);<br><br>const obj = {<br>  value: 1,<br>};<br>const setValue = value =&gt; {<br>  obj.value = value;<br>};<br><br>class SomeObject {<br>  value = 1;<br><br>  setValue(value) {<br>    this.value = value;<br>  }<br>}</pre><p>setter는 정말 어려운 존재입니다. 별 생각 없이 쓰기 정말 좋지만 뭔가 찜찜 합니다. ‘이렇게 쓰는 게 맞나?’</p><p>변수에 접근할 수 있는 방법이 많아지면 그 변수는 전역 변수화 됩니다. 전역 변수는 여러 곳에서 의존하기 때문에 변화에 대응하기 쉽지 않습니다. 그래서 변화를 위해 다른 변수를 추가하게 되고 그렇게 코드는 점점 꼬여만 갑니다.</p><p>setter는 감춰둔 변수에 접근할 수 있는 가장 쉬운 방법 중 하나입니다. 그렇기 때문에 단순하게 사용할 땐 유용하지만, 단순하게 사용하기 어려워지고 다른 변수와 함께 사용하는 경우가 많아지면 다시 감출 필요가 있습니다.</p><p>예를 들어, isChecked를 수정하는 setIsChecked가 있다고 해보겠습니다.</p><pre>const [isChecked, setIsChecked] = useState(false);</pre><p>처음엔 이렇게 사용하는 것만으로도 충분합니다. 하지만 시간이 갈수록 이 상태에 의존하는 로직이 많아진다면 setter를 숨기는 게 좋습니다. 그럼, 어떻게 숨기면 좋을까요? 전 이럴 때 값을 직접 다루도록 하지 않고 행동을 요청하도록 합니다.</p><pre>const useCheck = (initialValue) =&gt; {<br>  const [isChecked, setIsChecked] = useState(false);<br><br>  return {<br>    isChecked,<br>    check: () =&gt; {<br>      setIsChecked(true);<br>    },<br>    uncheck: () =&gt; {<br>      setIsChecked(false);<br>    },<br>  };<br>};</pre><p>이렇게 하면 외부에서 값을 직접 다루지 않기 때문에 변화에 유연해집니다. 예를 들어, 아무런 동작도 하지 않은 초기 상태가 필요하다면 true 또는 false 뿐만 아니라 또 다른 상태가 필요합니다. 이전 같으면 isChecked가 undefined도 다루도록 하고 isChecked를 다루는 다른 곳을 모두 살펴야 할 수 있습니다. 하지만 값을 감추면 다른 방법으로 대처할 수 있습니다.</p><pre>const useCheck = (initialValue) =&gt; {<br>  const [isDirty, setIsDirty] = useState(false);<br>  const [isChecked, setIsChecked] = useState(false);<br><br>  return {<br>    isDirty,<br>    isChecked,<br>    check: () =&gt; {<br>      if (!isDirty) {<br>        setIsDirty(true);<br>      }<br>    <br>      setIsChecked(true);<br>    },<br>    uncheck: () =&gt; {<br>      if (!isDirty) {<br>        setIsDirty(true);<br>      }<br>      <br>      setIsChecked(false);<br>    },<br>  };<br>};</pre><p>‘값을 감추기 위해 useCheck를 만들지 않더라도 가능한거 아닐까요?’</p><p>맞습니다. 하지만 외부에 노출된다면 어느 곳에서 setIsDirty를 쓰는지, setIsChecked를 isDirty 체크 없이 사용하지 않는지 또는 앞으로 규칙을 안 지키지 않을지 등을 보장할 수 없습니다. 그리고 변수명을 바꾸거나 다른 추가 로직이 필요하거나 할 때 useCheck와 아닌 곳을 분리해서 집중해 관리할 수 있다는 것 또한 장점입니다.</p><p>이러한 흐름과 방법은 아래와 같은 경우에도 적용됩니다.</p><pre>const Parent = () =&gt; {<br>  const [isChecked, setIsChecked] = useState(false);<br>  <br>  return (<br>    &lt;SomeComponent1 setIsChecked={setIsChecked} /&gt;<br>    &lt;SomeComponent2 setIsChecked={setIsChecked} /&gt;<br>  );<br>};</pre><p>컴포넌트에 이런식으로 setter를 직접 전달하면 지금은 편하지만 나중에 isChecked와 관련된 로직을 검토할 때 어려워질 수 있습니다. 왜냐하면 isChecked에 대해 살펴볼 때 SomeComponent1, SomeComponent2의 내부를 꼼꼼하게 검토해야 하기 때문입니다. 왜냐하면 isChecked에 관심을 갖는 컴포넌트는 Parent 뿐이거나 소수여야 대응이 수월하기 때문입니다. isChecked에 의존, 즉 관심을 가진 주체가 많을 수록 검토는 어렵습니다. 그렇기 때문에 아래와 같이 하면 좋습니다.</p><pre>const Parent = () =&gt; {<br>  const [isChecked, setIsChecked] = useState(false);<br>  <br>  return (<br>    &lt;SomeComponent1 onEvent1={checked =&gt; setIsChecked(checked)} /&gt;<br>    &lt;SomeComponent2 onEvent2={checked =&gt; setIsChecked(checked)} /&gt;<br>  );<br>};</pre><p>이러한 방법은 SomeComponent 등으로부터 isChecked를 숨기는 방법입니다.</p><h3>긍정이 부정보다 이해하기 쉽다</h3><p>변수명을 긍정의 방향으로 짓는 편입니다. 이전 예시를 참고해보면 isChecked로 짓지 isUnChecked라고 짓지 않습니다. ‘당연한거 아닌가?’ 할 수 있지만 자세히 살펴보면 부정형으로 변수명을 짓는 경우가 많다는 걸 알 수 있습니다.</p><p>그렇게 하는 이유 중 하나는 ‘<em>기본 값이 부정이고 어떤 행동이 있고 나서야 긍정으로 바뀌기 때문</em>’입니다.</p><p>부정형 변수명은 로직을 파악할 때 생각을 한 번 더 해야 합니다. 예를 들어, !isUnChecked는 어떤 의미일까요. ‘체크되지 않지 아니한 상태’입니다. !isChecked는 ‘체크되지 않은 상태’입니다.</p><p>조건 로직을 작성할 때에도 이 방법을 활용하곤 합니다.</p><p>이러고 저러다보면 아래와 같이 복잡한 조건문이 만들어질 때가 있습니다.</p><pre>if (!a || !b) {<br>  // a를 만족하지 않거나 b를 만족하지 않으면 동작<br>}</pre><p>이런 로직은 값을 검증하는 로직에서 많이 활용됩니다. 이 로직을 만족하는 경우는 a를 만족하지 않거나 b를 만족하지 않거나 둘 다 만족하지 않는 경우입니다. 눈치 채셨겠지만 a &amp;&amp; b를 제외한 다른 경우입니다. 즉, !(a &amp;&amp; b)입니다. 이럴 땐 아래와 같이 하는 것도 좋은 방법입니다.</p><pre>if (!(a &amp;&amp; b)) {<br>  // a와 b를 모두 만족하는 게 아니라면 동작<br>}<br><br>// 또는<br><br>if (a &amp;&amp; b) {<br>  // a와 b를 모두 만족하는 경우엔 동작하지 않아도 됩니다.<br>} else {<br>  // 동작<br>}</pre><p>이 방법은 컴포넌트를 렌더링 하는 곳에서도 활용할 수 있습니다.</p><pre>// 체크되지 않았을 때 렌더링<br>{!isChecked &amp;&amp; &lt;Component /&gt;}<br><br>// 또는<br><br>// 체크됐으면 렌더링 하지 않고 체크 안 되었으면 렌더링<br>{isChecked ? null : &lt;Component /&gt;}</pre><h3>함수의 인터페이스</h3><p>퍼뜨려져 있는 관련 로직을 함수로 모아서 분리하는 것도 좋지만 테스트에서 모킹(mocking)하기 위해 분리하는 경우도 종종 있습니다. 이렇게 하는 이유는 테스트 대상(SUT; System Under Test)과 아닌 부분을 분리해서 테스트 대상을 독립적으로 검증하기 위함입니다.</p><p>이렇게 하기 위해선 함수의 인터페이스가 잘 정의되어 있어야 합니다. 만약 인터페이스가 종종 바뀐다면 테스트는 정상적으로 통과하는 데 실제 코드에선 에러가 발생하는 경우가 발생 할 수 있습니다.</p><p>인터페이스가 잘 정의되어 있고 모킹을 할 수 있다는 것은 인터페이스를 기준으로 다른 구현체로 바꿀 수 있다는 걸 의미합니다.</p><p>인터페이스를 기준으로 다른 구현체로 바꾸는 경우는 외부 자원에 의존하는 구현체일 때가 많습니다. 예를 들어 백엔드 API를 호출하는 경우입니다.</p><p>이렇게 구현체를 바꾸는 건 단위 테스트 뿐만 아니라 프론트엔드의 기능을 직접 확인해야 할 때에도 활용될 수 있습니다. 그러기 위해선 함수의 인터페이스를 변경하기 않고 테스트를 위한 구현체를 만들어야 합니다.</p><p>말은 어렵지만 코드를 보면 크게 어렵지 않습니다. 우리는 API 응답을 활용하여 이런저런 동작을 하도록 하는 경우가 많습니다.</p><pre>const { mutate, isError, isSuccess } = useMutation({<br>  mutationFn: (productId) =&gt; {<br>    return fetch(<br>      &#39;/products/addToCart&#39;,<br>      { method: &#39;POST&#39;, ... }<br>    )<br>      .then(response =&gt; response.json());<br>  },<br>});<br><br>useEffect(() =&gt; {<br>  // ...<br>}, [<br>  isError,<br>  isSuccess,<br>]);<br><br>const handleSubmit = async (data) =&gt; {<br>  const response = await mutate(data.productId);<br>  // ...<br>};</pre><p>이때, 응답이 성공하거나 실패했을 때 다른 상태와 연계해서 UI를 그리는 경우 정상적으로 동작하는지 확인하는 작업은 상당히 번거롭기 마련입니다. 단위 테스트를 작성하자니 외부 의존성이 높아 난이도가 높고 E2E 테스트는 엄두가 나지 않습니다. 어쩔 수 없이 API를 호출하기 위해 입력하고 클릭하는 과정을 수도 없이 거칩니다.</p><p>이럴 땐 API 호출을 담당하는 로직을 분리해서 원하는 결과를 무조건 만들도록 구현체를 변경하면 테스트가 조금은 더 수월해집니다. 예를 들어 에러가 발생하는 경우를 검증하고 싶다면 아래와 같은 순서로 구현체를 변경합니다.</p><p>먼저 훅을 만듭니다.</p><pre>const useAddToCart = () =&gt; {<br>  const { mutate, isError, isSuccess } = useMutation({<br>    mutationFn: (productId) =&gt; {<br>      return fetch(<br>        &#39;/products/addToCart&#39;,<br>        { method: &#39;POST&#39;, ... }<br>      )<br>        .then(response =&gt; response.json());<br>    },<br>  });<br><br>  return {<br>    addToCart: mutate,<br>    isErrorAddToCart: isError,<br>    isSuccessAddToCart: isSuccess,<br>  };<br>};<br><br>const Component = (...) =&gt; {<br>  const {<br>    addToCart,<br>    isErrorAddToCart,<br>    isSuccessAddToCart<br>  } = useAddToCart();<br>  <br>  useEffect(() =&gt; {<br>    // ...<br>  }, [<br>    isErrorAddToCart,<br>    isSuccessAddToCart,<br>  ]);<br>  <br>  const handleSubmit = async (data) =&gt; {<br>    const response = await addToCart(data.productId);<br>    // ...<br>  };<br>};</pre><p>이제 useMutation은 우리가 다룰 수 있는 인터페이스를 가진 useAddToCart 훅(함수)으로 분리가 됐습니다. 그렇기 때문에 무조건 에러를 발생시키도록 구현체를 변경하는 것도 수월합니다.</p><pre>const useAddToCart = () =&gt; {<br>  const [isError, setIsError] = useState(false);<br>  const [isSuccess, setIsSuccess] = useState(false);<br>  <br>  return {<br>    addToCart: () =&gt; {<br>      setIsError(true);<br>    },<br>    isErrorAddToCart: isError,<br>    isSuccessAddToCart: isSuccess,<br>  };<br>};<br><br>// useAddToCart의 인터페이스가 바뀌지 않았기 때문에 사용하는 곳의 코드는 바뀌지 않습니다.<br>const Component = (...) =&gt; {<br>  const {<br>    addToCart,<br>    isErrorAddToCart,<br>    isSuccessAddToCart<br>  } = useAddToCart();<br>  <br>  useEffect(() =&gt; {<br>    // ...<br>  }, [<br>    isErrorAddToCart,<br>    isSuccessAddToCart,<br>  ]);<br>  <br>  const handleSubmit = async (data) =&gt; {<br>    const response = await addToCart(data.productId);<br>    // ...<br>  };<br>};</pre><p>단순한 예시이긴 하지만 활용 방법은 다양합니다. 이 방법은 구현체의 로직이 복잡할수록 검증할 때 더욱 유용해집니다. 물론, useAddToCart 처럼 분리하지 않고 useMutation의 mutationFn에서 매번 에러를 던지도록 코드를 수정해도 되지만, useAddToCart와 같이 인터페이스와 구현체 변경을 통해 하는 편이 독립적으로 로직을 파악하고 검증하기에 인지적으로 부담이 덜 합니다.</p><p>그리고 검증을 위해 무언가 분리됐다면 검증 대상과 관심사가 다를 수도 있음을 의미합니다. 그렇기 때문에 테스트를 하지 않더라도 ‘내가 만들고 있는 건 검증하기에 난이도가 높은 편인지’ 한 번 살펴보는 것도 좋은 코드를 만들기에 좋은 접근 방법입니다.</p><p>— -</p><p>¹: 달리기 몰입의 즐거움, 샘터, 2019</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aa6bf8864ce4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[FSD; Feature-Sliced Design에 대해]]></title>
            <link>https://medium.com/@junep/fsd-feature-sliced-design%EC%97%90-%EB%8C%80%ED%95%B4-11a7b88d5c9e?source=rss-7f8ab6539497------2</link>
            <guid isPermaLink="false">https://medium.com/p/11a7b88d5c9e</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[이문기]]></dc:creator>
            <pubDate>Thu, 14 Mar 2024 00:29:37 GMT</pubDate>
            <atom:updated>2024-07-05T00:24:52.176Z</atom:updated>
            <content:encoded><![CDATA[<h3>FSD; Feature-Sliced Design 에 대해</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tgUOa5Xjg9YTW0HD" /><figcaption>Photo by <a href="https://unsplash.com/@disruptxn?utm_source=medium&amp;utm_medium=referral">Desola Lanre-Ologun</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>이 글은 함께 일하는 <a href="https://emewjin.github.io/">동료</a>가 공유해준 글을 보고, 당장 그 자리에서 함께 이야기하기엔 생각이 너무 많아져서 정리한 글입니다.</p><p><a href="https://emewjin.github.io/feature-sliced-design/">(번역) 기능 분할 설계 - 최고의 프런트엔드 아키텍처</a></p><p>그렇기 때문에 다분히 제가 FSD를 어떻게 이해하고 바라보는지 정리한 글입니다. 그리고 관련해서 궁금하시거나 논의하고 싶으신 게 있다면 언제든 편하게 댓글 달아주세요 :)</p><h3>시작</h3><p>FSD에 대해 살펴보기 전 한 가지 짚어야 할 지점은 ‘견고함’입니다. 어떤 구조든 견고할 수록 무너지기 쉽습니다. 빠듯한 계획은 아주 작은 약속 불이행으로 깨집니다. 반면 느슨한 계획은 일정 규모 이상의 예측 실패에도 유연하게 대응할 수 있습니다. 이런 시각으로 봤을 때 FSD는 다소 견고하게 느껴집니다.</p><p>그만큼 FSD는 각 레이어가 다루는 부분이 모호하지 않습니다. 즉, 어떤 대상이 각 레이어를 벗어나 다른 레이어에도 포함될 수 있는 여지가 크게 존재하지 않습니다. 예를 들어, entities에 속해야 하는 무언가가 features에 속하기는 어려워 보입니다. 즉, 가능할 수도 있지만 다소 억지스러운 측면이 있습니다.</p><p>만약 ‘A 레이어에도 속할 수 있지만 B 레이어에 두기로 했다’는 결정을 했다면 FSD를 깨뜨리고 있는게 아닌지, FSD를 사용하면서 이전의 방법으로 돌아간건 아닌지 고민해봐야 합니다.</p><p>그럼 <a href="https://feature-sliced.design/docs/get-started/overview">문서</a>를 참고해서 각 레이어를 조금 더 구체적으로 살펴보겠습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QwIqUz4fNgjm5Q28.jpg" /><figcaption><a href="https://feature-sliced.design/docs/get-started/overview">https://feature-sliced.design/docs/get-started/overview</a></figcaption></figure><h3>Layers</h3><blockquote>The <strong>layers</strong> are standardized across all projects and vertically arranged. Modules on one layer can only interact with modules from the layers strictly below. There are currently seven of them (bottom to top):</blockquote><p>vertically arranged라는 건 vertical slice architecture와 비슷합니다.</p><ul><li><a href="https://medium.com/design-microservices-architecture-with-patterns/the-problem-with-clean-architecture-vertical-slices-111537c0ffcb">The Problem with Clean Architecture: Vertical Slices</a></li><li><a href="https://dev.to/rexebin/clean-architecture-vs-vertical-slice-architecture-3mja">Clean Architecture vs Vertical Slice Architecture</a></li></ul><p>이 방법은 프론트엔드 입장에서 보면 어떤 페이지 또는 어떤 기능을 수정하거나 추가할 때 해당 페이지 또는 기능만 살펴보는 걸로 가능하게 하는 것입니다. 따라서 페이지 간에 공유하는 컴포넌트, 로직 등을 최소화하는 게 중요합니다. 이건 마치 기능 별로 인프라까지 나누는 마이크로서비스와 일맥상통합니다. (정말 나이들어보이는 문장이네요…)</p><p>Modules on one layer can only interact with modules from the layers strictly below. 이 문장은 항상 옳은 문장은 아니지만, FSD 뿐만 아니라 계층을 나눈다면 어떤 방법론에서든 참고해야 하는 문장입니다. 만약 자신에게 인접한 계층을 통하지 않고 지나쳐서 다른 계층과 커뮤니케이션, 메세지를 주고받는다면 계층이 깨지고 유지보수가 어려워질 수 있습니다. (하지만 답글에 적혀있는 것처럼 지나치는 계층이 열려있는지 닫혀있는지에 따라 복잡성 및 유지보수성의 난이도는 달라질 수 있습니다.)</p><p>많이 언급되곤 하는 <a href="https://en.wikipedia.org/wiki/Law_of_Demeter">디미터 법칙</a>도 이와 관련이 있고 사실 대부분 소프트웨어 엔지니어링에서 통용되는 개념입니다. 근본적으로 캡슐화를 해치기 때문입니다.</p><h4>shared</h4><blockquote>shared — reusable functionality, detached from the specifics of the project/business. (e.g. UIKit, libs, API)</blockquote><p>shared에서 주목해야 하는 지점은 detached from the specifics of the project/business입니다. 특정 프로젝트 또는 비즈니스에서 분리되어(떨어져나와) 재사용 할 수 있는 기능이라는 건 강력한 조건이라고 생각합니다. 왜냐하면 반대로 프로젝트나 비즈니스에 붙어있다면 함부로 분리하면 안 된다는 의미이기도 하기 때문입니다.</p><p>따라서 특정 페이지나 프로젝트에서 사용하고 있는 비즈니스로직이 있고 다른 프로젝트에서도 사용할 한다면 함부로 분리하면 안 됩니다. 그렇게 되면 공통 로직이 거대해지고 수직 분할을 했을 때 효용이 줄어듭니다.</p><h4>entities</h4><blockquote>entities — business entities. (e.g., User, Product, Order)</blockquote><p>엔티티란 개인적인 이해해선 비즈니스로직의 주체입니다. 또한 개인적으로 서구권 언어의 특징이라고 생각하기도 합니다. 예전 언어에 대해 강의를 들을 기회가 있었는데, 동양권과 서구권의 가장 큰 차이 중 하나는 대상을 어떻게 바라보느냐 입니다. 예를 들어, 우리는 물건이 무얼 한다고 하면 이상하게 생각합니다. “빨간불이니까 (우린) 움직이면 안 되”라고 하지 “빨간불이 우리에게 멈추라고 했어”라고 하면 어색합니다. 반면 영어는 그렇게 표현하는 게 가능합니다. “The red light says stop!”</p><p>그렇게 보면 엔티티는 비즈니스로직의 주체입니다. 사용자가 될 수도 있고 상품이나 주문이 될 수도 있습니다. 예를 들어 우리는 사용자가 주문을 취소하지만 영어식 표현으로 보면 사람이 주문에게 취소를 요청하고 주문은 요청받은 취소를 수행합니다. 그렇기 때문에 주문도 엔티티가 될 수 있습니다.</p><p>이런 측면에서 보면 FSD에서 엔티티는 특정 프레임워크나 방법론에 속하는 게 아닌 범용적인 비즈니스로직입니다. 그리고 그래야만 다른 계층과 섞이지 않습니다.</p><p><a href="https://javascript.plainenglish.io/practical-ddd-in-typescript-entity-fc79002d0015">Entity</a>는 <a href="https://javascript.plainenglish.io/practical-ddd-in-typescript-value-object-b76bcd2d9283">Value Object</a>와 비교해서 볼 때 더 도움이 되기도 합니다. 특히 에릭 에반스의 <a href="https://product.kyobobook.co.kr/detail/S000001514402">도메인 주도 설계</a>를 참고하는 게 가장 좋습니다.</p><h4>features</h4><blockquote>features — user interactions, actions that bring business value to the user. (e.g. SendComment, AddToCart, UsersSearch)</blockquote><p>프론트엔드는 사용자의 동작을 입력받아 데이터로 변환하는 작업을 수행하는 곳이고 반대로 데이터를 사용자가 이해할 수 있는 형태로 출력하는 곳입니다.</p><p>그런 측면에서 features는 비즈니스 로직의 값 business value, 즉 데이터를 UI의 상태(리액트의 state 또는 BEM의 class selector)로 변환하거나 그러한 값들을 비즈니스 값으로 변환하는 곳입니다.</p><p>따라서 이벤트 리스너, 훅, 상태 관리도구, 리액트 쿼리 등이 위치할 수 있겠습니다.</p><h4>widgets</h4><blockquote>widgets — compositional layer to combine entities and features into meaningful blocks. (e.g. IssuesList, UserProfile)</blockquote><p>사실 compositional layer to combine entities and features into meaningful blocks 문장이 모든 걸 설명합니다. 이 패턴은 Controller, <a href="http://xunitpatterns.com/Humble%20Object.html">Humble Object</a>¹에서도 잘 드러납니다.</p><p><a href="https://martinfowler.com/bliki/HumbleObject.html">bliki: Humble Object</a></p><p>즉, 엔티티에 속하지 않고 features에 속하지 않으면서 이 둘을 합쳐서 활용하는 곳입니다. 리액트에선 컴포넌트와 훅이 대표적입니다. 물론 MVC 패턴에서 C(Controller)에 해당합니다.</p><h4>pages</h4><blockquote>pages — compositional layer to construct full pages from entities, features and widgets.</blockquote><p>여기에서 눈여겨 볼 것은 from entities, features and widgets입니다. 이 문장이 의미하는 건 pages 부턴 절대적인 계층이 조금은 허물어진다는 의미로 받아들여집니다. 즉, pages에서 무언가 구성하다보면 엔티티에 바로 접근할 수도 있고 피처스에도 그렇습니다.</p><p>이건 프론트엔드 구조의 특징으로 페이지 역시 페이지만의 고유한 기능을 담당하고 있기 때문입니다.</p><h4>processed</h4><blockquote>processes (deprecated) — complex inter-page scenarios. (e.g., authentication)</blockquote><p>deprecated가 눈에 띕니다. FSD라는 방법론이 그만큼 견고한 레이어 규칙을 갖고 있다고 해석할 수도, 변화의 방향에 따라 유연하게 대응한다고 해석할 수도 있겠습니다.</p><p>반면 작업을 하다보면 complex inter-page scenarios를 만나기 마련이라고 생각하는데, 빠지게 된 과정이 궁금하기도 합니다.</p><h4>app</h4><blockquote>app — app-wide settings, styles and providers.</blockquote><p>app은 우리가 만드는 애플리케이션 전체적으로 관리하는 대상을 포함합니다.</p><h3>Slices</h3><blockquote>Next up are slices, which partition the code by business domain. You’re free to choose any names for them, and create as many as you wish. Slices make your codebase easier to navigate by keeping logically related modules close together.</blockquote><blockquote>Slices cannot use other slices on the same layer, and that helps with high cohesion and low coupling.</blockquote><p>비지니스 도메인(business domain)은 서로 중복이 발생할 수 없습니다. 즉, Entities와 같이 동일한 계층 내에선 같은 비지니스 도메인이 있을 수 없습니다. 예를 들어, User라는 Slice가 있다면 User와 비슷한 다른 Slice, Person이 있으면 안 됩니다. 그렇기 때문에 하나의 Slice는 높은 응집과 낮은 결합도를 갖는 다소 독립적인 기능을 갖고 있습니다.</p><p>Comment Slice를 예로 들면, Comment 컴포넌트(ui), Comment 비지니스 로직(model), Comment API 로직(api)이 모두 하나로 구성됩니다.</p><pre>// CommentSegment.jsx<br>export function CommentSegment(props) {<br>  // model (business domain)<br>  const [comment, setComment] = useState(&#39;&#39;);<br>  const isValidComment = comment.length &gt; 0;<br><br>  // api<br>  const comment = useQuery({<br>    queryKey: [&#39;comment&#39;, props.commentId],<br>    queryFn: async () =&gt; {<br>      try {<br>        const response = await fetch(`/comment/${props.commentId}`);<br>        // ...<br>      } catch (error) {<br>        // ...<br>      }<br>    },<br>  });<br><br>  // ui<br>  return (...);<br>}<br><br>// CommentSlice.jsx<br>import { CommentSegment } from &#39;../segments/CommentSegment&#39;;<br><br>export function CommentSlice(props) {<br>  return &lt;CommentSegment /&gt;;<br>}<br></pre><p>여기에선 Comment Slice 하나에 Comment Segment 하나로 구성했지만 여러 Segment로 구성할 수도 있습니다.</p><p>Slices는 App 계층과 Shared 계층을 제외하면 어디든 구성할 수 있습니다. 예시 그림에선 Entities에 Slices와 Segments가 위치하고 있지만 Pages 계층에서도 Slices와 Segments가 있을 수 있습니다.</p><h3>Segments</h3><blockquote>Slices, as well as layers App and Shared, consist of segments, and segments group your code by its purpose.</blockquote><blockquote>Usually these segments are enough for most layers, you would only create your own segments in Shared or App, but this is not a rule</blockquote><p>Segment는 Slice를 구성하거나 App, Shared를 구성합니다.</p><p>이전에 살펴봤던 Comment Segment를 다시 살펴보면 <a href="https://getbem.com/introduction/">BEM</a>의 Block을 연상시킵니다. 즉, 어느 계층, 프로젝트의 어디든 사용(segments are enough for most layers)할 수 있습니다.</p><pre>// CommentSegment.jsx<br>export function CommentSegment(props) {<br>  // model (business domain)<br>  const [comment, setComment] = useState(&#39;&#39;);<br>  const isValidComment = comment.length &gt; 0;<br><br>  // api<br>  const comment = useQuery({<br>    queryKey: [&#39;comment&#39;, props.commentId],<br>    queryFn: async () =&gt; {<br>      try {<br>        const response = await fetch(`/comment/${props.commentId}`);<br>        // ...<br>      } catch (error) {<br>        // ...<br>      }<br>    },<br>  });<br><br>  // ui<br>  return (...);<br>}</pre><h3>마무리</h3><p>공리주의를 주장하는 정치철학자는 ‘이럴 땐 이렇게 저럴 땐 저렇게’라며 편리하게 의견을 바꿔선 안 됩니다. 하지만 공리주의를 선호하는 개인은 때론 개인주의적일 수 있습니다.</p><p>살펴본 것처럼 FSD는 견고한 구조를 갖고 있습니다. 마치 철학처럼 각 조직마다 유연하게 사용하겠지만 원리, 이론 등 가장 기본이 되는 곳은 그만큼 견고해야하기 때문입니다.</p><p>하지만 그럼에도 근간이 되는 방법을 크게 벗어나지 않도록 하는 게 좋습니다. 왜냐하면 우리만의 이유가 늘어난다는 건 그만한 근거가 충분해야하고, 많은 사람이 공유하려면 그만큼 비용이 더 들기 때문입니다. 무엇보다 우리만의 이유를 만든다는 건 그만큼 의견 충돌이 많이 발생할 수 있고 아마 가장 큰 비용이 되지 않을까 합니다.</p><p>마지막으로 특정 아키텍처를 도입하기 전에 반드시 고려해야 하는 건 콘웨이의 법칙이라고 생각합니다.</p><ul><li><a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway&#39;s law - Wikipedia</a></li><li><a href="https://johngrib.github.io/wiki/Conway-s-law/">콘웨이의 법칙(Conway&#39;s law)</a></li></ul><p>그렇기 때문에 FSD를 적용하기 전 우리는 FSD처럼 커뮤니케이션하고 협업을 하고 있는지 먼저 점검해봐야 합니다. 그러지 않으면 FSD는 각자가 일하는 방법, 우리가 협업하는 방법으로 멋대로 바뀌게 되고 어울리지 않는 옷을 입은 것처럼 삐그덕거릴 것이라고 생각합니다.</p><p>¹: 단위 테스트, 에이콘, pp232–236</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=11a7b88d5c9e" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>