<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dal-pi.log</title>
        <link>https://velog.io/</link>
        <description>개발 오답노트</description>
        <lastBuildDate>Wed, 02 Feb 2022 15:50:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dal-pi.log</title>
            <url>https://images.velog.io/images/dal-pi/profile/5ad5c471-66ea-406a-8c8d-8faac533efc3/20210505_023709.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dal-pi.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dal-pi" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Flutter] 다크 모드 적용하기]]></title>
            <link>https://velog.io/@dal-pi/Flutter-%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/Flutter-%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 02 Feb 2022 15:50:29 GMT</pubDate>
            <description><![CDATA[<p>Flutter Dart Mode</p>
<hr>
<p>내가 필요해서 만든 앱이지만 혹시나 필요한 사람이 있을까 해서 스토어에 올려두었는데 몇 개월이 지나고 보니 몇몇 의견이 달려 있었다. 그 중 가장 많은 요구사항은 <strong>다크모드</strong>를 적용해 달라는 거였다. 나는 다크모드를 사용하지 않아서 전혀 신경쓰지 못하고 있었다.</p>
<p>특별히 뷰의 색상을 지정하지 않았다면 테마의 색상을 따라 간다.
이 경우에는 <code>MaterialApp</code> 을 Build 하는 부분에서 <code>darkTheme: ThemeData.dark()</code> 한 줄만 추가하면 된다.</p>
<pre><code class="language-dart">@override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.white,
      ),
      darkTheme: ThemeData.dark(),
      home: Scaffold(
        body: _getBody(),
      ),
    );
  }</code></pre>
<p>나의 경우, 기본모드에서는 <code>primaryColor</code>를 <code>white</code>로 변경하였고, 다크모드에서는 다크모드의 기본 테마를 따르도록 한 코드이다.</p>
<p>만약, 기본 테마 색상이 아닌 별도의 색상을 사용하고 싶다면 아래와 같이 다크모드인지를 검사하여 사용하는 방법이 있다.</p>
<pre><code class="language-dart">_getBgColorByTheme() {
    final ThemeData theme = Theme.of(context);
    if (theme.brightness == Brightness.light) {
      return Colors.white;
    } else {
      return Colors.black;
    }
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Shuffle Gallery 앱 개발 로그]]></title>
            <link>https://velog.io/@dal-pi/Flutter-Shuffle-Gallery-%EC%95%B1-%EA%B0%9C%EB%B0%9C-%EB%A1%9C%EA%B7%B8</link>
            <guid>https://velog.io/@dal-pi/Flutter-Shuffle-Gallery-%EC%95%B1-%EA%B0%9C%EB%B0%9C-%EB%A1%9C%EA%B7%B8</guid>
            <pubDate>Thu, 29 Jul 2021 18:03:19 GMT</pubDate>
            <description><![CDATA[<h2 id="아이디어">아이디어</h2>
<blockquote>
<p>갤러리 앱에는 왜 셔플 모드가 없을까?</p>
</blockquote>
<p>개인적으로 사진이나 음악을 폴더별로 잘 저장해서 다니는 편이다.
음악 재생에는 셔플 모드가 있어서 오랫동안 잊고 있던 명곡들이 나오면 기분이 좋곤 했던 기억이 있다.
하지만 대부분의 사진 앨범(폴더)는 최신순으로 정렬되어 있고 오래된순 으로 바꿀 순 있지만 이 정렬 방법을 바꾸는 사람은 거의 없는것 같다.
만약 사진 앨범이나 앨범 안의 사진들을 무작위 순서로 보여줄 수 있다면 어떨까 하고 생각을 해 보았다.
오래전 사진을 우연히 발견해서 지인들에게 공유해서 추억을 되살리거나 새로운 아이디어를 얻는 경우가 생길 것 같아서 쓸모있지 않을까 싶었다.</p>
<p>개인적으로 쓰고 싶어서 만드는 앱이라서 간단하게만 만들어 사용할 수도 있겠지만 App Store에 올려본지도 오래 되었고 다른 사람들에게도 써 보라고 할 수 있는 정도까지 만들어보고 싶어서 미니 프로젝트로 진행해 보기로 하였다.
</br></br></p>
<hr>
<h2 id="미리-보는-결과물">미리 보는 결과물</h2>
<p><a href="https://play.google.com/store/apps/details?id=com.kania.shuffle_gallery"><img src="https://images.velog.io/images/dal-pi/post/002886bd-0287-44b2-b3b8-74cd75c58a45/app_store.jpg" alt="">Shuffle Gallery - Google Play 앱</a>
결과적으로 <code>Flutter</code>로 Android 앱을 개발하여 Google Play에 등록까지 해 보았다.
앱은 사진 앨범이나 앨범 안의 사진들을 무작위로 섞어서 보여주며 맘에 드는 사진을 공유할 수 있도록 만들었다
<img src="https://images.velog.io/images/dal-pi/post/c2f19c5e-fddf-4878-8129-b5e7cd11aaa7/1_main.mp4_20210728_231549.gif" alt="">
매우 간단한 앱이었지만 나에게 있어서는 꽤 고생을 했기 때문에 앱을 만들면서 고민한 과정들을 글로 써 보고자 한다.
</br></br></p>
<hr>
<h2 id="1-시장조사-및-self-요구사항-분석-해-보기">1. 시장조사 및 self 요구사항 분석 해 보기</h2>
<p>일단 내가 원하는 앱이 앱스토어에 올라와 있다면 굳이 만들 필요가 없으니 한 번 찾아 보았다.
내가 만드려는 앱과 유사한 앱은 찾아보니 <code>Random Gallery</code>라는 앱만 있는 것 같다.
내가 처음 생각한 이름도 Random Gallery였는데 이름이 너무 개발자스럽다는 주변 의견이 있어서 <code>Shuffle</code>이라는 단어를 쓰기로 했었다. 저 앱 개발자는 찐 개발자인가보다.
앱은 내가 바라던 것 처럼 사진을 무작위 순서로 보여주었지만 이런 부분이 부족했다.</p>
<hr>
<ul>
<li>앨범(폴더) 단위로 볼 수 없다
모든 파일을 섞어버려서 굳이 포함하고 싶지 않은 사진들도 보이게 된다. 제외할 수 있는 폴더를 지정할 순 있기는 하지만 한 폴더를 지정하기는 어렵다.</li>
<li>셔플된 전체 리스트를 볼 수는 없다.
동작을 보니 갤러리처럼 스크롤 방식이 아니라 랜덤하게 몇 장의 사진을 뽑아 보여주는 방식이다.
원래 순서대로는 볼 수 없다.</li>
<li>Grid View가 2칸 혹은 3칸으로만 볼 수 있다.</li>
<li>스크롤이 일반 갤러리 앱에 비해 느린 편으로 답답하다.</li>
<li>광고가 있다!</li>
</ul>
<hr>
<p>경험해 보니 내가 필요로 하는 기능을 리스트화 할 수 있었다.</p>
<ul>
<li>앨범 리스트 혹은 앨범 안의 사진 리스트를 무작위로 섞어 보여줄 수 있어야 한다.<ul>
<li>앨범 내 사진을 셔플할 수 있고 모든 사진에 대해서도 가능해야 한다. </li>
</ul>
</li>
<li>사진을 보는 데 있어서 기존 갤러리 앱들과 동일한 사용자 경험을 주어야 한다. (편집기능 제외)<ul>
<li>Grid View의 칸 수를 조절 가능했으면 좋겠다 - 사진을 보는 위주의 앱이므로 List View로도 볼 수 있으면 좋겠다.</li>
<li>Page View로 셔플된 사진을 한 장씩 볼 수 있어야 하며 사진 zoom in/out 이 가능해야 한다.</li>
<li>Share 기능이 있어야 한다.</li>
</ul>
</li>
<li>수익을 위한 것이 아니므로 보는데 방해되는 광고는 굳이 없어도 된다!</li>
<li>(optional)무조건 셔플된 순서가 아닌 원래 순서대로도 볼 수 있으면 좋겠다
</br></br></li>
</ul>
<hr>
<h2 id="2-ui-시나리오-그려-보기">2. UI 시나리오 그려 보기</h2>
<p>복잡한 시나리오를 가지고 있지 않으므로 간단하게 손그림으로 그려보았다.
<img src="https://images.velog.io/images/dal-pi/post/fffdc0db-213b-405c-b830-b2b2cfa2ba92/ui.jpg" alt="">
디자인 센스는 영 없나 보다.
</br></br></p>
<hr>
<h2 id="3-개발-방법-정하기">3. 개발 방법 정하기</h2>
<p>기존에 안드로이드를 해본 경험이 있고 <code>Kotlin</code>을 배우고 있던 참이라 안드로이드에서 프로토타이핑을 진행하였다.
하던 도중에 <code>Flutter</code>에 대해서 접하게 되었고, 아이폰에서도 돌아가는 앱을 만들 수 있다는 점이 흥미로워서 Flutter로 갈아탔다.
다만 지금은 Windows 개발 환경밖에 없어 일단 안드로이드 타겟으로 개발하기로 하였다.
개발 환경 설정은 <a href="https://velog.io/@dal-pi/Flutter-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95">Flutter 개발환경 설정 (with Flutter Doctor)</a>.</p>
<p>참고로
Mac : Android / iOS 앱 모두 개발 가능
Windows : Android 앱만 개발 가능하다.
얼른 돈모아서 Mac을 사도록 하자...
</br></br></p>
<hr>
<h2 id="4-스터디-해-보기">4. 스터디 해 보기</h2>
<h3 id="dart-언어">Dart 언어</h3>
<p>Flutter는 <code>Dart</code>라는 언어를 사용하기에 Dart언어에 대한 스터디를 유튜브로 매우 간략하게 해 봤다. 그런데 꽤 배우기 쉬운 언어였던것 같다.
<a href="https://www.youtube.com/watch?v=2g8DsOSreqk">30분만에 배우는 Dart 언어</a> 를 보고 이후에 필요한 내용은 구글링해서 가능할 정도였다!
평소 C++을 주로 사용하고 Java/Kotlin 을 알고 있었는데 몇 가지 언어의 패러다임이 다 들어가있는것 같아서 배우기에 접근성이 좋은 것 같다.</p>
<h3 id="flutter">Flutter</h3>
<p><a href="https://flutter-ko.dev/docs/reference/tutorials">튜토리얼</a> 을 따라해보고 정말 놀란 점은 간결하게 짠 코드 몇 줄로 안드로이드 앱을 쉽게 작성할 수 있다는 것이었다.
물론 안드로이드 native에 가까워질수록 활용하기 어려워 보이지만 일반적인 View만 가진 앱은 정말 쉽게 만들 수 있을 것 같아서 자주 활용하지 않을까 싶다.</p>
<h3 id="flutter-pub">Flutter pub</h3>
<p><a href="https://pub.dev/">pub.dev</a> 에서 이미 만들어진 앱 관련 패키지들을 사용할 수 있어 안그래도 빠른 앱 개발이 더 빠르게 가능하도록 한다.
Shuffle Gallery를 만들면서 사용한 패키지들은 </p>
<ul>
<li>permission_handler : 권한 확인 및 획득</li>
<li>photo_manager : 디바이스의 미디어를 얻어올 수 있음</li>
<li>preload_page_view : 끊김없는 PageView를 사용할 수 있도록 함</li>
<li>photo_view : zoom in/out 가능한 사진 view제공 (gif 애니메이션 지원)</li>
<li>share_plus : share 기능을 간편하게 사용</li>
</ul>
<p>패키지 이름들만 보아도 앱 하나가 뚝딱 만들어질 것 같아 보인다!
그래도 역시 세세한 부분은 각각의 패키지를 조금 활용하거나 추가로 구현해야 할 부분들이 있었으며 이후 글에서 하나하나 기록해 보고자 한다.
</br></br></p>
<hr>
<h2 id="5-개발해-보기">5. 개발해 보기</h2>
<h3 id="앨범-목록-화면">앨범 목록 화면</h3>
<p><img src="https://images.velog.io/images/dal-pi/post/50c752c3-1bc1-4fdf-b731-660a046b32e6/2_initial_permission_album_list.mp4_20210728_231649.gif" alt="">
첫 화면으로 진입 시 <code>permission_handler</code> 를 통해 권한을 획득한 다음,
<code>photo_manager</code> api를 사용하여 모든 앨범의 정보를 얻어온 후 이를 무작위로 셔플하여 Grid View로 나열하였다.
사용한 셔플 알고리즘은 <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Knuth Shuffle</a> 로 매우 간단하게 구현할 수 있었다.</p>
<pre><code class="language-dart">List&lt;T&gt; shuffle&lt;T&gt;(List&lt;T&gt; items) {
  var random = new Random();

  for (var i = items.length - 1; i &gt; 0; i--) {
    var n = random.nextInt(i + 1);

    var temp = items[i];
    items[i] = items[n];
    items[n] = temp;
  }

  return items;
}</code></pre>
<p>photo_manager 에서는 <code>Recent</code> 라는 이름으로 모든 사진을 얻어올 수 있어 이름만 <code>All</code> 로 바꾸고 항상 첫 번째 아이템으로 위치하도록 지정했다.
이후 앨범의 대표 썸네일 이미지를 얻기 위해 앨범마다 가장 최근 사진 1장의 썸네일만 로드하여 가장자리가 둥글게 <code>ClipRRect</code>를 사용하여 꾸며 보았다.</p>
<hr>
<h3 id="사진-목록-화면">사진 목록 화면</h3>
<p>각각의 앨범 항목을 누르면 앨범의 모든 사진을 섞어서 보여주는 Grid/List View를 구성하였다.
정렬 버튼을 눌러 최신 순서대로 볼 수도 있다
<img src="https://images.velog.io/images/dal-pi/post/1b0d160d-ff81-415b-abf0-235b9650a9ee/3_2_shuffle.mp4_20210728_232452.gif" alt="">
앞서 앨범 목록과 사진 목록 모두 스크롤 시에 더 많은 사진을 보여주기 위해 <code>SliverGrid(SliverList)</code> 를 사용하였다.
안드로이드 native 기준으로는 <code>CoordinatorLayout</code> 를 사용해야 하고 이를 위해서 알아야 할 점이 많았는데 Flutter에서 매우 간단하게 지원하고 있었다.</p>
<p>이 화면에서는 photo_manager api 를 통해 선택한 앨범 내 모든 사진 정보를 받아온 후 이를 셔플하고 각각의 썸네일을 받아서 목록으로 보여주었다.
이 때 썸네일을 로드하는 과정은 시간이 오래 걸리므로 비동기식으로 구현하게 되는데
만약 앨범 안에 수많은 사진이 있을 수 있으므로 한 화면에 보이는 썸네일만 먼저 로드하고 이후 Scroll Event를 받아와 일정 수준 이하로 스크롤이 내려가면 추가로 썸네일을 로드하도록 구현하였다.
일반 갤러리만큼은 아니어도 꽤 부드러운 수준으로 스크롤이 가능해서 만족스러웠다.
<img src="https://images.velog.io/images/dal-pi/post/9cb92306-68e1-4eb7-a2ac-0ca30f85d123/3_1_mediaview_scrolling.mp4_20210728_231923.gif" alt=""></p>
<hr>
<p>사진 목록 화면에서는 셔플하지 않은 원래대로의 목록 및 Grid 칸 수를 1~8칸까지 조절가능하도록 구성하였다.
만들면서 가장 맘에 드는 기능으로 사진 목록을 ListView(Grid 1칸)로 보는 경우 밋밋한 사각형 View가 아닌 <code>CardView</code>를 적용하여 나름 심미적인 부분을 신경써봤다.
<img src="https://images.velog.io/images/dal-pi/post/1cf6e3f6-95a9-4e86-8f5d-8968c2243ce3/4_listview.mp4_20210728_231445.gif" alt=""><img src="https://images.velog.io/images/dal-pi/post/c0c660a0-0430-4f7d-89e1-2172246c8eab/5_grid.mp4_20210728_232750.gif" alt="">
이 때 리스트가 변경되고 썸네일을 다시 로드하게 되는데 만약 이미 로드한 썸네일이 있다면 미리 캐싱해두었다가 다시 사용하도록 하여 나름 성능을 높여 보았다.
만들고 보니 알고리즘을 공부할때 배운 <code>동적 계획법</code>과 비슷한 것 같다. 현업에서도 활용할 일이 거의 없었는데 이럴 때 배운 것들을 사용해서 뭔가 뿌듯했다.</p>
<hr>
<h3 id="사진-상세-보기-화면">사진 상세 보기 화면</h3>
<p>사진 목록에서 사진을 선택하면 한 화면에서 볼 수 있게 하며 zoom in/out, full screen mode가 가능하도록 구성하였다.
그리고 기존 갤러리 앱과 동일하게 PageView 형식으로 볼 수 있게 구성하였다.
Flutter에는 <code>PageView</code>가 있지만 안드로이드처럼 미리 각 Page를 로드하지 않는 것 같았다.
<img src="https://images.velog.io/images/dal-pi/post/193eebd6-c11f-4d2d-923f-a98bf0f49762/6_page_full_zoom.mp4_20210728_232855.gif" alt="">
하지만 찾아보니 이미 <code>preload_page_view</code> 패키지가 있었고 이를 통해 쉽게 만들 수 있었다.
그리고 zoom in/out 을 쉽게 사용할 수 있는 <code>photo_view</code> 라는 패키지도 있어 두 패키지를 조합하기만 해도 상세 보기 화면은 쉽게 구성할 수 있을 것 같았다.
하지만 zoom in 한 이후 사진에 대한 스크롤이 Page 스크롤까지 넘어가는 문제가 있어 이 부분을 해결하는데 꽤 고생하였다. 앱을 만들면서 가장 어려운 부분이었던 것 같다.
결과적으로 photo_view 의  <strong>Scale Listener</strong>를 통해 preload_page_view 의 <strong>Page Scroll 을 제어</strong>하는 방법으로 해결하였고 해결했을 때의 성취감이 상당했다.
<img src="https://images.velog.io/images/dal-pi/post/aa94fa30-199b-4fb6-b5b3-7d1e52b395b7/7_1_problem.mp4_20210728_233043.gif" alt="">
(문제가 있던 부분. 사진의 오른쪽을 보려고 드래그했는데 페이지가 넘어간다)
<img src="https://images.velog.io/images/dal-pi/post/dcce584e-0ae2-47e3-ac60-47947f19dc82/7_2_resolved.mp4_20210728_233112.gif" alt="">
(사진이 확대 모드일때는 스크롤하지 않는다. 일반 갤러리 UI와 다르게 이렇게 적용해보고 싶기도 했다.)</p>
<p>추가로 photo_view 패키지는 사진을 double touch 하는 경우 화면에 꽉 차도록 zoom in 해 주는데 기본 앱 갤러리에서는 제공하지 않던 기능이라 매우 마음에 들었다.
<img src="https://images.velog.io/images/dal-pi/post/e59da322-afbc-4f2b-9c1a-f9cee9ef2cab/8_doublezoom.mp4_20210728_233240.gif" alt="">
(double touch 할 때마다 BoxFit 을 Contain -&gt; Cover -&gt; None 으로 변경한다.)</p>
<p>Share기능은 정말 간단하게 share_plus 패키지의 Share에 사진 파일 경로만 전달해주면 바로 공유 기능이 완성되었다.
</br></br></p>
<hr>
<h2 id="6-앱-릴리즈-해-보기">6. 앱 릴리즈 해 보기</h2>
<p>안드로이드 앱 배포는 2021년 8월부터 <code>Apk</code>형태가 아닌 <code>App Bundle</code> 로만 가능하다고 한다.
앱의 모든 컴파일된 코드 및 리소스를 포함하며 APK 생성 및 서명을 Google Play에 맡기는 게시 형식이라고 하며, 차이점이라면 기존 Apk에 비해 서명 key를 분실하는 위험이 줄어들고 기기 최적화에 더 좋다고 한다.
이전까지 apk로만 배포해 보았어서 앱 배포에 대한 내용은 구글링하여 따라해 보았다. (<a href="https://empering.tistory.com/entry/Flutter-%EC%95%B1%EC%B6%9C%EC%8B%9C-%EB%B0%A9%EB%B2%95-Appbundle">참조 링크</a>)</p>
<hr>
<p>앱을 올리면서 코딩 외적으로 가장 어려웠던 부분은 역시 <code>App Icon</code> 만들기였다.
첫 번째 어려움은 내가 디자인 센스가 전무하다는 점과 두 번째로 이후 iOS 에도 적용할 수 있는 형식으로 만드는 점이었다.
혼자서 생각해서 그려본 아이콘은 너무 개발자 같은 디자인이었기에 pinterest 와 구글 검색을 통해 여러 아이콘을 벤치마킹하였다.
<img src="https://images.velog.io/images/dal-pi/post/ef2ab6d3-93b2-4ff0-a827-1266a6a6e846/icon.jpg" alt="">
좌 : 손으로 그린 하찮은 아이콘
우 : 그나마 벤치마킹해서 툴로 그려본 아이콘</p>
<p>아이콘을 그릴 때 안드로이드의 <a href="https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive?hl=ko">Adaptive App Icon</a> 와 <a href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/">Apple App Icon</a> 의 가이드라인이 모두 맞게 디자인한 다음
그린 하나의 이미지를 <a href="https://appicon.co/">AppIcon</a> 이라는 웹에 업로드해서 각 플랫폼에 맞는 형식으로 변환할 수 있다. 결과물을 프로젝트 폴더에 그대로 덮어쓰기 하면 적용할 수 있었다.</p>
<hr>
<p>마지막으로 앱 스토어에 올릴 때 가이드 스크린샷을 첨부해야해서 예제 이미지들을 <a href="https://undraw.co/">UnDraw</a> 를 사용하여 만들어 보았다.
한국어와 영어 두 가지로 어떻게 만드는지 몰라서 아직 텍스트가 들어갈 부분이 비어 있는데 더 찾아보니 String들과 같이 별도로 업로드하면 되는 것 같다.</p>
<p>예전에 개인적으로나 회사에서의 Apk를 배포한 적이 있었는데 지금은 개인정보 이슈들로 인해 설문이나 작성할 내용이 훨씬 더 많아진것 같다.
거의 반나절을 어떻게 작성하는지 찾아보며 업로드한 것 같다.
승인이 나는 것도 예전엔 하루 내에도 가능한 게 지금은 3일 정도는 기다린것 같다.
<img src="https://images.velog.io/images/dal-pi/post/fe7b3d33-ef76-4457-bdf0-1f2049aea5f8/google_play.jpg" alt="">
(기다린 만큼 뿌듯했다!)
</br></br></p>
<hr>
<h2 id="7-개발-후기">7. 개발 후기</h2>
<p>가지고 있는 안드로이드 단말에 잘 설치가 되고 동작도 잘 되고 있다.
개인적으로 쓰려고 만든거라서 나에게는 매우 만족스러운 앱이지만 스토어에 올리고 보니 아직 부족한게 많아 보인다.
앱을 개발하고 느낀점을 간단하게 나열하면 이렇다.</p>
<ol>
<li>Flutter는 간단한 앱을 정말 쉽게 만들 수 있는 도구인것 같다.</li>
<li>개인적으로 사용할 앱을 만드는 것에 비해 출시할 앱을 만들 때에 신경써야 할 점은 정말 많았다.</li>
<li>앱 개발은 결과물이 바로 눈에 보이므로 재미나 성취감이 높다.</li>
</ol>
<p>이후 사용하며 버그나 Crash가 발생할 것이 자명한데 fix 및 새로운 기능을 업데이트하는 경험을 하게 될 것 같다. 벌써 버그를 하나 찾았기 때문이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter 개발환경 설정 (with Flutter Doctor)]]></title>
            <link>https://velog.io/@dal-pi/Flutter-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@dal-pi/Flutter-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 20 Jul 2021 03:23:12 GMT</pubDate>
            <description><![CDATA[<p>간단한 아이디어를 앱으로 만들고 싶었는데 android 말고 ios에서도 돌려보고 싶어서 크로스 플랫폼 개발 환경을 사용해보기로 하였다. 여러 선택지가 있었지만 Flutter가 기존 android studio에서도 개발 가능하기 때문에 제일 쉬워 보였다.
일단, Android Developers 와 같이 Flutter도 부분적으로 <a href="https://flutter-ko.dev/">한국어 문서</a>가 지원되며 <a href="https://flutter-ko.dev/docs/get-started/install">설치 문서</a>에도 번역이 되어 있다.
하지만 역시 개발 환경 설정은 뭐가 되었든 쉽지많은 않는 것 같다. Windows 에서 설치하면서 겪은 문제들을 쭉 나열해 보면 이렇다.</p>
<h3 id="flutter-doctor">Flutter Doctor</h3>
<p>콘솔에서 Flutter 설치 상태에 대해 알려주고 필요한 항목들을 알려주는 tool이다.
Android 개발 환경은 이미 가지고 있었지만 처음 doctor를 실행했을 때 아래처럼 ! 가 많이 보인다</p>
<blockquote>
<pre><code>PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.6, on Microsoft Windows [Version 10.0.19041.985], locale ko-KR)
[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    X Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
[√] Chrome - develop for the web
[!] Android Studio (not installed)
[√] IntelliJ IDEA Community Edition (version 2019.3)
[√] Connected device (2 available)</code></pre></blockquote>
<pre><code>
### Android licenses 동의 시 NoClassDefFoundError 문제
`Android toolchain` 을 보면 Flutter 에서 Android 사용을 위한 라이선스에 동의가 필요하다. 가이드 받은대로 입력해줬는데 java의 NoClassDefFoundError 에러가 발생한다.

&gt; ```bash
PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter doctor --android-licenses
Exception in thread &quot;main&quot; java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
        at com.android.repository.api.SchemaModule$SchemaModuleVersion.&lt;init&gt;(SchemaModule.java:156)
        at com.android.repository.api.SchemaModule.&lt;init&gt;(SchemaModule.java:75)
        at com.android.sdklib.repository.AndroidSdkHandler.&lt;clinit&gt;(AndroidSdkHandler.java:81)
        at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:73)
        at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:48)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
        ... 5 more</code></pre><p>현재 java 버전을 보니 </p>
<blockquote>
<pre><code class="language-bash">PS C:\flutter_windows_2.0.6-stable\flutter&gt; java -version
java version &quot;1.8.0_241&quot;
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) Client VM (build 25.241-b07, mixed mode)</code></pre>
</blockquote>
<pre><code>
최신의 자바를 깔면 해결된다는 블로그 글이 있어 바꾸는 김에 OpenJDK를 사용해보기로 했다.
openjdk8 - https://github.com/ojdkbuild/ojdkbuild

&gt; ```bash
PS C:\flutter_windows_2.0.6-stable\flutter&gt; java -version
openjdk version &quot;1.8.0_292&quot;
OpenJDK Runtime Environment (build 1.8.0_292-b10)
OpenJDK Server VM (build 25.292-b10, mixed mode)</code></pre><p>새로 설치하니 거짓말처럼 해결되었고 이후 무수한 약관 동의 과정이 진행되었다.</p>
<h3 id="android-studio--android-sdk-설치-경로-문제">Android Studio &amp; Android SDK 설치 경로 문제</h3>
<p>사용하던 Android Studio가 있는데 찾지 못한 것으로 기본 설치 경로를 수정해주면 된다.
(Android Studio 설치 시 설치 경로를 C:\Android\ 로 바꿨었다. 기본 경로는 Program Files\ 인 것으로 보임)</p>
<blockquote>
<pre><code class="language-bash">PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter config --android-studio-dir=&quot;C:\Android\Android Studio&quot;
Setting &quot;android-studio-dir&quot; value to &quot;C:\Android\Android Studio&quot;.</code></pre>
</blockquote>
<pre><code>
이후 다시 doctor를 실행시키면 license 가 다시 ! 로 되어 있었다.
&gt; ```bash
PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.6, on Microsoft Windows [Version 10.0.19041.985], locale ko-KR)
[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    X Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
[√] Chrome - develop for the web
[√] Android Studio
[√] IntelliJ IDEA Community Edition (version 2019.3)
[√] Connected device (2 available)</code></pre><p>가볍게 다시 <code>flutter doctor --android-licenses</code> 를 입력해 봤더니</p>
<blockquote>
<pre><code class="language-bash">PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter doctor --android-licenses
��: ⺻ Ŭ Kim\AppData\Local\Android\Sdk\cmdline-tools\latest\bin\\..��() ãų ε  ��ϴ.
: java.lang.ClassNotFoundException: Kim\AppData\Local\Android\Sdk\cmdline-tools\latest\bin\\//</code></pre>
</blockquote>
<pre><code>
깨진 글자들과 함께 ClassNotFoundException 에러가 발생한다.
자세히 보니 내 Windows 의 user folder 에 공백 문자가 있어서 발생하는 것 같아 보인다.
Android SDK의 위치는 Android Studio에서 찾아볼 수 있는데
문제가 없을 것 같은 C:\Android\Sdk 로 옮기고 다시 자세한 설명이 나오도록 doctor -v 옵션으로 실행해보았다.
하지만 아직도 이전 경로인 것으로 나온다.

&gt;```bash
PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter doctor -v
//...
[X] Android toolchain - develop for Android devices
    X Unable to locate Android SDK.
      Install Android Studio from: https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK components.
      (or visit https://flutter.dev/docs/get-started/install/windows#android-setup for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.
//...</code></pre><p>Android Studio 에서의 경로 뿐 아니라 Flutter에서 사용하는 Android SDK 경로를 다시 지정해주어야 했었다.
<code>flutter config --android-sdk &quot;C:\Android\Sdk&quot;</code> 로 경로를 다시 지정해준다.</p>
<h3 id="dart--flutter-plugin-설치">Dart &amp; Flutter plugin 설치</h3>
<p>이후 Dart 와 Flutter plugin 을 설치해 주어야 한다
doctor에 -v 옵션을 주고 실행시키면 plugin에 대한 설명도 나오지만
Android Studio를 켜고 gui 환경에서 설치할 수 있다.
(첫 화면 하단의) Configure - Plugins
<img src="https://images.velog.io/images/dal-pi/post/98052cff-24fd-4e16-bb4a-c0cc3ee44cc4/plugins.png" alt="">
<img src="https://images.velog.io/images/dal-pi/post/852acf9d-c330-4b5c-9d01-ad5a3256c1d1/%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8_dart.png" alt="">
<img src="https://images.velog.io/images/dal-pi/post/64760b6d-9869-4182-9421-744da247f5f5/%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8_flutter.png" alt=""></p>
<h3 id="완료">완료!</h3>
<p>모든 과정을 완료하고 doctor 를 실행하면 모든 항목이 clear 되어 있다. 속이 다 시원하다.</p>
<blockquote>
<pre><code>PS C:\flutter_windows_2.0.6-stable\flutter&gt; flutter doctor -v
[√] Flutter (Channel stable, 2.0.6, on Microsoft Windows [Version 10.0.19041.985], locale ko-KR)
    • Flutter version 2.0.6 at C:\flutter_windows_2.0.6-stable\flutter
    • Framework revision 1d9032c7e1 (3 weeks ago), 2021-04-29 17:37:58 -0700
    • Engine revision 05e680e202
    • Dart version 2.12.3
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at C:\Android\Sdk
    • Platform android-30, build-tools 30.0.3
    • Java binary at: C:\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6842174)
    • All Android licenses accepted.
[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
[√] Android Studio
    • Android Studio at C:\Android\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • android-studio-dir = C:\Android\Android Studio
    • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6842174)
[√] IntelliJ IDEA Community Edition (version 2019.3)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.3
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
[√] Connected device (2 available)
    • Chrome (web) • chrome • web-javascript • Google Chrome 90.0.4430.212
    • Edge (web)   • edge   • web-javascript • Microsoft Edge 90.0.818.62
• No issues found!</code></pre></blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[git] amend 되돌리기]]></title>
            <link>https://velog.io/@dal-pi/git-amend-%EB%90%98%EB%8F%8C%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/git-amend-%EB%90%98%EB%8F%8C%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Thu, 10 Jun 2021 02:37:32 GMT</pubDate>
            <description><![CDATA[<p>undo git --amend
출처 : <a href="https://stackoverflow.com/questions/1459150/how-to-undo-git-commit-amend-done-instead-of-git-commit/1459264">How to undo “git commit --amend” done instead of “git commit”</a></p>
<hr>
<p>새로운 commit으로 작성해야 하는데 실수로 <code>--amend</code> 옵션으로 작성해서 덮어씌워지는 경우가 있다. 이 경우에 아래의 명령어로 되돌아갈 수 있다. (단, 직전 명령어가 <code>git commit --amend</code> 인 경우이다)</p>
<pre><code class="language-bash">$ git reset --soft HEAD@{1}</code></pre>
<hr>
<p>돌아가는데 사용된 방법은 <code>reflog</code>를 사용하는 것이다.
예를 들어 아래의 commit들이 쌓여있는 상태라고 보자.</p>
<pre><code class="language-bash">$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit</code></pre>
<p>잘못해서 네 번째 commit을 <code>--amend</code> 로 작성했다고 하면 <code>git log</code> 는 세 번째 commit이 사라진 상태로 보인다</p>
<pre><code class="language-bash">$ git commit --amend -m &quot;fourth commit&quot;
484a59275031909e19aadb7c92262719cfcdf19a fourth commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit</code></pre>
<p>이 경우 <code>git reflog</code> 를 사용하면 사라진 세 번째 commit이 <code>HEAD@{1}</code> 에 나타난다</p>
<pre><code class="language-bash">$ git reflog
484a592 HEAD@{0}: commit (amend): third commit(2)
1a410ef HEAD@{1}: commit: third commit
cac0cab HEAD@{2}: commit: second commit
fdf4fc3 HEAD@{3}: commit: first commit</code></pre>
<p>이 때 <code>HEAD@{1}</code> 의 의미는 <code>reflog</code> 라는 것으로 git의 <code>HEAD</code>가 변경될 때마다 남는 로그이다. 흔히 쓰는 <code>git log</code> 를 사용하면 보이지 않으나 <code>git log -g</code> 혹은 <code>git reflog</code> 를 사용하면 볼 수 있다.</p>
<p><code>commit --amend</code> 를 통해 <code>HEAD</code>가 변경되었으므로 <code>reflog</code>가 남게 되며 이 <code>reflog</code>로 <code>reset --soft</code>를 사용해 돌아갈 수 있는 것이다.</p>
<p>더 자세한 <code>reflog</code>에 대한 내용은 <a href="https://git-scm.com/book/ko/v2/Git%EC%9D%98-%EB%82%B4%EB%B6%80-%EC%9A%B4%EC%98%81-%EB%B0%8F-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%B5%EA%B5%AC#_data_recovery">Git의 내부 - 운영 및 데이터 복구</a>의 &#39;데이터 복구&#39; 탭을 참고하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[git] 특정 branch 만 clone 하기]]></title>
            <link>https://velog.io/@dal-pi/git-%ED%8A%B9%EC%A0%95-branch-%EB%A7%8C-clone-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/git-%ED%8A%B9%EC%A0%95-branch-%EB%A7%8C-clone-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 04 Jun 2021 00:35:22 GMT</pubDate>
            <description><![CDATA[<h2 id="--single-branch-옵션">--single-branch 옵션</h2>
<p>remote repository 에 여러 branch가 있고 히스토리가 많은 경우 git clone하는 경우 많은 용량을 차지할 수 있다. 이 때 다른 branch를 사용하지 않고 하나의 branch만 사용하는 경우 불필요한 용량이 할당될 수 있다. </p>
<p>이런 경우 필요한 하나의 branch의 히스토리만 clone하는 방법이 있다.</p>
<pre><code class="language-base">$ git clone -b &lt;branch&gt; --single-branch &lt;repository&gt;</code></pre>
<p>위와 같이 clone하는 경우 <code>git branch -a</code> 로 모든 branch 를 출력해도 <code>--single-branch</code> 로 지정한 branch만 보인다.</p>
<h2 id="다른-branch를-사용하기">다른 branch를 사용하기</h2>
<p>이후 다른 branch 를 사용하고 싶은 경우가 있을 것이다. 이 경우에는 아래와 같이 branch를 추가하고 fetch 해 주면 된다.</p>
<pre><code class="language-bash">$ git remote set-branches --add origin &lt;branch&gt;
$ git fetch origin</code></pre>
<h2 id="모든-branch를-사용하기">모든 branch를 사용하기</h2>
<p>모든 branch를 사용하는 경우 <code>remote.origin.fetch</code> 를 변경해 주고 fetch 해 주면 된다.</p>
<pre><code class="language-bash">$ git config --replace-all remote.origin.fetch &quot;+refs/heads/*:refs/remotes/origin/*&quot;
$ git fetch origin</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[c++] deprecated 컴파일 경고 출력하기]]></title>
            <link>https://velog.io/@dal-pi/c-deprecated-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B2%BD%EA%B3%A0-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/c-deprecated-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B2%BD%EA%B3%A0-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 17 May 2021 03:51:51 GMT</pubDate>
            <description><![CDATA[<p>여러 개발자가 협업하거나 library 등을 통해 api를 배포하는 개발자라면 더 이상 지원하지 않는 함수에 대하여 경고 메세지를 보내고 싶을 때가 있다.</p>
<p>이 때 더 이상 사용하지 않거나 유지보수되지 않는 함수의 선언부에 아래와 같이 <code>[[deprecated]]</code> Attributes (C++14부터)를 적용하면 컴파일 시 경고 메세지를 출력한다. (빌드는 그대로 됨).</p>
<pre><code class="language-cpp">[[deprecated(&quot;use B() instead&quot;)]]
void A();</code></pre>
<pre><code class="language-bash">warning:  `void A()` is deprecated: use B() instead [-Wdeprecated-declarations]
A();
  ^ </code></pre>
<p>원형은 아래와 같고 deprecated 뒤에 경고 메세지를 작성할 수 있어 다른 함수를 사용하라고 경고할 수 있다.</p>
<pre><code class="language-cpp">[[deprecated]]                        (1)    
[[deprecated( string-literal )]]    (2)    </code></pre>
<p>deprecated 는 C++11부터 사용되는 <code>[[attrubutes]]</code> 기능 중의 하나이며 컴파일 시에 경고나 힌트를 주기 위한 도구이다. 다른 항목들에 대하여는 (<a href="https://en.cppreference.com/w/cpp/language/attributes)%5Bcppreference%5D">https://en.cppreference.com/w/cpp/language/attributes)[cppreference]</a> 를 참고하자. 여러 속성들이 있지만 주로 사용하는건 deprecated이다</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[linux] tar.gz 로 압축하고 압축 풀기]]></title>
            <link>https://velog.io/@dal-pi/linux-tar.gz-%EB%A1%9C-%EC%95%95%EC%B6%95%ED%95%98%EA%B3%A0-%EC%95%95%EC%B6%95-%ED%92%80%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/linux-tar.gz-%EB%A1%9C-%EC%95%95%EC%B6%95%ED%95%98%EA%B3%A0-%EC%95%95%EC%B6%95-%ED%92%80%EA%B8%B0</guid>
            <pubDate>Mon, 10 May 2021 03:34:44 GMT</pubDate>
            <description><![CDATA[<p>매번 할 때마다 잊어버리는 tar.gz 명령어 정리</p>
<h1 id="targz">tar.gz</h1>
<h2 id="압축-풀기">압축 풀기</h2>
<pre><code class="language-bash">$ tar -xvzf [filename]</code></pre>
<h2 id="압축하기">압축하기</h2>
<pre><code class="language-bash">$ tar -cvzf [filename] [target]</code></pre>
<h2 id="압축-파일-보기">압축 파일 보기</h2>
<pre><code class="language-bash">$ tar -tvzf [filename]</code></pre>
<p>-x는 압축을 해제하는 옵션(extract),
-c는 압축을 새로 만드는 옵션(create),
-t는 압축 파일의 내용을 나타내는 옵션(list),
-v는 진행 상황을 출력(vervose),
-z는 gzip을 사용하여 압축/해제 (gzip),
-f는 파일이름을 지정하겠다는 의미이다.</p>
<p>filename 은 풀거나 만들고자 하는 압축 파일 이름(.tar.gz로 끝남),
target 은 압축하고자 하는 파일 혹은 디렉토리이다.</p>
<h1 id="tar-gz-각각의-의미">tar, gz 각각의 의미</h1>
<p>tar는 <code>테이프 아카이버(Tape Archiver)</code> 의 줄임말로 압축하는 것이 아닌 여러 개의 파일을 하나로 합친다는 의미이다.
gz이 압축의 의미이며 gz는 아래와 같이 하나의 파일만을 압축할 수 있기 때문에 tar로 하나의 아카이브로 만든 다음 압축하는 것이다.</p>
<h2 id="gz-로-하나의-파일-압축하기">gz 로 하나의 파일 압축하기</h2>
<pre><code class="language-bash">$ gzip [target]</code></pre>
<h2 id="gz-압축-풀기">gz 압축 풀기</h2>
<pre><code class="language-bash">$ gzip -d [target]</code></pre>
<p>-d는 압축 해제(decompress) 옵션이다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[git] gerrit wip 사용 방법]]></title>
            <link>https://velog.io/@dal-pi/git-gerrit-wip-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dal-pi/git-gerrit-wip-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 07 May 2021 15:56:01 GMT</pubDate>
            <description><![CDATA[<p>git을 사용하는 경우 아직 commit이 완전히 작성되지 않았지만 이 내용을 공유하거나 저장하고 싶을 경우가 있다.
gerrit review 시스템을 사용하는 경우 commit을 push만 해두고 merge하지 않으면 된다. 하지만 gerrit의 장점인 자동으로 리뷰어가 추가되거나 commit을 체크하는 과정(ex, jenkins)가 멋대로 실행되어 원치 않게 commit이 공유되곤 한다.</p>
<p>이 경우에 사용할 수 있는 기능이 바로 WIP(work in progress)이다.
사용하는 방법은 gerrit으로 push하는 경우 %wip 를 붙여주는 것이다.</p>
<pre><code class="language-bash">$ git push origin HEAD:refs/for/master%wip</code></pre>
<p>위와 같이 wip로 push하게 되면 <code>Work in progress</code> 항목으로 업로드되며 별도의 탭에 관리된다.</p>
<p><img src="https://images.velog.io/images/dal-pi/post/547368a8-0631-4a61-a3c3-5979ceeb0989/image.png" alt="">
이 상태에서는 혼자 혹은 지정한 사람만 commit을 볼 수 있도록 할 수 있으며 gerrit에서 적용하는 자동 리뷰어 추가 등의 동작이 수행되지 않는다.
또한 <code>amend</code> 를 통한 patchset의 히스토리 편집 또한 가능하다.</p>
<p><img src="https://images.velog.io/images/dal-pi/post/987e2f2c-1533-4d88-86ac-272d5b297a43/image.png" alt=""></p>
<p>이 상태에서 자유롭게 <code>amend</code>로 편집하다가 review를 받을 준비가 되면 <code>start review</code> 를 눌러 publish 하면 보통의 commit으로 바뀌고 자동 tool 들이 적용된다.</p>
<p>wip에 대한 자세한 설명은 <a href="https://gerrit-review.googlesource.com/Documentation/intro-user.html">https://gerrit-review.googlesource.com/Documentation/intro-user.html</a> 을 참고하면 된다.</p>
<p>wip와 비슷한 기능으로 draft change 로 사용되는 경우도 있다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[c++] typedef 와 using의 차이점]]></title>
            <link>https://velog.io/@dal-pi/c-typedef-%EC%99%80-using%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@dal-pi/c-typedef-%EC%99%80-using%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Wed, 05 May 2021 16:02:23 GMT</pubDate>
            <description><![CDATA[<p>c++11 부터 사용할 수 있는 <code>using</code>키워드는 <code>typedef</code> 와 동일하게 type의 별칭을 만드는 키워드이다. 얼핏 사용하다 보면 같은 기능을 가진 것으로 보이지만 하나의 큰 차이점이 있다. 바로 template의 별칭을 지정할 수 있느냐 없느냐의 차이이다.</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
using ListType = vector&lt;T&gt;; // ok

template&lt;typename T&gt;
typedef vector&lt;T&gt; ListType; // error</code></pre>
<p>위의 특징 때문에 <code>using</code>키워드를 <strong>template alias</strong> 라고 부른다.
만약 template에 대한 별칭을 만들 필요가 없으며 c++11 보다 낮은 표준과의 이식성을 생각하면 typedef를 써도 전혀 문제는 없다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Scrum 과 Kanban]]></title>
            <link>https://velog.io/@dal-pi/Scrum-%EA%B3%BC-Kanban</link>
            <guid>https://velog.io/@dal-pi/Scrum-%EA%B3%BC-Kanban</guid>
            <pubDate>Wed, 05 May 2021 13:22:32 GMT</pubDate>
            <description><![CDATA[<p>스크럼에 대한 설명 및 칸반과의 차이점 정리</p>
<hr>
<h2 id="스크럼이란-무엇인가">스크럼이란 무엇인가?</h2>
<p><img src="https://images.velog.io/images/dal-pi/post/6f3d9428-4dde-4b41-ba32-086b1ab365a3/scrum_origin.png" alt="">
(미식축구의 포지션 이름에서 유래되었다)</p>
<blockquote>
<p>스크럼(Scrum)은 프로젝트관리를 위한 상호, 점진적 개발 방법론이며, 애자일(Agile) 소프트웨어 공학 중의 하나입니다. 스크럼은 소프트웨어 개발 프로젝트를 위하여 고안되었지만, 소프트웨어 유지보수 팀이나 일반적인 프로젝트/프로그램 관리에서도 적용될 수 있습니다. - 위키백과 </p>
<blockquote>
<p><strong>agile</strong><br>1.날렵한, 민첩한<br>2.(생각이) 재빠른, 기민한</p>
</blockquote>
</blockquote>
<p>스크럼은 프로세스 도구 중 하나로, 역할을 규정하며, 기간이 고정된 이터레이션을 규정하여 경험적으로 프로젝트를 관리해 나가는 방법이다.</p>
<p><img src="https://images.velog.io/images/dal-pi/post/08bb8a3a-4341-446b-a408-a946a4f3bafb/scrum_board.png" alt=""><img src="https://images.velog.io/images/dal-pi/post/22d334a1-75b9-4816-8327-39cf94606b35/scrum_board2.png" alt=""></p>
<p>(누구나 한 번쯤 봤을법한 스크럼 보드)</p>
<hr>
<h2 id="스크럼은-왜-하는가">스크럼은 왜 하는가?</h2>
<p><img src="https://images.velog.io/images/dal-pi/post/23d282e5-0e58-445d-80f7-dffcfbb74db5/srcum_why.png" alt=""></p>
<ul>
<li>계획한 대로 일이 진행되지 않음 : 스프린트 마다 계획과 프로세스를 수정하며 개선</li>
<li>요구사항이 수시로 바뀜 : 스프린트 백로그/제품 시연을 통해 백로그를 관리하고 한 회의 스프린트동안에는 백로그 추가를 허용하지 않음</li>
<li>자신의 능력으로 할 수 없는 일들에 막혀 있음 : 계획 회의/일일 미팅/회고 회의를 통해 팀원들간의 협력을 도모</li>
</ul>
<hr>
<h2 id="스크럼을-구성하는-인원들은-어떻게-이루어져-있는가">스크럼을 구성하는 인원들은 어떻게 이루어져 있는가?</h2>
<ol>
<li>Product Owner : 제품 책임자(프로젝트리더, PL 등이 될 수 있음)로서 제품 백 로그를 정의하여 우선순위를 정하는 역할이다.</li>
<li>Scrum Master : 스크럼 관리자 및 코치로서 일반적인 관리를 수행하는 프로젝트 관리자들과는 달리 팀원을 코칭하고 프로젝트의 문제 상황을 해결하는 역할을 하며, 제품 책임자가 독단적으로 목표를 결정하지 않게 고객과 관리자 및 팀원들이 모여서 목표를 정하도록 하는 역할이다.</li>
<li>Development Team : 개발팀은 팀원들이 주도적으로 스프린트 목표를 달성하기 위한 작업을 정해 나가도록 하는 역할이다. 물론, 작업을 정하고 할당하는데는 고객이나 제품 책임자와는 상관 없이 팀원 자율로 진행된다. 이와 같은 자율적인 행위를 통해서 팀원들은 의사를 활발하게 주고 받게 되고, 끈끈한 협업체계를 가지게 된다.</li>
</ol>
<hr>
<h2 id="스크럼은-어떻게-하는가">스크럼은 어떻게 하는가?</h2>
<h3 id="스크럼-프레임워크">스크럼 프레임워크</h3>
<p><img src="https://images.velog.io/images/dal-pi/post/563680c9-dff3-464b-b66b-0aebf8b173cf/scrum_ramework.png" alt=""></p>
<ol>
<li>제품에서 요구하는 기능과 우선순위를 제품 백로그로 정한다.</li>
<li>PO가 정한 제품의 우선순위에서 어디까지 작업을 할지 팀과 조율 한다. 조율하여 선정된 제품 백로그가 이번 스프린트의 목표가 된다.</li>
<li>스프린트 목표를 구현 가능 하도록 팀에서 스프린트 백로그를 작성한 뒤 작업을 할당한다.</li>
<li>스프린트를 진행하는 동안, 매일 정해진 장소와 시간에 모든 개발 팀원이 참여하는 일일 스크럼 회의를 가진다.</li>
<li>매회의 스프린트가 종료할 때마다, 스프린트 리뷰 미팅을 통해 만들어진 제품을 학습하고 이해 한다.</li>
<li>제품의 학습과 이해가 끝나면, 스프린트 회고를 통해 팀의 개발 프로세스에 대한 개선의 시간을 갖는다.</li>
<li>스프린트 기간 중 다음 스프린트를 준비 하기 위해 PO와 필요 인원이 모여 백로그를 준비하는 시간을 갖는다.</li>
</ol>
<hr>
<h2 id="스프린트sprint">스프린트(Sprint)</h2>
<blockquote>
<p><strong>sprint</strong><br>1.(짧은 거리를) 전력 질주하다<br>2.(달리기수영 등의) 단거리 경기<br>3.전력 질주</p>
</blockquote>
<p>스크럼에서 스프린트란 <strong>반복적인 개발 주기</strong> 를 뜻한다.<br>한 스프린트 내에서는 의미있는 산출물이 나오기 위한 목표를 설정한다.<br>정해진 목표는 그 누구도 팀원들의 동이 없이 바꿀 수 없다.</p>
<h3 id="스프린트를-이루는-요소">스프린트를 이루는 요소</h3>
<h4 id="1-스프린트-계획-회의-sprint-planning-meeting--스프린트가-끝나고-다음-스프린트의-시작-전">1. 스프린트 계획 회의 (Sprint Planning Meeting) : 스프린트가 끝나고 다음 스프린트의 시작 전</h4>
<p><img src="https://images.velog.io/images/dal-pi/post/e830ef74-b127-4f94-ba4c-7b6fb1c8e6ff/con_planning_req.png" alt=""><img src="https://images.velog.io/images/dal-pi/post/4391d306-8f71-4e6f-9291-9eb53897f6eb/con_planning_add_issue.png" alt=""></p>
<ul>
<li>스프린트 목표와 스프린트 백로그를 계획하는 회의</li>
<li>세부적으로 어떤 것을 구현해야 하는지에 대한 세부 작업 항목, 작업자, 예상 작업 시간 등을 수립한다.</li>
</ul>
<h4 id="2-일일-스크럼-회의daily-scrum-meeting---매일-정해진-시간에-일반적으로-15분">2. 일일 스크럼 회의(Daily Scrum Meeting) - 매일 정해진 시간에, 일반적으로 15분</h4>
<p><img src="https://images.velog.io/images/dal-pi/post/f3f0a25b-bce6-4246-9082-7b1c9452d55d/scrum_daily.png" alt=""> <img src="https://images.velog.io/images/dal-pi/post/5210041e-21fb-47d3-ba2c-ed37764ec43c/scrum_daily_flank.png" alt="">
(일반적인 일일 스크럼과 미팅 시간을 획기적으로 줄여주는 일일 스크럼 방법) </p>
<ul>
<li>매일 정해진 시간에 일어서서 한다.</li>
<li>모든 팀원이 참석한다.</li>
<li>스프린트 현황판에 대한 업데이트를 진행한다.</li>
<li>한 사람씩 어제 한 일과 오늘 할 일을 이야기하며 어려운 점이 있다면 이야기한다.</li>
<li>되도록 짧게 (일반적으로 15분) 한다.</li>
</ul>
<h4 id="3-스프린트-검토-회의-sprint-review---매-회-스프린트-종료-전">3. 스프린트 검토 회의 (Sprint Review) - 매 회 스프린트 종료 전</h4>
<p><img src="https://images.velog.io/images/dal-pi/post/63b5d566-f8ac-486d-8b57-661a7e4d9eee/con_review.png" alt=""></p>
<ul>
<li>고객이 요구했던 사항에 얼마나 부합하는지 참석자(고객포함)들 앞에서 시연한다.</li>
<li>개선할 점 등에 관해 피드백을 받는다</li>
</ul>
<h4 id="4-스프린트-회고-회의-sprint-retrospective---매-회-스프린트-종료-후-다음-스프린트-계획-회의-전">4. 스프린트 회고 회의 (Sprint Retrospective) - 매 회 스프린트 종료 후 다음 스프린트 계획 회의 전</h4>
<p><img src="https://images.velog.io/images/dal-pi/post/b504af44-24a2-40f8-9e9b-b77015443adf/con_retrospective.png" alt=""></p>
<ul>
<li>그동안 스프린트에서 수행한 활동과 개발한 것을 되돌아 보고, 개선점은 없는지, 팀이 정한 규칙이나 표준을 잘 준수했는지 검토한다.</li>
<li>단점보다는 강점을 찾아 극대화 시키는데 주안점을 둠</li>
<li>문제점을 확인하고 기록하는 정도로만 진행함.(문제점의 해결 방안을 찾는 회의가 아님)</li>
</ul>
<hr>
<h2 id="칸반kanban-이란">칸반(Kanban) 이란?</h2>
<p>일본어로 간판 이란 뜻이 어원으로 눈에 보이는 기록을 통해 제품을 개발하는 방법을 말한다.
많이 알려진 Scrum에 비해 Kanban은 조금 더 규범적이지 않은(rule이 적은) 개발 방법론이라고 할 수 있다.</p>
<p><img src="https://images.velog.io/images/dal-pi/post/0a4f5e17-ca62-4cf6-9330-c494bd195c7f/kanban_board.png" alt=""></p>
<hr>
<h2 id="칸반과-스크럼의-차이점">칸반과 스크럼의 차이점</h2>
<p>차이점 중 대표적인 몇 가지를 정리하면 이렇다.</p>
<ol>
<li><p>역할을 지정하지 않는다.
스크럼은 일반적으로 제품책임자/개발팀/스크럼 마스터 로 나뉘는데 반해 칸반은 이를 자유롭게 지정할 수 있다. (물론 스크럼도 유연하게 변경 가능하다)</p>
</li>
<li><p>기간이 고정된 이터레이션을 사용하지 않는다.
스크럼은 스프린트를 통하여 계획/개선/릴리즈 와 같은 단계를 밟아가며 경험적으로 개선해 나가지만, 칸반의 경우에는 이를 고정하지 않는다. 필요하면 스프린트의 이터레이션을 사용해도 좋고, 필요할 때마다 릴리즈하도록 지정할 수 있다.</p>
</li>
<li><p>워크플로우 상태에 기반하여 WIP(work in process)를 제한한다.
앞서 말했듯이 스프린트는 경험에 기반하여 매 스프린트마다 계획하는 일의 양을 조정하며 속도를 측정하지만, 칸반은 이터레이션을 사용하지 않기 때문에 워크플로우 상태에 WIP제한을 지정하여 일의 속도를 조절 및 파악한다. (보드 그림의 상태 이름 아래에 있는 숫자가 WIP를 의미하며 이를 넘는 아이템이 상태로 옮겨질 수 없다는 것을 의미한다)</p>
</li>
<li><p>이터레이션 내에서 변경이 가능하다.
스크럼은 변경이 발생할 경우 현재 스프린트의 아이템을 변경하지 않고 다음 스프린트 계획에 포함시키는 것에 비해 칸반은 백로그에 언제든지 아이템을 추가할 수 있다. 다만 이를 다음 상태로 전환할 때 앞서 말했던 WIP제한을 통해 상태별로 할 수 있는 아이템을 제한시켜 우선순위별로 처리될 수 있도록 할 것이다.</p>
</li>
<li><p>보드는 초기화되지 않는다.
스크럼은 스프린트마다 새롭게 보드를 구성하며 시작 시에 할일에 대부분, 끝날 때 완료에 대부분 아이템이 위치하는 반면 칸반은 그럴 필요가 없다. 따라서 칸반은 항상 할일/진행 중/완료 에 자유롭게 아이템이 위치하고 있다.</p>
</li>
</ol>
<hr>
<h2 id="칸반과-스크럼-중-어떤-것을-사용해야-하나">칸반과 스크럼 중 어떤 것을 사용해야 하나?</h2>
<p>깊게 들어가면 더 많은 차이점이 있지만 결국 칸반과 스크럼 둘 다 경험적으로 일을 개선하며 릴리즈한다는데 같은 목표가 있다. 음식을 먹을 때 포크와 젓가락 중 무엇을 고르냐는 음식의 종류에 따라 적절한 것을 고르는 것과 같이 제품이나 서비스의 성격에 맞추어 선택하여야 한다. 결국 둘 다 일을 할 때 쓰이는 도구이기 때문에 더 효율적인 도구를 찾아 그 도구를 손에 맞게 변형하여 사용한다고 생각하면 쉬울 것이다.</p>
<p>개인적으로는 개발 초기에는 스크럼을, 테스트나 유지보수 기간일때는 칸반을 주로 사용하면 적절할 것 같다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[c++] std::make_shared를 통해 private 생성자를 가진 class를 생성하는 방법]]></title>
            <link>https://velog.io/@dal-pi/c-stdmakeshared%EB%A5%BC-%ED%86%B5%ED%95%B4-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EA%B0%80%EC%A7%84-class%EB%A5%BC-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dal-pi/c-stdmakeshared%EB%A5%BC-%ED%86%B5%ED%95%B4-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EA%B0%80%EC%A7%84-class%EB%A5%BC-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 05 May 2021 07:37:19 GMT</pubDate>
            <description><![CDATA[<p>make_shared with private constructor</p>
<hr>
<p><code>std::enable_shared_from_this</code> 을 사용하거나 <code>std::shared_ptr</code>을 사용하여 객체를 생성하고자 할 때 아래의 create() 와 같은 factory method를 사용하곤 한다.</p>
<pre><code class="language-cpp">class A {
public:
    static shared_ptr&lt;A&gt; create() {
        return shared_ptr&lt;A&gt;(new A);
    }
private:
    A() { }
};

int main() {
    shared_ptr&lt;A&gt; sp_a = A::create();
}</code></pre>
<p>이 때 <code>std::shared_ptr</code>대신 예외 안정성이 높은 <code>std::make_shared</code>를 사용하는 경우 해당 클래스의 생성자가 private라면 컴파일 에러가 발생한다.  </p>
<pre><code class="language-cpp">static shared_ptr&lt;A&gt; create() {
    return make_shared&lt;A&gt;();
}</code></pre>
<pre><code class="language-bash">&#39;A::A&#39;: private 멤버(&#39;A&#39; 클래스에서 선언)에 액세스할 수 없습니다.    </code></pre>
<p>에러가 나는 이유는 <code>std::make_shared</code>는 함수 template 이며 A의 범위 밖에 있으므로 private영역을 볼 수 없기 때문이다.<br>최대한 간단한 형태로(실제로 더 복잡함) <code>std::make_shared</code>를 구성해보면 함수에 전달되는 <code>args</code>를 <code>T</code>에 그대로 perfect-forwarding하고 있음을 알 수 있다.</p>
<pre><code class="language-cpp">template&lt; class T, class... Args &gt;
shared_ptr&lt;T&gt; make_shared( Args&amp;&amp;... args ) {
    return new T(std::forward&lt;Args&gt;(args)...);
}</code></pre>
<p>어떻게든 <code>std::make_shared</code>를 쓰고 싶은(당신의 코드에서 모든 new 키워드가 보이지 않도록 하고 싶은) 경우에는 간단한 wrapper를 통해 동작하게 할 수 있다.  </p>
<pre><code class="language-cpp">class A {
public:
    static shared_ptr&lt;A&gt; create() {
    struct MakeSharedEnabler : public A {
        MakeSharedEnabler() : A() { }
    };
    return make_shared&lt;MakeSharedEnabler&gt;();
}
private:
    A() { }
};</code></pre>
<p><code>create()</code>는 <code>A</code>의 함수이므로 private 함수에 접근이 가능하며 리턴이 <code>MakeSharedEnabler</code> 형식으로 생성하지만 상속을 통해 <code>A</code>의 동작이 그대로 수행되도록 하는 방법이다.</p>
<p>출처 : <a href="https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const">https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const</a></p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[git] 하나의 파일만 hard reset하기]]></title>
            <link>https://velog.io/@dal-pi/git-%ED%95%98%EB%82%98%EC%9D%98-%ED%8C%8C%EC%9D%BC%EB%A7%8C-hard-reset%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/git-%ED%95%98%EB%82%98%EC%9D%98-%ED%8C%8C%EC%9D%BC%EB%A7%8C-hard-reset%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 05 May 2021 07:28:08 GMT</pubDate>
            <description><![CDATA[<p>Method to reset only some files.</p>
<hr>
<p>수정된 여러 파일들이 있는 경우 하나 혹은 몇 개만 되돌리고 싶을 때가 있다. 이런 경우에는</p>
<pre><code class="language-bash">$ git checkout -- &lt;파일&gt;</code></pre>
<p>을 사용하면 된다. </p>
<p>예를 들면</p>
<pre><code class="language-bash">$ git status
On branch master
Changes not staged for commit:
  (use &quot;git add &lt;file&gt;...&quot; to update what will be committed)
  (use &quot;git checkout -- &lt;file&gt;...&quot; to discard changes in working directory)
        ^-- 사실 git status를 치는 순간 알려주고 있다!

        modified:   A
        modified:   B

no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)</code></pre>
<p>와 같을 때 보통 현재 HEAD로 파일을 되돌릴 경우 hard reset을 사용하는데<br>hard reset은 모든 파일만 되돌릴 수 있으며 별개의 파일 목록에 대한 reset을 제공하지 않는다.  </p>
<pre><code class="language-bash">$ git reset --hard A
fatal: Cannot do hard reset with paths.</code></pre>
<p>이런 경우에는 checkout키워드를 사용하여  </p>
<pre><code class="language-bash">$ git checkout -- A

$ git status
On branch master
Changes not staged for commit:
  (use &quot;git add &lt;file&gt;...&quot; to update what will be committed)
  (use &quot;git checkout -- &lt;file&gt;...&quot; to discard changes in working directory)

        modified:   B

no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)</code></pre>
<p>와 같이 특정 파일만 reset할 수 있다.  </p>
<p>이 때 checkout 뒤의 <code>--</code>의 의미는 branch 이름과 file 이름을 구분하기 위함이다<br>정확히 하면 <code>&lt;tree-ish&gt;</code> 와 <code>&lt;pathsepc&gt;</code>으로 표현하는데 자세한 설명은 <a href="https://git-scm.com/docs/git-checkout">git-checkout 문서</a>를 참조.  </p>
<p>이것도 간단한 예를 들면<br><code>master</code> 라는 파일을 <code>master</code> 브렌치에서 수정하고 있는 경우에(이렇게 사용하는 사람이 있을까 싶지만..)  </p>
<pre><code class="language-bash">$ git status
On branch master
Changes not staged for commit:
  (use &quot;git add &lt;file&gt;...&quot; to update what will be committed)
  (use &quot;git checkout -- &lt;file&gt;...&quot; to discard changes in working directory)

        modified:   master

no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)

$ git checkout master
Already on &#39;master&#39;
M       master</code></pre>
<p><code>git checkout master</code>를 하면 master branch로 체크아웃 하려 하는 것으로 이해한다.<br><code>--</code>를 쓴 뒤의 master는 파일 이름으로 해석한다. 이 또한 자세한 설명은 <a href="https://git-scm.com/docs/git-checkout">git-checkout 문서</a>를 참조.  </p>
<pre><code class="language-bash">$ git checkout -- master

$ git status
On branch master
nothing to commit, working tree clean</code></pre>
<p>branch 명을 생략하면 현재 HEAD를 의미하므로 <code>git checkout master -- master</code>를 의미한다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[c++] 레퍼런스로 다형성 사용하기]]></title>
            <link>https://velog.io/@dal-pi/%EB%A0%88%ED%8D%BC%EB%9F%B0%EC%8A%A4%EB%A1%9C-%EB%8B%A4%ED%98%95%EC%84%B1-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/%EB%A0%88%ED%8D%BC%EB%9F%B0%EC%8A%A4%EB%A1%9C-%EB%8B%A4%ED%98%95%EC%84%B1-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 05 May 2021 07:17:26 GMT</pubDate>
            <description><![CDATA[<p>Polymorphism by Reference</p>
<hr>
<p>객체지향 언어인 C++에서 다형성(Polymorphism)을 사용하려면 보통 raw pointer, 혹은 smart pointer를 사용한다고 알려져 있다.<br>하지만 reference로도 동일하게 다형성을 구현할 수 있다.</p>
<pre><code class="language-cpp">class Base {
public:
    virtual void foo() {
        cout &lt;&lt; &quot;Base::foo() called&quot; &lt;&lt; endl;
    }
};

class Derived : public Base {
public:
    virtual void foo() override { // override는 쓰면 손해볼일이 없는 좋은 키워드다
        cout &lt;&lt; &quot;Derived::foo() called&quot; &lt;&lt; endl;
    }
};

int main() {
    Derived derived;
    Base* p_base = &amp;derived;
    p_base-&gt;foo();

    shared_ptr&lt;Base&gt; sp_base = make_shared&lt;Derived&gt;();
    sp_base-&gt;foo();

    Base&amp; ref_base = derived;
    ref_base.foo();

    return 0;
}</code></pre>
<p>결과는 모두 <code>Base</code>가 아닌 <code>Derived</code>의 <code>foo()</code>가 불리게 된다</p>
<pre><code>Derived::foo() called
Derived::foo() called
Derived::foo() called</code></pre><p>레퍼런스를 통한 다형성의 구현은 스마트포인터를 사용한 구현의 문제점을 해결하는데 사용할 수 있다.<br>아래의 <code>MyClass</code>에서 <code>shared_ptr</code>로 <code>Base</code>의 다형성을 사용하려는데 <code>Derived</code>가 <code>singleton</code>인 경우를 생각하면 아래와 같은 문제가 발생할 수 있다.</p>
<pre><code class="language-cpp">//(Base)

class Derived : public Base {
public:
    static Derived&amp; getInstance() {
        //mayer&#39;s singleton
        static Derived instance;
        return instance;
    }

    virtual void foo() override {
        cout &lt;&lt; &quot;Derived::foo() called&quot; &lt;&lt; endl;
    }
private:
    Derived() {}
};

class MyClass {
public:
    MyClass(shared_ptr&lt;Base&gt; base) : mBase(base) {}
private:
    shared_ptr&lt;Base&gt; mBase;
};

int main() {
    MyClass myClass1 = MyClass(shared_ptr&lt;Base&gt;(&amp;Derived::getInstance())); // Do not! 종료시 Derived singleton객체를 파괴하려 한다
    MyClass myClass2 = MyClass(shared_ptr&lt;Base&gt;(&amp;Derived::getInstance())); // Do not! Derived singleton객체의 control block이 중복 생성된다

    return 0;
}</code></pre>
<p>raw pointer를 사용해도 해결이 되지만 raw pointer의 사용을 지양하고자 하면(애초에 smart pointer는 raw pointer를 사용하지 않기 위해 사용하기에)<br>레퍼런스를 사용하면 좋은 해결 방안이 된다.</p>
<pre><code class="language-cpp">//(Base)
//(Derived)

class MyClass {
public:
    MyClass(Base&amp; base) : mBase(base) {}
private:
    Base&amp; mBase;
};

int main() {
    MyClass myClass1 = MyClass(Derived::getInstance());
    MyClass myClass2 = MyClass(Derived::getInstance());

    return 0;
}</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[c++] 기본 매개변수(Default Parameter)가 있는 함수의 상속]]></title>
            <link>https://velog.io/@dal-pi/%EA%B8%B0%EB%B3%B8-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98Default-Parameter%EA%B0%80-%EC%9E%88%EB%8A%94-%ED%95%A8%EC%88%98%EC%9D%98-%EC%83%81%EC%86%8D</link>
            <guid>https://velog.io/@dal-pi/%EA%B8%B0%EB%B3%B8-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98Default-Parameter%EA%B0%80-%EC%9E%88%EB%8A%94-%ED%95%A8%EC%88%98%EC%9D%98-%EC%83%81%EC%86%8D</guid>
            <pubDate>Tue, 04 May 2021 16:07:03 GMT</pubDate>
            <description><![CDATA[<p>기본 매개변수(Default Parameter)가 있는 함수는 override하면 의도하지 않은 동작을 할 수 있다.</p>
<blockquote>
<p>어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자</p>
</blockquote>
<p>Effective C++의 한 항목 제목이다. 왜 절대 하지 말라는지 아래 코드와 를 보면 알 수 있다.</p>
<pre><code class="language-cpp">class Base
{
public:
    virtual void print(int num = 1) = 0;
};

class Derived : public Base
{
public:
    virtual void print(int num = 3) override //기본 매개변수를 변경하였다
    {
        cout &lt;&lt; num &lt;&lt; endl;
    }
};

int main()
{
    Derived derived;
    derived.print();

    Base* pBase = &amp;derived;
    pBase-&gt;print();
}</code></pre>
<p><code>Derived::print()</code>는 <code>Base::print()</code>를 override하면서 기본 매개변수를 변경하였다.<br><code>pBase</code>가 <code>Base</code>의 포인터지만 가상함수 테이블에 따라 <code>Derived::print()</code>가 불리며 3이 출력될 것 같지만 결과는 아래와 같다.</p>
<pre><code>3
1 //왜 이게 나오지?</code></pre><p>원인은 기본 매개변수는 <strong>정적으로 바인딩</strong>되며 가상 함수는 런타임에 <strong>동적으로 바인딩</strong>되기 때문이다.
쉽게 말하면 파생(Derived) 클래스의 가상 함수를 호출해도 기본(Base) 클래스의 기본 매개변수를 사용해 버린다는 이야기다.
그렇다면 기본 매개변수를 항상 동일하게 맞춰 주면 괜찮은가? 하고 생각해보면 문제는 없어질 테지만 Base 클래스의 기본 매개변수가 변경되면 반드시 Derived 클래스도 바꿔줘야 하는 의존성이 생겨 좋지 않다.</p>
<p>그렇다면 기본 매개변수를 재정의하지 못한다면 어떻게 <code>Derived::print()</code> 를 호출할 수 있을까? 만약 기본 매개변수를 지우면 매개변수가 필요한 <code>Derived::print(int)</code> 로만 함수를 호출할 수 있게 된다.
이 문제를 해결하는 방법은 <strong>&quot;비 가상 인터페이스&quot;</strong> 라는 방법이다. 비슷한 용어로 Wrapper 정도를 들 수 있겠다.
요지는 기본 매개변수를 override할 수 없게 하는 대신 동작에 대한 부분만 override하도록 제공하는 것이다.</p>
<pre><code class="language-cpp">class Base
{
public:
    void print(int num = 1) //비 가상 인터페이스
    {
        doPrint(num);
    }
private:
    virtual void doPrint(int num) = 0; //동작을 재정의하려면 doPrint를 override하면 된다
};

class Derived : public Base
{
private:
    virtual void doPrint(int num) override
    {
        cout &lt;&lt; num &lt;&lt; endl;
    }
};

//(...)</code></pre>
<p>이제 파생 클래스는 <code>Base::print()</code>를 override할 수 없지만 <code>Derived::print()</code>가 호출 가능하게 되며 위에서 설명한 기본 매개변수로 인한 문제까지 예방할 수 있다.</p>
<p>실무에서도 자주 접하는 내용인데 잘못할 여지가 많아 보이는 내용이다.
기본 클래스를 설계하는 개발자는 이런 부분까지 염두해 둘 수 있어야 한다는 것을 새삼 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[c++] 간단하게 Singleton 구현하기 (feat. C++11)]]></title>
            <link>https://velog.io/@dal-pi/C11%EC%97%90%EC%84%9C-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-Singleton-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/C11%EC%97%90%EC%84%9C-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-Singleton-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 May 2021 10:29:04 GMT</pubDate>
            <description><![CDATA[<p>아래와 같이 작성하면 간단하게 Thread-safe한 Singleton이 구현된다.</p>
<pre><code class="language-cpp">class Singleton
{
public:
    static Singleton&amp; getInstance()
    {
        static Singleton instance;
        return instance;
    }
};</code></pre>
<p>이게 가능한 이유는 C+11의 아래 규칙 때문이다.</p>
<blockquote>
<p>C++11에서 정적 지역변수의 초기화는 멀티스레드 환경에서도 한 번만 수행됨이 보장된다.</p>
</blockquote>
<p>원문은 이렇다.</p>
<blockquote>
<p>If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.</p>
</blockquote>
<p>물론 클래스의 생성자 내에서 또 다른 복잡한 동기화가 필요하거나 언어 표준을 확실하게 지원하지 않는 환경에서는 부가적인 동기화 코드들을 추가하여야 한다.</p>
<p>위의 코드는 동기화 문제에 대한 부분만이므로 &quot;컴파일러가 자동으로 만들어 주는 함수들(Effective C++ 참고)&quot;이나 상속에 대한 부분 까지 신경쓰면 아래와 같이 구성할 수 있다. 
(이 때 move 생성자 및 move 할당 연산자는 소멸자나 복사 생성/할당 연산자 를 명시적으로 선언하면 자동 생성되지 않는다.)</p>
<pre><code class="language-cpp">class Singleton
{
public:
    Singleton(const Singleton&amp;) = delete;
    Singleton&amp; operator=(const Singleton&amp;) = delete;
    static Singleton&amp; getInstance()
    {
        static Singleton instance;
        return instance;
    }
    void callFunction() {}

protected:
    Singleton() {}
    virtual ~Singleton() {}
};</code></pre>
<p>위와 같이 만드는 singleton을 보통 <code>Meyer’s Singleton</code> 이라 부른다. effective 시리즈의 저자 이름인 scott meyers 에서 비롯된것으로 보인다.
간단한 Singleton을 구성할 때 템플릿 삼아 사용하면 유용하다.</p>
<p>하지만 구조적인 관점에서 전역 인스턴스처럼 쓰이는 Singleton은 되도록 사용하지 않는 것이 제일 좋은 것 같다. 편리함에는 반드시 따르는 위험이나 단점이 있기 마련이다.(ex. 단위테스트가 어려움)</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[gtest] Singleton mocking하기]]></title>
            <link>https://velog.io/@dal-pi/Singleton-mocking%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dal-pi/Singleton-mocking%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 02 May 2021 09:03:15 GMT</pubDate>
            <description><![CDATA[<p>Singleton은 인터페이스를 통해 추상화하지 않고 해당 클래스를 그대로 참조하므로 mock을 만드는데 어려움이 있다.
<a href="https://google.github.io/googletest/gmock_cook_book.html#MockingNonVirtualMethods">#MockingNonVirtualMethods</a> 를 활용하면 template를 통해 mocking이 가능하도록 만들 수 있다.</p>
<h2 id="1-class-이름이-typename이-되도록-수정하기">1. Class 이름이 typename이 되도록 수정하기</h2>
<p>일단 Singleton과 이를 사용하는 User에 대한 코드이다.
Singleton에 대한 구현은 <a href="https://velog.io/@dal-pi/C11%EC%97%90%EC%84%9C-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-Singleton-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0">간단한 Singleton 작성하기</a>를 참고</p>
<pre><code class="language-cpp">class Singleton
{
public:
    static Singleton&amp; getInstance()
    {
        static Singleton instance;
        return instance;
    }
    void callFunction() { /*do something*/ } //테스트하고자 하는 함수, 심지어 가상함수도 아니다.
};</code></pre>
<pre><code class="language-cpp">class User
{
public:
    void act()
    {
        Singleton::getInstance().callFunction(); //당연하지만 &quot;Singleton&quot;은 class이름으로 사용되고 있다
    }
};</code></pre>
<p>User 클래스는 Singleton을 직접 참조하고 있으므로 Singleton 클래스를 mock으로 대체하여 테스트할 수 없는 상태이다.</p>
<p>이 때 Singleton의 클래스이름이 typename이 되도록 테스트하는 코드에 template을 적용한다.
(Singleton 을 흔히 사용하는 T 가 된다고 생각하면 편하다)<br>이렇게 하면 User::act() 함수 안에서는 typename Singleton을 만족하는 모든 타입들에 대해 동일하게 callFunction()을 호출할 수 있게 된다.</p>
<pre><code class="language-cpp">template &lt;typename Singleton&gt;
class User
{
public:
    void act()
    {
        Singleton::getInstance().callFunction(); //이제부터 &quot;Singleton&quot;은 typename이다!
    }
};</code></pre>
<h2 id="2-typename에-맞게-mocking하기">2. typename에 맞게 mocking하기</h2>
<p>동일하게 동작하도록 getInstance() 를 만들어주고 테스트할 함수인 callFunction()을 mocking 해 준다.</p>
<pre><code class="language-cpp">class MockSingleton //Singleton을 상속받는 것이 아니다!
{
public:
    static MockSingleton&amp; getInstance() //getInstance의 함수 이름이 있어야 하며 원래의 class Singleton처럼 동작하도록 작성한다.
    {
        static MockSingleton instance;
        return instance;
    }
    MOCK_METHOD(void, callFunction, ()); //Singleton을 상속받는 것이 아니므로 override가 아니다
};

TEST(UserTest, SingletonCallTest)
{
    User&lt;MockSingleton&gt; user;
    EXPECT_CALL(MockSingleton::getInstance(), callFunction).Times(1);

    user.act(); //callFunction()이 한 번 호출되어 pass된다.
}</code></pre>
<p>테스트하고자 하는 원본 코드에도 수정이 가해야져 하는 단점이 있지만 Singleton을 mocking할 수 있다는 점에서 쓸만해 보인다.</p>
<hr>
]]></description>
        </item>
    </channel>
</rss>