<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wongyu_shin.log</title>
        <link>https://velog.io/</link>
        <description>생존형 개발자. 어디에 던져져도 살아 남는것이 목표입니다.</description>
        <lastBuildDate>Sun, 30 Mar 2025 14:37:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>wongyu_shin.log</title>
            <url>https://media.vlpt.us/images/wongue_shin/profile/424e62b5-0cc8-486a-953c-fa6f943d2525/KakaoTalk_20220310_155759107.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. wongyu_shin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wongue_shin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[3년간의 글또 활동을 마무리하며]]></title>
            <link>https://velog.io/@wongue_shin/3%EB%85%84%EA%B0%84%EC%9D%98-%EA%B8%80%EB%98%90-%ED%99%9C%EB%8F%99%EC%9D%84-%EB%A7%88%EB%AC%B4%EB%A6%AC%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@wongue_shin/3%EB%85%84%EA%B0%84%EC%9D%98-%EA%B8%80%EB%98%90-%ED%99%9C%EB%8F%99%EC%9D%84-%EB%A7%88%EB%AC%B4%EB%A6%AC%ED%95%98%EB%A9%B0</guid>
            <pubDate>Sun, 30 Mar 2025 14:37:08 GMT</pubDate>
            <description><![CDATA[<p>막 개발자로 취업을 성공한 신입 시절, 나는 비전공자 출신의 개발자여서 개발과 관련한 경험과 아이디어를 사람과 나누고 싶다는 갈증을 느끼고 있었다. 그래서 그 쯔음 진행되었던 컨퍼런스도 많이 참석했었지만,</p>
<p>컨퍼런스를 단순히 참석자로서 참가하는것으로는 일방적인 방향의 소통이기에 내가 가지고 있던 갈증의 해소가 잘 되지 않았었다.</p>
<p>이때쯤, 마침 글또라는 커뮤니티를 알게되어서 가벼운마음에 가입을 신청했었는데, 그 시점에는 이 커뮤니티 활동을 3년동안이나 길게 이어가게 될 줄은 전혀 몰랐었다.</p>
<h3 id="글또와의-첫-만남">글또와의 첫 만남</h3>
<p>글또에서는 신규분들이 활동하기 위해선, <code>삶의 지도</code> 라는 글을 작성해야한다는 특이한 규칙이 있었다. 
나는 8기수 부터 참여하게 되었는데, 내가 참여하기 이전 기수는 잘 알지 못하지만 7~10 기간에는 적어도 이 모집 요강이 바뀌지 않았었다.</p>
<p>나름 메타인지가 높은 편이라 생각하고 있어, 어떤 삶을 살아왔고, 그 중 어떤 사건이 나를 정의하는 조각이 되었는지를 설명하는건 그리 어렵지 않았다.</p>
<p>이 때 지금 생각하면 조금 웃긴 이야기는, 
입사 지원서를 쓸때와 비슷하게 강한 확신을 가지고 나를 어필하는 인재상을 요구할꺼란 생각에,</p>
<p>글의 내용과 문체를 그 쪽에 맞춰 썻던 기억이 있는데, 사실 실제로 글또를 운영하시는 성윤님을 뵙게된 이후에는, 처음에 내가 생각햇던 방향하고는 전혀 다른 느낌의 가치를 추구하시는 분이셨다
(작성한 글은 다시보니 좀.. 부끄러워 숨겨놓았다.)</p>
<p>그때 플러터를 현업에서 다루시는 분과 같이 글을 작성했던게 기억에 남는데,
그 분도 나와 비슷하게 3년동안 꾸준히 글또에서 활동하셨다.</p>
<p>개인적으로 친밀한 사이가 되지는 않았지만, 멀리서 가끔씩 소식을 듣고 잘 되길 응원하는 중.
약간 입사 동기? 와 같은 느낌이여서 혼자 왜인지 모르는 내적 친밀감이 들었다.</p>
<h3 id="8기">8기</h3>
<p>Velog 포스트의 2023 년 초반기에 작성한글 들을 이 기수 떄 작성하게 되었다.</p>
<p>브라우저는 어떻게 내가 작성한 코드를 실행할까?
FE TDD에서 상태전이도가 필요한 이유
Flutter APP lifecycle
왜 배열의 목록은 &#39;i&#39; 로 이름을 지을까?</p>
<p>등의 포스트를 작성했었다.
이 때 종종 커뮤니티 내부에서 공유할만한 글로 소개되는 큐레이션에 선정되었던 경험이,</p>
<p>꾸준하게 커뮤니티와 블로그 포스트를 쓰게 만드는 강한 외적 동기가 되었던것 같다.</p>
<p>이 당시 회사에서 테크리더분이 기술적 아이디어를 끈임없이 공유해주시려 노력해주셔서,
그 덕을 많이 봤었던것 같다.</p>
<p>이 기수때는 커피드백이라는 이름으로 임의의 조로 묶여 조별로 글의 내용을 피드백하는 일정이 있었는데,
선호 장소를 강남/서초 로 설정해서 그런지, 나 빼고 다른 모든 조원분들이 토스 현직자분들이여서 상당히 흥미로운 이야기를 들을 수 있던 기억이 난다.</p>
<p>아직 신입 시절이여서, 경험을 공유하는것 보다는 많은 걸 배우고 성장하는데 집중했었던 기간들 이였다.</p>
<h3 id="9기">9기</h3>
<p>이 기수때에는 어떤식으로 글을 표현하는게 좋을까? 와 같은 고민과 시도를 여러방향으로 진행했었던것 같다.</p>
<p>스타트업이란 밴드에서 개발자는 어떤 포지션일까요?
빵빵이의 일 두배로 잘하는 법~!
생각보다 수학으로 할 수 있는 많은것들
어느날 갑자기 앱이 켜지지 않는다
등등의 포스트를 작성하였다.</p>
<p>이때, Udemy 와 글또 커뮤니티간의 좋은 제휴가 있었어서 수강 후기를 쓰는 조건으로 강의 두편을 무료로 제공 받았던 기회가 있었던게 기억이 난다. 
(다시보니 왠지 모르게, 1부 후기가 날아가있다;; 벨로그의 오류인거 같은데, 이거 조만간 다시 복구를 해야겠다.)</p>
<p>여러 표현 방식으로 글을 작성해보기도 하고, 다양한 주제를 건드려보기도 하면서 얻은 결론은,
일단 쓰는 사람이 편해야 하겠다는 생각이었다.</p>
<p>글은 독자들에게 읽히며 완성되기도 하지만, 사실 내 블로그의 글들은 자전적인 성격이 강해서,
내가 글을 쓰며 생각을 정리하기 위함이 크다.</p>
<p>이 때, 많은 조회수와 같은 외연적인 성과를 위해 집중하다보면, 
그 과정에 들어가는 리소스가 너무 커 글을 주기적으로 쓰기가 어려움을 느끼는게 가장 큰 이유였다.
(사실 형식을 신경쓰지 않아도, 이미 충분히 글을 작성하는데 어려움을 느끼고 있다.. 
색채이론, 확률론등, 취미시간에 가볍게 읽고 넘어간 주제들을 다시 정리된 글로 엮어내려면, 생각보다 정말 많은 시간과 노력이 필요하다..)</p>
<p>이 기수에는 블로그 포스트를 컨퍼런스에서 할 발표의 초안과 같은 방식으로 많이 활용했던것 같다.
재밌는 주제를 발견하면, 짧은 글로서 이를 블로그에 저장하고, 이 목록을 추후 컨퍼런스 준비위들과 이야기해서 그 행사 주제에 가장 부합하는 주제를 찾아, 추가 조사 이후 발표를 진행하는쪽으로 이용하니, 블로그 글쓰기의 효용감이 더 좋았던 기억이 있다.</p>
<p>이 기수때는 8기보다는 더 많은 글또 회원들과 커뮤니케이션을 진행했었는데,
많은걸 느끼고, 또 내가 학습해 나가는 방향이 그리 틀리지 않은것 같다는 강한 자기확신을 갖을 수 있었던 시간이였던것 같다.</p>
<p>추가로 9기와 ~ 10기 사이의 빈 공간에는 ML 관련 스터디를 만들어 글또 인원분들과 진행했었는데,
나름 성과가 있었던 스터디여서 만족하고있다. (이 내용도 정리해 잊어버리기 전 포스트로 풀어야하는데...)</p>
<h3 id="10기">10기</h3>
<p>10기 때는 사실 포스팅보다는, 글또 회원분들과의 교류를 더 집중했었던것 같다..
(그래서 사실 포스팅간 주기도 불규칙적이고 잦지 않았었다..)</p>
<p>많은 커피챗, 혹은 내부 행사등에 시간과 채력이 허락하는 내로 참여하려 노력했었고,
그 이유는 사실 성장이나, 남들의 경험을 공유받아 이를 활용하기 위해서라기보단, 그냥 비슷한 직군에서 일하시는분들의 생각을 듣고 내 생각을 이야기하는것 자체가 재밌어서 그랬던것이 큰 것 같다.</p>
<p>이 기간 내에 특기할만한 것 이라면, 매 기수마다 한번씩 직군별로 모여서 작은 발표와 커뮤니케이션을 진행하는 행사인 &#39;&#39;반상회&#39;&#39; 라는 이름의 내부행사가 있는데,</p>
<p>처음으로 반상해 행사를 준비하는 준비위로서 활동해보았다.</p>
<p>사실 글또 행사를 이미 2기수간 참여하면서 느낀 경험이 좋아, 
이 노하우를 어떻게 내가 활동하는 다른 커뮤니티인 플러터 서울에 전파시킬 수 있을까.. 라는 불순한 동기로 시작한거였는데,</p>
<p>행사를 진행하며 얻은 결론은, 글또의 대표이신 성윤님의 막대한 시간적 노력 없이는 이런 경험을 주는 행사를 기획하기 쉽지 않겠다는 생각이 들었다. 
(관련해서 이야기 할 바는 많지만, 이거 또한 긴 포스트 주제이기도 하고, 지금 글하고는 별로 맞지 않는거 같다 여기까지만)</p>
<p>이후에 아직 글또 내부 인원분들이 경험을 공유하고자 하는 의지가 강한것 같아, &#39;&#39;미니 반상회&quot; 라는 이름으로 후속 행사를 기획후 진행해보았었다.</p>
<p>두 행사 모두 참석자 분들이 원하시는 경험을 가져가셨기를 바래본다.</p>
<h3 id="마무리">마무리</h3>
<p>글또를 마무리하며, 생각보다 정말 긴 기간동안 이렇게 활동을 꾸준히 진행했던게 뭐가 있지? 라는 의문이 들어, </p>
<p>곰곰히 생각해보니, 대학시절, 학부연구생(3년), 밴드부 동아리 활동(4년) 등 을 빼고는 이렇게 긴 기간 꾸준히 외부 활동을 진행했던게 없었던 것 같다.</p>
<p>저 두 활동 모두 나의 정체성을 이루는 큰 조각중 하나인데, 이미 글또에서 활동한 내용들도 그와 비슷한 위치가 되었구나를 자각하게 되었다.</p>
<p>아쉽게도 성윤님의 긴 기간 봉사를 끝으로 (글또는 비상업 커뮤니티이다) 더 이상의 다음 기수는 운영되지 않을 예정이다.</p>
<p>아쉬운 마음이 크지만, 이미 2년정도 전부터 이별이 공지되었어서 그런지, 좀 담담히 받아드릴 수 있는것 같다.</p>
<p>이 기간동안 정말 다양한 분들과 다양한 활동을 진행하였고, 이 경험이 글또 내부에서, 또 글또 외부의 활동과도 상상치 못하게 전이되어 나에게 많은 변화를 주었던 기간이다.</p>
<p>이러한 기회를 얻을 수 있게 해주셨던 성윤님께 많은 감사를 드리며, 3년간의 활동을 이번 포스트를 제출하면 마무리 해 본다. </p>
<p>(이후에도 포스트는 올라갑니다! 벌려놓은 시리즈와, 아직 정리하지 못한 주제를 마무리해야 하기  떄문에..)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[매일메일] JPA의 ddl-auto 옵션은 각각 어떤 동작을 하고 어떤 상황에서 사용해야 할까요?]]></title>
            <link>https://velog.io/@wongue_shin/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BC-JPA%EC%9D%98-ddl-auto-%EC%98%B5%EC%85%98%EC%9D%80-%EA%B0%81%EA%B0%81-%EC%96%B4%EB%96%A4-%EB%8F%99%EC%9E%91%EC%9D%84-%ED%95%98%EA%B3%A0-%EC%96%B4%EB%96%A4-%EC%83%81%ED%99%A9%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94</link>
            <guid>https://velog.io/@wongue_shin/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BC-JPA%EC%9D%98-ddl-auto-%EC%98%B5%EC%85%98%EC%9D%80-%EA%B0%81%EA%B0%81-%EC%96%B4%EB%96%A4-%EB%8F%99%EC%9E%91%EC%9D%84-%ED%95%98%EA%B3%A0-%EC%96%B4%EB%96%A4-%EC%83%81%ED%99%A9%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94</guid>
            <pubDate>Tue, 25 Mar 2025 13:15:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 설정에 대해 매우 주의할것.</p>
</blockquote>
<h2 id="hibernate가-지원해주는-ddl-관련-설정">Hibernate가 지원해주는 DDL 관련 설정</h2>
<p>ddl-auto 는 정확히는 JPA 의 설정이 아니라,<br>JPA 의 구현체인 HIbernate가 지원해주는 DDL(Data Definition Language) 관련 설정이다. 
<a href="https://docs.spring.io/spring-boot/docs/1.1.0.M1/reference/html/howto-database-initialization.html">관련한 스프링 공식 Docs</a></p>
<p>그래서 yaml 파일내에서 설정할 때도<code>jpa.hibernate.ddl-auto:</code> 로 설정해야한다.</p>
<p>해당 기능은 Hibernate 가 등록된 엔티티 기반으로 테이블을 생성하거나, 수정해주는 편의 기능이다.</p>
<p>설정할 수 있는 속성은,</p>
<ul>
<li><p><code>none(default)</code></p>
</li>
<li><p><code>validate</code></p>
</li>
<li><p><code>update</code></p>
</li>
<li><p><code>create</code></p>
</li>
<li><p><code>create-drop</code></p>
</li>
</ul>
<p>이렇게 총 5개의 속성이 있는데, 결론을 먼저 말하자면 <strong>validate, 아니면 none 말고는 쓰지 말자.</strong>
<strong>예상치 못한 오류의 원인</strong>이 될 가능성이 매우 높다.</p>
<p>차례로 각 옵션별 동작을 간단하게 설명하면,</p>
<h3 id="nonedefault">none(default)</h3>
<p>관련해 아무 동작도 실행하지 않겠다는 설정.
hibernate 의 편의 기능을 사용하지 않고, 모든 테이브를 직접 관리 할 때 사용할 것.</p>
<h3 id="valiate">valiate</h3>
<p>DB 의 테이블과 엔티티가 정상적으로 매핑되는지 검사만 수행하고,
실패하면 예외를 발생시키고 어플리케이션을 종료한다. (권장설정)</p>
<p>이때, 엔티티 필드에 해당하지 않는 추가 컬럼이 존재 여부는 검사하지 않는다.</p>
<h3 id="create">create</h3>
<p><code>create</code> 의 경우,  엔티티 클래스의 테이블을 생성할 수 있는 DDL 이 작성되어 
어플리케이션 실행시 DB에 반영된다. </p>
<p>이 때, <code>drop table if exists</code>로 동작하기 때문에 주의할것. 파괴적인 opearation 이 진행된다.</p>
<h3 id="create-drop">create-drop</h3>
<p><code>create-drop</code> 의 경우는 <code>create</code>와 비슷하지만, 어플리케이션이 종료될 때 테이블을 삭제한다.</p>
<h3 id="update">update</h3>
<p> <code>update</code>의 경우, 엔티티 필드의 추가 사항이 있으면 이를 반영해주는데,
이미 존재하는 컬럼의 속성 변경 (nullabe, 크기, 타입)등은 관리해주지 않는다.
새로 추가된 필드가 nonNull 이라면, 해당 변경사항이 반영되지 않는 버전 배포시, 데이터 insert 가 불가능해질수도. </p>
<h3 id="추가-설명">추가 설명</h3>
<p>공식 문서에서 보면 따로 설정을 하지 않았을 경우 
embedded db 의 경우에는 <code>create-drop</code>을 기본적으로 적용한다고 하는데, 
이게 뭔가 싶어서 찾아보니 
in-memory, 혹은 file system 으로 저장하는
외부 서버와 통신이 없이 어플리케이션 내부에서 사용할 수 있는 DB 를 의미한다.
<a href="https://en.wikipedia.org/wiki/Embedded_database#:~:text=An%20embedded%20database%20system%20is,coming%20as%20a%20standalone%20application">위키 Embedded database</a></p>
<h3 id="그럼-왜-이런-설정이-필요한걸까">그럼 왜 이런 설정이 필요한걸까?</h3>
<p>테스트, 개발 환경에서는 해당 옵션이 유용하기 때문에.</p>
<h3 id="여담">여담</h3>
<p>개발바닥의 <a href="https://youtu.be/SWZcrdmmLEU?si=LtDabtAimQK99m1s">이 유튜브</a>를 이전에 본 기억이 있는데, 오늘 정리한 내용과 관련한 이야기였구나?
처음에 볼땐 그냥 웃기다고만 생각했는데, 단순 설정파일 한줄에 따라서 이렇게 치명적인 결과가 나올 수 있을줄이야...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[매일메일] Spring Data JPA에서 새로운 Entity인지 판단하는 방법은 무엇일까요?]]></title>
            <link>https://velog.io/@wongue_shin/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BC-Spring-Data-JPA%EC%97%90%EC%84%9C-%EC%83%88%EB%A1%9C%EC%9A%B4-Entity%EC%9D%B8%EC%A7%80-%ED%8C%90%EB%8B%A8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C%EC%9A%94</link>
            <guid>https://velog.io/@wongue_shin/%EB%A7%A4%EC%9D%BC%EB%A9%94%EC%9D%BC-Spring-Data-JPA%EC%97%90%EC%84%9C-%EC%83%88%EB%A1%9C%EC%9A%B4-Entity%EC%9D%B8%EC%A7%80-%ED%8C%90%EB%8B%A8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C%EC%9A%94</guid>
            <pubDate>Mon, 24 Mar 2025 14:27:41 GMT</pubDate>
            <description><![CDATA[<h2 id="매일메일">매일메일</h2>
<p><a href="https://www.maeil-mail.kr/">매일메일</a> 이라는 서비스가,
매일 오전 09시 기술면접 질문을 보내준다길래 구독을 신청해봤다.</p>
<p>질문의 답이 무엇인지 공부한 기록을 시간 되는대로 기록해봐야지.
지금은 BE/FE 두 직군에 대해서만 질문을 제공하는듯?</p>
<h2 id="오늘의-질문">오늘의 질문</h2>
<blockquote>
<p>Spring Data JPA에서 새로운 Entity인지 판단하는 방법은 무엇일까요?</p>
</blockquote>
<p>일단 하나도 몰라서 차근차근히 접근해봤다.</p>
<h3 id="jparepository-의-구현부터-시작">JpaRepository 의 구현부터 시작</h3>
<p>이를 보기위해선 <code>JpaRepository</code> 의 기본 구현인 <code>SimpleJpaRepository</code> 의 <code>save()</code>를 봐야하는데,</p>
<pre><code class="language-java">public class SimpleJpaRepository&lt;T, ID&gt; implements JpaRepositoryImplementation&lt;T, ID&gt; {
  ...

  @Override //line 620
  @Transactional
  public &lt;S extends T&gt; S save(S entity) {

    Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);

    if (entityInformation.isNew(entity)) {
      entityManager.persist(entity);
      return entity;
    } else {
      return entityManager.merge(entity);
    }
  }</code></pre>
<p><code>entityInformation.isNew(entity)</code> 를 기반으로 판정하는걸 볼 수 있었다.</p>
<p>새로운 엔티티면, <code>entityManager.persist(entity)</code> INSERT 를,
기존의 엔티티면, <code>entitiyManager.merge(entity)</code> UPDATE 를  수행하는걸 볼 수 있었다.</p>
<h3 id="새로운-엔티티-판정-여부">새로운 엔티티 판정 여부</h3>
<p>그렇다면, <code>isNew</code> 는 어떻게 판정하는걸까? 
조건이 되는 <code>EntityInformaion.isNew()</code> 를 찾아보면</p>
<pre><code class="language-java">public abstract class AbstractEntityInformation&lt;T, ID&gt; implements EntityInformation&lt;T, ID&gt; {
  ...
  @Override // line 42
    public boolean isNew(T entity) {

        ID id = getId(entity);
        Class&lt;ID&gt; idType = getIdType();

        if (!idType.isPrimitive()) {
            return id == null;
        }

        if (id instanceof Number n) {
            return n.longValue() == 0L;
        }

        throw new IllegalArgumentException(String.format(&quot;Unsupported primitive id type %s&quot;, idType));
    }</code></pre>
<p>기본형 id type 에 대해서는, 해당 타입을 ID 로 든 엔티티가 신규인지 아닌지 여부를 판정하는 로직을 제공해주는걸 볼 수 있었다.</p>
<ul>
<li><p>기본형 타입을 가지며, null 이라면 신규</p>
<ul>
<li><p>id type 이 숫자이며 값이 0 이라면 신규.</p>
</li>
<li><p>그 외 기본형은 모두 기존의 엔티티이다.</p>
</li>
</ul>
</li>
<li><p>기본형이 아니면 IllegalArgumentException 발생</p>
</li>
</ul>
<h4 id="추가-조사-getidtype-은-어떻게-타입을-반환하지">추가 조사) getIdType() 은 어떻게 타입을 반환하지?</h4>
<p><code>getIdType()</code> 메소드는 어떻게 타입을 반환하는걸까? 궁금해서 추가로 조사한 결과.</p>
<p><code>EntityInformation&lt;T, TD&gt;</code> 인터페이스의 <code>Class&lt;ID&gt; getIdType()</code> 을
<code>JpaMetamodelEntityInformation</code> 에서 구현중인걸 볼 수 있었다.</p>
<pre><code class="language-java">public class JpaMetamodelEntityInformation&lt;T, ID&gt; extends JpaEntityInformationSupport&lt;T, ID&gt; {

  private final IdMetadata&lt;T&gt; idMetadata;
  ...
// 185 line
public Class&lt;ID&gt; getIdType() {
        // 타고 타고 들어가면,
    return type.getJavaType(); &lt;= 요 메소드 호출로 끝납니다.
}</code></pre>
<p><code>MetaModel</code>이라는 객체가 어노테이션 기반으로 필드를 찾는다고 한다.
(리플렉션인가?) 좀더... 찾아볼랬지만,  너무 깊어서 이해가 안되는지로 패스..
어차피 중요한 친구인거 같아 계속 공부하다보면 다시 볼꺼 같기도..</p>
<p><code>@StaticMetamodel()</code> 어노테이션으로 컴파일타임 정적 메타모델 클래스를 생성할 수도 있다고 한다.</p>
<h3 id="version-어노테이션">@Version 어노테이션</h3>
<p>신규 엔티티 판정여부에서 추가로 조건이 걸리는 경우가 있는데, 
해당 엔티티가 @Version 어노테이션을 가진 필드를 들고있는 경우가 있다고 한다.
(찾아보니 요건 낙관적 락을 사용할때의 핵심 어노테이션이라고 한다 👀)</p>
<pre><code class="language-java">@Entity
public class Product {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Version &lt;- 이런 경우
  private Long version;
}</code></pre>
<h4 id="추가조사-관련-로직은-어디에-작성되어있고-어노테이션-달려있는진-어떻게-알-수-있지">추가조사) 관련 로직은 어디에 작성되어있고, 어노테이션 달려있는진 어떻게 알 수 있지?</h4>
<p>@Version 의 유무를 판별하고, 이를 이용해 <code>isNew()</code> 를 override 하는 로직은 어디 구현되어 있을까? 를 찾아보니</p>
<p>이 또한 <code>JpaMetamodelEntityInformation</code> 에서 구현중이라는걸 알 수 있었다. 
상당히 중요한 로직이 모여있는 클래스인것 같은데..</p>
<pre><code class="language-java">public class JpaMetamodelEntityInformation&lt;T, ID&gt; extends JpaEntityInformationSupport&lt;T, ID&gt; {
    ...
  private final Optional&lt;SingularAttribute&lt;? super T, ?&gt;&gt; versionAttribute;
  ...
  // 220 line
  @Override
    public boolean isNew(T entity) {

         if (versionAttribute.isEmpty() &lt;= 이부분에서 version이 지정되어있는지 체크!
                || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
            return super.isNew(entity);
        }

        BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);

        return versionAttribute.map(it -&gt; wrapper.getPropertyValue(it.getName()) == null).orElse(true);
    }</code></pre>
<h3 id="직접-구현">직접 구현</h3>
<p>혹은 엔티티의 신규 판정 여부를 Persistable 인터페이스를 구현해 <code>isnew()</code> 를 직접 설정할 수 있다.</p>
<pre><code class="language-java">public interface Persistable&lt;ID&gt; {

    @Nullable
    ID getId();

    boolean isNew();
}</code></pre>
<h3 id="3줄요약">3줄요약</h3>
<ol>
<li>신규 여부 판정은, 기본형 ID 가 아니면 직접 구현해야한다.</li>
<li>기본형은 판정 로직을 제공해주는데, 숫자면 0, 다른 타입의 경우 null 이면 신규로 판정한다.</li>
<li>@Version 어노테이션이 붙어있으면 이것도 확인한다.</li>
</ol>
<p>음.. 다 정리하고 나니 <a href="https://www.maeil-mail.kr/question/27">답변 링크</a>가 제공된다는걸 뒤늦게 알았다.</p>
<p>그래도 혼자 공부해보고, 답변과 얼마나 차이나는지 비교해보는것도 나름 괜찮을듯..?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[직접 컨퍼런스를 열어보았다.]]></title>
            <link>https://velog.io/@wongue_shin/%EC%A7%81%EC%A0%91-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4%EB%A5%BC-%EC%97%B4%EC%96%B4%EB%B3%B4%EC%95%98%EB%8B%A4</link>
            <guid>https://velog.io/@wongue_shin/%EC%A7%81%EC%A0%91-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4%EB%A5%BC-%EC%97%B4%EC%96%B4%EB%B3%B4%EC%95%98%EB%8B%A4</guid>
            <pubDate>Sun, 16 Mar 2025 13:38:27 GMT</pubDate>
            <description><![CDATA[<p>지난 토요일 15일에 세분의 연사자분들과 함께, 글또 내부에서 약 20명 정도의 인원을 대상으로
소규모 컨퍼런스를 진행했었다.</p>
<p>이번 행사는 이전의 다른 행사와는 달리 혼자 주도해서 진행했어서 나에게는 특별한 의미를 지녔는데,</p>
<p>이 기억을 더 잊어버리기 전에 빨리 후기를 작성해본다. </p>
<h2 id="배경설명">배경설명</h2>
<p>Flutter 개발자로 일한지 이제 만 3년이 다가오는 시점,
특히 작년에는 실력에 비해 과분하게 운이 좋아 외부 컨퍼런스에서 발표를 진행할 기회를 많이 얻게 되었다.</p>
<p>이러한 경험에서 많은 보람을 느꼈기 때문에,
지금 활동하고 있는 커뮤니티인 글또의 구성원분들에게도 같은 기회를 드리고 싶다는 생각이 들었기 때문이다.</p>
<h2 id="행사를-준비할때는-어떤-단계가-있는지">행사를 준비할때는 어떤 단계가 있는지</h2>
<p>지난해에 크고 작은 규모의 컨퍼런스에서 7번 발표를 진행하며 내가 겪은 경험을 정리해보면,</p>
<h3 id="행사-기획-이전">행사 기획 이전</h3>
<p>첫번째로 알게된 점은
45분 세션 하나를 준비하기 위해서는 
길게 보면 3개월에서 6개월, 짧게보면 2주정도의 긴 시간이 필요하다는 점을 알게되었다.</p>
<p>나눌만한 내용이 없으면 연사대를 설 이유가 없기 때문에, 많은 분들과 이야기해볼만한 소재를 찾는데 긴 기간을 겪게 되고,</p>
<p>이 기간동안 현업에서, 혹은 퇴근 이후에 다양한 경험을 하면서 그 경험을 정리해 나름의 생각을 가지는 데에도 추가로 꽤나 많은 시간이 걸린다.</p>
<h3 id="행사-기획단계">행사 기획단계</h3>
<p>이 기간이 지난 후, 마침 좋은 행사에서 연단을 설 기회가 생긴다면</p>
<p>약 2~3주간 발표 대본과 자료를 준비하게 되는데,</p>
<p>이때 약 45분의 발표 하나를 준비하기 위해선 50~60시간 정도의 작업이 필요하다.</p>
<blockquote>
<p>물론, 시간을 계속 투자하면 투자할 수록 발표의 퀄리티가 더 좋아지기 때문에, 실제로는 일차로 완성한 이후에도 추가로 더 많은 시간을 투자하는 일이 잦다.</p>
</blockquote>
<p>특히 발표 대본과 자료를 준비하는 기간동안 진짜 많은 스트레스와 압박을 받게된다는점..을 잘 알게되었다.
(고민과 수정.. 다시 고민.. 다시 수정.. 의 무한 반복)</p>
<p>이렇게 작성된 자료와 대본을 활용해 몇 차례의 리허설을 진행하고, (대부분은 온라인으로 진행된다)
그 뒤 행사의 주제에 맞게 내용을 수정한다.</p>
<h3 id="행사-당일">행사 당일</h3>
<p>당일에는 참석자 분들보다 훨씬 먼저 행사장에 도착해 현장의 여러 문제가 될 수 있는 부분을 확인하게 된다.</p>
<p>행사가 진행되는 건물이 크다면, 건물의 입구부터 행사장까지 잘 찾아올 수 있는 구조인지,
역에서부터 잘 접근 할 수 있는 위치인지등을 파악한 뒤, 
필요한 곳에 엑스배너와 입간판등을 세워 행사 안내를 진행한다.</p>
<p>이후 배부할만한 굿즈가 있다면, 굿즈를 소분해 나눠드릴 수 있게 포장하고
(그렇습니다.. 이거 직접 해야하는 작업입니다...)</p>
<p>원활한 행사가 진행될 수 있게 입구에서 참석자를 체크할 수 있도록 접수 데스크를 설치,</p>
<p>마이크와 화면의 연결상태등을 확인하고, 
연사자분들이 잘 도착하시고 순서에 따라 입장 하실 수 있도록 안내드린다.</p>
<p>입장시간이 되면, 참석자분들을 입장 안내를 드리고, 시간에 맞춰 예정된 세션을 진행한다.</p>
<p>사실 행사 당일은 몇번을 진행해도 잘 기억이 나지 않는데,
수많은 돌발상황😇과 수많은 사람들, 점차 시간표보다 늦어지는 행사진행등.. </p>
<p>당일에는 여기저기 뛰어다니고 서있느라 정신없어서 긴장된다거나 설래는 느낌은 잘 못받았던것 같다.</p>
<p>이 외에도 수많은 잡무가 남아있는데,
대관비, 굿즈 디자인 발주 수령 배부등과 관련한 금액 정산등등... 정말 많은 일들이 뒤에서 필요하다.</p>
<h2 id="그래서-이번-행사를-기획한-이유">그래서 이번 행사를 기획한 이유</h2>
<p>사실 매번 연사를 신청한 이후, 밤을 새워가며 준비를 하며 신청한 걸 수없이 후회하는데,
행사를 마치고 한 이주만 지나면 다시 다음 발표 주제는 뭘 해야할지 생각나는 날 발견할 수 있다 😂</p>
<p>나에게는 개발 관련 이야기를 하는건 항상 즐거운 일이여서,
내가 관심있는 주제의 내용을, 많은 사람들에게 내가 말하고 싶은 방식으로 전달 할 수 있다는 그 상황이
매우 즐거운 경험인 것 같다.</p>
<p>내가 재밌었고 즐거웠던 경험을, 다른 많은 분들이 겪을 수 있도록 도와드리고 싶은데,
처음부터 큰 행사에서 발표를 진행하는것은 어려울 수 있으니,</p>
<p>유독 분위기가 따뜻하고 자기개발에 관심이 많은 분들이 모여있는 글또 내부에서 이를 진행해보면 좋지 않을까 하는 생각이 들게 되었다.</p>
<h2 id="이번-행사의-내용-요약">이번 행사의 내용 요약</h2>
<p><strong>첫번째로</strong> 최민주님이 최신 AI 에이전트에 관련한 내용과 이를 간단히 구현할 수 있는 방법에 대해 설명해주셨다.</p>
<p>내용이 흥미로웠던 발표였는데, 개인적으로 지금은 수기로 진행하고 있는 메모 관리등을 AI 에이전트를 통해서 자동화 할 수 있는 방법이 있지 않을까.. 생각하고 있다.</p>
<p><strong>두번째로는</strong>, 박일상님이 웹 애니메이션에 관련해 조사한 자료를 공유해주셨다.</p>
<p><strong>세번째는</strong> 내가 색채이론 관련한 간단한 배경지식을 소개하는 발표를 진행했다.
(지금 블로그에서 작성중인 포스트 시리즈를 조금 더 정리한 내용)</p>
<p><strong>마지막으로는</strong>, 강민석님이 phaser 프레임워크를 사용해 쉽고 빠르게 2D 웹 게임을 개발 할 수 있는 방법에 대해서 공유해주시고, 간단한 게임을 같이 시연해보는 경험을 가져보았다.</p>
<blockquote>
<p>개인적으로는 이 프로젝트 꼭 
글또 내부에서 대규모 인원이 같이 해 볼 수 있었으면...!  </p>
</blockquote>
<p>이후에는 45분정도 FE 와 AI 관련 주제로 자유롭게 나눠서 네트워킹을 진행했다.</p>
<h2 id="회고">회고</h2>
<h3 id="잘-한점">잘 한점</h3>
<p>일단.. 어떻게든 해냈다는 점.</p>
<p>연사자분들을 어떻게든 모았고, 참여자분들도 나름 잘 모았고, 예약도 어찌저찌 했고..
그 와중에 현업과 발표자료 준비도 어떻게든 끝냈다는 점.</p>
<h3 id="아쉬웠던-점">아쉬웠던 점</h3>
<p>혼자 진행하려니 손이 달리는 부분이 아주 많았다</p>
<p>만약 한번 더 진행한다면, 시작 이전에 의사가 맞는 몇분을 더 모아서 진행하는게 훨씬 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[밝기란 무엇일까? (GUI 개발자가 알아야하는 색) -2]]></title>
            <link>https://velog.io/@wongue_shin/%EB%B0%9D%EA%B8%B0%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-GUI-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%98%EB%8A%94-%EC%83%89-2</link>
            <guid>https://velog.io/@wongue_shin/%EB%B0%9D%EA%B8%B0%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-GUI-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%98%EB%8A%94-%EC%83%89-2</guid>
            <pubDate>Sun, 02 Mar 2025 02:40:06 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에서는 공개된 자료를 이용해, 이전 포스트의 내용을 실제로 계산하며  밝기에 대해서 좀더 구체적인 예시를 들어보겠습니다.</p>
<p>같은 에너지를 발산하고 있는 조명들이 있다고 할때, 에너지가 같다면 밝기 또한 같을까요?
전기에너지 에서 빛 에너지로 변환되는 효율의 차이나, 발열등은 모두 무시하고, 빛의 형태로 방출되는 에너지가 동일한 조명들이 있다고 가정해보겠습니다.</p>
<p>SPD(Spectral power distribution) 을 그리기 쉬운 pc-LED 와, python 에서 라이브러리 형태로 배포되어 관련 자료를 찾을 수 있는, Kinotion_75P, H38HT-100 두 조명을 대상으로 실제 밝기 효율을 계산해보겠습니다.</p>
<h3 id="spd-를-합성해보기">SPD 를 합성해보기</h3>
<p>pc-LED (phosphor-converted LED) 라는 LED 의 한 종류가 있습니다.
백색의 조명원으로 주로 사용되는데요, 이 LED 의 구조는 아레와같이</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/3ac0beb3-3238-429d-8446-4aa069862def/image.webp" alt="remote pc-led 의 구조"></p>
<p>광원으로 450nm 대의 청색광을 내는 LED 위, 그 빛을 받아 여러 파장의 빛을 방출하는 형광체를 덮은 형태의 LED 입니다.</p>
<p>그 구조가 가지는 특징에 따라서, pc-LED 의 SPD 그래프는 LED 가 발산하는 청색광에서 피크를 가진 뒤,
500~700nm 파장까지 넓은 주파수 대역을 가지는데요,
<img src="https://velog.velcdn.com/images/wongue_shin/post/2f0376d9-cdc1-48f2-8880-42f43072ca4d/image.png" alt="pc-LED 의 SPD 데이터 소스"></p>
<p><a href="https://www.researchgate.net/publication/235522168_Spectral_power_distribution_deconvolution_scheme_for_phosphor-converted_white_light-emitting_diode_using_multiple_Gaussian_functions">출처</a>에서 볼 수 있는 그래프의 개형을 보면,
P1 의 상대적으로 뾰족한 값을 가지는 커브와, P2 의 완만한 값을 가지는 커브가 있습니다.</p>
<p>P1 의 값은 원 광원 소스인 blue-LED 에서 나오는 파장이고,
그 뒤 완만한 P2 의 값은 이 빛을 흡수한 이후 형광체에서 다시 나오는 파장이겠군요.</p>
<p>이러한 특성때문에, pc-LED 의 SPD 는 정규분포 함수의 합으로 근사가 가능합니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/88e6cbfe-b9f5-4550-a799-484d99ccb3e9/image.png" alt="pc-LED spd 식"></p>
<p>또한, 같은 <a href="https://www.researchgate.net/publication/235522168_Spectral_power_distribution_deconvolution_scheme_for_phosphor-converted_white_light-emitting_diode_using_multiple_Gaussian_functions">출처</a>에서 완벽하진 않지만, 해당 그레프를
<img src="https://velog.velcdn.com/images/wongue_shin/post/eba74a6b-b1cd-4928-867d-58ef8455290c/image.png" alt="pc-LED 근사식">
으로 근사 할 수 있습니다. 이를 통해 합성함수를 만들어보면,
(위 출처의 내용은, 우리가 사용하는 근사식을 개선해 실제의 데이터와 어떻게 가깝게 표현할 수 있는지에 대한 내용입니다.)</p>
<pre><code class="language-python">wavelengths = np.arange(200, 1300, 5)

# 가우시안을 2개 더한 pc-LED의 SPD 개형
# 출처: https://www.researchgate.net/publication/235522168_Spectral_power_distribution_deconvolution_scheme_for_phosphor-converted_white_light-emitting_diode_using_multiple_Gaussian_functions
centerA = 444
centerB = 558
widthA = 16.5
widthB = 52
spdA = 1.0 * np.exp(-(wavelengths - centerA)**2 / widthA**2)
spdB = 0.54 * np.exp(-(wavelengths - centerB)**2 / widthB**2)
spd = spdA + spdB 

plt.plot(wavelengths, spd, label=&#39;SPD (interpolated)&#39;, color=&#39;blue&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/5ab8f1fa-1a02-41cf-801f-ca9df49ce9f0/image.png" alt="합성된 함수의 개형"></p>
<p>다음과 같이 어느정도 사용할 수 있는 그래프를 볼 수 있습니다.</p>
<p>전 포스트에서, SPD 에 시감함수(Photopic curve) 를 곱한 합성함수로, 유효한 밝기를 계산 할 수 있다고 말씀드렸었죠.</p>
<p>다행이 python 에 이를 제공하는 <code>colour</code> 라이브러리가 있습니다. 이를 사용하면,</p>
<pre><code class="language-python"># 이 함수의 의미는 다음 포스트에서 설명됩니다.
cmf_2deg = colour.MSDS_CMFS[&#39;cie_2_1931&#39;]

photopic_curve = cmf_2deg.values[:, 1]
original_wv = cmf_2deg.wavelengths

# 정의된 영역보다 좀더 넓은 range 에서 보기 위해 보간.
y_bar_extended = np.zeros_like(wavelengths, dtype=float)

# 360~830 구간에만 interp로 값을 넣어준다.
mask_valid = (wavelengths &gt;= original_wv[0]) &amp; (wavelengths &lt;= original_wv[-1])
wavelengths_valid = wavelengths[mask_valid]

y_bar_extended[mask_valid] = np.interp(
    wavelengths_valid,
    original_wv,
    y_bar
)

plt.plot(wavelengths, y_bar_extended, label=&#39;CIE 1931 2° - y_bar&#39;, color=&#39;red&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/fae574e5-55be-4b89-aa4b-dd6b047c5a99/image.png" alt="photopic_curve"></p>
<p>다음과 같은 시감함수를 얻을 수 있습니다.
이 둘을 곱한 합성함수로, SPD 로 나타내는 조명의 주파수별 에너지를 실제 체감하는 밝기로 옮길 수 있는데요,</p>
<pre><code class="language-python">plt.plot(wavelengths, spd, label=&#39;SPD (interpolated)&#39;, color=&#39;blue&#39;)
plt.plot(wavelengths, y_bar_extended, label=&#39;CIE 1931 2° - y_bar&#39;, color=&#39;red&#39;)
plt.plot(wavelengths, spd * y_bar_extended, label=&#39;SPD * CIE 1931 2° - y_bar&#39;, color=&#39;green&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/0dc1806d-89ce-4be6-ae07-b62875a9818d/image.png" alt="합성된 함수들의 개형 비교"></p>
<p>파랑색 곡선 안의 면적과, 초록색 곡선 안의 면적의 비율은 광원의 조사하는 에너지 대비 인간의 시각이 느끼는 밝기 효율을 의미하게 됩니다.</p>
<p>우리에게는 다행이도, python 에서는 numpy 를 이용해 함수의 면적을 쉽게 구할 수 있습니다.</p>
<pre><code class="language-python"># 전체 SPD (광원 에너지)의 적분값 (분모)
integral_spd = np.trapezoid(spd, wavelengths)

# 인간 시각 가중치가 적용된 SPD (SPD * y_bar_extended)의 적분값 (분자)
integral_weighted = np.trapezoid(spd * y_bar_extended, wavelengths)

# 두 적분값의 비율: 전체 에너지 중 인지되는 에너지의 비율
ratio = integral_weighted / integral_spd

# 플롯 상에 적분값 및 비율 결과 텍스트 추가
textstr = (
    f&#39;Ratio (Weighted / Total): {ratio:.3f}&#39;
)</code></pre>
<p><a href="https://gist.github.com/WongueShin/2794a4e9bd909f2ea9f43d9b0d2bcd6f">실행 가능한 예제</a>
<img src="https://velog.velcdn.com/images/wongue_shin/post/b15f7ea1-ef1e-4e36-88ea-00028102b1cc/image.png" alt="적분 결과값을 포함한 플롯 이미지"></p>
<h3 id="공개된-조명의-데이터를-통해-실제-예시를-계산해보기">공개된 조명의 데이터를 통해 실제 예시를 계산해보기</h3>
<p><code>Colour</code> 라이브러리에서 다양한 조명에 대해 SPD 데이터를 제공하니,
이를 통해 각 조명별 예시를 추가로 계산해 볼 수 있습니다.</p>
<pre><code class="language-python"># Colour 라이브러리에서 Kinoton 75P SPD 불러오기
light_data_source = colour.colorimetry.datasets.light_sources.sds.DATA_LIGHT_SOURCES_COMMON[&quot;Kinoton 75P&quot;]
# Colour 라이브러리에서 Natural SPD 불러오기
light_data_source = colour.colorimetry.datasets.light_sources.sds.DATA_LIGHT_SOURCES_RIT[&quot;Natural&quot;]
# Colour 라이브러리에서 H38HT-100 (Mercury) SPD 불러오기
light_data_source = colour.colorimetry.datasets.light_sources.sds.DATA_LIGHT_SOURCES_NIST_PHILIPS[&quot;H38HT-100 (Mercury)&quot;]

# SPD의 유효 파장과 값을 추출
spd_wavelengths = np.array(list(light_data_source.keys()))
spd_values = np.array(list(light_data_source.values()))

# SPD 값을 0~1로 정규화: 최대값으로 나눔
spd_values_normalized = spd_values / spd_values.max()

# SPD의 정의된 파장 범위 내에 해당하는 부분만 보간
mask_spd_valid = (wavelengths &gt;= spd_wavelengths.min()) &amp; (wavelengths &lt;= spd_wavelengths.max())
wavelengths_spd_valid = wavelengths[mask_spd_valid]

light_data_valid[mask_spd_valid] = np.interp(
    wavelengths_spd_valid,
    spd_wavelengths,
    spd_values_normalized
)</code></pre>
<p>제가 예시로 삼은 세가지의 조명은, </p>
<h4 id="kinotion_75p">Kinotion_75p</h4>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/4796a6b5-832b-45f5-8c81-ac98d2d85d7a/image.jpg" alt="Kinotion_75p">
영화관에서 사용되었던 영사기의 광원과,
<a href="https://gist.github.com/WongueShin/fef372cbfa328e51806f6acdcc896fca#file-kinotion_75p-spd-example-py">실행 가능한 예제</a></p>
<h4 id="자연광">자연광</h4>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/e504edcd-17b2-4b34-b9aa-a852576cd7be/image.png" alt="natural light">
한낮의 야외에서 받을 수 있는 자연광,</p>
<p><a href="https://gist.github.com/WongueShin/4f9436b8e4d7e42a1db3fa52590da77f">실행 가능한 예제</a></p>
<h4 id="h38ht-100">H38HT-100</h4>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/243971a5-0102-40e7-bb2f-d827ae009030/image.webp" alt="H38HT-100">
과거에는 널리 쓰였던, 수은등을 사용했습니다.</p>
<p><a href="https://gist.github.com/WongueShin/c4d1a630e8afc2c6a5b6cc8536319f45">실행 가능한 예제</a></p>
<p>간단한 파이썬 코드로 이 세가지의 다양한 조명들의 특징이 반영된 실제 SPD 데이터의 개형을 보며,
이 데이터들의 차이가 어떤 결과를 나타내느지를 계산해볼 수 있는데요,</p>
<p>아래의 그래프는 차례대로 Kinotion_75p, 자연광, H38HT-100 의 케이스입니다.
<img src="https://velog.velcdn.com/images/wongue_shin/post/5b1c4c47-80d6-4a09-abf1-1c4fdc56bd3d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/9c7a21e0-30c0-4395-993e-0663b4c3b7a2/image.png" alt=""></p>
<p>계산된 광원당 각 밝기 효율을 보면 주파수 대역이 전 영역에 고르게 분포하는것 보단, 
인간의 시각이 민감한 500~550nm 에 집중되어있는것이 중요함을 알 수 있는데요,</p>
<p>그렇기 때문에 같은 조건에서는 해당 영역에서 가장 뾰족한 스파이크를 가진 수은등의 경우가 가장 밝기 효율이 높은것을 알 수 있습니다.
(전기 - 빛 변환등의 조건을 무시한 경우이기 때문에, 조명의 실제 에너지 효율하고는 다릅니다.)</p>
<p>Pesudo 그래프가 아닌, 실제 SPD 데이터인 kinotion_75p, 자연광, h38ht_100 의 경우에는,
<code>638 lumens/W</code> 로 정의된 상수를 이용해, 조명의 밝기를 실제로 계산 할 수도 있겠습니다.</p>
<p>계산 식은 
Spd * photopic  cie curve * 683 lumens/W (555nm) 으로 정의되는데요,</p>
<p>단순히 638 lumens/W 를 곱하면 되는 이유는, photopic curve 가 0~1 로 정규화 되어 있으면서,
주파수별 효율이 이미 계산되었기 때문입니다.</p>
<pre><code class="language-python"># 638 lumens/W (555nm)
print(integral_weighted * 638)

# h38ht-100: 8622.96246513 lumens
# natural: 21616.7073389 lumens
# Kinoton_79P: 49773.3649838 lumens</code></pre>
<h3 id="결론">결론</h3>
<p>이번 포스트에서는 실제 예시를 통해 우리가 직관적으로 알 수 있는 밝기 라는 개념이 실제로는 어떤 복잡한 구조로 이루어지는지를 알아보았습니다.</p>
<p>이러한 특성은, 색과 밝기는 정신물리학적 현상(Psychophysical phenomenon)이기 때문입니다
이는 물리학적으로 정의되는 자극과, 이를 인간이 인지하는 지각 형태가 결합되어 나타내는 현상을 의미하는데요,</p>
<p>이러한 현상을 이용하거나, 예측하기 위해선
정신 물리학적 현상을  구성하는  두 요소를 모두 충분히 고려해야만 의도하는 결과를 가져갈 수 있기 때문입니다.</p>
<p>다음 포스트에서는 이제 정말로... 색이란 어떻게 정의할 수 있는가, 이를 &#39;잘&#39; 표현하기 위해선 어떤 기술이 필요한가에 대한 이야기를 진행해보겠습니다.</p>
<hr>
<p>출처</p>
<ul>
<li><p><a href="https://www.researchgate.net/publication/235522168_Spectral_power_distribution_deconvolution_scheme_for_phosphor-converted_white_light-emitting_diode_using_multiple_Gaussian_functions">Spectral power distribution deconvolution scheme for phosphor-converted white light-emitting diode using multiple Gaussian functions (Bongmin Song, Bongtae Han)</a></p>
</li>
<li><p><a href="https://luminusdevices.zendesk.com/hc/en-us/articles/4403685063437-What-do-CCT-CIE-and-SPD-mean-in-LED-lighting">What do CCT, CIE, and SPD mean in LED lighting? (Yi-Ying Lai)</a></p>
</li>
<li><p><a href="https://pubmed.ncbi.nlm.nih.gov/3831311/">The effects of spectral power distribution and illuminance levels on key parameters in the male golden hamster and rat with preliminary observations on the effects of pinealectomy (R A Hoffman, L B Johnson, R Corth)</a></p>
</li>
<li><p><a href="https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf">Radiometry and Photometry (Wei-chin Wang)</a></p>
</li>
<li><p><a href="https://www.researchgate.net/publication/284157299_Photometry_and_Photosynthesis_From_Photometry_to_PPFD_Revised">Photometry and Photosynthesis: From Photometry to PPFD (Revised) (an Ashdown)</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SWE 로서, 혹은 엔지니어로서 생각하는 수학과 공학의 관계 ]]></title>
            <link>https://velog.io/@wongue_shin/SWE-%EB%A1%9C%EC%84%9C-%ED%98%B9%EC%9D%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A1%9C%EC%84%9C-%EC%83%9D%EA%B0%81%ED%95%98%EB%8A%94-%EC%88%98%ED%95%99%EA%B3%BC-%EA%B3%B5%ED%95%99%EC%9D%98-%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@wongue_shin/SWE-%EB%A1%9C%EC%84%9C-%ED%98%B9%EC%9D%80-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A1%9C%EC%84%9C-%EC%83%9D%EA%B0%81%ED%95%98%EB%8A%94-%EC%88%98%ED%95%99%EA%B3%BC-%EA%B3%B5%ED%95%99%EC%9D%98-%EA%B4%80%EA%B3%84</guid>
            <pubDate>Fri, 14 Feb 2025 02:58:57 GMT</pubDate>
            <description><![CDATA[<h3 id="글을-쓰게-된-이유">글을 쓰게 된 이유</h3>
<p>아는 분에게 추상수학과 구체수학의 차이가 무엇인지,
그리고 추상 수학이 어떻게 개발에 도움이 되는지에 관해 질문을 받았다.</p>
<h3 id="구체-수학이란">구체 수학이란</h3>
<p><em><strong>&#39;구체수학&#39;</strong></em> 이라는 용어가 익숙치 않아 찾아보니
Concrete Mathematics 라는 스탠포드 교제 이름에서 나온 응용수학, 그 중 CS 에서 주로 사용되는 수학에 대한 교재를 의미하는걸 알 수 있었다.</p>
<h3 id="내가-생각하는-수학의-종류">내가 생각하는 수학의 종류</h3>
<p>사실 수학에는 경계가 없다 보니 
어떤 분야의 수학이 <strong>실용 수학</strong>인지, <strong>이론 수학</strong> (혹은 추상수학) 인지는 이러한 도구를 사용해 풀이하고자 하는 목표가 무엇인지에 따라 달라진다고 생각한다.</p>
<p>뉴턴이 <em>도함수</em> 라는 개념을 처음 발명한 시절에, 
미분은 천상위 별의 움직임을 기술하기 위한 순수한 이론수학 이였다.
이를 통해서 실용의 레벨에서 얻을 수 있는 이점 (즉, 미분을 이용해 잘 풀 수 있는 문제)를 아직 발견하지 못햇기 때문이다.</p>
<p>근대 수학에서 수학자들 스스로도 이게 과연 현실에서 사용할 일이 있을까? 
라 생각되었던 <strong>비-유클리드 기하학</strong>, <strong>리만기하학</strong>은 </p>
<p>아인슈타인의 <strong>일반 상대성 이론</strong>을 만나
공학 레벨(GPS 시스템에서 정확한 위치를 측량하기 위해선 유클리드 기하학에서의 삼각 층량으로는 부족하다)
에서 실제로 사용되기 시작하며 질문자의 용어인 ‘추상 수학’ 에서 ‘구체 수학’ 혹은 ‘응용 수학’ 의 레벨로 내려왔다고 볼 수 있다.</p>
<p><strong><em>그럼, 시간이 지나며 리만기하학의 성질이 변화한것인가?</em></strong></p>
<p>그렇다기보단, 이를 통해 풀이할만한 공학 문제를 발견하고, 이를 공학적인 용도로 사용하려는 의도를 가진 공학도가 많아졌다고 보는게 맞다.</p>
<h3 id="그럼-스탠포드-에서-한-과목으로-가리키는-concrete-mathematics-에는-의미가-없는것인가">그럼 스탠포드 에서 한 과목으로 가리키는 Concrete Mathematics 에는 의미가 없는것인가?</h3>
<p><strong>아니다.</strong>
수학이라는 세계는 너무 넓어, 현대 수학의 모든 분야를 이해한 사람은 없다고 말 할 수 있다.
콜라츠 추측부터, 매듭이론, 군론, P-NP 문제의 이산수학, 나비에-스톡스 방정식의 미적분학, 대수학, 기하학, 혹은 들어보지도 못한 난제들…</p>
<p>이러한 광활한 광야에서 적절한 배경지식이 없다면, 입문자는 이를 어떻게 배우기 시작해야하는지 알 수 없다.</p>
<p>또한, 수학의 많은 자료는 실용의 목적으로 기술되기보다는
이론 그 자체를 위해 기술된 자료가 많기 떄문에, 이를 어떻게 응용해야하는지도 알 수 없다.</p>
<p>다시 시작으로 돌아가서, 질문을 내가 생각하는 보다 적절한 두개의 질문으로 나눠보자면,</p>
<ul>
<li>개발자가 수학을 배워야할까?</li>
<li>어떤 수학의 분야는 어떤 문제를 푸는데 도움이 될 수 있을까? </li>
</ul>
<p>와 같이 질문을 바꿔보면,</p>
<h3 id="개발자가-수학을-배워야할까"><strong>개발자가 수학을 배워야할까?</strong></h3>
<p> 예, 본인의 지향점이 엔지니어라면.</p>
<p><strong>수학은 자연을 기술하는 언어</strong>이고,
엔지니어는 자연의 특성을 사용해 현실의 문제를 해결하는 사람이다.</p>
<p>단순한 코드 작성인의 역할을 벗어나, 실존하는 문제를 풀이하는 역할을 수행하고 싶다면,</p>
<p>공학적 사고가 필수적이고, 이 사고의 언어는 수학으로서 기술되기 떄문이다.</p>
<h3 id="그렇다면-어떤-분야의-수학을-배워야지-도움이-될-수-있을까"><strong>그렇다면 어떤 분야의 수학을 배워야지 도움이 될 수 있을까?</strong></h3>
<p>답은, <strong>‘가능한 많은 분야’</strong> </p>
<p>수학의 신기한 성질중 하나는, 
겉으로 보면 전혀 연관이 없어보이는 두개의 분야도, 
처음에는 상상하지 못한 부분에서 서로 깊은 연관성을 가지고 있다는 성질이 있다. 
( 자연수, 소수, 실수와, 집합이 매우 깊은 관계를 가지고 있듯이, 하나의 분야를 깊이 알고싶다면, 다른 분야의 수학이론이 필요한 경우는 드물지 않다.)</p>
<p>하나의 분야에서 쉽게 풀리지 않는 어려운 부분을 풀이하기 위해, 다른 분야의 수학의 아이디어를 불러와 이를 해결하는 경우와, </p>
<p>비록 아직 해결하지 못했더라도, 아직 이해하지 못한 깊은 영역에서 연관성이 있음을 암시하는 증거를 많이 볼 수 있다.</p>
<p>대표적 예시로 물리학에서의 슈레딩거 방정식으로 풀이되는, 
수소 원자에 구속된 전자의 에너지 준위와, 리만 제타 함수의 해석적 연속기법과 깊은 의미에서 연관되어 있단 사실을 알 수 있다. (아직 그 의미가 무엇인지는 밝혀지지 않았지만)</p>
<p>하지만, 수학의 모든 분야는 평생을 바쳐도 다 이해할 수 없을정도로 넓기 때문에
여태껏 내 경험으로 도움이되고, 주로 사용되었던 분야를 이야기하면,</p>
<p>사칙 연산 그 뒤를 바로 따르는 <strong>대수학</strong>(大 가 아닌 代 대신할 대 이다),
특히 대수학에서 선형적 관계에 대해 기술하는 <strong>선형대수</strong>를 이해해야 이제 수학을 이야기하는 시작점에 다다를 수 있다.</p>
<p>이를 통해 미분과 적분을 이해하면, 
연속된 데이터의 변화에 대해 다룰 수 있게 되고, </p>
<p>이를 통해 변곡점, 즉 변화하는 양이 급격히 반전되는 순간을 찾을 수 있다. 
(이는 대부분의 공학, 혹은 사업에서 중요한 목표중 하나가 된다. 정보의 선점을 통해 이득을 얻을 수 있기 때문이다).</p>
<p>미적분과 선형대수를 응용하면 <strong>다차원미분</strong>, <strong>그레이디언트</strong>를 다룰 수 있으며, </p>
<p>이는 현대 ML의 황금기가 back gradient, 역전파 기법을 통해 학습이 가능해 진점을 통해 알 수 있듯이, </p>
<p>ML 전범위적인 분야에서 사용되고, 이와 깊은 연관성이 있는 이미지처리와 이를 통해 기술할 수 있는 물리 현상을 이해 할 수 있게 된다.</p>
<p>(물리 시뮬레이션 중 비 압축성 유체의 시뮬레이션을 구현하기 위해 대표적으로 사용된다.</p>
<p>공간을 각 격자로 나누고, 비 압축성 유체는 그 정의로부터 그 격자에 인입되는 유체량과, 나가는 유체의 량이 동일해야한다는걸 알 수 있으므로, 이를 계산하기 위해선 자연스럽게 gradient 가 필요하기 떄문)</p>
<p>이제 이를 넘어가면 <strong>위상 수학</strong>이 나오게 되는데, 
다차원의 대규모 데이터를 다루기 위해서는 위상 수학의 <strong>메니폴드 이론</strong>이 적용되기 시작한다. (하지만 이 분야는 거의 모르기 때문에 생략. 물리학부 수준에서 배우지 않기 떄문…)</p>
<p>또한 수학의 다른 분야중 하나인, <strong>통계와 확률</strong>이다.</p>
<p>이 두 분야를 알게되면 현실에서의 복잡한 문제, 
단순한 선형 관계로서는 설명할 수 없는 복잡계, 수많은 구성요소들이 서로 연관되어 영향을 주고받는 상황에서의 문제를 조금씩 해결 해 나갈 수 있게 된다.</p>
<p>(여기서 도출된 계념 중 개발자가 익숙한 두 가지 개념이 “공간적, 시간적 복잡도” 이다.)</p>
<p>이를 통해 전체가 부분의 합보다 큰 창발적인 현상을 기술할 수 있고, 
이 창발적인 현상의 어느 시점부터 발생하는지에 대한 임계질량에 대해 기술할 수 있다.</p>
<p>사실 거의 모든 수학의 분야는 현대 사회에서 어딘가에서 사용되고 있기 때문에, 
수학을 배우고 이를 활용할 문제를 보는것 보단,</p>
<p>풀고싶은 문제에 연관된 수학 분야를 찾고, 이를 공부하는게 빠르다고 생각한다.</p>
<p>하지만 이러기 위해선 아까 말했듯이, 광야에서 길을 찾아갈 수 있는 기본적인 배경지식이 필요하다.</p>
<h3 id="내가-생각하는-가이드라인">내가 생각하는 가이드라인</h3>
<p>만약 내가 지금 개발자로 일/취업을 준비하고 있으며, 배경이 수학에 친숙하지 않았던 사람이라면, 나는 아래와 같이 학습 계획을 짤 거 같다.</p>
<ol>
<li><p><strong>대수학중 자연수, 실수, Quaternion과 같은 대수구조</strong>
 다른 분야의 수학에서 뼈대가 되는 지식들. 이를 모르면 애초에 설명이 되지 않는 분야가 많다.</p>
</li>
<li><p><strong>선형대수</strong>
 대수학중에 다른 비교적 간단한 관계에 대해 기술하는 분야.
 선형성 (Linearity) 이 무엇인지, 왜 이것이 중요한지, 이를 유지하는 변환이 무엇인지에 대해 알아야한다.</p>
</li>
<li><p><strong>미적분학</strong>
 변화, 그 중 연속적인 변화에 대해서 수학적으로 기술하는 방법을 알기 위해선 필수가 되는 기술. serial data 를 다루기 위해선 반드시 알아야 한다.
기초적인 미분과 적분, 체인룰 등을 학습한 후, 편미분과 다차원 미분까지 공부하면 매우 좋다.</p>
</li>
<li><p><strong>통계학</strong>
 빈도주의자 관점과 베이지안 관점에 대해 학습해야한다.
 통계와 확률을 이용해 이전에 배운 수학 분야가 어떻게 응용될 수 있는지를 공부 한 뒤, 연관된 주제인 엔트로피에 대해서도 잡고 가면 좋다.</p>
</li>
<li><p><strong>이후에는 본인이 관심 있는 분야를 자유롭게.</strong>
(e.g, 군론, 해석학, 위상수학, 정보이론, 괴델 수, 튜링머신, ....)</p>
</li>
</ol>
<p>사실 개발자로 전직하기 이전 물리학부 시절에도 수학을 그리 잘하지 못했었고,
지금도 RnD 분야에 있는 분들에 비해선 거의 아무것도 알지 못한다고 생각하지만,</p>
<p>앞으로 종종 비슷한 이야기를 할 일이 있을 때, 이 글을 전달하기 위해 남겨본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[색이란 무엇일까? (GUI 개발자가 알아야하는 색) -1]]></title>
            <link>https://velog.io/@wongue_shin/%EC%83%89%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-GUI-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%98%EB%8A%94-%EC%83%89-1</link>
            <guid>https://velog.io/@wongue_shin/%EC%83%89%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-GUI-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%98%EB%8A%94-%EC%83%89-1</guid>
            <pubDate>Sun, 19 Jan 2025 14:25:01 GMT</pubDate>
            <description><![CDATA[<p>저희는 화면에 표시되는 GUI (Graphic User interface) 를 다루며,
하루에도 수십번씩 GUI 의 색상과 스타일을 변경하기도 하며, 이를 사용자로서 이용하기도 합니다.</p>
<p>Apple 의 GUI 혁명 이후, IT 서비스에 GUI 가 빠진 서비스를 예시로 찾기 힘들정도로, 
GUI는 IT 의 불가결한 일부분이 되었는데요,</p>
<p>이 중 GUI 의 특징을 부여하는 스타일의 가장 중요한 구성요소는 색상이라 말할 수 있겠습니다.</p>
<p>하지만 여러분, 혹시 색이 무엇인지 자신있게 설명할 수 있으신가요?</p>
<h2 id="포스트의-제목대로-정말-색이란-무엇일까요">포스트의 제목대로, 정말 색이란 무엇일까요?</h2>
<p>RGB 의 조합? 빛 의 파장?
<img src="https://velog.velcdn.com/images/wongue_shin/post/93c5bc4f-dd0b-4ac1-b396-34d052a3f917/image.png" alt=""></p>
<p><strong>싱그러운 나뭇잎</strong> 이라는 글씨와, 위의 이미지를 보면, 우리에게는 &quot;<strong>초록색</strong>&quot; 이라는 개념이 너무나 직관적으로 자연스럽게 다가옵니다.</p>
<p>하지만, <strong>*&quot;그래서 초록색은 뭘까요? 어떻게 정의하고 설명할 수 있을까요?&quot;
&quot;만약 태어나서 한번도 시각적 정보를 느껴보지 못한 사람에게 초록색을 이해시키려면, 어떤 설명이 필요할까요?&quot;*</strong></p>
<p>위의 질문에 답을 해본 분들은 알아차리셨겠지만,
&quot;색이 무엇인가?&quot; 라는 질문은 생각보다 어려운 질문이라는걸 알 수 있겠습니다.</p>
<p>그 이유는 색, Color 는 대표적인 정신물리학(PsychoPhysical)적 현상이기 때문입니다.</p>
<blockquote>
<p>정신물리학이란, 물리학적 현상 이를 우리(뇌)가 자극하는 패턴의 관계를 연구하는 학문의 일종입니다.</p>
</blockquote>
<p>너무 일상적이고, 지금도 경험하고 있는 내용을 설명하는데 어려움을 느끼다니,
이상한 느낌이지만, 그나마 확실하게 이야기 할 수 있는 내용은, <strong>빛이 없으면 색또한 없다는건 확실해 보입니다.</strong></p>
<p>그렇다면, 색이 무엇인지에 대해서 이해하기 위해선 빛의 특성에 관한 이야기가 빠질 수 없겠군요.
이로부터 출발해 과연 색을 어떻게 설명 할 수 있을지에 대해 이야기해 보면 되겠군요.</p>
<h2 id="radiometry-복사량">Radiometry (복사량)</h2>
<blockquote>
<p><strong>Radiometry</strong> is the science of measuring light in any portion of the electromagnetic spectrum.</p>
<p>Rediometry 란, 빛의 전자기 스펙트럼의 전 영역에서, 빛을 측정하는 과학입니다. </p>
<p>Introduction to Radiometry and Photometry - Oxford Instruments</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/f45cbf8a-d2ce-41db-9571-93562b26e2af/image.png" alt="">
<strong>E</strong>lectro<strong>M</strong>agnetic<strong>R</strong>adiation, <strong>EMR</strong> 이라고 말하는 그것, 바로 전자기파를 다루는 학문입니다.</p>
</blockquote>
<p>색을 말하기에 앞서,
우리는 가장 먼저 빛의 단위 면적 당 전자기파의 형태로 입사되는 에너지를 측정해야 합니다.</p>
<p>이를 <strong>Radiance</strong> 라고 말합니다.</p>
<p>이를 측정하기 위해서는 복잡한 수학적, 물리학적인 이야기들이 뒤따르지만..
짧게 다룰 수 있는 이야기도 아니고, 제가 이 이야기를 오개념 없이 풀 수 있는 능력도 없으니 넘어가도록 하겠습니다.</p>
<blockquote>
<p>이러한 조명의 파장별 분포도를 얻을 수 있다고 가정하고 이야기 하겠습니다.
참고: 다음과 같은 그래프를 <strong>S</strong>pectral <strong>P</strong>ower <strong>D</strong>istributions <strong>SPD</strong> 라고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/541bc7a4-643b-4432-9874-53a8e6450df1/image.png" alt=""></p>
</blockquote>
<h2 id="photometry">Photometry</h2>
<blockquote>
<p><strong>Photometry</strong> is the science of <strong>measuring visible light</strong> in units that are weighted according to the sensitivity of the human eye.</p>
<p>Photometry 란, 인간 눈의 감도에 따른 가중치가 부여된 단위로, 가시광선을 측정하는 과학입니다.</p>
<p>Introduction to Radiometry and Photometry - Oxford Instruments</p>
</blockquote>
<p>알수 없는 마법같은 물리학과 수학으로, 단위 면적당 전차기파가 가지고 있는 에너지, 즉 <strong>Radiance</strong>를 구했으니,</p>
<p>우리는 이를 기반으로 <strong>Illuminance</strong>를 계산 해야합니다.</p>
<h3 id="radiance와-illuminance는-어떻게-다른걸까요">Radiance와 Illuminance는 어떻게 다른걸까요?</h3>
<p>Radiance는 전자기파의 전 영역을 통해 이동하고 있는 에너지의 흐름을 의미합니다.
에너지가 어떤 파장의 전가지파를 통해 이동하고 있는지는 구분하지 않아요.</p>
<p>하지만, 사람은 가시광선, 즉 파장이 380nm 부터 770nm 까지의 가시광선만 인지 할 수 있죠.</p>
<p>단적인 예로는, X-선 빛이 저를 쬐고 있다고 가정해봅시다.
<strong>&quot;강한 빛&quot;</strong> 이라고 는 할 수 있을거에요. X-선은 사람의 신체를 투과해 그 내부 구조를 파악하고,
DNA의 사슬 구조를 깨트릴 수 있을정도의 에너지를 가지고 있으니까요.</p>
<p>하지만 저는 그 빛을 전혀 볼 수 없겠죠? 이를 <strong>&quot;밝은 빛&quot;</strong> 이라고 표현 할 수 있을까요?
이와같이 전자기파가 가지고 있는 에너지와, 우리가 눈으로 느끼는 밝음은 단순하게 비례하지 않는데요,</p>
<p>따라서 이 두 관심사를 구분해 측정 할 필요가 있어요.</p>
<p>전자기파가 가지고 있는 &quot;에너지&quot; 자체에 집중한 단위는 바로 <strong>Radiance</strong>,
이를 통해서 우리 눈이 느끼는 밝음에 집중한 단위는 <strong>Illuminance</strong> 가 되는겁니다.</p>
<blockquote>
<p> Radiometry 의 관심사는 전자기판의 전체 영역인데 비해
<img src="https://velog.velcdn.com/images/wongue_shin/post/3074ea05-ea7e-4aea-8c9b-36f985e7a3ae/image.png" alt="">
Photometry 는 인간이 인식 할 수 있는 한정된 영역, 가시광선에 대해 집중합니다.</p>
</blockquote>
<h4 id="여기서-끝이-아닙니다">여기서 끝이 아닙니다.</h4>
<p>photometry 의 정의에는 &quot;인간 눈에 대한 감도 가중치&quot; 라는 단어가 들어갑니다.
이 때문에 아까 X-선 어쩌고 하는 이야기를 한게 아니냐구요?</p>
<p>아쉽게도, 그보다 조금 더 부연 설명이 필요합니다. 
혹시, *&quot;눈은 녹색에 민감하다.&quot;* 라는 이야기를 들어보신 적 있으실까요?</p>
<p> 사람의 눈은, 가시광선 내 빛이라도 그 주파수에 따라 다른 민감도를 가지고 있는데요,</p>
<p>실제로 같은 Radiance (1 와트 / 제곱미터) 를 가지는 <strong>녹색 조명</strong>과 <strong>빨강 조명</strong>이 저희를 비추고 있다면,
사람은 녹색 조명을 훨씬 더 밝게 인지합니다.</p>
<p>이러한 반응을 <strong>Photopic reponse(광시 반응)</strong> 이라 말합니다.
실제로는 눈에 들어오는 전자기파의 파장 뿐만이 아니라, 빛의 깜빡이는지의 여부, 홍채와 망막의 적응, 관찰차의 심리, 생리적 상태 등등 매우 복잡한 비선형적 관계를 가지고 있지만, </p>
<p>다행이 일부 조건을 고정한 대부분의 상황에서 사람의 시각적 반응은 정량화 될 수 있습니다.
우리에게는 더 다행이도, 이러한 정량화는 이미 1924 년에 국제 조명 위원회가 조사해 발표했습니다.</p>
<blockquote>
<p>국제 조명 위원회, International Commision on Illumination 의 약자는 <strong>CIE</strong> 입니다.
왜냐구요? 프랑스 기관이여서, 불어로 쓰면 Commission Internationale d&#39;Eclairage 이기 때문입니다.</p>
<p>앞으로 반복해 나올 단체이기 때문에 기억해두면 좋아요.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/56ced3a9-b462-4853-a914-447054452aee/image.png" alt=""></p>
<p>위의 그래프는 조사되는 파장에 따른 인간 눈의 민감도를 나타냅니다.</p>
<p>곡선이 두개인 이유는, 사람의 눈은 두가지 모드를 가지고 있는데요
주변이 충분히 밝을때는 Cone cell(원추세포) 이라는 시각세포가 주 영향을,
어두운 상황에서는 Rod cell(간상세포) 라는 시각세포가 빛에 대한 반응을 담당하기 때문입니다.</p>
<p>파랑색 곡선이 Rod Cell 의 반응을, 초록색 곡선이 Cone Cell 의 반응을 의미합니다.</p>
<p>저희는 앞으로 특별한 언급이 있지 않다면, 충분한 광도를 가진 상황의 Cone Cell 의 반응에 대해서 이야기를 하는거라 생각하시면 됩니다.</p>
<p>왜냐면, Rod Cell 은 색상을 감지하는 능력이 없기 때문입니다.</p>
<p>이번 포스트의 마지막으로, SPD 를 인간이 감지하는 밝기로 어떻게 변환 할 수 있는지 알아봅시다. </p>
<p>위의 SPD 중, 가장 첫번째 그래프인 ww pc-LED 의 형태를 봅시다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/af445156-24b0-43ee-99d6-cb4756c31e28/image.png" alt=""></p>
<p>이를 위의 Photopic response curce 와 합성하면, 다음과 같은 형태가 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/d5117134-cd92-4a74-ad84-fc56fd060158/image.png" alt="">
합성된 그래프의 면적 전체를 적분하면, 실제로 눈으로 느낄 수 있는 &#39;밝은 정도&#39; 를 계산 할 수 있겠습니다.</p>
<hr>
<h4 id="reference">reference</h4>
<ul>
<li><a href="https://andor.oxinst.com/learning/view/article/radiometry-photometry">https://andor.oxinst.com/learning/view/article/radiometry-photometry</a></li>
<li><a href="https://en.wikipedia.org/wiki/Radiometry">https://en.wikipedia.org/wiki/Radiometry</a></li>
<li><a href="https://en.wikipedia.org/wiki/Electromagnetic_spectrum">https://en.wikipedia.org/wiki/Electromagnetic_spectrum</a></li>
<li><a href="https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf">https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf</a></li>
<li><a href="https://en.wikipedia.org/wiki/Planck%27s_law">https://en.wikipedia.org/wiki/Planck%27s_law</a></li>
<li><a href="https://www.researchgate.net/figure/llustration-of-photopic-and-scotopic-curves_fig4_258169197">https://www.researchgate.net/figure/llustration-of-photopic-and-scotopic-curves_fig4_258169197</a></li>
<li><a href="https://luminusdevices.zendesk.com/hc/en-us/articles/4414846186253-What-is-the-CIE-Color-Space-What-s-the-difference-between-CIE-1931-and-CIE-1976">https://luminusdevices.zendesk.com/hc/en-us/articles/4414846186253-What-is-the-CIE-Color-Space-What-s-the-difference-between-CIE-1931-and-CIE-1976</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Anemic Domain model 을 탈출하자! 내 프로젝트 도메인 모델에 수혈하기]]></title>
            <link>https://velog.io/@wongue_shin/Anemic-Domain-model-%EC%9D%84-%ED%83%88%EC%B6%9C%ED%95%98%EC%9E%90-%EB%82%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%AA%A8%EB%8D%B8%EC%97%90-%EC%88%98%ED%98%88%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wongue_shin/Anemic-Domain-model-%EC%9D%84-%ED%83%88%EC%B6%9C%ED%95%98%EC%9E%90-%EB%82%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%AA%A8%EB%8D%B8%EC%97%90-%EC%88%98%ED%98%88%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 24 Nov 2024 13:38:43 GMT</pubDate>
            <description><![CDATA[<ul>
<li>예상독자: 초중급 개발자.</li>
<li>목표: 타성적으로 나눈 계층구조에서, rich domain model 이 무엇인지 이해할 수 있고,
이를 어떻게 유지하고 발전시킬지 이해할 수 있다.</li>
</ul>
<p>혹시 Anemic Domain model 이라는 말을 들어본 적 있으신가요?
이를 들어보지 못했다면, 지금 여러분이 작성하고 계시는 프로젝트의 소스코드는 빈혈에 걸려 시름시름 앓아가고 있을 수도 있답니다!</p>
<p>저와 함께 오늘 아키텍처의 도메인 모델중, 어떤 도메인 모델이 Rich domain model, 즉 풍부한 도메인 모델이고 어떤 모델이 Anemic domain model 인지 정리해보시죠.</p>
<h2 id="빈약한-도메인-모델-anemic-domain-model">빈약한 도메인 모델 (Anemic domain model)</h2>
<p>이 단어는, 마틴 파울러가 2003 년 동명의 블로그 포스트를 작성하며 처음 소프트웨어 공학 업계에 이야기되기 시작하였습니다. <a href="https://martinfowler.com/bliki/AnemicDomainModel.html">포스트 링크</a></p>
<p>이는 적합한 도메인 모델에 대한 충분한 경험과 이해가 부족한 상황에서,
피상적으로 계층구조의 아키택처를 적용할 때, 흔히 발생 할 수 있는 대표적인 안티패턴이라고 설명됩니다.</p>
<p>도메인 모델이 결국 여러 클래스의 선언문정도의 역할만 수행하게 되고, 다른 모든 비즈니스 로직은 도메인 모델 외부로 전파되어 수행되는거죠.</p>
<p>예를 들자면, 커머스 서비스에서는 필연적으로 주문 상태를 표현하기 위한 도메인 클래스가 있을겁니다.</p>
<pre><code class="language-dart">class Payment {
  final PayemtnState state;
  final Decimal amount;
  final Decimal discountAmount;
  final List&lt;Coupons&gt; appliedCoupons;
}

class Order {
  final String uid;
  final OrderState state;
  final List&lt;Product&gt; products;
  final List&lt;PaymentInfo&gt; paymentInfos;
  ...
}</code></pre>
<p>이 때, 주문 정보를 바탕으로 상품목록을 기반해 
적절한 쿠폰을 하나 추천해야한다는 요구사항이 들어온다고 가정하면, 다음과 같은 코드를 작성 할 겁니다. </p>
<pre><code class="language-dart">/// 쿠폰 목록과 주문의 상품목록을 모두 순회하며, 가장 할인률이 큰 쿠폰을 찾아냅니다.
final Coupon? properCoupon = User.coupons.fold(
        (final (Coupon, Deicimal)? prev, final Coupon coupon) {
      final Decimal couldBeBestDiscount =  Order.products.fold(
          (final Decimal pre, final Product curr) {
          if (coupon.canUse(curr)) {
            return max(pre, coupon.apply(curr));
          }
          return pre;
        }
      , Deicmal.zero);

      if ((prev?.$2?? Deicmal.zero) &lt; couldBeBestDiscount ) {
        return (coupon, couldBeBestDiscount);
      }
    }, null).$1;</code></pre>
<p>문제는 이 코드가 서비스 계층의 의 viewModel 혹은 model 에 작성되기 쉽다는 겁니다.</p>
<p>코드에는 생략되었지만
쿠폰 목록을 조회해 불러온 뒤, 이를 주문 정보와 조합해 가장 적절한 쿠폰으로 찾아내고, 이를 UI로 표시하는 코드가 추가로 작성되어야 할 것이니까요.</p>
<p>관성적으로 코드를 작성할 땐, 이러한 성질의 코드는 서비스 계층에 몰리기 쉽습니다. </p>
<p>위의 주문과 쿠폰 목록에 대한 상호작용과 같은 도메인 로직 (혹은 비즈니스 로직, Computation logic, ..etc 어떻게 부르던)이 모두 도메인 계층에서 서비스 계층으로 추출되어 버린다면,</p>
<p>도메인 계층에 남은 코드는 단순한 클래스 선언문 정도만 남게 될 입니다.</p>
<p>이러한 상태의 도메인 모델을 바로 빈약한 도메인 모델 (Anemic Domain Model, ADM) 이라 부릅니다.</p>
<h2 id="adm의-단점">ADM의 단점</h2>
<p>프로젝트의 구조가 빈약한 도메인 모델을 바탕으로 하고 있다면, 발생하게되는 문제점에 대해 설명하겠습니다.</p>
<h3 id="동일한-동작의-코드가-복제되어-관리되어야-합니다">동일한 동작의 코드가 복제되어 관리되어야 합니다.</h3>
<p>위의 적절한 쿠폰을 찾는 용례가 확장되어, 여러 화면에서 보여주어야하거나,
아니면 버튼만 누르면 결제가 바로 완료되고, 이후에 어떤 쿠폰을 적용해 할인 혜택을 받았는지 설명해주는 UX가 신설된다면 어떨까요?</p>
<p>이 모든 케이스가 병행되어 모두 동작해야하는데, 버전에 따라 쿠폰의 추천 로직이 남은 일자와 할인량을 모두 고려하는 등으로 변경된다면, 여러분은 이러한 변경이 있을 때 마다 프로젝트에서  내가 모든 유즈케이스를 다 찾아서 수정한게 맞는지 기도하고 있게 될 겁니다.</p>
<h3 id="뷰의-맥락과-강하게-결부되었다면-테스트-용이성이-보장되기-어렵습니다">뷰의 맥락과 강하게 결부되었다면, 테스트 용이성이 보장되기 어렵습니다.</h3>
<p>도메인 로직으로 풀이되어야 할 코드가 view 단에 작성되어서, BuildContext 와 엮이게된다면,
이 로직을 테스트하기 위해선 unit test로 테스트를 진행 할 수 없습니다.
적어도 Headless 형태로 Flutter app 을 띄워 실제로 앱을 구동시켜 테스트하는 통합 테스트의 형태로 기능을 증명해야합니다.</p>
<p>혹은, E2E 테스트를 작성해야지만 동작의 검증을 할 수 있는 상황도 발생할 수 있습니다.</p>
<p>테스트 코드는 Uint -&gt; 통합 -&gt; E2E 로 진행할 수록 테스트코드의 작성과 유지보수에 보다 많은 비용을 투자해야하는 경향이 있습니다.</p>
<p>소스코드의 구조 설계를 통해서 훨씬 유지비용을 절감 할 수 있는데, 비효율적으로 많은 노력과 시간을 들어 테스트코드를 유지보수 하는 그 상황 자체도 그리 좋지 못하고,</p>
<p>이 비용을 감당할 수 없어서 결국 관련 기능의 테스트 코드가 작성되지 않거나 삭제된다면, 이는 앱의 안정성을 보장 할 수 없어지므로 가장 최악의 결과가 발생 할 수 있습니다. </p>
<h3 id="oop-의-장점을-잘-활용하지-못하는-형식의-코드입니다">OOP 의 장점을 잘 활용하지 못하는 형식의 코드입니다.</h3>
<p>Class 가 C 의 구조체와 다른점중 하나는, method 를 통해 데이터 뿐만이 아닌, 동작을 정의할 수 있다는 점이겠죠. (C 의 구조체도 function pointer 를 사용해 일부분 가능하긴 합니다.)</p>
<p>빈약한 도메인 모델에서는 OOP 의 이러한 특장점을 잘 살리지 못하고, OOP이전의 절차적 기반의 코드 스타일과 비슷한 특징을 가지게 된다고 볼 수 있습니다.</p>
<h2 id="결론">결론</h2>
<p> 서비스 계층, 혹은 데이터계층에서 도메인 모델을 이용해 계산, 변환, 판별을 내리는 로직이 반복적으로 보인다면, 이를 도메인계층으로 이동시키고, 테스트를 만들어 서비스의 사양으로 병합합시다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동료가 컨벤션을 자꾸 까먹는다면]]></title>
            <link>https://velog.io/@wongue_shin/%EB%8F%99%EB%A3%8C%EA%B0%80-%EC%BB%A8%EB%B2%A4%EC%85%98%EC%9D%84-%EC%9E%90%EA%BE%B8-%EA%B9%8C%EB%A8%B9%EB%8A%94%EB%8B%A4%EB%A9%B4</link>
            <guid>https://velog.io/@wongue_shin/%EB%8F%99%EB%A3%8C%EA%B0%80-%EC%BB%A8%EB%B2%A4%EC%85%98%EC%9D%84-%EC%9E%90%EA%BE%B8-%EA%B9%8C%EB%A8%B9%EB%8A%94%EB%8B%A4%EB%A9%B4</guid>
            <pubDate>Sun, 10 Nov 2024 07:20:35 GMT</pubDate>
            <description><![CDATA[<h3 id="여러분들은-팀원이-반복적으로-소스코드에-사소한-실수를-내는걸-경험해-본적이-있으실까요">여러분들은 팀원이 반복적으로 소스코드에 사소한 실수를 내는걸 경험해 본적이 있으실까요?</h3>
<p>혹은 글을 읽는 여러분들이 그러한 실수를 반복하신 경험은 없으실까요?</p>
<p>예를 들어보면, 
카멜 케이스로 클래스 이름을 작성하기로 했는데, 스네이크 케이스로 이름을 작성한다던지,
클래스 내부 메소드 이름을 알파벳 순서대로 정렬하기로 했지만, 이를 지키지 않는다던가 하는 경우라던지요.</p>
<p>저는 팀원 전부가 다 같이 특정 부분에서 반복적으로 지키기로 한 컨밴션을 놓치는 경험을 했었습니다.
바로, flutter 패키지의 의존성을 관리하는 pubsepc.yaml 의 dependency 패키지 선언을 알파벳 순서대로 정렬해놓기로 한 것이였는데,  약속이 잘 지켜지지 않았었습니다. </p>
<blockquote>
<p>글을 읽는 여러분들에게 익숙한 js 로 설명하자면,</p>
<p>package.json 에 dependency 를 추가할 때, 패키지 순서를 알파벳 순서로 정렬하기로 약속했었습니다.</p>
</blockquote>
<p>이러한 컨밴션을 약속한 이유는, 프로젝트가 의존하는 패키지의 개수가 세자리수를 넘어가며 한 눈에 어떤 패키지를 의존하고 있는지 알 수 없었기 때문입니다.
떄문에, 이미 의존하고자 하는 패키지를 추가하려고 하기도 했었고, 더 이상 필요 없는 패키지를 관성적으로 의존하던 적도 있었죠.</p>
<p>따라서 파일의 길이를 줄일 순 없지만, 적어도 어떤 패키지가 어디쯤 있을지 알 수 있도록 의존하는 패키지 들을 알파벳 순으로 관리하자고 팀원 모두가 동의하였습니다.</p>
<p>하지만, 대부분의 좋은 목표(운동, 퇴근하고 공부하기, ..)를 다짐한 뒤의 우리의 결과와 비슷하게, 
좋은 의도와는 다르게 이 컨벤션은 잘 지켜지지 못했었습니다.
왜냐면, <code>flutter pub get xx</code> 와 같이 커맨드를 통해 의존하는 패키지를 추가하는 케이스라던가,
(이 땐 pubspec.yaml 의 최하단에 추가되게 됩니다.)</p>
<p>급하게 의존하는 패키지를 추가할 경우. 하나하나 알파벳 순서를 생각해서 써넣기는 상당히 귀찮은 일이였거든요.</p>
<p>또한 패키지 순서를 정렬하지 않는다고, 당장 프로젝트 코드가 멈추지는 않기 때문이였습니다.
작업을 수행하며 패키지가 필요해진 시점에 먼저 추가하고 병합 직전 수정해야지! 라는 생각을 하지만,
그 뒤 많은 작업을 수행하며, 병합 시점에는 이 변경이 있었다는걸 잊어버리게 됬던거죠.</p>
<p>이런 사소하고 반복적인 잘못은 어떻게 방지할 수 있을까요?
PR 리뷰때 이를 체크하는게 아마 가장 처음으로 생각할 수 있는 방법일겁니다.</p>
<p>하지만, 제 경험으로는 이러한 반복적이고 치명적이지 않은 문제에 대한 재발방지의 대책으로 PR 리뷰를 적용하는것은 해결책이 되지 못했습니다.</p>
<p>사람의 주의력, 집중력은 의지와 근성, 노력 여하에 따라서 무한히 쓸 수 있는 자원이라고 생각되기도 하지만
다른 모든 자원들과 동일하게 하루, 혹은 그 주에 쓸수 있는 양이 정해진 한정적인 자원이기 때문입니다.
특히 재미 없고, 반복적인 업무를 타의적으로 수행할 때 주의력은 매우 빨리 고갈되어 버리죠.</p>
<p>사소하고, 반복적인 교정을 PR 리뷰의 책임으로 미루어버린다면 몇차례 반복한 뒤에는 타성적이 되기 쉽고, 
학창시절 방 청소 하라는 어머니의 잔소리를 듣는 경험이 그리 유쾌하지는 않았던 것 처럼, 
오고가는 피드백이 건설적인 영향력을 주기보다는 불쾌하고 무력감을 주는 경우가 잦아질 수 있었습니다.</p>
<h3 id="이러한-문제를-어떻게-해결해야하는걸까요">이러한 문제를 어떻게 해결해야하는걸까요?</h3>
<p>반복적이지만, 치명적이지 않으니, 그냥 무시하고 넘어가도 되는 문제들 일까요?</p>
<p>아마 패키지 순서정도야 그렇게 넘어갈 수 있을것 같습니다.
중복해서 같은 패키지를 의존하면 IDE 가 알려주기도 하고, 패키지를 의존하고 있는지 한눈에 알기 어려울 뿐이지,
찾기 기능등을 통해 확인해볼수도 있는거니까요. </p>
<p>만약 그렇다면 우리는 소스 코드의 결함을 어디까지 양보할 수 있을까요?
사소한 결함들 하나하나가 모여 반년, 혹은 수년이 지난다면, 그 과정에서 작성된 소스코드는 유지보수성을 심각하게 저해하는 요인이 될 수 있지 않을까요? </p>
<p>저는 어떤 문제가 당장 프로젝트 운영에 치명적이지 않다고 해서 관리하지 않아도 되는것은 아니라고 생각합니다.</p>
<p>방 청소를 하루 거른다고 큰 문제를 일으키진 않는다고, 방 청소를 영영 안하면 어떻게 될까요?
언젠가는 쌓인 먼지와 쓰래기들이 큰 문제로 다가올 수 있을것이고, 이때서 뒤늦게 문제를 해결하려면 매우 큰 비용이 들지 않을까요?</p>
<p>저는 해결하기 어려운 문제를 마주치면, 먼저 문제가 발생하는 구조를 분석해보려 합니다.
어떤 맥락에서 왜 이런 문제가 발생하는지를 이해한다면 보다 적절한 해결책을 찾아 낼 수 있다고 생각하기 때문입니다.
우리의 문제를 딱 한마디로 요약하자면 프로젝트의 소스코드 관리비용 절감으로 정리 할 수 있겠네요.</p>
<p>비용을 절감하는 대표적인 방법은, 반복적인 업무를 자동화하거나, 필요없는 과정을 단축시키는 방법을 생각해 볼 수 있을겁니다.</p>
<p>그럼 먼저 프로젝트의 구조에 대해 한번 살펴봅시다. 
문제의 대상을 잘 파악할 수 있다면 자동화, 혹은 과정 개편 등 어떤 전략을 통해 문제를 해결할 수 있는지 아이디어를 생각 해볼 수 있을겁니다.</p>
<h3 id="프로젝트-소스코드의-공통점">프로젝트 소스코드의 공통점.</h3>
<p>이 글을 읽는 여러분들이 관리하시는 프로젝트는 대부분 현대 프로그래밍 아키택처를 준수 할 것이고 Modular programming 을 적극적으로 활용할 것이라 기대할 수 있을 겁니다.</p>
<p>git, 혹은 이와 비슷한 버전관리 시스템을 사용할 것이고,
<a href="https://melos.invertase.dev/">melos</a>, <a href="https://turbo.build/">turborepo</a> 와 같은 mono repo 관리도구를 적용 했을 수도 있겟죠.</p>
<p>또한, 작성된 소스코드는 유닉스 커맨드 명령어를 통해, 빌드되고, 실행될것입니다.
e.g) <code>npm install, npm start, melos bootstrap, flutter run, ...</code> </p>
<p>package.json, pubspec.yaml 과 같은 파일 내부에서 커맨드의 동작을 수정할 수 도 있을겁니다.</p>
<pre><code class="language-json">{ // package.json
    ...
  &quot;scripts&quot; : {
      &quot;dev&quot;: &quot;vite&quot; ==&gt; &quot;echo starting react project &amp;&amp; vite&quot;
  }
}</code></pre>
<p>output</p>
<pre><code>/opt/homebrew/bin/npm run dev

&gt; untitled@0.0.0 dev
&gt; echo start react product &amp;&amp; vite

start react product &lt;- 새로 생긴 부분!

  VITE v5.4.10  ready in 114 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
</code></pre><p>이렇게 각 커맨드에 해당하는 동작을 유닉스 다중 명령어등을 통해 변경하거나, 확장 할 수 있습니다.</p>
<blockquote>
<p> TMI)</p>
<ul>
<li>여러 명령어들을 성공 실패와 상관 없이 모두 실행하려면, <code>;</code>  키워드를 사용할 수 있습니다.
<code>$ echo &quot;a&quot;; echo &quot;b&quot;; echo &quot;c&quot;</code> </li>
<li>여러 명령어들을 순차적으로 실행하며, 이전 명령어가 성공할 때만 이어 실행하고 싶다면,
<code>&amp;&amp;</code> 키워드를 사용 할 수 있습니다.
<code>$ tsc &amp;&amp; vite</code></li>
<li>여러 명령어들을 순차적으로 실행하되, 이전 명령어가 성공하면 뒤의 명령을 실행하지 않고 싶다면
<code>||</code> 키워드를 사용 할 수 있습니다.
<code>tsc || echo 컴파일에 실패했습니다.</code></li>
</ul>
<p><code>|</code>, <code>&amp;</code>  와 같은 키워드도 있지만.. 이는 궁금하시면 직접 찾아보시는걸로. </p>
</blockquote>
<p>보다 복잡한 쉘 명령을 내리고 싶다면, .sh 파일을 생성해, 쉘 스트립트를 작성할 수 있습니다.</p>
<pre><code class="language-sh"># my_script.sh

# lint 실행
echo &quot;ES lint 를 실행합니다...&quot;
npx eslint src/

# 타입 체크
echo &quot;TS 타입 체크를 실행합니다...&quot;

# 환경 변수 로드
export $(cat .env | xargs)</code></pre>
<p>``$ ./my_script.sh &amp;&amp; vite`</p>
<p>하지만, 쉘 스크립트는 문법이 C 와 비슷하고, 여러분들이 주로 다루던 언어와는 약간 거리가 있을 수 있습니다.
이럴 때 사용할 수 있는 방법이 있는데요,</p>
<p>대부분의 언어는 그 언어를 활용해 쉘 스크립트를 작성할 수 있는 기능을 지원합니다.</p>
<p>javascript 를 사용하신다면 <a href="https://www.npmjs.com/package/shelljs,">shell js</a> 라는 도구가 있구요, dart 를 사용하신다면 <a href="https://pub.dev/documentation/shell/latest/">dart shell</a> 을 활용하실 수 있습니다.</p>
<blockquote>
<p>이 뿐만 아니라, 대부분의 언어(dart, js, kotlin, java, switf, ...)는  쉘 스크립트를 지원합니다.</p>
</blockquote>
<p>또한 git 은 <a href="https://git-scm.com/book/ko/v2/Git%eb%a7%9e%ec%b6%a4-Git-Hooks">git hooks</a> 이라는 도구를 제공하는데요,
이 중 우리는 주로 사용하게 될 client hooks 에 집중해 보겠습니다.</p>
<p>이를 활용하기 위해선, git 디렉토리 하위에 <code>hooks</code> 라는 디렉토리에 스크립트를 작성해두면 되는데요,
기본적인 위치는 <code>./git/hooks</code> 에 위치합니다.</p>
<p>이 디렉토리에 들어가보면 제동되는 다양한 예제들을 볼 수 있습니다.</p>
<pre><code class="language-sh">$ .git/hooks ls
# stdout 다양한 예제들을 볼 수 있다.
applypatch-msg.sample     pre-commit.sample         prepare-commit-msg.sample
commit-msg.sample         pre-merge-commit.sample   push-to-checkout.sample
fsmonitor-watchman.sample pre-push.sample           sendemail-validate.sample
post-update.sample        pre-rebase.sample         update.sample
pre-applypatch.sample     pre-receive.sample

$ cat pre-commit.sample
#std out
# 커밋 직전에 이런 스크립트를 실행할 수 있다는 예제.
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by &quot;git commit&quot; with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to &quot;pre-commit&quot;.

if git rev-parse --verify HEAD &gt;/dev/null 2&gt;&amp;1
then
        against=HEAD
else
        # Initial commit: diff against an empty tree object
        against=$(git hash-object -t tree /dev/null)
fi

# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)

# Redirect output to stderr.
exec 1&gt;&amp;2

# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ &quot;$allownonascii&quot; != &quot;true&quot; ] &amp;&amp;
        # Note that the use of brackets around a tr range is ok here, (it&#39;s
        # even required, for portability to Solaris 10&#39;s /usr/bin/tr), since
        # the square bracket bytes happen to fall in the designated range.
        test $(git diff-index --cached --name-only --diff-filter=A -z $against |
          LC_ALL=C tr -d &#39;[ -~]\0&#39; | wc -c) != 0
then
        cat &lt;&lt;\EOF
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
EOF
        exit 1
fi

# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
</code></pre>
<p>이를 활용해 스크립트를 설정해두면 커밋 직전, 커밋 직후, push 직전에 지정한 스크립트를 실행 시킬 수 있습니다.</p>
<p>그렇다면 git hooks, 각각의 언어로 작성된 shell sciprt, 패키지 매니저의 동작 재정의 등을 통해 문제를 해결 할 수 있지 않을까요? </p>
<h3 id="문제-해결방법">문제 해결방법.</h3>
<p>저는 위의 내용을 조사한 이후 하나의 아이디어를 생각할 수 있었습니다.</p>
<blockquote>
<ol>
<li>git hooks 을 활용해 커밋 직전에 해당 변경사항 목록을 가져온 뒤</li>
<li>변경된 파일중 package.json, 혹은 pubspec.yaml이 있다면 </li>
<li>dependency 목록을 파싱해 와, alphanumeric asending 으로 정렬을 시켜준 뒤</li>
<li>정렬한 목록을 다시 파일에다 써 주는 스크립트를 작성하면 되지 않을까요?</li>
</ol>
</blockquote>
<p>이를 shell script 를 통해 작성하려면 조금 어려웠겟지만, dart shell 을 통해 작성하니 매우 쉽게 스크립트를 작성 할 수 있었습니다.</p>
<pre><code class="language-dart">/// pre_commit_sciprt.dart
void main () {
  // multy module 구조여서, 프로젝트 내 여러 yaml 파일이 있습니다.
  final List&lt;File&gt; changedPubspecs = 
        // $ git diff --cached --name-only 를 dart code 로 실행하는 라인입니다.
    Process.runSync(&#39;git&#39;, [&quot;diff&quot;, &quot;--cached&quot;, &quot;--name-only&quot;]).
        // 실행한 결과(stdout 을 받아와 가공하는 코드) 
    let((res) =&gt; res.stdout
        .toSring() // 문자열로 변환 한 뒤 (dynamic =&gt; String),
        .trim() // 공백을 제거하고,
        .split(&#39;\n&#39;) // 개행문자를 기준으로 잘라
        .where(
          (e) =&gt; e.contains(&quot;pubspec.yaml&quot;)) // 그 중 pubspec 변경점이 있는지 확인
        .map((e) =&gt; File(e)) // 있다면 이를 파일로 변환
                .toList() // Iterable&lt;File&gt; -&gt; List&lt;File&gt; 타입 변환
       );

  if (changedPubspecs.isEmpty) { // 아무 pubspec 도 변경되지 않았다면,
    exitCode = 0; // 정상 종료
    return; 
  }

     for (final File yamlFile in changedPubspecs) {
    // 변경된 파일 내용을 문자열로 가져와 yaml parser 에게 넘겨줍니다.
    final YamlDocuments docs = loadYamlDocuments(yamlFile.readAsStringSync());

    void sortDependencies(final String key) {
                if (docs.contents.value[key] != null) {
          final Map deps = Map.from(docs.contents.value[key] as Map);
          // sort() 의 기본 정책이 alphanumeric ascending
          final sortedKeys = deps.keys.toList()..sort();

          // 정렬된 배열을 다시 yaml 인스턴스에 저장하는 코드
          for (final k in sortedKeys) {
            editor.remove([key, k]);
            editor.update([key, k], deps[k]);
          }
        }
      }

       sortDependencies(&#39;dependencies&#39;);
       sortDependencies(&#39;dev_dependencies&#39;);

        // 파일에 정렬된 내용을 쓴 뒤
            yaml.writeAsStringSync(editor.toString());
            // git add 로 스테이징 시켜줍니다.
        Process.runSync(&#39;git&#39;, [&#39;add&#39;, Yaml.path,])
    }
      // 정상 종료 설정.
      exitCode = 0;
  }
}</code></pre>
<p>이제 작성한 코드를 <code>.git/hooks</code>에서 실행하도록 설정만 하면 되겠군요.</p>
<p>하지만 여기서 문제가 하나 있습니다.
바로, git hooks 는 repository clone 을 할 때, 복사되지 않는다는 특징을 가지고 있습니다.</p>
<p>(아마 악의적인 스크립트가 로컬에서 자동으로 실행되는 부분을 걱정한 듯 합니다.)</p>
<p>그렇다면, 우리가 작성한 pre-commit hook 을 각 로컬 환경에 자동으로 등록해줄 수 있는 스크립트를 작성하고,
이 스크립트를 패키지 매니저, 혹은 모듈 관리 툴 스크립트에 붙여, 자동으로 재설정 될 수 있도록 변경하면 되겠군요.</p>
<p>이왕 만든김에, 동료분이 작성한 훅이 너무 싫다고 할때, 이를 비활성화 할 수 있는 스크립트까지 같이 만들어보죠.</p>
<pre><code class="language-dart">// setup_git_hooks_sciprt.dart
// pre-commit 훅을 설정하는 스크립트 
Future&lt;void&gt; main() async {
    final File preCommitHookFile = await _initFile();

  // window os 일때 권한 설정을 추가로 해줄 필요가 있음.
  if (!Platform.istWindows) {
    Process.runSync(&quot;chmod&quot;, [&quot;a+x&quot;, preCommitHookFile.path]);
  }

  exitcode = 0;
}

Future&lt;File&gt; _initFile() async {
  final File gitPreCommitHooks = File(&#39;스트립트 위치&#39;);

  // pre-commit hook 이 없다면,
     if (!gitPreCommitHooks.exists()) {
    // 만들어서 반환한다.
    await gitPreCommitHooks.create(recursive: true);
    return gitPreCommitHooks;
  } 
  // pre-commit hook 이 있다면
  // 삭제한 뒤 (이전 버전 스크립트 일 가능성이 있으니까)
  await gitPreCommitHooks.delete(); 
  // 새로 생성해
  await gitPreCommitHooks.create(recursive: true);
  // 반환합니다.
  return gitPreCommitHooks;
}</code></pre>
<pre><code class="language-dart">// reset_git_hooks.dart
// pre-commit hook 을 비활성화 하는 스크립트

void main() {
  final File gitPreCommitHooks = File(&quot;./git/hooks/pre-commit&quot;);

  // pre-commit hook 파일이 있다면 지워버립니다.
  if (gitPreCommitHooks.existsSync()){
    gitPreCommitHooks.deleteSync();
  }
  // 정상종료 설정
  exitCode = 0;
}</code></pre>
<p>이제 만든 두 스크립트를 패키지 매니저의 커맨드와 연동해봅시다!
저는 유지보수 하는 프로젝트에서 melos 라는 멀티 모듈 관리 Cli tool 을 사용하고 있으므로,
melos.yaml 에 들어가서, 커맨드를 수정해줍니다.</p>
<pre><code class="language-yaml"># melos.yaml
...
init-project:
    run: melos clean &amp;&amp; melos bootstrap &amp;&amp; ... &amp;&amp; dart run setup_git_hooks.dart

setup-git-hooks:
    run: dart run setup_git_hooks.dart

reset-git-hooks:
    run: dart run reset_git_hooks.dart
</code></pre>
<p>위와 같은 변경점을 리모트 저장소에 병합 한 이후에 팀원들에게는 의존성 관련 문제로 pull 받은 뒤 init-project 를 한번 실행해달라고 공지를 하면, 작성한 스크립트가 각각의 로컬환경에  등록될 수 있겠습니다!</p>
<p>이제 적어도 package 이름 정렬 문제로는 영영 골치를 안썩어도 되겠군요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter 로 살펴보는 IoC, DI 패턴]]></title>
            <link>https://velog.io/@wongue_shin/Flutter%EC%97%90%EC%84%9C%EC%9D%98-IoC-DI-%EC%97%90-%EB%8C%80%ED%95%9C-%EC%84%A4%EB%AA%85</link>
            <guid>https://velog.io/@wongue_shin/Flutter%EC%97%90%EC%84%9C%EC%9D%98-IoC-DI-%EC%97%90-%EB%8C%80%ED%95%9C-%EC%84%A4%EB%AA%85</guid>
            <pubDate>Sun, 13 Oct 2024 14:27:28 GMT</pubDate>
            <description><![CDATA[<h3 id="머릿말">머릿말</h3>
<h4 id="servicelocator-ioccontinaer-di-관련해-들어보신-적-있으신가요">ServiceLocator, IoCContinaer, DI 관련해 들어보신 적 있으신가요?</h4>
<p>위의 세 단어를 얼핏 들어보셨거나, 혹은 잘 모르시는 초보 Flutter 개발자들을 위한 포스트입니다.</p>
<p>신입 Flutter 개발자로 취업에 성공한지 시간이 조금 지난 시점,
더 이상 처음 출근할 때의 설램은 점점 기억나지 않고, 지난 기간동안 열심히 작성한 코드들이 쌓여 나를 괴롭히고 있지 않으신가요?
분명히 하나하나 최선을 다해 만들었던 기능들이였고, 잘 동작하는 기능들이였는데 왜 지금은 흉측하게 바뀌어 저를 괴롭히고 있는걸까요?</p>
<p>이번 글에서는 이러한 문제를 해결하기 위한 방법중, IoC 와 DI 에 대해서 알아보려 합니다.</p>
<p>혹시 Flutter에서 자주 사용하는 GetIt, Injectable, InheritedWidget, Provider는 각각 어떤 역할을 하며, 왜 사용되는지 궁금하지 않으셨나요?</p>
<p>이러한 모듈들이 등장한 이유와 이를 통해 달성하고자 하는 목적에 대해 짧게 이야기해보겠습니다.</p>
<p>위의 설명한 패키지, 클래스들은 모두 단순히 코드를 작성하는 것이 아니라, 더 효율적이고 유지보수하기 쉬운 코드를 만드는 데 있습니다.</p>
<p>이러한 목표를 객체간 강한 결합을 가지고 있는것을 약한 결합의 형태로 변경해, 결합도를 낮춤으로서 달성하는데요,
객체 간의 결합도를 낮추면, 기능을 추가하거나 수정할 때 어려움이 줄어들고, 테스트 또한 훨씬 간단해집니다.</p>
<p>특히 Flutter와 같은 UI 프레임워크에서는 상태 관리와 의존성 관리를 돕는 다양한 도구들이 제공되기 때문에, 이를 잘 활용하면 앱의 구조를 보다 간결하고 명확하게 유지할 수 있습니다.</p>
<h3 id="ioc-inversion-of-control">IoC (Inversion of Control)</h3>
<p>IoC는 간단히 말해, 객체의 제어권을 외부로 넘기는 것입니다.</p>
<p>그렇다면 왜 이러한 방법으로 코드를 작성해야 하는것일까요?
IoC 패턴이 강조되기 이전의 프로그래밍에서는 객체가 직접 다른 객체를 생성하고 관리했지만, 이러한 방식은 객체 간의 <strong>강한 결합</strong>(String Coupling)을 초래하게 됩니다.</p>
<p>이는 객체가 다른 객체의 구체적인 구현에 의존하게 되어, 두 객체가 긴밀하게 연결되기 때문입니다. 객체 간의 직접 참조는 코드 수정 시 영향을 받는 범위를 넓히고, 새로운 기능을 추가하거나 기존 기능을 변경할 때 많은 곳에서 코드를 수정해야 하는 상황을 초래합니다.</p>
<p>이를 객체의 구현 상세에 의존하지 않는 <strong>약한 결합</strong>(Loose Coupling)을 가지도록 수정해, 하나의 모듈에서 변경이 발생 할 때, 그 변경의 여파가 타 모듈로 이어지지 않도록 해야합니다.</p>
<p>리눅스 토발즈의 명언을 인용해볼 때인것 같네요.</p>
<blockquote>
<p>말은 쉽지. 코드를 보여줘.
Talk is cheap. Show me the code.</p>
<p>- Linus Torvalds</p>
</blockquote>
<p>어떤 코드가 강한 결합을 가지고 있는코드인지, 간단한 예시를 보여보겠습니다.</p>
<pre><code class="language-dart">/// Strong coupling 예시.
class A {
    final B fieldB;

    const A():
    this.fieldB = B(); // 생성자에서 구체 타입의 인스턴스를 직접 생성해 사용하고 있다.
}</code></pre>
<p>여러분의 소스코드에 위와 같은 패턴의 코드가 발견된다면, 그 모듈들은 강한 결합을 가지고 있는겁니다! </p>
<p>그렇다면 강한 결합의 코드가 왜 변경에 취약한 구조를 가진다는 것 일까요?
이를 보다 구체적인 코드와 예시를 들어 이야기해보죠.</p>
<p>여러분들은 갑자기 사내에서 앱 내에 게임 기능을 구현해야하는 임무를 맡았다고 가정해봅시다.</p>
<p>다음과 같은 코드를 작성하게 되겠죠.</p>
<pre><code class="language-dart">class Player {
  //.. 여러 코드..//

  String enroll() {
    print(&#39;player $this가 게임엔진에 등록 되었습니다.&#39;);
    return this.toString();
  }
}

class GameEngine {
  final Iterable&lt;Player&gt; players;

  GameEngine():
      this.players = [Player()];

  bool startEngine() {
    players.map((final e) =&gt; e.enroll(););

    print(&#39;게임 엔진 초기화 완료&#39;);

    return true;
  }
}</code></pre>
<p>위와같이  <code>GameEngine</code> 클래스가 <code>Player</code> 클래스를 직접 생성하고 관리한다면, 
새로운 유형의 플레이어를 추가하거나 기존 플레이어의 동작을 수정할 때 <code>GameEngne</code> 클래스도 함께 변경해야 합니다.</p>
<p>이러한 강한 결합으로 인해 코드 수정이 복잡해지고, 유닛 테스트가 어려워질 수 있다고 하는데,
실제로 신규 기능을 추가해 <code>Player</code> 클래스를 변경해보며, 어떻게 돌아가는지 살펴봅시다.</p>
<p>예를 들어, <code>Player</code> 클래스에 새로운 유형의 플레이어를 추가해야 한다 해볼까요?</p>
<p>저희 게임에는 여태까지 <strong>BM</strong>이 없었지만, 앞으로 구독형 요금제를 만들어 고객의 구독 플랜에 따라 플레이할 수 있는 게임의 레벨을 다르게 변경해야 하면 어떤 일이 벌어질까요?</p>
<p>이 경우 <code>GameEngine</code> 클래스 내부의 생성 방식도 변경해야 하기 때문에 변경 사항이 여러 곳에 전파됩니다.</p>
<p>새로운 플레이어 유형인 <code>PremiumPlayer</code>를 추가하고자 할 때, <code>GameEngine</code> 클래스에서 <code>PremiumPlayer</code>를 생성하도록 코드가 수정되어야 합니다.</p>
<p>이는 <code>GameEngine</code> 클래스가 각 플레이어 유형을 직접 알고 있기 때문입니다.</p>
<p>또한, 이러한 변경은 테스트에서도 영향을 미치며, 테스트 코드 역시 수정이 필요하게 됩니다.</p>
<p>아래는 변경 전파가 일어나는 코드 예시입니다:</p>
<pre><code class="language-dart">enum SubscriptionPlan {
  SlIVER, // 실버 등급 회원
  GOLD, // 골드 등급 회원
}

// 유료 구독자 플레이어 추가
class PremiumPlayer extends Player {
    final SubscriptionPlan plan; // 결제 플랜 정보

    const PremiumPlayer(SubscriptionPlan plan) {
        this.plan = plan;
    }

    @Override
    String enroll() {
      switch(plan) {
        case SlIVER:
          print(&#39;실버 플랜 회원의 플레이어가 게임에 등록되었습니다.&#39;);
        case GOLD:
          print(&#39;골드 등급 회원의 플레이어가 게임에 등록되었습니다.&#39;);
      }

      return this.toString();
    }
}

class GameEngine {
    final Iterable&lt;Player&gt; players;

    public Game(bool isPlayerSubscripted, [SubscriptionPlan? plan]) { // +1
        // 플레이어의 유형에 따라 객체 생성
                switch((isPlayerSubscripted, plan)) { // +2
            case(false, _): // +3
                players = [Player()]; // +4
            case(true, final plan) when plan != null: // +5
                players = [PremiumPlayer(plan)]; // +6
        } // +7
    }

  bool startEngine() {
    players.map((final e) =&gt; e.enroll(););

    print(&#39;게임 엔진 초기화 완료&#39;);

    return true;
  }
}</code></pre>
<p>위 코드에서 볼 수 있듯이, <code>Player</code>를 확장한 <code>PremiumPlayer</code>가 추가되었을 뿐인데도, <code>GameEngne</code> 클래스의 코드도 수정해야 합니다.</p>
<p>이러한 문제를 해결하기 위한 패러다임이 바로 <strong>IoC</strong>(Inversion of Controll) 입니다.</p>
<p> <code>GameEngine</code> 클래스가 <code>Player</code> 클래스의 구체적인 구현에 의존하지 않도록 변경하면,</p>
<p><code>Player</code> 객체의 생성과 관리 제어를 외부에서 담당하게 되어, 
<code>GameEngine</code> 클래스는 더 이상 특정 <code>Player</code> 유형에 대해 알 필요가 없습니다. </p>
<p>이를 통해 <code>Player</code>의 새로운 유형이 추가되더라도 <code>GameEngine</code> 클래스에는 변경이 필요하지 않으며, 결과적으로 코드의 결합도를 낮추고 유지보수를 쉽게 할 수 있습니다.</p>
<p>아래는 IoC를 사용한 코드 예시입니다:</p>
<pre><code class="language-dart">abstract interface class Player {
    String enroll();
}

final class PremiumPlayer implements Player {
    final PlayerPlan plan;

    const PremiumPlayer(PlayerPlan plan) {
        this.plan = plan;
    }

    @override
    void enroll() {
         switch(plan) {
        case SlIVER:
          print(&#39;실버 플랜 회원의 플레이어가 게임에 등록되었습니다.&#39;);
        case GOLD:
          print(&#39;골드 등급 회원의 플레이어가 게임에 등록되었습니다.&#39;);
      }
    }
}

class GameEngne {
    private final Player player;

    // 생성자를 통해 Player 주입
    public Game(Player player) {
        this.player = player;
    }

    void startGame() {
        player.play();
    }
}

public class Main {
    public static void main(String[] args) {
        PlayerPlan plan = PlayerPlan.GOLD;
        Player player = new PremiumPlayer(plan);
        Game game = new Game(player); // Player 객체를 외부에서 주입
        game.startGame();
    }
}</code></pre>
<p> <code>GameEngne</code> 클래스가 더 이상 <code>Player</code>의 구체적인 구현에 의존하지 않고, 대신 <code>Player</code> 인터페이스에만 의존합니다. <code>Player</code> 객체는 외부에서 생성되어 <code>GameEngne</code> 클래스에 주입되므로, 새로운 플레이어 유형이 추가되더라도 <code>GameEngne</code> 클래스의 코드는 변경할 필요가 없습니다. 이렇게 하면 코드의 유연성이 높아지고, 유지보수가 훨씬 쉬워집니다.</p>
<p>하지만, 실제 서비스 코드가 이런식으로 간단하게 돌아가진 않죠?</p>
<p>IoC 디자인 패턴을 이용해, 변경의 전파 차단을 달성하기 위한 상세 방법론에 대해서 이야기 해봅시다.</p>
<h3 id="di-의존성-주입">DI (의존성 주입)</h3>
<p>DI는 IoC의 한 형태로, 객체가 필요한 의존성을 외부에서 주입받는 방식입니다.</p>
<p>예를 들어, <strong>게임 엔진</strong>이 <strong>플레이어</strong>를 필요로 한다면, <strong>게임엔진</strong>이 직접 <strong>플레이어</strong>를 생성하는 것이 아니라, 외부에서 <strong>플레이어</strong>를 생성해 <strong>엔진</strong>에 전달해주는 방식입니다.</p>
<p>이렇게 하면 두 클래스 사이의 강한 결합을 줄이고, 테스트가 쉬워지며, 모듈화를 쉽게 할 수 있습니다. 
위에 설명된 인터페이스를 이용한 약결합으로 결합된 예시가 바로 DI를 활용한 예제가 되겠군요.</p>
<p>Flutter에서의 대표적인 DI를 사용하기 위한 수단은, <code>provider</code> 패키지나 <code>get_it</code> 패키지가 되겠습니다.</p>
<p>구체적인 예로 <code>InheritedWidget</code>과 <code>Provider</code>를 들 수 있습니다. </p>
<p><code>InheritedWidget</code>은 Flutter에서 위젯 트리 하위에 데이터를 효율적으로 전달할 수 있도록 도와주는 역할을 합니다. </p>
<p>앱의 테마나 사용자 설정 같은 글로벌 데이터를 <code>InheritedWidget</code>으로 정의하면, 해당 데이터를 필요로 하는 하위 위젯들은 손쉽게 접근할 수 있겠죠. </p>
<p>이를 적극적으로 사용하는 패키지는 <code>intl</code>  Flutter 앱의 i18n을 지원하기 위한 패키지가 그 예시가 될 것 같습니다. </p>
<pre><code class="language-json">{
  &quot;@greeting&quot;: {
    &quot;description&quot;: &quot;Greeting message displayed on the home page.&quot;,
    &quot;type&quot;: &quot;text&quot;
  },
  &quot;greeting&quot;: &quot;Hello World&quot;,
  &quot;@greeting_ko&quot;: {
    &quot;description&quot;: &quot;Greeting message in Korean.&quot;,
    &quot;type&quot;: &quot;text&quot;
  },
  &quot;greeting_ko&quot;: &quot;안녕하세요&quot;
}</code></pre>
<p>위와 같은 파일을 선언한 뒤,</p>
<pre><code class="language-dart">class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appSettings = AppSettings.of(context);
    return MaterialApp(
      locale: Locale(appSettings?.locale ?? &#39;en&#39;),
      localizationsDelegates: [
        // `intl` 패키지를 이용한 로컬라이제이션 설정
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale(&#39;en&#39;),
        const Locale(&#39;ko&#39;),
      ],
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appSettings = AppSettings.of(context);
    String greeting = Intl.message(
      &#39;Hello World&#39;,
      name: &#39;greeting&#39;,
      locale: appSettings?.locale,
    );
    return Scaffold(
      appBar: AppBar(title: Text(&#39;Home Page&#39;)),
      body: Center(
        child: Text(context.l10n.greeting),
      ),
    );
  }
}</code></pre>
<p>위젯에서 buildcontext 를 참조해, 상황과 local에 대응하는 문자열을 가져 올 수 있겠죠.</p>
<p>이런 방식은 특정 데이터를 여러 위젯들이 공유해야 할 때, 직접 참조 없이 데이터를 주입받을 수 있게 해주어 결합도를 낮추는 효과가 있습니다.</p>
<p>또한, <code>BuildContext</code>를 이용한 의존성 주입도 DI의 일종으로 볼 수 있습니다. </p>
<p>Flutter 의 Widget 클래스 자체가 IoC, DI 를 적극적으로 활용한 에시라고 말 할 수 있겠군요. </p>
<p>Flutter에서 <code>BuildContext</code>는 위젯 트리의 위치 정보를 가지고 있으며, 이를 통해 필요한 의존성을 전달받을 수 있습니다. 이를 통해 위젯은 트리 내에서 부모가 누구인지 일일이 그 정보를 전달받지 않아도 되겠군요.</p>
<p>이와 같이 DI를 활용하면 객체 간의 결합도를 줄이고, 코드의 모듈화를 높여 유지보수와 테스트의 효율성을 크게 향상시킬 수 있습니다.</p>
<p>하지만, 이와 같은 코드를 작성할때, Props Drilling 과 같이 DI를 의존관계 최상단까지 반복하는 문제를 경험 하실 수 있는데요, 이를 위한 해결법중 하나는 ServiceLocator 패턴이 있습니다.</p>
<h3 id="service-locator">Service Locator</h3>
<p>Service Locator는 필요한 객체를 중앙에서 조회해 가져오는 디자인 패턴입니다. </p>
<p>서비스 로케이터는 일종의 런타임-링커 역할을 수행하는 객체로, 프로그램 실행 중 필요한 객체의 의존성을 제공해줍니다. </p>
<p>이 방법은 의존성을 런타임에 해결할 수 있기 때문에 유연성을 제공합니다. 모듈의 수정 여파가 잘 전파되지 않고, 심지어는 런타임에 수정 여부를 바로 반영하도록 구성할 수도 있죠.</p>
<p>하지만, ServiceLocator 는 그만큼 단점도 존재합니다.</p>
<p>가장 큰 문제는 모듈같의 의존관계가 그대로 Service locator 의 모듈 등록 순서로 반영되어야 한다는 점 입니다.</p>
<p>이를 지키지 않으면, 런타임에 에외가 발생합니다. Linking 실패와 동일한 오류가 발생하는거죠.</p>
<p>Java 진영의 Spring core 는 이 문제를 jvm에서 지원하는 Class loader 를 사용해 
각각 모듈의 의존관계를  그래프로 작성하고, 가장 의존관계가 적은순부터 많은순으로 정렬하는 위상정렬을 실행한뒤, 정렬된 순서대로 ServiceLocator 에 모듈을 등록하는 방식으로 해결했지만,</p>
<p>아쉽게도 Flutter 에서는 아직 공식적으로 이러한 기능을 지원해주지 않습니다.
따라서, 원한다면 직접 이를 구현해야한다는 단점이 있습니다.</p>
<p>이 방식의 대표적인 구현 예시는 ServiceLocator.get(SomeService.class)와 같은 형태로 객체를 조회하는 것입니다. Flutter에서도 GetIt과 같은 라이브러리가 이러한 역할을 수행합니다.</p>
<p>이 패턴에서는 객체가 필요한 서비스의 의존성을 직접 가져오며, 이를 위해 서비스 로케이터라는 중앙 레지스트리가 사용됩니다. 코드에서 ServiceLocator.get(SomeService.class)와 같은 형태로 의존성을 조회하게 됩니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Udemy 수강후기] 시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리 2부- 저자직강]]></title>
            <link>https://velog.io/@wongue_shin/Udemy-%EC%88%98%EA%B0%95%ED%9B%84%EA%B8%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%9C-Arm-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9B%90%EB%A6%AC-2%EB%B6%80-%EC%A0%80%EC%9E%90%EC%A7%81%EA%B0%95</link>
            <guid>https://velog.io/@wongue_shin/Udemy-%EC%88%98%EA%B0%95%ED%9B%84%EA%B8%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%9C-Arm-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9B%90%EB%A6%AC-2%EB%B6%80-%EC%A0%80%EC%9E%90%EC%A7%81%EA%B0%95</guid>
            <pubDate>Sun, 12 May 2024 06:40:44 GMT</pubDate>
            <description><![CDATA[<h1 id=""></h1>
<p><a href="https://www.udemy.com/course/austin-armv8_v7_arch2/">강의 링크</a></p>
<p>저번 기회에 이어, Udemy와 저자분의 지원으로 강의를 지원받아,</p>
<p>해당 수강할 수 있는 기회를 얻었습니다.</p>
<p>1부에 이어, 2부에는 보다 직접적인 예시와 구체적인 내용에 관한 강의가 이어집니다.</p>
<p>2부의 큰 주제는 익셉션, 인터럽트, 함수호출과 같은 실제 시스템 아키텍처가 동작할 시 빈번하게 수행되거나 발생되는 동작들입니다.</p>
<p>이러한 주제를 각 Armv7, Armv8 두 아키택처의 버전으로 실제 예시에 관한 자세한 설명을 들으며 학습을 할 수 있습니다.</p>
<p>강의는 이론부터 시작해, 아키택처의 전체 흐름에서 해당 주제가 수행하는 역할을 공부한 뒤,</p>
<p>아주 구체적인 사례를 설명하며 이해를 도와주는데요,</p>
<p>비교적 해당 분야에 대한 배경지식이 적은 저도 학습에 큰 무리가 가지는 않았습니다.</p>
<p>물론, 주제 자체의 난이도가 낮지는 않은만큼 아주 수월하지는 않았지만, 강의에서 자주 언급되는 키워드 등을 구글링해가며 수강을 할 때, 완전히 이해할 수 없어 막히는 부분이 있지는 않았습니다.</p>
<ul>
<li>총평</li>
</ul>
<p>velog 에는 응용 소프트웨어 개발직군에 근무하시거나, 취업을 희망하시는 분들이 많은 것으로 알고 있습니다.</p>
<p>응용 소프트웨어인 웹앱, 서버 어플리케이션등을 작성하거나 유지보수하는데에는
해당 강의와 같은 시스템 아키택처의 지식이 큰 도움이 되지 않는다고 생각하실 수도 있을탠데요,</p>
<p>저는 현시점 크로스플랫폼 클라이언트 개발직군으로 근무를 하며,</p>
<p>실무에서 발생한 오류를 추적하기 위해 IOS 에서 제공해주는 크래시로그를 분석한 뒤 해당 원인을 추정, 수립한 가설을 증명한 뒤 문제를 해결하는 과정에서 이 강의의 내용이 큰 도움이 됬었던 적이 있습니다.</p>
<p><a href="https://velog.io/@wongue_shin/%EC%96%B4%EB%8A%90%EB%82%A0-%EA%B0%91%EC%9E%90%EA%B8%B0-%EC%95%B1%EC%9D%B4-%EC%BC%9C%EC%A7%80%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4">해당 포스트가 궁금하다면</a></p>
<p>프레임워크, OS, 타 SDK 등, 다양한 도구들를 사용하며 저희의 실제 로직을 작성을 할 때에는,
아키텍처 구조와 같은 low level 의 지식이 크게 요구되지 않거나,</p>
<p>이를 잘 안다고 해도 활용하기 어려운것도 사실입니다.</p>
<p>하지만, </p>
<p>라이브 서비스 환경에서는 가끔씩 저희가 당연히 정상적으로 동작할 것이라 가정하는 부분들에서도 문제가 발생 할 수 있는데요,</p>
<p>다행이 기존에 비슷한 오류가 빈번하게 발생해 관련 자료를 찾을 수 있다면 문제가 없지만,
이전에 보고된 케이스를 찾지 못하는 경우에는 결국은 작업자 스스로가 주어진 단서드를 조합해 스스로 문제의 원인을 유추해야만 합니다.</p>
<p>이러한 과정에서 모호하고 난해한 현상을 해결할 수 있는 열쇠와 같은 지식들은, 해당 강의와 같은 low level 의 지식들이라고 생각합니다.</p>
<p>따라서 응용 소프트웨어 개발자, 시스템 소프트웨어 개발자 구분하지 않고 알아두면 도움이 될 지식들이기에 저는 해당 강의를 추천한답니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[어느날 갑자기 앱이 켜지지 않는다 ]]></title>
            <link>https://velog.io/@wongue_shin/%EC%96%B4%EB%8A%90%EB%82%A0-%EA%B0%91%EC%9E%90%EA%B8%B0-%EC%95%B1%EC%9D%B4-%EC%BC%9C%EC%A7%80%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4</link>
            <guid>https://velog.io/@wongue_shin/%EC%96%B4%EB%8A%90%EB%82%A0-%EA%B0%91%EC%9E%90%EA%B8%B0-%EC%95%B1%EC%9D%B4-%EC%BC%9C%EC%A7%80%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4</guid>
            <pubDate>Sun, 14 Apr 2024 11:09:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>&quot;원규님, 고객분이 앱이 켜지질 안는다는데요?&quot; 😇
신규 기능은 추가하지 않고, 다음 배포를 위해 간단한 변경점만 추가한 버전에서 갑자기 의문의 VoC 가 들어오기 시작합니다...</p>
</blockquote>
<h4 id="여느-때와-같이-우당탕탕-배포를-끝마치고-다음-업무를-진행하려는-아침-의문의-voc-가-들어옵니다">여느 때와 같이 우당탕탕 배포를 끝마치고 다음 업무를 진행하려는 아침. 의문의 VoC 가 들어옵니다.</h4>
<p>내용은, &quot;오늘 아침부터 갑자기 앱이 켜지지 않는다.&quot; 였습니다. 아마 업데이트 이후 무언가 잘못된 모양인 거 같아요.</p>
<p>하지만, 아무리 테스트 버전으로 빌드해서 돌려봐도 현상이 재현되지 않습니다...
VoC 의 내용또한 불분명합니다. 정확한 재현 경로를 파악하기 어려움을 느꼈어요.</p>
<p>사용하고 있는 유저 세션 기록 서비스인 Firebase 와 DataDog 의 최근 2일간 모든 오류 보고를 찾아봤지만,  충돌 보고서나, 의심되는 오류가 감지된 퍼널 자체가 기록되지 않고 있었습니다..</p>
<p>도대체 알 수 없는 오류에 대한 티켓을 재현 경로 정보 부족으로 해결 불가로 기록하고 종료하려던 그 순간,
동일한 문의가 한건 더 들어오게 됩니다.</p>
<h4 id="상태가-심각해짐을-인식한-뒤-저는-다른-모든-작업을-중지하고-본격적으로-장애-대응에-착수합니다">상태가 심각해짐을 인식한 뒤, 저는 다른 모든 작업을 중지하고 본격적으로 장애 대응에 착수합니다.</h4>
<p>DataDog 과 서버 로그를 통해 분석한 결과, 문제를 제기한 모든 유저의 공통점은 IOS 를 사용중이라는 점을 확인 할 수 있었습니다.</p>
<p>Mac에서 IOS 실기기에 붙어 stdout 를 보며 앱의 초기화 과정을 분석하니, Flutter render engine 관련 warning 을 하나 찾을 수 있었습니다.</p>
<p>혹시, IOS 에서 skia 엔진이 impalla 로 교체되며 충돌이 일어나는건가 싶어 lnfo.plist 에 FLTEnableImpeller 값을 false 로 변경해 빌드해 보았지만 여전히 테스트 환경에서는 어떠한 문제도 없이 잘 돌아가는 앱을 볼 수 있었습니다.</p>
<p>별 수 없이 리뷰를 요청하고 이 변경점으로 문제가 해결되길 기도했습니다.</p>
<h4 id="하지만-심사가-통과된-다음날에도-같은-voc-는-계속해-들어왔습니다">하지만, 심사가 통과된 다음날에도, 같은 VoC 는 계속해 들어왔습니다...</h4>
<p>렌더 엔진 충돌건은 원인이 아닌것으로 판별, 그럼 다시 접속 로그를 보며 제보된 유저들의 공통점을 확인 할 수 있는지 자료를 조사해봅니다..</p>
<p>접속 위치, 접속 시간, 문제가 발생 한 기종명들은 모두 일치하지 않았습니다.
머리를 싸매고 조금 더 파악한 결과, 문제를 보고한 모든 고객분들은 마지막 접속 기록에서 IOS의 버전이 최신 IOS 17 버전이 아닌  IOS16 이였던 점을 알 수 있었습니다!</p>
<p>마지막 접속기록이 채 40시간이 지나지 않았으니, 지금도 동일한 os 버전을 유지하고 있을것이라는 가정을 세운 후, 회사의 모든 동료분들 중, IOS 업데이트를 아직 하지 않은 분이 있는지 찾기 시작했습니다.</p>
<p>다행이 타 개발팀에서 사용중인 테스트 시료가 아직 업데이트를 하지 않아 IOS 15 버전을 유지중인것을 확인, 
이 시료를 가져와 프로덕트의 최신 버전을 설치한 수 실행시켜보니, 다행이 실행하자 마자 바로 프로그램의 충돌이 발생 하는것을 볼 수 있었습니다!</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/d7c62828-b0f4-4c42-8580-93efc73981a9/image.gif" alt=""></p>
<blockquote>
<p>다행이다... 이제 재현경로와 크래시 로그를 확보했으니, 문제 해결만 하면 됩니다!</p>
</blockquote>
<p>IOS 의 크래시 로그는 Android 와 Web과는 다른게 보다 밑단의 로우(raw) 데이터를 제공해줍니다.
저도 최근에 arm 아키택처를 공부하지 않았다면. 무슨 일이 일어나고 있는지 알 수 없었을건데요,</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/af383bfa-31ec-4355-8268-929fe33e06db/image.png" alt=""></p>
<blockquote>
<p>제공된 크래시 로그의 일부분입니다.</p>
</blockquote>
<p>로그를 천천히 분석해보면, 
첫번째 문단에서는 실행 환경, 실행된 프로그램 정보등을 보여주고 있습니다.</p>
<blockquote>
<p>첫번째 문단 </p>
</blockquote>
<pre><code>Incident Identifier: 52D4DA86-E36E-4A1B-9D34-F40F34B78462
Hardware Model:      iPhone13,4
Process:             Runner [11335]
Path:                /private/var/containers/Bundle/Application/6BA54962-48F4-4155-B6B7-752A16B20B08/Runner.app/Runner
Identifier:          -------------------
Version:             1.0.44 (115)
AppStoreTools:       15E204
AppVariant:          1:iPhone13,4:15
Beta:                YES
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           -------------------</code></pre><p>iPhone 13,4 는 우리가 부르는 아이폰 12 프로맥스의 model identifier 모델 식별자를 의미합니다.</p>
<p>사용하고 있는 cpu 코어의 타입이 ARM-64 아키택처를 사용중이라는것을 확인 할 수 있고,
저희 프로그램은 launchd 라는 PID(Process ID) 1 을 가지는 프로세스에서 호출되었음을 확인 할 수 있습니다.</p>
<p>luachD 는 간단히 설명하면,  IOS, MacOS 의 초기화, 운영체제 서비스를 관리하는 데몬입니다.
<a href="https://en.wikipedia.org/wiki/Launchd">https://en.wikipedia.org/wiki/Launchd</a>
어플리케이션의 호출 또한 해당 프로세스가 관리중이라는것을 확인 할 수 있겠네요.
또한 어플리케이션이 충돌하기 직전 Foreground 상태였음을 확인 할 수 있습니다.</p>
<blockquote>
<p>두번째 문단</p>
</blockquote>
<pre><code>Date/Time:           2024-03-28 15:16:06.6836 +0900
Launch Time:         2024-03-28 15:16:06.3779 +0900
OS Version:          iPhone OS 15.4.1 (19E258)
Release Type:        User
Baseband Version:    2.53.01
Report Version:      104</code></pre><p>두번째 문단에서는 충돌 당시의 디바이스 로컬 시간과 프로그램이 시작한 시간, OS 버전을 볼수 있습니다.
거의 0.5초도 걸리지 못하고 프로그램이 충돌했음을 알 수 있네요.</p>
<blockquote>
<p>세번째 문단</p>
</blockquote>
<pre><code>Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Exception Codes: 0x0000000000000001, 0x0000000000000000
VM Region Info: 0 is not in any region.  Bytes before following region: 4375396352
      REGION TYPE                 START - END      [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
---&gt;  
      __TEXT                   104cb4000-104cb8000 [   16K] r-x/r-x SM=COW  ...er.app/Runner
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: SIGNAL 11 Segmentation fault: 11
Terminating Process: exc handler [11335]
Triggered by Thread:  0</code></pre><p>세번째 문단부터는 직접적인 충돌 원인을 볼 수 있습니다.
EXC_BAD_ACCESS (SIGSEGV) 는, 
OS 에서 지정해주는 메모리 세그멘테이션을 위배(SIGSEGV = <strong>Sig</strong>nal <strong>Seg</strong>mentation <strong>V</strong>iolation)하는 잘못된 주소의 접근(EXC_BAD_ACCESS)을 시도했음을 의미합니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/144cfb3c-66c8-4c3e-a855-8ade0f7cdbcf/image.png" alt=""></p>
<blockquote>
<p>OS 가 할당해주는 메모리 구조에서, 해당 프로그램에게 할당된 메모리 구간을 넘어서는 주소를 참조, 쓰기 요청을 할 때 발생하는 예외입니다.</p>
</blockquote>
<p>로그를 더 자세히 읽어봅시다!</p>
<p>두번째 줄에서는 Exception 의 Subtype, 즉 더 상세한 오류의 원인을 설명합니다.
KERN_INVALID_ADDRESS 는 참조중인 가상 주소가 페이지 테이블에 없거나, 접근 권한이 없음을 이야기합니다.</p>
<p>뒤에는 실제로 참조하려 시도했던 주소를 보여주네요 0x0000000000000000
어 근데 포인터의 값이 좀 이상합니다? 저렇게 주소가 다 0일 경우가 있을까요?</p>
<p>맞습니다. 0x0000000000000000 은 흔히 널 포인터 Null Pointer 로 사용되는 주소입니다.</p>
<p>이 시점에 문제의 원인을 절반 이상 추측 할 수 있었습니다.</p>
<p>앱의 초기화 과정에서 IOS 에서 제공하는 어떤 함수가 필요한데, 이 함수는 포인터로서 먼저 선언되고, 후에 실제 주소를 역참조로서 기록한뒤 사용하는 측에서는 포인터를 참조해 사용할 것으로 생각됩니다.</p>
<p>하지만, 포인터만 생성되고 역참조를 통한 초기화가 어떤 이유에서인지 정상적으로 진행되지 못해, 초기화 되지 못한 값은 NullPtr의 값에 적혀있는 코드를 CPU 가  실행하려고 해서 난 오류입니다.</p>
<p>이런 과정이 일어나려면 Dart 나 Java 같은 메모리를 VM 이 메니지먼트해주는 환경에서는 발생하지 않을꺼고, 그 밑단의 UnManagement 환경에서 발생할 것이라 추측할 수 있습니다.
swift 혹은 그 밑단의 영역에서 발생한 오류겠군요.</p>
<p>마지막으로, 이러한 오류는 0번 스래드, 즉 메인 스래드에서 발생했음을 확인 할 수 있습니다.</p>
<p>이제 충돌 보고서의 밑을 좀더 읽어보겠습니다.</p>
<p>각 스래드의 스택 트레이스가 적혀있는데요, 충돌이 발생한 0번 스래드 이외의 정보들은 큰 의미가 없기 때문에, 0번 스래드만 읽어보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/a8417ca1-d3af-4e11-9d9f-6160562c6787/image.png" alt=""></p>
<p>바로 문제의 원인을 파악 할 수 있겠죠?
DatadogCore 의 DatadogContextProvider.__allocation_init() 이라는 함수를 실행하다가 참조한 함수의 포인터가 nullPtr 이였던 오류였습니다.</p>
<p>크레시 로그의 마지막에는 충돌이 발생한 시점, CPU 레지스터의 저장된 상태를 볼 수 있는데요,
아이폰의 CPU 가 충돌 직전 어떤 일을 실행하려던 시점인지를 확인 할 수 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/5b6a783e-fc5a-4cb0-955b-3fa90450811c/image.png" alt=""></p>
<p>기본적으로 x0번 레지스터부터, x28번 레지스터는 어셈블리 명령어의 입력, 출력으로 사용됩니다.</p>
<p>여기에 저장되어있는 값들의 실제 의미는, 디버거를 붙여서 실제로 수행하는것을 보기 전까진 알기 힘들기 때문에, 우리는 실행 맥락을 담고있는 레지스터들에 집중해보겠습니다.</p>
<p>lr 레지스터는 Link Resister 를 의미합니다.
이 레지스터는 함수를 호출 할 때, 반환 주소를 저장하는데 사용합니다.</p>
<p>보다 자세히 설명하자면, lr 은 함수의 동작이 끝난 뒤 돌아갈 주소를 저장합니다.
반대로 말하면, 지금 함수를 호출한 코드의 영역임을 알 수 있겠네요.
저 위치에 작성된 코드가 지금 문제의 원인임을 알 수 있습니다.</p>
<p>마지막으로 보아야하는 레지스터는 pc 입니다.
pc 는 ProgramCounter 를 의미하고, 현재 실행중인 명령어의 주소를 의미합니다.</p>
<p>0x00...00 인 nullPtr 값을 가지고 있죠?  직전 위치에서 잘못된 위치로 실행 맥락이 이동된 상황입니다.</p>
<p>일단은 이 시점에서 Datadog SDK 가 문제의 원인임을 확인 할 수 있으므로, 급한대로 해당 SDK 를 비활성화 한 배포버전을 급하게 출시하며 문제의 표면적인 현상을 해결 할 수 있었습니다.</p>
<p>하지만, 정말 어디서 충돌이 발생했는지 조금 더 찾아볼까요?</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/cf3ae553-9c91-4a78-ac65-a1f5b22b4409/image.png" alt=""></p>
<p>실제 Xcode 의 충돌 당시 스택 트레이스입니다.
동일하게 nullPtr exception 이 발생하는것을 알 수 있죠?</p>
<p>이러한 오류가 발생하게 된 맥락을 9번째 호출지점인, DatadogSdkPlugin 의 초기화 코드부터 쫒아가보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/c247e64c-69a3-417b-a241-bbab59655e13/image.png" alt=""></p>
<p>DatadogSDKPlugin 의 handle 이란 메서드 안에서 초기화 코드가 실행되고 있군요,
그렇다면, initialize 코드를 좀더 보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/ef386413-2ff0-45ba-846a-950bc1ac2a53/image.png" alt=""></p>
<p>Datadog core 의 초기화 과정에서,  </p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/de455d7e-5c4e-45c0-b3b0-195ee5ce075f/image.png" alt=""></p>
<p>타겟 환경과, 현재 OS 등을 확인 한 뒤,</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/ff654721-3403-4f79-9b55-c78c05a9f255/image.png" alt=""></p>
<p>DataDogContextProvider 라는걸 생성하다가 실패했군요.
넘겨받는 인자들을 보아하니, 현재 프로세스가 실행되는 환경관련 정보를 제공하는 클래스 인것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/ae7704df-35ce-4de2-b41a-eea116a4e226/image.png" alt=""></p>
<p>결국 오류가 난 부분은 NWPathMonitor 의 .init() 메서드입니다.</p>
<p><a href="https://developer.apple.com/documentation/network/nwpathmonitor">https://developer.apple.com/documentation/network/nwpathmonitor</a></p>
<p>문서를 보아하니, 네트워크 변경사항을 모니터링하고 대응하는 관찰자 클래스이군요.</p>
<p>Xcode 15 버전부터 최소 배포대상을 IOS 12 이상으로 올렸기 때문에 발생하는 오류인 것 같습니다.</p>
<p>(말도 안하고 바꿔버리는 애플....)</p>
<p><a href="https://developer.apple.com/forums/thread/737672">https://developer.apple.com/forums/thread/737672</a></p>
<p>너무 막막한 VoC 부터, 정보 수집, 원인 추정 후 해결까지 빠른시간 안에 진행하는 경험은
제 문제 해결 능력을 향상시키는데 큰 도움이 되었다고 느꼈습니다.</p>
<p>물론 상당한 압박을 느꼈지만요...</p>
<p>또한, 이러한 문제를 해결하기 위해선 프레임워크 밑단의 로우 레벨 지식들을 미리 미리 공부해 두었던게 큰 도움이 되었다는 사실 또한 다시 한번 느끼게 되었습니다.</p>
<p>저의 경험을 공유하며 이 글을 읽으시는 여러분들도 프레임워크를 어떻게 사용할 까에 집중할 뿐만이 아니라, 보다 더 밑단의 지식을 공부하실 필요성을 느낄 수 있었으면 좋겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[현업 개발자가 생각하는 Flutter 의 장단점과 특징]]></title>
            <link>https://velog.io/@wongue_shin/%ED%98%84%EC%97%85-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%83%9D%EA%B0%81%ED%95%98%EB%8A%94-Flutter-%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90%EA%B3%BC-%ED%8A%B9%EC%A7%95</link>
            <guid>https://velog.io/@wongue_shin/%ED%98%84%EC%97%85-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%83%9D%EA%B0%81%ED%95%98%EB%8A%94-Flutter-%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90%EA%B3%BC-%ED%8A%B9%EC%A7%95</guid>
            <pubDate>Sun, 31 Mar 2024 14:18:26 GMT</pubDate>
            <description><![CDATA[<p>최근 플러터에 대한 관심이 꽤나 올라가는것을 느끼고 있습니다.
네이티브 개발자분들의 관심도나, FE, BE 개발자 분들 또한 종종 관심을 가지시는것을 볼 수 었었어요.</p>
<p>따라서 플러터에 대한 입문 강의나, 기술의 장점을 소개하는 글이나 영상또한 많이 제작되는것을 볼 수 있었습니다.</p>
<p>하지만, 아직 현업에서 플러터를 사용한 경험을 가진 분들은 아직 적어서 그런지,
대부분의 자료가 다른 개발스택을 사용하는 사람의 입장에서, 플러터를 먼저 배워보며 소개하는 느낌이 강하다는것을 느꼈습니다.</p>
<p>그래서, 큰 서비스를 다루거나 아직 긴 기간을 사용한 것은 아니지만, Flutter 를 사용해 초기 기획부터 런칭, 그 이후 운영까지 만 2년 동안 겪은 Flutter 의 장단점을 가볍게 이야기 해보자 합니다!</p>
<p>제가 Flutter 의 모든 부분을 잘 아는것은 아니고, 사용한 방식이 권장되는 방식이 아닐수도 있기 때문에 제가 느낀 소감이라 생각해주면 감사하겠습니다.</p>
<h2 id="one-source-multi-use">One source multi use</h2>
<h4 id="flutter-라는-기술의-존재의의기도-하지요">Flutter 라는 기술의 존재의의기도 하지요.</h4>
<p>하나의 소스코드를 가지고 여러 OS 를 대상으로 앱을 빌드 할 수 있다는 특징은 매우 매력적입니다.</p>
<p>실제로 이러한 특징으로 저희 팀은 FE 기술 스택으로 Flutter 를 선택했기 때문에 신입 개발자 2명으로 시작해 6개월만에 Web, IOS, Android 세 OS 에 앱을 모두 출시 할 수 있었습니다.</p>
<h4 id="하지만">하지만...</h4>
<p>서비스가 성장하고, 유저의 수가 확대되며 반복적으로 느낀 부분이 있었습니다.</p>
<p>IOS 앱을 그대로 Web 이나 안드로이드에 띄워놓는다고 모든 문제가 해결되지는 않는다는 것이였습니다.
각각의 OS 를 사용하는 유저가 기대하는 UX 가 많이 다르기 때문이였어요.</p>
<p>실제로 IOS 와 Android 는 화면 라우팅 구조부터 다르기 때문에, 하나의 OS 를 기준으로 앱의 코드를 작성하면 다른 OS 의 유저가 어색함을 느낄수 있습니다.</p>
<p>그나마 IOS 와 Android OS 가 설치된 기기는 비슷한 화면비, 비슷한 화면 크기를 가진다는 공통점이 있어, 서로의 장점을 흡수해가는 중이기에 어느정도 유사하다고 볼 수 있겠습니다.</p>
<p>하지만, Web 이나 테블릿 등의 경우에는 사용하는 화면비와 크기가 너무 상이하기 때문에, 모바일 디자인 시스템을 가진 flutter 웹앱을 브라우저에 띄울 순 있지만, </p>
<p>결국 멀티 OS 를 지원하는 이유는, 여러 OS 의 유저를 전환시켜 비즈니스적 성과를 달성하기 위함임을 생각해보면. 단순히 &#39;동작한다&#39; 만으로서는 그 의미가 크지 않다고 할 수 있겠습니다.</p>
<p>따라서, 결국 Flutter 의 내부 비즈니스 로직을 최대한 아키택처의 코어로 격리하고, view 코드는 OS 별 분기를 많이 추가하다가, 일정이상의 복잡도가 되면 각각 다른 프로젝트로 독립하는 형태가 될 것이라 예상해 볼 수 있겠습니다.</p>
<p>최종적으로 완성된 형태는, 사실 cpp, rust, golang 등으로 만든 핵심 패키지를 FFI 로 불러와 각각 Web, IOS, Android 앱을 개발하는 형태하고 크게 다르지 않을수도 있겠다는 생각이 들었습니다.</p>
<p>물론 사용하는 언어가 Dart 로 통일된다는것은, 위의 형태에서는 얻을 수 없는 장점이라고 할순 있겠습니다.</p>
<h2 id="dart-lang">Dart lang</h2>
<h4 id="좋긴-좋은데-kotlin-보다는">좋긴 좋은데.. Kotlin 보다는?</h4>
<p>Dart 는 비교적 최근에 3.0 으로 대규모 업데이트를 하며 상당히 강력한 기능들이 추가되었습니다.</p>
<p>sealed class 를 이용한 partern matching, TypeScript 에서는 지원하지 못하는 보다 강력한 interface 와 mixin, extend function 등은 JS 진영에서는 얻을 수 없는 확실한 장점이라 할 순 있겠습니다.</p>
<p>하지만, 항상 비교가 되는 언어가 있지요. 바로 Kotlin 입니다.</p>
<p>Dart 가 최근 아무리 강력해졌어도, Jvm 의 장점과 모던 언어의 장점을 같이 가지는 Kotiln 보다는 아직 많은 부분이 뒤떨어지는것이 사실입니다.</p>
<p>Android 네이티브 개발자분들은, Android 진영에서 제공해주는 강력한 도구들이 종종 그리워질 수 있을것 같습니다.</p>
<p>Dart VM 에는 JVM 의 class loader 같은 도구가 없기 때문에, DI 과정에서 상당한 귀찮음이 따라온답니다.</p>
<p>또한 Dart VM 에서는 reflection 을 지원하지만, Flutter 의 빌드과정중 앱의 용량을 줄이기 위한 tree-shaking  과정에서 이를 허용하지 않기 때문에, 크로스 플랫폼 앱을 제작하는 용도로 Dart 를 사용하신다면, 이는 사실상 없는 기능이라 생각하시면 되겠습니다.</p>
<h4 id="그래도-react-native-보다는">그래도 React Native 보다는..</h4>
<p>그래도 Flutter 의 DX 가 아예 엉망진창이지는 않습니다.</p>
<p>구글 팀이 그래도 디버깅 환경에는 많이 신경을 써준다고 할 수 있습니다.</p>
<p>test run이나, hot refresh, hot restart, dedubgging tool 등, 개발 과정에서 도구의 불편함으로 생산성을 걱정할 일은 많이 없다 생각하셔도 될 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/bc8090ee-a306-43d1-8fc2-617dd41c294d/image.png" alt=""></p>
<p>( 보다 더 자세한 기능이 궁금하시다면, 이 링크를 확인해 보셔도 좋습니다.
생각보다 정말 강력한 기능을 제공할꺼에요. <a href="https://docs.flutter.dev/tools/devtools/inspector">https://docs.flutter.dev/tools/devtools/inspector</a>)</p>
<h2 id="pubdev-오픈소스-커뮤니티">pub.dev 오픈소스 커뮤니티</h2>
<h3 id="정말-생각보다-당연히-있을만한것이-없을수도-있습니다">정말 생각보다 당연히 있을만한것이 없을수도 있습니다.</h3>
<p>위에 설명한 DI 관련해서, android 유저라면 당연히 기대하는 dagger2 와 같은 발전된 DI tool은 플러터에 있지 않습니다... 이러한 기능이 필요하다면, 직접 개발해 사용해야합니다.</p>
<p>또한 2-level 의 메모리 - 디스크 캐싱을 생각중이시라면, 이 또한 아직 flutter 에는 멀티레벨 캐싱을 지원하는 패키지가 없습니다. 이 또한 직접 만드셔야 해요...</p>
<p>오픈소스 패키지는 목업 혹은 초기 버전을 빠르게 만드는것을 도와주는쪽에 아직은 많은 집중이 되어있다 할 수 있습니다.</p>
<h4 id="하지만-최근-오픈소스-패키지의-빠른-발전이-이루어지고-있답니다">하지만, 최근 오픈소스 패키지의 빠른 발전이 이루어지고 있답니다.</h4>
<p>저는 정말 짧은시간 flutter 를 사용했지만, 그 짧은 시간 와중에서도, 최근 flutter 의 오픈소스 패키지의 개발속도가 보다 빨라짐을 많이 체감 할 수 있었습니다. </p>
<p>제가 단점으로 적은 문제 또한 앞으로 한두해 이후에는 더이상 단점이 되지 않을수도 있겠다는 생각이 드네요.</p>
<h3 id="총평">총평</h3>
<h3 id="만약-이미-대규모-서비스를-운영중이고-막대한-레거시-코드가-이미-존재한다면">만약 이미 대규모 서비스를 운영중이고, 막대한 레거시 코드가 이미 존재한다면...</h3>
<p>이러한 상황일 경우에는, Flutter 로 모든 앱을 재작성하는것은 아직까진 그리 추천하지 않습니다.</p>
<p>서비스의 규모가 크다면, 보다 복잡한 비즈니스적 문제를 기술적으로 해결했어야만 했을거고, Flutter 가 최근 많은 발전을 이루었지만, 아직까지는 정말 대규모 시스템에서 요구하는 특수한 수요와 정밀도를 보장할 수 있다는 예시는 찾기 힘들다는것 생각해 보아야 합니다.</p>
<h4 id="아직-상용-서비스가-없고-ios-android-동시-출시를-생각중이라면">아직 상용 서비스가 없고, IOS, Android 동시 출시를 생각중이라면...</h4>
<p>이러한 케이스는 이야기가 많이 다릅니다.</p>
<p>서비스 초기에는 고도화보다는 다양한 피처의 개발 속도가 매우 중요하다고 생각합니다.</p>
<p>이런 상황에서 Flutter 는 저비용 고효율의 성과를 도출 할 수 있는 좋은 방법이라 생각하고 있습니다.</p>
<p>각각의 OS 의 개발자를 따로 뽑아야 할 필요가 없으며, 같은 기술을 사용하는 개발자를 고용하기 때문에 흔히 이야기하는 bus factor (혹은 lottery factor) 의 위험요소가 보다 낮아진다는 이점까지 같이 가져갈 수 있습니다.</p>
<p>다만, 여러 OS 에 런칭할 계획이지만, Web 을 주력으로 타겟할 계획인 경우에는, Flutter Web 은 아직까진 진지하게 고려하는것이 좋진 않습니다.</p>
<p>web 진영의 다양한 서드파티 sdk 를 사용하는데 큰 어려움이 따름과 동시에, 퍼포먼스 마케팅, GTM, 히트맵 등 을 사용하기 가 매우 어렵거나 혹은 불가능할 수도 있습니다.</p>
<p>또한 DOM element 기반의 UI 구조를 사용하지 않기 떄문에, 접근성 지원과 SEO 에서 태생적인 약점을 가지고 있습니다.</p>
<p>마지막으로는, Flutter web 은 모든 flutter 엔진과 앱 코드를 모두 내려받은 뒤, Flutter 엔진을 실행한 이후 그 뒤 앱의 초기화가 들어가기 때문에, 초기 로딩속도가 비교적 느릴 수 밖에 없다는 단점을 가지고 있답니다.</p>
<p>(이를 줄이기 위해 deferred import 라는 방법이 있지만, 이 또한 장 단점을 가지고 있는 방법이랍니다.
<a href="https://docs.flutter.dev/perf/deferred-components">https://docs.flutter.dev/perf/deferred-components</a>)</p>
<p>개인적인 생각은 
Flutter 는 간혹 과대 포장된것 처럼 Native 개발을 아예 대체할 빛이 나는 신기술도, 또한 아직은 사용하기 어려운 결함 투성이의 기술도 아닌 그 중간 어딘가의 위치에 있다 생각하고 있습니다.</p>
<p>나름의 매력이 있는 기술이니, 현재 기술이 가지고 있는 장 단점이 비즈니스의 현상황과 잘 맞는다 생각할때는 충분히 도입을 고려해봄직 하다고 말하고 싶네요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Udemy 수강후기] 시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리 1부- 저자직강]]></title>
            <link>https://velog.io/@wongue_shin/Udemy-%EC%88%98%EA%B0%95%ED%9B%84%EA%B8%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%9C-Arm-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9B%90%EB%A6%AC-1%EB%B6%80-%EC%A0%80%EC%9E%90%EC%A7%81%EA%B0%95</link>
            <guid>https://velog.io/@wongue_shin/Udemy-%EC%88%98%EA%B0%95%ED%9B%84%EA%B8%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%9C-Arm-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9B%90%EB%A6%AC-1%EB%B6%80-%EC%A0%80%EC%9E%90%EC%A7%81%EA%B0%95</guid>
            <pubDate>Sun, 17 Mar 2024 11:54:49 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.udemy.com/course/austin-armv8_v7_arch1/">시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리 1부- 저자직강</a></p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/cdd1e93f-0ff4-4886-ace1-4571ea7eddd8/image.png" alt=""></p>
<p>이번 기수 글또에서 Udemy 강의를 무료로 수강 할 수 있는 기회를 받게 되었습니다.</p>
<p>저는 컴퓨터 공학 전공 과정을 이수하지 않아, 
하드웨어 밑단의 오류가 실무에서 발생하면 배경지식의 부족으로 이를 해석하기에 어려움을 느끼고 있는 와중이였는데, </p>
<p>우연찮게 좋은 기회를 얻게 되어 해당 과정을 수강하게 되었습니다!</p>
<p>강의는 총 3부로 제작되어있는데, 저는 1부와 2부를 제공 받았습니다.  (2부 후기도 곧 올리도록 하겠습니다.)</p>
<h3 id="강의-소개">강의 소개</h3>
<p>강의는 저자분이 작성한 도서를 기반으로 각 챕터를 해설해주시는 이론 수업 + 실제로 디버그 등을 수행해보는 실습수업으로 나누어집니다.</p>
<p>이러한 강의 과정이 형성된 이유는 저자분이 권장하시는 CPU 아키택처의 학습방법과 연관되어있는데요,
CPU 아키택처는 결국 어셈블리 명령어와는 떨어질 수 없는 불가분의 관계이기 때문에, 어떠한 방식으로 활용 될 수 있는지를 학습하지 않고 CPU 아키택처의 이론적인 내용만 학습하는건 큰 의미가 없기 때문입니다.</p>
<h3 id="강의를-수강하면-얻을-수-있는-지식">강의를 수강하면 얻을 수 있는 지식</h3>
<p>CPU(Arm) 아키택처를 잘 알게되면, 다음과 같은 작업을 수행할 수 있게됩니다.</p>
<ol>
<li>베어메탈의 보드의 브링업 코드를 작성할 수 있다.<ol>
<li>부트로더의 스타트업 코드를 작성할 수 있습니다.</li>
<li>메모리 캐시와 같은 시스템 자원의 초기화가 가능해집니다.</li>
<li>키보드와 USB와 같은 주병 장치의 초기화를 수행 할 수 있습니다.</li>
</ol>
</li>
<li>디바이스 드라이버 개발을 수행할 수 있습니다.<ol>
<li>인터럽트의 우선순위를 설정할 수 있습니다.</li>
<li>인터럽트의 설정 여부를 의도한 바 대로 다룰 수 있게됩니다.</li>
</ol>
</li>
<li>디버깅을 잘 할 수 있게됩니다.</li>
</ol>
<h3 id="아쉬운-점">아쉬운 점</h3>
<p>실습 예제는 보통 TRACE 32 를 사용해 진행됩니다.
대부분의 경우는 해설이 충분해 문제가 없지만, 가끔 직접 따라해보고 싶은 경우에는 그럴 수 없다는 사실이 조금 답답하긴 했었습니다.</p>
<p>이는 강의의 문제라기보단, 시스템 아키택처는 우리가 사용하는 컴퓨터의 추상화 계층 가장 밑단, 하드웨어와 아주 밀접한 관계여서 원래 조작이나 디버깅이 어려운 특성을 가지는 이유가 더 큰것 같습니다.</p>
<h3 id="추천대상">추천대상</h3>
<p>해당 강의는 아래와 같은 분들에게 도움이 될것입니다.</p>
<ul>
<li>시스템 반도체, 전기 자동차 분야를 포함한 시스템 소프트웨어 분야의 취업을 희망하는 구직자분, 혹은 역량을 발전시키고 싶은 주니어 개발자분.</li>
<li>시스탬 소프트웨어 분야로 커리어를 전환하고자 하는 타 분야의 개발자분.</li>
<li>시스템 소프트웨어 분야 (메모리, 파일 시스템, 운영체제)의 대학원 진학을 목표하시는 대학생분</li>
<li>혹은 저처럼 시스템 소프트웨어 분야의 하드웨어 지식에 흥미가 있으신 분들.</li>
</ul>
<h3 id="총평">총평</h3>
<p>시스템 아키택처를 입문하기에 추천할만한 강의라 생각됩니다.</p>
<p>저는 강의를 수강하기 전 리눅스 커널이나, CPU 아키택처 관련 지식이 거의 없었음에도, 강의를 따라가는데 큰 무리를 겪지 않았었습니다. </p>
<p>어셈블리 명령어에 대한 상세한 설명과 실습등에 큰 만족을 했었습니다.</p>
<p>마침 30% 할인 쿠폰을 발행중이니, 관심 있으신분들은 수강을 고려해보시는것도 나쁘지 않아보이네요.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/0669265c-8088-4083-b427-548e6cd7ba13/image.png" alt=""></p>
<p>해당 포스트는 글또 9기를 통해 Udemy 강의 쿠폰을 제공받아 작성하였습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[생각보다 수학으로 할 수 있는 많은것들 (Non-linear font scaling 구현하기)]]></title>
            <link>https://velog.io/@wongue_shin/%EC%83%9D%EA%B0%81%EB%B3%B4%EB%8B%A4-%EC%88%98%ED%95%99%EC%9C%BC%EB%A1%9C-%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%A7%8E%EC%9D%80%EA%B2%83%EB%93%A4-Non-linear-font-scaling-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wongue_shin/%EC%83%9D%EA%B0%81%EB%B3%B4%EB%8B%A4-%EC%88%98%ED%95%99%EC%9C%BC%EB%A1%9C-%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%A7%8E%EC%9D%80%EA%B2%83%EB%93%A4-Non-linear-font-scaling-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 03 Mar 2024 10:32:53 GMT</pubDate>
            <description><![CDATA[<p>CS 나, 수학적 사고력 등은 이직을 위한 공부라고 생각하거나
ML, CG, CV 등 연구직에서만 중요도가 높고 개발직군에서는 큰 연관도가 낮다는 주장을 주변에서 많이 접할 수 있었어요.</p>
<p>특히 FE는 대규모 시스템을 다룰 일이 적기 때문에 그 중요도가 더욱 낮다는 주장을 하시는 분들도 종종 볼 수 있었답니다.</p>
<p>저는 아직 그리 길지 않은 경력을 가지고 있지만, 크로스 플랫폼 앱을 개발하며 CS 지식은 물론이고, 수학적 사고력이 실무의 문제를 간단하게 풀 수 있는 해결책이 되는 경우를 많이 겪었어요.</p>
<p>그 중 하나의 예시를 보여드리고자 합니다.</p>
<p><a href="https://developer.android.com/about/versions/14/features?hl=ko#non-linear-font-scaling">NonLinear Font scaling</a> 이라는 용어가 있답니다 혹시 들어보셨나요?</p>
<p>자세한 코드레벨의 구현을 보고싶으시다면, <a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/content/res/FontScaleConverterFactory.java">이 링크</a>를 참조하셔도 좋습니다.</p>
<p>Android 14 버전에서 본격적으로 적용된 개념인데요, 모바일 화면을 개발하다보면 다음과 같은 상황을 겪으신 적이 종종 있으실 겁니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/8ff608d9-5f83-4a8f-9cd0-99d37fa4c1da/image.png" alt=""></p>
<p>데스크탑에 비해 작은 화면 안에, 복잡한 UI 레이아웃을 가지고 있는 화면에서 UI 에 표시되는 폰트의 크기가 일정 사이즈를 넘어갈 경우,
개행(line break)으로 인해 전체 레이아웃이 무너지거나, 위와 같은 경우처럼 레이아웃을 강제하다 보니, 텍스트의 겹침, 생략, 표기 오류등이 일어나게 되죠.</p>
<p>이때 새로운 아이디어가 도입되게 되요.
노인, 저시력자등을 위한 폰트 사이즈 설정의 핵심은 작은 텍스트를 크게 키우는것이지, 이미 큰 폰트를 더욱 크게 바꾸는건 시인성 증진에 큰 도움이 되지 않는다는 것이죠.</p>
<p>그래서 기존의 모든 폰트의 크기를 n 배 확대하는 선형적인 모델에서,
큰 폰트는 조금 확대하고 작은 폰트는 유저가 의도한 만큼 확대하는 비선형적 크기 조정 곡선을 사용하는 모델로 변경해, </p>
<p>다양한 크기의 텍스트 간의 계층구조를 유지함과 동시에 
(H1 이 H3 과 크기가 동일해 진다면, 유저는 디자인에서 의도한 UI 간 계층 구도를 이해하기 힘들겠죠?) 
텍스트가 잘리거나 표시되는 크기가 너무 커져 읽기 어려워지는 경우를 완하하는데 도움을 줄 수 있어요.</p>
<p>이제 저의 실무 이야기를 드려볼께요.</p>
<p>저는 Flutter를 사용해 Cross platform 앱을 개발하고 있답니다.</p>
<p>대부분의 코드를 작성할때, kotlin, swift 를 떠나 dart 라는 언어로 Flutter 라는 프레임 워크에서 작업을 하지만, 위에 설명한 non linear font scale 처럼 빌드 대상이 되는 OS 의 breaking change 가 있을 경우,</p>
<p>제가 사용하는 Flutter 에도 그 여파가 미치는 경우가 종종 발생해요.
<img src="https://velog.velcdn.com/images/wongue_shin/post/d42230b4-0d5f-45be-ad9e-e8c324e0eb8b/image.png" alt=""></p>
<p>이런식으로 업데이트가 일어나기 전에는 Os에서 제공하는 textScaleFactor 라는 변수를 받아와 font size 에 곱하는 단순한 선형적 모델을 사용했지만, Flutter 의 버전 업데이트 이후로 해당 필드가 dprecated 되어 새로운 방식을 적용해야 했어요.</p>
<p>그래서 저는 이전에 설명한 non linear font scale 에 대해서 조사한 이후 이를 적용해보려 했답니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/054d6e52-95d2-45a8-b077-9d30ace89fbd/image.png" alt=""></p>
<p>하지만, Flutter 프레임워크에서 제공하는 TextScaler 의 실 구현체가 _ClampedTextScaler 와 LinearTextScaler 두개 뿐인것을 확인했어요. (_UnspecifiedTextScaler 는 그 이름에서 알 수 있듯이 일반적으로 사용할 수 있는 클래스가 아니랍니다.)</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/48d66d5f-7e52-4011-a52b-3261f79a967d/image.png" alt="">
위의 목적을 달성하기 위해선 LinerTextScaler 는 선택할 수 없고, 
clampedTextScaler 는 fontSize 를 조정할때 clampDouble 이라는 함수를 사용하는데, 이는 단순히 설정한 최소-최대값을 벗어나는 입력을 보정해주는 함수라는걸 알 수 있었어요. </p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/327d52ef-6da3-4cb0-81e5-23750c411154/image.png" alt=""></p>
<p>하지만, 저는 단순한 범위 지정 clamping 보다는 좀더 부드러운 보정을 UI 시스템에 주고 싶었어요.
급격하게 cut-off 되는 변화보단, UI의 부드러운 변화를 주고 싶었어요.</p>
<p>여기서부터 아이디어를 떠올리기 시작합니다.
저는 이때 생각한 함수는 tanh 하이퍼볼릭 탄젠트 함수를 생각했습니다.
(이 게시글에선 함수의 정의는 별로 신경쓰지 않을꺼에요. 우리에게 중요한건, 이 함수가 어떻게 정의되었는지가 아니라, 어떤식으로 응용할 수 있는지를 알아보는거니까요.)</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/0706a64b-7a6d-462a-84a3-32864e4d0fa6/image.png" alt=""></p>
<p>tanh(x) 함수의 개형은, 최소-최댓값을 부드럽게 이어주는 모양을 가지고 있고,  -1, +1 에 수렴한다는 점이 매우 사용하기 좋은 모양이라 생각했어요.</p>
<p>또한 삼각함수와 비슷한 속성을 가지고 있기 때문에, x 를 적절히 나누면 함수의 위상을 조정할 수 있다는 장점도 가지고 있구요.</p>
<p>이제 저는 이 함수를 가지고 적절히 조절해가며 
보정함수{(OS에서 주는 폰트 스케일 펙터) * (우리 디자인 시스템에서 정의한 폰트 사이즈)} 가 우리의 의도에 맞게 동작하도록 설계할꺼에요.</p>
<p>일단, 이 함수의 출력은 글자의 변화되는 배율을 의미할꺼에요.
배율이 -1 배라는건 있을수 없는 상황이니, 함수 전체를 y축으로 1 평행이동 시켜보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/737b573a-0b9b-49bf-ab28-5a78624995a8/image.png" alt=""></p>
<p>아주 마음에 드는 모양이 나왔어요. </p>
<p><a href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-scale.html">W3C 에서 정의한 텍스트 크기 조정</a>또한, 200% 가 넘어가는 폰트 크기 조정에 대해서는 말하고 있지 않고 있기 때문이에요.</p>
<p>여기서 추가로 함수의 스케일을 조정할 필요 없이 적절히 몇개의 인자만 더해주면 될 것 같네요.</p>
<p>두번째로는 함수를 x 축으로 오른쪽으로 1 만큼 평행이동을 시켜주어야 할것 같아요.
아무런 폰트 스케일 조정이 없는 상황에서는 함수의 입력값 == 출력값이 되어야 하기 때문이에요.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/52108c81-0055-40ea-9c23-d58927b3da51/image.png" alt=""></p>
<p>마지막으로, 하나의 독립변수를 추가해야해요. 
저희는 글자 크기가 클 수록 변화에 대해 둔감하고, 작은 크기의 폰트일수록 스케일 증감에 대해 민감하게 반응하길 원하기 때문이에요.</p>
<p>tanh(폰트 크기 변수 * (시스템 폰트 펙트)) </p>
<p>위와같은 모양의 식을 구성한뒤, 적절한 모양을 갖도록 수정하면, 다음과 같은 그래프를 얻을 수 있어요.
<img src="https://velog.velcdn.com/images/wongue_shin/post/ffa26191-3330-4563-b11b-e26fe1fd5066/image.png" alt=""></p>
<p>a(폰트 크기) 가 10과 같이 작은 사이즈일 경우에는 y = x 에 가까운 크기 조정결과를 얻을 수 있고,
<img src="https://velog.velcdn.com/images/wongue_shin/post/9c434327-f128-4b7f-a268-9a3bc941a5fb/image.png" alt=""></p>
<p>폰트 크기가 50 과 같이 큰 경우에는 매우 둔감하게 움직이는 조정결과를 얻을 수 있어요.</p>
<p>이제 이를 코드로 옮긴다면,
<img src="https://velog.velcdn.com/images/wongue_shin/post/c769b729-aa97-4a92-8ae2-d71936889a09/image.png" alt=""></p>
<p>이런식으로 구현 할 수 있을꺼 같습니다.
 (Dart 의 math 패키지에서 tanh 함수를 기본적으로 제공하지 않기 때문에 정의를 기반으로 직접 구현했어요.)</p>
<p>마지막으로, 구현한 솔루션을 검증 할 수 있는 테스트 코드를 작성해야합니다.</p>
<ol>
<li>코드는 의도한 스펙을 달성했는지를 검사해야하고</li>
<li>제한된 시간복잡도 하에서 동작하는지를 검사해야합니다.</li>
</ol>
<p>이 코드는 화면에 표시된 모든 텍스트에서 한번씩 호출될 거라 생각 할 수 있어요.
따라서 그 시간복잡도가 매우 중요한데요, 적어도 1/60s ~= 0.016 s 안에 실행되려면, 
로직의 시간복잡도가 O(1) 과 O(N) 의 사이에 위치하고 있어야해요.</p>
<p>이를 테스트 코드로 검증하자면,</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/a13f9a64-7780-4011-9a9b-d217dd598992/image.png" alt="">
<img src="https://velog.velcdn.com/images/wongue_shin/post/a4de004f-e644-43ff-ac05-03df93df8da0/image.png" alt=""></p>
<p>아주 정밀하지는 않지만, 이러한 테스트케이스를 통과하면 실제로 활용할 수 있는 스펙을 충족했다 말할 수 있어요.
<img src="https://velog.velcdn.com/images/wongue_shin/post/ea9ff940-6a85-4f7c-8d02-ec5102d2c3f8/image.png" alt=""></p>
<p>물론 두가지의 테스트케이스를 모두 통과했답니다.</p>
<p>저는 이 글을 쓰며 프로그래밍은 응용수학이라는 것을 알리고 싶었어요.
단순히 면접이나 기초 지식을 위해서가 아닌, 수학적 사고력은 실무레벨에서도 적극적으로 쓰일 여지가 많다는 사실을 여기까지 읽으신 분들이 알아가시면 좋겠다는 생각을 하며 글을 마무리해 봅니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[빵빵이의 일 두 배로 잘하는 법 ~!]]></title>
            <link>https://velog.io/@wongue_shin/%EB%B9%B5%EB%B9%B5%EC%9D%B4%EC%9D%98-%EC%9D%BC-%EB%91%90-%EB%B0%B0%EB%A1%9C-%EC%9E%98%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@wongue_shin/%EB%B9%B5%EB%B9%B5%EC%9D%B4%EC%9D%98-%EC%9D%BC-%EB%91%90-%EB%B0%B0%EB%A1%9C-%EC%9E%98%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Sat, 23 Dec 2023 09:53:22 GMT</pubDate>
            <description><![CDATA[<p>취업난 기간에 열심히 준비해 간신히 프론트앤드 개발자로 합격한 빵빵이.
더는 길거리 시절로 돌아갈 수 없어 일을 열심히 쳐내가고 있던 와중..</p>
<p>어느날 갑자기 기획자 돈돈이가 화면을 보더니, 매인 메뉴를 표현하는 에셋 이미지가 보이지 않는다고 한다.
돈돈이: &quot;빵빵아, 메뉴 화면이 안 보여..&quot;
<img src="https://velog.velcdn.com/images/wongue_shin/post/a4a5c242-37f6-4b88-b067-526f7b8939bc/image.png" alt="빵빵아 메뉴 화면이 안 보여"></p>
<p>실시간으로 광고를 태워 들어오는 유저가 이탈을 해버리고 있는 절체절명의 상황!
빵빵이는 거지가 될 수 없기에 코드를 열심히 분석해본다.</p>
<p>누군가 프로젝트 안의 이미지파일 이름을 마구 바꿔버린 것을 발견하고, 
빵빵이는 이쒸... 누구야... 라고 투덜거리며 깨진 이미지 경로를 수정한 뒤 배포를 하고 이슈 티켓을 닫으려는 순간</p>
<p>갑자기 미래의 로봇빵빵이가 나타나 빵빵이의 머리를 때려버린다.
<img src="https://velog.velcdn.com/images/wongue_shin/post/326141b0-2b51-45a3-b33a-9e02a19575f5/image.png" alt="미래의 로봇빵빵이"></p>
<p>로봇빵빵이: &quot;얀마 일 제대로 해!&quot;</p>
<p>듣자하니 앞으로도 옥지가 마구 파일 경로를 바꿔버리며 유저는 자꾸 이탈하게 되고, 그 길로 다니는 스타트업은 폐업의 길로 접어들었다고 한다.
빵빵이는 그 뒤 거리를 전전하다가 납치되어 로봇빵빵이가 되어버렸다고 하는데..</p>
<p>빵빵이는 어이가 없었지만, 뭔 말을 하는지 일단 들어나 보기로 했다.</p>
<h3 id="문제의-원인-분석하기">문제의 원인 분석하기</h3>
<p>로봇빵빵이: &quot;애초에 이미지 경로나 이름을 바꾼다고, 이미지가 안 보이는것 자체가 문제야.
이번 경로를 수정하는 건 언발에 오줌 누기 같은 임시방편밖에 안 되는 거지.
현상을 해결하려고 하지 말고, 문제의 원인을 파악해 다신 재현이 안 되도록 똑똑하게 일을 해봐.&quot;</p>
<p>빵빵이: &quot;아니 그럼 어떻게 하란 말이에요? 프레임워크에서 파일 경로를 문자열로 받는 걸 제가 바꿀 순 없잖아요!&quot;</p>
<p>로봇빵빵이: &quot;파일 이름, 경로를 관리하는 enum을 생성해서 경로를 바로 입력하는 게 아니라 이넘의 값을 넘겨주는 식으로 바꾸면 되잖아.&quot;</p>
<pre><code class="language-dart">/// 예제의 코드는 Dart, Flutter 이지만, 
/// 아이디어 자체는 모든 언어, 프레임워크에서 사용할 수 있습니다!

enum AssetFiles {
  TEXT_LOGO(&#39;somePath/text_logo.svg&#39;)
  ...
  SPlASH_IMG(&#39;somePath/text_logo.png&#39;);

  final String path;

  const AssetFiles(this.path);
}</code></pre>
<p>빵빵이는 억울하다. 
빵빵이: &quot;이씌... 그래도, 이넘안의 경로가 오타가 나거나, 위치를 바꿔버리고 반영하지 않는다면 오류가 나는 건 똑같잖아요!&quot;</p>
<p>로봇 빵빵이: &quot;그렇다면, 에셋 파일 이넘을 관리하는 코드를 자동으로 생성하면 되는 거잖아.&quot;</p>
<p>빵빵이: &quot;그걸... 어떻게 해요?&quot;</p>
<p>로봇빵빵이: &quot;이미지, 폰트파일등은 하나의 &quot;assets&quot; 라는 디렉토리 안에서 관리하지.
몇개 의 파일은 2~3단계의 디렉토리로 감싸져 있지만, 이거 우리 알고리즘 공부할 때 봤었던 트리구조하고 같잖아.</p>
<p>그렇다면, assets 디렉토리 밑의 모든 파일을 가져오는 문제는, 트리를 순회하며 모든 leaf node 리스트를 가져오는 문제로 볼 수 있지 않겠니.&quot;</p>
<p>빵빵이: &quot;아, 그러면 스크립트 안에서 제가 처음에 asset 디렉토리를 찾아올 수 있도록 설정한 이후, 그걸 기반으로 DFS 서치를 하면 되는거 군요! &quot;</p>
<pre><code class="language-dart">void main() {
    try{
    final Directroy assetRootDir = Directroy(&quot;rootpath&quot;);
    final Directroy genDir = Directroy(&quot;genDirPath&quot;);
    final File outputFile = File(&quot;genDirPath/asset_paths.dart&quot;);

    final List&lt;File&gt; files = [];
    collectFilesDFS(assetRootDir, files);
  }
}

void collectFilesDFS(final Directory dir, final List&lt;File&gt; fileList) {
  final Lst&lt;FileSystemEntity&gt; entities = dir.listSync();

  // 자식 노드를 순회하며
  for (final FileSystemEntity entitiy in entities) {
    if (entity is Directory) {
      // 자식 노드가 디렉토리면 재귀호출을,
        collectFilesDFS(entity, fileList);      
      continue;
    }
        if (entity is File) {
      // 자식 노드가 파일이면 리스트에 추가한다.
      fileList.add(entity);
    }
  }
}</code></pre>
<p>빵빵이: &quot;가져오는 건 알겠는데... 이걸 어떻게 enum 으로 만들죠?&quot;</p>
<p>로봇빵빵이: &quot;문자열의 형태로 가공한 뒤, outputFile 에 쓰면 되는 거지.&quot;</p>
<pre><code class="language-dart">void main () {
  ...
  final File outputFile = File(&quot;genDirPath/asset_paths.dart&quot;);

  final List&lt;File&gt; files = [];
  collectFilesDFS(assetRootDir, files);

  // 가져온 file list 를 다음의 형태로 가공하는 코드
  // enumEntties = &quot;    FILE01_SVG(filePath),
  // ...,
  //    FILE_PNG(filePath),&quot;;
  final String enumEntries = files.map((final File file) {
    final String? path = regex.fistMatch(file.path)?.group(0);
        if (path == null) return &#39;&#39;;
    return &quot;   ${path.toUpperCase().replaceAll(&quot;/&quot;, &#39;_&#39;).replaceAll(&#39;.&#39;, &#39;_&#39;)} (\&#39;assets/$path\&#39;)&quot;.join(&#39;,\n&#39;);
  });

  // outputFile 의 경로에 가공한 코드를 저장한다.
    outputFile.wirteAsStringSync(&quot;&quot;&quot;enum AssetPath {
    ${&#39;assets&#39;;}

    const AssetPaths(this.path);
    final String path;
    }&quot;&quot;&quot;);
}</code></pre>
<p>빵빵이: &quot;좋아요. 이제 스크립트는 작성했는데, 이걸 어떻게 자동으로 실행시키게 하죠?&quot;</p>
<p>로봇빵빵이: &quot;package.json 이나, Flutter 의 경우에는 melos.yaml 파일의 스크립트를 바꾸면 되는 거야.&quot;</p>
<pre><code class="language-json">// json 의 경우
&quot;scripts&quot;: {
    &quot;start&quot;: &quot;node asset_codegen.js &amp;&amp; react-scripts start&quot;,
    &quot;build&quot;: &quot;react-scripts build&quot;,
    &quot;test&quot;: &quot;react-scripts test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;
  },

// yaml 의 경우
scripts: 
    run: dart run asset_codegen.dart &amp;&amp; flutter run</code></pre>
<p>빵빵이는 문제를 해결해서 기쁜 마음으로 PR 을 작성해 병합한 이후, 소주나 뿌시러 퇴근하려고 했다.</p>
<p>그때, 로봇빵빵이가 빵빵이의 머리를 한번더 때려버리는데..</p>
<p>빵빵이: &quot;아씨 왜 때려요!&quot;</p>
<p>로봇빵빵이: &quot;옥지를 봐봐.&quot;</p>
<h3 id="재발방지를-위한-방법">재발방지를 위한 방법</h3>
<p>옥지는 PR이 병합되어서 도구가 제공되었지만, 그런 건 신경 안 쓰고 이전 방식 그대로 코드를 적고 있는 것이다.
그렇다. 빵빵이는 아무리 도구를 제공한다고 하더라도 동료가 쓰지 않으면 운영상 리스크는 해결되지 못하는 것을 알아버렸다.</p>
<p>여전히 로봇빵빵이가 되는 미래는 바뀌지 않은 것이다!</p>
<p>빵빵이: &quot;옥지얌... 내가 올린 PR 봤어..?&quot;</p>
<p>옥지: &quot;뭐&quot;</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/0ee37b74-e088-4461-a042-f9dc547f7275/image.png" alt="옥지"></p>
<p>빵빵이: &quot;아...아니얌...&quot;</p>
<p>빵빵이는 옥지의 주먹이 무서워 차마 말하지는 못하고, 투덜거리며 자리에 돌아온 뒤 
옥지가 AssetPath enum 을 사용할 수 밖에 없도록 만들어, 미래를 바꾸겠다고 다짐한다.</p>
<p>빵빵이: &quot;로봇빵빵이님.. 어떡하면 옥지를 제가 만든 이넘을 쓰도록 만들 수 있을까요?&quot;</p>
<p>로봇빵빵이: &quot;커스텀 린트를 만들어서, AssetPath 를 쓰지 않으면 CI를 통과하지 못하도록 만들어.&quot;</p>
<p>빵빵이: &quot;그거.. 어떻게 하는 거죠?&quot;</p>
<p>로봇빵빵이: &quot;모든 프레임워크는 자신만의 analyze rule 을 설정하는 방법을 제공해.
Dart 의 경우는 analyze Plugin 의 형태로 제공하는데, 이걸 직접 활용하는것 보다는 좀더 사용하기 좋은 도구를 제공해주는 custom_lint 를 사용할 수 있지.&quot;</p>
<p>로봇 빵빵이는 커스텀 린트 패키지를 새로 만든 뒤, 커스텀 린트를 작성하는 법을 알려줬다.</p>
<pre><code class="language-dart">import &#39;package:custom_lint_builder/custom_lint_builder.dart&#39;;

PluginBase createPlugin() =&gt; _빵빵이플러그인();

final class _빵빵이플러그인 extends Pluginbase {
  @override
  List&lt;LintRule&gt; getLintRules (final CustomListConifgs configs) =&gt; [
    옥지얌_코드_그렇게짜는거_아니야(),
  ];
}

final class 옥지얌_코드_그렇게짜는거_아니야 extends DartLintRule {
  옥지얌_코드_그렇게짜는거_아니야() : super(code:_code);

  static const LintCode _code = LintCode(
    name: &quot;옥지얌... 코드 그렇게 짜는거 아니야...&quot;,
    problemMessage: &quot;내가 만든 AssetPath enum 사용해줘...&quot;,
  );

  @override
  void run(
    final CustomLintResoler resolver,
    final ErrorReporter reporter,
    final CustomLintContext context,
  ) {
    context.registry
      .addInstanceCreationExpression((final InstanceCreationExpression node) {
        // SvgPicture 인지 확인하는 방법.
        final bool isSvgPicture = node.staticType?.getDisplayString(withNullablity: false) == &#39;SvgPicture&#39;;
        // Image 인지 확인하는 방법.
        final bool isImage =  node.staticType?.getDisplayString(withNullability: false) == &#39;Image&#39;;
        // asset 이미지를 불러오고있는지 확인하는 방법.
        final bool isAsset = node.constructorName.name?.name.contiains(&#39;asset&#39;) ?? false;

        // svg 나 png 파일을 불러오면
        if ((isSvgPicture || isImage) &amp;&amp; isAsset) {
          // 생산자 함수에 제공되는 인자중에 &quot;AssetPaths&quot; 인 인자가 있는지 검사 한 후
          final bool isAssetPathContains = node.argementList.arguments
            .map((final Expression e) =&gt; e.begin.lexeme).contains(&quot;AssetPaths&quot;);

          // 포함되어있지 않다면 린트에 오류를 보고한다.
             if (!isAssetPathContains) {
            reporter.reportErrorForNode(_code, node);
          }
        }
      }
  }
}
</code></pre>
<p>작성한 린트를 프로젝트에 적용하니, 옥지는 더 이상 AssetPath enum 을 사용하지 않고는 배길 수 없어졌다!</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/264a484e-fbb1-4446-9b8c-969ddbeee7be/image.png" alt="옥지얌..코드 그따위로짜는거 아니야"></p>
<p>빵빵이는 로봇빵빵이의 도움으로 정말 문제를 해결하고 재발을 방지하기 위해서는 현상을 보는 것이 아닌, 원인을 분석하고 이를 기술적으로 재현 방지를 할 수 있도록 팀에게 기여해야 한다는 교훈을 얻게 되었다.</p>
<p>그리하여 어렵게 취업한 스타트업이 파산하지 않아, 로봇빵빵이로 변하는 불행한 미래는 사라지고 빵빵이는 옥지랑 행복하게 살았다고 한다~</p>
<p>2023.12.24 추가)
이 주제와 관련해 제가 실제로 사이드 프로젝트에서 작성한 코드가 궁금하신분들은
아래의 PR 을 참고해보셔도 좋을것 같습니다!
<a href="https://github.com/L1A-crew/zzekak_client/pull/6">https://github.com/L1A-crew/zzekak_client/pull/6</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스타트업이란 밴드에서 개발자는 어떤 포지션일까요?]]></title>
            <link>https://velog.io/@wongue_shin/%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%9D%B4%EB%9E%80-%EB%B0%B4%EB%93%9C%EC%97%90%EC%84%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EC%96%B4%EB%96%A4-%ED%8F%AC%EC%A7%80%EC%85%98%EC%9D%BC%EA%B9%8C%EC%9A%94</link>
            <guid>https://velog.io/@wongue_shin/%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%9D%B4%EB%9E%80-%EB%B0%B4%EB%93%9C%EC%97%90%EC%84%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EC%96%B4%EB%96%A4-%ED%8F%AC%EC%A7%80%EC%85%98%EC%9D%BC%EA%B9%8C%EC%9A%94</guid>
            <pubDate>Sat, 09 Dec 2023 08:42:59 GMT</pubDate>
            <description><![CDATA[<h3 id="만약--it-스타트업을-하나의-밴드-공연으로-비유한다면-저희-개발자의-포지션은-무엇일까요">만약  IT 스타트업을 하나의 밴드 공연으로 비유한다면, 저희 개발자의 포지션은 무엇일까요?</h3>
<p>IT 회사니까 개발자가 당연히 주인공인 보컬의 역할일까요?,
아니면 보컬 다음으로 인기가 많은 기타? 
여러분은 어떤 포지션이 어울린다고 생각하시나요?</p>
<p>저는 개발자의 포지션은 <strong>프론트맨</strong>보다는 뒤에서 지원을 해주는 베이스기타와 가장 비슷하다고 생각한답니다.</p>
<p>과연 왜 그럴까요?</p>
<blockquote>
<p>💡 프론트맨이란? :  </p>
<ol>
<li>락 밴드의 리드 싱어 혹은 기타리스트.</li>
<li>조직을 대표하고 그 조직의 이미지를 대중에게 더욱 매력적으로 만들기 위해 노력하는 사람.</li>
</ol>
</blockquote>
<h3 id="신규-서비스를-런칭하거나-소규모-서비스의-운영에서-개발자의-역량이-단기-성과에-큰-연관성이-없기-때문입니다">신규 서비스를 런칭하거나, 소규모 서비스의 운영에서 개발자의 역량이 단기 성과에 큰 연관성이 없기 때문입니다!</h3>
<p>왜냐면, 데이터가 충분히 쌓이지 않은 환경에서 신규 서비스를 런칭하거나, 
새로운 기능을 선보일때 시장이 어떻게 받아드릴지는 정말 미지수이기 때문이에요. 
(그리고 아마 높은 확률로 아무런 반응을 보이지 않겠죠ㅠ)</p>
<p>아무리 개발자가 아름다운 패턴과, 극한의 퍼포먼스를 구현해도, 결국 사용자가 없으면 그 코드는 충분한 가치, 즉 <strong>매출</strong>을 만들어 내지 못합니다.</p>
<p>이러한 상황에서 초기 고객을 유인해 서비스에 랜딩을 유도할 수 있는 방법은 매력적인 캐치 프라이즈를 사용한 마케팅, 재치 있는 기획들이죠.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/9765e714-6c54-4813-97bd-115230116503/image.png" alt=""></p>
<h3 id="초기-사업성을-검증-하는-개발비용은-매우-비싼-금액입니다">초기 사업성을 &#39;검증&#39; 하는 개발비용은 매우 비싼 금액입니다.</h3>
<p>아마존과 쿠팡과 같은 의도한 적자를 감수하는 특별한 모델이 아니면
사업 초기에 비즈니스 모델의 검증에서는 웹페이지가 없으면 이메일을 보내며 검증 할 수 있고
이메일도 보내기 어려우면 인스타그램과 블로그를 운영하며,
그것 또한 맞지 않는다면 직접 발로 뛰며 오프라인으로도 사업성을 검증할 수 있기 때문입니다.</p>
<p> 조금 더 단순한 형태로서 먼저 사업성을 검증하는 것이 현 스타트업 혹한기에서는 생존하기 더욱 유리한 방법이기 때문이에요.
예시 중 하나로는 <a href="https://www.hyungjoo.me/%EA%B5%AD%EB%AF%BC%EB%8C%80-%EB%B0%9C%ED%91%9C-%EC%9D%B8%ED%94%84%EB%9E%A9/">인프런의 초기 상황</a>이 있겠습니다. </p>
<p>이러한 방식이 생존에 유리한 이유는 기획안을 변경하는 작업이 작성한 코드를 변경하는것 보다 훨씬 쉽다는 이유에 기반해요.</p>
<p>개발 공수를 투자하기 이전에 기획 단계에서 가능한 많은 예외 상황과 고려하지 못한 케이스를 찾아 수정하는 것이 총비용을 아낄 수 있는 좋은 방법이 될 수 있어요.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/0543582b-c831-4584-87ab-8fddf22f38a8/image.png" alt=""></p>
<h3 id="초기-스타트업에서-사업팀과-개발조직의-관계는-길거리-버스킹-공연의-보컬과-베이시스트의-관계와-비슷합니다">초기 스타트업에서 사업팀과 개발조직의 관계는, 길거리 버스킹 공연의 보컬과 베이시스트의 관계와 비슷합니다.</h3>
<p>만약 우리가 길을 걸어가다 공연에 눈길이 간다면
그 이유는 베이스 기타의 소리보단 일반적으로는 보컬의 매력적인 목소리, 혹은 외모에 더 관심이 가기 쉬운 것과 비슷한 관계를 가지게 됩니다. (물론 저는 베이스 기타를 정말 좋아한답니다)</p>
<p><em>&#39;시장의 반응을 전혀 예측할 수 없다, 그리고 아마 지금 시도는 높은 확률로 성공하지 못한다&#39;</em> 라는 이유로 개발자의 역할은 <strong>&#39;일단 최대한 많은 기능을 출시하도록 도와주는 것&#39;</strong> 이 조직에서 기대하는 제1목표가 됩니다.</p>
<p>이러한 상황에서 개발속도가 가장 중요하게 여겨지게 되는 이유는 초기 스타트업의 위기 때문이에요.</p>
<p>신규 스타트업은 창업 이후 곧바로 위기에 닥치게 되는데요,
그 위기는 바로 <strong>&quot;시드머니가 다 소진되기 이전에 어떻게든 시장에서 유의미한 반응을 받아야 한다.&quot;</strong> 입니다.
그 외의 모든 일은 부차적인 문제입니다. 첫 번째 고비를 넘지 못하면 그 회사는 생존 할 수 없기 때문이죠.</p>
<p>이러한 과정은 <strong>타임어택</strong>이라고 볼 수 있습니다.
남은 시간과 탄창(자금)을 다 소진하기 이전에 필사적으로 어떠한 목표라도 맞춰야 하는 게임이기 때문이에요.</p>
<p>이때, 목표물을 맞추는 사수의 역할은 사업, 기획, 운영의 영향이 주도적이게 됩니다.
이전에 설명했듯이, 초기 사용자를 앱에 랜딩하게 만드는 건 앱의 좋은 퍼포먼스 보단, 마케팅과 기획이 주도적인 역할을 수행하기 때문이에요.</p>
<p>그럼 이러한 상황에서 개발팀의 역할은 무엇일까요?
저는 개발팀의 역량은 보급병에 가깝다고 생각합니다.
역량이 부족한 개발팀은, 단 한발의 총알밖에 제공하지 못해 올림픽 사격 선수들만 목표(시장의 반응)를 달성 할 수 있지만,</p>
<p>역량이 충분한 개발팀의 경우에는 충분한 탄약을 제공해 줘 제한 시간 내에 사수가 여기저기 최대한 많은 표적지를 노려볼 수 있게 되는 거죠.</p>
<p>결국 주니어~미드래벨 개발자의 역할은, 초기자본이 다 떨어지기 전에 최대한 많은 시도를 사업팀이 해볼 수 있는 기회를 제공하는 것이라 정리 할 수 있겠습니다.</p>
<blockquote>
<p>여담) 시니어를 따로 분리한 이유는,
개발자도 시니어 레벨이 되면 개발자 대부분에 대한 관리 감독, 높은 퀄리티의 코드 산출과 더붙어, 자신의 경험을 바탕으로 사업 전반에 대한 의견도 제시할 책임과 권한이 부여되는 것 같더군요. 쉽지 않은 자리인 것 같습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/94b0b65a-5b7d-4749-9889-bd41086a969d/image.png" alt=""></p>
<h3 id="그렇다면-빠르기가-전부일까">그렇다면 빠르기가 전부일까?</h3>
<p>지금쯤이면 여러분들은 코드 퀄리티는 생각하지 않고 최대한 빨리 구현만 하면 다 인 건가? 라는 생각이 들 수 있을 거에요.
시장의 초기 반응을 확보하는 데에는 개발자의 능력 중, 구현 속도가 가장 중요한 요소인 것은 사실입니다.</p>
<p>하지만 일단 반응이 온 뒤는 어떨까요? 
시장에서 유의미한 성과를 얻었다 판단한다면, 전사적으로 드라이브를 걸어야 해요.
이때, 재빠른 기능의 추가와 CS 대응, 사용자의 요구사항을 적극적으로 반영하기 위해선 단단하고 유연한 초기 설계가 매우 중요해져요.</p>
<p>어렵게 잡은 기회로 들어온 사용자들이 버그로 인해 실시간으로 이탈되어 가고 있는 상황에서, 더티 코드로 인해 이를 재빨리 잡지 못한다면... 생각만 해도 끔찍하네요.</p>
<p>따라서 개발자는 초기 스타트업에서 <strong>&quot;확장 가능성&quot;</strong> 과 <strong>&quot;구현속도&quot;</strong> 라는 두 가치를 저울 해가며 적절한 균형을 잡으려 노력해야 합니다. (이를 흔히 trade-off라 말하죠.)</p>
<p>어떤 부분에 확장 가능성을 충분히 고려해야 하는지, 적당히 빨리 시장에 내보내고 필요하다면 다시 고치면 되는지에 대한 판단은 작업하는 개발자 본인 말고는 알 수 없어요.</p>
<p>개발자 또한 이러한 trade-off를 적절히 수행하기 위해서는 높은 기술적인 역량은 물론이고, 그에 못지않게 비즈니스 도메인에 대한 이해가 중요해져요.</p>
<p>예를 들자면, 실물 상품을 다루는 커머스 비즈니스의 경우에는 고객의 주소지 정보가 비즈니스에 매우 중요한 정보이겠지만
제품을 직접 배송하지 않아도 되는 비즈니스, 예를 들어 게임의 경우에는 고객의 주소 정보는 비즈니스에 정말 핵심적이지는 않겠지요?</p>
<p>이처럼 본인이 작업하고 있는 비즈니스가 과연 어떤 시장의, 어떤 비즈니스 모델을 가지고 있는지에 대한 이해가 결국 제품의 UX 퀄리티로서 나타난답니다.</p>
<h4 id="또한-이러한-trade-off-가-정말-어려운-점은-정답이-없다는-점입니다">또한 이러한 trade-off 가 정말 어려운 점은, 정답이 없다는 점입니다!</h4>
<p>어떠한 대원칙을 따르며 진행할 수 이는 영역이 될 수 없고, 그때그때 보다 적절하다고 믿는 방향으로 결정해야 하기 때문이죠.</p>
<p>궁극적으로 추구해야 하는 방향이라는 어렴풋한 나침반은 가지고 있지만.
가장 빨리 가는 길이 지나고 보면 돌아가는 길이고, 
일반적인 정답 또한 때와 상황에 따라서 이번에는 다를 수도 있어요.</p>
<p>이러한 부분이 소프트웨어 엔지니어링을 정말 어렵고 가치 있게 만들고, 이러한 결정을 잘 수행하는 사람은 공학을 <strong>Technology</strong>에서 <strong>Art</strong> 의 영역으로 승화시키는 사람이라 생각해요.</p>
<p>이 글을 읽고 계신 여러분도 앞으로 주도적으로 확장 가능성과 개발 속도의 trade-off를 판단하고 왜, 얼마나 투자해야 하는지를 팀에게 적극적으로 공유해보려 노력해 보시는 건 어떨까요?</p>
<h3 id="결론">결론</h3>
<p>이제 글의 시작에서 말씀드린 스타트업이 만약에 밴드라면, 개발자의 포지션은 베이스 기타에 가깝다고 말씀드린 제 말을 조금은 동의하실지 모르겠네요.</p>
<p>필드의 최전선에서 고객의 피드백을 직접적으로 받는 사업, 운영팀에 대해 존중과 응원을, 
개발자가 스타트업 조직에서 해야 할 업무는 이를 돕기 위한 기술적인 지원이라는 걸 마지막으로 강조하며 글을 마무리해 봅니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상반기 회고]]></title>
            <link>https://velog.io/@wongue_shin/%EC%83%81%EB%B0%98%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@wongue_shin/%EC%83%81%EB%B0%98%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 16 Jul 2023 08:26:25 GMT</pubDate>
            <description><![CDATA[<p>작년 하반기에는 서비스 출시를 목표로 정신없이 달려왔다면,
올해 상반기에는 유지보수 + 추가기능개발을 하려 달려온 것 같다.</p>
<p>출시 직전에는 출시만 하면 좀 널널해질 줄 알았지 ㅋㅋㅋ 😂
하지만 멈춰있는 차를 만드는것 보단, 달리고있는 차의 엔진을 바꾸는게 더 어려운걸 그땐 몰랐다.</p>
<p>2023 상반기에는 정말 많은 일들이 있었고, 많은것 들이 변했는데 이제 한숨 가다듬고 하반기를 준비해기 위해서!</p>
<p>KPT로 상반기 회고를 진행해보고자 한다.</p>
<h3 id="keep-계속해야할-것">Keep (계속해야할 것)</h3>
<ul>
<li>여러 커뮤니티에 다른 개발자분들과 네트워킹을 한것.
성장에 대한 동기부여와 시야가 넓어지는 경험을 할 수 있었다.
무엇보다, 현업에서 어려운 문제를 어떻게 풀었는지에 대한 노하우를 공유하는 것 자체가 상당히 즐겁다.</li>
<li>OOP, Clean Code 에 대한 이해도를 높여가는것.
내가 작성했었던 코드의 리팩토링을 완료하며, 전반적인 코드 퀄리티에 대한 이해가 높아졌다 생각한다. 하지만, 대규모 리팩토링은 가능한 피하고, 조금씩 고쳐나가도록 하면 좋을것 같다.</li>
</ul>
<h3 id="problem-어려움을-느껴서-개선하고-싶은-것">Problem (어려움을 느껴서 개선하고 싶은 것)</h3>
<ul>
<li>운동을 꾸준히 하는 습관을 가지는것에 완전 대실패를 했다.
업무가 많다는 좋은 핑계를 댈 수 있었는데, 이제는 생존을 위해 운동을 시작해야한다.</li>
<li>꾸준히 블로그 포스팅을 작성하는것에 부분적으로 실패를 했다.
이 또한, 업무가 많다는 핑계였지만... 정말 물리적으로 불가능했냐면 그건 아니였던것 같다.</li>
</ul>
<h3 id="try-시도해볼-내용">TRY (시도해볼 내용)</h3>
<ul>
<li>모각코등을 통해 원하는 공부나 작업을 할 수 밖에 없는 시간을 만들어보자.
이제 글또 활동은 9기까지는 잠깐 멈추지만, 글또에서 만난분들과 별도로 활동하던가, 그래픽스 스터디원들과 활동해보면 괜찮을것 같다.</li>
<li>아침운동을 시도해보자.
저녁에는 일정이 생겨 꾸준히 시간을 내기 어려움을 느꼈다.
아침에 고정적으로 운동을 하는 시간을 만들어보자. 그러기 위해선 먼저 수면시간을 조절할 필요가 있다. 적어도 12시 전에는 자는 수면 패턴을 유지해야한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리팩토링 하지 마세요]]></title>
            <link>https://velog.io/@wongue_shin/%EB%A6%AC%ED%8C%A9%ED%84%B0-%ED%95%98%EC%A7%80-%EB%A7%88%EC%84%B8%EC%9A%94</link>
            <guid>https://velog.io/@wongue_shin/%EB%A6%AC%ED%8C%A9%ED%84%B0-%ED%95%98%EC%A7%80-%EB%A7%88%EC%84%B8%EC%9A%94</guid>
            <pubDate>Sun, 02 Jul 2023 11:36:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>3줄 요약
리팩토링은 신중하게 진행해야합니다.
프로그래머의 생산성은 측정하기 어렵기 때문에
그 비효율이 과대평가 되는 경향이 있습니다.</p>
</blockquote>
<p><a href="https://ko.wikipedia.org/wiki/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81">리팩토링</a>은 흔히 이야기되는 주제에 비해, 위험성이나 부작용에 대해서는 간과되어잇는거 같아 글을 적어봅니다.</p>
<h3 id="리팩토링-왜-하는걸까">리팩토링, 왜 하는걸까?</h3>
<p>작업 효율을 위해서 인건 이 글을 읽는 모든 분들이 알고 계실겁니다.</p>
<p>리팩토링을 진행하면 &#39;왜&#39;, &#39;어떤&#39; 효율이 증가하는걸까요?</p>
<h3 id="기술부채의-청산">기술부채의 청산</h3>
<p>마틴 파울러의 <a href="https://martinfowler.com/articles/is-quality-worth-cost.html">블로그 내용</a>에 따르면,
작업중에는 필연적으로 부스러기 코드 (cruft) 가 쌓입니다.
<img src="https://velog.velcdn.com/images/wongue_shin/post/f72228e2-7cdc-4df9-ad54-062e142e5156/image.png" alt="https://martinfowler.com/bliki/images/tech-debt/sketch.png"></p>
<p>부스러기 코드는 무엇일까요?
어플리케이션의 현 상황과 맞지 않은 가정들입니다.</p>
<ul>
<li>작업자 스스로가 코드를 작성하며 학습하며</li>
<li>비즈니스가 성장해 요구하는 기능이 달라지기도 하고 </li>
<li>시간이 지나며 사회와 기술이 발전해 요구하는 사항이 달라지기 때문입니다.</li>
</ul>
<p>즉 살아있는 서비스는 항상 변동하기 때문에, 어떠한 형태로던지 서비스와 잘 들어맞지 않은 부스러기 코드가 발생하는것은 필연적이란 겁니다.</p>
<p>이러한 기술부채는 새로운 기능을 작성할 때 작업자는 불필요한 코드를 파악해야 하게 만들기 때문에, 총 작업소요시간의 증가를 야기합니다.</p>
<p><img src="https://velog.velcdn.com/images/wongue_shin/post/5db4ee92-1975-4f0b-a232-8a22f632b6a8/image.png" alt="https://martinfowler.com/articles/is-quality-worth-cost/both.png"></p>
<p>기술 부채를 잘 관리한 소프트웨어 프로젝트 (파랑색) 는 그렇지 않은 프로젝트 (갈색) 에 비해 완만한 커브를 그리며 개발 난도가 상승할 거라 예측 할 수 있습니다.</p>
<p>하지만, 그래프의 정의역과 치역의 단위를 잘 살펴보면 y축은 개발된 누적 기능, x축은 시간입니다.
두 단위 모두 마땅한 기준단위를 측정하기 어렵습니다.</p>
<p>이 점에서 이 그래프는 pseudo-graph 임을 알 수있는데요.
그 말인즉슨, 모두 두 케이스가 저러한 경향성을 가진다는 것에는 이견이 없을겁니다.</p>
<p>하지만, 과연 &#39;언제&#39; 파랑색 그래프가 갈색 그래프를 추월하는지에 대해서는 명확한 시점을 계산하기 무척 어렵다는 것 입니다.</p>
<p>과연 한 프로젝트가 기술 부채를 따로 관리하지 않았을 때의 개발 불능영역 (기울기가 0에 수렴하는구간) 에는 언제 도달하게되는걸까요?</p>
<p>프로젝트 런칭 직후일까요? 혹은 런칭 후 1개월, 1년, 혹은 10년일까요?
여러분은 그 시점을 특정하기 정말 어렵다는것을 알 수 있을 겁니다.</p>
<p>또한 적극적으로 기술부채를 청산한 프로젝트는, 과연 어떤 이익을 얼마나 얻고 있는지 측정 할 수 있을까요?</p>
<h3 id="프로그래머의-성과-측정">프로그래머의 성과 측정</h3>
<p>이렇게 그 정확한 시점을 특정하기 어려운 이유는, 근본적으로 <a href="https://martinfowler.com/bliki/CannotMeasureProductivity.html">프로그래머의 성과는 측정하기 매우 어렵다</a> 라는 문제에서 기인합니다.</p>
<p>한 프로그래머의 성과를 line 수, 혹은 PR 수, 개발한 기능 수, 근무시간, 비즈니스적 성과등,
어떠한 기준점을 적용해도 이로 인한 부작용을 쉽게 생각하실 수 있을겁니다. 
(ex PR 을 무의미하게 잘게 나누어 올린다던지, 사이즈가 큰 기능을 피하고, 유지보수성 기능만 추가하며 총 개발한 기능수를 늘린다던지, ...)</p>
<p>결국 프로젝트의 생산성은 관리자와 실무자들의 주관적인 체감에 의존에 평가되는 경우가 잦은데
사람의 특성상 레거시 코드를 유지보수하는데에는 스트레스를 받기 쉽기 때문에, 자신이 작업하기 싫은 감정과,  프로젝트의 생산성을 동일시 할 위험이 있습니다.</p>
<p>그러나 리팩토링은 그 자체로서는 비즈니스에 끼치는 영향이 -0 라고 할 수 있는데요,
그 이유는 성공적으로 리팩토링을 완수해도 고객에게 끼치는 영향은 0이기 때문에 단기적으로는 사업에 끼치는 영향이 아예 없다고 할 수 있기 때문입니다.</p>
<p>심지어 발생할수 있는 이슈나 버그에 대한 리스크, 
리팩토링을 진행하지 않고 다른 기능을 개발할 수 있었던 기회비용까지 추정하면 리팩토링 자체는 사업적으로 손해를 볼 여지가 아주 다분한 행위라 볼 수 있습니다.</p>
<h3 id="그러면-리팩토링을-진짜-하지-말아야할까">그러면 리팩토링을 진짜 하지 말아야할까?</h3>
<p>그러면 결론은 리팩토링은 영영 하지 말아야할까요?
그래프의 개발 불능영역이 언제 올지 모르니, 최대한 버티다가 완전히 유지보수 불가판정이 나오면 새로 만들어야할까요?</p>
<p>제목을 하지 말라고 적어놨지만, 사실 필연적으로 해야하긴 합니다.
정확히는 한번에 대청소와 같은 느낌의 &quot;Grand refactor&quot; 를 가능한 하지 말라는 이야기입니다.</p>
<p>앞에 이야기했듯이, code cruft 는 필연적으로 발생합니다.
모든 기술 부채를 상환하고자 하는 행위 조차도 cruft를 만든다고 할 수 있습니다.</p>
<p>목표가 성취감이 아니라 집의 청결이 목적인 경우에,
시험기간에 한번씩만 진행하는 대청소보단 매 저녁에 30분 동안 청소하는것이 더 도움이 되듯</p>
<p>프로젝트의 코드 또한 목표가 성취감이 아니라, 지속가능한 발전에 있다면
작정하고 수행하는 대규모 리팩터링보단 평소 작업에 충분한 시간을 투자하며 조금씩 고쳐가는것이 좋다는 이야기입니다.</p>
<p>여러분은 기술 부채에 결벽증을 가질 필요가 없습니다.
총 자산 = 순자산 + 부채 로 계산되듯이 
적절한 정도의 기술부채는 (매 저녁마다 관리 할 수 있는 정도의 이자비용인) 오히려 총 생산성을 더 높이는 요인으로 작동 할 수 있습니다.</p>
<h3 id="이왕-고치는거-어떤-방법으로-고쳐야할까">이왕 고치는거, 어떤 방법으로 고쳐야할까?</h3>
<p>여러분이 레거시 코드를 대상으로 맘을 먹고 칼을 빼 들었으면 (대규모 리팩토링이든, PR을 올리는 김에 조금 고치는거든) 어떻게 고치는게 좋을까요?</p>
<p>다시말해 여러분이 한 작업이 단발성으로 끝나지 않고
그 결과가 부채가 아닌 유산으로 남아 프로젝트에 기여하고자 하려면, 어떻게 작업을 수행해야 할까요?</p>
<p>저는 그 해답은 <strong>테스트</strong>에 있다고 생각합니다.
명문화 되어있지 않았던 요구사항과 가정들을 작성해 발생할 수 있는 위험들을 <a href="https://engineering.linecorp.com/ko/blog/quality-advocator-shift-left-shift-right">Left-Shift</a>, QA 혹은 Relese 단계에서만 확인할 수 있었던 가정과 요구사항을 가능한 개발 과정의 왼쪽으로 옮기는것이 그 방법이라 생각합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 배열의 목록은 'i' 로 표현할까?]]></title>
            <link>https://velog.io/@wongue_shin/%EC%99%9C-%EB%B0%B0%EC%97%B4%EC%9D%98-%EB%AA%A9%EB%A1%9D%EC%9D%80-i-%EB%A1%9C-%ED%91%9C%ED%98%84%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@wongue_shin/%EC%99%9C-%EB%B0%B0%EC%97%B4%EC%9D%98-%EB%AA%A9%EB%A1%9D%EC%9D%80-i-%EB%A1%9C-%ED%91%9C%ED%98%84%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sun, 21 May 2023 14:09:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>흔히 배열의 목록을 표현하는 변수명을 &#39;i&#39;로 짓는 관행이 있는 것을 알고 계실 겁니다. 
혹시 그 유래에 대해서 알고 계신가요? 
오늘은 알고 사용하면 더 재밌는 대표적인 정수형 변수명의 유래에 관해서 설명해볼까 합니다.</p>
</blockquote>
<h2 id="목차">목차</h2>
<pre><code>1. 머릿말
2. 컴퓨터 과학의 태동
3. 허수의 i 였다고?
4. 여러분은, 변수명 i 를 명명할 때 마다 수학을 하고계십니다.</code></pre><h2 id="머릿말">머릿말</h2>
<p><strong>컴퓨터 과학</strong>은 그 학문의 태동을 응용수학의 품 안에서 시작했다고 합니다.
(1984년, 하버드 대학교의 컴퓨터과학과가 응용수학과에서 분과 된 예시가 대표적일 겁니다.)
역사의 위인분들이 이렇다 할 하드웨어가 만들어지기도 전에 벌써 소프트웨어를 설계했듯이, 초기 컴퓨터 과학은 그 특성이 수학과 엄밀한 구분을 하기 어려웠습니다.</p>
<h2 id="컴퓨터-과학의-태동">컴퓨터 과학의 태동</h2>
<p><strong>따라서,</strong>컴퓨터 과학에서 사용하는 용어나 표기법 또한 수학에서의 영향을 강하게 받은 흔적이 남아있는데요,
시간이 지나 컴퓨터 과학은 공학의 특성을 더 강하게 띄게 되었고,
저같은 수학적 소양이 부족한 사람도 쉽게 그 기술의 혜택을 누릴수 있게 된 결과물인 추상화, 계층화, OS 등의 수혜로 현 시점에선 소프트웨어 엔지니어로서 활동을 할 때, 수학적 사고가 그리 필수적이지 않다는 말이 많이 나오는 상황입니다.</p>
<p>하지만 가장 많이 사용하는 변수명에서조차 수학의 표기법 (<a href="https://en.wikipedia.org/wiki/Mathematical_notation">Mathematical Notation</a>) 의 영향을 찾아 볼 수 있는데요,
흔히 배열의 목록을 &#39;i&#39;로 표기하는것에서 이를 찾을 수 있습니다.</p>
<p>이 이유를 혹시 알고계시나요? 흔히 색인에 해당하는 &quot;index&quot;의 앞글자를 따 &#39;i&#39;라 표기한다는 이야기도 있는데요, 물론 사실과 거리가 멀다는것은 아니지만, 변수명 &#39;i&#39;에는 그보다 더 깊은 이야기가 숨어있답니다.</p>
<h2 id="허수의-i-였다고">허수의 i 였다고?</h2>
<p>배열의 목록을 표기하는 &#39;i&#39; 는 허수(Imaginary number)의 단위인 <em>i</em> 와 큰 연관이 있습니다.
너무 뜬끔없다 느껴지실 수 있겠지만, 천천히 들어보시면 이해하실 수 있을것이라 생각합니다.</p>
<p>흔히 다차원배열의 각 색인을 나타내는 변수명을 i, j, k, ... 로 작성하는 관행이 있음을 알고 계실겁니다.
이는 흔히 포트란의 정수형 변수 선언을 위한 지정된 변수명에서부터 온 관행이라는 사실도 이 글을 읽는 몇몇분은 알고 계실거라 생각합니다.</p>
<p>근데 왜 abcd나 xyz이 아닌, 좀 뜬금없어 보이는 i 에서부터 알파벳 순으로 명명하게 된 이유가 뭘까요?
우연히 포트란의 예약어나 할당한 변수명이 a 부터 h 까지는 다른 유형과 의미로 꽉 차있어서 그랬었던걸까요?</p>
<p>그런 이유보다는 수학에서의 3차원 공간의 직교좌표계(Coordinate space)의 기저(Basis) 를 의미하는 각 단위 벡터 i,j,k, 를 의미하는 의도가 더 강했을겁니다.</p>
<p>그럼 왜 수학에서는 이를 i, j, k 로 사용했을까요? 이는 사원수(Quaternion) 의 각 단위를 의미합니다.</p>
<p>사원수는 우리가 고등학교 과정에서 배운 복소수체계(Complex number) 의 확장인데요, 
실수부(스칼라) 와 3개의 직교하는 유닛 (i,j,k) 로 이루어진 허수부를 가지는 수 체계입니다.</p>
<p>사원수의 정의:  i^2 = j^2 = k^2 = ijk = -1 </p>
<p>이 포스팅의 주제는 사원수와 벡터연산의 개론이 아니므로 간단히 설명하면(오개념이 있을 수 있습니다.) 각 세개의 허수 단위는 그 정의에서부터 직교성(내적의 결과가 0)을 보장하므로, 3개의 축을 가지는 3차원 직교좌표계의 각 축(axis) 로 쓰이기 용이하기 때문입니다.</p>
<p>따라서 자연히 각각 직교성(서로 의존성이 없는) 삼차원 배열의 색인 변수명은 i,j,k 로 표현되는것이 자연스러운 겁니다.</p>
<h2 id="여러분은-변수명-i-를-명명할-때-마다-수학을-하고계십니다">여러분은, 변수명 i 를 명명할 때 마다 수학을 하고계십니다.</h2>
<p>따라서 여러분은, 변수 i 를 명명할때마다 수학적인 의미로서 1차원 임의의 수열 N 의 색인 i 에 접근하겠다. 라는 의미의 선언을 하고 계신겁니다.</p>
<p>어떠신가요 <code>var i:Int = 0;</code> 가 이제는 조금 달리 보이시나요?</p>
<p>현대 소프트웨어 공학에서는 OS 와 프레임워크가 저수준의 복잡하고 어려운 수학연산을 많이 대신해줌으로서 엔지니어가 정말 공학적으로 의미있는 비즈니스 로직에 집중 할 수 있는 기회를 제공합니다.</p>
<p>하지만, 그 편의에 젖어 정말 저희가 이용하는 응용수학(혹은 컴퓨터 과학)이 어떠한 배경을 가지고 있는지에 대해서는 잊지 말아야한다는 생각이 문득 들어 이 글을 작성해 봅니다.</p>
]]></description>
        </item>
    </channel>
</rss>