<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>행복한 시지프</title>
    <link>https://happysisyphe.tistory.com/</link>
    <description>Software engineer, Educator @Woowabros</description>
    <language>ko</language>
    <pubDate>Mon, 22 Jun 2026 19:41:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>행복한 시지프</managingEditor>
    <image>
      <title>행복한 시지프</title>
      <url>https://tistory1.daumcdn.net/tistory/5254548/attach/d7347eadf55943379035f0dcc4f5c878</url>
      <link>https://happysisyphe.tistory.com</link>
    </image>
    <item>
      <title>정책 추가에 따라 분기문 생길 때, 전략 패턴 적용하기</title>
      <link>https://happysisyphe.tistory.com/105</link>
      <description>&lt;h2 data-heading=&quot;들어가며&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발을 하다 보면 정책이 까다로운 코드가 생긴다. 처음에는 if, switch로 분기해도 충분해 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 목적이 늘어날 때 생긴다. 이번 사이드 프로젝트에서 S3 presigned-url API를 구현하면서 업로드 목적별 정책이 달라졌다. 프로필 이미지는 5MB까지만 허용하고, 활동 기록 이미지는 10MB까지 허용한다. OCR 오류 리포트 이미지는 저장 경로도 별도로 가져가야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 구현은 목적별 정책이 여러 파일에 흩어져 있었다. 새로운 업로드 목적을 추가하면서 한 곳을 고치고 다른 한 곳을 빼먹었다. 이때 전략 패턴을 적용해 정책을 업로드 목적 단위로 모았다.&lt;/p&gt;
&lt;h2 data-heading=&quot;전략 패턴이 무엇인가?&quot; data-ke-size=&quot;size26&quot;&gt;전략 패턴이 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전략 패턴은 바뀔 수 있는 규칙이나 알고리즘을 인터페이스로 분리하고, 실행 시점에 알맞은 구현체를 골라 쓰는 디자인 패턴이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &quot;분기문을 없앤다&quot;가 아니다. 핵심은 &quot;변경되는 이유가 같은 코드를 한 곳에 모은다&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략 패턴을 적용하기 전에는 호출하는 쪽이 여러 조건을 알고 있었다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (purpose == PROFILE_IMAGE) {
    // 프로필 이미지 정책
}

if (purpose == ACTIVITY_RECORD_IMAGE) {
    // 활동 기록 이미지 정책
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략 패턴을 적용하면 호출하는 쪽은 공통 인터페이스만 알고, 구체적인 정책은 각 전략 객체가 맡는다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;UploadPurposePolicy policy = uploadPurposePolicy(request.uploadPurpose());
policy.validateFileSize(request.fileSize());
String objectKey = policy.createObjectKey(userId, request.fileName());&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-heading=&quot;비즈니스 로직&quot; data-ke-size=&quot;size26&quot;&gt;비즈니스 로직&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 presigned-url API는 업로드 목적별로 다른 정책을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적 허용 content type 최대 파일 크기 object key prefix&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PROFILE_IMAGE&lt;/td&gt;
&lt;td&gt;공통 이미지 타입&lt;/td&gt;
&lt;td&gt;5MB&lt;/td&gt;
&lt;td&gt;profile-images/{userId}/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACTIVITY_RECORD_IMAGE&lt;/td&gt;
&lt;td&gt;공통 이미지 타입&lt;/td&gt;
&lt;td&gt;10MB&lt;/td&gt;
&lt;td&gt;activity-records/{userId}/{yyyy}/{MM}/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCREEN_TIME_OCR_REPORT_IMAGE&lt;/td&gt;
&lt;td&gt;공통 이미지 타입&lt;/td&gt;
&lt;td&gt;10MB&lt;/td&gt;
&lt;td&gt;screen-time-ocr-reports/{userId}/{yyyy}/{MM}/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 모든 정책이 목적별로 다른 것은 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;content type 검증은 공통이다.&lt;/li&gt;
&lt;li&gt;파일 크기 제한은 목적별로 다르다.&lt;/li&gt;
&lt;li&gt;object key 경로도 목적별로 다르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 모든 코드를 전략으로 밀어 넣으면 안 된다. 공통 정책은 공통 정책으로 남기고, 목적에 따라 달라지는 정책만 전략으로 묶어야 한다.&lt;/p&gt;
&lt;h2 data-heading=&quot;기존 구현의 문제&quot; data-ke-size=&quot;size26&quot;&gt;기존 구현의 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 업로드 목적이 두 개였다. 이때는 목적별 분기가 크게 문제처럼 보이지 않았다. 하지만 새 목적을 추가하자 변경 지점이 흩어져 있다는 문제가 드러났다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 5월 15일 오후 09_58_16.png&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL5qZ5/dJMcaiwy4Dj/N8uvIkEzjPmmruuyJUm9b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL5qZ5/dJMcaiwy4Dj/N8uvIkEzjPmmruuyJUm9b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL5qZ5/dJMcaiwy4Dj/N8uvIkEzjPmmruuyJUm9b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL5qZ5%2FdJMcaiwy4Dj%2FN8uvIkEzjPmmruuyJUm9b1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2110&quot; height=&quot;745&quot; data-filename=&quot;ChatGPT Image 2026년 5월 15일 오후 09_58_16.png&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 업로드 목적을 추가하려면 최소한 다음 파일들을 같이 봐야 했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UploadFileSizePolicy&lt;/li&gt;
&lt;li&gt;UploadObjectKeyFactory&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 UploadFileSizePolicy에 분기문을 추가하는 것을 잊었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 단순 실수가 아니다. 코드 구조가 실수를 유도한 것이다. 업로드 목적 하나를 추가하는 변경인데, 파일 크기 정책과 object key 생성 정책이 서로 다른 모듈에 흩어져 있었다. 변경 이유는 하나인데 수정해야 할 모듈은 여러 개였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응집도&lt;/b&gt;가 낮다는 것은 이런 상태다. 같이 바뀌어야 하는 코드가 같이 있지 않다. 그래서 유지보수할 때 여러 파일을 돌아다녀야 하고, 그중 하나를 놓치기 쉽다.&lt;/p&gt;
&lt;h2 data-heading=&quot;개선 방향&quot; data-ke-size=&quot;size26&quot;&gt;개선 방향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제의 축은 &quot;업로드 목적&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 목적이 바뀌면 함께 바뀌는 규칙이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 목적은 어떤 UploadPurpose에 대응하는가?&lt;/li&gt;
&lt;li&gt;최대 파일 크기는 얼마인가?&lt;/li&gt;
&lt;li&gt;사용자별 object key prefix는 무엇인가?&lt;/li&gt;
&lt;li&gt;실제 object key directory는 어떻게 구성하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 규칙들을 UploadPurposePolicy라는 하나의 인터페이스로 모았다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface UploadPurposePolicy {

    UploadPurpose purpose();

    long maxFileSize();

    String objectKeyPrefixForUser(Long userId);

    String objectKeyDirectory(Long userId);

    default void validateFileSize(Long fileSize) {
        // 공통 파일 크기 검증 흐름
    }

    default String createObjectKey(Long userId, String fileName) {
        // 공통 object key 생성 흐름
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 업로드 목적은 이 인터페이스를 구현한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProfileImageUploadPurposePolicy&lt;/li&gt;
&lt;li&gt;ActivityRecordImageUploadPurposePolicy&lt;/li&gt;
&lt;li&gt;ScreenTimeOcrReportImageUploadPurposePolicy&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파일 크기와 object key 경로는 목적별 정책 객체 안에 같이 있다.&lt;/p&gt;
&lt;h2 data-heading=&quot;개선된 구조&quot; data-ke-size=&quot;size26&quot;&gt;개선된 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 5월 15일 오후 10_29_35.png&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FEV4E/dJMcah5xr2Q/L0Okc0a0rSbGX72dKxmKF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FEV4E/dJMcah5xr2Q/L0Okc0a0rSbGX72dKxmKF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FEV4E/dJMcah5xr2Q/L0Okc0a0rSbGX72dKxmKF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFEV4E%2FdJMcah5xr2Q%2FL0Okc0a0rSbGX72dKxmKF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1890&quot; height=&quot;832&quot; data-filename=&quot;ChatGPT Image 2026년 5월 15일 오후 10_29_35.png&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3UploadService는 더 이상 목적별 세부 규칙을 직접 알 필요가 없다. 요청의 업로드 목적에 맞는 정책을 찾고, 그 정책에게 검증과 object key 생성을 맡긴다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;String contentType = uploadContentTypePolicy.validate(request.contentType());
UploadPurposePolicy uploadPurposePolicy = uploadPurposePolicy(request.uploadPurpose());
uploadPurposePolicy.validateFileSize(request.fileSize());

String objectKey = uploadPurposePolicy.createObjectKey(userId, request.fileName());
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 UploadContentTypePolicy는 그대로 공통 정책으로 남긴다. 업로드 목적과 무관하게 이미지 타입을 검증하는 규칙이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 maxFileSize, objectKeyPrefixForUser, objectKeyDirectory는 목적별로 달라진다. 그래서 ProfileImageUploadPurposePolicy, ActivityRecordImageUploadPurposePolicy, ScreenTimeOcrReportImageUploadPurposePolicy 안으로 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 새 업로드 목적이 추가될 때 기존 정책 클래스들을 수정하지 않아도 된다. 새 목적에 맞는 UploadPurposePolicy 구현체를 추가하면 된다. 전략 패턴이 줄여주는 것은 기존 정책 분기 수정이다.&lt;/p&gt;
&lt;h2 data-heading=&quot;무엇이 좋아졌는가?&quot; data-ke-size=&quot;size26&quot;&gt;무엇이 좋아졌는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 변경 지점이 줄었다. 새 업로드 목적을 추가할 때 기존 UploadFileSizePolicy, UploadObjectKeyFactory의 분기문을 다시 열지 않는다. 목적별 정책 클래스 하나를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 응집도가 높아졌다. SCREEN_TIME_OCR_REPORT_IMAGE의 파일 크기 제한과 저장 경로가 같은 클래스에 있다. 이 목적을 이해하려면 여러 정책 파일을 찾아다니지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 테스트가 목적 단위로 쉬워졌다. ScreenTimeOcrReportImageUploadPurposePolicy를 테스트하면 OCR 리포트 업로드 목적의 파일 크기와 경로 규칙을 함께 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. S3UploadService의 역할이 단순해졌다. 이 서비스는 presigned-url 발급 흐름을 조립한다. 목적별 정책 판단은 정책 객체가 담당한다.&lt;/p&gt;
&lt;h2 data-heading=&quot;전략 패턴을 쓸 때의 기준&quot; data-ke-size=&quot;size26&quot;&gt;전략 패턴을 쓸 때의 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략 패턴은 다음 조건이 있을 때 잘 맞는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규칙의 종류가 앞으로 늘어날 가능성이 있다.&lt;/li&gt;
&lt;li&gt;새 규칙을 추가할 때 기존 분기문을 여러 곳에서 수정해야 한다.&lt;/li&gt;
&lt;li&gt;호출하는 쪽이 세부 규칙을 몰라도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-heading=&quot;마치며&quot; data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략 패턴은 흩어진 변경 지점을 모으는 일이다. 이번 리팩터링의 핵심은 업로드 목적별로 달라지는 정책을 UploadPurposePolicy로 묶은 것이다. 공통 정책은 공통 정책으로 두고, 목적별로 바뀌는 파일 크기와 object key 생성 규칙만 전략으로 분리했다. 그 결과 새 목적을 추가할 때 기존 분기문을 수정하는 대신 새 전략을 추가하는 구조가 됐다. 유지보수하기 훨씬 좋아졌다.&lt;/p&gt;</description>
      <category>Tech/Clean Code</category>
      <category>전략패턴</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/105</guid>
      <comments>https://happysisyphe.tistory.com/105#entry105comment</comments>
      <pubDate>Fri, 15 May 2026 22:30:08 +0900</pubDate>
    </item>
    <item>
      <title>비서(Hermes Agent)를 잘 활용하는 멘탈 모델</title>
      <link>https://happysisyphe.tistory.com/104</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;hermes-agent-2.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVRuCA/dJMcafmjqh6/FyixzvCb2DmmeXWme007l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVRuCA/dJMcafmjqh6/FyixzvCb2DmmeXWme007l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVRuCA/dJMcafmjqh6/FyixzvCb2DmmeXWme007l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVRuCA%2FdJMcafmjqh6%2FFyixzvCb2DmmeXWme007l0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;hermes-agent-2.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요새 Hermes Agent 가 참 핫하다. 나도 관심이 매우 커졌고, 이제는 Hermes와 아침을 시작하고, Hermes와 하루를 마무리한다. 일어나서 날씨를 리포트 받는다. 마스크를 쓸지, 우산을 쓸지, 옷은 어떻게 입을지 제안받는다. 자기 전에는 나의 하루를 회고해준다. 내가 하루 동안 일하면서, 어떤 책을 읽었고, 어떤 것에 집중했는지, 어떤 생각을 했는지 돌아봐 주고, 나의 과거 생각들과 연결해 주거나 모순점을 발견해 준다. 일과 중에도 여러 번 나를 도와준다. 이렇듯 Hermes는 나의 삶을 편하게 만들어주고 있다. 더 잘 쓰면, 삶이 더 편해지리라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구를 잘 쓰는 방법이 고민일 때, 멘탈 모델을 묻곤 한다. '이 도구를 잘 사용하려면 어떤 멘탈 모델이 필요한가?', '잘 쓰는 사람과 못 쓰는 사람은 어떤 차이가 있을까?' 이에 대한 답은 &quot;비서&quot;이다. 내가 비서를 두고 있다는 생각으로 Hermes Agent를 대해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 문제는 우리는 비서를 모른다. 비서를 둬 본 적도 없을뿐더러, 비서가 어떻게 일하는지 본 적도 없을 가능성이 높다. 드라마에서나 보았으려나. 당연히 비서를 어떻게 활용하는지 배운 적도 없다. 비서의 본질과, C레벨은 왜 비서를 두는지 이야기하며 해소해 보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비서라는 직업은 왜 존재할까? 2가지이다. 인지심리학, 경제학 측면이 있다. 먼저 인지심리학 측면은, 허버트 사이먼의 제한된 합리성에 기반한다. 인간이 하루에 쓸 수 있는 의사결정 수에는 제한이 있다는 것이다. 즉, 인지 부하를 줄여서 제한된 인지 자원을 고부가가치 판단에 집중해서 쓸 수 있도록 해야 한다. 결정을 많이 해야 하는 C레벨에게는 이런 장치가 더욱 필요할 것이다. 그것의 발현이 비서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경제학 측면에서 보면, 비교우위 이론으로 설명할 수 있다. 국제무역론 시간에 리카도의 비교우위론을 배운다. 교과서 예시로는 타이거 우즈와 잔디 깎기가 나온다. 타이거 우즈가 몸이 좋으므로, 골프도 잘 치고, 잔디도 더 잘 깎을 것이다. 하지만 타이거 우즈가 잔디를 깎음으로 인해 포기해야 했던 CF 비용 10억, 즉 기회비용을 생각하면, 직접 잔디를 깎지 않고, 잔디 깎기를 고용하는 게 이득이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 2가지가 비서를 잘 활용하는 멘탈 모델이라고 생각한다. 인지 부하를 줄이고, 내가 더 잘하더라도 일을 시켜야 한다. 대표적으로 인지 부하를 가지는 일에 무엇이 있을까? 사실 이걸 생각하기가 어려운 것 같다. 매일 하는 일이기 때문에, 그게 인지 부하를 만들리라 생각하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령, 매일 아침 날씨를 확인하는 일이 있을 것이다. 옷을 고르는 일이 있을 것이다. 하루의 할 일을 정하는 일, 캘린더에 내가 직접 약속을 등록하는 일, 녹음한 파일을 AI로 전사해서 노트에 넣어두는 일, 밤에 회고해야겠다고 생각하는 일, 점심에 약을 먹어야겠다고 생각하는 일 등 무수히 많다. 내가 직접 생각하지 않고, 적절한 시간에 알려달라고 명령해 두고, 비서가 찾아오면 나는 그 일에 힘껏 집중하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비서라는 메타포와, 그 멘탈 모델을 기반으로 내 삶을 돌아보면 보자. 내가 실제 비서가 있다면, 이 정리를 내가 할까? 비서가 있다면 식당을 내가 예약할까? 비서가 있다면 이 이메일을 내가 쓰고 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 것은 설명을 듣기보다는, 비서를 고용하면서부터 시작된다. 비서를 두기 전에는 이 생각을 하기 어렵다. 나를 전적으로 보좌하는 비서가 있다면, 위임해야 할 일이 하나둘씩 떠오르게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 남는 컴퓨터 하나 잡고, Codex에 &quot;/goal Hermes Agent 세팅하고 Discord로 대화할 수 있게 만들어줘&quot;라고 명령하여, 비서를 고용해 보자.&lt;/p&gt;</description>
      <category>Tech/Agent</category>
      <category>agent</category>
      <category>HERMES</category>
      <category>비서</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/104</guid>
      <comments>https://happysisyphe.tistory.com/104#entry104comment</comments>
      <pubDate>Wed, 13 May 2026 22:30:04 +0900</pubDate>
    </item>
    <item>
      <title>시각화 #1. 어떻게 Local 에서 클라우드 RDS 로 query 를 날리나요?</title>
      <link>https://happysisyphe.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;서문&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. &amp;ldquo;시각화 한장으로 개념 이해하기&amp;rdquo; 시리즈를 써보려고 합니다. LLM 과 학습하는 시간이 늘어나면서, 뇌가 입력 받는 양이 늘어났습니다. 그러면서 Input 과 Output 의 비율이 깨지고 있습니다. 학습은 Output 을 통해 이루어집니다. 뱉어봐야 나의 것이 됩니다. 그 정수는 시각화라고 생각합니다. 시각화를 한다는건, 글보다 훨씬 어렵습니다. 어렵다는건, 더 많은 내용을, 더 깊이 이해하고 있어야 진정 제대로 그릴 수 있다는 것입니다. 바꿔 말하면, 시각화할 수 있다는건, 그 개념을 정확히 이해하고 있다는 것을 의미합니다. AI 시대에 거꾸로 가기 위해서, 시각화를 연습하고 글로 써봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 &amp;lt;어떻게&amp;nbsp;Local&amp;nbsp;에서&amp;nbsp;클라우드&amp;nbsp;RDS&amp;nbsp;로&amp;nbsp;query&amp;nbsp;를&amp;nbsp;날리나요?&amp;gt; 라는 질문에 답을 해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;문제 상황&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 개발을 하면, 어플리케이션 서버를 띄우고, RDBMS 서버를 띄웁니다. AWS 로 치면, EC2와 RDS 를 통해 호스팅합니다. 이때 실제 Database 에 query 를 날려서 데이터를 얻어와야 하는데요. EC2 에 직접 접속해서, 쿼리를 날리는건 너무나 불편합니다. 접속도 불편하고, Terminal UI 는 시각화와 쿼리 관리가 어렵죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Local 에서 GUI 로 된 DB Client 를 사용합니다. DBeaver 나 MySQLWorkbench, DataGrip 등이 유명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 대체 어떻게, Local 에서 RDS 로 바로 접속해서 query 를 날리는 것처럼 보일까요? 그 블랙박스를 시각화해서 그려봅니다.&lt;/p&gt;
&lt;h1&gt;시각화&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;local-ec2-rds.jpg&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx5ifS/dJMcaciFore/qfaDNmW2tOkMAQX3KPuhWk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx5ifS/dJMcaciFore/qfaDNmW2tOkMAQX3KPuhWk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx5ifS/dJMcaciFore/qfaDNmW2tOkMAQX3KPuhWk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx5ifS%2FdJMcaciFore%2FqfaDNmW2tOkMAQX3KPuhWk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2386&quot; height=&quot;1491&quot; data-filename=&quot;local-ec2-rds.jpg&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;시각화 전체 흐름 설명&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Local 은 &lt;b&gt;SSH Protocol&lt;/b&gt; 22 port 로 EC2 에 접속한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EC2 는 Local 에서 접속 가능하도록 &lt;b&gt;Security Group&lt;/b&gt;(보안 그룹)을 설정해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;EC2 는 &lt;b&gt;MySQL Protocol&lt;/b&gt; 3306 port 로 RDS 에 접속한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RDS 는 EC2 에서 접속 가능하도록 &lt;b&gt;Security Group&lt;/b&gt;(보안 그룹)을 설정해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Local 에서 query 요청을 날리면, EC2 를 거쳐서, RDS 로 가고, RDS 에서 응답을 하면, EC2 를 거쳐서 Local 로 응답이 온다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 그림을 잘 이해하기 위해서, SSH Protocol, MySQL Protocol, Security Group 을 이해해야 합니다.&lt;/p&gt;
&lt;h1&gt;SSH Protocol&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 의 풀네임은 Secure Shell 입니다. 클라이언트가 원격 서버에 안전하게 접속할 수 있도록 하는 프로토콜 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Shell은 터미널 명령을 실행하는 환경입니다. 즉 SSH는 &amp;ldquo;내 Local Terminal에서 EC2 Terminal에 안전하게 들어가는 방법&amp;rdquo;이라고 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MySQL Protocol&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL Protocol은 MySQL Client와 MySQL Server가 대화하는 규칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 DBeaver에서 아래 쿼리를 실행한다고 해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;SELECT * FROM users;&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 눈에는 SQL이지만, 실제 네트워크에서는 MySQL Client가 MySQL Protocol에 맞춰 요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBeaver --&amp;gt; EC2 -- MySQL Protocol --&amp;gt; RDS MySQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, SQL은 &amp;ldquo;무엇을 할지&amp;rdquo;를 표현하는 언어이고, MySQL Protocol은 &amp;ldquo;그 SQL을 DB 서버에 어떻게 보내고 응답을 어떻게 받을지&amp;rdquo;를 정한 통신 규칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 기본적으로&amp;nbsp;3306&amp;nbsp;포트를 사용합니다. 그래서 EC2에서 RDS로 갈 때는 보통&amp;nbsp;3306&amp;nbsp;포트가 열려 있어야 합니다.&lt;/p&gt;
&lt;h1&gt;Security Group&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Security Group은 AWS 리소스 앞에 붙는 방화벽입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Public Subnet에 있다고 해서 모든 요청이 열리는 것은 아닙니다. Public Subnet은 &amp;ldquo;인터넷으로 갈 수 있는 길이 있다&amp;rdquo;는 뜻이고, Security Group은 &amp;ldquo;그 길에서 어떤 문을 열지&amp;rdquo; 정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 구조에서는 두 가지 문이 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Local -&amp;gt; EC2
SSH 22번 허용
Source: 내 IP/32&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;EC2 -&amp;gt; RDS
MySQL 3306번 허용
Source: EC2 Security Group&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 RDS의 Source입니다. RDS 입장에서는 접속자가 Local이 아니라 EC2입니다. 그래서 RDS Security Group에는 내 IP가 아니라 EC2 Security Group을 허용해야 합니다.&lt;/p&gt;</description>
      <category>Tech/Server</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/103</guid>
      <comments>https://happysisyphe.tistory.com/103#entry103comment</comments>
      <pubDate>Fri, 1 May 2026 17:36:57 +0900</pubDate>
    </item>
    <item>
      <title>You are what you listen to</title>
      <link>https://happysisyphe.tistory.com/102</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;You are what you listen to-2.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sEQAq/dJMcadHZQro/Fk22XGawsL5ocwSRyl1xk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sEQAq/dJMcadHZQro/Fk22XGawsL5ocwSRyl1xk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sEQAq/dJMcadHZQro/Fk22XGawsL5ocwSRyl1xk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsEQAq%2FdJMcadHZQro%2FFk22XGawsL5ocwSRyl1xk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;You are what you listen to-2.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;경험이 중요하다&amp;rdquo;, &amp;ldquo;주변의 사람이 중요하다&amp;rdquo;라는 일반적인 말보다 더 무섭게 다가오는 말을 떠올려 보았다. &amp;ldquo;You are what you listen to&amp;rdquo;&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;사람이&amp;nbsp;일상적으로&amp;nbsp;듣고&amp;nbsp;있는&amp;nbsp;말들을&amp;nbsp;보면,&amp;nbsp;그&amp;nbsp;사람&amp;nbsp;자체를&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;어떤&amp;nbsp;것을&amp;nbsp;보고&amp;nbsp;우는가,&amp;nbsp;어떤&amp;nbsp;것을&amp;nbsp;보고&amp;nbsp;웃는가,&amp;nbsp;어떤&amp;nbsp;것에&amp;nbsp;시간을&amp;nbsp;많이&amp;nbsp;쓰는가.&lt;br /&gt;&lt;br /&gt;비트겐슈타인은&amp;nbsp;&amp;ldquo;언어의&amp;nbsp;한계는&amp;nbsp;곧&amp;nbsp;세계의&amp;nbsp;한계를&amp;nbsp;의미한다&amp;rdquo;라고&amp;nbsp;했다.&amp;nbsp;내가&amp;nbsp;듣고&amp;nbsp;말하면서&amp;nbsp;형성되는&amp;nbsp;언어,&amp;nbsp;딱&amp;nbsp;그만큼이&amp;nbsp;자신의&amp;nbsp;세계라는&amp;nbsp;것이다.&amp;nbsp;자신이&amp;nbsp;듣지&amp;nbsp;못한&amp;nbsp;말을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없다.&amp;nbsp;내가&amp;nbsp;들었던&amp;nbsp;말만&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;자신의&amp;nbsp;아버지를&amp;nbsp;&amp;ldquo;여보&amp;rdquo;&amp;nbsp;라고&amp;nbsp;부르는&amp;nbsp;아이를&amp;nbsp;떠올려&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;우리가&amp;nbsp;평소&amp;nbsp;무엇을&amp;nbsp;듣고&amp;nbsp;사는지&amp;nbsp;생각해&amp;nbsp;보자.&amp;nbsp;친구들,&amp;nbsp;회사&amp;nbsp;팀원,&amp;nbsp;가족들과의&amp;nbsp;대화가&amp;nbsp;있을&amp;nbsp;것이다.&amp;nbsp;그리고&amp;nbsp;책,&amp;nbsp;논문,&amp;nbsp;커뮤니티&amp;nbsp;등&amp;nbsp;글로서&amp;nbsp;생각을&amp;nbsp;듣는&amp;nbsp;매체도&amp;nbsp;있다.&amp;nbsp;그리고&amp;nbsp;유튜브,&amp;nbsp;인스타,&amp;nbsp;TV&amp;nbsp;프로그램처럼&amp;nbsp;영상으로&amp;nbsp;생각을&amp;nbsp;들을&amp;nbsp;수도&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;자신의&amp;nbsp;삶을&amp;nbsp;바꾸고&amp;nbsp;싶으면,&amp;nbsp;자신이&amp;nbsp;매일&amp;nbsp;듣는&amp;nbsp;것을&amp;nbsp;바꾸면&amp;nbsp;된다.&amp;nbsp;내가&amp;nbsp;더&amp;nbsp;웃긴&amp;nbsp;사람이&amp;nbsp;되고&amp;nbsp;싶으면,&amp;nbsp;웃긴&amp;nbsp;사람들과&amp;nbsp;함께하거나,&amp;nbsp;웃긴&amp;nbsp;채널을&amp;nbsp;보면&amp;nbsp;된다.&amp;nbsp;내가&amp;nbsp;돈을&amp;nbsp;많이&amp;nbsp;벌고&amp;nbsp;싶으면,&amp;nbsp;돈에&amp;nbsp;관해&amp;nbsp;이야기하는&amp;nbsp;사람들을&amp;nbsp;주변에&amp;nbsp;두고,&amp;nbsp;돈에&amp;nbsp;관해&amp;nbsp;이야기하는&amp;nbsp;유튜브로&amp;nbsp;구독&amp;nbsp;리스트를&amp;nbsp;싹&amp;nbsp;바꾸면&amp;nbsp;된다.&lt;br /&gt;&lt;br /&gt;그렇지만&amp;nbsp;그&amp;nbsp;전환이&amp;nbsp;절대&amp;nbsp;쉽지만은&amp;nbsp;않다.&amp;nbsp;그&amp;nbsp;장벽은&amp;nbsp;오로지&amp;nbsp;자기&amp;nbsp;자신이다.&amp;nbsp;일단&amp;nbsp;본인이&amp;nbsp;무얼&amp;nbsp;듣고&amp;nbsp;사는지&amp;nbsp;메타인지&amp;nbsp;하는&amp;nbsp;게&amp;nbsp;정말&amp;nbsp;어렵다.&amp;nbsp;두&amp;nbsp;번째로&amp;nbsp;그게&amp;nbsp;나에게&amp;nbsp;이로운지,&amp;nbsp;해로운지&amp;nbsp;판단하는&amp;nbsp;게&amp;nbsp;정말&amp;nbsp;어렵다.&amp;nbsp;누군가&amp;nbsp;보기엔&amp;nbsp;딱&amp;nbsp;봐도&amp;nbsp;해롭다고&amp;nbsp;할지라도,&amp;nbsp;나에겐&amp;nbsp;그저&amp;nbsp;어제와&amp;nbsp;같은&amp;nbsp;하루를&amp;nbsp;보내는&amp;nbsp;것에&amp;nbsp;불과하다.&amp;nbsp;셋째는&amp;nbsp;내가&amp;nbsp;새로이&amp;nbsp;들은&amp;nbsp;것이&amp;nbsp;없어서,&amp;nbsp;다른&amp;nbsp;선택을&amp;nbsp;떠올리는&amp;nbsp;것이&amp;nbsp;정말&amp;nbsp;어렵다.&amp;nbsp;인간의&amp;nbsp;알고리즘은&amp;nbsp;비슷한&amp;nbsp;부류의&amp;nbsp;사람에게&amp;nbsp;편안함을&amp;nbsp;느끼도록&amp;nbsp;설계되어&amp;nbsp;있고,&amp;nbsp;유튜브&amp;nbsp;알고리즘도&amp;nbsp;비슷한&amp;nbsp;이야기만&amp;nbsp;듣게&amp;nbsp;설계되었기&amp;nbsp;때문이다.&lt;br /&gt;&lt;br /&gt;그렇다면&amp;nbsp;이&amp;nbsp;전환의&amp;nbsp;출발점은&amp;nbsp;어디인가.&amp;nbsp;이는&amp;nbsp;한두&amp;nbsp;개의&amp;nbsp;강한&amp;nbsp;균열에&amp;nbsp;의해&amp;nbsp;이루어진다.&amp;nbsp;우연한&amp;nbsp;매체가&amp;nbsp;강한&amp;nbsp;균열을&amp;nbsp;만든다.&amp;nbsp;5년&amp;nbsp;만에&amp;nbsp;뵙는&amp;nbsp;삼촌,&amp;nbsp;7년&amp;nbsp;만에&amp;nbsp;보는&amp;nbsp;고등학교&amp;nbsp;동창,&amp;nbsp;여행에서&amp;nbsp;만난&amp;nbsp;행인.&amp;nbsp;또는&amp;nbsp;우연히&amp;nbsp;접한&amp;nbsp;영화나,&amp;nbsp;책,&amp;nbsp;글,&amp;nbsp;간혹&amp;nbsp;유튜브&amp;nbsp;영상에&amp;nbsp;의해&amp;nbsp;발생하기도&amp;nbsp;한다.&amp;nbsp;Loose&amp;nbsp;Connection은&amp;nbsp;종종&amp;nbsp;기존&amp;nbsp;세계관을&amp;nbsp;흔들곤&amp;nbsp;한다.&amp;nbsp;내가&amp;nbsp;빠져있던&amp;nbsp;사상,&amp;nbsp;철학에&amp;nbsp;완전히&amp;nbsp;새로운&amp;nbsp;의견을&amp;nbsp;제시하는&amp;nbsp;것이다.&amp;nbsp;가령&amp;nbsp;내&amp;nbsp;주변엔&amp;nbsp;열심히&amp;nbsp;사는&amp;nbsp;사람들만&amp;nbsp;있는데,&amp;nbsp;열심히&amp;nbsp;살지&amp;nbsp;않지만&amp;nbsp;행복하게&amp;nbsp;사는&amp;nbsp;사람들을&amp;nbsp;만나면,&amp;nbsp;내&amp;nbsp;삶의&amp;nbsp;전제가&amp;nbsp;흔들리는&amp;nbsp;것이다.&amp;nbsp;이는&amp;nbsp;새로운&amp;nbsp;씨앗이&amp;nbsp;되어,&amp;nbsp;복리로&amp;nbsp;내&amp;nbsp;삶에&amp;nbsp;영향을&amp;nbsp;미치게&amp;nbsp;된다.&lt;br /&gt;&lt;br /&gt;그래서&amp;nbsp;어떻게&amp;nbsp;해야&amp;nbsp;하는가?&amp;nbsp;먼저&amp;nbsp;호기심과&amp;nbsp;열린&amp;nbsp;마음이&amp;nbsp;토대가&amp;nbsp;되어야&amp;nbsp;한다.&amp;nbsp;그리고&amp;nbsp;의도적으로&amp;nbsp;Loose&amp;nbsp;Connection을&amp;nbsp;열어둔다.&amp;nbsp;나와&amp;nbsp;다른&amp;nbsp;분야&amp;nbsp;사람을&amp;nbsp;간헐적으로&amp;nbsp;만나거나,&amp;nbsp;여행이나&amp;nbsp;커뮤니티&amp;nbsp;활동으로&amp;nbsp;우연한&amp;nbsp;가능성을&amp;nbsp;열어둔다.&amp;nbsp;그&amp;nbsp;만남이&amp;nbsp;기존&amp;nbsp;관계와&amp;nbsp;다르다고&amp;nbsp;하여,&amp;nbsp;마음을&amp;nbsp;닫거나,&amp;nbsp;가만히&amp;nbsp;있어서는&amp;nbsp;새로운&amp;nbsp;전환의&amp;nbsp;기회를&amp;nbsp;잃게&amp;nbsp;된다.&amp;nbsp;다르면&amp;nbsp;오히려&amp;nbsp;반가워하고,&amp;nbsp;마음을&amp;nbsp;열고&amp;nbsp;이야기를&amp;nbsp;나눠봐야겠다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;글도&amp;nbsp;새로운&amp;nbsp;균열이&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;이&amp;nbsp;글을&amp;nbsp;끝까지&amp;nbsp;읽었다면,&amp;nbsp;새로운&amp;nbsp;자극이&amp;nbsp;들어왔을지도&amp;nbsp;모르겠다.&amp;nbsp;그러길&amp;nbsp;희망한다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/102</guid>
      <comments>https://happysisyphe.tistory.com/102#entry102comment</comments>
      <pubDate>Sun, 15 Mar 2026 22:43:55 +0900</pubDate>
    </item>
    <item>
      <title>좋은 순서를 만드는법 : 킹핀 질문</title>
      <link>https://happysisyphe.tistory.com/101</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;king-pin.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWBeX/dJMcagdCT4e/v8nFELgWp4Kwhk8CO6k1gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWBeX%2FdJMcagdCT4e%2Fv8nFELgWp4Kwhk8CO6k1gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;king-pin.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요새 꽂힌 질문이 있다. 이 질문 하나를 하면, 복잡한 의사결정 상황에서, 한 발짝 나아갈 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;많은 옵션 중에, 어떤 것을 먼저 하나 달성하면, 나머지 일들이 쉬워지나요?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문에 여러 비유가 있다. &amp;lt;원씽&amp;gt; 책에서는 도미노 비유를 든다. 하나를 쓰러뜨리면, 나머지 모든 것이 쉽게 무너지는 그 한 가지에 집중하라는 것이다. 어떤 곳에서는 킹핀 비유를 든다. 볼링에서 5번 핀을 말하는데, 이거 하나만 잘 쓰러뜨리면, 나머지 것은 자연스레 무너진다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문은 많은 선택지 사이에, 좋은 순서를 만드는 데 도움이 된다. 삶에서 좋은 순서는 너무 중요하다. 뭘 먼저 하냐에 따라서 삶이 바뀔 수 있다. 어떤 활동은 복리로 작용해서 삶 전반에 영향을 미칠 수도 있고, 그것 하나를 달성하면 나머지 모든 것을 잘하게 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커리어 예시를 들어보자. 해외 취업도 하고 싶고, 개발자로 일도 하고 싶고, 교육도 하고 싶다고 하자. 이때 킹핀 질문을 해보자. 어떤 것을 먼저 하나 해내면, 다른 모든 것이 쉬워지는가? 일단 개발자로 커리어를 쌓고 나면, 교육을 하기도 쉬워지고, 해외 취업을 하기도 쉬워진다. 처음부터 교육을 하면서, 다른 것들을 다 달성하기는 매우 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 공부 예시를 들어보자. 프로그래밍 언어도 잘 다루고 싶고, 프레임워크도 잘 다루고 싶고, AI 도 잘 쓰고 싶다고 하자. 무엇을 하나 하면 나머지 모든 게 쉬워질까? 나는 나만의 서비스를 직접 개발해 보는 것으로 생각한다. 언어 하나를 공부한다고 해서, 나머지 모든 게 달성되지 않는다. 그건 핵심이 아닌 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 좋은 순서를 짜게 되면, 삶이 여러모로 편해진다. 가장 중요한 한 가지를 빠르게 골라보고, 그것을 액션하면서 다음 액션을 또 고민하는 것이다. 다음에 또 킹핀 질문을 하고, 결정하는 것이다. 이렇게 매번 결정하면, sub optimal 결정을, global optimal로 만들 수 있다. 미래를 모르는 인간의 조건을 고려하면, global optimal을 한 번에 계획해 낼 수 없다. 인간은 sub optimal이 global optimal이 되도록, 결정을 고민해야 한다. 킹핀 질문이 그것에 다가갈 수 있는 하나의 핵심 질문이다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/101</guid>
      <comments>https://happysisyphe.tistory.com/101#entry101comment</comments>
      <pubDate>Sun, 8 Mar 2026 18:40:05 +0900</pubDate>
    </item>
    <item>
      <title>[우테코 코치 생활] 오늘이 아닌 10년 후를 위한 교육</title>
      <link>https://happysisyphe.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주입식 교육을 비판하는 것은 참 흔한 이야기 입니다. 대한민국 국민이면 대부분이 공감할 주제이지요. 그러면 대안은 무엇일까요? 저도 주입식 교육 방식에 싫증이 났고 비판적이었으면서, 대안을 상세히 떠올려 보지는 않았습니다. 지식을 주입하지 않고서 도대체 어떻게 교육을 할 수 있을까요? 우아한테크코스 개발 코치로 일하면서 느낀 몇 가지를 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리나라 고등학생의 학습 수준은 어느 나라보다 뛰어납니다. 고등학생이 미적분과 벡터 문제까지 풀어낼 수 있습니다. 올림피아드 상을 휩쓸기도 합니다. 그렇지만 성인기로 갈수록 경쟁력이 줄어듭니다. 축구선수도 마찬가지이고, 음악 분야도 마찬가지 입니다. 이는 모두 주입식 교육 방식에서 기인하는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 들어보죠. 학생이 오늘 하루 수학을 잘 해내려면, 어떻게 가르치면 될까요? 가장 많은 지식을 주입시키면 되겠지요. 반면, 학생이 10년 후에 수학을 잘 해내려면, 어떻게 가르쳐야 할까요? 오늘 하루 많은 지식을 붓는다고 하여, 10년 후 수학 역량을 보장하기 어렵습니다. 주입된 지식은 휘발됩니다. 그리고 다른 개념과 연결되지 않습니다. 이게 주입식 교육의 문제입니다. 10년후 잘 하기 위해서는, 스스로, 차근히 학습해가야 합니다. 지식을 단순히 소비해버리는게 아니라, 한 단계씩 스스로 도출할 수 있도록 이끌어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10년 후를 바라본다는 것은, 문제를 지식만으로 해결하지 못하는 때를 상정하는 것입니다. 대학만 오더라도, 지식으로 문제를 풀 수 없는 경우가 대부분입니다. 지식은 도구에 불과하고, 지식을 활용해서 현실의 문제를 풀어야 합니다. 현실의 문제는 복잡하고, 풀리지 않았기 때문에 문제로 남아있는 것입니다. 그렇다면 지금 어떻게 배워야 할까요? 학습자의 현재 지식 수준을 i 라고 했을 때, i+1 영역의 문제를 스스로 헤쳐 나가도록 도와줍니다. 해당 학문에서 i+1000 수준의 문제까지 풀렸다고 하더라도, i+1 지식 정도는 가벼운 검색으로 파악할 수 있다고 하더라도, i+1 문제를 스스로 풀도록 하는 것입니다. 그렇게 i+2, i+3,&amp;hellip; 1000개의 문제를 스스로 헤쳐나가다보면, i+1001 의 문제도 풀 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생 입장에서는 한번에 i+100 의 지식을 얻고 싶은 유혹을 참기 어려울 것입니다. 코치는 또한 한번에 i+100 의 지식을 가르치고 싶은 유혹을 참기 어려울 것입니다. 학생은 호기심과 인내심을 가져야 하고, 코치는 학습 원리에 대한 이해와 설계가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 측면에서 저는 &amp;ldquo;오늘은 객체지향 설계 원칙을 배워볼게요&amp;rdquo; 로 시작하는 수업을 하지 않습니다. 일명, 개념 중심 수업입니다. 갑자기 대뜸 i+100 을 들이미는 수업입니다. 그걸 왜 배워야 하는 지도 모른 채로요. 물론, 코치는 이걸 왜 배워야 하는지 설명해 냅니다. 다르게 말하면, 왜 배워야 하는지 주입시킵니다. 하지만 학습 동기를 설명하는 것만으로 주입식 교육 방식에서 벗어났다고 착각해서는 안 됩니다. 학습 동기도 주입시키는 것에 불과한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 어떻게 수업해야 할까요? 어찌보면 질문부터 잘못되었습니다. 이는 수업만으로 해결할 수 없습니다. 수업 이전에 실제 문제를 마주하도록 해야 합니다. 과제나 프로젝트에서 문제를 풀어보며, 코드가 복잡해지는 현상을 경험합니다. 그것을 해결하기 위해 여러 시도를 해보도록 유도합니다. 그리고 수업 시간에 와서, 각자 어떤 문제를 마주했고, 어떻게 해소하려고 했는지 토론합니다. 그리고 우리만의 설계 원칙을 한번 도출해봅니다. 그리고 드디어 객체지향 설계 원칙 이라는 이론을 제시합니다. 우리와 똑같은 고민을 선대 개발자들이 해왔다고 하면서요. 그러면 학생들이 그토록 찾고 싶었던 오아시스를 발견합니다. 그 기술이 우연히 나온 것이 아니라, 필연적으로 도출될 수밖에 없음을 이해하면서 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 학습하면 여러 장점이 있습니다. 첫 번째로 학습자가 배우는 재미가 있습니다. 실제 문제를 경험하면서, 필요성을 느끼고 학습하기 때문에, 이는 더 이상 &amp;lsquo;공부&amp;rsquo; 가 아닙니다. 문제를 풀기 위한 무기를 하나 얻는 것입니다. 두 번째로 문제 해결 역량이 업그레이드 됩니다. 이미 증명된 이론을, 배우지 않고 스스로 도출하려고 시도해보면서, 문제 푸는 과정을 직접적으로 경험합니다. 이후에, 증명되지 않은 복잡한 문제를 마주했을 때, 스스로 풀어나갈 힘이 길러집니다. 이것이 10년 이후에도 훌륭한 역량을 가진 사람으로 설 수 있게 도와줍니다. 10년 후부터는, 풀리지 않는 문제만 풀어야 하니까요. 세 번째로 기억에 오래 남습니다. 객체지향 설계 원칙이라는 이야기를 갑자기 들으면, 이렇게나 뚱딴지 같은 이야기가 없습니다. SOLID 라는 5가지 원칙이 갑자기 왜 나오는건지, 각각은 또 용어가 왜 이렇게나 어려운지, 기억에 남지 않습니다. 내 경험과 시냅스로 연결되지 않기 때문이죠. 문제를 기반으로 학습한다면, 나의 경험과 자연스레 연결되기 때문에 뇌는 이를 쉽게 기억해 냅니다. 억지로 외우려 하지 않아도, 뇌리에 강하게 남습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 중심 교육이 왜 우수한지, 뒷받침 되는 사례가 있습니다. 웨스턴 리저브 대학교에서 의과대학 교육 개혁이 있었습니다. 원래 의대 입학 시, 2년간 강의를 배우고, 2년간 실습을 나갔습니다. 이것이 문제라고 생각하여, 입학 첫해부터 환자와 가까이 하도록 과정을 설계했습니다. 문제를 먼저 경험하고, 이론을 배우게 만든 것이지요. 그러고나니 학습 동기가 오르고, 최종적으로 국가고시 점수도 1표준편차 이상 높았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크코스도 주입식 교육을 지양하고, 현장형/경험형 교육을 지향합니다. 일주일에 수업이 많아야 3시간도 되지 않습니다. 나머지는 스스로 학습하고, 문제를 경험하게 만듭니다. 진짜 학습이 일어나도록 합니다. 아직 우아한테크코스가 10년이 되지는 않아서, 우아한테크코스에서 학습한 경험이 생애에 걸쳐서 영향을 미칠지는 알 수 없습니다. 다만, 주변에서 이런 얘기를 많이 듣습니다. 우아한테크코스에서 학습한 것으로 밥 벌어 먹고 살고 있다고요. 그런 경험을 줄 수 있는 교육기관에 있다는 것이 너무나 영광이고 즐겁습니다.&lt;/p&gt;</description>
      <category>Tech/우아한테크코스</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/100</guid>
      <comments>https://happysisyphe.tistory.com/100#entry100comment</comments>
      <pubDate>Sun, 18 Jan 2026 21:49:08 +0900</pubDate>
    </item>
    <item>
      <title>2025년 자기계발 회고</title>
      <link>https://happysisyphe.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025년은 가장 다이나믹한 해가 아니었나 싶다. 한해를 그냥 흘러보내기엔 아쉬워 그 순간들을 잡아본다. 순간을 잡아볼 수 있는 여러 질문으로 글을 썼다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 내게 가장 의미있던 사건&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PT 를 배우고, 발성 학원에 가고, 패션 선생님을 두고, 상담을 가고, 코칭을 받은 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올해 가장 큰 변화라고 할 수 있다. 나 혼자 문제를 해결하지 않고, 내 문제/취약성을 드러내고 주변인/전문가를 찾아가게 되었다. 이전에도 모든 것을 혼자하는 것은 아니었지만, 이렇게나 적극적으로 사람을 찾아가고, 질문하고, 답을 얻어낼 수 있을지 몰랐다. 삶의 여러 문제가 훨씬 쉽게 풀리고 있고, 이것 덕분에 더 행복해지고 있다. 삶의 난이도가 더 쉬워졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;속독을 배워서, 텍스트 읽는 속도를 2배 늘렸음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 가장 어려워 하는 부류의 일이다. 하루에 1시간, 주 5일 꾸준히 하다보니 자연스레 잘 되는 그런 것. 나는 하루에 10시간 몰입하는 것은 자신이 있으나, 조금씩 신경 쓰는 것은 정말 못했다. 속독 같은 것을 잘하려면, 하루만에 되는게 아니라, 꾸준히 연습하며, 안구 근육을 키우고 뇌 이해도를 올려야 한다. 그걸 2달 가까이 매일 연습하고, 일지를 작성하고, 피드백을 받으면서, 텍스트 읽는 속도가 2배 빨라졌다. 속독을 해냈다는 기쁨도 있지만, 이젠 꾸준히 하면서 잘 해내는 방법을 알게 되었다는 것에서 큰 의미가 있다. 이를 시작으로, 패션, 독서 역량. 발성, 헬스 등을 정복하기 시작했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 내가 이룬 가장 자랑스러운 성취&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코칭을 배우면서, 사람들과 훨씬 풍요로운 대화를 할 수 있게 됨.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올해 얘기를 잘 들어준다는 피드백을 몇번 들었다. 살면서 처음 듣는 피드백이었다. 코칭 대화 덕분이라고 생각한다. 대화에서의 메타인지 역량이 엄청나게 올라갔다. 지금 상대방이 어떤 얘기를 하고 있고, 뭘 드러내고 있고, 거기에 내가 어떻게 표현해주는게, 서로 win-win 할 수 있을지가 감이 잡히게 되었다. &amp;ldquo;반영&amp;rdquo; 이라는 개념도 놀라웠다. 단지 미러링이 아니라, 상대의 말을 온전히 이해한 채, 돌려주는 대화 방식이다. 그런 걸 통해, 이전보다 더 풍요로운 대화가 가능해지고, 더 좋은 사람이 되었다고 느낀다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;ldquo;광활한 세상에서 방황하는 이들에게&amp;rdquo; 브런치북 발행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1챕터씩 글을 썼었는데, 올해는 일관된 10챕터를 쓰고 엮어서 브런치북으로 발행했다. 3일 정도 각 잡고, 하루에 3편의 글을 썼다. 무라카미 하루키 식 하루를 보내보았다. 글쓰기에 엄청나게 몰입했다. 이때를 거치면서, 1시간에 글 1편 (2000자 가량)을 쓸 수 있게 되었다. 이 글을 통해, 구독자도 조금 생겼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;우테코 레벨2 React 강의 개편.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강의 퀄리티를 상당히 올릴 수 있었다. 크루들의 근본 역량 향상에 기여했다고 생각한다. 내가 가진 모든 지식, 그 이상으로 설계하려고 했다. 개인적으로 React 강의 중, 가장 훌륭한 강의 중 하나라고 생각했다. 이렇게 React 를 깊이 이해하고 성장을 이끌어내는 강의가 있을까? 내가 그런 주제들을 몇가지 떠올린게 정말 뿌듯하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 내게 가장 새로웠던 시도&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현대철학, 논리학, 윤리학 공부함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전에는 철학을 공부하는 듯 마는 듯 했었다. 실존철학 정도만 공부했는데, 사실 이건 철학에서는 사파라고 볼 수도 있다. 철학의 근간은 일단 논리학, 존재론, 인식론, 윤리학, 미학이다. 논리학과 함께, 현대철학, 윤리학을 공부하면서, 엄밀하게 사고하는 역량이 생겼다. 이 말의 전제는 무엇이지? 건전성 혹은 타당성을 충족하는가?&lt;/li&gt;
&lt;li&gt;윤리학을 배운건 내 삶에 굉장히 의미있는 사건이었다. 이제 어떤 윤리적(옳고 그름) 문제를 함부로 말할 수 없게 되었다. 논리성, 일관성을 충족하는지 엄밀하게 검토한다. 그러면서 나의 윤리의식, 세상을 바라보는 마음이 조금 바뀌었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;몽골 여행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 고된 여행은 처음이고, 더불어 많은 친구들과 함께 하는 여행도 처음이었다. 함께 하는 것의 의미도 컸고, 몽골이라는 지역 자체도 너무 흥미로웠다. 내 시야가 한층 넓어지는 시간이었다. 유목민의 삶은 가히 충격적이고 매력적이어서 몇주간 그대로 빠져있었다. 불빛 하나 없는 밤, 별이 수놓은 하늘 아래에서 한걸음 한걸음 혼자 걸어갔던 밤, 그 느낌을 잊지 못한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 꼭 이루고 싶었는데 이루지 못했던 것&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수익화 실패
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;돈을 버는 행위를 하고 싶었다. 돈을 번다는 것은, 내가 그만큼의 책임을 진다는 것을 의미하고, 그만큼 나의 역량의 성장이 있을 것으로 기대했다. 서비스 개발로 방향성을 잡았고, 개발을 시작했다. 3개의 사이트를 개발했지만, 어느정도 세상에 내놓지 않았다. 광고를 붙이지 못했거나, 다른 중요한 것에 막혔거나, 100% 완결성을 가진 서비스가 되지 못하여서 배포하지 못한 채로 올해가 가버렸다. 무엇보다 세상에 내놓지 못했다는게 뼈아프다. 10번째 서비스에서 유저를 제대로 모아보자는 발상을 했지만, 실패를 통해 나아가는 방식으로 해내지는 못했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;글쓰기 브런치 구독자 200명, 인스타 구독자 500명
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생각보다 글로 사랑받기가 쉽지가 않구나 느꼈다. 26개의 글을 쓴 것은 잘했지만, 결과물을 내지는 못했다. 사랑받는 글보다는 내가 쓰고 싶은 글을 썼다. 내가 쓰고 싶은 글을 쓰다보면, 사랑 받을 것이라고 생각했다. 그렇진 않은 것 같다. 나라는 사람의 브랜드가 신뢰성을 뒷받침 하거나, 내 글 자체가 월등히 우월해야 한다. 그렇지 않으면, 사람들이 원하는 글을 써주어야 한다. 내 글 자체가 매력이 있다고 생각하여 계속 써나갔지만, 유의미한 피드백이 많이 찾아오지는 않았다. 조금 아쉽다. 그러든 말든 재밌으니까 계속 글을 써나가거나, 사람들이 찾아오게끔 만들거나, 방향성을 고민해보아야겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 발견한 나의 장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;청중의 니즈를 민감하게 파악하는 능력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나는 다양한 성향의 사람들과 관계를 맺고 있다. 그래서 다양한 사람들의 심리를 잘 이해한다. 더불어 난 예로부터 까다로운 수강생이었다. 학창시절부터 선생님의 강의 실력을 평가했다. 마음에 들지 않으면 수업을 듣지 않았다. 대학생때도 그랬다. 나는 아무 말이나 듣지 않는다. 그 예민함을 가지고 있어서, 청중들에게 어떤 것을 전달해야 도움이 될지 잘 설계할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;본질을 보는 능력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강의를 설계할 때, 본질적인 것을 잘 보려고 한다. React 의 본질은 무엇인가? JavaScript 에서 가장 중요한 것은 무엇인가? 학습의 본질은 무엇일까? 내가 매일 하던 사고이고, 이게 직업이 되니까, 강점이 되었다고 생각한다.&lt;/li&gt;
&lt;li&gt;코칭에서도 많이 도움이 된다. 부수적인 것에 대해서 고민하기보다, 본질적인 것으로 이끌어 가려고 노력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;집단 속의 나
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나는 어떤 집단에 속하든 가장 열심히 하는 편이고, 좋은 성과를 내는구나. 그러니까 만약 더 성취하고 싶다면, 답은 간단하다. 쩌는 집단에 속하면 된다. AC2, 발성 학원, PT, 우테코 조직에 속해보면서 느꼈다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 가장 고민했던 것&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떻게 전문가가 될 수 있을까?&lt;/li&gt;
&lt;li&gt;회피하지 않고 마주하고 표현하는 방법&lt;/li&gt;
&lt;li&gt;나의 취약성을 드러내는 법&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가장 몰입했던 순간&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우테코 레벨2 React 강의 준비할 때&lt;/li&gt;
&lt;li&gt;AC2 중간회고 준비할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 도움이 되었던 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EoA (Essense of Agility)&lt;/li&gt;
&lt;li&gt;습관 설계/행동 설계&lt;/li&gt;
&lt;li&gt;의식적인 연습 (피드백 루프, 전문가 찾아가기)&lt;/li&gt;
&lt;li&gt;코칭 (반영, 이너게임)&lt;/li&gt;
&lt;li&gt;퍼실리테이션 (결정적 순간의 대화, 절차적 회의 행동)&lt;/li&gt;
&lt;li&gt;도움 잘 요청하기&lt;/li&gt;
&lt;li&gt;강의 설계하는 법&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;올해 가장 달라진 부분&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 설계 역량이 상당히 올랐음. 무엇을 하든, 의지력에 기대는게 아니라, 환경과 분위기를 설계함. 될 수밖에 없게 만들기&lt;/li&gt;
&lt;li&gt;내 취약성을 드러내고, 사람들을 찾아가는 것. PT, 발성, 상담, 코칭, 일이 안될 때 팀원에게 찾아가기, 친구에게 고민 이야기 하기 등&lt;/li&gt;
&lt;li&gt;갈등 관리. 회사에서나, 개인적으로나, 의견 불일치, 갈등 상황에 내 의견 드러내기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Writing/회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/99</guid>
      <comments>https://happysisyphe.tistory.com/99#entry99comment</comments>
      <pubDate>Wed, 7 Jan 2026 22:08:23 +0900</pubDate>
    </item>
    <item>
      <title>모든 것이 가능한 시대, &amp;quot;Be Authentic&amp;quot;</title>
      <link>https://happysisyphe.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Be authentic-1.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CLFp0/dJMcajHkna2/kgzHdQBMASpKCdD2SpbLyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CLFp0/dJMcajHkna2/kgzHdQBMASpKCdD2SpbLyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CLFp0/dJMcajHkna2/kgzHdQBMASpKCdD2SpbLyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCLFp0%2FdJMcajHkna2%2FkgzHdQBMASpKCdD2SpbLyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;Be authentic-1.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 &amp;lt;Everything Everywhere All at Once&amp;gt; 영화를 다시 보았다. 핵심 어젠다는, &amp;ldquo;모든 가능성이 열린 세상에서 어떻게 살아갈 것인가&amp;rdquo;이다. 가능성 탐색을 좋아하는 나로서, 처음 보았을 때 매우 공감되었다. 다시 보니 AI 시대에 더욱 주목해야 할 영화라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주인공 조부 투파키는 수많은 가능성 속에서 길을 잃고 허무주의에 빠진다. 현대 사회에서 정보가 늘어남에 따라서, 이런 경향성은 더 심화했다. 인터넷의 보급으로, 스마트폰의 보급으로, LLM의 보급으로 정보의 접근성이 좋아졌다. 누구나 소프트웨어 개발을 할 수 있다고 한다. 누구나 디자인을 할 수 있다고 한다. 누구나 창업을 할 수 있다고 한다. 이때 우리는 무엇을 해야 할까? 가능의 세계가 무한대로 열렸을 때, 무엇을 선택해야 할까? 현대인은 조부 투파키의 감정에 공감할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영화에서는 &amp;ldquo;Be kind&amp;rdquo;, 즉 관계의 중요성을 핵심 메시지로 강조한다. 모든 것이 가능하더라도, 사랑의 경험만은 특수하다. 사랑하는 것은 큰 우연이며, 행복이고, 지금 이 자리에 있는, 사랑하는 이에게 친절함을 베푸는 것만이 허무주의를 극복하는 방법이라고 한다. 관계 측면에서 매우 공감하는 이야기이다. 다만, 사람은 노동을 해야 하므로, 노동의 측면에서도 생각해 보고 싶었다. &amp;ldquo;모든 것이 가능한 세계에서, 우리는 어떻게 일해야 할까?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해 &amp;ldquo;Be authentic&amp;rdquo;으로 표현하고 싶다. 내면에 더 귀 기울이고, 진실되게 살아가는 태도를 강조하고 싶다. 나는 AI 시대에, 좋아하는 일을 하는 게 훨씬 더 중요해졌다고 생각한다. &amp;ldquo;좋아하는 일 vs 잘하는 일&amp;rdquo; 딜레마는 고전적인 질문이다. 다만 나는 답이 점점 좋아하는 일로 기울고 있다고 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 좋아하는 일을 잘 해내기가 어려웠다. 세 가지 이유가 있었다. 자료를 찾기가 어려웠고, 내가 잘하도록 도와줄 코치/선생을 찾기도 어려웠다. 내 결과물에 대한 피드백을 받기도 어려웠다. 그래서 과거에는 우연히 좋은 선생을 만나고, 우연한 환경이 갖추어졌을 때, 우연히 잘 해낼 수 있었을 것이다. 점점 이게 필연의 영역으로 가고 있다. 필연적으로 좋은 자료를 찾고, 필연적으로 좋은 선생을 만나고, 필연적으로 좋은 피드백을 자주 받을 수 있다. 즉, 좋아하는 일을 잘 해내게 할 가능성이 높아졌고, 그러므로 좋아하는 일을 하라는 말의 정당성도 높아졌다는 논리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 자주 내면을 보지 않고, 외부의 기준만을 바라본다. 외부에 있는 수백 가지 일을 비교하느라 시간을 쓴다. 그럴 때마다 잠깐 그곳에서 빠져나와서, 내면에 있는 좋아하는 일 한 가지에 귀를 기울일 필요가 있다. AI 시대에 이런 진실성이 더욱 중요해질 것으로 생각한다. 그걸 해내는 사람은 더욱 멀리 나아갈 것이다. 그런 사람은 AI에게 단순히 대체되지 않을 것이고, AI를 즐거이 활용하는 사람이 될 것이다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/98</guid>
      <comments>https://happysisyphe.tistory.com/98#entry98comment</comments>
      <pubDate>Sun, 9 Nov 2025 23:51:51 +0900</pubDate>
    </item>
    <item>
      <title>애자일(Agile)에 대한 찬가</title>
      <link>https://happysisyphe.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;애자일에 대한 찬가.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8GRNy/dJMcahW0Thq/rZ8dWUfQtrfEhkiOOKMlRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8GRNy/dJMcahW0Thq/rZ8dWUfQtrfEhkiOOKMlRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8GRNy/dJMcahW0Thq/rZ8dWUfQtrfEhkiOOKMlRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8GRNy%2FdJMcahW0Thq%2FrZ8dWUfQtrfEhkiOOKMlRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;애자일에 대한 찬가.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 애자일이 너무 좋다. 내가 IT 업계에 남아있고 싶은 이유는 바로 애자일 때문이다. 애자일(Agile)은 보통 기민한 제품 개발 방법론을 칭한다. 나는 이것을 삶의 개념으로 확장하곤 했다. 이후에 김창준님의 함께 자라기 책을 읽었다. 그는 애자일을 단순히 제품 개발 방법론으로 보지 않고, 삶에 대한 자세와 사고방식 그 자체로 바라보았다. 불확실한 세계에서, 기민하게 대응해나가는 과정 자체를 애자일로 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 지금 애자일을 삶의 자세로 받아들이고 있다. 애자일을 이해하면, 삶의 많은 부분이 쉬워지기 때문이다. 그것은 바로 애자일의 본질이, 삶을 반영하고 있기 때문이라고 생각한다. 이제 내가 왜 애자일을 사랑하는지 5가지 이유로 적어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 인생을 불확실하기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일의 전제는 불확실성이다. 앞으로 일어날 일이 확실하다면, 애자일 방법론을 채택할 필요가 없다. 앞으로 일어날 일을 모두 계획하고, 그것대로 한단계씩 실행하면 된다. 불확실한 상황에서 고정된 계획은 중요하지 않다. 1가지를 수행하고 나면, 이후의 모든 일이 달라지기 때문이다. 1단계에서 성공하리라 생각했는데, 그것이 실패한다면, 뒤의 모든 순서가 달라질 것이다. 그래서 애자일은, 미래를 예측하는 순서를 제안하지 않는다. 지금 가장 중요한 것 1가지를 먼저 수행하고, 그에 맞게 기민하게 대응하길 기대한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업은 실제로 이렇게 일한다. 불확실한 시장에서 균열을 내고 싶어하기 때문이다. 한번에 큰 제품을 출시하는게 아니라, 작지만 핵심을 담은 제품 1가지를 빠르게 시도해본다. 이게 MVP(Minimum Viable Product) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 피드백을 중시하기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 피드백을 중시한다. 불확실한 상황에서는, 고정된 순서대로 행동하면 안 된다. 중요한 한가지 일을 먼저 수행하고, 피드백을 받고, 다음 태스크를 설계하는 편이 좋다. 여기서 피드백 과정이 없다면, 후에 더 나은 선택을 할 수 없다. 피드백에는 여러 종류가 있다. 제품 회고, 팀 회고, 개인 피드백 등이 있다. 여러 측면에서, 회고(retrospect)하고, 피드백을 받으며, 기민하게 적응해간다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이게 정말 멋지다고 생각한다. 이건 고이지 않겠다는 의지이다. 유독 소프트웨어 분야에서는 나이가 많지만, 꼰대라 불리지 않고, 존경할 만한 사람이 많은 것 같다. 20대와 50대가 허울 없이, 수평적으로 대화하는 경우가 많다. 그건 회고와 피드백을 통해 메타인지를 높였기 때문이라고 생각한다. 신체는 나이들어 가지만, 정신은 늙어가지 않는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 작은 시도를 권장하기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 작은 시도를 권장한다. 불확실한 상황에서 크게 시도하는 것은 리스크가 크다. 이 행동이 좋은 결과로 이어질지 알 수 없는데, 큰 비용을 투자하는 것은 비합리적이다. 일단 작지만, 핵심적인 행동을 실험하고, 그것이 좋은 결과로 이어지는지 확인해야 한다. 그게 예측과 맞다면, 이후에 큰 비용을 투자하는 것이다. 이것은 피드백을 받아야만 가능한 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 나의 삶의 태도에 엄청나게 큰 영향을 주었다. 컴퓨터공학과 복수전공을 하고 싶다고 생각하자. 그렇다고 첫 학기에 21학점을 컴퓨터공학 수업으로만 채우면, 어떻겠는가? 그 학기를 모조리 날려버릴 수도 있다. 일단 6학점 정도 들어보는 것이다. 이때 컴퓨터공학의 가장 핵심적인 과목들로 골라야 한다. 2과목을 듣는 것만으로, 컴퓨터공학의 전반을 대표할 수 있어야 한다. 그 2과목을 듣고, 여전히 흥미가 있고 적성에 맞다면, 그 다음부터 21학점을 들어볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 핵심을 바로 꿰뚫기 때문이다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 단순히 작게 시도하는 것이 아니다. 1단계를 억지로 10단계로 쪼개서 하나씩, 순차적으로 행동하라는 것이 아니다. 하나를 수행하는게, 전체를 수행하는 것과 크게 다를 바 없으며, 그 하나가 완수되면 그 이후의 모든 것이 쉬워지는, 그것 하나를 고르라고 한다. 그게 바로 핵심 행동이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운동으로 몸을 키워서 바디 프로필을 찍어야 한다고 하자. 거기까지 가는 여러 단계가 있을 것이다. 벤치 프레스를 알아야 하고, 스쿼트를 알아야 하고, 식단 관리도 알아야 한다. 그 중에서 무엇을 하나 하면, 그 이후의 모든 것이 쉬워질까. 나에게 정답은 PT 를 끊는 것이다. PT 를 끊으면 뒤의 모든 것이 자연스레 해결될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 사람을 중시하기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애자일은 고객을 중시하고, 함께 협업하는 동료를 중시한다. 애자일 원칙 중에서 사람과 관련된 원칙이 많다. &amp;ldquo;고객 만족을 최우선으로&amp;rdquo;, &amp;rdquo;업무 담당자와 개발자는 매일 협력한다&amp;rdquo;, &amp;ldquo;직접 대화가 가장 효과적인 커뮤니케이션이다&amp;rdquo; 등이 있다. 더 능동적으로 협업하며 문제를 풀고, 그게 사람들에게 어떤 가치를 전달하는가를 계속 생각한다. 이런 점에서 애자일은 너무나 인간적이고, 멋진 철학이다.&lt;/p&gt;</description>
      <category>Tech/Sofeware Development</category>
      <category>애자일</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/97</guid>
      <comments>https://happysisyphe.tistory.com/97#entry97comment</comments>
      <pubDate>Sun, 2 Nov 2025 23:46:15 +0900</pubDate>
    </item>
    <item>
      <title>나의 인생 교과서 연대기</title>
      <link>https://happysisyphe.tistory.com/96</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;나만의 인생 교과서 연대기-1.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q9Gmz/btsQ6efgPGR/7hYVvSfVix7crGidfSmvCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q9Gmz/btsQ6efgPGR/7hYVvSfVix7crGidfSmvCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q9Gmz/btsQ6efgPGR/7hYVvSfVix7crGidfSmvCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ9Gmz%2FbtsQ6efgPGR%2F7hYVvSfVix7crGidfSmvCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;나만의 인생 교과서 연대기-1.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분들의 인생 교과서는 무엇인가요? 지금 당신에게 가장 많은 배움을 주는 책은 무엇인가요? 저에게는 &amp;ldquo;이기적 유전자&amp;rdquo; 입니다. 문득 제 인생의 교과서 이력을 돌아보았습니다. 한때는 &amp;ldquo;여덟단어&amp;rdquo; 였다가, &amp;ldquo;데미안&amp;rdquo; 이었다가, &amp;ldquo;인간의 굴레에서&amp;rdquo; 였다가, &amp;ldquo;시지프 신화&amp;rdquo; 였다가, &amp;ldquo;이기적 유전자&amp;rdquo; 까지 왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학창 시절에 한 학기마다 교과서가 바뀌듯, 인생의 교과서가 바뀌는 것은 자연스럽다는 생각이 듭니다. 오히려 바뀌는 것이 참 의미 있다고 느껴지기도 합니다. 누군가 계속해서 중학교 1학년 교과서만 보고 있다면, 그것대로 경직된 인생일 것입니다. 이 이야기를 하는 이유는, 제가 말한 과거의 발언이 의심되었기 때문입니다. 제가 과거에 연애의 교과서라고 하며 즐겨 추천해 왔던 책이 &amp;ldquo;왜 나는 너를 사랑하는가&amp;rdquo; 입니다. 최근 들어 &amp;ldquo;행복한 커플은 어떻게 싸우는가 (Fight Right)&amp;rdquo; 책을 읽으면서, 교과서가 대체될 위기에 처했습니다. 과거의 경험이 스쳐 지나갔습니다. &amp;ldquo;왜 나는 너를 사랑하는가&amp;rdquo; 를 내 교과서로 삼겠다고 말했던 그때가요. 이걸 어떻게 받아들여야 할까 고민하면서, 지금의 생각에 이르게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교과서는 변해야 합니다. 내 지식이 깊어짐에 따라, 내 경험이 넓어짐에 따라, 내 철학이 확고해짐에 따라. 그때 그 시절, 각자의 수준에 맞는 교과서가 있는 것입니다. 중학교 1학년 때는 일차 방정식을 배워야 하고, 중학교 2학년 때는 연립 방정식을 배워야 합니다. 그것처럼 자신에게 맞는 책이 변하는 것도 당연합니다. 중요한 건 멈추지 않고, 고이지 않는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 지식이, 과거의 지식을 대체한다고 한들, 과거의 지식이 무의미해지는 것도 아닙니다. 연립 방정식을 배웠기 때문에, 미분을 더 잘 해낼 수 있는 것입니다. 과거의 저를 우습게 보고 싶지는 않습니다. 그때는 그것이 내가 배울 수 있는 최대 지식이었고, comfort zone을 조금 벗어난, 최적 난이도의 내용이었던 것입니다. 그것 없이 단번에 점프해 낼 순 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22살에 알랭 드 보통의 &amp;ldquo;불안&amp;rdquo;을 읽었던 기억이 납니다. 책이 너무 어려워서, 50페이지도 채 읽지 않고 포기했던 기억이 납니다. 내용이 이해되지 않았습니다. 28살에 다시 그 책을 들었습니다. 이번엔 되려 너무 쉽게 느껴졌습니다. 이미 그동안, 실존주의, 현대철학, 심리학 등 다양한 분야를 접하며 비슷한 내용을 수도 없이 읽으며 수련해왔기 때문입니다. 이처럼 현재의 자신에게 가장 적절한 책이 있을 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로도 교과서가 많이 대체될 것입니다. 저는 그간 섬겨왔던 모든 경험, 지식, 책, 사람을 존경하면서, 앞으로 나아갈 것입니다. 변화를 즐거이 맞이할 것입니다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/96</guid>
      <comments>https://happysisyphe.tistory.com/96#entry96comment</comments>
      <pubDate>Mon, 13 Oct 2025 00:05:55 +0900</pubDate>
    </item>
    <item>
      <title>철학이 아니라 철학함을 배워라</title>
      <link>https://happysisyphe.tistory.com/95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;철학이 아니라 철학함을 배워라.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc8oqP/btsQGTQThCP/LT2GhkH31fLEqATQETtQ9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc8oqP/btsQGTQThCP/LT2GhkH31fLEqATQETtQ9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc8oqP/btsQGTQThCP/LT2GhkH31fLEqATQETtQ9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc8oqP%2FbtsQGTQThCP%2FLT2GhkH31fLEqATQETtQ9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;철학이 아니라 철학함을 배워라.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;철학이 아니라 철학함을 배워라&amp;rdquo; 칸트가 한 유명한 말이다. 여기에서 &amp;ldquo;철학&amp;rdquo;은 지식의 습득을 말하고, &amp;ldquo;철학함&amp;rdquo; 은 비판적으로 스스로 생각하는 태도를 말한다. 지식에 일방적으로 의존하는 학습자가 되는게 아니라, 자율적인 사유자가 되길 칸트는 바랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;철학은 알지만 철학함은 모르는 사람이 많다. 다른 말로 하면, 지식을 잘 알고 시험은 잘 치지만, 그것의 본질을 놓치는 경우가 많다. 애자일(Agile) 방법론은 알지만, 애자일의 본질을 놓치는 경우가 많다. 이는 외부적 지식을 내면화하지 못했기 때문이다. 나의 바깥에 있는 지식을 단지 하나의 지식으로 받아들일 뿐, 그 지식까지 온 과정을 보지 않거나, 비판적으로 수용하지 않거나, 내 삶과의 연결성을 고민하지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 들어보자. 르네 데카르트의 &amp;ldquo;나는 생각한다, 고로 존재한다&amp;rdquo; 는 말은 너무나 유명하다. 이 말을 아는 것은 철학을 아는 것이고, 이 말의 의미와 타당성/건전성을 재고해보는 것이 철학함을 아는 것이다. 데카르트가 어떤 시대적 배경, 맥락과 논리적 사고 과정 속에서 이런 말을 했는가를 비판적으로 검토하는 것이 철학함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 학문도 똑같다. 애자일 방법론을 적용하기 위해, 스크럼, 칸반, 회고 등을 도입하곤 한다. 맹목적인 도구 도입은 애자일을 보장하지 않는다. 애자일함은 무엇인가? 불확실한 상황 속에서도 빠르게 실행하고, 피드백을 받으며 가치를 전달하는 것이다. 애자일함 없는 애자일은 사상누각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 철학만 아는 사람이 철학함을 아는 사람을 이길 수 없다고 생각한다. 우리가 살아가는 세상은 지식 대결이 아니기 때문이다. 데카르트가 무슨 주장을 했는지 안다고 하여, 삶의 문제를 풀 수 없다. 심지어 특정 철학자의 견해를 몰라도, 철학함만 배워도 된다고 생각한다. 나는 학문은 시간 문제라고 생각한다. 그 학문의 자세가 마음에 든다면, 나의 사상과 잘 맞아떨어진다면, 그 학문에 오랜 시간 정진하면 되는 것이다. 그럼에도 불구하고 지식을 배워야 하는 이유가 있다면, 그것은 철학함을 배우기 위해서이다. 소프트웨어 프로그래밍 언어를 배우는 이유가 있다면, 그것은 소프트웨어 공학함을 배우기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누구든 학습할 때 지식의 저주에 빠지기 쉽다. 지식을 알아야만 해결이 될 것 같다고 느낀다. 또는 지식이 남들에게 말하기 좋기 때문에 매력적으로 느껴지기도 한다. 그리하여 책을 읽을 때도 빠르게 읽는 데만 집중하여, 핵심 메시지를 꿰뚫지 못하기도 한다. 중요한 것은 하나의 지식이라도 충분히 내면화하는 것이다. 그게 충분히 쌓이고 나면, 전혀 모르는 지식도 같은 원리로 간파해 낼 수 있을 것이다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/95</guid>
      <comments>https://happysisyphe.tistory.com/95#entry95comment</comments>
      <pubDate>Sat, 20 Sep 2025 21:53:27 +0900</pubDate>
    </item>
    <item>
      <title>토스팀에서 배운 것 : 3. 성장의 핵심은 피드백이다.</title>
      <link>https://happysisyphe.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;토스에 입사하고 나서, 팀원들에게 어떤 회사에서 왔냐고 자주 질문 받았다. 나에게 첫 회사인데 말이다. 직급이 없으니 내가 말하지 않으면 내 연차를 몰랐다. 채용할 때부터, 그 일을 수행할 수 있는지가 중요할 뿐, 연차는 중요하지 않았다. 실제로 1년차와 10년차가 같은 일을 하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 토스는 역량 중심 회사이다. 토스 내에서 일반 사원 출신이 임원이 된 경우가 허다하다. 일반 PO 에서 계열사 CEO가 되거나, 일반 개발자에서 Head 가 되거나. 토스에서 자주 쓰이는 말 중 하나가, &amp;ldquo;역량에 따른 역할 확장&amp;rdquo;이다. 연차와 직급에 따른 역량 확장이 아니라, 역량이 핵심이다. 그 일을 할 역량이 있고, 그 일을 하고자 한다면, 기회를 쥐어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역량 중심 문화에 따라오는 키워드는 Feedback 이다. 역량 중심 회사는, 역량이 높은 사람을 채용한다는 의미이기도 하지만, 역량을 키워서 쓴다는 의미이기도 하다. 역량이 기대에 미치지 못하는 경우, 서로가 피드백을 한다. 이게 평가라고 들릴 수 있지만, 내가 경험한 것은 평가가 아니었다. 업무할 때 좋은 점, 아쉬운 점을 활발히 피드백 하지만, 이게 당신을 해하기 위함이 아니다. 진심으로 성장하길 바라고, 팀에 도움이 되었으면 하는 바람에 피드백을 주고 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 아래에서 위로의 피드백도 가능하다. 엄밀히 말하자면 아래, 위라는 것도 명확하지 않았다. 90%가 직급이 없이 그냥 사원이기 때문에 아래위를 구분할 수도 없었다. 그래서 7~8년씩 일한 팀원에게 겨우 1년 일한 내가 피드백을 드리곤 했다. 일반적인 회사였다면 대리가 과장에게 피드백하는 꼴이라, 상상하기 어려울 수 있다. 그렇지만 최소한 토스가 일하는 방식은 내가 잘 이해하고 있었기 때문에, 이러한 피드백이 당연히 가능하고, 가능해야만 하는 일이었다. 우리는 개인을 성장시키고, 일이 잘 되게 만들어서, 사업을 성공시키기 위해서 들어왔고, 그 목표를 기준으로 보면, 서로 피드백해야 함이 마땅하다. 그분도 겸손하게 피드백을 수용해 주시며 감사하다는 메시지를 주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자세는 나에게 유년 시절부터 뿌리 박혀 있었고, 토스 문화를 경험하며 더 극대화된 것 같다. 피드백은 삶을 더 나아지게 만든다. 문제를 초기 발견하여 최악으로 일이 진행되지 않게 만든다. 나아가 잘하고 싶은 것을 진정 잘하게 해주는 것이 바로 피드백이다. 피드백 없이는 어떤 것이든 잘 해낼 수 없다. 다만 피드백을 주고받는 것은 용기를 필요로 한다. 기존의 나를 무너뜨릴 용기, 비록 상대방이 수용하지 않을지도 모르지만 그럼에도 성장을 도와주는 용기가 필요하다. 토스에서 그 용기를 배웠다.&lt;/p&gt;</description>
      <category>Writing/토스에 다니며 배운 것</category>
      <category>조직문화</category>
      <category>토스</category>
      <category>피드백</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/94</guid>
      <comments>https://happysisyphe.tistory.com/94#entry94comment</comments>
      <pubDate>Sun, 7 Sep 2025 23:30:58 +0900</pubDate>
    </item>
    <item>
      <title>토스팀에서 배운 것 : 2. 토스의 성공은 8번의 실패에서 왔다.</title>
      <link>https://happysisyphe.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;토스가 대기업 사이에서도 두각을 드러내고 있는 이유는 가히 8번의 실패 때문이라고 말하고 싶다. 승건님의 8전 9기 이야기는 매우 유명하다. 창업에서 8번 실패하고, 9번 아이템이 토스였고 이때 드디어 많은 유저를 모을 수 있었다. 만약 토스가 첫 번째 창업 아이템이었다면, 토스가 지금까지 성공을 이어오지 못했을 것이라고 확신한다. 간편송금이라는 아이템 자체도 훌륭했지만, 사실 그 아이디어로 토스가 성공했다고 말할 수 없다. 곧바로 경쟁자가 달라붙었기 때문이다. 토스의 진짜 성공은, 단지 아이디어에서 온 것이 아니라, 계속된 경쟁 속에서 이겨나가고 있는 데서 온 것이다. 그렇다면 왜 8번의 실패가 지금의 토스를 만든 것인지, 토스의 3가지 문화로 이야기해 보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;첫 번째는 수평적 문화이다. 승건님은 본인이 성공할 것이라고 확신한 아이템이 실패하는 경험을 여러번 했다. 그러면서 겸손을 배웠다고 하셨다. 그래서 뿌리 깊게 자리 잡은 문화가 바로 수평적 문화이다. 모두의 의견이 틀릴 수 있으니, 수평적 토론을 통해 더 나은 의견으로 발전시킬 수 있어야 했다. 내가 경험한 토스는 상당히 수평적이었다. 대표도 일반 사원 속에서 똑같은 자리에 앉아있었다. 신입 개발자로 입사하여, 겨우 1개월된 나의 아이디어도 승건님은 긍정적으로 검토해주며, &amp;ldquo;의진님 한건 했다&amp;rdquo;고 하이파이브를 쳐주셨던게 기억에 남는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;두 번째는 데이터 기반 의사결정 문화이다. 수평적 토론만큼 중요한 것이 바로 데이터이다. 대표의 의견이 맞을지, 구성원의 의견이 맞을지 어떻게 판단할 수 있는가? 그냥 대화하다 보면 힘이 센 사람의 의견으로 기울기 쉽다. 이를 해소하기 위해 데이터 기반 의사결정이 필요했다. 그래서 지금의 토스는 어떤 기업보다 데이터 기반 의사결정이을 잘하고 있다. 과학적으로 제품을 개선해 나가기 때문에, 직감으로 대응하는 다른 기업이 상대가 되기 어려울 것이라고 생각한다. 토스가 처음에 직감을 통해 실패 없이 성공했다면, 데이터를 볼 필요성을 느끼지 못하고 여전히 직감으로 의사결정을 했을 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;세 번째는 토스에서 가장 중요한 핵심 가치인 &amp;ldquo;Focus on impact&amp;rdquo; 이다. 이는 &amp;ldquo;하면 좋을 10가지를 하지 말아야할 것으로 정의한다&amp;rdquo;로 해석된다. PMF(Product Market Fit)는 하면 좋을 10가지에서 오지 않는다. 고객이 꼭 필요로 하는 그것이 PMF 를 만든다. 토스팀은 이전에 울라블라 라는 앱을 만들었었고, 이는 하면 좋을 10가지에 집중하다가 망한 서비스로 회자된다. 울라블라는 제품 디자인 부문에서 상을 받을 만큼 우수한 디자인을 가졌었다. 다만 꼭 필요한 것에 집중하지 못해서 실패했다. 그래서 지금의 토스는 회의할 때 항상 임팩트에 대한 집착을 보인다. &amp;ldquo;매출 지표를 올리기 위해서 어떤 아이템이 가장 임팩트가 클까요?&amp;rdquo;, &amp;ldquo;이 작업은 Must 일까요? Good to have 일까요?&amp;rdquo; 를 토론하며, 임팩트가 작아 보이는 작업은 걷어내거나 다음으로 미루었다. 항상 가장 중요한 일을 하는 기업은 다른 기업에 비해 몇 배로 빠르게 달릴 수밖에 없을 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이처럼 토스는 실패를 통해 귀중한 자산을 남겼다. 그래서 토스는 실패를 두려워하지 않는다. 오히려 실패는 해야만 하는 일이라고 생각한다. 토스 핵심가치 3.0에 &amp;ldquo;Courage to fail fast&amp;rdquo; 가 있었다. 실패하기를 권장했던 것이다. 어차피 실패를 해야만 한다면, 실패를 통해 배워야만 한다면, 그 실패를 어떻게 값싸게 해낼 것인지가 중요한 질문이 된다. 실패해도 비용이 적도록 설계하여, 빠르게 러닝만 얻고 다음에 더 좋은 액션을 하는 것이다. 내가 입사하고 첫날 들었던 말 중 하나가 10일 안에 실패하라는 것이었다. 빠르게 실패해보고 그것을 통해 배움을 얻고 실패를 두려워하지 않는 팀원이 되길 바랐던 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 문화에 익숙해지면서, 나도 실패가 덜 두려워졌다. 지금은 토스를 퇴사하고 소프트웨어 개발 교육을 하고 있다. 토스에서 배운 것을 적용하여, 강의를 준비할 때도, 한번에 모두 다 준비하지 않는다. 개괄적인 진행 방식만 산정하고, 주변의 팀원에게 피드백을 받아본다. 이렇게 진행할 예정인데 어떻겠냐고 물어본다. 피드백을 반영하여 더 구체화하여 강의를 구성해본다. 그리고 다시 피드백을 받아보고 개선해보는 과정을 반복하고, 강의를 완성한다. 일상에서도 똑같다. 집을 구한다고 해도, 한번에 모든 것을 준비해서 부동산에 가지 않는다. 어느 정도 의견을 생각해보고, 빠르게 공인중개사를 만나서 내 생각에 대한 피드백을 받아본다. 그렇게 정보를 업데이트 한 후에 다시 피드백을 받아본다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;나는 완벽주의 성향을 가지고 있고, 혼자 깊이 생각하길 즐긴다. 그러다보면 나만의 세계에 빠져서 잘못된 방향으로 생각이 전개되곤 했고, 며칠이 지나고서야 그것을 깨닫곤 했다. 이제는 이러한 일이 현저히 줄었다. 토스에서 겸손함을 배웠고, 빠르게 실패할 용기를 배웠다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Writing/토스에 다니며 배운 것</category>
      <category>문화</category>
      <category>실패</category>
      <category>토스</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/93</guid>
      <comments>https://happysisyphe.tistory.com/93#entry93comment</comments>
      <pubDate>Sun, 24 Aug 2025 23:28:30 +0900</pubDate>
    </item>
    <item>
      <title>토스팀에서 배운 것 : 1. 나는 토스에서 주인의식을 배웠다.</title>
      <link>https://happysisyphe.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;나는 토스에서 주인이 되는 법을 배웠다. 신입 개발자로 입사해, 입사 두 달 만에 하나의 서비스를 온전히 혼자 개발하며 빠르게 성장할 수 있었다. 이는 토스가 주인의식을 강조하는 문화이기에 가능했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아마존의 제프 베조스는 &quot;피자 두 판의 법칙&quot;을 말했다. 한 팀은 피자 두 판을 배부르게 먹을 수 있을 정도의 인원이어야 한다는 것이다. 그 이상이 모이면 의사소통에 비효율이 생기고, 속도가 느려지며, 책임감이 약해진다는 의미다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;토스의 승건님도 유사한 문화를 지향한다. 토스에는 수백 개의 작은 스타트업이 있다고 말한다. 각 팀은 4~6명 정도로 구성되며, 하나의 서비스나 기능을 이 인원만으로 개발한다. 그 과정에서 모든 의사결정은 팀이 직접 내린다. 단순 산술로 보아도 한 명이 하나의 서비스에 약 20%를 기여하는 셈이다. 이 정도면 주인의식을 넘어서, 말 그대로 '주인'이라고 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이런 환경에서 일하면서 많은 것을 느꼈다. 한 팀에 누가 있느냐에 따라 결과가 완전히 달라질 수 있고, 내가 서비스를 바꿀 수도 있다. 나의 결정이 서비스의 성공을 좌우하고, 나의 실수로 인해 서비스가 실패할 수도 있다. 시스템뿐 아니라 개인의 영향력이 절대적으로 중요한 구조다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;false&quot; data-block-index=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;나는 입사 두 달 만에 '토스 쇼핑'이라는 서비스의 프론트엔드 파트를 혼자 개발했다. 팀은 PO, 디자이너, 서버 개발자, 데이터 분석가, 그리고 나, 프론트엔드 개발자로 구성되어 있었다. 나는 개발자였지만, 단순히 개발만 하는 식의 분업을 할 수는 없었다. 좋은 의사결정을 위해 모든 팀원이 기획과 디자인, 테스트, 배포, 모니터링에 관여해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개발자임에도 타사 서비스를 분석하고, 아이디어를 제시하고, 서비스의 근본적인 방향까지 고민하는 과정에서 내가 정말로 팀을 바꾸고 있다고 실감했다. 나의 선택이 서비스의 성장을 이끄는 순간들을 여러 번 경험했다. 그 경험을 통해 나는 더욱 서비스의 주인이 되었고, 일에 흥미를 느끼게 되었다. 내가 성장할수록 기여할 수 있는 영역이 넓어지며, 결국 세상을 바꾸는 사람이 될 수 있다. 토스팀에서는 수많은 개인이 그런 감각을 느낄 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이후 다른 회사로 옮기고 나서야 이 문화의 차이를 더욱 체감했다. 다른 회사 이야기를 들었을 때, 단지 회사를 자신과 분리하여 사고하는 사람이 대부분이었다. 나는 여전히 회사의 입장에서 생각하고, 행동하고, 제안하는 사람이었다. 노동자이지만, 곧 경영자처럼 사고하는 것이다. 나의 목표, 팀의 목표, 회사의 목표가 일치하는 지점을 찾아내려고 노력한다. 주인은 자신의 것을 쉽게 내팽개칠 수 없기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정리하자면, 나는 토스에서 '주인'이 되는 법을 배웠다. 물론 주인의식을 강요하는 문화에 비판적인 시선이 있다는 것도 알고 있다. 회사는 회사일 뿐, 왜 주인처럼 일해야 하느냐는 반문도 이해한다. 나 역시 그런 생각을 전적으로 부정하지 않는다. 그러나 나는 세상을 바꾸는 사람이 되고 싶고, 그러기 위해서는 주인이 되어야만 한다. 누군가를 위해서가 아니라, 그것이 나에게 즐거운 일이기 때문이다. 회사를 위해 억지로 주인이 되라는 요구는 고통스러울 수밖에 없다. 주인의식은 누가 시켜서 생기는 것이 아니라, 스스로 느끼고 원할 때 진정한 힘이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-shown=&quot;true&quot; data-block-index=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;지금은 교육을 위해 다른 회사에 와 있지만, 여전히 나는 토스에서 배운 주인의식을 실천하고 있다. 우리 팀의 교육 방향성과 조직의 생존에 기여하고 있다는 확신을 가지고 일하고 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Writing/토스에 다니며 배운 것</category>
      <category>문화</category>
      <category>주인의식</category>
      <category>토스</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/92</guid>
      <comments>https://happysisyphe.tistory.com/92#entry92comment</comments>
      <pubDate>Sun, 24 Aug 2025 23:27:49 +0900</pubDate>
    </item>
    <item>
      <title>소년등과 부득호사(少年登科 不得好死)</title>
      <link>https://happysisyphe.tistory.com/91</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;소년등과 부득호사.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHi6tI/btsPUUIZncO/OqJlNZjclxhBq86jgzlEU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHi6tI/btsPUUIZncO/OqJlNZjclxhBq86jgzlEU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHi6tI/btsPUUIZncO/OqJlNZjclxhBq86jgzlEU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHi6tI%2FbtsPUUIZncO%2FOqJlNZjclxhBq86jgzlEU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;소년등과 부득호사.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3년 공부 끝에 공무원 시험에 합격한 친구에게 물어보았다. 1년 만에 시험에 합격했다면 어땠겠냐고. 친구는 스스로가 너무 꼴 보기 싫을 것이라고 말했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 나이를 먹어감에 따라 어느 정도 정설이라 믿게 된 고사성어가 있다. 바로 소년등과 부득호사(少年登科 不得好死)이다. 어릴 적 과거에 급제하면 결국 비참한 죽음을 맞이한다는 의미이다. 다소 의역하면, 조속한 성공은 불행을 낳는다는 것이다. 격언을 진리로 치부하길 꺼리지만, 이 격언은 굉장히 설득력 있게 느껴진다. 살아가면서 이 가설의 검정력을 올리는 귀납적 근거를 많이 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 소년등과 하면, 부득호사 할까. 이 인과관계는 왜 일리 있을까. 여러 근거가 있을 것이다. 주변의 시기 질투를 받는다거나, 성숙도에 맞지 않는 지위를 누려 힘에 부친다거나 하여, 거창한 시작과 맞지 않게 미미한 결말을 맞을 수 있다. 하지만 내가 가장 주목하는 근거는 &amp;ldquo;실패 없음&amp;rdquo; 이다. 실패하지 않고 성공만 해온 삶은 위험하다. 사람은 마땅한 실패를 겪으며 성장해야 한다. 마땅한 때에 실패하지 않은 사람은, 과도한 자신감, 특별한 인간이라는 착각, 능력에 대한 과신을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 노력과 능력으로 모든 것을 이룰 수 있는가? 자신에 대해 그렇다고 답하는 것은 위험하며, 타인에 대해 그렇다고 답하는 것은 더욱 위험하다. 삶은 우연의 연속이고, 부조리하다. 나의 국적, 부모님, 유전자부터 삶의 사사로운 사건 대부분이 우연이다. 인간의 열망을 세계는 보기 좋게 부숴버린다. 자신이든, 자신과 가까운 사람이든 언젠가 실패할 수밖에 없다. 그때 스스로와 주변인을 위로할 수 있어야 한다. 단지 노력이 부족했다고 타이른다면, 이는 조속한 성공으로 삶의 본질을 심히 잘못 파악한 것이다. 그리고 주변에는 아무도 남지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 아주 다행히도, 조속한 성공을 거두지 못했다. 성인이 되고, 경제학도 전공하고, 프로그래밍도 배우고, 철학도 배우고, 교육에도 종사하며, 다양하게 시도하고 실패하며 살고 있다. 아마 앞으로 몇 년간 더 실패하며 지낼 것이다. 삶은 길고, 사람은 실패를 먹이로 하여 성숙해진다. 실패하고, 충분히 아파한 다음에, 즐겁게 성숙해진 나를 맞이하길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/91</guid>
      <comments>https://happysisyphe.tistory.com/91#entry91comment</comments>
      <pubDate>Sun, 17 Aug 2025 20:10:24 +0900</pubDate>
    </item>
    <item>
      <title>AI 시대, MVP 에 대한 생각</title>
      <link>https://happysisyphe.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 개발을 처음 시작하고, 1달여 지났을 때 만든 사이트가 있습니다. 발표시간 계산기 라는 사이트입니다. 대본을 넣으면, 발표시간을 예측해주는 간단한 기능을 가졌습니다. 약 4년간 꾸준히 이용되며, DAU 1,000 가량을 기록하고 있어요. 저의 생애 첫 프로젝트 치고는 훌륭한 성과를 내고 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 1년간 관심을 끄고 살다가, 오랜만에 구글에 발표시간 계산기를 검색해보니, 십수개의 카피캣이 생겼더군요. 당연히 유일무이한 기능은 아닌지라 그러려니 하지만, 어떤 사이트는 &lt;a href=&quot;http://co.kr&quot;&gt;co.kr&lt;/a&gt; 도메인을 kr 로만 바꿔서 퍼블리시 하고, &amp;ldquo;도움이 되셨다면&amp;nbsp;간단한 사용후기를 남겨주시겠어요?&amp;rdquo; 라고 하는 피드백 요청 멘트까지 동일하더군요. 자동으로 카피 하는 bot 이 있는걸까 하는 생각까지 드네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 사이트가 그만큼 따라하기 쉽고, 기능이 많지 않은 사이트이기 때문에 이렇게 많은 카피캣이 생겼다는 생각이 들었어요. 이렇게 작은 DAU 의 사이트에도 십수개의 카피캣이 있다면, 돈이 되는 기능을 가진 서비스들은 얼마나 더 심할까 싶네요. 이 현상을 보며 MVP 에 대해서 여러 생각이 떠올라서 공유드리려고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 최소 기능을 만드는 것은 더 쉬워졌다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 생성형 AI 를 활용하면 단번의 프롬프트로도 MVP 수준의 제품은 쉽게 만들 수 있습니다. 이전에는 그래도 하루 이틀은 개발해야 MVP 를 낼 수 있었지만 말이죠. 그말인 즉, 카피도 단번에 가능하다는거에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 그렇지만 초기선점은 여전히 중요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누구나 쉽게 제품을 만들 수 있기 때문에, 이제 MVP 의미가 퇴색되고 있다는 글들을 보았어요. 저는 여전히 MVP 는 중요하다고 생각해요. 초기선점은 큰 의미를 가집니다. 초기 바이럴은 후발주자가 들어오더라도 막아내기 어렵습니다. 먼저 터진 사이트가 SEO 상단에 위치할 확률이 높습니다. 아무리 많은 카피캣이 생겨도 제 사이트가 이미 바이럴이 되어있고, SEO 상단에 있으니, 후발주자가 큰 격차를 만들지 않는 이상 뒤집기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 아이디어로 만든 성공의 지속 기간이 짧아졌다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어를 실현하여 초기에 선점한다고 하더라도, 그 아이디어를 따라 만들고 기능을 추가하면 판도를 뒤집을 수 있습니다. 생성형 AI 와 함께 이 사이클이 짧아졌죠. 그러니, 아이디어를 처음 실현한 후에도, 꾸준한 개선이 필요합니다. 후발주자가 따라오지 못하도록요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 속도를 추구하는 문화가 중요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 속도가 굉장히 중요하다는 생각이 듭니다. 후발주자가 따라오더라도, 계속 나아가는 태도가 중요합니다. 또는 본인이 후발주자이더라도, 빠르게 쫓아가서 따라만들면 됩니다. 속도에 자신이 있다면요. 토스가 10년간 네이버와 카카오라는 거물들 사이에서 살아남고 있는 이유라고 생각합니다. 토스는 속도 면에서 다른 IT 기업보다 몇배 빠릅니다. 그래서 시장을 자주 선도하기도 하며, 뒤쳐진 사업은 누구보다 빠르게 쫓아갑니다. 이제는 조직 문화가 더욱이 중요해지는 시대가 올 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. MVP 는 무의미하지 않다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP 가 무의미하다고 생각하지 않습니다. 저는 MVP 를 단지 시장 선점을 위한 방법론만으로 생각하지 않습니다. MVP 는 린 스타트업 철학을 이행하는, 효율적이고 유연한 제품 개발 프로세스입니다. 핵심 기능이라는 본질에 집중하는 문화 그 자체입니다. MVP 를 만들기 쉬워졌으니, 더 크게 개발하고 배포하자는 의견도 있을 수 있습니다. 핵심이 아닌 기능은, MVP 를 빠르게 배포한 후에 바로 다음날에 데이터를 보고 디벨롭 하여 재배포하면 되는 일이지요. 저는 바이브 코딩이 성행하기 전부터 이미 토스에서 그렇게 일해왔습니다. 이젠 아마 하루가 아니라, 반나절밖에 걸리지 않을지도 모르죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰다보니 사실 옛날에도 똑같이 적용되는 말 같습니다. 정도가 심해진 것 뿐이지요. MVP 의 정신과 철학은 여전히 남아있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpod7/btsPKPI0dtD/NlviLeKrbh9nMjH1Kf5TNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpod7/btsPKPI0dtD/NlviLeKrbh9nMjH1Kf5TNk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;935&quot; data-filename=&quot;image2.png&quot; style=&quot;width: 49.4521%; margin-right: 10px;&quot; data-widthpercent=&quot;50.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpod7/btsPKPI0dtD/NlviLeKrbh9nMjH1Kf5TNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxpod7%2FbtsPKPI0dtD%2FNlviLeKrbh9nMjH1Kf5TNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1278&quot; height=&quot;935&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zGa69/btsPNFSaxnO/mJtHQ7WORo7hk62dEwhJK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zGa69/btsPNFSaxnO/mJtHQ7WORo7hk62dEwhJK0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1279&quot; data-origin-height=&quot;937&quot; data-filename=&quot;image3.png&quot; style=&quot;width: 49.3851%;&quot; data-widthpercent=&quot;49.97&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zGa69/btsPNFSaxnO/mJtHQ7WORo7hk62dEwhJK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzGa69%2FbtsPNFSaxnO%2FmJtHQ7WORo7hk62dEwhJK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1279&quot; height=&quot;937&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/90</guid>
      <comments>https://happysisyphe.tistory.com/90#entry90comment</comments>
      <pubDate>Fri, 8 Aug 2025 18:35:25 +0900</pubDate>
    </item>
    <item>
      <title>우테코 코치 생활 #1, 부분을 통해 전체를 가르친다</title>
      <link>https://happysisyphe.tistory.com/89</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;부분을 통해 전체를 가르치기.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPtDs8/btsPF5rr5pL/8SbnnwkUt3OlqHSQkMRhJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPtDs8/btsPF5rr5pL/8SbnnwkUt3OlqHSQkMRhJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPtDs8/btsPF5rr5pL/8SbnnwkUt3OlqHSQkMRhJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPtDs8%2FbtsPF5rr5pL%2F8SbnnwkUt3OlqHSQkMRhJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;부분을 통해 전체를 가르치기.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 주제를 1시간만 가르쳐야 한다면, 무엇을 가르쳐야 할까. 다시 말해, 주어진 강의 시간 안에 모든 진도를 뺄 수 없다면, 무엇을 선택해서 가르쳐야 할까. &amp;lt;가르칠 수 있는 용기&amp;gt; 의 저자 파커 J. 파머는 &amp;ldquo;부분을 통해 전체를 가르치기&amp;rdquo;를 강조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 개발을 가르친다고 해보자. 그 중 웹 개발을 배운다고 하면, 세부적으로 다룰 내용이 너무나 많다. HTML, CSS, JavaScript, 네트워크 통신, 서버 등. 최대한 빠르게 모든 내용을 다루는 것이 맞을까? 아니면 끝까지 완수하지 못 하더라도, 처음부터 내용을 다루고, 이후는 알아서 학습하도록 하는게 맞을까? 부분을 통해 전체를 가르친다는 사고를 적용해보자. 먼저 소프트웨어의 본질이 무엇인지 생각해보아야 한다. 소프트웨어는 코드를 활용해, 세상의 문제를 해결하는 학문이다. 웹사이트로 자신을 표현해서 친구들에게 링크를 전달해 본다거나, MBTI 사이트를 만들어 본다거나, 아이디어를 최소한의 기능으로라도 만들어볼 수 있을 것이다. 작은 문제라도 코드를 통해 해결하도록 만들면, 부분을 배웠지만 전체를 이해하게 되는 것이다. 요새는 AI 를 활용하면 소프트웨어의 본질을 빠르게 경험하기 훨씬 쉬울 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 대부분의 교사는 진도를 빠르게 나가야 한다고 압박을 느낀다. 무작정 빠르게 진도를 나간다고 하여 능사가 아니다. 학창시절, 대학 시절 그런 경험이 많을 것이다. 무엇을 위해 저렇게 빠르게 진도를 나가는가? 이것을 배워서 무엇을 할 수 있는지도 모른채 끌려다녀본 경험이 많을 것이다. 주어진 시간 안에 진도를 모두 빼주는 것이 강사의 책임이라고 느끼고, 충실히 이행했음은 이해하지만, 본질적으로 좋은 교육은 아니라고 생각한다. 목차에 따라 순차적으로 진도를 나가면, 마지막 장까지 배우고 난 후에야 본질을 이해할 수 있게 된다. 그 전까지는 혼란의 연속이고, 비효율적인 시간을 보내게 된다. 현실은 끝까지 가지도 못 하고 중간에 포기해버리는 사람이 부지기수이다. 왜 배우는지 이해하지 못하는데, 끝까지 달려나갈 사람은 심히 모범적인 학생밖에 없다. 동기부여 측면에서도, 학습의 효율성 측면에서도, 부분을 통해 전체를 가르치는 것이 효과적이다. 하나만 배우더라도, 전체를 이해하게끔 도와주는 것이 중요하다. 하나만 배우더라도, 나머지는 스스로 학습하고 싶게끔 만드는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 교사의 역할을 도출할 수 있다. 바로 핵심과 큰 그림을 짚어주고 경험하게 만드는 역할이다. 처음 배우는 사람은 수평적으로 나열된 지식 속에서, 무엇이 핵심인지 파악하기 어렵다. 방대하고, 잘 연결된 지식을 가지고 있는 교사만이 핵심을 짚어줄 수 있다. 강의도 그렇게 구성되어야 한다. 입문서도 그렇게 쓰여져야 한다. 심지어 AI 교사도 이런 프롬프트 하에 동작해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 코치로 활동 중인 우아한테크코스 조직은, 이런 교육을 지향한다. 10개월 과정 중, 5개월만 강의를 진행하고, 그 중에서도 주에 2회, 총 3시간밖에 강의하지 않는다. 학습은 스스로 하도록 유도하며, 코치는 핵심을 짚어주고, 빠르게 경험시키는 것에 집중한다. 전통적 교육과 다르게 모든 내용을 가르치지 않고, 진도 빼는 것에 혈안되지 않는다. 이런 환경에서는, 코치는 많은 강의를 할 필요가 없으니 강의 하나하나의 퀄리티가 높아지고, 핵심만을 가르치는데 집중할 수 있다. 학생 입장에서도 지겨운 강의를 매일같이 듣기 보다, 학습하고 싶은 것을 학습하면 되기 때문에, 스스로 학습하는 역량을 기를 수 있다. 강의는 가뭄의 비처럼 학생들이 강의를 필요로 할 때쯤 찾아온다. 그러니 수업에 집중할 수밖에 없고, 코치와 학생 모두 만족스러운 강의가 된다. 이렇게 부분을 통해 전체를 가르치는 방법의 이점을 많이 느끼고 있다. 앞으로도 지식을 알려줄 때 적극적으로 활용해볼 것이다.&lt;/p&gt;</description>
      <category>Education</category>
      <category>교육</category>
      <category>우아한테크코스</category>
      <category>우테코</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/89</guid>
      <comments>https://happysisyphe.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 5 Aug 2025 22:39:13 +0900</pubDate>
    </item>
    <item>
      <title>실행과 그릿에 대한 찬가</title>
      <link>https://happysisyphe.tistory.com/88</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;실행과 그릿에 대한 찬가-2.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pfqhK/btsPgwBFcqw/cS29GNfvKLkQurJv0BJjek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pfqhK/btsPgwBFcqw/cS29GNfvKLkQurJv0BJjek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pfqhK/btsPgwBFcqw/cS29GNfvKLkQurJv0BJjek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpfqhK%2FbtsPgwBFcqw%2FcS29GNfvKLkQurJv0BJjek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;708&quot; data-filename=&quot;실행과 그릿에 대한 찬가-2.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 경외감을 느끼는 부류의 사람이 있다. 바로 삶의 그릇이 큰 사람이다. 창업하고, 해외로 유학을 가고, 책을 출판하는 등, 새로운 도전을 하는 사람에게 그런 감정을 느꼈다. 그 이후로 그릇이 크다는 것이 대체 무엇인지 몇 개월간 생각해 왔다. 이제야 실마리가 잡혔다. 내가 내린 결론은 두 가지이다. 실행력과 그릿(Grit)이다. 풀어쓰자면, 일단 행동하고 그것을 끝까지 하는 사람이 그릇이 큰 사람이다. 이에 대해 몇 가지 오해한 바를 밝혀보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 실행력에 대한 오해를 해체해 보자. 나는 어떤 일이든 사전에 계획하는 것을 좋아했다. 즉흥적인 상황에 놓이는 것보다 준비된 것을 할 때, 내가 빛을 발한다고 생각했다. 이게 틀렸다는 것은 아니지만, 나는 종종 그것을 핑계로 삼아, 실행보다는 준비만을 해왔던 것 같다. 준비된 상황에 대한 갈망은 곧 실패에 대한 두려움이었다. 이게 내가 실행을 늦추거나, 아예 못 하도록 막았다. 사전에 모든 계획이 서 있을 수는 없다. 행동을 한 단계 해나감에 따라, 정보는 늘어나고, 판단이 바뀔 수 있다. 즉, 계획을 세우는 것보다 우월한 전략은 유연하게 대응하는 것이다. 현재 가장 옳아 보이는 선택을 해내고, 그 이후는 정보가 더 많을 미래의 나에게 위임하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 한계점을 깨닫고, 나는 이제 실행을 찬양하련다. 계획보다는 지금 눈앞에 있는 가장 중요한 1가지 행동에 집중할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 그릿에 대한 오해를 해체해 보자. 그릿은 &amp;ldquo;끈기와 열정으로 끝까지 해내는 힘&amp;rdquo;을 말한다. 책 그릿에 나오는 내용이 있다. &amp;ldquo;그릿의 전형인 사람들도 목표들을 포기한다.&amp;rdquo;, &amp;ldquo;푹 빠질 수 있는 종목을 찾을 때까지 여기저기 기웃거렸던 것 같아요.&amp;rdquo; 이 구절은, 그릿이 높은 사람도 무작정 하나를 끝까지 고집하지는 않는다는 말이다. 상위의 목표를 더 효율적으로 달성하는 하위 목표가 생기면 그것으로 이행하는 게 당연하다는 말이다. 이 말이 나에게 큰 위로가 되었다. 나는 크고 추상적인 목표만을 추구하고, 하위 목표는 자주 옮기는 사람이었다. 그렇게 확증편향을 더 강화하게 된 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 목표를 가지고 시작했다가, 더 이상 최선의 목표가 아니게 되면, 나는 쉽게 그만두고 다른 것을 시작했다. 그게 합리적이라고 생각했다. 하지만 몇 년이 지난 지금 과거를 돌아보았을 때, 우직하게 하나에 집중하지 못했다는 생각이 들었다. 커리어에 대한 이야기를 막론하고도, 보컬을 배웠다가, 힙합을 배웠다가, 과학을 공부했다가, AI를 공부했다가, 마구 실행하고 목표가 변함에 따라 마구 옮겨 다녔다. 그때마다 나름의 이유가 있었지만, 돌아보면 나에게 남은 것이 많이 없었다. 시도해 봤다는 의미뿐, 나는 그쪽의 준전문가도 되지 못했다. 어떤 깊이 있는 의미도 남기지 않았다. 또한 하나를 끝까지 집중할 수 있는 힘, 그 모멘텀이 생기지 않았기 때문에 다음의 태스크를 할 때도 쉽게 그만둬버리는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 실행력과 그릿에 대한 오해가 점점 나의 그릇을 좁히고 있다는 것을 깨달았다. 오래 고민하고 신중하게 실행하고도, 쉽사리 그만둬버린 일이 잦았다. 이상적으로 가야 할 방향은 쉽게 실행하고, 오랫동안 밀고 나가는 것이다. 나는 더 많은 도전을 하고 싶고, 더 큰 그릇을 가진 사람이 되고 싶다. 나의 본성이 그러한지라, 단번에 행동을 바꿀 수는 없을 것이다. 의사결정의 순간마다 한 줌씩 더 신경 써서 의식적으로 선택할 수 있다. 그리고 그 선택이 성공 경험으로 이어지는 순간이 쌓이면, 자연스레 의사결정 체계가 바뀔 것이라고 믿는다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/88</guid>
      <comments>https://happysisyphe.tistory.com/88#entry88comment</comments>
      <pubDate>Sat, 12 Jul 2025 11:23:03 +0900</pubDate>
    </item>
    <item>
      <title>React OOP #2, 접근제어와 전역상태</title>
      <link>https://happysisyphe.tistory.com/87</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. React OOP 시리즈 2번째 글을 작성합니다. 이번 글은 OOP 에서 중요한 개념인 접근제어(access control)에 대해서 알아보고, 이를 기반으로 전역상태 관리 라이브러리의 인터페이스를 분석하고, 사용 방향을 제안하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OOP 에서 접근제어(access control)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 왜 설계를 잘 하려고 할까요? 설계를 하는 이유는, 변경을 관리하기 위해서 입니다. 한번 쓰고 없어질 프로젝트라면, 객체지향이니, 함수형이니, 신경쓰지 않고, 동작하는 코드를 짜면 그만이겠죠. 그러나 유지보수 비용을 낮추기 위해 설계가 필요하며, 그 중 대표적인 패러다임으로 객체지향 프로그래밍이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP 에서 변경을 제어하는 대표적인 방법론이 접근 제어(access control) 입니다. 언어 레벨에서 private, public, protected 가 있습니다. 외부로 노출되어서는 안되는 요소를 private 메서드로 처리함으로써, 구현을 숨기고, 외부로 interface 만을 노출시킴으로써, 변경을 관리합니다. 이는 또 다른 원칙인 캡슐화, interface 설계 원칙, 은닉 등과 연결되지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private 과 public 을 사용하는 예시 코드는 다음과 같아요.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class User {
    #password;

    constructor(name, password) {
        this.name = name;
        this.#password = password;
    }

    // ✅ public 메서드
    login(inputPassword) {
        if (this.#checkPassword(inputPassword)) {
            console.log(`Welcome, ${this.name}!`);
        } else {
            console.log(&quot;Invalid password.&quot;);
        }
    }

    // ❌ private 메서드
    #checkPassword(inputPassword) {
        return this.#password === inputPassword;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript class 에서는 # 을 사용해서 private field/method 를 정의할 수 있습니다. TypeScript 에서는 private, public, protected 를 명시적으로 사용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JS 에서의 접근 제어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript 는 기본적으로 class 기반 언어가 아니죠. 상태와 행동을 클래스 뿐만 아니라, 함수, 변수로 관리할 수 있습니다. 오히려 함수가 익숙하죠. 함수를 사용하면, 어떻게 접근을 제어할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2가지 방법을 알아보겠습니다. 첫 번째는 closure 를 사용한 은닉이고, 두 번째는 module system 을 활용한 은닉입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;closure 를 활용한 접근 제어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 ES6 가 나오기 전 많이 쓰이던 방법입니다. 위의 class 코드를 closure 로 바꾸면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function createUser(name, password) {
    let _password = password;  

    // ❌ private 함수
    function checkPassword(inputPassword) {  
        return _password === inputPassword;
    }

    return {
        name,
        // ✅ public 메서드
        login(inputPassword) {
            if (checkPassword(inputPassword)) {
                console.log(`Welcome, ${name}!`);
            } else {
                console.log(&quot;Invalid password.&quot;);
            }
        }
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;closure 를 활용하면, 변수를 함수 안으로 은닉하고, 외부로 노출하지 않을 수 있습니다. 즉 private 과 같은 접근 제어 효과를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;module system 을 활용한 접근 제어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6 이후로, 은닉을 위한 closure 는 직접적으로 활용되지 않는데요. 이는 ES6에 모듈 시스템이 도입되었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 closure 코드를 module system 으로 표현하면 도움과 같아요.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const _users = new Map();

// ❌ private 함수
function checkPassword(name, inputPassword) {  
    return _users.get(name) === inputPassword;
}

// ✅ public 함수
export function createUser(name, password) {
    _users.set(name, password);
}

// ✅ public 함수
export function login(name, inputPassword) {
    if (checkPassword(name, inputPassword)) {
        console.log(`Welcome, ${name}!`);
    } else {
        console.log(&quot;Invalid password.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에 공개할 인터페이스만 export 하고, 나머지는 export 하지 않음으로써 자동으로 private, 은닉 처리합니다. React 개발자에겐 이러한 패턴이 더 익숙하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;jotai 인터페이스 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 설명한 접근제어를 기반으로, 전역상태 관리 라이브러리인 jotai 와 zustand 를 분석해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jotai 는 매우 간편한 인터페이스를 가집니다. atom 선언만 해주면, 어디서든 useState 와 동일한 인터페이스로 사용할 수 있는 것이 큰 장점입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// atom.js
export const countAtom = atom(0);

// Counter.js
import { countAtom } from &quot;./atom&quot;;

function Counter() {
  const [count, setCount] = useAtom(countAtom);

  const increaseCount = () =&amp;gt; {
    setCount((prev) =&amp;gt; prev + 1);
  };

  const decreaseCount = () =&amp;gt; {
    setCount((prev) =&amp;gt; prev + 1);
  };

  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 위 방식이 객체지향 원칙으로 보면 적절하지 않습니다. &amp;ldquo;변경이 관리되지 않기 때문&amp;rdquo;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;atom 을 어디서든 가져다 쓸 수 있고, 어디서든, 어떻게든 수정 가능 합니다. 0 이하의 값으로 수정해버릴 수도 있고, 1단위씩 증감시키는 제약도 걸지 못 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 제어를 통해 이 코드를 개선해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 활용한 접근제어, 모듈 시스템을 활용한 접근제어, 두가지 방식으로 개선해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;module system 을 활용한 접근 제어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 간단하게 모듈을 활용한 접근제어부터 알아봅니다. countAtom 자체를 export 하지 않고, increase, decrease 로직으로 캡슐화하여, 변경을 관리합니다. 더하여, 로직을 응집시켜서, 변경 사항을 추적하기 쉽다는 장점도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Counter.js 에서는 count 를 마음대로 수정할 수 없으며, atom.js 에서 캡슐화하여 public 으로 export 한 메서드를 호출해야만 수정 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// atom.js
const countAtom = atom(0);

export function useCountAtom() {
  const [count, setCount] = useAtom(countAtom);

  const increaseCount = useCallback(() =&amp;gt; {
    setCount((prev) =&amp;gt; prev + 1);
  }, []);

  const decreaseCount = useCallback(() =&amp;gt; {
    setCount((prev) =&amp;gt; prev - 1);
  }, []);

  return {
    count,
    increaseCount,
    decreaseCount,
  };
}

// Counter.js
import { useCountAtom } from './atom';

function Counter() {
  const { count, increaseCount, decreaseCount } = useCountAtom();

  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;closure 를 활용한 atom 접근제어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;closure 를 사용하여, atom 을 완전히 은닉할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setter 를 활용하여, 변경 범위를 제한시키고, 그 메서드만을 return 하는 함수를 만들어, 외부에서 atom 을 직접적으로 변경할 수 없도록 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// atom.js
function createCountAtom() {
  const countAtom = atom(0);

  const increaseSetAtom = atom(null, (get, set) =&amp;gt;
    set(countAtom, get(countAtom) + 1)
  );
  const decreaseSetAtom = atom(null, (get, set) =&amp;gt;
    set(countAtom, get(countAtom) - 1)
  );

  const countAtomValue = atom((get) =&amp;gt; get(countAtom));

  return {
    countAtomValue,
    increaseSetAtom,
    decreaseSetAtom,
  };
}

export const { countAtomValue, increaseSetAtom, decreaseSetAtom } =
  createCountAtom();

// Counter.js
import { countAtomValue, increaseSetAtom, decreaseSetAtom } from &quot;./atom&quot;;

function Counter() {
  const count = useAtomValue(countAtomValue);
  const increaseCount = useSetAtom(increaseSetAtom);
  const decreaseCount = useSetAtom(decreaseSetAtom);

  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 코드는 보일러 플레이트가 많고, 번거로워서 DX 가 떨어져 추천하지 않습니다. 코드를 변경하는 주체는 결국 인간이기 때문에, 불필요한 추상화는 다시 변경을 어렵게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 jotai 인터페이스 자체가 캡슐화를 지원하지는 않으므로, 안전하게 변경 가능하도록 하고, 로직을 응집시키기 위해서는 추가적인 노력을 해주는 편이 좋을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;zustand 인터페이스 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 zustand 가 무섭게 성장하고 있습니다. 여러 전역상태 라이브러리 중 download 수가 1위를 달리고 있죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;zustand-jotai.png&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CCkB5/btsMNbT2Uaj/yNj6MCioRciRPLJp5IUFAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CCkB5/btsMNbT2Uaj/yNj6MCioRciRPLJp5IUFAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CCkB5/btsMNbT2Uaj/yNj6MCioRciRPLJp5IUFAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCCkB5%2FbtsMNbT2Uaj%2FyNj6MCioRciRPLJp5IUFAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1222&quot; height=&quot;480&quot; data-filename=&quot;zustand-jotai.png&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zustand 의 인터페이스를 분석해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zustand 는 기본적으로 setter 자체가 노출되지 않고, 메서드를 노출시킵니다. jotai 와 비교해서, 상태 변경을 더 잘 관리할 수 있는 형태로 설계되었습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// store.js
import { create } from 'zustand';

export const useCountStore = create((set) =&amp;gt; ({
  count: 0,
  increaseCount: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count + 1 })),
  decreaseCount: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count - 1 })),
}));

// Counter.js
import { useCountStore } from './store';

function Counter() {
  const { count, increaseCount, decreaseCount } = useCountStore();

  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 redux 를 생각나게 합니다. reducer 로 명시적인 aciton 을 정의하고, dispatch 를 통해 상태를 업데이트 하는 방식이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zustand 에 reducer 와 action 을 도입하여, redux 스럽게 사용할 수도 있습니다. 사용처에서 dispatch 만을 가지고, 선언적으로 상태를 변경시킬 수 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// store.js
import { create } from 'zustand';

// action 정의
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';

// reducer 함수 정의
const reducer = (state, action) =&amp;gt; {
  switch (action.type) {
    case INCREASE:
      return { count: state.count + 1 };
    case DECREASE:
      return { count: state.count - 1 };
    default:
      return state;
  }
};

export const useCountStore = create((set) =&amp;gt; ({
  count: 0,
  dispatch: (action) =&amp;gt; set((state) =&amp;gt; reducer(state, action)),
}));

// Counter.js
import { useCountStore } from './store';

function Counter() {
  const { count, dispatch } = useCountStore();

  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 객체지향에서 중요한 접근제어 키워드를 바탕으로, 전역상태 라이브러리의 인터페이스를 분석하고, 더 나은 방향을 제안해보았습니다. 대표적인 라이브러리인 jotai 와 zustand 를 분석했고, 인터페이스 설계 측면에서는 zustand 가 더 안전하게 변경을 관리하도록 합니다. 그렇다고 하여, jotai 의 인터페이스를 비난할 수만은 없습니다. 사용처에서 별도로 로직을 응집시켜 변경을 제한시킬 수 있습니다. 라이브러리는 간편한 인터페이스를 제공하는 것만으로 역할을 다 한 것이며, 변경 관리의 책임은 개발자에게 있는 것입니다. 보다 안전하고, 유지보수 하기 좋은 코드를 짜는 데 도움이 되었으면 좋겠습니다.&lt;/p&gt;</description>
      <category>Tech/Clean Code</category>
      <category>Jotai</category>
      <category>OOP</category>
      <category>react</category>
      <category>zustand</category>
      <category>객체지향</category>
      <category>전역상태</category>
      <category>접근제어</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/87</guid>
      <comments>https://happysisyphe.tistory.com/87#entry87comment</comments>
      <pubDate>Sun, 16 Mar 2025 22:33:08 +0900</pubDate>
    </item>
    <item>
      <title>[광활한 세상에서 방황하는 이들에게] 브런치북 발행</title>
      <link>https://happysisyphe.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;안녕하세요.&lt;br&gt;행복한 시지프 입니다.&lt;br&gt;&lt;br&gt;10편의 글을 담아서, 브런치북을 발행했어요.&lt;br&gt;자아를 찾아 방황하는 이들을 위해 썼습니다. 저의 지난 세월과, 철학을 녹여보았어요.&lt;br&gt;삶의 고민을 진전시키는 데 도움이 될거에요.&lt;br&gt;&lt;br&gt;한번 읽어보시고, 감상을 남겨주시면 감사하겠습니다&lt;br&gt;궁금한 점 댓글로 달아주시면 답변 드릴게요. &lt;br&gt;&lt;br&gt;&lt;a href=&quot;https://brunch.co.kr/brunchbook/wardering-20s&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://brunch.co.kr/brunchbook/wardering-20s&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[브런치북] 광활한 세상에서 방황하는 이들에게&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;29살에 대학을 졸업합니다. 그동안 경제학, 소프트웨어 공학, 교육, 철학 등 다양한 학문을 공부하고, 업무해왔어요. 치열하게 자아를 탐구해왔어요. 그 과정을 소개드리며, 방황하는 학생들에게&quot; data-og-host=&quot;brunch.co.kr&quot; data-og-source-url=&quot;https://brunch.co.kr/brunchbook/wardering-20s&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQL4UT/hyYfQdOYFg/nrS4upDqeN52O3V9vkHCtK/img.jpg?width=1620&amp;amp;height=1080&amp;amp;face=0_0_1620_1080&quot; data-og-url=&quot;https://brunch.co.kr/brunchbook/wardering-20s&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/brunchbook/wardering-20s&quot; target=&quot;_blank&quot; data-source-url=&quot;https://brunch.co.kr/brunchbook/wardering-20s&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQL4UT/hyYfQdOYFg/nrS4upDqeN52O3V9vkHCtK/img.jpg?width=1620&amp;amp;height=1080&amp;amp;face=0_0_1620_1080')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;[브런치북] 광활한 세상에서 방황하는 이들에게&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;29살에 대학을 졸업합니다. 그동안 경제학, 소프트웨어 공학, 교육, 철학 등 다양한 학문을 공부하고, 업무해왔어요. 치열하게 자아를 탐구해왔어요. 그 과정을 소개드리며, 방황하는 학생들에게&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;brunch.co.kr&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/86</guid>
      <comments>https://happysisyphe.tistory.com/86#entry86comment</comments>
      <pubDate>Mon, 17 Feb 2025 20:24:41 +0900</pubDate>
    </item>
    <item>
      <title>React OOP #1, setState props 지양하기</title>
      <link>https://happysisyphe.tistory.com/85</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. &lt;b&gt;React OOP&lt;/b&gt;(Object-Oriented Programming) 시리즈 1번째 글을 씁니다. React 에 객체지향 원칙을 적용하는 과정을 쓸 것입니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;객체지향은 객체들간의 역할, 책임, 협력을 잘 명시하여 유지보수를 높이는 방법론입니다.&lt;span&gt; React 에서 함수 컴포넌트도 객체이고, 여러 도메인 로직도 모두 객체일 것입니다. 객체지향 프로그래밍 원칙을 지키면서, 각각의 컴포넌트가 어떤 역할과 책임을 갖고, 상호간에 어떻게 상호작용 할지 고민해보려고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 React 로 OOP 원칙을 지키는 것에 익숙해지기 위해서, 익히 알려진 Anti-Pattern 에 대해 말하고자 합니다. 바로 &lt;b&gt;&amp;ldquo;setState props 를 지양하기&amp;rdquo;&lt;/b&gt; 입니다. OOP 에서 setter 를 노출시키는 것이 지양되는 이유를 먼저 알아보고, 이를 React 측면에서 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OOP 에서 setter 를 노출시키는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP 에서는 setter 를 노출시키는 것은 일반적으로 지양됩니다. setter 를 노출시킨다는 것이 어떤 것인지 코드로 알아볼게요.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;class Todo {
  constructor(title) {
    this.title = title;
  }

  setTitle(newTitle) {
    this.title = newTitle;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 Todo 객체 외부에서 title 를 자유롭게 수정할 수 있습니다. title 이 어떤 식으로 들어와야 할지, validation 이 되지 않습니다. 더하여, Todo 를 쓰는 곳에서 어떤 식으로 update 하고 있을지 예측이 불가능 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 setter를 그대로 사용하는 것이 아니라, 함수명과 함수의 역할을 명시하고, 제한적으로 열어주는 것이 적절합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;class Todo {
  #title;
	
  constructor(title) {
    this.#title = title;
  }

  rename(newTitle) {
    if (typeof newTitle !== &quot;string&quot; || newTitle.length &amp;lt; 2) {
      throw new Error(&quot;할 일 제목은 2글자 이상이어야 합니다.&quot;);
    }
    this.#title = newTitle;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTitle 이 아니라, rename 이라는 이름으로 title 변경의 역할을 제한했습니다. 더하여, validation 의 책임을 Todo class 에 넣었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 담긴 2가지 객체지향 원칙을 이야기해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 캡슐화의 원칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캡슐화란 객체 내부 상태를 외부에서 직접 접근하지 못 하도록 보호하는 개념입니다.&lt;/b&gt; 객체 field 를 private 처리하고, public method 를 통해서만 변경할 수 있도록 하는 것을 추구합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부에서 발생할 무분별한 변경을 차단하고, 예측한 방식으로만 변경되도록 합니다.&lt;/b&gt; setter 자체를 외부에 열어둔다면, 내부 상태가 예측 불가능하게 변경될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rename 이라는 메소드로 수정함으로써, title 을 은닉하고, 예측가능하게 변경됨을 보장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 책임의 분리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 가진 객체가 상태의 변경을 관리할 책임을 가집니다. 무분별하게 상태가 변경되지 않도록 제한해주는 역할도 담당해야겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 변경할 setter를 외부에 열어주고, 외부에서 제어권을 가지고 변경하는 것은 책임이 분산된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rename 이 validation 의 역할까지 포함함으로써, 제어 권한을 Todo가 갖고, 제한된 방식으로만 변경되도록 외부에 열어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;React 에서 setState 를 노출시키는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP 에서 왜 setter 를 노출시키는 것을 지양하는지 설명하였습니다. 이 똑같은 원칙이 React 에 적용됩니다. 익히 아는 &lt;b&gt;&amp;ldquo;setState props 지양하기&amp;rdquo;&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState 를 props 로 넘기는 예제를 살펴보겠습니다. 똑같이 title 을 변경하는 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

function TodoApp() {
  const [todos, setTodos] = useState([{ id: 1, title: &quot;React OOP #1 글쓰기&quot;, completed: false }]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;
      {todos.map((todo) =&amp;gt; (
        &amp;lt;TodoItem key={todo.id} todo={todo} setTodos={setTodos} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}

function TodoItem({ todo, setTodos }) {
  const handleRename = () =&amp;gt; {
    const newTitle = prompt(&quot;새로운 제목을 입력하세요:&quot;, todo.title);
    if (newTitle !== null) {
      setTodos((prevTodos) =&amp;gt;
        prevTodos.map((t) =&amp;gt; (t.id === todo.id ? { ...t, title: newTitle } : t))
      );
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;span&amp;gt;{todo.title}&amp;lt;/span&amp;gt;
      &amp;lt;button onClick={handleRename}&amp;gt;이름 변경&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default TodoApp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 상태는 TodoApp 에서 관리하지만, 직접적인 제어는 TodoItem 에서 하고 있습니다. setTodos 를 넘겨줌으로써 상태 제어권을 넘겨주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 캡슐화의 원칙, 책임의 분리 측면에서 이 코드를 비판해보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 캡슐화의 원칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 todos 상태 변경이 자유롭게 이루어집니다. 변경에 제한이 없으므로, 어떤 식으로든 변경이 가능합니다. 특정 todo 를 삭제해버릴 수 있습니다. completed 를 바꿔버릴 수 있습니다. title 을 어떤 식으로든 변경시킬 수 있습니다. 그러므로 예측하지 못한 udpate 가 발생할 수 있고, 유지보수가 어려워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 책임의 분리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 가진 TodoApp 에서 상태 update 로직이 있지 않습니다. 상태를 관리할 책임을 가진 TodoApp 이 아닌, TodoItem 이 상태 변경 로직을 가지고 있습니다. 이는 잘못된 책임의 분리 입니다. TodoItem 이 CompletedTodoItem, InCompletedTodoItem 으로 분리된다면, 로직이 파편화되겠죠. 상태를 가진 쪽에서 제어 책임까지 가지는 것이 적절합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 기반으로 코드를 수정해봅시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

function TodoApp() {
  const [todos, setTodos] = useState([{ id: 1, title: &quot;React OOP #1 글쓰기&quot;, completed: false }]);

  const renameTodo = (id, newTitle) =&amp;gt; {
    if (typeof newTitle !== &quot;string&quot; || newTitle.trim().length &amp;lt; 2) {
      alert(&quot;할 일 제목은 최소 2글자 이상이어야 합니다.&quot;);
      return;
    }
    setTodos((prevTodos) =&amp;gt;
      prevTodos.map((todo) =&amp;gt; (todo.id === id ? { ...todo, title: newTitle } : todo))
    );
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;
      {todos.map((todo) =&amp;gt; (
        &amp;lt;TodoItem key={todo.id} todo={todo} renameTodo={renameTodo} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}

function TodoItem({ todo, renameTodo }) {
  const handleRename = () =&amp;gt; {
    const newTitle = prompt(&quot;새로운 제목을 입력하세요:&quot;, todo.title);
    if (newTitle !== null) {
      renameTodo(todo.id, newTitle);
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;span&amp;gt;{todo.title}&amp;lt;/span&amp;gt;
      &amp;lt;button onClick={handleRename}&amp;gt;이름 변경&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default TodoApp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TodoApp 에서 renameTodo 를 정의함으로써, 상태를 캡슐화하고, 책임을 명확히 하였습니다. props 를 setTodos 가 아니라, renameTodo 을 넘겨줌으로써, 권한을 제한시켰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 TodoApp은 name 을 수정하는 기능만 열어두고 있으므로, 명확히 제어됩니다. 외부에서 잘못된 update 가 발생할 우려가 없습니다. 결국 더 예측 가능하고, 유지보수 하기 쉬운 코드가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자가 어색한 개념인 OOP 와, 익숙한 &amp;ldquo;setState props 지양하기&amp;rdquo; 원칙을 엮어서 설명하였습니다. 간단한 원칙이지만, 이것이 어떤 상위 원칙에 기반하는가 설명하는 것이 저의 목표였습니다. 이런 식으로 React 를 클린하게 작성하는 원칙 다수가 객체지향 같은 소프트웨어 원칙에서 유래합니다. 앞으로도 어떻게 OOP 원칙을 React 에 적용할 수 있을지 더 작성해 볼 계획입니다. 객체지향에 대한 오해를 풀고, 더 예측 가능하고 유지보수 하기 좋은 코드를 짤 수 있길 기대합니다.&lt;/p&gt;</description>
      <category>Tech/Clean Code</category>
      <category>OOP</category>
      <category>props</category>
      <category>react</category>
      <category>setState</category>
      <category>객체지향</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/85</guid>
      <comments>https://happysisyphe.tistory.com/85#entry85comment</comments>
      <pubDate>Sun, 16 Feb 2025 14:10:51 +0900</pubDate>
    </item>
    <item>
      <title>2024년 독서 회고</title>
      <link>https://happysisyphe.tistory.com/84</link>
      <description>&lt;h3 id=&quot;SE-16df63c2-6f06-4c28-9e99-5449c65554c9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;총 11권&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이기적 유전자, 리차드 도킨스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데일 카네기 인간 관계론&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;챗GPT가 쏘아올린 신직업 프롬프트 엔지니어, 서승완&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역행자, 자청&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;운과 실력의 성공 방정식, 마이클 모부신&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;타이탄의 도구들, 팀 페리스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;부의 추월차선, 엠제이 드마코&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;욕망의 진화, 데이비드 버스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나로 존재하는 법, 헤르만 헤세&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데미안, 헤르만 헤세&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불안, 알랭 드 보통&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p id=&quot;SE-896aafda-c3cb-4afb-b4c1-d44d694e2537&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-10228846-fa05-42d8-91b5-5738a63a81c6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;24년에 총 11권을 읽었다. 출퇴근길에만 읽은 것을 감안하면, 괜찮은 성과라고 생각한다. 그렇지만, 24년에 독서가 나에게 가져다 준 파급력을 생각해보았을 때, 더 읽었으면 좋았겠다 싶다. 책이 가장 성장하기 좋은 도구임을 다시금 느꼈다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-3e82e5bc-b18f-4c14-a35e-49f1f6055e31&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-e0c20ddd-ca48-4654-8004-3bd269849ca4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-21b1d235-30d6-4aea-842a-bf4e8f42cdc4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;올해의 책 : 이기적 유전자&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-33a2a752-fb4d-44c2-ad20-99c9033cc1a8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;24년의 모든 고민은 진화론/유전자론과 함께 했다고 해도 과언이 아니다. 세상을 이해하는 인식의 패러다임이 바뀌었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-4cb70e48-ead0-4b81-86df-290a564a2199&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-14fcad62-2dd3-441a-bd6c-06e166fabf87&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 왜 진화론에 관심을 가지게 된 것은, 나에 대해 한 단계 더 깊은 질문을 하면서부터 였다. 이전의 나는 &amp;ldquo;내가 가장 중요시 하는 가치는 선한 영향력, 관계, 자아실현이다.&amp;rdquo; 에 머물렀다. 그러다가, 내가 대체 왜 선한 영향력을 중시하는지 한 단계 더 깊은 질문을 하게 되었다. 그러다보니 시선을 외부로 돌릴 수밖에 없었다. 나를 넘어서 인간은 왜 선한 영향력을 추구하는가. 이를 설명하기 위해서, 진화론에 관심을 가지게 되었고, 이기적 유전자를 읽게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f93edb7b-1cf2-4c83-86a4-36c2284c83d1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-9e97c8ae-6962-4dbe-b9b2-43553c36b165&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 나의 시야는, 나 &amp;rarr; 인간 &amp;rarr; 유전자 로 확장되었다. &amp;ldquo;나는&amp;rdquo;으로 시작하는 질문이 &amp;ldquo;인간은&amp;rdquo;, &amp;ldquo;유전자는&amp;rdquo;으로 바뀌었다. 나의 문제를 더 이상 사밀한 문제로 치부하지 않는다. 나는 유전자로 이루어진 기계임을 받아들였다. 모든 인간, 식물, 동물도 마찬가지이다. 이러한 인식의 확장에 이기적 유전자가 큰 도움을 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1b0b2ba8-49e9-4858-952e-3fb52d36d2a3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-4e6de556-498b-44b9-949d-19c3ab587d6b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 결론이 매우 슬프게 들릴 수도 있다. 리차드 도킨스 교수에게도 여러 사람들이 불만을 표출했다고 한다. 이런 것을 대체 왜 알려주었느냐고. 유전자는 단지 유전자를 잘 전달하기 위한 이기적인 목적으로 살아가고 있을 뿐인데, 인간이라는 개체가 만들어졌고, 불행히도 인간이라는 개체는 의식을 가지게 되었다. 아무 생각 없이 해대는 유전자의 번식에도, 인간은 여러 의미를 부여한다. 삶을 살아가는 고상한 의미를 부여하려고 한다. 하지만 유전자 기계론을 받아들이면, 주어진 의미는 아무 것도 없다. 이것들을 어떻게 양립하며 살아갈 것인가 하는 문제는 나에게 아직 숙제이고, 명확히 내린 답은 없다. 일단은 나는 빨간약을 먹고, 세상을 알게 되었음에 큰 기쁨을 느낀다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f39ef1de-e4ec-402c-992b-e95f742bbf01&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-124fbebe-8e38-43fb-af25-939ab77146a5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;진화론을 이해하며, 철학을 더 심도깊게 이해할 수 있었다고 생각한다. 리처드 도킨스는 이렇게 비판한다. &amp;ldquo;철학과 인문학 분야에서는 아직도 다윈이 존재한 적조차 없었던 것처럼 가르친다.&amp;rdquo; 내가 이전에 공부해온 방향들이 이 비판점과 크게 다르지 않았다고 느꼈다. 인간의 고상한 의미를 놓지 못했다. 현 시대부로, 과학 없는 철학은 의미가 없다고 생각한다. 이런 것을 받아들이며, 나의 철학하는 자세를 다시 돌아보게 되었다. 그리고 철학 공부를 시작하니, 아이러니하게 더 재밌었다. 과학과 철학이 하려고 했던 것이 다르지 않음을 느꼈다. 결국 세상에 대해 이해하고 싶었던 것이다. 세상의 본질은 무엇인가, 탈레스는 물이라 하였고, 라이프니츠는 모나드라 하였다. 현대 과학은 유전자라 말하고 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-52c7009f-5916-4a64-8a0e-f24182b85ed3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-0ca6e7a1-d81c-4ef2-8af3-9ec9ae908884&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-a180b2fc-0fc6-41cc-88d7-ad0c6a46abda&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;독해력 향상&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-491286c8-9cfb-4ee3-a681-ef295ec5ab49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불안 을 읽으며 내 독해력이 향상됨을 가시적으로 느낄 수 있었다. 22살의 나는 군대에서 불안을 읽으려고 시도했다. 그땐 책이 너무 어려웠다. 그 주제가 나에게 맞아떨어진다고 느끼지도 않았고, 표현 방식도 꽤 어려웠던 것으로 기억한다. 그래서 조금 읽다가 포기했었다. 그리고 이번에 다시 불안 책을 들었는데, 너무 쉬운 것이 아닌가. 그동안 많이 쌓였던 경험에 의해서, 알랭 드 보통이 묘사하는 다양한 감정을 이해할 수 있었다. 또한, 그간 철학적 토대가 많이 쌓여서 어려운 텍스트를 이해하는 역량도 성장했다고 느낀다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-45e45a12-18c0-4621-90a8-118d327c8acb&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-2478017e-8ea8-4f3a-bd8f-010e517563c7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가시적으로 나의 독해력이 상승한 것을 느껴서 좋았다. 지금 철학 공부가 굉장히 어려운데, 일시적인 현상이고, 내가 지식을 쌓아가면 언젠가 정복할 수 있으리라 자신감을 가질 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-4f4cf67f-44e3-4b20-b880-4b71b7eb1667&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-1f1b6163-c33b-4b98-a9bf-5e11d304fd15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-67a0fde4-fed9-4b65-aa8d-a880d4852b7b&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;독서 습관&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-9f5ce0da-ae70-460f-ad68-fe863e7fc37a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;23년 12월에 &amp;ldquo;아주 작은 습관의 힘&amp;rdquo; 을 읽고, 습관을 형성하는 것에 큰 관심을 가지게 되었다. 어떻게 하면, 의식적으로 노력하지 않아도, 원하는 행동을 하게 만들 수 있는가 많이 고민했다. 독서를 더 열심히 하고 싶었는데, 시간은 부족한 상태였다. 이때 만든 습관이 출퇴근길 독서하는 것이다. 아이패드 미니를 구매했고, 출퇴근 버스를 타자마자 아이패드를 꺼내 독서하는 습관을 길렀다. 그래서 1달에 1권 정도의 독서를 해낼 수 있었다. 습관 측면에서 좋은 성과를 냈다고 &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-22e14a6b-d9b5-4467-93eb-45111f56351b&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-25524e94-c54b-4205-adaf-54707e24f2b2&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내년에는 바쁜 일이 줄어들 것으로 예상된다. 그래서 더 열심히 독서하고, 더 많이 성장해내고 싶다. 출퇴근 시간 이외에도 독서하는 시간을 할당할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-e04284ff-0826-40f4-b3c3-8d632e1a19f3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-d80af9f8-0068-498d-9cf6-3b520d245e37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;책을 읽은 후에, 정리하지 않았다는 점은 아쉽다. 이제부터 꼭 독후감까지 쓰고 마무리할 것이다. 무겁게 쓰려고 하지 않고, 가볍게라도 써서 기록해볼 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-626963e8-4ec8-4a7d-8fc1-d7da0e9cd472&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-b19c0ac1-0569-4675-8ad2-b4720dfd9864&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-180e8b8c-4fa5-4b49-83b8-21d7c39406c2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;25년 독서 계획&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-f417beef-5f92-4d74-84dd-93ec1c57672b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;올해도 다양한 책을 읽어보려고 한다. 주요 분야는 교육, 소프트웨어 공학, 철학이다. 프론트엔드 교육자로 전향하게 되었기 때문에, 교육과 소프트웨어 공학에 대한 지식과 지혜를 쌓아나갈 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-b99fa3a0-00cb-4d57-a734-6ba8af1be26d&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-77875200-8799-4971-a9fe-928f8104e11a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;더하여, 철학을 심도있게 공부해볼 것이다. 24년도에 학교를 다니며 철학 수업을 들었는데, 너무나 흥미로웠다. 비판적으로 문제를 바라보고, 해석할 수 있게 만들어주는 엄청난 도구임을 깨달았다. 철학 개론 서적과, 분석철학, 언어철학, 실존철학, 현상학 등 다양한 분야를 학습해보고 싶다. 사실 이는 독서보다 공부에 가까울 수 있다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-c782ff7a-5242-49c5-9f5d-734b7f8536ca&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-1f7daa32-69dc-47dd-ad96-4ea762fe33fa&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한, 관계, 심리, 스타트업, 진화론 등에 관한 책들도 읽어보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-7b2aa6d2-1d34-4195-af4e-8f8434a520f3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-01649490-4fac-4382-a1da-8c721f9d59bb&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;점점 관심 분야가 확장되고 있어서 좋으면서도 큰일이다. 세상에 배우고 싶은 것이 너무나 많다. 25년도에는 의식적으로 독서하는 시간을 더 많이 두어서, 읽고 싶었던 책을 많이 읽어내고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Writing/회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/84</guid>
      <comments>https://happysisyphe.tistory.com/84#entry84comment</comments>
      <pubDate>Fri, 3 Jan 2025 21:34:44 +0900</pubDate>
    </item>
    <item>
      <title>2024-2 Self Integrity Report</title>
      <link>https://happysisyphe.tistory.com/83</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Self Integrity Report 를 왜 쓰는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Integrity 는 &amp;ldquo;진실성&amp;rdquo; 이라는 의미로 번역된다. 즉, Self Integrity 라는 이름으로 내가 나에게 얼마나 진실한가? 얼마나 나다움을 실현하고 있는가? 에 대해 고민해보는 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 작성하는 이유는, 삶의 근본적인 부분을 고민하기 위함이다. 내가 의식하지 못 한 채 나이가 들어버리고, 과거를 후회하는 것을 방어해주는 장치이다. 이것도 회고의 일종이지만, 단순히 일정 시간동안 잘한 점/아쉬운 점/개선할 점을 분석하는 것이 아니다. 현재의 시간을 초월하여, 내가 현재 가진 직업과 생각, 모든 것을 초월하여, 근본부터 생각하고 나를 점검해가는 시간이다. 그래서 1.1배 나아지는 것이 아니라, 방향 자체를 바꾸어 5배 나아지는 것을 목표로 한다. 23년 하반기부터 반년에 한번씩 쓰고 있고, 이번이 3번째이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삶에서 가장 중요한 가치는 무엇인가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선한 영향력&lt;/li&gt;
&lt;li&gt;관계&lt;/li&gt;
&lt;li&gt;자아실현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23-2 Self Integrity Report 에서 창조, 선한 영향력, 사랑, 유희를 꼽았다. 이번엔 선한 영향력, 자아실현, 관계를 꼽았다. 내가 이전에 알던 것보다, 내가 선한 영향력을 더 추구함을 깨달았다. 그게 곧 나의 일의 의미임을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계 또한 삶에서 더욱 중요함을 깨달았다. 휴직을 하며 쉼을 가지다 보니, 여유롭게 지인을 만나고, 이야기를 나누는 순간에서 오는 유희가 매우 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자아실현은 별도 요소로 뺐다. 선한 영향력, 관계와 겹치는 부분이 많을 것이다. 내가 표현하고 싶었던 것은 이런 것이다. 지식에 대한 추구, 철학적 깨달음, 더 현명해지는 것. 나는 남들보다 학문을 즐긴다는 것을 깨달았다. 철학이든, 컴퓨터공학이든, 경제학이든 학문이 정말 재밌다. 그것을 기반으로 세상과 나를 더 잘 이해할 수 있다는 점이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 삶의 가치를 고민하며, &amp;ldquo;창조&amp;rdquo;를 제외하였다. 이전에 창조하는 삶을 높은 가치로 두었었는데, 이것이 나에게 핵심 키워드는 아님을 확실히 느끼게 되었다. 창조할 수 있더라도, 결과물이 의미가 없다면 나는 그것을 하고 싶지 않았다. 이를 받아들이기 쉽지 않았다. 지난(24-1) Self Integrity Report 부터 창조에 대해 석연치 않은 감정이 올라옴을 느꼈다. 하지만 그 사실을 부정했다. 나는 알베르 카뮈를 존경했고, 내가 제일 좋아하는 말이 &amp;ldquo;진흙으로 조각품을 만드는 것&amp;rdquo;이라는 말이었다. 결과물이 무(無)로 감을 알면서도, 창조해내는 일, 그것이야말로 과거에 연연하지 않고, 미래에 대해 집착하지 않으며 현재에 집중할 수 있는 일이라 하였다. 내 블로그에도 여러 번 등장하는 문구이다. 그래서 더욱이 버리기 어려웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이제는 &amp;ldquo;창조&amp;rdquo;가 나에게 핵심이 아님을 인정하려고 한다. 나는 창조하지 않아도, 자아실현 하며 선한 영향력을 미칠 수 있다면 그것으로 충분하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일을 하는 이유는 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일은 나에게 자아를 실현시키는 수단이다. 그 중 특히 선한 영향력을 주는 수단이다. 24년 한 해는 내가 추구하는 선한 영향력이 대체 무엇인지 많이 고민해보는 시간이었다. 선한 영향력에는 종류가 참 많다. 그것을 퉁 쳐서 선한 영향력이라고 부르기엔, 너무나 가지각색이다. 나는 이 언어의 의미를 제대로 구분해야 한다고 생각한다. 후에 다른 글로서 길게 써볼 생각이다. 오늘은 &amp;ldquo;효율성을 증대시키는 선한 영향력&amp;rdquo; 과, &amp;ldquo;삶의 방향을 바꾸는 선한 영향력&amp;rdquo;에 대해서만 짧게 얘기해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠팡, 토스, 네이버페이 등 대부분의 IT 서비스는 다수의 사람의 삶에서 효율성을 증대시키는 선한 영향력을 준다. 하루에 30분 해야할 일을, 5분만에 할 수 있도록 해주어, 시간과 돈을 아껴준다. 하지만 그것이 누군가의 삶을 바꿀 수 있는 것은 아니다. 시간과 돈을 아낀 것으로, 원래 하던 일을 그대로 하며 살아갈 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에, 교육, 예술, 상담 등의 일은 삶의 방향을 바꿔줄 수 있는 일이다. 하지만 그 대상은 많지 않다. 기존에는 생각조차 할 수 없었던 것을 깨닫게 되고, 앞으로 헤쳐나갈 힘을 주는 일들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 단순히 직업만으로 나누기는 힘들지만, - 상담에 열의를 쏟지 않아 삶의 방향을 바꾸지 못 하는 상담사, 같은 케이스는 얼마든지 상상할 수 있다 - 삶의 방향을 바꾸는 일을 해내기 어려운 일들이 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 25년부터 교육자로서의 삶을 살아보기로 하였다. 삶의 방향을 바꿀 수 있는 일을 진지하게 임해보면서 더 고민해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 선한 영향력을 구분하며, 객관적 우열이 있음을 말하고자 하는 것은 아님을 밝혀둔다. 사람마다 가치가 다를 것이다. 내가 추구하는 것은 후자일 뿐이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10년 후 어떤 사람이 되어 있으면 좋겠는가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선한 영향력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글, 강연, 교육, 유튜브, 창업 등 어떤 수단으로든 많은 사람의 삶을 긍정적으로 굴절시키고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자아실현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;철학적, 심리학적 깊이를 가지기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도전
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사업&lt;/li&gt;
&lt;li&gt;해외에서의 생활&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;진심으로 사랑하는 이들이 곁에 있었으면&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5배 더 나은 사람이 되기 위해서 무엇을 하면 좋을까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공부
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;철학, 심리학, 문학&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도전
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사업, 해외거주, 나와 나의 지식을 도전적으로 알리기(글, 유튜브, 강연 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 학습이 지혜를 가져다준다고 믿는다. 매몰비용을 학습한 이는 매몰비용 상황에서 더 현명하게 행동함이 실험으로 입증되었다. 이처럼 앞으로 철학(윤리학, 언어철학, 실존철학 등)과 심리학, 문학 등을 더 열심히 학습하며 인격적으로 더 나은 사람이 되고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삶에서 여러 경험을 해보는게, 자신을 성장시키는 데 엄청난 도움이 된다고 생각한다. 그 중 내가 해보고 싶은 도전은 사업과 해외거주이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5배 더 좋은 관계를 맺기 위해서 무엇을 하면 좋을까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애인, 친구, 가족 등 관계의 중요성을 인식하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;프렌즈&amp;rdquo; (로빈 던바) 읽기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;소중한 사람들과 활동을 주기적으로 같이 하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운동 모임, 회고 모임, 스터디, 밴드 등&lt;/li&gt;
&lt;li&gt;매주 볼 기회가 생긴다는 점에서 일차적으로 훌륭한 장치일 뿐더러, 함께 무언가 해나갈 때 얻는 즐거움 또한 매우 크다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관계를 주기적으로 돌아보기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한달마다 회고하기&lt;/li&gt;
&lt;li&gt;내가 챙겨야 할 관계를 얼마나 잘 챙겼는지, 불필요한 관계는 얼마나 많았는지 돌아본다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;시간적 여유 가지기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바쁠 때, 관계를 도외시 하기 쉽다. 내 삶에 여유가 있어야만 관계를 소중히 여길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자주 연락하고, 마음 표현하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 Self Integrity Report 는 굉장히 의미있었다. 반년 사이에 나에 대해 더 잘 알게 되었음을 느낀다. Self integrity(자기 진실성)을 갖추기 위해서, 단지 &amp;ldquo;나에게 충실해야지&amp;rdquo; 라고 하는 주문 만으로 되지 않는다. 나를 잘 알아야 하고, 그전에 다양한 경험이 선행되어야 한다. 올해는 멘토링, 회사 생활(기술 탐구, 제품 성장), 학교 생활(철학, 경제학, 컴퓨터공학 공부) 등 다양한 경험을 하면서, 나를 더 잘 알게 되었음을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 주목할만 한 점은, 선한 영향력을 더 세밀하게 정의했다는 점, 관계를 높은 우선순위로 두었다는 점이다. 무엇을 할 때 내가 더 행복하고, 충만감을 느끼는가 깨달아가고 있다. 점점 자기 진실성을 갖춰가는 것 같다. 앞으로의 삶이 매우 긍정적이다.&lt;/p&gt;</description>
      <category>Writing/Self integrity report</category>
      <category>self integrity</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/83</guid>
      <comments>https://happysisyphe.tistory.com/83#entry83comment</comments>
      <pubDate>Fri, 27 Dec 2024 18:33:52 +0900</pubDate>
    </item>
    <item>
      <title>이득이라는 착각</title>
      <link>https://happysisyphe.tistory.com/82</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;글_이득이라는착각.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ok3be/btsLijmwRhb/Hm0liklVZNkZUQ56HslOnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ok3be/btsLijmwRhb/Hm0liklVZNkZUQ56HslOnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ok3be/btsLijmwRhb/Hm0liklVZNkZUQ56HslOnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fok3be%2FbtsLijmwRhb%2FHm0liklVZNkZUQ56HslOnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;400&quot; data-filename=&quot;글_이득이라는착각.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p id=&quot;SE-653b51f3-6d22-45ec-b259-54f76627de7f&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;인간은 높은 지능을 가지기 때문에, 자연스레 무엇이 이득인지 판단하고 그에 맞게 행동한다. 그 결정은 휴리스틱에 기반한 경우가 많다. 휴리스틱은 직관에 의한 결정을 말한다. 이것 덕분에 인간은 빠른 의사결정을 통해 위험을 피하고, 불필요한 의사결정 비용을 줄일 수 있다. 하지만, 나는 주변에서 이득을 잘못 판단하는 경우가 매우 많다고 생각한다. 그러니까, 경제학에서 정의하는 비용-편익 분석 모델로 분석해보면, 적절한 판단인지 의구심이 들 때가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1f2f6eea-9303-4dfe-addc-6a68a1af0493&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-e14de2d5-3ad8-4262-8de4-a85b2b2a8d5b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;군대에서 한 친구와 나눴던 대화가 기억난다. 내가 이렇게 말했다. &amp;ldquo;매일 공부를 5시간 하고 유명 대학에 가는 것과, 매일 공부를 10시간 하고 상대적으로 덜 유명한 대학교에 가는 것, 둘 중 하나를 택하라면 나는 후자를 택할거야&amp;rdquo; 이 말에 친구는 왜 둘 다 손해인 것을 선택 하느냐고 했다. 그 당시 나는 삶에서 노력해본 경험이 가장 중요하다고 보았고, 학벌을 버리더라도 노력해본 경험을 택할 수 있다면, 그게 비용-편익 관점에서 이득이라고 판단한 것이다. 그가 둘 다 손해라고 보는 관점을 이해하지 못한 것은 아니나, 의사결정 모델에서 결함을 보이고 있다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1788e9ef-c997-431e-aafa-03bcbf193f22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-bf360e24-db0a-4059-a222-61b6d49aa454&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;비용-편익을 판단할 때, 인간이 범하기 쉬운 오류가 몇가지 있다고 생각한다. 오늘은 두 가지를 언급하고자 한다. 첫 번째는 내재적 가치 무시 오류이다. 인간은 수치화 가능한 결과를 기준으로 판단하는 경향이 있고, 드러나지 않는 내재적 가치는 무시하는 경향이 있다. 수치화 불가능한 것들에는, 인간관계의 신뢰 형성, 역량 성장, 자아 실현 등이 있다. 내가 경제적으로 손해보더라도, 소중한 관계에서의 신뢰를 그 이상 얻어낼 수 있다면 그것은 합리적인 것이다. 대학교 팀플에서 내가 더 많은 시간과 노력을 투자하고, 내가 더 많이 성장할 수 있다면 내가 이득인 것이다. 내가 가진 지위와 경제력을 다 내려놓고 자아를 실현할 수 있다면 그게 진정한 이득인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-0f6882f6-b95e-4da8-8b2b-4e5e9d5fce1e&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-77633b7f-788d-4cfd-bb7f-4a69c94cd0cc&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;이를 제대로 판단하기 위해서는 자신이 어떤 내재적 가치를 중시하는지 이해하고, 그것의 가치가 얼마인지 제대로 이해하는 것이 중요하다. 자신에게 인간관계란 굉장히 중요함에도 불구하고, 관계의 소중함이 공기처럼 느껴져 제대로 인지하지 못 한다면, 그 이후의 의사결정 또한 오류가 있을 것이다. 또한, 사회적 통념에 의해서 자신이 가진 꿈의 가치를 평가절하 한다면, 그 이후의 의사결정 또한 오류로 가득찰 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-c9171dbc-0067-4b46-b974-d428994183a0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-4dfd14f8-2789-4931-8b52-1c8c7c910fbe&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;두 번째로 언급할 오류는, 위험회피 성향으로 인한 비용-편익 계산 오류이다. 위험회피 성향은 인간에게 보편적으로 관찰되는 특징이다. 이는 진화심리적으로 설명이 가능하다. 수렵채집 시대에 위험회피 성향이 아니었던 우리의 조상들은 대가 끊겼을 것이다. 숲속에서 바스락 거리는 소리를 듣고 위험이라고 인지하지 않고 단지 바람이 불었기 때문이라고 판단한 조상들의 최후는 쉽게 예측할 만 하다. 그렇게 현대인은 일부 돌연변이를 제외하고는 위험회피 성향을 기본적으로 탑재하고 살아간다. 그래서 50% 확률로 200 또는 100을 얻는 상황보다(E(U) = 150), 확실한 125를 더 선호하기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-753b2b8e-c6d7-4757-b7bb-d7dfe3f28e00&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-e32d99f9-3b64-44f3-8196-fb6ea40caf3e&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;그런 측면에서, 위험회피 성향 자체를 부정할 생각은 없다. 하지만 우리 뇌가 위험에 대해 과대평가 하고 있다는 사실을 메타인지 하는 것이 중요하다는 점을 말하고 싶다. 더하여, 현대의 위험 수위는 선대에 존재했던 위험 수위에 비해 극도로 낮음에도, 우리 뇌는 무조건 도전을 피하라고 명령한다는 사실을 메타인지 하는 것이 중요함을 말하고 싶다. 무작정 기대효용에 따라, 비용-편익을 계산하여 결정하라고 말하고 싶진 않다. 이것은 인간이란 존재를 망각하는 일이니. 단지 기대효용(E(U) = 150) 과, 확실성 등가(U=125)를 비교하는 과정에서, 확실성 등가를 너무 과소평가 하지 않았으면 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-8433d25c-6de8-4809-adca-c165d8d91f14&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-820babc5-11d1-4fc2-9f2f-a4de3f3ba2ba&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;나는 이런 의사결정 오류를 너무나 많이 관찰한다. 내 속에서도 그렇고, 타인에게서도 그렇다. 이 글을 읽으시는 분들도 자신의 의사결정 모델을 점검하고, 진실로 이득인 것을 찾아나갔으면 한다. 그 시작은 자아에 대한 탐구여야만 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/82</guid>
      <comments>https://happysisyphe.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 13 Dec 2024 16:10:59 +0900</pubDate>
    </item>
    <item>
      <title>오타니식 성장</title>
      <link>https://happysisyphe.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;오타니식_성장.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl7myu/btsK9kLYDjY/R90Rkq05k3j27HSKqFxKPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl7myu/btsK9kLYDjY/R90Rkq05k3j27HSKqFxKPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl7myu/btsK9kLYDjY/R90Rkq05k3j27HSKqFxKPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl7myu%2FbtsK9kLYDjY%2FR90Rkq05k3j27HSKqFxKPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;424&quot; data-filename=&quot;오타니식_성장.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt;자기계발을 좋아하는 이에게, 성장이라는 키워드는 매우 중요하다. 더 나은 인간이 되&lt;/span&gt;고자 하고, 자아를 실현하고자 하는 욕구는 굉장히 고상하다고 생각한다. 하지만, 종종 무분별하게 성장을 추구하는 이들을 마주한다. 성장을 어떻게 정의하고, 어떻게 측정할 것이냐에 따라서 성장의 진정한 의미가 사뭇 퇴색될 수 있다고 생각한다. 특히, 단기적인 선형적 성장을 추구하는 순간, 성장의 본질적 의미를 상실할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;선형적 성장이란, input 한단위 증가에 따라, 일정한 output 이 보장되는 성장을 의미한다. 경제학을 잘하기 위해서, 경제학 책을 읽는 행위는, 선형적 성장에 가까울 것이다. 경제학 책 1권은, 1권 만큼의 지식을 가져다주고, 딱 그만큼 나는 성장해있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;선형적 성장의 반대 개념은 다차원적 성장이다. 단지 2차원에서의 성장이 아니라, 차원이 다른, 여러 차원에서의 성장을 의미한다. 경제학을 잘 하기 위해서, 수학과 영어를 공부하는 것이 이에 해당할 것이다. 장기적으로 경제학을 잘 하기 위해서, 선형대수학, 미적분학 등의 이론과 테크닉이 필요하다. 하지만 이는 단기적으로 경제를 잘해진다는 느낌을 선형적으로 얻기 어려울 것이다. 또한 학문을 함에 있어서 영어를 잘하는 것도 장기적으로 유리할 것이다. 하지만, 단기적으로 성장한다는 느낌을 받지 못할 수 있다. 그렇지만 장기적으로는, 선형성만으로는 절대 얻을 수 없는 엄청난 성장을 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;선형적 성장은 절대적으로 나쁘고, 다차원적 성장이 절대적으로 좋은 것은 당연히 아니다. 때에 따라 다른 성장이 요구된다. 적어도 나는, 가장 훌륭한 경제학자가 한 평생 경제학만 해온 사람이 아닐 거라고 확신한다. 가장 훌륭한 교육자는, 곧바로 사범대에 진학하여 선생님이 되길 꿈꾸며 선형적으로 성장해온 이가 아닐 것이라고 확신한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;몇 년 전, 오타니가 야구계에서 성공하면서, 오타니가 고등학생 시절 그렸던 만다라트가 화제였다. 오타니는 최고의 투수가 되기 위해서, 단지 매순간 투구만을 하지는 않았다. 오타니는 스쿼트, 독서, 부실 청소까지 만다라트 계획표에 써두었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우리가 진정 주목해야 하는 것은, 오타니의 성취 결과가 아니다. 과정의 불안을 진정 이해해야 한다. 동료들은 매순간 투구를 연습하고 있는데, 혼자 헬스장에서 스쿼트를 하고 있는 자의 불안을 이해해야 한다. 주변 경제학도들은 모두 경제만을 공부하며 지식을 늘리고 있는데, 혼자 수학을 공부하고 있는 자의 불안을 이해해야 한다. 그것을 견디기 어렵기 때문에, 많은 이들이 단기적 선형적 성장을 추구하는 것이다. 과연 내가 성장하고 있는지 매순간 의심하면서 말이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이런 복잡한 상황에서, 어떻게 좋은 의사결정을 할 것인가? 매 상황이 변할 때마다 다이나믹하게, 의사결정 구조를 변경해도 좋다고 생각한다. 하지만 내가 제안하는 Rule of thumb 는 바로 Follow my heart 이다. 이 전략이 언제나 옳으리라 증명할 수는 없다. 대신 성장에 대한 불안을 느끼지 않고, 내가 좋아하는 일을 할 수 있다. 그러면서 언젠가 관련 없어 보이는 점들이 연결되며(Connecting the dots), 다차원적 성장을 이루어낼 확률이 높다. 이는 믿음의 영역이며, 경험의 귀납으로나마 입증될 수밖에 없다. 그것을 경험한 이는 다차원적 성장의 대단함을 알고, 불안감을 느끼지 않으며 자신의 심장을 따를 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <category>성장</category>
      <category>오타니</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/81</guid>
      <comments>https://happysisyphe.tistory.com/81#entry81comment</comments>
      <pubDate>Thu, 5 Dec 2024 22:31:55 +0900</pubDate>
    </item>
    <item>
      <title>진부한 만큼 중요한 말 : Connecting the dots</title>
      <link>https://happysisyphe.tistory.com/80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;connecting the dots.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qJhjj/btsKO7gPFLw/0F6I0nIMkZypbdsPwr1ah1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qJhjj/btsKO7gPFLw/0F6I0nIMkZypbdsPwr1ah1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qJhjj/btsKO7gPFLw/0F6I0nIMkZypbdsPwr1ah1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqJhjj%2FbtsKO7gPFLw%2F0F6I0nIMkZypbdsPwr1ah1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;connecting the dots.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진부한 말은 왜 진부해졌을까. 너무나 유명해서, 닳게 들으면 그 말은 진부하게 느껴진다. 하지만 역설적으로 진부한 만큼, 중요하다고 생각한다. 중요하지 않았다면 이야기가 들리지 않았을 터이고, 그 말은 새로이 느껴질 것이다. 오늘은 스티브 잡스가 스탠포드 대학교 졸업 연설에서 남긴 유명한 말, &amp;ldquo;Connecting the dots&amp;rdquo; 에 대해서 깊이 들여다보려 한다. 잡스가 이 말을 통해 무엇을 전달하고 싶었을까. Connecting the dots 는 경험의 연결을 의미하는데, 연설을 들어보면 잡스는 2가지를 강조하고 싶어하는 것으로 보인다. 경험의 예측 불가능성과, 내면의 소리는 따르는 것. 잡스의 생애를 통해, 이 말을 더 깊이 이해해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡스는 남들이 볼 때, 일반적이지 않은 삶을 살아왔다. 1학년 1학기를 마치고 대학교를 중퇴했으며, 30살에 자신이 설립한 회사에서 쫓겨나기도 했다. 주변에 모두가 대학을 다니는데, 혼자 자퇴를 하는 기분은 어떤 기분일까. 잡스는 자퇴생으로서 마음이 불안했다고 한다. 등록금이 없고, 학교가 도움이 되지 않아서 자퇴했으나, 이후 18개월간 수업을 청강하며 지냈다. 그때 들었던 수업 중 하나가 그 유명한 캘리그래피 강의이다. 이는 애플의 세련된 폰트 스타일을 만들어낸 계기가 되었다. 이 일화가 누군가에게는 진부한 일화로 들릴 것이라고 생각한다. 내가 주목하고 싶은 부분은, &amp;ldquo;자퇴 이후의 불안&amp;rdquo; 이다. 과연 그가 자퇴할 적에, 불안에 떨던 그때, 이 선택이 삶에 긍정적인 영향을 미칠 것을 알았을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 잡스는 30살에 자신이 설립한 애플에서 쫓겨나게 되었다. 자신이 설립한 회사에서 쫓겨난 기분은 어떨까. 잡스는 자신이 실패했다고 느꼈다고 한다. 하지만 그렇게 낙담하는 경험을 하면서도, 자신은 그 일을 계속 하고 싶었음을 깨달았다고 한다. 직장을 잃어보니, 그 일에 대한 사랑을 재확인할 수 있었던 것이다. 그러면서 애플의 코어 기술의 원천인 NeXT 를 창업할 수 있었다. 또한 Pixar 를 창업하면서 얻은 경험도 삶에서 매우 주요하게 작용했을 것이다. 더하여, 그 기간에 사랑하는 와이프까지 얻게 되었다. 이 또한 유명한 일화이다. 내가 주목하고 싶은 부분은 &amp;ldquo;해고 이후의 상실감&amp;rdquo; 이다. 과연 그가 회사에서 쫓겨났을 때, 상실감이 컸던 그때, 이것이 삶에 긍정적인 영향을 미칠 것을 알았을까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 사건 모두, 그 순간에는 그러지 않았다면 더 좋았으리라 생각할 수 있다. 등록금이 충분하여 자퇴하지 않는게 좋다고, 그리고 CEO 자리에서 쫓겨나지 않는게 좋다고 생각할 만 하다. 하지만 그 결과로 만들어진 점들이 서로 연결되어 삶에서 매우 주요한 역할을 하게 된 것이다. 불안하고 상실감을 주는 &amp;ldquo;불운한&amp;rdquo; 일이 없었더라면, 이루기 힘들었을지도 모르는 결과를 만들게 된 것이다. 이것이 경험의 예측 불가능성이다. 잡스는 연설에서 이와 같이 말했다. &amp;ldquo;You can&amp;rsquo;t connect the dots looking forward. You can only connect them looking backwards.&amp;rdquo; 경험이 진행중일 때는, 이 경험이 미래에 어떻게 작용할지 알 수 없다. 시간이 10여년 지나고 돌아다봐야만 점들의 연결을 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 대체 우리는 이 순간을 살아갈 때, 어떤 스탠스를 취해야 하는가? 순탄한 길과 도전의 길을 고민할 때 어떻게 결정해야 할까? 잡스는 &amp;ldquo;Follow my heart&amp;rdquo;를 이야기 한다. 현실적으로 심장을 따르기엔, 두려운 요소가 너무 많다. 실패에 대한 걱정, 무용할 것에 대한 걱정 등. 잡스는 이렇게 말한다. &amp;ldquo;Believing that the dots will connect down the road will give the confidence to follow your heart&amp;rdquo; 점들이 연결될 것이라는 믿음이 곧 내 심장을 따를 용기를 가져다준다. 잡스가 Connecting the dots 를 말하며, 궁극적으로 현재의 청년들에게 하고싶었던 이야기는, &amp;ldquo;Follow my heart&amp;rdquo; 라고 생각한다. 그것이 불안하겠지만, Connecting the dots 를 생각한다면, 불안해하지 않아도 괜찮고, 심장을 따라도 괜찮다는 말을 해주고 싶었으리라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면, 잡스는 Connecting the dots 라는 말로서 졸업생들에게, 경험이란 미래에 어떻게 작용할지 예측 불가능하므로, 언젠가 이것들이 연결될 것임을 믿고, 그저 내면의 소리를 따르라고 말하고 싶었던 것이다. 이 연설은 나에게도 큰 울림을 주었다. 내가 찍어온 점들을 돌아보니, 모두 심장을 따른 결과였다. 당시에는 의심하기도 했고, 불안하기도 했지만, 그것들은 현재 나의 삶에 각각, 또는 서로 연결되어 좋은 영향을 미치고 있다고 느낀다. 이를 돌아보고 나니, 미래를 더 뜨겁게 살아갈 용기가 생긴다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <category>Connecting the dots</category>
      <category>스티브 잡스</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/80</guid>
      <comments>https://happysisyphe.tistory.com/80#entry80comment</comments>
      <pubDate>Wed, 20 Nov 2024 23:19:15 +0900</pubDate>
    </item>
    <item>
      <title>Web Socket 으로 간단한 채팅앱 만들기 (Node.js, React)</title>
      <link>https://happysisyphe.tistory.com/79</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Web Socket Protocol 이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 프로그램간 양방향으로 메시지를 교환하기 위한 통신규약을 말한다. &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6455&quot;&gt;IETF의 RFC6455&lt;/a&gt; 에서 표준 프로토콜로 정의하고, &lt;a href=&quot;https://websockets.spec.whatwg.org//&quot;&gt;W3C&lt;/a&gt;에 의해서 웹 기술 표준으로 정의하고 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 소켓의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 양방향 통신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서버가, 서로 통신을 주고받을 수 있다. HTTP 프로토콜은 단방향 통신이 특징인 것과 차별점을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 지속적인 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 통신은 비연결성을 가진다. 한번 요청, 응답을 주고받으면 종료되는 것이 기본 특징이다. Web Socket은 한번 연결이 되면, close 할 때까지 지속적으로 서로 메시지를 주고받을 수 있다. 그러므로, 불필요한 handshake 과정이 없고, 불필요하게 큰 header 정보를 계속 주고받지 않아도 된다. 그래서 실시간 채팅앱, 주식 차트, IoT 기기 센싱 등 다양한 곳에서 사용된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 소켓의 동작&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;web-socket-way.png&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MWScc/btsKCLRUaru/gxIGKuj84BaiR08kTwiifk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MWScc/btsKCLRUaru/gxIGKuj84BaiR08kTwiifk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MWScc/btsKCLRUaru/gxIGKuj84BaiR08kTwiifk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMWScc%2FbtsKCLRUaru%2FgxIGKuj84BaiR08kTwiifk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1900&quot; height=&quot;1272&quot; data-filename=&quot;web-socket-way.png&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HTTP 프로토콜로 handshake 하여, 양쪽 서버 연결 시도&lt;/li&gt;
&lt;li&gt;ws 프로토콜로 메시지 양방향으로 주고받기&lt;/li&gt;
&lt;li&gt;close 호출 시, socket 연결 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 연결하고, 브라우저 네트워크탭의 &lt;b&gt;WS&lt;/b&gt; 로 필터링하면 세부 정보를 볼 수 있다. HTTP GET 으로 통신하며, 101 Switching Protocols 상태를 받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;network-tab.png&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wuzPT/btsKD0Avh8o/fnkaLrXllJ8IjxsqkagrA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wuzPT/btsKD0Avh8o/fnkaLrXllJ8IjxsqkagrA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wuzPT/btsKD0Avh8o/fnkaLrXllJ8IjxsqkagrA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwuzPT%2FbtsKD0Avh8o%2FfnkaLrXllJ8IjxsqkagrA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;741&quot; height=&quot;729&quot; data-filename=&quot;network-tab.png&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node.js 구현 (with ws)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js 라이브러리인 ws 를 활용하여 구현한다. 구현 자체는 매우 간단하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;WebSocket 서버를 생성한다.&lt;/li&gt;
&lt;li&gt;클라이언트에서 메시지 이벤트를 받았을 때, 모든 클라이언트로 데이터를 브로드캐스트해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { WebSocketServer, WebSocket } from &quot;ws&quot;;
import { createServer } from &quot;http&quot;;

// HTTP 서버 생성 (WebSocket 연결을 위해 HTTP 서버가 필요)
const server = createServer();
const wss = new WebSocketServer({ server });

// 클라이언트 연결 시 실행될 이벤트 리스너 설정
wss.on(&quot;connection&quot;, (ws: WebSocket) =&amp;gt; {
  console.log(&quot;New client connected&quot;);

  // 클라이언트에서 메시지 수신 시 실행되는 이벤트
  ws.on(&quot;message&quot;, (message: string) =&amp;gt; {
    const parsedMessage = JSON.parse(message);

    // 모든 클라이언트에게 브로드캐스트
    wss.clients.forEach((client) =&amp;gt; {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(parsedMessage));
      }
    });
  });
});

const PORT = 8080;
server.listen(PORT, () =&amp;gt; {
  console.log(`Server is running on &amp;lt;http://localhost&amp;gt;:${PORT}`);
});

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client 를 만들기 어렵다면, terminal 로 웹소켓 서버와 연결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install wscat -g 로 wscat 라이브러리를 설치하고, wscat -c ws://localhost:8080 로 웹소켓 서버와 연결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 prompt 가 가능하고, 메세지를 송수신 해볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Client 구현 (with React)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React를 활용해서 Web Socket Client 를 구현해보자. 간단한 채팅앱을 구현해볼 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sisyphe-talk.gif&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcN9B5/btsKD0gbtGf/iPjhdwEOZQmjddRHnLe93K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcN9B5/btsKD0gbtGf/iPjhdwEOZQmjddRHnLe93K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcN9B5/btsKD0gbtGf/iPjhdwEOZQmjddRHnLe93K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bcN9B5/btsKD0gbtGf/iPjhdwEOZQmjddRHnLe93K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;496&quot; height=&quot;241&quot; data-filename=&quot;sisyphe-talk.gif&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 기본적으로 제공하는 WebSocket 객체를 활용할 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;WebSocket Server 와 연결&lt;/li&gt;
&lt;li&gt;서버의 메세지 이벤트를 받아서, 상태 업데이트&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const [socket, setSocket] = useState&amp;lt;WebSocket | null&amp;gt;(null);
const [messages, setMessages] = useState&amp;lt;
  { clientId: string; text: string }[]
&amp;gt;([]);

useEffect(() =&amp;gt; {
  // WebSocket 서버 연결
  const ws = new WebSocket(&quot;ws://localhost:8080&quot;);
  setSocket(ws);

  // 서버로부터 메시지 수신
  ws.onmessage = (event) =&amp;gt; {
    const messageData = JSON.parse(event.data);
    setMessages((prevMessages) =&amp;gt; [...prevMessages, messageData]);
  };

  return () =&amp;gt; {
    // 컴포넌트 언마운트 시 WebSocket 연결 종료
    ws.close();
  };
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 클라이언트에서 메시지를 전송한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const sendMessage = () =&amp;gt; {
  if (socket &amp;amp;&amp;amp; input) {
    const message = { text: input, clientId };
    socket.send(JSON.stringify(message));
    setInput(&quot;&quot;);
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세한 구현은 아래 repository 에 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/euijinkk/web-socket-full-practice&quot;&gt;https://github.com/euijinkk/web-socket-full-practice&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현을 완료하고, 탭을 여러개 띄워서, 2개 이상의 Client 에 접속하여, 하나의 Web Socket server에 여러개의 Client 와 연결한다. 그리고 각 탭에서 채팅을 보내 실시간으로 정보를 주고받을 수 있는지 확인한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹소켓 사용시 주의할 점과 대안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소켓은 클라이언트-서버 간의 지속적인 연결을 유지한다. 그러므로, 잘못 사용하면 불필요하게 메모리, CPU 리소스를 소모할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 꼭 필요할 때만 사용하고, 그렇지 않다면 다른 통신 방법을 사용하는 것이 바람직하다. 꼭 필요한 상황은, 양방향 + 실시간 통신이 요구될 때이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안으로는 Polling, SSE(Server Sent Event) 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Polling 은 HTTP 요청을 주기적으로 보내는 방식을 말한다. 특정 시간 안에, 서버에서 데이터가 오는 것을 확신할 수 있고, 완벽한 실시간성을 요구하지 않는다면, Polling 을 사용하는 것으로 충분하다. 긴 시간 속에서 언제 서버가 update 되는지 알 수 없다면, 불필요한 HTTP 요청이 많아질 것이다. 또한 완벽한 실시간성을 요구한다면, Polling interval 을 줄여야 하고, 그러면 서버 부하가 더 커질뿐더러, HTTP 통신 자체가 느려서 어느정도 통신 지연을 가질 수밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSE는 Web Socket과 마찬가지로 한번 HTTP 통신으로 서버와 클라이언트를 연결한 후, 서버가 클라이언트로 단방향으로 지속적으로 메시지를 보낼 수 있다. 양방향 통신은 필요없지만, 실시간 통신을 요구하는 경우 이 기능을 사용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 형태의 채팅앱을 만들어보며, Server, Client 에서 어떻게 Web Socket 통신하는지 알아보았다. 구현하면서 프론트엔드, 서버 구조 모두에서 다양한 의문이 들었다. 클라이언트에서 Socket 이 끊어질 때를 대응하고, Socket 통신이 필요없을 때는 적절한 시점에 끊어서 메모리 누수를 방지한다거나 하는 고민을 해볼 수 있다. 서버에서는 로드밸런싱은 어떻게 할지, 실시간성을 지키기 위해서 어떻게 Socket 메시지를 처리해야할지, 어떤 DB를 사용해야 할지 등을 생각해볼 수 있다. 생각할 포인트가 너무 많았고, 웹소켓 통신으로 좋은 경험을 전달하는 것이 기술적으로 엄청나게 어려운 문제임을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/Sofeware Development</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/79</guid>
      <comments>https://happysisyphe.tistory.com/79#entry79comment</comments>
      <pubDate>Sun, 10 Nov 2024 19:10:54 +0900</pubDate>
    </item>
    <item>
      <title>학문을 대하는 바람직한 자세</title>
      <link>https://happysisyphe.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최근 다시 대학에 돌아와서, 마지막 학기를 보내고 있다. 막학기인지라, 내가 원하는 과목들을 들으며 아주 즐겁게 학문을 하고 있다. 경제, 컴퓨터과학, 철학 과목들을 수강 중이다. 다양한 과목들을 근본부터 들으니, 어떻게 학문을 대해야 하는지, 좋은 생각이 솟구친다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나의 현 상황을 서술하는 것이 이해에 도움이 되리라고 생각한다. 나는 현대 프로그래밍 언어는 잘 다룰 줄 아는데, 컴퓨터과학의 근간인 기계어, 어셈블리어, C언어 등은 잘 모른다. 나는 현대 철학인 실존주의에 대한 이해는 높은 편인데, 고전철학, 근대철학에는 그렇지 않다. 나는 SQL 은 다룰 줄 아는데, 데이타베이스 이론은 잘 모른다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 사례들이 잘못 되었음을 말하고자 하는 것은 절대 아니다. 나는 실용주의적, top-down 학습법이 매우 적절하다고 생각한다. 일단 현대적 쓰임새를 알고, 역사와 근본을 학습할 때 비로소 그것들을 제대로 소화할 수 있다. 근본을 이해하는 것이 왜 중요하고, 어떤 자세로 학문을 대해야 하는지 이야기하려고 한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컴퓨터과학에서 C언어를, 철학에서 플라톤, 데카르트를 대학교 1학년 전공기초로 배우는 이유는 무엇일까. 현재에 거의 쓰이지 않는 역사적 지식들, 근본적 지식을 왜 알아야 할까.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바로, 그 시대에 마주한 문제상황과, 문제해결 과정을 배우기 위한 목적이다. 그 시대의 지식 수준, 문화라는 제약 조건 하에서, 그 시대 사람들은 어떤 해결책을 떠올려서 문제를 해결했는가 를 이해하는 것이다. 데카르트는 대체 왜 &amp;ldquo;나는 생각한다 그러므로 존재한다&amp;rdquo; 는 말을 하며, 인간의 의식만을 유일하게 확실한 것으로 도출했을까. &amp;ldquo;나는 생각한다 그러므로 존재한다&amp;rdquo;라는 말 자체는 과장해서 표현하면, 전혀 중요하지 않다고 생각한다. 현대 과학의 관점에서는, 내 존재를 알게 해주는 것은, 내 생각만이 아니다. 심장이 뛴다는 것, 신체를 가진다는 것 등 다른 이유가 제시될 수 있다. 데카르트가 살았던 1600년대는 중세에서 근대로 넘어가는 시점이었고, 코페르니쿠스 등의 과학자가 이 세계를 설명하기 시작했고, 종교개혁이 일어나며, 여러 사회적 혼란이 있었다고 한다. 그 시대적 문제를 해결하기 위한 데카르트가 진행한 프로젝트의 결과가 바로 &amp;ldquo;나는 생각한다 그러므로 존재한다&amp;rdquo; 였다. 그 주장까지 가는 데 있어서, 방법적 회의, 토대론, 오류불가능주의 등 흥미로운 방법론들을 사용한다. 단지 해결책에만 주목하는 것이 아니라, 문제해결을 위한 데카르트의 아이디어와 컨셉을 배우는 것이 진정 의미가 있다고 생각한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그것이 왜 의미가 있을까? 여러 문제해결 컨셉과 테크닉을 이해할 수 있다면, 자신이 실제로 해결할 수 있는 문제의 범위를 넓힐 수 있다. 자신의 역량을 표현한 circle 이 있다고 하면, 이는 circle 의 면적을 넓히는 일이다. 우리가 더하기를 배우니 빼기를 할 수 있게 되고, 시키지 않아도 곱하기를 할 수 있게 된다. 이처럼, 우리가 역사 속에서 배운 문제해결 과정을 우리의 삶 어디서든 사용하여 문제를 풀어낼 수 있다. 사회 혼란 속에서, 누구도 부정할 수 없는 명석판명한 것을 찾으려고 했던 데카르트의 정신을 이어받아, 현대 사회의 문제를 해결할 수 없을까? 자아의 문제를 해결할 수 없을까? 과학적 사고에 적용할 수 없을까? 모두 가능하다. 즉, 우리는 과거를 통해서, 미래를 볼 수 있게 된다. 여러 정반합을 거쳐서, 지금까지 오게 된 과정을 이해하는 것은 정말 중요하다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;학문을 대할 때, 두 가지 중요한 포인트를 강조하고 싶다. 첫 번째는 그 때의 제약조건을 온전히 이해하는 것, 두 번째는 결론이 아니라, 해결과정에 집중하는 것이다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;플라톤의 철학을 그냥 먼 이야기로 치부해 버려서는 안 된다. 기원전으로 타임머신을 타고 돌아가서, 그 시대에 빠져들어야 한다. 내가 현재 신을 믿지 않는다고 하여, 유신론적 주장을 배제해서는 안 된다. 그 시기의 지식 수준은 어땠으며, 어떤 문제 상황이 있었고, 해결책으로는 어떤 것들이 있었고, 얼마나 고군분투하여 해결책을 떠올렸으며, 그것이 당대에 얼마나 멋진 컨셉이었고, 최선의 선택이었는가를 이해하는 것이 정말 중요하다. 이 측면에서 나는 천동설도 충분히 들여다보아야 한다고 생각한다. &amp;ldquo;옛날 사람들은 천동설은 믿었대요&amp;rdquo; 처럼 우스운 말이 없다. 다름에 주목하는게 아니라, 그들과 우리를 동일시하는 것이 배움의 출발점이다. 천동설의 논리도 상당히 탄탄했을 것이다. 그리고 천동설의 발전이 곧 지동설을 만들었음에 나는 일말의 의심도 하지 않는다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;밀란 쿤데라의 &amp;ldquo;배신당한 유언들&amp;rdquo;에 이런 말이 나온다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&amp;ldquo;인간은 안개속을 나아가는 자다. 그러나 과거의 사람들을 심판하기 위해 뒤돌아볼 때는 그들의 길 위에서 어떤 안개도 보지 못 한다. 누가 더 맹목적인가? 레닌에 대한 시를 쓰면서 레닌주의가 어떤 귀결에 이를지 몰랐던 마야코프스키인가? 아니면 수십년 시차를 두고 그를 심판하면서도 그를 감쌌던 안개를 보지못했던 우리인가. 마야코프스키의 맹목은 영원한 인간의 조건에 속한다. 마야코프스키가 걸어간 길 위에 안개를 보지 않는 것, 그것은 인간이 뭔지를 망각하는 것이오. 우리 자신이 누구인지를 망각하는 것이다.&amp;rdquo;&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역사적 선택에 있었던 안개를 고려하지 않고, 판단하는 것만큼 비인간적인 것은 없다. 그러한 냉소는 그 시대 사람들에 대한 존중이 없는 행위이며, 배움도 안겨주지 않는다. 우리는 그때의 안개를 그대로 재현하여 상상하며, 위인을 대하고, 학문을 대할 필요가 있다. 그래야만 비로소 그들이 단지 괴짜가 아니라, 문제해결사로 보이고, 그들의 유려함에 감탄하게 될 것이다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/78</guid>
      <comments>https://happysisyphe.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 11 Oct 2024 16:55:34 +0900</pubDate>
    </item>
    <item>
      <title>네 석공 이야기</title>
      <link>https://happysisyphe.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;탈무드에 나오는 세 석공 이야기가 있다. 어느 날, 세 석공이 공사장에서 열심히 돌을 다듬고 있었다. 지나가던 노인이 그들에게 왜 일을 하느냐고 물었다. 첫 번째 석공은 한숨을 내쉬며 죽지 못해 일한다고 하였다. 두 번째 석공은 가족의 생계를 위해 일한다고 하였다. 세 번째 석공은 웃으면서, 아름다운 성당을 짓기 위해 즐겁게 일한다고 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이야기는 일에 의미를 부여하는 것의 중요성을 시사하는 소재로 자주 사용된다. 하지만 과연 일에 의미를 부여하는 자가, 가장 일을 즐기는 사람일까? 나는 지극히 세 번째 석공 부류의 인간이다. 일에 큰 의미를 부여한다. 진로를 탐색할 때도 의미에 이끌려서 이리저리 찾아다녔다. 나에게 가장 큰 의미는 선한 영향력이었다. 그렇지만 내가 동료들 사이에서 가장 일을 즐기는 사람이냐고 하면, 그렇지 않았다. 되려 의미에만 미쳐있으면, 일을 즐기기 굉장히 어렵다는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 석공은 아름다운 성당을 짓는 게 삶의 의미이다. 자신이 할 수 있는 일은, 건물에 필요한 수만 개의 벽돌 중 몇 개를 만드는 것이다. 과연 그는 석공으로서 일을 계속 즐길 수 있을까? 석공으로서 일하는데, 자신이 짓는 건물이 아름다운 성당이 아니라면? 옆에 있는 성당과 똑같이 생긴 성당을 또 지어야 한다면? 이슬람 사원을 지어야 한다면? 또는 나이트클럽이라면? 그이는 석공으로서 일해야 하는 이유는 하나도 없어진다. 자신은 아무 의미 없는 사람이 되어 버린다. 뙤약볕 아래에서 돌을 다듬고 있는 그이만큼 불행할 수는 없을 것이다. 사실 첫 번째 석공의 꿈이 아름다운 이슬람 사원을 짓는 것이었을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 의미만으로 일을 즐기기에는 충분하지 않다. 그러므로 우리는 네 번째 석공을 떠올려야만 한다. 일을 진정 즐기는 사람은 어떤 사람인가? 그들은 바로 현재에 몰입하는 사람들이다. 눈앞에 있는 문제를 풀기 위해 노력하고, 탐구하고, 한 단계 더 나아가려고 하고, 도전하는 자들이 진실로 일을 즐기고 있었다. 그들에겐, 결과물이 중요하지 않다. 열심히 돌을 다듬은 결과로 지어진 건물이, 아름다운 성당이든, 나이트클럽이든, 그것은 중요하지 않다. 자기 육체로, 돌을 다듬어내고, 창조해 내고, 더 잘 깎기 위해 노력하는 순간이 중요한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 탈무드의 세 석공 이야기가 의미가 없냐고 하면, 그것은 아니다. 우리는 네 석공에게서 각자의 의미를 도출할 수 있어야 한다. 일에 의미를 가지는 것과, 일 자체에 몰입하는 것은 역할이 다르다. 의미를 가지는 것은, 우리로 하여금 멀리 갈 수 있도록 도와준다. 일을 하다 보면 재밌는 순간도 있고, 재미없는 순간도 있다. 의미는 하나의 목표를 향해 나아갈 수 있도록 한다. 또는 슬럼프에 빠져 삶이 무의미하게 느껴질 때가 있다. 이때 의미는 우리를 일으켜 세워준다. 암흑에서부터 우리를 구출해 준다. 하지만 의미란 너무 멀리 있다. 의미가 완성되기 위해서, 하루하루를 성실히 살아내는 것이 중요하다. 의미에 도달하기 위해서 한 발짝씩 꾸준히 걸어가야 한다. 그것이 바로, 일 자체에 몰입하는 것의 의미이다. 순간의 창조에 집중하고, 눈앞의 문제를 풀고, 한 단계씩 성장하기 위해 노력하며 즐길 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 석공 이야기를 통해 의미의 중요성을 배우는 것은 가치 있다. 하지만 우리는 돌을 다듬는 것에 온전히 집중하느라, 행인의 질문을 듣지 못했던 네 번째 석공을 마음속에 그려보지 않으면 안 된다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/77</guid>
      <comments>https://happysisyphe.tistory.com/77#entry77comment</comments>
      <pubDate>Sat, 17 Aug 2024 11:49:22 +0900</pubDate>
    </item>
    <item>
      <title>Optimality 만큼이나, Approximation 이 중요하다.</title>
      <link>https://happysisyphe.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Computer Science 에서 알고리즘 문제를 푸는 방법 중 하나로 Approximation algorithm이 있다. 계산해야할 것이 너무나 많아서, 주어진 시간 안에 Solution 을 찾지 못 하는 경우에 사용할 수 있는 미봉책이다. 가령 해결책을 찾기 위해서 10^8! (1억 팩토리얼)번 순회해야 한다고 해보자. 일반적인 Computing power 로는 완료할 수 없는 연산이다. 그럴 때, Optimal Solution 을 찾으려고 하는 것이 아니라, Approximation 을 통해서 근사적으로, Optimal 과 가까운 결과를 얻으려고 할 수 있다. 그렇게 함으로써 현실적인 시간 안에 답을 구하지 못하는 문제를, 차선의 답을 구할 수 있도록 바꿔낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 CS 이론이 너무나 현실적이고, 멋진 해결책이라는 생각이 들었다. 자칫 Approximation이 평가절하될 수 있다고 생각한다. 정확한 답을 찾아내지 못 했고, 그 자체로 불완전한 의미를 지니기 때문이다. 하지만 현실적인 제약을 고려한다면 이를 평가절하해선 안 된다. 이는 오히려 필수불가결하다. 그리고 이것이 우리의 삶과 맞닿아 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 삶에서의 선택은 Approximation algorithm 이어야 한다고 믿는다. Optimal Solution 을 찾기 위해서 traverse 해야하는 여정은 너무나 길다. 물리적으로, 현실적으로 resource가 부족하다. resource란, 돈/시간일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 직업을 선택한다고 해보자. 이 알고리즘 문제는 다음과 같다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삶의 효용을 극대화하는 최적의 직업을 찾으시오.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 찾기 위해서는 하나하나 경험해보는 수밖에 없다. 100년을 살더라도, 모든 것을 경험해보고 자신에게 최적인 직업을 찾을 수 있을까? 그렇지 않다. 아무리 직업을 잘 분류하고, 선택에 따른 가지치기를 잘 수행하더라도, Optimal 임을 증명할 수 있는 직업을 찾기란 불가능하다. 정확히는, 주어진 돈/시간 안에서 불가능할 것이다. 우리는 단지 삶의 효용을 근사적으로나마 최적인 것&amp;rdquo;처럼 보이는&amp;rdquo; 직업을 택할 수밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어린 시절 운동에 재능을 보이는 아이를 생각해보자. 운동에는 또 그 종류가 많다. 야구/축구/농구/하키/스키 등을 모두 접해보고, 방향을 결정하기엔 시간이 부족하다. 특히 운동은 어릴 때 시작해야 하므로, 더욱이 현실적인 제약이 엄격하다. 모든 것을 경험할 시간에, 근사적으로 적절해 보이는 것을 빠르게 선택하는 것이 현실적인 방향이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시 이외에도, 삶에서 대부분의 결정은 Optimal 을 찾는데 굉장히 큰 비용이 든다. 진로, 사랑, 주거 문제, 우정, 회사 선택 등 모두. 명확하게 말할 수 있는 것이 더 적다. 그러므로 비용을 줄이고, sub-optimal 을 선택하는게 옳은 결정일 때가 많다. 그리고 중요한 것은 선택 이후엔 믿음의 문제라는 것이다. 한 선택에 대해서, 그것이 최적임을 언젠가 알 수 있을까? 그렇지 않다. 동일한 시간은 한번밖에 살지 못하고, re-produce 할 수 없다. 즉 평생을 산다고 해도, 그 선택이 최적임을 증명하는 것은 불가능하다. 결국 우리는 그나마 적절해보이는 것을 선택하고, 그것이 최적이라고 믿고 살아가는 지혜를 배워야만 한다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/74</guid>
      <comments>https://happysisyphe.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 20 Jun 2024 00:59:42 +0900</pubDate>
    </item>
    <item>
      <title>글또 9기를 돌아보며</title>
      <link>https://happysisyphe.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 6개월간 글또에 참여했고, 오늘 회고를 써보려고 한다. &lt;a href=&quot;https://zzsza.notion.site/ac5b18a482fb4df497d4e8257ad4d516&quot;&gt;글또 커뮤니티&lt;/a&gt;는 글쓰는 또라이 의 줄임말이다. 글쓰는 개발자 모임이고, 돈을 걸고 2주마다 하나의 글을 제출하려고 모인 집단이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쓴 글 수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월동안 총 14개의 글을 썼다. 기술글 3개와 비기술글 11개를 썼다. 프론트엔드 직무로 들어왔기 때문에, 비기술글은 쓰고도 제출하지 않은 글이 많다. 쓴글을 모아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기술글&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/66&quot;&gt;ErrorBoundary 가 포착할 수 없는 에러와 그 이론적 원리 분석&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/70&quot;&gt;React CleanCode #2. UI Variation에 유연하게 대응하기&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/72&quot;&gt;React에서 중복호출(aka. 따닥)을 막는 완벽한 방법&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비기술글&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/67&quot;&gt;토스에서의 1년을 돌아보며&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/71&quot;&gt;왜 사람마다 생각의 양과 질에서 차이가 나는가?&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/69&quot;&gt;2024-1 Self-integrity report&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/68&quot;&gt;세상을 보는 창&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223367313729&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=1&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;『역행자』 를 읽으며 생각한 모든 것&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223360122575&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=1&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;타인을 함부로 비난하고 판단하는 행위&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223350555054&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=1&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;사고의 두 가지 방식 : 유추와 제1 원칙 사고&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223321616825&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=2&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;행동의 첫 번째 순서, &amp;ldquo;신호&amp;rdquo; 에 대하여&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223322016028&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=2&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;행동의 두 번째 순서, &amp;ldquo;열망&amp;rdquo;에 대하여&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223334851065&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=2&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;행동의 세 번째 순서, &amp;ldquo;반응&amp;rdquo;에 대하여&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=gytks4&amp;amp;logNo=223347638043&amp;amp;categoryNo=0&amp;amp;parentCategoryNo=0&amp;amp;viewDate=&amp;amp;currentPage=1&amp;amp;postListTopCurrentPage=&amp;amp;from=&quot;&gt;행동의 네 번째 순서, &amp;ldquo;보상&amp;rdquo;에 대하여&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월간 기술글이 3개밖에 되지 않는게 수치적으로 아쉽긴 하다. 하지만 3개의 글 모두, 개인적으로 만족할 만한 수준의 퀄리티로 썼기 때문에 충분히 만족스럽다. 이렇게 1년에 6개의 좋은 글을 쌓아갈 수 있다면, N년 후의 나는 더 높은 역량을 가진 사람이 되어있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 초에 비기술글은 정말 많이 썼다. 약 3년만에 다시 독서를 제대로 시작하며, 여러가지 생각이 활개를 쳤다. 끊임없이 발전적인 생각이 일어나고, 이를 항상 노트에 글감을 정리해두었다. 그리고 이동 중에나, 시간이 남을 때나 계속 글을 썼다. 덕분에 많은 생각을 정리했고, 내면적으로 한층 더 성장할 수 있었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;글또와 습관 형성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 쓴 글을 보면, 습관에 관한 글을 많이 썼다는 것을 알 수 있다. 연초부터 좋은 습관을 형성하는 것에 엄청난 관심을 부었다. &amp;ldquo;아주 작은 습관의 힘&amp;rdquo; 이라는 책도 읽고, 여러가지 습관 형성 이론을 공부했다. 행동이란 결국, 신호 &amp;rarr; 열망 &amp;rarr; 반응 &amp;rarr; 보상 이라는 4가지 과정으로 이루어진다. 글또라는 커뮤니티는 이에 매우 적절하기 때문에, 글쓰기 습관을 형성하기 최적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;신호&lt;/b&gt; : 2주마다 하나의 글을 써야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;열망&lt;/b&gt; : 글을 잘 쓰기를 열망하는 사람들이 모이고, 글의 가치를 아는 사람들이 모여있다. 자신도 글쓰기에 대한 열망을 계속 이어나갈 수밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반응&lt;/b&gt; : 행동하지 않으면 1만원의 벌금을 내야 한다. 반응을 하지 않기 어려운 구조이다. 사람들과 같이 글을 쓰는 자리도 많기 때문에, 행동하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보상&lt;/b&gt; : 명시적으로 글을 제출하며 즉시적인 보상을 받는다. 글을 제출하고, 주변의 반응을 얻으며 보상을 얻는다. 글쓰기 자체의 훌륭한 역할도 곧바로 느끼게 된다. 더하여, 1만원 뺏기지 않는 보상을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 측면에서 글또는 습관을 형성하기 매우 좋다. 다음에 또 참여하고 싶다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술글을 쓰는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 아쉬운 점 중 하나는 기술글을 3개밖에 쓰지 못 했다는 것이다. 어떻게 하면 기술글을 더 자주 쓸 수 있을까 생각해보았다. 나는 흥미로운 글감이 있을 때, 글을 쓰곤 한다. 흥미로운 글감이 없을 때, 억지로 글을 쓰지 않는다. 나도 쓸 수 있는 기술글은 많았다. Cypress Component Testing, Github OAuth 구현 방법, nodejs 로 스크래핑 스크립트 짜기 등 쓰다가 그만둔 글도 많다. 하지만 아무래도 단편적인 글은 쓰고 싶지 않았다. 이런 글은 글을 쓰면서 성장한다고 느끼지 못 하고, 글을 쓴 후의 브랜딩 효과를 누리기도 힘들다. 검색하면 바로 나오는 글은 좋은 글이 아니기 때문이다. 즉, 나는 깊이 digging 하고 나서 쓰는 글을 좋아한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떻게 기술글을 쓸 수 있을까? digging 을 자주 해서, 글감을 많이 모아야 한다. 나는 내가 어떤 지식을 파고들어서 이해하고, 좋은 방법을 발굴해내고 이를 공유하는 것을 좋아한다. 그러므로 digging 하고, 결실이 있으면 나는 언제나 글을 쓰고 싶어한다. 그러므로 결국 digging을 자주 하는게 나에게 중요하다. 회사에서 weekly-digging 모임을 만들었다. 1주마다 하나를 digging 하고 서로 공유하고 피드백 받는 것이다. 이것을 하다보면 글은 자연스레 쓰고 싶어진다. 일상적으로 digging 하며 지내야겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아쉬웠던 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬웠던 점을 정리하고 마치려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째는, 상술했듯 기술글을 많이 쓰지 못한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째는 책을 읽고 독후감을 많이 못 쓴 것이다. 상반기 동안, 아주 작은 습관의 힘, 운과 실력의 성공 방정식, 욕망의 진화, 챗 GPT가 쏘아올린 신직업 프롬프트 엔지니어, 타이탄의 도구들, 역행자 까지 해서 6권을 읽었는데 독후감을 2개밖에 쓰지 않았다. 책을 읽고 정리하고 생각을 공유하는 습관은 너무 이롭다고 느끼는데, 글을 쓰지 못한 것이 아쉽다. 왜 다 쓰지 못했냐면, 아직 독후감의 템플릿을 정하지 못 했기 때문인 것 같다. 한 책에서 다루는 내용이 너무나 방대하기에, 이를 다 글에 담는게 너무 어려웠다. 역행자 독후감 하나가 5800자에 달한다. 너무 많은 것을 쓰려고 했던 것이 패인이 아니었나 생각한다. 가볍게, 깨달은 것 하나라도 쓰자는 생각으로 접근해봐야겠다.&lt;/p&gt;</description>
      <category>Writing/회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/73</guid>
      <comments>https://happysisyphe.tistory.com/73#entry73comment</comments>
      <pubDate>Sun, 12 May 2024 22:39:25 +0900</pubDate>
    </item>
    <item>
      <title>React에서 중복호출(aka. 따닥)을 막는 완벽한 방법</title>
      <link>https://happysisyphe.tistory.com/72</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 최근에 퀄리티 높은 프론트엔드 제품을 만드는 것에 관심이 많은데요. 사소해보이는 디테일을 얼마나 능숙히 처리하느냐가 프론트엔드 개발자의 실력 척도 중 하나라고 생각했어요. 저는 여러 원칙들을 세우고 있지만, 오늘은 중복호출 (aka 따닥)을 방지하는 완벽한 방법을 탐구해볼 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제인식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 개발하다가, 중복호출이 발생해서 여러 문제가 발생하는 경우가 있습니다. 결제 요청이 2번 들어갈 수도 있고, 게시물 작성이 2번 될 수도 있고, 댓글이 2번 써질 수도 있습니다. 이로 인해 비즈니스적으로도 영향을 미칠 수도 있습니다. 작게는 서버 에러 수가 많아져서, noisy 해질 수 있죠. 이만큼 중요도가 높고, 프론트엔드 퀄리티에 큰 역할을 한다고 생각했는데요. 실제로 저도 명확한 해결책을 가지고 있지 않았고, 팀원들도 각자 다른 방식으로 해결을 하고 있었어요. 개중에는 결함이 있는 방식도 있었어요. 그래서 여러 방식을 탐구하고, 최적의 방식을 제안하기로 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 글은,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;중복호출을 해결하려는 여러 시도들을 먼저 알아보고,&lt;/li&gt;
&lt;li&gt;각각의 한계를 지적하고,&lt;/li&gt;
&lt;li&gt;더 나은 해결책을 제안하며&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마무리 해볼게요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 테스트 할 것인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅을 위해서는 먼저 테스트 환경을 갖추는 것이 중요합니다. 분석에 들어가기전에, 중복호출을 테스트 하는 방법을 찾아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 테스트 방법이 있겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;손 테스트&lt;/li&gt;
&lt;li&gt;브라우저에서 직접 click 함수 여러번 호출하기&lt;/li&gt;
&lt;li&gt;Cypress 테스트 코드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 손 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 목적은 1ms 안에서도 절대 중복호출이 발생하지 않는 코드를 찾는 것이므로, 1번만을 사용하는 것은 쉽지 않습니다. 아무리 빨리 눌러도 수십 ms 는 될 것이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 브라우저에서 직접 click 함수 여러번 호출하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼, DOM 을 select 해서, 동시에 여러번 click 하게 만드는 것입니다. 브라우저에서 직접 테스트하므로, 아주 정확합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const triggerClick = () =&amp;gt; $0.click();
for (let i = 0; i &amp;lt; 3; i++) {
  triggerClick()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://docs.cypress.io/guides/component-testing/overview&quot;&gt;Cypress Component Testing&lt;/a&gt;&amp;nbsp;테스트 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 DOM을 select 해서 테스트하는 것이 귀찮을 뿐더러, 문서화가 되지 않습니다. 테스트 코드와 함께라면, 테스트 문서를 작성하면서 정확하게 테스트 할 수 있습니다. Cypress를 택한 것은, 브라우저 환경에서의 정확한 재현이 중요하기 때문입니다. testing-library로 해보았는데, 브라우저 모킹 환경이라 중복호출을 정확히 테스트하기 쉽지 않더군요. 이번 글은 Cypress Component Testing&amp;nbsp;과 함께 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cypress-test.png&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LXDzb/btsGEh6LSXO/Yn8rP1Q2ErdYSxwpDB8C11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LXDzb/btsGEh6LSXO/Yn8rP1Q2ErdYSxwpDB8C11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LXDzb/btsGEh6LSXO/Yn8rP1Q2ErdYSxwpDB8C11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLXDzb%2FbtsGEh6LSXO%2FYn8rP1Q2ErdYSxwpDB8C11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;558&quot; data-filename=&quot;cypress-test.png&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;debounce / throttle 의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에 중복호출 방지 라고 검색을 하면, debounce/throttle 을 제안하는 글을 여러개 볼 수 있습니다. 저는 이것의 한계를 말씀드리고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debounce 는 정해진 시간동안 발생한 여러 이벤트 중, 앞(leading) 혹은 뒤(trailing)에 하나의 이벤트만 트리거 시키는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debounce를 react 에 적용한 코드입니다. debounce 는 &lt;a href=&quot;https://github.com/lodash/lodash/blob/main/src/debounce.ts&quot;&gt;lodash.debounce&lt;/a&gt; 를 가지고 왔습니다. Button 컴포넌트의 click handler에 debounce를 래핑한 컴포넌트 입니다. debounce 의 속성 중, leading 을 사용합니다. 사용자의 반응에 즉각적으로 이벤트를 발생시키기 위함입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import debounce from &quot;lodash.debounce&quot;;
import { useMemo } from &quot;react&quot;;

export default function DebounceButton({
  waitMS,
  onClick,
}: {
  waitMS: number;
  onClick: () =&amp;gt; void;
}) {
  const handleClick = useMemo(() =&amp;gt; {
    return debounce(onClick, waitMS, { leading: true, trailing: false });
  }, []);

  return (
    &amp;lt;button type=&quot;button&quot; onClick={handleClick}&amp;gt;
      DebounceButton
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debounce 를 활용한 방식은 해피 케이스에는 문제가 없을 것입니다. 여기서 고민이 되는 부분은 waitMS 를 얼마로 설정할 것이냐 입니다. API 는 보통 1초 안에 끝나니까 1초로? 여유롭게 3초로? 이런 직관으로 정할 순 없겠죠. API latency는 서버, DB 상황에 따라서 언제나 달라질 수 있습니다. 이를 고정한다는 것은 엄밀하지 않은 사고입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debounce 를 사용하면, 2가지 케이스가 발생한다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;api latency &amp;lt; debounce wait&lt;/li&gt;
&lt;li&gt;api latency &amp;gt; debounce wait&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 케이스부터 보겠습니다. api가 빠르게 응답이 온다면, 보통의 경우에 큰 문제가 없습니다. 문제가 없는 경우는, API 가 성공했을 때입니다. API 가 성공해서 다음 유저 플로우를 타게 된다면, button disabled 시간(debounce wait - api latency)이 존재해도 문제가 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 가 실패하면 어떻게 될까요? 일정 시간(debounce wait - api latency) 만큼, 사용자는 버튼을 다시 누르지 못 합니다. 그러므로 중복호출을 막기 위한 목적으로 함부로 debounce wait 를 길게 해서는 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 케이스를 보겠습니다. api latency 가 더 길다면, 정말로 문제 입니다. debounce wait 가 끝난다면, 다시 clickable 한 상태가 되고, 서버에 중복호출을 할 수 있습니다. 이때는 서버에서 중복호출을 막고 있길 기도해야겠지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 cypress 테스트 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;it(&quot;api latency가 debounce wait 보다 길 때, handler가 2회 이상 호출될 수 있다.&quot;, async () =&amp;gt; {
  let handlerCalledTimes = 0;
  const handleClick = async () =&amp;gt; {
    handlerCalledTimes = handlerCalledTimes + 1;
    await delay(2000);
  };

  cy.mount(&amp;lt;DebounceButton waitMS={1000} onClick={handleClick} /&amp;gt;);
  const button = cy.get(&quot;button&quot;);
  button.dblclick();

  cy.wait(1100);

  button.dblclick();
  cy.wrap(null).then(() =&amp;gt; {
    expect(handlerCalledTimes).to.be.greaterThan(1);
  });
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면, api latency 과 debounce wait 가 차이가 있기 때문에, debounce로 완벽한 중복호출을 막는 것은 본질적으로 불가능하다는 것입니다. throttle 도 마찬가지 논리이므로 생략합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 debounce, throttle 의 목적은 무엇일까요? 이들은 &amp;ldquo;중복호출&amp;rdquo;을 막기 위함이 아니라, &amp;ldquo;과도한 호출&amp;rdquo; 을 막기 위함입니다. 검색, 광클이 가능한 버튼 (게임 아이템 주기 등), 스크롤 이벤트 제어 등에 쓰입니다. 이를 중복 호출 방지에 쓰는 것은 적절하지 않습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;isLoading 상태를 활용한 분기의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복호출 방지를 검색해보면, isLoading 상태를 활용한 예시도 나옵니다. 실제로 회사에서도 많이 쓰이는 방식입니다. 예시 코드를 보겠습니다. handler가 호출되면, isLoading 상태를 true로 만들고, promise 가 settled 되면, isLoading 을 다시 false로 되돌립니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function LoadingStateButton({ onClick }: { onClick: () =&amp;gt; Promise&amp;lt;void&amp;gt; }) {
  const [isLoading, setIsLoading] = useState(false);

  return (
    &amp;lt;button
      type=&quot;button&quot;
      onClick={async () =&amp;gt; {
        if (isLoading) {
          return;
        }
        setIsLoading(true);
        await onClick();
        setIsLoading(false);
      }}
    &amp;gt;
      LoadingButton
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 문제가 없어보일 수 있습니다. 하지만 setState의 본질을 이해하면, 이는 문제가 됨을 짐작할 수 있습니다. react state는 성능 관리를 위해서, 일정 요청을 모아서 batch 로 상태를 업데이트 합니다. (&lt;a href=&quot;https://react.dev/reference/react/Component#setstate-caveats&quot;&gt;Reference&lt;/a&gt;) 즉, setState는 비동기 요청입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 예시로 설명하면, setIsLoading(true) 는 호출되는 시점에 바로 isLoading 상태를 수정하지 않습니다. 조건이 충족되었을 때 비동기로 update 합니다. 즉, 그 transition 기간 동안, click이 한번 더 발생한다면, 중복호출이 발생할 수 있습니다. 저는 실제로 이 방식을 활용하여 많은 중복호출 에러를 맞았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 cypress 테스트 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;it(`button 을 연속으로 2회 클릭하면, handler도 2회 호출된다.`, () =&amp;gt; {
  let handlerCalledTimes = 0;
  const handleClick = async () =&amp;gt; {
    handlerCalledTimes = handlerCalledTimes + 1;
    await delay(2000);
  };

  cy.mount(&amp;lt;LoadingStateButton onClick={handleClick} /&amp;gt;);
  const button = cy.get(&quot;button&quot;);
  button.dblclick();

  cy.wrap(null).then(() =&amp;gt; {
    expect(handlerCalledTimes).to.equal(2);
  });
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 isLoading 상태를 활용한 해결책도 근본적으로 중복호출을 막아주지 못 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Solution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 대체 어떻게 해야 근본적으로 중복호출을 막을 수 있을까요? 상태처럼 비동기 업데이트가 아니라, 즉각적인 업데이트 방식이 필요합니다. react에서 즉각적인 변수값 변경을 위해서 사용하는 것은 useRef 입니다. Ref 값은 상태는 아니기 때문에 re-render는 발생시키지 않으면서, 참조값을 즉시 변경시킵니다. 그러므로 중복호출을 완벽히 가드할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드를 살펴보겠습니다. isLoading 을 useRef로 선언하여, onClick 의 중복호출을 막고 있습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function LoadingRefButton({ onClick }: { onClick: () =&amp;gt; Promise&amp;lt;void&amp;gt; }) {
  const isLoadingRef = useRef(false);

  return (
    &amp;lt;button
      type=&quot;button&quot;
      disabled={isLoadingRef.current}
      onClick={async () =&amp;gt; {
        if (isLoadingRef.current) {
          return;
        }
        isLoadingRef.current = true;
        await onClick();
        isLoadingRef.current = false;
      }}
    &amp;gt;
      LoadingRefButton
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것의 단점은 무엇일까요? 위에 disabled 를 isLoadingRef.current 값으로 넣었는데요. 이게 정상적으로 동작할까요? 상술했듯, ref 변수 변화는, 상태 변화가 아니기 때문에 re-render를 유발하지 않습니다. 그러므로, button 컴포넌트가 다시 그려지지 않기 때문에, disabled가 true가 된 button UI 를 반영하지 못 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 더 나은 해결책을 떠올려봅시다. 완벽한 방어를 위한 조건은 아래 두가지입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;re-render를 유발한다.&lt;/li&gt;
&lt;li&gt;즉각적인 변수값 변경으로, 중복호출을 완전히 방어한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번을 위해서는 상태를 도입해야만 하고, 2번을 위해서는 useRef를 사용하거나, 즉각적인 상태 변경이 필요합니다. Advanced Solution 2가지를 살펴보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Advanced Solution 1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref 값과 상태를 모두 사용하여 해결하는 예시입니다. ref는 즉각적인 변수값 변경을 위해서 사용하고, 상태는 re-render 만을 위해 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function LoadingRefWithRenderButton({
  onClick,
}: {
  onClick: () =&amp;gt; Promise&amp;lt;void&amp;gt;;
}) {
  const isLoadingRef = useRef(false);
  const reRender = useReRenderer();

  return (
    &amp;lt;button
      type=&quot;button&quot;
      disabled={isLoadingRef.current}
      onClick={async () =&amp;gt; {
        if (isLoadingRef.current) {
          return;
        }
        isLoadingRef.current = true;
        reRender();
        await onClick();
        isLoadingRef.current = false;
        reRender();
      }}
    &amp;gt;
      LoadingRefButton
    &amp;lt;/button&amp;gt;
  );
}

function useReRenderer() {
  const [, setState] = useState({});
  return useCallback(() =&amp;gt; setState({}), []);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, disabled 상태를 반영하면서, 중복호출을 원천 차단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 cypress 테스트 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;it(`button 을 2회 연속 클릭해도, handler는 1회 호출된다.`, () =&amp;gt; {
  let handlerCalledTimes = 0;
  const handleClick = async () =&amp;gt; {
    handlerCalledTimes = handlerCalledTimes + 1;
    await delay(2000);
  };

  cy.mount(&amp;lt;LoadingRefReRenderButton onClick={handleClick} /&amp;gt;);
  const button = cy.get(&quot;button&quot;);
  button.dblclick();

  cy.wrap(null).then(() =&amp;gt; {
    expect(handlerCalledTimes).to.equal(1);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Advanced Solution 2&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변경을 비동기로 실행하지 않고, 동기적으로 실행하는 방법은 없을까요? 이를 위해서 react18 에서 &lt;a href=&quot;https://react.dev/reference/react-dom/flushSync&quot;&gt;flushSync&lt;/a&gt; API 가 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;flushSync lets you force React to flush any updates inside the provided callback synchronously. This ensures that the DOM is updated immediately.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 setIsLoading(true) 인 부분만 flushSync로 감싸줍니다. flushSync는 react 의 최적화된 성능관리를 거스르고 re-render 를 강제로 유발하는 API 이기 때문에, 권장되지 않습니다. 그러므로 사용을 최소화 하기 위해서 setIsLoading(false) 에는 flushSync를 사용하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function LoadingStateButton({ onClick }: { onClick: () =&amp;gt; Promise&amp;lt;void&amp;gt; }) {
  const [isLoading, setIsLoading] = useState(false);

  return (
    &amp;lt;button
      type=&quot;button&quot;
      disabled={isLoading}
      onClick={async () =&amp;gt; {
        if (isLoading) {
          return;
        }
        flushSync(() =&amp;gt; setIsLoading(true));
        await onClick();
        setIsLoading(false);
      }}
    &amp;gt;
      LoadingButton
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 react18 이상에만 적용되는 방법이므로, 범용적인 라이브러리 사용하는 것은 권장되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 cypress 예시 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;it(`button 을 2회 연속 클릭해도, handler는 1회 호출된다.`, () =&amp;gt; {
  let handlerCalledTimes = 0;
  const handleClick = async () =&amp;gt; {
    handlerCalledTimes = handlerCalledTimes + 1;
    await delay(1000);
    await delay(1000);
  };

  cy.mount(&amp;lt;LoadingSyncStateButton onClick={handleClick} /&amp;gt;);
  const button = cy.get(&quot;button&quot;);
  button.dblclick();

  cy.wrap(null).then(() =&amp;gt; {
    expect(handlerCalledTimes).to.equal(1);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 react에서 중복호출을 막기 위한 여러 방법들을 알아보았습니다. 흔히 사용되는 방법의 한계를 지적하고, 새로운 방법을 제안해보았습니다. 이를 근본적으로 이해하기 위해, 결국 react에 대한 이해가 많이 필요했습니다. react에 대한 이해만 있다면, 결론 자체는 어렵지 않았던 것 같습니다. react의 상태와 ref에 대한 이해를 기반으로 동작을 유추하는 과정, 테스트를 통해 재현 환경을 구성하고 명확한 결론을 이끌어내는 과정이 의미있었습니다. 이번 글과 함께 한층 더 완성도 있는 서비스를 만들 수 있길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 코드는 아래에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/euijinkk/prevent-duplicate-call/tree/main&quot;&gt;https://github.com/euijinkk/prevent-duplicate-call/tree/main&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/Sofeware Development</category>
      <category>cypress</category>
      <category>flushSync</category>
      <category>react</category>
      <category>useRef</category>
      <category>중복호출</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/72</guid>
      <comments>https://happysisyphe.tistory.com/72#entry72comment</comments>
      <pubDate>Sun, 14 Apr 2024 23:17:37 +0900</pubDate>
    </item>
    <item>
      <title>왜 사람마다 생각의 양과 질에서 차이가 나는가?</title>
      <link>https://happysisyphe.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;사람들을 만나고 얘기하다 보면 드는 생각이 있다. 사람마다 생각의 양과 질의 관점에서 차이가 있다는 것이다. 이를테면 특정 현상을 보고 어떤 이는 A라는 것을 느낄 수 있지만, 어떤 이는 A를 아예 생각조차 할 수 없다. 그리고 특정 현상을 보고 어떤 이는 피상적인 생각을 떠올리고, 어떤 이는 구체적이고 깊은 생각을 할 수 있다. 이 격차는 사실 모든 차이를 결정한다. 직업의 차이, 봉급의 차이, 풍요로움의 차이, 의사결정의 차이, 현명함의 차이 등. 생각의 격차가 태어나면서 모두 결정되는 것은 당연히 아니다. 후천적으로 충분히 개발될 수 있는 영역이다. 이 글에서는 어떻게 생각의 격차가 발생하게 되는지 고찰해본다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;나는 21살에 내가 무시받는게 상당히 싫었다. 그때의 나는 (어리석을 수 있지만) 이미 저 선배의 생각 수준을 넘어선 것 같은데, 나이가 적다는 이유로 무시받는 것은 이치에 맞지 않다고 생각했다. 아무리 생각해도, 나이란 전혀 중요한 지표가 아니었다. 7년이 지난 지금은 이 사실을 더욱 확신할 수 있게 되었다. 7년간 그 생각의 격차는 걷잡을 수 없이 커져서, 이제는 생물학적 나이의 우위로 어떤 것도 누를 수 없게 되었다. 시간의 차이는 생각의 지수적인 격차를 낳았다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;본론으로 돌아와서, 생각의 격차는 어떻게 발생하는지 논의를 전개해보자. 결론부터 말하면, 생각 격차의 원인은 &amp;ldquo;&lt;/span&gt;&lt;b&gt;깨달음 차이의 복리적 영향&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;rdquo; 이라고 표현하고 싶다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;백지 상태의 두 아이 민수와 철수를 생각해보자. 민수는 어떤 계기로 A라는 깨달음을 얻게 되었다. 철수는 A를 알지 못 한다. 그 이후로, 민수는 세상을 바라볼 때, A를 느낄 수 있게 되었다. 반면에 철수는 세상을 뚫어져라 쳐다봐도 A를 느끼지 못 한다. 민수는 A를 알기 때문에, B로의 사고를 확장할 수 있었다. 이제 세상을 바라볼 때, A와 B를 느낄 수 있다. 이 A, B를 가지고 또 C, D 를 알게 되었다. 그리고 다음에는 E, F, G, H 까지. 그리고 철수는 여전히 백지의 상태에 머물러 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;위 예시를 기반으로, 일반화된 이론을 전개해보자. 하나의 깨달음을 얻는다는 것을 엄청난 것을 의미한다. 20살에 어떤 깨달음을 얻었다면, 그것은 수십년간 그것과 관련된 것을 느끼고 사고할 수 있다는 것을 의미한다. 그리고 그 주변부로 사고를 확장해나갈 수 있음을 의미한다. 어떤 것을 깨닫지 못 했다면, 평생 그것을 볼 수 없는 것이다. 주변으로 사고를 확장할 수도 없다. 즉, 하나의 깨달음의 차이는 복리로서 작용하여, 종래에 지수적인 차이를 낳는 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;깨달음을 얻는다는 것은 하나의 씨앗을 심는 것과 같다. 그 씨앗은 무럭무럭 자라 큰 식물이 될 것이며, 나중에 더 많은 씨앗을 만들어낼 것이다. 씨앗을 심지 못하고 있다면, 시간이 지남에 따라, 씨앗을 심은 사람과의 격차는 더욱 커질 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;심리학을 예시로 들어보자. 심리학이라는 씨앗을 심고 나면 무슨 일이 벌어지는가? 먼저 주변인의 몇몇 행동 동기를 더욱 깊이 이해할 수 있다. 씨앗이 커져서 식물이 되면 어떻게 되는가? 나의 모든 행동, 주변인의 모든 행동을 이해할 수 있게 된다. 그리고 그 식물은, 마케팅, 경영, 진화론, 디자인 이라는 씨앗을 낳는다. 어떤 씨앗에게 물을 더 주며 키워낼지는 주인이 판단하는 것이다. 그 과정을 반복하다 보면, 풍요로운 식물원을 이룰 것이고, 누군가의 삭막한 사막과의 격차는 이제 걷잡을 수 없을 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;그렇다면 그 씨앗, 즉 깨달음은 어떻게 얻는가? 당연하게도 경험과 지식을 통해서만 얻을 수 있다. 동일한 경험을 한다고 해서, 깨달음이 많이 생길까? 아니다. 노하우를 기반으로 속도 개선을 이룰 수는 있겠지만, 변화를 만들기엔 부족하다. &amp;ldquo;새로운&amp;rdquo; 경험과 지식이 기반이 되어야 한다. 새로운 경험과 지식은 어떻게 얻는가? 책, 공부, 여행, 사랑, 우정 등 모든 것에서 얻을 수 있다. 자신이 책도 읽지 않고, 공부도 하지 않고, 여행도 가지 않고, 동일한 방식으로 사랑과 우정을 유지하고 있다면, 새로운 경험과 지식은 거의 없을 것이다. 또는 같은 장르의 책만 읽고, 같은 분야의 공부만 하고, 같은 장소로만 여행을 가고, 같은 사람만을 만나는 것도 마찬가지로 새로움의 측면에서 빈약할 것이다. 하나의 나무를 높이 세우는 것에 집중하는 것도 중요하지만, 주변의 다른 식물 없이 하나의 식물만 우뚝 서는 일은 굉장히 어렵다. 여러 씨앗을 남기고, 여러 식물을 키우며 지반을 다지고, 상호작용이 이루어져야 더 높이 자랄 수 있기 마련이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;결국 새로운 것을 많이 경험하는 것을 강조하고 싶다. 그전에 자신이 편식하고 있진 않은지 - 인간관계, 지식, 경험 등에서 - 객관적으로 인지하는 것은 매우 중요하다. 왜냐하면, 편식은 보통 암묵적으로 이루어지기 때문이다. 의식하지 않는다면 우린 편식하게 될 것이다. 그러므로 의식적으로 새로운 것을 시도하려고 노력해야 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/71</guid>
      <comments>https://happysisyphe.tistory.com/71#entry71comment</comments>
      <pubDate>Sun, 18 Feb 2024 23:01:28 +0900</pubDate>
    </item>
    <item>
      <title>React CleanCode #2. UI Variation에 유연하게 대응하기</title>
      <link>https://happysisyphe.tistory.com/70</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ui-variation.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A0jb8/btsD3Hg1282/pwosr4WIgRy8V1iaxnxMVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A0jb8/btsD3Hg1282/pwosr4WIgRy8V1iaxnxMVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A0jb8/btsD3Hg1282/pwosr4WIgRy8V1iaxnxMVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA0jb8%2FbtsD3Hg1282%2Fpwosr4WIgRy8V1iaxnxMVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;408&quot; height=&quot;331&quot; data-filename=&quot;ui-variation.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;서언&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. &lt;b&gt;React CleanCode&lt;/b&gt; 2번째 시리즈로 찾아왔습니다. 저는 토스 공동구매 팀에서 약 1년간 커머스 제품을 운영해왔는데요. 제품이 디벨롭 되면서, 상품 컴포넌트는 더욱 복잡해지고, 많은 Variation 이 생겼어요. 그러면서 일명 스파게티 코드를 만들게 되었어요. 사용처에서 유연하게 사용이 어렵고, 장애 확률이 높고, 새로운 기능 추가가 어려웠어요. 이를 유지보수하기 쉬운 코드로 리팩터링한 경험을 공유하려고 해요. UI Variation에 고통 받고 계신 분들이 읽으면 도움이 될 것입니다.&lt;/p&gt;
&lt;h1&gt;목차&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컴포넌트가 스파게티가 되어가는 과정&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개선 방향성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개선&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;언제 사용하면 좋은가요?&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부록 : Compound component pattern 인가요?&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;컴포넌트가 스파게티가 되어가는 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 망해가는 과정을 Step1 부터 Step5 까지 천천히 설명해볼거에요. 전체적인 흐름을 파악하시면, 좋을거에요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 1. 제품 개발 2개월 차&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 유일한 상품 UI가 있었어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (29).png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkm27Y/btsD3dtLsn9/KYzQvgmwkTdGnhaQX1CFW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkm27Y/btsD3dtLsn9/KYzQvgmwkTdGnhaQX1CFW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkm27Y/btsD3dtLsn9/KYzQvgmwkTdGnhaQX1CFW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkm27Y%2FbtsD3dtLsn9%2FKYzQvgmwkTdGnhaQX1CFW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;275&quot; data-filename=&quot;Untitled (29).png&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일한 UI에 맞게, 컴포넌트에 대해서도 크게 고민할 일이 없었답니다. 아래처럼 코드를 짜보았고, 누가 코드를 짜든 아마 큰 차이가 없을 거에요.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Product({ product }) {
  const {
    isWished,
    productId,
    imageUrl1X1,
    originPrice,
    price,
    productName,
    viewCount,
  } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;

  return (
    &amp;lt;Flex direction=&quot;column&quot;&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img aspectRatio=&quot;1 / 1&quot; src={imageUrl1X1} /&amp;gt;
        &amp;lt;Position type=&quot;absoulte&quot; right={12} bottom={12}&amp;gt;
          {isWished ? (
            &amp;lt;Icon
              name=&quot;heart-red&quot;
              onClick={() =&amp;gt; {
                deleteWishList(productId);
              }}
            /&amp;gt;
          ) : (
            &amp;lt;Icon
              name=&quot;heart-blank&quot;
              onClick={() =&amp;gt; {
                addWishList(productId);
              }}
            /&amp;gt;
          )}
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;Txt&amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt&amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt ellipsisAfterLines={2}&amp;gt;{productName}&amp;lt;/Txt&amp;gt;
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt&amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 2. 리뷰 기능의 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품 개발 3개월쯤 된 시점에, 리뷰 기능이 나왔어요. 그리고 &amp;ldquo;리뷰가 많은 상품&amp;rdquo; 섹션이 생겼고, 여기에 있는 상품 컴포넌트는 별점을 보여주어야 했어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (30).png&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/40wm4/btsD9Cepgnt/QdjypX0gAKPrTqVqHcXcZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/40wm4/btsD9Cepgnt/QdjypX0gAKPrTqVqHcXcZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/40wm4/btsD9Cepgnt/QdjypX0gAKPrTqVqHcXcZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F40wm4%2FbtsD9Cepgnt%2FQdjypX0gAKPrTqVqHcXcZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;315&quot; data-filename=&quot;Untitled (30).png&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 코드로 옮겨볼게요. &lt;b&gt;showReview&lt;/b&gt; 라는 props 를 추가했어요. 지금까지는 크게 이상하다고 느껴지진 않습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Product({ product, showReview }) {
  const {
    imageUrl1X1,
    originPrice,
    price,
    productName,
    viewCount,
    reviewRating,
    isWished,
    productId,
  } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;

  return (
    &amp;lt;Flex direction=&quot;column&quot;&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img aspectRatio=&quot;1 / 1&quot; src={imageUrl1X1} /&amp;gt;
        &amp;lt;Position type=&quot;absoulte&quot; right={12} bottom={12}&amp;gt;
          {isWished ? (
            &amp;lt;Icon
              name=&quot;heart-red&quot;
              onClick={() =&amp;gt; {
                deleteWishList(productId);
              }}
            /&amp;gt;
          ) : (
            &amp;lt;Icon
              name=&quot;heart-blank&quot;
              onClick={() =&amp;gt; {
                addWishList(productId);
              }}
            /&amp;gt;
          )}
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;Txt&amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt&amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt ellipsisAfterLines={2}&amp;gt;{productName}&amp;lt;/Txt&amp;gt;
      {showReview === true &amp;amp;&amp;amp; &amp;lt;StarRating rating={reviewRating} /&amp;gt;}
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt&amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 3. 5X2 이미지 의 등장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품이 디벨롭 됨에 따라, 광고 구좌 등을 위해서 하나의 Row를 모두 차지하는 상품 컴포넌트가 생겼어요. 5X2 이미지를 대응해줘야 했어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (31).png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxbGhk/btsD3K5HkyE/XaPMkscwSkmaYuMZZ98aGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxbGhk/btsD3K5HkyE/XaPMkscwSkmaYuMZZ98aGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxbGhk/btsD3K5HkyE/XaPMkscwSkmaYuMZZ98aGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxbGhk%2FbtsD3K5HkyE%2FXaPMkscwSkmaYuMZZ98aGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;295&quot; data-filename=&quot;Untitled (31).png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 급한대로 &lt;b&gt;imageRatio&lt;/b&gt; 라는 props 를 추가했어요. imageRatio가 1 / 1 이냐 5 / 2 이냐에 따라서 다른 이미지를 사용하도록 분기문을 추가했어요. 점점 Product 컴포넌트의 역할이 많아지고 있네요. 점점 스파게티 냄새가 납니다. 이때 수정했어야 했죠. 하지만 일은 더욱 커집니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Product({ product, showReview, imageRatio }) {
  const {
    isWished,
    productId,
    imageUrl1X1,
    originPrice,
    price,
    productName,
    viewCount,
    reviewRating,
    imageUrl5X2,
  } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;
  const imageUrl = imageRatio === &quot;1 / 1&quot; ? imageUrl1X1 : imageUrl5X2;

  return (
    &amp;lt;Flex direction=&quot;direction&quot;&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position type=&quot;absoulte&quot; right={12} bottom={12}&amp;gt;
          {isWished ? (
            &amp;lt;Icon
              name=&quot;heart-red&quot;
              onClick={() =&amp;gt; {
                deleteWishList(productId);
              }}
            /&amp;gt;
          ) : (
            &amp;lt;Icon
              name=&quot;heart-blank&quot;
              onClick={() =&amp;gt; {
                addWishList(productId);
              }}
            /&amp;gt;
          )}
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;Txt&amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt&amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt ellipsisAfterLines={2}&amp;gt;{productName}&amp;lt;/Txt&amp;gt;
      {showReview === true &amp;amp;&amp;amp; &amp;lt;StarRating rating={reviewRating} /&amp;gt;}
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt&amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 4. 상품 정보가 좌우로 배치되는 컴포넌트의 등장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 이미지와 정보가, 수직이 아니라 수평으로 배치되는 형태가 생겼어요. UI의 Variation이 유저의 discovery 흥미를 돋군다는 가설이 채택됨에 따라서, UI의 Variation은 더욱 많아졌어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (32).png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chuSnK/btsD3e7g8fG/YQOhjssJv6dVMm0wBgKrxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chuSnK/btsD3e7g8fG/YQOhjssJv6dVMm0wBgKrxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chuSnK/btsD3e7g8fG/YQOhjssJv6dVMm0wBgKrxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchuSnK%2FbtsD3e7g8fG%2FYQOhjssJv6dVMm0wBgKrxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;169&quot; data-filename=&quot;Untitled (32).png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 더 심각성을 느낀 것은, 변화해야할 것은 좌우 배치 뿐만 아니었어요. 이 경우 이미지가 작아서 찜버튼의 위치를 right, bottom 8px 으로 만들어야 했어요. 기존에는 12px 이었는데 말이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 구현해볼게요. &lt;b&gt;direction&lt;/b&gt; props 를 추가해볼게요. direction 을 분기로, 찜버튼의 위치를 결정했어요.이런 로직까지, Product 가 결정해야 한다니, 더 이상 견디기 어렵지만, 제품 개발이 더욱 중요하니 일단 빠르게 구현하고 넘어갔습니다. 빠르게 없어질 수도 있는 UI 이니까요.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Product({ product, showReview, imageRatio, direction }) {
  const {
    isWished,
    productId,
    imageUrl1X1,
    originPrice,
    price,
    productName,
    viewCount,
    imageUrl5X2,
    reviewRating,
  } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;
  const imageUrl = imageRatio === &quot;1 / 1&quot; ? imageUrl1X1 : imageUrl5X2;

  return (
    &amp;lt;Flex direction={direction}&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position
          type=&quot;absoulte&quot;
          right={direction === &quot;column&quot; ? 12 : 8}
          bottom={direction === &quot;column&quot; ? 12 : 8}
        &amp;gt;
          {isWished ? (
            &amp;lt;Icon
              name=&quot;heart-red&quot;
              onClick={() =&amp;gt; {
                deleteWishList(productId);
              }}
            /&amp;gt;
          ) : (
            &amp;lt;Icon
              name=&quot;heart-blank&quot;
              onClick={() =&amp;gt; {
                addWishList(productId);
              }}
            /&amp;gt;
          )}
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;Txt&amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt&amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt ellipsisAfterLines={2}&amp;gt;{productName}&amp;lt;/Txt&amp;gt;
      {showReview === true &amp;amp;&amp;amp; &amp;lt;StarRating rating={reviewRating} /&amp;gt;}
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt&amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 5. 미니 미니 상품 UI의 등장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 하나의 Row에 1개나 2개가 들어갔는데요. 이번에는 3개가 들어가야 하면서, 작은 상품 UI가 생겼어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (33).png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGwPmw/btsD1aYM98E/xnJTcRLZF0OT57kFeXM3oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGwPmw/btsD1aYM98E/xnJTcRLZF0OT57kFeXM3oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGwPmw/btsD1aYM98E/xnJTcRLZF0OT57kFeXM3oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGwPmw%2FbtsD1aYM98E%2FxnJTcRLZF0OT57kFeXM3oK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;264&quot; data-filename=&quot;Untitled (33).png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 3가지 분기가 필요했어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;할인율 / 가격의 direction이 column 이다.&lt;/li&gt;
&lt;li&gt;폰트 사이즈가 더 작다.&lt;/li&gt;
&lt;li&gt;상품명이 1줄로 되고, overflow는 ellipsis 다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 더 이상 현재 구조로 받을 수 없다는 것을 깨달았어요. 각 요소의 fontSize 까지, Product 컴포넌트에서 컨트롤 해야 한다니. 그래서 시간을 조금 더 벌고, 상품 컴포넌트를 전체적으로 리팩터링 하기로 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 리팩터링 하지 않고 이 요구사항을 충족하려면 어떤 형태가 되어야할지 시뮬레이션 해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Version1. size props 뚫기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;size props 를 뚫어서, size에 따라서 fontSize를 다르게 주는 분기를 추가해볼게요.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Product({ product, showReview, imageRatio, direction, size }) {
  const {
    isWished,
    productId,
    imageUrl1X1,
    originPrice,
    price,
    productName,
    viewCount,
    imageUrl5X2,
    reviewRating,
  } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;
  const imageUrl = imageRatio === &quot;1 / 1&quot; ? imageUrl1X1 : imageUrl5X2;

  return (
    &amp;lt;Flex direction={direction}&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position
          type=&quot;absoulte&quot;
          right={direction === &quot;column&quot; ? 12 : 8}
          bottom={direction === &quot;column&quot; ? 12 : 8}
        &amp;gt;
          {isWished ? (
            &amp;lt;Icon
              name=&quot;heart-red&quot;
              onClick={() =&amp;gt; {
                deleteWishList(productId);
              }}
            /&amp;gt;
          ) : (
            &amp;lt;Icon
              name=&quot;heart-blank&quot;
              onClick={() =&amp;gt; {
                addWishList(productId);
              }}
            /&amp;gt;
          )}
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;Txt
          fontSize={size === &quot;medium&quot; ? &quot;17px&quot; : &quot;14px&quot;}
        &amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt
          fontSize={size === &quot;medium&quot; ? &quot;17px&quot; : &quot;14px&quot;}
        &amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt
        fontSize={size === &quot;medium&quot; ? &quot;15px&quot; : &quot;13px&quot;}
        ellipsisAfterLines={size === &quot;medium&quot; ? &quot;2&quot; : &quot;1&quot;}
      &amp;gt;
        {productName}
      &amp;lt;/Txt&amp;gt;
      {showReview === true &amp;amp;&amp;amp; &amp;lt;StarRating rating={reviewRating} /&amp;gt;}
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt
          fontSize={size === &quot;medium&quot; ? &quot;14px&quot; : &quot;12px&quot;}
        &amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 분기가 너무 많아졌습니다. large size 까지 생긴다면? 이제 이 컴포넌트는 더 이상 기능을 추가하기 어려운 컴포넌트가 되었어요. 다른 방식으로 해볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Version 2. SmallProduct, MediumProduct 두벌을 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 복붙하여, 사이즈에 따른 두벌의 컴포넌트를 만들어볼게요.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function SmallProduct({ product, showReview, imageRatio, direction }) {
  const {
    isWished,
    productId,
    imageUrl1X1,
    originPrice,
    price,
    productName,
    viewCount,
    imageUrl5X2,
    reviewRating,
  } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;
  const imageUrl = imageRatio === &quot;1 / 1&quot; ? imageUrl1X1 : imageUrl5X2;
  return (
    &amp;lt;Flex direction={direction}&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position
          type=&quot;absoulte&quot;
          right={direction === &quot;column&quot; ? 12 : 8}
          bottom={direction === &quot;column&quot; ? 12 : 8}
        &amp;gt;
          {isWished ? (
            &amp;lt;Icon
              name=&quot;heart-red&quot;
              onClick={() =&amp;gt; {
                deleteWishList(productId);
              }}
            /&amp;gt;
          ) : (
            &amp;lt;Icon
              name=&quot;heart-blank&quot;
              onClick={() =&amp;gt; {
                addWishList(productId);
              }}
            /&amp;gt;
          )}
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;column&quot;&amp;gt;
        &amp;lt;Txt fontSize={&quot;14px&quot;}&amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt fontSize={&quot;14px&quot;}&amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt fontSize={&quot;13px&quot;} ellipsisAfterLines={1}&amp;gt;
        {productName}
      &amp;lt;/Txt&amp;gt;
      {showReview === true &amp;amp;&amp;amp; &amp;lt;StarRating rating={reviewRating} /&amp;gt;}
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt fontSize={&quot;12px&quot;}&amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}

function MediumProduct({ product, showReview, imageRatio, direction }) {
  const { imageUrl1X1, originPrice, price, productName, viewCount } = product;
  const discountRate = calculateDiscountRate({ originPrice, price });
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;
  const imageUrl = imageRatio === &quot;1 / 1&quot; ? imageUrl1X1 : imageUrl5X2;

  return (
    &amp;lt;Flex direction={direction}&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position
          type=&quot;absoulte&quot;
          right={direction === &quot;column&quot; ? 12 : 8}
          bottom={direction === &quot;column&quot; ? 12 : 8}
        &amp;gt;
          &amp;lt;WishButton /&amp;gt;
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;Txt fontSize={&quot;17px&quot;}&amp;gt;{`${discountRate}%`}&amp;lt;/Txt&amp;gt;
        &amp;lt;Txt fontSize={&quot;17px&quot;}&amp;gt;{`${price.toLocaleString()}원`}&amp;lt;/Txt&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;Txt fontSize={&quot;15px&quot;} ellipsisAfterLines={2}&amp;gt;
        {productName}
      &amp;lt;/Txt&amp;gt;
      {showReview === true &amp;amp;&amp;amp; &amp;lt;StarRating rating={reviewRating} /&amp;gt;}
      {showViewCount === true &amp;amp;&amp;amp; (
        &amp;lt;Txt fontSize={&quot;14px&quot;}&amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
      )}
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MediumProduct, SmallProduct의 로직은 더욱 단순화 되었어요. 이 방향이 좀 더 괜찮아 보입니다. 하지만 여전히 문제가 있어요. 로직이 변경되면, 두 곳을 건드려야 해요. 할인율을 소수점 한자리까지 표현하라고 바뀐다면? viewCount를 일의 자리는 버리고 보여줘야 한다면? 등의 요구사항이 생겼을 때, 유지보수가 어려워지죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 컴포넌트가 어떻게 스파게티가 되는지 알아보았어요. 기획/디자인 발전 방향에 따라서, 최대한 빠르게 대응한 것이라 어쩔 도리가 없긴 했습니다. 하지만, 이대로 두어서는 앞으로는 생산성이 크게 저하되고, 장애 확률이 높아질 거에요. 이를 리팩터링하여 생산성을 높이고, 유지보수 하기 좋고, 확장 가능한 방향으로 만들어볼 거에요.&lt;/p&gt;
&lt;h1&gt;개선 방향성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 방향성을 잡아보겠습니다. 어떤 조건을 충족시켜야 할까요? 몇가지 목표를 세워보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;반복되는 코드는 single source of truth를 유지하고, 재사용 가능하게 만든다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포넌트 각자가 독립적으로 책임을 가지도록 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용처에 따라서, 독립적인 컴포넌트를 조합하여 유연하게 사용할 수 있도록 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 하위 컴포넌트가 각자의 로직을 가지는 별도 컴포넌트로 만들고, 사용처에 따라서 하위 컴포넌트를 조합하여 사용해볼 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 프로토타입부터 만들어보고, 하나하나 개선해보도록 할게요.&lt;/p&gt;
&lt;h1&gt;개선&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 간단한 프로토타입을 살펴보겠습니다. 사용처에 따라서 아래와 같이 컴포넌트를 분리할 것입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function MediumRowProduct({ product }) {
  return (
    &amp;lt;Flex direction={&quot;row&quot;}&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;ProductImage imageRatio={&quot;1 / 1&quot;} /&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position type=&quot;absoulte&quot; right={8} bottom={8}&amp;gt;
          &amp;lt;WishButton /&amp;gt;
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;DiscountRate fontSize={&quot;medium&quot;} /&amp;gt;
        &amp;lt;Price fontSize={&quot;medium&quot;} /&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;ProductName fontSize={&quot;medium&quot;} ellipsisAfterLines={1} /&amp;gt;
      &amp;lt;ViewCount fontSize={&quot;medium&quot;} /&amp;gt;
    &amp;lt;/Flex&amp;gt;
  );
}

function MediumColumnProduct({ product }) {
  return (
    &amp;lt;Flex direction={&quot;column&quot;}&amp;gt;
      &amp;lt;Position type=&quot;relative&quot;&amp;gt;
        &amp;lt;ProductImage imageRatio={&quot;1 / 1&quot;} /&amp;gt;
        &amp;lt;Img src={imageUrl} /&amp;gt;
        &amp;lt;Position type=&quot;absoulte&quot; right={12} bottom={12}&amp;gt;
          &amp;lt;WishButton /&amp;gt;
        &amp;lt;/Position&amp;gt;
      &amp;lt;/Position&amp;gt;
      &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
        &amp;lt;DiscountRate fontSize={&quot;medium&quot;} /&amp;gt;
        &amp;lt;Price fontSize={&quot;medium&quot;} /&amp;gt;
      &amp;lt;/Flex&amp;gt;
      &amp;lt;ProductName fontSize={&quot;medium&quot;} ellipsisAfterLines={1} /&amp;gt;
      &amp;lt;ViewCount fontSize={&quot;medium&quot;} /&amp;gt;
    &amp;lt;/Flex&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 Product가 모든 책임, 로직을 가지고 있었는데요. ProductImage, WishButton, DiscountRate, Price, ProductName, ViewCount 들로 분리하여 각자의 컴포넌트가 책임을 가지게 되었어요. 그러면서, 반복되는 로직을 최소화 하여, single source of truth 를 지키고, 로직의 파편화를 막을 수 있어 유지 보수성이 좋아지겠죠. 그리고 이 하위 컴포넌트들을 사용처에 따라 다르게 Composition(합성) 하여 별도 Component (MediumRowProduct, MediumColumnProduct) 를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 컴포넌트를 가지고 있고, 사용처에 맞게 유연하게 이를 합성하기만 하면 되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 컨셉을 소개했고, 이를 세밀하게 개선해보도록 할게요. 지금은 사용처에서 사용이 조금 어려울 수 있어요. Product 가 무엇으로 합성되어야 하는지 명확히 파악하기 어렵기 때문이에요. 이를 해결하기 위해 객체로 컴포넌트를 보관합니다. 아래와 같은 형태로요.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Product.Image = Image;
Product.WishButton = WishButton;
Product.DiscountRate = DiscountRate;
Product.Price = Price;
Product.ProductName = ProductName;
Product.Review = Review;
Product.ViewCount = ViewCount;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Product가 어떤 컴포넌트들을 조합해서 만들어지는지 쉽게 파악할 수 있습니다. React의 유명한 패턴인 compound component 패턴도 이러한 방식을 사용하고 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 더 추가해볼게요. Context API를 활용하여 상품 데이터를 내려주고, 하위 컴포넌트에서 자유롭게 데이터에 접근하여 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 객체 형태를 활용하여, 상품 관련 컴포넌트가 동일한 Context 에서 사용함을 명시하였습니다. 그러므로 Leaf 컴포넌트들이 사용되는 Context가 명확하므로, Context API로 데이터를 주입해주어 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const ProductContext = createContext&amp;lt;ProductType&amp;gt;({} as ProductType);

function useProductConsumer() {
  return useContext(ProductContext);
}

function Product({
  children,
  product,
}: PropsWithChildren&amp;lt;{ product: ProductType }&amp;gt;) {
  return (
    &amp;lt;ProductContext.Provider value={product}&amp;gt;
      {children}
    &amp;lt;/ProductContext.Provider&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context를 사용하면 useProductConsumer 훅을 통해서 상위의 데이터에 편하게 접근할 수 있다는 점이 장점입니다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;function WishButton() {
  const { isWished, productId } = useProductConsumer();
  if (isWished === true) {
    return (
      &amp;lt;Icon
        name=&quot;heart-red&quot;
        onClick={() =&amp;gt; {
          deleteWishList(productId);
        }}
      /&amp;gt;
    );
  }
  return (
    &amp;lt;Icon
      name=&quot;heart-blank&quot;
      onClick={() =&amp;gt; {
        addWishList(productId);
      }}
    /&amp;gt;
  );
}

function ViewCount({ fontSize }: { fontSize: &quot;small&quot; | &quot;medium&quot; }) {
  const { viewCount } = useProductConsumer();
  const showViewCount = viewCount &amp;gt;= MIN_VIEW_COUNT_TO_SHOW;
  if (showViewCount !== true) {
    return null;
  }

  return (
    &amp;lt;Txt
      fontSize={fontSize === &quot;medium&quot; ? &quot;14px&quot; : &quot;12px&quot;}
    &amp;gt;{`${viewCount.toLocaleString()}명 구경함`}&amp;lt;/Txt&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 완성된 컴포넌트 형태를 살펴보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function MediumColumnProduct({ product }) {
  return (
    &amp;lt;Product product={product}&amp;gt;
      &amp;lt;Flex direction={&quot;column&quot;}&amp;gt;
        &amp;lt;Position type=&quot;relative&quot;&amp;gt;
          &amp;lt;Product.Image imageRatio={&quot;1 / 1&quot;} /&amp;gt;
          &amp;lt;Position type=&quot;absoulte&quot; right={12} bottom={12}&amp;gt;
            &amp;lt;Product.WishButton /&amp;gt;
          &amp;lt;/Position&amp;gt;
        &amp;lt;/Position&amp;gt;
        &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
          &amp;lt;Product.DiscountRate fontSize={&quot;medium&quot;} /&amp;gt;
          &amp;lt;Product.Price fontSize={&quot;medium&quot;} /&amp;gt;
        &amp;lt;/Flex&amp;gt;
        &amp;lt;Product.ProductName fontSize={&quot;medium&quot;} ellipsisAfterLines={2} /&amp;gt;
        &amp;lt;Product.ViewCount fontSize={&quot;medium&quot;} /&amp;gt;
      &amp;lt;/Flex&amp;gt;
    &amp;lt;/Product&amp;gt;
  );
}

function MediumRowProduct({ product }) {
  return (
    &amp;lt;Product product={product}&amp;gt;
      &amp;lt;Flex direction={&quot;row&quot;}&amp;gt;
        &amp;lt;Position type=&quot;relative&quot;&amp;gt;
          &amp;lt;Product.Image imageRatio={&quot;1 / 1&quot;} /&amp;gt;
          &amp;lt;Position type=&quot;absoulte&quot; right={8} bottom={8}&amp;gt;
            &amp;lt;Product.WishButton /&amp;gt;
          &amp;lt;/Position&amp;gt;
        &amp;lt;/Position&amp;gt;
        &amp;lt;Flex direction=&quot;row&quot;&amp;gt;
          &amp;lt;Product.DiscountRate fontSize={&quot;medium&quot;} /&amp;gt;
          &amp;lt;Product.Price fontSize={&quot;medium&quot;} /&amp;gt;
        &amp;lt;/Flex&amp;gt;
        &amp;lt;Product.ProductName fontSize={&quot;medium&quot;} ellipsisAfterLines={1} /&amp;gt;
        &amp;lt;Product.ViewCount fontSize={&quot;medium&quot;} /&amp;gt;
      &amp;lt;/Flex&amp;gt;
    &amp;lt;/Product&amp;gt;
  );
}

function SmallColumnProduct({ product }) {
  return (
    &amp;lt;Product product={product}&amp;gt;
      &amp;lt;Flex direction={&quot;column&quot;}&amp;gt;
        &amp;lt;Position type=&quot;relative&quot;&amp;gt;
          &amp;lt;Product.Image imageRatio={&quot;1 / 1&quot;} /&amp;gt;
          &amp;lt;Position type=&quot;absoulte&quot; right={12} bottom={12}&amp;gt;
            &amp;lt;Product.WishButton /&amp;gt;
          &amp;lt;/Position&amp;gt;
        &amp;lt;/Position&amp;gt;
        &amp;lt;Flex direction=&quot;column&quot;&amp;gt;
          &amp;lt;Product.DiscountRate fontSize={&quot;small&quot;} /&amp;gt;
          &amp;lt;Product.Price fontSize={&quot;small&quot;} /&amp;gt;
        &amp;lt;/Flex&amp;gt;
        &amp;lt;Product.ProductName fontSize={&quot;small&quot;} ellipsisAfterLines={1} /&amp;gt;
        &amp;lt;Product.ViewCount fontSize={&quot;small&quot;} /&amp;gt;
      &amp;lt;/Flex&amp;gt;
    &amp;lt;/Product&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 컴포넌트는 사용처에 따라서 별도 컴포넌트로 정의됩니다. 그리고 이는 Leaf 컴포넌트의 Composition으로 이루어지죠. 즉, 독립적인 컴포넌트를 정의하여 책임을 분리하고 재사용성을 높이면서, 동시에 유연하고 확장가능한 형태가 되었습니다. 로직이 변경되었을 때는 Leaf 컴포넌트만 수정하면 되고, UI Variation이 추가되었을 때는 이들을 조합하여 별도 컴포넌트를 만들어주면 되는 것이지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;언제 사용하면 좋은가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 패턴을 언제 사용하면 좋을지 생각해보았어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;한 컴포넌트가 여러개의 요소로 구성되는 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;각 요소가 고유의 로직을 가지고 있는 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포넌트가 여러개의 Variation이 있는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 Card 형태의 컴포넌트에 적용하면 좋습니다. 커머스의 상품 컴포넌트, 음식 배달 서비스의 음식 컴포넌트, 음악 서비스의 노래 컴포넌트, 아티클 컴포넌트 등이 예시가 될 수 있겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 컴포넌트는 이보다  훨씬 많은 관심사, 로직을 가지고 있습니다. 그리고 Variation은 더욱 다양합니다. 이 리팩터링을 통하여, 제품 발전에 따른 다양한 UI 패턴을 손쉽게 대응할 수 있었습니다. 로직이나 UI 변화에 명확하게 대응할 수 있어 유지보수성도 좋아졌습니다. 컴포넌트 내부에 책임이 많고, UI Variation이 많을 때 사용하면 매우 유용합니다. 복잡한 컴포넌트 구성 방식이 고민이라면, 적용해보시길 추천드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 코드는 여기에서 보실 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/euijinkk/ui-variation-component-refactor/blob/main/src/components/refactor/2-Object-Context.tsx&quot;&gt;https://github.com/euijinkk/ui-variation-component-refactor/blob/main/src/components/refactor/2-Object-Context.tsx&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;부록 : Compound component pattern 인가요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이야기를 해야할까 많이 고민했는데요. 많은 분들이 compound component pattern 을 떠올리며 읽었으리라 생각하여, 사견을 추가해봅니다. 이 패턴이 React에서 자주 사용되는 compound component 라고 부를 수 있을까요? 의미론적으로는 Compound(혼합물) 라고 볼 수 있습니다만, React에서 고유하게 사용되는 Compound component 방식을 생각하면, 이 패턴은 compound component 라고 말하기 어렵다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compound component pattern에 대한 Kent C. Dodds의 유명한 포스트를 보면, 그 고유의 형태를 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kentcdodds.com/blog/compound-components-with-react-hooks&quot;&gt;https://kentcdodds.com/blog/compound-components-with-react-hooks&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;component component 는 Context 내부에서 컴포넌트간 상태를 공유하고, 각 컴포넌트에서 상태를 조작할 수 있고, 이것이 다른 컴포넌트에도 영향을 미칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 제가 이 게시물에서 이야기하고 있는 패턴은, Leaf 컴포넌트에서 상태를 조작하지 않습니다. Leaf가 다른 Leaf 컴포넌트에 영향을 주지 못 합니다. 상위에서 데이터를 받아서 사용할 뿐 update 하진 않죠. 사실 Context API도 필수적으로 필요하지 않습니다. 편의상 사용했을 뿐입니다. 그래서 저는 해당 패턴은 엄밀한 의미에서 compound component pattern에 해당하지 않고, 컴포넌트 분리와 composition을 잘 활용한 예시라고 생각합니다.&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kentcdodds.com/blog/compound-components-with-react-hooks&quot;&gt;https://kentcdodds.com/blog/compound-components-with-react-hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@yesbb/객체지향의-관점으로-바라본-리액트-고급-패턴-Compound-component-Render-props?utm_source=oneoneone&quot;&gt;https://velog.io/@yesbb/객체지향의-관점으로-바라본-리액트-고급-패턴-Compound-component-Render-props?utm_source=oneoneone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@developInvestor/composition-pattern-을-react-component에-적용해보자-6bf82c564585&quot;&gt;https://medium.com/@developInvestor/composition-pattern-을-react-component에-적용해보자-6bf82c564585&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/docs/composition-vs-inheritance.html&quot;&gt;https://ko.legacy.reactjs.org/docs/composition-vs-inheritance.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fe-developers.kakaoent.com/2022/220731-composition-component/&quot;&gt;https://fe-developers.kakaoent.com/2022/220731-composition-component/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/ricardolmsilva/composition-pattern-in-react-28mj&quot;&gt;https://dev.to/ricardolmsilva/composition-pattern-in-react-28mj&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Tech/Clean Code</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/70</guid>
      <comments>https://happysisyphe.tistory.com/70#entry70comment</comments>
      <pubDate>Sun, 28 Jan 2024 23:52:26 +0900</pubDate>
    </item>
    <item>
      <title>2023-2 Self Integrity Report</title>
      <link>https://happysisyphe.tistory.com/69</link>
      <description>&lt;h1&gt;서언&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 나라는 사람의 Integrity 를 점검하는 글이다. Integrity 는 &amp;ldquo;진실성&amp;rdquo; 이라는 의미로 번역된다. 즉, Self Integrity 라는 이름으로 내가 나에게 얼마나 진실한가? 얼마나 나다움을 실현하고 있는가? 에 대해 이야기해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 주기적으로 작성해볼 것이다. 왜 작성하는지 먼저 짚고 넘어가보자. 이는 삶의 근본적인 부분을 주기적으로 고민하기 위함이다. 내가 의식하지 못 한 채 나이가 들어버리고, 과거를 후회하는 삶을 방어해주는 장치이다. 이것도 회고의 일종이지만, 단순히 일정 시간동안 잘한 점/아쉬운 점/개선할 점 을 분석하는 것이 아니다. 현재의 시간을 초월하여, 내가 현재 가진 직업과 생각, 모든 것을 초월하여, 근본부터 생각하고 나를 점검해가는 시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 진실성을 점검하기 위해서는, 체크리스트가 필요하다. 내 삶이 진실한지를 무엇으로 판단할 수 있을까? 일단 이 고민을 하기 위해서, 나만의 &amp;ldquo;삶의 가치&amp;rdquo;에 대해서 먼저 생각할 필요가 있다. 내 삶에서 중요한 것이 무엇이고, 그것을 현재 잘 이행하고 있는지를 근본적으로 생각해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것 외에, 내가 엇나가는 것을 지속적으로 점검하기 위해서는 나에게 무엇을 물어야할까? &amp;ldquo;이것&amp;rdquo;을 하고 있는 나는 진실할 확률이 매우 높다, &amp;ldquo;저것&amp;rdquo;을 하고 있지 않는 나는 진실하지 않을 확률이 매우 높다, 라고 결론 내릴 수 있는 &amp;ldquo;이것&amp;rdquo;과 &amp;ldquo;저것&amp;rdquo;을 떠올려야 했다. 현재로서 나에게 그것은 명확하다. &amp;ldquo;독서&amp;rdquo;, &amp;ldquo;글쓰기&amp;rdquo;를 하고 있는 나는 진실할 확률이 높다고 생각한다. 그것은 독서와 글쓰기를 한다는 것이 시사하는 바가 크기 때문이다. 독서와 글쓰기를 하는 과정에서, 새로운 정보를 받아들이고, 나의 생각을 해체하고, 나의 현재를 돌아보고, 나의 과거를 허물고, 나의 미래를 그려 나간다. 이것을 지속하고 있는 나는 진실할 수밖에 없다고 생각한다. 그러므로 독서와 글쓰기를 체크리스트에 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 장기적으로 자아를 실현함에 있어 중요한 요소인 &amp;ldquo;습관&amp;rdquo; 을 추가했다. 습관은 하루하루 쌓여서, 미래에 아주 큰 변화를 만들 수 있는 장치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 다음과 같은 질문들을 뽑았고, 이것들에 하나하나 답해보는 시간을 가질 것이다. 하나하나 엄청난 고민 끝에 뽑아낸 질문이다. 그렇지만 질문은 삶의 가치와 방향성이 변함에 따라서 언제든지 변화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삶의 가치
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나에게 중요한 가치는 무엇인가?&lt;/li&gt;
&lt;li&gt;지금 하는 일이 그 가치를 실현하는가?&lt;/li&gt;
&lt;li&gt;일 외에 어떤 것이 그 가치를 실현하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;독서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 책을 읽었는가?&lt;/li&gt;
&lt;li&gt;앞으로 어떤 책을 읽어야 할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;글쓰기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 글을 썼는가?&lt;/li&gt;
&lt;li&gt;앞으로 어떤 글을 써야 할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;습관
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 좋은 습관은 무엇인가?&lt;/li&gt;
&lt;li&gt;현재 안 좋은 습관은 무엇인가?&lt;/li&gt;
&lt;li&gt;앞으로 형성하고 싶은 습관은 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;삶의 가치&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;I. 나에게 중요한 가치는 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문 앞에서 골똘히 고민했다. 2년 전부터, 창조와 선한 영향력이 삶의 중요한 가치라고 외쳐왔다. 그 가치를 정립하고 2년이 지난 지금, 다른 측면도 보이기 시작한다. 이것 외에도 나의 행동의 동기가 되는 것은 너무나 많고, 이것만으로는 모두 설명이 되지 않는다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를테면 나는 왜 사람 만나는 것을 좋아하는가? 나는 왜 축구를 하는가? 이것이 삶의 중요한 가치였나? 생존을 위한 스트레스 해소에 불과한가? 정말 고민이 많았다. 그러다보니, 나의 내면에 귀기울이는 것을 넘어서, 인간 즉 호모 사피엔스 종에 대하여 생각하게 되었다. 인간은 무엇을 동기로 살아가는가? 세상에 던져진 존재인데, 어떤 상태로 던져진 것인가? 무엇을 추구하도록 만들어졌는가? &amp;ldquo;이기적 유전자&amp;rdquo; 에서 말하는 것처럼, 결국 유전자를 보존하는 기계에 불과한가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 지식과 경험이 부족한 현재로서 나를 완벽히 설명하는게 너무 어려웠다. 현재 내가 가진 지식과 경험 하에서, 잠정 결론을 내리기로 하였다. 당연히 현재의 가치일 뿐이고, 경험과 학습이 쌓이다 보면 언제든 수정될 수 있음을 받아들인다. 끈질기게 생각해낸 결론은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;창조&lt;/li&gt;
&lt;li&gt;선한 영향력&lt;/li&gt;
&lt;li&gt;사랑&lt;/li&gt;
&lt;li&gt;유희&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이정도면 나의 대부분의 행동을 잘 설명할 수 있을 것 같다. 사실 엄격하게 상호배타적이지는 않아서 아쉬운 측면이 있다. 선한 영향력이 사랑받기 위함인가? 사랑은 유희의 일환인가? 이처럼 완벽히 엄밀하진 않다만, 이것으로 2024년 1월 현재의 고민을 끝맺어 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4가지로 결론을 도출한 과정은 다음의 명제에서 기인했다. 가치는 돈과 시간을 쏟는 것에 의해서 추론할 수 있다는 것이다. 나는 저 가치들에 많은 돈과 시간을 붓는다. 대부분의 시간을 저것들에 사용하는 것 같다. 사실 창조, 선한 영향력, 사랑은 원래부터 중시했던 가치였다. 23년에 깨달은 것은 유희의 가치이다. 나는 유머를 매우 좋아하고, 재밌는 일들이 한바탕 지나가고 나면, 전율을 느낀다. 그 곳에 시간을 쓴 것이 보람차고, 다음에 또 경험할 것을 기약한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;II. 지금 하는 일이 그 가치를 실현하는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 지금 토스에서 개발자로 일 하는 것이, 이 가치를 실현하는가? 많은 부분 그러하다고 생각한다. 시지프 신화(알베르 카뮈)에 나오는 &amp;ldquo;진흙으로 조각품을 만드는 것&amp;rdquo; 이라는 말이 언제나 나의 심금을 울린다. 진흙으로 조각품을 만드는 행위는 무엇인가? 진흙은 잠깐 존재하는 것이다. 이내 말라서 무너지거나, 파도가 휩쓸어 가버릴 것이다. 다음 날에 다시 그 자리에 돌아오면 조각품은 사라져있을 것이다. 그럼에도 진흙 놀이를 좋아한다는 것은 무엇인가? 그 결과가 헛됨을 알지만, 창조의 미래가 없음을 알지만서도 과정 자체의 즐거움을 즐긴다는 것이다. 이처럼 무언가 만드는 과정, 즉 &amp;ldquo;창조&quot;는 나에게 그런 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스에서 일하는 것도 이와 같다. 토스에서 제품을 창조해내는 과정 자체가 즐겁다. 토스에서 많은 실험을 진행하므로, 코드의 반은 버려진다고 보면 된다. 하지만 그것이 아까운가? 그것 때문에 실험을 하기 싫은가? 아니다. 이는 그 자체로 즐겁다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;창조가 과정 자체를 즐기는 것이라고 하지만, 창조의 결과로 &amp;ldquo;선한 영향력&amp;rdquo;까지 줄 수 있다면 이는 더할나위 없다. 지금 내가 맡은 제품이 선한 영향력을 미치고 있는가? 이는 간접적인 것 같다. 지금 만드는 제품이, 혁신으로 사람들의 삶을 이롭게 만들지는 않는다. 하지만 토스라는 앱이 사람들의 금융 생활을 이롭게 하는 것은 확실하다. 우리 팀은 매출을 벌면서 간접적으로 선한 영향력을 실현하고 있다고 생각한다. 그래서 이 부분에서는 조금 아쉬움을 느낀다. 하지만 역시 혁신이란 쉬운 것이 아님을 안다. 우리 제품 자체로 혁신을 주고, 사람들의 삶을 이롭게 하는 방향을 고안 해낼 필요가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;III. 일 외에 어떤 것이 그 가치를 실현하는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선한 영향력은 멘토링, 글쓰기를 통해서 실현하고 있다. 2023년에는, 약 10회의 개인 멘토링과, 3회의 다대일 멘토링을 진행했다. 나는 내가 쓰는 글이 사람들에게 영감을 줄 수 있다고 믿는다. 얼마나 많은 사람이, 얼마나 깊이 영감을 받는지는 아직 구체적으로는 모르겠다. 종종 자극과 감명을 받는다는 이야기가 들려올 때, 매우 보람차다. 글쓰기 역량이 성장하고 있는 단계이고, 꾸준히 시도하면서, 더 좋은 글을 쓰며, 더 많은 사람들에게, 더 깊은 영향력을 미치고 싶다.&lt;/p&gt;
&lt;h1&gt;독서&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;I. 올해 어떤 책을 읽었는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 8권의 책을 읽었다. 올해 책을 많이 읽지 않았다고 생각했는데, 생각보다 만족스럽다. 연말에 책 읽는 습관을 제대로 들여서 11~12월에 3권을 읽을 수 있었다. 내년에는 더 많이 읽을 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;린 스타트업, 에릭 리에스&lt;/li&gt;
&lt;li&gt;자유론, 존 스튜어트 밀&lt;/li&gt;
&lt;li&gt;퓨처셀프, 벤저민 하디&lt;/li&gt;
&lt;li&gt;그릿, 안젤라 더크워스&lt;/li&gt;
&lt;li&gt;클루지, 개리 마커스&lt;/li&gt;
&lt;li&gt;당신의 뇌는 최적화를 원한다, 가바사와시온&lt;/li&gt;
&lt;li&gt;우리는 우리를 잊지 못 하고, 김민철&lt;/li&gt;
&lt;li&gt;아주 작은 습관의 힘, 제임스 클리어&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 자기계발 서적 비율이 높았다. 사실 소설을 많이 시도하긴 했다. 카라마조프가의 형제들, 반항하는 인간, 인생의 베일, 타인의 얼굴 등을 읽으려고 했다. 다 중간에 읽다가 포기했다. 나는 책 읽는 속도가 매우 느린 편이다. 하루에 책 읽는 시간이 부족하다보니, 하루에 20페이지 가량 읽게 된다. 소설을 20페이지씩 읽으면, 드라마를 5분씩 보는 느낌이다. 드라마도 몰아봐야 재밌어서 끝까지 보게 되는데, 끊어서 보면 또 안 보게 된다. 또한 소설 20페이지 동안 흥미롭지 않은 내용 전개, 배울 것 없는 부분들만 나오게 되면, 나는 보상감을 느끼지 못 했다. 내가 출근길에 노력하며 책을 읽은 것에 반해, 보상이 주어지지 않을 때 다시 시작할 흥미가 생기지 않았다. 그렇지만 내가 소설을 좋아하는 것은 사실이다. 인간은 단순한 명제를 듣는 것 보다, 이야기를 통해 명제를 이해할 때, 그 감동이 훨씬 깊이 자리잡고 많이 배울 수 있다고 생각한다. 나 또한 20대 초반 시절, 소설로부터 지대한 영향을 받아, 나의 현재 자아를 형성하게 되었다. 이만큼 나에게 소설의 의미가 커서 계속 소설을 시도하곤 했다. 하지만 이는 결과적으로 아쉬운 끝을 보았다. 일상적으로는 자기계발서나, 지식을 주는 책을 읽고, 시간이 여유로울 때 소설을 읽도록 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 베스트 책을 꼽자면, &amp;ldquo;아주 작은 습관의 힘&amp;rdquo; 과 &amp;ldquo;자유론&amp;rdquo; 이다. 아주 작은 습관의 힘은, 다른 자기계발서에 나오는 이론들과 동기부여를 직접 액션으로 취할 수 있게 해주는 책이다. 자기계발에 관심이 있다면 꼭 읽어야할 책이다. 퓨처셀프, 그릿, 당신의 뇌는 최적화를 원한다, 클루지를 읽었는데, 이것들이 이론적 바탕이 되어주었고, &amp;ldquo;아주 작은 습관의 힘&amp;rdquo;, 이 책으로 실제로 행동으로 옮길 수 있도록 만든다. 정말 위대한 책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자유론은 생각할 거리를 많이 던져주었다. 자유의 정의가 너무 좋았다. 자유는 &amp;ldquo;공권력으로부터의 자유&amp;rdquo; 와 &amp;ldquo;여론, 집단 정서로부터의 자유&amp;rdquo; 모두 충족되어야 한다. 공권력으로부터의 자유는 우리가 흔히 생각할 수 있는 자유의 개념이다. 나라에서 법으로 특정한 행동을 제한하지 않으면, 우리는 모든 행위를 할 수 있다. 그러나 공권력은 허용하지만 여론과 집단이 허용하지 않는 상황이 있다. 이 개념이 정말 흥미로웠다. 한국에서 살고 있는 나는, 여론과 집단으로부터 얼마나 자유로운가? 한국은 집단 문화가 뿌리 박혀 있다. 그러므로, 집단으로부터의 시선, 질타, 평가를 많이 받는다. 그러므로 사실상 이로부터 자유롭다고 말하기 힘들다. 이런 생각과 함께 많은 고민을 했다. 유럽에서 개발자로 일해보는 상상, 이미 한국에서 26년을 살아버려서 외국 생활이 잘 맞지 않고 결국 한국이 잘 맞는다면? 그렇다면 나는 여기에서 어떤 스탠스를 취해야 하는가?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;II. 앞으로 어떤 책을 읽어야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기계발 서적에 한동안 거부감이 있었는데, 최근에 흥미를 많이 가지고 있다. 결국 내가 가진 철학과 가치를 이행하려면, 그에 따른 행동이 필요하다. 자기계발서는 실행 대한 이야기이며, 내 삶에 매우 필요한 부분이라고 느낀다. 특히 군대에 있을 때 그런 생각을 한 적이 있다. 그 시절 책을 굉장히 많이 읽었고, 너무 훌륭한 생각들을 많이 했다. 이 철학을 바탕으로 사회에 나가서 아주 멋드러진 삶을 살고자 했다. 하지만 전역 후 그것들이 행동으로 옮겨지지 않는 것을 보고 스스로 엄청나게 좌절했다. 한동안 그 좌절감이 이어졌었는데, 이것에 대한 해결책을 알 것 같다. 철학이 뿌리를 키우는 행위라면, 자기계발서는 나뭇가지를 뻗치는 행위이다. 내가 좋아하는 철학 에세이 &amp;ldquo;시지프 신화&amp;rdquo;를 예시로 들어보자. 시지프 신화에서 이런 문장이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;헛되이 작업하고 창조하는 것, 진흙으로 조각품을 만드는 것, 자신의 창조에 미래가 없음을 아는 것, 자신이 만든 작품이 하루 아침에 파괴되는 것을 보면서 그것이 근본적으로는 수세기에 걸쳐 건축하는 것과 마찬가지로 아무 중요성이 없다는 것을 의식하는 것, 그것은 바로 부조리의 사고가 가능케 해 주는 어려운 예지(叡智)인 것이다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 철학에 굉장히 깊이 공감했었다. 하지만 나는 현실을 살아내고 있는 사람이다. 무려 26년의 역사를 거치면서 쌓아온 생각들이 있다. 이것이 하나의 공감으로 변화하기 어렵다. 어떻게 &amp;ldquo;헛되이 작업하고 창조&amp;rdquo;할 것인데? &amp;ldquo;자신이 만든 작품이 아무 중요성이 없다는 것을 의식하는 것&amp;rdquo;은 어떻게 하는데? 그것들에 대한 현실적인 해결책을 내놓는 것이 자기계발서이다. 그러므로 현재로서는 내년에도 자기계발서를 많이 읽어볼 것 같다. 그리고 최근에 사회학, 역사, 심리학, 진화론에 대해서도 관심이 많아서 이런 부류의 책들도 읽어볼 것이다.&lt;/p&gt;
&lt;h1&gt;글쓰기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;I. 1년간 어떤 글을, 얼마나 썼는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에 8개의 글을 썼다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;b&gt;토스 입사 후 7주간의 생활과 생각정리 (23.2.15)&lt;/b&gt;&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/62&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;b&gt;React CleanCode #1, 합성으로 관심사를 분리하기 (23.5.16)&lt;/b&gt;&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/63&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;b&gt;프론트엔드 개발자가 서버를 공부하는 이유 / 학습 방법에 관한 글 (23.9.29)&lt;/b&gt;&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.naver.com/gytks4/223242759715&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;자신의 미래는, 자신이 경험한 것에 기반할 수밖에 없다. (23.10.21)&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.naver.com/gytks4/223249121613&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;깨진 유리창의 법칙과 완벽주의 (23.10.28)&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.naver.com/gytks4/223251097667&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;대학생으로 돌아간다면, 어떤 과를 택하고 싶나요? (23.10.31)&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.naver.com/gytks4/223272534507&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;소 잃고 외양간 고치는 것 (23.11.23)&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;b&gt;ErrorBoundary 가 포착할 수 없는 에러와 그 이론적 원리 분석 (23.12.10)&lt;/b&gt;&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 상반기에는 글을 거의 쓰지 않았다. 입사 직후의 시간이었던지라, 학습하고 쉬기 바빴던 것 같다. 하지만 연말에 글쓰기와 독서의 중요성을 많이 느끼고 글을 연달아 많이 썼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 크게 깨달은 것이 있다. 블로그에 퀄리티 높은 글을 많이 써가면서, 독자도 많아졌다. 그래서 글을 쓰는 것에 대한 무게감이 커졌었다. 그래서 완벽한 생각, 논리가 포함된 좋은 글이 아니면, 글을 쓰고 내놓는 것이 두려워졌다. 이때 내가 완벽주의에 빠져있다는 인지를 하기 시작했다. 그리고 &lt;b&gt;깨진 유리창의 법칙과 완벽주의&lt;/b&gt; 이 글을 쓰면서 생각을 정리했다. 100% 완벽한 글이 아니라, 80% 완벽하게 쓰자는 것이었다. 완벽함을 잡으려고 하면 시작 하기도 두렵고 재미도 없다. 80% 완벽한 글을 여러개 쓰다보면, 어느새 지금의 80%가 과거의 100% 보다 더 나아지는 시점이 찾아온다고 생각했다. 그렇게 하나의 글을 1시간만에 마무리 하는 글을 썼다. 지하철로 이동하는 동안 글을 다 쓰고 포스팅 까지 해버렸다. 너무 즐거웠다. 앞으로도 이 자세를 취할 것이다. 내년에 글을 많이 써볼 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;II. 앞으로 어떤 글을 써야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년에는 회고 글 1개, 기술 글 3개, 에세이 4편을 작성했다. 글 비율은 괜찮은 것 같다. 일상적인 생각을 지속적으로 에세이 형식으로 옮기면서 생각을 더욱 구체화하고 싶다. 나는 개발자로 살아가고 있으니, 기술 글도 꾸준히 써가면서 나의 시장가치를 올리고 싶다. 회고 글은 올해도 꾸준히 썼으며, 내년에도 &amp;ldquo;Self Integrity report&amp;rdquo;와 주기적인 KPT 회고를 작성할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자의 목적은 다르다. 회고는 나의 삶을 근본적으로 돌아보고 더 나아가기 위함이다. 에세이는 머릿속에 떠다니는 생각을 정리하고, 나의 생각을 사람들에게 펼치는 목적이다. 기술 글은 개발자로서 나의 시장 가치를 올리기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 한쪽으로 편중되어서는 안되고, 3개 모두를 충족할 수 있도록 다양한 글을 쓸 것이다.&lt;/p&gt;
&lt;h1&gt;습관&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 습관을 가져야 자아에 더욱 다가갈 수 있다. 안 좋은 습관은 자아와 멀어지게 만든다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;I. 현재 좋은 습관은 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출근 버스에서 항상 책을 읽고 있다. 적어도 하루에 40분은 책을 읽고 있다. 너무 좋다. 독서를 하는 것은 삶에서 가장 좋은 습관이라고 생각한다. 독서를 꾸준히 한다는 것은 상당히 많은 것을 말해준다. 꾸준히 생각하고 있다는 것, 남들의 의견을 매일같이 듣고 있다는 것, 그러면서 스스로를 중화시키고 있다는 것, 다양한 삶의 형태를 이해하고 있다는 것 등등 무수히 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독서의 장점이라야 누구나 알고 있지만 이것을 실행하는 것이 어렵다. 꾸준한 습관으로 자리 잡는게 어렵다. 나의 독서 습관은 현재 버스에 의존하고 있다. 출근길 지하철을 타고 가면 30분이 걸리는 반면, 버스는 45분이 걸린다. 그렇지만 앉아서 갈 수 있는 버스를 타면서, 그 시간은 항상 책을 읽고 있다. 출근과 책을 연결 시킨 것이다. 이것이 &amp;ldquo;아주 작은 습관의 힘&amp;rdquo; 에서 말하는 습관 쌓기이다. 이를 통해서 책을 많이 읽어나가고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;II. 현재 안 좋은 습관은 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;균일하지 않은 일상 패턴이 가장 문제였다. 토스의 특성상, 일이 많고 언제나 일이 생긴다. 그리고 거기에 휩쓸리기 쉽다. 작년에 많은 날이 그러했다. 그리고 작년에는 알람을 맞추지 않고 잤던 날들이 많았다. 워낙에 자유로운 회사다보니, 그냥 일어나지는 대로 출근한 경우가 많았다. 하지만 올해는 나만의 시간을 지키면서, 나아가고자 한다. 취침, 기상, 출근, 퇴근 시간을 균일하게 할 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;III. 앞으로 형성하고 싶은 습관이 있는가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운동&lt;/li&gt;
&lt;li&gt;균일한 생활
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취침, 기상, 취침, 출근, 퇴근 시간 균일하게&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;꾸준한 글쓰기&lt;/li&gt;
&lt;li&gt;꾸준한 독서&lt;/li&gt;
&lt;li&gt;유튜브 / 인스타 디톡스 이어가기&lt;/li&gt;
&lt;li&gt;퇴근 후 자기계발&lt;/li&gt;
&lt;li&gt;집 청소 매일 하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;아주 작은 습관의 힘&amp;rdquo; 과 함께 습관 형성을 체계화 해볼 것이다. 이는 언젠가 글로 옮겨서 올릴 것이다.&lt;/p&gt;
&lt;h1&gt;마치며&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들을 써내려가며, 나 자신이 이전보다 많이 단단해졌음을 느꼈다. 예전에는 방향성이 명확하지 않고, 생각이 자주 막히곤 했었다. 최근에는 생각의 방향성이 일관적이고, 근본적인 생각을 전개해나가는 역량이 나아졌음을 느낀다. 생각이 막히더라도, 어떤 부분에서 내가 부족하고 이해할 수 있고, 이것을 내가 지금 고민으로 해낼 수 있는 부분인지, 학습이 필요한지 등을 스스로 판단하는 것이 가능해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 삶의 근본부터 생각한 경험은 또 오랜만이었다. 대학생때나, 군대에 있을 때는 시간이 많아서 매일같이 근본적인 생각을 했다. &amp;ldquo;어떻게 살 것인가&amp;rdquo; 라는 추상적인 질문을 해대며. 그때는 생각이 많고 행동이 부족했었는데, 최근에는 행동을 많이 하고 생각하는 시간이 조금 부족했다. 이번에 Self Integrity Report 를 쓰면서, 정말 많은 생각을 했다. 삶의 가치에 대해서도 근본적으로 생각해보고, 독서/글쓰기/습관 등 중요한 행동들을 하나하나 돌아보며 앞으로 살아갈 방향을 잡아보았다. 상당히 가치있는 시간이었고, 꼭 주기적으로 돌아봐야겠다는 생각이 들었다.&lt;/p&gt;</description>
      <category>Writing/Self integrity report</category>
      <category>self integrity</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/69</guid>
      <comments>https://happysisyphe.tistory.com/69#entry69comment</comments>
      <pubDate>Sun, 21 Jan 2024 22:19:17 +0900</pubDate>
    </item>
    <item>
      <title>세상을 보는 창</title>
      <link>https://happysisyphe.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;아는 만큼 보인다&quot; 라는 격언이 있다. 같은 현상을 보더라도, 무엇을 / 얼마나 아느냐에 따라서 관점이 달라진다. 즉, 지식이 세상을 보는 창이다. 어떤 이는 과학으로, 어떤 이는 역사로, 어떤 이는 철학으로, 어떤 이는 사회학으로, 어떤 이는 경제학으로, 어떤 이는 경영으로 세상을 바라본다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f6e53180-b484-44c7-a952-dd9167ec4dcc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-8d3f90a0-b26b-4241-8167-51067c545262&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예시를 들어보자. 산업 혁명이라는 하나의 사건을 다양한 시각에서 바라볼 수 있다. 철학으로는 데카르트의 합리론, 법학으로는 특허법, 역사로는 인클로저 운동, 등을 가지고 바라볼 수 있다. 각 분야는 각기 다른 관점을 가지고, 다른 배울 점을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-4942791d-07ef-47e2-910c-7ffaaf33d0da&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-4e3d98b3-1d2d-47dc-9bc6-f5e9d762abd1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리의 일상도 같다. 사람의 행동을 이해하려면 많은 도구가 필요하다. 이를테면 심리학에 대한 이해가 필요하다. 이 중에서도 자기방어, 열등감 등에 대한 심도있는 이해가 필요하다. 그렇지 않으면, 섣부른 판단을 내리고 잘못된 인식을 바탕으로 관계를 그르칠 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-8f9cb447-630b-4e35-a312-4af7151b18ba&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-11609823-fccc-48dd-923b-b243c9cf9cb0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히, 왜 &amp;ldquo;나&amp;rdquo;는 이렇게 행동하는가 를 파악하는 것은 삶에서 가장 중요한 부분이다. 이를 메타인지라고 한다. 메타인지는 어떻게 이루어지는가? 이는 굉장히 심오하다. 나를 똑바로 바라보기 위해서는, 우선 나 자신에 대한 이해가 필요하고, 더 나아가서 인간, 즉 호모 사피엔스 종에 대한 이해가 필요하다. 호모 사피엔스에서 더 나아가서, 유전자에 대한 이해가 필요하다. 이를 이해하기 위해서는, 크게는 진화론, 작게는 뇌과학과 심리학에 대한 이해가 필요하다. 이것으로 끝인가? 타인은 나를 보는 거울이다. 다른 사람의 이야기는 매우 중요하다. 역사와 철학, 소설 등을 통해 메타인지를 키울 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f3087455-3f99-4de0-8e84-656346ea0b5d&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-fcbe1e9b-5f07-4950-882b-728204a7b101&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나는 철학만이 깨달음으로 가는 길이라고 생각했다. 여러 철학자의 견해를 공부하고, 나의 행동을 돌아보았다. 그렇지만 여전히 해석되지 않는 부분이 많았다. 또는 결론을 내리고도, 마음속으로 석연치 않은 부분들이 있었다. 세상을 보는 창이 편협했기 때문이다. 철학은 &amp;ldquo;인간은 어떻게 살아야 하는가?&amp;rdquo; 에 대한 질문을 던지고, 답을 내는 과정이다. 이것을 인간에 대한 이해 없이, 유전자에 대한 이해 없이, 심리학과 뇌과학 등에 대한 이해 없이 답을 내놓은 것은 매우 위험하다. 이런 생각이 들자, 내가 가진 지식이 얼마나 초라한가, 나는 얼마나 자만했는가, 세상을 다 안다는 듯이, 그것들이 부끄러웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f491f7ad-7009-4f0b-8ab3-5a41a1003c16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-431452d1-ce46-4f91-8901-86d6cc658efe&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지식은 세상을 보는 창이다. 아는 만큼 보인다. 현재 창에서 보이는 풍경이 전부가 아니다. 반대편 풍경은 전혀 다른 양상이 펼쳐지고 있을 수 있다. 그리고 풍경은 시시각각 달라질 것이다. 내가 가진 편협한 지식으로, 그것이 전부라고 생각하며 정답을 정해놓고 살아가는 것만큼 어리석은 일은 없을 것이다. 많은 것을 배우고 경험하고 다양한 창을 갖도록 노력해야 한다. 이 깨달음조차 언젠가 더 강화되거나, 무너지고 말겠지.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/68</guid>
      <comments>https://happysisyphe.tistory.com/68#entry68comment</comments>
      <pubDate>Tue, 9 Jan 2024 23:58:35 +0900</pubDate>
    </item>
    <item>
      <title>토스에서의 1년을 돌아보며</title>
      <link>https://happysisyphe.tistory.com/67</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서언&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스에서 Frontend Developer로 일한 지 1년이 지났다. 정말 밀도 높은 한 해를 보냈다. 매일을 살아가는 것도 중요하지만, 과거를 숙고하는 것이 기반이 되어야 한다. 내가 지금 어디에 있고 앞으로 어디로 가야 할지를 생각해야 한다. 이게 내 삶에서 자정 역할을 해왔다. 오늘도 잠깐 멈춰서서 1년을 돌아보고, 나아갈 길을 정의해볼 생각이다. 글 형식은 토스에서 인상 깊었던 요소를 써내려가며 느낀점을 쓰는 형식으로 해보겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;목적조직&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스는 목적조직 문화를 가진다. 목적조직은 직무와 상관없이 목표를 이루기 위해 팀원이 모이는 조직형태를 의미한다. 이게 토스에서 일하는 것이 즐거운 이유 중 하나라고 생각한다. 내가 토스에 합류한 이유이기도 하다. 그래서 대개 한팀에 직무 별로 1명씩 배치된다. 우리 팀도 PO 1명, 디자이너 1명, 프론트엔드 개발자 1명, 백엔드 개발자 1명, 데이터 분석가 1명으로 시작했다. 이런 조직의 몇가지 특징이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1명의 만들 수 있는 기여도가 크다.&lt;/li&gt;
&lt;li&gt;모두가 아이디어를 내면서 제품을 만들어 간다. 그러므로 제품에 대한 오너십이 크다.&lt;/li&gt;
&lt;li&gt;속도가 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 동안 만든 서비스에 내가 산술적으로 20%를 기여한 셈이다. 팀원이 5명밖에 되지 않기 때문에, 자신이 더 많이 기여하고자 하면 그 수치를 훨씬 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로서 신입부터 서비스에 대한 전적인 책임을 지는 것은 쉽지 않았다. 그것을 해내는 것부터가 어려운 일이며, 신뢰를 받는 것은 더 어렵다. 토스는 채용 시에 경력을 나누어 뽑지 않는다. 단지 한 팀에서 혼자 개발할 수 있는 역량을 가진 사람을 뽑는다. 그렇게 첫 경력임에도 불구하고, 오랫동안 혼자서 서비스를 개발했다. 기술스택 선택, SSR과 CSR 선택, 개발 방식, 일정 산정, 장애 대응 전략 등을 모두 주도적으로 결정했다. 프론트엔드는 전적으로 나 혼자 책임지는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식이면 팀의 속도가 매우 빨라진다. 각 직무별로 소통은 직접적으로 이루어진다. 대개 소통의 중간자가 없다. 프로젝트가 정해지면 각자의 역할이 바로 정해지고, 모두 이를 이해하고 있다. 이를테면, 3시에 정해진 실험이 5시에 배포되는 경험도 있었다. 실험 방향성이 정해지고, 디자이너가 바로 디자인하고, 프론트엔드에서 실험 코드를 넣은 후 바로 배포했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 얘기하면, 매우 두렵게 느껴지거나, 오히려 성장하기 어렵다고 느낄 수 있다. 하지만 토스의 매트릭스 구조 때문에 부담감을 덜 수 있다. 각 사일로에 1명씩 배치되고, 같은 직무의 사람들이 챕터라는 이름으로 모인다. 그래서 맡은 프로젝트는 다르지만, 서로 고민을 공유하고 코드리뷰를 받을 수 있는 구조를 가지고 있다. 즉, 결정은 프로젝트의 주인이 하지만, 주변에 의견을 구하고 모르는 것을 쉽게 물어볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (28).png&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b36eSc/btsC4seqtEM/kHfrRf2e0WNm9QReKZshm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b36eSc/btsC4seqtEM/kHfrRf2e0WNm9QReKZshm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b36eSc/btsC4seqtEM/kHfrRf2e0WNm9QReKZshm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb36eSc%2FbtsC4seqtEM%2FkHfrRf2e0WNm9QReKZshm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;384&quot; data-filename=&quot;Untitled (28).png&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 내가 왜 토스를 선택하였는지 적었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/59&quot;&gt;https://happysisyphe.tistory.com/59&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704593322221&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;토스 &amp;amp; 우아한형제들 합격, 회사 선택 이유와 앞으로의 목표&quot; data-og-description=&quot;서언 안녕하세요. 이번에 신입 프론트엔드 개발자 채용을 거쳐 토스(코어)와 우아한형제들에 합격했습니다. 프론트엔드 개발자로서 두 회사 모두 객관적으로 너무나 좋은 회사들이고, 저에게 &quot; data-og-host=&quot;happysisyphe.tistory.com&quot; data-og-source-url=&quot;https://happysisyphe.tistory.com/59&quot; data-og-url=&quot;https://happysisyphe.tistory.com/59&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rl5dG/hyU2hkmnVM/yqeFpkyCHtcXTf7x9Wi0hk/img.png?width=800&amp;amp;height=453&amp;amp;face=586_123_629_170,https://scrap.kakaocdn.net/dn/FsiOM/hyUXLm9j2k/IIVoLKkJ8fy9KAsWiUQLs1/img.png?width=800&amp;amp;height=453&amp;amp;face=586_123_629_170,https://scrap.kakaocdn.net/dn/1uLqp/hyUXVDhf9A/XzSdTX1Wr5rjB8GXpoG620/img.png?width=1600&amp;amp;height=906&amp;amp;face=0_0_1600_906&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://happysisyphe.tistory.com/59&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rl5dG/hyU2hkmnVM/yqeFpkyCHtcXTf7x9Wi0hk/img.png?width=800&amp;amp;height=453&amp;amp;face=586_123_629_170,https://scrap.kakaocdn.net/dn/FsiOM/hyUXLm9j2k/IIVoLKkJ8fy9KAsWiUQLs1/img.png?width=800&amp;amp;height=453&amp;amp;face=586_123_629_170,https://scrap.kakaocdn.net/dn/1uLqp/hyUXVDhf9A/XzSdTX1Wr5rjB8GXpoG620/img.png?width=1600&amp;amp;height=906&amp;amp;face=0_0_1600_906');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;토스 &amp;amp; 우아한형제들 합격, 회사 선택 이유와 앞으로의 목표&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서언 안녕하세요. 이번에 신입 프론트엔드 개발자 채용을 거쳐 토스(코어)와 우아한형제들에 합격했습니다. 프론트엔드 개발자로서 두 회사 모두 객관적으로 너무나 좋은 회사들이고, 저에게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;happysisyphe.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 토스를 선택한 이유를 도전과 주체성이라고 적었다. 토스의 조직문화는 이를 이루기 매우 좋은 형태이기 때문에, 1년간 일하면서 많이 성장할 수 있었다. 팀 인원이 적고, 개인이 맡은 역할이 커서 역할을 확장하기도 쉽다. 내년에는 팀 내에서 영향력을 더 키우고 싶다. 즉, 개발 뿐만 아니라 제품과 팀 문화 등 다양한 측면에서 역할을 확장하고 싶다. 이것에 대해서는 아래에서 더 구체적으로 써보겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;코드 치는 것은 개발자의 일부일 뿐이다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 코드만 치는 사람인가? 전혀 아니라는 걸 많이 깨달았다. 프론트엔드 개발자가 해결해야 할 문제는 코드 치는 것 이외에도 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각나는 것을 써보자면, 아래와 같이 다양한 문제가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;성능 문제 해결&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 성능&lt;/li&gt;
&lt;li&gt;폰트 성능&lt;/li&gt;
&lt;li&gt;리소스 번들 사이즈&lt;/li&gt;
&lt;li&gt;리소스 캐싱 전략 (브라우저, CDN)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장애 줄이기 / 장애 대응 방법&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드리뷰 시스템이 없는데, 어떻게 할 것인가?&lt;/li&gt;
&lt;li&gt;장애가 났다는 사실을 빠르게 인지하려면 어떻게 해야 하는가?&lt;/li&gt;
&lt;li&gt;페이지에서 하나의 API 에서 장애가 나도, 전체 페이지를 못 보게 하는게 맞는가? 에러를 전파시키지 않으려면 어떻게 해야할까?&lt;/li&gt;
&lt;li&gt;모니터링을 어떻게 잘 할 수 있을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;휴먼 전략 문제&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 팀에 프론트엔드 1명으로 충분한가? 1명이 늘어나면, 효율을 얼마나 높일 수 있을까?&lt;/li&gt;
&lt;li&gt;사람이 더 있어야 할 것 같은데, 누구한테 뭐라고 설득하지?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 역할을 잘 해내기 위해서는 결국 문제 해결 능력, 커뮤니케이션 역량, 적극적인 태도가 가장 중요하다고 생각한다. 그러므로 토스에서 일하다 보면, 개발 뿐만 아니라 다양한 역량을 강화할 수 있다. 그게 내가 바라던 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 치는 것보다 근본적으로 문제를 푸는 데 관심이 많은 나로서는 일 자체가 매우 흥미롭다. 동료들과 문제를 어떻게 풀지 치열하게 논의하는 과정이 기억에 남는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실험&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스는 실험을 많이 한다. 유저가 마주하는 화면의 크기에는 제한이 있고, 그 안에서 유저에게 더 좋은 경험을 주기 위해서, 돈을 더 잘 벌기 위해서 가장 효율적인 방법을 찾아야 한다. 토스는 함부로 결정하지 않는다. 수평적인 문화 때문에 아무도 함부로 결정할 수도 없다. 이때 우리는 실험을 진행한다. 예를 들어 유저가 특정 상품에 가입하기 위해 정보를 입력해야 하는 상황이 있다고 치자. 누군가는 한 페이지에서 모든 정보를 입력해야 한다고 주장할 수 있고, 누군가는 한 페이지에 하나의 정보만 입력하도록 해야 한다고 주장할 수 있다. 결국 뭐가 더 나은지 판단하려면 데이터가 필요하다. 이 결정이 특정 리더의 추측에 의한 명령으로 이루어지지 않는다. A/B 테스트 실험을 하고, 데이터를 확인한 후 의사결정을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 나에게 매우 흥미롭게 다가왔다. 모든 결정은 데이터를 기반으로 하고, 언제나 데이터를 바꿀 수 있는 아이디어를 제안할 수 있다. 그리고 구현할 때가 오면 정말 재미있다. 일 할 때 동기부여가 떨어지는 이유 중 하나는 효능감을 느끼지 못하기 때문이다. 왜 해야 하는지 납득하지 못 하는데도 그 일을 해야 한다면 효능감이 떨어진다. 하지만 데이터로 결정된 것을 수행해야 할 때, 그 아이디어가 내게서 나왔을 때, 이를 구현하는 것은 매우 즐거운 일이다. 일하면서 살아있다고 느끼고 더 적극적으로 해내게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 무작정 실험을 진행한다고 하여 좋은 것은 아니다. 좋은 실험을 진행해야 한다. 그 실험이 비즈니스적으로 중요하지 않은 가설임에도 실험을 진행하거나, 검증하고자 하는 것을 정확히 검증해내지 못 한다면, 시간만 더 쓸 뿐 실제로는 도움이 되지 않는다. 그래서 좋은 실험에 대해 관심을 가지고 공부하고 있다. 팀 내에서 더 좋은 실험을 진행할 수 있도록 많은 도움을 주고 싶다. 23년은 좋은 실험에 대해 개괄적으로 이해하는 시간이었다고 하면, 24년에는 이를 심도있게 파악하고 액션을 취해보고 싶다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Focus on impact&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스의 Core Values 중 하나가 Focus on impact다. 임팩트에 집중하라는 건데, 이 세부 설명을 보면 놀랍다. 하면 좋을 10가지 일을 하지 말아야 할 일로 규정하는 것부터 시작하라고 한다. 일하다 보면, 하면 좋은 일들이 넘쳐난다. 중요하지 않은 디자인을 변경하고, 애니메이션을 추가하고, 기능을 덕지덕지 추가하는 일이 그 예시가 될 수 있다. 이런 일들을 모두 하다 보면, 핵심적인 임팩트를 만들지 못 한다. 그러니 하지 말아야 할 일을 정의하고, OKR을 달성하기 위해 어떤 액션을 해야 할지 근본부터 다시 생각한다. 여기서 가장 임팩트 있는 액션만을 취한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 회의할 때 항상 임팩트의 크기를 논의한다. 이것을 왜 해야 하는지, 동일 시간 대비 더 큰 임팩트를 낼 수 있는 일은 없는지, 모두가 지속적으로 고민한다. 그러면서 &amp;ldquo;왜 해야 하는지 모르겠다&amp;rdquo;, &amp;ldquo;이것 보다 저것을 하는 것이 더 낫지 않을까요?&amp;rdquo; 같은 의견이 쏟아지면서, 더 나은 방향성을 찾아간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Focus on impact 라는 가치를 중시하면서 일 하는 것은 매우 흥미롭다. 덕분에 무의미한 일은 거의 하지 않고, 임팩트 있는 일만 할 수 있다. 그러므로 일 자체가 언제나 의미가 있고, 그 자체로 자기 효능감을 가져다 준다. 하지만 임팩트 라는 것을 잘 정의하는 것이 중요하다. 임팩트가 크지만, 작다고 판단하면 안 된다. 또는 단기적인 임팩트에만 집중하다 보면, 장기적인 임팩트를 놓치게 된다. 가령, 조금의 유저 경험 개선은 단기적으로 아무것도 아닌 것처럼 보일 수 있지만, 계속 쌓여가다 보면 전체적으로 경험이 매우 안 좋아질 수 있다. 그러면 이것이 곧 큰 Bad 임팩트가 될 수 있다. 그러므로 임팩트 라는 것을 잘 이해하고 판단하는 것이 중요하다. 아래에서는 기술이 가져다주는 임팩트를 간과하여, 잘못된 방향으로 갔던 경험을 서술하겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기술의 중요성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Focus on impact를 단기적으로 이해하다 보니, 임팩트를 오직 제품 관점으로만 생각했다. 유저에게 좋은 경험을 주고, 우리의 매출을 올려주는 액션이 아니라면 모두 의미없다고 생각했다. 그래서 나는 개발자이지만서도 코드 퀄리티, 개발 안정성 등을 크게 고려하지 않은 순간들이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사례 1&lt;/b&gt; : 사라질지도 모르는 기능이므로, 코드 퀄리티를 신경 쓰지 않고 최대한 빠르게 구현했다. 그리고 그 기능이 확장되고 핵심 기능이 되었지만, 여전히 코드가 리팩터링 되지 않아, 개발 시간이 오래 걸리고 장애 확률이 높아졌다. 단기적으로는 코드 개선보다, 기능을 추가하는 것이 더 큰 임팩트를 가져다 준다고 생각했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사례 2&lt;/b&gt; : 장애 감지가 정확히 되지 않는다는 것을 알았지만, 현재로서 크게 문제를 겪은 적이 없고, 더 시급해 보이는 문제들이 있어서 외면하다가 큰 장애를 맞이한 순간이 있었다. 테스트를 열심히 해서 장애를 내지 않으면 그만이라고 생각했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경험들이 쌓이다 보니, 내가 개발자로서 기술의 중요성을 간과했다는 생각이 들었다. 빠르게 구현하여 비즈니스 임팩트를 주는 것도 중요하지만, 이 프로젝트에 유저가 많아지고, 장기적으로 지속될 기능이라면 기술이 꼭 뒷받침해 줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 부채가 쌓여서 생산성을 과도하게 깎아 먹으면 안 되고, 내가 아닌 다른 사람도 편하게 개발할 수 있어야 하며, 장애가 나더라도 빠르게 감지 및 대응할 수 있어야 한다. 혼자 개발하다 보니, 그런 측면을 고려하지 못 했다. 하지만 팀이 커지면서 프론트엔드 개발자 동료가 한명 더 합류했고, 이게 문제가 되는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 깨달은 후, 비즈니스와 개발 안정성 사이에서 적절한 의사결정을 하려고 많이 노력하고 있다. 기술 부채를 컨트롤 하며 장기적인 안정성 및 생산성을 보수하려고 노력하고 있다. 앞으로 좀 더 적극적으로 기술 부채 해소를 제안하고 싶다. 이대로 기술 부채가 지속되면, 생산성이 저하되고, 장애 발생 확률이 높아지므로 지금 해소하는 것이 좋겠다는 의견을 합리적으로 내면서 설득하는 과정을 경험하고 싶다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;역할의 확장&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 안주하기를 싫어한다. 하는 일에 적응이 되면 새로운 일을 찾아나간다. 그리고 토스는 이에 열려 있다. 팀에서 프론트엔드 개발을 하는 것에 익숙해지자, 다른 것들도 잘하고 싶어졌다. 프로덕트 매니징, 기획 아이디에이션, DB 조회 등이 있다. 하반기는 이것들에 눈을 뜨고, 조금씩 해나가는 과정이었다. 24년에는 더 적극적으로 역할을 확장해나가고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 하고 싶은 것들을 써보자&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 을 좀 더 자유자재로 쓰면서, DB를 조회하여 CS 대응 및 데이터 분석 하기&lt;/li&gt;
&lt;li&gt;팀의 데이터 지표를 이해하고, 이를 바꿀 수 있는 기획 아이디어를 많이 내기&lt;/li&gt;
&lt;li&gt;웹 서비스 바깥의 문제 풀기&lt;/li&gt;
&lt;li&gt;매니징 역할 많이 해보기 (프로젝트 매니징, 신규 입사자 온보딩)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동료들과의 신뢰관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훌륭한 동료들과 함께 하는 것은 언제나 즐겁다. 배울 점이 많다. 토스에서는 각 파트별로 1명씩이 모여서 팀을 이루고 있다. 그러려면 각 파트의 사람이 그 일에 능통해야 한다. 척하면 척 해낼 줄 알아야 한다. 이것이 가능한 사람들이 모여 있으니, 그들의 역량이 매우 훌륭하다고 할 수 있겠다. 그들과 일하는 것이 영광이다. 팀원들을 전적으로 신뢰할 수 있고, 팀원들도 나를 신뢰해준다. 신뢰관계가 있으면 불필요한 논의가 최소화된다. 이것이 토스가 일이 많고 어렵지만, 계속 지속할 수 있게 해주는 힘인 것 같다. 역량이 부족한 동료에 대한 스트레스가 적고, 훌륭한 동료들과 끈끈한 팀을 이루어서 어려운 문제를 풀어가는 과정이 즐겁다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;끝마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 훌륭한 조직에 들어와서 일 할 수 있음에 감사하다. 첫 직장생활이지만, 무탈히 랜딩해냈다. 쉽지만은 않았지만, 좋은 시작을 했다고 생각한다. 즐겁게 일하면서 성장해낸 시간이었다. 아마 이후에도 그런 시간이 지속될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하고 싶은 것, 잘 해내고 싶은 것이 넘쳐난다. 새롭게 경험하는 것이 많으니, 호기심이 많이 생긴다. 이것도 마찬가지로 Focus on impact 하는게 중요한 것 같다. 모든 것을 잘 해내려고 하다 보면, 아무 것도 잘 해내지 못 할 가능성이 높다. 올해 하반기에 그러한 경향이 있었다. 잘 해내고 싶은게 많았고, 그 사이에서 허둥지둥 헤맸다. 잘 해내고 싶은 것을 리스트업 하고, 그 중 가장 중요한 것 하나를 꼽아서 진득하게 해내는 1년을 만들고 싶다. 훌륭한 동료들과 즐겁게 제품을 성공적으로 이끌면서, 개인적인 성장을 이룩하고 싶다.&lt;/p&gt;</description>
      <category>Writing/회고</category>
      <category>토스</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/67</guid>
      <comments>https://happysisyphe.tistory.com/67#entry67comment</comments>
      <pubDate>Sun, 7 Jan 2024 11:14:20 +0900</pubDate>
    </item>
    <item>
      <title>ErrorBoundary 가 포착할 수 없는 에러와 그 이론적 원리 분석</title>
      <link>https://happysisyphe.tistory.com/66</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ErrorBoundary&lt;/b&gt; 는 하위 컴포넌트에서 발생하는 자바스크립트 에러를 잡아서 fallback UI를 보여주는 React 컴포넌트 입니다. 하지만 하위에 존재하는 컴포넌트에서 에러가 발생한다고 하여, &lt;b&gt;모든 에러를 잡아주는 것은 아닙니다.&lt;/b&gt; React 공식문서에는 다음과 같은 에러는 ErrorBoundary가 포착할 수 없다고 합니다. 과연 저것은 암기해야하는 것들일까요? 그렇지 않습니다. &lt;b&gt;JavaScript 와 React를 잘 이해하고 있다면 당연한 현상입니다.&lt;/b&gt; 이 글에서는 &lt;b&gt;에러바운더리가 특정 에러를 포착하지 못 하는 원리&lt;/b&gt;를 설명해보려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (19).png&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnusO3/btsBFG53y0F/4EjoXtAxesljz01vEPgk60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnusO3/btsBFG53y0F/4EjoXtAxesljz01vEPgk60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnusO3/btsBFG53y0F/4EjoXtAxesljz01vEPgk60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnusO3%2FbtsBFG53y0F%2F4EjoXtAxesljz01vEPgk60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1124&quot; height=&quot;546&quot; data-filename=&quot;Untitled (19).png&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;[사전지식] 실행 컨텍스트와 예외처리&lt;/li&gt;
&lt;li&gt;Case1. 이벤트 핸들러&lt;/li&gt;
&lt;li&gt;Case2. 비동기적 코드&lt;/li&gt;
&lt;li&gt;Case3. 서버 사이드 렌더링&lt;/li&gt;
&lt;li&gt;Case4. 자식에서가 아닌 에러 경계 자체에서 발생하는 에러&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[사전지식] 실행 컨텍스트와 예외처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript를 학습하다 보면, 유명한 예제를 배우게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;try {
  function throwErrorFn() {
    throw new Error(&quot;will it be catched?&quot;);
  }
  setTimeout(throwErrorFn, 1000);
} catch (e) {
  console.log(e);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout 에서 Error를 throw 하고, 저렇게 try/catch 를 감싸면 과연 catch 문에 잡힐까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;잡히지 않는다&amp;rdquo; 가 정답입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &lt;b&gt;실행 컨텍스트&lt;/b&gt;의 동작으로 설명할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 컨텍스트란, 소스코드 평가 및 실행을 통해 발생하는 환경을 관리하는 객체 를 말합니다. 쉽게 말하자면, 함수 실행에 필요한 변수(Context)를 관리하는 역할을 합니다. 코드 예시를 들어보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function foo(a) {
  const x = 10;
  const y = 20;

  function bar() {
    return x + y + a;
  }

  return bar();
}

foo(100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foo 코드가 실행되면, foo 실행 컨텍스트가 만들어지고, 실행 컨텍스트에는 내부 변수인 x, y, a, bar 라는 변수를 관리합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (20).png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDbXf1/btsBF0wpWA7/Q2y1ZPIspVktkm4jCsdBk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDbXf1/btsBF0wpWA7/Q2y1ZPIspVktkm4jCsdBk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDbXf1/btsBF0wpWA7/Q2y1ZPIspVktkm4jCsdBk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDbXf1%2FbtsBF0wpWA7%2FQ2y1ZPIspVktkm4jCsdBk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;552&quot; data-filename=&quot;Untitled (20).png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 실행 컨텍스트는 &lt;b&gt;call stack&lt;/b&gt; 이라고 하는 곳에 순차적으로 쌓이고 pop 됩니다. foo 위에 bar가 쌓이고, bar가 실행되고 나면, bar가 pop 되고 foo만 남게 됩니다. foo 까지 실행되고 나면 foo 도 pop 되고, 콜스택은 비게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (21).png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mIaWq/btsBKjBAy3O/0FlYKTVQkXc64xls7U5TO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mIaWq/btsBKjBAy3O/0FlYKTVQkXc64xls7U5TO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mIaWq/btsBKjBAy3O/0FlYKTVQkXc64xls7U5TO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmIaWq%2FbtsBKjBAy3O%2F0FlYKTVQkXc64xls7U5TO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1702&quot; height=&quot;646&quot; data-filename=&quot;Untitled (21).png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 하나의 컨텍스트에서 에러가 발생하면 어떻게 될까요? &lt;b&gt;에러는 상위 컨텍스트로 전파됩니다.&lt;/b&gt; bar 에서 에러가 발생한다고 해볼게요. bar 에서 에러가 throw 되는데, bar 에서 처리되지 않으면 foo 까지로 전파됩니다. foo 에서도 처리되지 않으면, &lt;b&gt;최상위 실행 컨텍스트로 전파되고 crash 가 일어납니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (22).png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBdjtT/btsBFoxF0u7/RRKVDpKhsn6AK8iP2JGk90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBdjtT/btsBFoxF0u7/RRKVDpKhsn6AK8iP2JGk90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBdjtT/btsBFoxF0u7/RRKVDpKhsn6AK8iP2JGk90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBdjtT%2FbtsBFoxF0u7%2FRRKVDpKhsn6AK8iP2JGk90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;562&quot; data-filename=&quot;Untitled (22).png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 배경지식을 기반으로 setTimeout 예제를 다시 보도록 할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout 내의 callback 은 1초 후에 실행 컨텍스트에서 실행됩니다. 그때는 이미 try/catch 문이 있던 컨텍스트가 pop 된 이후의 상황이기 때문에 catch 가 잡을 수 없고, 최상단에 에러가 던져지게 됩니다. (main 함수는 최상위 컨텍스트 함수를 의미합니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (23).png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceogTP/btsBGUJbCKN/bj2XzKRNphEVKczbe04mFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceogTP/btsBGUJbCKN/bj2XzKRNphEVKczbe04mFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceogTP/btsBGUJbCKN/bj2XzKRNphEVKczbe04mFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceogTP%2FbtsBGUJbCKN%2Fbj2XzKRNphEVKczbe04mFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;730&quot; data-filename=&quot;Untitled (23).png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, try/catch 문의 컨텍스트 내에서 throwErrorFn이 실행되지 않기 때문에, 에러를 포착할 수 없는 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러바운더리도 try/catch 와 동일한 원리로 동작합니다. 리액트 공식문서에서도 다음과 같은 문장을 볼 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;에러 경계는 자바스크립트의 catch {} 구문과 유사하게 동작하지만 컴포넌트에 적용됩니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러바운더리가 잡을 수 있는 에러인지 판단할 때 가장 중요한 것은 &lt;b&gt;&amp;ldquo;ErrorBoundary 실행 컨텍스트&amp;rdquo;&lt;/b&gt; 입니다. &lt;b&gt;그 안에 있다면 잡을 수 있고, 그 안에 없다면 잡을 수 없습니다. 유일한 원리입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 나오는 ErrorBoundary 가 포착할 수 없는 에러의 사례를 이 개념을 통해서 설명해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Case 1. 이벤트 핸들러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 핸들러 내에서 발생한 에러는 ErrorBoundary에 잡히지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function App() {
  return (
    &amp;lt;ErrorBoundary&amp;gt;
      &amp;lt;Button /&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  );
}

function Button() {
  return (
    &amp;lt;button
      onClick={() =&amp;gt; {
        throw new Error(&quot;will it be catched?&quot;);
      }}
    &amp;gt;
      click
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이해하기 위해서는 React의 이벤트 핸들러가 바인딩되는 과정을 이해할 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React v17의 Relase note를 읽어보면, React의 이벤트 위임 방식 변화를 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html#changes-to-event-delegation&quot;&gt;React v17.0 Release Candidate: No New Features &amp;ndash; React Blog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript에서는 DOM node에 직접 이벤트를 등록합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;document.getElementById(&quot;home_button&quot;).addEventListener(&quot;click&quot;, (e) =&amp;gt; {
  // callback
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 React에서는 모든 이벤트가 root element 에서 핸들링 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (24).png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;975&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BmbHu/btsBG4LGW2X/aNSMBzvn8VrnAtB9F7iNek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BmbHu/btsBG4LGW2X/aNSMBzvn8VrnAtB9F7iNek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BmbHu/btsBG4LGW2X/aNSMBzvn8VrnAtB9F7iNek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBmbHu%2FbtsBG4LGW2X%2FaNSMBzvn8VrnAtB9F7iNek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;975&quot; data-filename=&quot;Untitled (24).png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;975&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 아래처럼 console을 찍어보면 root 에서 이벤트가 다뤄지고 있음을 알 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;button
  onClick={(e) =&amp;gt; {
    console.log(e.nativeEvent.currentTarget);
  }}
&amp;gt;
  click
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (25).png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OGcEE/btsBGpQk8K3/zPJmMQAgeI37IyVFjz3Ik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OGcEE/btsBGpQk8K3/zPJmMQAgeI37IyVFjz3Ik1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OGcEE/btsBGpQk8K3/zPJmMQAgeI37IyVFjz3Ik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOGcEE%2FbtsBGpQk8K3%2FzPJmMQAgeI37IyVFjz3Ik1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;270&quot; data-filename=&quot;Untitled (25).png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 root 에 등록된 eventListeners 들을 한번에 조회해볼 수도 있는데요.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;getEventListeners(document.getElementById('root'))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (26).png&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;1700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r2vIt/btsBFp4rPS2/vtRFG6OZ1m2zbN2CoOOBh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r2vIt/btsBFp4rPS2/vtRFG6OZ1m2zbN2CoOOBh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r2vIt/btsBFp4rPS2/vtRFG6OZ1m2zbN2CoOOBh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr2vIt%2FbtsBFp4rPS2%2FvtRFG6OZ1m2zbN2CoOOBh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1432&quot; height=&quot;1700&quot; data-filename=&quot;Untitled (26).png&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;1700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 모든 이벤트가 사전에 root 에 등록되어 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React가 이벤트를 핸들링하는 방식을 알아보았습니다. 이를 기반으로 보면 왜 ErrorBoundary가 이벤트핸들러에서 발생한 에러를 잡을 수 없는지 이해할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;root는 최상위 tag 이고, 이곳에서 이벤트 핸들링이 이루어집니다. 즉, Button 컴포넌트에서 onClick 이벤트가 다루어지는 것처럼 보이지만, 이는 root tag 에서 다루어지고 있고, 그곳에서 에러가 발생한다면 그것의 실행 컨텍스트는, &lt;b&gt;ErrorBoundary 내부에 있지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇기 때문에 ErrorBoundary가 이벤트 핸들러에서 발생한 에러를 잡을 수 없습니다.&lt;/b&gt; 이벤트 핸들러에서 에러를 핸들링하고 싶다면 try/catch 구문을 사용해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Case 2. 비동기적 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 ErrorBoundary 가 컴포넌트 내 비동기적 동작에서 발생하는 에러를 포착할 수 없는 이유를 알아보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export default function App() {
  return (
    &amp;lt;ErrorBoundary&amp;gt;
      &amp;lt;Children /&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  );
}

function Children() {
  function throwErrorFn() {
    throw new Error(&quot;will it be catched?&quot;);
  }
  setTimeout(throwErrorFn, 1000);
  return &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 위에서 예시로 보여드린 setTimeout - try/catch 와 완전히 동일한 케이스 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout 의 callback은 1초 후에, 실행 컨텍스트에 들어와서 실행되며, &lt;b&gt;이는 ErrorBoundary의 컨텍스트가 끝난 시점입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (27).png&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTLCK1/btsBHsMrpQD/k6dhQwdCxuZiv8oAO0kxg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTLCK1/btsBHsMrpQD/k6dhQwdCxuZiv8oAO0kxg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTLCK1/btsBHsMrpQD/k6dhQwdCxuZiv8oAO0kxg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTLCK1%2FbtsBHsMrpQD%2Fk6dhQwdCxuZiv8oAO0kxg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1964&quot; height=&quot;630&quot; data-filename=&quot;Untitled (27).png&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 실무적인 예시를 들어보겠습니다. axios를 통한 비동기 통신입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export default function App() {
  return (
    &amp;lt;ErrorBoundary&amp;gt;
      &amp;lt;Children /&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  );
}

function Children() {
  const [todos, setTodos] = useState([]);
  useEffect(() =&amp;gt; {
    (async () =&amp;gt; {
      try {
        const res = await axios.get(
          &quot;https://jsonplaceholder.typicode.com/todos21231&quot;
        );
        setTodos(res);
      } catch (e) {
        throw new Error(&quot;is it ?&quot;);
      }
    })();
  });

  return &amp;lt;button&amp;gt;click&amp;lt;/button&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서도 에러가 잡히지 않습니다. ErrorBoundary가 이를 잡을 수 있도록 하려면 어떻게 해야할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 도출한 원리를 그대로 활용하자면,&lt;b&gt; ErrorBoundary 내의 컨텍스트 내에서 throw 을 일으켜야 합니다.&lt;/b&gt; 그러므로 error 상태를 별도로 분리하고, 에러가 있으면 실행 컨텍스트 내에서 동기적으로 직접 던져버리면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function Children() {
  const [todos, setTodos] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() =&amp;gt; {
    (async () =&amp;gt; {
      try {
        const res = await axios.get(
          &quot;https://jsonplaceholder.typicode.com/todos21231&quot;
        );
        setTodos(res.data);
      } catch (e) {
        setError(e);
      }
    })();
  }, []);

  if (error) {
    throw error; // 에러 상태가 있으면 에러를 던집니다
  }

  return click;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-query 비동기 통신 라이브러리를 많이들 활용하실 텐데요. &lt;b&gt;useErrorBoundary (v5 에서는 throwOnError)&lt;/b&gt; 라는 옵션을 true로 설정하면 위의 기능을 대신해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Case 3. 서버 사이드 렌더링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 사이드에서 발생한 에러는 ErrorBoundary가 잡을 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ErrorBoundary는 &lt;b&gt;getDerivedStateFromError&lt;/b&gt; 를 기반으로, &lt;b&gt;프론트엔드 상태(hasError)를 변경하여 동작합니다&lt;/b&gt;. 이러한 상태를 변화시키는 메서드는 &lt;b&gt;클라이언트 사이드에서만 실행됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 사이드 렌더링은 서버 사이드에서(ex - node) API 를 요청하고 그 응답으로 HTML 을 만들어서 내려주는 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;getDerivedStateFromError 은 상태 변화가 존재하는 브라우저 환경에서만 실행되고, node 환경에서 실행되지 않습니다. 그러므로 본질적으로 서버 사이드에서 에러를 포착할 수 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Case 4. 자식에서가 아닌 에러 경계 자체에서 발생하는 에러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 try/catch 를 예시로 들어서 설명하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;try {
  throw new Error(&quot;Error&quot;);
} catch (e) {
  throw e;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;try 에서 에러가 발생하고, catch 에서 잡혔는데 이것을 다시 던진다면 해당 catch 에서 잡을 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 잡기 위해서는 다른 try/catch 가 필요한 것이지요.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;try {
  try {
    throw new Error(&quot;Error&quot;);
  } catch (e) {
    throw e;
  }
} catch (e) {
  console.log(e);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ErrorBoundary도 이와 동일합니다. ErrorBoundary 에서 다시 에러가 throw 된다면, 에러를 다시 상위 컴포넌트로 던지는 것입니다. 상위 ErrorBoundary가 있어야만 포착될 것이고, 그렇지 않다면 최상위에 Error가 도달할 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 ErrorBoundary가 포착할 수 없는 에러들과, 그 이유를 케이스별로 알아보았습니다. 이 케이스들이 ErrorBoundary의 특수한 성질이라고 생각하여 암기해야 한다고 생각할 수 있습니다. 하지만 이 원리는 실행 컨텍스트, React 이벤트 핸들링 방식, 서버 사이드 렌더링 원리에 기반하고 있습니다. 이것들을 기반으로 바라보면 아주 당연한 성질에 불과합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ErrorBoundary는 리액트로 서비스를 운영함에 있어 매우 중요합니다. 부분별로 에러 전파를 방지할 수 있기 때문에, 하나의 에러가 어플리케이션 전체에 영향을 주는 일을 방지해주죠. 하지만 이에 대한 이해가 부족한 상태로 사용하면 에러가 중간에 잡히지 않고 전체로 전파되는 현상이 발생할 수 있습니다. 이 글을 이해하고, 안정적인 서비스를 운영함에 있어 도움이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/docs/error-boundaries.html&quot;&gt;에러 경계(Error Boundaries) &amp;ndash; React&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html#changes-to-event-delegation&quot;&gt;React v17.0 Release Candidate: No New Features &amp;ndash; React Blog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.mathpresso.com/react-deep-dive-react-event-system-1-759523d90341&quot;&gt;React Deep Dive&amp;mdash; React Event System (1)&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/Sofeware Development</category>
      <category>ErrorBoundary</category>
      <category>react</category>
      <category>throwOnError</category>
      <category>useErrorBoundary</category>
      <category>비동기에러</category>
      <category>에러경계</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/66</guid>
      <comments>https://happysisyphe.tistory.com/66#entry66comment</comments>
      <pubDate>Sun, 10 Dec 2023 01:11:43 +0900</pubDate>
    </item>
    <item>
      <title>나는 왜 개발자가 되었는가</title>
      <link>https://happysisyphe.tistory.com/65</link>
      <description>&lt;h2&gt;프로그래머&lt;/h2&gt;
&lt;p&gt;저는 온전한 자기 의지로 프로그래밍을 시작했다고 생각합니다. 나라는 사람은 어떤 사람인지, 무엇을 좋아하는지, 무엇을 잘하는지, 수년간 고민한 결과로 내린 결론이기 때문입니다.&lt;/p&gt;
&lt;h2&gt;선한 영향력&lt;/h2&gt;
&lt;p&gt;지금의 시점에서 과거를 충분히 반추한 후 내린 결론입니다만, 저는 어릴 적부터 제가 가진 능력으로 남을 도와주는 것에서 아주 큰 보람을 느꼈습니다. 이는 어떠한 보상 없이도 스스로를 움직이게 하는 원동력이었습니다. 가령, 게임에서 퀘스트를 깨는 방법에 대한 블로그 글을 쓰기도 하고, 네이버 지식인에서 모르는 이들에게 답글을 달아주기도 하였습니다. 그리고 진로를 고민해봐야 할 나이가 되었을 때에는 정치인이 되리라 다짐했었습니다. 그 당시에는 공적인 일만이 사람들에게 선한 영향력을 줄 수 있는 일이라고 생각했기 때문입니다. 그리고 경세제민 즉, 세상을 경영하고 백성을 구제하는 학문인 경제학을 공부하기 시작했습니다.&lt;/p&gt;
&lt;h2&gt;경제학도&lt;/h2&gt;
&lt;p&gt;냉철한 머리와 따듯한 가슴을 요구하는 경제학은 매력적인 학문이었습니다. 수학과 인문학을 좋아했던 저를 사로잡기에 충분했습니다. 그렇게 몇년간 경제학을 탐구했습니다. 그러면서도 이때를 돌아보면 틈틈이 선한 영향력을 놓치지 않았습니다. 주위 친구들에게 경제학을 알려주는 등 제가 가진 능력으로 가치를 실현하고자 했습니다. 그리고 시간이 지나 직업을 선택해야 할 나이가 되었지만, 시원하게 직업을 결정하지 못 했습니다. 경제가 가진 무언가 미심쩍은 부분을 깨달았기 때문입니다.&lt;/p&gt;
&lt;p&gt;이유는 바로 경제의 불가항력 때문입니다. 최근 우리나라 금리가 상승하고 있습니다. 이는 미국이라는 불가항력이 작용한 결과라고 생각합니다. 이를 저항하면 일본의 엔화 가치 폭락 같은 결과를 맞을 수밖에 없을 것입니다. 이처럼 거대한 힘 아래에서의 행위는 제 기준에서 영향력 있는 행동이 아니라는 생각에 이릅니다. 이후 경제 분야 진로를 고민하기 시작합니다.&lt;/p&gt;
&lt;h2&gt;시지프 신화&lt;/h2&gt;
&lt;p&gt;저는 삶에 고민이 있을 때에 책에 기대는 경향이 있습니다. 모든 것을 제쳐두고, 독서에 몰두하는 시간을 보냅니다. 이때 운명 같은 책을 읽게 됩니다. 바로 시지프 신화라는 책입니다. 이 책에서 많은 이야기를 하지만, 제가 빠졌던 부분은 &amp;quot;창조&amp;quot;에 관한 이야기입니다. 알베르 카뮈는 현재를 사는 방법으로 창조를 언급합니다. 현재를 산다는 것은 과거를 후회하지 않고, 미래만 바라보고 살지 않으며, 지금 이 순간 자체를 즐기고 몰두하는 것을 의미합니다. 예시를 하나 들어보겠습니다. 어릴 때에 흙으로 모래성을 쌓아본 경험이 있을 것입니다. 지금 쌓고 있는 모래성이 영원하지 않을 것임을 압니다. 하지만 이 일 자체가 굉장히 즐겁습니다. 이처럼 창조는 어떤 이유 없이도 그 순간에 몰입할 수 있도록 만듭니다. 그러고 제가 순간을 온전히 즐기며 몰입했던 때는 언제인가 고민해보았습니다. 신입생 시절 공연을 준비할 때, 나만의 논문을 적었을 때 등의 창조했던 순간이 떠올랐습니다. 그때는 시간 가는 줄 모르고, 몇 달을 보냈었습니다. 그렇게 저는 창조와 선한 영향력을 끼칠 수 있는 일을 하고자 다짐합니다.&lt;/p&gt;
&lt;h2&gt;프로그래머&lt;/h2&gt;
&lt;p&gt;창조와 선한 영향력을 동시에 발휘할 수 있는 일은 많습니다. 개그맨, PD, 작가, 연구원 등 다양한 직업이 있을 수 있겠지요. 저 목표를 충족시켜줄 수 있다면, 어떠한 일이든 무방하다고 생각했습니다. 그렇다면 가장 잘 할 수 있는 일을 하는 것이 옳은 방향이겠지요. 경제학을 공부하며 몇 번의 프로그래밍을 해보았습니다. 이 경험을 통해 프로그래밍을 잘 해낼 수 있다는 확신이 있었기에, 개발자의 길을 걷기로 선택했습니다.&lt;/p&gt;
&lt;p&gt;정리하자면, 저는 창조하고 선한 영향력을 미치는 삶을 살아가기 위해 개발자가 되기로 다짐하였습니다. 창조하는 삶을 살며 현재를 즐기고 몰입할 수 있는 삶을 살아갈 것입니다. 창조의 결과가 선한 영향력이 될 수 있도록 삶을 살아갈 것입니다. 개발이란 실은 저의 가치를 실현하는 도구일 뿐입니다. 이 도끼가 얼만큼 좋은 도끼인지 확신할 수는 없습니다. 또한 제가 패고자 하는 장작도 어떠한 형태인지 아직도 선명하지 않습니다. 가령 제가 바라는 선한 영향력이 너비우선인지, 깊이우선인지 모르겠습니다. 다수의 사람들에게 선한 영향력을 미치는 것도 보람차고, 소수의 사람에게 깊이있는 영향력을 미치는 것도 보람찹니다. 그렇지만 확실한 것은, IT 서비스 개발자는 창조와 선한 영향력을 가능케한다는 것입니다. 현재까지의 결론으로는 알맞은 길을 걸어가고 있다고 생각합니다. 앞으로 개발자의 길을 살아가며 꾸준히, 고민하고 주도적으로 행동하며 스스로를 온전히 실현하기 위해 부단히 노력할 것입니다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/65</guid>
      <comments>https://happysisyphe.tistory.com/65#entry65comment</comments>
      <pubDate>Sat, 11 Nov 2023 23:28:44 +0900</pubDate>
    </item>
    <item>
      <title>[공지사항] 프론트엔드 직무 관련 무료 멘토링 진행해요 Ver.2</title>
      <link>https://happysisyphe.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2024.02.05 바빠져서 멘토링 쉬어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 아래와 같이, 멘토링을 하는 글을 썼는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://happysisyphe.tistory.com/61&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699627128486&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[공지사항] 프론트엔드 직무 관련 무료 멘토링 진행해요&quot; data-og-description=&quot;23.06.01 업무가 바쁜 관계로 잠깐 중단합니다. 안녕하세요. 블로그 주인 행복한 시지프 라고 합니다. 멘토링 활동에 관심이 많이 생겨서, 일단 무료 멘토링을 진행해보려고 합니다. 부족한 점이 &quot; data-og-host=&quot;happysisyphe.tistory.com&quot; data-og-source-url=&quot;https://happysisyphe.tistory.com/61&quot; data-og-url=&quot;https://happysisyphe.tistory.com/61&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bUpULq/hyUuW2fQe3/FunvEL8kheGyQQhWbQ3Z50/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/NT7vx/hyUrA7Da6R/VYKU4w4EwaTm0Fl48EhaGk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://happysisyphe.tistory.com/61&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bUpULq/hyUuW2fQe3/FunvEL8kheGyQQhWbQ3Z50/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/NT7vx/hyUrA7Da6R/VYKU4w4EwaTm0Fl48EhaGk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[공지사항] 프론트엔드 직무 관련 무료 멘토링 진행해요&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;23.06.01 업무가 바쁜 관계로 잠깐 중단합니다. 안녕하세요. 블로그 주인 행복한 시지프 라고 합니다. 멘토링 활동에 관심이 많이 생겨서, 일단 무료 멘토링을 진행해보려고 합니다. 부족한 점이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;happysisyphe.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인당 한시간씩 열명 정도 멘토링을 진행했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즐겁고 보람찬 시간이었습니다만, 사람들의 고민이 비슷하다는 생각이 들었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;똑같은 말을 되풀이 하게 되었을 때, 방식의 아쉬움을 많이 느꼈어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 조금씩 흥미가 떨어지고, 바쁜 일이 맞물리면서 잠정 중단하게 되었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 여전히 사람들을 도와주고 싶은 마음은 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그 방식을 변경해서 진행해보려고 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 시간을 효율적으로 쓰면서 더 많은 사람에게, 더 깊이있는 영향을 주고 싶어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 방식이 있겠지만, 일단 텍스트 방식을 취해보려고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 방식을 취하는 이유는, 서로가 충분히 고민한 채로 대화할 수 있으며, 비동기적으로 소통이 가능하고, 제가 시간이 날 때마다 틈틈이 답변을 드릴 수 있기 때문이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;댓글로 질문을 달아주시면, 내가 시간 날 때마다 틈틈이 답변을 드리도록 할게요. 필요 시에는 메일을 주셔도 돼요. (euijinkk97@gmail.com)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;멘토링 한줄 소개&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;비전공자에서 토스 프론트엔드 개발자가 되기까지. 학습방법을 많이 고민했고, 가장 효율적인 학습법을 제안해드려요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;나의 커리어 패스와 경험 소개&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2021.1 프론트엔드 개발 시작&lt;/li&gt;
&lt;li&gt;2021.2 발표시간 계산기 웹사이트 출시&lt;/li&gt;
&lt;li&gt;2021.3~2022.1 IT 벤처창업동아리(SOPT) 활동&lt;/li&gt;
&lt;li&gt;2022.2~10 우아한테크코스 4기&lt;/li&gt;
&lt;li&gt;2022.12~ 토스 프론트엔드 개발자&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2021년 개발을 시작하고, 1달도 안 돼서 발표시간 계산기 라는 웹사이트를 만들어서 배포했어요.&lt;br /&gt;6개월만에 20명에게 웹 프론트엔드 세미나 32시간을 진행해보았습니다.&lt;br /&gt;2022년에는 우아한테크코스 라는 교육기관에 들어갔고, 10개월간 트레이닝 했습니다.&lt;br /&gt;2022년 12월에 신입 프론트엔드 개발자로 토스에 입사했습니다.&lt;br /&gt;&lt;br /&gt;이처럼 빠르게 배우고, 성장하는 것에 능합니다. 머리가 좋아서가 아닙니다.&lt;br /&gt;경제학도로서 혼자 프론트엔드 개발을 시작하고, 토스에 오게되기까지 많은 학습 방법과 전략이 있었습니다. 멘티와 동일한 눈높이에서 최선의 방법을 제시해드리고, 고민을 들어드리려고 합니다.&lt;br /&gt;짧은 멘토링으로 수백시간을 아껴보세요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;어떤 주제로 이야기 할 수 있나요?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취업 전략 세우기&lt;/li&gt;
&lt;li&gt;프론트엔드 개발을 더 잘하기 위한 학습 전략 세우기&lt;/li&gt;
&lt;li&gt;학습 방법에 대한 고민 논의하기&lt;/li&gt;
&lt;li&gt;포트폴리오, 블로그 전략&lt;/li&gt;
&lt;li&gt;어떤 기업을 선택해야 할까?&lt;/li&gt;
&lt;li&gt;기타&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;질문에 아래 내용들이 담긴다면, 더욱 의미있는 대화가 가능할 것 같아요.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자신의 현재 상황 (개발 공부 기간, 현재 실력 등)&lt;/li&gt;
&lt;li&gt;구체적인 고민과 어떤 해결책들을 고민해보았는지&lt;/li&gt;
&lt;li&gt;핑퐁 보다는, 한번에 이해할 수 있게 고민을 적어주시고, 제가 깊이 고민해보고 답변드릴 수 있는 구조면 좋을 것 같아요. 질문이 이해가 되지 않는다거나, 제가 여러분에 대해서 가지고 있는 배경 지식이 부족하다면 충분한 답변을 드리기 어렵겠죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 고민이신 그것, 나누어주시면 제가 아는 한에서 도움을 드리도록 하겠습니다.&lt;/p&gt;</description>
      <category>Education</category>
      <category>멘토링</category>
      <category>프론트엔드</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/64</guid>
      <comments>https://happysisyphe.tistory.com/64#entry64comment</comments>
      <pubDate>Sat, 11 Nov 2023 00:00:04 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자가 서버를 공부하는 이유 / 학습 방법에 관한 글</title>
      <link>https://happysisyphe.tistory.com/63</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서언&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 최근에 서버 개발 공부의 필요성을 많이 느껴서, 그 필요성을 공유하고, 서버 기술 스택 채택 과정과 학습 방법을 정리하고자 글을 씁니다. 오늘은 가벼운 마음으로, 가볍게 글을 씁니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버를 공부하는 이유&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 엔지니어를 넘어서, 소프트웨어 엔지니어가 되기 위해서&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;소프트웨어 엔지니어가 되고 싶은 이유&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;나는 비즈니스 문제를 푸는 사람이 되고 싶다. 그러려면 시야를 넓혀야 한다. 문제를 프론트엔드 기술로만 푸는게 아니라, 서버/데이터/기획/디자인 무엇으로든 풀 수 있어야 한다. 그 관점에서, 먼저 프론트엔드 개발자에서 벗어나서 소프트웨어 엔지니어가 되어야 한다. 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;DB 에 대한 이해가 없으니, 팀의 DB 설계 토론에 참여하지 못 하니, 비즈니스 적으로 적절한 제안을 할 수 없다. 그냥 팔로워가 될 뿐이다.&lt;/li&gt; 
   &lt;li&gt;DB 구조를 모르고, SQL을 모르니, 데이터를 직접 조회하기 어렵다. 필요한 데이터를 내가 직접 얻을 수 있다면, 더 많은 인사이트를 가질 수 있을 것 같다.&lt;/li&gt; 
   &lt;li&gt;요악하자면, 의사결정을 더 잘하는 사람이 되기 위해서.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;현실적으로는, 해외에서 개발자로 일 하고 싶은데, 찾아보니 소프트웨어 엔지니어 직종으로 많이 뽑고, 프론트엔드/백엔드를 많이 구분하지 않는 것을 알았다.&lt;/li&gt; 
&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br&gt;무슨 언어/프레임워크를 사용할 것인가?&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;서버의 개괄적인 동작, 개념들을 익히고 싶다. 그리고 최대한 빠르게 서비스를 만들 수 있는 수준까지 다다르고 싶다. 그래서 익숙한 JavaScript(node.js)로 학습할 것이다. 근본적으로 node냐 Java냐 하는 것은 중요하지 않을 것이다. 언어는 기술을 구현하기 위한 도구에 불과하다. “비동기” 라는 기술이 있고, 그것을 다루는 각각의 구현체들이 node, Java에 들어가 있는 것이다. 그러므로 하나의 구현체를 이해하고 사용할 수 있다면, 다른 것들 형식만 바꾼 것에 불과한 것이다.&lt;/li&gt;&lt;li&gt;node로 하기로 마음을 먹었고, 프레임워크로는 일단 가장 기본적인 express로 학습할 것이다. nest.js 는 express를 확장성 있게/유지보수 하기 좋게 짤 수 있도록 하는 프레임워크이다보니, express를 모르고 nest.js 패턴을 채택하는 것은 적절하지 않아 보인다. (express, nest 둘 다 조금 학습해보고 든 생각) 내 목적을 이루기에는 express로 충분하고, 구조에 대한 필요성을 느낀다면, nest.js 도 학습해볼 것이다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 방법&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;내가 새로운 기술을 익히는 방법이 있다. 가장 빠르게, 기본적인 기능을 만들어보는 것이다. 프론트엔드/백엔드의 경우, 가장 빠르게 Todo List를 만들어보는 것이다. 그것을 만들기 위한 배경지식은 전혀 중요하지 않고, 정상적으로 동작하는 기능을 빠르게 따라 만들어보는 것이 중요하다. 그러면 기본적으로 재미가 있고, 자신감이 생긴다. 생각보다 별 것 없음을 깨닫는다.&lt;br&gt;&lt;br&gt;같은 선상에서 더 얘기해보자면, 처음 학습할 때 불필요한 것들을 파고드는 것을 크게 지양한다. 초급자가 DB의 종류에 대해서 심도있게 학습하는 것이 그 예이다. 갑자기 SQL, NoSQL 을 학습하고, 그 중에서도 어떤 것을 택할지 계속 찾아보는 것이다. 이것은 중요하지 않은 것 같다. 그리고 학습의 흥미를 떨어뜨릴 수 있다. 그냥 가장 많이 쓰이고, 간편한, 혹은 많이 들어본 것을 사용하는 것이다. Todo List를 구현하는 데 무엇이 중요하겠는가.&lt;br&gt;내가 이번에 학습하면서 Chat GPT에게 많이 한 질문의 흐름은 다음과 같다&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;나 : A 어떻게 구현해?&lt;/li&gt;&lt;li&gt;GPT : A 는, B, C, D 등의 라이브러리를 활용하여 구현할 수 있습니다.&lt;/li&gt;&lt;li&gt;나 : 그 중에서 가장 쉽고 많이 쓰이는 라이브러리가 무엇이니?&lt;/li&gt;&lt;li&gt;GPT : B 입니다.&lt;/li&gt;&lt;li&gt;나 : B를 써보려고 하는데, 간단한 가이드라인을 제시해줘&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 별 생각 없이, DB(MongoDB), 로그(winston, morgan), 유효성 검사(express-validator) 를 골라서 학습했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다시 돌아와서, 나는 기본적인 기능들인 Todo List, 유저/게시판/댓글 기능을 빠르게 만들어보는 것을 목표로 학습했다. 한 두세시간만에 프로젝트 첫 세팅부터 Todo List CRUD 까지 만들어본 것 같다.&lt;br&gt;&lt;br&gt;Todo List를 먼저 구현하고, 지식을 일부 보강하고, 게시판을 구현하고, 지식을 일부 보강하고, 댓글을 구현하는 식으로 학습했다. 게시물을 구현하다보니, 유효성 검사/response 단일화/로깅 등에 대한 필요성이 생겨서 이것들을 보강하고 리팩터링 한 후에, 댓글에 적용해보는 식으로 학습하였다.&lt;br&gt;&lt;a href=&quot;https://github.com/euijinkk/express-practice-log&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/euijinkk/express-practice-log&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - euijinkk/express-practice-log&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Contribute to euijinkk/express-practice-log development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/euijinkk/express-practice-log&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/erMkuu/hyT2yhqd8y/ANKMo1UUpBTbs52CrcmFL0/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_125_1042_173&quot; data-og-url=&quot;https://github.com/euijinkk/express-practice-log&quot;&gt;&lt;a href=&quot;https://github.com/euijinkk/express-practice-log&quot; target=&quot;_blank&quot; data-source-url=&quot;https://github.com/euijinkk/express-practice-log&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/erMkuu/hyT2yhqd8y/ANKMo1UUpBTbs52CrcmFL0/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_125_1042_173')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;GitHub - euijinkk/express-practice-log&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Contribute to euijinkk/express-practice-log development by creating an account on GitHub.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Chat GPT&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;GPT와 함께 학습하다보니, 학습속도가 5배는 빠른 것 같다. 궁금증을 10초만에 해결할 수 있다. 처음 공부하는 사람 입장에서는 사실 모든게 궁금하다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;app.use 랑 router.use 뭐가 다르지?&lt;/li&gt;&lt;li&gt;router paramter랑 query paramter 가 뭐가 다른지?&lt;/li&gt;&lt;li&gt;populate는 무엇인지? join 은 무엇인지?&lt;/li&gt;&lt;li&gt;미들웨어의 실행순서는 어떻게 되는지?&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;등등을 검색하여 좋은 블로그 글을 골라서 읽을 필요 없이, 매우 도움되는 답변을 매우 빠르게 얻을 수 있었다.&lt;br&gt;1뎁스 지식을 빠르고 능동적으로 공부하기에 정말 좋은 것 같다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 간단한 CRUD 류 서비스는 만들 수 있을 것 같다. 서버라는 것이 학습할 양이 아주 방대하겠지만, 좋은 스타트를 했고, 앞으로 꾸준히 학습해볼 것이다. 여담이지만, 프론트엔드 기술을 공부하는 데도 여러모로 도움이 될 것 같다. 가령, node의 테스트툴로 jest는, 프론트엔드 개발에서도 많이 활용된다. 서버 코드를 짜다보니, 테스트 하기 쉬운 코드를 짜기 위해서 책임 분리가 중요하다는 말이 무슨 말인지 이해할 수 있게 되었다.&lt;br&gt;&lt;br&gt;그리고 우리팀 서버개발자들의 고민/개발 과정을 조금이라도 이해할 수 있게 되었다. “아, 이래서 이 API에 이 필드를 추가하는 것을 망설였던 것이군” 라는 생각이 들었다.&lt;/p&gt;</description>
      <category>Tech/Sofeware Development</category>
      <category>express</category>
      <category>백엔드</category>
      <category>프론트엔드</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/63</guid>
      <comments>https://happysisyphe.tistory.com/63#entry63comment</comments>
      <pubDate>Fri, 29 Sep 2023 22:18:34 +0900</pubDate>
    </item>
    <item>
      <title>React CleanCode #1, 합성으로 관심사를 분리하기</title>
      <link>https://happysisyphe.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SUZWn/btsf4gsXxvD/pbaSFQ1k33fGg7ypx8WvBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SUZWn/btsf4gsXxvD/pbaSFQ1k33fGg7ypx8WvBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SUZWn/btsf4gsXxvD/pbaSFQ1k33fGg7ypx8WvBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSUZWn%2Fbtsf4gsXxvD%2FpbaSFQ1k33fGg7ypx8WvBK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;221&quot; height=&quot;221&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. &lt;b&gt;React CleanCode&lt;/b&gt; 첫 번째 주제로 &lt;b&gt;Composition(합성)&lt;/b&gt;을 다룹니다. 최근에 회사에서 많은 코드를 작성하면서 느끼는 것이 있었는데요. 바로 프론트엔드가 &lt;b&gt;다루어야할 관심사가 너무나 많다는 것&lt;/b&gt;입니다. 크게는 UI 로직(단순 UI, 애니메이션 로직, 하드코딩적인 요소), 서버 로직(데이터 패칭, 업데이트 로직, 유저 인증인가 로직, 로딩처리, 에러처리), 로그 등 이 있습니다. React를 사용하며 이러한 관심사를 잘 분리하지 않는다면, 스파게티 코드가 된다는 것을 체감했습니다. 그러면 어떻게 관심사의 지옥에서 벗어날 수 있을까요? 즉 관심사를 어떻게 잘 분리해야 할까요? 관심사 분리는 보통 함수(클래스) 분리를 통해 이루어집니다. &lt;b&gt;React에서 함수의 실체는 훅, 컴포넌트, 유틸함수 입니다.&lt;/b&gt; 유틸함수는 리액트에 종속된 이야기는 아니고, 리팩터링이나 클린코드 같은 책에서 잘 설명하고 있습니다. 그래서 앞으로 훅과 컴포넌트 분리 방법을 이야기할 것이며, 오늘은 그중에서 &lt;b&gt;합성을 통한 관심사 분리 방법&lt;/b&gt;을 다루겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;합성(Composition)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 합성에 대해서 간단하게 설명하고자 합니다.&lt;br /&gt;&lt;b&gt;React에서 합성이란 컴포넌트 안에 다른 컴포넌트를 담는 방법&lt;/b&gt; 입니다. 컴포넌트 자체를 props 로 넘겨주는 방식을 취합니다. 이때 다음과 같이 children을 활용한 방법이 주로 사용됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function GreenBackground(props) {
  return &amp;lt;div style={{ backgroundColor: 'green' }}&amp;gt;{props.children}&amp;lt;/div&amp;gt;;
}

function Forest() {
  return (
    &amp;lt;GreenBackground&amp;gt;
      &amp;lt;Tree /&amp;gt;
      &amp;lt;Tree /&amp;gt;
      &amp;lt;Tree /&amp;gt;
    &amp;lt;/GreenBackground&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;합성의 목적&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합성의 목적은 무엇일까요?&lt;br /&gt;우리가 흔히 사용하는 합성 패턴의 목적은 &lt;b&gt;&amp;ldquo;재사용&amp;rdquo;&lt;/b&gt; 입니다. 공식문서에서도 이러한 예제를 다루고 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://ko.legacy.reactjs.org/docs/composition-vs-inheritance.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;i&gt;&amp;ldquo;어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올 지 미리 예상할 수 없는 경우가 있습니다. 범용적인 &amp;lsquo;박스&amp;rsquo; 역할을 하는&amp;nbsp;Sidebar&amp;nbsp;혹은&amp;nbsp;Dialog와 같은 컴포넌트에서 특히 자주 볼 수 있습니다.&amp;rdquo;&lt;/i&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다시 말하자면, 내부에 들어갈 엘리먼트의 형태가 정해져 있지 않아, 유연하게 받아서 재사용성을 극대화하려는 목적에서 쓰입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이것도 매우 중요하지만, 이것만큼 중요하다고 생각하는 포인트를 다루고 싶습니다. 바로&lt;b&gt; &amp;ldquo;관심사의 분리(Separation of concerns)&amp;rdquo;&lt;/b&gt; 목적입니다. 관심사란, 특정 기능이나 역할을 의미하고, 관심사의 분리란, 특정 기능이나 역할에 따라서 분리하는 것을 의미합니다. 관심사를 분리하는 목적은 무엇일까요?&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명확한 관리포인트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 컴포넌트가 B, C, D, E 관심사를 가지고 있는 상황이면, B 관심사를 수정하게 위해서 A 컴포넌트를 찾아가서, B, C, D, E 를 모두 읽으며 판단해야하는 문제&lt;/li&gt;
&lt;li&gt;관심사가 잘 분리되어 있다면, B에 수정사항이 생겼을 때 B를 가진 컴포넌트만 읽으면 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가독성 증대&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수는 하나의 일만 하도록 하라! 고 클린코드에 쓰여져있는데요. 같은 관점입니다. 컴포넌트(함수)가 하나의 역할을 할 때, 가독성이 좋아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존관계의 분리로 인한, 에러 가능성 저하&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스파게티 처럼 꼬여있지 않고, 명확한 역할을 하는 컴포넌트들로 나누어져 있으면 로직을 파악하기 쉽고, 장기적으로 에러 가능성을 낮춥니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 합성을 통해서 어떻게 관심사를 분리할 수 있는지 구체적으로 설명드려보겠습니다. 먼저 스파게티 코드를 소개하고, 5번의 Step으로 관심사를 분리해보며 합성의 힘을 느껴보도록 합시다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 0 : 스파게티 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파게티 코드를 보여드리겠습니다.&lt;br /&gt;아래 코드는, 회원의 좋아요 리스트를 보여주는 페이지를 구현한 컴포넌트 입니다. 코드를 모두 이해하실 필요는 없습니다. 주석을 중심으로, 이 페이지가 다루는 관심사에는 어떤 것들이 있는지 읽어주시면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;export default function LikeListPage() {
  // 1. 유저 정보를 가지고 오는 Query
  const meQuery = useQuery([&quot;me&quot;], () =&amp;gt; getMe());
  // 2. 유저 정보가 있으면, 좋아요 리스트를 가지고 오는 Query
  const likeListQuery = useQuery([&quot;likeList&quot;], () =&amp;gt; getLikeList(), {
    enable: meQuery.data != null,
  });

  const router = useRouter();

  // 3. 토스트 로직
  const [message, setMessage] = useState(&quot;&quot;);
  const toastTimer = useRef&amp;lt;ReturnType&amp;lt;typeof setTimeout&amp;gt; | null&amp;gt;(null);
  const [isOpenToast, setIsOpenToast] = useState(false);
  const showToast = (message: string) =&amp;gt; {
    setIsOpenToast(true);
    setMessage(message);

    if (toastTimer.current) {
      clearTimeout(toastTimer.current);
    }

    const timer = setTimeout(() =&amp;gt; {
      setIsOpenToast(false);
      setMessage(&quot;&quot;);
    }, 3000);
    toastTimer.current = timer;
  };

  // 4. Mount 시에 amplitude 를 활용한 page log 를 찍는다.
  useEffect(() =&amp;gt; {
    amplitude.getInstance().logEvent(&quot;viewed-page&quot;, {
      pageName: &quot;Home&quot;,
      pageTitle: &quot;My Awesome Website&quot;,
      url: window.location.href,
    });
  }, []);

  // 5. Mount 시에 scroll을 최상단까지 올린다.
  useEffect(() =&amp;gt; {
    window.scrollTo({ top: 0 });
  }, []);

  // 6. 유저 정보가 없을 경우, Login 시키기
  useEffect(() =&amp;gt; {
    if (meQuery == null) {
      router.push(&quot;/login&quot;);
    }
  }, []);

  // 7. Query 로딩을 처리한다.
  if (meQuery.isLoading || likeListQuery.isLoading) {
    return &amp;lt;Loading /&amp;gt;;
  }

  // 8. Query 에러를 처리한다.
  if (meQuery.isError || likeListQuery.isError) {
    return &amp;lt;ErrorPage /&amp;gt;;
  }

  // 6. 유저 정보가 없을 경우, Guard
  if (meQuery == null) {
    return null;
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
      {isOpenToast &amp;amp;&amp;amp; (
        &amp;lt;Toast message={message} onClick={() =&amp;gt; setIsOpenToast(false)} /&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드가 가질 수 있는 관심사의 대부분 서술해본 코드입니다. 운영 중인 서비스라면 위의 관심사 중 많은 것들을 가지고 있을 것입니다.&lt;br /&gt;코드가 어떻게 느껴지시나요? 크게 이상하다고 느끼지 않으실 수도 있습니다. 페이지 컴포넌트에서 가져야 할 로직을 잘 가지고 있어 문제가 없다고 느끼실 수도 있습니다. &lt;b&gt;하지만 위 코드는 관심사 분리 측면에서 봤을 때, 잘못 작성된 코드입니다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;관심사 분리의 적절성은 &lt;b&gt;&amp;ldquo;함수가 단일 관심사(책임) 가지고 있는가&amp;rdquo;&lt;/b&gt; 를 기준으로 생각하면 됩니다. LikeListPage라는 함수가 하나의 책임을 가지고 있나요?&lt;br /&gt;&amp;nbsp;&lt;br /&gt;관심사를 숫자로 카운팅을 해보았는데요. 8개의 관심사가 존재합니다. 정리해보자면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;회원/비회원 판별&lt;/li&gt;
&lt;li&gt;좋아요 데이터 받아오기&lt;/li&gt;
&lt;li&gt;토스트&lt;/li&gt;
&lt;li&gt;로깅&lt;/li&gt;
&lt;li&gt;마운트 시 스크롤&lt;/li&gt;
&lt;li&gt;로딩 처리&lt;/li&gt;
&lt;li&gt;에러 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;좋아요 리스트를 보여주는 LikeListPage 가 7개의 관심사를 가지고 있습니다.&lt;br /&gt;5단계에 거쳐서 관심사를 분리하여, &lt;b&gt;각 컴포넌트가 최대한 하나의 책임을 가지도록 만들어볼 것&lt;/b&gt;입니다. 먼저 해보고 오셔도 좋습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 1 : Loading, Error 관심사 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로, 간단하게 로딩과 에러 로직을 분리하려고 합니다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;// 기존 코드
// Query 로딩을 처리한다.
if (meQuery.isLoading || likeListQuery.isLoading) {
  return &amp;lt;Loading /&amp;gt;;
}

// Query 에러를 처리한다.
if (meQuery.isError || likeListQuery.isError) {
  return &amp;lt;ErrorPage /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 보통 Loading은 Suspense를 통해서, Error는 ErrorBoundary를 통해서 분리합니다. 이게 왜 합성이냐구요? 이들도 컴포넌트가 컴포넌트를 리턴하는 패턴이기 때문입니다.&lt;br /&gt;가령 에러바운더리는, 에러가 존재할 경우 ErrorPage Fallback을 return 하고, 정상 케이스일 경우, children을 return 합니다.&lt;br /&gt;서스펜스는, 로딩 상태일 경우, Loading Fallback 을 return 하고, 그렇지 않을 경우 children을 return 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래는 리팩터링한 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// Loading, Error 관심사 분리
export default function LikeListPage() {
  return (
    &amp;lt;ErrorBoundary fallback={&amp;lt;ErrorPage /&amp;gt;}&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
        &amp;lt;LikeList /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LikeList 컴포넌트에서 로딩 처리, 에러 처리를 관심사로 가지지 않고, 상단 Suspense, ErrorBoundary에서 관리하고 있음을 알 수 있습니다. LikeList 는 성공 케이스에만 관심을 가지면 됩니다.&lt;br /&gt;LikeList 에서 불필요한 if 문을 없애서, 한결 컴포넌트 흐름을 파악하기 쉬워졌습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step2 : 비회원 관심사 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 회원 인가(Authentication)에 대한 관심사를 분리하려고 합니다. LikeList 컴포넌트는 애초에 회원인 경우에만 보여지면 되는 것이고, 회원인 상황에만 관심을 가지면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 기존 코드
function LikeList() {
  // 유저 정보를 가지고 오는 Query
  const meQuery = useQuery([&quot;me&quot;], () =&amp;gt; getMe(), { suspense: true });
  // 유저 정보가 있으면, 좋아요 리스트를 가지고 오는 Query
  const likeListQuery = useQuery([&quot;likeList&quot;], () =&amp;gt; getLikeList(), {
    enable: meQuery.data != null,
    suspense: true,
  });

  // 유저 정보가 없을 경우, Login 시키기
  useEffect(() =&amp;gt; {
    if (meQuery == null) {
      router.push(&quot;/login&quot;);
    }
  }, []);

  // 유저 정보가 없을 경우, Guard
  if (meQuery == null) {
    return null;
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 컴포넌트에서는 회원이 아닌 경우 로그인 페이지로 라우팅 하거나, 회원인 경우에만 좋아요 리스트를 패칭해오는 등, 관심사가 얽혀있는 모습을 볼 수 있습니다. 이를 합성을 사용해 분리해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;회원이 아닌 경우, Guard 하여 LikeList를 렌더링 하지 못 하게 만드는 구현을 써보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function UserGuard({ children }: { children: ReactNode }) {
  const router = useRouter();

  // 유저 정보를 가지고 오는 Query
  const meQuery = useQuery([&quot;me&quot;], () =&amp;gt; getMe(), { suspense: true });

  // 유저 정보가 없을 경우, Login 시키기
  useEffect(() =&amp;gt; {
    if (meQuery == null) {
      router.push(&quot;/login&quot;);
    }
  }, []);

  // 유저 정보가 없을 경우, Guard
  if (meQuery == null) {
    return null;
  }

  return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserGuard 컴포넌트에서 유저 정보를 받아오고, 비회원인 경우 렌더링 하지 않고 라우팅 시키는 방식으로 관심사를 분리했습니다.&lt;br /&gt;이렇게 되면 LikeList는 이미 회원인 경우에만 렌더링 되는 것이니, LikeList는 &amp;ldquo;회원의 좋아요 리스트&amp;rdquo;만 관심사로 가지면 됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래처럼 상위에서 감싸주기만 하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export default function LikeListPage() {
  return (
    &amp;lt;UserGuard&amp;gt;
      &amp;lt;LikeList /&amp;gt;
    &amp;lt;/UserGuard&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step3 : 토스트 관심사 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 토스트 로직인데요. LikeList에서는 토스트 로직을 가지고 와서 사용하기만 하면 될뿐, 구체적인 구현체를 알 필요는 없습니다. 기존 코드는 토스트 관심사 때문에, 코드 흐름 파악에 어려움이 생깁니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 기존 코드
function LikeList() {
  // 토스트 로직
  const [message, setMessage] = useState(&quot;&quot;);
  const toastTimer = useRef&amp;lt;NodeJS.Timeout&amp;gt;();
  const [isOpenToast, setIsOpenToast] = useState(false);
  const showToast = (message: string) =&amp;gt; {
    setIsOpenToast(true);
    setMessage(message);

    if (toastTimer.current) {
      clearTimeout(toastTimer.current);
    }

    const timer = setTimeout(() =&amp;gt; {
      setIsOpenToast(false);
      setMessage(&quot;&quot;);
    }, 3000);
    toastTimer.current = timer;
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
      {isOpenToast &amp;amp;&amp;amp; (
        &amp;lt;Toast message={message} onClick={() =&amp;gt; setIsOpenToast(false)} /&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Toast 로직도 합성을 통해서 외부로 분리하려고 합니다.&lt;br /&gt;ToastProvider를 만들었습니다. Provider도 합성 입니다. Provider는 로직을 하위 Context로 내려주면서 children을 return 하는 함수입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 토스트 관심사 분리
const ToastContext = createContext({ showToast(message: string) {} });
const ToastProvider = ({ children }: { children: ReactNode }) =&amp;gt; {
  const [message, setMessage] = useState(&quot;&quot;);
  const [isOpenToast, setIsOpenToast] = useState(false);
  const toastTimer = useRef&amp;lt;NodeJS.Timeout&amp;gt;();

  const showToast = (message: string) =&amp;gt; {
    setIsOpenToast(true);
    setMessage(message);

    if (toastTimer.current) {
      clearTimeout(toastTimer.current);
    }

    const timer = setTimeout(() =&amp;gt; {
      setIsOpenToast(false);
      setMessage(&quot;&quot;);
    }, 3000);
    toastTimer.current = timer;
  };

  return (
    &amp;lt;ToastContext.Provider value={{ showToast }}&amp;gt;
      {children}
      {isOpenToast &amp;amp;&amp;amp; &amp;lt;Toast message={message} /&amp;gt;}
    &amp;lt;/ToastContext.Provider&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context API 를 통한 Toast 설계는 아래 글에서 구체적으로 살펴보실 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/51&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://happysisyphe.tistory.com/51&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Toast 로직을 ToastProvider에 응집시켜준 후, 세부 구현을 숨기고, showToast라는 trigger 함수만 내려주어 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function LikeListPage() {
  return (
    &amp;lt;ToastProvider&amp;gt;
      &amp;lt;LikeList /&amp;gt;
    &amp;lt;/ToastProvider&amp;gt;
  );
}

function LikeList() {
  // 유저 정보가 있으면, 좋아요 리스트를 가지고 오는 Query
  const likeListQuery = useQuery([&quot;likeList&quot;], () =&amp;gt; getLikeList(), {
    suspense: true,
  });

  const { showToast } = useContext(ToastContext);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step4 : 로깅 관심사 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 로깅 관심사를 분리하려고 합니다. 기존 코드를 먼저 살펴봅시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 기존 코드
function LikeList() {
  // Mount 시에 amplitude 를 활용한 page log 를 찍는다.
  useEffect(() =&amp;gt; {
    amplitude.getInstance().logEvent(&quot;viewed-page&quot;, {
      pageName: &quot;Home&quot;,
      pageTitle: &quot;My Awesome Website&quot;,
      url: window.location.href,
    });
  }, []);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
      {isOpenToast &amp;amp;&amp;amp; (
        &amp;lt;Toast message={message} onClick={() =&amp;gt; setIsOpenToast(false)} /&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 아래 두가지 원칙을 지키면 더 나은 코드가 될 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;재사용이 가능해야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;amplitude 라는 세부 구현은 드러내지 않는다.&lt;/b&gt; 로깅 툴에 변경사항이 발생했을 때, 함수의 구현부만 수정하면 되도록 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 합성을 통해서 분리해보도록 합니다. LoggingPage 컴포넌트를 정의합니다. Effect 로직을 가지고, children을 그대로 뱉어줌으로써, 세부 구현을 숨기고, 관심사만 정확히 분리해냅니다. props 를 적절히 뚫어서 재사용이 가능한 형태로 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function LoggingPage({
  pageName,
  pageTitle,
  children,
}: {
  pageName: string;
  pageTitle: string;
  children: ReactNode;
}) {
  // Mount 시에 amplitude 를 활용한 page log 를 찍는다.
  useEffect(() =&amp;gt; {
    amplitude.getInstance().logEvent(&quot;viewed-page&quot;, {
      pageName,
      pageTitle,
      url: window.location.href,
    });
  }, [pageName, pageTitle]);

  return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 형태로 사용만 해주면, 로깅을 깔끔하게 찍을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function LikeList() {
	// ...

  return (
    // 합성을 띄워줌으로써 관심사 분리
    &amp;lt;LoggingPage pageName=&quot;Home&quot; pageTitle=&quot;My Awesome Website&quot;&amp;gt;
      // ...
    &amp;lt;/LoggingPage&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 5: scrollToTop 관심사 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 어플리케이션을 구현하다보면, 스크롤을 제어해야할 때가 많습니다. 기존 코드에서는 LikeList 내부에 useEffect 로 스크롤을 올려주고 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 기존 코드
function LikeList() {
  // Mount 시에 scroll을 최상단까지 올린다.
  useEffect(() =&amp;gt; {
    window.scrollTo({ top: 0 });
  }, []);

  // ...

  return (
    &amp;lt;LoggingPage pageName=&quot;Home&quot; pageTitle=&quot;My Awesome Website&quot;&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
        {isOpenToast &amp;amp;&amp;amp; (
          &amp;lt;Toast message={message} onClick={() =&amp;gt; setIsOpenToast(false)} /&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/LoggingPage&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Effect 로직이 하나만 있으면 상관이 없겠지만, 컴포넌트에서는 다양한 Effect가 들어올 수 있습니다. 그런 경우, 각 Effect가 어떤 역할을 하는지 한번에 파악하기 어렵고, 이는 컴포넌트 흐름을 파악하는데 어려움을 만듭니다. 관심사의 분리와 더불어, 재사용의 효과도 얻을 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;합성을 사용해서 이를 구현해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function ScrollToTop({ children }: { children: ReactNode }) {
  // Mount 시에 scroll을 최상단까지 올린다.
  useEffect(() =&amp;gt; {
    window.scrollTo({ top: 0 });
  }, []);

  return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅 예시와 동일하게 Effect를 수행하며, children을 그대로 return 하는 합성 컴포넌트입니다. 관심사를 명확히 분리해줍니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function LikeList() {
	// ...

  return (
    &amp;lt;LoggingPage pageName=&quot;Home&quot; pageTitle=&quot;My Awesome Website&quot;&amp;gt;
      &amp;lt;ScrollToTop&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/ScrollToTop&amp;gt;
    &amp;lt;/LoggingPage&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;LoggingPage, ScrollToTop 의 구현은 훅이나, (합성이 아닌) 일반 컴포넌트 형태로도 관심사를 분리할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;훅으로 구현 예시&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function useScrollToTop() {
  // Mount 시에 scroll을 최상단까지 올린다.
  useEffect(() =&amp;gt; {
    window.scrollTo({ top: 0 });
  }, []);
}

function LikeList() {
  useScrollToTop();

  return (
    // ...
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;일반 컴포넌트로 구현 예시&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function ScrollToTop({ children }: { children: ReactNode }) {
  // Mount 시에 scroll을 최상단까지 올린다.
  useEffect(() =&amp;gt; {
    window.scrollTo({ top: 0 });
  }, []);

  return null;
}

function LikeList() {
  // ...
  return (
    &amp;lt;LoggingPage pageName=&quot;Home&quot; pageTitle=&quot;My Awesome Website&quot;&amp;gt;
      &amp;lt;ScrollToTop /&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/LoggingPage&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합성 컴포넌트, 훅, 일반 컴포넌트 3가지 방법은 재사용 / 관심사를 분리하는 목적으로 모두 쓰입니다. 상황에 따라서 필요에 따라서 유연하게 사용하면 좋습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 리팩터링 코드&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 리팩터링된 코드를 첨부합니다. 스파게티가 해체된 것이 느껴지시나요? 한 컴포넌트가 하나의 관심사를 가지고, 그것을 합성하여 로직을 완성하는 모습을 볼 수 있습니다. 이는 유지 보수성이 매우 좋습니다. 회원 로직을 수정하기 위해서는 UserGuard만 찾아가면 됩니다. 토스트 로직은 ToastProvider만 찾아가면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export default function LikeListPage() {
&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ToastProvider&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ErrorBoundary fallback={&amp;lt;ErrorPage /&amp;gt;}&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;UserGuard&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;LikeList /&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/UserGuard&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/Suspense&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ErrorBoundary&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ToastProvider&amp;gt;
&amp;nbsp;&amp;nbsp;);
}

function UserGuard({ children }: { children: ReactNode }) {
&amp;nbsp;&amp;nbsp;const router = useRouter();

&amp;nbsp;&amp;nbsp;// 유저 정보를 가지고 오는 Query
&amp;nbsp;&amp;nbsp;const meQuery = useQuery([&quot;me&quot;], () =&amp;gt; getMe(), { suspense: true });

&amp;nbsp;&amp;nbsp;// 유저 정보가 없을 경우, Login 시키기
&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (meQuery == null) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;router.push(&quot;/login&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}, []);

&amp;nbsp;&amp;nbsp;// 유저 정보가 없을 경우, Guard
&amp;nbsp;&amp;nbsp;if (meQuery == null) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return null;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
}

const ToastContext = createContext({ showToast(message: string) {} });
const ToastProvider = ({ children }: { children: ReactNode }) =&amp;gt; {
&amp;nbsp;&amp;nbsp;const [message, setMessage] = useState(&quot;&quot;);
&amp;nbsp;&amp;nbsp;const [isOpenToast, setIsOpenToast] = useState(false);
&amp;nbsp;&amp;nbsp;const toastTimer = useRef&amp;lt;NodeJS.Timeout&amp;gt;();

&amp;nbsp;&amp;nbsp;const showToast = (message: string) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setIsOpenToast(true);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setMessage(message);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (toastTimer.current) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clearTimeout(toastTimer.current);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const timer = setTimeout(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setIsOpenToast(false);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setMessage(&quot;&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, 3000);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;toastTimer.current = timer;
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ToastContext.Provider value={{ showToast }}&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{children}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{isOpenToast &amp;amp;&amp;amp; &amp;lt;Toast message={message} /&amp;gt;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ToastContext.Provider&amp;gt;
&amp;nbsp;&amp;nbsp;);
};

function LoggingPage({
&amp;nbsp;&amp;nbsp;pageName,
&amp;nbsp;&amp;nbsp;pageTitle,
&amp;nbsp;&amp;nbsp;children,
}: {
&amp;nbsp;&amp;nbsp;pageName: string;
&amp;nbsp;&amp;nbsp;pageTitle: string;
&amp;nbsp;&amp;nbsp;children: ReactNode;
}) {
&amp;nbsp;&amp;nbsp;// Mount 시에 amplitude 를 활용한 page log 를 찍는다.
&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;amplitude.getInstance().logEvent(&quot;viewed-page&quot;, {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pageName,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pageTitle,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url: window.location.href,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}, [pageName, pageTitle]);

&amp;nbsp;&amp;nbsp;return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
}

function ScrollToTop({ children }: { children: ReactNode }) {
&amp;nbsp;&amp;nbsp;// Mount 시에 scroll을 최상단까지 올린다.
&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;window.scrollTo({ top: 0 });
&amp;nbsp;&amp;nbsp;}, []);

&amp;nbsp;&amp;nbsp;return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
}

function LikeList() {
&amp;nbsp;&amp;nbsp;// 유저 정보가 있으면, 좋아요 리스트를 가지고 오는 Query
&amp;nbsp;&amp;nbsp;const likeListQuery = useQuery([&quot;likeList&quot;], () =&amp;gt; getLikeList(), {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspense: true,
&amp;nbsp;&amp;nbsp;});

&amp;nbsp;&amp;nbsp;const { showToast } = useContext(ToastContext);

&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;LoggingPage pageName=&quot;Home&quot; pageTitle=&quot;My Awesome Website&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ScrollToTop&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;button onClick={() =&amp;gt; showToast(&quot;show toast&quot;)}&amp;gt;Show Toast!&amp;lt;/button&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ScrollToTop&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/LoggingPage&amp;gt;
&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;맺으며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합성을 통해서 관심사를 분리하는 방법을 알아보았습니다. 코드가 복잡해지지 않기 위해서, &lt;b&gt;안전한 시스템을 가진 소프트웨어&lt;/b&gt;를 만들기 위해서, 관심사를 분리해주는 것은 매우 중요합니다. &lt;b&gt;장기적으로 에러 발생 가능성을 줄이고, 개발 생산성을 높여줍니다.&lt;/b&gt; 기본적으로 관심사는 함수나 클래스를 통해서 분리할 수 있습니다. 하지만, 리액트를 하다보면 &amp;ldquo;훅&amp;rdquo;, &amp;ldquo;컴포넌트&amp;rdquo; 라는 개념이 등장하면서 뭔가 새로운 개념을 사용하고 있다는 생각이 들 수 있습니다. &lt;b&gt;원론적으로 돌아가면, 훅과 컴포넌트도 함수입니다.&lt;/b&gt; &lt;b&gt;즉, 훅과 컴포넌트를 통해서 관심사를 분리하여 더 나은 코드를 작성할 수 있습니다.&lt;/b&gt; 컴포넌트가 복잡해지는 것을 느꼈을 때 해결책에 대해서 고민이 있었던 분, 훅으로 관심사 분리하는 것에만 초점을 두신 분들에게 이 글이 도움이 되셨길 바랍니다.&lt;/p&gt;</description>
      <category>Tech/Clean Code</category>
      <category>CleanCode</category>
      <category>composition</category>
      <category>react</category>
      <category>separationofconcerns</category>
      <category>관심사분리</category>
      <category>합성</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/62</guid>
      <comments>https://happysisyphe.tistory.com/62#entry62comment</comments>
      <pubDate>Tue, 16 May 2023 00:07:52 +0900</pubDate>
    </item>
    <item>
      <title>[공지사항] 프론트엔드 직무 관련 무료 멘토링 진행해요</title>
      <link>https://happysisyphe.tistory.com/61</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;업무가 바쁜 관계로 잠깐 중단합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 블로그 주인 행복한 시지프 라고 합니다.&lt;br /&gt;멘토링 활동에 관심이 많이 생겨서, 일단 무료 멘토링을 진행해보려고 합니다.&lt;br /&gt;부족한 점이 많지만, 제가 도움이 될 수 있는 한에서 도움을 드려보고자 해요.&lt;br /&gt;관심있으신분 읽어보시고 연락주세요. 저의 지인이든, 처음보는 분이든 상관없습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;한줄 소개&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;비전공자에서 토스 프론트엔드 개발자가 되기까지. 학습방법을 많이 고민했고, 가장 효율적인 학습법을 제안해드려요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;나의 커리어 패스와 경험 소개&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2021.1 프론트엔드 개발 시작&lt;/li&gt;
&lt;li&gt;2021.2 발표시간 계산기 웹사이트 출시&lt;/li&gt;
&lt;li&gt;2021.3~2022.1 IT 벤처창업동아리(SOPT) 활동&lt;/li&gt;
&lt;li&gt;2022.2~10 우아한테크코스 4기&lt;/li&gt;
&lt;li&gt;2022.12~ 토스 프론트엔드 개발자&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2021년 개발을 시작하고, 1달도 안 돼서 발표시간 계산기 라는 웹사이트를 만들어서 배포했어요.&lt;br /&gt;6개월만에 20명에게 웹 프론트엔드 세미나 32시간을 진행해보았습니다.&lt;br /&gt;2022년에는 우아한테크코스 라는 교육기관에 들어갔고, 10개월간 트레이닝 했습니다.&lt;br /&gt;2022년 12월에 신입 프론트엔드 개발자로 토스에 입사했습니다.&lt;br /&gt;&lt;br /&gt;이처럼 빠르게 배우고, 성장하는 것에 능합니다. 머리가 좋아서가 아닙니다.&lt;br /&gt;경제학도로서 혼자 프론트엔드 개발을 시작하고, 토스에 오게되기까지 많은 학습 방법과 전략이 있었습니다. 멘티와 동일한 눈높이에서 최선의 방법을 제시해드리고, 고민을 들어드리려고 합니다.&lt;br /&gt;짧은 멘토링으로 수백시간을 아껴보세요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;어떤 주제로 이야기 할 수 있나요?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취업 전략 세우기&lt;/li&gt;
&lt;li&gt;프론트엔드 개발을 더 잘하기 위한 학습 전략 세우기&lt;/li&gt;
&lt;li&gt;학습 방법에 대한 고민 논의하기&lt;/li&gt;
&lt;li&gt;포트폴리오, 블로그 전략&lt;/li&gt;
&lt;li&gt;기타&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;멘토링 어떻게 진행하나요?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. euijinkk97@gmail.com 으로 아래 내용을 담아서 메일을 보냅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 본인 소개&lt;/li&gt;
&lt;li&gt;개발 경력 및 현재 개발 수준 (깃허브나 블로그 링크를 첨부해주시면 더 좋아요)&lt;/li&gt;
&lt;li&gt;상의하고 싶은 것 (구체적으로 적어주시면, 제가 더 많이 생각해보고 올 수 있어서 효율적일 거에요. 잘 잡히지 않는다면 그대로 오셔도 돼요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2. 제가 메일을 읽습니다. 시간을 조율합니다.&lt;br /&gt;&lt;br /&gt;3. 약속한 시간에 온라인 화상(Zoom or Google meet)으로 30분~1시간 진행합니다.&lt;br /&gt;&lt;br /&gt;4. 화상 멘토링이 끝난 후, 멘토링 시간에 대한 솔직한 피드백을 메일로 보내줍니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;남들에게 도움 주는 것을 좋아해서, 멘토링이라는 활동을 해보고 싶어서, 간소화된 형태로 진행해보는 것입니다. 서로 윈윈 하는 방식이니 부담없이 연락주시면 되겠습니다.&lt;br /&gt;&lt;br /&gt;프론트엔드를 2년 이상 공부하셨거나, 이미 취직을 하셨다면 적절하지 않을 수 있습니다.&lt;/p&gt;</description>
      <category>Education</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/61</guid>
      <comments>https://happysisyphe.tistory.com/61#entry61comment</comments>
      <pubDate>Sat, 25 Feb 2023 15:29:25 +0900</pubDate>
    </item>
    <item>
      <title>토스 입사 후 7주간의 생활과 생각정리</title>
      <link>https://happysisyphe.tistory.com/60</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;그간의 회사생활&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입사하고 7주가 지났다. 7주동안 54개의 PR을 올렸다. 진짜 시간 가는 줄 모르고 하루종일 문제를 풀었다.&lt;/li&gt;
&lt;li&gt;현재 신규회원을 모으기 위한 서비스를 만들고 있다. 도메인은 정해져 있지 않고, 신규 회원을 모을 수 있다면 어떤 일이든 한다. 그러다보니 아이디에이션에도 많이 참여한다. 스타트업에서 일하는 기분이다. 토스는 수백개의 스타트업이 모인 조직이라고 들었는데, 진짜다. 각각의 팀이 스타트업 형태로 일한다. 그러면서 공동의 목표를 공유한다. 승건님이 말씀하시는 피자 두판의 법칙이란게 있는데, 한 팀의 인원이 8명이 넘으면 비효율, 불통이 생긴다는 것이다. 토스는 이런 가치를 지키기 위해서 조직을 나누고 나눠서, 소규모 스타트업 형태로 운영한다. 매력적이다.&lt;/li&gt;
&lt;li&gt;데이터 기반 사고가 재밌다. Growth Hacker 직무에 관심이 생긴다. 언젠가 타파해보고 싶다.&lt;/li&gt;
&lt;li&gt;토스에 신입 개발자는 정말 적다. 경력이 있다는 것이 당연하게 인식되는 분위기이다. 전 회사와 비교했을 때 어떤가 하는 질문을 자연스레 받게 되는 것이 이를 증명한다. 어떻게 보면 좋고, 어떻게 보면 아쉽다. 경력의 유무에 상관없이 일이 주어지고, 그만큼의 역량을 요구받는다. 그러니까 매일매일 나의 한계치를 건드리며 일을 한다. 7주동안 수십번의 한계에 마주했다. 사람은 Comfort zone에서 벗어나야 성장한다고 하는데, 매일매일이 uncomfort 하다. 풀어야할 문제가 uncomfort zone에 있거나, 문제가 comfort하다면, deadline이 uncomfort하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발에 대한 생각&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프론트엔드 개발자는 본질적으로 개발자이고, 개발자에게는 결국 문제해결 능력이 제일 중요하다. 개발자의 문제해결능력은 알고리즘, CS에 대한 이해, 서비스와 데이터에 대한 이해에서 나온다. 네이버, 카카오는 현업 실력이 부족해도, 알고리즘과 CS로만 뽑아간다고 하는데, 그 이유를 납득하지 못 했었다. 이제 조금 알겠다. 알고리즘과 CS만 잘하면, React라거니, Spring이라거니 하는것들은 따라오는 것에 불과하기 때문이다.&lt;/li&gt;
&lt;li&gt;이렇게 보면 회사의 전략은 2가지이다. CS와 알고리즘 만으로 채용하기(소위 대기업형), 실무 능력을 보고 채용하기(소위 스타트업형). 시간 변화에 따른 실력 변화를 그래프로 표현해보면 아래와 같을 것이다. 지금 생각으로는 두 그래프의 접점의 x좌표는 1년 정도 될 것 같다. 즉, 스타트업형 인재가 1년 동안은 더 좋은 실력을 발휘할 터이고, 1년 정도 후에는 대기업형 인재가 스타트업형 인재의 실력을 뛰어넘을 것이라고 생각한다. 대기업형 회사는 평균 근속 년수가 기니 해당 전략이 효과적인 것이고, 6개월이고 1년이고 온보딩만 할 수 있는 것이다. 스타트업형 회사는 평균 근속 년도가 짧으니 해당 전략이 효과적인 것이고, 바로 실무에 투입하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (18).png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vTQLj/btrZeykKLeN/OGT3nLWOVvEqeq4oZSzMC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vTQLj/btrZeykKLeN/OGT3nLWOVvEqeq4oZSzMC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vTQLj/btrZeykKLeN/OGT3nLWOVvEqeq4oZSzMC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvTQLj%2FbtrZeykKLeN%2FOGT3nLWOVvEqeq4oZSzMC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;417&quot; height=&quot;420&quot; data-filename=&quot;Untitled (18).png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;1068&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그렇지만 위에 언급한 스타트업형의 실무 역량에 더해서 어떠한 역량이 더해지면 접점은 영원히 생기지 않을 수 있다. 그것은 스스로 동기부여 하는 능력이다. 스스로 동기부여하는 자는 끝없이 학습하고, 부족한 점을 채워갈 것이다. 토스에서 가장 중요한 가치중 하나이다. 스스로 동기부여하는 자와 그렇지 않은 자의 미래 가치 차이는 엄청나다. 나는 스타트업형 인재로 토스에 입사했지만, 스스로 동기부여할 수 있고, 단지 초반에만 빛나는 사람으로 머물지 않을 것이다.&lt;/li&gt;
&lt;li&gt;최근에 CS와 알고리즘에 대한 학습 의지가 생겼다. 현업의 문제를 풀기 위해서, 빠르게 풀기 위해서는 꼭 필요한 역량이라고 느꼈다. 그래서 최근에 알고리즘과 자료구조 학습을 시작했다. 취업을 위해 이것들을 학습하는 것은 싫었고, 공부 동기를 느끼지 못 했었다. 이제 이유를 알고 공부하니 재미가 있다. 현업에 필요한 역량들 위주로 채워나갈 것이다. 스택, 큐, 정렬, 탐색, 구현문제 위주일 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/euijinkk/algorithm&quot;&gt;https://github.com/euijinkk/algorithm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생각보다 기술적인 성장은 어렵다. 토스는 서비스 위주의 회사고 속도와 효율성이 정말 중요하다. 그래서 개발자의 효율을 위해 일하는 생산성팀, devOps팀이 존재하고, 이쪽에서 사내 라이브러리와 인프라 환경을 모두 구축해준다. 사일로 개발자는 이를 활용해서 빠르게 구현만 하면 되는 것이다. 개발이 편해서, 빠르게 서비스를 검증할 수 있다는 장점이 있지만, 개인적으로 기술적인 성장이 어려울 수 있다는 생각이 든다. 기술적인 성장을 위해서는 스스로 구현체를 뜯어보고, 라이브러리에 기여하며 따라가는 수밖에 없다.&lt;/li&gt;
&lt;li&gt;토스에서 일하다보면 자동으로 키워지는 역량은 무엇일까? 자동으로 키워지는 역량은, 속도, 클린코드, 구현 능력, 꼼꼼함, 서비스와 개발의 접목, 린 스타트업 전략에 대한 이해, 데이터 기반 결정 정도인 것 같고, 채우기 힘든 역량은 프론트엔드 기술에 대한 깊은 이해, 모듈의 인터페이스 설계 등이 있다. 토스에서 일하면서 나의 미래를 더 그려보고, 학습의 중점을 확고히 해야겠다. 현재는 진성 개발자의 길을 걸을지, 리더십 있는 개발자의 길을 걸을지, 기획자의 길을 걸을지 고민 중이다. 일단 근 1년간은 프론트엔드와 개발 자체를 잘하기 위한 학습을 할 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서비스에 대한 생각&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토스가 은행들과 경쟁한다고 생각했다. 하지만 구체적으로 보면, 지향하는 바가 달랐다. 토스는 금융 회사의 플랫폼이 되고자 하는 것이었다. 시중 은행은 자사 제품을 만드느라 노력을 기울이겠지만, 토스는 이 제품들을 한 데 모아서 보여주는 플랫폼을 지향한다. 이렇게 봤을 때 사업이 더 매력적이게 느껴졌다. 여전히 이 사업이 힘들고 어려운 길임은 확실하다.&lt;/li&gt;
&lt;li&gt;서비스를 만듦에 있어서 중요한 것은 무엇일까. 이것은 시간을 어디에 많이 써야할까 라는 질문에 대한 답과 같다. 내 생각에 프론트엔드 개발자에게는 개발은 아닌 것 같다. 프론트엔드는 확장성이 떨어져도 대응하는 데 시간이 오래 걸리지 않는다. 그렇지만 클린코드는 중요하다. 기능을 추가하려면 내가/남이 이전에 짠 코드를 읽어야하는데, 보통 이를 위해 시간을 다 보낸다. 코드 짜는 것은 어렵지 않다. 클린 코드가 몸에 베어있다면, 크게 오래걸리는 작업이 아니라고 본다. 그래서 우리 팀은 진짜 빠르게 개발하고 배포한다. 그러다보니 최대한 빠르게, 일정 수준 이상의 클린 코드를 지키기 위한 개발을 잘 하게 된다.&lt;/li&gt;
&lt;li&gt;프론트엔드에서 Logging이 진짜 중요하다는 것을 깨달았다. 해당 화면에 들어오는 사람은 몇명인지, 다음 화면까지 넘어가는 비율은 어떻게 되는지, 해당 버튼을 클릭하는 사람은 얼마나 되는지, 리텐션은 얼마나 되는지 등의 정보는 서비스를 개발함에 있어서 사실상 가장 중요한 정보이다. 이것을 알아야, 서비스가 얼마나 잘 되고 있는지, 개선해야할 지표는 무엇인지 판단할 수 있다. 그렇게 서비스는 성장할 수 있는 것이다. 왜 로깅의 중요성을 이제 처음 알게 되었을까? 왜 아무도 중요성을 말하지 않는걸까. 로깅을 통한 데이터 분석, 그로스 해킹을 가장 잘 하는 회사는 단연 토스이다. 이 부분에서 압도적인 실력을 키우고 싶다.&lt;/li&gt;
&lt;li&gt;서비스를 잘 만드는 사람이 되고 싶다. 서비스를 잘 만든다는 건 정말 많은 의미를 가진다. 아이디어, 시장조사, 실행력, 인사관리, 데이터 분석 등. 현재는 데이터 분석을 잘 하고 싶다. 로깅을 통한 데이터 수집, 각 지표 분석, 지표 개선의 우선순위 설정(일반적으로 Retention &amp;rarr; Activation &amp;rarr; Acquisition), 특정 지표 해킹을 위한 아이디에이션. 이 과정이 굉장히 흥미롭고, 잘 하고 싶은 분야이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삶에 대한 생각&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삶의 90%가 일에 집중되어 있다. 평일 5일은 100% 일에 붓고, 주말은 일을 더 잘하기 위한 공부를 조금 한다. 주말에는 Life를 좀 챙길 필요가 있어 보인다. 누워있거나, 술 먹거나 허송세월 보내기보다 뭐라고 배우고 건강한 취미를 가지는게 좋겠다. 운동 하나, 음악 하나 하고 싶다. 빠른 시일 내에 실행해보겠다.&lt;/li&gt;
&lt;li&gt;나는 새로운 집단에 들어가면, 지극히 평범한 사람이고 싶어한다는 것을 깨달았다. 최대한 눈에 띄지 않고, 정상적인, 보편적인 사람이 되려고 굉장히 열심히 노력한다. 아직 상대방의 생각이 측정되지 않은 상태로 나를 드러내는 것을 두려워 한다. 그래서 할 수 있는 말/행동 중에 가장 일반적인 형태를 택한다. 안 좋다고 할 순 없지만, 왜 이런 행동양식을 가지게 되었는지 궁금하다. 음.. 사람들의 차가운 반응을 두려워 하는 것 같다. 그걸 회피하기 위한 자기방어이지 않을까. 이 부분에 대해 더 고민해보자.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/60</guid>
      <comments>https://happysisyphe.tistory.com/60#entry60comment</comments>
      <pubDate>Wed, 15 Feb 2023 02:51:19 +0900</pubDate>
    </item>
    <item>
      <title>토스 &amp;amp; 우아한형제들 합격, 회사 선택 이유와 앞으로의 목표</title>
      <link>https://happysisyphe.tistory.com/59</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서언&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 이번에 신입 프론트엔드 개발자 채용을 거쳐 토스(코어)와 우아한형제들에 합격했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCTL1f/btrTEPESZ2V/4fKGdVI7myZs10U7CEj9PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCTL1f/btrTEPESZ2V/4fKGdVI7myZs10U7CEj9PK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;906&quot; data-filename=&quot;Untitled (16).png&quot; style=&quot;width: 39.2392%; margin-right: 10px;&quot; data-widthpercent=&quot;39.7&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCTL1f/btrTEPESZ2V/4fKGdVI7myZs10U7CEj9PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCTL1f%2FbtrTEPESZ2V%2F4fKGdVI7myZs10U7CEj9PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/66vPy/btrTFgWmAms/Yjr1sLxVLtKAEv5K9c0Ki1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/66vPy/btrTFgWmAms/Yjr1sLxVLtKAEv5K9c0Ki1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;598&quot; data-filename=&quot;Untitled (17).png&quot; style=&quot;width: 59.598%;&quot; data-widthpercent=&quot;60.3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/66vPy/btrTFgWmAms/Yjr1sLxVLtKAEv5K9c0Ki1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F66vPy%2FbtrTFgWmAms%2FYjr1sLxVLtKAEv5K9c0Ki1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1604&quot; height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로서 두 회사 모두 객관적으로 너무나 좋은 회사들이고, 저에게 과분한 기회가 찾아왔다고 생각합니다. 둘 다 엄청나게 가고 싶은 회사였기 때문에, 하나를 놓는다는게 여간 어려운 일이 아니었습니다. 이 글은 회사의 우열을 따지지 않으며, 저의 주관적인 선택 과정을 담습니다. 내적으로 이미 정했지만, 스스로 합리화하지 않으면 아쉬움이 떠나가지 않을 것 같아 명문화해 봅니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;외부적인 기준을 비교하는 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 회사에 다녀본 적이 없었기 때문에 선택이 특히나 어려웠습니다. 처음에 두 회사를 두고 외부적인 기준을 비교했습니다. 지인들에게서 이 회사는 이래서 좋더라, 저 회사는 저래서 좋더라 하는 이야기들을 듣다보니 자연스럽게 외부 조건들을 비교하게 되었습니다. 세부적인 조건은 공개하기 조심스러우므로 생략하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 만족스러운 조건에, 좋은 회사 문화, 구성원의 높은 회사 만족도를 가지고 있었습니다. 우형이 좋은 이유가 명확하고, 토스가 좋은 이유가 명확했습니다. 외부적인 조건을 비교하여 선택하기란 불가능한 일이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 내면의 소리에 귀를 기울어야 했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내면의 기준을 비교하는 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로에게 많은 질문을 해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;회사라는 베일을 벗겨내고 나만 남아있다고 했을 때 나는 어떤 사람이 되고 싶은가&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 도전하며 살아가고 싶습니다. 도전의 방식은 다양합니다. 창업을 통해서도 좋고, 해외 개발자로 도전하는 방향도 생각 중입니다. 프론트엔드 개발자로 이 생태계에 영향을 주는 일도 생각 중입니다. 유튜브를 한다거나, 강의를 찍는다거나, 컨퍼런스에 참여한다거나 하는 일을 통해서 말입니다. 기획자라는 전혀 다른 길도 생각 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한없이 주체적인 사람이 되고 싶습니다. 프론트엔드, 기획 뿐만 아니라, 인생 전방위적으로 극도의 주체성을 가지고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도전과 주체성을 발휘하는 구체화된 행동 예시를 생각해보면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;이 프로덕트 재밌을 것 같다&amp;rdquo; &amp;rarr; 주체적인 시장 조사 &amp;rarr; 도전. 린하게 만들어보기 &amp;rarr; 유저 반응을 능동적으로 확인 &amp;rarr; 스스로 지속해야할 지 판단하기&lt;/li&gt;
&lt;li&gt;&amp;ldquo;해외에서 살아보고 싶다&amp;rdquo; &amp;rarr; 해외 기업 조사 &amp;rarr; 지역/회사 선택 &amp;rarr; 이력서 돌리기 &amp;rarr; 필요하다면 언어 공부 &amp;rarr; 비행기/숙소 등 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 사례들이 떠오릅니다. 제가 추구하는 것은 적절한 무모함(도전)과 주체성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;그러면 어떤 역량을 키우고 싶은가?&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도전 역량과 주체성을 키우고 싶습니다. &amp;ldquo;도전&amp;rdquo;과 &amp;ldquo;주체성&amp;rdquo;은 자세히 들어다보면 추상적인 역량입니다. &amp;ldquo;도전하는 사람이 되어야지!&amp;rdquo; 라고 해서 도전하는 사람이 되지 않습니다. 주변 환경, 도전해본 경험, 실패해본 경험을 축적해야 도전할 수 있습니다. 무언가 시도해보고 싶다고 했을 때, 주변에 만류하는 부류, 왜 안 되는지 이야기하는 부류, 현실적인 이야기를 하는 부류의 사람이 적어야 도전할 수 있습니다. 이를 꺾어내는 것은 쉽지 않습니다. 반면에 아무렇지 않게 시도하는 부류, 실패를 두려워하지 않는 부류의 사람이 주변에 많다면 도전이 당연한 행동이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주체성도 마찬가지의 이야기입니다. &amp;ldquo;주체적인 사람이 되어야지!&amp;rdquo;는 아무 도움이 되지 않습니다. 주체적일 수 있는 환경이 주어지고, 주체적으로 선택하여 이루어본 경험, 주체적으로 선택하여 실패해본 경험이 축적되어야 합니다. 나에게 책임이 주어지고, 자율성이 주어지는 환경에 있어야 합니다. 이 환경 속에서 주체적으로 선택해본 경험을 쌓아야 합니다. 이런 기준에 따라 행동해보니 원하는 결과를 얻을 수 있더라, 저런 기준에 따라 행동해보니 아쉬운 결과를 내더라, 하는 경험을 하며 판단 기준을 수정해나갈 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 현재 주체적이고 도전적인 사람이라고 이야기하기는 어렵습니다. 하지만 저는 꼭 그런 사람이 되고 싶습니다. 주체적이지 못 하고, 도전하지 않았던 뼈아픈 과거가 스쳐지나갑니다. 다시는 그런 삶을 살고 싶지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 최근에 큰 돈을 벌고 싶다는 생각을 합니다. 이는 큰 임팩트를 통해서 이룰 수가 있겠죠. 그러면 결국 창업 역량을 키워야 합니다. 창업 역량에는 도전적인 자세, 실패를 두려워하지 않는 용기, 비즈니스에 대한 이해, 주체성 등을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;그러면 어떤 집단에 가고 싶은가?&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높은 주체성을 가진 집단, 아무렇지 않게 도전할 수 있는 집단, 도전이 문화인 집단에 가고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로서 창업 역량을 키울 수 있는 집단에 가고 싶습니다. 새로운 프로덕트를 많이 만들어내는 집단. 개발 외적인 부분도 고려하여 프로덕트를 창조할 수 있는 집단. 즉, 목적조직 하에서 오너십을 가지고 프로덕트를 만들 수 있는 집단.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;그러면 우아한형제들과 토스 중에 어디로 가야할까?&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 목표로 한 바를 이루기에 토스가 더 적합하다고 생각합니다. 토스는 &amp;ldquo;Courage to fail fast&amp;rdquo;, &amp;ldquo;Focus on impact&amp;rdquo; 를 강조하고 있습니다. 임팩트를 만들기 위해 도전의 횟수를 늘리고, 빠르게 실패하고, 피드백을 받으며 마켓핏을 조정해나갑니다. 이를 작은 스타트업 형태의 목적조직(사일로)으로 운영하기 때문에, 누구나 오너십을 가지고 제안을 할 수 있습니다. 도전하는 문화가 기반이 되기 때문에 공격적으로 도전할 수 있으리라 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스에는 DRI(Directive Responsible Individual) 라는 개념이 강조됩니다. 이는 애플의 스티브 잡스가 제시한 개념입니다. 모든 구성원이 직접적으로 책임을 가지고 결정할 수 있는 개인이라는 말입니다. 즉, 최종의사결정자 라는 것입니다. 토스에서는 이를 위해서 모든 정보를 제공합니다. 의사결정을 위해서는 모두가 동일한 정보를 가지고 있어야 하기 때문입니다. 한 쪽이 우위의 정보를 가지고 있는 일반적인 대기업의 경우, 윗사람이 결정권자인 것이 당연한 것이죠. 이처럼 토스는 모든 정보, 모든 주체성을 개인에게 부여하고, 개인을 신뢰합니다. 지금 가지고 있는 주체성을 더욱 성장시킬 수 있을 것이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 제가 원하는 역량을 더 잘 성장시킬 수 있는 곳은 토스라고 판단했습니다. 그러므로 저는 아쉬움을 뒤로 하고, 토스팀에 합류하기로 결정했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선택에 대한 나의 철학&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양갈래길에서 결정해야할 때, 한 가지 선택밖에 할 수 없고 한 가지 인생밖에 살 수 없습니다. &amp;lt;참을 수 없는 존재의 가벼움&amp;gt; 에서 이와 같은 구절이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;ldquo;도무지 비교할 길이 없으니 어느쪽 결정이 좋을지 확인할 길도 없다. 모든 것이 일순간, 난생 처음, 준비도 없이 닥친 것이다. 마치 한번도 리허설을 하지 않고 무대에 오른 배우처럼&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 가능성의 왕국에 살지만, 우린 결국 하나의 가능성을 택해야 합니다. 하지만 이 사실을 안다고 해서 선택하지 못한 가능성에 대한 아쉬움이 사라지는 것은 아닙니다. 사실 이 선택을 하고도 우아한형제들을 뒤로 하고 발걸음을 떼는 것이 너무 어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 과거부터 선택 이후 돌아보지 않기 위해 외치는 주문이 한 가지 있습니다. 이는 &amp;lt;여덟단어&amp;gt; 에서 나오는 구절인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;ldquo;완벽한 선택은 없습니다. 옳은 선택은 없는 겁니다. 선택을 하고 옳게 만드는 과정이 있을 뿐입니다.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택이 그 자체로 중요하다는 것은 부정할 수 없지만, 이는 우연의 영역이 크다고 생각합니다. 미래를 추측할 뿐이고, 그 미래에 어떤 일이 일어날지는 정확히 파악할 수 없습니다. 제가 위에서 언급한 회사의 풍경도 단지 추측일 뿐이니까요. 그렇다면 확실한 한가지는 자신의 행동입니다. 자기의지를 통해, 선택 이후의 삶을 옳게 만드는 것입니다. 한번의 선택이 인생을 바꾸지 못 합니다. 그 이후의 행동들이 인생을 바꾸는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 회사이기 때문에 이 결정이 만들어낼 파급력이 크다고 생각하여 더 결정하기 어려웠던 것 같습니다. 하지만 두 개의 좋은 회사에 합류할 기회가 주어진 이상, 이 선택의 영향은 크지 않을 것 같습니다. 이렇게 생각하고 나니 가볍게 하나를 두고 여행할 수 있을 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;토스에서 이루고 싶은 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 방점을 찍어야 한다면, &amp;ldquo;선택 과정&amp;rdquo;이 아니라 &amp;ldquo;선택 이후의 삶&amp;rdquo; 입니다. 여기서는 선택 이후에 이루고 싶은 것에 대해서 논해보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주체적으로, 책임감을 가지고 프론트엔드 업무를 맡는 것. 어떤 것이든 빠르고 정확하게 만들 수 있다는 자신감을 가질 수 있도록.&lt;/li&gt;
&lt;li&gt;기획적인 인사이트 얻기. 맡은 프로젝트를 개발뿐만 아니라 기획적으로도 분석하는 과정을 거쳐보기. 기획 책을 많이 읽어보자&lt;/li&gt;
&lt;li&gt;빠르게 도전하고, 빠르게 실패할 수 있는 성미를 키우기&lt;/li&gt;
&lt;li&gt;조직의 심리적 안정감을 어떻게 형성되는가? 에 대해 탐구하기&lt;/li&gt;
&lt;li&gt;동료와 Radical Candor(극단적인 솔직함)을 이루어보기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경제학도에서 프론트엔드 개발자까지 약 2년의 시간이 걸렸습니다. 과분한 기회가 왔고, 행복한 고민이라지만 선택 과정이 그만큼 힘들기도 했습니다. 이 고민과 선택을 통해 한층 더 성숙해졌다고 생각합니다. 돌아보면 아무래도 삶은 한 순간에 결정되지는 않는 것 같습니다. 경제학과로 진학했지만 당시로서 생각치도 못한 일을 하게 된 것처럼요. 지금 이 선택도 당연히 근본적으로 저를 바꾸지는 못할 것입니다. 이후의 수많은 결정과 행동만이 삶을 바꿀 수 있을 것입니다. 역사의 방향성은 절대로 한순간에 꺾이지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 행운이 따랐고, 행운을 잡으려는 노력을 많이 했습니다. 그래서 제가 여기까지 온 것에 대하여 행운이라고 표현하고 싶지는 않습니다. 저에게 행운이 찾아왔을 때, 그것이 내것이 되도록 만들었고, 이를 넘어 이 행운의 결과를 극대화하려고 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 찾아온 기회도 최선을 다해서 잡을 것이고, 결과를 극대화 해내려고 합니다. 앞으로도 지켜봐주셨으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 오기까지 도움을 주신 많은 지인들에게 감사하다는 말씀 전하고 싶습니다.&lt;/p&gt;</description>
      <category>Writing/삶에 대한 생각</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/59</guid>
      <comments>https://happysisyphe.tistory.com/59#entry59comment</comments>
      <pubDate>Thu, 15 Dec 2022 05:27:39 +0900</pubDate>
    </item>
    <item>
      <title>3. 우아한테크코스에서 후회되는 점</title>
      <link>https://happysisyphe.tistory.com/58</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;0. 우아한테크코스 회고&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/56&quot; target=&quot;_self&quot;&gt;&lt;span&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/57&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;3. 우아한테크코스에서 후회되는 점&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 우아한테크코스에서 후회되는 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후회되는 점을 서술하고자 한다. 이후 삶에서 신경써야 할 가장 중요한 부분들이다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 건강&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건강을 많이 신경쓰지 못 했다. 반년 이상 아침을 굶었다. 일년 정도 운동을 하지 않았다. 확실히 힘을 몰아썼다는 느낌을 받는다. 이는 지속가능한 자기계발이 아니다. 롱런하기 위해서는 꼭 건강을 챙겨야 한다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 일상의 루틴&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 하나에 집중하면 나머지는 모두 미루는 경향이 있다. 청소, 빨래, 글쓰기, 독서, 친구와의 연락 등 미뤄온 것들이 너무 많다. 일상에 오직 하나만을 넣으려고 하기 때문에 다른 모든 것을 미루게 된다. 큰 사건이 일단락 되고 나서 몰아서 일을 처리한다. 그 사이에 쌓인 것을 볼 때 참 만족스럽지 못 하다. 루틴을 세워야할 것 같다. 청소 주기, 빨래 주기를 세워서 시간을 할당해야겠다. 독서 시간도 꼭 필요하다고 생각한다. 반복가능한 안정적인 루틴을 만들어서 장기적인 관점에서 일상을 살아내야 한다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. fundamental의 성장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코에서 기술적인 성장을 많이 했으나, fundamental 성장이 부족했다고 생각한다. 사실 개발 지식이라야, 누구나 시간을 붓고 열심히 하면 채워지기 마련이다. 시간에 비례한 성장이라는 측면에서 fundamental 성장이라고 보기는 어렵다. 내가 생각하는 fundamental은 생각의 크기, 기획 역량, 도전 역량, 리딩 역량, 의사 결정 역량 등을 의미한다. 이것을 키우기 위해서는 경험을 늘려야 한다. 독서를 통해 간접 경험을 늘리고, 이를 바탕으로 무수한 도전을 하며 직접 경험을 해야 한다. 그리고 빠르게 피드백을 받는 과정을 반복해야 한다. 아무래도 개발 학습에만 몰두하다 보니 이런 측면까지 챙기지는 못 했다. 앞으로는 바쁜 와중에서도 fundamental 성장을 위해서 꾸준히 독서를 할 예정이다. 회사에 가서 매일 아침 30분은 독서 시간으로 채울 생각이다.&lt;br&gt; &lt;br&gt; &lt;br&gt;우아한테크코스 10개월 동안 정말 많은 것을 배웠다. 이런 콤팩트한 시간을 보낼 수 있었음에 감사하고, 건강하게, 무탈히, 상처없이 마칠 수 있었음에 감사하다. 다음 회고는 경제활동인구로서의 회고가 될 것이다.&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;0. 우아한테크코스 회고&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/56&quot; target=&quot;_self&quot;&gt;&lt;span&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/57&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;3. 우아한테크코스에서 후회되는 점&lt;/p&gt;</description>
      <category>Tech/우아한테크코스</category>
      <category>우아한테크코스</category>
      <category>회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/58</guid>
      <comments>https://happysisyphe.tistory.com/58#entry58comment</comments>
      <pubDate>Sat, 10 Dec 2022 18:59:19 +0900</pubDate>
    </item>
    <item>
      <title>2. 우아한테크코스에서의 자기 성장 기록</title>
      <link>https://happysisyphe.tistory.com/57</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;0. 우아한테크코스 회고&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/56&quot; target=&quot;_self&quot;&gt;&lt;span&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/58&quot; target=&quot;_self&quot;&gt;&lt;span&gt;3. 우아한테크코스에서 후회되는 점&lt;/span&gt;&lt;/a&gt;&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;다음으로 자기 성장 (기술 이외의 성장)을 서술하고자 한다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 리더 의식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 성인 이후로 리더 역할을 많이 했었다. 성향 상, 계획적이고, 효율성을 추구하고, 문제해결을 좋아하다보니까 집단을 주도할 수밖에 없었다. 문화예술동아리 회장, IT 벤처창업동아리 SOPT 웹파트장, &lt;s&gt;(분대장)&lt;/s&gt; 등을 해왔다. 그러면서도 스스로를 리더라고 정체성을 부여한 적은 없었다. 나의 직함은 리더였고, 그 자리로서 해야할 일을 마땅히 해냈음에도 리더 의식을 가지진 않았다. 그렇기 때문에 은연 중에 구성원과 팀을 온전히 고려하지 못 하는 경우가 있었다. 우테코를 하면서 처음으로 리더 의식을 가지게 되었다. 새로운 집단에서 나도 모르게 또 주도를 하고 있는 것이었다. 이런 사건이 반복되고, 팀에 헌신하며 이끄는 것을 좋아하는 나를 보니, 내가 이 일을 좋아한다고 몸소 느꼈다. 이렇게 나는 리더로 살아야겠다는 의식을 처음 가졌다.&lt;br&gt; &lt;br&gt;그러면 어떻게 좋은 리더가 되어야할 지 고민해야했다. 리더로서 나를 메타인지 하기 위해, 과거에 리더했을 때의 동료들에게 피드백을 부탁했다. 너무나 고맙게도 솔직하게 장단점 등 피드백을 얻을 수 있었다. 이를 기반으로 나의 리더 정체성을 명확히 하고, 개선 포인트를 잡았다. 리더로서 의식을 가진 것이 좋은 시작이었다만, 리더로서 집단을 이끄는 것은 여간 어려운 일이 아니었다. 또 “잘” 하려고 하다보니 신경 써야할 포인트가 많았다. 이전에는 그냥 생각 없이 넘어갔을 법한 일인데, 이번에는 그 사건의 원인을 분석하고 능동적으로 개선해나가려고 노력해야 했다. 그렇지만 이 일 자체가 즐거웠던 것만은 확실하다. 그리고 이제 돌아보니 리더로서 나의 약점을 잘 알겠다. 나는 효율적인 조직 운영에는 뛰어나지만, 감정적인 고려는 조금 떨어진다. 누군가의 기분을 능동적으로 상하게 한다는 것은 아니다. 조직의 심리적 안정감을 증대시키기, 조직 내의 신뢰 형성, 조직 내 고립되는 인원을 케어하기 등의 일은 너무 어려웠다. 감정적인 부분에서 벽을 깨부수고 도전해나가야 한다는 점이 특히 어려웠다. 나에게 감정을 직면하고, 터놓는 행위는 매우 힘든 일이었다. 그렇게 벽을 부수지 못한 여러 순간들이 떠오르고, 후회가 된다. 추후 기업에 들어가서 이 기업이 어떻게 구성원의 심리적 안정감을 조성하는지, 상호간의 신뢰를 어떻게 쌓아가는지를 면밀히 관찰해볼 것이고, 그 장치들을 파악해볼 것이다. 리더 의식은 많이 성장한 부분이면서도, 아쉬움이 많이 남는 영역이다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 피드백 주고 받기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성장을 위해 중요한 것 중 하나는 피드백이라고 생각한다. 피드백은 그 종류가 다양하다. 제품 피드백, 코드 피드백, 리더 피드백, 동료 피드백, 스프린트 피드백 등. 모든 과정에서 3가지가 필요하다. (1) 나 자신이 솔직한 피드백을 내놓을 수 있는 용기와 (2) 남의 피드백을 겸허히 받아들일 수 있는 넓은 아량, (3) 개선하려는 노력. 하나도 빠짐 없이 모두 만족해야 좋은 서비스 개발자가 될 수 있다고 생각한다. 솔직한 피드백을 위해 용기를 내본 사건이 몇 번 있었고, 피드백도 다수 받아보고 개선하려고 노력했다. 토스에서 Radical Candor(극단적인 솔직함)이 핵심가치 중 하나이다. 이는 스타트업에서 소중하게 생각하는 가치 중 하나인 것 같다. 그리고 팀에서 중요한 것은 이 과정의 이터레이션을 의도적으로 늘리는 것이다. 즉, 피드백을 위한 장치를 마련해두는 것이 꼭 필요하다. 프로젝트를 진행하면서 회고라는 장치를 심어두어 이터레이션을 주기적으로 가졌다. 하지만, 그 장치가 조금 모자랐다는 생각은 든다. 항상 단체 회고만 했기 때문에 개인적인 피드백을 주고 받기는 어려웠다. 조직적인 문제는 잘 해소해나갔지만, 개인적인 관계는 멈춰있는 경우가 많았다. 이 부분은 더 고민하고 성장해야한다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 질문하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 성장하기 위해 함께 자라는 것이 매우 중요하다. 이 중 특히 “질문하기”가 중요하다. 두 가지 포인트에서 생각해볼 수 있다. 첫 번째는 도전적으로 질문하기, 두 번째는 잘 질문하기. 일단 질문하는 것 자체가 중요하다. 모르는 것이 생겼을 때, 내가 학습을 해도 알지 못 할 때, 내가 직접 학습하는 것보다 먼저 학습해본 사람한테 팁을 듣는게 시간을 단축시킬 수 있을 때, 전문가 앞에 있을 때 등의 상황에 도전적으로 질문하는 것이 필요하다. 이때 망설이지 말고 질문하는 자세가 필요하다. 나는 우테코 수업시간에, 페어 프로그래밍을 하며, 코드 리뷰를 주고받을 때 항상 많이 질문하려고 노력했다. 특히 FEConf에서도 연사님께 질문했었는데, 아마 전 방문객 중 나 혼자만 질문했던 것 같다. 도전적으로 질문하는 자세는 많이 키울 수 있었다. 이 자신감이 어디서 왔는가 하면은, 논리에 대한 자신감에서 왔다고 느낀다. 지금 상대방이 말한 내용 중, 논리적으로 설명되지 않는 부분이 있다고 느끼면 그건 나의 이해력이 부족한 것이 아니라 상대방의 설명에서 실수로 빠진 포인트가 있거나, 당연히 알 것이라고 생각하고 언급하지 않은 부분이 있기 때문이다. 나의 논리에 확신을 가지고 있으면 “이런 질문을 해도 괜찮을까?”와 같은 생각을 많이 줄여낼 수 있다.&lt;br&gt; &lt;br&gt;그리고 현업자 리뷰어와 코드리뷰를 주고받으면서 잘 질문하는 법을 배웠다. 내가 이 질문을 하기까지 어떤 고민을 했고, 어떤 시도를 했고, 어떻게 생각하는지 이야기를 한 후, 의견을 묻는다. 그러면 질문을 받는 입장에서는 질문자의 의도를 온전히 파악할 수 있고, 질문자의 간지러운 부분을 정확히 긁어줄 수 있게 된다. 그러면 빠르게 원하는 바를 얻고, 성장할 수 있게 된다. 그리고 앞서나간 사람들에게 정성껏 질문하면 누구나 흔쾌히, 즐겁게 답변해준다는 것을 몸소 깨달았다. 한문장으로 간단하게 질문해버리면 질문받는 사람 입장에서는 귀찮게 느껴질 수 있는데, 정성껏 질문했을 때는 오히려 알려주고자 하는 욕구를 자극하게 된다. 나도 마찬가지이다. 누군가가 충분히 탐구해보았다는 것이 느껴지게 질문을 하면 너무 알려주고 싶어서 안달이 나게 된다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 기록하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기록 체계를 갖추었다. 많이 부족하지만, 기록하지 않은 것보다는 나으니 성장한 점에 적는다. 기술 기록, 고민 기록, 트러블 슈팅 기록, 수업 필기, 회고 기록, 스터디 기록 등 무엇이든 기록하려고 노력했다. 하지만 문서의 응집과 분리가 제대로 이루어져있지 않다. 이를 개선해나가서 확장성 있는 기록 방식을 설계해보았다.&lt;br&gt; &lt;br&gt;&lt;b&gt;이전 버전&lt;/b&gt;&lt;br&gt;이 페이지 이외에도 수십개의 페이지에 내용이 흩어져 있었다. 항상 찾아가기 어려워서 노션 검색 기능을 활용하곤 했다. 지금이라야 기억할 수 있는 정도의 양이기에 망정이지, 더 커지면 찾아가는 데 큰 비용이 발생할 것이라 예상할 수 있었다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;1694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpW3Ta/btrSIRK3aw4/cKW3ToXmyBkDAEBfqW1eD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpW3Ta/btrSIRK3aw4/cKW3ToXmyBkDAEBfqW1eD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpW3Ta/btrSIRK3aw4/cKW3ToXmyBkDAEBfqW1eD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpW3Ta%2FbtrSIRK3aw4%2FcKW3ToXmyBkDAEBfqW1eD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1620&quot; height=&quot;1694&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;1694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;&lt;b&gt;수정 버전&lt;/b&gt;&lt;br&gt;페이지를 하나로 합치고, 한눈에 볼 수 있도록 카테고리를 나누었다. 앞으로 꾸준히 확장성 있고, 잘 응집/분리되도록 노션을 리팩터링(?) 해나갈 예정이다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uYEh3/btrTk1YFW03/AKEAxtcJCC371mbQGI53LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uYEh3/btrTk1YFW03/AKEAxtcJCC371mbQGI53LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uYEh3/btrTk1YFW03/AKEAxtcJCC371mbQGI53LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuYEh3%2FbtrTk1YFW03%2FAKEAxtcJCC371mbQGI53LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2876&quot; height=&quot;1448&quot; data-origin-width=&quot;2876&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;0. 우아한테크코스 회고&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/56&quot; target=&quot;_self&quot;&gt;&lt;span&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/58&quot; target=&quot;_self&quot;&gt;&lt;span&gt;3. 우아한테크코스에서 후회되는 점&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/우아한테크코스</category>
      <category>우아한테크코스</category>
      <category>회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/57</guid>
      <comments>https://happysisyphe.tistory.com/57#entry57comment</comments>
      <pubDate>Sat, 10 Dec 2022 18:59:09 +0900</pubDate>
    </item>
    <item>
      <title>1. 우아한테크코스에서의 기술 성장 기록</title>
      <link>https://happysisyphe.tistory.com/56</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;0. 우아한테크코스 회고&lt;/span&gt;&lt;/a&gt;&lt;br&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/57&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/58&quot; target=&quot;_self&quot;&gt;&lt;span&gt;3. 우아한테크코스에서 후회되는 점&lt;/span&gt;&lt;/a&gt;&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크코스를 진행하며 기술적으로 정말 많이 성장했다고 생각한다. 어떤 부분이 부족했었고, 어떤 노력을 통해 얼마나 성장할 수 있었는지 서술하고자 한다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. JavaScript 성장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 이전에 자바스크립트를 정말 못 했던 것 같다. 리액트 개발자로서 자바스크립트의 중요성은 익히 들어 알고 있었다만, 왜 중요한지 어떤 점을 잘 해야 하는지 알지 못 했다. spread operator, 불변성, Promise, nullish coalescing 등의 개념을 알고 사용할 줄 안다면 자바스크립트를 잘 하는 것이라고 생각했다. 그래서 스스로 문제점을 파악하지 못 했었다. 추후에 “자바스크립트가 중요하다는 말은 어떤 의미일까?” 를 주제로 하나의 글을 쓰려고 생각 중인데, 지금은 간단하게 언급해보겠다. 실행 컨텍스트, 프로토타입, 생성자 함수, 이벤트 루프와 큐를 잘 알아야 자바스크립트를 잘 하는 것이다. 이들은 실제로 개발하면서 직접적으로 사용해본 적이 없는 것일 가능성이 높다. 이들은 자바스크립트의 철학, 평가 및 실행 과정을 이해하는 데 중요한 요소들이다. 이들을 알아야 에러가 났을 때 문제점을 정확히 파악하여 디버깅을 할 수 있다. 예시를 하나 들어보자. 아래 코드에서 에러가 catch 되지 않는다. 실행 컨텍스트를 알아야 이를 완전히 이해할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;try {
  setTimeout(() =&amp;gt; {
    throw new Error('error')
  });
} catch (e) {
  console.log(e);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코를 하면서 이런 자바스크립트의 핵심적인 개념들을 채워나갔다. 공부한 방법은 주로 “모던 자바스크립트 Deep Dive”, &lt;a href=&quot;https://tc39.es/ecma262/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;“ECMAScript spec”&lt;/span&gt;&lt;/a&gt; 이었다. 딥다이브 책은 1000페이지 정도의 분량이고, 개념을 깊이 있게 서술해두어서 정독하면 매우 도움이 된다. 한 2달 동안 매일 아침 1시간씩 이 책을 읽었다. 실행 컨텍스트 개념을 이해하면서 hoisting, closure, async/await 등의 개념을 완전히 이해할 수 있었다. 생성자 함수와 프로토타입을 이해하면서 class, 정적 프로퍼티/멤버변수/메서드의 차이 등을 이해할 수 있었다. 하지만 이 책도 한계가 있다. 실행컨텍스트를 ES3 기반으로 서술하고 있다. 그래서 블록 스코프 개념이 서술되어 있지 않다. 하지만 실행 컨텍스트는 자바스크립트에서 정말 중요한 개념이기 때문에 이를 이해할 필요가 있었다. 이를 위해서 ECMAScript 2023 spec을 읽으며 이해했다. 또한 generator 도 실행 컨텍스트를 어떻게 관리하는지 공부하기 위해 spec을 읽어보았다. 이렇게 공부하여 이제 자바스크립트 디버깅에 자신감이 생겼다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 타입스크립트 성장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 활동 초기부터 나는 타입스크립트를 썼다. 타입스크립트가 너무 재밌었다. 타입 에러를 해결하고, 정확히 타입을 맞춰나가는 과정이 흥미로웠다. &lt;a href=&quot;https://typescript-exercises.github.io/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;typescript-exercises&lt;/span&gt;&lt;/a&gt; 를 11 레벨까지 풀었다. 또한 redux 타이핑을 하면서 많이 성장했다. 이때 useThunkFetch 라는 훅을 만들었다. 서버 통신을 하고, 그 값을 redux store에 저장한 후, data, isLoading, error 를 반환하는 훅이었다. 여기서 redux-thunk를 쓰려다보니 타이핑이 너무 어려웠다. 3개의 generic을 뚫었던 것으로 기억하는데, 이를 타입추론만으로 해결했었다. 3개의 타입변수가 있었는데, 함수 호출시에 제네릭을 사용하지 않아도 되는 마법같은 경험을 했었다.&lt;br&gt; &lt;br&gt;또한 프로젝트를 진행하면서는 타입스크립트 설계에 신경을 썼다. 아무리 타이핑을 잘 해도, 실질적으로 프로젝트에 중요한 것은 전체적인 타입설계였다. DTO 타입을 어떻게 분리할 것인가, 어떻게 재사용할 것인가, 타입을 어디에 응집/분리 시킬 것인가 고민했다. 그리고 Effective TypeScript 책을 읽었다. 구조적 타입 시스템이라는 개념을 알게 되었다. 서브 타이핑, 잉여속성체크 등을 이해했고, 타입스크립트의 design goals를 이해할 수 있었다. 이 과정에서 &lt;a href=&quot;https://www.youtube.com/watch?v=kMuJz6N-Grw&quot; target=&quot;_self&quot;&gt;&lt;span&gt;타입스크립트 도약하기&lt;/span&gt;&lt;/a&gt; 테코톡 영상을 통해 사람들에게 설명하는 시간을 가졌다. 마지막으로 공변/반공변/이변/불변성을 이해했다. react에서 bivarianceHack 을 사용하는데 이런 개념들을 이해할 수 있었다.&lt;br&gt;이런 과정을 거쳐서 타입스크립트에서는 신입 개발자가 할 수 있는 최고 수준으로 성장할 수 있었던 것 같다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. React 성장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트, 클린코드, 타입스크립트를 잘 할 수 있다면, 리액트는 사실 그냥 따라오는 기술 같다. 우테코 초창기에 나의 문제점은 기초 없이 React를 이해하려고 노력했던 점이다. class 컴포넌트와 함수 컴포넌트의 동작 방식을 자바스크립트 기반 없이, 리액트만으로 해석하려고 했었다. setState를 props으로 내려주는 것이 안티패턴이라는 것도 리액트만으로 해석하려고 했었다. 자바스크립트, 클린코드를 이해한다면, React에서 정말 많은 것이 그냥 잡히게 되는 것 같다. 컴포넌트 분리, 훅 분리, 파일 구조 설계, API 레이어 분리 등. 그렇다면 이외에 React 라이브러리를 추가로 학습해주기만 하면 된다. 함수 컴포넌트는 어떻게 상태를 기억하는가, 이벤트 위임은 어떻게 처리하는가, concurrent 는 무엇인가, 여러 훅들의 기능 등을 학습했다.&lt;br&gt; &lt;br&gt;초반에 hook을 통한 비즈니스 로직 분리에 대해서 정말 많이 고민했었다. 잡히지 않을 것 같았는데, 계속 고민하다 보니 어떻게 훅을 나누는 것이 잘 나눈 것인지 어느 정도 기준을 수립할 수 있었다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. http &amp;amp; network 공부&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9월부터 네트워크 스터디를 했다. &lt;a href=&quot;http://www.yes24.com/Product/Goods/15894097&quot; target=&quot;_self&quot;&gt;&lt;span&gt;그림으로 배우는 HTTP &amp;amp; Network&lt;/span&gt;&lt;/a&gt;를 읽고, 김영한님의 network 기본 강의를 보면서 학습했다. CORS, http 프로토콜, http 1.1/2.0/3.0, 쿠키 및 세션, DNS, 프록시 등의 개념을 학습할 수 있었다. 이를 통해 웹을 더 큰 그림에서 바라볼 수 있었다. 면접에서 뜨거운 토픽이 검색창에 &lt;a href=&quot;http://www.google.com을&quot; target=&quot;_self&quot;&gt;&lt;span&gt;www.google.com을&lt;/span&gt;&lt;/a&gt; 쳤을 때 어떤 일이 발생하는가? 였는데, 이것을 더 풍부하게 생각해볼 수 있었다. 아쉽게도 어떤 면접에서도 네트워크 지식을 질문 받지 않았다만, CS 지식을 처음 접한 것이 매우 귀중한 경험이었다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 점점 원본에 가까운 자료를 찾게 되었다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료의 신뢰성을 순서로 매기자면, 블로그 → 책 → 공식문서 → 코드 이다. 가령 React를 공부한다고 하면, 블로그 → React 책 → &lt;a href=&quot;http://react.org&quot; target=&quot;_self&quot;&gt;&lt;span&gt;reactjs.org&lt;/span&gt;&lt;/a&gt; → &lt;a href=&quot;https://github.com/facebook/react&quot; target=&quot;_self&quot;&gt;&lt;span&gt;react repository&lt;/span&gt;&lt;/a&gt; 순서이다. 자바스크립트로 보자면, 블로그 → JS 책 → &lt;a href=&quot;https://tc39.es/ecma262/#sec-execution-contexts&quot; target=&quot;_self&quot;&gt;&lt;span&gt;ecma spec&lt;/span&gt;&lt;/a&gt; → javascript engine(ex, V8) 순서이다. v8은 c++ 코드이므로 현실적으로 자바스크립트 개발자가 읽기는 어렵다. 이벤트 루프도 블로그와 책 보다는 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model&quot; target=&quot;_self&quot;&gt;&lt;span&gt;html spec&lt;/span&gt;&lt;/a&gt; 을 보거나, blink나 node 에 가면 코드를 볼 수 있을 것이다. 블로그는 신뢰성이 떨어지거나, 짜깁기 된 경우가 많기 때문에 모든 내용을 파악하기는 어렵다. 책은 최신성이 보장되지 않으므로 특정 부분에 대하여 구식 자료일 것이다. 공식문서가 잘 나와있는 경우 공식문서로 충분할 것이다. 라이브러리 공식문서는 이것만으로 100% 이해하기 어려운 경우가 있고, javascript나 react로 개발된 코드는 읽을만 하므로 원본 코드를 읽으며 이해하는 것도 좋다. react-query, react, react-error-boundary, redux-thunk, jotai 등은 경우에 따라 원본 코드를 읽으며 이해하려고 했다. 이 과정에서 오픈소스에 contribute 할 수도 있다. 현재 react-query에 1개의 contribute 했고, 자신감이 생겨 앞으로 더 해나갈 수 있을 것 같다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 분리/응집(co-location)에 대한 고민&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 코드 조각을 함께 위치(co-locate) 시켜야할지 분리 시켜야할지 고민을 많이 했다. 함께 위치 시키는 것도, 한 파일 내부에 시켜야 할지, 같은 폴더에 넣어야할지, 같은 파일이라면 바로 아래에 두어야할지, 위에 두어야할지 등을 고민해볼 수 있다. Snackbar 코드를 예시로 들어보자. 사용처에서 message 상태, isShow 상태를 정의하고 isShow 상태에 따라서 &amp;lt;Snackbar /&amp;gt; 를 렌더링 시킨다. 그러면 그 컴포넌트 내부에서 다양한 로직들과 Snackbar 로직이 공존하게 된다. 그리고 Snackbar 로직은 상태, 컴포넌트 등 넓은 커버리지를 가진다. 상태 로직을 useSnackbar로 분리한다고 하더라도, 커버리지가 넓어지고 응집되지 않는 것은 변하지 않는다. 이를 어떻게 응집시킬 것인가? Context API를 통해 전역으로 컴포넌트를 렌더링하고, 전역으로 상태를 보관함으로써 하나의 Provider에 모든 로직을 응집시킬 수 있다.&lt;br&gt; &lt;br&gt;두 번째 예시는 API 로직들이다. react-query를 활용한다고 했을 때, fetcher, Query/Mutation hook, DTO type 등을 정의해야 한다. 이를 응집 시킬지 분리 시킬지, 한 폴더에 넣을지, 한 파일에 넣을지, 그 파일/폴더 이름은 무엇으로 해야할지 등의 고민을 할 수 있다. 분리해보기도 하면서 불편함을 느껴보고, 응집해보기도 하면서 장점을 느껴보고 있다. 아직 확정된 방식은 없지만, 어떤 것을 택하더라도 논리적인 이유를 가지고 택할 수 있을 것 같다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. 도구로서의 기술 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 이전에는 기술 하나하나를 배우는 것이 벅차고 힘든 일이었다. storybook 을 처음 해보려고 할 때, 두려워서 시도조차 못 했던 기억이 난다. 하나의 기술을 학습할 때 각 잡고 정리하며 완벽하게 학습하려다 보니 그런 두려움이 생겼다고 생각한다. 이제는 필요한 기술이 있으면 바로 채택해서 사용할 수 있는 자신감/능력이 생겼다. docs 읽으면서 하나하나 따라해보는 미니 프로젝트를 만들어보면 그만이다. 기술은 도구이고, 필요 시에 도입해서 적용하면 된다. 이를 위한 오픈소스는 넘쳐난다. 이 방식으로 react-query, storybook, react-testing-library, msw, webpack, yarn berry 등을 학습하고 적용해볼 수 있었다.&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;0. 우아한테크코스 회고&lt;/span&gt;&lt;/a&gt;&lt;br&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/57&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/58&quot; target=&quot;_self&quot;&gt;&lt;span&gt;3. 우아한테크코스에서 후회되는 점&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/우아한테크코스</category>
      <category>우아한테크코스</category>
      <category>회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/56</guid>
      <comments>https://happysisyphe.tistory.com/56#entry56comment</comments>
      <pubDate>Sat, 10 Dec 2022 18:58:56 +0900</pubDate>
    </item>
    <item>
      <title>0. 우아한테크코스 회고</title>
      <link>https://happysisyphe.tistory.com/55</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. 우아한테크코스 회고&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/56&quot; target=&quot;_self&quot;&gt;&lt;span&gt;1. 우아한테크코스에서의 기술 성장 기록 &lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/57&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2. 우아한테크코스에서의 자기 성장 기록 &lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/58&quot; target=&quot;_self&quot;&gt;&lt;span&gt;3. 우아한테크코스에서 후회되는 점&lt;/span&gt;&lt;/a&gt;&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우아한테크코스 회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크코스 4기 프론트엔드 과정이 끝났다. 2022월 2월 8일부터, 11월 25일까지 약 10개월의 과정이었다. 매일 아침 10시부터 최소 밤 10시까지 공부했다. 공부 과정에는 기술 공부, 프로젝트 개발, 애자일 공부, 글쓰기, 리더 공부, 면접 준비 등이 포함된다. 고등학교 3학년 이후로 이렇게 열심히 한 적은 처음이었다. 인생에 고3 때처럼 노력하는 시기가 또 와야 한다고 생각하고 있었는데, 그게 우아한테크코스 기간이었다. 고등학교 때 2년의 공부를 통해 이전에 상상할 수 없는 대학교를 간 것 처럼, 올해 1년간의 공부를 통해 이전에 상상할 수 없는 기업에 갈 수 있게 되었다. 뭐, 대학교니 기업이니 하는 것은 근본적으로 중요하지 않다. 그런 수준의 능력을 가질 수 있게 성장했다는 것이 중요하다.&lt;br&gt; &lt;br&gt;올해 1년의 성장은 우테코에 의존하여 성장했다. 즉 우테코가 아니었다면 지금만큼 성장하지 못 했을 것이 분명하다. 그만큼 우테코는 훌륭한 교육기관이다. 그 첫 번째 이유는 규율 속 자율을 추구하기 때문이다. 등하교 시간이 정해져있지만 해당 시간 안에 잠을 자도 되고, 공부를 해도 되고, 크루들과 수다를 떨어도 되고, 코치와 면담을 해도 되고 모두 자율적으로 시간은 채워간다. 주기적으로 미션을 수행해야 하고, 미션은 필수 기능 요구사항을 전달하지만, 그 구현 방식은 거의 자율이다. 가령 바닐라 자바스크립트 미션이라고 하면, 그 구현을 MVC 패턴을 사용하든, 나만의 패턴을 사용하든, 웹 컴포넌트 기술을 적용하든 가능한 모든 시도를 해볼 수 있다.&lt;br&gt; &lt;br&gt;두 번째 이유는 함께 자라기를 강조하기 때문이다. 페어 프로그래밍, 현업자 리뷰, 스터디 등을 통해 소통하고 피드백을 주고받으며 함께 성장하는 것을 강조한다. 나도 처음에는 혼자 하려는 경향이 강했던 것 같다. 어떤 문제에 맞닥뜨렸을 때, 이를 주변인의 도움을 받아서 해결하는 것은 근본적으로 의미가 없다고 생각했다. 현업에 가서는 결국 혼자 해결해야할 터인데, 어떻게든 내가 직접 해결해서 나의 문제 해결 능력을 키우고 증명하는 것만이 의미가 있다고 생각했다. 하지만 지금은 생각이 바뀌었다. 배워야할 지식이 너무 많고, 각 지식마다 전문가가 있기 마련이다. 내가 전문인 부분은 남들에게 공유해주고, 내가 부족한 부분은 빠르게 질문하여 공유 받음으로써 효율성을 높일 수 있다. 잘 질문하여 빠르게 해결하는 것도 능력이라는 것을 깨달았다. 속도 또한 문제해결의 중요한 축을 담당하고 있기 때문이다. 이때 “잘” 질문하여 주변에 효과적으로 도움을 요청하는 것이 중요하다.&lt;br&gt; &lt;br&gt;마지막 이유는 피드백을 강조하기 때문이다. 우테코에서 성적을 매기는 것은 없지만, 피드백을 받는 과정은 존재한다. 페어 프로그래밍, 코드리뷰 등을 진행하며 피드백을 주고 받는다. 코드리뷰를 할 때 어떤 소통 태도를 가지고 있는지, 코드/소통 측면에서 어떻게 개선했으면 좋겠는지 등을 가감없이 들을 수 있다. 페어 프로그래밍에서도 피드백을 강조하는데, 이를 위해서 페어 프로그래밍 회고를 주도했었다. 특히 감정회고를 통해 나의 속마음을 드러냈던 경험들이 인상깊게 남았고, 인격적으로 성장할 수 있는 시간이 되었다고 생각한다.&lt;br&gt; &lt;br&gt;우아한테크코스가 어떤 교육 철학을 가지고 교육하며, 이 사이에서 내가 어떤 생각을 하면서 지냈는지 서술해보았다. 다음으로 이 기간동안 성장한 포인트를 더욱 구체적으로 짚어보고자 한다. 기술적 성장과 자기 성장으로 나누어서 서술하고자 한다.&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시리즈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. 우아한테크코스 회고&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/56&quot; target=&quot;_self&quot;&gt;&lt;span&gt;1. 우아한테크코스에서의 기술 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/57&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2. 우아한테크코스에서의 자기 성장 기록&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://happysisyphe.tistory.com/58&quot; target=&quot;_self&quot;&gt;&lt;span&gt;3. 우아한테크코스에서 후회되는 점&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/우아한테크코스</category>
      <category>우아한테크코스</category>
      <category>회고</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/55</guid>
      <comments>https://happysisyphe.tistory.com/55#entry55comment</comments>
      <pubDate>Sat, 10 Dec 2022 18:58:44 +0900</pubDate>
    </item>
    <item>
      <title>혹시 무분별하게 Suspense 를 사용하고 계신가요? (react-query)</title>
      <link>https://happysisyphe.tistory.com/54</link>
      <description>&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;suspense-image.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIYDJV/btrOQl2ufan/Ve1Lw4TdKdQs77m7RqE6JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIYDJV/btrOQl2ufan/Ve1Lw4TdKdQs77m7RqE6JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIYDJV/btrOQl2ufan/Ve1Lw4TdKdQs77m7RqE6JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIYDJV%2FbtrOQl2ufan%2FVe1Lw4TdKdQs77m7RqE6JK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;400&quot; data-filename=&quot;suspense-image.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h1&gt;서언&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactjs.org/blog/2022/03/29/react-v18.html#new-suspense-features&quot;&gt;React v18 부터 Suspense&lt;/a&gt;가 API call에 따른 Loading 상태를 표현할 수 있게 되었습니다. 그에 따라, react-query, swr 같은 data fetching library가 Suspense를 지원하고 있습니다. suspense 옵션만 true로 설정해주면, API 요청 훅이 알아서 내부 처리를 통해 Suspense를 동작시킵니다. 이로써 로딩을 선언적으로 보여줄 수 있게 되었고, ErrorBoundary 조합과 함께라면 컴포넌트는 Success 케이스만 표현하면 되었습니다. 이런 혁신에 대해서는 잘 인지하고 있었지만, Suspense의 위험성에 대해서는 인지하지 못 했습니다. 특히 저희 서비스는 잘못된 Suspense 사용으로 어플리케이션 로딩 성능 저하를 겪었습니다. Suspense가 어떻게 로딩 성능을 저하시킬 수 있을까요? 이번 글은 무분별한 Suspense 사용이 초래할 수 있는 문제점과, 그에 대한 해결책을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 react-query를 도구로 Suspense를 설명합니다. 다른 data fetching library도 동일하게 동작할 것입니다.&lt;/p&gt;
&lt;h1&gt;Simple use case of Suspense&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 Suspense 의 사용 방법을 알아보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function App() {
  return (
    &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;...loading&amp;lt;/div&amp;gt;}&amp;gt;
      &amp;lt;TodoList /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  );
}

function TodoList() {
  const { data: todoList } = useQuery(&quot;todos&quot;, () =&amp;gt; client.get(&quot;/todos&quot;), {
    suspense: true,
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;h2&amp;gt;Todo List&amp;lt;/h2&amp;gt;
        {todoList?.data.map(({ id, title }) =&amp;gt; (
          &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위에서 Suspense로 컴포넌트를 감싸주고, useQuery 옵션에서 suspense 를 켜주기만 하면, Loading 상태를 나타냅니다. TodoList 에서 API fetch가 발생하는 동안 Loading fallback을 보여주는 것이지요. 매우 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suspense는 어떻게 동작하는 것일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suspense의 원리를 알아야 이후 발생할 문제점을 제대로 이해할 수 있습니다. 이번 아티클은 Suspense의 동작방식 보다는, Suspense가 발생시킬 수 있는 문제점에 집중할 것이므로 자세한 설명은 타 아티클에 맡기도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/react-suspense/#suspense-사용-후&quot;&gt;https://www.daleseo.com/react-suspense/#suspense-사용-후&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 설명드리자면, &lt;b&gt;Suspense는 Promise를 catch 합니다.&lt;/b&gt; ErrorBoundary가 error을 catch 하는 것처럼 말이죠. 그렇다면 Suspense가 Loading을 보여주게 하기 위해서는 Promise를 throw 하면 될 것입니다. API call의 Promise를 던지면, Suspense가 이를 catch하고, Promise가 pending 상태이면 LoadingFallback을 return하고, settled(fulfilled, rejected)이면 children을 return 하는 형태일 것입니다.&lt;/p&gt;
&lt;h1&gt;Suspense 남용의 위험성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 컴포넌트를 보겠습니다. Before 컴포넌트를 Suspense가 감싸고 있습니다. 그리고 내부에서 &lt;b&gt;&quot;2개의 Query&quot;&lt;/b&gt;를 요청하고 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// App.jsx
function App() {
  return (
    &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;...loading&amp;lt;/div&amp;gt;}&amp;gt;
      &amp;lt;Before /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  );
}

// Before.jsx
const BASE_URL = &quot;https://jsonplaceholder.typicode.com&quot;;
const client = axios.create({ baseURL: BASE_URL });

function Before() {
  const { data: todoList } = useQuery(&quot;todos&quot;, () =&amp;gt; client.get(&quot;/todos&quot;), {
    suspense: true,
  });

  const { data: postList } = useQuery(&quot;posts&quot;, () =&amp;gt; client.get(&quot;/posts&quot;), {
    suspense: true,
  });

  return (
    &amp;lt;div style={{ display: &quot;flex&quot; }}&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;h2&amp;gt;Todo List&amp;lt;/h2&amp;gt;
        {todoList?.data.map(({ id, title }) =&amp;gt; (
          &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/section&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;h2&amp;gt;Post List&amp;lt;/h2&amp;gt;
        {postList?.data.map(({ id, title }) =&amp;gt; (
          &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 어떻게 가고 있는지 네트워크 탭을 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;networktab.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L1IDJ/btrOQmtwvv9/d6yJpOLEQKwdHGkQkJZfyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L1IDJ/btrOQmtwvv9/d6yJpOLEQKwdHGkQkJZfyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L1IDJ/btrOQmtwvv9/d6yJpOLEQKwdHGkQkJZfyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL1IDJ%2FbtrOQmtwvv9%2Fd6yJpOLEQKwdHGkQkJZfyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2032&quot; height=&quot;210&quot; data-filename=&quot;networktab.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;network waterfall&lt;/b&gt;을 만들고 있습니다. 저희 서비스는 이 문제점을 겪었고, 앱의 Loading이 길어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 저희 서비스의 예제입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;networktab2.png&quot; data-origin-width=&quot;2772&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HDDP9/btrOGn7neaV/YiK1wgJ00s4uMs85bbSd4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HDDP9/btrOGn7neaV/YiK1wgJ00s4uMs85bbSd4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HDDP9/btrOGn7neaV/YiK1wgJ00s4uMs85bbSd4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHDDP9%2FbtrOGn7neaV%2FYiK1wgJ00s4uMs85bbSd4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2772&quot; height=&quot;1136&quot; data-filename=&quot;networktab2.png&quot; data-origin-width=&quot;2772&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번의 API 요청을 하는데요. waterfall이 발생하여 무려 4번의 Loading을 보여주고 있는 것입니다. 그래서 실제로 모바일에서 앱에 접속하는데 3초 이상이 소요되는 현상이 발생했습니다. Lighthouse의 LCP(Largest Contentful Paint) 지표도 악화된 것을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 상황이 발생할까요? 바로 &lt;b&gt;Suspense의 잘못된 사용&lt;/b&gt; 때문입니다. 위에서 설명한대로 Suspense는 Promise를 catch하여 Promise 상태에 따라서 children 또는 LoadingFallback 컴포넌트를 반환합니다. &lt;b&gt;즉, pending 상태일 때에는 Loading을 반환하고 있고, children을 실행시키지 않는다는 것이죠.&lt;/b&gt; 그렇기 때문에, 하나의 API 요청이 발생하면, children 컴포넌트의 실행은 멈추고 Loading을 반환하게 됩니다. Promise가 settled 상태가 되면 다시 children을 반환하여 children 컴포넌트를 렌더링하겠지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유 때문에 &lt;b&gt;Suspense가 감싸고 있는 하나의 컴포넌트에서 2개 이상의 요청을 할 때 네트워크 병목이 발생하는 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;해결방안 1 - useQueries&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-query를 사용하는 경우, 여러 query를 동시에 요청하는 방법으로 &lt;b&gt;useQueries&lt;/b&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function After_useQueries() {
  const [{ data: todoList }, { data: postList }] = useQueries([
    {
      queryKey: [&quot;todo&quot;],
      queryFn: () =&amp;gt; client.get&amp;lt;Todo[]&amp;gt;(&quot;/todos&quot;),
      suspense: true,
    },
    {
      queryKey: [&quot;post&quot;],
      queryFn: () =&amp;gt; client.get&amp;lt;Post[]&amp;gt;(&quot;/posts&quot;),
      suspense: true,
    },
  ]);

  return (
    &amp;lt;div style={{ display: &quot;flex&quot; }}&amp;gt;
      &amp;lt;section style={{ width: &quot;50%&quot; }}&amp;gt;
        &amp;lt;h2&amp;gt;Todo List&amp;lt;/h2&amp;gt;
        {todoList?.data.map(({ id, title }) =&amp;gt; (
          &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/section&amp;gt;
      &amp;lt;section style={{ width: &quot;50%&quot; }}&amp;gt;
        &amp;lt;h2&amp;gt;Post List&amp;lt;/h2&amp;gt;
        {postList?.data.map(({ id, title }) =&amp;gt; (
          &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 탭에서 보시는 바와 같이 병렬 처리를 지원하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZqYuj/btrPjkHJEiH/mkdd538twneZXO8ZiJa7QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZqYuj/btrPjkHJEiH/mkdd538twneZXO8ZiJa7QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZqYuj/btrPjkHJEiH/mkdd538twneZXO8ZiJa7QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZqYuj%2FbtrPjkHJEiH%2Fmkdd538twneZXO8ZiJa7QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2028&quot; height=&quot;216&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 화면을 한번 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;useQueries-suspense.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfBldE/btrPiujsBY8/7s5llhzjyEnkU8Mt7LhtMk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfBldE/btrPiujsBY8/7s5llhzjyEnkU8Mt7LhtMk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfBldE/btrPiujsBY8/7s5llhzjyEnkU8Mt7LhtMk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cfBldE/btrPiujsBY8/7s5llhzjyEnkU8Mt7LhtMk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;899&quot; data-filename=&quot;useQueries-suspense.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;loading이 찍히고 있지 않습니다.&lt;/b&gt; 무슨 일일까요? github issue를 열심히 찾아보았습니다. react-query 메인테이너는 다음과 같이 말하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;useQueries.png&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pApai/btrPgePtACO/jm5nI0xY0hVn54lGOqk9Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pApai/btrPgePtACO/jm5nI0xY0hVn54lGOqk9Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pApai/btrPgePtACO/jm5nI0xY0hVn54lGOqk9Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpApai%2FbtrPgePtACO%2Fjm5nI0xY0hVn54lGOqk9Hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1874&quot; height=&quot;364&quot; data-filename=&quot;useQueries.png&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;ldquo;v4(현재 버전)에서도 여전히 &lt;b&gt;useQueries에 suspense를 지원하지 않습니다.&lt;/b&gt; 우리는 다양한 아이디어를 실현시켜줄 새로운 API를 가지고 있지만, 아직 아무 것도 해결되지는 않았습니다.&lt;br /&gt;&lt;b&gt;현재로서 useQuery는 waterfall을 만드는 한계를 가지고 있기 때문에,&lt;/b&gt; 저는 이 기능(useQueries의 suspense)를 결국 해내고 싶습니다.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQueries에 suspense 옵션을 구현하기 어려운 배경을 찾아보니, useQueries는 useQuery의 복수가 아니라, 아예 다른 메커니즘으로 동작하고 있기 때문입니다. 그래서 useQuery에서 suspense가 지원된다고 하여 useQueries에도 지원되는 것이 성립하지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이슈와 PR을 찾아보시면, 더욱 상세한 내용을 이해할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TanStack/query/issues/1523&quot;&gt;https://github.com/TanStack/query/issues/1523&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TanStack/query/pull/2109&quot;&gt;https://github.com/TanStack/query/pull/2109&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TanStack/query/issues/2923&quot;&gt;https://github.com/TanStack/query/issues/2923&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 돌아와서, &lt;b&gt;useQueries는 suspense가 지원되지 않는다는 것을 알았습니다.&lt;/b&gt; 더불어 &lt;b&gt;useQueries는 useErrorBoundary 옵션도 지원하지 않습니다.&lt;/b&gt; suspense가 되지 않는 것과 동일한 매커니즘입니다. 이런 점들로 미루어보았을 때, useQueries는 아직 안정된 기능이 아니라는 생각이 듭니다. 가령, v3에서 v4로 바뀌면서 명세 자체가 변경되었습니다. v3에서 하나의 인자(queries)만 받다가, v4에서 2개의 인자로 들어나면서 객체 구조의 인자로 변경되었습니다. 즉, 저희 프로젝트는 v3를 사용 중인데, 이후 버전 업데이트 시 호환되지 않을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-query-v3.tanstack.com/reference/useQueries#_top&quot;&gt;https://react-query-v3.tanstack.com/reference/useQueries#_top&lt;/a&gt; &lt;a href=&quot;https://tanstack.com/query/v4/docs/reference/useQueries&quot;&gt;https://tanstack.com/query/v4/docs/reference/useQueries&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 단점을 가진 useQueries를 사용하기엔 다소 무리가 있어 보입니다. Suspense와 ErrorBoundary를 모두 사용하지 않겠다고 하면 일부 단점을 안고 useQueries를 사용해도 무방할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 방안들을 떠올려보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 useQueries suspense 라이브러리 문제는 @tanstack/react-query v4.15.0 에서 해결되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TanStack/query/releases/tag/v4.15.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/TanStack/query/releases/tag/v4.15.0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680805974230&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Release v4.15.0 &amp;middot; TanStack/query&quot; data-og-description=&quot;Version 4.15.0 - 11/12/2022, 7:28 AM Changes Feat react-query: suspense for useQueries (#4498) (9d9aea5) by Dominik Dorfmeister Packages @tanstack/query-core@4.15.0 @tanstack/react-query@4.15.0 ...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/TanStack/query/releases/tag/v4.15.0&quot; data-og-url=&quot;https://github.com/TanStack/query/releases/tag/v4.15.0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RMTmF/hySa5hD7yV/0JlzU0FALZKyjbhzZBz7SK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/TanStack/query/releases/tag/v4.15.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/TanStack/query/releases/tag/v4.15.0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RMTmF/hySa5hD7yV/0JlzU0FALZKyjbhzZBz7SK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Release v4.15.0 &amp;middot; TanStack/query&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Version 4.15.0 - 11/12/2022, 7:28 AM Changes Feat react-query: suspense for useQueries (#4498) (9d9aea5) by Dominik Dorfmeister Packages @tanstack/query-core@4.15.0 @tanstack/react-query@4.15.0 ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;해결방안 2 - Suspense 2개, Component 2개, API call 2개&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Suspense 내의 컴포넌트에서 두 개 이상의 요청이 발생하면, 네트워크 워터폴 현상이 생깁니다.&lt;/b&gt; 그렇다면 한 컴포넌트에 하나의 요청을 유지하면 해결되겠지요. 아래 코드는 2개의 컴포넌트로 분리하고, 각각 Query 요청을 수행하고, 각각 Suspense로 감싸준 코드입니다. (Suspense 2개, Component 2개, API call 2개)&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function AfterEachSuspense() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div style={{ display: &quot;flex&quot; }}&amp;gt;
        &amp;lt;section&amp;gt;
          &amp;lt;h2&amp;gt;Todo List&amp;lt;/h2&amp;gt;
          &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;...loading&amp;lt;/div&amp;gt;}&amp;gt;
            &amp;lt;TodoList /&amp;gt;
          &amp;lt;/Suspense&amp;gt;
        &amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;
          &amp;lt;h2&amp;gt;Post List&amp;lt;/h2&amp;gt;
          &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;...loading&amp;lt;/div&amp;gt;}&amp;gt;
            &amp;lt;PostList /&amp;gt;
          &amp;lt;/Suspense&amp;gt;
        &amp;lt;/section&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

const TodoList = () =&amp;gt; {
  const { data: todoList } = useQuery(&quot;todos&quot;, () =&amp;gt; client.get(&quot;/todos&quot;), {
    suspense: true,
  });

  return (
    &amp;lt;div&amp;gt;
      {todoList?.data.map(({ id, title }) =&amp;gt; (
        &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
};

const PostList = () =&amp;gt; {
  const { data: postList } = useQuery(&quot;posts&quot;, () =&amp;gt; client.get(&quot;/posts&quot;), {
    suspense: true,
  });

  return (
    &amp;lt;div&amp;gt;
      {postList?.data.map(({ id, title }) =&amp;gt; (
        &amp;lt;div key={id}&amp;gt;{title}&amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 네트워크 요청 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;networktab3.png&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4faeU/btrO4uLDLcN/HklvBR7rXfLWRTZPlpA8kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4faeU/btrO4uLDLcN/HklvBR7rXfLWRTZPlpA8kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4faeU/btrO4uLDLcN/HklvBR7rXfLWRTZPlpA8kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4faeU%2FbtrO4uLDLcN%2FHklvBR7rXfLWRTZPlpA8kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2044&quot; height=&quot;204&quot; data-filename=&quot;networktab3.png&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 병목 현상을 해결하고, 정상적으로 &lt;b&gt;병렬 처리되는 모습을 확인할 수 있습니다.&lt;/b&gt; Suspense가 분리되었고, 각각의 Suspense가 각각의 children을 관장하므로 병렬적으로 실행되는 것이 당연한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방식은 상황에 따라서 문제가 될 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로, 로딩 상태가 제각각 이라는 것입니다. 위 네트워크 탭에서 todos 와 posts 요청을 보시면, 끝나는 시간이 다른데요. 각각의 Suspense는 각각의 네트워크 요청이 끝나는 것을 감지하여 children을 렌더링 시킵니다. 그렇다면, &lt;b&gt;요청이 먼저 끝난 컴포넌트가 먼저 보여지겠죠.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-10-20 오전 12.04.07.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjNI70/btrODOrk9sV/d4hgrBsUVM9X2IMKsKaDWk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjNI70/btrODOrk9sV/d4hgrBsUVM9X2IMKsKaDWk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjNI70/btrODOrk9sV/d4hgrBsUVM9X2IMKsKaDWk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bjNI70/btrODOrk9sV/d4hgrBsUVM9X2IMKsKaDWk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;629&quot; data-filename=&quot;화면 기록 2022-10-20 오전 12.04.07.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버벅이는 느낌이 들지 않나요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;suspense1.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4f2H3/btrO4udMYqv/Yu8AKKNkrhjzGNBZkr0AG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4f2H3/btrO4udMYqv/Yu8AKKNkrhjzGNBZkr0AG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4f2H3/btrO4udMYqv/Yu8AKKNkrhjzGNBZkr0AG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4f2H3%2FbtrO4udMYqv%2FYu8AKKNkrhjzGNBZkr0AG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;628&quot; data-filename=&quot;suspense1.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TodoList가 먼저 렌더링되기 때문입니다. &lt;b&gt;유저에게 버벅거리는 느낌을 준다는 점에서 유저 경험을 악화시킬 가능성도 있습니다.&lt;/b&gt;&amp;nbsp;이는 상황에 따라서 좋은 경험일 수도 있고, 좋지 않은 경험일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면, 컴포넌트를 분리한 후, 각각 Suspense를 감싸주는 것은 유저 경험을 악화시킬 가능성이 있습니다. 그렇다면 두 개 모두가 로딩이 끝나는 시점에 한번에 렌더링 하려면 어떻게 해야할까요?&lt;/p&gt;
&lt;h1&gt;해결방안 3 - Suspense 1개, Component 2개, API call 2개&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드부터 살펴보겠습니다. 위 코드와 같이 컴포넌트와 Query는 분리되었지만, Suspense는 하나로 감싸고 있다는 점이 다릅니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function AfterSameSuspense() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;...loading&amp;lt;/div&amp;gt;}&amp;gt;
        &amp;lt;div style={{ display: &quot;flex&quot; }}&amp;gt;
          &amp;lt;section&amp;gt;
            &amp;lt;h2&amp;gt;Todo List&amp;lt;/h2&amp;gt;
            &amp;lt;TodoList /&amp;gt;
          &amp;lt;/section&amp;gt;
          &amp;lt;section&amp;gt;
            &amp;lt;h2&amp;gt;Post List&amp;lt;/h2&amp;gt;
            &amp;lt;PostList /&amp;gt;
          &amp;lt;/section&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 탭을 살펴보겠습니다. 동일하게 병렬 처리되고 있고, 임의의 API call이 먼저 끝나는 상황입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;networktab4.png&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5UR40/btrO50C2uAW/QUYyMyTT1D2gkkZz3XCcC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5UR40/btrO50C2uAW/QUYyMyTT1D2gkkZz3XCcC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5UR40/btrO50C2uAW/QUYyMyTT1D2gkkZz3XCcC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5UR40%2FbtrO50C2uAW%2FQUYyMyTT1D2gkkZz3XCcC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2046&quot; height=&quot;224&quot; data-filename=&quot;networktab4.png&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gif를 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;same-suspense.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVneS9/btrOQE10CeB/CB6k2U2dgO1b8ToKESuOYK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVneS9/btrOQE10CeB/CB6k2U2dgO1b8ToKESuOYK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVneS9/btrOQE10CeB/CB6k2U2dgO1b8ToKESuOYK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cVneS9/btrOQE10CeB/CB6k2U2dgO1b8ToKESuOYK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;629&quot; data-filename=&quot;same-suspense.gif&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;1528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 의도한 대로, &lt;b&gt;두개의 요청이 모두 끝날 때를 기다리고 렌더링 해줍니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Suspense 내부 코드를 살펴봐야 정확히 알겠지만, Suspense 내부에서 API 요청이 발생한 소재를 파악하여, 그곳은 LoadingFallback으로 대체하고, 나머지 부분은 정상적으로 실행시키는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;1 Suspense, 1 Component, 2 API call&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;네트워크 워터폴을 만듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;useQueries&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;병렬 처리가 가능하지만, suspense를 지원하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2 Suspense, 2 Component, 2 API call&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Suspense가 제각각 실현되고, 먼저 끝난 요청이 먼저 렌더링됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1 Suspense, 2 Component, 2 API call&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Suspense가 하나로 실현되고, 2개의 요청이 모두 끝나야 렌더링 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 상황은 지양해야합니다. Suspense를 사용하면서 한 컴포넌트에서 두 개 이상의 요청을 하면, 네트워크 워터폴을 만들어 로딩 시간을 길게 합니다. useQueries를 대안으로 떠올릴 수 있으나 이는 아직 suspense를 지원하기 않기 때문에 한계가 있습니다. 그러므로 Suspense를 사용하기 위해서는 한 컴포넌트 당 하나의 API call을 유지해야 합니다. 이를 위해 컴포넌트를 분리하는 작업이 수반될 수 있습니다. 3번과 4번의 경우는 미세한 차이를 보이면서, 어떤 유저 경험을 주고 싶냐에 따라서 다른 선택을 할 수 있습니다. 먼저 끝난 요청을 먼저 렌더링 하여 유저에게 빠르게 보여주길 원한다면 3번을 택하면 될 것이고, 한번에 렌더링하여 버벅거리는 유저 경험을 주고 싶지 않다면 4번을 택하면 좋을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;또는 가장 근본적으로 suspense의 사용 자체를 고민해보는 것도 좋은 접근입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;마치며&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 아티클에서 살펴보았듯, &lt;b&gt;무분별한 Suspense는 심각한 로딩 성능 저하를 낳습니다.&lt;/b&gt; 어떤 기술이나 그렇듯, Suspense의 장점만 인지하고 Suspense를 사용해서는 안 되며, 동작 원리와 적절한 use case를 꾸준히 탐구해야 합니다. 저희 서비스에서는 이 문제를 해결하였고, 현재 Suspense 사용 자체를 고민 중인 단계입니다. Suspense 내의 한 컴포넌트에서 여러개의 API 요청을 병렬적으로 처리할 수 없는지 고민하고 있습니다. 특별한 조치를 취해주지 않는다면, Suspense를 사용하기 위해서는 1 Component, 1 API call을 유지해야 하며, Suspense를 감싸는 위치까지 고려해줘야 하는 것입니다. 이는 선언적인 프로그래밍이라는 확실한 장점이 있지만, 개발 비용을 높이고 Suspense와 isLoading 중 하나의 원칙을 가져갈 수 없다는 점에서 일관성 문제가 부각될 수 있습니다. Suspense의 장단점을 명확히 파악했길 바라며 글을 마칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 Suspense를 더 잘 활용하는 방법에 대해 토론거리가 있다면 댓글 부탁드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성자 : &lt;a href=&quot;https://github.com/euijinkk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/euijinkk&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codesandbox.io/s/suspicious-euclid-6ld1yn?file=/src/App.tsx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;코드 확인하러 가기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/Sofeware Development</category>
      <category>react</category>
      <category>Suspense</category>
      <category>useQueries</category>
      <author>행복한 시지프</author>
      <guid isPermaLink="true">https://happysisyphe.tistory.com/54</guid>
      <comments>https://happysisyphe.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 20 Oct 2022 00:40:29 +0900</pubDate>
    </item>
  </channel>
</rss>