<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Analog</title>
        <link>https://velog.io/</link>
        <description>배우면 까먹는 개발자 😵‍💫</description>
        <lastBuildDate>Sat, 18 Jan 2025 07:29:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Analog</title>
            <url>https://velog.velcdn.com/images/cafefarm-johnny/profile/9d3415f9-36ec-43ad-ad36-0547040f326d/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Analog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cafefarm-johnny" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Flutter Lint 구성]]></title>
            <link>https://velog.io/@cafefarm-johnny/Flutter-Lint-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@cafefarm-johnny/Flutter-Lint-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sat, 18 Jan 2025 07:29:59 GMT</pubDate>
            <description><![CDATA[<h1 id="lint">Lint</h1>
<p>팀에서 Flutter 프로젝트를 진행하고 있는데 코딩 컨벤션을 비롯하여 스타일을 통일하기 위해 Lint를 알아보았다.</p>
<h1 id="analysis_optionsyaml">analysis_options.yaml</h1>
<p>Flutter 프로젝트를 생성하면 프로젝트 루트에 <code>analysis_options.yaml</code> 파일이 생성된다. </p>
<pre><code class="language-bash">$ ll
total 88
-rw-r--r--   1 johnnyuhm  staff   566B Jun 25  2022 README.md
-rw-r--r--   1 johnnyuhm  staff   1.4K Jun 25  2022 analysis_options.yaml # &lt;- 요놈
drwxr-xr-x  13 johnnyuhm  staff   416B Jun 25  2022 android
drwxr-xr-x   9 johnnyuhm  staff   288B Jul 24  2022 build
drwxr-xr-x   4 johnnyuhm  staff   128B Jun 26  2022 images
drwxr-xr-x   7 johnnyuhm  staff   224B Jun 25  2022 ios
drwxr-xr-x   5 johnnyuhm  staff   160B Jun 25  2022 lib
-rw-r--r--@  1 johnnyuhm  staff   5.9K Jan 18 16:12 pubspec.lock
-rw-r--r--   1 johnnyuhm  staff   3.6K Jun 26  2022 pubspec.yaml
drwxr-xr-x   3 johnnyuhm  staff    96B Jun 25  2022 test
-rw-r--r--   1 johnnyuhm  staff    23K Dec 23  2022 youtube_clone.iml</code></pre>
<p>이 파일은 다음과 같이 작성되어 있다. 기본적으로 flutter팀에서 정의하고 있는 Lint 스타일을 <code>include</code> 명령어를 통해 따르도록 구성되어 있다.
대표적인 예시로 코드 끝에 콤마 찍으면 개행되는게 이것 때문이다.</p>
<pre><code class="language-bash">$ cat analysis_options.yaml

include: package:flutter_lints/flutter.yaml # 기본적으로 flutter_lints 패키지의 룰을 따른다.

linter:
  rules: # 위 룰을 기반으로, 추가적으로 사용하고 싶은 Lint 룰이 있다면 여기에 작성하기만 하면 된다.
    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule</code></pre>
<h2 id="dart-lint-rules">Dart Lint Rules</h2>
<p>Dart 언어에 적용할 수 있는 Lint 룰들은 다음의 링크를 통해 확인할 수 있다. 필요한 룰 항목이 있다면 위에서 언급한 <code>analysis_options.yaml</code> 파일에 추가하기만 하면 된다.</p>
<blockquote>
<p><a href="https://dart.dev/tools/linter-rules">https://dart.dev/tools/linter-rules</a></p>
</blockquote>
<h1 id="flutter-analyze">flutter analyze</h1>
<p>위의 과정을 통해 Lint 구성을 완료했다면 다음과 같은 명령어를 통해 소스코드가 Lint를 잘 지키고 있는지 검토해볼 수 있다.</p>
<pre><code class="language-bash">$ flutter analyze

Analyzing youtube_clone...                                              

   info • Use &#39;const&#39; with the constructor to improve performance • lib/main.dart:14:12 • prefer_const_constructors
   info • Use &#39;const&#39; with the constructor to improve performance • lib/pages/home.dart:23:13 • prefer_const_constructors
   info • Constructors for public widgets should have a named &#39;key&#39; parameter • lib/widgets/bottom_navigation_bar.dart:3:7 • use_key_in_widget_constructors
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:10:18 • prefer_const_constructors
   info • Use &#39;const&#39; literals as arguments to constructors of &#39;@immutable&#39; classes • lib/widgets/bottom_navigation_bar.dart:12:23 • prefer_const_literals_to_create_immutables
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:13:15 • prefer_const_constructors
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:22:18 • prefer_const_constructors
   info • Use &#39;const&#39; literals as arguments to constructors of &#39;@immutable&#39; classes • lib/widgets/bottom_navigation_bar.dart:24:23 • prefer_const_literals_to_create_immutables
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:25:15 • prefer_const_constructors
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:34:18 • prefer_const_constructors
   info • Use &#39;const&#39; literals as arguments to constructors of &#39;@immutable&#39; classes • lib/widgets/bottom_navigation_bar.dart:36:23 • prefer_const_literals_to_create_immutables
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:37:15 • prefer_const_constructors
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:46:18 • prefer_const_constructors
   info • Use &#39;const&#39; literals as arguments to constructors of &#39;@immutable&#39; classes • lib/widgets/bottom_navigation_bar.dart:48:23 • prefer_const_literals_to_create_immutables
   info • Use &#39;const&#39; with the constructor to improve performance • lib/widgets/bottom_navigation_bar.dart:49:15 • prefer_const_constructors

15 issues found. (ran in 1.5s)</code></pre>
<p>룰에 위배되는 사항이 있는 경우 소스 파일 정보들과 어떠한 Lint 룰을 위배했는지 나타내준다.</p>
<p>이 과정으로 Flutter Lint 환경 구성을 완료했고, 이제 이를 Git hook을 통해 자동화 및 강제화를 팀에 적용했다.</p>
<h1 id="git-hook-구성">Git hook 구성</h1>
<p>합의한 Flutter Lint 룰을 모든 팀원에게 강제화하기 위해 Git hook을 통해 pre-commit 단계에 적용하여 커밋 과정에서 Lint를 강제화하였고, 룰에 위배되는 코드 사항이 생길 경우 이를 보고하여 반드시 수정하도록 환경을 구성했다.</p>
<p>Git hook 디렉토리는 다음과 같은 위치에 있다.</p>
<pre><code class="language-txt">$ cd &lt;프로젝트 루트&gt;/.git/hook

$ ll
total 120
-rwxr-xr-x  1 johnnyuhm  staff   478B Aug 18 04:18 applypatch-msg.sample
-rwxr-xr-x  1 johnnyuhm  staff   896B Aug 18 04:18 commit-msg.sample
-rwxr-xr-x  1 johnnyuhm  staff   4.6K Aug 18 04:18 fsmonitor-watchman.sample
-rwxr-xr-x  1 johnnyuhm  staff   189B Aug 18 04:18 post-update.sample
-rwxr-xr-x  1 johnnyuhm  staff   424B Aug 18 04:18 pre-applypatch.sample
-rwxr-xr-x  1 johnnyuhm  staff   1.6K Aug 18 04:18 pre-commit.sample # &lt;- 이 녀석이 git commit 명령 수행 전 동작하는 hook 파일이다.
-rwxr-xr-x  1 johnnyuhm  staff   416B Aug 18 04:18 pre-merge-commit.sample
-rwxr-xr-x  1 johnnyuhm  staff   1.3K Aug 18 04:18 pre-push.sample
-rwxr-xr-x  1 johnnyuhm  staff   4.8K Aug 18 04:18 pre-rebase.sample
-rwxr-xr-x  1 johnnyuhm  staff   544B Aug 18 04:18 pre-receive.sample
-rwxr-xr-x  1 johnnyuhm  staff   1.5K Aug 18 04:18 prepare-commit-msg.sample
-rwxr-xr-x  1 johnnyuhm  staff   2.7K Aug 18 04:18 push-to-checkout.sample
-rwxr-xr-x  1 johnnyuhm  staff   3.6K Aug 18 04:18 update.sample</code></pre>
<p>pre-commit.sample 파일을 수정하여 Flutter Lint가 동작하는 스크립트를 작성해보자</p>
<pre><code class="language-bash">$ cp ./pre-commit.sample ./pre-commit # pre-commit 쉘 스크립트를 만들기 위해 pre-commit.sample 을 복사한다. 이 때 확장자는 제거한다.
$ chmod +x ./pre-commit # 실행 권한을 부여하고
$ vi ./pre-commit # 편집기로 파일을 연다.</code></pre>
<p>pre-commit 파일을 열고 아래의 스크립트를 작성해주자.</p>
<pre><code class="language-bash">#!/bin/bash

#### dart fix와 dart format 명령어를 통해 스타일과 개행을 프로젝트 전역적으로 한번 수행한다.
echo &quot;dart fix --apply 명령어 수행 중...&quot;
dart fix --apply
dart format .

#### 이후 flutter analyze를 수행하여 Lint를 위배하는 사항이 없는지 조사하고, 결과 레포트에서 Lint 룰에 위배된 사항이 나오는 문자열 항목들만 grep 명령어를 통해 변수로 저장한다.
echo &quot;flutter analyze 명령어 수행 중...&quot;
analyze_result=$(flutter analyze | grep -Ei &quot;info • |warning • |error • &quot;)

#### Lint 룰에 위배되는 사항이 있으면 결과를 출력하고 커밋을 중지한다.
if [ -n &quot;$analyze_result&quot; ]; then
    echo &quot;⚠️ Lint 룰에 위배되는 사항이 있습니다. 결과를 확인하여 코드를 수정해주세요. ⚠️&quot;
    echo &quot;$analyze_result&quot;

    exit 1
fi</code></pre>
<p>이렇게 팀에 Flutter Lint 강제하는 환경을 구성했다. v</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Riverpod]]></title>
            <link>https://velog.io/@cafefarm-johnny/RiverPod</link>
            <guid>https://velog.io/@cafefarm-johnny/RiverPod</guid>
            <pubDate>Sat, 17 Aug 2024 20:45:13 GMT</pubDate>
            <description><![CDATA[<h1 id="riverpod">Riverpod?</h1>
<p>Flutter 애플리케이션에서 상태 관리를 쉽게 관리할 수 있도록 도와주는 반응형 캐싱 프레임워크이다.</p>
<p>Riverpod은 상태관리를 위해 사용하는 주요 라이브러리인 Provider의 한계를 극복하고 개선된 기능을 제공하기 위한 목적으로 개발되었다.</p>
<p>Riverpod의 주요 특징은 다음과 같다.</p>
<ul>
<li>쉬운 전역 상태 관리 구현: Riverpod을 사용하면 전역 상태 관리를 쉽게 구현할 수 있다. 상태는 전역적으로 사용 가능하면서도 필요한 경우에만 초기화되며, 메모리 관리도 자동으로 이루어진다.</li>
<li>의존성 주입: Riverpod은 종속성을 명확하게 관리할 수 있다.</li>
<li>AutoDispose: 더 이상 필요하지 않은 provider들을 자동으로 제거하여 메모리 관리를 해준다.</li>
<li>테스트 용이성: 상태 관리 로직과 UI로직을 분리하여 관리하기 때문에 테스트 작성에 용이하다. 또 provider를 모킹할 수도 있어 다양한 테스트 시나리오를 쉽게 구성할 수 있다.</li>
<li>유연성: Riverpod은 다양한 상태 관리 패턴을 지원하기 때문에 간단한 상태 관리부터 복잡한 상태 관리까지 대응할 수 있다.</li>
</ul>
<h2 id="provider">provider?</h2>
<p>Riverpod은 모든 provider를 선언적으로 정의하고, 타입 안정성을 제공하기 때문에 컴파일 타임에 오류를 발견할 수 있다.</p>
<p>provider의 종류로는 다음과 같다.</p>
<ul>
<li>provider: Riverpod의 기본 요소로, 데이터를 제공하고 상태를 관리하며, 종속성을 주입하는 역할을 수행한다. </li>
<li>StateProvider: 단순한 상태를 관리하는 provider이다.</li>
<li>FutureProvider: 비동기 작업을 관리하고, <code>Future</code>객체를 반환하는 provider이다. API 호출이나, 데이터베이스 쿼리와 같은 비동기 작업에 사용된다.</li>
<li>StreamProvider: 스트림 데이터를 관리하는 provider이다. 데이터를 실시간으로 업데이트해야 할 경우에 사용한다.</li>
<li>StateNotifierProvider: 상태 변화가 복잡한 경우에 사용할 수 있는 provider이다. <code>StateNotifier</code>를 통해 상태를 관리하고, 변경사항을 전파할 수 있다.</li>
</ul>
<h1 id="설치">설치</h1>
<p>pubspec.yaml에 다음과 같이 의존성을 추가한 후 <code>flutter pub get</code>으로 다운로드한다.</p>
<pre><code class="language-yaml">name: my_app_name
environment:
  sdk: &quot;&gt;=3.0.0 &lt;4.0.0&quot;
  flutter: &quot;&gt;=3.0.0&quot;

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  build_runner:
  custom_lint:
  riverpod_generator: ^2.4.2
  riverpod_lint: ^2.3.12</code></pre>
<h2 id="lint-활성화하기">Lint 활성화하기</h2>
<p>Riverpod은 riverpod_lint 패키지를 제공한다. lint를 통해 코드 작성에 린트 규칙을 적용할 수 있다.
프로젝트에 <code>analysis_options.yaml</code>파일을 생성하고 다음의 내용을 추가한다.</p>
<pre><code class="language-yaml">analyzer:
  plugins:
    - custom_lint</code></pre>
<p>위 내용을 추가하고 나면 코드 작성 시 Riverpod 관련 코드에서 lint가 경고를 안내한다.</p>
<blockquote>
<p>lint에 대한 규칙은 아래의 riverpod_lint pub dev 페이지를 참고하자.
<a href="https://pub.dev/packages/riverpod_lint">https://pub.dev/packages/riverpod_lint</a></p>
</blockquote>
<h2 id="android-studio-plugins">Android Studio Plugins</h2>
<p>Flutter Riverpod Snippets를 사용하여 간단한 스니펫으로 Riverpod 기능을 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/ed1fc5ab-131b-4a69-8350-1cc99192f44b/image.png" alt=""></p>
<h1 id="hello-world">Hello, world!</h1>
<p>Riverpod을 통해 상태 데이터를 사용하는 기본적인 방법은 다음과 같다.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter_riverpod/flutter_riverpod.dart&#39;;
import &#39;package:riverpod_annotation/riverpod_annotation.dart&#39;;

part &#39;main.g.dart&#39;;

void main() {
  runApp(
    // 1. root 위젯에서 providers를 관리할 수 있게 전체 앱을 ProviderScope으로 감싼다. 
    // 이렇게하면 애플리케이션 전체에 Riverpod이 활성화된다.
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

// 2. provider를 생성한다.
@riverpod
String helloWorld(HelloWorldRef ref) {
  return &quot;Hello, World!&quot;;
}

// 3. Riverpod에서 관리되는 ConsumerWidget을 확장한다.
class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final String word = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(&quot;example&quot;),
        ),
        body: Center(
          child: Text(word),
        ),
      ),
    );
  }
}</code></pre>
<p>위 코드를 작성하면 빨간 밑줄이 나타나는데, 터미널에 다음의 명령어를 입력하여 Code generator를 통해 <code>main.g.dart</code> 파일을 생성한다.</p>
<pre><code class="language-bash">$ fvm flutter pub run build_runner watch
Deprecated. Use `dart run` instead.
Building package executable... 
Built build_runner:build_runner.
[INFO] Generating build script completed, took 159ms
[INFO] Setting up file watchers completed, took 6ms
[INFO] Waiting for all file watchers to be ready completed, took 201ms
[INFO] Reading cached asset graph completed, took 106ms
[INFO] Checking for updates since last build completed, took 607ms
[INFO] Running build completed, took 6ms
[INFO] Caching finalized dependency graph completed, took 37ms
[INFO] Succeeded after 46ms with 0 outputs (0 actions)
[INFO] ------------------------------------------------------------------------
[INFO] Starting Build
[INFO] Updating asset graph completed, took 3ms
[WARNING] source_gen:combining_builder on lib/main.dart:
main.g.dart must be included as a part directive in the input library with:
    part &#39;main.g.dart&#39;;
[INFO] Running build completed, took 8.3s
[INFO] Caching finalized dependency graph completed, took 59ms
[INFO] Succeeded after 8.4s with 519 outputs (1038 actions)
[INFO] ------------------------------------------------------------------------
[INFO] Starting Build
[INFO] Updating asset graph completed, took 2ms
[INFO] Running build completed, took 36ms
[INFO] Caching finalized dependency graph completed, took 67ms
[INFO] Succeeded after 107ms with 2 outputs (2 actions)</code></pre>
<p>이후 프로젝트를 에뮬레이터에 실행할 수 있고, <code>lib</code>디렉토리 하위에 다음과 같이 <code>main.g.dart</code> 파일이 생성된다.</p>
<pre><code class="language-dart">// GENERATED CODE - DO NOT MODIFY BY HAND

part of &#39;main.dart&#39;;

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$helloWorldHash() =&gt; r&#39;2ed8016b6f9c8762482dd2701393dd3d69c48550&#39;;

/// See also [helloWorld].
@ProviderFor(helloWorld)
final helloWorldProvider = AutoDisposeProvider&lt;String&gt;.internal(
  helloWorld,
  name: r&#39;helloWorldProvider&#39;,
  debugGetCreateSourceHash:
      const bool.fromEnvironment(&#39;dart.vm.product&#39;) ? null : _$helloWorldHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef HelloWorldRef = AutoDisposeProviderRef&lt;String&gt;;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member</code></pre>
<ul>
<li><code>@riverpod</code>: <ul>
<li>모든 provider는 <code>@riverpod</code>, <code>@Riverpod()</code>로 어노테이션을 부착해야 한다. </li>
<li>이 어노테이션은 전역 함수나 클래스에 부착할 수 있고, 어노테이션을 통해 provider를 구성할 수 있다.</li>
</ul>
</li>
<li><code>String helloWorld(...) {}</code>: <ul>
<li>어노테이션이 부착된 함수의 이름에 따라 provider와 상호작용하는 방식이 결정된다. <code>helloWorld</code>함수의 이름에 따라 Code generator를 통해 <code>helloWorldProvider</code>변수가 생성된다.</li>
<li>어노테이션이 부착된 함수는 첫번째 맥변수로 &quot;ref&quot;를 지정해야 한다. 그 외에도 제네릭을 포함하여 여러개의 매개변수를 정의할 수도 있다.</li>
<li>정의한 함수는 provider를 처음 읽을 때 호출된다. 이후 읽기는 함수를 호출하지 않고, 캐싱된 값을 반환한다. (=캐싱 기능을 포함한다.)</li>
</ul>
</li>
<li>Ref: <ul>
<li>다른 providers와 상호작용하기 위해e 사용되는 객체이다.</li>
<li>함수명을 prefix로 가지며, 마지막 suffix로 Ref가 붙는다.<ul>
<li>예) <code>Future&lt;User&gt; fetchUser(FetchUserRef ref) {}</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/e637e0df-c04f-4b00-b795-7bc4ee5c6b53/image.png" alt=""></p>
<h1 id="사용-방법">사용 방법</h1>
<p>Riverpod을 사용하여 네트워크 통신을 수행하는 간단한 앱을 만들어보며 각 요소들을 알아보자.</p>
<h2 id="provider-scope">Provider Scope</h2>
<p>가장먼저 내 앱에 Riverpod을 구성하기 위해서 root 위젯을 <code>ProviderScope</code> 위젯으로 감싸야 한다.</p>
<pre><code class="language-dart">void main() {
    runApp(
        ProviderScope(
            child: MyApp(),
        ),
    );
}</code></pre>
<p>ProviderScope 위젯으로 감싸면 모든 영역에서 provider에 접근할 수 있게 된다.</p>
<h2 id="프로젝트-구조">프로젝트 구조</h2>
<p>Riverpod을 사용하는 주요 목적 중 하나는 복잡해져가는 앱의 비즈니스 로직과 UI로직의 분리와 간편한 상태 관리라 할 수 있다.
비즈니스 로직과 UI 로직을 분리하기 위해 다음과 같이 디렉토리를 생성하자.</p>
<ul>
<li><code>libs</code><ul>
<li><code>/providers</code></li>
<li><code>/models</code></li>
<li><code>/views</code></li>
</ul>
</li>
</ul>
<p>이제 각각 디렉토리에 해당하는 파일들을 생성하며 앱을 만들어보자.</p>
<h2 id="provider-1">provider</h2>
<p>provider는 네트워크 통신과 같은 비즈니스 로직을 담당한다.
회원들의 프로필 목록을 불러오는 API가 있다고 가정하고 fetch 기능을 구현한다.</p>
<pre><code class="language-dart">/// user_provider.dart
&#39;package:riverpod_annotation/riverpod_annotation.dart&#39;;

part &#39;user_provider.g.dart&#39;;

// provider임을 명시하기 위해 @riverpod 어노테이션을 부착한다.
@riverpod
Future&lt;List&lt;Profile&gt;&gt; fetchProfiles(FetchProfilesRef ref) async {
  // 1. API를 통해 회원들의 프로필 목록을 불러온다.
  final response = await http.get(
    Uri.parse(
      &#39;https://gist.githubusercontent.com/&#39;
      &#39;cafefarm-johnny/&#39;
      &#39;ad6ca270049e3fbd24be0cae2e20fd6d/&#39;
      &#39;raw/&#39;
      &#39;e20114cc476ddb54cdc33bf2223dfc55c74074a3/&#39;
      &#39;chat_app_peoples_data.json&#39;,
    ),
  );

  if (kDebugMode) {
    print(&#39;status code: ${response.statusCode}&#39;);
    print(&#39;response body: ${response.body}&#39;);
  }

  // 2. 불러온 데이터를 데이터 형식에 맞게 적절한 자료구조로 파싱한다.
  final json = jsonDecode(response.body) as Map&lt;String, dynamic&gt;;
  final rawProfiles = json[&#39;Johnny&#39;] as List&lt;dynamic&gt;;

  if (rawProfiles.isEmpty) {
    return List.empty();
  }

  // 3. raw 데이터를 모델 데이터로 변환하여 반환한다.
  return rawProfiles.map((rawProfile) =&gt; Profile.fromJson(rawProfile)).toList();
}</code></pre>
<ul>
<li><code>@riverpod</code> 어노테이션을 부착한 함수에서 반환되는 값에 대해 상태 관리를 시작한다.</li>
</ul>
<blockquote>
<p><code>@riverpod</code> 어노테이션은 top-level 함수에만 부착이 가능하다. 따라서 top-level이 클래스인 경우 클래스 내부의 메서드에는 부착할 경우 오류가 발생한다.</p>
</blockquote>
<h2 id="model">Model</h2>
<p>네트워크로 조회해온 데이터를 바인딩할 모델을 정의한다.</p>
<pre><code class="language-dart">class Profile {
  const Profile({
    required this.url,
    required this.nickname,
    required this.statusMessage,
  });

  final String url;
  final String nickname;
  final String statusMessage;

  factory Profile.fromJson(Map&lt;String, dynamic&gt; json) {
    return Profile(
      url: json[&#39;url&#39;] as String,
      nickname: json[&#39;nickname&#39;] as String,
      statusMessage: json[&#39;statusMessage&#39;] as String,
    );
  }
}</code></pre>
<h2 id="widget">Widget</h2>
<p>불러온 정보를 ListView로 간단하게 보여주는 UI를 작성하자.</p>
<ul>
<li>통신 과정 시에는 로딩 인디케이터를 노출한다. </li>
<li>오류가 발생하면 화면에 오류 메시지를 노출한다. </li>
<li>통신에 성공하여 데이터를 받아오면 프로필 목록을 스크롤 가능한 ListView로 렌더링한다.</li>
</ul>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter_riverpod/flutter_riverpod.dart&#39;;

class UserList extends StatelessWidget {
  const UserList({super.key});

  @override
  Widget build(BuildContext context) {
    // 1. Consumer 위젯을 통해 Ref에 접근하고
    return Consumer(
      builder: (context, ref, child) {
        // 2. 필요로 하는 provider를 호출한다.
        final AsyncValue&lt;List&lt;Profile&gt;&gt; profiles = ref.watch(
          fetchProfilesProvider,
        );

        return Center(
          // 3. 패턴매칭을 사용하여 각 상태에 따른 위젯을 반환한다.
          child: switch (profiles) {
            // 3-1. API 통신에 성공했다면 ListView를 반환한다.
            AsyncData(:final value) =&gt; ListView.builder(
                itemCount: value.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    leading: CircleAvatar(
                      backgroundImage: NetworkImage(value[index].url),
                    ),
                    title: Text(value[index].nickname),
                    subtitle: Text(value[index].statusMessage),
                  );
                },
              ),
            // 3-2. API 통신에 실패했다면 에러 메시지를 반환한다.
            AsyncError() =&gt; const Text(&#39;Error occur!&#39;),
            // 3-3. 통신 과정 중이라면 로딩 인디케이터를 반환한다.
            _ =&gt; const CircularProgressIndicator(),
          },
        );
      },
    );
  }
}
</code></pre>
<p>이제 앱을 실행해보면 다음과 같이 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/4d7953a6-8ff2-425d-a56a-40103561be9d/image.gif" alt=""></p>
<p>위 과정을 수행하여 앱을 실행해보며 테스트해보면 몇 가지 특징을 알 수 있다.</p>
<ul>
<li>Consumer는 provider가 업데이트될 때 마다 re-build를 수행한다.</li>
<li>provider는 Lazy 형태로 동작한다. 따라서 해당 provider를 사용하는 위젯이 없다면 네트워크 요청이 수행되지 않는다.</li>
<li>요청이 수행된 후 결과는 캐싱되어 이후 provider를 호출하더라도 네트워크 요청이 수행되지 않는다.</li>
<li>UI가 provider를 사용하지 않으면 캐시는 삭제된다. 즉, 다시 UI가 provider를 사용할 때 네트워크 요청이 수행된다.</li>
<li>통신에 오류가 발생할 경우 provider 내부에서 오류를 처리하고 AsyncError를 반환한다.</li>
</ul>
<h2 id="consumer">Consumer</h2>
<p>UI로직에서 provider를 통해 비즈니스 로직을 호출하기 위해서는 <code>WidgetRef</code>라는 객체를 필요로 한다. provider를 정의할 때는 ref 객체를 매개변수로 정의하기 때문에 접근할 수 있다. UI에서는 ref 객체에 어떻게 접근할 수 있을까?</p>
<p>Riverpod은 <code>WidgetRef</code> 객체에 접근하기 위해 <code>Consumer</code>라는 위젯을 제공한다. <code>Consumer</code> 위젯으로 렌더링할 위젯을 감싸면 ref 객체에 접근할 수 있게되고, 이를 통해 provider에 접근할 수 있게 된다.</p>
<pre><code class="language-dart">@sealed
class Consumer extends ConsumerWidget {
  /// {@macro riverpod.consumer}
  const Consumer({super.key, required ConsumerBuilder builder, Widget? child})
      : _child = child,
        _builder = builder;

  final ConsumerBuilder _builder;
  final Widget? _child;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return _builder(context, ref, _child);
  }
}</code></pre>
<p>위의 View에서 작성했던 코드를 다시 보자.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext context) {
  // 1. Consumer 위젯을 통해 Ref에 접근하고
  return Consumer(
    builder: (context, ref, child) {
      // 2. 필요로 하는 Provider를 호출한다.
      final AsyncValue&lt;List&lt;Profile&gt;&gt; profiles = ref.watch(
          fetchProfilesProvider,
      );

      return Center(...);
    },
  );
}</code></pre>
<p>정리하면 </p>
<ul>
<li>위젯에서 Provider를 호출하기 위해서는 <code>WidgetRef</code> 객체에 접근해야 한다.</li>
<li><code>WidgetRef</code>객체에 접근할 수 있게 <code>Consumer</code> 위젯을 제공한다.</li>
</ul>
<h3 id="consumerwidget">ConsumerWidget</h3>
<p><code>Consumer</code>위젯은 <code>WidgetRef</code>에 접근하기 위해 제공되는 위젯이지만 다른 위젯을 감싸서 사용하기 때문에 들여쓰기가 깊어진다는 문제가 발생한다.
이를 해결하기 위해 <code>ConsumerWidget</code>, <code>ConsumerStatefulWidget</code>을 제공한다. 이 위젯들은 <code>StatelessWidget</code>, <code>StatefulWidget</code>과 <code>Consumer</code> 위젯을 결합한 위젯이다.</p>
<p>사용 방법은 다음과 같다.</p>
<pre><code class="language-dart">// 1. StatelessWidget 대신 ConsumerWidget을 확장한다.
class UserList extends ConsumerWidget {
  const UserList({super.key});

  // 2. Consumer를 통해 Ref에 접근했던 방식에서 
  // build 메서드를 오버라이딩하며 접근할 수 있게 되었다.
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final AsyncValue&lt;List&lt;Profile&gt;&gt; profiles = ref.watch(
      fetchProfilesProvider,
    );

    // UI 로직은 변경 없이 Consumer만 제거되었다.
    return Center(...);
  }
}</code></pre>
<p>이전과 다르게 <code>extends StatelessWidget</code>이 <code>extends ConsumerWidget</code>으로 변경되었고, <code>build()</code>메서드 내부에 <code>Consumer</code> 위젯이 제거되어 들여쓰기가 줄어들었다.</p>
<h3 id="consumerstatefulwidget">ConsumerStatefulWidget</h3>
<p>마찬가지로 <code>StatefulWidget</code>에 해당하는 위젯은 <code>ConsumerStatefulWidget</code>을 사용하면 된다.</p>
<pre><code class="language-dart">// 1. StatefulWidget 대신 ConsumerStatefulWidget을 확장한다.
class UserList extends ConsumerStatefulWidget {
  const UserList({super.key});

  @override
  ConsumerState createState() =&gt; _UserListState();
}

// 2. State 대신 ConsumerState를 확장한다.
class _UserListState extends ConsumerState&lt;UserList&gt; {
  @override
  void initState() {
    super.initState();

    // 3. 라이프사이클 과정에서 ConsumerState의 프로퍼티로써 WidgetRef에 접근할 수 있다. 
    ref.listenManual(fetchProfilesProvider, (previous, next) {
      // provider에 리스너를 등록하여 스낵바 등을 구현할 수 있다.
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text(&#39;최신 친구 목록을 조회했습니다.&#39;),
        ),
      );
    });
  }

  // 3. ConsumerStatefulWidget에서는 Ref가 build 메서드의 매개변수로 제공되지 않고,
  // ConsumerState의 프로퍼티로써 접근할 수 있다.
  @override
  Widget build(BuildContext context) {
    final AsyncValue&lt;List&lt;Profile&gt;&gt; profiles = ref.watch(
      fetchProfilesProvider,
    );

    return Center(...);
  }
}</code></pre>
<h2 id="notifier">Notifier</h2>
<p>지금까지는 외부에서 데이터를 가져오고 상태 데이터를 생성하는 과정이었다. 하지만 앱은 데이터를 가져오기만 할 뿐 만 아니라 수정, 삭제등의 행위를 통해 데이터를 변경하며 UI를 갱신해야 한다.</p>
<p>UI 정보를 갱신하기 위해서는 Consumer위젯이 상태 데이터를 바라보며 상태 데이터의 변경을 감지할 수 있어야 한다.</p>
<p>provider는 관심사 분리를 보장하기 위해 자신의 상태 데이터를 변경할 수 있는 방법을 Consumer로 제공하지 않는다. 대신 <code>Notifier</code>를 통해 상태 데이터를 변경할 수 있는 방법을 제공한다.</p>
<p>이전에 작성한 top-level 함수가 정의되어 있던 provider를 top-level 클래스로 변경하여 <code>Notifier</code>를 확장해야 한다.</p>
<pre><code class="language-dart">part &#39;user_provider.g.dart&#39;;

/// 네트워크 처리는 비즈니스 로직에 해당한다.
/// Riverpod에서 비즈니스 로직은 provider 내부에 작성한다.

// 1. provider를 class로 승격하여 Notifier로 변경한다.
@riverpod
class UserNotifier extends _$UserNotifier {
  // 2. 이전에 있었던 fetchProfilesProvider 데이터 fetch 로직을 build 메서드로 이동한다.
  @override
  Future&lt;List&lt;Profile&gt;&gt; build() async {
    return await _fetchProfiles();
  }

  Future&lt;List&lt;Profile&gt;&gt; _fetchProfiles() async {
    ...
  }
}</code></pre>
<ul>
<li><code>Notifier</code>: <ul>
<li><code>@riverpod</code> 어노테이션을 클래스 레벨에 부착하면 클래스를 Notifier로 승격된다. </li>
<li>클래스는 반드시 <code>_$Notifier</code>를 확장해야 한다.</li>
<li>Notifier는 provider의 상태를 수정하는 메서드를 제공해야 한다.</li>
<li>Consumer는 <code>WidgetRef.read(provider.notifier).methodName()</code>를 사용하여 액세스 할 수 있다.</li>
</ul>
</li>
<li><code>build()</code>: <ul>
<li>Notifier는 반드시 <code>build</code>메서드를 override 해야 한다.</li>
<li><code>build</code> 메서드는 절대 직접 호출하면 안된다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>⚠️ 주의사항
notifier 생성자에서는 <code>ref</code> 및 기타 프로퍼티에 접근할 수 없기 때문에 생성자 내부에 로직을 작성하면 예외가 발생한다. </p>
</blockquote>
<pre><code class="language-dart">class MyNotifier extends $_MyNotifier {
  MyNotifier() {
    // ❌ 이렇게 하지 마세요.
    // 이 경우 예외가 발생합니다.
    state = AsyncValue.data(42);
  }
  @override
  Result build() {
    // ✅ 대신 이렇게 하세요.
    state = AsyncValue.data(42);
  }
}</code></pre>
<h3 id="상태-변수-갱신하기">상태 변수 갱신하기</h3>
<p>위의 앱에서 floating 버튼을 하나 생성하고 클릭하면 새로운 사용자가 목록에 추가되도록 사용자를 추가하는 기능을 구현하며 상태 변수를 갱신하는 방법을 알아보자.</p>
<p>가장 먼저 Notifier에 프로필 추가 기능을 구현하자.</p>
<pre><code class="language-dart">part &#39;user_provider.g.dart&#39;;

@riverpod
class UserNotifier extends _$UserNotifier {
  // 1. 외부 데이터 베이스를 흉내내기 위해 데이터 저장 변수를 선언한다.
  final List&lt;Profile&gt; _db = [];

  @override
  Future&lt;List&lt;Profile&gt;&gt; build() async {
    return await _fetch();
  }

  Future&lt;List&lt;Profile&gt;&gt; _fetch() async {
    ...

    if (rawProfiles.isEmpty) {
      return List.empty();
    }

    // 3. build 메서드가 re-build될 때 데이터가 갱신될 수 있도록 
    // addAll() 메서드로 신규 데이터를 추가하여 반환한다.
    return rawProfiles
        .map((rawProfile) =&gt; Profile.fromJson(rawProfile))
        .toList()
      ..addAll(_db);
  }

  // 2. Create 로직을 추가한다.
  Future&lt;void&gt; addUser(Profile profile) async {
    // (저장) like 네트워크 통신 처리
    Future.delayed(const Duration(seconds: 3));
    _db.add(profile);

    // POST 요청이 완료되면 로컬 캐시를 Dirty 상태로 변경한다.
    // 로컬 캐시가 Dirty 상태로 변경되면 notifier의 build 메서드가 비동기적으로 재호출되며,
    // 리스너들에게 변경사항을 전달한다.
    ref.invalidateSelf();

    // 상태가 새로 갱신될 때 까지 대기한다.
    await future;
  }
}</code></pre>
<ul>
<li><code>ref.invalidateSelf()</code>:<ul>
<li>로컬 캐시의 데이터를 Dirty 상태로 변경한다.</li>
<li>로컬 캐시가 Dirty로 변경되면 각 Consumer들은 로컬 캐시를 갱신하기 위해 re-build 라이프사이클을 수행한다.</li>
</ul>
</li>
</ul>
<p>신규 프로필 정보를 추가하는 Floating 버튼을 구현한다.</p>
<pre><code class="language-dart">class AddUserFloatingButton extends ConsumerStatefulWidget {
  const AddUserFloatingButton({super.key});

  @override
  ConsumerState&lt;AddUserFloatingButton&gt; createState() =&gt;
      _AddUserFloatingButtonState();
}

class _AddUserFloatingButtonState extends ConsumerState&lt;AddUserFloatingButton&gt; {
  int _count = 0;

  void addUserRandomly(WidgetRef ref) {
    _count++;

    Profile newProfile = Profile(
      nickname: &#39;Jay$_count&#39;,
      statusMessage: &#39;반가워요!!&#39;,
      url: &#39;https://via.placeholder.com/200/000000/FFFFFF/?text=Jay$_count&#39;,
    );

    // ref.read를 사용하여 notifier에 접근한다.
    // notifier에 접근하여 addUser 메서드를 호출한다.
    ref.read(userNotifierProvider.notifier).addUser(newProfile);
  }

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () =&gt; addUserRandomly(ref),
      child: const Icon(Icons.add),
    );
  }
}</code></pre>
<blockquote>
<p>Notifier의 <code>addUser()</code>를 호출할 때 <code>ref.watch</code>가 아닌  <code>ref.read</code>를 호출한다.
<code>ref.watch</code>도 기술적으로는 동작할 수 있지만, onPressed와 같은 이벤트 핸들러에서 로직이 수행되는 경우에는 <code>ref.read</code>를 사용하는 것이 좋다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/21cde918-fc5b-4fe3-803b-d1b590f6bbf4/image.gif" alt=""></p>
<h3 id="build-메서드로-매개변수-전달하기">build 메서드로 매개변수 전달하기</h3>
<p>데이터를 조회해오는 API는 흔히 페이지네이션 매개변수를 가진다.</p>
<p>Consumer에서 Notifier 또는 provider로 매개변수를 전달해야 페이지네이션 처리가 가능해지는데 이를 어떻게 구현하는지 알아보자.</p>
<pre><code class="language-dart">part &#39;user_provider.g.dart&#39;;

@riverpod
class UserNotifier extends _$UserNotifier {
  final List&lt;Profile&gt; _db = [];

  // 매개변수를 받고자 하면 그냥 build 메서드 시그니처에 매개변수를 선언하면 된다.
  @override
  Future&lt;List&lt;Profile&gt;&gt; build([int size = 0]) async {
    return await _fetch(size);
  }

  Future&lt;List&lt;Profile&gt;&gt; _fetch(int size) async {
    ...
  }
}</code></pre>
<p>Notifier 또는 provider에서는 매개변수를 받고자하는 경우 해당하는 build 메서드나 함수에 매개변수를 정의하면 된다.</p>
<p>이후 UI에서 Notifier 또는 provider를 호출하는 로직에서 매개변수를 전달하면 된다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext context) {
  final AsyncValue&lt;List&lt;Profile&gt;&gt; profiles = ref.watch(
    userNotifierProvider(5), // 매개변수 전달
  );
}</code></pre>
<p>여기서 캐싱과 관련된 몇가지 특징이 나타나는데 그것은 다음과 같다.</p>
<ul>
<li>매개변수를 provider/Notifier에 전달할 때 매개변수 유형별로 캐시된다.</li>
<li>예를 들어, <code>size = 5</code> 매개변수를 전달하면 네트워크 요청이 캐싱되어 이후 <code>size = 5</code>로 매개변수가 전달되면 캐싱된 응답을 전달한다.</li>
</ul>
<h1 id="code-generator">Code Generator?</h1>
<p>Code Generator는 내가 작성한 코드를 기반으로 Riverpod에 필요한 부가적인 코드들을 생산하는 도구이다. </p>
<p>예를 들어 아래와 같이 Notifier 구현 코드를 작성하면 Code Generator가 이를 읽고 해당하는 Dart 코드를 생성해준다.</p>
<pre><code class="language-dart">part &#39;user_provider.g.dart&#39;; // 반드시 Code Generator가 생성한 파일을 part 문법으로 선언해야지만 Riverpod 코드에 대해 컴파일러가 인식한다.
@riverpod
class UserNotifier extends _$UserNotifier {
  final List&lt;Profile&gt; _db = [];

  @override
  Future&lt;List&lt;Profile&gt;&gt; build([int size = 0]) async {
    return await _fetch(size);
  }

  Future&lt;List&lt;Profile&gt;&gt; _fetch(int size) async {
    ...
  }

  Future&lt;void&gt; addUser(Profile profile) async {
    ...
  }
}</code></pre>
<p>위와 같이 짧게 작성한 코드가 실은 Dart 언어로 작성 시 아래와 같이 매우 긴 코드가 되어야 하는 것이다.</p>
<pre><code class="language-dart">// user_provider.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of &#39;user_provider.dart&#39;;

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$userNotifierHash() =&gt; r&#39;99ab39ac7d8c07e6876affb49c4ed9510089e9cc&#39;;

/// Copied from Dart SDK
class _SystemHash {
  _SystemHash._();

  static int combine(int hash, int value) {
    // ignore: parameter_assignments
    hash = 0x1fffffff &amp; (hash + value);
    // ignore: parameter_assignments
    hash = 0x1fffffff &amp; (hash + ((0x0007ffff &amp; hash) &lt;&lt; 10));
    return hash ^ (hash &gt;&gt; 6);
  }

  static int finish(int hash) {
    // ignore: parameter_assignments
    hash = 0x1fffffff &amp; (hash + ((0x03ffffff &amp; hash) &lt;&lt; 3));
    // ignore: parameter_assignments
    hash = hash ^ (hash &gt;&gt; 11);
    return 0x1fffffff &amp; (hash + ((0x00003fff &amp; hash) &lt;&lt; 15));
  }
}

abstract class _$UserNotifier
    extends BuildlessAutoDisposeAsyncNotifier&lt;List&lt;Profile&gt;&gt; {
  late final int size;

  FutureOr&lt;List&lt;Profile&gt;&gt; build([
    int size = 0,
  ]);
}

/// Copied from [UserNotifier].
@ProviderFor(UserNotifier)
const userNotifierProvider = UserNotifierFamily();

/// Copied from [UserNotifier].
class UserNotifierFamily extends Family&lt;AsyncValue&lt;List&lt;Profile&gt;&gt;&gt; {
  /// Copied from [UserNotifier].
  const UserNotifierFamily();

  /// Copied from [UserNotifier].
  UserNotifierProvider call([
    int size = 0,
  ]) {
    return UserNotifierProvider(
      size,
    );
  }

  @override
  UserNotifierProvider getProviderOverride(
    covariant UserNotifierProvider provider,
  ) {
    return call(
      provider.size,
    );
  }

  static const Iterable&lt;ProviderOrFamily&gt;? _dependencies = null;

  @override
  Iterable&lt;ProviderOrFamily&gt;? get dependencies =&gt; _dependencies;

  static const Iterable&lt;ProviderOrFamily&gt;? _allTransitiveDependencies = null;

  @override
  Iterable&lt;ProviderOrFamily&gt;? get allTransitiveDependencies =&gt;
      _allTransitiveDependencies;

  @override
  String? get name =&gt; r&#39;userNotifierProvider&#39;;
}

/// Copied from [UserNotifier].
class UserNotifierProvider
    extends AutoDisposeAsyncNotifierProviderImpl&lt;UserNotifier, List&lt;Profile&gt;&gt; {
  /// Copied from [UserNotifier].
  UserNotifierProvider([
    int size = 0,
  ]) : this._internal(
          () =&gt; UserNotifier()..size = size,
          from: userNotifierProvider,
          name: r&#39;userNotifierProvider&#39;,
          debugGetCreateSourceHash:
              const bool.fromEnvironment(&#39;dart.vm.product&#39;)
                  ? null
                  : _$userNotifierHash,
          dependencies: UserNotifierFamily._dependencies,
          allTransitiveDependencies:
              UserNotifierFamily._allTransitiveDependencies,
          size: size,
        );

  UserNotifierProvider._internal(
    super._createNotifier, {
    required super.name,
    required super.dependencies,
    required super.allTransitiveDependencies,
    required super.debugGetCreateSourceHash,
    required super.from,
    required this.size,
  }) : super.internal();

  final int size;

  @override
  FutureOr&lt;List&lt;Profile&gt;&gt; runNotifierBuild(
    covariant UserNotifier notifier,
  ) {
    return notifier.build(
      size,
    );
  }

  @override
  Override overrideWith(UserNotifier Function() create) {
    return ProviderOverride(
      origin: this,
      override: UserNotifierProvider._internal(
        () =&gt; create()..size = size,
        from: from,
        name: null,
        dependencies: null,
        allTransitiveDependencies: null,
        debugGetCreateSourceHash: null,
        size: size,
      ),
    );
  }

  @override
  AutoDisposeAsyncNotifierProviderElement&lt;UserNotifier, List&lt;Profile&gt;&gt;
      createElement() {
    return _UserNotifierProviderElement(this);
  }

  @override
  bool operator ==(Object other) {
    return other is UserNotifierProvider &amp;&amp; other.size == size;
  }

  @override
  int get hashCode {
    var hash = _SystemHash.combine(0, runtimeType.hashCode);
    hash = _SystemHash.combine(hash, size.hashCode);

    return _SystemHash.finish(hash);
  }
}

mixin UserNotifierRef on AutoDisposeAsyncNotifierProviderRef&lt;List&lt;Profile&gt;&gt; {
  /// The parameter `size` of this provider.
  int get size;
}

class _UserNotifierProviderElement
    extends AutoDisposeAsyncNotifierProviderElement&lt;UserNotifier, List&lt;Profile&gt;&gt;
    with UserNotifierRef {
  _UserNotifierProviderElement(super.provider);

  @override
  int get size =&gt; (origin as UserNotifierProvider).size;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member</code></pre>
<p>Code Generator는 이러한 불편한 코드 작성을 편리하게 도와주어 코드 작성 시 개발자가 보일러플레이트를 작성하지 않게 도와준다. 뿐 아니라 아래와 같은 장점도 있다.</p>
<ul>
<li>상태를 가지는 핫 리로드(stateful hot-reload)를 사용할 수 있다.</li>
<li>원활한 디버깅을 할 수 있도록 추가적인 메타데이터를 생성하고 디버거가 수집한다.</li>
<li>일부 Riverpod 기능은 Code Generator를 통해서만 사용이 가능하다.</li>
</ul>
<h1 id="providernotifier-문법">provider/Notifier 문법</h1>
<h2 id="sync">Sync</h2>
<h3 id="functional-provider">Functional (provider)</h3>
<pre><code class="language-dart">@riverpod
String example(ExampleRef ref) {
  return &#39;foo&#39;;
}</code></pre>
<h3 id="class-based-notifier">Class-Based (Notifier)</h3>
<pre><code class="language-dart">@riverpod
class Example extends _$ExampleNotifier {
  @override
  String build() {
    return &#39;foo&#39;;
  }
}</code></pre>
<h2 id="asyncfuture">Async/Future</h2>
<h3 id="functional">Functional</h3>
<pre><code class="language-dart">@riverpod
Future&lt;String&gt; example(ExampleRef ref) async {
  return Future.value(&#39;foo&#39;);
}</code></pre>
<h3 id="class-based">Class-Based</h3>
<pre><code class="language-dart">@riverpod
class Example extends _$ExampleNotifier {
  @override
  Future&lt;String&gt; build() async {
    return Future.value(&#39;foo&#39;);
  }
}</code></pre>
<h2 id="asyncstream">Async/Stream</h2>
<h3 id="functional-1">Functional</h3>
<pre><code class="language-dart">@riverpod
Stream&lt;String&gt; example(ExampleRef ref) async* {
  yield &#39;foo&#39;;
}</code></pre>
<h3 id="class-based-1">Class-Based</h3>
<pre><code class="language-dart">@riverpod
class Example extends _$ExampleNotifier {
  @override
  Stream&lt;String&gt; build() async* {
    yield &#39;foo&#39;;
  }
}</code></pre>
<h1 id="provider-vs-riverpod">Provider vs Riverpod</h1>
<h2 id="정의-방식">정의 방식</h2>
<p><code>pkg:Provider</code>에서는 provider가 위젯이기 때문에 위젯 트리 안에 코드가 배치된다.</p>
<pre><code class="language-dart">class Counter extends ChangeNotifier {
 ...
}

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider&lt;Counter&gt;(create: (context) =&gt; Counter()),
      ],
      child: MyApp(),
    )
  );
}</code></pre>
<p><code>Riverpod</code>에서 provider는 위젯이 아닌 Dart 객체이다. 그래서 위젯 트리 외부에서 정의된다.</p>
<pre><code class="language-dart">// Provider는 최상위 변수
final counterProvider = ChangeNotifierProvider&lt;Counter&gt;((ref) =&gt; Counter());

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}</code></pre>
<h2 id="provider-호출-방식">provider 호출 방식</h2>
<p><code>pkg:Provider</code>에서는 provider에 접근하기 위해 <code>BuildContext</code>를 사용한다.</p>
<pre><code class="language-dart">class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Model model = context.watch&lt;Model&gt;();
    ...
  }
}</code></pre>
<p><code>Riverpod</code>에서는 StatelessWidget 대신 <code>ConsumerWidget</code>을 확장하며, BuildContext가 아닌 <code>WidgetRef</code>라는 별도의 객체를 통해 provider에 접근한다.</p>
<pre><code class="language-dart">class Example extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    Model model = ref.watch(modelProvider);
    ...
  }
}</code></pre>
<p>Riverpod은 제네릭 타입에 의존하지 않고, 정의된 provider를 통해 생성된 변수에 의존한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태 관리]]></title>
            <link>https://velog.io/@cafefarm-johnny/%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@cafefarm-johnny/%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 27 Apr 2023 15:13:34 GMT</pubDate>
            <description><![CDATA[<p><code>Do it! 깡샘의 플러터 &amp; 다트 프로그래밍</code> 책에서 상태 관리를 위한 다양한 내용들을 읽으면서 사실 왜 상태 관리를 위해 다른 패키지를 사용해야 하는지 근본적인 이유를 이해하지 못했다. 책의 설명이 부족한건지 아니면 내가 바보라서 인지 ㅎㅎㅎ;;</p>
<p>오히려 <a href="https://docs.flutter.dev/data-and-backend/state-mgmt/intro">docs.flutter.dev</a> 사이트에 쉽고 명확하게 설명하고 있어서 해당 내용을 읽으면서 상태 데이터에 대한 종류, 구분 방법, 관리를 위해 외부 패키지를 사용하는 등의 이유를 이해했다.</p>
<h1 id="상태-관리">상태 관리</h1>
<p>유저와 상호 작용하는 프로그램은 여러 화면 간에 프로그램에서 관리하는 데이터를 서로 공유하며 사용해야 하는 상황이 발생한다. </p>
<p>아래의 예시를 보자.
<img src="https://velog.velcdn.com/images/cafefarm-johnny/post/01a0bebe-3c34-47f3-9a85-309cef456a05/image.gif" alt="상태 관리 예시"></p>
<p>사용자는 상품을 구매하기 위해 상품을 장바구니에 추가하고<code>(데이터)</code> 장바구니로 이동하여 내가 선택한 상품 정보를 확인할 수 있다. <code>(공유)</code></p>
<p>흔히 상태라고 부르는 일반적인 의미는 <code>UI를 리빌드 하기 위해 필요한 데이터</code>를 의미하며 이러한 데이터를 공유하기 위해 글로벌 스코프로 선언하여 여러 위젯에서 접근하거나, 특정한 위젯 내부에 선언하여 로컬 스코프로 선언하여 관리하는 행위를 뜻한다.</p>
<h1 id="상태-종류">상태 종류</h1>
<p>상태는 <code>임시 상태(ephemeral state)</code>와 <code>앱 상태(app state)</code> 2가지 유형으로 분류 할 수 있다.</p>
<h2 id="임시-상태-ephemeral-state">임시 상태 (Ephemeral state)</h2>
<blockquote>
<p>임시 상태는 UI상태(UI state) 또는 로컬 상태(local state)라고도 칭한다.</p>
</blockquote>
<p>임시 상태는 현재 위젯에서 사용하는 상태 데이터를 칭한다.
예로 다음과 같다.</p>
<ul>
<li>현재 페이지를 구성하는 데이터</li>
<li>애니메이션의 진행 상황을 표현하기 위한 데이터</li>
<li><code>BottomNavigationBar</code> 위젯에서 선택된 탭을 판단하기 위한 데이터</li>
</ul>
<p>다른 위젯에서는 이런 유형의 상태 데이터에 접근할 필요가 거의 없다. 이런 종류의 상태는 Redux 같은 상태 관리 기법을 사용할 필요가 없이 <code>StatefulWidget</code>을 사용해서 관리하는 것으로 충분하다.</p>
<p><code>BottomNavigationBar</code>를 빌드하는 예제 코드를 통해 어떠한 의미인지 확인해보자.</p>
<pre><code class="language-dart">// 1. 임시 상태를 관리하기 위해 StatefulWidget으로 페이지를 구성한다.
class Homepage extends StatefulWidget {
    const Homepage({supser.key});

    @override
    State&lt;Homepage&gt; createState() =&gt; _HomepageState();
}

class _HomepageState extends State&lt;Homepage&gt; {
    int _index = 0; // 2. 관리할 상태 데이터를 선언한다.

    @override
    Widget build(BuildContext context) {
        // 3. setState로 상태 데이터를 갱신하며 관리한다.
        return BottomNavigationBar(
            currentIndex: _index, 
            onTap: (newIndex) =&gt; setState(() { 
                _index = newIndex; 
            }); 
        );
    }
}</code></pre>
<p>정리하면 외부에서 접근할 필요가 없는 상태 데이터 =&gt; <code>StatefulWidget</code>으로 관리하면 된다.</p>
<blockquote>
<p>Provider, GetX와 같은 상태 관리를 제공하는 외부 패키지가 존재하는데 StatefulWidget을 왜 사용하는지 이유를 몰랐었는데 이와 같은 기준을 세워 관리한다는 것을 알게 되었다.</p>
</blockquote>
<h2 id="앱-상태-app-state">앱 상태 (App state)</h2>
<p>앱 상태란 현재 위젯을 벗어나 다른 위젯에서도 데이터를 공유하고 세션을 유지해야 하는 데이터를 칭한다.
예로 다음과 같다.</p>
<ul>
<li>사용자 설정 정보</li>
<li>로그인 정보</li>
<li>알림 상태 정보</li>
<li>e-커머스 앱의 장바구니 데이터</li>
<li>게시글의 읽음/읽지 않음 상태 표현을 위한 데이터</li>
</ul>
<p>앱 상태를 구성하려면 여러 상황들을 고려해야 한다. 앱의 복잡성과 특성, 개발자의 상태 관리를 위해 시도해본 경험 등을 고려해서 디자인해야 한다.</p>
<h3 id="명확한-정답이-없는-앱-상태-관리">명확한 정답이 없는 앱 상태 관리</h3>
<p>앱 상태 관리를 위한 명확한 정답은 없다. 그저 조금 더 나은 방식, 현재 구성에서 가장 나은 방식을 고려해서 채택할 뿐이다.</p>
<p>임시 상태에서 예시로 든 <code>BottomNavigationBar</code>의 탭 인덱스 상태 데이터도 필요에 따라 앱 상태로 분류할 수도 있다. (ex: 외부 위젯에서 탭 인덱스를 변경)</p>
<p>그렇기 때문에 앱 상태 관리를 위한 명확한 규칙같은 건 없다. 아래의 그림처럼 데이터를 기준으로 필요에 따라 구분지어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/8e43be9f-f9c7-4055-802d-b5e0bb212ee2/image.png" alt="앱 상태 관리를 위한 고려사항"></p>
<p>이런 부분이 가장 머리가 아프면서 재밌는 요소인 것 같다. 임시 상태로 관리하던 데이터들을 요구사항 변경으로 인해 앱 상태로 변경하게 되면 코드 수정이 커지게 될 텐데... 미래를 내다 보는 눈을 길러야겠다. ㅋㅋㅋ... </p>
<h1 id="쉽게-앱-상태-관리하기">쉽게 앱 상태 관리하기</h1>
<p>맨 위의 링크를 통해서 챕터들을 봤다면 알 수 있을텐데, flutter 개발팀은 기본적으로 <code>provider</code> 패키지를 통해 상태 관리하는 방식을 시작하는 것을 추천하고 있다. 
<code>provider</code> 패키지가 이해하기 쉽고 앱 상태 관리를 위해 작성하는 코드량이 많지 않다는 것이 이유다.</p>
<p>provider 패키지를 통한 상태 관리 예제는 Do it! 깡샘의 플러터 &amp; 다트 프로그래밍 여섯째 마당을 통해 확인할 수 있으므로 생략한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성]]></title>
            <link>https://velog.io/@cafefarm-johnny/%EB%8F%99%EC%8B%9C%EC%84%B1</link>
            <guid>https://velog.io/@cafefarm-johnny/%EB%8F%99%EC%8B%9C%EC%84%B1</guid>
            <pubDate>Fri, 24 Feb 2023 15:15:27 GMT</pubDate>
            <description><![CDATA[<p><a href="https://dart.dev/guides/language/concurrency">https://dart.dev/guides/language/concurrency</a></p>
<p>UI 처리는 어떻게 이루어지는지 궁금해서 찾아보다가 스레드에 대한 정보를 보다보니 동시성을 다루게되는…🙄</p>
<p>부끄럽지만 시간나면 봐야지 하다가 영문서라 미루고 미룬 페이지이기도 합니다.</p>
<p>그래서 요악부터 하자면 … 자바스크립트인데..?</p>
<h1 id="isolate">Isolate</h1>
<ul>
<li>Dart는 싱글 스레드 기반 언어지만 타 언어들과 같이 비동기 처리를 위해 Isolate라는 일종의 스레드를 제공한다.</li>
<li>모든 Dart 코드는 Isolate 내부에서 처리되며, Thread Safe 하게 격리된 상태로 실행된다.<ul>
<li>Thread Safe할 수 있는 이유는 Isolate간에 데이터를 공유하지 않도록 디자인되었다.</li>
<li>따라서 공유 메모리 영역이 존재하지 않기 때문에 하나의 공유 자원에 둘 이상의 스레드가 접근하는 임계 영역에 대한 문제가 발생하지 않는다.</li>
<li>Isolate간 데이터를 주고 받고 싶다면 메시지 패싱이라는 기술을 사용하여 격리된 환경에서도 데이터를 주고받을 수 있다.</li>
</ul>
</li>
<li>각각의 Isolate는 내부적으로 힙 메모리 영역과 스택, 이벤트 큐와 이벤트 루프를 가진다.<ul>
<li>자바스크립트처럼 이벤트 큐에 작업을 쌓고 이벤트 루프를 통해 하나씩 꺼내어 동작시킨다.</li>
<li>이벤트 큐?<ul>
<li>예로, 버튼 터치에 대한 액션을 이벤트라는 일련의 작업으로 정의하고, 이러한 작업들을 쌓아두는 공간</li>
<li>이벤트 루프가 큐를 읽어 작업들을 꺼내어 그에 해당하는 코드를 실행하여 반응한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/8c1b3978-4b53-4c50-9c3b-3d7fc38d9e42/image.png" alt="Isolate 구조">
(하나의 Isolate는 하나의 스레드와 힙, 스택 메모리 영역, 이벤트 루프를 가진다. 자바스크립트랑 비슷하네)</p>
<ul>
<li>필요한 경우 Isolate를 추가로 생성하여 병렬 처리하는 것도 가능하다. (그냥 스레드를 isolate라고 부르는거구나..)</li>
</ul>
<h1 id="main-isolate">Main Isolate</h1>
<ul>
<li>Dart로 만들어진 앱은 하나의 메인 Isolate로 구성된다. (싱글 스레드 기반에서 UI 스레드, 메인 스레드)</li>
<li>메인 Isolate의 역할은 main() 함수를 실행하고 UI 이벤트에 대한 응답을 무한정 수행하는 것이다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/3e01a1ce-6b4d-4a32-b548-4a50163dd932/image.png" alt="Main Isolate 역할"></p>
<ol>
<li>사용자로부터 이벤트가 발생</li>
<li>메인 Isolate 이벤트 큐에 순차적으로 이벤트를 적재</li>
<li>이벤트 루프를 통해 쌓인 순서대로 이벤트가 처리된다. (First in, First out 알고리즘)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/b7fe6c59-f8c1-4599-83f0-13baa1d5dbb0/image.png" alt="Event loop"></p>
<ol>
<li>main() 함수를 수행</li>
<li>이벤트 루프를 통해 큐에 적재된 이벤트 조회</li>
<li>페인트 작업 수행</li>
<li>이벤트 루프를 통해 이벤트 조회</li>
<li>Tap 이벤트에 대한 응답 코드 수행</li>
<li>반복</li>
</ol>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/5a5130d6-bee2-441b-9e2e-5e79ec480475/image.png" alt="Event loop detail"></p>
<ul>
<li>이러한 일련의 작업들을 동기식으로 처리한다면 작업이 오래 걸리는 코드는 그마만큼 응답이 느려진다.<ul>
<li>예를 들면 매머드 커피점에서 커피를 주문한 경우 음료 제조가 완료되는 시간까지 기다려야 한다.</li>
<li>이처럼 사용자와 상호작용하는 앱은 사용자의 요청을 처리 완료할 때 까지 사용자를 기다리게 해야한다. (이 집 서비스 별로네)</li>
</ul>
</li>
<li>그래서 필요한 것이 비동기 처리다.</li>
</ul>
<ol>
<li>메인 함수를 수행</li>
<li>이벤트 루프를 통해 큐에 적재된 이벤트 조회</li>
<li>페인트 작업 수행</li>
<li>페인트 작업이 완료되든 말든 다음 이벤트를 조회</li>
<li>Tap 이벤트 작업 수행</li>
<li>반복</li>
</ol>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/53ea8659-5002-41e2-b786-6da0aab904c5/image.png" alt="비동기 처리"></p>
<ul>
<li>싱글 Isolate/멀티 Isolate와 동기/비동기 처리는 엄연히 다르다.<ul>
<li>예시를 통해 싱글 Isolate를 이해해보자.</li>
<li>가정1) 메머드 커피 직원 한 분이 아이스 아메리카노 한잔의 주문을 받았다.<ul>
<li>직원분이 혼자서 동기 처리할 경우<ol>
<li>에스프레소를 추출한다.</li>
<li>에스프레소 추출이 완료되면 컵에 얼음을 담는다.</li>
<li>컵에 물을 받는다. </li>
<li>에스프레소를 섞는다.</li>
<li>주문자를 호출하여 제품을 전달한다.</li>
</ol>
</li>
<li>직원분이 혼자서 비동기 처리할 경우<ol>
<li>에스프레소를 추출한다.</li>
<li>에스프레소가 추출되는 동안 컵에 얼음을 담고 물을 받는다.</li>
<li>에스프레소 추출이 완료되면 컵에 에스프레소를 섞는다.</li>
<li>주문자를 호출하여 제품을 전달한다.</li>
</ol>
</li>
</ul>
</li>
<li>멀티 Isolate는 위의 작업을 직원 A, B가 각각 처리하는 것이다.</li>
</ul>
</li>
</ul>
<h1 id="다중-isolate">다중 Isolate</h1>
<p><a href="https://dart.dev/guides/language/concurrency#background-workers">https://dart.dev/guides/language/concurrency#background-workers</a></p>
<p>작성중..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter 4.0 주요 기능들]]></title>
            <link>https://velog.io/@cafefarm-johnny/Flutter-4.0-%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5%EB%93%A4</link>
            <guid>https://velog.io/@cafefarm-johnny/Flutter-4.0-%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5%EB%93%A4</guid>
            <pubDate>Sun, 29 Jan 2023 11:24:05 GMT</pubDate>
            <description><![CDATA[<p>아직 릴리즈되지 않은 Flutter 4.0 주요 기능들에 대한 내용이 돌아다니는 모양이다. 
찾아보니 <a href="https://github.com/orgs/flutter/projects?query=is%3Aopen">Flutter GitHub</a> 프로젝트 항목에서 주요 기능들에 대한 계획을 확인해볼 수 있더라.</p>
<p>어떠한 기능들이 릴리즈될지 한번 알아보자.</p>
<h1 id="데스크톱-신규-기능">데스크톱 신규 기능</h1>
<ul>
<li><a href="https://www.microsoft.com/en-us/windows/accessibility-features?r=1">윈도우즈 11 접근성</a> 지원을 위해 플러터 엔진에 <a href="https://learn.microsoft.com/ko-kr/windows/win32/winauto/microsoft-active-accessibility-and-ui-automation-compared">MSAA API</a> 지원 추가<ul>
<li>Flutter 4.0에서 기초 작업을 진행하고 있고, Flutter 5.0에 추가될 가능성이 있음</li>
</ul>
</li>
<li>데스크톱 쉘에서 다수의 윈도우를 실행할 수 있도록 지원 추가 (???)</li>
</ul>
<p>MSAA API라는건 옛 버전의 윈도우즈에 대한 레거시 기능을 접근하기 위한 API인 것 같다.</p>
<h1 id="material-design-3-업데이트">Material Design 3 업데이트</h1>
<ul>
<li>iOS에 Material Design 3 적용<ul>
<li>추가적인 호환성 작업인듯</li>
</ul>
</li>
<li>Ink 정리 기능<ul>
<li><a href="https://api.flutter.dev/flutter/material/InkSparkle-class.html">InkSparkle</a> 클래스와 같은 <a href="https://github.com/flutter/flutter/issues/98669">연관된 기능들의 이슈</a> 대응으로 보임</li>
</ul>
</li>
<li>신규 Segmented Button API<ul>
<li><a href="https://github.com/flutter/flutter/pull/113723">SegmentedButton</a> 같은 위젯이 추가되는듯 함</li>
</ul>
</li>
<li><a href="https://github.com/flutter/flutter/issues/103530">하단 앱 바, 플로팅 액션 바</a> 디자인 업데이트</li>
<li><a href="https://github.com/flutter/flutter/pull/113894">Drawer 메뉴</a> 디자인 업데이트</li>
<li><a href="https://github.com/flutter/flutter/issues/103536">Switch 버튼</a> 디자인 업데이트</li>
</ul>
<p>3D 렌더링 관련된 내용도 있었는데 찾아보니 글이 안나온다. 나중에 추가할 수 있으면 추가하고...</p>
<p>최근엔 다시 만들었다는 그래픽 엔진 <a href="https://twitter.com/FlutterDev/status/1618280682379251716">Impeller</a> 라는 것도 4.0에 포함되지 않을까? 찾아보니 Skia 코드를 iOS의 메탈, 안드로이드의 불칸 같은 최신 하드웨어 가속 그래픽 API를 활용하는 그래픽 처리 엔진인듯 하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인프런 Flutter 응용 - 공공 API를 활용한 앱 만들기 (MVVM 패턴) 수강 후기]]></title>
            <link>https://velog.io/@cafefarm-johnny/%EC%9D%B8%ED%94%84%EB%9F%B0-Flutter-%EC%9D%91%EC%9A%A9-%EA%B3%B5%EA%B3%B5-API%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-MVVM-%ED%8C%A8%ED%84%B4-%EC%88%98%EA%B0%95-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@cafefarm-johnny/%EC%9D%B8%ED%94%84%EB%9F%B0-Flutter-%EC%9D%91%EC%9A%A9-%EA%B3%B5%EA%B3%B5-API%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-MVVM-%ED%8C%A8%ED%84%B4-%EC%88%98%EA%B0%95-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 26 Jan 2023 08:05:12 GMT</pubDate>
            <description><![CDATA[<p>인프런 <a href="https://www.inflearn.com/course/flutter-%EA%B3%B5%EA%B3%B5-api-%EC%95%B1-%EC%9D%91%EC%9A%A9/dashboard">Flutter 응용 - 공공 API를 활용한 앱 만들기 (MVVM 패턴) 강의</a> 수강 후기</p>
<h2 id="학습-목표">학습 목표</h2>
<ul>
<li>MVVM 패턴에 대한 이해</li>
<li>Provider 라이브러리를 활용한 상태 관리 이해</li>
<li>최종적으로 회사의 프로덕트 수준의 앱의 구조를 <strong>팀원들과 함께</strong>😆 MVVM 패턴으로 리팩토링하여 구조 개선하기</li>
</ul>
<h2 id="학습-후기">학습 후기</h2>
<ul>
<li>강의 소개에서는 수강자에게 중급 수준 이상을 요구했지만 초급이 수강하기에도 어렵지 않은 난이도</li>
<li>Provider 패턴을 통해 View와 ViewModel 간 커뮤니케이션 발생 원리를 이해함<ol>
<li>View와 비즈니스 로직의 관심사를 분리함</li>
<li>상태 관리를 통해 한 곳에서 데이터를 관리하고 필요로 하는 페이지에 공유함</li>
<li>로직을 통해 데이터를 변경 -&gt; Listener에게 상태 변경을 noti -&gt; Listener들은 noti를 캐치하여 build 사이클 수행</li>
</ol>
</li>
<li><code>setState()</code>와 Provider 패턴간 차이점을 이해함<ul>
<li><code>setState()</code> 호출 시 호출된 위젯의 하위 위젯(자식 요소)까지 모두 rebuild 과정이 수행됨 -&gt; 렌더링 처리에 의한 성능 저하가 발생함</li>
<li>Provider는 데이터를 변경 후 noti, Listening 하고 있는 위젯 대상만 rebuild 과정이 수행됨 -&gt; 렌더링 처리를 최소화하여 성능 저하를 방지함</li>
</ul>
</li>
</ul>
<h2 id="아쉬운-점">아쉬운 점</h2>
<ul>
<li>강의 소개에서는 수강자에게 중급 수준 이상을 요구했지만 초급이 수강하기에도 어렵지 않은 난이도 </li>
<li>플러터 내부 프로세스에 대한 설명을 다루는 경우가 많지 않았고, 클라이언트 범주 내 공통적으로 통용되는 내용을 주로 다룸<ul>
<li><a href="https://pub.dev/packages/geolocator">geolocator</a> 라이브러리 초기화 실패에 대한 해결책으로 <code>WidgetsFlutterBindings.ensureInitialized()</code>를 왜 사용해야 하는가? 와 같은 궁금증을 해결해주지 않았음</li>
</ul>
</li>
<li>강좌에 사용된 앱의 크기가 생각했던 것 보다 작아 실제 프로덕트 수준의 앱의 구조를 MVVM 패턴으로 리팩토링하는 과정에 대한 디자인이 예상되지 않음</li>
<li>MVVM 아키텍처에서 흔히 겪을 수 있는 에로사항과 해결을 위한 노하우 전수가 없어 아쉬웠음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[flutter doctor 이슈 대응 목록]]></title>
            <link>https://velog.io/@cafefarm-johnny/flutter-doctor-%EC%9D%B4%EC%8A%88-%EB%8C%80%EC%9D%91-%EB%AA%A9%EB%A1%9D</link>
            <guid>https://velog.io/@cafefarm-johnny/flutter-doctor-%EC%9D%B4%EC%8A%88-%EB%8C%80%EC%9D%91-%EB%AA%A9%EB%A1%9D</guid>
            <pubDate>Tue, 17 Jan 2023 08:02:56 GMT</pubDate>
            <description><![CDATA[<h1 id="android-studio-unable-to-find-bundled-java-version">(Android Studio) Unable to find bundled Java version.</h1>
<h2 id="문제">문제</h2>
<p>안드로이드 스튜디오의 메이저 버전이 업데이트되고 난 후에 Flutter doctor를 수행하면 Java JDK 경로를 인식하지 못하는 오류가 발생하곤 합니다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/38356321-78e9-42e4-ac05-cd42b1bac618/image.png" alt="flutter doctor warning"></p>
<h2 id="원인">원인</h2>
<p>이는 Flutter 프레임워크 내부에 JDK 경로를 찾는 코드가 존재하는데, 안드로이드 스튜디오 앱이 업데이트 되며 내부적으로 JDK 경로가 변경되어 발생하는 문제일 가능성이 높습니다.</p>
<p>Flutter 프레임워크의 <a href="https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/android/android_studio.dart#L448">android_studio.dart</a> 파일의 내부를 살펴보면 안드로이드 스튜디오 버전별로 jdk를 찾는 코드가 분기처리 되어있는 것을 알 수 있습니다. (왜 이렇게 해놨을까..?)</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/bc825acf-f7c1-48b1-b776-ba46b267ffa8/image.png" alt="android_studio.dart"></p>
<h2 id="해결방법">해결방법</h2>
<p>이에 대한 해결방법은 Flutter 프레임워크에서 공식적으로 최신 버전의 안드로이드 스튜디오에 대한 대응 코드가 추가되길 기다리거나, 임의로 상기의 코드에서 처리될 수 있도록 물리적으로 JDK 경로를 변경해주는 방법이 있습니다.</p>
<p>Flutter에서 대응해주길 기다리기엔 몇달이 걸리 수도 있으니... 임의로 JDK 경로를 인식할 수 있도록 대응하는 방법을 알아보겠습니다.</p>
<p>먼저 안드로이드 스튜디오가 설치된 경로로 접근합니다.</p>
<ul>
<li>안드로이드 스튜디오가 설치된 경로는 &quot;flutter doctor -v&quot;를 통해 도출되는 보고서에서 찾을 수 있습니다.</li>
</ul>
<p>Contents 디렉토리로 이동하여 jre 폴더를 생성해주세요.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/4697fbff-3a4a-418c-9901-ba9149509d8c/image.png" alt="android studio app package folder"></p>
<p>jbr 디렉토리로 이동하여 Contents 폴더를 통째로 복사하여 위에서 생성한 jre 폴더에 붙여넣기 해주세요.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/fcbe8fdb-39ab-4e8e-a7d4-ef1e899f1adb/image.png" alt="jdk folder"></p>
<p>이후 &quot;flutter doctor -v&quot; 명령을 다시 실행하면 문제가 해결되는 것을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/4b7b0410-654d-4e07-92a6-d2becfc417c9/image.png" alt="solved"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Studio 세팅]]></title>
            <link>https://velog.io/@cafefarm-johnny/Android-Studio-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@cafefarm-johnny/Android-Studio-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Fri, 06 Jan 2023 18:59:05 GMT</pubDate>
            <description><![CDATA[<h1 id="android-studio">Android Studio</h1>
<p>Flutter 앱 개발을 쉽게 개발하기 위해 필요한 도구로는 Microsoft 사의 <a href="https://code.visualstudio.com/">Visual Studio Code</a>, JetBrains 사의 <a href="https://www.jetbrains.com/ko-kr/idea/">IntelliJ Ultimate</a>, Google의 <a href="https://developer.android.com/studio">Android Studio</a> 가 있습니다.
안드로이드 스튜디오가 가장 흔하게 사용되는 툴입니다.</p>
<h2 id="plugins">Plugins</h2>
<h3 id="dart-flutter">Dart, Flutter</h3>
<p>안드로이드 스튜디오를 실행한 뒤 &quot;Preferences -&gt; Plugins&quot; 항목으로 이동하여 <code>Dart</code>와 <code>Flutter</code>를 검색하여 설치합니다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/8482c3e6-63e1-4d49-b2c9-d6ecb8185e3c/image.png" alt="Plugin 화면"></p>
<h2 id="android-emulator">Android Emulator</h2>
<p>안드로이드 개발을 하려면 안드로이드 SDK를 비롯하여 몇가지 SDK를 설치해야 합니다. </p>
<h3 id="android-sdk">Android SDK</h3>
<p>&quot;Preferences -&gt; Appearence &amp; Behavier -&gt; System Settings -&gt; Android SDK&quot; 메뉴로 이동하여 <code>SDK Platforms</code> 탭에서 원하는 버전의 안드로이드 SDK를 체크하여 설치합니다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/e8c14e7d-a3f8-4504-b27f-f6c35434fa76/image.png" alt="SDK 설치"></p>
<p><code>SDK Tools</code> 탭으로 이동하여 다음의 항목을 체크하여 설치합니다.</p>
<ul>
<li>Android SDK Build-Tools</li>
<li>Android SDK Command-line Tools</li>
<li>Android Emulator</li>
<li>Android SDK Platform-Tools</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/ec5ffbf7-3966-4e1b-b429-c3559dfe3541/image.png" alt="SDK 설치 2"></p>
<h3 id="가상-디바이스-생성">가상 디바이스 생성</h3>
<p>여기까지 완료되면 안드로이드 에뮬레이터를 실행할 수 있습니다.
안드로이드 스튜디오의 우측 사이드에서 <code>Device Manager</code> 메뉴를 클릭하고 <code>Virtual</code> 탭에서 &quot;Create device&quot; 버튼을 클릭합니다.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/89ced55a-b137-46a8-80ba-dd6f6229a41a/image.png" alt="Device Manager"></p>
<p><code>Virtual Device Configuration</code>창이 나타납니다. 좌측 <code>Category</code> 항목에서 원하는 기기 유형을 선택할 수 있습니다. 이후 우측에 유형에 맞는 기기 목록들이 나타납니다. 원하는 기기를 선택하고 &quot;Next&quot;버튼을 클릭하세요.</p>
<ul>
<li>Play Store 항목에 아이콘이 표기된 기기들은 구글 플레이 스토어 앱 및 플레이 스토어 API를 지원하는 기기입니다. 필요한 경우 해당 아이콘이 표기된 기기를 선택하여 생성하세요.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/bbbdfc8d-214f-4d08-93ff-0e694eda8ac4/image.png" alt="Select Hardware"></p>
<p>기기에 설치할 안드로이드 시스템 이미지를 선택할 수 있습니다. 원하는 안드로이드 버전을 선택하고 &quot;Next&quot; 버튼을 클릭하세요.</p>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/ef6667a2-67df-49f5-8fc8-8640d3d658bf/image.png" alt="System Image"></p>
<p>가상 기기에 할당할 &quot;RAM, VM heap, Internal Storage, SD card&quot;에 대한 기준을 설정할 수 있습니다. 원하는 만큼 설정한 후 &quot;Finish&quot; 버튼을 클릭하여 생성하면 안드로이드 에뮬레이터를 실행할 수 있습니다.</p>
<ul>
<li>기기 선택 과정에서 구글 플레이 스토어 앱을 지원하는 기기를 선택한 경우 &quot;RAM, VM heap, SD card&quot; 항목에 대한 설정이 비활성화 됩니다. 이는 수작업으로 변경할 수 있으니 필요한 경우 검색하여 찾아보세요.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/991ba839-4efd-4eab-bedd-97c8e2e66554/image.png" alt="Configuration"></p>
<h2 id="vm-가속-설정">VM 가속 설정</h2>
<p>안드로이드 에뮬레이터가 느린 경우 VM 가속을 구성하여 퍼포먼스를 향상시킬 수 있습니다.
MacOS는 Yesemite 이상의 OS에서 <a href="https://developer.apple.com/documentation/hypervisor">Hypervisor.Framework</a>를 사용하여 VM 가속을 사용합니다.
만약 안드로이드 에뮬레이터가 Hypervisor.Framework 초기화에 실패한 경우, Intel HAXM을 사용하여 가속 환경을 구성합니다.</p>
<p>안드로이드 스튜디오에서 SDK Manager를 열고 아래의 항목을 체크하여 설치합니다.</p>
<ul>
<li>Intel x86 Emulator Accelerator (HAXM Installer)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/17faad61-c83c-436c-845c-3b71f130b1cd/image.png" alt="SDK Manager"></p>
<p>설치가 완료되면 아래의 경로로 통해 dmg 파일을 실행하여 HAXM 드라이버를 설치합니다.</p>
<pre><code class="language-bash">$ {HOME_DIR}/Library/Android/sdk/extras/intel/Hardware_Accelerated_ExecutionManager/IntelHAXM_x.y.z.dmg</code></pre>
<p>설치가 완료되면 터미널에서 아래의 명령어를 입력하여 커널 확장 프로그램이 실행되었는지 확인할 수 있습니다.</p>
<pre><code class="language-bash">$ kextstat | grep intel

com.intel.kext.intelhaxm</code></pre>
<h2 id="그래픽-가속-설정">그래픽 가속 설정</h2>
<p>안드로이드 SDK를 API 레벨 27 이상의 이미지를 사용하는 경우 에뮬레이터에 <a href="https://skia.org/">Skia</a> 렌더링 엔진을 설정할 수 있습니다.
Skia 렌더링 엔진은 에뮬레이터가 그래픽을 더 부드럽고 효율적으로 렌더링하도록 퍼포먼스를 향상시킵니다.</p>
<p>Skia 렌더링을 사용하려면 터미널에서 다음과 같이 명령어를 입력하세요.</p>
<pre><code class="language-bash">$ {HOME_DIR}/Library/Android/sdk/platform-tools/adb shell

&gt; su
&gt; setprop debug.hwui.renderer skiagl
&gt; stop
&gt; start</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[fvm (Flutter Version Manager)]]></title>
            <link>https://velog.io/@cafefarm-johnny/fvm</link>
            <guid>https://velog.io/@cafefarm-johnny/fvm</guid>
            <pubDate>Thu, 27 Oct 2022 13:30:43 GMT</pubDate>
            <description><![CDATA[<h1 id="목차">목차</h1>
<ol>
<li><a href="#fvm?">fvm?</a></li>
<li><a href="#%EC%84%A4%EC%B9%98">설치</a></li>
<li><a href="#%EC%82%AD%EC%A0%9C">삭제</a></li>
<li><a href="#%ED%94%8C%EB%9F%AC%ED%84%B0-%EC%84%A4%EC%B9%98">플러터 설치</a></li>
<li><a href="#%ED%94%8C%EB%9F%AC%ED%84%B0-%EB%B2%84%EC%A0%84-%EC%82%AC%EC%9A%A9-%EC%84%A4%EC%A0%95">플러터 버전 사용 설정</a></li>
<li><a href="#%ED%94%8C%EB%9F%AC%ED%84%B0-%EB%B2%84%EC%A0%84-%EC%82%AD%EC%A0%9C">플러터 버전 삭제</a></li>
<li><a href="#%ED%94%8C%EB%9F%AC%ED%84%B0-%EC%84%A4%EC%B9%98-%EB%AA%A9%EB%A1%9D-%EC%A1%B0%ED%9A%8C">플러터 설치 목록 조회</a></li>
<li><a href="#%ED%94%8C%EB%9F%AC%ED%84%B0-%EB%A6%B4%EB%A6%AC%EC%A6%88-%EB%B2%84%EC%A0%84-%EB%AA%A9%EB%A1%9D-%EC%A1%B0%ED%9A%8C">플러터 릴리즈 버전 목록 조회</a></li>
</ol>
<h1 id="fvm">fvm?</h1>
<p>fvm은 플러터의 버전을 관리해주는 관리 툴이다. Node.js 관리툴인 nvm과 동일하다.</p>
<h1 id="설치">설치</h1>
<pre><code class="language-bash">$ brew tap leoafarias/fvm
$ brew install fvm</code></pre>
<h1 id="삭제">삭제</h1>
<pre><code class="language-bash">$ brew uninstall fvm
$ brew untap leoafarias/fvm</code></pre>
<h1 id="플러터-설치">플러터 설치</h1>
<pre><code class="language-bash">$ fvm install stable # 최신 안정 버전 설치
$ fvm install {version} # 특정 버전 설치

Flutter 3.3.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision d9111f6402 (8 days ago) • 2022-10-19 12:27:13 -0700
Engine • revision 3ad69d7be3
Tools • Dart 2.18.2 • DevTools 2.15.0</code></pre>
<h1 id="플러터-버전-사용-설정">플러터 버전 사용 설정</h1>
<pre><code class="language-bash">$ cd /{project}

$ fvm use {version}

Project now uses Flutter [stable]</code></pre>
<p>버전 설치 후 Flutter SDK Path를 지정한다.
<img src="https://velog.velcdn.com/images/cafefarm-johnny/post/29105d1b-22c5-4d8a-8be7-eb8d4b2a7963/image.png" alt="Flutter SDK"></p>
<pre><code>Preferences ⇒ 언어  &amp; 프레임워크 ⇒ Flutter ⇒ SDK ⇒ Flutter SDK path ⇒ “/{USER_HOME}/fvm/versions/{version}</code></pre><h1 id="플러터-버전-삭제">플러터 버전 삭제</h1>
<pre><code class="language-bash">$ fvm remove {version}

Removing 3.3.4...
3.3.4 removed.</code></pre>
<h1 id="플러터-설치-목록-조회">플러터 설치 목록 조회</h1>
<pre><code class="language-bash">$ fvm list

Cache Directory:  /Users/{USER_HOME}/fvm/versions

stable (active)
3.3.4</code></pre>
<h1 id="플러터-릴리즈-버전-목록-조회">플러터 릴리즈 버전 목록 조회</h1>
<pre><code class="language-bash">$ fvm releases</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter]]></title>
            <link>https://velog.io/@cafefarm-johnny/Flutter</link>
            <guid>https://velog.io/@cafefarm-johnny/Flutter</guid>
            <pubDate>Sun, 10 Apr 2022 10:15:34 GMT</pubDate>
            <description><![CDATA[<h1 id="flutter-프로젝트-구조">Flutter 프로젝트 구조</h1>
<pre><code class="language-bash">hello_world
    ㄴ .dart_tool
    ㄴ .idea
    ㄴ android         - Android 빌드 시 필요한 파일들이 포함된 디렉토리
    ㄴ build
    ㄴ ios           - iOS 빌드 시 필요한 파일들이 포함된 디렉토리
    ㄴ lib           - 소스 패키지
    ㄴ test          - 테스트 코드 패키지
    ㄴ .metadata
    ㄴ .packages
    ㄴ analysis_options.yaml
    ㄴ hello_world.iml
    ㄴ pubspec.lock
    ㄴ pubsepc.yaml  - 앱 이름, 버전, 의존성, 리소스 경로를 관리하는 파일
    ㄴ README.md</code></pre>
<h1 id="flutter-앱-구조">Flutter 앱 구조</h1>
<p>Flutter 앱은 일반적으로 다음과 같은 구조를 갖는다.</p>
<p><img src="https://github.com/cafefarm-johnny/fortune_cookie_app/raw/mission/docs/fortune-cookie-app-widget-diagram.jpg" alt=""></p>
<p>앱을 구성하는 테마를(MaterialApp 또는 CupertinoApp) 베이스로하여 그 위에 <code>Scaffold(캔버스)</code> 를 그리고 그 위에 <strong>AppBar나 Container와 같은 Widget을 덧그리는 형태</strong>다.</p>
<p><strong>Flutter는 이러한 모든 요소들을 크게 Widget이라고 표현</strong>하고 있으며, <strong>Flutter 앱을 구성하는 모든 요소들은 Widget들의 모음</strong>이다. 모든 것이 Widget이기 때문에 (화면을 구성하는 레이아웃마저도) 약 1500개 이상의 Widget을 가지고 있다고 한다.</p>
<h1 id="flutter-구성-요소">Flutter 구성 요소</h1>
<p>Flutter 앱은 위젯의 모음으로 구성된다. 위젯은 StatefulWidget과 StatelessWidget 총 두가지의 종류가 있다.</p>
<ul>
<li>StatefulWidget: 화면의 구성이 상태가 변화함에 따라 재구성되어야 할 때 사용하는 위젯이다.</li>
<li>StatelessWidget: 변화가 발생하지 않는 정적인 화면을 구성할 때 사용하는 위젯이다.</li>
</ul>
<h2 id="materialapp-클래스">MaterialApp 클래스</h2>
<p>MaterialApp 테마 클래스는 Google의 Material 디자인을 사용하는 앱을 만들때 사용된다. 기본적으로 흔히 사용되는 디자인이기 때문에 기본이 되는 클래스라 할 수 있다.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

void main() {
    // 1. HelloWorld 생성자 호출
    runApp(const HelloWorld());
}

// 2. HelloWorld는 SFW 위젯을 확장하는 앱의 기본 클래스
class HelloWorld extends StatefulWidget {
    const HelloWorld({Key? key}) : super(key: key);

    @override
    State&lt;HelloWorld&gt; createState() =&gt; _HelloWorldState();
}

// 3. _HelloWorldState는 State를 확장하는 클래스
class _HelloWorldState extends State&lt;HelloWorld&gt; {
    @override
    Widget build(BuildContext ctx) {
        return MaterialApp();
    }
}</code></pre>
<h3 id="title">title</h3>
<p>title 속성은 사용자에게 보여주는 앱에 대한 설명이다.  최근 사용한 앱 목록 같은 곳에서 표시되는 앱의 제목이다. 그런데 현재 기준으로는 안드로이드나 iOS나 모두 직접 확인이 불가능하다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return MaterialApp(
        title: &#39;My HelloWorld App&#39;,
    );
}</code></pre>
<h3 id="theme">theme</h3>
<p>Material 위젯에 색상, 폰트, 모양과 같은 시각적 속성을 일관되게 적용되는 테마를 설정한다. </p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return MaterialApp(
        theme: ThemeData.dark(),
    );
}</code></pre>
<h3 id="home">home</h3>
<p>앱 실행 시 먼저 표시되는 화면을 설정한다. 주로 Scaffold 위젯을 사용하여 정의한다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return MaterialApp(
        home: Scaffold(), 
    );
}</code></pre>
<h3 id="routes">routes</h3>
<p>화면 전환 시 named route를 정의하여 다른 화면으로 전환시키는 역할을 한다.</p>
<h2 id="scaffold">Scaffold</h2>
<p>Material 디자인의 시각적인 레이아웃 구조를 구현하는 위젯이다.</p>
<h3 id="appbar">appBar</h3>
<p>Scaffold 상단에 표시되는 앱바를 표시하는 속성이다. 주로 화면의 제목과 액션 버튼을 제공한다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            title: const Text(&#39;Hello World!&#39;), 
        ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/7ab07945-97c6-4e7a-820a-92af0c2bc62e/image.png" alt=""></p>
<h3 id="backgroundcolor">backgroundColor</h3>
<p>Scaffold 화면의 배경색을 지정하는 속성이다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            backgroundColor: Colors.red 
        ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/493d6492-e48e-404b-abcd-f0fcac893a1d/image.png" alt=""></p>
<h3 id="body">body</h3>
<p>Scaffold 화면의 콘텐츠를 표시하는 속성이다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        body: Center(
            child: MaterialButton(
                child: const Text(&#39;Please Press&#39;),
            ),
        ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/7d6adbd1-5ef8-465c-aa22-5673cc37a567/image.png" alt=""></p>
<h3 id="bottomnavigationbar">bottomNavigationBar</h3>
<p>화면 하단에 네비게이션 바를 표시하는 속성이다. 주로 하단에 탭바를 지정해서 화면에 대한 전환을 구성하는데 사용된다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        bottomNavigationBar: NavigationBar(
      destinations: [
        MaterialButton(
          onPressed: () {},
          child: const Text(&#39;Back&#39;),
        ),
        MaterialButton(
          onPressed: () {},
          child: const Text(&#39;Home&#39;),
        ),
                MaterialButton(
          onPressed: () {},
          child: const Text(&#39;Current&#39;),
        ),
      ],
    ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/ba3380f4-b48f-4a79-a120-2b398a9fad1d/image.png" alt=""></p>
<h3 id="drawer">drawer</h3>
<p>화면 좌/우측에 터치 시 표시되는 패널을 표시하는 속성이다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        drawer: Drawer(),  
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/c52c76f5-d6d4-4aab-ad5e-0aac8cc087f2/image.png" alt=""></p>
<h2 id="appbar-1">AppBar</h2>
<p>AppBar 클래스는 Scaffold 상단에 표시되는 제목 표시줄이다.</p>
<h3 id="actions">actions</h3>
<p>AppBar 좌측에 표시되는 행위를 위한 버튼 위젯 목록을 선언하는 속성이다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            actions: [
                MaterialButton(
                    child: const Text(&#39;action1&#39;),
                ), 
                MaterialButton(
                    child: const Text(&#39;action2&#39;),
                ), 
            ],
        ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/55344020-901e-45cc-aaf1-c544bdc3ca3f/image.png" alt=""></p>
<h3 id="backgroundcolor-1">backgroundColor</h3>
<p>AppBar 배경색을 지정하는 속성이다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            backgroundColor: Colors.brown
        ),
    );
}</code></pre>
<h3 id="bottom">bottom</h3>
<p>AppBar 하단에 표시되는 위젯의 속성이다. 주로 TabBar를 구성할 때 사용된다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            bottom: const Tab(
                icon: Icon(Icons.add_photo_alternate_outlined), 
            ),
        ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/b77e6792-82dd-4e2f-8ede-86431bf7c0e9/image.png" alt=""></p>
<h3 id="centertitle">centerTitle</h3>
<p>AppBar 타이틀을 중앙 정렬하는 속성이다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            centerTitle: true,
        ),
    );
}</code></pre>
<h3 id="elevation">elevation</h3>
<p>AppBar에 z좌표를 부여하는 속성이다. Shadow 효과가 나타난다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            elevation: 30,
        ),
    );
}</code></pre>
<h3 id="leading">leading</h3>
<p>타이틀 앞에 위젯을 배치시킬 수 있는 속성이다. 주로 화면 전환 시 뒤로가기 버튼을 표현하는데 활용된다.</p>
<pre><code class="language-dart">@override
Widget build(BuildContext ctx) {
    return Scaffold(
        appBar: AppBar(
            leading: const BackButton(),
        ),
    );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/cafefarm-johnny/post/3e03e52c-3bba-4dd9-871d-093008e00427/image.png" alt=""></p>
<h3 id="title-1">title</h3>
<p>AppBar에 제목을 작성하는 속성이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[변수 선언]]></title>
            <link>https://velog.io/@cafefarm-johnny/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8</link>
            <guid>https://velog.io/@cafefarm-johnny/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8</guid>
            <pubDate>Fri, 08 Apr 2022 16:50:48 GMT</pubDate>
            <description><![CDATA[<h1 id="상수">상수</h1>
<p>코틀린에서 상수 선언은 <code>val</code> 키워드로 선언한다.</p>
<pre><code class="language-kotlin">val timeInSeconds = 30</code></pre>
<p>위 코드를 통해 타입 추론이 이루어진다는 것을 짐작할 수 있다. 정수 30을 상수로 할당하는데 상수의 타입을 지정하지 않았다. 컴파일러는 스스로 추론하여 해당 상수가 정수값를 할당한다는 것을 판단하고 <code>timeInSeconds</code> 상수를 <strong>Int</strong> 타입으로 간주한다.</p>
<p>물론 다음과 같이 타입을 직접 명시해줄 수도 있다.</p>
<pre><code class="language-kotlin">val timeInSeconds: Int = 30</code></pre>
<p>상수명 작성 후 콜론<code>:</code> 다음 타입이 온다.</p>
<pre><code class="language-kotlin">val text: String
text = &quot;Hello, Kotlin!&quot;</code></pre>
<p>상수 초기화를 lazy하게 하려면 상수 선언 시 반드시 타입을 명시해주어야 한다. 그렇지 않으면 컴파일러는 오류를 내뱉는다.</p>
<h1 id="변수">변수</h1>
<p>코틀린에서 변수 선언은 <code>var</code> 키워드로 선언한다.</p>
<pre><code class="language-kotlin">var sum = 1
sum = sum + 2
sum = sum + 3</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린이란?]]></title>
            <link>https://velog.io/@cafefarm-johnny/%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@cafefarm-johnny/%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Fri, 08 Apr 2022 16:48:37 GMT</pubDate>
            <description><![CDATA[<p>코틀린은 안전성, 간결성, 상호 운용성을 강조하는 다중 패러다임, 다중 플랫폼 프로그래밍 언어다. 코틀린은 자바보다 더 나은 대안을 제공하는 것과 다중 플랫폼을 지원하는 것이 목적이다.</p>
<p>코틀린으로 개발 가능한 분야</p>
<ul>
<li>안드로이드 개발</li>
<li>데스크톱 애플리케이션</li>
<li>서버 개발</li>
</ul>
<h2 id="안전성">안전성</h2>
<p>코틀린의 설계 목표 중 하나는 프로그램의 안전성과 개발자의 생산성을 덜 해치는 트레이드 오프의 황금비율을 찾는 것이었다. 즉, <strong>자바보다 더 안전성을 보장하는 언어를 설계하되, 개발자의 생산성을 덜 해치는 언어를 만드는 것이었다</strong>.</p>
<ul>
<li>타입 추론으로 인해 개발자가 타입을 명시하지 않아도 변수 선언이 가능하다.</li>
<li>Nullable type을 통해 null의 사용을 제한하고, NPE를 쉽게 방지한다.</li>
<li>Smart cast를 통해 타입을 안전하게 변환하고, 런타임에 타입 캐스팅 오류를 방지한다.</li>
</ul>
<h2 id="다중-패러다임">다중 패러다임</h2>
<p><strong>코틀린은 객체지향 패러다임 외 함수형 프로그래밍을 지원한다.</strong> 자바에는 익명 클래스를 통해 람다를 도입했지만 함수형 코드를 작성하는데 편리한 문법을 제공하지 않는다. 반면 코틀린은 함수 타입 시스템을 제공하여 자바보다 편리한 문법을 제공한다.</p>
<p>뿐 아니라 API를 <strong>도메인 특화 언어(Domain Specific Language, DSL)로 정의할 수 있는 기능을 지원</strong>하여 코틀린을 선언적인 스타일로 개발할 수 있도록 지원하고, Golang언어와 같이 <strong>동시성 프로그래밍을 위해 코틀린은 코루틴을 제공한다.</strong> </p>
<h2 id="간결성과-표현력">간결성과 표현력</h2>
<p>코틀린은 Getter, Setter, 익명 클래스, 명시적인 위임 등과 같은 자바의 <strong>보일러 플레이트를 제거하여 언어를 가능한 간결하게 작성할 수 있도록 노력했다.</strong></p>
<h2 id="상호-운용성">상호 운용성</h2>
<p><strong>코틀린은 독립적인 언어가 아닌 자바 언어 호환 가능한 언어이다.</strong> 기존 자바 코드를 코틀린에서 사용할 수 있고, 코틀린의 코드를 큰 노력없이 자바에서 사용할 수 있도록 상호 운용성을 보장한다. </p>
<p>최근에는 자바스크립트, C, C++, Objective-C, Swift 코드와 상호 작용할 수 있도록 확장되고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Concurrency in Go - 스터디 회고]]></title>
            <link>https://velog.io/@cafefarm-johnny/Concurrency-in-Go-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@cafefarm-johnny/Concurrency-in-Go-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 05 Nov 2021 15:56:41 GMT</pubDate>
            <description><![CDATA[<p>올 초에 Saturday Night 스터디를 참여한 이후 <code>The Ultimate Go</code>, <code>Concurrency in Go</code>두번째 주제까지 스터디가 완료되었다.</p>
<p>일과 스터디를 병행하면서 그동안 생각하고 느꼈던 점들을 KPT 방식의 회고를 해보려 한다.</p>
<h1 id="saturday-night이-뭐야-🤷♂️">Saturday Night이 뭐야? 🤷‍♂️</h1>
<p>Saturday Night 스터디는 뜻이 맞는 주변의 지인들이 함께 모여 만들어진 주말 스터디 그룹이다.
다 함께 스터디할 주제를 정하고, 발표하고자 하는 챕터를 스스로 선정하여 주중에 학습하고 매 주 토요일 밤 9시부터 한두시간 정도 발표를 통해 그룹 스터디를 진행한다.</p>
<blockquote>
<p><a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a>
주변인이 아니더라도 공개 스터디 그룹이기 때문에 누구나 언제든 참여 의사만 있다면 참여할 수 있습니다.
Go언어 주력의 스터디가 아닌 모두가 흥미를 가지는 주제라면 한다! 우리는
<img src="https://w.namu.la/s/fa412b9961cb1c17c625dc7da2fbfd01f81441e6caba162de6622793629826139f7d642065a9ff4a2aafa0c43b5d264a2635fe74e657245ad02cef5462ab9025f15839388d029f3368dc541f567807e49ca406d1a893e456cb9dafb235ddc8b0" alt="왈도체"></p>
</blockquote>
<h2 id="🐔기">🐔기</h2>
<ol>
<li>어느날 현재 재직 중인 회사의 팀장님으로부터 커피한잔 하자는 제안을 받았다.</li>
<li>커피마시러 갔다.</li>
<li>슬랙 스터디 채널에 끌려갔다. (?)</li>
</ol>
<p><code>개발자 = 자기주도 성장</code>은 정말이지 누구나 꿈꾸는 이상일 것이다. 이러한 이상을 실현하기 위해 <code>자습, TIL, 1일 1커밋</code>을 계획하곤 하지만 이런 목표들은 보통 실천하기가 어렵거나 오랜기간 유지되기가 힘들다.
그 이유는 <code>일</code>과 <code>학습</code>을 병행해야 한다는 개발자의 현실이 가장 큰 발목을 잡곤 하는데, 한번 생각해보자. 
하루종일 회사 의자에 앉아서 코드를 보고 퇴근 후 또 코드를 봐야 한다는게 쉬운 일일까?</p>
<p>나는 게으른 사람이고 게으르기 때문에 강제성이 없으면 스스로 무언가를 오랜 기간 하기 어려운 유형의 사람이다. 심지어 아무리 재밌는 게임이어도 두 세달만 해도 질려서 하지 않는다. 캐릭터도 마찬가지다. 생성해서 열심히 키우다가 어느덧 질려서 다른 유형의 캐릭터를 생성하는 사람이다.</p>
<p>이런 나도 위에서 언급한 3가지 방식의 자기주도 성장을 시도해본 사람이다. </p>
<p><img src="https://image.aladin.co.kr/product/191/72/cover500/899537943x_1.jpg" alt="Java의 정석">
자습하기 위해 구매한 Java의 정석은 절반도 못읽었다. </p>
<p><img src="http://image.yes24.com/goods/62597864/L" alt="Node.js 교과서">
Node.js 교과서도 70%밖에 읽지 못했다. </p>
<p><img src="http://image.yes24.com/goods/85430390/L" alt="Do it! 스위프트로 아이폰 앱 만들기 입문">
Swift를 배우고 싶어서 구매한 전자책도 20%밖에 못읽음(...)</p>
<p><img src="http://image.yes24.com/goods/58206961/L" alt="Do it! Vue.js 입문">
유일하게 내가 재밌게 다 읽고 블로그에 정리까지 한 책이다.</p>
<p>&#39;책을 읽는 루즈한 행위가 나랑 맞지 않는건 아닐까?&#39;</p>
<p>그래서 TIL처럼 매일매일 개발일지를 블로그에 작성해본 적도 있다. 첫 개발일지는 2019년 8월에 시작해서 2020년 2월을 마지막으로 작성된 적이 없다. </p>
<p>&#39;그럼 1일 1커밋은 가능하지 않을까?&#39;</p>
<p>하겠냐</p>
<blockquote>
<p>난 시도는 하는 사람이다. 끝을 보지 못하는 사람일 뿐
by 찍먹왕 Johnny</p>
</blockquote>
<p>Saturday Night을 시작하고 벌써 해가 바뀌어 간다. 언제까지 할 수 있을까?</p>
<h2 id="why-go🐭">Why Go?🐭</h2>
<p>스터디를 주최하신 Gump님은 Go언어에 열광하고 계셨고 스터디 설립 기념으로 첫 주제는 <code>The Ultimate Go</code>가 되었다.</p>
<p>Go언어는 현 회사에 재직하며 접하게된 언어다. 이전부터 존재는 알고 있었지만 딱히 배우려는 시도는 없었던 언어였다. 오히려 나는 Rust에 관심이 조금 있었다. Node.js로 프로젝트를 진행하던 당시 오픈채팅에 들어갔었고 그곳의 JavaScript 개발자들은 Rust를 좋아하는 경향이 있었기에 자연스레 관심이 생겼었다.</p>
<p>여담으로 사실 이 시기에 내가 속한 팀에는 상당히 큰 모험이 있었는데, Go언어를 학습하지 않은 상태에서 회사로부터 Go언어 기반의 프로젝트 과제가 주어졌었다. 
시작 전부터 고난이 예상되었는데 팀원들과 학습을 병행하며 프로젝트를 진행했지만 역시 결과는 처참했다. 
Go언어를 모르고 익숙하지 않은 QUIC 프로토콜을 사용하니 매일매일이 삽질이었고, 삽질이 반복되니 사기는 저하되고 그것이 일정 관리에까지 영향이 전해져 결국 프로젝트가 중단되는 경험을 겪었다.</p>
<p>결과는 실패지만 그 실패가 나에게 긍정적인 영향도 준 것 같다. 
프로젝트의 주제로부터 내가 배워야할 게 얼마나 많은지 뼈저리게 느꼈고 Go언어로 코드를 작성하면서 Java 언어로 코드를 작성하는 것과 비교하며 왜 Go가 코드를 작성하기 편한지를 알게 되었다.</p>
<h2 id="무엇을-얻었지">무엇을 얻었지?</h2>
<p>얼마전만 해도 나는 CLI 프로그램이 어떻게 만들어지는지 원리를 궁금해하던 사람이었다.
그랬던 내가 이제 Go언어로 CLI 프로그램을 만들어서 실무에서 사용하고, 다른 팀에 도움이 될만한 프로그램을 만들고 있다.
물론 아직까지 영어마냥 Go언어가 네이티브하지는 않지만 남들이 한번이라도 써보게 할 수 있는 프로그램을 만들 수 있게 된건 나름 자랑스럽다. 😤</p>
<p>아 참!
MongoDB 데이터 마이그레이션 작업을 한다고 메모리 점유율을 8GB나 처먹고 OOM 발생하는 배치 프로그램을 만든건 안자랑이다. ^^ㅋㅋㅋㅋㅋㅋ</p>
<h1 id="concurrency-in-go">Concurrency in Go</h1>
<p><img src="https://images-na.ssl-images-amazon.com/images/I/51A95VkLj%2BL._M218_BO1,204,203,200_QL40_ML2_.jpg" alt="Concurrency in Go"></p>
<p>누군가로부터 <code>개발에서 동시성 요소는 중요하다.</code>라는 이야기를 귀에 딱지가 앉게 듣다가... 개념만 듣고 이해하기에는 내가 너무 햇병아리라 공부하게 되는 계기가 되었다. </p>
<p>그리고 난 그 선택을 후회했다...
동시성은 생각보다 상당히 어려운 주제였고, 심지어 그것을 어렵게 써낸 책이라고 생각한다. 물론 좋은 내용이지만 지금의 내가 이해하기에 어렵다는 뜻이다.
햇병아리인 나에게는 아직 와닿지 않는 내용들을 담고 있으며 실무적인 요소로 응용하여 풀어내는 생각을 하기엔 나에게는 실무 경험이 너무나도 부족했다.</p>
<p>오랜 시간이 지난 후엔 내가 이해할 수 있는 수준으로 성장되어 있으면 좋겠다.</p>
<h2 id="keep">Keep</h2>
<ol>
<li><p>서브 주제 발표
이번 <code>Concurrency in Go</code> 메인 주제는 상당히 어려웠기 때문에 시간이 지날수록 루즈하게 느껴지고 이해도 안가서 숨이 턱하고 막혔는데 서브 주제 발표들이 끼어들면서 환기가 이루어졌다. 
Dave님의 gRPC 주제는 모두 받아들이기엔 하이 레벨의 내용이었지만 QUIC 프로토콜을 사용해본 경험이 있어서 기본적인 개념은 알아들을 수 있었다! 
Owen님의 주사위 포커 게임 프로젝트는 매우 참신했다!</p>
</li>
<li><p>무언가를 한다라는 안도감
개발자라는 직업은 빠르게 변화하는 트렌드와 새로운 지식들에 센시티브하게 반응할 수 있어야 한다.
위에도 소개했지만 나는 게으르다. 게으른 내가 새로운 것에 센시티브하게 반응하지 않을 때 Saturday Nitght 스터디는 그런 나를 센시티브하도록 강제해준다.
&#39;게으른 내가 뭐라도 하는구나&#39;라는 현실은 나의 마음을 안심시켜준다.</p>
</li>
</ol>
<h2 id="problem">Problem</h2>
<ol>
<li><p>스터디 개근 실패
백신 맞고 저승을 헤맸기 때문에 두 번을 참석하지 못했다. 심지어 그때 바이너리님의 서브 주제인 Git 발표를 듣지 못한게 너무너무 아쉽다... 재밌는 주제였고 오랜 토론이 오갔다고 들었는데 너무 아쉽다.</p>
</li>
<li><p>회피하게 되는 발표
주제들이 어려웠고 어려운 주제들을 이해하지 못한 상태에서 내용을 정리하고 발표해야 한다는 것에 큰 압박감을 느꼈다. 살면서 발표를 자주 해본 적도 없고 글을 잘 정리하는 편도 아니어서 매번 발표 순번이 오게되는게 스트레스다.</p>
</li>
<li><p>습득되지 않는 내용을 스터디하는 것
학습이란 내가 모르는 지식을 습득하기 위해 하는 행위다. 하지만 내가 모르는 지식이라고 해서 모든 것이 학습을 통해 습득될 수 있을까? 
흥미가 생기지 않거나, 너무 어려워서 이해를 하지 못하는 주제들을 스터디를 통해 시간을 할애하는 것에 회의감이 드는 경우가 종종 있었다.</p>
</li>
</ol>
<h2 id="try">Try</h2>
<ol>
<li><p>스터디 개근에 대한 집착 버리기
회사를 다니며 매 번 주말에 시간을 내서 스터디를 한다는건 쉽지 않은 일이다. 
사람의 생활이 일정한 패턴으로 반복되게 되면 우울함과 스트레스를 받을 수 밖에 없고 주중마다 얻는 피로도는 일정할 수 없다.
스터디를 개근해야 한다는 목표는 또 다른 스트레스를 낳을 수 있는 접근 방식이라고 생각한다.
물론 &#39;그룹&#39; 스터디이기 때문에 스터디원들에게 피해가 가지 않는 선에서 나를 위한 선택을 하는 것도 한가지 방법일 수 있겠다.</p>
</li>
<li><p>적정 레벨의 주제 선택
나의 상태를 기준으로 너무 높은 레벨의 주제를 잡으면 내 스스로가 그 주제를 이해하지 못하는 상태로 발표를 하게 될 뿐더러 그런 발표를 듣는 스티디원들의 입장을 고려해볼 필요가 있다.
지금까지의 나는 스터디원들의 질문에 속시원하게 긁어주는 발표를 해본 적이 없다. 또 어떤 날은 내가 발표한 주제를 이해하지 못한 상태로 발표를 진행해 스터디원들에게 도움이 되지 못하는 발표가 된 적도 있었다. 
누구를 위한 발표이고 누구를 위한 스터디인지를 생각해보자.
내가 이해할 수 있을만한 주제인지 파악을 하려면 스터디 주제를 선택하기 전에 먼저 가볍게라도 학습할 내용을 훑어볼 필요가 있다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Concurrency in Go - 에러 전파]]></title>
            <link>https://velog.io/@cafefarm-johnny/Concurrency-in-Go-%EC%97%90%EB%9F%AC-%EC%A0%84%ED%8C%8C</link>
            <guid>https://velog.io/@cafefarm-johnny/Concurrency-in-Go-%EC%97%90%EB%9F%AC-%EC%A0%84%ED%8C%8C</guid>
            <pubDate>Sat, 02 Oct 2021 05:26:55 GMT</pubDate>
            <description><![CDATA[<p>📖 이 글은 <a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a> 스터디에서 Concurrency in Go를 주제로 발표하기 위해 만들어졌습니다. </p>
<hr>
<h1 id="에러-전파">에러 전파</h1>
<p><strong><em>에러</em></strong>
사용자가 요청한 작업을 프로그램이 수행할 수 없는 상태에 들어갔음을 나타냅니다.</p>
<p>개발자들이 프로그램을 돌아가도록 기능을 만드는 것에는 집중하지만 에러가 발생했을 때 이를 처리하는 것을 크게 신경쓰지 않는 경향이 있다고 저자는 설명합니다.
프로그램을 구성할 때 에러 또한 신경써서 처리하여 사용자 친화적인 프로그램을 만들어야하는 것이 개발자의 역할입니다.</p>
<h2 id="에러의-구성-요소">에러의 구성 요소</h2>
<p>에러는 다음과 같이 두 가지로 분류 합니다.</p>
<ul>
<li>버그: 프로그램에서 처리되지 않은<sup>raw</sup> 에러</li>
<li>시스템 예외상황: 네트워크 연결 끊김, 디스크 쓰기 실패 등</li>
</ul>
<p>저자는 프로그램에서 처리되지 않은 에러들을 버그로 취급하고 이를 사용자에게 바로 전파되지 않도록 프로그램에서 처리해야 한다고 이야기합니다.</p>
<p>에러는 원활한 분석을 위해 아래와 같이 중요한 정보를 포함해야 합니다.</p>
<ol>
<li>발생 이유</li>
</ol>
<ul>
<li>디스크가 가득차거나, 소켓이 닫히는 등으로 개발자가 기대하지 않은 상황에 대한 정보를 포함해야 함</li>
</ul>
<ol start="2">
<li>발생한 위치 및 시점</li>
</ol>
<ul>
<li>호출이 시작된 위치부터 에러가 인스턴스화된 위치까지 전체 스택 트레이스를 포함하여 문제가 발생한 지점과 에러 원인을 파악할 수 있어야 함 </li>
<li>분산된 프로그램 환경인 경우 실행 컨텍스트와 관련된 정보를 포함하여 에러가 발생된 프로그램을 식별할 수 있어야 함</li>
<li>UTC로 에러가 인스턴스화된 시스템 시간을 포함해야 함</li>
</ul>
<ol start="3">
<li>사용자 친화적인 메시지</li>
</ol>
<ul>
<li>요청 처리 중 에러가 발생했을 시 사용자를 이해시키기 위해 메시지를 통해 피드백 할 수 있어야 함</li>
<li>단, 프로그램 친화적인 메시지가 아닌 사용자 친화적인 메시지를 피드백해야 함</li>
</ul>
<ol start="4">
<li>사용자를 위한 추가 정보 제공</li>
</ol>
<ul>
<li>사용자는 에러가 발생했을 때 자세한 사유를 알고 싶어할 수도 있음</li>
<li>개발자가 사용자와 상호작용하기 위해 에러가 발생한 시간 정보와 로그에 기록된 스택 트레이스를 추적할 수 있게 참조 가능한 식별값(ID)을 제공해야 함</li>
</ul>
<p>위와 같은 정보는 기본적으로 에러에 포함되지 않기 때문에 에러 처리 시스템을 직접 구축하거나 범용적인 프레임워크를 사용하는 것을 고민해야 합니다.</p>
<h2 id="에러-처리-시스템-구축">에러 처리 시스템 구축</h2>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/cb2d88e4-6d08-448c-9cd2-7e1403a32ccb/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-02%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%208.07.22.png" alt="여러 모듈을 가진 시스템 구성도">
(여러 모듈로 구성된 시스템 구성도)</p>
<p>코어 모듈에서 발생한 에러는 코어 모듈의 컨텍스트 내에서는 올바른 형식으로 간주될 수 있지만 프로그램 전체의 컨텍스트 내에서는 알 수 없는 에러로 간주될 수 있습니다.</p>
<p>에러가 발생한 근본적인 원인의 세부 정보는 에러가 최초 인스턴스화 될 때 포함되지만, 프로그램 전반적으로 에러를 식별하기 위해서는 에러에 식별 정보를 포함하여 약속된 에러 형태를 반환해야 합니다.</p>
<p>예)</p>
<pre><code class="language-go"> func PostReport(id string) error {
     result, err := lowlevel.DoWork();
     if err != nil {
         if _, ok := err.(lowlevel.Error); ok { // 1
             err = WrapErr(err, &quot;cannot post report with id %q&quot;, id) // 2
         }
         return err
     }
     // do work
 }</code></pre>
<ol>
<li>프로그램에서 정의된 에러가 발생한 것인지 확인합니다. 정의되지 않은 에러 상황인 경우 raw 에러를 그대로 전파합니다.</li>
<li>정의된 에러가 발생한 경우 모듈을 식별하기 위한 정보와 함께 에러를 Wrapping합니다.</li>
</ol>
<p>이를 통해 에러의 정확성을 높혀 프로그램이 에러에 대해 대응할 수 있게 되고, 식별할 수 없는 타입의 에러가 발생하는 경우 버그로 취급할 수 있게 됩니다.</p>
<p>위와 같은 형태로 프레임워크를 구축해보겠습니다.</p>
<pre><code class="language-go">// err_ext
type MyError struct {
    Inner         error
    Message       string
    StackTrace    string
    Misc          map[string]interface{}
}

func (err MyError) Error() string {
    return err.Message
}

func wrapError(err error, msgF string, msgArgs ...interface{}) MyError {
    return MyError {
        Inner:      err, // 1
        Message:    fmt.SprintF(msgF, msgArgs...), 
        StackTrace: string(debug.Stack()), // 2
        Misc:       make(map[string]interface{}), // 3
    }
}</code></pre>
<ol>
<li>에러를 Wrapping합니다.</li>
<li>에러가 발생했을 때 스택 트레이스를 기록합니다.</li>
<li>디버깅을 위한 기타 정보를 저장합니다.</li>
</ol>
<pre><code class="language-go">// lowlevel 모듈 (코어 구성 요소)
type LowLevelErr struct {
    error
}

func isGloballyExec(path string) (bool, error) {
    info, err := os.Stat(path)
    if err != nil {
        return false, LowLevelErr{(wrapError(err, err.Error()))} // 1
    }
    return info.Mode().Perm() &amp; 0100 == 0100, nil
}</code></pre>
<ol>
<li>os.Stat 호출로 발생한 원시 에러를 커스텀 타입으로 Wrapping 합니다.</li>
</ol>
<pre><code class="language-go">// intermediate 모듈 (중개자 구성 요소)
type IntermediateErr struct {
    error
}

func runJob(id string) error {
    const jobBinPath = &quot;/bad/job/binary&quot;
    isExecutable, err := isGloballyExec(jobBinPath)
    if err != nil {
        return err // 1
    } else if isExecutable == false {
        return wrapError(nil, &quot;job binary is not executable&quot;)
    }
    return exec.Command(jobBinPath, &quot;--id=&quot; + id).Run() // 1
}</code></pre>
<ol>
<li>lowlevel 모듈에서 발생한 에러를 CLI로 전달합니다. 이 시점에서 커스텀 에러 타입으로 Wrapping하지 않은 에러는 버그 요소<sup>raw error</sup>로 판단합니다.</li>
</ol>
<pre><code class="language-go">// main (CLI 구성 요소)
func main() {
    log.SetOutput(os.Stdout)
    log.SetFlags(log.Ltime | log.LUTC)
    err := runJob(&quot;1&quot;)
    if err != nil {
        msg := &quot;There was an unexpected issue; please report this as a bug.&quot;
        if _, ok := err.(IntermediateErr); ok { // 1
            msg = err.Error()
        }
        handleError(1, err, msg) // 2
    }
}

func handleError(key int, err error, msg string) {
    log.SetPrefix(fmt.Sprintf(&quot;[logID: %v]: &quot;, key))
    log.Printf(&quot;%#v&quot;, err) // 3 
    fmt.Printf(&quot;[errorID: %v] %v&quot;, key, msg)
}
</code></pre>
<ol>
<li>에러가 정의된 에러 타입인지 확인합니다. 약속된 형식의 에러라는 것을 식별할 수 있기 때문에 사용자에게 메세지를 피드백합니다. 
타입을 알 수 없는 raw 에러라면 &quot;알 수 없는 오류가 발생했다.&quot;는 메시지를 피드백 합니다.</li>
<li>로그에 에러 아이디를 부여합니다. 사용자 메시지와 로그에 기록하여 추후 에러가 발생할 경우 분석을 진행하는데 도움이 됩니다.</li>
<li>에러 발생 시 디버깅을 위해 전체 에러를 로깅합니다.</li>
</ol>
<pre><code class="language-text">// log
[logID: 1]: 03:16:32 lowlevel.LowLevelErr{error:err_ext.MyError{
    Inner:(*fs.PathError)(0xc000098180), 
    Message:&quot;stat /bad/job/binary: no such file or directory%!(EXTRA []interface {}=[])&quot;, 
    StackTrace:&quot;goroutine 1 [running]:
    runtime/debug.Stack(0xc0000b0030, 0x2f, 0xc000092d48)
        /Users/johnny/.gvm/gos/go1.16/src/runtime/debug/stack.go:24 +0x9f
    saturday-night/src/err_ext.WrapError(0x1101e68, 0xc000098180, 0xc0000b0030, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
        /Users/johnny/git_workspace/saturday-night/src/err_ext/my_error.go:23 +0xef
    saturday-night/src/lowlevel.IsGloballyExec(0x10e0965, 0xf, 0x3, 0x1005ecb, 0xc000056058)
        /Users/johnny/git_workspace/saturday-night/src/lowlevel/lowlevel.go:16 +0xc5
    saturday-night/src/intermediate.RunJob(0x10df028, 0x1, 0x1018301, 0x0)
        /Users/johnny/git_workspace/saturday-night/src/intermediate/intermediate.go:15 +0x48 // 에러 인스턴스 생성
    main.main()
        /Users/johnny/git_workspace/saturday-night/src/main.go:14 +0x70&quot;, 
    Misc:map[string]interface {}{}}
}

// message
[errorID: 1] There was an unexpected issue; please report this as a bug.</code></pre>
<p>작성한 프로그램을 실행시켜보면 위와 같이 로그에는 에러의 원인을 분석할 수 있도록 스택 트레이스를 기록하며, 사용자에게는 사용자 친화적인 메시지를 전달합니다.</p>
<p>사용자는 프로그램을 이용하다가 에러가 발생하면 errorID를 첨부하여 원활하지 못했던 서비스 이용에 대한 원인 분석을 요구할 수 있고, 개발자는 해당 식별값을 통해 로그를 확인하여 분석 결과를 제공하고 프로그램을 개선할 수 있습니다.</p>
<h3 id="버그-퇴치">버그 퇴치</h3>
<p>개발자는 사용자와의 커뮤니케이션을 통해 버그를 찾아 퇴치하여 프로그램을 개선할 수 있습니다.
위의 로그를 분석하면 intermediate.go 15번 라인에서 에러가 발생했음을 알 수 있습니다.</p>
<pre><code class="language-go">// intermediate 모듈

func runJob(id string) error {
    const jobBinPath = &quot;/bad/job/binary&quot;
    isExecutable, err := isGloballyExec(jobBinPath)
    if err != nil {
        return IntermediateErr{
            wrapError(err, &quot;cannot run job %q: requisite binaries not available&quot;, id)
        } // 1
    } else if isExecutable == false {
        return wrapError(nil, &quot;cannot run job %q: requisite binaries are not executable&quot;, id)
    }
    return exec.Command(jobBinPath, &quot;--id=&quot; + id).Run() // 1
}</code></pre>
<ol>
<li>raw 에러를 Wrapping합니다. 에러가 발생한 구체적인 이유는 사용자가 알아야할 필요가 없으므로 숨기고, 대신 피드백할  메시지를 작성합니다.</li>
</ol>
<pre><code class="language-text">//log 
[logID: 1]: 04:19:25 intermediate.IntermediateErr{error:err_ext.MyError{
  Inner:lowlevel.LowLevelErr{
    error:err_ext.MyError{
      Inner:(*fs.PathError)(0xc000098180), 
      Message:&quot;stat /bad/job/binary: no such file or directory%!(EXTRA []interface {}=[])&quot;, 
      StackTrace:&quot;goroutine 1 [running]:
        runtime/debug.Stack(0xc0000b0030, 0x2f, 0xc000092d18)
            /Users/johnny/.gvm/gos/go1.16/src/runtime/debug/stack.go:24 +0x9f
        saturday-night/src/err_ext.WrapError(0x1102148, 0xc000098180, 0xc0000b0030, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
            /Users/johnny/git_workspace/saturday-night/src/err_ext/my_error.go:23 +0xef
        saturday-night/src/lowlevel.IsGloballyExec(0x10e0bbe, 0xf, 0x55, 0x13bec40, 0x1012c1b)
            /Users/johnny/git_workspace/saturday-night/src/lowlevel/lowlevel.go:16 +0xc5
        saturday-night/src/intermediate.RunJob(0x10df288, 0x1, 0x1018301, 0x0)
            /Users/johnny/git_workspace/saturday-night/src/intermediate/intermediate.go:15 +0x48
        main.main()
            /Users/johnny/git_workspace/saturday-night/src/main.go:14 +0x70&quot;, 
      Misc:map[string]interface {}{}
    }
  }, 
  Message:&quot;cannot run job [&quot;1&quot;]: requisite binaries not available&quot;, 
  StackTrace:&quot;goroutine 1 [running]:
    runtime/debug.Stack(0x10e69b4, 0x33, 0xc000092df0)
        /Users/johnny/.gvm/gos/go1.16/src/runtime/debug/stack.go:24 +0x9f
    saturday-night/src/err_ext.WrapError(0x11022c8, 0xc000096280, 0x10e69b4, 0x33, 0xc000096290, 0x1, 0x1, 0x0, 0x0, 0x0, ...)
        /Users/johnny/git_workspace/saturday-night/src/err_ext/my_error.go:23 +0xef
    saturday-night/src/intermediate.RunJob(0x10df288, 0x1, 0x1018301, 0x0)
        /Users/johnny/git_workspace/saturday-night/src/intermediate/intermediate.go:18 +0x319
    main.main()
        /Users/johnny/git_workspace/saturday-night/src/main.go:14 +0x70&quot;, 
  Misc:map[string]interface {}{}}
}

// message
[errorID: 1] cannot run job [&quot;1&quot;]: requisite binaries not available
// 1번 작업을 실행할 수 없음: 필요한 바이너리를 사용할 수 없습니다.</code></pre>
<p>이전의 raw 에러를 Wrapping하여 추가적인 처리를 한 내역을 로그를 통해 알 수 있으며, 메세지 또한 사용자가 최소한의 원인은 알 수 있도록 변경되었습니다.</p>
<h2 id="끝">끝</h2>
<p>이러한 에러 처리 접근방식의 패키지들이 존재하지만<sup><a href="http://github.com/pkg/errors">github.com/pkg/errors</a></sup>, 위와 같이 유기적인 시스템을 직접 구축할 수도 있습니다.
중요한건 기능을 만드는 것과 같은 수준의 관심으로 에러를 다루어야하며 버그와 올바른 에러를 구분짓고 점진적으로 버그가 없는 시스템으로 개선해 나아가야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[가끔 쓰는 일기 2021.07.09]]></title>
            <link>https://velog.io/@cafefarm-johnny/%EA%B0%80%EB%81%94-%EC%93%B0%EB%8A%94-%EC%9D%BC%EA%B8%B0.2021.07.09</link>
            <guid>https://velog.io/@cafefarm-johnny/%EA%B0%80%EB%81%94-%EC%93%B0%EB%8A%94-%EC%9D%BC%EA%B8%B0.2021.07.09</guid>
            <pubDate>Fri, 09 Jul 2021 21:58:15 GMT</pubDate>
            <description><![CDATA[<h1 id="왜-매일-쓰는-일기도-아니고-자주-쓰는-일기도-아니고-가끔-쓰는-일기일까-🤔">왜 매일 쓰는 일기도 아니고 자주 쓰는 일기도 아니고 가끔 쓰는 일기일까? 🤔</h1>
<p>티스토리로 블로깅할 때는 매일을 기록하는 개발일지도 써봤는데 그 자체가 일이 되어가더라.</p>
<p>사실 처음에는 차가운 도시속의 쿨하고 펀한 개발자 모습을 생각하며 시작했었는데 매일 글을 쓰기 위해 수 시간을 소모하는 블로그의 노예가 되어갔다...
내가 원하는 이상과 달랐던 현실에 결국 패배의 깃발을 들었다.</p>
<p><img src="https://storage.googleapis.com/jjalbot-jjals/2018/12/i6fcPXIOC0/zzal.png" alt="패배함"></p>
<p>하지만 그렇다고 안하자기에는 사람이 살다보면 기록하고 싶고, 그 간 내가 보내온 시간들을 되돌아보며 간단하게 회고를 하게 되지 않겠는가? 그럴 때 마다 일기를 쓰듯 작성하고 싶었다.</p>
<p>사실 꽤 이전부터 생각했던 콘텐츠인데 그 간 이직도 있었고, 이직한 회사에 적응하고, 귀찮았고(?) 해서 이제서야 작성한다.</p>
<h1 id="이직-👋">이직 👋</h1>
<p>그렇다. 나는! 나는!! 나는!!! 이직을 했다!!!!
<img src="https://www.wetrend.co.kr/data/file2/daily_board/1903/15/3743817098_Fusgicy8_906cbeaec0944e7a72aa155bfa61cde1ab9df63c.jpg" alt="나는 이직을 했다!"></p>
<p>2019년에는 핫하다 못해 철사장같은 코인판에 끼려던 작은 스타트업에서 일을 했다. 자체 코인을 개발해서 게임이나 활동을 통한 자연스러운 채굴 시스템을 주 서비스하는 업체였는데 당시에는 코인이 활성화되서 판매하고 현물화하고 뭐 그러지 못했던거 같은데 요즘엔 어떨지 모르겠다. </p>
<h2 id="왜-퇴사를-결심했는데">왜 퇴사를 결심했는데?</h2>
<p>뭐 어쨌든 여느 스타트업이 그렇듯 항상 성공할지 어떨지도 모르는 불확실한 미래의 사업을 바라보는 회사였는데 내가 느끼기에 근무 조건은 썩 나쁘진 않았다. 별 일이 없으면 퇴근 시간 되서 퇴근을 할 수 있었고, 점심 식대도 제공해주는 회사였으니까. (물론 제공받는 식대 대비 선릉은 주변 물가가 너무 비쌌다!)
가끔은 간식을 사서 임원진들, 직원들 모두 모여 먹으며 사소한 이야기나 나누는 등 소소한 재미도 있는 회사였다.</p>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/efa87d3a-4ef8-4f87-b226-36ff3cc5d373/ABAC00DE-4A30-4DC9-8291-DB3891CB75FB_1_201_a.jpeg" alt="선릉역 우마쿠라">
(선릉역에 있는 우마쿠라. 저녁에는 선술집을 운영하고 점심에는 식사메뉴를 판매한다.)</p>
<p>그런 회사를 나는 왜 그만두고 이직하기를 마음 먹었더라?</p>
<p>아마 첫 단추부터 잘못 꿰였던 것 같다. 잡 코리아에서 지원을 하고 서류 통과와 면접에 합격하며 입사하게 되었는데 당시 팀은 백엔드를 전문으로 하는 중고 신입인 나를 비롯해서 프론트 팀 신입 1명, 팀장 1명으로 총 3명으로 구성되었다. (백엔드 신입 분이 한명 더 들어왔었지만 한 달 만에 그만두게 되었다.)</p>
<p>중고 신입으로 반년 가량의 공백을 끝으로 나름 기합이 잔뜩 들어간 상태로 입사했었는데 의욕도 가득찼었고, 매일 아침마다 출근 후 팀에서 인프런으로 Spring Boot, JPA, Vue, Webpack 스터디를 하면서 배우는게 많았다.</p>
<p><img src="https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F234D473956A085C529" alt="이해 못하는 나"></p>
<p>그러다가 돌연 팀장이 회사를 떠나게 되었고 그렇게 그 해를 팀장없이 운영되었기에 일찍이 혼자 인프라 구축 경험을 할 수 있었다. AWS, 리눅스, 가용성 구성 등 서비스의 퀄리티는 썩 만족스럽지 않았지만 다양한 경험을 할 수 있었다.</p>
<p>그러다 퇴사를 결심하게 된 원인은 아무래도 좀 더 전문적인 도메인을 다루고, 더 나은 개발 환경에서 일해보고 싶어서였다. 자체 코인을 다루는 회사였지만 정작 거래나 채굴등의 서비스를 다뤄볼 수 있는 기회가 없었고 그것이 이직을 결심하게 된 큰 원인이었다. </p>
<h2 id="지금은-🧑💻">지금은 🧑‍💻</h2>
<p>현재는 만화, 소설을 웹으로 서비스하는 곳에서 일하고 있다. 평소에 웹툰 콘텐츠를 자주 소비하는 사람으로써 꽤 흥미로운 서비스였고 한번쯤 경험해보고 싶었다. 2020년 3월에 입사하였고 2021년 7월인 현재까지 재직 중이다.</p>
<h3 id="적응">적응</h3>
<p>이직 하자마자 코로나로 감염자가 1,500명 넘게 찍었던 것으로 기억하는데 출근한 지 일주일만에 재택근무로 전환되었다. 다른 팀에 입사하신 분들의 경우에 재택근무로 인해 부적응으로 퇴사하는 분들이 있었다고 들었는데 충분히 이해는 가더라.</p>
<p>아직 낯설고 친하지 않은 팀원들에게 도움을 받기 위해 선뜻 슬랙으로 질문하는 것이 쉽지 않았다. (그럼에도 따뜻하게 다 하나하나 알려주는 당신들은 천사인가)
더군다나 다른 곳들과 똑같이 회사가 평소에 재택근무 환경에 경험이 없었기 때문에 업무 환경을 위한 인프라 설정이 필요했을 것이고, 규칙과 체계를 잡느라 이제 막 입사한 나를 챙기기에는 정신이 없었을 것이다.
<img src="https://images.velog.io/images/cafefarm-johnny/post/0a5a4806-d6cb-4d00-932a-87fb33c2340f/unnamed.png" alt="정신없는 분위기"></p>
<p>그래서 적응을 하기위해 무엇을 해야하는지 스스로 생각하려 했고, 서로 말이 통하려면 이 회사가 무엇을 서비스하는지 도메인에 대한 이해가 우선이지 않겠는가? 우선 도메인을 이해하려고 했다. 소스코드를 보면서 궁금한 것들은 슬랙을 통해 질문했고 WIKI문서, JIRA 이슈들, 정의서들과 API 툴을 이용해 직접 요청을 날려보면서 어떠한 응답을 내리고 어디에 사용되는지를 이해해 나가며 한 달 가량을 재택근무하며 보냈던 것 같다.</p>
<p><img src="https://crevate.com/wp-content/uploads/2020/07/home_office-1080x600.jpg" alt="내가 이런 걸 할 줄 몰랐다.">
(내가 이런 걸 할 줄 몰랐지...)</p>
<h3 id="rd와-리뷰">R&amp;D와 리뷰</h3>
<p>이 곳에서 나는 연구원 직책으로 입사하게 되었는데, 평소에 R&amp;D라는 말만 들어봤지 정확하게 어떠한 일을 하고 어떤 업무 환경인지 알지 못했었다. 
그래서 &quot;Johnny OO에 대해서 R&amp;D를 해보세요.&quot; 라거나 &quot;이 업무에 대해서 스스로 일정을 산출해보세요.&quot; 하면 무엇부터 시작해야하는지 감이 오지 않았다. 지금까지 일정은 업무를 요구하는 측에서 일방적으로 통보 받아오는 환경에서 일을 해왔고, 무언가에 대해 찾아보고 리서치하기 위해 시간을 소모해본 적이 많지 않았기에 지금은 해당 업무에 대해서 크게 부담을 가지지는 않게 되었지만 당시에는 꽤 신선한 오더였고 나를 긴장시키게 만드는 요소였던 것 같다.</p>
<p>현재 팀은 리뷰 환경에 꽤 적극적인 자세를 가지고 있는데, 난 아직도 내 첫 커밋에 대한 Merge Request를 잊지 못한다.
당시 리뷰를 위한 코멘트가 한 15개 가량 달렸던 것 같은데 코드 스타일에 대한 의견이 많았었다. 도메인에 대한 관심사 분리(Separation Of Concern) 개념을 배울 수 있었고, Spring의 validation 어노테이션등 전반적으로 코드를 깔끔하게 정리하여 작성할 수 있는 방법들을 배웠다.</p>
<p>리뷰 시스템은 당시 나에게 큰 도움을 주었기에 나 또한 팀원들에게 도움을 주고자 산출물에 대한 리뷰를 적극적으로 하고 있으며, 아는 것은 아는대로 모르는 것은 질문하는 자세로 열심히 커뮤니케이션을 하고 있다.</p>
<h1 id="나는">나는</h1>
<p>이전까지 Java와 Node.js를 기반으로 웹 백엔드 서비스 개발을 해왔었고 지금은 Java, Golang을 이용해서 웹 백엔드 서비스를 비롯해서 업무에 도움이 되는 CLI를 개발하고 있다.</p>
<p>어릴 때 부터 게임을 하다보면 잡캐를 만들곤 했었는데 지금 나는 이거저거 하는 잡캐가 되어가고 있다.
(캐릭터는 삭제하고 다시 만들 수라도 있지 이건...)
뭐 꼭 부정적인 생각만 가지고 있지는 않다. 관점에 따라서 개발자에게 언어란 필요한 상황에 적재적소로 사용할 도구로 인식할 수도 있고, 장인이 되기 위해 하나만을 깊게 파는 전문 기술로 인식할 수도 있다.</p>
<p><img src="https://image.freepik.com/free-photo/blacksmith-craftsman-in-apron-works-in-blacksmith-s-shop_216977-472.jpg" alt="장인"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Concurrency in Go - Go의 동시성 구성 요소 ]]></title>
            <link>https://velog.io/@cafefarm-johnny/concurrency-component</link>
            <guid>https://velog.io/@cafefarm-johnny/concurrency-component</guid>
            <pubDate>Mon, 28 Jun 2021 14:10:51 GMT</pubDate>
            <description><![CDATA[<p>📖 이 글은 <a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a> 스터디에서 Concurrency in Go를 주제로 발표하기 위해 만들어졌습니다. </p>
<hr>
<h1 id="go의-동시성-구성-요소">Go의 동시성 구성 요소</h1>
<p>3장에서는 Go의 동시성을 지원하는 기능들을 알아보는 챕터입니다.
이번에 발표하게 된 주제는 고루틴입니다. (개발하면서 고루틴 써본적 손에 꼽는데 🙄?)</p>
<h2 id="고루틴이란">고루틴이란?</h2>
<p>고루틴은 OS 스레드보다 가벼운 경량 스레드로 이야기되며(go 1.4 기준 2KB의 스택 공간 요구), 고루틴은 Go 프로그램을 구성하는 가장 기본적인 단위입니다. 모든 Go 프로그램은 하나 이상의 고루틴(main 함수)이 반드시 존재하게 됩니다. 
또한 고루틴은 다른 코드와 함께 <code>동시에 실행</code>되는 함수입니다.</p>
<blockquote>
<p>고루틴은 2KB가 부족하면 런타임이 알아서 스택을 저장하기 위한 메모리 공간을 조정하여 고루틴이 문제없이 실행될 수 있도록 합니다.</p>
</blockquote>
<h3 id="how-to-use">how to use</h3>
<p>고루틴은 이렇게 사용합니다. </p>
<pre><code class="language-go">func sayHello() {
    fmt.Println(&quot;hello&quot;)
}

func main() {
    go sayHello()
    // sayHello()의 종료를 기다리기 위해 blocking 하지 않고 다음 코드를 이어서 진행함
    ...
}</code></pre>
<p>Go에는 익명함수가 존재하고, 익명함수 또한 고루틴으로 동작시킬 수 있습니다.</p>
<pre><code class="language-go">func main() {
    go func() {
        fmt.Println(&quot;hello&quot;)
    }()
}</code></pre>
<p>Go에서는 함수를 일급 함수(First-Class Function)로 취급하기 때문에 변수로 할당할 수 있으며, 따라서 고루틴으로 동작시킬 수 있습니다.</p>
<pre><code class="language-go">func main() {
    sayHello := func() {
        fmt.Println(&quot;hello&quot;)
    }

    go sayHello()
}</code></pre>
<h2 id="고루틴은-깃털처럼-가볍다🪶">고루틴은 깃털처럼 가볍다!🪶</h2>
<p>고루틴은 OS 스레드가 아니기 때문에 가볍고 (OS에 스레드 사용을 위한 리소스 요청이 없음), 런타임 라이브러리나 VM 환경에서 관리되는 그린 스레드도 아닙니다. </p>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/Green_threads">Green Thread</a>
그린 스레드는 OS에 의존하지 않고 멀티스레드 환경을 흉내내며 커널 영역 대신 사용자 영역에서 관리되는 스레드입니다.</p>
</blockquote>
<p>고루틴은 코루틴과 같은 높은 수준의 추상화 개념입니다.</p>
<blockquote>
<p><a href="https://ko.wikipedia.org/wiki/%EC%BD%94%EB%A3%A8%ED%8B%B4">Coroutine</a>
코루틴은 비선점형 멀티태스킹을 위한 서브 루틴 개념입니다. (루틴은 어떠한 일을 실행하기 위한 단위) 
예를 들어 메인 함수가 실행되면 해당 시점이 메인 루틴이 되고, 메인 함수 내에서 특정 함수를 호출하면 해당 특정 함수가 서브 루틴이 됩니다.
(코루틴에 대한 자세한 설명은 바이너리님께...🙏)</p>
</blockquote>
<blockquote>
<p><a href="https://ko.wikipedia.org/wiki/%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81_(%EC%BB%B4%ED%93%A8%ED%8C%85)#%EB%B9%84%EC%84%A0%EC%A0%90%ED%98%95%EA%B3%BC_%EC%84%A0%EC%A0%90%ED%98%95">비선점형 멀티태스킹 (Non-preemptive Multi-tasking)</a>
프로그램이 실행되면 프로세스가 되고 프로세스는 여러 스레드를 실행시키는데 이 때, 여러 프로그램에서 만들어지는 스레드들은 CPU라는 한정된 자원을 가지고 사용하기 위해 경쟁하게 됩니다.
운영체제는 CPU를 시분할하여 스레드들을 돌아가며 실행시키는데 <code>CPU를 차지하고 있는 스레드가 자발적으로 중지될 경우(작업을 완료한 경우)에만 운영체제가 회수할 수 있도록 작동하는 개념을 비선점형 멀티태스킹</code>이라고 합니다.</p>
</blockquote>
<p>코루틴은 실행시킨 스레드의 동작을 중단(suspend)하거나 중단한 코루틴을 다시 실행시키기 위해 재진입(re-entry)하여 관리할 수 있다고 하는데 고루틴에는 이러한 기능을 존재하지 않습니다.
대신 Go 런타임이 고루틴을 관리하여 대기중(block)일 때 일시 중지 처리, 재개 처리 등의 작업을 합니다.</p>
<p>책에서는 이런 부분을 런타임과 고루틴간의 우아한 파트너십이라고 표현하네요. 😒?</p>
<h2 id="코-고루틴은-무조건-동시에-실행된다🤔">코-고루틴은 무조건 동시에 실행된다?🤔</h2>
<p>코루틴이나 고루틴은 동시성과 함께 이야기되지만 동시성은 코-고루틴이 포함하는 특성이 아닙니다.
고루틴을 실행시킨다고 해서 그것이 꼭 동시에 실행된다고 보장할 수 없으며, 병렬로 처리된다고 이야기할 수 없습니다.
서브 고루틴을 실행시킬 호스트 고루틴이 여러 고루틴을 실행시킬 수 있어야 동시에 실행이 가능한겁니다. (🙄 뭔 말이지)</p>
<h2 id="go는-mn-스케줄러-매커니즘⏰">Go는 M:N 스케줄러 매커니즘⏰</h2>
<p>Go는 M:N 스케줄러 매커니즘을 구현하고 있습니다. 이는 M개의 그린 스레드를 N개의 커널 스레드에 매핑한다는 의미인데요. 스레드의 종류는 다음과 같이 3개로 분류할 수 있습니다.</p>
<ul>
<li><code>N:1</code>: 여러 user-level 스레드가 하나의 OS 스레드 위에서 돌아감 <ul>
<li>컨텍스트 스위칭 속도가 빠르지만 멀티코어를 활용할 수 없음</li>
</ul>
</li>
<li><code>1:1</code>: 1개의 스레드를 1개의 OS 스레드와 매핑<ul>
<li>멀티코어를 제대로 활용할 수 있지만 컨텍스트 스위칭 속도가 매우 느림</li>
</ul>
</li>
<li><code>M:N</code>: 여러 개의 커널 스레드 위에 여러개의 루틴을 매핑<ul>
<li>컨텍스트 스위칭 속도가 빠르며, 멀티코어도 활용할 수 있지만 구현이 어려움</li>
</ul>
</li>
</ul>
<p>Go언어는 <code>M:N</code> 스케줄러 매커니즘을 언어 차원에서 구현해 제공하여 프로그래머들이 쉽게 사용할 수 있도록 제공하고 있습니다.</p>
<h3 id="gmp-스케줄러">GMP 스케줄러</h3>
<p>Go 스케줄러 내부를 확인해보면 G(Goroutines), M(Machine=Thread), P(Processor)로 구성되어 돌아가고 있습니다. 프로세서는 고루틴의 스케줄러이며, M은 스레드입니다.
G, M, P는 Go SDK의 runtime 패키지에 정의되어 있습니다.
<code>Go SDK/src/runtime/runtime2.go</code>파일 내에 각각의 구조체가 정의되어 있습니다.</p>
<p><img src="https://www.programmersought.com/images/846/a1fe5504ded8050f2692d826a514f34e.png" alt="구조">
(<a href="https://www.programmersought.com/article/94216822356/">이 링크를 참고했습니다.</a>)</p>
<p>위 그림을 기준으로 스케줄러는 다음의 형태로 수행된다고 합니다.</p>
<ul>
<li><code>Global Queue</code>: 고루틴(G)을 실행시키기 위해 대기시키는 저장소</li>
<li><code>P local Queue</code>: 고루틴(G)이 생성되면 먼저 로컬 큐에 추가함. 만약 로컬 큐가 가득차면 생성된 고루틴을 글로벌 큐로 저장시킴. (각 프로세서의 로컬 큐에는 최대 256개의 고루틴만 저장 가능함.)</li>
<li><code>P</code>: 프로그램 초기화 시 runtime.GOMAXPROCS(n)에 의해 n만큼 프로세서가 생성됨. (단, 실제 코어 최대 갯수 만큼만 생성 가능함. 싱글코어 = 1P = 1 P&#39;s 로컬 큐)</li>
<li><code>M</code>: 스레드(M)는 프로세서(P)의 로컬 큐에서 고루틴(G)를 가져옴. 만약 프로세서 내 큐가 비어있으면 스레드는 글로벌 큐에서 고루틴 묶음을 가져와서 프로세서의 로컬 큐에 넣거나, 다른 프로세서들의 로컬 큐에 쌓인 고루틴 묶음을 절반씩 뺏어서 비어있는 프로세서의 로컬 큐에 넣는 일을 함.</li>
</ul>
<h3 id="fork-join🍽">fork-join🍽</h3>
<p>Go는 fork-join 모델이라는 동시성 모델을 따르고 있습니다. 특정 시점에 자식 분기(fork)를 만들어 부모와 <code>동시에</code> 실행해 나가고 미래의 어느 시점에 부모와 자식 분기가 합류(join)하여 하나로  합쳐지는 형태입니다. (Git으로 치면 branch를 통해 협업하는 형태네요.)</p>
<p>예제와 그림을 통해 알아봅시다.</p>
<p><a href="https://play.golang.org/p/Mt589V4ZR7N">예제 코드</a></p>
<pre><code class="language-go">func main() {
    var wg sync.WaitGroup

    sayHello := func() {
        defer wg.Done()
        fmt.Println(&quot;hello&quot;)
    }

    wg.Add(1) // Add(n) 메소드의 인자는 실행할 고루틴의 수를 넘긴다.
    go sayHello()
    wg.Wait() // 이 시점에 서브 고루틴에서 wg.Done()이 호출되기 전까지 메인 고루틴을 일시 중지함


    fmt.Println(&quot;Done&quot;)
}

// output
hello
Done

Program exited.</code></pre>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/032c2df2-b088-42a0-8ab9-89409c894242/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-07-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.48.16.png" alt="fork-join"></p>
<p>그림으로 표현하면 메인 함수에서 <code>sayHello()</code> 함수를 고루틴으로 호출하고(fork) sync.WaitGroup을 통해 고루틴이 끝나는 시그널 <code>wg.done()</code>을 보내면 메인 함수에서 기다리고 있던 지점 <code>wg.wait()</code>에서 만나(join) 다시 메인 루틴을 진행합니다.</p>
<h2 id="고루틴의-메모리-공간">고루틴의 메모리 공간</h2>
<p>고루틴은 자신이 생성된 곳과 동일한 주소 공간에서 실행됩니다.</p>
<p>클로저 예제와 함께 알아봅시다.</p>
<p><a href="https://play.golang.org/p/9459Jg0W7ch">예제 코드</a></p>
<pre><code class="language-go">func main() {
    var wg sync.WaitGroup

    salutation := &quot;hello&quot;

    wg.Add(1)
    go func() {
        defer wg.Done()
        salutation = &quot;welcome&quot;
    }()
    wg.Wait()


    fmt.Println(salutation)
}

// output 
welcome

Program exited.</code></pre>
<p>위의 코드를 실행하면 출력으로 <code>welcome</code>이 출력됩니다. 
고루틴은 자신이 <code>생성된 곳과 동일한 주소 공간</code>에서 실행이 되기 때문에 위와 같은 결과가 나오게 됩니다. </p>
<p>그럼 아래의 코드 결과는 어떨까요?</p>
<pre><code class="language-go">var wg sync.WaitGroup

func update2Welcome(salutation string) {
    defer wg.Done()
    salutation = &quot;welcome&quot;
}

func main() {
    salutation := &quot;hello&quot;

    wg.Add(1)
    go update2Welcome(salutation)
    wg.Wait()


    fmt.Println(salutation)
}</code></pre>
<p>한번 코드만 보고 맞춰보세요. 😝</p>
<p>하나 더 예제를 보고 이해해봅시다.</p>
<p><a href="https://play.golang.org/p/q9EEPMkc7Rn">예제코드</a></p>
<pre><code class="language-go">func main() {
    var wg sync.WaitGroup
    for _, salutation := range []string{&quot;hello&quot;, &quot;greetings&quot;, &quot;good day&quot;} {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(salutation)
        }()
    }

    wg.Wait()
}
// output
./prog.go:14:25: loop variable salutation captured by func literal
Go vet exited.

good day
good day
good day

Program exited.</code></pre>
<p>위 코드는 얼핏 보면 <code>hello, greetings, good day</code>를 출력할 것 같지만 <code>good day</code>만 3번 출력하고 종료됩니다. 
문자열 타입의 변수 salutation에 대해서 닫혀있는 클로저를 실행 중이기 때문인데요. 루프가 반복될 때 salutation에는 슬라이스 리터럴의 다음 문자열 값이 할당되게 됩니다. 그러나 스케줄링된 고루틴은(프로세서 로컬 큐 혹은 글로벌 큐에 담겨있는) 어떤 시점에서든 실행될 수가 있는데 고루틴 내부에서 어떤 값이 출력될지 결정될 수가 없기 때문에 salutation의 변수가 범위를 벗어나게 됩니다.</p>
<p>고루틴이 실행되기도 전에 루프가 종료되고 salutation 변수는 슬라이스의 마지막 값인 <code>good day</code>에 대한 참조를 저장하고 있는 힙으로 옮겨지게 되고 고루틴이 실행되어 <code>good day</code>가 3번 출력됩니다.</p>
<p>사실 위의 코드를 플레이 그라운드나 IDE에서 실행하게 되면 <code>Go vet;정적 분석기</code>가 &quot;salutation 변수는 함수 리터럴에 의해 캡쳐되었다&quot;고 힌트를 줍니다. 😏</p>
<p>위의 코드가 우리가 기대한 결과에 맞게 작동하게 하려면 다음과 같이 수정해야 합니다.
<a href="https://play.golang.org/p/uq9E5x4dhGp">예제코드</a></p>
<pre><code class="language-go">func main() {
    var wg sync.WaitGroup
    for _, salutation := range []string{&quot;hello&quot;, &quot;greetings&quot;, &quot;good day&quot;} {
        wg.Add(1)
        go func(s string) { // 전달된 복사본 정보를 메모리에 저장하고(고루틴 내부 스택이겠지..?) 고루틴의 실행 시점에 저장된 값으로 동작 
            defer wg.Done()
            fmt.Println(s)
        }(salutation) // salutation을 복사본을 클로저로 전달
    }

    wg.Wait()
}

// output
good day
hello
greetings

Program exited.</code></pre>
<p>Go 컴파일러는 고루틴이 실수로 할당이 해제된 메모리에 접근하지 않도록 메모리 상에 고정되어 있는 변수를 알아서 처리해주기 때문에 개발자는 메모리 관리보다 <code>문제 공간</code>에만 집중할 수 있습니다. </p>
<p>고루틴은 서로 동일한 주소 공간에서 작동하기 때문에 동기화 처리에 대해 고민해야 합니다. 고루틴이 접근하는 공유 메모리 공간에 대해 동기화된 접근을 하도록 하거나, CSP 기본 요소(채널과 sync 패키지)를 사용해 통신으로 메모리를 공유할 수 있습니다.</p>
<p>고루틴의 스택 크기를 알아보는 항목은 예제 코드만 남기고 넘어가겠습니다.
<a href="https://play.golang.org/p/nXUHa4ejyCq">스택 크기 조회 예제코드</a></p>
<hr>
<p>아 이 책은 뭔 이 얘기 하다가 저 얘기하다가 진짜 정신없네..😠
하나만 쭉 설명해 제발..</p>
<p>저는 Go를 이용해서 아래와 같은 프로그램을 개발합니다.
<img src="https://images.velog.io/images/cafefarm-johnny/post/b89a868c-743e-4d5f-a6b2-12d4898db3d4/b5bb853a69206d970e24a139e67c84e8.gif" alt="내가 만든 프로그램"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 에러 처리 - 기본 에러 값, 에러 변수 (Error)]]></title>
            <link>https://velog.io/@cafefarm-johnny/golang-error</link>
            <guid>https://velog.io/@cafefarm-johnny/golang-error</guid>
            <pubDate>Sat, 08 May 2021 07:09:48 GMT</pubDate>
            <description><![CDATA[<p>📖 이 글은 <a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a> 스터디에서 The Ultimate Go를 주제로 발표하기 위해 만들어졌습니다. </p>
<hr>
<h1 id="에러">에러</h1>
<p>에러 핸들링은 무결성의 한 부분으로 아주 중요한 부분입니다. 에러를 핸들링하지 않으면 어떠한 문제가 발생했을 때 프로그램이 비정상 종료되어 안정성을 보장할 수 없습니다.</p>
<br>
<br>

<h2 id="기본-에러-값">기본 에러 값</h2>
<hr>
<p>언어에서 제공하는 기본 에러 타입 구현을 살펴봅시다.
<a href="http://golang.org/pkg/builtin/#error">golang.org/pkg/builtin/#error</a></p>
<pre><code class="language-go">// Go SDK/src/builtin/builtin.go
package builtin

...

// The error built-in interface type is the conventional interface for error
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

...</code></pre>
<p>error 타입은 builtin 패키지에 정의되어 있습니다.
<code>error</code>는 interface 타입이며, 문자열을 반환하는 <code>Error()</code> 메소드가 정의되어 있습니다.
우리가 에러를 처리할 때는 항상 error 인터페이스 타입을 사용하기 때문에 디커플링되어 있다고 할 수 있습니다.</p>
<p>Go에서 에러는 값이며, 인터페이스 디커플링을 통해 값을 평가하게 됩니다.
에러 처리를 디커플링하는 이유는 유지보수를 통해 애플리케이션의 코드가 계속해서 변경이 발생하게 되고 이러한 변경을 통해 광범위하게 영향을 발생시키기 때문입니다.</p>
<pre><code class="language-go">// Go SDK/src/errors/errors.go
package errors

func New(text string) error {
    return &amp;errorString{text}
}

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}</code></pre>
<p>errors 패키지에는 error 타입에 대한 기능들이 구현되어 있습니다. </p>
<p>에러 내용의 문자열 필드를 가지는 <code>errorString</code> 타입의 구조체가 있고 해당 구조체가 error 인터페이스의 <code>Error()</code> 메소드를 구현하고 있습니다.
<code>New(text string)</code> 생성자 함수를 통해 error 인터페이스 타입으로 반환하며 구체적인 타입은 외부로 노출시키지 않도록 구현되어 있습니다.</p>
<pre><code class="language-go">func (e *errorString) Error() string {
    return e.s
}</code></pre>
<p><code>Error()</code> 메소드는 오류 상황에 대한 내용을 얻을 수 있어 에러에 대한 정보를 로깅할 때 사용합니다.</p>
<pre><code class="language-go">func main() {
    if err := something(); err != nil {
        fmt.Println(err)
        return
    }
    ...
}

func something() error {
    return errors.New(&quot;it will occur error&quot;)
}</code></pre>
<p>흔히 에러처리를 할 때 무조건 작성하게 되는 패턴의 코드입니다.
error 인터페이스 타입을 반환하는 <code>something()</code>함수의 값을 err 변수에 할당하고 nil체크를 통해 에러가 발생했는지를 검사합니다.</p>
<blockquote>
</blockquote>
<p>representing an error condition, with the nil value representing no error.</p>
<p>builtin 패키지에서 error 인터페이스 타입에 작성되어 있는 주석내용 입니다.
nil 값은 error로 치지 않기 때문에 <code>err != nil</code> 코드가 성립하게 됩니다.</p>
<br>
<br>

<h2 id="에러-변수">에러 변수</h2>
<hr>
<p>저는 실제로 실무에서 Go 프로젝트를 할 때 <a href="https://github.com/TangoEnSkai/uber-go-style-guide-kr#%EC%97%90%EB%9F%AC-%ED%98%95error-types">Uber Go Style Guide</a>를 참고해서 코드를 작성하는 편인데 이 챕터에서도 이와 같은 내용을 다루고 있습니다.</p>
<p>코드를 작성할 때 에러 처리를 좀 더 명확하게 알 수 있도록 에러를 변수화하여 처리하는 방법을 알아보겠습니다.</p>
<pre><code class="language-go">package httpErrors

import (
    &quot;errors&quot;
    &quot;fmt&quot;
)

var (
    ErrBadRequest = errors.New(&quot;bad request&quot;)
    ErrPageMoved = errors.New(&quot;page moved&quot;)
)</code></pre>
<p>일종의 컨벤션인데, 에러 변수의 첫 글자는 <code>Err</code>로 시작합니다. 보통 에러들은 공통적으로 사용되는 경우가 많기 때문에 외부에서 접근이 가능하도록 대문자로 작성하는 것이 좋습니다.</p>
<p>위에 선언한 변수들은 에러에 대한 컨텍스트를 담게 되고 외부에서 해당 변수들을 타입으로써 검증을 진행하여 에러 처리를 할 수 있습니다.</p>
<pre><code class="language-go">import (
    . &quot;httpErrors&quot;
)

func main() {
    if err := something(); err != nil {
        switch err {
        case ErrBadRequest:
            fmt.Println(&quot;Bad request occured&quot;)
            return
        case ErrPageMoved:
            fmt.Println(&quot;The Page moved&quot;)
            return
        default:
            fmt.Println(err)
            return
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 소프트웨어 설계 - 그룹화 (Grouping)]]></title>
            <link>https://velog.io/@cafefarm-johnny/Golang-SoftwareArchitecture-Grouping</link>
            <guid>https://velog.io/@cafefarm-johnny/Golang-SoftwareArchitecture-Grouping</guid>
            <pubDate>Tue, 20 Apr 2021 12:32:20 GMT</pubDate>
            <description><![CDATA[<p>📖 이 글은 <a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a> 스터디에서 The Ultimate Go를 주제로 발표하기 위해 만들어졌습니다. </p>
<hr>
<h1 id="그룹화">그룹화</h1>
<hr>
<p>OOP패턴 중 타입 계층(상속)에 대한 예시입니다. OOP를 기반으로 하는 언어 (C#, Java, Python등)를 다루는 개발자들은 기본적으로 타입 계층 형태로 구조를 만드는 것이 숙련되어 있는데요. 이전 스터디에서도 나온 이야기지만 아쉽게도 <strong>Go에서는 상속에 대한 동일한 수준의 기능을 제공하지 않습니다.</strong> 
그래서 Go에서는 상속 개념을 활용하기 위해서는 접근 방식을 바꿔야만 합니다.</p>
<h2 id="상태에-의한-그룹화">상태에 의한 그룹화</h2>
<hr>
<p>기존의 OOP는 <code>공통 요소들을 묶어서</code> 부모와 자식 형태로 설계를 하곤 합니다.
Go에 이러한 접근 방식으로 설계를 해봅시다.</p>
<p>이전 스터디 시간에 임베딩을 통해 상속을 흉내내본 적이 있었습니다. 아래에는 동물을 주제로 임베딩을 통해 OOP를 흉내내는 예제입니다.</p>
<p><a href="https://play.golang.org/p/_fCsOo_yIsM">예제코드</a></p>
<pre><code class="language-go">type Animal struct {
    Name      string
    IsMammal bool
}

func (a *Animal) Speak() {
    fmt.Println(&quot;UGH!&quot;, 
        &quot;My name is&quot;, a.Name, 
        &quot;, it is&quot;, a.IsMammal, 
        &quot;I am a mammal.&quot;)
}</code></pre>
<p>Animal이라는 구체적인 타입의 구조체가 존재하고 모든 동물에게 존재하는 이름과 포유류 여부를 판단하는 두개의 필드를 가지고 있습니다. 
그리고 동물은 소리를 낼 수 있기 때문에 Speak라는 행동(메소드)를 정의했습니다.</p>
<br>

<p>부모 구조체를 만들었으니 자식 구조체를 만들어서 구체적인 동물을 설계해봅시다.
우선은 개를 설계해보죠.</p>
<pre><code class="language-go">type Dog struct {
    Animal
    PackFactor int
}</code></pre>
<p>여기서 동물의 특성을 포함시키기 Animal 타입을 임베딩하고 PackFactor(무리짓는 특성...?)라는 Dog만이 가지는 고유한 필드를 정의하여 Dog 타입의 구조체를 정의했습니다.</p>
<p><strong>Animal 타입을 재사용 했고 임베딩을 통해 상속을 흉내냈습니다.</strong></p>
<pre><code class="language-go">func (d *Dog) Speak() {
    fmt.Println(&quot;Woof!&quot;, 
        &quot;My name is&quot;, d.Name, 
        &quot;, it is&quot;, d.IsMammal, 
        &quot;I am a mammal with a pack factor of&quot;, d.PackFactor)
}</code></pre>
<p>개는 말할 수 있는 동물이기 때문에 Animal 타입의 Speak 메소드를 오버라이딩하여 Speak을 구현했습니다.</p>
<br>

<p>이번엔 고양이 타입을 만들어봅시다.</p>
<pre><code class="language-go">type Cat struct {
    Animal
    ClimbFactor int
}</code></pre>
<p>Dog와 마찬가지로 Animal 타입을 임베딩하고 Cat만이 가지는 필드를 정의하여 구조체를 정의했습니다.</p>
<pre><code class="language-go">func (c *Cat) Speak() {
    fmt.Println(&quot;Meow!&quot;,
        &quot;My name is&quot;, c.Name,
        &quot;, it is&quot;, c.IsMammal,
        &quot;I am a mammal with a climb factor of&quot;, c.ClimbFactor)
}</code></pre>
<p>고양이도 마찬가지로 말을 할 수 있기 때문에 Speak를 오버라이딩하여 구현했습니다.</p>
<pre><code class="language-go">func main() {
    dog := animals.Dog{
        Animal: animals.Animal{ Name: &quot;Brian&quot;, IsMammal: true },
        PackFactor: 5,
    }

    cat := animals.Cat{
        Animal: animals.Animal{ Name: &quot;Navi&quot;, IsMammal: true },
        ClimbFactor: 4,
    }

    dog.Speak()
    cat.Speak()
}</code></pre>
<p>main 함수에서 구조체 리터럴을 통해 임베딩된 Animal 필드와 PackFactor, ClimbFactor 필드를 초기화하여 dog와 cat을 생성했습니다. 그리고 dog 값과 cat 값을 통해 각각 Speak 메소드를 호출했습니다. 
이는 정상적으로 컴파일되며 호출이 됩니다.</p>
<p>위의 예제에서 임베딩을 활용하여 상속을 구현했지만 Go에서는 서브타이핑의 개념을 지원하지 않으며, 위와 같은 설계 방식에는 문제가 있습니다.</p>
<p>위 예제에서 Animal의 기본 타입으로 Dog와 Cat을 사용할 수가 없습니다.
<strong>Animal을 기준으로 Dog와 Cat을 그룹화할 수 없다는 뜻입니다.</strong> </p>
<pre><code class="language-go">// Attempt to use Animal as a base type.
animals := []Animal{
    Dog{},
    Cat{},
}

// COMPILE ERROR
: cannot use Dog literal (type Dog) as type Animal in array or slice literal
: cannot use Cat literal (type Cat) as type Animal in array or slice literal</code></pre>
<p>Animal 타입으로 Dog와 Cat타입을 배열이나 슬라이스로 선언하려고하면 컴파일러는 Dog와 Cat타입이 Animal 타입으로 사용할 수 없다고 에러를 내뿜습니다.</p>
<p>이런 특징들 때문에 Go는 공통된 필드를 바탕으로 그룹핑하여 설계하는 것을 권장하지 않습니다.</p>
<br>
<br>

<h2 id="행동에-의한-그룹화">행동에 의한 그룹화</h2>
<hr>
<p>이번에는 Go에 권장되는 형태로 만들어 봅시다.</p>
<p>위의 구조에서 인터페이스를 활용해 공통 요소가 아니라 <strong>공통 행동으로 그룹화</strong>하는 접근 방식으로 구현해봅시다.</p>
<p>Speaker 라는 공통된 행위를 의미하는 인터페이스를 생성합니다.</p>
<p><a href="https://play.golang.org/p/04Rs29rw2lm">예제코드</a></p>
<pre><code class="language-go">type Speaker interface {
    Speak()
}</code></pre>
<p>이제 Dog와 Cat은 Speaker라는 행위에 충족하는 메소드를 만들면 됩니다.</p>
<pre><code class="language-go">type Dog struct {
    Name       string
    IsMammal   bool
    PackFactor int
}

func (d Dog) Speak() {
    fmt.Println(&quot;Woof!&quot;,
        &quot;My name is&quot;, d.Name,
        &quot;, it is&quot;, d.IsMammal,
        &quot;I am a mammal with a pack factor of&quot;, d.PackFactor)
}</code></pre>
<pre><code class="language-go">type Cat struct {
    Name        string
    IsMammal    bool
    ClimbFactor int
}

func (c Cat) Speak() {
    fmt.Println(&quot;Meow!&quot;,
        &quot;My name is&quot;, c.Name,
        &quot;, it is&quot;, c.IsMammal,
        &quot;I am a mammal with a climb factor of&quot;, c.ClimbFactor)
}</code></pre>
<p>각 Dog와 Cat은 중복되는 필드를 각각 정의하여 중복 작성하는 행위를 유발시키지만 이러한 디커플링은 코드 재사용성보다 더 나은 방법일 수도 있습니다.</p>
<p>왜 이런 형태로 구현하는게 더 나은 방법일까요? 이전 예제는 다음과 같았습니다.</p>
<ul>
<li>Animal 타입은 재사용이 가능한 추상화 계층을 제공하고 있었습니다.</li>
<li>Animal 타입으로 값을 생성하거나 단독으로 사용할 이유가 없습니다.</li>
<li>Animal 타입에서 Speak 메소드가 구현되었습니다.</li>
<li>일반적으로 Animal 타입의 Speak 메소드는 호출될 일이 없습니다. (추상화 객체를 생성해서 사용하던가요?)</li>
</ul>
<p>Go에서 타입을 선언하기 위한 가이드라인은 다음과 같습니다.</p>
<ul>
<li>새로운 타입이나 고유한 타입을 선언하세요.</li>
<li>모든 타입의 값이 생성되거나 사용되는지 확인하세요.</li>
<li>충족해야하는 기존 동작을 재사용하려면 타입을 임베딩하세요.</li>
<li>기존 타입의 별칭이거나 추상화 타입인지 고려하세요.</li>
<li>공통 상태를 공유하는 걸 목적으로하는 타입인지 고려하세요.</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li>Declare types that represent something new or unique.</li>
<li>Validate that a value of any type is created or used on its own.</li>
<li>Embed types to reuse existing behaviors you need to satisfy.</li>
<li>Question types that are an alias or abstraction for an existing type.</li>
<li>Question types whose sole purpose is to share common state.
<a href="https://laptrinhx.com/reducing-type-hierarchies-2298559900/">참고</a>
아 영어 어렵다. ^^ 🤯</li>
</ul>
<p>다시 돌아와서 Speaker로 그룹핑을 하여 Dog와 Cat이 짖도록 할 수 있습니다.</p>
<pre><code class="language-go">func main() {
    spkrs := []animals.Speaker{
        animals.Dog{
            Name:       &quot;Brian&quot;,
            IsMammal:   true,
            PackFactor: 5,
        },
        animals.Cat{
            Name:        &quot;Navi&quot;,
            IsMammal:    true,
            ClimbFactor: 4,
        },
    }

    for _, spkr := range spkrs {
        spkr.Speak()
    }
}
</code></pre>
<p>Speaker 인터페이스 슬라이스로 Dog와 Cat을 그룹화하였고 슬라이스를 반복하여 Dog와 Cat이 짖을 수 있도록 했습니다.</p>
<h2 id="결론">결론</h2>
<hr>
<p>행동에 의한 그룹화에 대한 결론은 다음과 같습니다.</p>
<ul>
<li>구체적인 타입의 값을 그룹화하기 위해 추상 타입이나 타입 계층 같은 것이 필요없습니다.</li>
<li>인터페이스를 통해 각각 다른 타입의 값을 슬라이스 요소로 만들고 기능을 호출할 수 있습니다.</li>
<li>불필요하게 타입을 선언했던(Animal) 코드를 제거할 수 있었습니다.</li>
</ul>
<br>
<br>


<p>한글로 번역된 ulitmate-go 문서의 내용은 번역한 내용이 이해가 잘 안가서 <a href="https://laptrinhx.com/reducing-type-hierarchies-2298559900/">이 글</a>을 더 참고했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 디커플링 - 메소드 (Method)]]></title>
            <link>https://velog.io/@cafefarm-johnny/Golang-%EB%94%94%EC%BB%A4%ED%94%8C%EB%A7%81-%EB%A9%94%EC%86%8C%EB%93%9C-Method</link>
            <guid>https://velog.io/@cafefarm-johnny/Golang-%EB%94%94%EC%BB%A4%ED%94%8C%EB%A7%81-%EB%A9%94%EC%86%8C%EB%93%9C-Method</guid>
            <pubDate>Mon, 29 Mar 2021 14:34:21 GMT</pubDate>
            <description><![CDATA[<p>📖 이 글은 <a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a> 스터디에서 The Ultimate Go를 주제로 발표하기 위해 만들어졌습니다. </p>
<hr>
<h1 id="디커플링-decoupling">디커플링 (Decoupling)</h1>
<p>소프트웨어 공학에서 결합도(Coupling)란 모듈간 의존도를 나타내는 것을 의미합니다. 반대로 디커플링이란 인터페이스 등을 활용하여 모듈간 의존도를 최소화하여 개발하는 방법을 의미합니다.</p>
<br>
<br>

<h2 id="메소드-method">메소드 (Method)</h2>
<p>Go에서 메소드는 마치 다른 언어에서의 메소드를 흉내내는듯한 함수입니다.</p>
<p>우선 코드를 볼까요? 코드 형태는 다음과 같습니다.</p>
<p><a href="https://play.golang.org/p/y_iLYP9wwRU">예제코드</a></p>
<pre><code class="language-go">type user struct {
    name     string
    email    string
}

func (u user) notify() {
    fmt.Printf(&quot;Sending User Email to %s&lt;%s&gt; \n&quot;, u.name, u.email)
}</code></pre>
<p>함수 키워드<code>func</code>과 함수명 <code>notify()</code> 사이에 <code>(u user)</code>라는 구문이 보입니다. 이를 Go진영에서 리시버라고 부르며, 이를 명시함으로써 함수를 메소드로써 작동하게 합니다.</p>
<br>
<br>

<h3 id="리시버-receiver">리시버 (Receiver)</h3>
<p>Go에서는 리시버라는 개념이 존재하는데요. 위 코드에서 <strong>함수 키워드와 함수명 사이에 존재하는 것이 리시버</strong>입니다. 리시버는 구조체와 함수를 서로 연결 짓는 매개체 역할을 수행하여 마치 함수가 메소드인 것과 같이 작동합니다.</p>
<p>흔히 메소드는 C++, Java, Python같은 OOP 기반의 언어에서 찾아볼 수 있는데, Go는 OOP 기반의 언어는 아니지만 메소드와 같은 형태를 지원합니다.</p>
<br>
<br>

<h4 id="값-리시버-value-receiver">값 리시버 (Value receiver)</h4>
<p>리시버는 값 리시버와 포인터 리시버로 분류됩니다.
값 리시버를 가지는 메소드는 호출한 구조체가 복사되어 복사본을 기반으로 동작합니다.</p>
<pre><code class="language-go">func (u user) notify() {
    fmt.Printf(&quot;Sending User Email to %s&lt;%s&gt; \n&quot;, u.name, u.email)
}</code></pre>
<p>구조체 타입을 명시할 때 *를 붙이지 않으면 이 리시버는 값 리시버로써 작동합니다.</p>
<br>
<br>

<h4 id="포인터-리시버-pointer-receiver">포인터 리시버 (Pointer receiver)</h4>
<p>포인터 리시버를 가지는 메소드는 호출한 구조체를 참조하여 동작합니다.</p>
<p><a href="https://play.golang.org/p/hOpclWyM9-M">예제코드</a></p>
<pre><code class="language-go">func (u *user) changeEmail(email string) {
    u.email = email
    fmt.Printf(&quot;Changed User Email to %s&lt;%s&gt; \n&quot;, u.name, u.email)
}</code></pre>
<p>구조체 타입을 명시할 때 *를 붙이면 이 리시버는 포인터 리시버로써 작동합니다.</p>
<br>
<br>

<h4 id="값-리시버와-포인터-리시버를-이용한-호출">값 리시버와 포인터 리시버를 이용한 호출</h4>
<p>user 구조체 타입의 변수는 값 리시버와 포인터 리시버 모두 호출하여 사용할 수 있습니다.</p>
<p><a href="https://play.golang.org/p/PURnehyeide">예제코드</a></p>
<pre><code class="language-go">johnny := user{ name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; }
johnny.notify()
johnny.changeEmail(&quot;change@github.com&quot;)</code></pre>
<pre><code class="language-text">[OUTPUT]
Sending User Email to Johnny&lt;johnny@gmail.com&gt; 
Changed User Email to Johnny&lt;change@github.com&gt; </code></pre>
<p>johnny라는 user 값 타입의 변수를 생성했습니다. 그리고 포인터 리시버를 가지는 <code>changeEmail(string)</code> 메소드를 호출할 수가 있다는 걸 알 수 있습니다.
<br></p>
<p>user 포인터 타입의 변수도 마찬가지로 값 리시버와 포인터 리시버를 사용하는 모든 메소드를 호출할 수 있습니다.
<a href="https://play.golang.org/p/39pzil14UcJ">예제코드</a></p>
<pre><code class="language-go">mason := &amp;user{ name: &quot;Mason&quot;, email: &quot;mason@gmail.com&quot; }
mason.notify()
mason.changeEmail(&quot;change@github.com&quot;)</code></pre>
<pre><code class="language-text">[OUTPUT]
Sending User Email to Mason&lt;mason@gmail.com&gt; 
Changed User Email to Mason&lt;change@github.com&gt; </code></pre>
<p>위의 코드에서 mason은 user 포인터 타입의 변수입니다. 이 또한 값 리시버로 구현한 <code>notify()</code>를 호출하는게 가능합니다.
<br></p>
<pre><code class="language-go">mason := &amp;user{ name: &quot;Mason&quot;, email: &quot;mason@gmail.com&quot; }
mason.notify()</code></pre>
<p>위 코드에서 user 포인터 타입 변수로 <code>notify()</code> 메소드를 호출하는데 이는 사실 내부적으로 <code>(*user).notify()</code>;역참조 형태로 호출하게 됩니다. </p>
<blockquote>
<p>📝 mason변수는 user의 포인터 타입으로 선언되었기 때문에 코드로 표현하면 <code>var mason *user</code>와 같아요.
그래서 <code>(*user).notify()</code> == <code>(*mason).notify()</code>
즉, 포인터 타입의 변수인 mason을 역참조하여 값에 접근한 다음 <code>notify()</code>를 호출합니다.
헷갈리지 마세요!🤯</p>
</blockquote>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/98f0c942-30da-4703-a226-5b1f233e378e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-04-03%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.04.16.png" alt="">
<img src="https://images.velog.io/images/cafefarm-johnny/post/21bab2be-21f1-4286-910c-e99401cac88c/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-04-03%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.11.37.png" alt="">
Go는 mason 포인터 변수가 가리키는 값을 찾아서 복사하고 <code>notify()</code>를 호출하여 값에 의한 호출이 가능하게 합니다.
<br></p>
<pre><code class="language-go">johnny := user{ name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; }
johnny.changeEmail(&quot;change@github.com&quot;)</code></pre>
<p>반대로 johnny 값 변수도 포인터 리시버로 구현된 <code>changeEmail(string)</code> 메소드를 호출하면 내부적으로 <code>(&amp;user).changeEmail(string)</code>;참조 형태로 호출합니다.</p>
<blockquote>
<p>📝 코드로 표현하면 <code>var johnny user</code>이고 <code>(&amp;johnny).changeEmail(string)</code>으로 작동합니다. 
이제 슬슬 감이 오시죠? 🤓</p>
</blockquote>
<br>
<br>

<h4 id="심화과정">심화과정</h4>
<p>구조체 타입을 가지는 슬라이스가 있다고 가정했을 때, 이를 <code>for range</code> 반복문을 돌리는 경우에 대한 예제입니다.</p>
<p>우선 값 리시버를 가지는 메소드에 대한 심화 내용입니다.</p>
<pre><code class="language-go">users := []user{
    { name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; },
    { name: &quot;Mason&quot;, email: &quot;mason@gmail.com&quot; },
}

for _, u := range users {
    u.notify()
}</code></pre>
<pre><code class="language-text">[OUTPUT]
Sending User Email To Johnny&lt;johnny@email.com&gt;
Sending User Email To Mason&lt;mason@email.com&gt;</code></pre>
<p>위의 코드는 다음과 같이 작동합니다.
users로부터 각 원소들의 복사본(u)가 만들어지고, 복사본 u변수에서 <code>notify()</code>를 호출하게 되면 u로부터 또 새로운 복사본이 만들어집니다. </p>
<blockquote>
<p>📝 <code>u.notify()</code>를 호출하게 되면 새로운 복사본이 만들어지는 이유는 <code>값 리시버</code>파트에서 설명했듯 값 리시버는 복사본을 만들어서 작동하기 때문이에요. 🤪</p>
</blockquote>
<br>

<p><a href="https://play.golang.org/p/4pxShUsMLxt">예제코드</a>에서 각각 변수들의 메모리 주소를 출력해보면 다음과 같습니다.</p>
<pre><code class="language-text">[OUTPUT]
users[0]: Johnny&lt;0xc0000bc040&gt; // 1. users slice
users[1]: Mason&lt;0xc0000bc060&gt; 

u[0]: Johnny&lt;0xc0000c0000&gt;  // 2. in for range
u.notify(): Johnny&lt;0xc0000c0020&gt;  // 3. in notify() method

u[1]: Mason&lt;0xc0000c0000&gt; 
u.notify(): Mason&lt;0xc0000c0040&gt; </code></pre>
<blockquote>
<p>📝 <code>for range</code> 구문은 원소들을 임시 저장하기 위해 선언한 u변수를 최초 한번 할당한 후 매 반복마다 재사용하므로 메모리 주소 정보가 변하지 않아요. 🤓
<a href="https://velog.io/@kineo2k/about">Gump</a>님께서 이전 스터디 시간에 지나가듯 설명해주신 적이 있죠! 😉</p>
</blockquote>
<br>

<p>이번엔 포인터 리시버에 대한 심화 내용입니다.</p>
<pre><code class="language-go">users := []user{
    { name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; },
    { name: &quot;Mason&quot;, email: &quot;mason@gmail.com&quot; },
}

for _, u := range users {
    u.changeEmail(&quot;change@github.com&quot;)
}

fmt.Printf(&quot;%s&lt;%s&gt; \n&quot;, users[0].name, users[0].email)
fmt.Printf(&quot;%s&lt;%s&gt; \n&quot;, users[1].name, users[1].email)</code></pre>
<pre><code class="language-text">[OUTPUT]
Johnny&lt;johnny@gmail.com&gt; 
Mason&lt;mason@gmail.com&gt; </code></pre>
<p><code>for range</code> 구문에서 u는 users 슬라이스로부터 값을 복사하여 할당하게 되고, <code>u.changeEmail(string)</code>을 호출하게 되면 포인터 리시버는 u에 대한 주소를 바라보기 때문에 원본인 users 슬라이스의 원소값들이 수정되지 않습니다.</p>
<blockquote>
<p>📝 슬라이스의 원본 요소들을 수정하려고 포인터 리시버로 작동하는 메소드를 호출했는데 의도와 다르게 원본이 수정되지 않으니 주의하세요!</p>
</blockquote>
<br>

<p><a href="https://play.golang.org/p/lDJlX57EVt3">예제코드</a>에서 각각 변수들의 메모리 주소를 출력해보면 다음과 같습니다.</p>
<pre><code class="language-text">[OUTPUT]
users[0]: Johnny&lt;0xc0000bc040&gt; // 1. users slice 
users[1]: Mason&lt;0xc0000bc060&gt;  

u[0]: Johnny&lt;0xc0000c0000&gt;  // 2. in for range
u.changeEmail(string): Johnny&lt;0xc0000c0000&gt; // 3. in changeEmail() method

u[1]: Mason&lt;0xc0000c0000&gt; 
u.changeEmail(string): Mason&lt;0xc0000c0000&gt; </code></pre>
<p>출력에서 알 수 있듯이 users의 원소들을 u변수에 복사하고 u를 기반으로 포인터 리시버가 작동합니다.
<br></p>
<p>따라서 위의 코드에서 포인터 리시버를 통해 원본을 수정하려면 <code>역참조</code>를 해야합니다.</p>
<pre><code class="language-go">// user 타입의 포인터를 값으로 가지는 slice 생성
users := []*user{
    { name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; },
    { name: &quot;Mason&quot;, email: &quot;mason@gmail.com&quot; },
}

// users 슬라이스에서 포인터를 u에 복사본으로 할당
for _, u := range users {
    u.changeEmail(&quot;change@github.com&quot;)
}

fmt.Printf(&quot;%s&lt;%s&gt; \n&quot;, users[0].name, users[0].email)
fmt.Printf(&quot;%s&lt;%s&gt; \n&quot;, users[1].name, users[1].email)</code></pre>
<pre><code class="language-text">[OUTPUT]
Johnny&lt;change@github.com&gt; 
Mason&lt;change@github.com&gt; </code></pre>
<br>

<p>또는 슬라이스에 인덱스로 접근하여 원본을 수정할 수 있습니다.</p>
<pre><code class="language-go">users := []user{
    { name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; },
    { name: &quot;Mason&quot;, email: &quot;mason@gmail.com&quot; },
}

// users 슬라이스에 인덱스로 접근하여 원본을 수정
for i, _ := range users {
    users[i].changeEmail(&quot;change@github.com&quot;)
}

fmt.Printf(&quot;%s&lt;%s&gt; \n&quot;, users[0].name, users[0].email)
fmt.Printf(&quot;%s&lt;%s&gt; \n&quot;, users[1].name, users[1].email)</code></pre>
<pre><code class="language-text">[OUTPUT]
Johnny&lt;change@github.com&gt; 
Mason&lt;change@github.com&gt; </code></pre>
<blockquote>
<p>📝 Go언어에서는 사용하고 싶지 않은 값은 underscore를 명시하여 할당하지 않을 수 있습니다.
<br>
Q. <code>for i, _ := range users</code>에서 슬라이스로부터 원소들을 할당할 필요가 없으므로 underscore로 명시했는데요. 이 경우 스택 영역에 메모리 할당도 안할까요? 🤔</p>
</blockquote>
<hr>
<h3 id="값에-의한-호출과-참조에-의한-호출-value-and-pointer-semantics">값에 의한 호출과 참조에 의한 호출 (Value and Pointer semantics)</h3>
<p>숫자, 문자열, bool과 같은 primitive 타입을 사용하는 경우엔 값에 의한 호출을 사용하는 것을 권장합니다. 일반적으로 가벼운 primitive 타입의 변수를 메모리 누수와 같은 위험이 있는 힙 메모리에 만들 필요는 없습니다.</p>
<p>슬라이스, 맵, 채널, 인터페이스와 같은 참조 타입의 변수들도 역시 기본적으로 값에 의한 호출을 사용하는 걸 권장합니다. 단, <code>json.Unmarshal()</code>과 같이 주소값을 파라미터로 요구하는 함수들을 사용할 때에는 주소값을 사용해야 합니다.</p>
<br>
<br>

<h4 id="값에-의한-호출">값에 의한 호출</h4>
<p>Go의 net 패키지는 IP와 IPMask 타입을 제공하는데, byte 타입의 슬라이스입니다. 아래의 예제들은 참조 타입들을 값에 의한 호출을 통해 사용하는 것을 보여줍니다.</p>
<pre><code class="language-go">type IP []byte
type IPMask []byte</code></pre>
<br>

<p><code>Mask()</code>는 IP 타입의 값 리시버를 사용하며, IP 타입의 값을 반환합니다. 이 메소드는 IP 값 리서버를 사용해서 값을 반환하므로 값에 의한 호출입니다.</p>
<pre><code class="language-go">func (ip IP) Mask(mask IPMask) IP {
    if len(mask) == IPv6len &amp;&amp; len(ip) == IPv4len &amp;&amp; allFF(mask[:12]) {
        mask = mask[12:]
    }
    if len(mask) == IPv4len &amp;&amp; len(ip) == IPv6len &amp;&amp; bytesEqual(ip[:12], v4InV6Prefix) {
        ip = ip[12:]
    }

    n := len(ip)
    if n != len(mask) {
        return nil
    }
    out := make(IP, n)
    for i := 0; i &lt; n; i++ {
        out[i] = ip[i] &amp; mask[i]
    }

    return out
}</code></pre>
<br>

<p><code>ipEmptyString()</code>은 IP타입의 값을 파라미터로 받고, 문자열 타입의 값을 반환합니다. 이 함수는 파라미터로 IP 값 타입을 가지고 값을 복사하여 반환하므로 값에 의한 호출입니다.</p>
<pre><code class="language-go">func ipEmptyString(ip IP) string {
    if len(ip) == 0 {
        return &quot;&quot;
    }

    return ip.String()
}</code></pre>
<blockquote>
<p>📝 ip파라미터가 <code>IP 값 타입</code>이므로 복사 발생!</p>
</blockquote>
<br>
<br>

<h4 id="참조에-의한-호출">참조에 의한 호출</h4>
<p>Time 타입은 값에 의한 호출과 참조에 의한 호출 중 어떤 방법을 사용해야 할까요?</p>
<pre><code class="language-go">type Time struct {
    sec     int64
    nsec     int32
    loc     *Location
}</code></pre>
<br>

<p>타입에 대해 어떤 호출을 사용할지 결정하는 가장 좋은 방법은 타입의 생성자 함수를 확인하는 겁니다. 
아래의 생성자 함수는 어떤 호출을 사용해야 하는지 알 수 있습니다. <code>Now()</code>함수는 Time 타입의 값을 반환합니다.</p>
<pre><code class="language-go">func Now() Time {
    sec, nsec := now()
    return Time{ sec + unixToInternal, nsec, Local }
}</code></pre>
<p>반환 시 Time 타입의 값은 복사가 이루어지고, <code>Now()</code>함수를 호출한 곳으로 반환됩니다.
즉, 이 Time 타입의 값은 스택에 저장됩니다. 따라서 값에 의한 호출을 사용하는 것이 권장됩니다.
<br></p>
<p><code>Add()</code>메소드는 기존의 Time 타입의 값에서 다른 값을 얻기 위한 메소드입니다. 만약 값을 변경할 때 무조건 참조에 의한 호출을 하고, 그렇지 않을 때는 값에 의한 호출을 해야 한다고 가정을 지어버리면 이 메소드의 구현은 잘못되었다고 이야기할 수 있습니다.
하지만 리시버의 타입은 어떤 호출을 사용할지 결정되는 것이지 메소드의 구현을 결정짓지 않습니다.</p>
<pre><code class="language-go">func (t Time) Add(d Duration) Time {
    t.sec += int64(d / 1e9)
    nsec := int32(t.nsec) + int32(d%1e9)
    if nsec &gt;= 1e9 {
        t.sec++
        nsec -= 1e9
    } else if nsec &lt;0 {
        t.sec--
        nsec += 1e9
    }
    t.nsec = nsec
    return t
}</code></pre>
<p><code>Add()</code>메소드는 값 리시버를 사용하고 있고, Time 타입의 값을 반환합니다. 이 메소드는 실제 Time 타입 변수의 복사본을 변경하여 원본 값이 변경되지 않고 완전히 새로운 값을 반환하게 됩니다.</p>
<blockquote>
<p>📝 철수와 영희가 대화를 나누네요. 👀
<br>
    🙋🏻‍♂️철수: 값을 변경하는 유형의 메소드는 반드시 포인터 타입의 리시버를 가져야해!
    🙅🏻‍♀️영희: 아니야 철수야. 값을 변경하는 유형의 메소드도 목적(요구사항)이 무엇이냐에 따라 값 리시버를 가질 수 있어.</p>
</blockquote>
<br>

<p>Time 타입에 대한 참조에 의한 호출은 주어진 데이터를 Time 타입으로 변환하여 원본을 수정할 때만 사용합니다.</p>
<pre><code class="language-go">func (t *Time) UnmarshalBinary(data []type) error {}
func (t *Time) GobDecode(data []byte) error {}
func (t *Time) UnmarshalJSON(data []byte) error {}
func (t *Time) UnmarshalText(data []byte) error {}</code></pre>
<br>


<p>아래는 File 타입의 포인터를 반환합니다. 이는 File 타입에 대해서 참조에 의한 호출을 사용하여 값을 공유할 수 있다는 것을 의미합니다.</p>
<pre><code class="language-go">func Open(name string) (f *File, err error) {
    return OpenFile(name, O_RDONLY, 0)
}</code></pre>
<br>

<p><a href="https://golang.org/pkg/os/#File.Chdir">Chdir()</a>메소드는 File 타입의 포인터 리시버를 사용합니다. 이 메소드는 File 타입에 대해 참조에 의한 호출을 사용하고 있습니다.</p>
<pre><code class="language-go">func (f *File) Chdir() error {
    if f == nil {
        return ErrInvalid
    }
    if e := syscall.Fchdir(f.fd); e != nil {
        return &amp;PathError{ &quot;chdir&quot;, f.name, e }
    }
    return nil
}</code></pre>
<br>

<p><code>epipecheck()</code> 메소드는 File 타입의 포인터를 파라미터로 받습니다. 따라서 이 함수는 File 타입에 대해 참조에 의한 호출을 사용하고 있습니다.</p>
<pre><code class="language-go">func epipecheck(f *File, e error) {
    if e == syscall.EPIPE {
        if atomic.AddInt32(&amp;f.nepipe, 1) &gt;= 10 {
            sigpipe()
        }
    } else {
        atomic.StoreInt32(&amp;f.nepipe, 0)
    }
}</code></pre>
<br>

<hr>
<h3 id="메소드는-단지-함수일-뿐">메소드는 단지 함수일 뿐</h3>
<p>메소드는 특수한 기능이 아니라 문법적인 필요성으로 인해 만들어졌습니다. 메소드를 사용하면 데이터와 관련된 일부 기능을 외부에서 사용할 수 있는 것처럼 이해하게 만듭니다. 객체지향 프로그래밍에서도 이러한 설계를 권장하는데요. Go에서는 객체지향 프로그래밍을 추구하는 것은 아니지만 데이터와 연관된 동작이 필요하기 때문에 메소드가 만들어졌습니다.</p>
<blockquote>
<p>📝 Go언어에서 함수는 일급 함수입니다. 그래서 기본 타입도 존재하고, 함수를 파라미터로 전달하거나 리턴 타입으로 명시하여 사용할 수도 있습니다.</p>
</blockquote>
<br>

<p>다시 한번 위에서 사용했던 코드를 가져와봅시다.</p>
<pre><code class="language-go">type user struct {
    name     string
    email    string
}

func (u user) notify() {
    fmt.Printf(&quot;Sending User Email to %s&lt;%s&gt; \n&quot;, u.name, u.email)
}

func (u *user) changeEmail(email string) {
    u.email = email
    fmt.Printf(&quot;Changed User Email to %s&lt;%s&gt; \n&quot;, u.name, u.email)
}</code></pre>
<br>
<br>

<h4 id="함수형-변수">함수형 변수</h4>
<p>함수형 변수를 선언하고, 이 변수에 user 타입 변수의 메소드를 할당할 수 있습니다.</p>
<pre><code class="language-go">f1 := u.notify</code></pre>
<p>위의 코드와 같이 f1 변수에 <code>u.notify()</code> 메소드를 할당할 수 있습니다.
이 때, f1 변수는 포인터(uintptr)이고, 2개의 워드를 가지는 특별한 자료 구조가 됩니다.</p>
<p><a href="https://play.golang.org/p/V9HQPKBiC9-">예제코드</a></p>
<pre><code class="language-go">u := &amp;user{ name: &quot;Johnny&quot;, email: &quot;johnny@gmail.com&quot; } // 32byte

f1 := u.notify
fmt.Println(unsafe.Sizeof(f1))

f1()</code></pre>
<pre><code class="language-text">[OUTPUT]
8 // f1 byte size (uintptr)
Sending User Email to Johnny&lt;johnny@gmail.com&gt; // user.notify() call througth f1()</code></pre>
<br>

<p>f1변수의 내부 구조에서 첫번째 워드는 실행 대상 메소드 <code>u.notify()</code>를 가리킵니다. 
<code>notify()</code>메소드는 값 리시버를 사용하기 때문에 실행하기 위해서는 user 타입의 값을 필요로 하므로 u값의 복사본을 만들게 됩니다. 그래서 두번째 워드는 u값의 복사본의 값을 가리킵니다.</p>
<blockquote>
<p>📝 u변수의 멤버 변수인 name값을 변경하더라도 f1에는 이런 변경사항이 반영되지 않습니다!</p>
</blockquote>
<br>
<br>


<h4 id="🎸-기타">🎸 기타</h4>
<p>메소드는 연결할 구조체와 동일한 패키지에서만 정의할 수 있습니다. 패키지 레벨이 다른 경우 메소드를 정의할 수 없습니다.</p>
<pre><code class="language-go">package model

type User struct {
    Name    string
    Email   string
}

------------------------------

package method

import &quot;saturday-night/src/model&quot;

// 📝 패키지는 타입이 아니기 때문에 리시버에 명시할 수 없어요. 
// 그래서 [package].[struct] 형태로 다른 패키지의 구조체를 명시할 수 없습니다.
// 메소드는 반드시 구조체와 동일한 레벨의 패키지에서만 정의할 수 있습니다.

// Error: Unresolved type &#39;model&#39;
func (u model.User) notify() {
    fmt.Printf(&quot;Sending User Email to %s&lt;%s&gt; \n&quot;, u.Name, u.Email)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 문법 - 변수 (Variable)]]></title>
            <link>https://velog.io/@cafefarm-johnny/Golang-%EB%AC%B8%EB%B2%95-%EB%B3%80%EC%88%98</link>
            <guid>https://velog.io/@cafefarm-johnny/Golang-%EB%AC%B8%EB%B2%95-%EB%B3%80%EC%88%98</guid>
            <pubDate>Mon, 29 Mar 2021 12:42:41 GMT</pubDate>
            <description><![CDATA[<p>📖 이 글은 <a href="https://velog.io/@kineo2k/Saturday-Night-%EC%8A%A4%ED%84%B0%EB%94%94">Saturday Night</a> 스터디에서 The Ultimate Go를 주제로 발표하기 위해 만들어졌습니다. </p>
<hr>
<h1 id="빌트인-타입-built-in-type">빌트인 타입 (Built-in Type)</h1>
<p>Go언어에서 타입은 두 가지의 질문을 통해 완전성과 가독성을 제공합니다.</p>
<blockquote>
<p>할당한 메모리의 크기는 얼마인가? (예: 32-bit, 64-bit)
이 메모리 데이터는 무엇을 의미하는가? (예: int, uint, bool, ...)</p>
</blockquote>
<p>타입은 int32, int64처럼 명확한 이름도 있습니다.</p>
<p>uint8은 1 byte 메모리 크기에 10진수 숫자를 가지고 int32는 4 byte 메모리 크기에 10진수 숫자를 가집니다.
uint나 int처럼 메모리 크기가 명확하지 않은 타입도 존재하는데, 이를 선언하면 빌드 시 프로그램이 돌아갈 아키텍처에 따라 크기가 결정됩니다.</p>
<p>32-bit 운영체제: int32, unint32
64-bit 운영체제: int64, unint64</p>
<ul>
<li>Go언어에서 지원하는 크로스 컴파일 종류</li>
</ul>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/60ea9957-0bf6-44a9-a441-f7dd15800480/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3.png" alt="Go언어에서 지원하는 크로스 컴파일 종류"></p>
<p>출처: <a href="https://dev93.tistory.com/7">https://dev93.tistory.com/7</a>
<br>
<br></p>
<ul>
<li><a href="https://golang.org/ref/spec#Numeric_types">데이터 타입 종류</a></li>
</ul>
<table>
<thead>
<tr>
<th align="left">type</th>
<th align="left">description</th>
<th align="left">byte</th>
</tr>
</thead>
<tbody><tr>
<td align="left">bool</td>
<td align="left">true, false</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">uint8</td>
<td align="left">0 ~ 255</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">uint16</td>
<td align="left">0 ~ 65,535</td>
<td align="left">2</td>
</tr>
<tr>
<td align="left">uint32</td>
<td align="left">0 ~ 4,294,967,295</td>
<td align="left">4</td>
</tr>
<tr>
<td align="left">uint64</td>
<td align="left">0 ~ 18,446,744,073,709,551,615</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">int8</td>
<td align="left">-128 ~ 127</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">int16</td>
<td align="left">-32,768 ~ 32,767</td>
<td align="left">2</td>
</tr>
<tr>
<td align="left">int32</td>
<td align="left">-2,147,483,647 ~ 2,147,483,647</td>
<td align="left">4</td>
</tr>
<tr>
<td align="left">int64</td>
<td align="left">-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">byte</td>
<td align="left">uint8의 alias</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">rune</td>
<td align="left">int32의 alias <br>(Go언어는 char type이 존재하지 않으며 이를 rune이라는 개념으로 문자의 코드 값을 표현합니다.)</td>
<td align="left">4</td>
</tr>
<tr>
<td align="left">uint</td>
<td align="left">하드웨어 아키텍처에 따라 uint32 or uint64</td>
<td align="left">4, 8</td>
</tr>
<tr>
<td align="left">int</td>
<td align="left">하드웨어 아키텍처에 따라 int32 또는 int64</td>
<td align="left">4, 8</td>
</tr>
<tr>
<td align="left">uintptr</td>
<td align="left">포인터 값을 저장하기 위한 unsigned int</td>
<td align="left">4, 8</td>
</tr>
<tr>
<td align="left">float32</td>
<td align="left">32-bit 부동 소수점 숫자</td>
<td align="left">4</td>
</tr>
<tr>
<td align="left">float64</td>
<td align="left">64-bit 부동 소수점 숫자</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">complex64</td>
<td align="left">float32 실수부와 float32 허수부로 구성</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">complex128</td>
<td align="left">float64실수부와 float64 허수부로 구성</td>
<td align="left">16</td>
</tr>
<tr>
<td align="left">string</td>
<td align="left">배열을 가리키는 포인터와 배열의 길이 두 값으로 구성</td>
<td align="left">8, 16</td>
</tr>
</tbody></table>
<br>
<br>


<h2 id="워드-크기-word">워드 크기 (Word)</h2>
<p>오늘 날 컴퓨터는 32-bit와 64-bit 아키텍쳐로 나뉩니다. 이는 워드와 연관이 있는데요.</p>
<p>컴퓨터 구조에서 워드란 연산을 통해 저장 장치로부터 프로세서의 레지스터에 옮겨놓을 수 있는 데이터 단위입니다. 예전에 사용해왔던 컴퓨터 구조에서는 한 워드에 4 byte 길이, 즉 32-bit였습니다. 현재는 주로 64-bit(8 byte) 구조의 프로세서들을 사용하고 있습니다.</p>
<p>한 워드에는 하나의 데이터를 저장할 수 있는데, 워드의 크기란 워드가 몇 byte인지를 의미하며 메모리 주소의 크기와 같습니다.</p>
<p>예를 들어 64-bit 아키텍처에서 워드 사이즈는 64-bit (8 byte)이고, 메모리 주소의 크기도 64-bit 입니다. 따라서 int는 64-bit입니다.</p>
<br>
<br>

<h2 id="문자열-string">문자열 (String)</h2>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/9eeb62f1-5a27-4de1-8eca-41295f1c9325/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3%20(1).png" alt="">
출처: <a href="https://research.swtch.com/godata#Strings">https://research.swtch.com/godata#Strings</a></p>
<p>string의 구조는 두 개의 워드로 이루어져있습니다. 첫번째 워드는 배열로 구성된 값의 첫 인덱스를 가리키는 포인터이고, 두번째 워드는 배열의 길이입니다. string의 값은 배열로 메모리 영역 어딘가에 저장되어 있습니다.</p>
<p>그래서 string이 짧건 길건 변수의 사이즈는 동일합니다. (<a href="https://play.golang.org/p/8F6dTXE6UA0">예제 코드</a>)</p>
<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;runtime&quot;
    &quot;unsafe&quot;
)

func main() {
    fmt.Println(runtime.GOARCH)
    fmt.Println(unsafe.Sizeof(&quot;hi&quot;))
    fmt.Println(unsafe.Sizeof(&quot;Hello&quot;))
}</code></pre>
<pre><code class="language-text">[OUTPUT]
amd64  // 64-bit 아키텍처 기반이므로 하나의 워드는 8byte
16     // string은 2개의 워드로 이루어져있으므로 16byte
16</code></pre>
<br>
<br>

<h2 id="제로값-개념-zero-value">제로값 개념 (Zero Value)</h2>
<p>모든 변수는 초기화되어야 합니다. 어떤 값으로 초기화 할 지를 명시하지 않으면 제로값으로 초기화됩니다.</p>
<p>할당하는 메모리의 모든 bit는 0으로 리셋됩니다.</p>
<table>
<thead>
<tr>
<th align="left">Type</th>
<th align="left">Zero Value</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Boolean</td>
<td align="left">false</td>
</tr>
<tr>
<td align="left">Integer</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">Floating Point</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">Complex</td>
<td align="left">0i</td>
</tr>
<tr>
<td align="left">String</td>
<td align="left">&quot;&quot;</td>
</tr>
<tr>
<td align="left">Pointer</td>
<td align="left">nil</td>
</tr>
</tbody></table>
<br>
<br>

<h2 id="선언과-초기화-declare--initialize">선언과 초기화 (Declare &amp; Initialize)</h2>
<p>var 키워드로 변수를 선언하면 타입의 제로값으로 초기화됩니다. (<a href="https://play.golang.org/p/PdL9HbQdKiR">예제 코드</a>)</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int
    var b string
    var c float64
    var d bool

    fmt.Printf(&quot;var a int \t %T [%v] \n&quot;, a, a)
    fmt.Printf(&quot;var b string \t %T [%v] \n&quot;, b, b)
    fmt.Printf(&quot;var c float64 \t %T [%v] \n&quot;, c, c)
    fmt.Printf(&quot;var d bool \t %T [%v] \n&quot;, d, d)
}</code></pre>
<pre><code class="language-text">[OUTPUT]
var a int        int [0] 
var b string     string [] 
var c float64    float64 [0] 
var d bool       bool [false] </code></pre>
<br>

<p>짧은 변수 선언 (short variable declaration) 연산자를 사용하면 선언과 동시에 초기화를 할 수 있습니다. (<a href="https://play.golang.org/p/weaF04mUgHW">예제 코드</a>)</p>
<p>(이를 짧은 선언이라고 하겠습니다.)</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    // short variable declaration
    aa := 10
    bb := &quot;hello&quot;
    cc := 3.14159
    dd := true

    fmt.Printf(&quot;aa := 10 \t %T [%v] \n&quot;, aa, aa)
    fmt.Printf(&quot;bb := \&quot;hello\&quot; \t %T [%v] \n&quot;, bb, bb)
    fmt.Printf(&quot;cc := 3.14159 \t %T [%v] \n&quot;, cc, cc)
    fmt.Printf(&quot;dd := true \t %T [%v] \n&quot;, dd, dd)
}
</code></pre>
<pre><code class="language-text">[OUTPUT]
aa := 10         int [10] 
bb := &quot;hello&quot;    string [hello] 
cc := 3.14159    float64 [3.14159] 
dd := true       bool [true] </code></pre>
<br>
<br>


<h3 id="주의사항">주의사항</h3>
<p>짧은 선언 연산자로 초기화한 변수는 지역 변수로만 취급됩니다. 같은 이름의 전역 변수가 존재하더라도 짧은 선언 연산자로 생성한 변수는 지역 변수로 판단하기 때문에 전역 변수가 영향을 미치지 않습니다.</p>
<p><strong>단, 이 경우 해당 함수 scope 내에서는 전역 변수에 직접 접근할 수 없습니다.</strong> 컴파일 에러가 아닌 상황이므로 코드 작성 시 유의해야 합니다.</p>
<p>이를 Variable Shadowing이라고 합니다.(<a href="https://play.golang.org/p/JqBIayzM3KH">예제 코드</a>)</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

var value int // global scope

func main() {
    // local scope
    value := &quot;값&quot; // 타입 추론 결과: var value string

    fmt.Printf(&quot;num := \&quot;값\&quot; \t %T [%v] \n&quot;, value, value)
}</code></pre>
<pre><code class="language-text">[OUTPUT]
num := &quot;값&quot;      string [값] </code></pre>
<br>

<p>또한 함수 scope 내 var 키워드로 변수를 선언한 후 짧은 선언 연산자로 중복된 이름을 가지는 변수를 생성할 수 없습니다. (<a href="https://play.golang.org/p/XehwTDcGFHq">예제 코드</a>)</p>
<pre><code class="language-go">package main

func main() {
    var value string
    value := &quot;값&quot; // No new variables on left sdie of :=
}</code></pre>
<br>

<p>named return value를 사용할 경우에도 짧은 선언 연산자를 사용할 수 없습니다.</p>
<pre><code class="language-go">func getUserInfo() (name string, age int) {
    name := &quot;Johnny&quot; // no new variables on left side of :=
    age := 28

    return
}</code></pre>
<pre><code class="language-go">func getUserInfo() (name string, age int) {
    name = &quot;Johnny&quot; // OK
    age = 28

    return
}</code></pre>
<p>코드만 봐도 알 수 있지만 이러한 경우는 눈에 잘 띄지 않기 때문에 코드 작성 시 주의해야 합니다.</p>
<p>(물론 똑똑한 IDE는 다 알려줍니다 👀..)</p>
<br>
<br>

<h2 id="변환과-타입-변경-conversion-vs-casting">변환과 타입 변경 (Conversion vs Casting)</h2>
<p>Go는 Type casting을 지원하지 않고 Type conversion을 지원합니다.</p>
<blockquote>
<p>Type conversion happens when we assign the value of one data type to another. Statically typed languages like 
C / C++,  Java, provide the support for Implicit Type Conversion but Golang is different, <strong>as it doesn’t support the Automatic Type Conversion or Implicit Type Conversion even if the data types are compatible.</strong> The reason for this is the Strong Type System of the Golang which doesn’t allow to do this. For type conversion, you must perform explicit conversion.
As per <a href="https://golang.org/ref/spec">Golang Specification</a>, there is no typecasting word or terminology in Golang. If you will try to search Type Casting in Golang Specifications or Documentation, you will find nothing like this. There is only Type Conversion. In Other programming languages, typecasting is also termed as the type conversion.
<br>
형변환(type conversion)은 값을 다른 데이터 타입으로 할당할 때 발생합니다. C / C++, Java와 같은 정적 타입 언어들은 암시적 형변환을 지원하지만 Go언어는 그렇지 않습니다. <strong>데이터 타입이 호환되더라도 자동 형변환이나 암시적 형변환을 지원하지 않습니다.</strong> 그 이유는 Go언어의 강타입 시스템에서는 허용하지 않기 때문입니다. 형변환을 하기 위해서는 반드시 명시적 형변환으로 수행해야 합니다.
<a href="https://golang.org/ref/spec">Go언어 스펙</a>을 보면 Type casting이라는 용어가 없습니다. Type casting이란 단어를 스펙이나 문서에서 찾아 볼 수 없고 오직 Type conversion만 존재하며, 다른 프로그래밍 언어에서는 Type casting을 Type Conversion과 혼용하기도 합니다.
출처: <a href="https://www.geeksforgeeks.org/type-casting-or-type-conversion-in-golang/">GeeksforGeeks</a></p>
</blockquote>
<br>

<p>타입 형변환은 다음과 같이 할 수 있습니다. (<a href="https://play.golang.org/p/dE0TOvfaoAy">예제 코드</a>)</p>
<pre><code class="language-go">package main

func main() {
    var integer int = 845

    // 명시적 Type conversion
    var fl float64 = float64(integer)

    var integer64 int64 = int64(integer)

    var unsigned uint = uint(integer)
}</code></pre>
<br>
<br>

<hr>
<h1 id="구조체struct">구조체(Struct)</h1>
<p>구조체는 기본 데이터 타입들을 그룹화하여 새롭게 정의하는 사용자 정의 데이터 타입입니다.</p>
<pre><code class="language-go">type example struct {
    flag      bool
    counter   int16
    pi        float32
}</code></pre>
<p>example 구조체에 할당되는 메모리의 크기는 얼마일까요?</p>
<p>bool 타입은 1 byte, int16은 2 byte, float32는 4 byte로 총합 7 byte지만, 실제로는 8 byte를 할당합니다. </p>
<p>이를 이해하려면 패딩(byte padding)과 정렬 (byte alignment) 개념이 활용됩니다.</p>
<ul>
<li><p>바이트 패딩: 클래스나 구조체에 바이트를 추가하여 CPU 접근에 부하를 덜어주는 기법</p>
</li>
<li><p>바이트 정렬: 메모리에 데이터를 저장할 때 CPU의 처리 효율을 위해 변수 크기에 맞게 정렬하여 메모리에 할당하는 방법</p>
</li>
</ul>
<br>


<p>바이트 패딩은 bool과 int16 사이에 위치합니다.</p>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/ce7c22e0-d7f8-4d94-acdc-d54ebb6f3056/img.png" alt="">
32-bit 프로세서는 4byte씩 접근하여 연산을 합니다. 이 때 CPU가 bool, int16 데이터의 연산을 위해 4byte를 읽게 되면 그 안에 float32의 첫 byte가 포함되게 됩니다.</p>
<p>연산할 필요가 없는 데이터를 읽게됩니다.
<br></p>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/a1d4a9cd-f6ec-4ebe-83e1-a64f5d43380c/img%20(1).png" alt=""></p>
<p>위와 같이 bool 데이터를 저장할 때 1byte를 패딩하여 저장하게 된다면 CPU는 불필요하게 float32 데이터를 읽어올 필요가 없습니다.</p>
<p>하드웨어는 정렬 경계(byte alignment boundary)내의 메모리를 읽게 하는 것이 효율적입니다. 하드웨어가 정렬 경계에 맞춰 읽을 수 있게 소프트웨어에서 처리해주는 것이 정렬입니다.</p>
<br>


<p>Go언어에서는 아래의 규칙이 있습니다.</p>
<p>규칙 1:</p>
<p>특정 값의 메모리 크기에 따라 Go언어는 어떤 정렬이 필요한지 결정합니다. 모든 2 byte 크기의 값은 2 byte 경계를 가집니다.</p>
<p>bool 값은 1 byte이기 때문에 주소 0번지에서 시작합니다. 그러면 다음 int16 값은 2번지에서 시작해야 합니다. 건너뛰게 되는 1 byte에 패딩 1 byte가 들어갑니다. 만약 int 16이 아니라 int32라면 3 byte의 패딩이 들어갑니다.</p>
<p>규칙2: </p>
<p>가장 큰 크기의 필드가 전체 구조체의 패딩을 결정합니다. 가능한 패딩이 적을 수록 좋기 때문에 큰 필드부터 가장 작은 필드의 순서로 위치시키는 것이 좋습니다. 
<br>
<br></p>
<h2 id="선언과-초기화-declare--initialize-1">선언과 초기화 (Declare &amp; Initialize)</h2>
<p>example 구조체 타입의 변수를 선언하면 구조체 내의 필드들은 제로값으로 초기화됩니다.</p>
<pre><code class="language-go">var e example

fmt.Println(&quot;%+v\n&quot;, e)</code></pre>
<pre><code class="language-text">[OUTPUT]
{flag:false counter:0 pi:0}</code></pre>
<br>

<p>구조체 리터럴을 사용하여 내부 필드를 초기화할 수 있습니다. 이 때, 각 필드는 콤마(,)로 구분하여 초기화합니다.</p>
<pre><code class="language-go">e := example {
    flag:    true,
    counter: 10, 
    pi:      3.141592,
}

fmt.Println(&quot;Flag&quot;, e.flag)
fmt.Println(&quot;Counter&quot;, e.counter)
fmt.Println(&quot;Pi&quot;, e.pi)</code></pre>
<pre><code class="language-text">[OUTPUT]
Counter 10
Pi 3.141592
Flag true</code></pre>
<br>

<p>또한 구조체는 익명의 타입으로 선언함과 동시에 구조체 리터럴로 초기화할 수 있습니다. 단, 익명으로 선언한 구조체 타입은 재사용할 수 없습니다.</p>
<pre><code class="language-go">e := struct {
    flag     bool
    counter  int16
    pi       float32
}{
    flag:    true,
    counter: 10,
    pi:      3.141592,
}

fmt.Println(&quot;Flag&quot;, e.flag)
fmt.Println(&quot;Counter&quot;, e.counter)
fmt.Println(&quot;Pi&quot;, e.pi)</code></pre>
<pre><code class="language-text">[OUTPUT]
Counter 10
Pi 3.141592
Flag true</code></pre>
<br>
<br>

<h2 id="이름이-있는-타입과-익명-타입-name-type-vs-anonymous-type">이름이 있는 타입과 익명 타입 (Name type VS anonymous type)</h2>
<p>두 구조체 타입의 필드가 완전히 같다고 하더라도, 한 타입의 구조체 변수를 다른 타입의 구조체 변수에 대입할 수는 없습니다.</p>
<pre><code class="language-go">type example1 struct {
    count int
}
type example2 struct {
    count int
}

var ex1 example1
var ex2 example2

ex1 = ex2 // cannot use ex2 (type example2) as type example1 in assignment</code></pre>
<br>

<p>ex1 = ex2 를 올바르게 하려면 명시적인 형변환(type conversion)을 수행해야 합니다.</p>
<pre><code class="language-go">ex1 = example1(ex2) // OK</code></pre>
<br>

<p>단, <strong>동일한 구조의 익명 구조체 타입인 경우</strong>에는 명시적인 형변환 선언 없이도 ex1 = ex가 가능합니다.</p>
<pre><code class="language-go">type example1 struct {
    count int
}

var ex1 example1
var ex2 struct {
    count int
}

ex1 = ex2 // OK!</code></pre>
<hr>
<h1 id="포인터">포인터</h1>
<p>포인터란 메모리 주소를 저장하는 변수 타입입니다.</p>
<p><strong><em>Go언어에서는 항상 값을 전달합니다.</em></strong></p>
<p>Go언어에서 모든 데이터는 값의 전달 (Pass by value)로 작동합니다. 전달해야 할 데이터의 원본이 아닌 복사본을 만들어 전달합니다.</p>
<p>반면 포인터는 값을 전달하지 않고 원본 데이터의 메모리 주소 정보를 전달합니다. 그래서 전달받은 곳에서는 데이터의 메모리 주소 정보로 접근하여 데이터를 확인할 수 있습니다. (메모리 주소 또한 값으로 취급합니다.)</p>
<br>
<br>

<h2 id="고루틴">고루틴</h2>
<p>메모리 관련 얘기가 나왔으니 고루틴을 잠시 언급하고 가겠습니다.</p>
<p>고루틴이란 2kb 크기의 스택 프레임을 가지는 경량 쓰레드입니다. </p>
<p>(<a href="https://medium.com/a-journey-with-go/go-how-does-the-goroutine-stack-size-evolve-447fc02085e5">참고</a>: Go 언어 초창기에 고루틴에 할당되는 스택의 크기는 4kb크기였습니다. 1.2 버전에서 8kb크기로 커졌으나, 1.4 버전에서 2kb로 감소하였네요.)</p>
<p>Go언어에서는 <strong>main함수도 고루틴</strong>입니다. main함수를 &#39;메인 고루틴&#39;이라고도 칭합니다!
<br></p>
<p>예제를 통해 고루틴을 어떻게 사용하는지 알아봅시다. (<a href="https://play.golang.org/p/10G6l1CxDkL">예제 코드</a>)</p>
<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func main() {
    go print() // 고루틴은 go 키워드를 함수 앞에 선언하여 사용

    time.Sleep(3 * time.Second) // 고루틴은 비동기로 수행되기 때문에 고루틴이 종료되기 전에 메인함수가 먼저 종료될 수 있다.
}

func print() {
    fmt.Println(&quot;hello, world&quot;)
}</code></pre>
<p>고루틴은 비동기처럼 실행하고자 하는 함수를 호출할 때 go 키워드를 앞에 선언하여 사용할 수 있습니다.
<br></p>
<p>컴파일러가 컴파일을 수행할 때 값들의 크기를 판단하여 스택에 저장합니다. 이 때 스택 프레임의 크기가 결정됩니다. 만약 컴파일러가 크기를 알 수 없는 값들이 있다면 이 값들은 힙에 저장합니다.
<br>
<br></p>
<h2 id="값의-전달-pass-by-value">값의 전달 (Pass by value)</h2>
<p>int 타입의 변수의 값을 10으로 선언하면 스택에 저장됩니다.</p>
<p>스택에 저장된 변수 메모리 주소에 접근하려면 다음과 같이 할 수 있습니다. (<a href="https://play.golang.org/p/bdweQbQs_80">예제 코드</a>)</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    count := 10

    fmt.Println(&quot;value: &quot;, count) // count의 값 전달
    fmt.Println(&quot;addr: &quot;, &amp;count) // count의 메모리 주소를 값으로 전달
}</code></pre>
<pre><code class="language-text">[OUTPUT]
value: 10
addr: 0xc00050738</code></pre>
<p>&amp; 문자를 변수 앞에 선언하여 메모리 주소를 값으로 함수에 전달할 수 있습니다.
<br></p>
<p>좀 더 자세히 알아볼까요? (<a href="https://play.golang.org/p/jOSnoNKJaRG">예제 코드</a>)</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    count := 10

    // count 값을 전달
    increase(count)

    fmt.Println(&quot;count: &quot;, count)
    fmt.Println(&quot;count.addr: &quot;, &amp;count)
}

func increase(v int) {
    v++

    fmt.Println(&quot;v: &quot;, v)
    fmt.Println(&quot;v.addr: &quot;, &amp;v)
}</code></pre>
<pre><code class="language-text">[OUTPUT]
v: 11
v.addr: 0xc0000b8018

count: 10
count.addr: 0xc0000b6020</code></pre>
<p>increase 함수에 &#39;count의 값&#39;을 전달했습니다. increase 내부에서는 전달 받은 count의 값에 +1 연산을 수행하는 코드입니다.
<br></p>
<p>그러나 출력해보면 서로 값이 다른데요. 이는 increase 함수로 값을 전달할 때 <strong>값이 복사되어 전달</strong>되었기 때문입니다.
<img src="https://images.velog.io/images/cafefarm-johnny/post/28da87c9-99a3-40b0-a0ca-c8d58e30eebc/img%20(2).png" alt="">
v 메모리 영역에는 10이라는 값이 복사되어 할당되었습니다. 그렇기 때문에 산술 연산을 시도하면 v 메모리 영역에 할당된 값에 연산 작업이 이루어지게 됩니다.
<br>
<img src="https://images.velog.io/images/cafefarm-johnny/post/af8f6493-b8ab-4e98-a929-35d6ccc389df/img%20(3).png" alt="">
<br></p>
<p>반면 포인터를 사용하게 되면 이야기가 다릅니다. (<a href="https://play.golang.org/p/WAABXU1Cldy">예제 코드</a>)</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    count := 10

    // count 값의 메모리 주소를 전달 (이 또한 pass by value로 작동한다.)
    increase(&amp;count)

    fmt.Println(&quot;count: &quot;, count)
    fmt.Println(&quot;addr: &quot;, &amp;count)
}

func increase(v *int) { // 타입 앞에 * 문자를 명시하여 int의 포인터 타입으로 선언
    *v++ // 포인터 변수를 연산하려면 해당 포인터를 역참조하여 값에 접근해야 한다.

    fmt.Println(&quot;v: &quot;, v)
    fmt.Println(&quot;v.addr: &quot;, &amp;v)
}</code></pre>
<pre><code class="language-text">[OUTPUT]
v:  0xc0000b6020 // count.addr의 정보를 값으로 가지고 있는다.
v.addr:  0xc0000b8018 // count.addr의 정보값을 가지는 v 변수의 메모리 주소

count:  11 // 원본에 연산처리가 반영됨
count.addr:  0xc0000b6020</code></pre>
<p>increase 함수에 count 값의 메모리 주소를 전달하였습니다. 이를 참조(reference)라고 합니다.</p>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/0bb448f7-1dff-4a91-b2de-365dd34cae87/img%20(4).png" alt="">
increase 함수의 v 변수는 포인터로, count 값의 메모리 주소를 &quot;값으로&quot; 할당받게 됩니다.
<br></p>
<p>이후 산술 연산을 위해 증가연산자를 사용했는데요. 이 때 포인터 값을 연산처리 하려면 해당 포인터를 역참조(dereference) 해야합니다.</p>
<p><img src="https://images.velog.io/images/cafefarm-johnny/post/3f809a86-668b-45ef-aa5d-81952cef0f1c/img%20(5).png" alt="">
역참조를 하게 되면 count의 메모리 주소값이 아닌 값을 바라보게 되기 때문에 산술 연산이 가능해집니다.</p>
<hr>
<h2 id="포인터-크기">포인터 크기</h2>
<p>포인터 변수의 크기는 &quot;빌트인 타입&quot; 챕터에서도 설명했지만 uintptr 이라는 데이터 타입으로, uint 이기 때문에 CPU 아키텍쳐에 따라 4byte 혹은 8byte입니다. (<a href="https://play.golang.org/p/hPH_O-nNqcK">예제 코드</a>)</p>
<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;runtime&quot;
    &quot;unsafe&quot;
)

type human struct {
    name string
    age int32
}

func main() {
    johnny := human{
        name: &quot;Johnny&quot;, 
        age: 28, 
    }

    var johnnyPtr *human
    johnnyPtr = &amp;johnny

    fmt.Println(&quot;Architecture: &quot;, runtime.GOARCH)
    fmt.Println(&quot;johnny: &quot;, unsafe.Sizeof(johnny), &quot;byte&quot;)
    fmt.Println(&quot;johnnyPtr: &quot;, unsafe.Sizeof(johnnyPtr), &quot;byte&quot;)
}</code></pre>
<pre><code class="language-text">[OUTPUT]
Architecture:  amd64
johnny:        24 byte
johnnyPtr:     8 byte</code></pre>
<p>지금까지의 스터디한 내용이 이해가 되었다면 위의 OUTPUT에서 &quot;johnny&quot; 구조체의 크기가 24byte인 것을 설명할 수 있어야 합니다!</p>
]]></description>
        </item>
    </channel>
</rss>